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.
Files changed (6) hide show
  1. checksums.yaml +7 -0
  2. data/lib/array.rb +59 -0
  3. data/lib/opl.rb +235 -193
  4. data/lib/string.rb +81 -0
  5. data/lib/sudoku.rb +71 -0
  6. 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
- #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
- #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
- #4.0
29
- #absolute value: abs()
30
-
31
- #4.1
32
- #if --> then statements
33
-
34
- #4.2
35
- #or statements
36
-
37
- #4.3
38
- #piecewise statements
39
-
40
- #4.4
41
- #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
42
59
  #access to the rglpk object wrapper
43
60
 
44
- $default_epsilon = 0.01
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
- def inject_dim(int)
139
- arr = self
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
- def matrix(int_arr, current_arr=[])
147
- int = int_arr[0]
148
- new_int_arr = int_arr[1..-1]
149
- if int_arr.empty?
150
- return(current_arr)
151
- else
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
- def insert_at(position_arr, value)
164
- arr = self
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
- sums = (equation+"#").split("sum(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}.find_all{|eeee|!eeee.include?("forall")}
354
- sums.each do |text|
355
- e = text
356
- unless indexvalues[:indices].empty?
357
- indexvalues[:indices].each_index do |i|
358
- index = indexvalues[:indices][i]
359
- value = indexvalues[:values][i].to_s
360
- e = e.gsub("("+index, "("+value)
361
- e = e.gsub(index+")", value+")")
362
- e = e.gsub("["+index, "["+value)
363
- e = e.gsub(index+"]", value+"]")
364
- e = e.gsub("=>"+index, "=>"+value)
365
- e = e.gsub("<="+index, "<="+value)
366
- e = e.gsub(">"+index, ">"+value)
367
- e = e.gsub("<"+index, "<"+value)
368
- e = e.gsub("="+index, "="+value)
369
- e = e.gsub("=> "+index, "=> "+value)
370
- e = e.gsub("<= "+index, "<= "+value)
371
- e = e.gsub("> "+index, "> "+value)
372
- e = e.gsub("< "+index, "< "+value)
373
- 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
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
- equation = self.add_ones(text, lp)
398
- 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
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(name, lower_bound, upper_bound, epsilon)
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
- lp.matrix_solution = lp.solution_as_matrix
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.0
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: 2013-10-13 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: 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
- - GNU
27
- post_install_message:
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
- rubyforge_project:
45
- rubygems_version: 1.8.23
46
- signing_key:
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: []