mipper 0.0.4
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 +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:
|