opl 2.1.0 → 2.5.1
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 +7 -0
- data/lib/array.rb +59 -0
- data/lib/opl.rb +361 -220
- data/lib/string.rb +83 -0
- data/lib/sudoku.rb +71 -0
- metadata +29 -16
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 577e5853623d778f8c4aa44f28e787180aededce471e953107af513a132793d8
|
4
|
+
data.tar.gz: 58364f2f6b34053fa45a8a6bdb827b6fcd777cfb14d4752a6be82c1f214d6b7f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d529766f941755f63be3a3e44a1cb8986c16368119493076bdb61aaf1f91c63e8bc73d6ab66b5bca512aebaf1ce72c99a1cf94f37a041000fa99cbc0bfbc43a4
|
7
|
+
data.tar.gz: 98d388bdd84b40c9476031fffd036255eb06e9c120b84ef916f9bf796714b68087bf7fa7dcc379fc7c5e6d25e270283f08219ec8edf828c68591f46612135963
|
data/lib/array.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
class Array
|
2
|
+
def dimension
|
3
|
+
a = self
|
4
|
+
return 0 if a.class != Array
|
5
|
+
result = 1
|
6
|
+
a.each do |sub_a|
|
7
|
+
if sub_a.class == Array
|
8
|
+
dim = sub_a.dimension
|
9
|
+
result = dim + 1 if dim + 1 > result
|
10
|
+
end
|
11
|
+
end
|
12
|
+
return result
|
13
|
+
end
|
14
|
+
|
15
|
+
def values_at_a(indices, current_array=self)
|
16
|
+
#in: self = [3,4,[6,5,[3,4]],3], indices = [2,2,0]
|
17
|
+
#out: 3
|
18
|
+
if indices.size == 1
|
19
|
+
return(current_array[indices[0]])
|
20
|
+
else
|
21
|
+
values_at_a(indices[1..-1], current_array[indices[0]])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def inject_dim(int)
|
26
|
+
arr = self
|
27
|
+
int.times do
|
28
|
+
arr << []
|
29
|
+
end
|
30
|
+
arr
|
31
|
+
end
|
32
|
+
|
33
|
+
def matrix(int_arr, current_arr=[])
|
34
|
+
int = int_arr[0]
|
35
|
+
new_int_arr = int_arr[1..-1]
|
36
|
+
if int_arr.empty?
|
37
|
+
return(current_arr)
|
38
|
+
else
|
39
|
+
if current_arr.empty?
|
40
|
+
new_arr = current_arr.inject_dim(int)
|
41
|
+
self.matrix(new_int_arr, new_arr)
|
42
|
+
else
|
43
|
+
current_arr.each do |arr|
|
44
|
+
arr.matrix(int_arr, arr)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def insert_at(position_arr, value)
|
51
|
+
arr = self
|
52
|
+
if position_arr.size == 1
|
53
|
+
arr[position_arr[0]] = value
|
54
|
+
return(arr)
|
55
|
+
else
|
56
|
+
arr[position_arr[0]].insert_at(position_arr[1..-1], value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/opl.rb
CHANGED
@@ -1,143 +1,78 @@
|
|
1
1
|
require "rglpk"
|
2
|
+
require_relative "array.rb"
|
3
|
+
require_relative "string.rb"
|
4
|
+
require_relative "sudoku.rb"
|
2
5
|
|
3
|
-
#
|
4
|
-
#unbounded or conflicting bounds messages
|
5
|
-
# e.g.
|
6
|
-
# lp = maximize(
|
7
|
-
# "x",
|
8
|
-
# subject_to([
|
9
|
-
# "x >= 0"
|
10
|
-
# ]))
|
6
|
+
# Notes for future functionality
|
11
7
|
#
|
12
|
-
#
|
13
|
-
|
14
|
-
#2.2
|
15
|
-
#a matrix representation of the solution if using
|
16
|
-
#sub notation
|
17
|
-
|
18
|
-
#2.3
|
19
|
-
#parse data from a file
|
20
|
-
|
21
|
-
#2.4
|
22
|
-
#way more comprehensive test suite of functionality so far
|
23
|
-
|
24
|
-
#3.0
|
25
|
-
#multiple level sub notation e.g. x[1][[3]]
|
26
|
-
|
27
|
-
#3.1
|
28
|
-
#make sure extreme cases of foralls and sums
|
8
|
+
# Implement more advanced TSPs in order to
|
9
|
+
#make sure extreme cases of foralls and sums
|
29
10
|
#are handled
|
11
|
+
# Make an ERRORS : ON option
|
12
|
+
#
|
13
|
+
# need to handle multiple abs() in one constraint:
|
14
|
+
#
|
15
|
+
# 4 + abs(x - y) + abs(z) <= 4
|
16
|
+
#
|
17
|
+
# still need to implement abs() in objective
|
18
|
+
# I am not sure how to handle arithmetic inside
|
19
|
+
# an abs() in an objective function
|
20
|
+
#
|
21
|
+
# perhaps I should split into two LPs,
|
22
|
+
# one with +objective and one with -objective,
|
23
|
+
# solve both, and then take the more optimal solution
|
24
|
+
#
|
25
|
+
# maximize(abs(x))
|
26
|
+
# subject to:
|
27
|
+
# x < 3
|
28
|
+
# x > -7
|
29
|
+
#
|
30
|
+
# we set x = x1 - x2, x1,x2>=0
|
31
|
+
# so abs(x) = x1 + x2
|
32
|
+
# the lp should become
|
33
|
+
#
|
34
|
+
# maximize(x1 + x2)
|
35
|
+
# subject to:
|
36
|
+
# x1 - x2 < 3
|
37
|
+
# x1 - x2 > -7
|
38
|
+
# NONNEGATIVE: x1, x2
|
30
39
|
|
31
|
-
#
|
32
|
-
#
|
33
|
-
|
34
|
-
#
|
35
|
-
#
|
40
|
+
# 3.2
|
41
|
+
# or statements
|
42
|
+
# in order to do this I first have to do some refactoring:
|
43
|
+
# constraints need to be immediately turned in to objects
|
44
|
+
# step 1: turn constraints into objects and make current code work
|
45
|
+
# step 2: add a property to Constraint called or_index
|
46
|
+
# The or_index property represents the m[i] that belongs to that constraint
|
47
|
+
# step 3: after parsing is done, add the m[i] code
|
48
|
+
# Turning objects into constraints will be useful for any processing
|
49
|
+
# that I want to leave for later - i.e. if-->then, piecewise
|
36
50
|
|
37
|
-
#
|
38
|
-
#
|
51
|
+
# 3.3
|
52
|
+
# if --> then statements
|
39
53
|
|
40
|
-
#4
|
41
|
-
#piecewise statements
|
54
|
+
# 3.4
|
55
|
+
# piecewise statements
|
42
56
|
|
43
|
-
#
|
44
|
-
#duals, sensitivity, etc. - I could simply allow
|
57
|
+
# 3.5
|
58
|
+
# duals, sensitivity, etc. - I could simply allow
|
45
59
|
#access to the rglpk object wrapper
|
46
60
|
|
47
|
-
|
61
|
+
# 4.0
|
62
|
+
# import excel sheets as data
|
48
63
|
|
49
|
-
|
50
|
-
|
51
|
-
#in: "(2..5)"
|
52
|
-
#out: "[2,3,4,5]"
|
53
|
-
text = self
|
54
|
-
start = text[1].to_i
|
55
|
-
stop = text[-2].to_i
|
56
|
-
(start..stop).map{|i|i}.to_s
|
57
|
-
end
|
64
|
+
# 4.1
|
65
|
+
# add a SUBTOURS: option to eliminate subtours in a TSP
|
58
66
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
return(text)
|
66
|
-
end
|
67
|
-
|
68
|
-
def to_array(current_array=[self])
|
69
|
-
#in: "[1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]"
|
70
|
-
#out: [1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]
|
71
|
-
def current_level_information(b)
|
72
|
-
b = b.gsub(" ","")
|
73
|
-
stripped_array = b[1..-2]
|
74
|
-
in_array = 0
|
75
|
-
inside_arrays_string = ""
|
76
|
-
inside_values_string = ""
|
77
|
-
stripped_array.split("").each do |char|
|
78
|
-
if char == "["
|
79
|
-
in_array += 1
|
80
|
-
elsif char == "]"
|
81
|
-
in_array += -1
|
82
|
-
end
|
83
|
-
if (in_array > 0) || (char == "]")
|
84
|
-
inside_arrays_string += char
|
85
|
-
end
|
86
|
-
end
|
87
|
-
stripped_array_without_arrays = stripped_array
|
88
|
-
inside_arrays_string.gsub("][","],,,[").split(",,,").each do |str|
|
89
|
-
stripped_array_without_arrays = stripped_array_without_arrays.gsub(str,"")
|
90
|
-
end
|
91
|
-
inside_values_string = stripped_array_without_arrays.split(",").find_all{|e|e!=""}.join(",")
|
92
|
-
return {:values => inside_values_string, :arrays => inside_arrays_string}
|
93
|
-
end
|
94
|
-
if !current_array.join(",").include?("[")
|
95
|
-
return(current_array)
|
96
|
-
else
|
97
|
-
a = []
|
98
|
-
element = current_array.find_all{|e|e.include?("[")}.first
|
99
|
-
i = current_array.index(element)
|
100
|
-
info = current_level_information(element)
|
101
|
-
info[:values].split(",").each do |v|
|
102
|
-
a << v
|
103
|
-
end
|
104
|
-
info[:arrays].gsub("][","],,,[").split(",,,").each do |v|
|
105
|
-
a << v.to_array
|
106
|
-
end
|
107
|
-
current_array[i] = a
|
108
|
-
return(current_array[0])
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def to_a
|
113
|
-
self.to_array
|
114
|
-
end
|
115
|
-
end
|
67
|
+
# 4.2
|
68
|
+
# have an option where you can pass in a function.
|
69
|
+
#the function takes the resulting lp as input.
|
70
|
+
#you can add constraints based on the lp and re-run it.
|
71
|
+
#this will be useful for adding sub-tours to a problem
|
72
|
+
#without having to look at the output manually every time
|
116
73
|
|
117
|
-
|
118
|
-
|
119
|
-
a = self
|
120
|
-
return 0 if a.class != Array
|
121
|
-
result = 1
|
122
|
-
a.each do |sub_a|
|
123
|
-
if sub_a.class == Array
|
124
|
-
dim = sub_a.dimension
|
125
|
-
result = dim + 1 if dim + 1 > result
|
126
|
-
end
|
127
|
-
end
|
128
|
-
return result
|
129
|
-
end
|
130
|
-
|
131
|
-
def values_at_a(indices, current_array=self)
|
132
|
-
#in: self = [3,4,[6,5,[3,4]],3], indices = [2,2,0]
|
133
|
-
#out: 3
|
134
|
-
if indices.size == 1
|
135
|
-
return(current_array[indices[0]])
|
136
|
-
else
|
137
|
-
values_at_a(indices[1..-1], current_array[indices[0]])
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
74
|
+
$default_epsilon = 0.01
|
75
|
+
$default_m = 1000000000000.0
|
141
76
|
|
142
77
|
class OPL
|
143
78
|
class Helper
|
@@ -153,19 +88,27 @@ class OPL
|
|
153
88
|
end
|
154
89
|
|
155
90
|
def self.forall(text)
|
156
|
-
#need to be able to handle sums inside here
|
157
91
|
#in: "i in (0..2), x[i] <= 5"
|
158
92
|
#out: ["x[0] <= 5", "x[1] <= 5", "x[2] <= 5"]
|
93
|
+
helper = self
|
159
94
|
text = text.sub_paren_with_array
|
160
|
-
|
95
|
+
if ((text.gsub(" ","")).scan(/\]\,/).size) + ((text.gsub(" ","")).scan(/\)\,/).size) != text.gsub(" ","").scan(/in/).size
|
96
|
+
raise "The following forall() constraint is incorrectly formatted: #{text}. Please see the examples in test.rb for forall() constraints. I suspect you are missing a comma somewhere."
|
97
|
+
end
|
161
98
|
final_constraints = []
|
162
|
-
|
163
|
-
|
99
|
+
if text.include?("sum")
|
100
|
+
indices = text.split("sum")[0].scan(/[a-z] in/).map{|sc|sc[0]}
|
101
|
+
values = text.split("sum")[0].scan(/\s\[[\-\s\d+,]+\]/).map{|e|e.gsub(" ", "").scan(/[\-\d]+/)}
|
102
|
+
else
|
103
|
+
indices = text.scan(/[a-z] in/).map{|sc|sc[0]}
|
104
|
+
values = text.scan(/\s\[[\-\s\d+,]+\]/).map{|e|e.gsub(" ", "").scan(/[\-\d]+/)}
|
105
|
+
end
|
106
|
+
#TODO: the indices and values should only be those
|
107
|
+
#of the forall(), not of any sum() that is
|
108
|
+
#inside the forall()
|
164
109
|
index_value_pairs = indices.zip(values)
|
165
110
|
variable = text.scan(/[a-z]\[/)[0].gsub("[","")
|
166
|
-
|
167
|
-
#or is this even used at all????
|
168
|
-
value_combinations = self.mass_product(values)
|
111
|
+
value_combinations = helper.mass_product(values)
|
169
112
|
value_combinations.each_index do |vc_index|
|
170
113
|
value_combination = value_combinations[vc_index]
|
171
114
|
value_combination = [value_combination] unless value_combination.is_a?(Array)
|
@@ -247,11 +190,19 @@ class OPL
|
|
247
190
|
equation.gsub("#","")
|
248
191
|
end
|
249
192
|
|
250
|
-
def self.sum(text, indexvalues={:indices => [], :values => []})
|
193
|
+
def self.sum(text, lp, indexvalues={:indices => [], :values => []})
|
251
194
|
#in: "i in [0,1], j in [4,-5], 3x[i][j]"
|
252
195
|
#out: "3x[0][4] + 3x[0][-5] + 3x[1][4] + 3x[1][-5]"
|
253
196
|
text = text.sub_paren_with_array
|
254
|
-
|
197
|
+
if text.scan(/\(\d+\+\d+\)/).size > 0
|
198
|
+
text.scan(/\(\d+\+\d+\)/).each {|e| text = text.gsub(e,eval(e.gsub("(","").gsub(")","")).to_s) }
|
199
|
+
end
|
200
|
+
text = text.sub_paren_with_array
|
201
|
+
if (text.gsub(" ","")).scan(/\]\,/).size != text.scan(/in/).size
|
202
|
+
raise "The following sum() constraint is incorrectly formatted: #{text}. Please see the examples in test.rb for sum() constraints. I suspect you are missing a comma somewhere."
|
203
|
+
elsif (text.gsub(" ","").include?("=") || text.gsub(" ","").include?("<") || text.gsub(" ","").include?(">"))
|
204
|
+
raise "The following sum() constraint cannot have a equalities in it (a.k.a. =, <, >): #{text}"
|
205
|
+
end
|
255
206
|
final_text = ""
|
256
207
|
element = text.split(",")[-1].gsub(" ","")
|
257
208
|
indices = text.scan(/[a-z] in/).map{|sc|sc[0]}
|
@@ -305,35 +256,37 @@ class OPL
|
|
305
256
|
final_text
|
306
257
|
end
|
307
258
|
|
308
|
-
def self.sub_sum(equation, indexvalues={:indices => [], :values => []})
|
259
|
+
def self.sub_sum(equation, lp, indexvalues={:indices => [], :values => []})
|
309
260
|
#in: "sum(i in (0..3), x[i]) <= 100"
|
310
261
|
#out: "x[0]+x[1]+x[2]+x[3] <= 100"
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
indexvalues[:indices].
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
262
|
+
if equation.include?("sum(")
|
263
|
+
sums = (equation+"#").split("sum(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}.find_all{|eeee|!eeee.include?("forall")}
|
264
|
+
sums.each do |text|
|
265
|
+
e = text
|
266
|
+
unless indexvalues[:indices].empty?
|
267
|
+
indexvalues[:indices].each_index do |i|
|
268
|
+
index = indexvalues[:indices][i]
|
269
|
+
value = indexvalues[:values][i].to_s
|
270
|
+
e = e.gsub("("+index, "("+value)
|
271
|
+
e = e.gsub(index+")", value+")")
|
272
|
+
e = e.gsub("["+index, "["+value)
|
273
|
+
e = e.gsub(index+"]", value+"]")
|
274
|
+
e = e.gsub("=>"+index, "=>"+value)
|
275
|
+
e = e.gsub("<="+index, "<="+value)
|
276
|
+
e = e.gsub(">"+index, ">"+value)
|
277
|
+
e = e.gsub("<"+index, "<"+value)
|
278
|
+
e = e.gsub("="+index, "="+value)
|
279
|
+
e = e.gsub("=> "+index, "=> "+value)
|
280
|
+
e = e.gsub("<= "+index, "<= "+value)
|
281
|
+
e = e.gsub("> "+index, "> "+value)
|
282
|
+
e = e.gsub("< "+index, "< "+value)
|
283
|
+
e = e.gsub("= "+index, "= "+value)
|
284
|
+
end
|
332
285
|
end
|
286
|
+
equation = equation.gsub(text, e)
|
287
|
+
result = self.sum(text, lp)
|
288
|
+
equation = equation.gsub("sum("+text+")", result)
|
333
289
|
end
|
334
|
-
equation = equation.gsub(text, e)
|
335
|
-
result = self.sum(text)
|
336
|
-
equation = equation.gsub("sum("+text+")", result)
|
337
290
|
end
|
338
291
|
return(equation)
|
339
292
|
end
|
@@ -352,8 +305,12 @@ class OPL
|
|
352
305
|
end
|
353
306
|
|
354
307
|
def self.variables(text, lp)#parameter is one side of the equation
|
355
|
-
|
356
|
-
|
308
|
+
text = self.add_ones(text, lp)
|
309
|
+
text = text.gsub("abs","").gsub("(","").gsub(")","")
|
310
|
+
variables = text.scan(/[a-z][\[\]\d]*/)
|
311
|
+
raise("The variable letter a is reserved for special processes. Please rename your variable to something other than a.") if variables.join.include?("a")
|
312
|
+
raise("The variable letter m is reserved for special processes. Please rename your variable to something other than m.") if variables.join.include?("m")
|
313
|
+
return variables
|
357
314
|
end
|
358
315
|
|
359
316
|
def self.get_all_vars(constraints, lp)
|
@@ -369,26 +326,26 @@ class OPL
|
|
369
326
|
def self.get_constants(text)
|
370
327
|
#in: "-8 + x + y + 3"
|
371
328
|
#out: "[-8, +3]"
|
372
|
-
text = text.gsub(" ","")
|
373
|
-
text = text+"#"
|
374
|
-
cs = []
|
375
|
-
potential_constants = text.scan(/[\d\.]+[^a-z^\[^\]^\d^\.^\)^\*]/)
|
376
|
-
constants = potential_constants.find_all{|c|![*('a'..'z'),*('A'..'Z'),"["].include?(text[text.index(c)-1])}
|
377
|
-
searchable_text = text
|
378
|
-
constants.each_index do |i|
|
379
|
-
constant = constants[i]
|
380
|
-
c = constant.scan(/[\d\.]+/)[0]
|
381
|
-
index = searchable_text.index(constant)
|
382
|
-
if index == 0
|
383
|
-
c = "+"+c
|
384
|
-
else
|
385
|
-
constant = constant.gsub('+','[+]')
|
386
|
-
constant = constant.gsub('-','[-]')
|
387
|
-
c = searchable_text.scan(/[\-\+]#{constant}/)[0]
|
388
|
-
end
|
389
|
-
cs << c.scan(/[\-\+][\d\.]+/)[0]
|
390
|
-
searchable_text[index] = "**"
|
391
|
-
end
|
329
|
+
text = text.gsub(" ","")
|
330
|
+
text = text+"#"
|
331
|
+
cs = []
|
332
|
+
potential_constants = text.scan(/[\d\.]+[^a-z^\[^\]^\d^\.^\)^\*]/)
|
333
|
+
constants = potential_constants.find_all{|c|![*('a'..'z'),*('A'..'Z'),"["].include?(text[text.index(c)-1])}
|
334
|
+
searchable_text = text
|
335
|
+
constants.each_index do |i|
|
336
|
+
constant = constants[i]
|
337
|
+
c = constant.scan(/[\d\.]+/)[0]
|
338
|
+
index = searchable_text.index(constant)
|
339
|
+
if index == 0
|
340
|
+
c = "+"+c
|
341
|
+
else
|
342
|
+
constant = constant.gsub('+','[+]')
|
343
|
+
constant = constant.gsub('-','[-]')
|
344
|
+
c = searchable_text.scan(/[\-\+]#{constant}/)[0]
|
345
|
+
end
|
346
|
+
cs << c.scan(/[\-\+][\d\.]+/)[0]
|
347
|
+
searchable_text[index] = "**"
|
348
|
+
end
|
392
349
|
return({:formatted => cs, :unformatted => constants})
|
393
350
|
end
|
394
351
|
|
@@ -686,6 +643,103 @@ end
|
|
686
643
|
text = text[1..-1] if text[0] == "+"
|
687
644
|
return(text)
|
688
645
|
end
|
646
|
+
|
647
|
+
def self.check_options_syntax(options)
|
648
|
+
return if options.empty?
|
649
|
+
options.each do |option|
|
650
|
+
if option.include?(":")
|
651
|
+
title = option.gsub(" ","").split(":")[0]
|
652
|
+
value = option.gsub(" ","").split(":")[1]
|
653
|
+
if !["nonnegative", "integer", "boolean", "data", "epsilon"].include?(title.downcase)
|
654
|
+
raise "Did not recognize the TITLE parameter '#{title}' in the options."
|
655
|
+
end
|
656
|
+
else
|
657
|
+
raise "Options parameter '#{option}' does not have a colon in it. The proper syntax of an option is TITLE: VALUE"
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
def self.negate(text, explicit=false)
|
663
|
+
# text is one side of an equation
|
664
|
+
# there will be no foralls, no sums, and no abs
|
665
|
+
# in: "z - 3"
|
666
|
+
# out: "-z + 3"
|
667
|
+
working_text = text = text.gsub(" ","")
|
668
|
+
#!("a".."z").to_a.include?(text[0]) &&
|
669
|
+
if !["-","+"].include?(text[0])
|
670
|
+
working_text = "+"+working_text
|
671
|
+
end
|
672
|
+
indices_of_negatives = working_text.index_array("-")
|
673
|
+
indices_of_positives = working_text.index_array("+")
|
674
|
+
indices_of_negatives.each {|i| working_text[i] = "+"}
|
675
|
+
indices_of_positives.each {|i| working_text[i] = "-"}
|
676
|
+
if !explicit && working_text[0] == "+"
|
677
|
+
working_text = working_text[1..-1]
|
678
|
+
end
|
679
|
+
return(working_text)
|
680
|
+
end
|
681
|
+
|
682
|
+
def self.replace_absolute_value(text)
|
683
|
+
# text is a constraint
|
684
|
+
# there will be no foralls and no sums
|
685
|
+
# in: "abs(x) <= 1"
|
686
|
+
# out: "x <= 1", "-x <= 1"
|
687
|
+
# in: "4x + 3y - 6abs(z - 3) <= 4"
|
688
|
+
# out: "4x + 3y - 6z + 18 <= 4", "4x + 3y + 6z - 18 <= 4"
|
689
|
+
if text.include?("abs")
|
690
|
+
helper = self
|
691
|
+
constraints_to_delete = [text]
|
692
|
+
text = text.gsub(" ","")
|
693
|
+
constraints_to_delete << text
|
694
|
+
working_text = "#"+text
|
695
|
+
absolute_value_elements = working_text.scan(/[\-\+\#]\d*abs\([a-z\-\+\*\d\[\]]+\)/)
|
696
|
+
constraints_to_add = []
|
697
|
+
absolute_value_elements.each do |ave|
|
698
|
+
ave = ave.gsub("#","")
|
699
|
+
inside_value = ave.split("(")[1].split(")")[0]
|
700
|
+
positive_ave = inside_value
|
701
|
+
negative_ave = helper.negate(inside_value)
|
702
|
+
if ave[0] == "-" || ave[1] == "-"
|
703
|
+
positive_ave = helper.negate(positive_ave, true)
|
704
|
+
negative_ave = helper.negate(negative_ave, true)
|
705
|
+
elsif ave[0] == "+"
|
706
|
+
positive_ave = "+"+positive_ave
|
707
|
+
end
|
708
|
+
positive_constraint = text.gsub(ave, positive_ave)
|
709
|
+
negative_constraint = text.gsub(ave, negative_ave)
|
710
|
+
constraints_to_add << positive_constraint
|
711
|
+
constraints_to_add << negative_constraint
|
712
|
+
end
|
713
|
+
return {:constraints_to_delete => constraints_to_delete, :constraints_to_add => constraints_to_add}
|
714
|
+
else
|
715
|
+
return {:constraints_to_delete => [], :constraints_to_add => []}
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
def self.strip_abs(text)
|
720
|
+
# in: "3 + abs(x[1] - y) + abs(x[3]) <= 3*abs(-x[2])"
|
721
|
+
# out: {:positive => "3 + x[1] - y + x[3] <= -3*x[2]",
|
722
|
+
# :negative => "3 - x[1] + y - x[3] <= 3*x[2]"}
|
723
|
+
text = text.gsub(" ","")
|
724
|
+
working_text = "#"+text
|
725
|
+
|
726
|
+
end
|
727
|
+
|
728
|
+
def self.either_or(lp, constraint1, constraint2, i)
|
729
|
+
# in: lp, "10.0*x1+4.0*x2+5.0*x3<=600", "2.0*x1+2.0*x2+6.0*x3<=300"
|
730
|
+
# out: "10.0*x1+4.0*x2+5.0*x3<=600+#{$default_m}", "2.0*x1+2.0*x2+6.0*x3<=300+#{$default_m}"
|
731
|
+
index1 = lp.constraints.index(constraint1)
|
732
|
+
lp.constraints[index1] = lp.constraints[index1]+"#{$default_m}*m[#{i}]"
|
733
|
+
# add "+M[n]y[n]" to the rhs of the constraint
|
734
|
+
end
|
735
|
+
|
736
|
+
# make a default OPTION that m is BOOLEAN
|
737
|
+
def self.split_ors(lp, constraints)
|
738
|
+
# in: ["x <= 10", "y <= 24 or y + x <= 14"]
|
739
|
+
# out: ["x <= 10", "y - 1000000000000.0*m[0] <= 24", "y + x - 1000000000000.0 + 1000000000000.0*m[0] <= 14"]
|
740
|
+
|
741
|
+
# add m[i+1] to left side of constraints
|
742
|
+
end
|
689
743
|
end
|
690
744
|
|
691
745
|
class LinearProgram
|
@@ -704,6 +758,12 @@ end
|
|
704
758
|
attr_accessor :variable_types
|
705
759
|
attr_accessor :column_bounds
|
706
760
|
attr_accessor :epsilon
|
761
|
+
attr_accessor :matrix_solution
|
762
|
+
attr_accessor :error_message
|
763
|
+
attr_accessor :stop_processing
|
764
|
+
attr_accessor :solution_type
|
765
|
+
attr_accessor :negated_objective_lp
|
766
|
+
attr_accessor :m_index
|
707
767
|
|
708
768
|
def keys
|
709
769
|
[:objective, :constraints, :rows, :solution, :formatted_constraints, :rglpk_object, :solver, :matrix, :simplex_message, :mip_message, :data]
|
@@ -713,6 +773,52 @@ end
|
|
713
773
|
@rows = []
|
714
774
|
@data = []
|
715
775
|
@epsilon = $default_epsilon
|
776
|
+
@matrix_solution = {}
|
777
|
+
@stop_processing = false
|
778
|
+
end
|
779
|
+
|
780
|
+
def solution_as_matrix
|
781
|
+
lp = self
|
782
|
+
variables = lp.solution.keys.map do |key|
|
783
|
+
key.scan(/[a-z]/)[0] if key.include?("[")
|
784
|
+
end.uniq.find_all{|e|!e.nil?}
|
785
|
+
matrix_solution = {}
|
786
|
+
variables.each do |var|
|
787
|
+
elements = lp.solution.keys.find_all{|key|key.include?(var) && key.include?("[")}
|
788
|
+
num_dims = elements[0].scan(/\]\[/).size + 1
|
789
|
+
dim_limits = []
|
790
|
+
indices_value_pairs = []
|
791
|
+
[*(0..(num_dims-1))].each do |i|
|
792
|
+
dim_limit = 0
|
793
|
+
elements.each do |e|
|
794
|
+
indices = e.scan(/\[\d+\]/).map{|str|str.scan(/\d+/)[0].to_i}
|
795
|
+
value = lp.solution[e]
|
796
|
+
indices_value_pairs << [indices, value]
|
797
|
+
dim_limit = indices[i] if indices[i] > dim_limit
|
798
|
+
end
|
799
|
+
dim_limits << dim_limit+1
|
800
|
+
end
|
801
|
+
matrix = [].matrix(dim_limits)
|
802
|
+
indices_value_pairs.each do |ivp|
|
803
|
+
matrix.insert_at(ivp[0], ivp[1].to_f)
|
804
|
+
end
|
805
|
+
matrix_solution[var] = matrix
|
806
|
+
end
|
807
|
+
return(matrix_solution)
|
808
|
+
end
|
809
|
+
|
810
|
+
def recreate_with_objective_abs(objective)
|
811
|
+
#in: "abs(x)"
|
812
|
+
#out: {:objective => "x1 + x2", :constraints => "x1 * x2 = 0"}
|
813
|
+
#this is a really tough problem - first time I am considering
|
814
|
+
#abandoning the string parsing approach. Really need to think
|
815
|
+
#about how to attack this
|
816
|
+
lp_class = self
|
817
|
+
helper = OPL::Helper
|
818
|
+
variabes = helper.variables(objective, lp_class.new)
|
819
|
+
new_objective = ""
|
820
|
+
constraints_to_add = []
|
821
|
+
#return(self)
|
716
822
|
end
|
717
823
|
end
|
718
824
|
|
@@ -781,6 +887,7 @@ end
|
|
781
887
|
end
|
782
888
|
|
783
889
|
def subject_to(constraints, options=[])
|
890
|
+
OPL::Helper.check_options_syntax(options)
|
784
891
|
lp = OPL::LinearProgram.new
|
785
892
|
lp.original_constraints = constraints
|
786
893
|
variable_types = options.find_all{|option|option.downcase.include?("boolean") || option.downcase.include?("integer")} || []
|
@@ -796,44 +903,55 @@ def subject_to(constraints, options=[])
|
|
796
903
|
end
|
797
904
|
lp.epsilon = epsilon
|
798
905
|
constraints = constraints.flatten
|
906
|
+
#constraints = constraints.split_ors(constraints)
|
799
907
|
constraints = OPL::Helper.split_equals_a(constraints)
|
800
908
|
data_names = lp.data.map{|d|d.name}
|
801
|
-
constraints = constraints.map do |constraint|
|
802
|
-
OPL::Helper.sub_forall(constraint)
|
803
|
-
end.flatten
|
804
|
-
constraints = constraints.map do |constraint|
|
805
|
-
OPL::Helper.sum_indices(constraint)
|
806
|
-
end
|
807
|
-
constraints = constraints.map do |constraint|
|
808
|
-
OPL::Helper.sub_sum(constraint)
|
809
|
-
end
|
810
|
-
constraints = constraints.map do |constraint|
|
811
|
-
OPL::Helper.sum_indices(constraint)
|
812
|
-
end
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
end
|
822
|
-
constraints =
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
909
|
+
constraints = constraints.map do |constraint|
|
910
|
+
OPL::Helper.sub_forall(constraint)
|
911
|
+
end.flatten
|
912
|
+
constraints = constraints.map do |constraint|
|
913
|
+
OPL::Helper.sum_indices(constraint)
|
914
|
+
end
|
915
|
+
constraints = constraints.map do |constraint|
|
916
|
+
OPL::Helper.sub_sum(constraint, lp)
|
917
|
+
end
|
918
|
+
constraints = constraints.map do |constraint|
|
919
|
+
OPL::Helper.sum_indices(constraint)
|
920
|
+
end
|
921
|
+
new_constraints = []
|
922
|
+
constraints.each do |constraint|
|
923
|
+
replace_absolute_value_results = OPL::Helper.replace_absolute_value(constraint)
|
924
|
+
if replace_absolute_value_results[:constraints_to_add].empty?
|
925
|
+
new_constraints << constraint
|
926
|
+
else
|
927
|
+
new_constraints += replace_absolute_value_results[:constraints_to_add]
|
928
|
+
end
|
929
|
+
end
|
930
|
+
constraints = new_constraints
|
931
|
+
constraints = constraints.map do |constraint|
|
932
|
+
OPL::Helper.put_constants_on_rhs(constraint)
|
933
|
+
end
|
934
|
+
constraints = constraints.map do |constraint|
|
935
|
+
OPL::Helper.put_variables_on_lhs(constraint)
|
936
|
+
end
|
937
|
+
constraints = constraints.map do |constraint|
|
938
|
+
OPL::Helper.sub_rhs_with_summed_constants(constraint)
|
939
|
+
end
|
940
|
+
constraints = constraints.map do |constraint|
|
941
|
+
OPL::Helper.substitute_data(constraint, lp)
|
942
|
+
end
|
943
|
+
constraints = constraints.map do |constraint|
|
944
|
+
OPL::Helper.put_constants_on_rhs(constraint)
|
945
|
+
end
|
946
|
+
constraints = constraints.map do |constraint|
|
947
|
+
OPL::Helper.put_variables_on_lhs(constraint)
|
948
|
+
end
|
949
|
+
constraints = constraints.map do |constraint|
|
950
|
+
OPL::Helper.sub_rhs_with_summed_constants(constraint)
|
951
|
+
end
|
952
|
+
constraints = constraints.map do |constraint|
|
953
|
+
OPL::Helper.sum_variables(constraint, lp)
|
954
|
+
end
|
837
955
|
lp.constraints = constraints
|
838
956
|
all_vars = OPL::Helper.get_all_vars(constraints, lp)
|
839
957
|
variable_type_hash = OPL::Helper.produce_variable_type_hash(variable_types, all_vars)
|
@@ -904,7 +1022,12 @@ end
|
|
904
1022
|
|
905
1023
|
def optimize(optimization, objective, lp)
|
906
1024
|
original_objective = objective
|
907
|
-
|
1025
|
+
while original_objective.include?("abs")
|
1026
|
+
#need to add some constraints, change the objective,
|
1027
|
+
#and reprocess the constraints
|
1028
|
+
#lp = lp.recreate_with_objective_abs(original_objective)
|
1029
|
+
end
|
1030
|
+
objective = OPL::Helper.sub_sum(objective, lp)
|
908
1031
|
objective = OPL::Helper.sum_indices(objective)
|
909
1032
|
objective = OPL::Helper.substitute_data(objective, lp)
|
910
1033
|
objective_constants = OPL::Helper.get_constants(objective)
|
@@ -993,5 +1116,23 @@ def optimize(optimization, objective, lp)
|
|
993
1116
|
end
|
994
1117
|
lp.solution = answer
|
995
1118
|
lp.rglpk_object = p
|
1119
|
+
begin
|
1120
|
+
lp.matrix_solution = lp.solution_as_matrix
|
1121
|
+
rescue
|
1122
|
+
return lp
|
1123
|
+
end
|
1124
|
+
if lp.stop_processing
|
1125
|
+
lp.solution = lp.error_message
|
1126
|
+
lp.matrix_solution = lp.error_message
|
1127
|
+
lp.rglpk_object = lp.error_message
|
1128
|
+
lp.objective = lp.error_message
|
1129
|
+
end
|
1130
|
+
if lp.rglpk_object.status.to_s == "4"
|
1131
|
+
raise "There is no feasible solution."
|
1132
|
+
elsif lp.rglpk_object.status.to_s == "6"
|
1133
|
+
raise "The solution is unbounded."
|
1134
|
+
elsif lp.rglpk_object.status.to_s == "1"
|
1135
|
+
raise "The solution is undefined."
|
1136
|
+
end
|
996
1137
|
lp
|
997
1138
|
end
|
data/lib/string.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
class String
|
2
|
+
def paren_to_array
|
3
|
+
#in: "(2..5)"
|
4
|
+
#out: "[2,3,4,5]"
|
5
|
+
start = self[1].to_i
|
6
|
+
stop = self[-2].to_i
|
7
|
+
(start..stop).map{|i|i}.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def sub_paren_with_array
|
11
|
+
text = self
|
12
|
+
targets = text.scan(/\([\d]+\.\.[\d]+\)/)
|
13
|
+
targets.each do |target|
|
14
|
+
text = text.gsub(target, target.paren_to_array)
|
15
|
+
end
|
16
|
+
return(text)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_array(current_array=[self])
|
20
|
+
#in: "[1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]"
|
21
|
+
#out: [1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]
|
22
|
+
def current_level_information(b)
|
23
|
+
b = b.gsub(" ","")
|
24
|
+
stripped_array = b[1..-2]
|
25
|
+
in_array = 0
|
26
|
+
inside_arrays_string = ""
|
27
|
+
inside_values_string = ""
|
28
|
+
stripped_array.split("").each do |char|
|
29
|
+
if char == "["
|
30
|
+
in_array += 1
|
31
|
+
elsif char == "]"
|
32
|
+
in_array += -1
|
33
|
+
end
|
34
|
+
if (in_array > 0) || (char == "]")
|
35
|
+
inside_arrays_string += char
|
36
|
+
end
|
37
|
+
end
|
38
|
+
stripped_array_without_arrays = stripped_array
|
39
|
+
inside_arrays_string.gsub("][","],,,[").split(",,,").each do |str|
|
40
|
+
stripped_array_without_arrays = stripped_array_without_arrays.gsub(str,"")
|
41
|
+
end
|
42
|
+
inside_values_string = stripped_array_without_arrays.split(",").find_all{|e|e!=""}.join(",")
|
43
|
+
return {:values => inside_values_string, :arrays => inside_arrays_string}
|
44
|
+
end
|
45
|
+
if !current_array.join(",").include?("[")
|
46
|
+
return(current_array)
|
47
|
+
else
|
48
|
+
a = []
|
49
|
+
element = current_array.find_all{|e|e.include?("[")}.first
|
50
|
+
i = current_array.index(element)
|
51
|
+
info = current_level_information(element)
|
52
|
+
info[:values].split(",").each do |v|
|
53
|
+
a << v
|
54
|
+
end
|
55
|
+
info[:arrays].gsub("][","],,,[").split(",,,").each do |v|
|
56
|
+
a << v.to_array
|
57
|
+
end
|
58
|
+
current_array[i] = a
|
59
|
+
return(current_array[0])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_a
|
64
|
+
self.to_array
|
65
|
+
end
|
66
|
+
|
67
|
+
def index_array(str)
|
68
|
+
indices = []
|
69
|
+
string = self
|
70
|
+
ignore_indices = []
|
71
|
+
search_length = str.size
|
72
|
+
[*(0..string.size-1)].each do |i|
|
73
|
+
if !ignore_indices.include?(i)
|
74
|
+
compare_str = string[i..(i+search_length-1)]
|
75
|
+
if compare_str == str
|
76
|
+
indices << i
|
77
|
+
ignore_indices = ignore_indices + [i..(i+search_length-1)]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
return(indices)
|
82
|
+
end
|
83
|
+
end
|
data/lib/sudoku.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
class OPL
|
2
|
+
class Sudoku
|
3
|
+
attr_accessor :input_matrix
|
4
|
+
attr_accessor :lp
|
5
|
+
attr_accessor :solution
|
6
|
+
|
7
|
+
def initialize(input_matrix)
|
8
|
+
@input_matrix = input_matrix
|
9
|
+
""
|
10
|
+
end
|
11
|
+
|
12
|
+
def solve
|
13
|
+
size = input_matrix.count
|
14
|
+
rubysize = size-1
|
15
|
+
|
16
|
+
constant_constraints = []
|
17
|
+
input_matrix.each_index do |i|
|
18
|
+
row = input_matrix[i]
|
19
|
+
|
20
|
+
row.each_index do |j|
|
21
|
+
element = input_matrix[i][j]
|
22
|
+
|
23
|
+
if element != 0
|
24
|
+
constant_constraints << "x[#{i}][#{j}][#{element-1}] = 1"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
@lp = minimize("y", subject_to([
|
30
|
+
"y = 2",# y is a dummy variable so I don't have to worry about the objective function
|
31
|
+
"forall(i in (0..#{rubysize}), j in (0..#{rubysize}), sum(k in (0..#{rubysize}), x[i][j][k]) = 1)",# an element contains only one number
|
32
|
+
"forall(i in (0..#{rubysize}), k in (0..#{rubysize}), sum(j in (0..#{rubysize}), x[i][j][k]) = 1)",# every row contains every number
|
33
|
+
"forall(j in (0..#{rubysize}), k in (0..#{rubysize}), sum(i in (0..#{rubysize}), x[i][j][k]) = 1)",# every column contains every number
|
34
|
+
"forall(u in [0,3,6], v in [0,3,6], k in (0..#{rubysize}), sum(i in ((0+u)..(#{(size/3)-1}+u)), j in ((0+v)..(#{(size/3)-1}+v)), x[i][j][k]) = 1)",# every 3x3 grid contains every number
|
35
|
+
constant_constraints# some elements already have their values set
|
36
|
+
].flatten,["BOOLEAN: x"]))
|
37
|
+
""
|
38
|
+
end
|
39
|
+
|
40
|
+
def format_solution
|
41
|
+
@lp.matrix_solution["x"]
|
42
|
+
mat = @lp.matrix_solution["x"]
|
43
|
+
sol = Array.new(mat[0][0].size) { Array.new(mat[0][0].size, 0) }
|
44
|
+
mat.each_index do |i|
|
45
|
+
mat[i].each_index do |j|
|
46
|
+
mat[i][j].each_index do |k|
|
47
|
+
if mat[i][j][k].to_f == 1.0
|
48
|
+
sol[i][j] = k+1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@solution = sol
|
54
|
+
""
|
55
|
+
end
|
56
|
+
|
57
|
+
def print_problem
|
58
|
+
@input_matrix.each do |row|
|
59
|
+
puts row.join(" ")
|
60
|
+
end
|
61
|
+
""
|
62
|
+
end
|
63
|
+
|
64
|
+
def print_solution
|
65
|
+
@solution.each do |row|
|
66
|
+
puts row.join(" ")
|
67
|
+
end
|
68
|
+
""
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
CHANGED
@@ -1,49 +1,62 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: opl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1
|
5
|
-
prerelease:
|
4
|
+
version: 2.5.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Benjamin Godlove
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
date: 2020-06-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rglpk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.4.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.4.0
|
27
|
+
description: This gem gives you a beautifully simple way to formulate your linear
|
28
|
+
or mixed integer program. The syntax is inspired by OPL Studio, which remains my
|
29
|
+
favorite linear programming software, but the license is quite expensive.
|
18
30
|
email: bgodlove88@gmail.com
|
19
31
|
executables: []
|
20
32
|
extensions: []
|
21
33
|
extra_rdoc_files: []
|
22
34
|
files:
|
35
|
+
- lib/array.rb
|
23
36
|
- lib/opl.rb
|
37
|
+
- lib/string.rb
|
38
|
+
- lib/sudoku.rb
|
24
39
|
homepage: http://github.com/brg8/opl
|
25
40
|
licenses:
|
26
|
-
-
|
41
|
+
- MIT
|
42
|
+
metadata: {}
|
27
43
|
post_install_message:
|
28
44
|
rdoc_options: []
|
29
45
|
require_paths:
|
30
46
|
- lib
|
31
47
|
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
-
none: false
|
33
48
|
requirements:
|
34
|
-
- -
|
49
|
+
- - ">="
|
35
50
|
- !ruby/object:Gem::Version
|
36
51
|
version: '0'
|
37
52
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
-
none: false
|
39
53
|
requirements:
|
40
|
-
- -
|
54
|
+
- - ">="
|
41
55
|
- !ruby/object:Gem::Version
|
42
56
|
version: '0'
|
43
57
|
requirements: []
|
44
|
-
|
45
|
-
rubygems_version: 1.8.23
|
58
|
+
rubygems_version: 3.0.6
|
46
59
|
signing_key:
|
47
|
-
specification_version:
|
60
|
+
specification_version: 4
|
48
61
|
summary: Linear Or Mixed Integer Program Solver
|
49
62
|
test_files: []
|