or-tools 0.13.1 → 0.14.1

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.
@@ -56,22 +56,9 @@ namespace Rice::detail
56
56
  };
57
57
  }
58
58
 
59
- namespace Rice::detail
60
- {
61
- template<class T, class U>
62
- class To_Ruby<std::pair<T, U>>
63
- {
64
- public:
65
- VALUE convert(std::pair<T, U> const & x)
66
- {
67
- return rb_ary_new3(2, To_Ruby<T>().convert(x.first), To_Ruby<U>().convert(x.second));
68
- }
69
- };
70
- }
71
-
72
59
  void init_routing(Rice::Module& m) {
73
60
  auto rb_cRoutingSearchParameters = Rice::define_class_under<RoutingSearchParameters>(m, "RoutingSearchParameters");
74
- auto rb_cIntVar = Rice::define_class_under<operations_research::IntVar>(m, "IntVar");
61
+ auto rb_cIntVar = Rice::define_class_under<operations_research::IntVar>(m, "RoutingIntVar");
75
62
 
76
63
  m.define_singleton_function("default_routing_search_parameters", &DefaultRoutingSearchParameters);
77
64
 
@@ -213,7 +200,6 @@ void init_routing(Rice::Module& m) {
213
200
 
214
201
  Rice::define_class_under<RoutingDimension>(m, "RoutingDimension")
215
202
  .define_method("transit_value", &RoutingDimension::GetTransitValue)
216
- // TODO GetTransitValueFromClass
217
203
  .define_method("cumul_var", &RoutingDimension::CumulVar)
218
204
  .define_method("transit_var", &RoutingDimension::TransitVar)
219
205
  .define_method("fixed_transit_var", &RoutingDimension::FixedTransitVar)
@@ -239,7 +225,7 @@ void init_routing(Rice::Module& m) {
239
225
  .define_method("post", &operations_research::Constraint::Post)
240
226
  .define_method("debug_string", &operations_research::Constraint::DebugString);
241
227
 
242
- Rice::define_class_under<operations_research::Solver>(m, "Solver2")
228
+ Rice::define_class_under<operations_research::Solver>(m, "RoutingSolver")
243
229
  .define_method(
244
230
  "add",
245
231
  [](operations_research::Solver& self, Object o) {
@@ -247,7 +233,7 @@ void init_routing(Rice::Module& m) {
247
233
  if (o.respond_to("left")) {
248
234
  operations_research::IntExpr* left(Rice::detail::From_Ruby<operations_research::IntVar*>().convert(o.call("left")));
249
235
  operations_research::IntExpr* right(Rice::detail::From_Ruby<operations_research::IntVar*>().convert(o.call("right")));
250
- auto op = o.call("operator").to_s().str();
236
+ auto op = o.call("op").to_s().str();
251
237
  if (op == "==") {
252
238
  constraint = self.MakeEquality(left, right);
253
239
  } else if (op == "<=") {
@@ -304,8 +290,13 @@ void init_routing(Rice::Module& m) {
304
290
  .define_method(
305
291
  "register_unary_transit_callback",
306
292
  [](RoutingModel& self, Object callback) {
293
+ // TODO guard callback?
307
294
  return self.RegisterUnaryTransitCallback(
308
295
  [callback](int64_t from_index) -> int64_t {
296
+ if (!ruby_native_thread_p()) {
297
+ throw std::runtime_error("Non-Ruby thread");
298
+ }
299
+
309
300
  return Rice::detail::From_Ruby<int64_t>().convert(callback.call("call", from_index));
310
301
  }
311
302
  );
@@ -314,8 +305,13 @@ void init_routing(Rice::Module& m) {
314
305
  .define_method(
315
306
  "register_transit_callback",
316
307
  [](RoutingModel& self, Object callback) {
308
+ // TODO guard callback?
317
309
  return self.RegisterTransitCallback(
318
310
  [callback](int64_t from_index, int64_t to_index) -> int64_t {
311
+ if (!ruby_native_thread_p()) {
312
+ throw std::runtime_error("Non-Ruby thread");
313
+ }
314
+
319
315
  return Rice::detail::From_Ruby<int64_t>().convert(callback.call("call", from_index, to_index));
320
316
  }
321
317
  );
@@ -328,13 +324,8 @@ void init_routing(Rice::Module& m) {
328
324
  .define_method("add_constant_dimension", &RoutingModel::AddConstantDimension)
329
325
  .define_method("add_vector_dimension", &RoutingModel::AddVectorDimension)
330
326
  .define_method("add_matrix_dimension", &RoutingModel::AddMatrixDimension)
331
- // TODO AddDimensionDependentDimensionWithVehicleCapacity
332
- // .define_method("make_path_spans_and_total_slacks", &RoutingModel::MakePathSpansAndTotalSlacks)
333
327
  .define_method("all_dimension_names", &RoutingModel::GetAllDimensionNames)
334
- // .define_method("dimensions", &RoutingModel::GetDimensions)
335
- // .define_method("dimensions_with_soft_or_span_costs", &RoutingModel::GetDimensionsWithSoftOrSpanCosts)
336
328
  .define_method("dimension?", &RoutingModel::HasDimension)
337
- // .define_method("dimension_or_die", &RoutingModel::GetDimensionOrDie)
338
329
  .define_method("mutable_dimension", &RoutingModel::GetMutableDimension)
339
330
  .define_method("set_primary_constrained_dimension", &RoutingModel::SetPrimaryConstrainedDimension)
340
331
  .define_method("primary_constrained_dimension", &RoutingModel::GetPrimaryConstrainedDimension)
@@ -373,9 +364,6 @@ void init_routing(Rice::Module& m) {
373
364
  }
374
365
  return positions;
375
366
  })
376
- // TODO SetPickupAndDeliveryPolicyOfAllVehicles
377
- // TODO SetPickupAndDeliveryPolicyOfVehicle
378
- // TODO GetPickupAndDeliveryPolicyOfVehicle
379
367
  .define_method("num_of_singleton_nodes", &RoutingModel::GetNumOfSingletonNodes)
380
368
  .define_method("unperformed_penalty", &RoutingModel::UnperformedPenalty)
381
369
  .define_method("unperformed_penalty_or_value", &RoutingModel::UnperformedPenaltyOrValue)
@@ -444,8 +432,6 @@ void init_routing(Rice::Module& m) {
444
432
  .define_method("compact_and_check_assignment", &RoutingModel::CompactAndCheckAssignment)
445
433
  .define_method("add_to_assignment", &RoutingModel::AddToAssignment)
446
434
  .define_method("add_interval_to_assignment", &RoutingModel::AddIntervalToAssignment)
447
- // TODO PackCumulsOfOptimizerDimensionsFromAssignment
448
- // TODO AddLocalSearchFilter
449
435
  .define_method("start", &RoutingModel::Start)
450
436
  .define_method("end", &RoutingModel::End)
451
437
  .define_method("start?", &RoutingModel::IsStart)
data/lib/or-tools.rb CHANGED
@@ -1,39 +1,50 @@
1
1
  # ext
2
2
  require "or_tools/ext"
3
3
 
4
- # modules
4
+ # expressions
5
+ require_relative "or_tools/expression"
5
6
  require_relative "or_tools/comparison"
6
- require_relative "or_tools/comparison_operators"
7
- require_relative "or_tools/bool_var"
8
7
  require_relative "or_tools/constant"
8
+ require_relative "or_tools/product"
9
+ require_relative "or_tools/variable"
10
+
11
+ # bin packing
12
+ require_relative "or_tools/knapsack_solver"
13
+
14
+ # constraint
9
15
  require_relative "or_tools/cp_model"
10
16
  require_relative "or_tools/cp_solver"
11
17
  require_relative "or_tools/cp_solver_solution_callback"
12
- require_relative "or_tools/int_var"
13
- require_relative "or_tools/knapsack_solver"
14
- require_relative "or_tools/linear_constraint"
15
- require_relative "or_tools/linear_expr"
16
- require_relative "or_tools/mp_variable"
17
- require_relative "or_tools/product_cst"
18
- require_relative "or_tools/routing_index_manager"
19
- require_relative "or_tools/routing_model"
20
- require_relative "or_tools/sat_linear_expr"
21
- require_relative "or_tools/sat_int_var"
22
- require_relative "or_tools/solver"
23
- require_relative "or_tools/sum_array"
24
- require_relative "or_tools/version"
25
-
26
- # solution printers
27
18
  require_relative "or_tools/objective_solution_printer"
28
19
  require_relative "or_tools/var_array_solution_printer"
29
20
  require_relative "or_tools/var_array_and_objective_solution_printer"
30
21
 
22
+ # linear
23
+ require_relative "or_tools/solver"
24
+
25
+ # math opt
26
+ require_relative "or_tools/math_opt/model"
27
+ require_relative "or_tools/math_opt/variable"
28
+
29
+ # routing
30
+ require_relative "or_tools/routing_index_manager"
31
+ require_relative "or_tools/routing_model"
32
+
31
33
  # higher level interfaces
32
34
  require_relative "or_tools/basic_scheduler"
33
35
  require_relative "or_tools/seating"
34
36
  require_relative "or_tools/sudoku"
35
37
  require_relative "or_tools/tsp"
36
38
 
39
+ # modules
40
+ require_relative "or_tools/utils"
41
+ require_relative "or_tools/version"
42
+
37
43
  module ORTools
38
44
  class Error < StandardError; end
45
+
46
+ # previous names
47
+ Solver2 = RoutingSolver
48
+ IntVar = RoutingIntVar
49
+ BoolVar = SatBoolVar
39
50
  end
@@ -1,19 +1,16 @@
1
1
  module ORTools
2
2
  class Comparison
3
- attr_reader :operator, :left, :right
3
+ attr_reader :left, :op, :right
4
4
 
5
- def initialize(operator, left, right)
6
- @operator = operator
7
- @left = left
8
- @right = right
9
- end
10
-
11
- def to_s
12
- "#{left} #{operator} #{right}"
5
+ def initialize(left, op, right)
6
+ @left = Expression.to_expression(left)
7
+ @op = op
8
+ @right = Expression.to_expression(right)
13
9
  end
14
10
 
15
11
  def inspect
16
- "#<#{self.class.name} #{to_s}>"
12
+ "#{@left.inspect} #{@op} #{@right.inspect}"
17
13
  end
14
+ alias_method :to_s, :inspect
18
15
  end
19
16
  end
@@ -1,23 +1,26 @@
1
1
  module ORTools
2
- class Constant < LinearExpr
3
- def initialize(val)
4
- @val = val
2
+ class Constant < Expression
3
+ attr_reader :value
4
+
5
+ def initialize(value)
6
+ @value = value
5
7
  end
6
8
 
7
- def to_s
8
- @val.to_s
9
+ # simplify Ruby sum
10
+ def +(other)
11
+ @value == 0 ? other : super
9
12
  end
10
13
 
11
- def add_self_to_coeff_map_or_stack(coeffs, multiplier, stack)
12
- coeffs[OFFSET_KEY] += @val * multiplier
14
+ def inspect
15
+ @value.to_s
13
16
  end
14
- end
15
17
 
16
- class FakeMPVariableRepresentingTheConstantOffset
17
- def solution_value
18
- 1
18
+ def -@
19
+ Constant.new(-value)
19
20
  end
20
- end
21
21
 
22
- OFFSET_KEY = FakeMPVariableRepresentingTheConstantOffset.new
22
+ def vars
23
+ @vars ||= []
24
+ end
25
+ end
23
26
  end
@@ -4,18 +4,18 @@ module ORTools
4
4
  case comparison
5
5
  when Comparison
6
6
  method_name =
7
- case comparison.operator
8
- when "=="
7
+ case comparison.op
8
+ when :==
9
9
  :add_equality
10
- when "!="
10
+ when :!=
11
11
  :add_not_equal
12
- when ">"
12
+ when :>
13
13
  :add_greater_than
14
- when ">="
14
+ when :>=
15
15
  :add_greater_or_equal
16
- when "<"
16
+ when :<
17
17
  :add_less_than
18
- when "<="
18
+ when :<=
19
19
  :add_less_or_equal
20
20
  else
21
21
  raise ArgumentError, "Unknown operator: #{comparison.operator}"
@@ -32,7 +32,7 @@ module ORTools
32
32
  end
33
33
 
34
34
  def sum(arr)
35
- arr.sum(SatLinearExpr.new)
35
+ Expression.new(arr)
36
36
  end
37
37
 
38
38
  def inspect
@@ -5,16 +5,16 @@ module ORTools
5
5
  def value(expr)
6
6
  case expr
7
7
  when SatIntVar
8
- @response.solution_integer_value(expr)
8
+ @response&.solution_integer_value(expr)
9
9
  when BoolVar
10
- @response.solution_boolean_value(expr)
10
+ @response&.solution_boolean_value(expr)
11
11
  else
12
12
  raise "Unsupported type: #{expr.class.name}"
13
13
  end
14
14
  end
15
15
 
16
16
  def objective_value
17
- @response.objective_value
17
+ @response&.objective_value
18
18
  end
19
19
  end
20
20
  end
@@ -0,0 +1,85 @@
1
+ module ORTools
2
+ module ExpressionMethods
3
+ attr_reader :parts
4
+
5
+ def +(other)
6
+ Expression.new((parts || [self]) + [Expression.to_expression(other)])
7
+ end
8
+
9
+ def -(other)
10
+ Expression.new((parts || [self]) + [-Expression.to_expression(other)])
11
+ end
12
+
13
+ def -@
14
+ -1 * self
15
+ end
16
+
17
+ def *(other)
18
+ Expression.new([Product.new(self, Expression.to_expression(other))])
19
+ end
20
+
21
+ def >(other)
22
+ Comparison.new(self, :>, other)
23
+ end
24
+
25
+ def <(other)
26
+ Comparison.new(self, :<, other)
27
+ end
28
+
29
+ def >=(other)
30
+ Comparison.new(self, :>=, other)
31
+ end
32
+
33
+ def <=(other)
34
+ Comparison.new(self, :<=, other)
35
+ end
36
+
37
+ def ==(other)
38
+ Comparison.new(self, :==, other)
39
+ end
40
+
41
+ def !=(other)
42
+ Comparison.new(self, :!=, other)
43
+ end
44
+
45
+ def inspect
46
+ @parts.reject { |v| v.is_a?(Constant) && v.value == 0 }.map(&:inspect).join(" + ").gsub(" + -", " - ")
47
+ end
48
+
49
+ def to_s
50
+ inspect
51
+ end
52
+
53
+ # keep order
54
+ def coerce(other)
55
+ if other.is_a?(Numeric)
56
+ [Constant.new(other), self]
57
+ else
58
+ raise TypeError, "#{self.class} can't be coerced into #{other.class}"
59
+ end
60
+ end
61
+
62
+ def vars
63
+ @vars ||= @parts.flat_map(&:vars)
64
+ end
65
+ end
66
+
67
+ class Expression
68
+ include ExpressionMethods
69
+
70
+ def initialize(parts = [])
71
+ @parts = parts
72
+ end
73
+
74
+ # private
75
+ def self.to_expression(other)
76
+ if other.is_a?(Numeric)
77
+ Constant.new(other)
78
+ elsif other.is_a?(Variable) || other.is_a?(Expression)
79
+ other
80
+ else
81
+ raise TypeError, "can't cast #{other.class.name} to Expression"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,54 @@
1
+ module ORTools
2
+ module MathOpt
3
+ class Model
4
+ def add_linear_constraint(expr)
5
+ left, op, const = Utils.index_constraint(expr)
6
+
7
+ constraint = _add_linear_constraint
8
+ left.each do |var, c|
9
+ _set_coefficient(constraint, var, c)
10
+ end
11
+ case op
12
+ when :<=
13
+ _set_upper_bound(constraint, const)
14
+ when :>=
15
+ _set_lower_bound(constraint, const)
16
+ when :==
17
+ _set_lower_bound(constraint, const)
18
+ _set_upper_bound(constraint, const)
19
+ else
20
+ raise ArgumentError, "Supported operations are ==, <=, and >="
21
+ end
22
+ nil
23
+ end
24
+
25
+ def maximize(objective)
26
+ set_objective(objective)
27
+ _set_maximize
28
+ end
29
+
30
+ def minimize(objective)
31
+ set_objective(objective)
32
+ _set_minimize
33
+ end
34
+
35
+ # TODO change default for MIP
36
+ def solve(solver_type = :glop)
37
+ _solve(solver_type)
38
+ end
39
+
40
+ private
41
+
42
+ def set_objective(objective)
43
+ objective = Expression.to_expression(objective)
44
+ coeffs = Utils.index_expression(objective, check_linear: true)
45
+ offset = coeffs.delete(nil)
46
+
47
+ objective.set_offset(offset) if offset
48
+ coeffs.each do |var, c|
49
+ _set_objective_coefficient(var, c)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,15 @@
1
+ module ORTools
2
+ module MathOpt
3
+ class Variable
4
+ include ORTools::Variable
5
+
6
+ def eql?(other)
7
+ other.is_a?(self.class) && _eql?(other)
8
+ end
9
+
10
+ def hash
11
+ id.hash
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ module ORTools
2
+ class Product < Expression
3
+ attr_reader :left, :right
4
+
5
+ def initialize(left, right)
6
+ @left = left
7
+ @right = right
8
+ end
9
+
10
+ def inspect
11
+ if @left.is_a?(Constant) && @right.is_a?(Variable) && left.value == -1
12
+ "-#{inspect_part(@right)}"
13
+ else
14
+ "#{inspect_part(@left)} * #{inspect_part(@right)}"
15
+ end
16
+ end
17
+
18
+ def vars
19
+ @vars ||= (@left.vars + @right.vars).uniq
20
+ end
21
+
22
+ private
23
+
24
+ def inspect_part(var)
25
+ if var.instance_of?(Expression)
26
+ "(#{var.inspect})"
27
+ else
28
+ var.inspect
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,11 +1,27 @@
1
1
  module ORTools
2
2
  class Solver
3
3
  def sum(arr)
4
- SumArray.new(arr)
4
+ Expression.new(arr)
5
5
  end
6
6
 
7
7
  def add(expr)
8
- expr.extract(self)
8
+ left, op, const = Utils.index_constraint(expr)
9
+
10
+ constraint =
11
+ case op
12
+ when :<=
13
+ self.constraint(-infinity, const)
14
+ when :>=
15
+ self.constraint(const, infinity)
16
+ when :==
17
+ self.constraint(const, const)
18
+ else
19
+ raise ArgumentError, "Supported operations are ==, <=, and >="
20
+ end
21
+ left.each do |var, c|
22
+ constraint.set_coefficient(var, c)
23
+ end
24
+ nil
9
25
  end
10
26
 
11
27
  def maximize(expr)
@@ -18,28 +34,30 @@ module ORTools
18
34
  objective.set_minimization
19
35
  end
20
36
 
37
+ def solve(parameters = nil)
38
+ parameters ||= MPSolverParameters.new
39
+ _solve(parameters)
40
+ end
41
+
21
42
  private
22
43
 
23
44
  def set_objective(expr)
45
+ coeffs = Utils.index_expression(expr, check_linear: true)
46
+ offset = coeffs.delete(nil)
47
+
24
48
  objective.clear
25
- coeffs = expr.coeffs
26
- offset = coeffs.delete(OFFSET_KEY)
27
49
  objective.set_offset(offset) if offset
28
- coeffs.each do |v, c|
29
- objective.set_coefficient(v, c)
50
+ coeffs.each do |var, c|
51
+ objective.set_coefficient(var, c)
30
52
  end
31
53
  end
32
54
 
33
- # hack to work with Rice constructor
34
- m = Module.new do
35
- def new(solver_id, *args)
36
- if args.empty?
37
- _create(solver_id)
38
- else
39
- super
40
- end
55
+ def self.new(solver_id, *args)
56
+ if args.empty?
57
+ _create(solver_id)
58
+ else
59
+ _new(solver_id, *args)
41
60
  end
42
61
  end
43
- singleton_class.prepend(m)
44
62
  end
45
63
  end
@@ -0,0 +1,107 @@
1
+ module ORTools
2
+ module Utils
3
+ def self.index_constraint(constraint)
4
+ raise ArgumentError, "Expected Comparison" unless constraint.is_a?(Comparison)
5
+
6
+ left = index_expression(constraint.left, check_linear: true)
7
+ right = index_expression(constraint.right, check_linear: true)
8
+
9
+ const = right.delete(nil).to_f - left.delete(nil).to_f
10
+ right.each do |k, v|
11
+ left[k] -= v
12
+ end
13
+
14
+ [left, constraint.op, const]
15
+ end
16
+
17
+ def self.index_expression(expression, check_linear: true)
18
+ vars = Hash.new(0)
19
+ case expression
20
+ when Numeric
21
+ vars[nil] += expression
22
+ when Constant
23
+ vars[nil] += expression.value
24
+ when Variable
25
+ vars[expression] += 1
26
+ when Product
27
+ if check_linear && expression.left.vars.any? && expression.right.vars.any?
28
+ raise ArgumentError, "Nonlinear"
29
+ end
30
+ vars = index_product(expression.left, expression.right)
31
+ when Expression
32
+ expression.parts.each do |part|
33
+ index_expression(part, check_linear: check_linear).each do |k, v|
34
+ vars[k] += v
35
+ end
36
+ end
37
+ else
38
+ raise TypeError, "Unsupported type"
39
+ end
40
+ vars
41
+ end
42
+
43
+ def self.index_product(left, right)
44
+ # normalize
45
+ types = [Constant, Variable, Product, Expression]
46
+ if types.index { |t| left.is_a?(t) } > types.index { |t| right.is_a?(t) }
47
+ left, right = right, left
48
+ end
49
+
50
+ vars = Hash.new(0)
51
+ case left
52
+ when Constant
53
+ vars = index_expression(right)
54
+ vars.transform_values! { |v| v * left.value }
55
+ when Variable
56
+ case right
57
+ when Variable
58
+ vars[quad_key(left, right)] = 1
59
+ when Product
60
+ index_expression(right).each do |k, v|
61
+ case k
62
+ when Array
63
+ raise Error, "Non-quadratic"
64
+ when Variable
65
+ vars[quad_key(left, k)] = v
66
+ else # nil
67
+ raise "Bug?"
68
+ end
69
+ end
70
+ else
71
+ right.parts.each do |part|
72
+ index_product(left, part).each do |k, v|
73
+ vars[k] += v
74
+ end
75
+ end
76
+ end
77
+ when Product
78
+ index_expression(left).each do |lk, lv|
79
+ index_expression(right).each do |rk, rv|
80
+ if lk.is_a?(Variable) && rk.is_a?(Variable)
81
+ vars[quad_key(lk, rk)] = lv * rv
82
+ else
83
+ raise "todo"
84
+ end
85
+ end
86
+ end
87
+ else # Expression
88
+ left.parts.each do |lp|
89
+ right.parts.each do |rp|
90
+ index_product(lp, rp).each do |k, v|
91
+ vars[k] += v
92
+ end
93
+ end
94
+ end
95
+ end
96
+ vars
97
+ end
98
+
99
+ def self.quad_key(left, right)
100
+ if left.object_id <= right.object_id
101
+ [left, right]
102
+ else
103
+ [right, left]
104
+ end
105
+ end
106
+ end
107
+ end