opl 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/opl.rb +239 -69
  2. 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)#parameter is one side of the equation
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.variables(text)#parameter is one side of the equation
275
- equation = self.add_ones(text)
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
- coefs = helper.coefficients(formatted_lhs)
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(objective, constraints)
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
- variable_types = options.find_all{|option|option.downcase.include?("boolean") || option.downcase.include?("integer")} || []
560
- epsilon = options.find_all{|option|option.downcase.include?("epsilon")}.first.gsub(" ","").split(":")[1].to_f rescue $default_epsilon
561
- constraints = constraints.flatten
562
- constraints = OPL::Helper.split_equals_a(constraints)
563
- constraints = constraints.map do |constraint|
564
- OPL::Helper.sub_forall(constraint)
565
- end.flatten
566
- constraints = constraints.map do |constraint|
567
- OPL::Helper.sum_indices(constraint)
568
- end
569
- constraints = constraints.map do |constraint|
570
- OPL::Helper.sub_sum(constraint)
571
- end
572
- constraints = constraints.map do |constraint|
573
- OPL::Helper.sum_indices(constraint)
574
- end
575
- constraints = constraints.map do |constraint|
576
- OPL::Helper.put_constants_on_rhs(constraint)
577
- end
578
- constraints = constraints.map do |constraint|
579
- OPL::Helper.put_variables_on_lhs(constraint)
580
- end
581
- constraints = constraints.map do |constraint|
582
- OPL::Helper.sub_rhs_with_summed_constants(constraint)
583
- end
584
- constraints = constraints.map do |constraint|
585
- OPL::Helper.sum_variables(constraint)
586
- end
587
- all_vars = OPL::Helper.get_all_vars(constraints)
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
- pairs << OPL::VariableCoefficientPair.new(var, coef, variable_type)
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, rows_c)#objective function has no = in it
640
- optimize("maximize", objective, rows_c)
793
+ def maximize(objective, lp)
794
+ optimize("maximize", objective, lp)
641
795
  end
642
796
 
643
- def minimize(objective, rows_c)#objective function has no = in it
644
- optimize("minimize", objective, rows_c)
797
+ def minimize(objective, lp)
798
+ optimize("minimize", objective, lp)
645
799
  end
646
800
 
647
- def optimize(optimization, objective, rows_c)
801
+ def optimize(optimization, objective, lp)
802
+ objective = OPL::Helper.substitute_data(objective, lp)
648
803
  o = OPL::Objective.new(objective, optimization)
649
- lp = OPL::LinearProgram.new(o, rows_c.map{|row|row.constraint})
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 = rows_c
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
- rows[i].set_bounds(Rglpk::GLP_UP, nil, row.upper_bound) unless row.upper_bound.nil?
670
- rows[i].set_bounds(Rglpk::GLP_LO, nil, row.lower_bound) unless row.lower_bound.nil?
671
- #rows[i].set_bounds(Rglpk::GLP_FR, row.lower_bound, row.upper_bound)
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
- cols[i].set_bounds(Rglpk::GLP_FR, nil, nil)
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
- obj_coefficients = OPL::Helper.coefficients(objective.gsub(" ","")).map{|c|c.to_f}
690
- obj_vars = OPL::Helper.variables(objective.gsub(" ",""))
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: 1.1.0
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-25 00:00:00.000000000 Z
12
+ date: 2013-09-26 00:00:00.000000000 Z
13
13
  dependencies: []
14
- description: Built on top of the glpk gem for linear programming. The syntax is copied
15
- from OPL Studio, which remains my favorite linear programming software, but the
16
- license is quite expensive.
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: []