ruby-cbc 0.3.10 → 0.3.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/.ruby-style.yml +1354 -0
- data/README.md +2 -1
- data/lib/ruby-cbc.rb +12 -12
- data/lib/ruby-cbc/conflict_solver.rb +4 -5
- data/lib/ruby-cbc/ilp/constant.rb +23 -18
- data/lib/ruby-cbc/ilp/constraint.rb +10 -15
- data/lib/ruby-cbc/ilp/objective.rb +2 -8
- data/lib/ruby-cbc/ilp/term.rb +13 -15
- data/lib/ruby-cbc/ilp/term_array.rb +41 -40
- data/lib/ruby-cbc/ilp/var.rb +14 -14
- data/lib/ruby-cbc/model.rb +34 -36
- data/lib/ruby-cbc/problem.rb +8 -12
- data/lib/ruby-cbc/utils/compressed_row_storage.rb +97 -95
- data/lib/ruby-cbc/version.rb +1 -1
- data/ruby-cbc.gemspec +1 -1
- metadata +7 -4
data/lib/ruby-cbc/model.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module Cbc
|
2
|
-
|
3
2
|
INF = 1.0 / 0.0 # Useful for ranges
|
4
3
|
|
5
4
|
def self.add_all(variables)
|
@@ -10,15 +9,13 @@ module Cbc
|
|
10
9
|
when Ilp::Var
|
11
10
|
Ilp::Term.new(variable)
|
12
11
|
else
|
13
|
-
raise
|
12
|
+
raise "Not a variable, a term or a numeric"
|
14
13
|
end
|
15
14
|
end
|
16
15
|
Ilp::TermArray.new(to_add)
|
17
16
|
end
|
18
17
|
|
19
18
|
class Model
|
20
|
-
|
21
|
-
|
22
19
|
attr_accessor :vars, :constraints, :objective, :name
|
23
20
|
|
24
21
|
def initialize(name: "ILP Problem")
|
@@ -48,7 +45,7 @@ module Cbc
|
|
48
45
|
var(Ilp::Var::CONTINUOUS_KIND, range, name)
|
49
46
|
end
|
50
47
|
|
51
|
-
def cont_var_array(length, range = nil,
|
48
|
+
def cont_var_array(length, range = nil, names: nil)
|
52
49
|
array_var(length, Ilp::Var::CONTINUOUS_KIND, range, names)
|
53
50
|
end
|
54
51
|
|
@@ -57,12 +54,12 @@ module Cbc
|
|
57
54
|
if constraint.instance_of? Ilp::Constraint
|
58
55
|
self.constraints << constraint
|
59
56
|
elsif constraint.instance_of? Array
|
60
|
-
self.constraints
|
57
|
+
self.constraints.concat constraint
|
61
58
|
elsif constraint.instance_of? Hash
|
62
|
-
constraint.
|
63
|
-
|
64
|
-
c.function_name = name.to_s
|
59
|
+
to_add = constraint.map do |name, cons|
|
60
|
+
cons.tap { |c| c.function_name = name.to_s }
|
65
61
|
end
|
62
|
+
self.constraints.concat to_add
|
66
63
|
else
|
67
64
|
puts "Not a constraint: #{constraint}"
|
68
65
|
end
|
@@ -84,30 +81,32 @@ module Cbc
|
|
84
81
|
end
|
85
82
|
|
86
83
|
def to_s
|
87
|
-
str =
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
84
|
+
str = if objective
|
85
|
+
"#{objective}\n"
|
86
|
+
else
|
87
|
+
"Maximize\n 0 #{vars.first}\n"
|
88
|
+
end
|
89
|
+
|
93
90
|
str << "\nSubject To\n"
|
94
91
|
constraints.each do |cons|
|
95
|
-
str << "
|
92
|
+
str << " #{cons}\n"
|
96
93
|
end
|
97
|
-
bounded_vars = vars.select{ |v| v.kind != Ilp::Var::BINARY_KIND }
|
98
|
-
|
94
|
+
bounded_vars = vars.select { |v| v.kind != Ilp::Var::BINARY_KIND }
|
95
|
+
unless bounded_vars.empty?
|
99
96
|
str << "\nBounds\n"
|
100
|
-
bounded_vars.each
|
97
|
+
bounded_vars.each do |v|
|
98
|
+
str << " #{lb_to_s(v.lower_bound)} <= #{v} <= #{ub_to_s(v.upper_bound)}\n"
|
99
|
+
end
|
101
100
|
end
|
102
101
|
|
103
|
-
int_vars = vars.select{ |v| v.kind == Ilp::Var::INTEGER_KIND }
|
104
|
-
|
102
|
+
int_vars = vars.select { |v| v.kind == Ilp::Var::INTEGER_KIND }
|
103
|
+
unless int_vars.empty?
|
105
104
|
str << "\nGenerals\n"
|
106
105
|
int_vars.each { |v| str << " #{v}\n" }
|
107
106
|
end
|
108
107
|
|
109
|
-
bin_vars = vars.select{ |v| v.kind == Ilp::Var::BINARY_KIND }
|
110
|
-
|
108
|
+
bin_vars = vars.select { |v| v.kind == Ilp::Var::BINARY_KIND }
|
109
|
+
unless bin_vars.empty?
|
111
110
|
str << "\nBinaries\n"
|
112
111
|
bin_vars.each { |v| str << " #{v}\n" }
|
113
112
|
end
|
@@ -116,35 +115,34 @@ module Cbc
|
|
116
115
|
str
|
117
116
|
end
|
118
117
|
|
119
|
-
|
118
|
+
private
|
119
|
+
|
120
120
|
def array_var(length, kind, range, names)
|
121
121
|
ar = Array.new(length) { var(kind, range, nil) }
|
122
|
-
ar.zip(names).
|
122
|
+
ar.zip(names).each { |var, name| var.name = name } unless names.nil?
|
123
123
|
ar
|
124
124
|
end
|
125
125
|
|
126
126
|
def var(kind, range, name)
|
127
|
-
if range.nil?
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
v = Ilp::Var.new(kind: kind, name: name, lower_bound: range.min, upper_bound: range.max)
|
127
|
+
v = if range.nil?
|
128
|
+
Ilp::Var.new(kind: kind, name: name)
|
129
|
+
else
|
130
|
+
Ilp::Var.new(kind: kind, name: name, lower_bound: range.min, upper_bound: range.max)
|
131
|
+
end
|
133
132
|
@vars << v
|
134
133
|
v
|
135
134
|
end
|
136
135
|
|
137
136
|
def lb_to_s(lb)
|
138
|
-
return "-inf" if
|
137
|
+
return "-inf" if lb.nil? || lb == -Cbc::INF
|
139
138
|
return "+inf" if lb == Cbc::INF
|
140
|
-
|
139
|
+
lb.to_s
|
141
140
|
end
|
142
141
|
|
143
142
|
def ub_to_s(ub)
|
144
|
-
return "+inf" if
|
143
|
+
return "+inf" if ub.nil? || ub == Cbc::INF
|
145
144
|
return "-inf" if ub == -Cbc::INF
|
146
|
-
|
145
|
+
ub.to_s
|
147
146
|
end
|
148
|
-
|
149
147
|
end
|
150
148
|
end
|
data/lib/ruby-cbc/problem.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Cbc
|
2
2
|
class Problem
|
3
|
-
|
4
3
|
attr_accessor :model, :variable_index, :crs
|
5
4
|
|
6
5
|
def self.from_model(model, continuous: false)
|
@@ -40,10 +39,10 @@ module Cbc
|
|
40
39
|
cols_idx = ccs.col_ptr.clone
|
41
40
|
row_idx = 0
|
42
41
|
end_row_idx = crs.row_ptr.size - 1
|
43
|
-
while row_idx < end_row_idx
|
42
|
+
while row_idx < end_row_idx
|
44
43
|
current_idx = crs.row_ptr[row_idx]
|
45
44
|
last_idx = crs.row_ptr[row_idx + 1] - 1
|
46
|
-
while current_idx <= last_idx
|
45
|
+
while current_idx <= last_idx
|
47
46
|
col_idx = crs.col_idx[current_idx]
|
48
47
|
ccs_col_idx = cols_idx[col_idx]
|
49
48
|
cols_idx[col_idx] += 1
|
@@ -75,7 +74,6 @@ module Cbc
|
|
75
74
|
to_double_array(ccs.values), nil, nil, to_double_array(objective),
|
76
75
|
nil, nil)
|
77
76
|
|
78
|
-
|
79
77
|
# Segmentation errors when setting name
|
80
78
|
# Cbc_wrapper.Cbc_setProblemName(@cbc_model, model.name) if model.name
|
81
79
|
|
@@ -85,13 +83,13 @@ module Cbc
|
|
85
83
|
end
|
86
84
|
|
87
85
|
idx = 0
|
88
|
-
while idx < @crs.nb_constraints
|
86
|
+
while idx < @crs.nb_constraints
|
89
87
|
c = @crs.model.constraints[idx]
|
90
88
|
set_constraint_bounds(c, idx)
|
91
89
|
idx += 1
|
92
90
|
end
|
93
91
|
idx = 0
|
94
|
-
while idx < ccs.nb_vars
|
92
|
+
while idx < ccs.nb_vars
|
95
93
|
v = @crs.model.vars[idx]
|
96
94
|
if continuous
|
97
95
|
Cbc_wrapper.Cbc_setContinuous(@cbc_model, idx)
|
@@ -111,10 +109,8 @@ module Cbc
|
|
111
109
|
ObjectSpace.define_finalizer(self, self.class.finalizer(@cbc_model, @int_arrays, @double_arrays))
|
112
110
|
|
113
111
|
@default_solve_params = {
|
114
|
-
log: 0
|
112
|
+
log: 0
|
115
113
|
}
|
116
|
-
|
117
|
-
|
118
114
|
end
|
119
115
|
|
120
116
|
def set_constraint_bounds(c, idx)
|
@@ -198,12 +194,12 @@ module Cbc
|
|
198
194
|
Cbc_wrapper.Cbc_writeMps(@cbc_model, "test")
|
199
195
|
end
|
200
196
|
|
201
|
-
|
197
|
+
private
|
202
198
|
|
203
199
|
def to_int_array(array)
|
204
200
|
c_array = Cbc_wrapper::IntArray.new(array.size)
|
205
201
|
idx = 0
|
206
|
-
while idx < array.size
|
202
|
+
while idx < array.size
|
207
203
|
c_array[idx] = array[idx]
|
208
204
|
idx += 1
|
209
205
|
end
|
@@ -214,7 +210,7 @@ module Cbc
|
|
214
210
|
def to_double_array(array)
|
215
211
|
c_array = Cbc_wrapper::DoubleArray.new(array.size)
|
216
212
|
idx = 0
|
217
|
-
while idx < array.size
|
213
|
+
while idx < array.size
|
218
214
|
c_array[idx] = array[idx]
|
219
215
|
idx += 1
|
220
216
|
end
|
@@ -1,121 +1,123 @@
|
|
1
|
-
module
|
2
|
-
|
1
|
+
module Cbc
|
2
|
+
module Util
|
3
|
+
class CompressedRowStorage
|
4
|
+
attr_accessor :model, :variable_index, :row_ptr, :col_idx, :values
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
crs.variable_index = {}
|
10
|
-
idx = 0
|
11
|
-
while idx < model.vars.size do
|
12
|
-
v = model.vars[idx]
|
13
|
-
crs.variable_index[v] = idx
|
14
|
-
idx += 1
|
6
|
+
def self.from_model(model)
|
7
|
+
new.tap do |crs|
|
8
|
+
crs.model = model
|
9
|
+
crs.make_variable_index
|
10
|
+
crs.fill_matrix
|
15
11
|
end
|
16
|
-
crs.fill_matrix
|
17
12
|
end
|
18
|
-
end
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
|
14
|
+
def nb_constraints
|
15
|
+
row_ptr.size - 1
|
16
|
+
end
|
23
17
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@values = Array.new(nb_values)
|
18
|
+
def make_variable_index
|
19
|
+
indexes = @model.vars.size.times.to_a
|
20
|
+
@variable_index = model.vars.zip(indexes).to_h
|
21
|
+
end
|
29
22
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@
|
35
|
-
nb_insert = constraint.terms.count
|
36
|
-
@col_idx[nb_cols, nb_insert] = constraint.terms.map { |term| variable_index[term.var] }
|
37
|
-
@values[nb_cols, nb_insert] = constraint.terms.map { |term| term.mult }
|
38
|
-
nb_cols += nb_insert
|
39
|
-
c_idx += 1
|
23
|
+
def init_matrix
|
24
|
+
nb_values = model.constraints.map { |c| c.terms.size }.inject(:+) || 0
|
25
|
+
@row_ptr = Array.new(model.constraints.size)
|
26
|
+
@col_idx = Array.new(nb_values)
|
27
|
+
@values = Array.new(nb_values)
|
40
28
|
end
|
41
|
-
@row_ptr << @col_idx.count
|
42
|
-
end
|
43
29
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
30
|
+
def fill_matrix
|
31
|
+
init_matrix
|
32
|
+
nb_cols = 0
|
33
|
+
c_idx = 0
|
34
|
+
while c_idx < @model.constraints.size
|
35
|
+
constraint = @model.constraints[c_idx]
|
36
|
+
@row_ptr[c_idx] = nb_cols
|
37
|
+
nb_insert = constraint.terms.size
|
38
|
+
@col_idx[nb_cols, nb_insert] = constraint.terms.map { |term| variable_index[term.var] }
|
39
|
+
@values[nb_cols, nb_insert] = constraint.terms.map(&:mult)
|
40
|
+
nb_cols += nb_insert
|
41
|
+
c_idx += 1
|
42
|
+
end
|
43
|
+
@row_ptr << @col_idx.size
|
53
44
|
end
|
54
|
-
end
|
55
45
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
46
|
+
def restrict_to_n_constraints(nb_constraints)
|
47
|
+
length_of_values = @row_ptr[nb_constraints]
|
48
|
+
CompressedRowStorage.new.tap do |crs|
|
49
|
+
crs.model = @model.clone
|
50
|
+
crs.variable_index = @variable_index
|
51
|
+
crs.row_ptr = @row_ptr[0, nb_constraints + 1]
|
52
|
+
crs.col_idx = @col_idx[0, length_of_values]
|
53
|
+
crs.values = @values[0, length_of_values]
|
54
|
+
crs.delete_missing_vars
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def present_var_indexes
|
59
|
+
present = Array.new(@variable_index.size, false)
|
60
|
+
@col_idx.each { |col_idx| present[col_idx] = true }
|
61
|
+
present
|
60
62
|
end
|
61
|
-
is_present
|
62
|
-
end
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
if here[idx]
|
64
|
+
def new_indexes
|
65
|
+
present = present_var_indexes
|
66
|
+
return nil if present.all?
|
67
|
+
new_idx = Array.new(@variable_index.size, -1)
|
68
|
+
current_index = 0
|
69
|
+
new_idx.size.times.each do |idx|
|
70
|
+
next unless present[idx]
|
72
71
|
new_idx[idx] = current_index
|
73
72
|
current_index += 1
|
74
|
-
else
|
75
|
-
at_least_one_missing = true
|
76
73
|
end
|
77
|
-
|
74
|
+
new_idx
|
78
75
|
end
|
79
76
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
77
|
+
def change_indexes(new_idx)
|
78
|
+
new_variable_index = {}
|
79
|
+
@variable_index.each do |v, i|
|
80
|
+
new_variable_index[v] = new_idx[i] if new_idx[i] != -1
|
81
|
+
end
|
82
|
+
@variable_index = new_variable_index
|
85
83
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
84
|
+
|
85
|
+
def delete_missing_vars
|
86
|
+
new_idx = new_indexes
|
87
|
+
return if new_idx.nil?
|
88
|
+
|
89
|
+
change_indexes(new_idx)
|
90
|
+
|
91
|
+
@col_idx.map! { |i| new_idx[i] }
|
92
|
+
@model.vars = Array.new(@variable_index.size)
|
93
|
+
@variable_index.each { |var, i| @model.vars[i] = var }
|
91
94
|
end
|
92
|
-
end
|
93
95
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
96
|
+
def move_constraint_to_start(range_idxs)
|
97
|
+
# Move in the model
|
98
|
+
constraints = model.constraints[range_idxs]
|
99
|
+
@model.constraints = @model.constraints.clone
|
100
|
+
@model.constraints[constraints.size, range_idxs.max] = model.constraints[0, range_idxs.min]
|
101
|
+
@model.constraints[0, constraints.size] = constraints
|
100
102
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
103
|
+
# Move in the matrix
|
104
|
+
constraint_start_idx = @row_ptr[range_idxs.min]
|
105
|
+
nb_vars = @row_ptr[range_idxs.max + 1] - constraint_start_idx
|
106
|
+
offset = @row_ptr[range_idxs.min]
|
107
|
+
new_begin = @row_ptr[range_idxs].map! { |idx| idx - offset }
|
108
|
+
((range_idxs.size)..(range_idxs.max)).reverse_each do |idx|
|
109
|
+
@row_ptr[idx] = @row_ptr[idx - range_idxs.size] + nb_vars
|
110
|
+
end
|
111
|
+
@row_ptr[0, range_idxs.size] = new_begin
|
112
|
+
move_block_to_start(@col_idx, constraint_start_idx, nb_vars)
|
113
|
+
move_block_to_start(@values, constraint_start_idx, nb_vars)
|
108
114
|
end
|
109
|
-
@row_ptr[0, range_idxs.count] = new_begin
|
110
|
-
move_block_to_start(@col_idx, constraint_start_idx, nb_vars)
|
111
|
-
move_block_to_start(@values, constraint_start_idx, nb_vars)
|
112
|
-
end
|
113
115
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
116
|
+
def move_block_to_start(array, block_start_idx, nb_values)
|
117
|
+
to_move = array[block_start_idx, nb_values]
|
118
|
+
array[nb_values, block_start_idx] = array[0, block_start_idx]
|
119
|
+
array[0, nb_values] = to_move
|
120
|
+
end
|
118
121
|
end
|
119
|
-
|
120
122
|
end
|
121
123
|
end
|
data/lib/ruby-cbc/version.rb
CHANGED
data/ruby-cbc.gemspec
CHANGED