ruby-cbc 0.3.10 → 0.3.12
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 +4 -4
- data/.rubocop.yml +1 -0
- data/.ruby-style.yml +1354 -0
- data/README.md +2 -1
- data/lib/ruby-cbc.rb +12 -12
- data/lib/ruby-cbc/conflict_solver.rb +4 -5
- data/lib/ruby-cbc/ilp/constant.rb +23 -18
- data/lib/ruby-cbc/ilp/constraint.rb +10 -15
- data/lib/ruby-cbc/ilp/objective.rb +2 -8
- data/lib/ruby-cbc/ilp/term.rb +13 -15
- data/lib/ruby-cbc/ilp/term_array.rb +41 -40
- data/lib/ruby-cbc/ilp/var.rb +14 -14
- data/lib/ruby-cbc/model.rb +34 -36
- data/lib/ruby-cbc/problem.rb +8 -12
- data/lib/ruby-cbc/utils/compressed_row_storage.rb +97 -95
- data/lib/ruby-cbc/version.rb +1 -1
- data/ruby-cbc.gemspec +1 -1
- metadata +7 -4
data/README.md
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
[](https://travis-ci.org/gverger/ruby-cbc)
|
1
2
|
# Ruby-Cbc
|
2
3
|
|
3
4
|
This gem is using Cbc, an Integer Linear Programming Library, to provide optimization problems solving
|
@@ -120,7 +121,7 @@ a1 * x1 + a2 * x2 + ... + an * xn == C
|
|
120
121
|
|
121
122
|
With Ruby-Cbc you can write
|
122
123
|
```ruby
|
123
|
-
2 * (2 + 5 * x) + 4 * 5 + 1 == 1 + 4 * 5
|
124
|
+
2 * (2 + 5 * x) + 4 * 5 + 1 == 1 + 4 * 5 * y
|
124
125
|
```
|
125
126
|
The (in)equation must still be a **linear** (in)equation, you cannot multiply two variables !
|
126
127
|
|
data/lib/ruby-cbc.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
require "ruby-cbc/version"
|
2
|
-
require
|
2
|
+
require "cbc-wrapper"
|
3
3
|
|
4
4
|
module Cbc
|
5
5
|
end
|
6
6
|
|
7
7
|
files = %w(
|
8
|
-
conflict_solver
|
9
|
-
model
|
10
|
-
problem
|
11
|
-
version
|
12
|
-
ilp/constant
|
13
|
-
ilp/constraint
|
14
|
-
ilp/objective
|
15
|
-
ilp/term
|
16
|
-
ilp/term_array
|
17
|
-
ilp/var
|
18
|
-
utils/compressed_row_storage
|
8
|
+
conflict_solver
|
9
|
+
model
|
10
|
+
problem
|
11
|
+
version
|
12
|
+
ilp/constant
|
13
|
+
ilp/constraint
|
14
|
+
ilp/objective
|
15
|
+
ilp/term
|
16
|
+
ilp/term_array
|
17
|
+
ilp/var
|
18
|
+
utils/compressed_row_storage
|
19
19
|
)
|
20
20
|
|
21
21
|
files.each do |file|
|
@@ -1,7 +1,5 @@
|
|
1
1
|
module Cbc
|
2
|
-
|
3
2
|
class ConflictSolver
|
4
|
-
|
5
3
|
def initialize(problem)
|
6
4
|
# clone the model minus the objective
|
7
5
|
@model = Model.new
|
@@ -12,7 +10,7 @@ module Cbc
|
|
12
10
|
|
13
11
|
def find_conflict
|
14
12
|
crs = Util::CompressedRowStorage.from_model(@model)
|
15
|
-
continuous =
|
13
|
+
continuous = continuous_conflict?(crs)
|
16
14
|
unless continuous
|
17
15
|
p = Problem.from_compressed_row_storage(crs, continuous: false)
|
18
16
|
return [] unless infeasible?(p)
|
@@ -57,12 +55,13 @@ module Cbc
|
|
57
55
|
crs.model.constraints[0, conflict_set_size]
|
58
56
|
end
|
59
57
|
|
60
|
-
def
|
58
|
+
def continuous_conflict?(crs)
|
61
59
|
problem = Problem.from_compressed_row_storage(crs, continuous: true)
|
62
60
|
infeasible?(problem)
|
63
61
|
end
|
64
62
|
|
65
|
-
|
63
|
+
private
|
64
|
+
|
66
65
|
# finds the first constraint from constraints that makes the problem infeasible
|
67
66
|
def first_failing(conflict_set_size, crs, continuous: false, max_iterations: nil)
|
68
67
|
min_idx = conflict_set_size
|
@@ -2,35 +2,40 @@ module Ilp
|
|
2
2
|
class Constant
|
3
3
|
attr_accessor :value
|
4
4
|
def initialize(value)
|
5
|
-
raise ArgumentError,
|
5
|
+
raise ArgumentError, "Argument is not numeric" unless value.is_a? Numeric
|
6
6
|
@value = value
|
7
7
|
end
|
8
|
-
|
9
|
-
|
8
|
+
|
9
|
+
def <=(other)
|
10
|
+
other >= value
|
10
11
|
end
|
11
|
-
|
12
|
-
|
12
|
+
|
13
|
+
def <(other)
|
14
|
+
other > value
|
13
15
|
end
|
14
|
-
|
15
|
-
|
16
|
+
|
17
|
+
def >=(other)
|
18
|
+
other <= value
|
16
19
|
end
|
17
|
-
|
18
|
-
|
20
|
+
|
21
|
+
def >(other)
|
22
|
+
other < value
|
19
23
|
end
|
20
|
-
|
21
|
-
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
other == value
|
22
27
|
end
|
23
28
|
|
24
|
-
def *(
|
25
|
-
|
29
|
+
def *(other)
|
30
|
+
other * value
|
26
31
|
end
|
27
|
-
|
28
|
-
def +(
|
29
|
-
|
32
|
+
|
33
|
+
def +(other)
|
34
|
+
other + value
|
30
35
|
end
|
31
36
|
|
32
|
-
def -(
|
33
|
-
-1 *
|
37
|
+
def -(other)
|
38
|
+
-1 * other + value
|
34
39
|
end
|
35
40
|
|
36
41
|
def to_s
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module Ilp
|
2
2
|
class Constraint
|
3
|
-
|
4
3
|
LESS_OR_EQ = :less_or_eq
|
5
4
|
GREATER_OR_EQ = :greater_or_eq
|
6
5
|
EQUALS = :equals
|
@@ -15,26 +14,22 @@ module Ilp
|
|
15
14
|
end
|
16
15
|
|
17
16
|
def vars
|
18
|
-
terms.vars
|
17
|
+
terms.vars
|
19
18
|
end
|
20
19
|
|
21
20
|
def to_function_s
|
22
|
-
"#{function_name || 'constraint'}(#{vars.map(&:name).join(', ')})"
|
21
|
+
"#{function_name || 'constraint'}(#{vars.map!(&:name).join(', ')})"
|
23
22
|
end
|
24
23
|
|
24
|
+
SIGN_TO_STRING = {
|
25
|
+
LESS_OR_EQ => "<=",
|
26
|
+
GREATER_OR_EQ => ">=",
|
27
|
+
EQUALS => "=="
|
28
|
+
}
|
29
|
+
|
25
30
|
def to_s
|
26
|
-
|
27
|
-
|
28
|
-
sign = '<='
|
29
|
-
when GREATER_OR_EQ
|
30
|
-
sign = '>='
|
31
|
-
when EQUALS
|
32
|
-
sign = '='
|
33
|
-
else
|
34
|
-
sign = '??'
|
35
|
-
end
|
36
|
-
"#{@terms.to_s} #{sign} #{@bound}"
|
31
|
+
sign = SIGN_TO_STRING(@type) || "??"
|
32
|
+
"#{@terms} #{sign} #{@bound}"
|
37
33
|
end
|
38
|
-
|
39
34
|
end
|
40
35
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module Ilp
|
2
2
|
class Objective
|
3
|
-
|
4
3
|
MINIMIZE = :min
|
5
4
|
MAXIMIZE = :max
|
6
5
|
|
@@ -10,17 +9,12 @@ module Ilp
|
|
10
9
|
@terms = Ilp::Term.new(@terms) if @terms.is_a? Ilp::Var
|
11
10
|
@terms = Ilp::TermArray.new([@terms]) if @terms.is_a? Ilp::Term
|
12
11
|
@terms.normalize!
|
13
|
-
|
14
|
-
puts "Removing constant [#{cste}] in objective" if cste != 0
|
12
|
+
@terms.send(:pop_constant)
|
15
13
|
@objective_function = objective_function
|
16
14
|
end
|
17
15
|
|
18
16
|
def to_s
|
19
|
-
|
20
|
-
str << (@objective_function == :max ? "Maximize" : "Minimize")
|
21
|
-
str << "\n "
|
22
|
-
str << terms.to_s
|
17
|
+
"#{(@objective_function == :max ? 'Maximize' : 'Minimize')}\n #{terms}"
|
23
18
|
end
|
24
|
-
|
25
19
|
end
|
26
20
|
end
|
data/lib/ruby-cbc/ilp/term.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Ilp
|
2
2
|
class Term
|
3
|
-
|
4
3
|
attr_accessor :mult, :var
|
5
4
|
|
6
5
|
def initialize(var, mult = 1)
|
@@ -8,30 +7,29 @@ module Ilp
|
|
8
7
|
@var = var
|
9
8
|
end
|
10
9
|
|
11
|
-
def +(
|
12
|
-
Ilp::TermArray.new([self]) +
|
10
|
+
def +(other)
|
11
|
+
Ilp::TermArray.new([self]) + other
|
13
12
|
end
|
14
13
|
|
15
|
-
def -(
|
16
|
-
Ilp::TermArray.new([self]) -
|
14
|
+
def -(other)
|
15
|
+
Ilp::TermArray.new([self]) - other
|
17
16
|
end
|
18
17
|
|
19
|
-
def ==(
|
20
|
-
Ilp::TermArray.new([self]) ==
|
18
|
+
def ==(other)
|
19
|
+
Ilp::TermArray.new([self]) == other
|
21
20
|
end
|
22
21
|
|
23
|
-
def <=(
|
24
|
-
Ilp::TermArray.new([self]) <=
|
22
|
+
def <=(other)
|
23
|
+
Ilp::TermArray.new([self]) <= other
|
25
24
|
end
|
26
25
|
|
27
|
-
def >=(
|
28
|
-
Ilp::TermArray.new([self]) >=
|
26
|
+
def >=(other)
|
27
|
+
Ilp::TermArray.new([self]) >= other
|
29
28
|
end
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
Ilp::Term.new(@var, @mult * mult)
|
30
|
+
def *(other)
|
31
|
+
raise ArgumentError, "Argument is not numeric" unless other.is_a? Numeric
|
32
|
+
Ilp::Term.new(@var, @mult * other)
|
35
33
|
end
|
36
34
|
|
37
35
|
def coerce(num)
|
@@ -1,86 +1,87 @@
|
|
1
1
|
module Ilp
|
2
2
|
class TermArray
|
3
|
-
|
3
|
+
extend Forwardable
|
4
4
|
|
5
5
|
attr_accessor :terms
|
6
|
+
def_delegators :@terms, :map, :each, :size
|
6
7
|
|
7
8
|
def initialize(terms)
|
8
9
|
@terms = terms
|
9
10
|
end
|
10
11
|
|
11
|
-
def +(
|
12
|
+
def +(other)
|
12
13
|
new_terms = terms.dup
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
case other
|
15
|
+
when Numeric
|
16
|
+
new_terms << other
|
17
|
+
when Ilp::Var
|
18
|
+
new_terms << Ilp::Term.new(other)
|
19
|
+
when Ilp::Term
|
20
|
+
new_terms << other
|
21
|
+
when Ilp::TermArray
|
22
|
+
new_terms.concat(other.terms)
|
21
23
|
else
|
22
|
-
raise ArgumentError, "Argument is not allowed: #{
|
24
|
+
raise ArgumentError, "Argument is not allowed: #{other} of type #{other.class}"
|
23
25
|
end
|
24
26
|
TermArray.new(new_terms)
|
25
27
|
end
|
26
28
|
|
27
|
-
def -(
|
28
|
-
self + -1 *
|
29
|
+
def -(other)
|
30
|
+
self + -1 * other
|
29
31
|
end
|
30
32
|
|
31
|
-
def *(
|
32
|
-
raise ArgumentError, 'Argument is not numeric' unless
|
33
|
-
new_terms = terms.map { |term| term *
|
33
|
+
def *(other)
|
34
|
+
raise ArgumentError, 'Argument is not numeric' unless other.is_a? Numeric
|
35
|
+
new_terms = terms.map { |term| term * other }
|
34
36
|
TermArray.new(new_terms)
|
35
37
|
end
|
36
38
|
|
37
39
|
# cste + nb * var + nb * var...
|
38
40
|
def normalize!
|
39
|
-
constant =
|
40
|
-
hterms =
|
41
|
-
@terms
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
41
|
+
constant = 0
|
42
|
+
hterms = {}
|
43
|
+
@terms.each do |term|
|
44
|
+
case term
|
45
|
+
when Numeric
|
46
|
+
constant += term
|
47
|
+
when Ilp::Term
|
48
|
+
v = term.var
|
49
|
+
hterms[v] ||= Ilp::Term.new(v, 0)
|
50
|
+
hterms[v].mult += term.mult
|
49
51
|
end
|
50
|
-
end
|
51
|
-
|
52
|
+
end
|
53
|
+
reduced = hterms.map { |_, term| term unless term.mult.zero? }
|
54
|
+
reduced.compact!
|
55
|
+
@terms = [constant].concat reduced
|
52
56
|
self
|
53
57
|
end
|
54
58
|
|
55
|
-
def <=(
|
56
|
-
Ilp::Constraint.new(self, Ilp::Constraint::LESS_OR_EQ,
|
59
|
+
def <=(other)
|
60
|
+
Ilp::Constraint.new(self, Ilp::Constraint::LESS_OR_EQ, other)
|
57
61
|
end
|
58
62
|
|
59
|
-
def >=(
|
60
|
-
Ilp::Constraint.new(self, Ilp::Constraint::GREATER_OR_EQ,
|
63
|
+
def >=(other)
|
64
|
+
Ilp::Constraint.new(self, Ilp::Constraint::GREATER_OR_EQ, other)
|
61
65
|
end
|
62
66
|
|
63
|
-
def ==(
|
64
|
-
Ilp::Constraint.new(self, Ilp::Constraint::EQUALS,
|
67
|
+
def ==(other)
|
68
|
+
Ilp::Constraint.new(self, Ilp::Constraint::EQUALS, other)
|
65
69
|
end
|
66
70
|
|
67
71
|
def coerce(value)
|
68
72
|
[Ilp::Constant.new(value), self]
|
69
73
|
end
|
70
74
|
|
71
|
-
def each(&block)
|
72
|
-
@terms.each(&block)
|
73
|
-
end
|
74
|
-
|
75
75
|
def to_s
|
76
|
-
@terms.map(&:to_s).join(
|
76
|
+
@terms.map(&:to_s).join(" ")
|
77
77
|
end
|
78
78
|
|
79
79
|
def vars
|
80
80
|
@terms.map(&:var)
|
81
81
|
end
|
82
82
|
|
83
|
-
|
83
|
+
private
|
84
|
+
|
84
85
|
# Must be normalized!
|
85
86
|
def pop_constant
|
86
87
|
terms.slice!(0)
|
data/lib/ruby-cbc/ilp/var.rb
CHANGED
@@ -9,7 +9,7 @@ module Ilp
|
|
9
9
|
def initialize(name: nil, kind: INTEGER_KIND, lower_bound: nil, upper_bound: nil)
|
10
10
|
@kind = kind
|
11
11
|
@name = name
|
12
|
-
@name = (
|
12
|
+
@name = ("a".."z").to_a.sample(8).join if name.nil?
|
13
13
|
@lower_bound = lower_bound
|
14
14
|
@upper_bound = upper_bound
|
15
15
|
end
|
@@ -23,32 +23,32 @@ module Ilp
|
|
23
23
|
@lower_bound..@upper_bound
|
24
24
|
end
|
25
25
|
|
26
|
-
def +(
|
27
|
-
Ilp::Term.new(self) +
|
26
|
+
def +(other)
|
27
|
+
Ilp::Term.new(self) + other
|
28
28
|
end
|
29
29
|
|
30
|
-
def -(
|
31
|
-
Ilp::Term.new(self) -
|
30
|
+
def -(other)
|
31
|
+
Ilp::Term.new(self) - other
|
32
32
|
end
|
33
33
|
|
34
34
|
def -@
|
35
35
|
Ilp::Term.new(self, -1)
|
36
36
|
end
|
37
37
|
|
38
|
-
def *(
|
39
|
-
Ilp::Term.new(self) *
|
38
|
+
def *(other)
|
39
|
+
Ilp::Term.new(self) * other
|
40
40
|
end
|
41
41
|
|
42
|
-
def ==(
|
43
|
-
Ilp::Term.new(self) ==
|
42
|
+
def ==(other)
|
43
|
+
Ilp::Term.new(self) == other
|
44
44
|
end
|
45
45
|
|
46
|
-
def <=(
|
47
|
-
Ilp::Term.new(self) <=
|
46
|
+
def <=(other)
|
47
|
+
Ilp::Term.new(self) <= other
|
48
48
|
end
|
49
49
|
|
50
|
-
def >=(
|
51
|
-
Ilp::Term.new(self) >=
|
50
|
+
def >=(other)
|
51
|
+
Ilp::Term.new(self) >= other
|
52
52
|
end
|
53
53
|
|
54
54
|
def coerce(num)
|
@@ -56,7 +56,7 @@ module Ilp
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def to_s
|
59
|
-
name
|
59
|
+
name.to_s
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|