ruby-cbc 0.2.5 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -1
- data/lib/ruby-cbc/conflict_solver.rb +59 -0
- data/lib/ruby-cbc/ilp/constraint.rb +9 -1
- data/lib/ruby-cbc/ilp/term_array.rb +5 -1
- data/lib/ruby-cbc/model.rb +17 -4
- data/lib/ruby-cbc/problem.rb +10 -0
- data/lib/ruby-cbc/version.rb +1 -1
- data/lib/ruby-cbc.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: def94f63dff658ab3ab9eeeccea2035d7d7e48b0
|
4
|
+
data.tar.gz: 69d3ebaa3e51a0d84bbd36ec51de60284146850a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9dc9707ea5aea3055b058a373e3490332f86d2ce2a7f3954bafd054dbce1f81d37dec5c72d7948cd20aa21e6353f6b76935af8aafe337c83a3d80dbfea699a14
|
7
|
+
data.tar.gz: 13daf4f282e9c9863a9c23d00c296924b0e7c30c31951343a43b6538953fa64e1126c0726b985e1cb6775f84ef84156cac2fd83a5ce8a0a9d9c660a6d3cd43db
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Ruby-Cbc
|
2
2
|
|
3
|
-
This gem
|
3
|
+
This gem is using Cbc, an Integer Linear Programming Library, to provide optimization problems solving
|
4
|
+
to ruby. With Ruby-Cbc, you can model you problem, solve it and find conflicts in case of infeasibility.
|
5
|
+
|
4
6
|
It uses the version 2.9.7 of Cbc, and requires the version 2.9.7 of gem cbc-wrapper.
|
5
7
|
|
6
8
|
## Installation
|
@@ -102,6 +104,13 @@ model.enforce(x + 2 == y + 2)
|
|
102
104
|
model.enforce(0 == x - y)
|
103
105
|
```
|
104
106
|
|
107
|
+
Ruby-Cbc allows you to name your constraints. Beware that their name is not an unique id. It is only a helper
|
108
|
+
for human readability, and several constraints can share the same function name.
|
109
|
+
```ruby
|
110
|
+
model.enforce(my_function_name: x + y <= 50)
|
111
|
+
model.constraints.map(&:to_function_s) # => ["my_function_name(x, y)"]
|
112
|
+
```
|
113
|
+
|
105
114
|
Linear constraints are usually of the form
|
106
115
|
```ruby
|
107
116
|
a1 * x1 + a2 * x2 + ... + an * xn <= C
|
@@ -197,6 +206,24 @@ problem.best_bound # Will tell you the best known bound
|
|
197
206
|
problem.value_of(var) # will tell you the computed value or a variable
|
198
207
|
```
|
199
208
|
|
209
|
+
### Finding conflicts
|
210
|
+
|
211
|
+
Sometimes a problem has no feasible solution. In this case, one may wonder what is the minimum subset of conflicting
|
212
|
+
inequations. For this prupose, you can use
|
213
|
+
```ruby
|
214
|
+
problem.find_conflict # Will return an array of constraints that form an unsatifiable set
|
215
|
+
problem.find_conflict_vars # Will return all variables involved in the unsatisfiable minimum set of constraints
|
216
|
+
```
|
217
|
+
It finds a minimum subset of constraints that make the problem unsatisfiable. Note that there could be several of them,
|
218
|
+
but the solver only computes the first one it finds. Note also that it does so by solving several instances
|
219
|
+
of relaxed versions of the problem. It might take some time! It is based on QuickXplain
|
220
|
+
(http://dl.acm.org/citation.cfm?id=1597177).
|
221
|
+
|
222
|
+
One way to see the results nicely could be
|
223
|
+
```ruby
|
224
|
+
problem.find_conflict.map(&:to_function_s)
|
225
|
+
```
|
226
|
+
|
200
227
|
## Contributing
|
201
228
|
|
202
229
|
Bug reports and pull requests are welcome on GitHub at https://github.com/gverger/ruby-cbc.
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Cbc
|
2
|
+
|
3
|
+
class ConflictSolver
|
4
|
+
|
5
|
+
def initialize(model)
|
6
|
+
@model = model
|
7
|
+
end
|
8
|
+
|
9
|
+
# Assuming there is a conflict
|
10
|
+
def find_conflict
|
11
|
+
conflict_set = []
|
12
|
+
all_constraints = @model.constraints.to_a
|
13
|
+
loop do
|
14
|
+
m = Model.new
|
15
|
+
m.vars = @model.vars
|
16
|
+
m.enforce(conflict_set)
|
17
|
+
return conflict_set if infeasible?(m)
|
18
|
+
|
19
|
+
constraint = first_failing(conflict_set, all_constraints)
|
20
|
+
return conflict_set if !constraint
|
21
|
+
|
22
|
+
conflict_set << constraint
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
# finds the first constraint from constraints that makes the problem infeasible
|
28
|
+
def first_failing(conflict_set, constraints)
|
29
|
+
min_nb_constraints = 1
|
30
|
+
max_nb_constraints = constraints.count + 1
|
31
|
+
|
32
|
+
loop do
|
33
|
+
m = Model.new
|
34
|
+
m.vars = @model.vars
|
35
|
+
m.enforce(conflict_set)
|
36
|
+
|
37
|
+
nb_constraints = (max_nb_constraints + min_nb_constraints) / 2
|
38
|
+
m.enforce(constraints.take(nb_constraints))
|
39
|
+
if infeasible?(m)
|
40
|
+
max_nb_constraints = nb_constraints
|
41
|
+
else
|
42
|
+
min_nb_constraints = nb_constraints
|
43
|
+
end
|
44
|
+
if max_nb_constraints - min_nb_constraints <= 1
|
45
|
+
return nil if max_nb_constraints > constraints.count
|
46
|
+
return constraints[max_nb_constraints - 1]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
# Shouldn't come here if the whole problem is infeasible
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def infeasible?(model)
|
54
|
+
problem = model.to_problem
|
55
|
+
problem.solve
|
56
|
+
problem.proven_infeasible?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -5,7 +5,7 @@ module Ilp
|
|
5
5
|
GREATER_OR_EQ = :greater_or_eq
|
6
6
|
EQUALS = :equals
|
7
7
|
|
8
|
-
attr_accessor :terms, :type, :bound
|
8
|
+
attr_accessor :terms, :type, :bound, :function_name
|
9
9
|
|
10
10
|
def initialize(terms, type, bound)
|
11
11
|
@terms = terms - bound
|
@@ -14,6 +14,14 @@ module Ilp
|
|
14
14
|
@type = type
|
15
15
|
end
|
16
16
|
|
17
|
+
def vars
|
18
|
+
terms.vars.uniq
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_function_s
|
22
|
+
"#{function_name || 'constraint'}(#{vars.join(', ')})"
|
23
|
+
end
|
24
|
+
|
17
25
|
def to_s
|
18
26
|
case @type
|
19
27
|
when LESS_OR_EQ
|
@@ -41,7 +41,7 @@ module Ilp
|
|
41
41
|
@terms = []
|
42
42
|
constant ||= 0
|
43
43
|
@terms << constant
|
44
|
-
hterms.each do |v, ts|
|
44
|
+
hterms.each do |v, ts|
|
45
45
|
t = ts.inject(Ilp::Term.new(v, 0)) { |v1, v2| v1.mult += v2.mult; v1 }
|
46
46
|
terms << t if t.mult != 0
|
47
47
|
end
|
@@ -72,6 +72,10 @@ module Ilp
|
|
72
72
|
@terms.map(&:to_s).join(' ')
|
73
73
|
end
|
74
74
|
|
75
|
+
def vars
|
76
|
+
@terms.map(&:var)
|
77
|
+
end
|
78
|
+
|
75
79
|
private
|
76
80
|
# Must be normalized!
|
77
81
|
def pop_constant
|
data/lib/ruby-cbc/model.rb
CHANGED
@@ -40,8 +40,21 @@ module Cbc
|
|
40
40
|
array_var(length, Ilp::Var::CONTINUOUS_KIND, range, names)
|
41
41
|
end
|
42
42
|
|
43
|
-
def enforce(
|
44
|
-
constraints
|
43
|
+
def enforce(*constraints)
|
44
|
+
constraints.each do |constraint|
|
45
|
+
if constraint.instance_of? Ilp::Constraint
|
46
|
+
self.constraints << constraint
|
47
|
+
elsif constraint.instance_of? Array
|
48
|
+
self.constraints += constraint
|
49
|
+
elsif constraint.instance_of? Hash
|
50
|
+
constraint.each do |name, c|
|
51
|
+
self.constraints << c
|
52
|
+
c.function_name = name.to_s
|
53
|
+
end
|
54
|
+
else
|
55
|
+
puts "Not a constraint: #{constraint}"
|
56
|
+
end
|
57
|
+
end
|
45
58
|
end
|
46
59
|
|
47
60
|
def minimize(expression)
|
@@ -109,13 +122,13 @@ module Cbc
|
|
109
122
|
v
|
110
123
|
end
|
111
124
|
|
112
|
-
def lb_to_s(lb)
|
125
|
+
def lb_to_s(lb)
|
113
126
|
return "-inf" if ! lb || lb == -Cbc::INF
|
114
127
|
return "+inf" if lb == Cbc::INF
|
115
128
|
return "#{lb}"
|
116
129
|
end
|
117
130
|
|
118
|
-
def ub_to_s(ub)
|
131
|
+
def ub_to_s(ub)
|
119
132
|
return "+inf" if ! ub || ub == Cbc::INF
|
120
133
|
return "-inf" if ub == -Cbc::INF
|
121
134
|
return "#{ub}"
|
data/lib/ruby-cbc/problem.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
module Cbc
|
2
2
|
class Problem
|
3
3
|
|
4
|
+
attr_reader :model
|
4
5
|
|
5
6
|
def initialize(model)
|
6
7
|
|
7
8
|
@int_arrays = []
|
8
9
|
@double_arrays = []
|
9
10
|
|
11
|
+
@model = model
|
10
12
|
@variables = {}
|
11
13
|
vars = model.vars
|
12
14
|
vars_data = {}
|
@@ -137,6 +139,14 @@ module Cbc
|
|
137
139
|
Cbc_wrapper.Cbc_getBestPossibleObjValue(@cbc_model)
|
138
140
|
end
|
139
141
|
|
142
|
+
def find_conflict
|
143
|
+
@conflict_set ||= ConflictSolver.new(model).find_conflict
|
144
|
+
end
|
145
|
+
|
146
|
+
def find_conflict_vars
|
147
|
+
@conflict_vars ||= find_conflict.map(&:vars).flatten.uniq
|
148
|
+
end
|
149
|
+
|
140
150
|
def self.finalizer(cbc_model, int_arrays, double_arrays)
|
141
151
|
proc do
|
142
152
|
Cbc_wrapper.Cbc_deleteModel(cbc_model)
|
data/lib/ruby-cbc/version.rb
CHANGED
data/lib/ruby-cbc.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-cbc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guillaume Verger
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -125,6 +125,7 @@ files:
|
|
125
125
|
- bin/console
|
126
126
|
- bin/setup
|
127
127
|
- lib/ruby-cbc.rb
|
128
|
+
- lib/ruby-cbc/conflict_solver.rb
|
128
129
|
- lib/ruby-cbc/ilp/constant.rb
|
129
130
|
- lib/ruby-cbc/ilp/constraint.rb
|
130
131
|
- lib/ruby-cbc/ilp/objective.rb
|