opl 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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.