opl 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/opl.rb +163 -54
- metadata +2 -2
data/lib/opl.rb
CHANGED
@@ -8,10 +8,8 @@ require "rglpk"
|
|
8
8
|
# subject_to([
|
9
9
|
# "x >= 0"
|
10
10
|
# ]))
|
11
|
-
|
12
|
-
#
|
13
|
-
#ability to do arithmetic with data indices
|
14
|
-
#forall(i in (2..3) d[i-1]x[i] <= 10)
|
11
|
+
#
|
12
|
+
#should return an error message
|
15
13
|
|
16
14
|
#2.2
|
17
15
|
#a matrix representation of the solution if using
|
@@ -20,6 +18,9 @@ require "rglpk"
|
|
20
18
|
#2.3
|
21
19
|
#parse data from a file
|
22
20
|
|
21
|
+
#2.4
|
22
|
+
#way more comprehensive test suite of functionality so far
|
23
|
+
|
23
24
|
#3.0
|
24
25
|
#multiple level sub notation e.g. x[1][[3]]
|
25
26
|
|
@@ -39,6 +40,10 @@ require "rglpk"
|
|
39
40
|
#4.3
|
40
41
|
#piecewise statements
|
41
42
|
|
43
|
+
#4.4
|
44
|
+
#duals, sensitivity, etc. - I could simply allow
|
45
|
+
#access to the rglpk object wrapper
|
46
|
+
|
42
47
|
$default_epsilon = 0.01
|
43
48
|
|
44
49
|
class String
|
@@ -59,6 +64,54 @@ class String
|
|
59
64
|
end
|
60
65
|
return(text)
|
61
66
|
end
|
67
|
+
|
68
|
+
def to_array(current_array=[self])
|
69
|
+
#in: "[1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]"
|
70
|
+
#out: [1,2,[3,4],[4,2,[3,2,[4,2]]],2,[4,2]]
|
71
|
+
def current_level_information(b)
|
72
|
+
b = b.gsub(" ","")
|
73
|
+
stripped_array = b[1..-2]
|
74
|
+
in_array = 0
|
75
|
+
inside_arrays_string = ""
|
76
|
+
inside_values_string = ""
|
77
|
+
stripped_array.split("").each do |char|
|
78
|
+
if char == "["
|
79
|
+
in_array += 1
|
80
|
+
elsif char == "]"
|
81
|
+
in_array += -1
|
82
|
+
end
|
83
|
+
if (in_array > 0) || (char == "]")
|
84
|
+
inside_arrays_string += char
|
85
|
+
end
|
86
|
+
end
|
87
|
+
stripped_array_without_arrays = stripped_array
|
88
|
+
inside_arrays_string.gsub("][","],,,[").split(",,,").each do |str|
|
89
|
+
stripped_array_without_arrays = stripped_array_without_arrays.gsub(str,"")
|
90
|
+
end
|
91
|
+
inside_values_string = stripped_array_without_arrays.split(",").find_all{|e|e!=""}.join(",")
|
92
|
+
return {:values => inside_values_string, :arrays => inside_arrays_string}
|
93
|
+
end
|
94
|
+
if !current_array.join(",").include?("[")
|
95
|
+
return(current_array)
|
96
|
+
else
|
97
|
+
a = []
|
98
|
+
element = current_array.find_all{|e|e.include?("[")}.first
|
99
|
+
i = current_array.index(element)
|
100
|
+
info = current_level_information(element)
|
101
|
+
info[:values].split(",").each do |v|
|
102
|
+
a << v
|
103
|
+
end
|
104
|
+
info[:arrays].gsub("][","],,,[").split(",,,").each do |v|
|
105
|
+
a << v.to_array
|
106
|
+
end
|
107
|
+
current_array[i] = a
|
108
|
+
return(current_array[0])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_a
|
113
|
+
self.to_array
|
114
|
+
end
|
62
115
|
end
|
63
116
|
|
64
117
|
class Array
|
@@ -316,22 +369,26 @@ class OPL
|
|
316
369
|
def self.get_constants(text)
|
317
370
|
#in: "-8 + x + y + 3"
|
318
371
|
#out: "[-8, +3]"
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
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
|
335
392
|
return({:formatted => cs, :unformatted => constants})
|
336
393
|
end
|
337
394
|
|
@@ -457,7 +514,8 @@ class OPL
|
|
457
514
|
end
|
458
515
|
|
459
516
|
def self.sum_indices(constraint)
|
460
|
-
pieces_to_sub = constraint.scan(/[a-z]\[\d[\d\+\-]+\]/)
|
517
|
+
#pieces_to_sub = constraint.scan(/[a-z]\[\d[\d\+\-]+\]/)
|
518
|
+
pieces_to_sub = constraint.scan(/[a-z\]]\[\d[\d\+\-]+\]/)
|
461
519
|
pieces_to_sub.each do |piece|
|
462
520
|
characters_to_sum = piece.scan(/[\d\+\-]+/)[0]
|
463
521
|
index_sum = self.sum_constants(characters_to_sum)
|
@@ -496,7 +554,6 @@ class OPL
|
|
496
554
|
lhs = helper.sides(formatted_constraint)[:lhs]
|
497
555
|
formatted_lhs = helper.add_ones(lhs, lp)
|
498
556
|
vars = helper.variables(formatted_lhs, lp)
|
499
|
-
#data coefficients should already be substituted
|
500
557
|
coefs = helper.coefficients(formatted_lhs, lp)
|
501
558
|
var_coef_hash = {}
|
502
559
|
vars.each_index do |i|
|
@@ -548,18 +605,12 @@ class OPL
|
|
548
605
|
data_string = data_hash_string.gsub("{",",").gsub("}",",")
|
549
606
|
names = data_string.scan(/,[a-z]/).map{|comma_name|comma_name.gsub(",","")}
|
550
607
|
string_values = data_string.scan(/\=\>[\[\d\.\]\,\-]+,/).map{|scanned_value|scanned_value[2..-2]}
|
551
|
-
values =
|
552
|
-
string_values.each do |string_value|
|
553
|
-
if string_value.include?("[")
|
554
|
-
values << string_value.gsub("[","").gsub("]","").split(",")
|
555
|
-
else
|
556
|
-
values << string_value.to_f
|
557
|
-
end
|
558
|
-
end
|
608
|
+
values = string_values.map{|sv|sv.to_a}
|
559
609
|
data_hash = {}
|
560
610
|
names.each_index do |i|
|
561
611
|
name = names[i]
|
562
612
|
value = values[i]
|
613
|
+
value = value[0] if value.size == 1
|
563
614
|
data_hash[name] = value
|
564
615
|
end
|
565
616
|
return(data_hash)
|
@@ -580,14 +631,19 @@ class OPL
|
|
580
631
|
things_to_substitute[dname] = targets
|
581
632
|
targets.each do |target|
|
582
633
|
indices = target.scan(/\d+/).map{|ind|ind.to_i}
|
583
|
-
|
584
|
-
|
634
|
+
indices = "" if indices.empty?
|
635
|
+
if dvalue.is_a?(Array)
|
636
|
+
value = dvalue.values_at_a(indices)
|
637
|
+
else
|
638
|
+
value = dvalue
|
639
|
+
end
|
640
|
+
data_values[dname+indices.to_s.gsub(",","][").gsub(" ","")] = value
|
585
641
|
end
|
586
642
|
end
|
587
643
|
data_values.keys.each do |key|
|
588
644
|
name = key
|
589
645
|
value = data_values[key]
|
590
|
-
text = text.gsub(name, value)
|
646
|
+
text = text.gsub(name, value.to_s)
|
591
647
|
end
|
592
648
|
plus_minus = text.scan(/\+[\ ]+-/)
|
593
649
|
plus_minus.each do |pm|
|
@@ -595,14 +651,49 @@ class OPL
|
|
595
651
|
end
|
596
652
|
return(text)
|
597
653
|
end
|
654
|
+
|
655
|
+
def self.remove_constants(text)
|
656
|
+
helper = self
|
657
|
+
text = text.gsub(" ","")
|
658
|
+
text = "+"+text if text[0]!="-"
|
659
|
+
text = text+"#"
|
660
|
+
constants = helper.get_constants(text)
|
661
|
+
replacements = []
|
662
|
+
constants[:formatted].each_index do |i|
|
663
|
+
formatted = constants[:formatted][i]
|
664
|
+
unformatted = constants[:unformatted][i]
|
665
|
+
if formatted.include?("+")
|
666
|
+
if unformatted.include?("+")
|
667
|
+
replacements << ["+"+unformatted, "+"]
|
668
|
+
elsif unformatted.include?("-")
|
669
|
+
replacements << ["-"+unformatted, "-"]
|
670
|
+
elsif unformatted.include?("#")
|
671
|
+
replacements << [formatted+"#",""]
|
672
|
+
end
|
673
|
+
elsif formatted.include?("-")
|
674
|
+
if unformatted.include?("+")
|
675
|
+
replacements << ["-"+unformatted, "+"]
|
676
|
+
elsif unformatted.include?("-")
|
677
|
+
replacements << ["-"+unformatted, "-"]
|
678
|
+
elsif unformatted.include?("#")
|
679
|
+
replacements << [formatted+"#",""]
|
680
|
+
end
|
681
|
+
end
|
682
|
+
end
|
683
|
+
replacements.each do |replacement|
|
684
|
+
text = text.gsub(replacement[0],replacement[1])
|
685
|
+
end
|
686
|
+
text = text[1..-1] if text[0] == "+"
|
687
|
+
return(text)
|
688
|
+
end
|
598
689
|
end
|
599
690
|
|
600
691
|
class LinearProgram
|
601
692
|
attr_accessor :objective
|
602
693
|
attr_accessor :constraints
|
694
|
+
attr_accessor :original_constraints
|
603
695
|
attr_accessor :rows
|
604
696
|
attr_accessor :solution
|
605
|
-
attr_accessor :formatted_constraints
|
606
697
|
attr_accessor :rglpk_object
|
607
698
|
attr_accessor :solver
|
608
699
|
attr_accessor :matrix
|
@@ -627,9 +718,11 @@ class OPL
|
|
627
718
|
|
628
719
|
class Objective
|
629
720
|
attr_accessor :function
|
721
|
+
attr_accessor :expanded_function
|
630
722
|
attr_accessor :optimization#minimize, maximize, equals
|
631
723
|
attr_accessor :variable_coefficient_pairs
|
632
724
|
attr_accessor :optimized_value
|
725
|
+
attr_accessor :addition
|
633
726
|
|
634
727
|
def initialize(function, optimization)
|
635
728
|
@function = function
|
@@ -688,22 +781,23 @@ class OPL
|
|
688
781
|
end
|
689
782
|
|
690
783
|
def subject_to(constraints, options=[])
|
691
|
-
lp = OPL::LinearProgram.new
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
parsed_data
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
end
|
703
|
-
|
704
|
-
|
705
|
-
constraints =
|
706
|
-
|
784
|
+
lp = OPL::LinearProgram.new
|
785
|
+
lp.original_constraints = constraints
|
786
|
+
variable_types = options.find_all{|option|option.downcase.include?("boolean") || option.downcase.include?("integer")} || []
|
787
|
+
epsilon = options.find_all{|option|option.downcase.include?("epsilon")}.first.gsub(" ","").split(":")[1].to_f rescue $default_epsilon
|
788
|
+
bounded_columns = options.find_all{|option|option.downcase.include?("negative") || option.downcase.include?("positive") || option.downcase.include?("nonnegative")}
|
789
|
+
data = options.find_all{|option|option.gsub(" ","").downcase.include?("data:")}[0]
|
790
|
+
if data
|
791
|
+
parsed_data = OPL::Helper.parse_data(data)
|
792
|
+
parsed_data.keys.each do |data_key|
|
793
|
+
data_value = parsed_data[data_key]
|
794
|
+
lp.data << OPL::Data.new(data_key, data_value)
|
795
|
+
end
|
796
|
+
end
|
797
|
+
lp.epsilon = epsilon
|
798
|
+
constraints = constraints.flatten
|
799
|
+
constraints = OPL::Helper.split_equals_a(constraints)
|
800
|
+
data_names = lp.data.map{|d|d.name}
|
707
801
|
constraints = constraints.map do |constraint|
|
708
802
|
OPL::Helper.sub_forall(constraint)
|
709
803
|
end.flatten
|
@@ -729,8 +823,18 @@ constraints = constraints.map do |constraint|
|
|
729
823
|
OPL::Helper.substitute_data(constraint, lp)
|
730
824
|
end
|
731
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|
|
732
835
|
OPL::Helper.sum_variables(constraint, lp)
|
733
836
|
end
|
837
|
+
lp.constraints = constraints
|
734
838
|
all_vars = OPL::Helper.get_all_vars(constraints, lp)
|
735
839
|
variable_type_hash = OPL::Helper.produce_variable_type_hash(variable_types, all_vars)
|
736
840
|
column_bounds = OPL::Helper.get_column_bounds(bounded_columns, all_vars)
|
@@ -799,16 +903,22 @@ def minimize(objective, lp)
|
|
799
903
|
end
|
800
904
|
|
801
905
|
def optimize(optimization, objective, lp)
|
802
|
-
|
803
|
-
o = OPL::Objective.new(objective, optimization)
|
804
|
-
lp.objective = o
|
906
|
+
original_objective = objective
|
805
907
|
objective = OPL::Helper.sub_sum(objective)
|
908
|
+
objective = OPL::Helper.sum_indices(objective)
|
909
|
+
objective = OPL::Helper.substitute_data(objective, lp)
|
806
910
|
objective_constants = OPL::Helper.get_constants(objective)
|
807
911
|
if objective_constants[:formatted].empty?
|
808
912
|
objective_addition = 0
|
809
913
|
else
|
810
914
|
objective_addition = OPL::Helper.sum_constants(objective_constants[:formatted].inject("+"))
|
811
915
|
end
|
916
|
+
objective = OPL::Helper.remove_constants(objective)
|
917
|
+
objective = OPL::Helper.sum_variables(objective, lp)
|
918
|
+
o = OPL::Objective.new(original_objective, optimization)
|
919
|
+
o.expanded_function = objective
|
920
|
+
lp.objective = o
|
921
|
+
lp.objective.addition = objective_addition
|
812
922
|
rows_c = lp.rows
|
813
923
|
p = Rglpk::Problem.new
|
814
924
|
p.name = "sample"
|
@@ -855,7 +965,6 @@ def optimize(optimization, objective, lp)
|
|
855
965
|
end
|
856
966
|
lp.solver = solver
|
857
967
|
all_vars = rows_c.first.variable_coefficient_pairs.map{|vcp|vcp.variable}
|
858
|
-
#not sure here
|
859
968
|
obj_coefficients = OPL::Helper.coefficients(objective.gsub(" ",""), lp).map{|c|c.to_f}
|
860
969
|
obj_vars = OPL::Helper.variables(objective.gsub(" ",""), lp)
|
861
970
|
all_obj_coefficients = []
|
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.1.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-
|
12
|
+
date: 2013-10-04 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.
|