opl 2.3.0 → 2.5.3
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 +235 -193
- data/lib/string.rb +81 -0
- data/lib/sudoku.rb +71 -0
- metadata +32 -19
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fef938a40faedf2591a3fd279c08f8bbf8a191f258f049af67d643f59c352bc2
|
4
|
+
data.tar.gz: a0503b49464613cd0d388c331df774cda3120659b0755c76f3ccf01218e81f3d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0cdcd508d5b5cb1e1385a90c5451cdf82b37121a2d716c5324e25746b2bc271c852389208eb55f3a4f0f16ab3e51efc401ff2f0bee93798de512da5d7549286
|
7
|
+
data.tar.gz: a8bc9492fc5baddefdca243da8baedd9bed515af67359d2bfad17493cdd49bfd5cee5c9799ce4dca4e22d34637a27fec60cad8181a90e6a70202ceb3cd26df6f
|
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,175 +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
|
-
#catch this error for sum() in forall()
|
16
|
-
#"forall(i in (0..2), sum(j in (0..2), x[i][j] = 1))"
|
17
|
-
#should be:
|
18
|
-
#"forall(i in (0..2), sum(j in (0..2), x[i][j]) = 1)"
|
19
|
-
|
20
|
-
#3.0
|
21
|
-
#multiple level sub notation e.g. x[1][[3]]
|
22
|
-
#why would one use that notation rather than x[1][3]???
|
23
|
-
|
24
|
-
#3.1
|
25
|
-
#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
|
26
10
|
#are handled
|
27
|
-
|
28
|
-
#
|
29
|
-
#
|
30
|
-
|
31
|
-
#4
|
32
|
-
#
|
33
|
-
|
34
|
-
#
|
35
|
-
#
|
36
|
-
|
37
|
-
#
|
38
|
-
#
|
39
|
-
|
40
|
-
#
|
41
|
-
#
|
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
|
42
59
|
#access to the rglpk object wrapper
|
43
60
|
|
44
|
-
|
45
|
-
|
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)
|
63
|
-
end
|
64
|
-
|
65
|
-
def to_array(current_array=[self])
|
66
|
-
#in: "[1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]"
|
67
|
-
#out: [1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]
|
68
|
-
def current_level_information(b)
|
69
|
-
b = b.gsub(" ","")
|
70
|
-
stripped_array = b[1..-2]
|
71
|
-
in_array = 0
|
72
|
-
inside_arrays_string = ""
|
73
|
-
inside_values_string = ""
|
74
|
-
stripped_array.split("").each do |char|
|
75
|
-
if char == "["
|
76
|
-
in_array += 1
|
77
|
-
elsif char == "]"
|
78
|
-
in_array += -1
|
79
|
-
end
|
80
|
-
if (in_array > 0) || (char == "]")
|
81
|
-
inside_arrays_string += char
|
82
|
-
end
|
83
|
-
end
|
84
|
-
stripped_array_without_arrays = stripped_array
|
85
|
-
inside_arrays_string.gsub("][","],,,[").split(",,,").each do |str|
|
86
|
-
stripped_array_without_arrays = stripped_array_without_arrays.gsub(str,"")
|
87
|
-
end
|
88
|
-
inside_values_string = stripped_array_without_arrays.split(",").find_all{|e|e!=""}.join(",")
|
89
|
-
return {:values => inside_values_string, :arrays => inside_arrays_string}
|
90
|
-
end
|
91
|
-
if !current_array.join(",").include?("[")
|
92
|
-
return(current_array)
|
93
|
-
else
|
94
|
-
a = []
|
95
|
-
element = current_array.find_all{|e|e.include?("[")}.first
|
96
|
-
i = current_array.index(element)
|
97
|
-
info = current_level_information(element)
|
98
|
-
info[:values].split(",").each do |v|
|
99
|
-
a << v
|
100
|
-
end
|
101
|
-
info[:arrays].gsub("][","],,,[").split(",,,").each do |v|
|
102
|
-
a << v.to_array
|
103
|
-
end
|
104
|
-
current_array[i] = a
|
105
|
-
return(current_array[0])
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def to_a
|
110
|
-
self.to_array
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
class Array
|
115
|
-
def dimension
|
116
|
-
a = self
|
117
|
-
return 0 if a.class != Array
|
118
|
-
result = 1
|
119
|
-
a.each do |sub_a|
|
120
|
-
if sub_a.class == Array
|
121
|
-
dim = sub_a.dimension
|
122
|
-
result = dim + 1 if dim + 1 > result
|
123
|
-
end
|
124
|
-
end
|
125
|
-
return result
|
126
|
-
end
|
127
|
-
|
128
|
-
def values_at_a(indices, current_array=self)
|
129
|
-
#in: self = [3,4,[6,5,[3,4]],3], indices = [2,2,0]
|
130
|
-
#out: 3
|
131
|
-
if indices.size == 1
|
132
|
-
return(current_array[indices[0]])
|
133
|
-
else
|
134
|
-
values_at_a(indices[1..-1], current_array[indices[0]])
|
135
|
-
end
|
136
|
-
end
|
61
|
+
# 4.0
|
62
|
+
# import excel sheets as data
|
137
63
|
|
138
|
-
|
139
|
-
|
140
|
-
int.times do
|
141
|
-
arr << []
|
142
|
-
end
|
143
|
-
arr
|
144
|
-
end
|
64
|
+
# 4.1
|
65
|
+
# add a SUBTOURS: option to eliminate subtours in a TSP
|
145
66
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
if current_arr.empty?
|
153
|
-
new_arr = current_arr.inject_dim(int)
|
154
|
-
self.matrix(new_int_arr, new_arr)
|
155
|
-
else
|
156
|
-
current_arr.each do |arr|
|
157
|
-
arr.matrix(int_arr, arr)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
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
|
162
73
|
|
163
|
-
|
164
|
-
|
165
|
-
if position_arr.size == 1
|
166
|
-
arr[position_arr[0]] = value
|
167
|
-
return(arr)
|
168
|
-
else
|
169
|
-
arr[position_arr[0]].insert_at(position_arr[1..-1], value)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
74
|
+
$default_epsilon = 0.01
|
75
|
+
$default_m = 1000000000000.0
|
173
76
|
|
174
77
|
class OPL
|
175
78
|
class Helper
|
@@ -291,8 +194,14 @@ class OPL
|
|
291
194
|
#in: "i in [0,1], j in [4,-5], 3x[i][j]"
|
292
195
|
#out: "3x[0][4] + 3x[0][-5] + 3x[1][4] + 3x[1][-5]"
|
293
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
|
294
201
|
if (text.gsub(" ","")).scan(/\]\,/).size != text.scan(/in/).size
|
295
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}"
|
296
205
|
end
|
297
206
|
final_text = ""
|
298
207
|
element = text.split(",")[-1].gsub(" ","")
|
@@ -350,32 +259,34 @@ class OPL
|
|
350
259
|
def self.sub_sum(equation, lp, indexvalues={:indices => [], :values => []})
|
351
260
|
#in: "sum(i in (0..3), x[i]) <= 100"
|
352
261
|
#out: "x[0]+x[1]+x[2]+x[3] <= 100"
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
indexvalues[:indices].
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
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
|
374
285
|
end
|
286
|
+
equation = equation.gsub(text, e)
|
287
|
+
result = self.sum(text, lp)
|
288
|
+
equation = equation.gsub("sum("+text+")", result)
|
375
289
|
end
|
376
|
-
equation = equation.gsub(text, e)
|
377
|
-
result = self.sum(text, lp)
|
378
|
-
equation = equation.gsub("sum("+text+")", result)
|
379
290
|
end
|
380
291
|
return(equation)
|
381
292
|
end
|
@@ -394,8 +305,12 @@ class OPL
|
|
394
305
|
end
|
395
306
|
|
396
307
|
def self.variables(text, lp)#parameter is one side of the equation
|
397
|
-
|
398
|
-
|
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
|
399
314
|
end
|
400
315
|
|
401
316
|
def self.get_all_vars(constraints, lp)
|
@@ -556,8 +471,9 @@ class OPL
|
|
556
471
|
end
|
557
472
|
|
558
473
|
def self.sum_indices(constraint)
|
559
|
-
#pieces_to_sub = constraint.scan(/[a-z]\[\d[\d\+\-]+\]/)
|
560
|
-
pieces_to_sub = constraint.scan(/[a-z\]]\[\d[\d\+\-]+\]/)
|
474
|
+
# pieces_to_sub = constraint.scan(/[a-z]\[\d[\d\+\-]+\]/)
|
475
|
+
# pieces_to_sub = constraint.scan(/[a-z\]]\[\d[\d\+\-]+\]/)
|
476
|
+
pieces_to_sub = constraint.scan(/\[[\d\+\-]+\]/)
|
561
477
|
pieces_to_sub.each do |piece|
|
562
478
|
characters_to_sum = piece.scan(/[\d\+\-]+/)[0]
|
563
479
|
index_sum = self.sum_constants(characters_to_sum)
|
@@ -743,6 +659,88 @@ class OPL
|
|
743
659
|
end
|
744
660
|
end
|
745
661
|
end
|
662
|
+
|
663
|
+
def self.negate(text, explicit=false)
|
664
|
+
# text is one side of an equation
|
665
|
+
# there will be no foralls, no sums, and no abs
|
666
|
+
# in: "z - 3"
|
667
|
+
# out: "-z + 3"
|
668
|
+
working_text = text = text.gsub(" ","")
|
669
|
+
#!("a".."z").to_a.include?(text[0]) &&
|
670
|
+
if !["-","+"].include?(text[0])
|
671
|
+
working_text = "+"+working_text
|
672
|
+
end
|
673
|
+
indices_of_negatives = working_text.index_array("-")
|
674
|
+
indices_of_positives = working_text.index_array("+")
|
675
|
+
indices_of_negatives.each {|i| working_text[i] = "+"}
|
676
|
+
indices_of_positives.each {|i| working_text[i] = "-"}
|
677
|
+
if !explicit && working_text[0] == "+"
|
678
|
+
working_text = working_text[1..-1]
|
679
|
+
end
|
680
|
+
return(working_text)
|
681
|
+
end
|
682
|
+
|
683
|
+
def self.replace_absolute_value(text)
|
684
|
+
# text is a constraint
|
685
|
+
# there will be no foralls and no sums
|
686
|
+
# in: "abs(x) <= 1"
|
687
|
+
# out: "x <= 1", "-x <= 1"
|
688
|
+
# in: "4x + 3y - 6abs(z - 3) <= 4"
|
689
|
+
# out: "4x + 3y - 6z + 18 <= 4", "4x + 3y + 6z - 18 <= 4"
|
690
|
+
if text.include?("abs")
|
691
|
+
helper = self
|
692
|
+
constraints_to_delete = [text]
|
693
|
+
text = text.gsub(" ","")
|
694
|
+
constraints_to_delete << text
|
695
|
+
working_text = "#"+text
|
696
|
+
absolute_value_elements = working_text.scan(/[\-\+\#]\d*abs\([a-z\-\+\*\d\[\]]+\)/)
|
697
|
+
constraints_to_add = []
|
698
|
+
absolute_value_elements.each do |ave|
|
699
|
+
ave = ave.gsub("#","")
|
700
|
+
inside_value = ave.split("(")[1].split(")")[0]
|
701
|
+
positive_ave = inside_value
|
702
|
+
negative_ave = helper.negate(inside_value)
|
703
|
+
if ave[0] == "-" || ave[1] == "-"
|
704
|
+
positive_ave = helper.negate(positive_ave, true)
|
705
|
+
negative_ave = helper.negate(negative_ave, true)
|
706
|
+
elsif ave[0] == "+"
|
707
|
+
positive_ave = "+"+positive_ave
|
708
|
+
end
|
709
|
+
positive_constraint = text.gsub(ave, positive_ave)
|
710
|
+
negative_constraint = text.gsub(ave, negative_ave)
|
711
|
+
constraints_to_add << positive_constraint
|
712
|
+
constraints_to_add << negative_constraint
|
713
|
+
end
|
714
|
+
return {:constraints_to_delete => constraints_to_delete, :constraints_to_add => constraints_to_add}
|
715
|
+
else
|
716
|
+
return {:constraints_to_delete => [], :constraints_to_add => []}
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
def self.strip_abs(text)
|
721
|
+
# in: "3 + abs(x[1] - y) + abs(x[3]) <= 3*abs(-x[2])"
|
722
|
+
# out: {:positive => "3 + x[1] - y + x[3] <= -3*x[2]",
|
723
|
+
# :negative => "3 - x[1] + y - x[3] <= 3*x[2]"}
|
724
|
+
text = text.gsub(" ","")
|
725
|
+
working_text = "#"+text
|
726
|
+
|
727
|
+
end
|
728
|
+
|
729
|
+
def self.either_or(lp, constraint1, constraint2, i)
|
730
|
+
# in: lp, "10.0*x1+4.0*x2+5.0*x3<=600", "2.0*x1+2.0*x2+6.0*x3<=300"
|
731
|
+
# 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}"
|
732
|
+
index1 = lp.constraints.index(constraint1)
|
733
|
+
lp.constraints[index1] = lp.constraints[index1]+"#{$default_m}*m[#{i}]"
|
734
|
+
# add "+M[n]y[n]" to the rhs of the constraint
|
735
|
+
end
|
736
|
+
|
737
|
+
# make a default OPTION that m is BOOLEAN
|
738
|
+
def self.split_ors(lp, constraints)
|
739
|
+
# in: ["x <= 10", "y <= 24 or y + x <= 14"]
|
740
|
+
# out: ["x <= 10", "y - 1000000000000.0*m[0] <= 24", "y + x - 1000000000000.0 + 1000000000000.0*m[0] <= 14"]
|
741
|
+
|
742
|
+
# add m[i+1] to left side of constraints
|
743
|
+
end
|
746
744
|
end
|
747
745
|
|
748
746
|
class LinearProgram
|
@@ -764,6 +762,9 @@ class OPL
|
|
764
762
|
attr_accessor :matrix_solution
|
765
763
|
attr_accessor :error_message
|
766
764
|
attr_accessor :stop_processing
|
765
|
+
attr_accessor :solution_type
|
766
|
+
attr_accessor :negated_objective_lp
|
767
|
+
attr_accessor :m_index
|
767
768
|
|
768
769
|
def keys
|
769
770
|
[:objective, :constraints, :rows, :solution, :formatted_constraints, :rglpk_object, :solver, :matrix, :simplex_message, :mip_message, :data]
|
@@ -806,6 +807,20 @@ class OPL
|
|
806
807
|
end
|
807
808
|
return(matrix_solution)
|
808
809
|
end
|
810
|
+
|
811
|
+
def recreate_with_objective_abs(objective)
|
812
|
+
#in: "abs(x)"
|
813
|
+
#out: {:objective => "x1 + x2", :constraints => "x1 * x2 = 0"}
|
814
|
+
#this is a really tough problem - first time I am considering
|
815
|
+
#abandoning the string parsing approach. Really need to think
|
816
|
+
#about how to attack this
|
817
|
+
lp_class = self
|
818
|
+
helper = OPL::Helper
|
819
|
+
variabes = helper.variables(objective, lp_class.new)
|
820
|
+
new_objective = ""
|
821
|
+
constraints_to_add = []
|
822
|
+
#return(self)
|
823
|
+
end
|
809
824
|
end
|
810
825
|
|
811
826
|
class Objective
|
@@ -889,6 +904,7 @@ def subject_to(constraints, options=[])
|
|
889
904
|
end
|
890
905
|
lp.epsilon = epsilon
|
891
906
|
constraints = constraints.flatten
|
907
|
+
#constraints = constraints.split_ors(constraints)
|
892
908
|
constraints = OPL::Helper.split_equals_a(constraints)
|
893
909
|
data_names = lp.data.map{|d|d.name}
|
894
910
|
constraints = constraints.map do |constraint|
|
@@ -903,6 +919,16 @@ def subject_to(constraints, options=[])
|
|
903
919
|
constraints = constraints.map do |constraint|
|
904
920
|
OPL::Helper.sum_indices(constraint)
|
905
921
|
end
|
922
|
+
new_constraints = []
|
923
|
+
constraints.each do |constraint|
|
924
|
+
replace_absolute_value_results = OPL::Helper.replace_absolute_value(constraint)
|
925
|
+
if replace_absolute_value_results[:constraints_to_add].empty?
|
926
|
+
new_constraints << constraint
|
927
|
+
else
|
928
|
+
new_constraints += replace_absolute_value_results[:constraints_to_add]
|
929
|
+
end
|
930
|
+
end
|
931
|
+
constraints = new_constraints
|
906
932
|
constraints = constraints.map do |constraint|
|
907
933
|
OPL::Helper.put_constants_on_rhs(constraint)
|
908
934
|
end
|
@@ -966,7 +992,7 @@ def subject_to(constraints, options=[])
|
|
966
992
|
end
|
967
993
|
vars = OPL::Helper.variables(lhs, lp)
|
968
994
|
zero_coef_vars = all_vars - vars
|
969
|
-
row = OPL::Row.new(
|
995
|
+
row = OPL::Row.new("row-#{rand(10000)}", lower_bound, upper_bound, epsilon)
|
970
996
|
row.constraint = constraint
|
971
997
|
coefs = coefs + zero_coef_vars.map{|z|0}
|
972
998
|
vars = vars + zero_coef_vars
|
@@ -997,6 +1023,11 @@ end
|
|
997
1023
|
|
998
1024
|
def optimize(optimization, objective, lp)
|
999
1025
|
original_objective = objective
|
1026
|
+
while original_objective.include?("abs")
|
1027
|
+
#need to add some constraints, change the objective,
|
1028
|
+
#and reprocess the constraints
|
1029
|
+
#lp = lp.recreate_with_objective_abs(original_objective)
|
1030
|
+
end
|
1000
1031
|
objective = OPL::Helper.sub_sum(objective, lp)
|
1001
1032
|
objective = OPL::Helper.sum_indices(objective)
|
1002
1033
|
objective = OPL::Helper.substitute_data(objective, lp)
|
@@ -1086,12 +1117,23 @@ def optimize(optimization, objective, lp)
|
|
1086
1117
|
end
|
1087
1118
|
lp.solution = answer
|
1088
1119
|
lp.rglpk_object = p
|
1089
|
-
|
1120
|
+
begin
|
1121
|
+
lp.matrix_solution = lp.solution_as_matrix
|
1122
|
+
rescue
|
1123
|
+
return lp
|
1124
|
+
end
|
1090
1125
|
if lp.stop_processing
|
1091
1126
|
lp.solution = lp.error_message
|
1092
1127
|
lp.matrix_solution = lp.error_message
|
1093
1128
|
lp.rglpk_object = lp.error_message
|
1094
1129
|
lp.objective = lp.error_message
|
1095
1130
|
end
|
1131
|
+
if lp.rglpk_object.status.to_s == "4"
|
1132
|
+
puts "There is no feasible solution."
|
1133
|
+
elsif lp.rglpk_object.status.to_s == "6"
|
1134
|
+
puts "The solution is unbounded."
|
1135
|
+
elsif lp.rglpk_object.status.to_s == "1"
|
1136
|
+
puts "The solution is undefined."
|
1137
|
+
end
|
1096
1138
|
lp
|
1097
1139
|
end
|
data/lib/string.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
class String
|
2
|
+
def paren_to_array
|
3
|
+
#in: "(2..5)"
|
4
|
+
#out: "[2,3,4,5]"
|
5
|
+
eval(self).map{|i|i}.to_s
|
6
|
+
end
|
7
|
+
|
8
|
+
def sub_paren_with_array
|
9
|
+
text = self
|
10
|
+
targets = text.scan(/\([\d]+\.\.[\d]+\)/)
|
11
|
+
targets.each do |target|
|
12
|
+
text = text.gsub(target, target.paren_to_array)
|
13
|
+
end
|
14
|
+
return(text)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_array(current_array=[self])
|
18
|
+
#in: "[1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]"
|
19
|
+
#out: [1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]
|
20
|
+
def current_level_information(b)
|
21
|
+
b = b.gsub(" ","")
|
22
|
+
stripped_array = b[1..-2]
|
23
|
+
in_array = 0
|
24
|
+
inside_arrays_string = ""
|
25
|
+
inside_values_string = ""
|
26
|
+
stripped_array.split("").each do |char|
|
27
|
+
if char == "["
|
28
|
+
in_array += 1
|
29
|
+
elsif char == "]"
|
30
|
+
in_array += -1
|
31
|
+
end
|
32
|
+
if (in_array > 0) || (char == "]")
|
33
|
+
inside_arrays_string += char
|
34
|
+
end
|
35
|
+
end
|
36
|
+
stripped_array_without_arrays = stripped_array
|
37
|
+
inside_arrays_string.gsub("][","],,,[").split(",,,").each do |str|
|
38
|
+
stripped_array_without_arrays = stripped_array_without_arrays.gsub(str,"")
|
39
|
+
end
|
40
|
+
inside_values_string = stripped_array_without_arrays.split(",").find_all{|e|e!=""}.join(",")
|
41
|
+
return {:values => inside_values_string, :arrays => inside_arrays_string}
|
42
|
+
end
|
43
|
+
if !current_array.join(",").include?("[")
|
44
|
+
return(current_array)
|
45
|
+
else
|
46
|
+
a = []
|
47
|
+
element = current_array.find_all{|e|e.include?("[")}.first
|
48
|
+
i = current_array.index(element)
|
49
|
+
info = current_level_information(element)
|
50
|
+
info[:values].split(",").each do |v|
|
51
|
+
a << v
|
52
|
+
end
|
53
|
+
info[:arrays].gsub("][","],,,[").split(",,,").each do |v|
|
54
|
+
a << v.to_array
|
55
|
+
end
|
56
|
+
current_array[i] = a
|
57
|
+
return(current_array[0])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_a
|
62
|
+
self.to_array
|
63
|
+
end
|
64
|
+
|
65
|
+
def index_array(str)
|
66
|
+
indices = []
|
67
|
+
string = self
|
68
|
+
ignore_indices = []
|
69
|
+
search_length = str.size
|
70
|
+
[*(0..string.size-1)].each do |i|
|
71
|
+
if !ignore_indices.include?(i)
|
72
|
+
compare_str = string[i..(i+search_length-1)]
|
73
|
+
if compare_str == str
|
74
|
+
indices << i
|
75
|
+
ignore_indices = ignore_indices + [i..(i+search_length-1)]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
return(indices)
|
80
|
+
end
|
81
|
+
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.3
|
5
|
-
prerelease:
|
4
|
+
version: 2.5.3
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Benjamin Godlove
|
9
|
-
autorequire:
|
8
|
+
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
date: 2021-03-26 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
|
-
-
|
27
|
-
|
41
|
+
- MIT
|
42
|
+
metadata: {}
|
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
|
-
|
46
|
-
|
47
|
-
specification_version: 3
|
58
|
+
rubygems_version: 3.0.9
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
48
61
|
summary: Linear Or Mixed Integer Program Solver
|
49
62
|
test_files: []
|