mipper 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,4 @@
1
+ require_relative 'lp_solve/ext'
2
+
3
+ require_relative 'lp_solve/model'
4
+
@@ -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
@@ -0,0 +1,5 @@
1
+ class Integer
2
+ def finite?
3
+ true
4
+ end
5
+ end
@@ -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: