mipper 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mipper.rb +1 -0
- data/lib/mipper/cbc/model.rb +118 -54
- data/lib/mipper/constraint.rb +4 -0
- data/lib/mipper/glpk/ext/structs.rb +22 -21
- data/lib/mipper/glpk/model.rb +30 -28
- data/lib/mipper/gurobi/model.rb +81 -75
- data/lib/mipper/lp_solve/model.rb +66 -57
- data/lib/mipper/model.rb +51 -23
- data/lib/mipper/solution.rb +11 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99f7bd81a866c8127ba5527dd8eb2107fc3c3ee1
|
4
|
+
data.tar.gz: 6b94eefa7fb9ab14b6400963f7975fca4b40b538
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7c81f9e6743229544bd55ba20796c39a80aa35ba63cea211722ab2d56056dd9cc86314d685cd0ef746daca4ebe03da8b9b92da3925bc76e5dc21ee1f30f00b6
|
7
|
+
data.tar.gz: 1c43605bf6efdb20bd227f569b385c89bc25567416e5511d28125d81ac081884c93d754fe4e6667631b6be31440c836384b808e72503203980628dff1140e700
|
data/lib/mipper.rb
CHANGED
data/lib/mipper/cbc/model.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'zlib'
|
2
2
|
|
3
3
|
module MIPPeR
|
4
|
+
# A linear programming model using the COIN-OR solver
|
4
5
|
class CbcModel < Model
|
5
6
|
attr_reader :ptr
|
6
7
|
|
@@ -11,18 +12,31 @@ module MIPPeR
|
|
11
12
|
@constr_count = 0
|
12
13
|
|
13
14
|
# Construct a new model
|
14
|
-
@ptr =
|
15
|
-
Cbc.method(:Cbc_deleteModel)
|
16
|
-
Cbc.Cbc_setParameter @ptr, 'logLevel', '0'
|
15
|
+
@ptr = new_model
|
17
16
|
end
|
18
17
|
|
19
18
|
# Write the model to a file in MPS format
|
20
19
|
def write_mps(filename)
|
21
|
-
|
22
|
-
|
20
|
+
# Make a new model and ensure everything is added
|
21
|
+
old_ptr = @ptr
|
22
|
+
@ptr = new_model
|
23
|
+
parent_update
|
24
|
+
|
25
|
+
Cbc.Cbc_writeMps @ptr, filename.chomp('.mps')
|
23
26
|
contents = Zlib::GzipReader.open(filename + '.gz').read
|
24
27
|
File.delete(filename + '.gz')
|
25
28
|
File.open(filename, 'w').write contents
|
29
|
+
|
30
|
+
# Reset to the original model
|
31
|
+
@ptr = old_ptr
|
32
|
+
reset_model
|
33
|
+
end
|
34
|
+
|
35
|
+
alias_method :parent_update, :update
|
36
|
+
|
37
|
+
# Avoid doing anything here. Updating multiple times will
|
38
|
+
# break the model so we defer to #solve.
|
39
|
+
def update
|
26
40
|
end
|
27
41
|
|
28
42
|
# Set the sense of the model
|
@@ -35,40 +49,24 @@ module MIPPeR
|
|
35
49
|
# Optimize the model
|
36
50
|
def optimize
|
37
51
|
# Ensure pending variables and constraints are added
|
38
|
-
|
52
|
+
parent_update
|
39
53
|
|
40
54
|
# Run the solver and save the status for later
|
41
55
|
Cbc.Cbc_solve @ptr
|
42
56
|
fail if Cbc.Cbc_status(@ptr) != 0
|
43
57
|
|
44
|
-
|
45
|
-
if Cbc.Cbc_isProvenOptimal(@ptr) == 1
|
46
|
-
@status = :optimized
|
47
|
-
elsif Cbc.Cbc_isProvenInfeasible(@ptr) == 1 or
|
48
|
-
Cbc.Cbc_isContinuousUnbounded(@ptr) == 1
|
49
|
-
@status = :invalid
|
50
|
-
else
|
51
|
-
@status = :unknown
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Get the status of the model
|
56
|
-
def status
|
57
|
-
@status
|
58
|
-
end
|
58
|
+
save_solution
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
Cbc.Cbc_getObjValue @ptr
|
60
|
+
@ptr = new_model
|
61
|
+
reset_model
|
63
62
|
end
|
64
63
|
|
65
|
-
#
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
64
|
+
# Set the bounds of a variable in the model
|
65
|
+
def set_variable_bounds(var_index, lb, ub, force = false)
|
66
|
+
# This is a bit of a hack so that we don't try to set
|
67
|
+
# the variable bounds before they get added to the model
|
68
|
+
return unless force
|
70
69
|
|
71
|
-
def set_variable_bounds(var_index, lb, ub)
|
72
70
|
Cbc.Cbc_setColLower @ptr, var_index, lb
|
73
71
|
Cbc.Cbc_setColUpper @ptr, var_index, ub
|
74
72
|
end
|
@@ -87,11 +85,40 @@ module MIPPeR
|
|
87
85
|
|
88
86
|
# Add multiple constraints at once
|
89
87
|
def add_constraints(constrs)
|
90
|
-
|
88
|
+
start, index, value = build_constraint_matrix constrs
|
89
|
+
start_buffer = build_pointer_array start, :int
|
90
|
+
index_buffer = build_pointer_array index, :int
|
91
|
+
value_buffer = build_pointer_array value, :double
|
92
|
+
|
93
|
+
Cbc.Cbc_loadProblem @ptr, @variables.length, constrs.length,
|
94
|
+
start_buffer, index_buffer, value_buffer,
|
95
|
+
nil, nil, nil, nil, nil
|
96
|
+
|
97
|
+
store_model constrs, @variables
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# Construct a new model object
|
103
|
+
def new_model
|
104
|
+
ptr = FFI::AutoPointer.new Cbc.Cbc_newModel,
|
105
|
+
Cbc.method(:Cbc_deleteModel)
|
106
|
+
Cbc.Cbc_setParameter ptr, 'logLevel', '0'
|
107
|
+
|
108
|
+
ptr
|
109
|
+
end
|
110
|
+
|
111
|
+
# Store the index which will be used for each constraint
|
112
|
+
def store_constraint_indexes(constrs)
|
91
113
|
constrs.each do |constr|
|
92
114
|
constr.instance_variable_set :@index, @constr_count
|
93
115
|
@constr_count += 1
|
94
116
|
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Build a constraint matrix for the currently existing variables
|
120
|
+
def build_constraint_matrix(constrs)
|
121
|
+
store_constraint_indexes constrs
|
95
122
|
|
96
123
|
# Construct a matrix of non-zero values in CSC format
|
97
124
|
start = []
|
@@ -110,30 +137,60 @@ module MIPPeR
|
|
110
137
|
end
|
111
138
|
start << col_start
|
112
139
|
|
113
|
-
|
114
|
-
|
115
|
-
index_buffer = FFI::MemoryPointer.new :int, index.length
|
116
|
-
index_buffer.write_array_of_int index
|
117
|
-
value_buffer = FFI::MemoryPointer.new :double, value.length
|
118
|
-
value_buffer.write_array_of_double value
|
119
|
-
|
120
|
-
Cbc.Cbc_loadProblem @ptr, @variables.length, constrs.length,
|
121
|
-
start_buffer, index_buffer, value_buffer,
|
122
|
-
nil, nil, nil, nil, nil
|
140
|
+
[start, index, value]
|
141
|
+
end
|
123
142
|
|
143
|
+
# Store all data for the model
|
144
|
+
def store_model(constrs, vars)
|
145
|
+
# Store all constraints
|
124
146
|
constrs.each do |constr|
|
125
147
|
store_constraint constr
|
126
148
|
@constraints << constr
|
127
149
|
end
|
128
150
|
|
129
151
|
# We store variables now since they didn't exist earlier
|
130
|
-
|
152
|
+
vars.each_with_index do |var, i|
|
131
153
|
var.instance_variable_set(:@index, i)
|
132
154
|
store_variable var
|
133
155
|
end
|
134
156
|
end
|
135
157
|
|
136
|
-
|
158
|
+
# Save the solution to the model for access later
|
159
|
+
def save_solution
|
160
|
+
# Check and store the model status
|
161
|
+
if Cbc.Cbc_isProvenOptimal(@ptr) == 1
|
162
|
+
status = :optimized
|
163
|
+
elsif Cbc.Cbc_isProvenInfeasible(@ptr) == 1 ||
|
164
|
+
Cbc.Cbc_isContinuousUnbounded(@ptr) == 1
|
165
|
+
status = :invalid
|
166
|
+
else
|
167
|
+
status = :unknown
|
168
|
+
end
|
169
|
+
|
170
|
+
if status == :optimized
|
171
|
+
objective_value = Cbc.Cbc_getObjValue @ptr
|
172
|
+
dblptr = Cbc.Cbc_getColSolution @ptr
|
173
|
+
values = dblptr.read_array_of_double(@variables.length)
|
174
|
+
variable_values = Hash[@variables.map do |var|
|
175
|
+
[var.name, values[var.index]]
|
176
|
+
end]
|
177
|
+
else
|
178
|
+
objective_value = nil
|
179
|
+
variable_values = {}
|
180
|
+
end
|
181
|
+
|
182
|
+
@solution = Solution.new status, objective_value, variable_values
|
183
|
+
end
|
184
|
+
|
185
|
+
# Reset the internal state of the model so we can reuse it
|
186
|
+
def reset_model
|
187
|
+
@var_count = 0
|
188
|
+
@constr_count = 0
|
189
|
+
@pending_variables = @variables
|
190
|
+
@pending_constraints = @constraints
|
191
|
+
@variables = []
|
192
|
+
@constraints = []
|
193
|
+
end
|
137
194
|
|
138
195
|
# Save the constraint to the model and update the constraint pointers
|
139
196
|
def store_constraint(constr)
|
@@ -143,17 +200,22 @@ module MIPPeR
|
|
143
200
|
|
144
201
|
# Set constraint properties
|
145
202
|
Cbc.Cbc_setRowName(@ptr, index, constr.name) unless constr.name.nil?
|
203
|
+
store_constraint_bounds index, constr.sense, constr.rhs
|
204
|
+
end
|
146
205
|
|
147
|
-
|
206
|
+
# Store the bounds for a given constraint
|
207
|
+
def store_constraint_bounds(index, sense, rhs)
|
208
|
+
case sense
|
148
209
|
when :==
|
149
|
-
lb = ub =
|
210
|
+
lb = ub = rhs
|
150
211
|
when :>=
|
151
|
-
lb =
|
212
|
+
lb = rhs
|
152
213
|
ub = Float::INFINITY
|
153
214
|
when :<=
|
154
215
|
lb = -Float::INFINITY
|
155
|
-
ub =
|
216
|
+
ub = rhs
|
156
217
|
end
|
218
|
+
|
157
219
|
Cbc.Cbc_setRowLower @ptr, index, lb
|
158
220
|
Cbc.Cbc_setRowUpper @ptr, index, ub
|
159
221
|
end
|
@@ -165,21 +227,23 @@ module MIPPeR
|
|
165
227
|
var.instance_variable_set(:@lower_bound, 0)
|
166
228
|
var.instance_variable_set(:@upper_bound, 1)
|
167
229
|
end
|
230
|
+
set_variable_bounds var.index, var.lower_bound, var.upper_bound, true
|
168
231
|
|
169
|
-
Cbc.Cbc_setColLower @ptr, var.index, var.lower_bound
|
170
|
-
Cbc.Cbc_setColUpper @ptr, var.index, var.upper_bound
|
171
232
|
Cbc.Cbc_setObjCoeff @ptr, var.index, var.coefficient
|
233
|
+
Cbc.Cbc_setColName(@ptr, var.index, var.name) unless var.name.nil?
|
234
|
+
set_variable_type var.index, var.type
|
235
|
+
end
|
172
236
|
|
173
|
-
|
237
|
+
# Set the type of a variable
|
238
|
+
def set_variable_type(index, type)
|
239
|
+
case type
|
174
240
|
when :continuous
|
175
|
-
Cbc.Cbc_setContinuous @ptr,
|
241
|
+
Cbc.Cbc_setContinuous @ptr, index
|
176
242
|
when :integer, :binary
|
177
|
-
Cbc.Cbc_setInteger @ptr,
|
243
|
+
Cbc.Cbc_setInteger @ptr, index
|
178
244
|
else
|
179
245
|
fail :type
|
180
246
|
end
|
181
|
-
|
182
|
-
Cbc.Cbc_setColName(@ptr, var.index, var.name) unless var.name.nil?
|
183
247
|
end
|
184
248
|
end
|
185
249
|
end
|
data/lib/mipper/constraint.rb
CHANGED
@@ -1,28 +1,29 @@
|
|
1
1
|
module MIPPeR
|
2
2
|
module GLPK
|
3
|
+
# Integer optimization control parameters
|
3
4
|
class IOCP < FFI::Struct
|
4
5
|
layout :msg_lev, :int,
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
6
|
+
:br_tech, :int,
|
7
|
+
:bt_tech, :int,
|
8
|
+
:tol_int, :double,
|
9
|
+
:tol_obj, :double,
|
10
|
+
:tm_lim, :int,
|
11
|
+
:out_frq, :int,
|
12
|
+
:out_dly, :int,
|
13
|
+
:cb_func, callback([:pointer, :pointer], :void),
|
14
|
+
:cb_info, :pointer,
|
15
|
+
:cb_size, :int,
|
16
|
+
:pp_tech, :int,
|
17
|
+
:mip_gap, :double,
|
18
|
+
:mir_cuts, :int,
|
19
|
+
:gmi_cuts, :int,
|
20
|
+
:cov_cuts, :int,
|
21
|
+
:clq_cuts, :int,
|
22
|
+
:presolve, :int,
|
23
|
+
:binarize, :int,
|
24
|
+
:fp_heur, :int,
|
25
|
+
:alien, :int,
|
26
|
+
:foo_bar, [:double, 29]
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
data/lib/mipper/glpk/model.rb
CHANGED
@@ -49,29 +49,9 @@ module MIPPeR
|
|
49
49
|
iocp = GLPK::IOCP.new
|
50
50
|
GLPK.glp_init_iocp iocp
|
51
51
|
iocp[:presolve] = GLPK::GLP_ON
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
# Get the status of the model
|
56
|
-
def status
|
57
|
-
case @status
|
58
|
-
when 0, GLPK::GLP_EMIPGAP
|
59
|
-
:optimized
|
60
|
-
when GLPK::GLP_EBOUND, GLPK::GLP_EROOT, GLPK::GLP_ENOPFS
|
61
|
-
:invalid
|
62
|
-
else
|
63
|
-
:unknown
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# The value of the objective function
|
68
|
-
def objective_value
|
69
|
-
GLPK.glp_mip_obj_val @ptr
|
70
|
-
end
|
52
|
+
status = GLPK.glp_intopt(@ptr, iocp)
|
71
53
|
|
72
|
-
|
73
|
-
def variable_value(var)
|
74
|
-
GLPK.glp_mip_col_val(@ptr, var.index)
|
54
|
+
save_solution status
|
75
55
|
end
|
76
56
|
|
77
57
|
def set_variable_bounds(var_index, lb, ub)
|
@@ -116,12 +96,9 @@ module MIPPeR
|
|
116
96
|
end
|
117
97
|
end
|
118
98
|
|
119
|
-
ia_buffer =
|
120
|
-
|
121
|
-
|
122
|
-
ja_buffer.write_array_of_int @ja
|
123
|
-
ar_buffer = FFI::MemoryPointer.new :pointer, @ar.length
|
124
|
-
ar_buffer.write_array_of_double @ar
|
99
|
+
ia_buffer = build_pointer_array @ia, :int
|
100
|
+
ja_buffer = build_pointer_array @ja, :int
|
101
|
+
ar_buffer = build_pointer_array @ar, :double
|
125
102
|
|
126
103
|
GLPK.glp_load_matrix(@ptr, @ar.length - 1, ia_buffer, ja_buffer,
|
127
104
|
ar_buffer)
|
@@ -131,6 +108,31 @@ module MIPPeR
|
|
131
108
|
|
132
109
|
private
|
133
110
|
|
111
|
+
# Save the solution to the model for access later
|
112
|
+
def save_solution(status)
|
113
|
+
status = case status
|
114
|
+
when 0, GLPK::GLP_EMIPGAP
|
115
|
+
:optimized
|
116
|
+
when GLPK::GLP_EBOUND, GLPK::GLP_EROOT, GLPK::GLP_ENOPFS
|
117
|
+
:invalid
|
118
|
+
else
|
119
|
+
:unknown
|
120
|
+
end
|
121
|
+
|
122
|
+
if status == :optimized
|
123
|
+
objective_value = GLPK.glp_mip_obj_val @ptr
|
124
|
+
variable_values = Hash[@variables.map do |var|
|
125
|
+
value = GLPK.glp_mip_col_val(@ptr, var.index)
|
126
|
+
[var.name, value]
|
127
|
+
end]
|
128
|
+
else
|
129
|
+
objective_value = nil
|
130
|
+
variable_values = {}
|
131
|
+
end
|
132
|
+
|
133
|
+
@solution = Solution.new status, objective_value, variable_values
|
134
|
+
end
|
135
|
+
|
134
136
|
# Save the constraint to the model and update the constraint pointers
|
135
137
|
def store_constraint(constr)
|
136
138
|
# Update the constraint to track the index in the model
|
data/lib/mipper/gurobi/model.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
module MIPPeR
|
2
|
+
# A linear programming model using the Gurobi solver
|
2
3
|
class GurobiModel < Model
|
3
4
|
attr_reader :ptr, :environment
|
4
5
|
|
@@ -51,10 +52,25 @@ module MIPPeR
|
|
51
52
|
|
52
53
|
ret = Gurobi.GRBoptimize @ptr
|
53
54
|
fail if ret != 0
|
55
|
+
|
56
|
+
save_solution
|
54
57
|
end
|
55
58
|
|
59
|
+
# Compute an irreducible inconsistent subsytem for the model
|
60
|
+
def compute_iis
|
61
|
+
ret = Gurobi.GRBcomputeIIS @ptr
|
62
|
+
fail if ret != 0
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_variable_bounds(var_index, lb, ub)
|
66
|
+
set_double_attribute Gurobi::GRB_DBL_ATTR_LB, var_index, lb
|
67
|
+
set_double_attribute Gurobi::GRB_DBL_ATTR_UB, var_index, ub
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
56
72
|
# Get the status of the model
|
57
|
-
def
|
73
|
+
def gurobi_status
|
58
74
|
intptr = FFI::MemoryPointer.new :pointer
|
59
75
|
ret = Gurobi.GRBgetintattr @ptr, Gurobi::GRB_INT_ATTR_STATUS, intptr
|
60
76
|
fail if ret != 0
|
@@ -63,59 +79,31 @@ module MIPPeR
|
|
63
79
|
when Gurobi::GRB_OPTIMAL
|
64
80
|
:optimized
|
65
81
|
when Gurobi::GRB_INFEASIBLE, Gurobi::GRB_INF_OR_UNBD,
|
66
|
-
|
82
|
+
Gurobi::GRB_UNBOUNDED
|
67
83
|
:invalid
|
68
84
|
else
|
69
85
|
:unknown
|
70
86
|
end
|
71
87
|
end
|
72
88
|
|
73
|
-
# Compute an irreducible inconsistent subsytem for the model
|
74
|
-
def compute_IIS
|
75
|
-
ret = Gurobi.GRBcomputeIIS @ptr
|
76
|
-
fail if ret != 0
|
77
|
-
end
|
78
|
-
|
79
89
|
# The value of the objective function
|
80
|
-
def
|
90
|
+
def gurobi_objective
|
81
91
|
dblptr = FFI::MemoryPointer.new :pointer
|
82
92
|
ret = Gurobi.GRBgetdblattr @ptr, Gurobi::GRB_DBL_ATTR_OBJVAL, dblptr
|
83
93
|
fail if ret != 0
|
84
94
|
dblptr.read_double
|
85
95
|
end
|
86
96
|
|
87
|
-
def set_variable_bounds(var_index, lb, ub)
|
88
|
-
set_double_attribute Gurobi::GRB_DBL_ATTR_LB, var_index, lb
|
89
|
-
set_double_attribute Gurobi::GRB_DBL_ATTR_UB, var_index, ub
|
90
|
-
end
|
91
|
-
|
92
|
-
# Get the value of a variable from the model
|
93
|
-
def variable_value(var)
|
94
|
-
dblptr = FFI::MemoryPointer.new :pointer
|
95
|
-
Gurobi.GRBgetdblattrarray @ptr, Gurobi::GRB_DBL_ATTR_X,
|
96
|
-
var.index, 1, dblptr
|
97
|
-
dblptr.read_array_of_double(1)[0]
|
98
|
-
end
|
99
|
-
|
100
|
-
protected
|
101
|
-
|
102
97
|
# Add multiple variables to the model simultaneously
|
103
98
|
def add_variables(vars)
|
104
|
-
objective_buffer =
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
type_buffer = FFI::MemoryPointer.new :char, vars.length
|
114
|
-
type_buffer.write_array_of_char vars.map { |var| gurobi_type(var.type) }
|
115
|
-
|
116
|
-
names = array_to_pointers_to_names vars
|
117
|
-
names_buffer = FFI::MemoryPointer.new :pointer, vars.length
|
118
|
-
names_buffer.write_array_of_pointer names
|
99
|
+
objective_buffer = build_pointer_array vars.map(&:coefficient), :double
|
100
|
+
lb_buffer = build_pointer_array vars.map(&:lower_bound), :double
|
101
|
+
ub_buffer = build_pointer_array vars.map(&:upper_bound), :double
|
102
|
+
type_buffer = build_pointer_array(vars.map do |var|
|
103
|
+
gurobi_type(var.type)
|
104
|
+
end, :char)
|
105
|
+
names_buffer = build_pointer_array array_to_pointers_to_names(vars),
|
106
|
+
:pointer
|
119
107
|
|
120
108
|
ret = Gurobi.GRBaddvars @ptr, vars.length, 0, nil, nil, nil,
|
121
109
|
objective_buffer, lb_buffer, ub_buffer,
|
@@ -125,6 +113,10 @@ module MIPPeR
|
|
125
113
|
|
126
114
|
# Store all the variables in the model
|
127
115
|
vars.each { |var| store_variable var }
|
116
|
+
|
117
|
+
# Update the model with variables so constraint adds succeed
|
118
|
+
ret = Gurobi.GRBupdatemodel @ptr
|
119
|
+
fail if ret != 0
|
128
120
|
end
|
129
121
|
|
130
122
|
# Add a new variable to the model
|
@@ -139,37 +131,18 @@ module MIPPeR
|
|
139
131
|
|
140
132
|
# Add multiple constraints at once
|
141
133
|
def add_constraints(constrs)
|
142
|
-
cbeg =
|
143
|
-
cind = []
|
144
|
-
cval = []
|
145
|
-
constrs.each_with_index.map do |constr, i|
|
146
|
-
cbeg << cind.length
|
147
|
-
constr.expression.terms.each do |var, coeff|
|
148
|
-
cind << var.instance_variable_get(:@index)
|
149
|
-
cval << coeff
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
cbeg_buffer = FFI::MemoryPointer.new :pointer, cbeg.length
|
154
|
-
cbeg_buffer.write_array_of_int cbeg
|
155
|
-
|
156
|
-
cind_buffer = FFI::MemoryPointer.new :pointer, cind.length
|
157
|
-
cind_buffer.write_array_of_int cind
|
134
|
+
cbeg, cind, cval = build_constraint_matrix constrs
|
158
135
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
sense_buffer =
|
163
|
-
sense_buffer.write_array_of_char(constrs.map do |c|
|
136
|
+
cbeg_buffer = build_pointer_array cbeg, :int
|
137
|
+
cind_buffer = build_pointer_array cind, :int
|
138
|
+
cval_buffer = build_pointer_array cval, :double
|
139
|
+
sense_buffer = build_pointer_array(constrs.map do |c|
|
164
140
|
gurobi_sense(c.sense)
|
165
|
-
end)
|
166
|
-
|
167
|
-
rhs_buffer = FFI::MemoryPointer.new :pointer, constrs.length
|
168
|
-
rhs_buffer.write_array_of_double constrs.map(&:rhs)
|
141
|
+
end, :char)
|
142
|
+
rhs_buffer = build_pointer_array constrs.map(&:rhs), :double
|
169
143
|
|
170
|
-
|
171
|
-
|
172
|
-
names_buffer.write_array_of_pointer names
|
144
|
+
names_buffer = build_pointer_array array_to_pointers_to_names(constrs),
|
145
|
+
:pointer
|
173
146
|
|
174
147
|
ret = Gurobi.GRBaddconstrs @ptr, constrs.length, cind.length,
|
175
148
|
cbeg_buffer, cind_buffer, cval_buffer,
|
@@ -182,13 +155,10 @@ module MIPPeR
|
|
182
155
|
# Add a new constraint to the model
|
183
156
|
def add_constraint(constr)
|
184
157
|
terms = constr.expression.terms
|
185
|
-
indexes_buffer =
|
186
|
-
indexes_buffer.write_array_of_int(terms.each_key.map do |var|
|
158
|
+
indexes_buffer = build_pointer_array(terms.each_key.map do |var|
|
187
159
|
var.instance_variable_get(:@index)
|
188
|
-
end)
|
189
|
-
|
190
|
-
values_buffer = FFI::MemoryPointer.new :double, terms.length
|
191
|
-
values_buffer.write_array_of_double terms.values
|
160
|
+
end, :int)
|
161
|
+
values_buffer = build_pointer_array terms.values, :double
|
192
162
|
|
193
163
|
ret = Gurobi.GRBaddconstr @ptr, terms.length,
|
194
164
|
indexes_buffer, values_buffer,
|
@@ -206,6 +176,43 @@ module MIPPeR
|
|
206
176
|
proc { Gurobi.GRBfreemodel ptr }
|
207
177
|
end
|
208
178
|
|
179
|
+
# Construct a matrix of values for the given list of constraints
|
180
|
+
def build_constraint_matrix(constrs)
|
181
|
+
cbeg = []
|
182
|
+
cind = []
|
183
|
+
cval = []
|
184
|
+
constrs.each.map do |constr|
|
185
|
+
cbeg << cind.length
|
186
|
+
constr.expression.terms.each do |var, coeff|
|
187
|
+
cind << var.instance_variable_get(:@index)
|
188
|
+
cval << coeff
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
[cbeg, cind, cval]
|
193
|
+
end
|
194
|
+
|
195
|
+
# Save the solution to the model for access later
|
196
|
+
def save_solution
|
197
|
+
status = gurobi_status
|
198
|
+
|
199
|
+
if status == :optimized
|
200
|
+
objective_value = gurobi_objective
|
201
|
+
variable_values = Hash[@variables.map do |var|
|
202
|
+
dblptr = FFI::MemoryPointer.new :pointer
|
203
|
+
Gurobi.GRBgetdblattrarray @ptr, Gurobi::GRB_DBL_ATTR_X,
|
204
|
+
var.index, 1, dblptr
|
205
|
+
value = dblptr.read_array_of_double(1)[0]
|
206
|
+
[var.name, value]
|
207
|
+
end]
|
208
|
+
else
|
209
|
+
objective_value = nil
|
210
|
+
variable_values = {}
|
211
|
+
end
|
212
|
+
|
213
|
+
@solution = Solution.new status, objective_value, variable_values
|
214
|
+
end
|
215
|
+
|
209
216
|
# Save the variable to the model and update the variable pointers
|
210
217
|
def store_variable(var)
|
211
218
|
# Update the variable to track the index in the model
|
@@ -217,8 +224,7 @@ module MIPPeR
|
|
217
224
|
end
|
218
225
|
|
219
226
|
def set_double_attribute(name, var_index, value)
|
220
|
-
buffer =
|
221
|
-
buffer.write_array_of_double [value]
|
227
|
+
buffer = build_pointer_array [value], :double
|
222
228
|
ret = Gurobi.GRBsetdblattrarray @ptr, name, var_index, 1, buffer
|
223
229
|
fail if ret != 0
|
224
230
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module MIPPeR
|
2
|
+
# A linear programming model using the lp_solve solver
|
2
3
|
class LPSolveModel < Model
|
3
4
|
attr_reader :ptr
|
4
5
|
|
@@ -30,35 +31,8 @@ module MIPPeR
|
|
30
31
|
update
|
31
32
|
|
32
33
|
# Run the solver and save the status for later
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# Get the status of the model
|
37
|
-
def status
|
38
|
-
case @status
|
39
|
-
when LPSolve::OPTIMAL
|
40
|
-
:optimized
|
41
|
-
when LPSolve::INFEASIBLE, LPSolve::UNBOUNDED, LPSolve::DEGENERATE
|
42
|
-
:invalid
|
43
|
-
else
|
44
|
-
:unknown
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# The value of the objective function
|
49
|
-
def objective_value
|
50
|
-
LPSolve.get_objective @ptr
|
51
|
-
end
|
52
|
-
|
53
|
-
# Get the value of a variable from the model
|
54
|
-
def variable_value(var)
|
55
|
-
# To access the value of a result variable we need an index into the
|
56
|
-
# solution which setarts with the objective function, followed by all
|
57
|
-
# of the constraints, and finally the variables
|
58
|
-
# We explicitly ask for the number of rows since extra constraints
|
59
|
-
# may have been added
|
60
|
-
rows = LPSolve.get_Nrows(@ptr)
|
61
|
-
LPSolve.get_var_primalresult @ptr, rows + var.index
|
34
|
+
status = LPSolve.solve(@ptr)
|
35
|
+
save_solution status
|
62
36
|
end
|
63
37
|
|
64
38
|
def set_variable_bounds(var_index, lb, ub)
|
@@ -90,6 +64,52 @@ module MIPPeR
|
|
90
64
|
|
91
65
|
private
|
92
66
|
|
67
|
+
# Build the constraint matrix and add it to the model
|
68
|
+
def store_constraint_matrix(constr, type)
|
69
|
+
# Initialize arrays used to hold the coefficients for each variable
|
70
|
+
row = []
|
71
|
+
colno = []
|
72
|
+
constr.expression.terms.each do |var, coeff|
|
73
|
+
row << coeff * 1.0
|
74
|
+
colno << var.instance_variable_get(:@index)
|
75
|
+
end
|
76
|
+
|
77
|
+
row_buffer = build_pointer_array row, :double
|
78
|
+
colno_buffer = build_pointer_array colno, :int
|
79
|
+
|
80
|
+
ret = LPSolve.add_constraintex(@ptr, constr.expression.terms.length,
|
81
|
+
row_buffer, colno_buffer,
|
82
|
+
type, constr.rhs)
|
83
|
+
fail if ret != 1
|
84
|
+
end
|
85
|
+
|
86
|
+
# Save the solution to the model for access later
|
87
|
+
def save_solution(status)
|
88
|
+
status = case status
|
89
|
+
when LPSolve::OPTIMAL
|
90
|
+
:optimized
|
91
|
+
when LPSolve::INFEASIBLE, LPSolve::UNBOUNDED,
|
92
|
+
LPSolve::DEGENERATE
|
93
|
+
:invalid
|
94
|
+
else
|
95
|
+
:unknown
|
96
|
+
end
|
97
|
+
|
98
|
+
if status == :optimized
|
99
|
+
objective_value = LPSolve.get_objective @ptr
|
100
|
+
rows = LPSolve.get_Nrows(@ptr)
|
101
|
+
variable_values = Hash[@variables.map do |var|
|
102
|
+
value = LPSolve.get_var_primalresult @ptr, rows + var.index
|
103
|
+
[var.name, value]
|
104
|
+
end]
|
105
|
+
else
|
106
|
+
objective_value = nil
|
107
|
+
variable_values = {}
|
108
|
+
end
|
109
|
+
|
110
|
+
@solution = Solution.new status, objective_value, variable_values
|
111
|
+
end
|
112
|
+
|
93
113
|
# Save the constraint to the model and update the constraint pointers
|
94
114
|
def store_constraint(constr)
|
95
115
|
# Update the constraint to track the index in the model
|
@@ -114,24 +134,7 @@ module MIPPeR
|
|
114
134
|
type = LPSolve::LE
|
115
135
|
end
|
116
136
|
|
117
|
-
|
118
|
-
row = []
|
119
|
-
colno = []
|
120
|
-
constr.expression.terms.each do |var, coeff|
|
121
|
-
row << coeff * 1.0
|
122
|
-
colno << var.instance_variable_get(:@index)
|
123
|
-
end
|
124
|
-
|
125
|
-
row_buffer = FFI::MemoryPointer.new :pointer, row.length
|
126
|
-
row_buffer.write_array_of_double row
|
127
|
-
colno_buffer = FFI::MemoryPointer.new :pointer, colno.length
|
128
|
-
colno_buffer.write_array_of_int colno
|
129
|
-
|
130
|
-
ret = LPSolve.add_constraintex(@ptr, constr.expression.terms.length,
|
131
|
-
row_buffer, colno_buffer,
|
132
|
-
type, constr.rhs)
|
133
|
-
fail if ret != 1
|
134
|
-
|
137
|
+
store_constraint_matrix constr, type
|
135
138
|
@constraints << constr
|
136
139
|
end
|
137
140
|
|
@@ -147,7 +150,22 @@ module MIPPeR
|
|
147
150
|
fail if ret != 1
|
148
151
|
|
149
152
|
# Set variable properties
|
150
|
-
|
153
|
+
set_variable_type index, var.type
|
154
|
+
|
155
|
+
ret = LPSolve.set_col_name(@ptr, index, var.name)
|
156
|
+
fail if ret != 1
|
157
|
+
|
158
|
+
ret = LPSolve.set_obj(@ptr, index, var.coefficient)
|
159
|
+
fail if ret != 1
|
160
|
+
|
161
|
+
set_variable_bounds index, var.lower_bound, var.upper_bound
|
162
|
+
|
163
|
+
@variables << var
|
164
|
+
end
|
165
|
+
|
166
|
+
# Set the type of a variable
|
167
|
+
def set_variable_type(index, type)
|
168
|
+
case type
|
151
169
|
when :integer
|
152
170
|
ret = LPSolve.set_int @ptr, index, 1
|
153
171
|
when :binary
|
@@ -157,17 +175,8 @@ module MIPPeR
|
|
157
175
|
else
|
158
176
|
fail type
|
159
177
|
end
|
160
|
-
fail if ret != 1
|
161
178
|
|
162
|
-
ret = LPSolve.set_col_name(@ptr, index, var.name)
|
163
179
|
fail if ret != 1
|
164
|
-
|
165
|
-
ret = LPSolve.set_obj(@ptr, index, var.coefficient)
|
166
|
-
fail if ret != 1
|
167
|
-
|
168
|
-
set_variable_bounds index, var.lower_bound, var.upper_bound
|
169
|
-
|
170
|
-
@variables << var
|
171
180
|
end
|
172
181
|
end
|
173
182
|
end
|
data/lib/mipper/model.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
module MIPPeR
|
2
|
+
# Stores a linear programming model for a particular solver
|
2
3
|
class Model
|
3
4
|
attr_reader :variables, :constraints
|
4
5
|
|
5
6
|
def initialize
|
7
|
+
@solution = nil
|
6
8
|
@sense = :min
|
7
9
|
|
8
10
|
@variables = []
|
@@ -26,33 +28,22 @@ module MIPPeR
|
|
26
28
|
|
27
29
|
# Update the model
|
28
30
|
def update
|
29
|
-
|
30
|
-
|
31
|
-
elsif @pending_variables.length > 0
|
32
|
-
add_variables @pending_variables
|
33
|
-
end
|
34
|
-
@pending_variables = []
|
35
|
-
|
36
|
-
if @pending_constraints.length == 1
|
37
|
-
add_constraint @pending_constraints.first
|
38
|
-
elsif @pending_constraints.length > 0
|
39
|
-
add_constraints @pending_constraints
|
40
|
-
end
|
41
|
-
@pending_constraints = []
|
31
|
+
update_variables
|
32
|
+
update_constraints
|
42
33
|
end
|
43
34
|
|
44
35
|
# Write the model to a file in CPLEX LP format
|
45
|
-
def write_lp(
|
36
|
+
def write_lp(_filename)
|
46
37
|
fail NotImplementedError
|
47
38
|
end
|
48
39
|
|
49
40
|
# Write the model to a file in MPS format
|
50
|
-
def write_mps(
|
41
|
+
def write_mps(_filename)
|
51
42
|
fail NotImplementedError
|
52
43
|
end
|
53
44
|
|
54
45
|
# Set the sense of the model
|
55
|
-
def sense=(
|
46
|
+
def sense=(_sense)
|
56
47
|
fail NotImplementedError
|
57
48
|
end
|
58
49
|
|
@@ -63,35 +54,72 @@ module MIPPeR
|
|
63
54
|
|
64
55
|
# Get the status of the model
|
65
56
|
def status
|
66
|
-
|
57
|
+
if @solution.nil?
|
58
|
+
:unknown
|
59
|
+
else
|
60
|
+
@solution.status
|
61
|
+
end
|
67
62
|
end
|
68
63
|
|
69
64
|
# Compute an irreducible inconsistent subsytem for the model
|
70
|
-
def
|
65
|
+
def compute_iis
|
71
66
|
fail NotImplementedError
|
72
67
|
end
|
73
68
|
|
74
|
-
#
|
69
|
+
# Get the value of the objective function from a previous solution
|
75
70
|
def objective_value
|
76
|
-
|
71
|
+
@solution.objective_value unless @solution.nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get the value of a variable from a previous solution
|
75
|
+
def variable_value(var)
|
76
|
+
@solution.variable_values[var.name] unless @solution.nil?
|
77
77
|
end
|
78
78
|
|
79
79
|
protected
|
80
80
|
|
81
|
+
# Shortcut to build a C array from a Ruby array
|
82
|
+
def build_pointer_array(array, type)
|
83
|
+
buffer = FFI::MemoryPointer.new type, array.length
|
84
|
+
buffer.send("write_array_of_#{type}".to_sym, array)
|
85
|
+
|
86
|
+
buffer
|
87
|
+
end
|
88
|
+
|
89
|
+
# Just add the variable as an array
|
81
90
|
def add_variable(var)
|
82
91
|
add_variables([var])
|
83
92
|
end
|
84
93
|
|
94
|
+
# Just add the constraint as an array
|
85
95
|
def add_constraint(constr)
|
86
96
|
add_constraints([constr])
|
87
97
|
end
|
88
98
|
|
89
|
-
def set_variable_bounds(
|
99
|
+
def set_variable_bounds(_var_index, _ub, _lb)
|
90
100
|
fail NotImplementedError
|
91
101
|
end
|
92
102
|
|
93
|
-
|
94
|
-
|
103
|
+
private
|
104
|
+
|
105
|
+
# Add any pending variables to the model
|
106
|
+
def update_variables
|
107
|
+
if @pending_variables.length == 1
|
108
|
+
add_variable @pending_variables.first
|
109
|
+
elsif @pending_variables.length > 0
|
110
|
+
add_variables @pending_variables
|
111
|
+
end
|
112
|
+
@pending_variables = []
|
113
|
+
end
|
114
|
+
|
115
|
+
# Add any pending constraints to the model
|
116
|
+
def update_constraints
|
117
|
+
if @pending_constraints.length == 1
|
118
|
+
add_constraint @pending_constraints.first
|
119
|
+
elsif @pending_constraints.length > 0
|
120
|
+
add_constraints @pending_constraints
|
121
|
+
end
|
122
|
+
@pending_constraints = []
|
95
123
|
end
|
96
124
|
end
|
97
125
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module MIPPeR
|
2
|
+
class Solution
|
3
|
+
attr_accessor :status, :objective_value, :variable_values
|
4
|
+
|
5
|
+
def initialize(status, objective_value, variable_values)
|
6
|
+
@status = status
|
7
|
+
@objective_value = objective_value
|
8
|
+
@variable_values = variable_values
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mipper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Mior
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -51,6 +51,7 @@ files:
|
|
51
51
|
- lib/mipper/lp_solve/ext/constants.rb
|
52
52
|
- lib/mipper/lp_solve/model.rb
|
53
53
|
- lib/mipper/model.rb
|
54
|
+
- lib/mipper/solution.rb
|
54
55
|
- lib/mipper/util.rb
|
55
56
|
- lib/mipper/variable.rb
|
56
57
|
homepage: https://github.com/michaelmior/mipper
|