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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d14f860956338d307ca81c97bdbffa02c18fa1fe
4
- data.tar.gz: 8a6bccaf1dbfddb49b480763de807c9b9f5b4cc7
3
+ metadata.gz: def94f63dff658ab3ab9eeeccea2035d7d7e48b0
4
+ data.tar.gz: 69d3ebaa3e51a0d84bbd36ec51de60284146850a
5
5
  SHA512:
6
- metadata.gz: 2c9262c9b81f0e9a56f9dad3aca561815e50fd664f5f4a94f55cc3f27a7fb975ca4e6857a3ae94498e1a02babe640c2bb56ec24f76825eb33af1a6ed833490bd
7
- data.tar.gz: d6a23b5ba7347fbe5168a5a823f32e9a267e4a7e10dfaa596dbbb0fecb5802280c096a36a39b55d6bf506d19b81bd9e4dd7768a3075fe499bea8ca7589c5209e
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 wraps the Coin-Or Cbc Mixed Integer Linear Programming Library.
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
@@ -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(constraint)
44
- constraints << constraint
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}"
@@ -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)
@@ -1,3 +1,3 @@
1
1
  module Cbc
2
- VERSION = "0.2.5"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/ruby-cbc.rb CHANGED
@@ -5,6 +5,7 @@ module Cbc
5
5
  end
6
6
 
7
7
  files = %w(
8
+ conflict_solver
8
9
  model
9
10
  problem
10
11
  version
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.2.5
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-19 00:00:00.000000000 Z
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