opl 2.1.0 → 2.2.0

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 (2) hide show
  1. data/lib/opl.rb +159 -72
  2. metadata +2 -2
data/lib/opl.rb CHANGED
@@ -11,13 +11,6 @@ require "rglpk"
11
11
  #
12
12
  #should return an error message
13
13
 
14
- #2.2
15
- #a matrix representation of the solution if using
16
- #sub notation
17
-
18
- #2.3
19
- #parse data from a file
20
-
21
14
  #2.4
22
15
  #way more comprehensive test suite of functionality so far
23
16
 
@@ -137,6 +130,41 @@ class Array
137
130
  values_at_a(indices[1..-1], current_array[indices[0]])
138
131
  end
139
132
  end
133
+
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
140
168
  end
141
169
 
142
170
  class OPL
@@ -153,18 +181,17 @@ class OPL
153
181
  end
154
182
 
155
183
  def self.forall(text)
156
- #need to be able to handle sums inside here
157
184
  #in: "i in (0..2), x[i] <= 5"
158
185
  #out: ["x[0] <= 5", "x[1] <= 5", "x[2] <= 5"]
159
186
  text = text.sub_paren_with_array
160
- #text = sub_paren_with_array(text)
187
+ if ((text.gsub(" ","")).scan(/\]\,/).size) + ((text.gsub(" ","")).scan(/\)\,/).size) != text.gsub(" ","").scan(/in/).size
188
+ 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
+ end
161
190
  final_constraints = []
162
191
  indices = text.scan(/[a-z] in/).map{|sc|sc[0]}
163
192
  values = text.scan(/\s\[[\-\s\d+,]+\]/).map{|e|e.gsub(" ", "").scan(/[\-\d]+/)}
164
193
  index_value_pairs = indices.zip(values)
165
194
  variable = text.scan(/[a-z]\[/)[0].gsub("[","")
166
- #will need to make this multiple variables??
167
- #or is this even used at all????
168
195
  value_combinations = self.mass_product(values)
169
196
  value_combinations.each_index do |vc_index|
170
197
  value_combination = value_combinations[vc_index]
@@ -247,11 +274,13 @@ class OPL
247
274
  equation.gsub("#","")
248
275
  end
249
276
 
250
- def self.sum(text, indexvalues={:indices => [], :values => []})
277
+ def self.sum(text, lp, indexvalues={:indices => [], :values => []})
251
278
  #in: "i in [0,1], j in [4,-5], 3x[i][j]"
252
279
  #out: "3x[0][4] + 3x[0][-5] + 3x[1][4] + 3x[1][-5]"
253
280
  text = text.sub_paren_with_array
254
- #text = sub_paren_with_array(text)
281
+ if (text.gsub(" ","")).scan(/\]\,/).size != text.scan(/in/).size
282
+ 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."
283
+ end
255
284
  final_text = ""
256
285
  element = text.split(",")[-1].gsub(" ","")
257
286
  indices = text.scan(/[a-z] in/).map{|sc|sc[0]}
@@ -305,7 +334,7 @@ class OPL
305
334
  final_text
306
335
  end
307
336
 
308
- def self.sub_sum(equation, indexvalues={:indices => [], :values => []})
337
+ def self.sub_sum(equation, lp, indexvalues={:indices => [], :values => []})
309
338
  #in: "sum(i in (0..3), x[i]) <= 100"
310
339
  #out: "x[0]+x[1]+x[2]+x[3] <= 100"
311
340
  sums = (equation+"#").split("sum(").map{|ee|ee.split(")")[0..-2].join(")")}.find_all{|eee|eee!=""}.find_all{|eeee|!eeee.include?("forall")}
@@ -332,7 +361,7 @@ class OPL
332
361
  end
333
362
  end
334
363
  equation = equation.gsub(text, e)
335
- result = self.sum(text)
364
+ result = self.sum(text, lp)
336
365
  equation = equation.gsub("sum("+text+")", result)
337
366
  end
338
367
  return(equation)
@@ -369,26 +398,26 @@ class OPL
369
398
  def self.get_constants(text)
370
399
  #in: "-8 + x + y + 3"
371
400
  #out: "[-8, +3]"
372
- text = text.gsub(" ","")
373
- text = text+"#"
374
- cs = []
375
- potential_constants = text.scan(/[\d\.]+[^a-z^\[^\]^\d^\.^\)^\*]/)
376
- constants = potential_constants.find_all{|c|![*('a'..'z'),*('A'..'Z'),"["].include?(text[text.index(c)-1])}
377
- searchable_text = text
378
- constants.each_index do |i|
379
- constant = constants[i]
380
- c = constant.scan(/[\d\.]+/)[0]
381
- index = searchable_text.index(constant)
382
- if index == 0
383
- c = "+"+c
384
- else
385
- constant = constant.gsub('+','[+]')
386
- constant = constant.gsub('-','[-]')
387
- c = searchable_text.scan(/[\-\+]#{constant}/)[0]
388
- end
389
- cs << c.scan(/[\-\+][\d\.]+/)[0]
390
- searchable_text[index] = "**"
391
- end
401
+ text = text.gsub(" ","")
402
+ text = text+"#"
403
+ cs = []
404
+ potential_constants = text.scan(/[\d\.]+[^a-z^\[^\]^\d^\.^\)^\*]/)
405
+ constants = potential_constants.find_all{|c|![*('a'..'z'),*('A'..'Z'),"["].include?(text[text.index(c)-1])}
406
+ searchable_text = text
407
+ constants.each_index do |i|
408
+ constant = constants[i]
409
+ c = constant.scan(/[\d\.]+/)[0]
410
+ index = searchable_text.index(constant)
411
+ if index == 0
412
+ c = "+"+c
413
+ else
414
+ constant = constant.gsub('+','[+]')
415
+ constant = constant.gsub('-','[-]')
416
+ c = searchable_text.scan(/[\-\+]#{constant}/)[0]
417
+ end
418
+ cs << c.scan(/[\-\+][\d\.]+/)[0]
419
+ searchable_text[index] = "**"
420
+ end
392
421
  return({:formatted => cs, :unformatted => constants})
393
422
  end
394
423
 
@@ -686,6 +715,21 @@ end
686
715
  text = text[1..-1] if text[0] == "+"
687
716
  return(text)
688
717
  end
718
+
719
+ def self.check_options_syntax(options)
720
+ return if options.empty?
721
+ options.each do |option|
722
+ if option.include?(":")
723
+ title = option.gsub(" ","").split(":")[0]
724
+ value = option.gsub(" ","").split(":")[1]
725
+ if !["nonnegative", "integer", "boolean", "data", "epsilon"].include?(title.downcase)
726
+ raise "Did not recognize the TITLE parameter '#{title}' in the options."
727
+ end
728
+ else
729
+ raise "Options parameter '#{option}' does not have a colon in it. The proper syntax of an option is TITLE: VALUE"
730
+ end
731
+ end
732
+ end
689
733
  end
690
734
 
691
735
  class LinearProgram
@@ -704,6 +748,9 @@ end
704
748
  attr_accessor :variable_types
705
749
  attr_accessor :column_bounds
706
750
  attr_accessor :epsilon
751
+ attr_accessor :matrix_solution
752
+ attr_accessor :error_message
753
+ attr_accessor :stop_processing
707
754
 
708
755
  def keys
709
756
  [:objective, :constraints, :rows, :solution, :formatted_constraints, :rglpk_object, :solver, :matrix, :simplex_message, :mip_message, :data]
@@ -713,6 +760,38 @@ end
713
760
  @rows = []
714
761
  @data = []
715
762
  @epsilon = $default_epsilon
763
+ @matrix_solution = {}
764
+ @stop_processing = false
765
+ end
766
+
767
+ def solution_as_matrix
768
+ lp = self
769
+ variables = lp.solution.keys.map do |key|
770
+ key.scan(/[a-z]/)[0] if key.include?("[")
771
+ end.uniq.find_all{|e|!e.nil?}
772
+ matrix_solution = {}
773
+ variables.each do |var|
774
+ elements = lp.solution.keys.find_all{|key|key.include?(var) && key.include?("[")}
775
+ num_dims = elements[0].scan(/\]\[/).size + 1
776
+ dim_limits = []
777
+ indices_value_pairs = []
778
+ [*(0..(num_dims-1))].each do |i|
779
+ dim_limit = 0
780
+ elements.each do |e|
781
+ indices = e.scan(/\[\d+\]/).map{|str|str.scan(/\d+/)[0].to_i}
782
+ value = lp.solution[e]
783
+ indices_value_pairs << [indices, value]
784
+ dim_limit = indices[i] if indices[i] > dim_limit
785
+ end
786
+ dim_limits << dim_limit+1
787
+ end
788
+ matrix = [].matrix(dim_limits)
789
+ indices_value_pairs.each do |ivp|
790
+ matrix.insert_at(ivp[0], ivp[1].to_f)
791
+ end
792
+ matrix_solution[var] = matrix
793
+ end
794
+ return(matrix_solution)
716
795
  end
717
796
  end
718
797
 
@@ -781,6 +860,7 @@ end
781
860
  end
782
861
 
783
862
  def subject_to(constraints, options=[])
863
+ OPL::Helper.check_options_syntax(options)
784
864
  lp = OPL::LinearProgram.new
785
865
  lp.original_constraints = constraints
786
866
  variable_types = options.find_all{|option|option.downcase.include?("boolean") || option.downcase.include?("integer")} || []
@@ -798,42 +878,42 @@ def subject_to(constraints, options=[])
798
878
  constraints = constraints.flatten
799
879
  constraints = OPL::Helper.split_equals_a(constraints)
800
880
  data_names = lp.data.map{|d|d.name}
801
- constraints = constraints.map do |constraint|
802
- OPL::Helper.sub_forall(constraint)
803
- end.flatten
804
- constraints = constraints.map do |constraint|
805
- OPL::Helper.sum_indices(constraint)
806
- end
807
- constraints = constraints.map do |constraint|
808
- OPL::Helper.sub_sum(constraint)
809
- end
810
- constraints = constraints.map do |constraint|
811
- OPL::Helper.sum_indices(constraint)
812
- end
813
- constraints = constraints.map do |constraint|
814
- OPL::Helper.put_constants_on_rhs(constraint)
815
- end
816
- constraints = constraints.map do |constraint|
817
- OPL::Helper.put_variables_on_lhs(constraint)
818
- end
819
- constraints = constraints.map do |constraint|
820
- OPL::Helper.sub_rhs_with_summed_constants(constraint)
821
- end
822
- constraints = constraints.map do |constraint|
823
- OPL::Helper.substitute_data(constraint, lp)
824
- end
825
- constraints = constraints.map do |constraint|
826
- OPL::Helper.put_constants_on_rhs(constraint)
827
- end
828
- constraints = constraints.map do |constraint|
829
- OPL::Helper.put_variables_on_lhs(constraint)
830
- end
831
- constraints = constraints.map do |constraint|
832
- OPL::Helper.sub_rhs_with_summed_constants(constraint)
833
- end
834
- constraints = constraints.map do |constraint|
835
- OPL::Helper.sum_variables(constraint, lp)
836
- end
881
+ constraints = constraints.map do |constraint|
882
+ OPL::Helper.sub_forall(constraint)
883
+ end.flatten
884
+ constraints = constraints.map do |constraint|
885
+ OPL::Helper.sum_indices(constraint)
886
+ end
887
+ constraints = constraints.map do |constraint|
888
+ OPL::Helper.sub_sum(constraint, lp)
889
+ end
890
+ constraints = constraints.map do |constraint|
891
+ OPL::Helper.sum_indices(constraint)
892
+ end
893
+ constraints = constraints.map do |constraint|
894
+ OPL::Helper.put_constants_on_rhs(constraint)
895
+ end
896
+ constraints = constraints.map do |constraint|
897
+ OPL::Helper.put_variables_on_lhs(constraint)
898
+ end
899
+ constraints = constraints.map do |constraint|
900
+ OPL::Helper.sub_rhs_with_summed_constants(constraint)
901
+ end
902
+ constraints = constraints.map do |constraint|
903
+ OPL::Helper.substitute_data(constraint, lp)
904
+ end
905
+ constraints = constraints.map do |constraint|
906
+ OPL::Helper.put_constants_on_rhs(constraint)
907
+ end
908
+ constraints = constraints.map do |constraint|
909
+ OPL::Helper.put_variables_on_lhs(constraint)
910
+ end
911
+ constraints = constraints.map do |constraint|
912
+ OPL::Helper.sub_rhs_with_summed_constants(constraint)
913
+ end
914
+ constraints = constraints.map do |constraint|
915
+ OPL::Helper.sum_variables(constraint, lp)
916
+ end
837
917
  lp.constraints = constraints
838
918
  all_vars = OPL::Helper.get_all_vars(constraints, lp)
839
919
  variable_type_hash = OPL::Helper.produce_variable_type_hash(variable_types, all_vars)
@@ -904,7 +984,7 @@ end
904
984
 
905
985
  def optimize(optimization, objective, lp)
906
986
  original_objective = objective
907
- objective = OPL::Helper.sub_sum(objective)
987
+ objective = OPL::Helper.sub_sum(objective, lp)
908
988
  objective = OPL::Helper.sum_indices(objective)
909
989
  objective = OPL::Helper.substitute_data(objective, lp)
910
990
  objective_constants = OPL::Helper.get_constants(objective)
@@ -993,5 +1073,12 @@ def optimize(optimization, objective, lp)
993
1073
  end
994
1074
  lp.solution = answer
995
1075
  lp.rglpk_object = p
1076
+ lp.matrix_solution = lp.solution_as_matrix
1077
+ if lp.stop_processing
1078
+ lp.solution = lp.error_message
1079
+ lp.matrix_solution = lp.error_message
1080
+ lp.rglpk_object = lp.error_message
1081
+ lp.objective = lp.error_message
1082
+ end
996
1083
  lp
997
1084
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opl
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-04 00:00:00.000000000 Z
12
+ date: 2013-10-12 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Built on top of the rglpk gem for linear programming, this gem is meant
15
15
  to give you a beautifully simple way to formulate your linear or mixed integer program.