opl 2.0.0 → 2.1.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 +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.
|