Project

ruby-cbc

0.02
Low commit activity in last 3 years
No release in over a year
Wrapper around Cbc Linear Programming Solver
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

~> 2.9.9.2
 Project Readme

Build Status

Ruby-Cbc

This gem is using Cbc, an Integer Linear Programming Library, to provide optimization problems solving to ruby. With Ruby-Cbc, you can model you problem, solve it and find conflicts in case of infeasibility.

It uses the version 2.9.9 of Cbc, and requires the version 2.9.9 of gem cbc-wrapper.

Installation

You need to have cbc installed for ruby-cbc to work.

  • On a mac, you can execute brew install cbc
  • On Debian and Ubuntu, use apt-get install coinor-libcbc-dev
  • On Archlinux, use pacman -S coin-or-cbc
  • On docker you can check the dockerfiles in test/installation-tests/

Add this line to your application's Gemfile:

gem 'ruby-cbc'

And then execute:

$ bundle

Or install it yourself as:

$ gem install ruby-cbc

Heroku

On Heroku, you will need to tweak your installation a bit: you can install the cbc library with the Apt buildpack with an Aptfile of:

coinor-libcbc-dev

You will need to set LD_LIBRARY_PATH so it can find LAPACK and BLAS (see this issue).

heroku config:set LD_LIBRARY_PATH=/app/.apt/usr/lib/x86_64-linux-gnu/lapack:/app/.apt/usr/lib/x86_64-linux-gnu/blas

Usage

# The same Brief Example as found in section 1.3 of
# glpk-4.44/doc/glpk.pdf.
#
# maximize
#   z = 10 * x1 + 6 * x2 + 4 * x3
#
# subject to
#   p:      x1 +     x2 +     x3 <= 100
#   q: 10 * x1 + 4 * x2 + 5 * x3 <= 600
#   r:  2 * x1 + 2 * x2 + 6 * x3 <= 300
#
# where all variables are non-negative
#   x1 >= 0, x2 >= 0, x3 >= 0
#
m = Cbc::Model.new
x1, x2, x3 = m.int_var_array(3, 0..Cbc::INF)

m.maximize(10 * x1 + 6 * x2 + 4 * x3)

m.enforce(x1 + x2 + x3 <= 100)
m.enforce(10 * x1 + 4 * x2 + 5 * x3 <= 600)
m.enforce(2 * x1 + 2 * x2 + 6 * x3 <= 300)

p = m.to_problem

p.solve

unless p.proven_infeasible?
  puts "x1 = #{p.value_of(x1)}"
  puts "x2 = #{p.value_of(x2)}"
  puts "x3 = #{p.value_of(x3)}"
end

Modelling

Let's have a model :

model = Cbc::Model.new

The variables

3 variable kinds are available:

x = model.bin_var(name: "x")        # a binary variable (i.e. can take values 0 and 1)
y = model.int_var(L..U, name: "y")  # a integer variable, L <= y <= U
z = model.cont_var(L..U, name: "z") # a continuous variable, L <= z <= U

Name is optional and used only when displaying the model.

If you don't specify the range, the variables are free. You can also use Cbc::INF as the infinity bound.

Each one of these 3 kinds have also an array method that generate several variables. For instance to generate 3 positive integer variables named x, y and z :

x, y, z = model.int_var_array(3, 0..Cbc::INF, names: ["x", "y", "z"])

The constraints

You can enforce constraints:

model.enforce(x + y - z <= 10)

You are not restricted to usual linear programming rules when writing a constraint. Usually you would have to write x - y = 0 to express x = y. Ruby-Cbc allows you to put variables and constants on both sides of the comparison operator. You can write

model.enforce(x - y == 0)
model.enforce(x == y)
model.enforce(x + 2 == y + 2)
model.enforce(0 == x - y)

Ruby-Cbc allows you to name your constraints. Beware that their name is not an unique id. It is only a helper for human readability, and several constraints can share the same function name.

model.enforce(my_function_name: x + y <= 50)
model.constraints.map(&:to_function_s) # => ["my_function_name(x, y)"]

Linear constraints are usually of the form

a1 * x1 + a2 * x2 + ... + an * xn <= C
a1 * x1 + a2 * x2 + ... + an * xn >= C
a1 * x1 + a2 * x2 + ... + an * xn == C

With Ruby-Cbc you can write

2 * (2 + 5 * x) + 4 * 5 + 1 == 1 + 4 * 5 * y

The (in)equation must still be a linear (in)equation, you cannot multiply two variables !

Objective

You can set the objective:

model.maximize(3 * x + 2 * y)
model.minimize(3 * x + 2 * y)

Displaying the model

the Model instances have a to_s method. You can then run

puts model

The model will be printed in LP format.

For instance:

Maximize
  + 10 x1 + 6 x2 + 4 x3

Subject To
  + x1 + x2 + x3 <= 100
  + 10 x1 + 4 x2 + 5 x3 <= 600
  + 2 x1 + 2 x2 + 6 x3 <= 300

Bounds
  0 <= x1 <= +inf
  0 <= x2 <= +inf
  0 <= x3 <= +inf

Generals
  x1
  x2
  x3

End

Solving

To solve the model, you need to first transform it to a problem.

problem = model.to_problem

You can define a time limit to the resolution

problem.set_time_limit(nb_seconds)

You can solve the Linear Problem

problem.solve

You can specify arguments that match the cbc command line

problem.solve(sec: 60) # equivalent to $ cbc -sec 60
problem.solve(log: 1)  # equivalent to $ cbc -log 1

For more examples of available options, if coinor-cbc is installed run

$ cbc

then type ?

Once problem.solve has finished you can query the status:

problem.proven_infeasible?  # will tell you if the problem has no solution
problem.proven_optimal?     # will tell you if the problem is solved optimally
problem.time_limit_reached? # Will tell you if the solve timed out

To have the different values, do

problem.objective_value # Will tell you the value of the best objective
problem.best_bound      # Will tell you the best known bound
                        # if the bound equals the objective value, the problem is optimally solved
problem.value_of(var)   # will tell you the computed value or a variable

Finding conflicts

Sometimes a problem has no feasible solution. In this case, one may wonder what is the minimum subset of conflicting inequations. For this prupose, you can use

problem.find_conflict      # Will return an array of constraints that form an unsatifiable set
problem.find_conflict_vars # Will return all variables involved in the unsatisfiable minimum set of constraints

It finds a minimum subset of constraints that make the problem unsatisfiable. Note that there could be several of them, but the solver only computes the first one it finds. Note also that it does so by solving several instances of relaxed versions of the problem. It might take some time! It is based on QuickXplain (http://dl.acm.org/citation.cfm?id=1597177).

One way to see the results nicely could be

problem.find_conflict.map(&:to_function_s)

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/gverger/ruby-cbc.