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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f3df4957c76c02dc47894208b573de31ffb70d8e
4
+ data.tar.gz: d83b68ff194145a7dd6ab43f34df6480eef6901f
5
+ SHA512:
6
+ metadata.gz: 3c2fccdb2cfa9fd3796344c09b1335b460446d1a674034fd9143e3400cede2cc6483b386e297d01028ec64deff57d9a7886ea3fbe86a0863de047d266fb37014
7
+ data.tar.gz: f93a8c1ffa8e7ff786ebbcc7fd551deca62506e85bc99d2b5bcc0c14e40c2bf25363c42bb8864b095567c96319f0547491320d376b7329aa13c93842a44550e2
@@ -0,0 +1,36 @@
1
+ require 'ffi'
2
+
3
+ module MIPPeR
4
+ module Cbc
5
+ extend FFI::Library
6
+ ffi_lib_flags :now, :global
7
+ ffi_lib 'z', 'bz2', 'CoinUtils', 'Osi', 'Clp', 'OsiClp', 'Cgl', 'Cbc',
8
+ 'CbcSolver'
9
+
10
+ attach_function :Cbc_newModel, [], :pointer
11
+ attach_function :Cbc_deleteModel, [:pointer], :void
12
+ attach_function :Cbc_loadProblem, [:pointer, :int, :int, :pointer, :pointer,
13
+ :pointer, :pointer, :pointer, :pointer,
14
+ :pointer, :pointer], :void
15
+ attach_function :Cbc_setColName, [:pointer, :int, :string], :void
16
+ attach_function :Cbc_setRowName, [:pointer, :int, :string], :void
17
+ attach_function :Cbc_setObjSense, [:pointer, :double], :void
18
+ attach_function :Cbc_setRowLower, [:pointer, :int, :double], :void
19
+ attach_function :Cbc_setRowUpper, [:pointer, :int, :double], :void
20
+ attach_function :Cbc_setObjCoeff, [:pointer, :int, :double], :void
21
+ attach_function :Cbc_setColLower, [:pointer, :int, :double], :void
22
+ attach_function :Cbc_setColUpper, [:pointer, :int, :double], :void
23
+ attach_function :Cbc_setContinuous, [:pointer, :int], :void
24
+ attach_function :Cbc_setInteger, [:pointer, :int], :void
25
+ attach_function :Cbc_solve, [:pointer], :int
26
+ attach_function :Cbc_getColSolution, [:pointer], :pointer
27
+ attach_function :Cbc_getObjValue, [:pointer], :double
28
+ attach_function :Cbc_status, [:pointer], :int
29
+ attach_function :Cbc_secondaryStatus, [:pointer], :int
30
+ attach_function :Cbc_printModel, [:pointer, :string], :void
31
+ attach_function :Cbc_isProvenOptimal, [:pointer], :int
32
+ attach_function :Cbc_isProvenInfeasible, [:pointer], :int
33
+ attach_function :Cbc_isContinuousUnbounded, [:pointer], :int
34
+ attach_function :Cbc_setParameter, [:pointer, :string, :string], :void
35
+ end
36
+ end
@@ -0,0 +1,174 @@
1
+ module MIPPeR
2
+ class CbcModel < 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 Cbc.Cbc_newModel,
13
+ Cbc.method(:Cbc_deleteModel)
14
+ Cbc.Cbc_setParameter @ptr, 'logLevel', '0'
15
+ end
16
+
17
+ # Set the sense of the model
18
+ def sense=(sense)
19
+ @sense = sense
20
+ sense = sense == :min ? 1 : -1
21
+ Cbc.Cbc_setObjSense @ptr, sense
22
+ end
23
+
24
+ # Optimize the model
25
+ def optimize
26
+ # Ensure pending variables and constraints are added
27
+ update
28
+
29
+ # Run the solver and save the status for later
30
+ Cbc.Cbc_solve @ptr
31
+ fail if Cbc.Cbc_status(@ptr) != 0
32
+
33
+ # Check and store the model status
34
+ if Cbc.Cbc_isProvenOptimal(@ptr) == 1
35
+ @status = :optimized
36
+ elsif Cbc.Cbc_isProvenInfeasible(@ptr) == 1 or
37
+ Cbc.Cbc_isContinuousUnbounded(@ptr) == 1
38
+ @status = :invalid
39
+ else
40
+ @status = :unknown
41
+ end
42
+ end
43
+
44
+ # Get the status of the model
45
+ def status
46
+ @status
47
+ end
48
+
49
+ # The value of the objective function
50
+ def objective_value
51
+ Cbc.Cbc_getObjValue @ptr
52
+ end
53
+
54
+ # Get the value of a variable from the model
55
+ def variable_value(var)
56
+ dblptr = Cbc.Cbc_getColSolution @ptr
57
+ dblptr.read_array_of_double(@variables.length)[var.index]
58
+ end
59
+
60
+ def set_variable_bounds(var_index, lb, ub)
61
+ Cbc.Cbc_setColLower @ptr, var_index, lb
62
+ Cbc.Cbc_setColUpper @ptr, var_index, ub
63
+ end
64
+
65
+ protected
66
+
67
+ # Add multiple variables to the model simultaneously
68
+ def add_variables(vars)
69
+ # Store all the variables in the model
70
+ # Most of the work will be done when we add the constraints
71
+ vars.each do |var|
72
+ var.instance_variable_set :@model, self
73
+ @variables << var
74
+ end
75
+ end
76
+
77
+ # Add multiple constraints at once
78
+ def add_constraints(constrs)
79
+ # Store the index which will be used for each constraint
80
+ constrs.each do |constr|
81
+ constr.instance_variable_set :@index, @constr_count
82
+ @constr_count += 1
83
+ end
84
+
85
+ # Construct a matrix of non-zero values in CSC format
86
+ start = []
87
+ index = []
88
+ value = []
89
+ col_start = 0
90
+ @variables.each do |var|
91
+ # Mark the start of this column
92
+ start << col_start
93
+
94
+ var.constraints.each do |constr|
95
+ col_start += 1
96
+ index << constr.instance_variable_get(:@index)
97
+ value << constr.expression.terms[var]
98
+ end
99
+ end
100
+ start << col_start
101
+
102
+ start_buffer = FFI::MemoryPointer.new :int, start.length
103
+ start_buffer.write_array_of_int start
104
+ index_buffer = FFI::MemoryPointer.new :int, index.length
105
+ index_buffer.write_array_of_int index
106
+ value_buffer = FFI::MemoryPointer.new :double, value.length
107
+ value_buffer.write_array_of_double value
108
+
109
+ Cbc.Cbc_loadProblem @ptr, @variables.length, constrs.length,
110
+ start_buffer, index_buffer, value_buffer,
111
+ nil, nil, nil, nil, nil
112
+
113
+ constrs.each do |constr|
114
+ store_constraint constr
115
+ @constraints << constr
116
+ end
117
+
118
+ # We store variables now since they didn't exist earlier
119
+ @variables.each_with_index do |var, i|
120
+ var.instance_variable_set(:@index, i)
121
+ store_variable var
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ # Save the constraint to the model and update the constraint pointers
128
+ def store_constraint(constr)
129
+ # Update the constraint to track the index in the model
130
+ index = constr.instance_variable_get(:@index)
131
+ constr.instance_variable_set :@model, self
132
+
133
+ # Set constraint properties
134
+ Cbc.Cbc_setRowName(@ptr, index, constr.name) unless constr.name.nil?
135
+
136
+ case constr.sense
137
+ when :==
138
+ lb = ub = constr.rhs
139
+ when :>=
140
+ lb = constr.rhs
141
+ ub = Float::INFINITY
142
+ when :<=
143
+ lb = -Float::INFINITY
144
+ ub = constr.rhs
145
+ end
146
+ Cbc.Cbc_setRowLower @ptr, index, lb
147
+ Cbc.Cbc_setRowUpper @ptr, index, ub
148
+ end
149
+
150
+ # Set the properties of a variable in the model
151
+ def store_variable(var)
152
+ # Force the correct bounds since we can't explicitly specify binary
153
+ if var.type == :binary
154
+ var.instance_variable_set(:@lower_bound, 0)
155
+ var.instance_variable_set(:@upper_bound, 1)
156
+ end
157
+
158
+ Cbc.Cbc_setColLower @ptr, var.index, var.lower_bound
159
+ Cbc.Cbc_setColUpper @ptr, var.index, var.upper_bound
160
+ Cbc.Cbc_setObjCoeff @ptr, var.index, var.coefficient
161
+
162
+ case var.type
163
+ when :continuous
164
+ Cbc.Cbc_setContinuous @ptr, var.index
165
+ when :integer, :binary
166
+ Cbc.Cbc_setInteger @ptr, var.index
167
+ else
168
+ fail :type
169
+ end
170
+
171
+ Cbc.Cbc_setColName(@ptr, var.index, var.name) unless var.name.nil?
172
+ end
173
+ end
174
+ end
data/lib/mipper/cbc.rb ADDED
@@ -0,0 +1,3 @@
1
+ require_relative 'cbc/ext'
2
+
3
+ require_relative 'cbc/model'
@@ -0,0 +1,17 @@
1
+ module MIPPeR
2
+ class Constraint
3
+ attr_reader :expression, :sense, :rhs, :name
4
+
5
+ def initialize(expr, sense, rhs, name = nil)
6
+ @expression = expr
7
+ @sense = sense
8
+ @rhs = rhs
9
+ @name = name
10
+
11
+ # Store this constraint for each associated variable
12
+ @expression.terms.each_key do |var|
13
+ var.constraints << self
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ module MIPPeR
2
+ class LinExpr
3
+ attr_reader :terms
4
+
5
+ def initialize(terms = {})
6
+ @terms = terms
7
+ end
8
+
9
+ # Add two {LinExpr}s
10
+ def +(other)
11
+ case other
12
+ when LinExpr
13
+ # Add the terms of two expressions
14
+ # For now, this assumes variables are not duplicated
15
+ LinExpr.new(@terms.merge(other.terms) { |_, c1, c2| c1 + c2 })
16
+ when Variable
17
+ # Add the variable to the expression
18
+ self + other * 1.0
19
+ else
20
+ fail TypeError
21
+ end
22
+ end
23
+
24
+ # Add terms from the other expression to this one
25
+ def add(other)
26
+ case other
27
+ when LinExpr
28
+ @terms.merge!(other.terms) { |_, c1, c2| c1 + c2 }
29
+ when Variable
30
+ if @terms.key? other
31
+ @terms[other] += 1.0
32
+ else
33
+ @terms[other] = 1.0
34
+ end
35
+ else
36
+ fail TypeError
37
+ end
38
+
39
+ self
40
+ end
41
+
42
+ # Produce a string representing the expression
43
+ def inspect
44
+ @terms.map do |var, coeff|
45
+ # Skip if the coefficient is zero or the value is zero
46
+ value = var.value
47
+ next if coeff == 0 || value == 0 || value == false
48
+
49
+ coeff == 1 ? var.name : "#{var.name} * #{coeff}"
50
+ end.compact.join(' + ')
51
+ end
52
+ end
53
+ end