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.
Files changed (6) hide show
  1. checksums.yaml +7 -0
  2. data/lib/array.rb +59 -0
  3. data/lib/opl.rb +243 -189
  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: a772d39a2592f51445d27d838fb5c0fa303e54f874f37171cd2b1357462a3442
4
+ data.tar.gz: 8e874232cf7834b8dd2eb898dd10d382988594c957ebdc3fe1bf618494150554
5
+ SHA512:
6
+ metadata.gz: 6a166aceae348d8505d78a5b338b66758082967504d7770516802ae97c747b9f525a5b993901f7252e6042d6723e8dd70ba5bcfc499e2f2c39d60b18f4ccb64b
7
+ data.tar.gz: a423e7cd941be1e9ea65f61fcc29cdc55a397a577acfe112d440be947d490ca3cd6773b083f1b2b06d890beff158398e7fa1a7a7ba2c67586913a7fd728e53b0
@@ -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
- #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.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
- #4.0
25
- #absolute value: abs()
26
-
27
- #4.1
28
- #if --> then statements
29
-
30
- #4.2
31
- #or statements
32
-
33
- #4.3
34
- #piecewise statements
35
-
36
- #4.4
37
- #duals, sensitivity, etc. - I could simply allow
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
- $default_epsilon = 0.01
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
- class Array
111
- def dimension
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
- def values_at_a(indices, current_array=self)
125
- #in: self = [3,4,[6,5,[3,4]],3], indices = [2,2,0]
126
- #out: 3
127
- if indices.size == 1
128
- return(current_array[indices[0]])
129
- else
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
- def inject_dim(int)
135
- arr = self
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
- indices = text.scan(/[a-z] in/).map{|sc|sc[0]}
192
- 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()
193
109
  index_value_pairs = indices.zip(values)
194
110
  variable = text.scan(/[a-z]\[/)[0].gsub("[","")
195
- value_combinations = self.mass_product(values)
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
- sums = (equation+"#").split("sum(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}.find_all{|eeee|!eeee.include?("forall")}
341
- sums.each do |text|
342
- e = text
343
- unless indexvalues[:indices].empty?
344
- indexvalues[:indices].each_index do |i|
345
- index = indexvalues[:indices][i]
346
- value = indexvalues[:values][i].to_s
347
- e = e.gsub("("+index, "("+value)
348
- e = e.gsub(index+")", value+")")
349
- e = e.gsub("["+index, "["+value)
350
- e = e.gsub(index+"]", value+"]")
351
- e = e.gsub("=>"+index, "=>"+value)
352
- e = e.gsub("<="+index, "<="+value)
353
- e = e.gsub(">"+index, ">"+value)
354
- e = e.gsub("<"+index, "<"+value)
355
- e = e.gsub("="+index, "="+value)
356
- e = e.gsub("=> "+index, "=> "+value)
357
- e = e.gsub("<= "+index, "<= "+value)
358
- e = e.gsub("> "+index, "> "+value)
359
- e = e.gsub("< "+index, "< "+value)
360
- 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
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
- equation = self.add_ones(text, lp)
385
- 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
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
- lp.matrix_solution = lp.solution_as_matrix
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
@@ -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.2.0
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: 2013-10-12 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: []