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.
@@ -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: