opl 2.4.0 → 2.4.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 (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: []