diff --git a/README.md b/README.md index 5ab6740..836371d 100644 --- a/README.md +++ b/README.md @@ -59,25 +59,25 @@ Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP libr given[ - X_i >= 0, - Y_i >= 0, - Z_i >= 0 + X_lpi >= 0, + Y_lpi >= 0, + Z_lpi >= 0 ] - result = Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) [ - X_i + Y_i + Z_i <= 100, - 10 * X_i + 4 * Y_i + 5 * Z_i <= 600, - 2 * X_i + 2 * Y_i + 6 * Z_i <= 300 + result = Rulp::Max( 10 * X_lpi + 6 * Y_lpi + 4 * Z_lpi ) [ + X_lpi + Y_lpi + Z_lpi <= 100, + 10 * X_lpi + 4 * Y_lpi + 5 * Z_lpi <= 600, + 2 * X_lpi + 2 * Y_lpi + 6 * Z_lpi <= 300 ].solve ## # 'result' is the result of the objective function. # You can retrieve the values of variables by using the 'value' method # E.g - # X_i.value == 32 - # Y_i.value == 67 - # Z_i.value == 0 + # X_lpi.value == 32 + # Y_lpi.value == 67 + # Z_lpi.value == 0 ## ``` @@ -189,18 +189,18 @@ At this point, you should have GLPK installed. Verify it: # need to initialize them. # They follow a naming convention that defines their type. # A variable is declared as a constant with one of three different suffixes. - # 'f' or '_f' indicates a general variable (No constraints) - # 'i' or '_i' indicates a integer variable - # 'b' or '_b' indicates a binary/boolean variable + # 'lpf' or '_lpf' indicates a general variable (No constraints) + # 'lpi' or '_lpi' indicates a integer variable + # 'lpb' or '_lpb' indicates a binary/boolean variable - An_Integer_i + An_Integer_lpi => # - Generalf + Generallpf => # - Bool_Val_b + Bool_Val_lpb => # # In some cases it is implausible to generate a unique name for every possible variable @@ -209,13 +209,13 @@ At this point, you should have GLPK installed. Verify it: # accept index parameters to create large ranges of unique variables. # Examples of how indexed variables can be declared are as follows: - Item_i(4,5) + Item_lpi(4,5) # - Item_i("store_3", "table_2") + Item_lpi("store_3", "table_2") # - [*0..10].map(&Unit_f) + [*0..10].map(&Unit_lpf) => [#, #, #, @@ -237,16 +237,16 @@ Constraints on a variable can only use numeric literals and not other variables. Inter-variable constraints should be expressed as problem constrants. (Explained below.) ```ruby - X_i < 5 - X_i.bounds + X_lpi < 5 + X_lpi.bounds => "X <= 5" - 3 <= X_i < 15 + 3 <= X_lpi < 15 X_i.bounds => "3 <= X <= 15" - Y_f == 10 - Y_f.bounds + Y_lpf == 10 + Y_lpf.bounds => "y = 10" ``` @@ -255,14 +255,14 @@ Inter-variable constraints should be expressed as problem constrants. (Explained Constraints are added to a problem using the :[] syntax. ```ruby - problem = Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) + problem = Rulp::Max( 10 * X_lpi + 6 * Y_lpi + 4 * Z_lpi ) problem[ - X_i + Y_i + Z_i <= 100 + X_lpi + Y_lpi + Z_lpi <= 100 ] problem[ - 10 * X_i + 4 * Y_i + 5 * Z_i <= 600 + 10 * X_lpi + 4 * Y_lpi + 5 * Z_lpi <= 600 ] ... problem.solve @@ -271,10 +271,10 @@ Constraints are added to a problem using the :[] syntax. You can add multiple constraints at once by comma separating them as seen in the earlier examples: ```ruby - Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) [ - X_i + Y_i + Z_i <= 100, - 10 * X_i + 4 * Y_i + 5 * Z_i <= 600, - 2 * X_i + 2 * Y_i + 6 * Z_i <= 300 + Rulp::Max( 10 * X_lpi + 6 * Y_lpi + 4 * Z_lpi ) [ + X_lpi + Y_lpi + Z_lpi <= 100, + 10 * X_lpi + 4 * Y_lpi + 5 * Z_lpi <= 600, + 2 * X_lpi + 2 * Y_lpi + 6 * Z_lpi <= 300 ] ``` @@ -288,10 +288,10 @@ such that the command `which [exec_name]` returns a path. (I.e they must be on y Given a problem there are multiple ways to initiate a solver. ```ruby - @problem = Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) [ - X_i + Y_i + Z_i <= 100, - 10 * X_i + 4 * Y_i + 5 * Z_i <= 600, - 2 * X_i + 2 * Y_i + 6 * Z_i <= 300 + @problem = Rulp::Max( 10 * X_lpi + 6 * Y_lpi + 4 * Z_lpi ) [ + X_lpi + Y_lpi + Z_lpi <= 100, + 10 * X_lpi + 4 * Y_lpi + 5 * Z_lpi <= 600, + 2 * X_lpi + 2 * Y_lpi + 6 * Z_lpi <= 300 ] ``` @@ -410,13 +410,13 @@ You can then play with and attempt LP and MIP problems straight from the command ```ruby - [1] pry(main)> 13 <= X_i <= 45 # Declare integer variable + [1] pry(main)> 13 <= X_lpi <= 45 # Declare integer variable => X(i)[undefined] - [2] pry(main)> -15 <= Y_f <= 15 # Declare float variable + [2] pry(main)> -15 <= Y_lpf <= 15 # Declare float variable => Y(f)[undefined] - [3] pry(main)> @problem = Rulp::Min(X_i - Y_f) # Create min problem + [3] pry(main)> @problem = Rulp::Min(X_lpi - Y_lpf) # Create min problem [info] Creating minimization problem => Minimize @@ -430,7 +430,7 @@ You can then play with and attempt LP and MIP problems straight from the command X End - [4] @problem[ X_i - 2 * Y_f < 40] #Adding a problem constraint + [4] @problem[ X_lpi - 2 * Y_lpf < 40] #Adding a problem constraint => Minimize obj: X -Y @@ -450,10 +450,10 @@ You can then play with and attempt LP and MIP problems straight from the command [info] Parsing result => -2.0 #(Minimal result) - [6] pry(main)> Y_f # See value calculated for Y now that solver has run + [6] pry(main)> Y_lpf # See value calculated for Y now that solver has run => Y(f)[15.0] - [8] pry(main)> X_i + [8] pry(main)> X_lpi => X(i)[13.0] # The result of the objective function (-2.0) was returned by the call to .solve @@ -461,10 +461,10 @@ You can then play with and attempt LP and MIP problems straight from the command # objective function (or any function) by calling evaluate on it. # E.g - [9] (X_i - Y_f).evaluate + [9] (X_lpi - Y_lpf).evaluate => -2.0 - [10] pry(main)> (2 * X_i + 15 * Y_f).evaluate + [10] pry(main)> (2 * X_lpi + 15 * Y_lpf).evaluate => 251.0 ``` @@ -481,7 +481,7 @@ we don't. We can't partially purchase one.) ```ruby # Generate the data randomly for this example. costs, points = [*0..1000].map do |i| - [Purchase_b(i) * Random.rand(1.0..3.0), Purchase_b(i) * Random.rand(5.0..10.0)] + [Purchase_lpb(i) * Random.rand(1.0..3.0), Purchase_lpb(i) * Random.rand(5.0..10.0)] end.transpose.map(&:sum) #We sum the array of points and array of costs to create a Rulp expression # And this is where the magic happens!. We ask rulp to maximise the number of points given @@ -493,7 +493,7 @@ Rulp::Max(points)[ => 538.2125623353652 (# You will get a different value as data was generated randomly) # Now how do we check which purchases were selected? -selected_purchases = [*0..1000].map(&Purchase_b).select(&:selected?) +selected_purchases = [*0..1000].map(&Purchase_lpb).select(&:selected?) => [Purchase27(b)[true], Purchase86(b)[true], diff --git a/examples/boolean_example.rb b/examples/boolean_example.rb index 53046c4..c3b1f3d 100644 --- a/examples/boolean_example.rb +++ b/examples/boolean_example.rb @@ -9,7 +9,7 @@ # ## -items = 50.times.map(&Shop_Item_b) +items = 50.times.map(&Shop_Item_lpb) items_count = items.sum items_costs = items.map{|item| item * Random.rand(1.0...5.0)}.sum @@ -25,5 +25,5 @@ # 'cost' is the result of the objective function. # You can retrieve allocations by querying the variables. # E.g -# Shop_Item_b(4).value -## \ No newline at end of file +# Shop_Item_lpb(4).value +## diff --git a/examples/simple_example.rb b/examples/simple_example.rb index 3b36a13..3b3410d 100644 --- a/examples/simple_example.rb +++ b/examples/simple_example.rb @@ -18,16 +18,16 @@ given[ -X_i >= 0, -Y_i >= 0, -Z_i >= 0 +X_lpi >= 0, +Y_lpi >= 0, +Z_lpi >= 0 ] -Rulp::Max( objective = 10 * X_i + 6 * Y_i + 4 * Z_i ) [ - X_i + Y_i + Z_i <= 100, - 10 * X_i + 4 * Y_i + 5 * Z_i <= 600, - 2 * X_i + 2 * Y_i + 6 * Z_i <= 300 +Rulp::Max( objective = 10 * X_lpi + 6 * Y_lpi + 4 * Z_lpi ) [ + X_lpi + Y_lpi + Z_lpi <= 100, + 10 * X_lpi + 4 * Y_lpi + 5 * Z_lpi <= 600, + 2 * X_lpi + 2 * Y_lpi + 6 * Z_lpi <= 300 ].cbc result = objective.evaluate @@ -36,7 +36,7 @@ # 'result' is the result of the objective function. # You can retrieve the values of variables by using the 'value' method # E.g -# X_i.value == 32 -# Y_i.value == 67 -# Z_i.value == 0 -## \ No newline at end of file +# X_lpi.value == 32 +# Y_lpi.value == 67 +# Z_lpi.value == 0 +## diff --git a/examples/whiskas_model2.rb b/examples/whiskas_model2.rb index 1701385..3869d25 100644 --- a/examples/whiskas_model2.rb +++ b/examples/whiskas_model2.rb @@ -5,7 +5,7 @@ # require_relative "../lib/rulp" -ingredients = [Chicken_i, Beef_i, Mutton_i, Rice_i, Wheat_i, Gel_i] +ingredients = [Chicken_lpi, Beef_lpi, Mutton_lpi, Rice_lpi, Wheat_lpi, Gel_lpi] costs = {Chicken: 0.013, Beef: 0.008, Mutton: 0.010, Rice: 0.002, Wheat: 0.005, Gel: 0.001} protein_percent = {Chicken: 0.100, Beef: 0.200, Mutton: 0.150, Rice: 0.000, Wheat: 0.040, Gel: 0.000} fat_percent = {Chicken: 0.080, Beef: 0.100, Mutton: 0.110, Rice: 0.010, Wheat: 0.010, Gel: 0.000} @@ -26,9 +26,9 @@ puts "Total Cost Per Can: #{result}" puts -puts "Chicken: #{Chicken_i.value}" -puts "Beef: #{Beef_i.value}" -puts "Mutton: #{Mutton_i.value}" -puts "Rice: #{Rice_i.value}" -puts "Wheat: #{Wheat_i.value}" -puts "Gel: #{Gel_i.value}" +puts "Chicken: #{Chicken_lpi.value}" +puts "Beef: #{Beef_lpi.value}" +puts "Mutton: #{Mutton_lpi.value}" +puts "Rice: #{Rice_lpi.value}" +puts "Wheat: #{Wheat_lpi.value}" +puts "Gel: #{Gel_lpi.value}" diff --git a/lib/extensions/kernel_extensions.rb b/lib/extensions/kernel_extensions.rb index 1b198a2..d9b502f 100644 --- a/lib/extensions/kernel_extensions.rb +++ b/lib/extensions/kernel_extensions.rb @@ -7,11 +7,11 @@ # This is similar to the syntax defined in the object extensions but allows for numbered # suffixes to quickly generate ranges of similar variables. # -# Where lp var type suffix is either _b for binary, _i for integer, or _f for float. +# Where lp var type suffix is either _lpb for binary, _lpi for integer, or _lpf for float. # I.e # -# Rating_i(5) is the equivalent of Rating_5 (type integer) -# Is_happy_b(2) is the equivalent of Is_happy_2 (type binary/boolean) +# Rating_lpi(5) is the equivalent of Rating_5 (type integer) +# Is_happy_lpb(2) is the equivalent of Is_happy_2 (type binary/boolean) # ... ## module Kernel @@ -20,15 +20,15 @@ def method_missing(value, *args) method_name = "#{value}" rescue "" start = method_name[0] if (start <= "Z" && start >= "A") - case method_name[-1] - when "b" - method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)] + case method_name[-3..-1] + when "lpb" + method_name = method_name[0..(method_name[-4] == "_" ? -5 : -4)] return BV.definition(method_name, args) - when "i" - method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)] + when "lpi" + method_name = method_name[0..(method_name[-4] == "_" ? -5 : -4)] return IV.definition(method_name, args) - when "f" - method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)] + when "lpf" + method_name = method_name[0..(method_name[-4] == "_" ? -5 : -4)] return LV.definition(method_name, args) end end diff --git a/lib/extensions/object_extensions.rb b/lib/extensions/object_extensions.rb index 1adfc85..b27747b 100644 --- a/lib/extensions/object_extensions.rb +++ b/lib/extensions/object_extensions.rb @@ -4,23 +4,23 @@ # # [Capitalized_varname][lp var type suffix] # -# Where lp var type suffix is either _b for binary, _i for integer, or _f for float. +# Where lp var type suffix is either _lpb for binary, _lpi for integer, or _lpf for float. # I.e # -# Rating_i is the equivalent of Rating (type integer) -# Is_happy_b is the equivalent of Is_happy (type binary/boolean) +# Rating_lpi is the equivalent of Rating (type integer) +# Is_happy_lpb is the equivalent of Is_happy (type binary/boolean) ## class << Object alias_method :old_const_missing, :const_missing def const_missing(value) method_name = "#{value}".split("::")[-1] rescue "" - if (("A".."Z").include?(method_name[0])) - if(method_name.end_with?("b")) - return BV.definition(method_name[0..(method_name[-2] == "_" ? -3 : -2)]) - elsif(method_name.end_with?("i")) - return IV.definition(method_name[0..(method_name[-2] == "_" ? -3 : -2)]) - elsif(method_name.end_with?("f")) - return LV.definition(method_name[0..(method_name[-2] == "_" ? -3 : -2)]) + if ("A".."Z").include?(method_name[0]) + if(method_name.end_with?("lpb")) + return BV.definition(method_name[0..(method_name[-4] == "_" ? -5 : -4)]) + elsif(method_name.end_with?("lpi")) + return IV.definition(method_name[0..(method_name[-4] == "_" ? -5 : -4)]) + elsif(method_name.end_with?("lpf")) + return LV.definition(method_name[0..(method_name[-4] == "_" ? -5 : -4)]) end end old_const_missing(value) diff --git a/lib/rulp/lv.rb b/lib/rulp/lv.rb index e237ea1..bd0b64a 100644 --- a/lib/rulp/lv.rb +++ b/lib/rulp/lv.rb @@ -22,7 +22,7 @@ def meth end def suffix - "f" + "lpf" end def self.definition(name, *args) @@ -75,11 +75,11 @@ def inspect class BV < LV; def suffix - "b" + "lpb" end end class IV < LV; def suffix - "i" + "lpi" end -end \ No newline at end of file +end diff --git a/test/test_basic_suite.rb b/test/test_basic_suite.rb index 496bd1b..637508f 100644 --- a/test/test_basic_suite.rb +++ b/test/test_basic_suite.rb @@ -4,55 +4,55 @@ class BasicSuite < Minitest::Test def test_single_binary_var each_solver do |solver| - assert_equal X_b.value, nil + assert_equal X_lpb.value, nil # The minimal value for a single binary variable is 0 - Rulp::Min(X_b).(solver) - assert_equal X_b.value, false + Rulp::Min(X_lpb).(solver) + assert_equal X_lpb.value, false # The maximal value for a single binary variable is 1 - Rulp::Max(X_b).(solver) - assert_equal X_b.value, true + Rulp::Max(X_lpb).(solver) + assert_equal X_lpb.value, true # If we set an upper bound this is respected by the solver - Rulp::Max(X_b)[1 * X_b <= 0].(solver) - assert_equal X_b.value, false + Rulp::Max(X_lpb)[1 * X_lpb <= 0].(solver) + assert_equal X_lpb.value, false # If we set a lower bound this is respected by the solver - Rulp::Min(X_b)[1 * X_b >= 1].(solver) - assert_equal X_b.value, true + Rulp::Min(X_lpb)[1 * X_lpb >= 1].(solver) + assert_equal X_lpb.value, true end end def test_single_integer_var each_solver do |solver| - assert_equal X_i.value, nil + assert_equal X_lpi.value, nil - given[ -35 <= X_i <= 35 ] + given[ -35 <= X_lpi <= 35 ] # Integer variables respect integer bounds - Rulp::Min(X_i).(solver) - assert_equal X_i.value, -35 + Rulp::Min(X_lpi).(solver) + assert_equal X_lpi.value, -35 # Integer variables respect integer bounds - Rulp::Max(X_i).(solver) - assert_equal X_i.value, 35 + Rulp::Max(X_lpi).(solver) + assert_equal X_lpi.value, 35 end end def test_single_general_var each_solver do |solver| - assert_equal X_f.value, nil + assert_equal X_lpf.value, nil - given[ -345.4321 <= X_f <= 345.4321 ] + given[ -345.4321 <= X_lpf <= 345.4321 ] # Integer variables respect integer bounds - Rulp::Min(X_f).(solver) - assert_in_delta X_f.value, -345.4321, 0.001 + Rulp::Min(X_lpf).(solver) + assert_in_delta X_lpf.value, -345.4321, 0.001 # Integer variables respect integer bounds - Rulp::Max(X_f).(solver) - assert_in_delta X_f.value, 345.4321, 0.001 + Rulp::Max(X_lpf).(solver) + assert_in_delta X_lpf.value, 345.4321, 0.001 end end -end \ No newline at end of file +end diff --git a/test/test_boolean.rb b/test/test_boolean.rb index d5296d9..d74cc98 100644 --- a/test/test_boolean.rb +++ b/test/test_boolean.rb @@ -7,7 +7,7 @@ ## class BooleanTest < Minitest::Test def setup - @items = 30.times.map(&Shop_Item_b) + @items = 30.times.map(&Shop_Item_lpb) items_count = @items.sum @items_costs = @items.map{|item| item * Random.rand(1.0...5.0)}.sum diff --git a/test/test_infeasible.rb b/test/test_infeasible.rb index b024b66..45ef836 100644 --- a/test/test_infeasible.rb +++ b/test/test_infeasible.rb @@ -7,7 +7,7 @@ ## class InfeasibleTest < Minitest::Test def setup - @items = 30.times.map(&Shop_Item_b) + @items = 30.times.map(&Shop_Item_lpb) items_count = @items.sum @items_costs = @items.map{|item| item * Random.rand(1.0...5.0)}.sum diff --git a/test/test_save_to_file.rb b/test/test_save_to_file.rb index f8eccd3..3722119 100644 --- a/test/test_save_to_file.rb +++ b/test/test_save_to_file.rb @@ -14,12 +14,12 @@ class SaveToFile < Minitest::Test def setup LV::clear - given[ X_i >= 0, Y_i >= 0, Z_i >= 0 ] - @objective = 10 * X_i + 6 * Y_i + 4 * Z_i + given[ X_lpi >= 0, Y_lpi >= 0, Z_lpi >= 0 ] + @objective = 10 * X_lpi + 6 * Y_lpi + 4 * Z_lpi @problem = Rulp::Max( @objective ) [ - X_i + Y_i + Z_i <= 100, - 10 * X_i + 4 * Y_i + 5 * Z_i <= 600, - 2 * X_i + 2 * Y_i + 6 * Z_i <= 300 + X_lpi + Y_lpi + Z_lpi <= 100, + 10 * X_lpi + 4 * Y_lpi + 5 * Z_lpi <= 600, + 2 * X_lpi + 2 * Y_lpi + 6 * Z_lpi <= 300 ] end diff --git a/test/test_simple.rb b/test/test_simple.rb index 46dcc0e..78a5517 100644 --- a/test/test_simple.rb +++ b/test/test_simple.rb @@ -13,12 +13,12 @@ class SimpleTest < Minitest::Test def setup - given[ X_i >= 0, Y_i >= 0, Z_i >= 0 ] - @objective = 10 * X_i + 6 * Y_i + 4 * Z_i + given[ X_lpi >= 0, Y_lpi >= 0, Z_lpi >= 0 ] + @objective = 10 * X_lpi + 6 * Y_lpi + 4 * Z_lpi @problem = Rulp::Max( @objective ) [ - X_i + Y_i + Z_i <= 100, - 10 * X_i + 4 * Y_i + 5 * Z_i <= 600, - 2 * X_i + 2 * Y_i + 6 * Z_i <= 300 + X_lpi + Y_lpi + Z_lpi <= 100, + 10 * X_lpi + 4 * Y_lpi + 5 * Z_lpi <= 600, + 2 * X_lpi + 2 * Y_lpi + 6 * Z_lpi <= 300 ] end @@ -26,9 +26,9 @@ def test_simple each_solver do |solver| setup @problem.send(solver) - assert_equal X_i.value, 33 - assert_equal Y_i.value, 67 - assert_equal Z_i.value, 0 + assert_equal X_lpi.value, 33 + assert_equal Y_lpi.value, 67 + assert_equal Z_lpi.value, 0 assert_equal @objective.evaluate , 732 end end