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.
Files changed (6) hide show
  1. checksums.yaml +7 -0
  2. data/lib/array.rb +59 -0
  3. data/lib/opl.rb +361 -220
  4. data/lib/string.rb +83 -0
  5. data/lib/sudoku.rb +71 -0
  6. metadata +29 -16
@@ -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
@@ -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
- #TODO
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
- #should return an error message
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
- #4.0
32
- #absolute value: abs()
33
-
34
- #4.1
35
- #if --> then statements
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
- #4.2
38
- #or statements
51
+ # 3.3
52
+ # if --> then statements
39
53
 
40
- #4.3
41
- #piecewise statements
54
+ # 3.4
55
+ # piecewise statements
42
56
 
43
- #4.4
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
- $default_epsilon = 0.01
61
+ # 4.0
62
+ # import excel sheets as data
48
63
 
49
- class String
50
- def paren_to_array
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
- def sub_paren_with_array
60
- text = self
61
- targets = text.scan(/\([\d]+\.\.[\d]+\)/)
62
- targets.each do |target|
63
- text = text.gsub(target, target.paren_to_array)
64
- end
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
- class Array
118
- def dimension
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
- #text = sub_paren_with_array(text)
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
- indices = text.scan(/[a-z] in/).map{|sc|sc[0]}
163
- values = text.scan(/\s\[[\-\s\d+,]+\]/).map{|e|e.gsub(" ", "").scan(/[\-\d]+/)}
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
- #will need to make this multiple variables??
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
- #text = sub_paren_with_array(text)
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
- sums = (equation+"#").split("sum(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}.find_all{|eeee|!eeee.include?("forall")}
312
- sums.each do |text|
313
- e = text
314
- unless indexvalues[:indices].empty?
315
- indexvalues[:indices].each_index do |i|
316
- index = indexvalues[:indices][i]
317
- value = indexvalues[:values][i].to_s
318
- e = e.gsub("("+index, "("+value)
319
- e = e.gsub(index+")", value+")")
320
- e = e.gsub("["+index, "["+value)
321
- e = e.gsub(index+"]", value+"]")
322
- e = e.gsub("=>"+index, "=>"+value)
323
- e = e.gsub("<="+index, "<="+value)
324
- e = e.gsub(">"+index, ">"+value)
325
- e = e.gsub("<"+index, "<"+value)
326
- e = e.gsub("="+index, "="+value)
327
- e = e.gsub("=> "+index, "=> "+value)
328
- e = e.gsub("<= "+index, "<= "+value)
329
- e = e.gsub("> "+index, "> "+value)
330
- e = e.gsub("< "+index, "< "+value)
331
- e = e.gsub("= "+index, "= "+value)
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
- equation = self.add_ones(text, lp)
356
- equation.scan(/[a-z]+[\[\]\d]*/)
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
- constraints = constraints.map do |constraint|
814
- OPL::Helper.put_constants_on_rhs(constraint)
815
- end
816
- constraints = constraints.map do |constraint|
817
- OPL::Helper.put_variables_on_lhs(constraint)
818
- end
819
- constraints = constraints.map do |constraint|
820
- OPL::Helper.sub_rhs_with_summed_constants(constraint)
821
- end
822
- constraints = constraints.map do |constraint|
823
- OPL::Helper.substitute_data(constraint, lp)
824
- end
825
- constraints = constraints.map do |constraint|
826
- OPL::Helper.put_constants_on_rhs(constraint)
827
- end
828
- constraints = constraints.map do |constraint|
829
- OPL::Helper.put_variables_on_lhs(constraint)
830
- end
831
- constraints = constraints.map do |constraint|
832
- OPL::Helper.sub_rhs_with_summed_constants(constraint)
833
- end
834
- constraints = constraints.map do |constraint|
835
- OPL::Helper.sum_variables(constraint, lp)
836
- end
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
- objective = OPL::Helper.sub_sum(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
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
@@ -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
@@ -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.0
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: 2013-10-04 00:00:00.000000000 Z
13
- dependencies: []
14
- description: Built on top of the rglpk gem for linear programming, this gem is meant
15
- to give you a beautifully simple way to formulate your linear or mixed integer program.
16
- The syntax is inspired by OPL Studio, which remains my favorite linear programming
17
- software, but the license is quite expensive.
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
- - GNU
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
- rubyforge_project:
45
- rubygems_version: 1.8.23
58
+ rubygems_version: 3.0.6
46
59
  signing_key:
47
- specification_version: 3
60
+ specification_version: 4
48
61
  summary: Linear Or Mixed Integer Program Solver
49
62
  test_files: []