or-tools 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)
@@ -21,25 +37,22 @@ module ORTools
21
37
  private
22
38
 
23
39
  def set_objective(expr)
40
+ coeffs = Utils.index_expression(expr, check_linear: true)
41
+ offset = coeffs.delete(nil)
42
+
24
43
  objective.clear
25
- coeffs = expr.coeffs
26
- offset = coeffs.delete(OFFSET_KEY)
27
44
  objective.set_offset(offset) if offset
28
- coeffs.each do |v, c|
29
- objective.set_coefficient(v, c)
45
+ coeffs.each do |var, c|
46
+ objective.set_coefficient(var, c)
30
47
  end
31
48
  end
32
49
 
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
50
+ def self.new(solver_id, *args)
51
+ if args.empty?
52
+ _create(solver_id)
53
+ else
54
+ _new(solver_id, *args)
41
55
  end
42
56
  end
43
- singleton_class.prepend(m)
44
57
  end
45
58
  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
@@ -0,0 +1,29 @@
1
+ module ORTools
2
+ module Variable
3
+ include ExpressionMethods
4
+
5
+ def inspect
6
+ name
7
+ end
8
+
9
+ def vars
10
+ @vars ||= [self]
11
+ end
12
+ end
13
+
14
+ class MPVariable
15
+ include Variable
16
+ end
17
+
18
+ class SatIntVar
19
+ include Variable
20
+ end
21
+
22
+ class SatBoolVar
23
+ include Variable
24
+ end
25
+
26
+ class RoutingIntVar
27
+ include Variable
28
+ end
29
+ end
@@ -1,3 +1,3 @@
1
1
  module ORTools
2
- VERSION = "0.13.0"
2
+ VERSION = "0.14.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: or-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-17 00:00:00.000000000 Z
11
+ date: 2024-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rice
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.3'
19
+ version: 4.3.3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '4.3'
26
+ version: 4.3.3
27
27
  description:
28
28
  email: andrew@ankane.org
29
29
  executables: []
@@ -42,36 +42,33 @@ files:
42
42
  - ext/or-tools/ext.h
43
43
  - ext/or-tools/extconf.rb
44
44
  - ext/or-tools/linear.cpp
45
+ - ext/or-tools/math_opt.cpp
45
46
  - ext/or-tools/network_flows.cpp
46
47
  - ext/or-tools/routing.cpp
47
48
  - ext/or-tools/vendor.rb
48
49
  - lib/or-tools.rb
49
50
  - lib/or_tools/basic_scheduler.rb
50
- - lib/or_tools/bool_var.rb
51
51
  - lib/or_tools/comparison.rb
52
- - lib/or_tools/comparison_operators.rb
53
52
  - lib/or_tools/constant.rb
54
53
  - lib/or_tools/cp_model.rb
55
54
  - lib/or_tools/cp_solver.rb
56
55
  - lib/or_tools/cp_solver_solution_callback.rb
57
- - lib/or_tools/int_var.rb
56
+ - lib/or_tools/expression.rb
58
57
  - lib/or_tools/knapsack_solver.rb
59
- - lib/or_tools/linear_constraint.rb
60
- - lib/or_tools/linear_expr.rb
61
- - lib/or_tools/mp_variable.rb
58
+ - lib/or_tools/math_opt/model.rb
59
+ - lib/or_tools/math_opt/variable.rb
62
60
  - lib/or_tools/objective_solution_printer.rb
63
- - lib/or_tools/product_cst.rb
61
+ - lib/or_tools/product.rb
64
62
  - lib/or_tools/routing_index_manager.rb
65
63
  - lib/or_tools/routing_model.rb
66
- - lib/or_tools/sat_int_var.rb
67
- - lib/or_tools/sat_linear_expr.rb
68
64
  - lib/or_tools/seating.rb
69
65
  - lib/or_tools/solver.rb
70
66
  - lib/or_tools/sudoku.rb
71
- - lib/or_tools/sum_array.rb
72
67
  - lib/or_tools/tsp.rb
68
+ - lib/or_tools/utils.rb
73
69
  - lib/or_tools/var_array_and_objective_solution_printer.rb
74
70
  - lib/or_tools/var_array_solution_printer.rb
71
+ - lib/or_tools/variable.rb
75
72
  - lib/or_tools/version.rb
76
73
  homepage: https://github.com/ankane/or-tools-ruby
77
74
  licenses:
@@ -1,9 +0,0 @@
1
- module ORTools
2
- class BoolVar
3
- include ComparisonOperators
4
-
5
- def *(other)
6
- SatLinearExpr.new([[self, other]])
7
- end
8
- end
9
- end
@@ -1,9 +0,0 @@
1
- module ORTools
2
- module ComparisonOperators
3
- ["==", "!=", ">", ">=", "<", "<="].each do |operator|
4
- define_method(operator) do |other|
5
- Comparison.new(operator, self, other)
6
- end
7
- end
8
- end
9
- end
@@ -1,5 +0,0 @@
1
- module ORTools
2
- class IntVar
3
- include ComparisonOperators
4
- end
5
- end
@@ -1,50 +0,0 @@
1
- module ORTools
2
- class LinearConstraint
3
- attr_reader :expr, :lb, :ub
4
-
5
- def initialize(expr, lb, ub)
6
- @expr = expr
7
- @lb = lb
8
- @ub = ub
9
- end
10
-
11
- def to_s
12
- if @lb > -Float::INFINITY && @ub < Float::INFINITY
13
- if @lb == @ub
14
- "#{@expr} == #{@lb}"
15
- else
16
- "#{@lb} <= #{@expr} <= #{@ub}"
17
- end
18
- elsif @lb > -Float::INFINITY
19
- "#{@expr} >= #{@lb}"
20
- elsif @ub < Float::INFINITY
21
- "#{@expr} <= #{@ub}"
22
- else
23
- "Trivial inequality (always true)"
24
- end
25
- end
26
-
27
- def inspect
28
- "#<#{self.class.name} #{to_s}>"
29
- end
30
-
31
- def extract(solver)
32
- coeffs = @expr.coeffs
33
- constant = coeffs.delete(OFFSET_KEY) || 0.0
34
- lb = -solver.infinity
35
- ub = solver.infinity
36
- if @lb > -Float::INFINITY
37
- lb = @lb - constant
38
- end
39
- if @ub < Float::INFINITY
40
- ub = @ub - constant
41
- end
42
-
43
- constraint = solver.constraint(lb, ub)
44
- coeffs.each do |v, c|
45
- constraint.set_coefficient(v, c.to_f)
46
- end
47
- constraint
48
- end
49
- end
50
- end