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.
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.