Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

RulpGem VersionDownloadsInline docsCodeship Status for wouterken/rulp

Table of Contentsgenerated with DocToc

Rulp

Rulp is an easy to use Ruby DSL for generating linear programming and mixed integer programming problem descriptions in the LP file format.

The [.lp] file format can be read and executed by most LP solvers including coin-Cbc, Scip, GLPK, CPLEX and Gurobi.

Rulp will execute and parse the results generated by Cbc, Scip, fscip(ug) and GLPK.

Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP library "Pulp".

Sample Code

# maximize# z = 10 * x + 6 * y + 4 * z## subject to# p: x + y + z <= 100# q: 10 * x + 4 * y + 5 * z <= 600# r: 2 * x + 2 * y + 6 * z <= 300## where all variables are non-negative integers# x >= 0, y >= 0, z >= 0#given[X_i >= 0,Y_i >= 0,Z_i >= 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].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##

Installation

To use Rulp as a complete solver toolkit you will need one of either Glpsol(GLPK), Scip, Coin-Cbc or HiGHS installed. Here are some sample instructions of how you install each of these solvers. Be sure to read the license terms for each of these solvers before using them.

Scip:

Go to install directory

cd /usr/local

Download Scip source (Be sure to visit the SCIP website and check the license terms first.)

curl -O http://scip.zib.de/download/release/scipoptsuite-3.1.1.tgz

Extract Scip source

gunzip -c scipoptsuite-3.1.1.tgz | tar xvf -

Scip relies on a number of libraries including ZLIB, GMP and READLINE. Please read the INSTALL directory in the application directory for help on getting these installed.

Build Scip

cd scipoptsuite-3.1.1
make

Add scip bin directory to your path E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.zshrc

You should now have scip installed.

UG (Scip parallel)

To run scip using multiple cores you will need to use the UG library for scip. This is bundled in the scipoptsuite directory. To install this simply run

make ug

From the base directory and then add the fscip binary to your path. E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.zshrc

Coin Cbc:

Navigate to install location

cd /usr/local

Follow CBC installation instructions

svn co https://projects.coin-or.org/svn/Cbc/stable/2.8 coin-Cbc
cd coin-Cbc
./configure -C #Optionally add --enable-cbc-parallel here to enable multiple threads for cbc
make
make install

Add Coin-cbc to path

E.g

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.zshrc

You should now have coin-Cbc installed.

GLPK:

Download the latest version of GLPK from http://www.gnu.org/software/glpk/#downloading

From the download directory

tar -xzf glpk-4.55.tar.gz
cd glpk-4.55
./configure --prefix=/usr/local
make
sudo make install

At this point, you should have GLPK installed. Verify it:

which glpsol
=> /usr/local/bin/glpsol

HiGHS:

Install HiGHS following the installation instructions at https://ergo-code.github.io/HiGHS/dev/interfaces/cpp/

Usage

Variables

# Rulp variables are initialized as soon as they are needed so there is no# 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 variableAn_Integer_i=>#<IV:0x007ffc4b651b80 @name="An_Integer">Generalf=>#<LV:0x007ffc4b651b80 @name="General">Bool_Val_b=>#<BV:0x007ffc4b67b6b0 @name="Bool_Val"># In some cases it is implausible to generate a unique name for every possible variable# as an LP problem description may contain many hundreds of variables.# To handle these scenarios variable definitions can# 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)#<IV:0x007ffc4b3ea518 @name="Item4_5">Item_i("store_3","table_2")#<IV:0x007ffc4b3a3cd0 @name="Itemstore_3_table_2">[*0..10].map(&Unit_f)=>[#<LV:0x007ffc4cc25768 @name="Unit0">,#<LV:0x007ffc4cc24cf0 @name="Unit1">,#<LV:0x007ffc4cc0fc88 @name="Unit2">,#<LV:0x007ffc4cc0f260 @name="Unit3">,#<LV:0x007ffc4cc0ecc0 @name="Unit4">,#<LV:0x007ffc4cc0e748 @name="Unit5">,#<LV:0x007ffc4cc0df50 @name="Unit6">,#<LV:0x007ffc4cc0d9d8 @name="Unit7">,#<LV:0x007ffc4cc0d460 @name="Unit8">,#<LV:0x007ffc4cc0cee8 @name="Unit9">,#<LV:0x007ffc4cc0c970 @name="Unit10">]

Variable Constraints

Add variable constraints to a variable using the <,>,<=,>=,== operators. Be careful to use '==' and not '=' when expressing equality. Constraints on a variable can only use numeric literals and not other variables. Inter-variable constraints should be expressed as problem constrants. (Explained below.)

X_i < 5X_i.bounds=>"X <= 5"3 <= X_i < 15X_i.bounds=>"3 <= X <= 15"Y_f == 10Y_f.bounds=>"y = 10"

Problem constraints

Constraints are added to a problem using the :[] syntax.

problem=Rulp::Max(10 * X_i + 6 * Y_i + 4 * Z_i)problem[X_i + Y_i + Z_i <= 100]problem[10 * X_i + 4 * Y_i + 5 * Z_i <= 600]
...
problem.solve

You can add multiple constraints at once by comma separating them as seen in the earlier examples:

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]

Solving or saving 'lp' files

There are multiple ways to solve the problem or output the problem to an 'lp' file. Currently the Rulp library supports calling installed executables for Scip, Cbc and Glpk. For each of these solvers it requires the solver command line binaries to be installed such that the command which [exec_name] returns a path. (I.e they must be on your PATH. See Installation).

Given a problem there are multiple ways to initiate a solver.

@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]

Default solver:

@problem.solve# this will use the solver specified in the environment variable 'SOLVER' by default.# This can be 'scip', 'cbc', 'glpk' or 'gurobi'.# You can also try and run scip or cbc in parallel using by passing an options argument containing { parallel: true }# If no variable is given it uses 'scip' as a default.

If you had a linear equation in a file named 'problem.rb' from the command line you could specify an alternate solver by executing:

SOLVER=cbcrubyproblem.rb

Explicit solver:

@problem.scip# Or@problem.cbc# Or@problem.glpk# Or@problem.cbc(parallel: true)# Or@problem.scip(parallel: true)

Or

Rulp::Scip(@problem)Rulp::Glpk(@problem)Rulp::Cbc(@problem)Rulp::scip(@problem,parallel: true)Rulp::cbc(@problem,parallel: true)

For debugging purposes you may wish to see the input and output files generated and consumed by Rulp. To do this you can pass open_definition and open_solution as options with the following extended syntax:

Rulp::Cbc(@problem,open_definition: true,open_solution: true)

The optional booleans will optionally call the 'open' utility to open the problem definition or the solution. (This utility is installed by default on a mac and will not work if the utility is not on your PATH)

Additional Options

Mip tolerance.

You can provide a MIP tolerance to any of the solvers to allow it to return a sub-optimal result within this tolerance from the dual bound.

# Various ways to invoke this option@problem.cbc(gap: 0.05)Rulp::Cbc(@problem,gap: 0.05)@problem.solve(gap: 0.05)
Node Limit.

You can limit the number of nodes the solver is able to explore before it must return it's current most optimal solution. If it has not discovered a solution by the time it hits the node limit it will return nil. Glpk does not accept a node limit option.

# Various ways to invoke this option@problem.cbc(node_limit: 10_000)Rulp::Cbc(@problem,node_limit: 10_000)@problem.solve(node_limit: 10_000)

For Scip you can also specify these limits in a settings file called scip.set in the current directory. Options passed to Rulp will overwrite these values.

# E.g inside ./scip.set
limits/gap = 0.15 # optimality within 0.1%.
limits/nodes = 200000 # No more than 200000 nodes explored

Saving LP files.

You may not wish to use one of the RULP compatible solvers but instead another solver that is able to read .lp files. (E.g CPLEX) but still want to use Rulp to generate your LP file. In this case you should use Rulp to output your lp problem description to a file of your choice. To do this simply use the following call

@problem.save("/Users/johndoe/Desktop/myproblem.lp")

OR

@problem.output("/Users/johndoe/Desktop/myproblem.lp")

You should also be able to call

@problem.save

Without parameters to be prompted for a save location.

Examples.

Take a look at some basic examples in the ./examples directory in the source code.

Rulp Executable

Rulp comes bundled with a 'rulp' executable which by default loads the rulp environment and either the Pry or Irb REPL. (Preference for Pry). Once installed you should be able to simply execute 'rulp' to launch this rulp enabled REPL. You can then play with and attempt LP and MIP problems straight from the command line.

[1]pry(main)> 13 <= X_i <= 45# Declare integer variable=>X(i)[undefined][2]pry(main)> -15 <= Y_f <= 15# Declare float variable=>Y(f)[undefined][3]pry(main)> @problem=Rulp::Min(X_i - Y_f)# Create min problem[info]Creatingminimizationproblem=>Minimizeobj: X -YSubjectto0X=0Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[4]@problem[X_i - 2 * Y_f < 40]#Adding a problem constraint=>Minimizeobj: X -YSubjecttoc0: X -2Y <= 40Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[5]pry(main)> @problem.solve# Solve
...
[info]Solvertook0.12337[info]Parsingresult=> -2.0#(Minimal result)[6]pry(main)> Y_f# See value calculated for Y now that solver has run=>Y(f)[15.0][8]pry(main)> X_i=>X(i)[13.0]# The result of the objective function (-2.0) was returned by the call to .solve# Now the solver has run and calculated values for our variables we can also test the# objective function (or any function) by calling evaluate on it.# E.g[9](X_i - Y_f).evaluate=> -2.0[10]pry(main)> (2 * X_i + 15 * Y_f).evaluate=>251.0

A larger example

Here is a basic example of how Rulp can help you model problems with a large number of variables. Suppose we are playing an IOS app which contains in-app purchases. Each of these in-app purchases costs a variable amount and gives us a certain number of in-game points. Suppose our mother gave us $55 to spend. We want to find the maximal number of in-game points we can buy using this money. Here is a simple example of how we could use Rulp to formulate this problem.

We decide to model each of these possible purchases as a binary variable (as we either purchase them or we don't. We can't partially purchase one.)

# Generate the data randomly for this example.costs,points=[*0..1000].mapdo |i|
[Purchase_b(i) * Random.rand(1.0..3.0),Purchase_b(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# the constraint that costs must be less than $55Rulp::Max(points)[costs < 55].solve=>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?)=>[Purchase27(b)[true],Purchase86(b)[true],Purchase120(b)[true],Purchase141(b)[true],Purchase154(b)[true],
...

MIP Minimization Example

Here is another example of using array style variables. Let's use the example of making a cup of coffee. Personally, I like my coffee with 2 creams and 1 sugar. There are many types of products that can add cream, or sugar to taste. Each has a different associated cost. As a price sensitive consumer what combination of products will allow me to flavor my coffee as desired with the lowest total cost?

desired=[{ingredient: :cream,amount: 2},{ingredient: :sugar,amount: 1},]products=[{name: 'Milk',cost: 0.50,supplies: {cream: 1}},{name: 'Baileys_Irish_Cream',cost: 2.99,supplies: {cream: 1,sugar: 1}},{name: 'Non_Dairy_Creamer',cost: 0.10,supplies: {cream: 1}},{name: 'Sugar',cost: 0.10,supplies: {sugar: 1}},]variables=products.mapdo |product|
Products_i(product[:name])end# => [ ProductMilk_i, ProductBaileys_Irish_Cream_i, ProductNon_Dairy_Creamer_i, ProductSugar_i ]given[variables.map{ |i| i >= 0}]# given [# ProductMilk_i >= 0,# ProductBaileys_Irish_Cream_i >= 0,# ProductNon_Dairy_Creamer_i >= 0,# ProductSugar_i >= 0,# ]constraints=desired.mapdo |minimum|
ingredient=minimum[:ingredient]desired_amount=minimum[:amount]products.map.with_indexdo |p,i|
coefficient=p[:supplies][ingredient] || 0coefficient * variables[i]end.inject(:+) >= desired_amountend# => [# 1 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 1 * ProductNon_Dairy_Creamer_i + 0 * ProductSugar_i >= 2# 0 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 0 * ProductNon_Dairy_Creamer_i + 1 * ProductSugar_i >= 1# ]objective=products.map.with_index{ |p,i| p[:cost] * variables[i]}.inject(:+)# => 0.50 * ProductMilk_i + 2.99 * ProductBaileys_Irish_Cream_i + 0.10 * ProductNon_Dairy_Creamer_i + 0.10 * ProductSugar_iproblem=Rulp::Min(objective)problem[constraints]Rulp::Glpk(problem)# DEBUG -- : Objective: 0.30000000000000004# Products = 0.0# Products = 0.0# Products = 2.0# Products = 1.0variables.map(&:value)# => [ 0, 0, 2, 1 ]

About

Ruby Linear Programming

Resources

Stars

44 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

RulpGem VersionDownloadsInline docsCodeship Status for wouterken/rulp

Table of Contentsgenerated with DocToc

Rulp

Rulp is an easy to use Ruby DSL for generating linear programming and mixed integer programming problem descriptions in the LP file format.

The [.lp] file format can be read and executed by most LP solvers including coin-Cbc, Scip, GLPK, CPLEX and Gurobi.

Rulp will execute and parse the results generated by Cbc, Scip, fscip(ug) and GLPK.

Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP library "Pulp".

Sample Code

# maximize# z = 10 * x + 6 * y + 4 * z## subject to# p: x + y + z <= 100# q: 10 * x + 4 * y + 5 * z <= 600# r: 2 * x + 2 * y + 6 * z <= 300## where all variables are non-negative integers# x >= 0, y >= 0, z >= 0#given[X_i >= 0,Y_i >= 0,Z_i >= 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].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##

Installation

To use Rulp as a complete solver toolkit you will need one of either Glpsol(GLPK), Scip, Coin-Cbc or HiGHS installed. Here are some sample instructions of how you install each of these solvers. Be sure to read the license terms for each of these solvers before using them.

Scip:

Go to install directory

cd /usr/local

Download Scip source (Be sure to visit the SCIP website and check the license terms first.)

curl -O http://scip.zib.de/download/release/scipoptsuite-3.1.1.tgz

Extract Scip source

gunzip -c scipoptsuite-3.1.1.tgz | tar xvf -

Scip relies on a number of libraries including ZLIB, GMP and READLINE. Please read the INSTALL directory in the application directory for help on getting these installed.

Build Scip

cd scipoptsuite-3.1.1
make

Add scip bin directory to your path E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.zshrc

You should now have scip installed.

UG (Scip parallel)

To run scip using multiple cores you will need to use the UG library for scip. This is bundled in the scipoptsuite directory. To install this simply run

make ug

From the base directory and then add the fscip binary to your path. E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.zshrc

Coin Cbc:

Navigate to install location

cd /usr/local

Follow CBC installation instructions

svn co https://projects.coin-or.org/svn/Cbc/stable/2.8 coin-Cbc
cd coin-Cbc
./configure -C #Optionally add --enable-cbc-parallel here to enable multiple threads for cbc
make
make install

Add Coin-cbc to path

E.g

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.zshrc

You should now have coin-Cbc installed.

GLPK:

Download the latest version of GLPK from http://www.gnu.org/software/glpk/#downloading

From the download directory

tar -xzf glpk-4.55.tar.gz
cd glpk-4.55
./configure --prefix=/usr/local
make
sudo make install

At this point, you should have GLPK installed. Verify it:

which glpsol
=> /usr/local/bin/glpsol

HiGHS:

Install HiGHS following the installation instructions at https://ergo-code.github.io/HiGHS/dev/interfaces/cpp/

Usage

Variables

# Rulp variables are initialized as soon as they are needed so there is no# 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 variableAn_Integer_i=>#<IV:0x007ffc4b651b80 @name="An_Integer">Generalf=>#<LV:0x007ffc4b651b80 @name="General">Bool_Val_b=>#<BV:0x007ffc4b67b6b0 @name="Bool_Val"># In some cases it is implausible to generate a unique name for every possible variable# as an LP problem description may contain many hundreds of variables.# To handle these scenarios variable definitions can# 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)#<IV:0x007ffc4b3ea518 @name="Item4_5">Item_i("store_3","table_2")#<IV:0x007ffc4b3a3cd0 @name="Itemstore_3_table_2">[*0..10].map(&Unit_f)=>[#<LV:0x007ffc4cc25768 @name="Unit0">,#<LV:0x007ffc4cc24cf0 @name="Unit1">,#<LV:0x007ffc4cc0fc88 @name="Unit2">,#<LV:0x007ffc4cc0f260 @name="Unit3">,#<LV:0x007ffc4cc0ecc0 @name="Unit4">,#<LV:0x007ffc4cc0e748 @name="Unit5">,#<LV:0x007ffc4cc0df50 @name="Unit6">,#<LV:0x007ffc4cc0d9d8 @name="Unit7">,#<LV:0x007ffc4cc0d460 @name="Unit8">,#<LV:0x007ffc4cc0cee8 @name="Unit9">,#<LV:0x007ffc4cc0c970 @name="Unit10">]

Variable Constraints

Add variable constraints to a variable using the <,>,<=,>=,== operators. Be careful to use '==' and not '=' when expressing equality. Constraints on a variable can only use numeric literals and not other variables. Inter-variable constraints should be expressed as problem constrants. (Explained below.)

X_i < 5X_i.bounds=>"X <= 5"3 <= X_i < 15X_i.bounds=>"3 <= X <= 15"Y_f == 10Y_f.bounds=>"y = 10"

Problem constraints

Constraints are added to a problem using the :[] syntax.

problem=Rulp::Max(10 * X_i + 6 * Y_i + 4 * Z_i)problem[X_i + Y_i + Z_i <= 100]problem[10 * X_i + 4 * Y_i + 5 * Z_i <= 600]
...
problem.solve

You can add multiple constraints at once by comma separating them as seen in the earlier examples:

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]

Solving or saving 'lp' files

There are multiple ways to solve the problem or output the problem to an 'lp' file. Currently the Rulp library supports calling installed executables for Scip, Cbc and Glpk. For each of these solvers it requires the solver command line binaries to be installed such that the command which [exec_name] returns a path. (I.e they must be on your PATH. See Installation).

Given a problem there are multiple ways to initiate a solver.

@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]

Default solver:

@problem.solve# this will use the solver specified in the environment variable 'SOLVER' by default.# This can be 'scip', 'cbc', 'glpk' or 'gurobi'.# You can also try and run scip or cbc in parallel using by passing an options argument containing { parallel: true }# If no variable is given it uses 'scip' as a default.

If you had a linear equation in a file named 'problem.rb' from the command line you could specify an alternate solver by executing:

SOLVER=cbcrubyproblem.rb

Explicit solver:

@problem.scip# Or@problem.cbc# Or@problem.glpk# Or@problem.cbc(parallel: true)# Or@problem.scip(parallel: true)

Or

Rulp::Scip(@problem)Rulp::Glpk(@problem)Rulp::Cbc(@problem)Rulp::scip(@problem,parallel: true)Rulp::cbc(@problem,parallel: true)

For debugging purposes you may wish to see the input and output files generated and consumed by Rulp. To do this you can pass open_definition and open_solution as options with the following extended syntax:

Rulp::Cbc(@problem,open_definition: true,open_solution: true)

The optional booleans will optionally call the 'open' utility to open the problem definition or the solution. (This utility is installed by default on a mac and will not work if the utility is not on your PATH)

Additional Options

Mip tolerance.

You can provide a MIP tolerance to any of the solvers to allow it to return a sub-optimal result within this tolerance from the dual bound.

# Various ways to invoke this option@problem.cbc(gap: 0.05)Rulp::Cbc(@problem,gap: 0.05)@problem.solve(gap: 0.05)
Node Limit.

You can limit the number of nodes the solver is able to explore before it must return it's current most optimal solution. If it has not discovered a solution by the time it hits the node limit it will return nil. Glpk does not accept a node limit option.

# Various ways to invoke this option@problem.cbc(node_limit: 10_000)Rulp::Cbc(@problem,node_limit: 10_000)@problem.solve(node_limit: 10_000)

For Scip you can also specify these limits in a settings file called scip.set in the current directory. Options passed to Rulp will overwrite these values.

# E.g inside ./scip.set
limits/gap = 0.15 # optimality within 0.1%.
limits/nodes = 200000 # No more than 200000 nodes explored

Saving LP files.

You may not wish to use one of the RULP compatible solvers but instead another solver that is able to read .lp files. (E.g CPLEX) but still want to use Rulp to generate your LP file. In this case you should use Rulp to output your lp problem description to a file of your choice. To do this simply use the following call

@problem.save("/Users/johndoe/Desktop/myproblem.lp")

OR

@problem.output("/Users/johndoe/Desktop/myproblem.lp")

You should also be able to call

@problem.save

Without parameters to be prompted for a save location.

Examples.

Take a look at some basic examples in the ./examples directory in the source code.

Rulp Executable

Rulp comes bundled with a 'rulp' executable which by default loads the rulp environment and either the Pry or Irb REPL. (Preference for Pry). Once installed you should be able to simply execute 'rulp' to launch this rulp enabled REPL. You can then play with and attempt LP and MIP problems straight from the command line.

[1]pry(main)> 13 <= X_i <= 45# Declare integer variable=>X(i)[undefined][2]pry(main)> -15 <= Y_f <= 15# Declare float variable=>Y(f)[undefined][3]pry(main)> @problem=Rulp::Min(X_i - Y_f)# Create min problem[info]Creatingminimizationproblem=>Minimizeobj: X -YSubjectto0X=0Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[4]@problem[X_i - 2 * Y_f < 40]#Adding a problem constraint=>Minimizeobj: X -YSubjecttoc0: X -2Y <= 40Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[5]pry(main)> @problem.solve# Solve
...
[info]Solvertook0.12337[info]Parsingresult=> -2.0#(Minimal result)[6]pry(main)> Y_f# See value calculated for Y now that solver has run=>Y(f)[15.0][8]pry(main)> X_i=>X(i)[13.0]# The result of the objective function (-2.0) was returned by the call to .solve# Now the solver has run and calculated values for our variables we can also test the# objective function (or any function) by calling evaluate on it.# E.g[9](X_i - Y_f).evaluate=> -2.0[10]pry(main)> (2 * X_i + 15 * Y_f).evaluate=>251.0

A larger example

Here is a basic example of how Rulp can help you model problems with a large number of variables. Suppose we are playing an IOS app which contains in-app purchases. Each of these in-app purchases costs a variable amount and gives us a certain number of in-game points. Suppose our mother gave us $55 to spend. We want to find the maximal number of in-game points we can buy using this money. Here is a simple example of how we could use Rulp to formulate this problem.

We decide to model each of these possible purchases as a binary variable (as we either purchase them or we don't. We can't partially purchase one.)

# Generate the data randomly for this example.costs,points=[*0..1000].mapdo |i|
[Purchase_b(i) * Random.rand(1.0..3.0),Purchase_b(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# the constraint that costs must be less than $55Rulp::Max(points)[costs < 55].solve=>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?)=>[Purchase27(b)[true],Purchase86(b)[true],Purchase120(b)[true],Purchase141(b)[true],Purchase154(b)[true],
...

MIP Minimization Example

Here is another example of using array style variables. Let's use the example of making a cup of coffee. Personally, I like my coffee with 2 creams and 1 sugar. There are many types of products that can add cream, or sugar to taste. Each has a different associated cost. As a price sensitive consumer what combination of products will allow me to flavor my coffee as desired with the lowest total cost?

desired=[{ingredient: :cream,amount: 2},{ingredient: :sugar,amount: 1},]products=[{name: 'Milk',cost: 0.50,supplies: {cream: 1}},{name: 'Baileys_Irish_Cream',cost: 2.99,supplies: {cream: 1,sugar: 1}},{name: 'Non_Dairy_Creamer',cost: 0.10,supplies: {cream: 1}},{name: 'Sugar',cost: 0.10,supplies: {sugar: 1}},]variables=products.mapdo |product|
Products_i(product[:name])end# => [ ProductMilk_i, ProductBaileys_Irish_Cream_i, ProductNon_Dairy_Creamer_i, ProductSugar_i ]given[variables.map{ |i| i >= 0}]# given [# ProductMilk_i >= 0,# ProductBaileys_Irish_Cream_i >= 0,# ProductNon_Dairy_Creamer_i >= 0,# ProductSugar_i >= 0,# ]constraints=desired.mapdo |minimum|
ingredient=minimum[:ingredient]desired_amount=minimum[:amount]products.map.with_indexdo |p,i|
coefficient=p[:supplies][ingredient] || 0coefficient * variables[i]end.inject(:+) >= desired_amountend# => [# 1 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 1 * ProductNon_Dairy_Creamer_i + 0 * ProductSugar_i >= 2# 0 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 0 * ProductNon_Dairy_Creamer_i + 1 * ProductSugar_i >= 1# ]objective=products.map.with_index{ |p,i| p[:cost] * variables[i]}.inject(:+)# => 0.50 * ProductMilk_i + 2.99 * ProductBaileys_Irish_Cream_i + 0.10 * ProductNon_Dairy_Creamer_i + 0.10 * ProductSugar_iproblem=Rulp::Min(objective)problem[constraints]Rulp::Glpk(problem)# DEBUG -- : Objective: 0.30000000000000004# Products = 0.0# Products = 0.0# Products = 2.0# Products = 1.0variables.map(&:value)# => [ 0, 0, 2, 1 ]

About

Ruby Linear Programming

Resources

Stars

44 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

RulpGem VersionDownloadsInline docsCodeship Status for wouterken/rulp

Table of Contentsgenerated with DocToc

Rulp

Rulp is an easy to use Ruby DSL for generating linear programming and mixed integer programming problem descriptions in the LP file format.

The [.lp] file format can be read and executed by most LP solvers including coin-Cbc, Scip, GLPK, CPLEX and Gurobi.

Rulp will execute and parse the results generated by Cbc, Scip, fscip(ug) and GLPK.

Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP library "Pulp".

Sample Code

# maximize# z = 10 * x + 6 * y + 4 * z## subject to# p: x + y + z <= 100# q: 10 * x + 4 * y + 5 * z <= 600# r: 2 * x + 2 * y + 6 * z <= 300## where all variables are non-negative integers# x >= 0, y >= 0, z >= 0#given[X_i >= 0,Y_i >= 0,Z_i >= 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].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##

Installation

To use Rulp as a complete solver toolkit you will need one of either Glpsol(GLPK), Scip, Coin-Cbc or HiGHS installed. Here are some sample instructions of how you install each of these solvers. Be sure to read the license terms for each of these solvers before using them.

Scip:

Go to install directory

cd /usr/local

Download Scip source (Be sure to visit the SCIP website and check the license terms first.)

curl -O http://scip.zib.de/download/release/scipoptsuite-3.1.1.tgz

Extract Scip source

gunzip -c scipoptsuite-3.1.1.tgz | tar xvf -

Scip relies on a number of libraries including ZLIB, GMP and READLINE. Please read the INSTALL directory in the application directory for help on getting these installed.

Build Scip

cd scipoptsuite-3.1.1
make

Add scip bin directory to your path E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.zshrc

You should now have scip installed.

UG (Scip parallel)

To run scip using multiple cores you will need to use the UG library for scip. This is bundled in the scipoptsuite directory. To install this simply run

make ug

From the base directory and then add the fscip binary to your path. E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.zshrc

Coin Cbc:

Navigate to install location

cd /usr/local

Follow CBC installation instructions

svn co https://projects.coin-or.org/svn/Cbc/stable/2.8 coin-Cbc
cd coin-Cbc
./configure -C #Optionally add --enable-cbc-parallel here to enable multiple threads for cbc
make
make install

Add Coin-cbc to path

E.g

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.zshrc

You should now have coin-Cbc installed.

GLPK:

Download the latest version of GLPK from http://www.gnu.org/software/glpk/#downloading

From the download directory

tar -xzf glpk-4.55.tar.gz
cd glpk-4.55
./configure --prefix=/usr/local
make
sudo make install

At this point, you should have GLPK installed. Verify it:

which glpsol
=> /usr/local/bin/glpsol

HiGHS:

Install HiGHS following the installation instructions at https://ergo-code.github.io/HiGHS/dev/interfaces/cpp/

Usage

Variables

# Rulp variables are initialized as soon as they are needed so there is no# 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 variableAn_Integer_i=>#<IV:0x007ffc4b651b80 @name="An_Integer">Generalf=>#<LV:0x007ffc4b651b80 @name="General">Bool_Val_b=>#<BV:0x007ffc4b67b6b0 @name="Bool_Val"># In some cases it is implausible to generate a unique name for every possible variable# as an LP problem description may contain many hundreds of variables.# To handle these scenarios variable definitions can# 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)#<IV:0x007ffc4b3ea518 @name="Item4_5">Item_i("store_3","table_2")#<IV:0x007ffc4b3a3cd0 @name="Itemstore_3_table_2">[*0..10].map(&Unit_f)=>[#<LV:0x007ffc4cc25768 @name="Unit0">,#<LV:0x007ffc4cc24cf0 @name="Unit1">,#<LV:0x007ffc4cc0fc88 @name="Unit2">,#<LV:0x007ffc4cc0f260 @name="Unit3">,#<LV:0x007ffc4cc0ecc0 @name="Unit4">,#<LV:0x007ffc4cc0e748 @name="Unit5">,#<LV:0x007ffc4cc0df50 @name="Unit6">,#<LV:0x007ffc4cc0d9d8 @name="Unit7">,#<LV:0x007ffc4cc0d460 @name="Unit8">,#<LV:0x007ffc4cc0cee8 @name="Unit9">,#<LV:0x007ffc4cc0c970 @name="Unit10">]

Variable Constraints

Add variable constraints to a variable using the <,>,<=,>=,== operators. Be careful to use '==' and not '=' when expressing equality. Constraints on a variable can only use numeric literals and not other variables. Inter-variable constraints should be expressed as problem constrants. (Explained below.)

X_i < 5X_i.bounds=>"X <= 5"3 <= X_i < 15X_i.bounds=>"3 <= X <= 15"Y_f == 10Y_f.bounds=>"y = 10"

Problem constraints

Constraints are added to a problem using the :[] syntax.

problem=Rulp::Max(10 * X_i + 6 * Y_i + 4 * Z_i)problem[X_i + Y_i + Z_i <= 100]problem[10 * X_i + 4 * Y_i + 5 * Z_i <= 600]
...
problem.solve

You can add multiple constraints at once by comma separating them as seen in the earlier examples:

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]

Solving or saving 'lp' files

There are multiple ways to solve the problem or output the problem to an 'lp' file. Currently the Rulp library supports calling installed executables for Scip, Cbc and Glpk. For each of these solvers it requires the solver command line binaries to be installed such that the command which [exec_name] returns a path. (I.e they must be on your PATH. See Installation).

Given a problem there are multiple ways to initiate a solver.

@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]

Default solver:

@problem.solve# this will use the solver specified in the environment variable 'SOLVER' by default.# This can be 'scip', 'cbc', 'glpk' or 'gurobi'.# You can also try and run scip or cbc in parallel using by passing an options argument containing { parallel: true }# If no variable is given it uses 'scip' as a default.

If you had a linear equation in a file named 'problem.rb' from the command line you could specify an alternate solver by executing:

SOLVER=cbcrubyproblem.rb

Explicit solver:

@problem.scip# Or@problem.cbc# Or@problem.glpk# Or@problem.cbc(parallel: true)# Or@problem.scip(parallel: true)

Or

Rulp::Scip(@problem)Rulp::Glpk(@problem)Rulp::Cbc(@problem)Rulp::scip(@problem,parallel: true)Rulp::cbc(@problem,parallel: true)

For debugging purposes you may wish to see the input and output files generated and consumed by Rulp. To do this you can pass open_definition and open_solution as options with the following extended syntax:

Rulp::Cbc(@problem,open_definition: true,open_solution: true)

The optional booleans will optionally call the 'open' utility to open the problem definition or the solution. (This utility is installed by default on a mac and will not work if the utility is not on your PATH)

Additional Options

Mip tolerance.

You can provide a MIP tolerance to any of the solvers to allow it to return a sub-optimal result within this tolerance from the dual bound.

# Various ways to invoke this option@problem.cbc(gap: 0.05)Rulp::Cbc(@problem,gap: 0.05)@problem.solve(gap: 0.05)
Node Limit.

You can limit the number of nodes the solver is able to explore before it must return it's current most optimal solution. If it has not discovered a solution by the time it hits the node limit it will return nil. Glpk does not accept a node limit option.

# Various ways to invoke this option@problem.cbc(node_limit: 10_000)Rulp::Cbc(@problem,node_limit: 10_000)@problem.solve(node_limit: 10_000)

For Scip you can also specify these limits in a settings file called scip.set in the current directory. Options passed to Rulp will overwrite these values.

# E.g inside ./scip.set
limits/gap = 0.15 # optimality within 0.1%.
limits/nodes = 200000 # No more than 200000 nodes explored

Saving LP files.

You may not wish to use one of the RULP compatible solvers but instead another solver that is able to read .lp files. (E.g CPLEX) but still want to use Rulp to generate your LP file. In this case you should use Rulp to output your lp problem description to a file of your choice. To do this simply use the following call

@problem.save("/Users/johndoe/Desktop/myproblem.lp")

OR

@problem.output("/Users/johndoe/Desktop/myproblem.lp")

You should also be able to call

@problem.save

Without parameters to be prompted for a save location.

Examples.

Take a look at some basic examples in the ./examples directory in the source code.

Rulp Executable

Rulp comes bundled with a 'rulp' executable which by default loads the rulp environment and either the Pry or Irb REPL. (Preference for Pry). Once installed you should be able to simply execute 'rulp' to launch this rulp enabled REPL. You can then play with and attempt LP and MIP problems straight from the command line.

[1]pry(main)> 13 <= X_i <= 45# Declare integer variable=>X(i)[undefined][2]pry(main)> -15 <= Y_f <= 15# Declare float variable=>Y(f)[undefined][3]pry(main)> @problem=Rulp::Min(X_i - Y_f)# Create min problem[info]Creatingminimizationproblem=>Minimizeobj: X -YSubjectto0X=0Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[4]@problem[X_i - 2 * Y_f < 40]#Adding a problem constraint=>Minimizeobj: X -YSubjecttoc0: X -2Y <= 40Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[5]pry(main)> @problem.solve# Solve
...
[info]Solvertook0.12337[info]Parsingresult=> -2.0#(Minimal result)[6]pry(main)> Y_f# See value calculated for Y now that solver has run=>Y(f)[15.0][8]pry(main)> X_i=>X(i)[13.0]# The result of the objective function (-2.0) was returned by the call to .solve# Now the solver has run and calculated values for our variables we can also test the# objective function (or any function) by calling evaluate on it.# E.g[9](X_i - Y_f).evaluate=> -2.0[10]pry(main)> (2 * X_i + 15 * Y_f).evaluate=>251.0

A larger example

Here is a basic example of how Rulp can help you model problems with a large number of variables. Suppose we are playing an IOS app which contains in-app purchases. Each of these in-app purchases costs a variable amount and gives us a certain number of in-game points. Suppose our mother gave us $55 to spend. We want to find the maximal number of in-game points we can buy using this money. Here is a simple example of how we could use Rulp to formulate this problem.

We decide to model each of these possible purchases as a binary variable (as we either purchase them or we don't. We can't partially purchase one.)

# Generate the data randomly for this example.costs,points=[*0..1000].mapdo |i|
[Purchase_b(i) * Random.rand(1.0..3.0),Purchase_b(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# the constraint that costs must be less than $55Rulp::Max(points)[costs < 55].solve=>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?)=>[Purchase27(b)[true],Purchase86(b)[true],Purchase120(b)[true],Purchase141(b)[true],Purchase154(b)[true],
...

MIP Minimization Example

Here is another example of using array style variables. Let's use the example of making a cup of coffee. Personally, I like my coffee with 2 creams and 1 sugar. There are many types of products that can add cream, or sugar to taste. Each has a different associated cost. As a price sensitive consumer what combination of products will allow me to flavor my coffee as desired with the lowest total cost?

desired=[{ingredient: :cream,amount: 2},{ingredient: :sugar,amount: 1},]products=[{name: 'Milk',cost: 0.50,supplies: {cream: 1}},{name: 'Baileys_Irish_Cream',cost: 2.99,supplies: {cream: 1,sugar: 1}},{name: 'Non_Dairy_Creamer',cost: 0.10,supplies: {cream: 1}},{name: 'Sugar',cost: 0.10,supplies: {sugar: 1}},]variables=products.mapdo |product|
Products_i(product[:name])end# => [ ProductMilk_i, ProductBaileys_Irish_Cream_i, ProductNon_Dairy_Creamer_i, ProductSugar_i ]given[variables.map{ |i| i >= 0}]# given [# ProductMilk_i >= 0,# ProductBaileys_Irish_Cream_i >= 0,# ProductNon_Dairy_Creamer_i >= 0,# ProductSugar_i >= 0,# ]constraints=desired.mapdo |minimum|
ingredient=minimum[:ingredient]desired_amount=minimum[:amount]products.map.with_indexdo |p,i|
coefficient=p[:supplies][ingredient] || 0coefficient * variables[i]end.inject(:+) >= desired_amountend# => [# 1 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 1 * ProductNon_Dairy_Creamer_i + 0 * ProductSugar_i >= 2# 0 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 0 * ProductNon_Dairy_Creamer_i + 1 * ProductSugar_i >= 1# ]objective=products.map.with_index{ |p,i| p[:cost] * variables[i]}.inject(:+)# => 0.50 * ProductMilk_i + 2.99 * ProductBaileys_Irish_Cream_i + 0.10 * ProductNon_Dairy_Creamer_i + 0.10 * ProductSugar_iproblem=Rulp::Min(objective)problem[constraints]Rulp::Glpk(problem)# DEBUG -- : Objective: 0.30000000000000004# Products = 0.0# Products = 0.0# Products = 2.0# Products = 1.0variables.map(&:value)# => [ 0, 0, 2, 1 ]

About

Ruby Linear Programming

Resources

Stars

44 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

RulpGem VersionDownloadsInline docsCodeship Status for wouterken/rulp

Table of Contentsgenerated with DocToc

Rulp

Rulp is an easy to use Ruby DSL for generating linear programming and mixed integer programming problem descriptions in the LP file format.

The [.lp] file format can be read and executed by most LP solvers including coin-Cbc, Scip, GLPK, CPLEX and Gurobi.

Rulp will execute and parse the results generated by Cbc, Scip, fscip(ug) and GLPK.

Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP library "Pulp".

Sample Code

# maximize# z = 10 * x + 6 * y + 4 * z## subject to# p: x + y + z <= 100# q: 10 * x + 4 * y + 5 * z <= 600# r: 2 * x + 2 * y + 6 * z <= 300## where all variables are non-negative integers# x >= 0, y >= 0, z >= 0#given[X_i >= 0,Y_i >= 0,Z_i >= 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].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##

Installation

To use Rulp as a complete solver toolkit you will need one of either Glpsol(GLPK), Scip, Coin-Cbc or HiGHS installed. Here are some sample instructions of how you install each of these solvers. Be sure to read the license terms for each of these solvers before using them.

Scip:

Go to install directory

cd /usr/local

Download Scip source (Be sure to visit the SCIP website and check the license terms first.)

curl -O http://scip.zib.de/download/release/scipoptsuite-3.1.1.tgz

Extract Scip source

gunzip -c scipoptsuite-3.1.1.tgz | tar xvf -

Scip relies on a number of libraries including ZLIB, GMP and READLINE. Please read the INSTALL directory in the application directory for help on getting these installed.

Build Scip

cd scipoptsuite-3.1.1
make

Add scip bin directory to your path E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.zshrc

You should now have scip installed.

UG (Scip parallel)

To run scip using multiple cores you will need to use the UG library for scip. This is bundled in the scipoptsuite directory. To install this simply run

make ug

From the base directory and then add the fscip binary to your path. E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.zshrc

Coin Cbc:

Navigate to install location

cd /usr/local

Follow CBC installation instructions

svn co https://projects.coin-or.org/svn/Cbc/stable/2.8 coin-Cbc
cd coin-Cbc
./configure -C #Optionally add --enable-cbc-parallel here to enable multiple threads for cbc
make
make install

Add Coin-cbc to path

E.g

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.zshrc

You should now have coin-Cbc installed.

GLPK:

Download the latest version of GLPK from http://www.gnu.org/software/glpk/#downloading

From the download directory

tar -xzf glpk-4.55.tar.gz
cd glpk-4.55
./configure --prefix=/usr/local
make
sudo make install

At this point, you should have GLPK installed. Verify it:

which glpsol
=> /usr/local/bin/glpsol

HiGHS:

Install HiGHS following the installation instructions at https://ergo-code.github.io/HiGHS/dev/interfaces/cpp/

Usage

Variables

# Rulp variables are initialized as soon as they are needed so there is no# 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 variableAn_Integer_i=>#<IV:0x007ffc4b651b80 @name="An_Integer">Generalf=>#<LV:0x007ffc4b651b80 @name="General">Bool_Val_b=>#<BV:0x007ffc4b67b6b0 @name="Bool_Val"># In some cases it is implausible to generate a unique name for every possible variable# as an LP problem description may contain many hundreds of variables.# To handle these scenarios variable definitions can# 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)#<IV:0x007ffc4b3ea518 @name="Item4_5">Item_i("store_3","table_2")#<IV:0x007ffc4b3a3cd0 @name="Itemstore_3_table_2">[*0..10].map(&Unit_f)=>[#<LV:0x007ffc4cc25768 @name="Unit0">,#<LV:0x007ffc4cc24cf0 @name="Unit1">,#<LV:0x007ffc4cc0fc88 @name="Unit2">,#<LV:0x007ffc4cc0f260 @name="Unit3">,#<LV:0x007ffc4cc0ecc0 @name="Unit4">,#<LV:0x007ffc4cc0e748 @name="Unit5">,#<LV:0x007ffc4cc0df50 @name="Unit6">,#<LV:0x007ffc4cc0d9d8 @name="Unit7">,#<LV:0x007ffc4cc0d460 @name="Unit8">,#<LV:0x007ffc4cc0cee8 @name="Unit9">,#<LV:0x007ffc4cc0c970 @name="Unit10">]

Variable Constraints

Add variable constraints to a variable using the <,>,<=,>=,== operators. Be careful to use '==' and not '=' when expressing equality. Constraints on a variable can only use numeric literals and not other variables. Inter-variable constraints should be expressed as problem constrants. (Explained below.)

X_i < 5X_i.bounds=>"X <= 5"3 <= X_i < 15X_i.bounds=>"3 <= X <= 15"Y_f == 10Y_f.bounds=>"y = 10"

Problem constraints

Constraints are added to a problem using the :[] syntax.

problem=Rulp::Max(10 * X_i + 6 * Y_i + 4 * Z_i)problem[X_i + Y_i + Z_i <= 100]problem[10 * X_i + 4 * Y_i + 5 * Z_i <= 600]
...
problem.solve

You can add multiple constraints at once by comma separating them as seen in the earlier examples:

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]

Solving or saving 'lp' files

There are multiple ways to solve the problem or output the problem to an 'lp' file. Currently the Rulp library supports calling installed executables for Scip, Cbc and Glpk. For each of these solvers it requires the solver command line binaries to be installed such that the command which [exec_name] returns a path. (I.e they must be on your PATH. See Installation).

Given a problem there are multiple ways to initiate a solver.

@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]

Default solver:

@problem.solve# this will use the solver specified in the environment variable 'SOLVER' by default.# This can be 'scip', 'cbc', 'glpk' or 'gurobi'.# You can also try and run scip or cbc in parallel using by passing an options argument containing { parallel: true }# If no variable is given it uses 'scip' as a default.

If you had a linear equation in a file named 'problem.rb' from the command line you could specify an alternate solver by executing:

SOLVER=cbcrubyproblem.rb

Explicit solver:

@problem.scip# Or@problem.cbc# Or@problem.glpk# Or@problem.cbc(parallel: true)# Or@problem.scip(parallel: true)

Or

Rulp::Scip(@problem)Rulp::Glpk(@problem)Rulp::Cbc(@problem)Rulp::scip(@problem,parallel: true)Rulp::cbc(@problem,parallel: true)

For debugging purposes you may wish to see the input and output files generated and consumed by Rulp. To do this you can pass open_definition and open_solution as options with the following extended syntax:

Rulp::Cbc(@problem,open_definition: true,open_solution: true)

The optional booleans will optionally call the 'open' utility to open the problem definition or the solution. (This utility is installed by default on a mac and will not work if the utility is not on your PATH)

Additional Options

Mip tolerance.

You can provide a MIP tolerance to any of the solvers to allow it to return a sub-optimal result within this tolerance from the dual bound.

# Various ways to invoke this option@problem.cbc(gap: 0.05)Rulp::Cbc(@problem,gap: 0.05)@problem.solve(gap: 0.05)
Node Limit.

You can limit the number of nodes the solver is able to explore before it must return it's current most optimal solution. If it has not discovered a solution by the time it hits the node limit it will return nil. Glpk does not accept a node limit option.

# Various ways to invoke this option@problem.cbc(node_limit: 10_000)Rulp::Cbc(@problem,node_limit: 10_000)@problem.solve(node_limit: 10_000)

For Scip you can also specify these limits in a settings file called scip.set in the current directory. Options passed to Rulp will overwrite these values.

# E.g inside ./scip.set
limits/gap = 0.15 # optimality within 0.1%.
limits/nodes = 200000 # No more than 200000 nodes explored

Saving LP files.

You may not wish to use one of the RULP compatible solvers but instead another solver that is able to read .lp files. (E.g CPLEX) but still want to use Rulp to generate your LP file. In this case you should use Rulp to output your lp problem description to a file of your choice. To do this simply use the following call

@problem.save("/Users/johndoe/Desktop/myproblem.lp")

OR

@problem.output("/Users/johndoe/Desktop/myproblem.lp")

You should also be able to call

@problem.save

Without parameters to be prompted for a save location.

Examples.

Take a look at some basic examples in the ./examples directory in the source code.

Rulp Executable

Rulp comes bundled with a 'rulp' executable which by default loads the rulp environment and either the Pry or Irb REPL. (Preference for Pry). Once installed you should be able to simply execute 'rulp' to launch this rulp enabled REPL. You can then play with and attempt LP and MIP problems straight from the command line.

[1]pry(main)> 13 <= X_i <= 45# Declare integer variable=>X(i)[undefined][2]pry(main)> -15 <= Y_f <= 15# Declare float variable=>Y(f)[undefined][3]pry(main)> @problem=Rulp::Min(X_i - Y_f)# Create min problem[info]Creatingminimizationproblem=>Minimizeobj: X -YSubjectto0X=0Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[4]@problem[X_i - 2 * Y_f < 40]#Adding a problem constraint=>Minimizeobj: X -YSubjecttoc0: X -2Y <= 40Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[5]pry(main)> @problem.solve# Solve
...
[info]Solvertook0.12337[info]Parsingresult=> -2.0#(Minimal result)[6]pry(main)> Y_f# See value calculated for Y now that solver has run=>Y(f)[15.0][8]pry(main)> X_i=>X(i)[13.0]# The result of the objective function (-2.0) was returned by the call to .solve# Now the solver has run and calculated values for our variables we can also test the# objective function (or any function) by calling evaluate on it.# E.g[9](X_i - Y_f).evaluate=> -2.0[10]pry(main)> (2 * X_i + 15 * Y_f).evaluate=>251.0

A larger example

Here is a basic example of how Rulp can help you model problems with a large number of variables. Suppose we are playing an IOS app which contains in-app purchases. Each of these in-app purchases costs a variable amount and gives us a certain number of in-game points. Suppose our mother gave us $55 to spend. We want to find the maximal number of in-game points we can buy using this money. Here is a simple example of how we could use Rulp to formulate this problem.

We decide to model each of these possible purchases as a binary variable (as we either purchase them or we don't. We can't partially purchase one.)

# Generate the data randomly for this example.costs,points=[*0..1000].mapdo |i|
[Purchase_b(i) * Random.rand(1.0..3.0),Purchase_b(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# the constraint that costs must be less than $55Rulp::Max(points)[costs < 55].solve=>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?)=>[Purchase27(b)[true],Purchase86(b)[true],Purchase120(b)[true],Purchase141(b)[true],Purchase154(b)[true],
...

MIP Minimization Example

Here is another example of using array style variables. Let's use the example of making a cup of coffee. Personally, I like my coffee with 2 creams and 1 sugar. There are many types of products that can add cream, or sugar to taste. Each has a different associated cost. As a price sensitive consumer what combination of products will allow me to flavor my coffee as desired with the lowest total cost?

desired=[{ingredient: :cream,amount: 2},{ingredient: :sugar,amount: 1},]products=[{name: 'Milk',cost: 0.50,supplies: {cream: 1}},{name: 'Baileys_Irish_Cream',cost: 2.99,supplies: {cream: 1,sugar: 1}},{name: 'Non_Dairy_Creamer',cost: 0.10,supplies: {cream: 1}},{name: 'Sugar',cost: 0.10,supplies: {sugar: 1}},]variables=products.mapdo |product|
Products_i(product[:name])end# => [ ProductMilk_i, ProductBaileys_Irish_Cream_i, ProductNon_Dairy_Creamer_i, ProductSugar_i ]given[variables.map{ |i| i >= 0}]# given [# ProductMilk_i >= 0,# ProductBaileys_Irish_Cream_i >= 0,# ProductNon_Dairy_Creamer_i >= 0,# ProductSugar_i >= 0,# ]constraints=desired.mapdo |minimum|
ingredient=minimum[:ingredient]desired_amount=minimum[:amount]products.map.with_indexdo |p,i|
coefficient=p[:supplies][ingredient] || 0coefficient * variables[i]end.inject(:+) >= desired_amountend# => [# 1 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 1 * ProductNon_Dairy_Creamer_i + 0 * ProductSugar_i >= 2# 0 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 0 * ProductNon_Dairy_Creamer_i + 1 * ProductSugar_i >= 1# ]objective=products.map.with_index{ |p,i| p[:cost] * variables[i]}.inject(:+)# => 0.50 * ProductMilk_i + 2.99 * ProductBaileys_Irish_Cream_i + 0.10 * ProductNon_Dairy_Creamer_i + 0.10 * ProductSugar_iproblem=Rulp::Min(objective)problem[constraints]Rulp::Glpk(problem)# DEBUG -- : Objective: 0.30000000000000004# Products = 0.0# Products = 0.0# Products = 2.0# Products = 1.0variables.map(&:value)# => [ 0, 0, 2, 1 ]

About

Ruby Linear Programming

Resources

Stars

44 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

RulpGem VersionDownloadsInline docsCodeship Status for wouterken/rulp

Table of Contentsgenerated with DocToc

Rulp

Rulp is an easy to use Ruby DSL for generating linear programming and mixed integer programming problem descriptions in the LP file format.

The [.lp] file format can be read and executed by most LP solvers including coin-Cbc, Scip, GLPK, CPLEX and Gurobi.

Rulp will execute and parse the results generated by Cbc, Scip, fscip(ug) and GLPK.

Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP library "Pulp".

Sample Code

# maximize# z = 10 * x + 6 * y + 4 * z## subject to# p: x + y + z <= 100# q: 10 * x + 4 * y + 5 * z <= 600# r: 2 * x + 2 * y + 6 * z <= 300## where all variables are non-negative integers# x >= 0, y >= 0, z >= 0#given[X_i >= 0,Y_i >= 0,Z_i >= 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].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##

Installation

To use Rulp as a complete solver toolkit you will need one of either Glpsol(GLPK), Scip, Coin-Cbc or HiGHS installed. Here are some sample instructions of how you install each of these solvers. Be sure to read the license terms for each of these solvers before using them.

Scip:

Go to install directory

cd /usr/local

Download Scip source (Be sure to visit the SCIP website and check the license terms first.)

curl -O http://scip.zib.de/download/release/scipoptsuite-3.1.1.tgz

Extract Scip source

gunzip -c scipoptsuite-3.1.1.tgz | tar xvf -

Scip relies on a number of libraries including ZLIB, GMP and READLINE. Please read the INSTALL directory in the application directory for help on getting these installed.

Build Scip

cd scipoptsuite-3.1.1
make

Add scip bin directory to your path E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.zshrc

You should now have scip installed.

UG (Scip parallel)

To run scip using multiple cores you will need to use the UG library for scip. This is bundled in the scipoptsuite directory. To install this simply run

make ug

From the base directory and then add the fscip binary to your path. E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.zshrc

Coin Cbc:

Navigate to install location

cd /usr/local

Follow CBC installation instructions

svn co https://projects.coin-or.org/svn/Cbc/stable/2.8 coin-Cbc
cd coin-Cbc
./configure -C #Optionally add --enable-cbc-parallel here to enable multiple threads for cbc
make
make install

Add Coin-cbc to path

E.g

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.zshrc

You should now have coin-Cbc installed.

GLPK:

Download the latest version of GLPK from http://www.gnu.org/software/glpk/#downloading

From the download directory

tar -xzf glpk-4.55.tar.gz
cd glpk-4.55
./configure --prefix=/usr/local
make
sudo make install

At this point, you should have GLPK installed. Verify it:

which glpsol
=> /usr/local/bin/glpsol

HiGHS:

Install HiGHS following the installation instructions at https://ergo-code.github.io/HiGHS/dev/interfaces/cpp/

Usage

Variables

# Rulp variables are initialized as soon as they are needed so there is no# 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 variableAn_Integer_i=>#<IV:0x007ffc4b651b80 @name="An_Integer">Generalf=>#<LV:0x007ffc4b651b80 @name="General">Bool_Val_b=>#<BV:0x007ffc4b67b6b0 @name="Bool_Val"># In some cases it is implausible to generate a unique name for every possible variable# as an LP problem description may contain many hundreds of variables.# To handle these scenarios variable definitions can# 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)#<IV:0x007ffc4b3ea518 @name="Item4_5">Item_i("store_3","table_2")#<IV:0x007ffc4b3a3cd0 @name="Itemstore_3_table_2">[*0..10].map(&Unit_f)=>[#<LV:0x007ffc4cc25768 @name="Unit0">,#<LV:0x007ffc4cc24cf0 @name="Unit1">,#<LV:0x007ffc4cc0fc88 @name="Unit2">,#<LV:0x007ffc4cc0f260 @name="Unit3">,#<LV:0x007ffc4cc0ecc0 @name="Unit4">,#<LV:0x007ffc4cc0e748 @name="Unit5">,#<LV:0x007ffc4cc0df50 @name="Unit6">,#<LV:0x007ffc4cc0d9d8 @name="Unit7">,#<LV:0x007ffc4cc0d460 @name="Unit8">,#<LV:0x007ffc4cc0cee8 @name="Unit9">,#<LV:0x007ffc4cc0c970 @name="Unit10">]

Variable Constraints

Add variable constraints to a variable using the <,>,<=,>=,== operators. Be careful to use '==' and not '=' when expressing equality. Constraints on a variable can only use numeric literals and not other variables. Inter-variable constraints should be expressed as problem constrants. (Explained below.)

X_i < 5X_i.bounds=>"X <= 5"3 <= X_i < 15X_i.bounds=>"3 <= X <= 15"Y_f == 10Y_f.bounds=>"y = 10"

Problem constraints

Constraints are added to a problem using the :[] syntax.

problem=Rulp::Max(10 * X_i + 6 * Y_i + 4 * Z_i)problem[X_i + Y_i + Z_i <= 100]problem[10 * X_i + 4 * Y_i + 5 * Z_i <= 600]
...
problem.solve

You can add multiple constraints at once by comma separating them as seen in the earlier examples:

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]

Solving or saving 'lp' files

There are multiple ways to solve the problem or output the problem to an 'lp' file. Currently the Rulp library supports calling installed executables for Scip, Cbc and Glpk. For each of these solvers it requires the solver command line binaries to be installed such that the command which [exec_name] returns a path. (I.e they must be on your PATH. See Installation).

Given a problem there are multiple ways to initiate a solver.

@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]

Default solver:

@problem.solve# this will use the solver specified in the environment variable 'SOLVER' by default.# This can be 'scip', 'cbc', 'glpk' or 'gurobi'.# You can also try and run scip or cbc in parallel using by passing an options argument containing { parallel: true }# If no variable is given it uses 'scip' as a default.

If you had a linear equation in a file named 'problem.rb' from the command line you could specify an alternate solver by executing:

SOLVER=cbcrubyproblem.rb

Explicit solver:

@problem.scip# Or@problem.cbc# Or@problem.glpk# Or@problem.cbc(parallel: true)# Or@problem.scip(parallel: true)

Or

Rulp::Scip(@problem)Rulp::Glpk(@problem)Rulp::Cbc(@problem)Rulp::scip(@problem,parallel: true)Rulp::cbc(@problem,parallel: true)

For debugging purposes you may wish to see the input and output files generated and consumed by Rulp. To do this you can pass open_definition and open_solution as options with the following extended syntax:

Rulp::Cbc(@problem,open_definition: true,open_solution: true)

The optional booleans will optionally call the 'open' utility to open the problem definition or the solution. (This utility is installed by default on a mac and will not work if the utility is not on your PATH)

Additional Options

Mip tolerance.

You can provide a MIP tolerance to any of the solvers to allow it to return a sub-optimal result within this tolerance from the dual bound.

# Various ways to invoke this option@problem.cbc(gap: 0.05)Rulp::Cbc(@problem,gap: 0.05)@problem.solve(gap: 0.05)
Node Limit.

You can limit the number of nodes the solver is able to explore before it must return it's current most optimal solution. If it has not discovered a solution by the time it hits the node limit it will return nil. Glpk does not accept a node limit option.

# Various ways to invoke this option@problem.cbc(node_limit: 10_000)Rulp::Cbc(@problem,node_limit: 10_000)@problem.solve(node_limit: 10_000)

For Scip you can also specify these limits in a settings file called scip.set in the current directory. Options passed to Rulp will overwrite these values.

# E.g inside ./scip.set
limits/gap = 0.15 # optimality within 0.1%.
limits/nodes = 200000 # No more than 200000 nodes explored

Saving LP files.

You may not wish to use one of the RULP compatible solvers but instead another solver that is able to read .lp files. (E.g CPLEX) but still want to use Rulp to generate your LP file. In this case you should use Rulp to output your lp problem description to a file of your choice. To do this simply use the following call

@problem.save("/Users/johndoe/Desktop/myproblem.lp")

OR

@problem.output("/Users/johndoe/Desktop/myproblem.lp")

You should also be able to call

@problem.save

Without parameters to be prompted for a save location.

Examples.

Take a look at some basic examples in the ./examples directory in the source code.

Rulp Executable

Rulp comes bundled with a 'rulp' executable which by default loads the rulp environment and either the Pry or Irb REPL. (Preference for Pry). Once installed you should be able to simply execute 'rulp' to launch this rulp enabled REPL. You can then play with and attempt LP and MIP problems straight from the command line.

[1]pry(main)> 13 <= X_i <= 45# Declare integer variable=>X(i)[undefined][2]pry(main)> -15 <= Y_f <= 15# Declare float variable=>Y(f)[undefined][3]pry(main)> @problem=Rulp::Min(X_i - Y_f)# Create min problem[info]Creatingminimizationproblem=>Minimizeobj: X -YSubjectto0X=0Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[4]@problem[X_i - 2 * Y_f < 40]#Adding a problem constraint=>Minimizeobj: X -YSubjecttoc0: X -2Y <= 40Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[5]pry(main)> @problem.solve# Solve
...
[info]Solvertook0.12337[info]Parsingresult=> -2.0#(Minimal result)[6]pry(main)> Y_f# See value calculated for Y now that solver has run=>Y(f)[15.0][8]pry(main)> X_i=>X(i)[13.0]# The result of the objective function (-2.0) was returned by the call to .solve# Now the solver has run and calculated values for our variables we can also test the# objective function (or any function) by calling evaluate on it.# E.g[9](X_i - Y_f).evaluate=> -2.0[10]pry(main)> (2 * X_i + 15 * Y_f).evaluate=>251.0

A larger example

Here is a basic example of how Rulp can help you model problems with a large number of variables. Suppose we are playing an IOS app which contains in-app purchases. Each of these in-app purchases costs a variable amount and gives us a certain number of in-game points. Suppose our mother gave us $55 to spend. We want to find the maximal number of in-game points we can buy using this money. Here is a simple example of how we could use Rulp to formulate this problem.

We decide to model each of these possible purchases as a binary variable (as we either purchase them or we don't. We can't partially purchase one.)

# Generate the data randomly for this example.costs,points=[*0..1000].mapdo |i|
[Purchase_b(i) * Random.rand(1.0..3.0),Purchase_b(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# the constraint that costs must be less than $55Rulp::Max(points)[costs < 55].solve=>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?)=>[Purchase27(b)[true],Purchase86(b)[true],Purchase120(b)[true],Purchase141(b)[true],Purchase154(b)[true],
...

MIP Minimization Example

Here is another example of using array style variables. Let's use the example of making a cup of coffee. Personally, I like my coffee with 2 creams and 1 sugar. There are many types of products that can add cream, or sugar to taste. Each has a different associated cost. As a price sensitive consumer what combination of products will allow me to flavor my coffee as desired with the lowest total cost?

desired=[{ingredient: :cream,amount: 2},{ingredient: :sugar,amount: 1},]products=[{name: 'Milk',cost: 0.50,supplies: {cream: 1}},{name: 'Baileys_Irish_Cream',cost: 2.99,supplies: {cream: 1,sugar: 1}},{name: 'Non_Dairy_Creamer',cost: 0.10,supplies: {cream: 1}},{name: 'Sugar',cost: 0.10,supplies: {sugar: 1}},]variables=products.mapdo |product|
Products_i(product[:name])end# => [ ProductMilk_i, ProductBaileys_Irish_Cream_i, ProductNon_Dairy_Creamer_i, ProductSugar_i ]given[variables.map{ |i| i >= 0}]# given [# ProductMilk_i >= 0,# ProductBaileys_Irish_Cream_i >= 0,# ProductNon_Dairy_Creamer_i >= 0,# ProductSugar_i >= 0,# ]constraints=desired.mapdo |minimum|
ingredient=minimum[:ingredient]desired_amount=minimum[:amount]products.map.with_indexdo |p,i|
coefficient=p[:supplies][ingredient] || 0coefficient * variables[i]end.inject(:+) >= desired_amountend# => [# 1 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 1 * ProductNon_Dairy_Creamer_i + 0 * ProductSugar_i >= 2# 0 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 0 * ProductNon_Dairy_Creamer_i + 1 * ProductSugar_i >= 1# ]objective=products.map.with_index{ |p,i| p[:cost] * variables[i]}.inject(:+)# => 0.50 * ProductMilk_i + 2.99 * ProductBaileys_Irish_Cream_i + 0.10 * ProductNon_Dairy_Creamer_i + 0.10 * ProductSugar_iproblem=Rulp::Min(objective)problem[constraints]Rulp::Glpk(problem)# DEBUG -- : Objective: 0.30000000000000004# Products = 0.0# Products = 0.0# Products = 2.0# Products = 1.0variables.map(&:value)# => [ 0, 0, 2, 1 ]

About

Ruby Linear Programming

Resources

Stars

44 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

RulpGem VersionDownloadsInline docsCodeship Status for wouterken/rulp

Table of Contentsgenerated with DocToc

Rulp

Rulp is an easy to use Ruby DSL for generating linear programming and mixed integer programming problem descriptions in the LP file format.

The [.lp] file format can be read and executed by most LP solvers including coin-Cbc, Scip, GLPK, CPLEX and Gurobi.

Rulp will execute and parse the results generated by Cbc, Scip, fscip(ug) and GLPK.

Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP library "Pulp".

Sample Code

# maximize# z = 10 * x + 6 * y + 4 * z## subject to# p: x + y + z <= 100# q: 10 * x + 4 * y + 5 * z <= 600# r: 2 * x + 2 * y + 6 * z <= 300## where all variables are non-negative integers# x >= 0, y >= 0, z >= 0#given[X_i >= 0,Y_i >= 0,Z_i >= 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].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##

Installation

To use Rulp as a complete solver toolkit you will need one of either Glpsol(GLPK), Scip, Coin-Cbc or HiGHS installed. Here are some sample instructions of how you install each of these solvers. Be sure to read the license terms for each of these solvers before using them.

Scip:

Go to install directory

cd /usr/local

Download Scip source (Be sure to visit the SCIP website and check the license terms first.)

curl -O http://scip.zib.de/download/release/scipoptsuite-3.1.1.tgz

Extract Scip source

gunzip -c scipoptsuite-3.1.1.tgz | tar xvf -

Scip relies on a number of libraries including ZLIB, GMP and READLINE. Please read the INSTALL directory in the application directory for help on getting these installed.

Build Scip

cd scipoptsuite-3.1.1
make

Add scip bin directory to your path E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.zshrc

You should now have scip installed.

UG (Scip parallel)

To run scip using multiple cores you will need to use the UG library for scip. This is bundled in the scipoptsuite directory. To install this simply run

make ug

From the base directory and then add the fscip binary to your path. E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.zshrc

Coin Cbc:

Navigate to install location

cd /usr/local

Follow CBC installation instructions

svn co https://projects.coin-or.org/svn/Cbc/stable/2.8 coin-Cbc
cd coin-Cbc
./configure -C #Optionally add --enable-cbc-parallel here to enable multiple threads for cbc
make
make install

Add Coin-cbc to path

E.g

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.zshrc

You should now have coin-Cbc installed.

GLPK:

Download the latest version of GLPK from http://www.gnu.org/software/glpk/#downloading

From the download directory

tar -xzf glpk-4.55.tar.gz
cd glpk-4.55
./configure --prefix=/usr/local
make
sudo make install

At this point, you should have GLPK installed. Verify it:

which glpsol
=> /usr/local/bin/glpsol

HiGHS:

Install HiGHS following the installation instructions at https://ergo-code.github.io/HiGHS/dev/interfaces/cpp/

Usage

Variables

# Rulp variables are initialized as soon as they are needed so there is no# 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 variableAn_Integer_i=>#<IV:0x007ffc4b651b80 @name="An_Integer">Generalf=>#<LV:0x007ffc4b651b80 @name="General">Bool_Val_b=>#<BV:0x007ffc4b67b6b0 @name="Bool_Val"># In some cases it is implausible to generate a unique name for every possible variable# as an LP problem description may contain many hundreds of variables.# To handle these scenarios variable definitions can# 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)#<IV:0x007ffc4b3ea518 @name="Item4_5">Item_i("store_3","table_2")#<IV:0x007ffc4b3a3cd0 @name="Itemstore_3_table_2">[*0..10].map(&Unit_f)=>[#<LV:0x007ffc4cc25768 @name="Unit0">,#<LV:0x007ffc4cc24cf0 @name="Unit1">,#<LV:0x007ffc4cc0fc88 @name="Unit2">,#<LV:0x007ffc4cc0f260 @name="Unit3">,#<LV:0x007ffc4cc0ecc0 @name="Unit4">,#<LV:0x007ffc4cc0e748 @name="Unit5">,#<LV:0x007ffc4cc0df50 @name="Unit6">,#<LV:0x007ffc4cc0d9d8 @name="Unit7">,#<LV:0x007ffc4cc0d460 @name="Unit8">,#<LV:0x007ffc4cc0cee8 @name="Unit9">,#<LV:0x007ffc4cc0c970 @name="Unit10">]

Variable Constraints

Add variable constraints to a variable using the <,>,<=,>=,== operators. Be careful to use '==' and not '=' when expressing equality. Constraints on a variable can only use numeric literals and not other variables. Inter-variable constraints should be expressed as problem constrants. (Explained below.)

X_i < 5X_i.bounds=>"X <= 5"3 <= X_i < 15X_i.bounds=>"3 <= X <= 15"Y_f == 10Y_f.bounds=>"y = 10"

Problem constraints

Constraints are added to a problem using the :[] syntax.

problem=Rulp::Max(10 * X_i + 6 * Y_i + 4 * Z_i)problem[X_i + Y_i + Z_i <= 100]problem[10 * X_i + 4 * Y_i + 5 * Z_i <= 600]
...
problem.solve

You can add multiple constraints at once by comma separating them as seen in the earlier examples:

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]

Solving or saving 'lp' files

There are multiple ways to solve the problem or output the problem to an 'lp' file. Currently the Rulp library supports calling installed executables for Scip, Cbc and Glpk. For each of these solvers it requires the solver command line binaries to be installed such that the command which [exec_name] returns a path. (I.e they must be on your PATH. See Installation).

Given a problem there are multiple ways to initiate a solver.

@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]

Default solver:

@problem.solve# this will use the solver specified in the environment variable 'SOLVER' by default.# This can be 'scip', 'cbc', 'glpk' or 'gurobi'.# You can also try and run scip or cbc in parallel using by passing an options argument containing { parallel: true }# If no variable is given it uses 'scip' as a default.

If you had a linear equation in a file named 'problem.rb' from the command line you could specify an alternate solver by executing:

SOLVER=cbcrubyproblem.rb

Explicit solver:

@problem.scip# Or@problem.cbc# Or@problem.glpk# Or@problem.cbc(parallel: true)# Or@problem.scip(parallel: true)

Or

Rulp::Scip(@problem)Rulp::Glpk(@problem)Rulp::Cbc(@problem)Rulp::scip(@problem,parallel: true)Rulp::cbc(@problem,parallel: true)

For debugging purposes you may wish to see the input and output files generated and consumed by Rulp. To do this you can pass open_definition and open_solution as options with the following extended syntax:

Rulp::Cbc(@problem,open_definition: true,open_solution: true)

The optional booleans will optionally call the 'open' utility to open the problem definition or the solution. (This utility is installed by default on a mac and will not work if the utility is not on your PATH)

Additional Options

Mip tolerance.

You can provide a MIP tolerance to any of the solvers to allow it to return a sub-optimal result within this tolerance from the dual bound.

# Various ways to invoke this option@problem.cbc(gap: 0.05)Rulp::Cbc(@problem,gap: 0.05)@problem.solve(gap: 0.05)
Node Limit.

You can limit the number of nodes the solver is able to explore before it must return it's current most optimal solution. If it has not discovered a solution by the time it hits the node limit it will return nil. Glpk does not accept a node limit option.

# Various ways to invoke this option@problem.cbc(node_limit: 10_000)Rulp::Cbc(@problem,node_limit: 10_000)@problem.solve(node_limit: 10_000)

For Scip you can also specify these limits in a settings file called scip.set in the current directory. Options passed to Rulp will overwrite these values.

# E.g inside ./scip.set
limits/gap = 0.15 # optimality within 0.1%.
limits/nodes = 200000 # No more than 200000 nodes explored

Saving LP files.

You may not wish to use one of the RULP compatible solvers but instead another solver that is able to read .lp files. (E.g CPLEX) but still want to use Rulp to generate your LP file. In this case you should use Rulp to output your lp problem description to a file of your choice. To do this simply use the following call

@problem.save("/Users/johndoe/Desktop/myproblem.lp")

OR

@problem.output("/Users/johndoe/Desktop/myproblem.lp")

You should also be able to call

@problem.save

Without parameters to be prompted for a save location.

Examples.

Take a look at some basic examples in the ./examples directory in the source code.

Rulp Executable

Rulp comes bundled with a 'rulp' executable which by default loads the rulp environment and either the Pry or Irb REPL. (Preference for Pry). Once installed you should be able to simply execute 'rulp' to launch this rulp enabled REPL. You can then play with and attempt LP and MIP problems straight from the command line.

[1]pry(main)> 13 <= X_i <= 45# Declare integer variable=>X(i)[undefined][2]pry(main)> -15 <= Y_f <= 15# Declare float variable=>Y(f)[undefined][3]pry(main)> @problem=Rulp::Min(X_i - Y_f)# Create min problem[info]Creatingminimizationproblem=>Minimizeobj: X -YSubjectto0X=0Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[4]@problem[X_i - 2 * Y_f < 40]#Adding a problem constraint=>Minimizeobj: X -YSubjecttoc0: X -2Y <= 40Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[5]pry(main)> @problem.solve# Solve
...
[info]Solvertook0.12337[info]Parsingresult=> -2.0#(Minimal result)[6]pry(main)> Y_f# See value calculated for Y now that solver has run=>Y(f)[15.0][8]pry(main)> X_i=>X(i)[13.0]# The result of the objective function (-2.0) was returned by the call to .solve# Now the solver has run and calculated values for our variables we can also test the# objective function (or any function) by calling evaluate on it.# E.g[9](X_i - Y_f).evaluate=> -2.0[10]pry(main)> (2 * X_i + 15 * Y_f).evaluate=>251.0

A larger example

Here is a basic example of how Rulp can help you model problems with a large number of variables. Suppose we are playing an IOS app which contains in-app purchases. Each of these in-app purchases costs a variable amount and gives us a certain number of in-game points. Suppose our mother gave us $55 to spend. We want to find the maximal number of in-game points we can buy using this money. Here is a simple example of how we could use Rulp to formulate this problem.

We decide to model each of these possible purchases as a binary variable (as we either purchase them or we don't. We can't partially purchase one.)

# Generate the data randomly for this example.costs,points=[*0..1000].mapdo |i|
[Purchase_b(i) * Random.rand(1.0..3.0),Purchase_b(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# the constraint that costs must be less than $55Rulp::Max(points)[costs < 55].solve=>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?)=>[Purchase27(b)[true],Purchase86(b)[true],Purchase120(b)[true],Purchase141(b)[true],Purchase154(b)[true],
...

MIP Minimization Example

Here is another example of using array style variables. Let's use the example of making a cup of coffee. Personally, I like my coffee with 2 creams and 1 sugar. There are many types of products that can add cream, or sugar to taste. Each has a different associated cost. As a price sensitive consumer what combination of products will allow me to flavor my coffee as desired with the lowest total cost?

desired=[{ingredient: :cream,amount: 2},{ingredient: :sugar,amount: 1},]products=[{name: 'Milk',cost: 0.50,supplies: {cream: 1}},{name: 'Baileys_Irish_Cream',cost: 2.99,supplies: {cream: 1,sugar: 1}},{name: 'Non_Dairy_Creamer',cost: 0.10,supplies: {cream: 1}},{name: 'Sugar',cost: 0.10,supplies: {sugar: 1}},]variables=products.mapdo |product|
Products_i(product[:name])end# => [ ProductMilk_i, ProductBaileys_Irish_Cream_i, ProductNon_Dairy_Creamer_i, ProductSugar_i ]given[variables.map{ |i| i >= 0}]# given [# ProductMilk_i >= 0,# ProductBaileys_Irish_Cream_i >= 0,# ProductNon_Dairy_Creamer_i >= 0,# ProductSugar_i >= 0,# ]constraints=desired.mapdo |minimum|
ingredient=minimum[:ingredient]desired_amount=minimum[:amount]products.map.with_indexdo |p,i|
coefficient=p[:supplies][ingredient] || 0coefficient * variables[i]end.inject(:+) >= desired_amountend# => [# 1 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 1 * ProductNon_Dairy_Creamer_i + 0 * ProductSugar_i >= 2# 0 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 0 * ProductNon_Dairy_Creamer_i + 1 * ProductSugar_i >= 1# ]objective=products.map.with_index{ |p,i| p[:cost] * variables[i]}.inject(:+)# => 0.50 * ProductMilk_i + 2.99 * ProductBaileys_Irish_Cream_i + 0.10 * ProductNon_Dairy_Creamer_i + 0.10 * ProductSugar_iproblem=Rulp::Min(objective)problem[constraints]Rulp::Glpk(problem)# DEBUG -- : Objective: 0.30000000000000004# Products = 0.0# Products = 0.0# Products = 2.0# Products = 1.0variables.map(&:value)# => [ 0, 0, 2, 1 ]

About

Ruby Linear Programming

Resources

Stars

44 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

RulpGem VersionDownloadsInline docsCodeship Status for wouterken/rulp

Table of Contentsgenerated with DocToc

Rulp

Rulp is an easy to use Ruby DSL for generating linear programming and mixed integer programming problem descriptions in the LP file format.

The [.lp] file format can be read and executed by most LP solvers including coin-Cbc, Scip, GLPK, CPLEX and Gurobi.

Rulp will execute and parse the results generated by Cbc, Scip, fscip(ug) and GLPK.

Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP library "Pulp".

Sample Code

# maximize# z = 10 * x + 6 * y + 4 * z## subject to# p: x + y + z <= 100# q: 10 * x + 4 * y + 5 * z <= 600# r: 2 * x + 2 * y + 6 * z <= 300## where all variables are non-negative integers# x >= 0, y >= 0, z >= 0#given[X_i >= 0,Y_i >= 0,Z_i >= 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].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##

Installation

To use Rulp as a complete solver toolkit you will need one of either Glpsol(GLPK), Scip, Coin-Cbc or HiGHS installed. Here are some sample instructions of how you install each of these solvers. Be sure to read the license terms for each of these solvers before using them.

Scip:

Go to install directory

cd /usr/local

Download Scip source (Be sure to visit the SCIP website and check the license terms first.)

curl -O http://scip.zib.de/download/release/scipoptsuite-3.1.1.tgz

Extract Scip source

gunzip -c scipoptsuite-3.1.1.tgz | tar xvf -

Scip relies on a number of libraries including ZLIB, GMP and READLINE. Please read the INSTALL directory in the application directory for help on getting these installed.

Build Scip

cd scipoptsuite-3.1.1
make

Add scip bin directory to your path E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.zshrc

You should now have scip installed.

UG (Scip parallel)

To run scip using multiple cores you will need to use the UG library for scip. This is bundled in the scipoptsuite directory. To install this simply run

make ug

From the base directory and then add the fscip binary to your path. E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.zshrc

Coin Cbc:

Navigate to install location

cd /usr/local

Follow CBC installation instructions

svn co https://projects.coin-or.org/svn/Cbc/stable/2.8 coin-Cbc
cd coin-Cbc
./configure -C #Optionally add --enable-cbc-parallel here to enable multiple threads for cbc
make
make install

Add Coin-cbc to path

E.g

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.zshrc

You should now have coin-Cbc installed.

GLPK:

Download the latest version of GLPK from http://www.gnu.org/software/glpk/#downloading

From the download directory

tar -xzf glpk-4.55.tar.gz
cd glpk-4.55
./configure --prefix=/usr/local
make
sudo make install

At this point, you should have GLPK installed. Verify it:

which glpsol
=> /usr/local/bin/glpsol

HiGHS:

Install HiGHS following the installation instructions at https://ergo-code.github.io/HiGHS/dev/interfaces/cpp/

Usage

Variables

# Rulp variables are initialized as soon as they are needed so there is no# 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 variableAn_Integer_i=>#<IV:0x007ffc4b651b80 @name="An_Integer">Generalf=>#<LV:0x007ffc4b651b80 @name="General">Bool_Val_b=>#<BV:0x007ffc4b67b6b0 @name="Bool_Val"># In some cases it is implausible to generate a unique name for every possible variable# as an LP problem description may contain many hundreds of variables.# To handle these scenarios variable definitions can# 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)#<IV:0x007ffc4b3ea518 @name="Item4_5">Item_i("store_3","table_2")#<IV:0x007ffc4b3a3cd0 @name="Itemstore_3_table_2">[*0..10].map(&Unit_f)=>[#<LV:0x007ffc4cc25768 @name="Unit0">,#<LV:0x007ffc4cc24cf0 @name="Unit1">,#<LV:0x007ffc4cc0fc88 @name="Unit2">,#<LV:0x007ffc4cc0f260 @name="Unit3">,#<LV:0x007ffc4cc0ecc0 @name="Unit4">,#<LV:0x007ffc4cc0e748 @name="Unit5">,#<LV:0x007ffc4cc0df50 @name="Unit6">,#<LV:0x007ffc4cc0d9d8 @name="Unit7">,#<LV:0x007ffc4cc0d460 @name="Unit8">,#<LV:0x007ffc4cc0cee8 @name="Unit9">,#<LV:0x007ffc4cc0c970 @name="Unit10">]

Variable Constraints

Add variable constraints to a variable using the <,>,<=,>=,== operators. Be careful to use '==' and not '=' when expressing equality. Constraints on a variable can only use numeric literals and not other variables. Inter-variable constraints should be expressed as problem constrants. (Explained below.)

X_i < 5X_i.bounds=>"X <= 5"3 <= X_i < 15X_i.bounds=>"3 <= X <= 15"Y_f == 10Y_f.bounds=>"y = 10"

Problem constraints

Constraints are added to a problem using the :[] syntax.

problem=Rulp::Max(10 * X_i + 6 * Y_i + 4 * Z_i)problem[X_i + Y_i + Z_i <= 100]problem[10 * X_i + 4 * Y_i + 5 * Z_i <= 600]
...
problem.solve

You can add multiple constraints at once by comma separating them as seen in the earlier examples:

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]

Solving or saving 'lp' files

There are multiple ways to solve the problem or output the problem to an 'lp' file. Currently the Rulp library supports calling installed executables for Scip, Cbc and Glpk. For each of these solvers it requires the solver command line binaries to be installed such that the command which [exec_name] returns a path. (I.e they must be on your PATH. See Installation).

Given a problem there are multiple ways to initiate a solver.

@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]

Default solver:

@problem.solve# this will use the solver specified in the environment variable 'SOLVER' by default.# This can be 'scip', 'cbc', 'glpk' or 'gurobi'.# You can also try and run scip or cbc in parallel using by passing an options argument containing { parallel: true }# If no variable is given it uses 'scip' as a default.

If you had a linear equation in a file named 'problem.rb' from the command line you could specify an alternate solver by executing:

SOLVER=cbcrubyproblem.rb

Explicit solver:

@problem.scip# Or@problem.cbc# Or@problem.glpk# Or@problem.cbc(parallel: true)# Or@problem.scip(parallel: true)

Or

Rulp::Scip(@problem)Rulp::Glpk(@problem)Rulp::Cbc(@problem)Rulp::scip(@problem,parallel: true)Rulp::cbc(@problem,parallel: true)

For debugging purposes you may wish to see the input and output files generated and consumed by Rulp. To do this you can pass open_definition and open_solution as options with the following extended syntax:

Rulp::Cbc(@problem,open_definition: true,open_solution: true)

The optional booleans will optionally call the 'open' utility to open the problem definition or the solution. (This utility is installed by default on a mac and will not work if the utility is not on your PATH)

Additional Options

Mip tolerance.

You can provide a MIP tolerance to any of the solvers to allow it to return a sub-optimal result within this tolerance from the dual bound.

# Various ways to invoke this option@problem.cbc(gap: 0.05)Rulp::Cbc(@problem,gap: 0.05)@problem.solve(gap: 0.05)
Node Limit.

You can limit the number of nodes the solver is able to explore before it must return it's current most optimal solution. If it has not discovered a solution by the time it hits the node limit it will return nil. Glpk does not accept a node limit option.

# Various ways to invoke this option@problem.cbc(node_limit: 10_000)Rulp::Cbc(@problem,node_limit: 10_000)@problem.solve(node_limit: 10_000)

For Scip you can also specify these limits in a settings file called scip.set in the current directory. Options passed to Rulp will overwrite these values.

# E.g inside ./scip.set
limits/gap = 0.15 # optimality within 0.1%.
limits/nodes = 200000 # No more than 200000 nodes explored

Saving LP files.

You may not wish to use one of the RULP compatible solvers but instead another solver that is able to read .lp files. (E.g CPLEX) but still want to use Rulp to generate your LP file. In this case you should use Rulp to output your lp problem description to a file of your choice. To do this simply use the following call

@problem.save("/Users/johndoe/Desktop/myproblem.lp")

OR

@problem.output("/Users/johndoe/Desktop/myproblem.lp")

You should also be able to call

@problem.save

Without parameters to be prompted for a save location.

Examples.

Take a look at some basic examples in the ./examples directory in the source code.

Rulp Executable

Rulp comes bundled with a 'rulp' executable which by default loads the rulp environment and either the Pry or Irb REPL. (Preference for Pry). Once installed you should be able to simply execute 'rulp' to launch this rulp enabled REPL. You can then play with and attempt LP and MIP problems straight from the command line.

[1]pry(main)> 13 <= X_i <= 45# Declare integer variable=>X(i)[undefined][2]pry(main)> -15 <= Y_f <= 15# Declare float variable=>Y(f)[undefined][3]pry(main)> @problem=Rulp::Min(X_i - Y_f)# Create min problem[info]Creatingminimizationproblem=>Minimizeobj: X -YSubjectto0X=0Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[4]@problem[X_i - 2 * Y_f < 40]#Adding a problem constraint=>Minimizeobj: X -YSubjecttoc0: X -2Y <= 40Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[5]pry(main)> @problem.solve# Solve
...
[info]Solvertook0.12337[info]Parsingresult=> -2.0#(Minimal result)[6]pry(main)> Y_f# See value calculated for Y now that solver has run=>Y(f)[15.0][8]pry(main)> X_i=>X(i)[13.0]# The result of the objective function (-2.0) was returned by the call to .solve# Now the solver has run and calculated values for our variables we can also test the# objective function (or any function) by calling evaluate on it.# E.g[9](X_i - Y_f).evaluate=> -2.0[10]pry(main)> (2 * X_i + 15 * Y_f).evaluate=>251.0

A larger example

Here is a basic example of how Rulp can help you model problems with a large number of variables. Suppose we are playing an IOS app which contains in-app purchases. Each of these in-app purchases costs a variable amount and gives us a certain number of in-game points. Suppose our mother gave us $55 to spend. We want to find the maximal number of in-game points we can buy using this money. Here is a simple example of how we could use Rulp to formulate this problem.

We decide to model each of these possible purchases as a binary variable (as we either purchase them or we don't. We can't partially purchase one.)

# Generate the data randomly for this example.costs,points=[*0..1000].mapdo |i|
[Purchase_b(i) * Random.rand(1.0..3.0),Purchase_b(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# the constraint that costs must be less than $55Rulp::Max(points)[costs < 55].solve=>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?)=>[Purchase27(b)[true],Purchase86(b)[true],Purchase120(b)[true],Purchase141(b)[true],Purchase154(b)[true],
...

MIP Minimization Example

Here is another example of using array style variables. Let's use the example of making a cup of coffee. Personally, I like my coffee with 2 creams and 1 sugar. There are many types of products that can add cream, or sugar to taste. Each has a different associated cost. As a price sensitive consumer what combination of products will allow me to flavor my coffee as desired with the lowest total cost?

desired=[{ingredient: :cream,amount: 2},{ingredient: :sugar,amount: 1},]products=[{name: 'Milk',cost: 0.50,supplies: {cream: 1}},{name: 'Baileys_Irish_Cream',cost: 2.99,supplies: {cream: 1,sugar: 1}},{name: 'Non_Dairy_Creamer',cost: 0.10,supplies: {cream: 1}},{name: 'Sugar',cost: 0.10,supplies: {sugar: 1}},]variables=products.mapdo |product|
Products_i(product[:name])end# => [ ProductMilk_i, ProductBaileys_Irish_Cream_i, ProductNon_Dairy_Creamer_i, ProductSugar_i ]given[variables.map{ |i| i >= 0}]# given [# ProductMilk_i >= 0,# ProductBaileys_Irish_Cream_i >= 0,# ProductNon_Dairy_Creamer_i >= 0,# ProductSugar_i >= 0,# ]constraints=desired.mapdo |minimum|
ingredient=minimum[:ingredient]desired_amount=minimum[:amount]products.map.with_indexdo |p,i|
coefficient=p[:supplies][ingredient] || 0coefficient * variables[i]end.inject(:+) >= desired_amountend# => [# 1 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 1 * ProductNon_Dairy_Creamer_i + 0 * ProductSugar_i >= 2# 0 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 0 * ProductNon_Dairy_Creamer_i + 1 * ProductSugar_i >= 1# ]objective=products.map.with_index{ |p,i| p[:cost] * variables[i]}.inject(:+)# => 0.50 * ProductMilk_i + 2.99 * ProductBaileys_Irish_Cream_i + 0.10 * ProductNon_Dairy_Creamer_i + 0.10 * ProductSugar_iproblem=Rulp::Min(objective)problem[constraints]Rulp::Glpk(problem)# DEBUG -- : Objective: 0.30000000000000004# Products = 0.0# Products = 0.0# Products = 2.0# Products = 1.0variables.map(&:value)# => [ 0, 0, 2, 1 ]

About

Ruby Linear Programming

Resources

Stars

44 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

RulpGem VersionDownloadsInline docsCodeship Status for wouterken/rulp

Table of Contentsgenerated with DocToc

Rulp

Rulp is an easy to use Ruby DSL for generating linear programming and mixed integer programming problem descriptions in the LP file format.

The [.lp] file format can be read and executed by most LP solvers including coin-Cbc, Scip, GLPK, CPLEX and Gurobi.

Rulp will execute and parse the results generated by Cbc, Scip, fscip(ug) and GLPK.

Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP library "Pulp".

Sample Code

# maximize# z = 10 * x + 6 * y + 4 * z## subject to# p: x + y + z <= 100# q: 10 * x + 4 * y + 5 * z <= 600# r: 2 * x + 2 * y + 6 * z <= 300## where all variables are non-negative integers# x >= 0, y >= 0, z >= 0#given[X_i >= 0,Y_i >= 0,Z_i >= 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].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##

Installation

To use Rulp as a complete solver toolkit you will need one of either Glpsol(GLPK), Scip, Coin-Cbc or HiGHS installed. Here are some sample instructions of how you install each of these solvers. Be sure to read the license terms for each of these solvers before using them.

Scip:

Go to install directory

cd /usr/local

Download Scip source (Be sure to visit the SCIP website and check the license terms first.)

curl -O http://scip.zib.de/download/release/scipoptsuite-3.1.1.tgz

Extract Scip source

gunzip -c scipoptsuite-3.1.1.tgz | tar xvf -

Scip relies on a number of libraries including ZLIB, GMP and READLINE. Please read the INSTALL directory in the application directory for help on getting these installed.

Build Scip

cd scipoptsuite-3.1.1
make

Add scip bin directory to your path E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.zshrc

You should now have scip installed.

UG (Scip parallel)

To run scip using multiple cores you will need to use the UG library for scip. This is bundled in the scipoptsuite directory. To install this simply run

make ug

From the base directory and then add the fscip binary to your path. E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.zshrc

Coin Cbc:

Navigate to install location

cd /usr/local

Follow CBC installation instructions

svn co https://projects.coin-or.org/svn/Cbc/stable/2.8 coin-Cbc
cd coin-Cbc
./configure -C #Optionally add --enable-cbc-parallel here to enable multiple threads for cbc
make
make install

Add Coin-cbc to path

E.g

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.zshrc

You should now have coin-Cbc installed.

GLPK:

Download the latest version of GLPK from http://www.gnu.org/software/glpk/#downloading

From the download directory

tar -xzf glpk-4.55.tar.gz
cd glpk-4.55
./configure --prefix=/usr/local
make
sudo make install

At this point, you should have GLPK installed. Verify it:

which glpsol
=> /usr/local/bin/glpsol

HiGHS:

Install HiGHS following the installation instructions at https://ergo-code.github.io/HiGHS/dev/interfaces/cpp/

Usage

Variables

# Rulp variables are initialized as soon as they are needed so there is no# 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 variableAn_Integer_i=>#<IV:0x007ffc4b651b80 @name="An_Integer">Generalf=>#<LV:0x007ffc4b651b80 @name="General">Bool_Val_b=>#<BV:0x007ffc4b67b6b0 @name="Bool_Val"># In some cases it is implausible to generate a unique name for every possible variable# as an LP problem description may contain many hundreds of variables.# To handle these scenarios variable definitions can# 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)#<IV:0x007ffc4b3ea518 @name="Item4_5">Item_i("store_3","table_2")#<IV:0x007ffc4b3a3cd0 @name="Itemstore_3_table_2">[*0..10].map(&Unit_f)=>[#<LV:0x007ffc4cc25768 @name="Unit0">,#<LV:0x007ffc4cc24cf0 @name="Unit1">,#<LV:0x007ffc4cc0fc88 @name="Unit2">,#<LV:0x007ffc4cc0f260 @name="Unit3">,#<LV:0x007ffc4cc0ecc0 @name="Unit4">,#<LV:0x007ffc4cc0e748 @name="Unit5">,#<LV:0x007ffc4cc0df50 @name="Unit6">,#<LV:0x007ffc4cc0d9d8 @name="Unit7">,#<LV:0x007ffc4cc0d460 @name="Unit8">,#<LV:0x007ffc4cc0cee8 @name="Unit9">,#<LV:0x007ffc4cc0c970 @name="Unit10">]

Variable Constraints

Add variable constraints to a variable using the <,>,<=,>=,== operators. Be careful to use '==' and not '=' when expressing equality. Constraints on a variable can only use numeric literals and not other variables. Inter-variable constraints should be expressed as problem constrants. (Explained below.)

X_i < 5X_i.bounds=>"X <= 5"3 <= X_i < 15X_i.bounds=>"3 <= X <= 15"Y_f == 10Y_f.bounds=>"y = 10"

Problem constraints

Constraints are added to a problem using the :[] syntax.

problem=Rulp::Max(10 * X_i + 6 * Y_i + 4 * Z_i)problem[X_i + Y_i + Z_i <= 100]problem[10 * X_i + 4 * Y_i + 5 * Z_i <= 600]
...
problem.solve

You can add multiple constraints at once by comma separating them as seen in the earlier examples:

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]

Solving or saving 'lp' files

There are multiple ways to solve the problem or output the problem to an 'lp' file. Currently the Rulp library supports calling installed executables for Scip, Cbc and Glpk. For each of these solvers it requires the solver command line binaries to be installed such that the command which [exec_name] returns a path. (I.e they must be on your PATH. See Installation).

Given a problem there are multiple ways to initiate a solver.

@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]

Default solver:

@problem.solve# this will use the solver specified in the environment variable 'SOLVER' by default.# This can be 'scip', 'cbc', 'glpk' or 'gurobi'.# You can also try and run scip or cbc in parallel using by passing an options argument containing { parallel: true }# If no variable is given it uses 'scip' as a default.

If you had a linear equation in a file named 'problem.rb' from the command line you could specify an alternate solver by executing:

SOLVER=cbcrubyproblem.rb

Explicit solver:

@problem.scip# Or@problem.cbc# Or@problem.glpk# Or@problem.cbc(parallel: true)# Or@problem.scip(parallel: true)

Or

Rulp::Scip(@problem)Rulp::Glpk(@problem)Rulp::Cbc(@problem)Rulp::scip(@problem,parallel: true)Rulp::cbc(@problem,parallel: true)

For debugging purposes you may wish to see the input and output files generated and consumed by Rulp. To do this you can pass open_definition and open_solution as options with the following extended syntax:

Rulp::Cbc(@problem,open_definition: true,open_solution: true)

The optional booleans will optionally call the 'open' utility to open the problem definition or the solution. (This utility is installed by default on a mac and will not work if the utility is not on your PATH)

Additional Options

Mip tolerance.

You can provide a MIP tolerance to any of the solvers to allow it to return a sub-optimal result within this tolerance from the dual bound.

# Various ways to invoke this option@problem.cbc(gap: 0.05)Rulp::Cbc(@problem,gap: 0.05)@problem.solve(gap: 0.05)
Node Limit.

You can limit the number of nodes the solver is able to explore before it must return it's current most optimal solution. If it has not discovered a solution by the time it hits the node limit it will return nil. Glpk does not accept a node limit option.

# Various ways to invoke this option@problem.cbc(node_limit: 10_000)Rulp::Cbc(@problem,node_limit: 10_000)@problem.solve(node_limit: 10_000)

For Scip you can also specify these limits in a settings file called scip.set in the current directory. Options passed to Rulp will overwrite these values.

# E.g inside ./scip.set
limits/gap = 0.15 # optimality within 0.1%.
limits/nodes = 200000 # No more than 200000 nodes explored

Saving LP files.

You may not wish to use one of the RULP compatible solvers but instead another solver that is able to read .lp files. (E.g CPLEX) but still want to use Rulp to generate your LP file. In this case you should use Rulp to output your lp problem description to a file of your choice. To do this simply use the following call

@problem.save("/Users/johndoe/Desktop/myproblem.lp")

OR

@problem.output("/Users/johndoe/Desktop/myproblem.lp")

You should also be able to call

@problem.save

Without parameters to be prompted for a save location.

Examples.

Take a look at some basic examples in the ./examples directory in the source code.

Rulp Executable

Rulp comes bundled with a 'rulp' executable which by default loads the rulp environment and either the Pry or Irb REPL. (Preference for Pry). Once installed you should be able to simply execute 'rulp' to launch this rulp enabled REPL. You can then play with and attempt LP and MIP problems straight from the command line.

[1]pry(main)> 13 <= X_i <= 45# Declare integer variable=>X(i)[undefined][2]pry(main)> -15 <= Y_f <= 15# Declare float variable=>Y(f)[undefined][3]pry(main)> @problem=Rulp::Min(X_i - Y_f)# Create min problem[info]Creatingminimizationproblem=>Minimizeobj: X -YSubjectto0X=0Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[4]@problem[X_i - 2 * Y_f < 40]#Adding a problem constraint=>Minimizeobj: X -YSubjecttoc0: X -2Y <= 40Bounds13 <= X <= 45
-15 <= Y <= 15GeneralXEnd[5]pry(main)> @problem.solve# Solve
...
[info]Solvertook0.12337[info]Parsingresult=> -2.0#(Minimal result)[6]pry(main)> Y_f# See value calculated for Y now that solver has run=>Y(f)[15.0][8]pry(main)> X_i=>X(i)[13.0]# The result of the objective function (-2.0) was returned by the call to .solve# Now the solver has run and calculated values for our variables we can also test the# objective function (or any function) by calling evaluate on it.# E.g[9](X_i - Y_f).evaluate=> -2.0[10]pry(main)> (2 * X_i + 15 * Y_f).evaluate=>251.0

A larger example

Here is a basic example of how Rulp can help you model problems with a large number of variables. Suppose we are playing an IOS app which contains in-app purchases. Each of these in-app purchases costs a variable amount and gives us a certain number of in-game points. Suppose our mother gave us $55 to spend. We want to find the maximal number of in-game points we can buy using this money. Here is a simple example of how we could use Rulp to formulate this problem.

We decide to model each of these possible purchases as a binary variable (as we either purchase them or we don't. We can't partially purchase one.)

# Generate the data randomly for this example.costs,points=[*0..1000].mapdo |i|
[Purchase_b(i) * Random.rand(1.0..3.0),Purchase_b(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# the constraint that costs must be less than $55Rulp::Max(points)[costs < 55].solve=>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?)=>[Purchase27(b)[true],Purchase86(b)[true],Purchase120(b)[true],Purchase141(b)[true],Purchase154(b)[true],
...

MIP Minimization Example

Here is another example of using array style variables. Let's use the example of making a cup of coffee. Personally, I like my coffee with 2 creams and 1 sugar. There are many types of products that can add cream, or sugar to taste. Each has a different associated cost. As a price sensitive consumer what combination of products will allow me to flavor my coffee as desired with the lowest total cost?

desired=[{ingredient: :cream,amount: 2},{ingredient: :sugar,amount: 1},]products=[{name: 'Milk',cost: 0.50,supplies: {cream: 1}},{name: 'Baileys_Irish_Cream',cost: 2.99,supplies: {cream: 1,sugar: 1}},{name: 'Non_Dairy_Creamer',cost: 0.10,supplies: {cream: 1}},{name: 'Sugar',cost: 0.10,supplies: {sugar: 1}},]variables=products.mapdo |product|
Products_i(product[:name])end# => [ ProductMilk_i, ProductBaileys_Irish_Cream_i, ProductNon_Dairy_Creamer_i, ProductSugar_i ]given[variables.map{ |i| i >= 0}]# given [# ProductMilk_i >= 0,# ProductBaileys_Irish_Cream_i >= 0,# ProductNon_Dairy_Creamer_i >= 0,# ProductSugar_i >= 0,# ]constraints=desired.mapdo |minimum|
ingredient=minimum[:ingredient]desired_amount=minimum[:amount]products.map.with_indexdo |p,i|
coefficient=p[:supplies][ingredient] || 0coefficient * variables[i]end.inject(:+) >= desired_amountend# => [# 1 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 1 * ProductNon_Dairy_Creamer_i + 0 * ProductSugar_i >= 2# 0 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 0 * ProductNon_Dairy_Creamer_i + 1 * ProductSugar_i >= 1# ]objective=products.map.with_index{ |p,i| p[:cost] * variables[i]}.inject(:+)# => 0.50 * ProductMilk_i + 2.99 * ProductBaileys_Irish_Cream_i + 0.10 * ProductNon_Dairy_Creamer_i + 0.10 * ProductSugar_iproblem=Rulp::Min(objective)problem[constraints]Rulp::Glpk(problem)# DEBUG -- : Objective: 0.30000000000000004# Products = 0.0# Products = 0.0# Products = 2.0# Products = 1.0variables.map(&:value)# => [ 0, 0, 2, 1 ]

About

Ruby Linear Programming

Resources

Stars

44 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages