opl 0.2.0 → 1.0.0

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