opl 1.1.0 → 2.0.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 +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: []
|