mipper 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/mipper/cbc/ext.rb +36 -0
- data/lib/mipper/cbc/model.rb +174 -0
- data/lib/mipper/cbc.rb +3 -0
- data/lib/mipper/constraint.rb +17 -0
- data/lib/mipper/expression.rb +53 -0
- data/lib/mipper/glpk/ext/constants.rb +587 -0
- data/lib/mipper/glpk/ext/structs.rb +28 -0
- data/lib/mipper/glpk/ext.rb +43 -0
- data/lib/mipper/glpk/model.rb +186 -0
- data/lib/mipper/glpk.rb +3 -0
- data/lib/mipper/gurobi/env.rb +25 -0
- data/lib/mipper/gurobi/ext/constants.rb +1899 -0
- data/lib/mipper/gurobi/ext.rb +47 -0
- data/lib/mipper/gurobi/model.rb +246 -0
- data/lib/mipper/gurobi.rb +4 -0
- data/lib/mipper/lp_solve/ext/constants.rb +2075 -0
- data/lib/mipper/lp_solve/ext.rb +35 -0
- data/lib/mipper/lp_solve/model.rb +173 -0
- data/lib/mipper/lp_solve.rb +4 -0
- data/lib/mipper/model.rb +92 -0
- data/lib/mipper/util.rb +5 -0
- data/lib/mipper/variable.rb +79 -0
- data/lib/mipper.rb +11 -0
- metadata +81 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module MIPPeR
|
4
|
+
module LPSolve
|
5
|
+
# Hide the constants inside the MIPPeR::LPSolve module
|
6
|
+
module_eval File.read(File.expand_path './ext/constants.rb',
|
7
|
+
File.dirname(__FILE__))
|
8
|
+
|
9
|
+
extend FFI::Library
|
10
|
+
ffi_lib 'lpsolve55'
|
11
|
+
|
12
|
+
attach_function :get_objective, [:pointer], :double
|
13
|
+
attach_function :get_ptr_primal_solution, [:pointer, :pointer], :int
|
14
|
+
attach_function :add_constraintex, [:pointer, :int, :pointer, :pointer,
|
15
|
+
:int, :double], :int
|
16
|
+
attach_function :make_lp, [:int, :int], :pointer
|
17
|
+
attach_function :resize_lp, [:pointer, :int, :int], :int
|
18
|
+
attach_function :set_binary, [:pointer, :int, :int], :int
|
19
|
+
attach_function :set_int, [:pointer, :int, :int], :int
|
20
|
+
attach_function :set_bounds, [:pointer, :int, :double, :double], :int
|
21
|
+
attach_function :set_col_name, [:pointer, :int, :string], :int
|
22
|
+
attach_function :set_add_rowmode, [:pointer, :int], :int
|
23
|
+
attach_function :set_row_name, [:pointer, :int, :string], :int
|
24
|
+
attach_function :set_upbo, [:pointer, :int, :double], :int
|
25
|
+
attach_function :set_lowbo, [:pointer, :int, :double], :int
|
26
|
+
attach_function :set_obj, [:pointer, :int, :double], :int
|
27
|
+
attach_function :solve, [:pointer], :int
|
28
|
+
attach_function :set_sense, [:pointer, :int], :void
|
29
|
+
attach_function :add_columnex, [:pointer, :int, :pointer, :pointer], :int
|
30
|
+
attach_function :delete_lp, [:pointer], :int
|
31
|
+
attach_function :get_var_primalresult, [:pointer, :int], :double
|
32
|
+
attach_function :set_outputfile, [:pointer, :string], :int
|
33
|
+
attach_function :get_Nrows, [:pointer], :int
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module MIPPeR
|
2
|
+
class LPSolveModel < Model
|
3
|
+
attr_reader :ptr
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
super
|
7
|
+
|
8
|
+
@var_count = 0
|
9
|
+
@constr_count = 0
|
10
|
+
|
11
|
+
# Construct a new model
|
12
|
+
@ptr = FFI::AutoPointer.new LPSolve.make_lp(0, 0),
|
13
|
+
LPSolve.method(:delete_lp)
|
14
|
+
|
15
|
+
# Disable output
|
16
|
+
ret = LPSolve.set_outputfile @ptr, ''
|
17
|
+
fail if ret != 1
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set the sense of the model
|
21
|
+
def sense=(sense)
|
22
|
+
@sense = sense
|
23
|
+
sense = sense == :min ? 0 : 1
|
24
|
+
LPSolve.set_sense @ptr, sense
|
25
|
+
end
|
26
|
+
|
27
|
+
# Optimize the model
|
28
|
+
def optimize
|
29
|
+
# Ensure pending variables and constraints are added
|
30
|
+
update
|
31
|
+
|
32
|
+
# Run the solver and save the status for later
|
33
|
+
@status = LPSolve.solve @ptr
|
34
|
+
end
|
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
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_variable_bounds(var_index, lb, ub)
|
65
|
+
ret = LPSolve.set_bounds @ptr, var_index, lb, ub
|
66
|
+
fail if ret != 1
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
# Add multiple variables to the model simultaneously
|
72
|
+
def add_variables(vars)
|
73
|
+
# Allocate space for the new variables
|
74
|
+
ret = LPSolve.resize_lp(@ptr, @constraints.length,
|
75
|
+
@variables.length + vars.length)
|
76
|
+
fail if ret != 1
|
77
|
+
|
78
|
+
# Store all the variables in the model
|
79
|
+
vars.each { |var| store_variable var }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Add multiple constraints at once
|
83
|
+
def add_constraints(constrs)
|
84
|
+
ret = LPSolve.resize_lp(@ptr, @constraints.length + constrs.length,
|
85
|
+
@variables.length)
|
86
|
+
fail if ret != 1
|
87
|
+
|
88
|
+
constrs.each { |constr| store_constraint constr }
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Save the constraint to the model and update the constraint pointers
|
94
|
+
def store_constraint(constr)
|
95
|
+
# Update the constraint to track the index in the model
|
96
|
+
index = LPSolve.get_Nrows(@ptr)
|
97
|
+
constr.instance_variable_set :@model, self
|
98
|
+
constr.instance_variable_set :@index, index
|
99
|
+
@constr_count += 1
|
100
|
+
|
101
|
+
# Set constraint properties
|
102
|
+
if constr.name
|
103
|
+
ret = LPSolve.set_row_name(@ptr, index, constr.name)
|
104
|
+
fail if ret != 1
|
105
|
+
end
|
106
|
+
|
107
|
+
# Determine the correct constant for the type of constraint
|
108
|
+
case constr.sense
|
109
|
+
when :==
|
110
|
+
type = LPSolve::EQ
|
111
|
+
when :>=
|
112
|
+
type = LPSolve::GE
|
113
|
+
when :<=
|
114
|
+
type = LPSolve::LE
|
115
|
+
end
|
116
|
+
|
117
|
+
# Initialize arrays used to hold the coefficients for each variable
|
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
|
+
|
135
|
+
@constraints << constr
|
136
|
+
end
|
137
|
+
|
138
|
+
# Save the variable to the model and update the variable pointers
|
139
|
+
def store_variable(var)
|
140
|
+
# Update the variable to track the index in the model
|
141
|
+
index = @var_count + 1
|
142
|
+
var.instance_variable_set :@model, self
|
143
|
+
var.instance_variable_set :@index, index
|
144
|
+
@var_count += 1
|
145
|
+
|
146
|
+
ret = LPSolve.add_columnex @ptr, 0, nil, nil
|
147
|
+
fail if ret != 1
|
148
|
+
|
149
|
+
# Set variable properties
|
150
|
+
case var.type
|
151
|
+
when :integer
|
152
|
+
ret = LPSolve.set_int @ptr, index, 1
|
153
|
+
when :binary
|
154
|
+
ret = LPSolve.set_binary @ptr, index, 1
|
155
|
+
when :continuous
|
156
|
+
ret = LPSolve.set_int @ptr, index, 0
|
157
|
+
else
|
158
|
+
fail type
|
159
|
+
end
|
160
|
+
fail if ret != 1
|
161
|
+
|
162
|
+
ret = LPSolve.set_col_name(@ptr, index, var.name)
|
163
|
+
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
|
+
end
|
172
|
+
end
|
173
|
+
end
|
data/lib/mipper/model.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module MIPPeR
|
2
|
+
class Model
|
3
|
+
attr_reader :variables, :constraints
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@sense = :min
|
7
|
+
|
8
|
+
@variables = []
|
9
|
+
@constraints = []
|
10
|
+
|
11
|
+
@pending_variables = []
|
12
|
+
@pending_constraints = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add new objects (variables and constraints) to the model
|
16
|
+
def <<(obj)
|
17
|
+
case obj
|
18
|
+
when Variable
|
19
|
+
@pending_variables << obj
|
20
|
+
when Constraint
|
21
|
+
@pending_constraints << obj
|
22
|
+
else
|
23
|
+
fail TypeError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Update the model
|
28
|
+
def update
|
29
|
+
if @pending_variables.length == 1
|
30
|
+
add_variable @pending_variables.first
|
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 = []
|
42
|
+
end
|
43
|
+
|
44
|
+
# Write the model to a file
|
45
|
+
def write(filename)
|
46
|
+
fail NotImplementedError
|
47
|
+
end
|
48
|
+
|
49
|
+
# Set the sense of the model
|
50
|
+
def sense=(sense)
|
51
|
+
fail NotImplementedError
|
52
|
+
end
|
53
|
+
|
54
|
+
# Optimize the model
|
55
|
+
def optimize
|
56
|
+
fail NotImplementedError
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get the status of the model
|
60
|
+
def status
|
61
|
+
:unknown
|
62
|
+
end
|
63
|
+
|
64
|
+
# Compute an irreducible inconsistent subsytem for the model
|
65
|
+
def compute_IIS
|
66
|
+
fail NotImplementedError
|
67
|
+
end
|
68
|
+
|
69
|
+
# The value of the objective function
|
70
|
+
def objective_value
|
71
|
+
fail NotImplementedError
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def add_variable(var)
|
77
|
+
add_variables([var])
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_constraint(constr)
|
81
|
+
add_constraints([constr])
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_variable_bounds(var_index, ub, lb)
|
85
|
+
fail NotImplementedError
|
86
|
+
end
|
87
|
+
|
88
|
+
def variable_value(var)
|
89
|
+
fail NotImplementedError
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/mipper/util.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
module MIPPeR
|
2
|
+
class Variable
|
3
|
+
attr_accessor :constraints
|
4
|
+
attr_reader :lower_bound, :upper_bound, :coefficient, :type, :name, :model,
|
5
|
+
:index
|
6
|
+
|
7
|
+
def initialize(lb, ub, coeff, type, name = nil)
|
8
|
+
@lower_bound = lb
|
9
|
+
@upper_bound = ub
|
10
|
+
@coefficient = coeff
|
11
|
+
@type = type
|
12
|
+
@name = name
|
13
|
+
@constraints = []
|
14
|
+
|
15
|
+
# These will be populated when this is added to a model
|
16
|
+
@model = nil
|
17
|
+
@index = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set the variable lower bound
|
21
|
+
def lower_bound=(lb)
|
22
|
+
@model.set_variable_bounds @index, lb, @upper_bound unless @model.nil?
|
23
|
+
@lower_bound = lb
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set the variable upper bound
|
27
|
+
def upper_bound=(ub)
|
28
|
+
@model.set_variable_bounds @index, @lower_bound, ub unless @model.nil?
|
29
|
+
@upper_bound = ub
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get the final value of this variable
|
33
|
+
def value
|
34
|
+
# Model must be solved to have a value
|
35
|
+
return nil unless @model && @model.status == :optimized
|
36
|
+
|
37
|
+
value = @model.variable_value self
|
38
|
+
|
39
|
+
case @type
|
40
|
+
when :integer
|
41
|
+
value.round
|
42
|
+
when :binary
|
43
|
+
[false, true][value.round]
|
44
|
+
else
|
45
|
+
value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create a {LinExpr} consisting of a single term
|
50
|
+
# which is this variable multiplied by a constant
|
51
|
+
def *(coeff)
|
52
|
+
fail TypeError unless coeff.is_a? Numeric
|
53
|
+
|
54
|
+
LinExpr.new({ self => coeff })
|
55
|
+
end
|
56
|
+
|
57
|
+
def +(other)
|
58
|
+
case other
|
59
|
+
when LinExpr
|
60
|
+
other + self * 1.0
|
61
|
+
when Variable
|
62
|
+
LinExpr.new({self => 1.0, other => 1.0})
|
63
|
+
else
|
64
|
+
fail TypeError
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Produce the name of the variable and the value if the model is solved
|
69
|
+
def inspect
|
70
|
+
if @model && @model.status == :optimized
|
71
|
+
value = self.value
|
72
|
+
else
|
73
|
+
value = '?'
|
74
|
+
end
|
75
|
+
|
76
|
+
"#{@name} = #{value}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/mipper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative 'mipper/util'
|
2
|
+
|
3
|
+
require_relative 'mipper/constraint'
|
4
|
+
require_relative 'mipper/expression'
|
5
|
+
require_relative 'mipper/model'
|
6
|
+
require_relative 'mipper/variable'
|
7
|
+
|
8
|
+
require_relative 'mipper/cbc'
|
9
|
+
require_relative 'mipper/lp_solve'
|
10
|
+
require_relative 'mipper/glpk'
|
11
|
+
require_relative 'mipper/gurobi'
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mipper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Mior
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
description: MIPPeR is a Ruby interface to various mixed integer programming solvers
|
28
|
+
email: michael.mior@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- lib/mipper.rb
|
34
|
+
- lib/mipper/cbc.rb
|
35
|
+
- lib/mipper/cbc/ext.rb
|
36
|
+
- lib/mipper/cbc/model.rb
|
37
|
+
- lib/mipper/constraint.rb
|
38
|
+
- lib/mipper/expression.rb
|
39
|
+
- lib/mipper/glpk.rb
|
40
|
+
- lib/mipper/glpk/ext.rb
|
41
|
+
- lib/mipper/glpk/ext/constants.rb
|
42
|
+
- lib/mipper/glpk/ext/structs.rb
|
43
|
+
- lib/mipper/glpk/model.rb
|
44
|
+
- lib/mipper/gurobi.rb
|
45
|
+
- lib/mipper/gurobi/env.rb
|
46
|
+
- lib/mipper/gurobi/ext.rb
|
47
|
+
- lib/mipper/gurobi/ext/constants.rb
|
48
|
+
- lib/mipper/gurobi/model.rb
|
49
|
+
- lib/mipper/lp_solve.rb
|
50
|
+
- lib/mipper/lp_solve/ext.rb
|
51
|
+
- lib/mipper/lp_solve/ext/constants.rb
|
52
|
+
- lib/mipper/lp_solve/model.rb
|
53
|
+
- lib/mipper/model.rb
|
54
|
+
- lib/mipper/util.rb
|
55
|
+
- lib/mipper/variable.rb
|
56
|
+
homepage: https://github.com/michaelmior/mipper
|
57
|
+
licenses:
|
58
|
+
- GPLv3
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.4.6
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: A Ruby interface to various MIP solvers
|
80
|
+
test_files: []
|
81
|
+
has_rdoc:
|