or-tools 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d08782975a4c387d502318dbd8c9335166a42ab76807b13ba78c7b249d6c879
4
- data.tar.gz: 1653c6a0bfac136e9bdbe22d6bc10bc24d699926d79963cb41777f9e90eb880f
3
+ metadata.gz: c2d774f3459e19c0fc640d1c60c05cb896edec872fafb765b1162e08c79e921b
4
+ data.tar.gz: 40199c012d102d861fe405d7e93e51bf5232bcb402fa563ee63de6fe00db5ec8
5
5
  SHA512:
6
- metadata.gz: 582adf83f9dfcdfe426364a22efc2bdd8733b5582a4aa3bb0b720910bd4a5d75a8466937b6b8bfed892c655017d246da3953081d56533a15aac8ae10b1ec96c5
7
- data.tar.gz: e97b40eefc43fd253d2da0ad17f02a923649502d294904d49d4a431d1adc44c23222c8a59d61e36142400478bb6e128c90d00bee8419f0294c47f29aba4c4a93
6
+ metadata.gz: ac775e43751213a80c29ac82f38487e287891243d96b26e622474c034c44683135e412b5caa86e47232c36b32f1bfafec4b7152cab9fc652c1384a399e002072
7
+ data.tar.gz: f3a47294fd32b8464e33e566b277936c9ed687b79dfd3fece814cd9d5247252395131ddaf79b05cc2db8c8a76ad1e19610e9a151d8aaa5ea72d5d8cbd76135bb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.18.0 (2026-07-06)
2
+
3
+ - Added support for releasing GVL
4
+ - Added `stop_search` method to `CpSolverSolutionCallback`
5
+ - Fixed error with `apply_locks` and `set_allowed_vehicles_for_index` methods
6
+ - Removed `solve_with_solution_callback` and `search_for_all_solutions` methods
7
+ - Dropped support for Ruby < 3.3
8
+
1
9
  ## 0.17.1 (2026-02-19)
2
10
 
3
11
  - Fixed error with Rice 4.11
data/README.md CHANGED
@@ -93,15 +93,15 @@ Specify people and their availabililty
93
93
  people = [
94
94
  {
95
95
  availability: [
96
- {starts_at: Time.parse("2025-01-01 08:00:00"), ends_at: Time.parse("2025-01-01 16:00:00")},
97
- {starts_at: Time.parse("2025-01-02 08:00:00"), ends_at: Time.parse("2025-01-02 16:00:00")}
96
+ {starts_at: Time.parse("2026-01-01 08:00:00"), ends_at: Time.parse("2026-01-01 16:00:00")},
97
+ {starts_at: Time.parse("2026-01-02 08:00:00"), ends_at: Time.parse("2026-01-02 16:00:00")}
98
98
  ],
99
99
  max_hours: 40 # optional, applies to entire scheduling period
100
100
  },
101
101
  {
102
102
  availability: [
103
- {starts_at: Time.parse("2025-01-01 08:00:00"), ends_at: Time.parse("2025-01-01 16:00:00")},
104
- {starts_at: Time.parse("2025-01-03 08:00:00"), ends_at: Time.parse("2025-01-03 16:00:00")}
103
+ {starts_at: Time.parse("2026-01-01 08:00:00"), ends_at: Time.parse("2026-01-01 16:00:00")},
104
+ {starts_at: Time.parse("2026-01-03 08:00:00"), ends_at: Time.parse("2026-01-03 16:00:00")}
105
105
  ],
106
106
  max_hours: 20
107
107
  }
@@ -112,9 +112,9 @@ Specify shifts
112
112
 
113
113
  ```ruby
114
114
  shifts = [
115
- {starts_at: Time.parse("2025-01-01 08:00:00"), ends_at: Time.parse("2025-01-01 16:00:00")},
116
- {starts_at: Time.parse("2025-01-02 08:00:00"), ends_at: Time.parse("2025-01-02 16:00:00")},
117
- {starts_at: Time.parse("2025-01-03 08:00:00"), ends_at: Time.parse("2025-01-03 16:00:00")}
115
+ {starts_at: Time.parse("2026-01-01 08:00:00"), ends_at: Time.parse("2026-01-01 16:00:00")},
116
+ {starts_at: Time.parse("2026-01-02 08:00:00"), ends_at: Time.parse("2026-01-02 16:00:00")},
117
+ {starts_at: Time.parse("2026-01-03 08:00:00"), ends_at: Time.parse("2026-01-03 16:00:00")}
118
118
  ]
119
119
  ```
120
120
 
@@ -537,7 +537,8 @@ end
537
537
  # invoke the solver
538
538
  solver = ORTools::CpSolver.new
539
539
  solution_printer = VarArraySolutionPrinter.new(letters)
540
- status = solver.search_for_all_solutions(model, solution_printer)
540
+ solver.parameters.enumerate_all_solutions = true
541
+ status = solver.solve(model, solution_printer)
541
542
 
542
543
  puts
543
544
  puts "Statistics"
@@ -598,7 +599,8 @@ end
598
599
  # call the solver and display the results
599
600
  solver = ORTools::CpSolver.new
600
601
  solution_printer = SolutionPrinter.new(queens)
601
- status = solver.search_for_all_solutions(model, solution_printer)
602
+ solver.parameters.enumerate_all_solutions = true
603
+ status = solver.solve(model, solution_printer)
602
604
  puts
603
605
  puts "Solutions found : %i" % solution_printer.solution_count
604
606
  ```
@@ -1887,11 +1889,13 @@ end
1887
1889
 
1888
1890
  # call the solver and display the results
1889
1891
  solver = ORTools::CpSolver.new
1892
+ solver.parameters.linearization_level = 0
1893
+ solver.parameters.enumerate_all_solutions = true
1890
1894
  a_few_solutions = 5.times.to_a
1891
1895
  solution_printer = NursesPartialSolutionPrinter.new(
1892
1896
  shifts, num_nurses, num_days, num_shifts, a_few_solutions
1893
1897
  )
1894
- solver.search_for_all_solutions(model, solution_printer)
1898
+ solver.solve(model, solution_printer)
1895
1899
 
1896
1900
  puts
1897
1901
  puts "Statistics"
@@ -30,7 +30,7 @@ void init_assignment(Rice::Module& m) {
30
30
  } else if (status == SimpleLinearSumAssignment::Status::POSSIBLE_OVERFLOW) {
31
31
  return Symbol("possible_overflow");
32
32
  } else {
33
- throw std::runtime_error("Unknown status");
33
+ throw std::runtime_error{"Unknown status"};
34
34
  }
35
35
  });
36
36
  }
@@ -27,11 +27,11 @@ namespace Rice::detail {
27
27
  double is_convertible(VALUE value) { return Convertible::Exact; }
28
28
 
29
29
  KnapsackSolver::SolverType convert(VALUE x) {
30
- auto s = Symbol(x).str();
30
+ std::string s = Symbol(x).str();
31
31
  if (s == "branch_and_bound") {
32
32
  return KnapsackSolver::KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER;
33
33
  } else {
34
- throw std::runtime_error("Unknown solver type: " + s);
34
+ throw std::runtime_error{"Unknown solver type: " + s};
35
35
  }
36
36
  }
37
37
 
@@ -47,7 +47,7 @@ void init_bin_packing(Rice::Module& m) {
47
47
  .define_method("best_solution_contains?", &KnapsackSolver::BestSolutionContains)
48
48
  .define_method(
49
49
  "init",
50
- [](KnapsackSolver& self, std::vector<int64_t> values, std::vector<std::vector<int64_t>> weights, std::vector<int64_t> capacities) {
50
+ [](KnapsackSolver& self, const std::vector<int64_t>& values, const std::vector<std::vector<int64_t>>& weights, const std::vector<int64_t>& capacities) {
51
51
  self.Init(values, weights, capacities);
52
52
  });
53
53
  }
@@ -0,0 +1,37 @@
1
+ #pragma once
2
+
3
+ #include <chrono>
4
+ #include <condition_variable>
5
+ #include <mutex>
6
+ #include <optional>
7
+ #include <queue>
8
+
9
+ template<typename T>
10
+ class Channel {
11
+ std::queue<T> queue;
12
+ std::mutex mutex;
13
+ std::condition_variable cv;
14
+
15
+ public:
16
+ void send(T message) {
17
+ std::lock_guard<std::mutex> guard(mutex);
18
+ queue.push(std::move(message));
19
+ cv.notify_one();
20
+ }
21
+
22
+ template<typename U, typename V>
23
+ std::optional<T> recv_timeout(const std::chrono::duration<U, V>& duration) {
24
+ std::unique_lock<std::mutex> lock(mutex);
25
+ if (!cv.wait_for(lock, duration, [&] { return !queue.empty(); })) {
26
+ return std::nullopt;
27
+ }
28
+ T message = std::move(queue.front());
29
+ queue.pop();
30
+ return message;
31
+ }
32
+
33
+ bool empty() {
34
+ std::lock_guard<std::mutex> guard(mutex);
35
+ return queue.empty();
36
+ }
37
+ };
@@ -1,3 +1,6 @@
1
+ #include <atomic>
2
+ #include <chrono>
3
+ #include <optional>
1
4
  #include <string>
2
5
  #include <vector>
3
6
 
@@ -6,21 +9,23 @@
6
9
  #include <rice/rice.hpp>
7
10
  #include <rice/stl.hpp>
8
11
 
12
+ #include "channel.hpp"
13
+
9
14
  using operations_research::Domain;
10
15
  using operations_research::sat::BoolVar;
11
16
  using operations_research::sat::Constraint;
12
- using operations_research::sat::TableConstraint;
13
17
  using operations_research::sat::CpModelBuilder;
14
18
  using operations_research::sat::CpSolverResponse;
15
19
  using operations_research::sat::CpSolverStatus;
16
- using operations_research::sat::LinearExpr;
17
- using operations_research::sat::IntVar;
18
20
  using operations_research::sat::IntervalVar;
21
+ using operations_research::sat::IntVar;
22
+ using operations_research::sat::LinearExpr;
19
23
  using operations_research::sat::Model;
20
24
  using operations_research::sat::NewFeasibleSolutionObserver;
21
25
  using operations_research::sat::SatParameters;
22
26
  using operations_research::sat::SolutionBooleanValue;
23
27
  using operations_research::sat::SolutionIntegerValue;
28
+ using operations_research::sat::TableConstraint;
24
29
 
25
30
  using Rice::Array;
26
31
  using Rice::Class;
@@ -131,37 +136,18 @@ void init_constraint(Rice::Module& m) {
131
136
 
132
137
  Rice::define_class_under<SatParameters>(m, "SatParameters")
133
138
  .define_constructor(Rice::Constructor<SatParameters>())
134
- .define_method(
135
- "max_time_in_seconds=",
136
- [](SatParameters& self, double value) {
137
- self.set_max_time_in_seconds(value);
138
- })
139
- .define_method(
140
- "enumerate_all_solutions=",
141
- [](SatParameters& self, bool value) {
142
- self.set_enumerate_all_solutions(value);
143
- })
144
- .define_method(
145
- "enumerate_all_solutions",
146
- [](SatParameters& self) {
147
- return self.enumerate_all_solutions();
148
- })
149
- .define_method("num_workers=",
150
- [](SatParameters& self, int32_t value){
151
- self.set_num_workers(value);
152
- })
153
- .define_method("cp_model_presolve=",
154
- [](SatParameters& self, bool value) {
155
- self.set_cp_model_presolve(value);
156
- })
157
- .define_method("random_seed",
158
- [](SatParameters& self) {
159
- return self.random_seed();
160
- })
161
- .define_method("random_seed=",
162
- [](SatParameters& self, int32_t value) {
163
- self.set_random_seed(value);
164
- });
139
+ .define_method("cp_model_presolve", &SatParameters::cp_model_presolve)
140
+ .define_method("cp_model_presolve=", &SatParameters::set_cp_model_presolve)
141
+ .define_method("enumerate_all_solutions", &SatParameters::enumerate_all_solutions)
142
+ .define_method("enumerate_all_solutions=", &SatParameters::set_enumerate_all_solutions)
143
+ .define_method("linearization_level", &SatParameters::linearization_level)
144
+ .define_method("linearization_level=", &SatParameters::set_linearization_level)
145
+ .define_method("max_time_in_seconds", &SatParameters::max_time_in_seconds)
146
+ .define_method("max_time_in_seconds=", &SatParameters::set_max_time_in_seconds)
147
+ .define_method("num_workers", &SatParameters::num_workers)
148
+ .define_method("num_workers=", &SatParameters::set_num_workers)
149
+ .define_method("random_seed", &SatParameters::random_seed)
150
+ .define_method("random_seed=", &SatParameters::set_random_seed);
165
151
 
166
152
  Rice::define_class_under<CpModelBuilder>(m, "CpModel")
167
153
  .define_constructor(Rice::Constructor<CpModelBuilder>())
@@ -193,32 +179,32 @@ void init_constraint(Rice::Module& m) {
193
179
  })
194
180
  .define_method(
195
181
  "new_interval_var",
196
- [](CpModelBuilder& self, IntVar start, IntVar size, IntVar end, const std::string& name) {
182
+ [](CpModelBuilder& self, const IntVar& start, const IntVar& size, const IntVar& end, const std::string& name) {
197
183
  return self.NewIntervalVar(start, size, end).WithName(name);
198
184
  })
199
185
  .define_method(
200
186
  "new_optional_interval_var",
201
- [](CpModelBuilder& self, IntVar start, IntVar size, IntVar end, BoolVar presence, const std::string& name) {
187
+ [](CpModelBuilder& self, const IntVar& start, const IntVar& size, const IntVar& end, const BoolVar& presence, const std::string& name) {
202
188
  return self.NewOptionalIntervalVar(start, size, end, presence).WithName(name);
203
189
  })
204
190
  .define_method(
205
191
  "add_bool_or",
206
- [](CpModelBuilder& self, std::vector<BoolVar> literals) {
192
+ [](CpModelBuilder& self, const std::vector<BoolVar>& literals) {
207
193
  return self.AddBoolOr(literals);
208
194
  })
209
195
  .define_method(
210
196
  "add_bool_and",
211
- [](CpModelBuilder& self, std::vector<BoolVar> literals) {
197
+ [](CpModelBuilder& self, const std::vector<BoolVar>& literals) {
212
198
  return self.AddBoolAnd(literals);
213
199
  })
214
200
  .define_method(
215
201
  "add_bool_xor",
216
- [](CpModelBuilder& self, std::vector<BoolVar> literals) {
202
+ [](CpModelBuilder& self, const std::vector<BoolVar>& literals) {
217
203
  return self.AddBoolXor(literals);
218
204
  })
219
205
  .define_method(
220
206
  "add_implication",
221
- [](CpModelBuilder& self, BoolVar a, BoolVar b) {
207
+ [](CpModelBuilder& self, const BoolVar& a, const BoolVar& b) {
222
208
  return self.AddImplication(a, b);
223
209
  })
224
210
  .define_method(
@@ -253,7 +239,7 @@ void init_constraint(Rice::Module& m) {
253
239
  })
254
240
  .define_method(
255
241
  "add_linear_expression_in_domain",
256
- [](CpModelBuilder& self, LinearExpr expr, Domain domain) {
242
+ [](CpModelBuilder& self, LinearExpr expr, const Domain& domain) {
257
243
  return self.AddLinearConstraint(expr, domain);
258
244
  })
259
245
  .define_method(
@@ -263,7 +249,7 @@ void init_constraint(Rice::Module& m) {
263
249
  })
264
250
  .define_method(
265
251
  "add_all_different",
266
- [](CpModelBuilder& self, std::vector<IntVar> vars) {
252
+ [](CpModelBuilder& self, const std::vector<IntVar>& vars) {
267
253
  return self.AddAllDifferent(vars);
268
254
  })
269
255
  .define_method(
@@ -278,7 +264,7 @@ void init_constraint(Rice::Module& m) {
278
264
  })
279
265
  .define_method(
280
266
  "add_inverse_constraint",
281
- [](CpModelBuilder& self, std::vector<IntVar> variables, std::vector<IntVar> inverse_variables) {
267
+ [](CpModelBuilder& self, const std::vector<IntVar>& variables, const std::vector<IntVar>& inverse_variables) {
282
268
  return self.AddInverseConstraint(variables, inverse_variables);
283
269
  })
284
270
  .define_method(
@@ -313,7 +299,7 @@ void init_constraint(Rice::Module& m) {
313
299
  })
314
300
  .define_method(
315
301
  "add_no_overlap",
316
- [](CpModelBuilder& self, std::vector<IntervalVar> vars) {
302
+ [](CpModelBuilder& self, const std::vector<IntervalVar>& vars) {
317
303
  return self.AddNoOverlap(vars);
318
304
  })
319
305
  .define_method(
@@ -348,12 +334,12 @@ void init_constraint(Rice::Module& m) {
348
334
  })
349
335
  .define_method(
350
336
  "add_assumption",
351
- [](CpModelBuilder& self, BoolVar lit) {
337
+ [](CpModelBuilder& self, const BoolVar& lit) {
352
338
  self.AddAssumption(lit);
353
339
  })
354
340
  .define_method(
355
341
  "add_assumptions",
356
- [](CpModelBuilder& self, std::vector<BoolVar> literals) {
342
+ [](CpModelBuilder& self, const std::vector<BoolVar>& literals) {
357
343
  self.AddAssumptions(literals);
358
344
  })
359
345
  .define_method(
@@ -370,7 +356,9 @@ void init_constraint(Rice::Module& m) {
370
356
  "to_s",
371
357
  [](CpModelBuilder& self) {
372
358
  std::string proto_string;
373
- google::protobuf::TextFormat::PrintToString(self.Proto(), &proto_string);
359
+ if (!google::protobuf::TextFormat::PrintToString(self.Proto(), &proto_string)) {
360
+ throw Rice::Exception(rb_eRuntimeError, "PrintToString failed");
361
+ }
374
362
  return proto_string;
375
363
  });
376
364
 
@@ -402,7 +390,7 @@ void init_constraint(Rice::Module& m) {
402
390
  } else if (status == CpSolverStatus::UNKNOWN) {
403
391
  return Symbol("unknown");
404
392
  } else {
405
- throw std::runtime_error("Unknown solver status");
393
+ throw std::runtime_error{"Unknown solver status"};
406
394
  }
407
395
  })
408
396
  .define_method(
@@ -426,34 +414,105 @@ void init_constraint(Rice::Module& m) {
426
414
  "_solve",
427
415
  [](Object self, CpModelBuilder& model, SatParameters& parameters, Object callback) {
428
416
  Model m;
417
+ m.Add(NewSatParameters(parameters));
429
418
 
430
- if (!callback.is_nil()) {
431
- // use a single worker since Ruby code cannot be run in a non-Ruby thread
432
- parameters.set_num_search_workers(1);
419
+ std::atomic<bool> done{false};
420
+ Channel<CpSolverResponse> channel;
421
+ Rice::Object ruby_thread;
422
+ std::optional<Rice::Exception> exception;
433
423
 
434
- m.Add(NewFeasibleSolutionObserver(
435
- [&callback](const CpSolverResponse& r) {
436
- if (!ruby_native_thread_p()) {
437
- throw std::runtime_error("Non-Ruby thread");
424
+ auto with_gvl = [](std::function<void()> f) {
425
+ auto ruby_wrapper = [](void* arg) -> void* {
426
+ (*static_cast<decltype(f)*>(arg))();
427
+ return nullptr;
428
+ };
429
+ rb_thread_call_with_gvl(ruby_wrapper, &f);
430
+ };
431
+
432
+ auto ruby_observer = [&]() {
433
+ return Rice::detail::no_gvl([&]() {
434
+ while (true) {
435
+ if (done.load() && channel.empty()) {
436
+ break;
437
+ }
438
+
439
+ while (true) {
440
+ std::optional<CpSolverResponse> response = channel.recv_timeout(std::chrono::milliseconds(10));
441
+ if (!response) {
442
+ break;
443
+ }
444
+
445
+ bool stop = false;
446
+ with_gvl([&]() {
447
+ try {
448
+ try {
449
+ callback.call("response=", response.value());
450
+ callback.call("on_solution_callback");
451
+ stop = static_cast<bool>(callback.attr_get("@stopped"));
452
+ } catch (const Rice::Exception& e) {
453
+ exception = e;
454
+ stop = true;
455
+ }
456
+ callback.call("response=", Object(Qnil));
457
+ } catch (const Rice::Exception& e) {
458
+ exception = e;
459
+ stop = true;
460
+ }
461
+ });
462
+
463
+ if (stop) {
464
+ StopSearch(&m);
465
+ return Qnil;
466
+ }
438
467
  }
439
468
 
440
- callback.call("response=", r);
441
- callback.call("on_solution_callback");
469
+ with_gvl([]() {
470
+ Rice::detail::protect(rb_thread_schedule);
471
+ });
472
+ }
473
+ return Qnil;
474
+ });
475
+ };
476
+
477
+ auto ruby_wrapper = [](void* arg) {
478
+ return (*static_cast<decltype(ruby_observer)*>(arg))();
479
+ };
480
+
481
+ if (!callback.is_nil()) {
482
+ ruby_thread = Rice::detail::protect([&]() {
483
+ return rb_thread_create(ruby_wrapper, &ruby_observer);
484
+ });
485
+
486
+ m.Add(NewFeasibleSolutionObserver(
487
+ [&](const CpSolverResponse& response) {
488
+ channel.send(response);
442
489
  })
443
490
  );
444
491
  }
445
492
 
446
- m.Add(NewSatParameters(parameters));
447
- return SolveCpModel(model.Build(), &m);
493
+ CpSolverResponse response = Rice::detail::no_gvl([&]() {
494
+ return SolveCpModel(model.Build(), &m);
495
+ });
496
+
497
+ if (!callback.is_nil()) {
498
+ done = true;
499
+ ruby_thread.call("value");
500
+
501
+ if (exception.has_value()) {
502
+ throw exception.value();
503
+ }
504
+ }
505
+
506
+ return response;
448
507
  })
449
508
  .define_method(
450
509
  "_solution_integer_value",
451
- [](Object self, const CpSolverResponse& response, IntVar x) {
510
+ [](Object self, const CpSolverResponse& response, const IntVar& x) {
452
511
  return SolutionIntegerValue(response, x);
453
512
  })
454
513
  .define_method(
455
514
  "_solution_boolean_value",
456
- [](Object self, const CpSolverResponse& response, BoolVar x) {
515
+ [](Object self, const CpSolverResponse& response, const BoolVar& x) {
457
516
  return SolutionBooleanValue(response, x);
458
517
  });
459
518
  }
data/ext/or-tools/ext.cpp CHANGED
@@ -16,7 +16,7 @@ void init_routing(Rice::Module& m);
16
16
 
17
17
  extern "C"
18
18
  void Init_ext() {
19
- auto m = Rice::define_module("ORTools");
19
+ Rice::Module m = Rice::define_module("ORTools");
20
20
 
21
21
  m.define_singleton_function("lib_version", &operations_research::OrToolsVersionString);
22
22
 
@@ -8,8 +8,8 @@
8
8
 
9
9
  using operations_research::MPConstraint;
10
10
  using operations_research::MPObjective;
11
- using operations_research::MPSolverParameters;
12
11
  using operations_research::MPSolver;
12
+ using operations_research::MPSolverParameters;
13
13
  using operations_research::MPVariable;
14
14
 
15
15
  using Rice::Array;
@@ -34,13 +34,13 @@ namespace Rice::detail {
34
34
  double is_convertible(VALUE value) { return Convertible::Exact; }
35
35
 
36
36
  static MPSolver::OptimizationProblemType convert(VALUE x) {
37
- auto s = Symbol(x).str();
37
+ std::string s = Symbol(x).str();
38
38
  if (s == "glop") {
39
39
  return MPSolver::OptimizationProblemType::GLOP_LINEAR_PROGRAMMING;
40
40
  } else if (s == "cbc") {
41
41
  return MPSolver::OptimizationProblemType::CBC_MIXED_INTEGER_PROGRAMMING;
42
42
  } else {
43
- throw std::runtime_error("Unknown optimization problem type: " + s);
43
+ throw std::runtime_error{"Unknown optimization problem type: " + s};
44
44
  }
45
45
  }
46
46
 
@@ -175,7 +175,7 @@ void init_linear(Rice::Module& m) {
175
175
  [](const std::string& name, MPSolver::OptimizationProblemType problem_type) {
176
176
  MPSolver* solver = new MPSolver(name, problem_type);
177
177
  if (!solver) {
178
- throw std::runtime_error("Unrecognized solver type");
178
+ throw std::runtime_error{"Unrecognized solver type"};
179
179
  }
180
180
  return solver;
181
181
  }, Rice::Return().takeOwnership())
@@ -184,7 +184,7 @@ void init_linear(Rice::Module& m) {
184
184
  [](const std::string& solver_id) {
185
185
  MPSolver* solver = MPSolver::CreateSolver(solver_id);
186
186
  if (!solver) {
187
- throw std::runtime_error("Unrecognized solver type");
187
+ throw std::runtime_error{"Unrecognized solver type"};
188
188
  }
189
189
  return solver;
190
190
  }, Rice::Return().takeOwnership())
@@ -239,7 +239,7 @@ void init_linear(Rice::Module& m) {
239
239
  } else if (status == MPSolver::ResultStatus::NOT_SOLVED) {
240
240
  return Symbol("not_solved");
241
241
  } else {
242
- throw std::runtime_error("Unknown status");
242
+ throw std::runtime_error{"Unknown status"};
243
243
  }
244
244
  })
245
245
  .define_method(
@@ -247,7 +247,7 @@ void init_linear(Rice::Module& m) {
247
247
  [](MPSolver& self, bool obfuscate) {
248
248
  std::string model_str;
249
249
  if (!self.ExportModelAsLpFormat(obfuscate, &model_str)) {
250
- throw std::runtime_error("Export failed");
250
+ throw std::runtime_error{"Export failed"};
251
251
  }
252
252
  return model_str;
253
253
  })
@@ -256,7 +256,7 @@ void init_linear(Rice::Module& m) {
256
256
  [](MPSolver& self, bool fixed_format, bool obfuscate) {
257
257
  std::string model_str;
258
258
  if (!self.ExportModelAsMpsFormat(fixed_format, obfuscate, &model_str)) {
259
- throw std::runtime_error("Export failed");
259
+ throw std::runtime_error{"Export failed"};
260
260
  }
261
261
  return model_str;
262
262
  });
@@ -32,7 +32,7 @@ namespace Rice::detail {
32
32
  double is_convertible(VALUE value) { return Convertible::Exact; }
33
33
 
34
34
  static SolverType convert(VALUE x) {
35
- auto s = Symbol(x).str();
35
+ std::string s = Symbol(x).str();
36
36
  if (s == "gscip") {
37
37
  return SolverType::kGscip;
38
38
  } else if (s == "gurobi") {
@@ -54,7 +54,7 @@ namespace Rice::detail {
54
54
  } else if (s == "santorini") {
55
55
  return SolverType::kSantorini;
56
56
  } else {
57
- throw std::runtime_error("Unknown solver type: " + s);
57
+ throw std::runtime_error{"Unknown solver type: " + s};
58
58
  }
59
59
  }
60
60
 
@@ -71,11 +71,11 @@ void init_math_opt(Rice::Module& m) {
71
71
  .define_method(
72
72
  "name",
73
73
  [](Variable& self) {
74
- return std::string(self.name());
74
+ return std::string{self.name()};
75
75
  })
76
76
  .define_method(
77
77
  "_eql?",
78
- [](Variable& self, Variable &other) {
78
+ [](Variable& self, Variable& other) {
79
79
  return static_cast<bool>(self == other);
80
80
  });
81
81
 
@@ -106,7 +106,7 @@ void init_math_opt(Rice::Module& m) {
106
106
  } else if (reason == TerminationReason::kOtherError) {
107
107
  return Rice::Symbol("other");
108
108
  } else {
109
- throw std::runtime_error("Unknown termination reason");
109
+ throw std::runtime_error{"Unknown termination reason"};
110
110
  }
111
111
  });
112
112
 
@@ -143,18 +143,18 @@ void init_math_opt(Rice::Module& m) {
143
143
  })
144
144
  .define_method(
145
145
  "_set_upper_bound",
146
- [](Model& self, LinearConstraint constraint, double upper_bound) {
146
+ [](Model& self, const LinearConstraint& constraint, double upper_bound) {
147
147
  self.set_upper_bound(constraint, upper_bound);
148
148
  })
149
149
  .define_method(
150
150
  "_set_lower_bound",
151
- [](Model& self, LinearConstraint constraint, double upper_bound) {
151
+ [](Model& self, const LinearConstraint& constraint, double upper_bound) {
152
152
  self.set_lower_bound(constraint, upper_bound);
153
153
  })
154
154
  .define_method("_set_coefficient", &Model::set_coefficient)
155
155
  .define_method(
156
156
  "_set_objective_coefficient",
157
- [](Model& self, Variable variable, double value) {
157
+ [](Model& self, const Variable& variable, double value) {
158
158
  self.set_objective_coefficient(variable, value);
159
159
  })
160
160
  .define_method("_clear_objective", &Model::clear_objective)
@@ -36,7 +36,7 @@ void init_network_flows(Rice::Module& m) {
36
36
  } else if (status == SimpleMaxFlow::Status::BAD_RESULT) {
37
37
  return Symbol("bad_result");
38
38
  } else {
39
- throw std::runtime_error("Unknown status");
39
+ throw std::runtime_error{"Unknown status"};
40
40
  }
41
41
  })
42
42
  .define_method(
@@ -98,7 +98,7 @@ void init_network_flows(Rice::Module& m) {
98
98
  } else if (status == SimpleMinCostFlow::Status::BAD_COST_RANGE) {
99
99
  return Symbol("bad_cost_range");
100
100
  } else {
101
- throw std::runtime_error("Unknown status");
101
+ throw std::runtime_error{"Unknown status"};
102
102
  }
103
103
  });
104
104
  }
@@ -58,7 +58,7 @@ namespace Rice::detail {
58
58
 
59
59
  explicit To_Ruby(Arg* arg) : arg_(arg) { }
60
60
 
61
- VALUE convert(RoutingNodeIndex const & x) {
61
+ VALUE convert(const RoutingNodeIndex& x) {
62
62
  return To_Ruby<int>().convert(x.value());
63
63
  }
64
64
 
@@ -81,13 +81,13 @@ namespace Rice::detail {
81
81
  double is_convertible(VALUE value) { return Convertible::Exact; }
82
82
 
83
83
  RoutingModel::PenaltyCostBehavior convert(VALUE x) {
84
- auto s = Symbol(x).str();
84
+ std::string s = Symbol(x).str();
85
85
  if (s == "penalize_once") {
86
86
  return RoutingModel::PenaltyCostBehavior::PENALIZE_ONCE;
87
87
  } else if (s == "penalize_per_inactive") {
88
88
  return RoutingModel::PenaltyCostBehavior::PENALIZE_PER_INACTIVE;
89
89
  } else {
90
- throw std::runtime_error("Unknown penalty cost behavior: " + s);
90
+ throw std::runtime_error{"Unknown penalty cost behavior: " + s};
91
91
  }
92
92
  }
93
93
 
@@ -138,7 +138,7 @@ void init_routing(Rice::Module& m) {
138
138
  } else if (s == "first_unbound_min_value") {
139
139
  v = FirstSolutionStrategy::FIRST_UNBOUND_MIN_VALUE;
140
140
  } else {
141
- throw std::runtime_error("Unknown first solution strategy: " + s);
141
+ throw std::runtime_error{"Unknown first solution strategy: " + s};
142
142
  }
143
143
 
144
144
  return self.set_first_solution_strategy(v);
@@ -158,7 +158,7 @@ void init_routing(Rice::Module& m) {
158
158
  } else if (s == "simulated_annealing") {
159
159
  v = LocalSearchMetaheuristic::SIMULATED_ANNEALING;
160
160
  } else {
161
- throw std::runtime_error("Unknown local search metaheuristic: " + s);
161
+ throw std::runtime_error{"Unknown local search metaheuristic: " + s};
162
162
  }
163
163
 
164
164
  return self.set_local_search_metaheuristic(v);
@@ -192,7 +192,7 @@ void init_routing(Rice::Module& m) {
192
192
  })
193
193
  .define_singleton_function(
194
194
  "_new_starts_ends",
195
- [](int num_nodes, int num_vehicles, std::vector<RoutingNodeIndex> starts, std::vector<RoutingNodeIndex> ends) {
195
+ [](int num_nodes, int num_vehicles, const std::vector<RoutingNodeIndex>& starts, const std::vector<RoutingNodeIndex>& ends) {
196
196
  return RoutingIndexManager(num_nodes, num_vehicles, starts, ends);
197
197
  })
198
198
  .define_method("index_to_node", &RoutingIndexManager::IndexToNode)
@@ -273,13 +273,13 @@ void init_routing(Rice::Module& m) {
273
273
  if (o.respond_to("left")) {
274
274
  operations_research::IntExpr* left(Rice::detail::From_Ruby<operations_research::IntVar*>().convert(o.call("left")));
275
275
  operations_research::IntExpr* right(Rice::detail::From_Ruby<operations_research::IntVar*>().convert(o.call("right")));
276
- auto op = o.call("op").to_s().str();
276
+ std::string op = o.call("op").to_s().str();
277
277
  if (op == "==") {
278
278
  constraint = self.MakeEquality(left, right);
279
279
  } else if (op == "<=") {
280
280
  constraint = self.MakeLessOrEqual(left, right);
281
281
  } else {
282
- throw std::runtime_error("Unknown operator");
282
+ throw std::runtime_error{"Unknown operator"};
283
283
  }
284
284
  } else {
285
285
  constraint = Rice::detail::From_Ruby<operations_research::Constraint*>().convert(o);
@@ -288,12 +288,12 @@ void init_routing(Rice::Module& m) {
288
288
  })
289
289
  .define_method(
290
290
  "fixed_duration_interval_var",
291
- [](operations_research::Solver& self, operations_research::IntVar* const start_variable, int64_t duration, const std::string& name) {
292
- return self.MakeFixedDurationIntervalVar(start_variable, duration, name);
291
+ [](operations_research::Solver& self, operations_research::IntVar& start_variable, int64_t duration, const std::string& name) {
292
+ return self.MakeFixedDurationIntervalVar(&start_variable, duration, name);
293
293
  })
294
294
  .define_method(
295
295
  "cumulative",
296
- [](operations_research::Solver& self, Array rb_intervals, std::vector<int64_t> demands, int64_t capacity, const std::string& name) {
296
+ [](operations_research::Solver& self, Array rb_intervals, const std::vector<int64_t>& demands, int64_t capacity, const std::string& name) {
297
297
  std::vector<operations_research::IntervalVar*> intervals;
298
298
  for (const auto& v : rb_intervals) {
299
299
  intervals.push_back(Rice::detail::From_Ruby<operations_research::IntervalVar*>().convert(v.value()));
@@ -332,13 +332,13 @@ void init_routing(Rice::Module& m) {
332
332
  .define_constructor(Rice::Constructor<RoutingModel, RoutingIndexManager, RoutingModelParameters>(), Rice::Arg("_index_manager"), Rice::Arg("_parameters") = operations_research::DefaultRoutingModelParameters())
333
333
  .define_method("register_unary_transit_vector", &RoutingModel::RegisterUnaryTransitVector)
334
334
  .define_method(
335
- "register_unary_transit_callback",
335
+ "_register_unary_transit_callback",
336
336
  [](RoutingModel& self, Object callback) {
337
337
  // TODO guard callback?
338
338
  return self.RegisterUnaryTransitCallback(
339
339
  [callback](int64_t from_index) -> int64_t {
340
340
  if (!ruby_native_thread_p()) {
341
- throw std::runtime_error("Non-Ruby thread");
341
+ throw std::runtime_error{"Non-Ruby thread"};
342
342
  }
343
343
 
344
344
  return Rice::detail::From_Ruby<int64_t>().convert(callback.call("call", from_index));
@@ -347,13 +347,13 @@ void init_routing(Rice::Module& m) {
347
347
  }, Rice::Arg("_callback").keepAlive())
348
348
  .define_method("register_transit_matrix", &RoutingModel::RegisterTransitMatrix)
349
349
  .define_method(
350
- "register_transit_callback",
350
+ "_register_transit_callback",
351
351
  [](RoutingModel& self, Object callback) {
352
352
  // TODO guard callback?
353
353
  return self.RegisterTransitCallback(
354
354
  [callback](int64_t from_index, int64_t to_index) -> int64_t {
355
355
  if (!ruby_native_thread_p()) {
356
- throw std::runtime_error("Non-Ruby thread");
356
+ throw std::runtime_error{"Non-Ruby thread"};
357
357
  }
358
358
 
359
359
  return Rice::detail::From_Ruby<int64_t>().convert(callback.call("call", from_index, to_index));
@@ -386,7 +386,11 @@ void init_routing(Rice::Module& m) {
386
386
  .define_method("perfect_binary_disjunctions", &RoutingModel::GetPerfectBinaryDisjunctions)
387
387
  .define_method("ignore_disjunctions_already_forced_to_zero", &RoutingModel::IgnoreDisjunctionsAlreadyForcedToZero)
388
388
  .define_method("add_soft_same_vehicle_constraint", &RoutingModel::AddSoftSameVehicleConstraint)
389
- .define_method("set_allowed_vehicles_for_index", &RoutingModel::SetAllowedVehiclesForIndex)
389
+ .define_method(
390
+ "set_allowed_vehicles_for_index",
391
+ [](RoutingModel& self, const std::vector<int>& vehicles, int64_t index) {
392
+ self.SetAllowedVehiclesForIndex(vehicles, index);
393
+ })
390
394
  .define_method("vehicle_allowed_for_index?", &RoutingModel::IsVehicleAllowedForIndex)
391
395
  .define_method("add_pickup_and_delivery", &RoutingModel::AddPickupAndDelivery)
392
396
  .define_method("add_pickup_and_delivery_sets", &RoutingModel::AddPickupAndDeliverySets)
@@ -426,14 +430,26 @@ void init_routing(Rice::Module& m) {
426
430
  .define_method("close_model", &RoutingModel::CloseModel)
427
431
  // solve defined in Ruby
428
432
  .define_method(
429
- "solve_with_parameters",
430
- [](RoutingModel& self, const RoutingSearchParameters& search_parameters) {
431
- return self.SolveWithParameters(search_parameters);
433
+ "_solve_with_parameters",
434
+ [](RoutingModel& self, const RoutingSearchParameters& search_parameters, bool release_gvl) {
435
+ if (release_gvl) {
436
+ return Rice::detail::no_gvl([&]() {
437
+ return self.SolveWithParameters(search_parameters);
438
+ });
439
+ } else {
440
+ return self.SolveWithParameters(search_parameters);
441
+ }
432
442
  })
433
443
  .define_method(
434
- "solve_from_assignment_with_parameters",
435
- [](RoutingModel& self, const Assignment* assignment, const RoutingSearchParameters& search_parameters) {
436
- return self.SolveFromAssignmentWithParameters(assignment, search_parameters);
444
+ "_solve_from_assignment_with_parameters",
445
+ [](RoutingModel& self, const Assignment& assignment, const RoutingSearchParameters& search_parameters, bool release_gvl) {
446
+ if (release_gvl) {
447
+ return Rice::detail::no_gvl([&]() {
448
+ return self.SolveFromAssignmentWithParameters(&assignment, search_parameters);
449
+ });
450
+ } else {
451
+ return self.SolveFromAssignmentWithParameters(&assignment, search_parameters);
452
+ }
437
453
  })
438
454
  .define_method("compute_lower_bound", &RoutingModel::ComputeLowerBound)
439
455
  .define_method("status",
@@ -451,10 +467,14 @@ void init_routing(Rice::Module& m) {
451
467
  } else if (status == RoutingSearchStatus::ROUTING_INVALID) {
452
468
  return Symbol("invalid");
453
469
  } else {
454
- throw std::runtime_error("Unknown solver status");
470
+ throw std::runtime_error{"Unknown solver status"};
455
471
  }
456
472
  })
457
- .define_method("apply_locks", &RoutingModel::ApplyLocks)
473
+ .define_method(
474
+ "apply_locks",
475
+ [](RoutingModel& self, const std::vector<int64_t>& locks) {
476
+ return self.ApplyLocks(locks);
477
+ })
458
478
  .define_method("apply_locks_to_all_vehicles", &RoutingModel::ApplyLocksToAllVehicles)
459
479
  .define_method("pre_assignment", &RoutingModel::PreAssignment)
460
480
  .define_method("mutable_pre_assignment", &RoutingModel::MutablePreAssignment)
@@ -1,6 +1,6 @@
1
1
  require "digest"
2
2
  require "fileutils"
3
- require "net/http"
3
+ require "open-uri"
4
4
  require "tmpdir"
5
5
 
6
6
  version = "9.15.6755"
@@ -70,62 +70,31 @@ end
70
70
  short_version = version.split(".").first(2).join(".")
71
71
  url = "https://github.com/google/or-tools/releases/download/v#{short_version}/#{filename}"
72
72
 
73
- $stdout.sync = true
74
-
75
- def download_file(url, download_path, redirects = 0)
76
- raise "Too many redirects" if redirects > 10
77
-
78
- uri = URI(url)
79
- location = nil
80
-
81
- Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
82
- request = Net::HTTP::Get.new(uri)
83
- http.request(request) do |response|
84
- case response
85
- when Net::HTTPRedirection
86
- location = response["location"]
87
- when Net::HTTPSuccess
88
- i = 0
89
- File.open(download_path, "wb") do |f|
90
- response.read_body do |chunk|
91
- f.write(chunk)
92
-
93
- # print progress
94
- putc "." if i % 50 == 0
95
- i += 1
96
- end
97
- end
98
- puts # newline
99
- else
100
- raise "Bad response"
101
- end
102
- end
103
- end
104
-
105
- # outside of Net::HTTP block to close previous connection
106
- download_file(location, download_path, redirects + 1) if location
107
- end
108
-
109
- # download
110
- download_path = "#{Dir.tmpdir}/#{filename}"
111
- unless File.exist?(download_path)
112
- puts "Downloading #{url}..."
113
- download_file(url, download_path)
114
- end
115
-
116
- # check integrity - do this regardless of if just downloaded
117
- download_checksum = Digest::SHA256.file(download_path).hexdigest
118
- raise "Bad checksum: #{download_checksum}" if download_checksum != checksum
119
-
120
73
  path = File.expand_path("../../tmp/or-tools", __dir__)
121
74
  FileUtils.mkdir_p(path)
122
75
 
123
- # extract - can't use Gem::Package#extract_tar_gz from RubyGems
124
- # since it limits filenames to 100 characters (doesn't support UStar format)
125
- # for space, only keep licenses, include, and shared library
126
76
  Dir.mktmpdir do |extract_path|
127
- tar_args = Gem.win_platform? ? ["--force-local"] : []
128
- system "tar", "zxf", download_path, "-C", extract_path, "--strip-components=1", *tar_args
77
+ # download
78
+ puts "Downloading #{url}..."
79
+ URI.parse(url).open(max_redirects: 10) do |download|
80
+ download_checksum =
81
+ if download.respond_to?(:path)
82
+ download.flush
83
+ Digest::SHA256.file(download.path).hexdigest
84
+ else
85
+ Digest::SHA256.hexdigest(download.string)
86
+ end
87
+ raise "Bad checksum: #{download_checksum}" if download_checksum != checksum
88
+
89
+ # should never happen
90
+ raise "Expected file" if !download.respond_to?(:path)
91
+
92
+ # extract - can't use Gem::Package#extract_tar_gz from RubyGems
93
+ # since it limits filenames to 100 characters (doesn't support UStar format)
94
+ # for space, only keep licenses, include, and shared library
95
+ tar_args = Gem.win_platform? ? ["--force-local"] : []
96
+ system "tar", "zxf", download.path, "-C", extract_path, "--strip-components=1", *tar_args
97
+ end
129
98
 
130
99
  # include
131
100
  FileUtils.mv(File.join(extract_path, "include"), File.join(path, "include"))
@@ -8,6 +8,7 @@ module ORTools
8
8
 
9
9
  def solve(model, observer = nil)
10
10
  @response = _solve(model, parameters, observer)
11
+ observer.response = @response if observer
11
12
  @response.status
12
13
  end
13
14
 
@@ -25,22 +26,6 @@ module ORTools
25
26
  end
26
27
  end
27
28
 
28
- def solve_with_solution_callback(model, observer)
29
- warn "[or-tools] solve_with_solution_callback is deprecated; use solve(model, callback)"
30
- solve(model, observer)
31
- end
32
-
33
- def search_for_all_solutions(model, observer)
34
- warn "[or-tools] search_for_all_solutions is deprecated; use solve() with solver.parameters.enumerate_all_solutions = true"
35
- previous_value = parameters.enumerate_all_solutions
36
- begin
37
- parameters.enumerate_all_solutions = true
38
- solve(model, observer)
39
- ensure
40
- parameters.enumerate_all_solutions = previous_value
41
- end
42
- end
43
-
44
29
  def solution_info
45
30
  @response.solution_info
46
31
  end
@@ -16,5 +16,9 @@ module ORTools
16
16
  def objective_value
17
17
  @response&.objective_value
18
18
  end
19
+
20
+ def stop_search
21
+ @stopped = true
22
+ end
19
23
  end
20
24
  end
@@ -21,5 +21,23 @@ module ORTools
21
21
  def add_disjunction(indices, penalty, max_cardinality = 1, penalty_cost_behavior = :penalize_once)
22
22
  _add_disjunction(indices, penalty, max_cardinality, penalty_cost_behavior)
23
23
  end
24
+
25
+ def solve_with_parameters(search_parameters)
26
+ _solve_with_parameters(search_parameters, !@ruby_callback)
27
+ end
28
+
29
+ def solve_from_assignment_with_parameters(assignment, search_parameters)
30
+ _solve_from_assignment_with_parameters(assignment, search_parameters, !@ruby_callback)
31
+ end
32
+
33
+ def register_unary_transit_callback(callback)
34
+ @ruby_callback = true
35
+ _register_unary_transit_callback(callback)
36
+ end
37
+
38
+ def register_transit_callback(callback)
39
+ @ruby_callback = true
40
+ _register_transit_callback(callback)
41
+ end
24
42
  end
25
43
  end
@@ -1,3 +1,3 @@
1
1
  module ORTools
2
- VERSION = "0.17.1"
2
+ VERSION = "0.18.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: or-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.1
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
@@ -35,6 +35,7 @@ files:
35
35
  - README.md
36
36
  - ext/or-tools/assignment.cpp
37
37
  - ext/or-tools/bin_packing.cpp
38
+ - ext/or-tools/channel.hpp
38
39
  - ext/or-tools/constraint.cpp
39
40
  - ext/or-tools/ext.cpp
40
41
  - ext/or-tools/extconf.rb
@@ -78,14 +79,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
79
  requirements:
79
80
  - - ">="
80
81
  - !ruby/object:Gem::Version
81
- version: '3.1'
82
+ version: '3.3'
82
83
  required_rubygems_version: !ruby/object:Gem::Requirement
83
84
  requirements:
84
85
  - - ">="
85
86
  - !ruby/object:Gem::Version
86
87
  version: '0'
87
88
  requirements: []
88
- rubygems_version: 4.0.3
89
+ rubygems_version: 4.0.14
89
90
  specification_version: 4
90
91
  summary: Operations research tools for Ruby
91
92
  test_files: []