opl 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/opl.rb +159 -72
- 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
|
-
|
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
|
-
|
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.
|
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-
|
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.
|