ruby-cbc 0.3.10 → 0.3.12
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/.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