ruby-cbc 0.2.5 → 0.3.0
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/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
|