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.
- 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.
|