or-tools 0.13.1 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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