ruby-cbc 0.3.10 → 0.3.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/gverger/ruby-cbc.svg?branch=master)](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
|