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 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