ruby-cbc 0.3.18 → 0.3.19

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -1,4 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.2.3
4
- before_install: gem install bundler -v 1.10.6
4
+ before_install:
5
+ - sudo apt-get -y install coinor-libcbc-dev
6
+ - gem install bundler -v 1.10.6
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in cbc.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  [![Build Status](https://travis-ci.org/gverger/ruby-cbc.svg?branch=master)](https://travis-ci.org/gverger/ruby-cbc)
2
+
2
3
  # Ruby-Cbc
3
4
 
4
5
  This gem is using Cbc, an Integer Linear Programming Library, to provide optimization problems solving
@@ -8,6 +9,13 @@ It uses the version 2.9.9 of Cbc, and requires the version 2.9.9 of gem cbc-wrap
8
9
 
9
10
  ## Installation
10
11
 
12
+ You need to have `cbc` installed for `ruby-cbc` to work.
13
+
14
+ - On a mac, you can execute `brew install cbc`
15
+ - On Debian and Ubuntu, use `apt-get install coinor-libcbc-dev`
16
+ - On Archlinux, use `pacman -S coin-or-cbc`
17
+ - On docker you can check the dockerfiles in test/installation-tests/
18
+
11
19
  Add this line to your application's Gemfile:
12
20
 
13
21
  ```ruby
@@ -22,16 +30,25 @@ Or install it yourself as:
22
30
 
23
31
  $ gem install ruby-cbc
24
32
 
25
- The gem includes a version of the Coin-Or Cbc library. If the system on which
26
- it is installed is not Linux 64bits, it downloads the library sources and
27
- recompiles them at installation.
33
+ ### Heroku
34
+
35
+ On Heroku, you will need to tweak your installation a bit: you can install the cbc library with
36
+ the [Apt](https://github.com/heroku/heroku-buildpack-apt) buildpack with an Aptfile of:
37
+
38
+ ```
39
+ coinor-libcbc-dev
40
+ ```
41
+
42
+ You will need to set LD_LIBRARY_PATH so it can find LAPACK and BLAS (see this [issue](https://github.com/heroku/heroku-buildpack-apt/issues/35)).
28
43
 
29
- It also works on Heroku.
44
+ ```
45
+ heroku config:set LD_LIBRARY_PATH=/app/.apt/usr/lib/x86_64-linux-gnu/lapack:/app/.apt/usr/lib/x86_64-linux-gnu/blas
46
+ ```
30
47
 
31
48
  ## Usage
32
49
 
33
50
  ```ruby
34
- # The same Brief Example as found in section 1.3 of
51
+ # The same Brief Example as found in section 1.3 of
35
52
  # glpk-4.44/doc/glpk.pdf.
36
53
  #
37
54
  # maximize
@@ -52,39 +69,45 @@ m.maximize(10 * x1 + 6 * x2 + 4 * x3)
52
69
 
53
70
  m.enforce(x1 + x2 + x3 <= 100)
54
71
  m.enforce(10 * x1 + 4 * x2 + 5 * x3 <= 600)
55
- m.enforce(2 * x1 + 2 * x2 + 6* x3 <= 300)
72
+ m.enforce(2 * x1 + 2 * x2 + 6 * x3 <= 300)
56
73
 
57
74
  p = m.to_problem
58
75
 
59
76
  p.solve
60
77
 
61
- if ! p.proven_infeasible?
78
+ unless p.proven_infeasible?
62
79
  puts "x1 = #{p.value_of(x1)}"
63
80
  puts "x2 = #{p.value_of(x2)}"
64
81
  puts "x3 = #{p.value_of(x3)}"
65
82
  end
66
83
  ```
84
+
67
85
  ### Modelling
68
86
 
69
87
  Let's have a model :
88
+
70
89
  ```ruby
71
90
  model = Cbc::Model.new
72
91
  ```
92
+
73
93
  #### The variables
74
94
 
75
95
  3 variable kinds are available:
96
+
76
97
  ```ruby
77
98
  x = model.bin_var(name: "x") # a binary variable (i.e. can take values 0 and 1)
78
99
  y = model.int_var(L..U, name: "y") # a integer variable, L <= y <= U
79
100
  z = model.cont_var(L..U, name: "z") # a continuous variable, L <= z <= U
80
101
  ```
102
+
81
103
  Name is optional and used only when displaying the model.
82
104
 
83
105
  If you don't specify the range, the variables are free.
84
- You can also use ```Cbc::INF``` as the infinity bound.
106
+ You can also use `Cbc::INF` as the infinity bound.
85
107
 
86
108
  Each one of these 3 kinds have also an array method that generate several variables.
87
109
  For instance to generate 3 positive integer variables named x, y and z :
110
+
88
111
  ```ruby
89
112
  x, y, z = model.int_var_array(3, 0..Cbc::INF, names: ["x", "y", "z"])
90
113
  ```
@@ -92,12 +115,15 @@ x, y, z = model.int_var_array(3, 0..Cbc::INF, names: ["x", "y", "z"])
92
115
  #### The constraints
93
116
 
94
117
  You can enforce constraints:
118
+
95
119
  ```ruby
96
120
  model.enforce(x + y - z <= 10)
97
121
  ```
122
+
98
123
  You are not restricted to usual linear programming rules when writing a constraint.
99
- Usually you would have to write ```x - y = 0``` to express ```x = y```.
124
+ Usually you would have to write `x - y = 0` to express `x = y`.
100
125
  Ruby-Cbc allows you to put variables and constants on both sides of the comparison operator. You can write
126
+
101
127
  ```ruby
102
128
  model.enforce(x - y == 0)
103
129
  model.enforce(x == y)
@@ -107,12 +133,14 @@ model.enforce(0 == x - y)
107
133
 
108
134
  Ruby-Cbc allows you to name your constraints. Beware that their name is not an unique id. It is only a helper
109
135
  for human readability, and several constraints can share the same function name.
136
+
110
137
  ```ruby
111
138
  model.enforce(my_function_name: x + y <= 50)
112
139
  model.constraints.map(&:to_function_s) # => ["my_function_name(x, y)"]
113
140
  ```
114
141
 
115
142
  Linear constraints are usually of the form
143
+
116
144
  ```ruby
117
145
  a1 * x1 + a2 * x2 + ... + an * xn <= C
118
146
  a1 * x1 + a2 * x2 + ... + an * xn >= C
@@ -120,14 +148,17 @@ a1 * x1 + a2 * x2 + ... + an * xn == C
120
148
  ```
121
149
 
122
150
  With Ruby-Cbc you can write
151
+
123
152
  ```ruby
124
153
  2 * (2 + 5 * x) + 4 * 5 + 1 == 1 + 4 * 5 * y
125
154
  ```
155
+
126
156
  The (in)equation must still be a **linear** (in)equation, you cannot multiply two variables !
127
157
 
128
158
  #### Objective
129
159
 
130
160
  You can set the objective:
161
+
131
162
  ```ruby
132
163
  model.maximize(3 * x + 2 * y)
133
164
  model.minimize(3 * x + 2 * y)
@@ -136,12 +167,15 @@ model.minimize(3 * x + 2 * y)
136
167
  #### Displaying the model
137
168
 
138
169
  the `Model` instances have a `to_s` method. You can then run
170
+
139
171
  ```ruby
140
172
  puts model
141
173
  ```
174
+
142
175
  The model will be printed in LP format.
143
176
 
144
177
  For instance:
178
+
145
179
  ```
146
180
  Maximize
147
181
  + 10 x1 + 6 x2 + 4 x3
@@ -167,25 +201,30 @@ End
167
201
  ### Solving
168
202
 
169
203
  To solve the model, you need to first transform it to a problem.
204
+
170
205
  ```ruby
171
206
  problem = model.to_problem
172
207
  ```
173
208
 
174
209
  You can define a time limit to the resolution
210
+
175
211
  ```ruby
176
212
  problem.set_time_limit(nb_seconds)
177
213
  ```
178
214
 
179
215
  You can solve the Linear Problem
216
+
180
217
  ```ruby
181
218
  problem.solve
182
219
  ```
183
220
 
184
221
  You can specify arguments that match the cbc command line
222
+
185
223
  ```ruby
186
224
  problem.solve(sec: 60) # equivalent to $ cbc -sec 60
187
225
  problem.solve(log: 1) # equivalent to $ cbc -log 1
188
226
  ```
227
+
189
228
  For more examples of available options, if `coinor-cbc` is installed run
190
229
 
191
230
  $ cbc
@@ -193,6 +232,7 @@ For more examples of available options, if `coinor-cbc` is installed run
193
232
  then type `?`
194
233
 
195
234
  Once `problem.solve` has finished you can query the status:
235
+
196
236
  ```ruby
197
237
  problem.proven_infeasible? # will tell you if the problem has no solution
198
238
  problem.proven_optimal? # will tell you if the problem is solved optimally
@@ -200,6 +240,7 @@ problem.time_limit_reached? # Will tell you if the solve timed out
200
240
  ```
201
241
 
202
242
  To have the different values, do
243
+
203
244
  ```ruby
204
245
  problem.objective_value # Will tell you the value of the best objective
205
246
  problem.best_bound # Will tell you the best known bound
@@ -211,16 +252,19 @@ problem.value_of(var) # will tell you the computed value or a variable
211
252
 
212
253
  Sometimes a problem has no feasible solution. In this case, one may wonder what is the minimum subset of conflicting
213
254
  inequations. For this prupose, you can use
255
+
214
256
  ```ruby
215
257
  problem.find_conflict # Will return an array of constraints that form an unsatifiable set
216
258
  problem.find_conflict_vars # Will return all variables involved in the unsatisfiable minimum set of constraints
217
259
  ```
260
+
218
261
  It finds a minimum subset of constraints that make the problem unsatisfiable. Note that there could be several of them,
219
262
  but the solver only computes the first one it finds. Note also that it does so by solving several instances
220
263
  of relaxed versions of the problem. It might take some time! It is based on QuickXplain
221
264
  (http://dl.acm.org/citation.cfm?id=1597177).
222
265
 
223
266
  One way to see the results nicely could be
267
+
224
268
  ```ruby
225
269
  problem.find_conflict.map(&:to_function_s)
226
270
  ```
@@ -228,4 +272,3 @@ problem.find_conflict.map(&:to_function_s)
228
272
  ## Contributing
229
273
 
230
274
  Bug reports and pull requests are welcome on GitHub at https://github.com/gverger/ruby-cbc.
231
-
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
- require 'rake/extensiontask'
3
+ require "rake/extensiontask"
4
4
 
5
5
  def in_dir(path)
6
6
  original_dir = Dir.pwd
@@ -12,14 +12,14 @@ end
12
12
 
13
13
  RSpec::Core::RakeTask.new(:spec)
14
14
 
15
- spec = Gem::Specification.load('ruby-cbc.gemspec')
16
- Rake::ExtensionTask.new('ruby-cbc', spec) do |ext|
17
- ext.lib_dir = 'lib/ruby-cbc'
15
+ spec = Gem::Specification.load("ruby-cbc.gemspec")
16
+ Rake::ExtensionTask.new("ruby-cbc", spec) do |ext|
17
+ ext.lib_dir = "lib/ruby-cbc"
18
18
  ext.tmp_dir = "tmp"
19
- ext.name = 'cbc_wrapper'
19
+ ext.name = "cbc_wrapper"
20
20
  end
21
21
 
22
- task :default => [:spec]
22
+ task default: [:spec]
23
23
 
24
24
  task :benchmark do
25
25
  ruby "test/benchmark.rb"
@@ -19,7 +19,8 @@ module Cbc
19
19
  max_iter = 1
20
20
  conflict_set_size = 0
21
21
  loop do
22
- range_idxs = first_failing(conflict_set_size, crs, continuous: continuous, max_iterations: max_iter)
22
+ range_idxs =
23
+ first_failing(conflict_set_size, crs, continuous: continuous, max_iterations: max_iter)
23
24
  break if range_idxs.nil?
24
25
  crs = crs.restrict_to_n_constraints(range_idxs.max + 1)
25
26
  crs.move_constraint_to_start(range_idxs)
@@ -43,7 +44,9 @@ module Cbc
43
44
  nb_clusters_one_constraint += 1
44
45
  end
45
46
  if nb_clusters_one_constraint > 0
46
- crs.move_constraint_to_start((crs.nb_constraints - nb_clusters_one_constraint)..(crs.nb_constraints - 1))
47
+ crs.move_constraint_to_start(
48
+ (crs.nb_constraints - nb_clusters_one_constraint)..(crs.nb_constraints - 1)
49
+ )
47
50
  clusters[nb_clusters_one_constraint, clusters.size - nb_clusters_one_constraint] =
48
51
  clusters[0, clusters.size - nb_clusters_one_constraint]
49
52
  clusters[0, nb_clusters_one_constraint] = Array.new(nb_clusters_one_constraint, 1)
@@ -83,14 +86,12 @@ module Cbc
83
86
  min_idx = half_constraint_idx + 1
84
87
  # puts " FEAS"
85
88
  end
86
- if max_idx == min_idx
87
- # puts "found: max = #{max_idx} min = #{min_idx} nb = #{crs.nb_constraints}"
88
- return nil if max_idx > crs.nb_constraints
89
- return min_idx..max_idx
90
- end
89
+ next if max_idx != min_idx
90
+ return nil if max_idx > crs.nb_constraints
91
+ return min_idx..max_idx
91
92
  end
92
93
  # Shouldn't come here if the whole problem is infeasible
93
- return nil
94
+ nil
94
95
  end
95
96
 
96
97
  def infeasible?(problem)
@@ -25,7 +25,7 @@ module Ilp
25
25
  LESS_OR_EQ => "<=",
26
26
  GREATER_OR_EQ => ">=",
27
27
  EQUALS => "="
28
- }
28
+ }.freeze
29
29
 
30
30
  def to_s
31
31
  sign = SIGN_TO_STRING[@type] || "??"
@@ -1,4 +1,4 @@
1
- require 'forwardable'
1
+ require "forwardable"
2
2
 
3
3
  module Ilp
4
4
  class TermArray
@@ -33,7 +33,7 @@ module Ilp
33
33
  end
34
34
 
35
35
  def *(other)
36
- raise ArgumentError, 'Argument is not numeric' unless other.is_a? Numeric
36
+ raise ArgumentError, "Argument is not numeric" unless other.is_a? Numeric
37
37
  new_terms = terms.map { |term| term * other }
38
38
  TermArray.new(new_terms)
39
39
  end
@@ -91,7 +91,7 @@ module Cbc
91
91
  constraints.each do |cons|
92
92
  str << " #{cons}\n"
93
93
  end
94
- bounded_vars = vars.select { |v| v.kind != Ilp::Var::BINARY_KIND }
94
+ bounded_vars = vars.reject { |v| v.kind == Ilp::Var::BINARY_KIND }
95
95
  unless bounded_vars.empty?
96
96
  str << "\nBounds\n"
97
97
  bounded_vars.each do |v|
@@ -133,16 +133,16 @@ module Cbc
133
133
  v
134
134
  end
135
135
 
136
- def lb_to_s(lb)
137
- return "-inf" if lb.nil? || lb == -Cbc::INF
138
- return "+inf" if lb == Cbc::INF
139
- lb.to_s
136
+ def lb_to_s(lower_bound)
137
+ return "-inf" if lower_bound.nil? || lower_bound == -Cbc::INF
138
+ return "+inf" if lower_bound == Cbc::INF
139
+ lower_bound.to_s
140
140
  end
141
141
 
142
- def ub_to_s(ub)
143
- return "+inf" if ub.nil? || ub == Cbc::INF
144
- return "-inf" if ub == -Cbc::INF
145
- ub.to_s
142
+ def ub_to_s(upper_bound)
143
+ return "+inf" if upper_bound.nil? || upper_bound == Cbc::INF
144
+ return "-inf" if upper_bound == -Cbc::INF
145
+ upper_bound.to_s
146
146
  end
147
147
  end
148
148
  end
@@ -56,9 +56,6 @@ module Cbc
56
56
  end
57
57
 
58
58
  def create_cbc_problem(continuous: false)
59
- @int_arrays = []
60
- @double_arrays = []
61
-
62
59
  ccs = self.class.crs_to_ccs(@crs)
63
60
  objective = Array.new(ccs.nb_vars, 0)
64
61
  if model.objective
@@ -69,10 +66,18 @@ module Cbc
69
66
 
70
67
  @cbc_model = Cbc_wrapper.Cbc_newModel
71
68
  Cbc_wrapper.Cbc_loadProblem(
72
- @cbc_model, ccs.nb_vars, @crs.nb_constraints,
73
- to_int_array(ccs.col_ptr), to_int_array(ccs.row_idx),
74
- to_double_array(ccs.values), nil, nil, to_double_array(objective),
75
- nil, nil)
69
+ @cbc_model,
70
+ ccs.nb_vars,
71
+ @crs.nb_constraints,
72
+ to_int_array(ccs.col_ptr),
73
+ to_int_array(ccs.row_idx),
74
+ to_double_array(ccs.values),
75
+ nil,
76
+ nil,
77
+ to_double_array(objective),
78
+ nil,
79
+ nil
80
+ )
76
81
 
77
82
  # Segmentation errors when setting name
78
83
  # Cbc_wrapper.Cbc_setProblemName(@cbc_model, model.name) if model.name
@@ -106,22 +111,20 @@ module Cbc
106
111
  idx += 1
107
112
  end
108
113
 
109
- ObjectSpace.define_finalizer(self, self.class.finalizer(@cbc_model, @int_arrays, @double_arrays))
114
+ ObjectSpace.define_finalizer(self, self.class.finalizer(@cbc_model))
110
115
 
111
- @default_solve_params = {
112
- log: 0
113
- }
116
+ @default_solve_params = { log: 0 }
114
117
  end
115
118
 
116
- def set_constraint_bounds(c, idx)
117
- case c.type
119
+ def set_constraint_bounds(constraint, idx)
120
+ case constraint.type
118
121
  when Ilp::Constraint::LESS_OR_EQ
119
- Cbc_wrapper.Cbc_setRowUpper(@cbc_model, idx, c.bound)
122
+ Cbc_wrapper.Cbc_setRowUpper(@cbc_model, idx, constraint.bound)
120
123
  when Ilp::Constraint::GREATER_OR_EQ
121
- Cbc_wrapper.Cbc_setRowLower(@cbc_model, idx, c.bound)
124
+ Cbc_wrapper.Cbc_setRowLower(@cbc_model, idx, constraint.bound)
122
125
  when Ilp::Constraint::EQUALS
123
- Cbc_wrapper.Cbc_setRowUpper(@cbc_model, idx, c.bound)
124
- Cbc_wrapper.Cbc_setRowLower(@cbc_model, idx, c.bound)
126
+ Cbc_wrapper.Cbc_setRowUpper(@cbc_model, idx, constraint.bound)
127
+ Cbc_wrapper.Cbc_setRowLower(@cbc_model, idx, constraint.bound)
125
128
  end
126
129
  end
127
130
 
@@ -131,8 +134,6 @@ module Cbc
131
134
  end
132
135
  Cbc_wrapper.Cbc_solve(@cbc_model)
133
136
  @solution = Cbc_wrapper::DoubleArray.frompointer(Cbc_wrapper.Cbc_getColSolution(@cbc_model))
134
- @double_arrays << @solution
135
- @solution
136
137
  end
137
138
 
138
139
  def value_of(var)
@@ -145,9 +146,12 @@ module Cbc
145
146
  end
146
147
  end
147
148
 
149
+ # Keep this one for back compatibility
150
+ # rubocop:disable Naming/AccessorMethodName
148
151
  def set_time_limit(seconds)
149
152
  @default_solve_params[:sec] = seconds
150
153
  end
154
+ # rubocop:enable Naming/AccessorMethodName
151
155
 
152
156
  def proven_optimal?
153
157
  Cbc_wrapper.Cbc_isProvenOptimal(@cbc_model) == 1
@@ -175,18 +179,16 @@ module Cbc
175
179
  end
176
180
 
177
181
  def find_conflict
178
- @conflict_set ||= ConflictSolver.new(self).find_conflict
182
+ @find_conflict ||= ConflictSolver.new(self).find_conflict
179
183
  end
180
184
 
181
185
  def find_conflict_vars
182
- @conflict_vars ||= find_conflict.map(&:vars).flatten.uniq
186
+ @find_conflict_vars ||= find_conflict.map(&:vars).flatten.uniq
183
187
  end
184
188
 
185
- def self.finalizer(cbc_model, int_arrays, double_arrays)
189
+ def self.finalizer(cbc_model)
186
190
  proc do
187
191
  Cbc_wrapper.Cbc_deleteModel(cbc_model)
188
- int_arrays.each { |ar| Cbc_wrapper.delete_intArray(ar) }
189
- double_arrays.each { |ar| Cbc_wrapper.delete_doubleArray(ar) }
190
192
  end
191
193
  end
192
194
 
@@ -203,7 +205,6 @@ module Cbc
203
205
  c_array[idx] = array[idx]
204
206
  idx += 1
205
207
  end
206
- @int_arrays << c_array
207
208
  c_array
208
209
  end
209
210
 
@@ -214,7 +215,6 @@ module Cbc
214
215
  c_array[idx] = array[idx]
215
216
  idx += 1
216
217
  end
217
- @double_arrays << c_array
218
218
  c_array
219
219
  end
220
220
  end
@@ -1,3 +1,3 @@
1
1
  module Cbc
2
- VERSION = "0.3.18"
2
+ VERSION = "0.3.19".freeze
3
3
  end
data/lib/ruby-cbc.rb CHANGED
@@ -4,7 +4,7 @@ require "cbc-wrapper"
4
4
  module Cbc
5
5
  end
6
6
 
7
- files = %w(
7
+ files = %w[
8
8
  conflict_solver
9
9
  model
10
10
  problem
@@ -16,7 +16,7 @@ files = %w(
16
16
  ilp/term_array
17
17
  ilp/var
18
18
  utils/compressed_row_storage
19
- )
19
+ ]
20
20
 
21
21
  files.each do |file|
22
22
  require File.expand_path("../ruby-cbc/#{file}", __FILE__)
data/ruby-cbc.gemspec CHANGED
@@ -1,7 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("lib", __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'ruby-cbc/version'
3
+ require "ruby-cbc/version"
5
4
 
6
5
  Gem::Specification.new do |spec|
7
6
  spec.name = "ruby-cbc"
@@ -19,14 +18,16 @@ Gem::Specification.new do |spec|
19
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
19
  spec.require_paths = ["lib"]
21
20
 
22
- spec.add_development_dependency "bundler", "~> 1.10"
23
- spec.add_development_dependency "rake", "~> 10.0"
24
- spec.add_development_dependency "rspec"
25
- spec.add_development_dependency "rake-compiler"
21
+ spec.add_development_dependency "benchmark-ips"
22
+ spec.add_development_dependency "bundler", "~> 2.3"
26
23
  spec.add_development_dependency "pry"
27
24
  spec.add_development_dependency "pry-byebug"
28
- spec.add_development_dependency "benchmark-ips"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rake-compiler"
27
+ spec.add_development_dependency "rspec"
28
+ spec.add_development_dependency "rubocop"
29
+ spec.add_development_dependency "rubocop-rspec"
29
30
  spec.add_development_dependency "ruby-prof"
30
31
 
31
- spec.add_dependency "cbc-wrapper", '~> 2.9.9.2'
32
+ spec.add_dependency "cbc-wrapper", "~> 2.9.9.2"
32
33
  end