opl 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/opl.rb +478 -456
  2. metadata +1 -1
data/lib/opl.rb CHANGED
@@ -1,533 +1,554 @@
1
1
  require "rglpk"
2
2
 
3
3
  #TODO
4
- #1.0
5
- #object structure
6
-
7
- #2.1
4
+ #1.1
8
5
  #setting variable values in constraints
9
6
  #and then using that variable value in further constraints
10
7
  #Look into fixed variables in the glp documentation
11
- #2.2
8
+ #1.2
12
9
  #summing of variables
13
10
  #e.g. x1 + x1 <= 3
14
- #2.3
11
+
12
+ #1.3
15
13
  #allow a POSITIVE: x option or NEGATIVE: x
16
14
 
17
- #3.0
15
+ #1.4
16
+ #float coefficients
17
+
18
+ #2.0
19
+ #data arrays
20
+
21
+ #2.1
18
22
  #a matrix representation of the solution if using
19
23
  #sub notation
20
- #data arrays
21
24
 
22
- #4.0
25
+ #3.0
26
+ #multiple level sub notation e.g. x[1][[3]]
27
+
28
+ #3.1
23
29
  #make sure extreme cases of foralls and sums
24
30
  #are handled
25
- #multiple level sub notation e.g. x[1][[3]]
26
31
 
27
- #5.0
32
+ #4.0
28
33
  #absolute value: abs()
34
+
35
+ #4.1
29
36
  #if --> then statements
37
+
38
+ #4.2
30
39
  #or statements
31
- #piecewise statements
32
40
 
33
- #write as module
41
+ #4.3
42
+ #piecewise statements
34
43
 
35
44
  $default_epsilon = 0.01
36
45
 
37
- def sides(equation)
38
- if equation.include?("<=")
39
- char = "<="
40
- elsif equation.include?(">=")
41
- char = ">="
42
- elsif equation.include?("<")
43
- char = "<"
44
- elsif equation.include?(">")
45
- char = ">"
46
- elsif equation.include?("=")
47
- char = "="
46
+ class String
47
+ def paren_to_array
48
+ #in: "(2..5)"
49
+ #out: "[2,3,4,5]"
50
+ text = self
51
+ start = text[1].to_i
52
+ stop = text[-2].to_i
53
+ (start..stop).map{|i|i}.to_s
54
+ end
55
+
56
+ def sub_paren_with_array
57
+ text = self
58
+ targets = text.scan(/\([\d]+\.\.[\d]+\)/)
59
+ targets.each do |target|
60
+ text = text.gsub(target, target.paren_to_array)
61
+ end
62
+ return(text)
48
63
  end
49
- sides = equation.split(char)
50
- {:lhs => sides[0], :rhs => sides[1]}
51
64
  end
52
65
 
53
- def add_ones(equation)
54
- equation = "#"+equation
55
- equation.scan(/[#+-][a-z]/).each do |p|
56
- if p.include?("+")
57
- q = p.gsub("+", "+1*")
58
- elsif p.include?("-")
59
- q = p.gsub("-","-1*")
60
- elsif p.include?("#")
61
- q = p.gsub("#","#1*")
62
- end
63
- equation = equation.gsub(p,q)
64
- end
65
- equation.gsub("#","")
66
- end
66
+ class OPL
67
+ class Helper
68
+ def self.mass_product(array_of_arrays, base=[])
69
+ return(base) if array_of_arrays.empty?
70
+ array = array_of_arrays[0]
71
+ new_array_of_arrays = array_of_arrays[1..-1]
72
+ if base==[]
73
+ self.mass_product(new_array_of_arrays, array)
74
+ else
75
+ self.mass_product(new_array_of_arrays, base.product(array).map{|e|e.flatten})
76
+ end
77
+ end
67
78
 
68
- def paren_to_array(text)
69
- #in: "(2..5)"
70
- #out: "[2,3,4,5]"
71
- start = text[1].to_i
72
- stop = text[-2].to_i
73
- (start..stop).map{|i|i}.to_s
74
- end
79
+ def self.forall(text)
80
+ #need to be able to handle sums inside here
81
+ #in: "i in (0..2), x[i] <= 5"
82
+ #out: ["x[0] <= 5", "x[1] <= 5", "x[2] <= 5"]
83
+ text = text.sub_paren_with_array
84
+ #text = sub_paren_with_array(text)
85
+ final_constraints = []
86
+ indices = text.scan(/[a-z] in/).map{|sc|sc[0]}
87
+ values = text.scan(/\s\[[\-\s\d+,]+\]/).map{|e|e.gsub(" ", "").scan(/[\-\d]+/)}
88
+ index_value_pairs = indices.zip(values)
89
+ variable = text.scan(/[a-z]\[/)[0].gsub("[","")
90
+ #will need to make this multiple variables??
91
+ #or is this even used at all????
92
+ value_combinations = OPL::Helper.mass_product(values)
93
+ value_combinations.each_index do |vc_index|
94
+ value_combination = value_combinations[vc_index]
95
+ value_combination = [value_combination] unless value_combination.is_a?(Array)
96
+ if text.include?("sum")
97
+ constraint = "sum"+text.split("sum")[1..-1].join("sum")
98
+ else
99
+ constraint = text.split(",")[-1].gsub(" ","")
100
+ end
101
+ e = constraint
102
+ value_combination.each_index do |i|
103
+ index = indices[i]
104
+ value = value_combination[i]
105
+ e = e.gsub("("+index, "("+value)
106
+ e = e.gsub(index+")", value+")")
107
+ e = e.gsub("["+index, "["+value)
108
+ e = e.gsub(index+"]", value+"]")
109
+ e = e.gsub("=>"+index, "=>"+value)
110
+ e = e.gsub("<="+index, "<="+value)
111
+ e = e.gsub(">"+index, ">"+value)
112
+ e = e.gsub("<"+index, "<"+value)
113
+ e = e.gsub("="+index, "="+value)
114
+ e = e.gsub("=> "+index, "=> "+value)
115
+ e = e.gsub("<= "+index, "<= "+value)
116
+ e = e.gsub("> "+index, "> "+value)
117
+ e = e.gsub("< "+index, "< "+value)
118
+ e = e.gsub("= "+index, "= "+value)
119
+ end
120
+ final_constraints += [e]
121
+ end
122
+ final_constraints
123
+ end
75
124
 
76
- def sub_paren_with_array(text)
77
- targets = text.scan(/\([\d]+\.\.[\d]+\)/)
78
- targets.each do |target|
79
- text = text.gsub(target, paren_to_array(target))
80
- end
81
- return(text)
82
- end
125
+ def self.sub_forall(equation, indexvalues={:indices => [], :values => []})
126
+ #in: "forall(i in (0..2), x[i] <= 5)"
127
+ #out: ["x[0] <= 5", "x[1] <= 5", "x[2] <= 5"]
128
+ return equation unless equation.include?("forall")
129
+ foralls = (equation+"#").split("forall(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}
130
+ constraints = []
131
+ if foralls.empty?
132
+ return(equation)
133
+ else
134
+ foralls.each do |text|
135
+ constraints << self.forall(text)
136
+ end
137
+ return(constraints.flatten)
138
+ end
139
+ end
83
140
 
84
- def mass_product(array_of_arrays, base=[])
85
- return(base) if array_of_arrays.empty?
86
- array = array_of_arrays[0]
87
- new_array_of_arrays = array_of_arrays[1..-1]
88
- if base==[]
89
- mass_product(new_array_of_arrays, array)
90
- else
91
- mass_product(new_array_of_arrays, base.product(array).map{|e|e.flatten})
92
- end
93
- end
141
+ def self.sides(text)
142
+ equation = text
143
+ if equation.include?("<=")
144
+ char = "<="
145
+ elsif equation.include?(">=")
146
+ char = ">="
147
+ elsif equation.include?("<")
148
+ char = "<"
149
+ elsif equation.include?(">")
150
+ char = ">"
151
+ elsif equation.include?("=")
152
+ char = "="
153
+ end
154
+ sides = equation.split(char)
155
+ {:lhs => sides[0], :rhs => sides[1]}
156
+ end
94
157
 
95
- def forall(text)
96
- #need to be able to handle sums inside here
97
- #in: "i in (0..2), x[i] <= 5"
98
- #out: ["x[0] <= 5", "x[1] <= 5", "x[2] <= 5"]
99
- text = sub_paren_with_array(text)
100
- final_constraints = []
101
- indices = text.scan(/[a-z] in/).map{|sc|sc[0]}
102
- values = text.scan(/\s\[[\-\s\d+,]+\]/).map{|e|e.gsub(" ", "").scan(/[\-\d]+/)}
103
- index_value_pairs = indices.zip(values)
104
- variable = text.scan(/[a-z]\[/)[0].gsub("[","")
105
- #will need to make this multiple variables??
106
- #or is this even used at all????
107
- value_combinations = mass_product(values)
108
- value_combinations.each_index do |vc_index|
109
- value_combination = value_combinations[vc_index]
110
- value_combination = [value_combination] unless value_combination.is_a?(Array)
111
- if text.include?("sum")
112
- constraint = "sum"+text.split("sum")[1..-1].join("sum")
113
- else
114
- constraint = text.split(",")[-1].gsub(" ","")
115
- end
116
- e = constraint
117
- value_combination.each_index do |i|
118
- index = indices[i]
119
- value = value_combination[i]
120
- e = e.gsub("("+index, "("+value)
121
- e = e.gsub(index+")", value+")")
122
- e = e.gsub("["+index, "["+value)
123
- e = e.gsub(index+"]", value+"]")
124
- e = e.gsub("=>"+index, "=>"+value)
125
- e = e.gsub("<="+index, "<="+value)
126
- e = e.gsub(">"+index, ">"+value)
127
- e = e.gsub("<"+index, "<"+value)
128
- e = e.gsub("="+index, "="+value)
129
- e = e.gsub("=> "+index, "=> "+value)
130
- e = e.gsub("<= "+index, "<= "+value)
131
- e = e.gsub("> "+index, "> "+value)
132
- e = e.gsub("< "+index, "< "+value)
133
- e = e.gsub("= "+index, "= "+value)
134
- end
135
- final_constraints += [e]
136
- end
137
- final_constraints
138
- end
158
+ def self.add_ones(text)
159
+ equation = text
160
+ equation = "#"+equation
161
+ equation.scan(/[#+-][a-z]/).each do |p|
162
+ if p.include?("+")
163
+ q = p.gsub("+", "+1*")
164
+ elsif p.include?("-")
165
+ q = p.gsub("-","-1*")
166
+ elsif p.include?("#")
167
+ q = p.gsub("#","#1*")
168
+ end
169
+ equation = equation.gsub(p,q)
170
+ end
171
+ equation.gsub("#","")
172
+ end
139
173
 
140
- def sub_forall(equation, indexvalues={:indices => [], :values => []})
141
- #in: "forall(i in (0..2), x[i] <= 5)"
142
- #out: ["x[0] <= 5", "x[1] <= 5", "x[2] <= 5"]
143
- return equation unless equation.include?("forall")
144
- foralls = (equation+"#").split("forall(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}
145
- constraints = []
146
- if foralls.empty?
147
- return(equation)
148
- else
149
- foralls.each do |text|
150
- constraints << forall(text)
174
+ def self.sum(text, indexvalues={:indices => [], :values => []})
175
+ #in: "i in [0,1], j in [4,-5], 3x[i][j]"
176
+ #out: "3x[0][4] + 3x[0][-5] + 3x[1][4] + 3x[1][-5]"
177
+ text = text.sub_paren_with_array
178
+ #text = sub_paren_with_array(text)
179
+ final_text = ""
180
+ element = text.split(",")[-1].gsub(" ","")
181
+ indices = text.scan(/[a-z] in/).map{|sc|sc[0]}
182
+ input_indices = indexvalues[:indices] - indices
183
+ if not input_indices.empty?
184
+ input_values = input_indices.map{|ii|indexvalues[:values][indexvalues[:indices].index(ii)]}
185
+ else
186
+ input_values = []
187
+ end
188
+ values = text.scan(/\s\[[\-\s\d+,]+\]/).map{|e|e.gsub(" ", "").scan(/[\-\d]+/)}
189
+ indices += input_indices
190
+ values += input_values
191
+ index_value_pairs = indices.zip(values)
192
+ variable = text.scan(/[a-z]\[/)[0].gsub("[","")
193
+ coefficient_a = text.split(",")[-1].split("[")[0].scan(/\-?[\d\*]+[a-z]/)
194
+ if coefficient_a.empty?
195
+ if text.split(",")[-1].split("[")[0].include?("-")
196
+ coefficient = "-1"
197
+ else
198
+ coefficient = "1"
199
+ end
200
+ else
201
+ coefficient = coefficient_a[0].scan(/[\d\-]+/)
202
+ end
203
+ value_combinations = OPL::Helper.mass_product(values)
204
+ value_combinations.each_index do |vc_index|
205
+ value_combination = value_combinations[vc_index]
206
+ e = element
207
+ value_combination = [value_combination] unless value_combination.is_a?(Array)
208
+ value_combination.each_index do |i|
209
+ index = indices[i]
210
+ value = value_combination[i]
211
+ e = e.gsub("("+index, "("+value)
212
+ e = e.gsub(index+")", value+")")
213
+ e = e.gsub("["+index, "["+value)
214
+ e = e.gsub(index+"]", value+"]")
215
+ e = e.gsub("=>"+index, "=>"+value)
216
+ e = e.gsub("<="+index, "<="+value)
217
+ e = e.gsub(">"+index, ">"+value)
218
+ e = e.gsub("<"+index, "<"+value)
219
+ e = e.gsub("="+index, "="+value)
220
+ e = e.gsub("=> "+index, "=> "+value)
221
+ e = e.gsub("<= "+index, "<= "+value)
222
+ e = e.gsub("> "+index, "> "+value)
223
+ e = e.gsub("< "+index, "< "+value)
224
+ e = e.gsub("= "+index, "= "+value)
225
+ end
226
+ e = "+"+e unless (coefficient.include?("-") || vc_index==0)
227
+ final_text += e
228
+ end
229
+ final_text
151
230
  end
152
- return(constraints.flatten)
153
- end
154
- end
155
231
 
156
- def sum(text, indexvalues={:indices => [], :values => []})
157
- #in: "i in [0,1], j in [4,-5], 3x[i][j]"
158
- #out: "3x[0][4] + 3x[0][-5] + 3x[1][4] + 3x[1][-5]"
159
- text = sub_paren_with_array(text)
160
- final_text = ""
161
- element = text.split(",")[-1].gsub(" ","")
162
- indices = text.scan(/[a-z] in/).map{|sc|sc[0]}
163
- input_indices = indexvalues[:indices] - indices
164
- if not input_indices.empty?
165
- input_values = input_indices.map{|ii|indexvalues[:values][indexvalues[:indices].index(ii)]}
166
- else
167
- input_values = []
168
- end
169
- values = text.scan(/\s\[[\-\s\d+,]+\]/).map{|e|e.gsub(" ", "").scan(/[\-\d]+/)}
170
- indices += input_indices
171
- values += input_values
172
- index_value_pairs = indices.zip(values)
173
- variable = text.scan(/[a-z]\[/)[0].gsub("[","")
174
- coefficient_a = text.split(",")[-1].split("[")[0].scan(/\-?[\d\*]+[a-z]/)
175
- if coefficient_a.empty?
176
- if text.split(",")[-1].split("[")[0].include?("-")
177
- coefficient = "-1"
178
- else
179
- coefficient = "1"
232
+ def self.sub_sum(equation, indexvalues={:indices => [], :values => []})
233
+ #in: "sum(i in (0..3), x[i]) <= 100"
234
+ #out: "x[0]+x[1]+x[2]+x[3] <= 100"
235
+ sums = (equation+"#").split("sum(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}.find_all{|eeee|!eeee.include?("forall")}
236
+ sums.each do |text|
237
+ e = text
238
+ unless indexvalues[:indices].empty?
239
+ indexvalues[:indices].each_index do |i|
240
+ index = indexvalues[:indices][i]
241
+ value = indexvalues[:values][i].to_s
242
+ e = e.gsub("("+index, "("+value)
243
+ e = e.gsub(index+")", value+")")
244
+ e = e.gsub("["+index, "["+value)
245
+ e = e.gsub(index+"]", value+"]")
246
+ e = e.gsub("=>"+index, "=>"+value)
247
+ e = e.gsub("<="+index, "<="+value)
248
+ e = e.gsub(">"+index, ">"+value)
249
+ e = e.gsub("<"+index, "<"+value)
250
+ e = e.gsub("="+index, "="+value)
251
+ e = e.gsub("=> "+index, "=> "+value)
252
+ e = e.gsub("<= "+index, "<= "+value)
253
+ e = e.gsub("> "+index, "> "+value)
254
+ e = e.gsub("< "+index, "< "+value)
255
+ e = e.gsub("= "+index, "= "+value)
256
+ end
257
+ end
258
+ equation = equation.gsub(text, e)
259
+ result = self.sum(text)
260
+ equation = equation.gsub("sum("+text+")", result)
261
+ end
262
+ return(equation)
180
263
  end
181
- else
182
- coefficient = coefficient_a[0].scan(/[\d\-]+/)
183
- end
184
- value_combinations = mass_product(values)
185
- value_combinations.each_index do |vc_index|
186
- value_combination = value_combinations[vc_index]
187
- e = element
188
- value_combination = [value_combination] unless value_combination.is_a?(Array)
189
- value_combination.each_index do |i|
190
- index = indices[i]
191
- value = value_combination[i]
192
- e = e.gsub("("+index, "("+value)
193
- e = e.gsub(index+")", value+")")
194
- e = e.gsub("["+index, "["+value)
195
- e = e.gsub(index+"]", value+"]")
196
- e = e.gsub("=>"+index, "=>"+value)
197
- e = e.gsub("<="+index, "<="+value)
198
- e = e.gsub(">"+index, ">"+value)
199
- e = e.gsub("<"+index, "<"+value)
200
- e = e.gsub("="+index, "="+value)
201
- e = e.gsub("=> "+index, "=> "+value)
202
- e = e.gsub("<= "+index, "<= "+value)
203
- e = e.gsub("> "+index, "> "+value)
204
- e = e.gsub("< "+index, "< "+value)
205
- e = e.gsub("= "+index, "= "+value)
206
- end
207
- e = "+"+e unless (coefficient.include?("-") || vc_index==0)
208
- final_text += e
209
- end
210
- final_text
211
- end
212
264
 
213
- def sub_sum(equation, indexvalues={:indices => [], :values => []})
214
- #in: "sum(i in (0..3), x[i]) <= 100"
215
- #out: "x[0]+x[1]+x[2]+x[3] <= 100"
216
- sums = (equation+"#").split("sum(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}.find_all{|eeee|!eeee.include?("forall")}
217
- sums.each do |text|
218
- e = text
219
- unless indexvalues[:indices].empty?
220
- indexvalues[:indices].each_index do |i|
221
- index = indexvalues[:indices][i]
222
- value = indexvalues[:values][i].to_s
223
- e = e.gsub("("+index, "("+value)
224
- e = e.gsub(index+")", value+")")
225
- e = e.gsub("["+index, "["+value)
226
- e = e.gsub(index+"]", value+"]")
227
- e = e.gsub("=>"+index, "=>"+value)
228
- e = e.gsub("<="+index, "<="+value)
229
- e = e.gsub(">"+index, ">"+value)
230
- e = e.gsub("<"+index, "<"+value)
231
- e = e.gsub("="+index, "="+value)
232
- e = e.gsub("=> "+index, "=> "+value)
233
- e = e.gsub("<= "+index, "<= "+value)
234
- e = e.gsub("> "+index, "> "+value)
235
- e = e.gsub("< "+index, "< "+value)
236
- e = e.gsub("= "+index, "= "+value)
265
+ def self.coefficients(text)#parameter is one side of the equation
266
+ equation = OPL::Helper.add_ones(text)
267
+ if equation[0]=="-"
268
+ equation.scan(/[+-][\d\.]+/)
269
+ else
270
+ ("#"+equation).scan(/[#+-][\d\.]+/).map{|e|e.gsub("#","+")}
237
271
  end
238
272
  end
239
- equation = equation.gsub(text, e)
240
- result = sum(text)
241
- equation = equation.gsub("sum("+text+")", result)
242
- end
243
- return(equation)
244
- end
245
273
 
246
- def coefficients(equation)#parameter is one side of the equation
247
- equation = add_ones(equation)
248
- if equation[0]=="-"
249
- equation.scan(/[+-][\d\.]+/)
250
- else
251
- ("#"+equation).scan(/[#+-][\d\.]+/).map{|e|e.gsub("#","+")}
252
- end
253
- end
274
+ def self.variables(text)#parameter is one side of the equation
275
+ equation = OPL::Helper.add_ones(text)
276
+ equation.scan(/[a-z]+[\[\]\d]*/)
277
+ end
254
278
 
255
- def variables(equation)#parameter is one side of the equation
256
- equation = add_ones(equation)
257
- equation.scan(/[a-z]+[\[\]\d]*/)
258
- end
279
+ def self.get_all_vars(constraints)
280
+ all_vars = []
281
+ constraints.each do |constraint|
282
+ constraint = constraint.gsub(" ", "")
283
+ value = constraint.split(":")[1] || constraint
284
+ all_vars << self.variables(value)
285
+ end
286
+ all_vars.flatten.uniq
287
+ end
259
288
 
260
- class LinearProgram
261
- attr_accessor :objective
262
- attr_accessor :constraints
263
- attr_accessor :rows
264
- attr_accessor :solution
265
- attr_accessor :formatted_constraints
266
- attr_accessor :rglpk_object
267
- attr_accessor :solver
268
-
269
- def initialize(objective, constraints)
270
- @objective = objective
271
- @constraints = constraints
272
- @rows = []
273
- end
274
- end
289
+ def self.get_constants(text)
290
+ #in: "-8 + x + y + 3"
291
+ #out: "[-8, +3]"
292
+ text = text.gsub(" ","")
293
+ text = text+"#"
294
+ cs = []
295
+ potential_constants = text.scan(/[\d\.]+[^a-z^\[^\]^\d^\.^\)]/)
296
+ #potential_constants = text.scan(/\d+[^a-z^\[^\]^\d]/)
297
+ constants = potential_constants.find_all{|c|![*('a'..'z'),*('A'..'Z')].include?(text[text.index(c)-1])}
298
+ constants.each do |constant|
299
+ c = constant.scan(/[\d\.]+/)[0]
300
+ index = text.index(constant)
301
+ if index == 0
302
+ c = "+"+c
303
+ else
304
+ c = text.scan(/[\-\+]#{constant}/)[0]
305
+ end
306
+ cs << c.scan(/[\-\+][\d\.]+/)[0]
307
+ end
308
+ return({:formatted => cs, :unformatted => constants})
309
+ end
275
310
 
276
- class Objective
277
- attr_accessor :function
278
- attr_accessor :optimization#minimize, maximize, equals
279
- attr_accessor :variable_coefficient_pairs
280
- attr_accessor :optimized_value
311
+ def self.put_constants_on_rhs(text)
312
+ #in: "-8 + x + y + 3 <= 100"
313
+ #out: "x + y <= 100 + 5"
314
+ text = text.gsub(" ","")
315
+ s = OPL::Helper.sides(text)
316
+ constants_results = OPL::Helper.get_constants(s[:lhs])
317
+ constants = []
318
+ constants_results[:formatted].each_index do |i|
319
+ formatted_constant = constants_results[:formatted][i]
320
+ unformatted_constant = constants_results[:unformatted][i]
321
+ unless unformatted_constant.include?("*")
322
+ constants << formatted_constant
323
+ end
324
+ end
325
+ unless constants.empty?
326
+ sum = constants.map{|cc|cc.to_f}.inject("+").to_s
327
+ if sum.include?("-")
328
+ sum = sum.gsub("-","+")
329
+ else
330
+ sum = "-"+sum
331
+ end
332
+ lhs = s[:lhs].gsub(" ","")+"#"
333
+ constants_results[:unformatted].each do |constant|
334
+ index = lhs.index(constant)
335
+ if index == 0
336
+ lhs = lhs[(constant.size-1)..(lhs.size-1)]
337
+ else
338
+ lhs = lhs[0..(index-2)]+lhs[(index+(constant.size-1))..(lhs.size-1)]
339
+ end
340
+ end
341
+ text = text.gsub(s[:lhs], lhs[0..-2])
342
+ text += sum
343
+ end
344
+ return(text)
345
+ end
281
346
 
282
- def initialize(function, optimization)
283
- @function = function
284
- @optimization = optimization
285
- end
286
- end
347
+ def self.sum_constants(text)
348
+ #in: "100+ 10-3"
349
+ #out: "107"
350
+ constants = self.get_constants(text)[:formatted]
351
+ if constants.to_s.include?(".")
352
+ constants.map{|c|c.to_f}.inject("+").to_s
353
+ else
354
+ constants.map{|c|c.to_i}.inject("+").to_s
355
+ end
356
+ end
287
357
 
288
- class Row
289
- attr_accessor :name
290
- attr_accessor :constraint
291
- attr_accessor :lower_bound
292
- attr_accessor :upper_bound
293
- attr_accessor :variable_coefficient_pairs
294
- attr_accessor :epsilon
295
-
296
- def initialize(name, lower_bound, upper_bound, epsilon)
297
- @name = name
298
- @lower_bound = lower_bound
299
- @upper_bound = upper_bound
300
- @variable_coefficient_pairs = []
301
- @epsilon = epsilon
302
- end
303
- end
358
+ def self.sub_rhs_with_summed_constants(constraint)
359
+ rhs = OPL::Helper.sides(constraint)[:rhs]
360
+ constraint.gsub(rhs, self.sum_constants(rhs))
361
+ end
304
362
 
305
- class VariableCoefficientPair
306
- attr_accessor :variable
307
- attr_accessor :coefficient
308
- attr_accessor :variable_type
363
+ def self.get_coefficient_variable_pairs(text)
364
+ text.scan(/\d*[\*]*[a-z]\[*\d*\]*/)
365
+ end
309
366
 
310
- def initialize(variable, coefficient, variable_type=1)
311
- @variable = variable
312
- @coefficient = coefficient
313
- @variable_type = variable_type
314
- end
315
- end
367
+ def self.operator(constraint)
368
+ if constraint.include?(">=")
369
+ ">="
370
+ elsif constraint.include?("<=")
371
+ "<="
372
+ elsif constraint.include?(">")
373
+ ">"
374
+ elsif constraint.include?("<")
375
+ "<"
376
+ elsif constraint.include?("=")
377
+ "="
378
+ end
379
+ end
316
380
 
317
- def get_all_vars(constraints)
318
- all_vars = []
319
- constraints.each do |constraint|
320
- constraint = constraint.gsub(" ", "")
321
- value = constraint.split(":")[1] || constraint
322
- all_vars << variables(value)
323
- end
324
- all_vars.flatten.uniq
325
- end
381
+ def self.put_variables_on_lhs(text)
382
+ #in: "x + y - x[3] <= 3z + 2x[2] - 10"
383
+ #out: "x + y - x[3] - 3z - 2x[2] <= -10"
384
+ text = text.gsub(" ", "")
385
+ s = OPL::Helper.sides(text)
386
+ oper = self.operator(text)
387
+ rhs = s[:rhs]
388
+ lhs = s[:lhs]
389
+ coefficient_variable_pairs = self.get_coefficient_variable_pairs(rhs)
390
+ add_to_left = []
391
+ remove_from_right = []
392
+ coefficient_variable_pairs.each do |cvp|
393
+ index = rhs.index(cvp)
394
+ if index == 0
395
+ add_to_left << "-"+cvp
396
+ remove_from_right << cvp
397
+ else
398
+ if rhs[index-1] == "+"
399
+ add_to_left << "-"+cvp
400
+ remove_from_right << "+"+cvp
401
+ else
402
+ add_to_left << "+"+cvp
403
+ remove_from_right << "-"+cvp
404
+ end
405
+ end
406
+ end
407
+ new_lhs = lhs+add_to_left.join("")
408
+ text = text.gsub(lhs+oper, new_lhs+oper)
409
+ new_rhs = rhs
410
+ remove_from_right.each do |rfr|
411
+ new_rhs = new_rhs.gsub(rfr, "")
412
+ end
413
+ new_rhs = "0" if new_rhs == ""
414
+ text = text.gsub(oper+rhs, oper+new_rhs)
415
+ return(text)
416
+ end
326
417
 
327
- def get_constants(text)
328
- #in: "-8 + x + y + 3"
329
- #out: "[-8, +3]"
330
- text = text.gsub(" ","")
331
- text = text+"#"
332
- cs = []
333
- potential_constants = text.scan(/[\d\.]+[^a-z^\[^\]^\d^\.^\)]/)
334
- #potential_constants = text.scan(/\d+[^a-z^\[^\]^\d]/)
335
- constants = potential_constants.find_all{|c|![*('a'..'z'),*('A'..'Z')].include?(text[text.index(c)-1])}
336
- constants.each do |constant|
337
- c = constant.scan(/[\d\.]+/)[0]
338
- index = text.index(constant)
339
- if index == 0
340
- c = "+"+c
341
- else
342
- c = text.scan(/[\-\+]#{constant}/)[0]
343
- end
344
- cs << c.scan(/[\-\+][\d\.]+/)[0]
345
- end
346
- return({:formatted => cs, :unformatted => constants})
347
- end
418
+ def self.split_equals(constraint)
419
+ [constraint.gsub("=", "<="), constraint.gsub("=", ">=")]
420
+ end
348
421
 
349
- def put_constants_on_rhs(text)
350
- #in: "-8 + x + y + 3 <= 100"
351
- #out: "x + y <= 100 + 5"
352
- text = text.gsub(" ","")
353
- s = sides(text)
354
- constants_results = get_constants(s[:lhs])
355
- constants = []
356
- constants_results[:formatted].each_index do |i|
357
- formatted_constant = constants_results[:formatted][i]
358
- unformatted_constant = constants_results[:unformatted][i]
359
- unless unformatted_constant.include?("*")
360
- constants << formatted_constant
422
+ def self.split_equals_a(constraints)
423
+ constraints.map do |constraint|
424
+ if (constraint.split("") & ["<=",">=","<",">"]).empty?
425
+ self.split_equals(constraint)
426
+ else
427
+ constraint
428
+ end
429
+ end.flatten
361
430
  end
362
- end
363
- unless constants.empty?
364
- sum = constants.map{|cc|cc.to_f}.inject("+").to_s
365
- if sum.include?("-")
366
- sum = sum.gsub("-","+")
367
- else
368
- sum = "-"+sum
369
- end
370
- lhs = s[:lhs].gsub(" ","")+"#"
371
- constants_results[:unformatted].each do |constant|
372
- index = lhs.index(constant)
373
- if index == 0
374
- lhs = lhs[(constant.size-1)..(lhs.size-1)]
375
- else
376
- lhs = lhs[0..(index-2)]+lhs[(index+(constant.size-1))..(lhs.size-1)]
431
+
432
+ def self.sum_indices(constraint)
433
+ pieces_to_sub = constraint.scan(/[a-z]\[\d[\d\+\-]+\]/)
434
+ pieces_to_sub.each do |piece|
435
+ characters_to_sum = piece.scan(/[\d\+\-]+/)[0]
436
+ index_sum = self.sum_constants(characters_to_sum)
437
+ new_piece = piece.gsub(characters_to_sum, index_sum)
438
+ constraint = constraint.gsub(piece, new_piece)
377
439
  end
440
+ return(constraint)
378
441
  end
379
- text = text.gsub(s[:lhs], lhs[0..-2])
380
- text += sum
381
- end
382
- return(text)
383
- end
384
442
 
385
- def sum_constants(text)
386
- #in: "100+ 10-3"
387
- #out: "107"
388
- constants = get_constants(text)[:formatted]
389
- if constants.to_s.include?(".")
390
- constants.map{|c|c.to_f}.inject("+").to_s
391
- else
392
- constants.map{|c|c.to_i}.inject("+").to_s
443
+ def self.produce_variable_type_hash(variable_types, all_variables)
444
+ #in: ["BOOLEAN: x, y", "INTEGER: z"]
445
+ #out: {:x => 3, :y => 3, :z => 2}
446
+ variable_type_hash = {}
447
+ variable_types.each do |vt|
448
+ type = vt.gsub(" ","").split(":")[0]
449
+ if type.downcase == "boolean"
450
+ type_number = 3
451
+ elsif type.downcase == "integer"
452
+ type_number = 2
453
+ end
454
+ variables = vt.split(":")[1].gsub(" ","").split(",")
455
+ variables.each do |root_var|
456
+ all_variables_with_root = all_variables.find_all{|var|var.include?("[") && var.split("[")[0]==root_var}+[root_var]
457
+ all_variables_with_root.each do |var|
458
+ variable_type_hash[var.to_sym] = type_number
459
+ end
460
+ end
461
+ end
462
+ variable_type_hash
463
+ end
393
464
  end
394
- end
395
465
 
396
- def sub_rhs_with_summed_constants(constraint)
397
- rhs = sides(constraint)[:rhs]
398
- constraint.gsub(rhs, sum_constants(rhs))
399
- end
466
+ class LinearProgram
467
+ attr_accessor :objective
468
+ attr_accessor :constraints
469
+ attr_accessor :rows
470
+ attr_accessor :solution
471
+ attr_accessor :formatted_constraints
472
+ attr_accessor :rglpk_object
473
+ attr_accessor :solver
400
474
 
401
- def get_coefficient_variable_pairs(text)
402
- text.scan(/\d*[\*]*[a-z]\[*\d*\]*/)
403
- end
404
-
405
- def operator(constraint)
406
- if constraint.include?(">=")
407
- ">="
408
- elsif constraint.include?("<=")
409
- "<="
410
- elsif constraint.include?(">")
411
- ">"
412
- elsif constraint.include?("<")
413
- "<"
414
- elsif constraint.include?("=")
415
- "="
416
- end
417
- end
418
-
419
- def put_variables_on_lhs(text)
420
- #in: "x + y - x[3] <= 3z + 2x[2] - 10"
421
- #out: "x + y - x[3] - 3z - 2x[2] <= -10"
422
- text = text.gsub(" ", "")
423
- s = sides(text.gsub(" ",""))
424
- oper = operator(text)
425
- rhs = s[:rhs]
426
- lhs = s[:lhs]
427
- coefficient_variable_pairs = get_coefficient_variable_pairs(rhs)
428
- add_to_left = []
429
- remove_from_right = []
430
- coefficient_variable_pairs.each do |cvp|
431
- index = rhs.index(cvp)
432
- if index == 0
433
- add_to_left << "-"+cvp
434
- remove_from_right << cvp
435
- else
436
- if rhs[index-1] == "+"
437
- add_to_left << "-"+cvp
438
- remove_from_right << "+"+cvp
439
- else
440
- add_to_left << "+"+cvp
441
- remove_from_right << "-"+cvp
442
- end
475
+ def initialize(objective, constraints)
476
+ @objective = objective
477
+ @constraints = constraints
478
+ @rows = []
443
479
  end
444
480
  end
445
- new_lhs = lhs+add_to_left.join("")
446
- text = text.gsub(lhs+oper, new_lhs+oper)
447
- new_rhs = rhs
448
- remove_from_right.each do |rfr|
449
- new_rhs = new_rhs.gsub(rfr, "")
450
- end
451
- new_rhs = "0" if new_rhs == ""
452
- text = text.gsub(oper+rhs, oper+new_rhs)
453
- return(text)
454
- end
455
481
 
456
- def split_equals(constraint)
457
- [constraint.gsub("=", "<="), constraint.gsub("=", ">=")]
458
- end
482
+ class Objective
483
+ attr_accessor :function
484
+ attr_accessor :optimization#minimize, maximize, equals
485
+ attr_accessor :variable_coefficient_pairs
486
+ attr_accessor :optimized_value
459
487
 
460
- def split_equals_a(constraints)
461
- constraints.map do |constraint|
462
- if (constraint.split("") & ["<=",">=","<",">"]).empty?
463
- split_equals(constraint)
464
- else
465
- constraint
488
+ def initialize(function, optimization)
489
+ @function = function
490
+ @optimization = optimization
466
491
  end
467
- end.flatten
468
- end
492
+ end
469
493
 
470
- def sum_indices(constraint)
471
- pieces_to_sub = constraint.scan(/[a-z]\[\d[\d\+\-]+\]/)
472
- pieces_to_sub.each do |piece|
473
- characters_to_sum = piece.scan(/[\d\+\-]+/)[0]
474
- index_sum = sum_constants(characters_to_sum)
475
- new_piece = piece.gsub(characters_to_sum, index_sum)
476
- constraint = constraint.gsub(piece, new_piece)
494
+ class Row
495
+ attr_accessor :name
496
+ attr_accessor :constraint
497
+ attr_accessor :lower_bound
498
+ attr_accessor :upper_bound
499
+ attr_accessor :variable_coefficient_pairs
500
+ attr_accessor :epsilon
501
+
502
+ def initialize(name, lower_bound, upper_bound, epsilon)
503
+ @name = name
504
+ @lower_bound = lower_bound
505
+ @upper_bound = upper_bound
506
+ @variable_coefficient_pairs = []
507
+ @epsilon = epsilon
508
+ end
477
509
  end
478
- return(constraint)
479
- end
480
510
 
481
- def produce_variable_type_hash(variable_types, all_variables)
482
- #in: ["BOOLEAN: x, y", "INTEGER: z"]
483
- #out: {:x => 3, :y => 3, :z => 2}
484
- variable_type_hash = {}
485
- variable_types.each do |vt|
486
- type = vt.gsub(" ","").split(":")[0]
487
- if type.downcase == "boolean"
488
- type_number = 3
489
- elsif type.downcase == "integer"
490
- type_number = 2
491
- end
492
- variables = vt.split(":")[1].gsub(" ","").split(",")
493
- variables.each do |root_var|
494
- all_variables_with_root = all_variables.find_all{|var|var.include?("[") && var.split("[")[0]==root_var}+[root_var]
495
- all_variables_with_root.each do |var|
496
- variable_type_hash[var.to_sym] = type_number
497
- end
511
+ class VariableCoefficientPair
512
+ attr_accessor :variable
513
+ attr_accessor :coefficient
514
+ attr_accessor :variable_type
515
+
516
+ def initialize(variable, coefficient, variable_type=1)
517
+ @variable = variable
518
+ @coefficient = coefficient
519
+ @variable_type = variable_type
498
520
  end
499
521
  end
500
- variable_type_hash
501
522
  end
502
523
 
503
524
  def subject_to(constraints, options=[])
504
525
  variable_types = options.find_all{|option|option.downcase.include?("boolean") || option.downcase.include?("integer")} || []
505
526
  epsilon = options.find_all{|option|option.downcase.include?("epsilon")}.first.gsub(" ","").split(":")[1].to_f rescue $default_epsilon
506
527
  constraints = constraints.flatten
507
- constraints = split_equals_a(constraints)
528
+ constraints = OPL::Helper.split_equals_a(constraints)
508
529
  constraints = constraints.map do |constraint|
509
- sub_forall(constraint)
530
+ OPL::Helper.sub_forall(constraint)
510
531
  end.flatten
511
532
  constraints = constraints.map do |constraint|
512
- sum_indices(constraint)
533
+ OPL::Helper.sum_indices(constraint)
513
534
  end
514
535
  constraints = constraints.map do |constraint|
515
- sub_sum(constraint)
536
+ OPL::Helper.sub_sum(constraint)
516
537
  end
517
538
  constraints = constraints.map do |constraint|
518
- sum_indices(constraint)
539
+ OPL::Helper.sum_indices(constraint)
519
540
  end
520
541
  constraints = constraints.map do |constraint|
521
- put_constants_on_rhs(constraint)
542
+ OPL::Helper.put_constants_on_rhs(constraint)
522
543
  end
523
544
  constraints = constraints.map do |constraint|
524
- put_variables_on_lhs(constraint)
545
+ OPL::Helper.put_variables_on_lhs(constraint)
525
546
  end
526
547
  constraints = constraints.map do |constraint|
527
- sub_rhs_with_summed_constants(constraint)
548
+ OPL::Helper.sub_rhs_with_summed_constants(constraint)
528
549
  end
529
- all_vars = get_all_vars(constraints)
530
- variable_type_hash = produce_variable_type_hash(variable_types, all_vars)
550
+ all_vars = OPL::Helper.get_all_vars(constraints)
551
+ variable_type_hash = OPL::Helper.produce_variable_type_hash(variable_types, all_vars)
531
552
  rows = []
532
553
  constraints.each do |constraint|
533
554
  negate = false
@@ -548,7 +569,8 @@ def subject_to(constraints, options=[])
548
569
  bound = (value.split(">")[1]).to_f + epsilon
549
570
  upper_bound = (bound*-1).to_s
550
571
  end
551
- coefs = coefficients(sides(value)[:lhs])
572
+ lhs = OPL::Helper.sides(constraint)[:lhs]
573
+ coefs = OPL::Helper.coefficients(lhs)
552
574
  if negate
553
575
  coefs = coefs.map do |coef|
554
576
  if coef.include?("+")
@@ -558,9 +580,9 @@ def subject_to(constraints, options=[])
558
580
  end
559
581
  end
560
582
  end
561
- vars = variables(sides(value)[:lhs])
583
+ vars = OPL::Helper.variables(lhs)
562
584
  zero_coef_vars = all_vars - vars
563
- row = Row.new(name, lower_bound, upper_bound, epsilon)
585
+ row = OPL::Row.new(name, lower_bound, upper_bound, epsilon)
564
586
  row.constraint = constraint
565
587
  coefs = coefs + zero_coef_vars.map{|z|0}
566
588
  vars = vars + zero_coef_vars
@@ -569,7 +591,7 @@ def subject_to(constraints, options=[])
569
591
  all_vars.each do |var|
570
592
  coef = coefs[vars.index(var)]
571
593
  variable_type = variable_type_hash[var.to_sym] || 1
572
- pairs << VariableCoefficientPair.new(var, coef, variable_type)
594
+ pairs << OPL::VariableCoefficientPair.new(var, coef, variable_type)
573
595
  end
574
596
  row.variable_coefficient_pairs = pairs
575
597
  rows << row
@@ -586,14 +608,14 @@ def minimize(objective, rows_c)#objective function has no = in it
586
608
  end
587
609
 
588
610
  def optimize(optimization, objective, rows_c)
589
- o = Objective.new(objective, optimization)
590
- lp = LinearProgram.new(o, rows_c.map{|row|row.constraint})
591
- objective = sub_sum(objective)
592
- objective_constants = get_constants(objective)
611
+ o = OPL::Objective.new(objective, optimization)
612
+ lp = OPL::LinearProgram.new(o, rows_c.map{|row|row.constraint})
613
+ objective = OPL::Helper.sub_sum(objective)
614
+ objective_constants = OPL::Helper.get_constants(objective)
593
615
  if objective_constants[:formatted].empty?
594
616
  objective_addition = 0
595
617
  else
596
- objective_addition = sum_constants(objective_constants[:formatted].inject("+"))
618
+ objective_addition = OPL::Helper.sum_constants(objective_constants[:formatted].inject("+"))
597
619
  end
598
620
  lp.rows = rows_c
599
621
  p = Rglpk::Problem.new
@@ -623,8 +645,8 @@ def optimize(optimization, objective, rows_c)
623
645
  end
624
646
  end
625
647
  all_vars = rows_c.first.variable_coefficient_pairs.map{|vcp|vcp.variable}
626
- obj_coefficients = coefficients(objective.gsub(" ","")).map{|c|c.to_f}
627
- obj_vars = variables(objective.gsub(" ",""))
648
+ obj_coefficients = OPL::Helper.coefficients(objective.gsub(" ","")).map{|c|c.to_f}
649
+ obj_vars = OPL::Helper.variables(objective.gsub(" ",""))
628
650
  all_obj_coefficients = []
629
651
  all_vars.each do |var|
630
652
  i = obj_vars.index(var)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: