opl 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/opl.rb +239 -69
- metadata +7 -6
data/lib/opl.rb
CHANGED
@@ -9,19 +9,17 @@ require "rglpk"
|
|
9
9
|
# "x >= 0"
|
10
10
|
# ]))
|
11
11
|
|
12
|
-
#1.2
|
13
|
-
#allow a POSITIVE: x option or NEGATIVE: x
|
14
|
-
|
15
|
-
#1.3
|
16
|
-
#float coefficients
|
17
|
-
|
18
|
-
#2.0
|
19
|
-
#data arrays
|
20
|
-
|
21
12
|
#2.1
|
13
|
+
#ability to do arithmetic with data indices
|
14
|
+
#forall(i in (2..3) d[i-1]x[i] <= 10)
|
15
|
+
|
16
|
+
#2.2
|
22
17
|
#a matrix representation of the solution if using
|
23
18
|
#sub notation
|
24
19
|
|
20
|
+
#2.3
|
21
|
+
#parse data from a file
|
22
|
+
|
25
23
|
#3.0
|
26
24
|
#multiple level sub notation e.g. x[1][[3]]
|
27
25
|
|
@@ -63,6 +61,31 @@ class String
|
|
63
61
|
end
|
64
62
|
end
|
65
63
|
|
64
|
+
class Array
|
65
|
+
def dimension
|
66
|
+
a = self
|
67
|
+
return 0 if a.class != Array
|
68
|
+
result = 1
|
69
|
+
a.each do |sub_a|
|
70
|
+
if sub_a.class == Array
|
71
|
+
dim = sub_a.dimension
|
72
|
+
result = dim + 1 if dim + 1 > result
|
73
|
+
end
|
74
|
+
end
|
75
|
+
return result
|
76
|
+
end
|
77
|
+
|
78
|
+
def values_at_a(indices, current_array=self)
|
79
|
+
#in: self = [3,4,[6,5,[3,4]],3], indices = [2,2,0]
|
80
|
+
#out: 3
|
81
|
+
if indices.size == 1
|
82
|
+
return(current_array[indices[0]])
|
83
|
+
else
|
84
|
+
values_at_a(indices[1..-1], current_array[indices[0]])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
66
89
|
class OPL
|
67
90
|
class Helper
|
68
91
|
def self.mass_product(array_of_arrays, base=[])
|
@@ -155,7 +178,7 @@ class OPL
|
|
155
178
|
{:lhs => sides[0], :rhs => sides[1]}
|
156
179
|
end
|
157
180
|
|
158
|
-
def self.add_ones(text)
|
181
|
+
def self.add_ones(text, lp)
|
159
182
|
equation = text
|
160
183
|
equation = "#"+equation
|
161
184
|
equation.scan(/[#+-][a-z]/).each do |p|
|
@@ -262,8 +285,8 @@ class OPL
|
|
262
285
|
return(equation)
|
263
286
|
end
|
264
287
|
|
265
|
-
def self.coefficients(text)#
|
266
|
-
equation = self.add_ones(text)
|
288
|
+
def self.coefficients(text, lp)#text is one side of the equation
|
289
|
+
equation = self.add_ones(text, lp)
|
267
290
|
if equation[0]=="-"
|
268
291
|
equation.scan(/[+-][\d\.]+/)
|
269
292
|
else
|
@@ -271,17 +294,21 @@ class OPL
|
|
271
294
|
end
|
272
295
|
end
|
273
296
|
|
274
|
-
def self.
|
275
|
-
|
297
|
+
def self.data_coefficients
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
def self.variables(text, lp)#parameter is one side of the equation
|
302
|
+
equation = self.add_ones(text, lp)
|
276
303
|
equation.scan(/[a-z]+[\[\]\d]*/)
|
277
304
|
end
|
278
305
|
|
279
|
-
def self.get_all_vars(constraints)
|
306
|
+
def self.get_all_vars(constraints, lp)
|
280
307
|
all_vars = []
|
281
308
|
constraints.each do |constraint|
|
282
309
|
constraint = constraint.gsub(" ", "")
|
283
310
|
value = constraint.split(":")[1] || constraint
|
284
|
-
all_vars << self.variables(value)
|
311
|
+
all_vars << self.variables(value, lp)
|
285
312
|
end
|
286
313
|
all_vars.flatten.uniq
|
287
314
|
end
|
@@ -462,14 +489,15 @@ class OPL
|
|
462
489
|
variable_type_hash
|
463
490
|
end
|
464
491
|
|
465
|
-
def self.sum_variables(formatted_constraint)
|
492
|
+
def self.sum_variables(formatted_constraint, lp)
|
466
493
|
#in: x + y - z + x[3] + z + y - z + x - y <= 0
|
467
494
|
#out: 2*x + y - z + x[3] <= 0
|
468
495
|
helper = self
|
469
496
|
lhs = helper.sides(formatted_constraint)[:lhs]
|
470
|
-
formatted_lhs = helper.add_ones(lhs)
|
471
|
-
vars = helper.variables(formatted_lhs)
|
472
|
-
|
497
|
+
formatted_lhs = helper.add_ones(lhs, lp)
|
498
|
+
vars = helper.variables(formatted_lhs, lp)
|
499
|
+
#data coefficients should already be substituted
|
500
|
+
coefs = helper.coefficients(formatted_lhs, lp)
|
473
501
|
var_coef_hash = {}
|
474
502
|
vars.each_index do |i|
|
475
503
|
var = vars[i]
|
@@ -492,6 +520,81 @@ class OPL
|
|
492
520
|
end
|
493
521
|
formatted_constraint.gsub(lhs, new_lhs)
|
494
522
|
end
|
523
|
+
|
524
|
+
def self.get_column_bounds(bound_info, all_variables)
|
525
|
+
#in: ["NONNEGATIVE: x1"]
|
526
|
+
#out: {:x1 => {:lower => 0}}
|
527
|
+
column_bounds = {}
|
528
|
+
bound_info.each do |info|
|
529
|
+
type = info.gsub(" ","").split(":")[0]
|
530
|
+
if type.downcase == "nonnegative"
|
531
|
+
bounds = {:lower => 0}
|
532
|
+
end
|
533
|
+
variables = info.split(":")[1].gsub(" ","").split(",")
|
534
|
+
variables.each do |root_var|
|
535
|
+
all_variables_with_root = all_variables.find_all{|var|var.include?("[") && var.split("[")[0]==root_var}+[root_var]
|
536
|
+
all_variables_with_root.each do |var|
|
537
|
+
column_bounds[var.to_sym] = bounds
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
column_bounds
|
542
|
+
end
|
543
|
+
|
544
|
+
def self.parse_data(data_info)
|
545
|
+
#in: "DATA: {d => [1, 0.3, 1.5], o => [10.3, 4.0005, -1]}"
|
546
|
+
#out: [data_object_1, data_object_2]
|
547
|
+
data_hash_string = data_info.gsub(" ","").split(":")[1]
|
548
|
+
data_string = data_hash_string.gsub("{",",").gsub("}",",")
|
549
|
+
names = data_string.scan(/,[a-z]/).map{|comma_name|comma_name.gsub(",","")}
|
550
|
+
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
|
559
|
+
data_hash = {}
|
560
|
+
names.each_index do |i|
|
561
|
+
name = names[i]
|
562
|
+
value = values[i]
|
563
|
+
data_hash[name] = value
|
564
|
+
end
|
565
|
+
return(data_hash)
|
566
|
+
end
|
567
|
+
|
568
|
+
def self.substitute_data(text, lp)
|
569
|
+
helper = self
|
570
|
+
potential_things_to_substitute = helper.variables(text, lp)
|
571
|
+
data_names = lp.data.map{|d|d.name}
|
572
|
+
things_to_substitute = {}
|
573
|
+
data_values = {}
|
574
|
+
lp.data.each do |data|
|
575
|
+
dname = data.name
|
576
|
+
dvalue = data.value
|
577
|
+
targets = potential_things_to_substitute.find_all do |ptts|
|
578
|
+
dname == ptts[0]
|
579
|
+
end
|
580
|
+
things_to_substitute[dname] = targets
|
581
|
+
targets.each do |target|
|
582
|
+
indices = target.scan(/\d+/).map{|ind|ind.to_i}
|
583
|
+
value = dvalue.values_at_a(indices)
|
584
|
+
data_values[dname+indices.to_s] = value
|
585
|
+
end
|
586
|
+
end
|
587
|
+
data_values.keys.each do |key|
|
588
|
+
name = key
|
589
|
+
value = data_values[key]
|
590
|
+
text = text.gsub(name, value)
|
591
|
+
end
|
592
|
+
plus_minus = text.scan(/\+[\ ]+-/)
|
593
|
+
plus_minus.each do |pm|
|
594
|
+
text = text.gsub(pm,"-")
|
595
|
+
end
|
596
|
+
return(text)
|
597
|
+
end
|
495
598
|
end
|
496
599
|
|
497
600
|
class LinearProgram
|
@@ -505,11 +608,20 @@ class OPL
|
|
505
608
|
attr_accessor :matrix
|
506
609
|
attr_accessor :simplex_message
|
507
610
|
attr_accessor :mip_message
|
611
|
+
attr_accessor :data
|
612
|
+
attr_accessor :data_hash
|
613
|
+
attr_accessor :variable_types
|
614
|
+
attr_accessor :column_bounds
|
615
|
+
attr_accessor :epsilon
|
616
|
+
|
617
|
+
def keys
|
618
|
+
[:objective, :constraints, :rows, :solution, :formatted_constraints, :rglpk_object, :solver, :matrix, :simplex_message, :mip_message, :data]
|
619
|
+
end
|
508
620
|
|
509
|
-
def initialize
|
510
|
-
@objective = objective
|
511
|
-
@constraints = constraints
|
621
|
+
def initialize
|
512
622
|
@rows = []
|
623
|
+
@data = []
|
624
|
+
@epsilon = $default_epsilon
|
513
625
|
end
|
514
626
|
end
|
515
627
|
|
@@ -546,6 +658,8 @@ class OPL
|
|
546
658
|
attr_accessor :variable
|
547
659
|
attr_accessor :coefficient
|
548
660
|
attr_accessor :variable_type
|
661
|
+
attr_accessor :lower_bound
|
662
|
+
attr_accessor :upper_bound
|
549
663
|
|
550
664
|
def initialize(variable, coefficient, variable_type=1)
|
551
665
|
@variable = variable
|
@@ -553,39 +667,75 @@ class OPL
|
|
553
667
|
@variable_type = variable_type
|
554
668
|
end
|
555
669
|
end
|
670
|
+
|
671
|
+
class Data
|
672
|
+
attr_accessor :name
|
673
|
+
attr_accessor :value
|
674
|
+
attr_accessor :value_type#number, array
|
675
|
+
attr_accessor :array_dimension
|
676
|
+
|
677
|
+
def initialize(name, value)
|
678
|
+
@name = name
|
679
|
+
@value = value
|
680
|
+
if value.is_a?(Array)
|
681
|
+
@value_type = Array
|
682
|
+
@array_dimension = value.dimension
|
683
|
+
else
|
684
|
+
@value_type = Integer
|
685
|
+
end
|
686
|
+
end
|
687
|
+
end
|
556
688
|
end
|
557
689
|
|
558
690
|
def subject_to(constraints, options=[])
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
691
|
+
lp = OPL::LinearProgram.new
|
692
|
+
variable_types = options.find_all{|option|option.downcase.include?("boolean") || option.downcase.include?("integer")} || []
|
693
|
+
epsilon = options.find_all{|option|option.downcase.include?("epsilon")}.first.gsub(" ","").split(":")[1].to_f rescue $default_epsilon
|
694
|
+
bounded_columns = options.find_all{|option|option.downcase.include?("negative") || option.downcase.include?("positive") || option.downcase.include?("nonnegative")}
|
695
|
+
data = options.find_all{|option|option.gsub(" ","").downcase.include?("data:")}[0]
|
696
|
+
if data
|
697
|
+
parsed_data = OPL::Helper.parse_data(data)
|
698
|
+
parsed_data.keys.each do |data_key|
|
699
|
+
data_value = parsed_data[data_key]
|
700
|
+
lp.data << OPL::Data.new(data_key, data_value)
|
701
|
+
end
|
702
|
+
end
|
703
|
+
lp.epsilon = epsilon
|
704
|
+
constraints = constraints.flatten
|
705
|
+
constraints = OPL::Helper.split_equals_a(constraints)
|
706
|
+
data_names = lp.data.map{|d|d.name}
|
707
|
+
constraints = constraints.map do |constraint|
|
708
|
+
OPL::Helper.sub_forall(constraint)
|
709
|
+
end.flatten
|
710
|
+
constraints = constraints.map do |constraint|
|
711
|
+
OPL::Helper.sum_indices(constraint)
|
712
|
+
end
|
713
|
+
constraints = constraints.map do |constraint|
|
714
|
+
OPL::Helper.sub_sum(constraint)
|
715
|
+
end
|
716
|
+
constraints = constraints.map do |constraint|
|
717
|
+
OPL::Helper.sum_indices(constraint)
|
718
|
+
end
|
719
|
+
constraints = constraints.map do |constraint|
|
720
|
+
OPL::Helper.put_constants_on_rhs(constraint)
|
721
|
+
end
|
722
|
+
constraints = constraints.map do |constraint|
|
723
|
+
OPL::Helper.put_variables_on_lhs(constraint)
|
724
|
+
end
|
725
|
+
constraints = constraints.map do |constraint|
|
726
|
+
OPL::Helper.sub_rhs_with_summed_constants(constraint)
|
727
|
+
end
|
728
|
+
constraints = constraints.map do |constraint|
|
729
|
+
OPL::Helper.substitute_data(constraint, lp)
|
730
|
+
end
|
731
|
+
constraints = constraints.map do |constraint|
|
732
|
+
OPL::Helper.sum_variables(constraint, lp)
|
733
|
+
end
|
734
|
+
all_vars = OPL::Helper.get_all_vars(constraints, lp)
|
588
735
|
variable_type_hash = OPL::Helper.produce_variable_type_hash(variable_types, all_vars)
|
736
|
+
column_bounds = OPL::Helper.get_column_bounds(bounded_columns, all_vars)
|
737
|
+
lp.variable_types = variable_type_hash
|
738
|
+
lp.column_bounds = column_bounds
|
589
739
|
rows = []
|
590
740
|
constraints.each do |constraint|
|
591
741
|
negate = false
|
@@ -607,7 +757,7 @@ def subject_to(constraints, options=[])
|
|
607
757
|
upper_bound = (bound*-1).to_s
|
608
758
|
end
|
609
759
|
lhs = OPL::Helper.sides(constraint)[:lhs]
|
610
|
-
coefs = OPL::Helper.coefficients(lhs)
|
760
|
+
coefs = OPL::Helper.coefficients(lhs, lp)
|
611
761
|
if negate
|
612
762
|
coefs = coefs.map do |coef|
|
613
763
|
if coef.include?("+")
|
@@ -617,7 +767,7 @@ def subject_to(constraints, options=[])
|
|
617
767
|
end
|
618
768
|
end
|
619
769
|
end
|
620
|
-
vars = OPL::Helper.variables(lhs)
|
770
|
+
vars = OPL::Helper.variables(lhs, lp)
|
621
771
|
zero_coef_vars = all_vars - vars
|
622
772
|
row = OPL::Row.new(name, lower_bound, upper_bound, epsilon)
|
623
773
|
row.constraint = constraint
|
@@ -628,25 +778,30 @@ def subject_to(constraints, options=[])
|
|
628
778
|
all_vars.each do |var|
|
629
779
|
coef = coefs[vars.index(var)]
|
630
780
|
variable_type = variable_type_hash[var.to_sym] || 1
|
631
|
-
|
781
|
+
vcp = OPL::VariableCoefficientPair.new(var, coef, variable_type)
|
782
|
+
vcp.lower_bound = column_bounds[var.to_sym][:lower] rescue nil
|
783
|
+
vcp.upper_bound = column_bounds[var.to_sym][:upper] rescue nil
|
784
|
+
pairs << vcp
|
632
785
|
end
|
633
786
|
row.variable_coefficient_pairs = pairs
|
634
787
|
rows << row
|
635
788
|
end
|
636
|
-
rows
|
789
|
+
lp.rows = rows
|
790
|
+
lp
|
637
791
|
end
|
638
792
|
|
639
|
-
def maximize(objective,
|
640
|
-
optimize("maximize", objective,
|
793
|
+
def maximize(objective, lp)
|
794
|
+
optimize("maximize", objective, lp)
|
641
795
|
end
|
642
796
|
|
643
|
-
def minimize(objective,
|
644
|
-
optimize("minimize", objective,
|
797
|
+
def minimize(objective, lp)
|
798
|
+
optimize("minimize", objective, lp)
|
645
799
|
end
|
646
800
|
|
647
|
-
def optimize(optimization, objective,
|
801
|
+
def optimize(optimization, objective, lp)
|
802
|
+
objective = OPL::Helper.substitute_data(objective, lp)
|
648
803
|
o = OPL::Objective.new(objective, optimization)
|
649
|
-
lp =
|
804
|
+
lp.objective = o
|
650
805
|
objective = OPL::Helper.sub_sum(objective)
|
651
806
|
objective_constants = OPL::Helper.get_constants(objective)
|
652
807
|
if objective_constants[:formatted].empty?
|
@@ -654,7 +809,7 @@ def optimize(optimization, objective, rows_c)
|
|
654
809
|
else
|
655
810
|
objective_addition = OPL::Helper.sum_constants(objective_constants[:formatted].inject("+"))
|
656
811
|
end
|
657
|
-
lp.rows
|
812
|
+
rows_c = lp.rows
|
658
813
|
p = Rglpk::Problem.new
|
659
814
|
p.name = "sample"
|
660
815
|
if optimization == "maximize"
|
@@ -666,9 +821,15 @@ def optimize(optimization, objective, rows_c)
|
|
666
821
|
rows_c.each_index do |i|
|
667
822
|
row = rows_c[i]
|
668
823
|
rows[i].name = row.name
|
669
|
-
|
670
|
-
|
671
|
-
|
824
|
+
if row.lower_bound.nil? && row.upper_bound.nil?
|
825
|
+
rows[i].set_bounds(Rglpk::GLP_FR, nil, nil)
|
826
|
+
elsif row.lower_bound.nil?
|
827
|
+
rows[i].set_bounds(Rglpk::GLP_UP, nil, row.upper_bound)
|
828
|
+
elsif row.upper_bound.nil?
|
829
|
+
rows[i].set_bounds(Rglpk::GLP_LO, row.lower_bound, nil)
|
830
|
+
else
|
831
|
+
rows[i].set_bounds(Rglpk::GLP_DB, row.lower_bound, row.upper_bound)
|
832
|
+
end
|
672
833
|
end
|
673
834
|
vars = rows_c.first.variable_coefficient_pairs
|
674
835
|
cols = p.add_cols(vars.size)
|
@@ -678,7 +839,15 @@ def optimize(optimization, objective, rows_c)
|
|
678
839
|
cols[i].name = column_name
|
679
840
|
cols[i].kind = vars[i].variable_type#boolean, integer, etc.
|
680
841
|
if [1,2].include? cols[i].kind
|
681
|
-
|
842
|
+
if vars[i].lower_bound.nil? && vars[i].upper_bound.nil?
|
843
|
+
cols[i].set_bounds(Rglpk::GLP_FR, nil, nil)
|
844
|
+
elsif vars[i].lower_bound.nil?
|
845
|
+
cols[i].set_bounds(Rglpk::GLP_UP, nil, vars[i].upper_bound)
|
846
|
+
elsif vars[i].upper_bound.nil?
|
847
|
+
cols[i].set_bounds(Rglpk::GLP_LO, vars[i].lower_bound, nil)
|
848
|
+
else
|
849
|
+
cols[i].set_bounds(Rglpk::GLP_DB, vars[i].lower_bound, vars[i].upper_bound)
|
850
|
+
end
|
682
851
|
end
|
683
852
|
if vars[i].variable_type != 1
|
684
853
|
solver = "mip"
|
@@ -686,8 +855,9 @@ def optimize(optimization, objective, rows_c)
|
|
686
855
|
end
|
687
856
|
lp.solver = solver
|
688
857
|
all_vars = rows_c.first.variable_coefficient_pairs.map{|vcp|vcp.variable}
|
689
|
-
|
690
|
-
|
858
|
+
#not sure here
|
859
|
+
obj_coefficients = OPL::Helper.coefficients(objective.gsub(" ",""), lp).map{|c|c.to_f}
|
860
|
+
obj_vars = OPL::Helper.variables(objective.gsub(" ",""), lp)
|
691
861
|
all_obj_coefficients = []
|
692
862
|
all_vars.each do |var|
|
693
863
|
i = obj_vars.index(var)
|
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:
|
4
|
+
version: 2.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,12 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-09-
|
12
|
+
date: 2013-09-26 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description: Built on top of the
|
15
|
-
|
16
|
-
|
14
|
+
description: Built on top of the rglpk gem for linear programming, this gem is meant
|
15
|
+
to give you a beautifully simple way to formulate your linear or mixed integer program.
|
16
|
+
The syntax is inspired by OPL Studio, which remains my favorite linear programming
|
17
|
+
software, but the license is quite expensive.
|
17
18
|
email: bgodlove88@gmail.com
|
18
19
|
executables: []
|
19
20
|
extensions: []
|
@@ -44,5 +45,5 @@ rubyforge_project:
|
|
44
45
|
rubygems_version: 1.8.23
|
45
46
|
signing_key:
|
46
47
|
specification_version: 3
|
47
|
-
summary: Linear Program Solver
|
48
|
+
summary: Linear Or Mixed Integer Program Solver
|
48
49
|
test_files: []
|