opl 2.4.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/lib/array.rb +59 -0
  3. data/lib/opl.rb +211 -168
  4. data/lib/string.rb +83 -0
  5. metadata +12 -13
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1a11eb865eed8b1f280c8dd764f76fa4916357d3
4
+ data.tar.gz: 78ec8f5196e4d90f2d6f237c21571262cab92dd0
5
+ SHA512:
6
+ metadata.gz: 5124341d93c8438663e4c2244c7abad2f51036ea6278b0f6e9f2ae092823cf29a821cc4d29541a3ee64ee668a4bad1068e424a2c39f950a0d0ff85f468ec7eee
7
+ data.tar.gz: fd1490b7e159ab31361fe0e748bafb0dfd8f03a2c578e732f79d0eaea7a25309d37cbb302b4ca11e2f8b84dd7a1671e34c75f2e59bc8adea02fdda441783597f
@@ -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,158 +1,77 @@
1
1
  require "rglpk"
2
+ require_relative "string.rb"
3
+ require_relative "array.rb"
2
4
 
3
- #3.0
4
- #Implement more advanced TSPs in order to
5
+ # Notes for future functionality
6
+ #
7
+ # Implement more advanced TSPs in order to
5
8
  #make sure extreme cases of foralls and sums
6
9
  #are handled
10
+ # Make an ERRORS : ON option
11
+ #
12
+ # need to handle multiple abs() in one constraint:
13
+ #
14
+ # 4 + abs(x - y) + abs(z) <= 4
15
+ #
16
+ # still need to implement abs() in objective
17
+ # I am not sure how to handle arithmetic inside
18
+ # an abs() in an objective function
19
+ #
20
+ # perhaps I should split into two LPs,
21
+ # one with +objective and one with -objective,
22
+ # solve both, and then take the more optimal solution
23
+ #
24
+ # maximize(abs(x))
25
+ # subject to:
26
+ # x < 3
27
+ # x > -7
28
+ #
29
+ # we set x = x1 - x2, x1,x2>=0
30
+ # so abs(x) = x1 + x2
31
+ # the lp should become
32
+ #
33
+ # maximize(x1 + x2)
34
+ # subject to:
35
+ # x1 - x2 < 3
36
+ # x1 - x2 > -7
37
+ # NONNEGATIVE: x1, x2
7
38
 
8
- #3.1
9
- #absolute value: abs()
39
+ # 3.2
40
+ # or statements
41
+ # in order to do this I first have to do some refactoring:
42
+ # constraints need to be immediately turned in to objects
43
+ # step 1: turn constraints into objects and make current code work
44
+ # step 2: add a property to Constraint called or_index
45
+ # The or_index property represents the m[i] that belongs to that constraint
46
+ # step 3: after parsing is done, add the m[i] code
47
+ # Turning objects into constraints will be useful for any processing
48
+ # that I want to leave for later - i.e. if-->then, piecewise
10
49
 
11
- #3.2
12
- #if --> then statements
50
+ # 3.3
51
+ # if --> then statements
13
52
 
14
- #3.3
15
- #or statements
53
+ # 3.4
54
+ # piecewise statements
16
55
 
17
- #3.4
18
- #piecewise statements
19
-
20
- #3.5
21
- #duals, sensitivity, etc. - I could simply allow
56
+ # 3.5
57
+ # duals, sensitivity, etc. - I could simply allow
22
58
  #access to the rglpk object wrapper
23
59
 
24
- #4.0
25
- #import excel sheets as data
26
-
27
- $default_epsilon = 0.01
28
-
29
- class String
30
- def paren_to_array
31
- #in: "(2..5)"
32
- #out: "[2,3,4,5]"
33
- text = self
34
- start = text[1].to_i
35
- stop = text[-2].to_i
36
- (start..stop).map{|i|i}.to_s
37
- end
38
-
39
- def sub_paren_with_array
40
- text = self
41
- targets = text.scan(/\([\d]+\.\.[\d]+\)/)
42
- targets.each do |target|
43
- text = text.gsub(target, target.paren_to_array)
44
- end
45
- return(text)
46
- end
47
-
48
- def to_array(current_array=[self])
49
- #in: "[1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]"
50
- #out: [1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]
51
- def current_level_information(b)
52
- b = b.gsub(" ","")
53
- stripped_array = b[1..-2]
54
- in_array = 0
55
- inside_arrays_string = ""
56
- inside_values_string = ""
57
- stripped_array.split("").each do |char|
58
- if char == "["
59
- in_array += 1
60
- elsif char == "]"
61
- in_array += -1
62
- end
63
- if (in_array > 0) || (char == "]")
64
- inside_arrays_string += char
65
- end
66
- end
67
- stripped_array_without_arrays = stripped_array
68
- inside_arrays_string.gsub("][","],,,[").split(",,,").each do |str|
69
- stripped_array_without_arrays = stripped_array_without_arrays.gsub(str,"")
70
- end
71
- inside_values_string = stripped_array_without_arrays.split(",").find_all{|e|e!=""}.join(",")
72
- return {:values => inside_values_string, :arrays => inside_arrays_string}
73
- end
74
- if !current_array.join(",").include?("[")
75
- return(current_array)
76
- else
77
- a = []
78
- element = current_array.find_all{|e|e.include?("[")}.first
79
- i = current_array.index(element)
80
- info = current_level_information(element)
81
- info[:values].split(",").each do |v|
82
- a << v
83
- end
84
- info[:arrays].gsub("][","],,,[").split(",,,").each do |v|
85
- a << v.to_array
86
- end
87
- current_array[i] = a
88
- return(current_array[0])
89
- end
90
- end
91
-
92
- def to_a
93
- self.to_array
94
- end
95
- end
60
+ # 4.0
61
+ # import excel sheets as data
96
62
 
97
- class Array
98
- def dimension
99
- a = self
100
- return 0 if a.class != Array
101
- result = 1
102
- a.each do |sub_a|
103
- if sub_a.class == Array
104
- dim = sub_a.dimension
105
- result = dim + 1 if dim + 1 > result
106
- end
107
- end
108
- return result
109
- end
110
-
111
- def values_at_a(indices, current_array=self)
112
- #in: self = [3,4,[6,5,[3,4]],3], indices = [2,2,0]
113
- #out: 3
114
- if indices.size == 1
115
- return(current_array[indices[0]])
116
- else
117
- values_at_a(indices[1..-1], current_array[indices[0]])
118
- end
119
- end
63
+ # 4.1
64
+ # add a SUBTOURS: option to eliminate subtours in a TSP
120
65
 
121
- def inject_dim(int)
122
- arr = self
123
- int.times do
124
- arr << []
125
- end
126
- arr
127
- end
66
+ # 4.2
67
+ # have an option where you can pass in a function.
68
+ #the function takes the resulting lp as input.
69
+ #you can add constraints based on the lp and re-run it.
70
+ #this will be useful for adding sub-tours to a problem
71
+ #without having to look at the output manually every time
128
72
 
129
- def matrix(int_arr, current_arr=[])
130
- int = int_arr[0]
131
- new_int_arr = int_arr[1..-1]
132
- if int_arr.empty?
133
- return(current_arr)
134
- else
135
- if current_arr.empty?
136
- new_arr = current_arr.inject_dim(int)
137
- self.matrix(new_int_arr, new_arr)
138
- else
139
- current_arr.each do |arr|
140
- arr.matrix(int_arr, arr)
141
- end
142
- end
143
- end
144
- end
145
-
146
- def insert_at(position_arr, value)
147
- arr = self
148
- if position_arr.size == 1
149
- arr[position_arr[0]] = value
150
- return(arr)
151
- else
152
- arr[position_arr[0]].insert_at(position_arr[1..-1], value)
153
- end
154
- end
155
- end
73
+ $default_epsilon = 0.01
74
+ $default_m = 1000000000000.0
156
75
 
157
76
  class OPL
158
77
  class Helper
@@ -274,6 +193,10 @@ class OPL
274
193
  #in: "i in [0,1], j in [4,-5], 3x[i][j]"
275
194
  #out: "3x[0][4] + 3x[0][-5] + 3x[1][4] + 3x[1][-5]"
276
195
  text = text.sub_paren_with_array
196
+ if text.scan(/\(\d+\+\d+\)/).size > 0
197
+ text.scan(/\(\d+\+\d+\)/).each {|e| text = text.gsub(e,eval(e.gsub("(","").gsub(")","")).to_s) }
198
+ end
199
+ text = text.sub_paren_with_array
277
200
  if (text.gsub(" ","")).scan(/\]\,/).size != text.scan(/in/).size
278
201
  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."
279
202
  elsif (text.gsub(" ","").include?("=") || text.gsub(" ","").include?("<") || text.gsub(" ","").include?(">"))
@@ -335,32 +258,34 @@ class OPL
335
258
  def self.sub_sum(equation, lp, indexvalues={:indices => [], :values => []})
336
259
  #in: "sum(i in (0..3), x[i]) <= 100"
337
260
  #out: "x[0]+x[1]+x[2]+x[3] <= 100"
338
- sums = (equation+"#").split("sum(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}.find_all{|eeee|!eeee.include?("forall")}
339
- sums.each do |text|
340
- e = text
341
- unless indexvalues[:indices].empty?
342
- indexvalues[:indices].each_index do |i|
343
- index = indexvalues[:indices][i]
344
- value = indexvalues[:values][i].to_s
345
- e = e.gsub("("+index, "("+value)
346
- e = e.gsub(index+")", value+")")
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)
261
+ if equation.include?("sum(")
262
+ sums = (equation+"#").split("sum(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}.find_all{|eeee|!eeee.include?("forall")}
263
+ sums.each do |text|
264
+ e = text
265
+ unless indexvalues[:indices].empty?
266
+ indexvalues[:indices].each_index do |i|
267
+ index = indexvalues[:indices][i]
268
+ value = indexvalues[:values][i].to_s
269
+ e = e.gsub("("+index, "("+value)
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
+ end
359
284
  end
285
+ equation = equation.gsub(text, e)
286
+ result = self.sum(text, lp)
287
+ equation = equation.gsub("sum("+text+")", result)
360
288
  end
361
- equation = equation.gsub(text, e)
362
- result = self.sum(text, lp)
363
- equation = equation.gsub("sum("+text+")", result)
364
289
  end
365
290
  return(equation)
366
291
  end
@@ -379,8 +304,12 @@ class OPL
379
304
  end
380
305
 
381
306
  def self.variables(text, lp)#parameter is one side of the equation
382
- equation = self.add_ones(text, lp)
383
- equation.scan(/[a-z]+[\[\]\d]*/)
307
+ text = self.add_ones(text, lp)
308
+ text = text.gsub("abs","").gsub("(","").gsub(")","")
309
+ variables = text.scan(/[a-z][\[\]\d]*/)
310
+ raise("The variable letter a is reserved for special processes. Please rename your variable to something other than a.") if variables.join.include?("a")
311
+ raise("The variable letter m is reserved for special processes. Please rename your variable to something other than m.") if variables.join.include?("m")
312
+ return variables
384
313
  end
385
314
 
386
315
  def self.get_all_vars(constraints, lp)
@@ -728,6 +657,88 @@ class OPL
728
657
  end
729
658
  end
730
659
  end
660
+
661
+ def self.negate(text, explicit=false)
662
+ # text is one side of an equation
663
+ # there will be no foralls, no sums, and no abs
664
+ # in: "z - 3"
665
+ # out: "-z + 3"
666
+ working_text = text = text.gsub(" ","")
667
+ #!("a".."z").to_a.include?(text[0]) &&
668
+ if !["-","+"].include?(text[0])
669
+ working_text = "+"+working_text
670
+ end
671
+ indices_of_negatives = working_text.index_array("-")
672
+ indices_of_positives = working_text.index_array("+")
673
+ indices_of_negatives.each {|i| working_text[i] = "+"}
674
+ indices_of_positives.each {|i| working_text[i] = "-"}
675
+ if !explicit && working_text[0] == "+"
676
+ working_text = working_text[1..-1]
677
+ end
678
+ return(working_text)
679
+ end
680
+
681
+ def self.replace_absolute_value(text)
682
+ # text is a constraint
683
+ # there will be no foralls and no sums
684
+ # in: "abs(x) <= 1"
685
+ # out: "x <= 1", "-x <= 1"
686
+ # in: "4x + 3y - 6abs(z - 3) <= 4"
687
+ # out: "4x + 3y - 6z + 18 <= 4", "4x + 3y + 6z - 18 <= 4"
688
+ if text.include?("abs")
689
+ helper = self
690
+ constraints_to_delete = [text]
691
+ text = text.gsub(" ","")
692
+ constraints_to_delete << text
693
+ working_text = "#"+text
694
+ absolute_value_elements = working_text.scan(/[\-\+\#]\d*abs\([a-z\-\+\*\d\[\]]+\)/)
695
+ constraints_to_add = []
696
+ absolute_value_elements.each do |ave|
697
+ ave = ave.gsub("#","")
698
+ inside_value = ave.split("(")[1].split(")")[0]
699
+ positive_ave = inside_value
700
+ negative_ave = helper.negate(inside_value)
701
+ if ave[0] == "-" || ave[1] == "-"
702
+ positive_ave = helper.negate(positive_ave, true)
703
+ negative_ave = helper.negate(negative_ave, true)
704
+ elsif ave[0] == "+"
705
+ positive_ave = "+"+positive_ave
706
+ end
707
+ positive_constraint = text.gsub(ave, positive_ave)
708
+ negative_constraint = text.gsub(ave, negative_ave)
709
+ constraints_to_add << positive_constraint
710
+ constraints_to_add << negative_constraint
711
+ end
712
+ return {:constraints_to_delete => constraints_to_delete, :constraints_to_add => constraints_to_add}
713
+ else
714
+ return {:constraints_to_delete => [], :constraints_to_add => []}
715
+ end
716
+ end
717
+
718
+ def self.strip_abs(text)
719
+ # in: "3 + abs(x[1] - y) + abs(x[3]) <= 3*abs(-x[2])"
720
+ # out: {:positive => "3 + x[1] - y + x[3] <= -3*x[2]",
721
+ # :negative => "3 - x[1] + y - x[3] <= 3*x[2]"}
722
+ text = text.gsub(" ","")
723
+ working_text = "#"+text
724
+
725
+ end
726
+
727
+ def self.either_or(lp, constraint1, constraint2, i)
728
+ # in: lp, "10.0*x1+4.0*x2+5.0*x3<=600", "2.0*x1+2.0*x2+6.0*x3<=300"
729
+ # 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}"
730
+ index1 = lp.constraints.index(constraint1)
731
+ lp.constraints[index1] = lp.constraints[index1]+"#{$default_m}*m[#{i}]"
732
+ # add "+M[n]y[n]" to the rhs of the constraint
733
+ end
734
+
735
+ # make a default OPTION that m is BOOLEAN
736
+ def self.split_ors(lp, constraints)
737
+ # in: ["x <= 10", "y <= 24 or y + x <= 14"]
738
+ # out: ["x <= 10", "y - 1000000000000.0*m[0] <= 24", "y + x - 1000000000000.0 + 1000000000000.0*m[0] <= 14"]
739
+
740
+ # add m[i+1] to left side of constraints
741
+ end
731
742
  end
732
743
 
733
744
  class LinearProgram
@@ -750,6 +761,8 @@ class OPL
750
761
  attr_accessor :error_message
751
762
  attr_accessor :stop_processing
752
763
  attr_accessor :solution_type
764
+ attr_accessor :negated_objective_lp
765
+ attr_accessor :m_index
753
766
 
754
767
  def keys
755
768
  [:objective, :constraints, :rows, :solution, :formatted_constraints, :rglpk_object, :solver, :matrix, :simplex_message, :mip_message, :data]
@@ -792,6 +805,20 @@ class OPL
792
805
  end
793
806
  return(matrix_solution)
794
807
  end
808
+
809
+ def recreate_with_objective_abs(objective)
810
+ #in: "abs(x)"
811
+ #out: {:objective => "x1 + x2", :constraints => "x1 * x2 = 0"}
812
+ #this is a really tough problem - first time I am considering
813
+ #abandoning the string parsing approach. Really need to think
814
+ #about how to attack this
815
+ lp_class = self
816
+ helper = OPL::Helper
817
+ variabes = helper.variables(objective, lp_class.new)
818
+ new_objective = ""
819
+ constraints_to_add = []
820
+ #return(self)
821
+ end
795
822
  end
796
823
 
797
824
  class Objective
@@ -875,6 +902,7 @@ def subject_to(constraints, options=[])
875
902
  end
876
903
  lp.epsilon = epsilon
877
904
  constraints = constraints.flatten
905
+ #constraints = constraints.split_ors(constraints)
878
906
  constraints = OPL::Helper.split_equals_a(constraints)
879
907
  data_names = lp.data.map{|d|d.name}
880
908
  constraints = constraints.map do |constraint|
@@ -889,6 +917,16 @@ def subject_to(constraints, options=[])
889
917
  constraints = constraints.map do |constraint|
890
918
  OPL::Helper.sum_indices(constraint)
891
919
  end
920
+ new_constraints = []
921
+ constraints.each do |constraint|
922
+ replace_absolute_value_results = OPL::Helper.replace_absolute_value(constraint)
923
+ if replace_absolute_value_results[:constraints_to_add].empty?
924
+ new_constraints << constraint
925
+ else
926
+ new_constraints += replace_absolute_value_results[:constraints_to_add]
927
+ end
928
+ end
929
+ constraints = new_constraints
892
930
  constraints = constraints.map do |constraint|
893
931
  OPL::Helper.put_constants_on_rhs(constraint)
894
932
  end
@@ -983,6 +1021,11 @@ end
983
1021
 
984
1022
  def optimize(optimization, objective, lp)
985
1023
  original_objective = objective
1024
+ while original_objective.include?("abs")
1025
+ #need to add some constraints, change the objective,
1026
+ #and reprocess the constraints
1027
+ #lp = lp.recreate_with_objective_abs(original_objective)
1028
+ end
986
1029
  objective = OPL::Helper.sub_sum(objective, lp)
987
1030
  objective = OPL::Helper.sum_indices(objective)
988
1031
  objective = OPL::Helper.substitute_data(objective, lp)
@@ -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
metadata CHANGED
@@ -1,49 +1,48 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opl
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
5
- prerelease:
4
+ version: 2.4.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-21 00:00:00.000000000 Z
11
+ date: 2015-02-15 00:00:00.000000000 Z
13
12
  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.
13
+ description: This gem gives you a beautifully simple way to formulate your linear
14
+ or mixed integer program. The syntax is inspired by OPL Studio, which remains my
15
+ favorite linear programming software, but the license is quite expensive.
18
16
  email: bgodlove88@gmail.com
19
17
  executables: []
20
18
  extensions: []
21
19
  extra_rdoc_files: []
22
20
  files:
21
+ - lib/array.rb
23
22
  - lib/opl.rb
23
+ - lib/string.rb
24
24
  homepage: http://github.com/brg8/opl
25
25
  licenses:
26
26
  - GNU
27
+ metadata: {}
27
28
  post_install_message:
28
29
  rdoc_options: []
29
30
  require_paths:
30
31
  - lib
31
32
  required_ruby_version: !ruby/object:Gem::Requirement
32
- none: false
33
33
  requirements:
34
- - - ! '>='
34
+ - - ">="
35
35
  - !ruby/object:Gem::Version
36
36
  version: '0'
37
37
  required_rubygems_version: !ruby/object:Gem::Requirement
38
- none: false
39
38
  requirements:
40
- - - ! '>='
39
+ - - ">="
41
40
  - !ruby/object:Gem::Version
42
41
  version: '0'
43
42
  requirements: []
44
43
  rubyforge_project:
45
- rubygems_version: 1.8.23
44
+ rubygems_version: 2.2.2
46
45
  signing_key:
47
- specification_version: 3
46
+ specification_version: 4
48
47
  summary: Linear Or Mixed Integer Program Solver
49
48
  test_files: []