opl 2.2.0 → 2.5.2
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 +243 -189
- 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: a772d39a2592f51445d27d838fb5c0fa303e54f874f37171cd2b1357462a3442
|
4
|
+
data.tar.gz: 8e874232cf7834b8dd2eb898dd10d382988594c957ebdc3fe1bf618494150554
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6a166aceae348d8505d78a5b338b66758082967504d7770516802ae97c747b9f525a5b993901f7252e6042d6723e8dd70ba5bcfc499e2f2c39d60b18f4ccb64b
|
7
|
+
data.tar.gz: a423e7cd941be1e9ea65f61fcc29cdc55a397a577acfe112d440be947d490ca3cd6773b083f1b2b06d890beff158398e7fa1a7a7ba2c67586913a7fd728e53b0
|
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,171 +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.4
|
15
|
-
#way more comprehensive test suite of functionality so far
|
16
|
-
|
17
|
-
#3.0
|
18
|
-
#multiple level sub notation e.g. x[1][[3]]
|
19
|
-
|
20
|
-
#3.1
|
21
|
-
#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
|
22
10
|
#are handled
|
23
|
-
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
27
|
-
#4
|
28
|
-
#
|
29
|
-
|
30
|
-
#
|
31
|
-
#
|
32
|
-
|
33
|
-
#
|
34
|
-
#
|
35
|
-
|
36
|
-
#
|
37
|
-
#
|
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
|
39
|
+
|
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
|
50
|
+
|
51
|
+
# 3.3
|
52
|
+
# if --> then statements
|
53
|
+
|
54
|
+
# 3.4
|
55
|
+
# piecewise statements
|
56
|
+
|
57
|
+
# 3.5
|
58
|
+
# duals, sensitivity, etc. - I could simply allow
|
38
59
|
#access to the rglpk object wrapper
|
39
60
|
|
40
|
-
|
41
|
-
|
42
|
-
class String
|
43
|
-
def paren_to_array
|
44
|
-
#in: "(2..5)"
|
45
|
-
#out: "[2,3,4,5]"
|
46
|
-
text = self
|
47
|
-
start = text[1].to_i
|
48
|
-
stop = text[-2].to_i
|
49
|
-
(start..stop).map{|i|i}.to_s
|
50
|
-
end
|
51
|
-
|
52
|
-
def sub_paren_with_array
|
53
|
-
text = self
|
54
|
-
targets = text.scan(/\([\d]+\.\.[\d]+\)/)
|
55
|
-
targets.each do |target|
|
56
|
-
text = text.gsub(target, target.paren_to_array)
|
57
|
-
end
|
58
|
-
return(text)
|
59
|
-
end
|
60
|
-
|
61
|
-
def to_array(current_array=[self])
|
62
|
-
#in: "[1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]"
|
63
|
-
#out: [1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]
|
64
|
-
def current_level_information(b)
|
65
|
-
b = b.gsub(" ","")
|
66
|
-
stripped_array = b[1..-2]
|
67
|
-
in_array = 0
|
68
|
-
inside_arrays_string = ""
|
69
|
-
inside_values_string = ""
|
70
|
-
stripped_array.split("").each do |char|
|
71
|
-
if char == "["
|
72
|
-
in_array += 1
|
73
|
-
elsif char == "]"
|
74
|
-
in_array += -1
|
75
|
-
end
|
76
|
-
if (in_array > 0) || (char == "]")
|
77
|
-
inside_arrays_string += char
|
78
|
-
end
|
79
|
-
end
|
80
|
-
stripped_array_without_arrays = stripped_array
|
81
|
-
inside_arrays_string.gsub("][","],,,[").split(",,,").each do |str|
|
82
|
-
stripped_array_without_arrays = stripped_array_without_arrays.gsub(str,"")
|
83
|
-
end
|
84
|
-
inside_values_string = stripped_array_without_arrays.split(",").find_all{|e|e!=""}.join(",")
|
85
|
-
return {:values => inside_values_string, :arrays => inside_arrays_string}
|
86
|
-
end
|
87
|
-
if !current_array.join(",").include?("[")
|
88
|
-
return(current_array)
|
89
|
-
else
|
90
|
-
a = []
|
91
|
-
element = current_array.find_all{|e|e.include?("[")}.first
|
92
|
-
i = current_array.index(element)
|
93
|
-
info = current_level_information(element)
|
94
|
-
info[:values].split(",").each do |v|
|
95
|
-
a << v
|
96
|
-
end
|
97
|
-
info[:arrays].gsub("][","],,,[").split(",,,").each do |v|
|
98
|
-
a << v.to_array
|
99
|
-
end
|
100
|
-
current_array[i] = a
|
101
|
-
return(current_array[0])
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def to_a
|
106
|
-
self.to_array
|
107
|
-
end
|
108
|
-
end
|
61
|
+
# 4.0
|
62
|
+
# import excel sheets as data
|
109
63
|
|
110
|
-
|
111
|
-
|
112
|
-
a = self
|
113
|
-
return 0 if a.class != Array
|
114
|
-
result = 1
|
115
|
-
a.each do |sub_a|
|
116
|
-
if sub_a.class == Array
|
117
|
-
dim = sub_a.dimension
|
118
|
-
result = dim + 1 if dim + 1 > result
|
119
|
-
end
|
120
|
-
end
|
121
|
-
return result
|
122
|
-
end
|
64
|
+
# 4.1
|
65
|
+
# add a SUBTOURS: option to eliminate subtours in a TSP
|
123
66
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
values_at_a(indices[1..-1], current_array[indices[0]])
|
131
|
-
end
|
132
|
-
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
|
133
73
|
|
134
|
-
|
135
|
-
|
136
|
-
int.times do
|
137
|
-
arr << []
|
138
|
-
end
|
139
|
-
arr
|
140
|
-
end
|
141
|
-
|
142
|
-
def matrix(int_arr, current_arr=[])
|
143
|
-
int = int_arr[0]
|
144
|
-
new_int_arr = int_arr[1..-1]
|
145
|
-
if int_arr.empty?
|
146
|
-
return(current_arr)
|
147
|
-
else
|
148
|
-
if current_arr.empty?
|
149
|
-
new_arr = current_arr.inject_dim(int)
|
150
|
-
self.matrix(new_int_arr, new_arr)
|
151
|
-
else
|
152
|
-
current_arr.each do |arr|
|
153
|
-
arr.matrix(int_arr, arr)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def insert_at(position_arr, value)
|
160
|
-
arr = self
|
161
|
-
if position_arr.size == 1
|
162
|
-
arr[position_arr[0]] = value
|
163
|
-
return(arr)
|
164
|
-
else
|
165
|
-
arr[position_arr[0]].insert_at(position_arr[1..-1], value)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
74
|
+
$default_epsilon = 0.01
|
75
|
+
$default_m = 1000000000000.0
|
169
76
|
|
170
77
|
class OPL
|
171
78
|
class Helper
|
@@ -183,16 +90,25 @@ class OPL
|
|
183
90
|
def self.forall(text)
|
184
91
|
#in: "i in (0..2), x[i] <= 5"
|
185
92
|
#out: ["x[0] <= 5", "x[1] <= 5", "x[2] <= 5"]
|
93
|
+
helper = self
|
186
94
|
text = text.sub_paren_with_array
|
187
95
|
if ((text.gsub(" ","")).scan(/\]\,/).size) + ((text.gsub(" ","")).scan(/\)\,/).size) != text.gsub(" ","").scan(/in/).size
|
188
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."
|
189
97
|
end
|
190
98
|
final_constraints = []
|
191
|
-
|
192
|
-
|
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()
|
193
109
|
index_value_pairs = indices.zip(values)
|
194
110
|
variable = text.scan(/[a-z]\[/)[0].gsub("[","")
|
195
|
-
value_combinations =
|
111
|
+
value_combinations = helper.mass_product(values)
|
196
112
|
value_combinations.each_index do |vc_index|
|
197
113
|
value_combination = value_combinations[vc_index]
|
198
114
|
value_combination = [value_combination] unless value_combination.is_a?(Array)
|
@@ -278,8 +194,14 @@ class OPL
|
|
278
194
|
#in: "i in [0,1], j in [4,-5], 3x[i][j]"
|
279
195
|
#out: "3x[0][4] + 3x[0][-5] + 3x[1][4] + 3x[1][-5]"
|
280
196
|
text = text.sub_paren_with_array
|
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
|
281
201
|
if (text.gsub(" ","")).scan(/\]\,/).size != text.scan(/in/).size
|
282
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}"
|
283
205
|
end
|
284
206
|
final_text = ""
|
285
207
|
element = text.split(",")[-1].gsub(" ","")
|
@@ -337,32 +259,34 @@ class OPL
|
|
337
259
|
def self.sub_sum(equation, lp, indexvalues={:indices => [], :values => []})
|
338
260
|
#in: "sum(i in (0..3), x[i]) <= 100"
|
339
261
|
#out: "x[0]+x[1]+x[2]+x[3] <= 100"
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
indexvalues[:indices].
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
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
|
361
285
|
end
|
286
|
+
equation = equation.gsub(text, e)
|
287
|
+
result = self.sum(text, lp)
|
288
|
+
equation = equation.gsub("sum("+text+")", result)
|
362
289
|
end
|
363
|
-
equation = equation.gsub(text, e)
|
364
|
-
result = self.sum(text, lp)
|
365
|
-
equation = equation.gsub("sum("+text+")", result)
|
366
290
|
end
|
367
291
|
return(equation)
|
368
292
|
end
|
@@ -381,8 +305,12 @@ class OPL
|
|
381
305
|
end
|
382
306
|
|
383
307
|
def self.variables(text, lp)#parameter is one side of the equation
|
384
|
-
|
385
|
-
|
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
|
386
314
|
end
|
387
315
|
|
388
316
|
def self.get_all_vars(constraints, lp)
|
@@ -730,6 +658,88 @@ class OPL
|
|
730
658
|
end
|
731
659
|
end
|
732
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
|
733
743
|
end
|
734
744
|
|
735
745
|
class LinearProgram
|
@@ -751,6 +761,9 @@ class OPL
|
|
751
761
|
attr_accessor :matrix_solution
|
752
762
|
attr_accessor :error_message
|
753
763
|
attr_accessor :stop_processing
|
764
|
+
attr_accessor :solution_type
|
765
|
+
attr_accessor :negated_objective_lp
|
766
|
+
attr_accessor :m_index
|
754
767
|
|
755
768
|
def keys
|
756
769
|
[:objective, :constraints, :rows, :solution, :formatted_constraints, :rglpk_object, :solver, :matrix, :simplex_message, :mip_message, :data]
|
@@ -793,6 +806,20 @@ class OPL
|
|
793
806
|
end
|
794
807
|
return(matrix_solution)
|
795
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)
|
822
|
+
end
|
796
823
|
end
|
797
824
|
|
798
825
|
class Objective
|
@@ -876,6 +903,7 @@ def subject_to(constraints, options=[])
|
|
876
903
|
end
|
877
904
|
lp.epsilon = epsilon
|
878
905
|
constraints = constraints.flatten
|
906
|
+
#constraints = constraints.split_ors(constraints)
|
879
907
|
constraints = OPL::Helper.split_equals_a(constraints)
|
880
908
|
data_names = lp.data.map{|d|d.name}
|
881
909
|
constraints = constraints.map do |constraint|
|
@@ -890,6 +918,16 @@ def subject_to(constraints, options=[])
|
|
890
918
|
constraints = constraints.map do |constraint|
|
891
919
|
OPL::Helper.sum_indices(constraint)
|
892
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
|
893
931
|
constraints = constraints.map do |constraint|
|
894
932
|
OPL::Helper.put_constants_on_rhs(constraint)
|
895
933
|
end
|
@@ -984,6 +1022,11 @@ end
|
|
984
1022
|
|
985
1023
|
def optimize(optimization, objective, lp)
|
986
1024
|
original_objective = objective
|
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
|
987
1030
|
objective = OPL::Helper.sub_sum(objective, lp)
|
988
1031
|
objective = OPL::Helper.sum_indices(objective)
|
989
1032
|
objective = OPL::Helper.substitute_data(objective, lp)
|
@@ -1073,12 +1116,23 @@ def optimize(optimization, objective, lp)
|
|
1073
1116
|
end
|
1074
1117
|
lp.solution = answer
|
1075
1118
|
lp.rglpk_object = p
|
1076
|
-
|
1119
|
+
begin
|
1120
|
+
lp.matrix_solution = lp.solution_as_matrix
|
1121
|
+
rescue
|
1122
|
+
return lp
|
1123
|
+
end
|
1077
1124
|
if lp.stop_processing
|
1078
1125
|
lp.solution = lp.error_message
|
1079
1126
|
lp.matrix_solution = lp.error_message
|
1080
1127
|
lp.rglpk_object = lp.error_message
|
1081
1128
|
lp.objective = lp.error_message
|
1082
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
|
1083
1137
|
lp
|
1084
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.2
|
5
|
-
prerelease:
|
4
|
+
version: 2.5.2
|
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: []
|