opl 2.0.0 → 2.1.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 +163 -54
  2. 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
- #2.1
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
- text = text.gsub(" ","")
320
- text = text+"#"
321
- cs = []
322
- potential_constants = text.scan(/[\d\.]+[^a-z^\[^\]^\d^\.^\)]/)
323
- #potential_constants = text.scan(/\d+[^a-z^\[^\]^\d]/)
324
- constants = potential_constants.find_all{|c|![*('a'..'z'),*('A'..'Z')].include?(text[text.index(c)-1])}
325
- constants.each do |constant|
326
- c = constant.scan(/[\d\.]+/)[0]
327
- index = text.index(constant)
328
- if index == 0
329
- c = "+"+c
330
- else
331
- c = text.scan(/[\-\+]#{constant}/)[0]
332
- end
333
- cs << c.scan(/[\-\+][\d\.]+/)[0]
334
- end
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
- value = dvalue.values_at_a(indices)
584
- data_values[dname+indices.to_s] = value
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
- 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}
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
- objective = OPL::Helper.substitute_data(objective, lp)
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.0.0
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-09-26 00:00:00.000000000 Z
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.