Skip to content

Commit

Permalink
Add installation notes
Browse files Browse the repository at this point in the history
Update the readme according to issue #22

Making sure we can use ruby-cbc on different linux distributions with
docker tests.
gverger committed Sep 11, 2020
1 parent e3dfbc3 commit 149e46a
Showing 8 changed files with 155 additions and 11 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
language: ruby
rvm:
- 2.2.3
before_install: gem install bundler -v 1.10.6
before_install:
- sudo apt-get -y install coinor-libcbc-dev
- gem install bundler -v 1.10.6
63 changes: 53 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[![Build Status](https://travis-ci.org/gverger/ruby-cbc.svg?branch=master)](https://travis-ci.org/gverger/ruby-cbc)

# Ruby-Cbc

This gem is using Cbc, an Integer Linear Programming Library, to provide optimization problems solving
@@ -8,6 +9,13 @@ It uses the version 2.9.9 of Cbc, and requires the version 2.9.9 of gem cbc-wrap

## 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:

```ruby
@@ -22,16 +30,25 @@ Or install it yourself as:

$ gem install ruby-cbc

The gem includes a version of the Coin-Or Cbc library. If the system on which
it is installed is not Linux 64bits, it downloads the library sources and
recompiles them at installation.
### Heroku

On Heroku, you will need to tweak your installation a bit: you can install the cbc library with
the [Apt](https://github.com/heroku/heroku-buildpack-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](https://github.com/heroku/heroku-buildpack-apt/issues/35)).

It also works on Heroku.
```
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

```ruby
# The same Brief Example as found in section 1.3 of
# The same Brief Example as found in section 1.3 of
# glpk-4.44/doc/glpk.pdf.
#
# maximize
@@ -52,52 +69,61 @@ 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)
m.enforce(2 * x1 + 2 * x2 + 6 * x3 <= 300)

p = m.to_problem

p.solve

if ! p.proven_infeasible?
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 :

```ruby
model = Cbc::Model.new
```

#### The variables

3 variable kinds are available:

```ruby
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.
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 :

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

#### The constraints

You can enforce constraints:

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

```ruby
model.enforce(x - y == 0)
model.enforce(x == y)
@@ -107,27 +133,32 @@ 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.

```ruby
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

```ruby
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

```ruby
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:

```ruby
model.maximize(3 * x + 2 * y)
model.minimize(3 * x + 2 * y)
@@ -136,12 +167,15 @@ model.minimize(3 * x + 2 * y)
#### Displaying the model

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

```ruby
puts model
```

The model will be printed in LP format.

For instance:

```
Maximize
+ 10 x1 + 6 x2 + 4 x3
@@ -167,39 +201,46 @@ End
### Solving

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

```ruby
problem = model.to_problem
```

You can define a time limit to the resolution

```ruby
problem.set_time_limit(nb_seconds)
```

You can solve the Linear Problem

```ruby
problem.solve
```

You can specify arguments that match the cbc command line

```ruby
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:

```ruby
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

```ruby
problem.objective_value # Will tell you the value of the best objective
problem.best_bound # Will tell you the best known bound
@@ -211,21 +252,23 @@ problem.value_of(var) # will tell you the computed value or a variable

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

```ruby
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

```ruby
problem.find_conflict.map(&:to_function_s)
```

## Contributing

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

9 changes: 9 additions & 0 deletions test/installation-tests/Dockerfile-archlinux
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM archlinux:latest

RUN pacman -Sy --noconfirm gcc make ruby coin-or-cbc

RUN gem install ruby-cbc

COPY ./cbc_test.rb /cbc_test.rb

CMD ["ruby", "/cbc_test.rb"]
9 changes: 9 additions & 0 deletions test/installation-tests/Dockerfile-debian
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM debian:bullseye-slim

RUN apt-get update && apt-get install -y gcc make coinor-libcbc-dev ruby-full

RUN gem install ruby-cbc

COPY ./cbc_test.rb /cbc_test.rb

CMD ["ruby", "/cbc_test.rb"]
9 changes: 9 additions & 0 deletions test/installation-tests/Dockerfile-ubuntu
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM ubuntu:18.04

RUN apt-get update && apt-get install -y gcc make coinor-libcbc-dev ruby-full

RUN gem install ruby-cbc

COPY ./cbc_test.rb /cbc_test.rb

CMD ["ruby", "/cbc_test.rb"]
16 changes: 16 additions & 0 deletions test/installation-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Testing installation of ruby-cbc

Since there have been numerous problems with the installation of ruby-cbc on different platforms,
I have set up some tests for installing ruby-cbc in different environments.

These tests use docker, and thus only test linux distributions.
These tests use the production ruby-cbc gem, installing it with `gem install ruby-cbc`.

To run it:

```bash
ruby tests.rb
```

For each distribution (1 per dockerfile), we install ruby-cbc, and run a little sample to make sure
everything is ok.
31 changes: 31 additions & 0 deletions test/installation-tests/cbc_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require 'ruby-cbc'

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

if p.proven_infeasible?
puts 'Infeasible problem!'
exit 1
end

unless p.proven_optimal?
puts 'Not proven optimal!'
exit 1
end

if p.objective_value != 732
puts "Objective value should be 732, but it is #{p.objective_value}"
exit 1
end
25 changes: 25 additions & 0 deletions test/installation-tests/tests.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

def can_install_on?(os)
image = "cbc-test-#{os}"
install_ok = system("docker build . -q -f Dockerfile-#{os} -t #{image} >/dev/null")
return false unless install_ok

run_ok = system("docker run --rm #{image}")

!!run_ok
end

os_list = %w[ubuntu debian archlinux]

passed = os_list.all? do |os|
puts "Testing ruby-cbc on #{os}"
can_install_on?(os)
end

if passed
puts 'Sucessfully launched ruby-cbc on all os'
else
puts 'Error!'
exit 1
end

0 comments on commit 149e46a

Please sign in to comment.