Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
RAMS_TEST_CBC: true
RAMS_TEST_CLP: true
RAMS_TEST_GLPK: true
RAMS_TEST_HIGHS: true
RAMS_TEST_SCIP: true

steps:
Expand All @@ -32,7 +33,7 @@ jobs:

- name: Install optimization solvers
run: |
dnf install -y coin-or-Cbc coin-or-Clp glpk-utils scip
dnf install -y coin-or-Cbc coin-or-Clp coin-or-HiGHS glpk-utils scip

- name: Run tests
run: bundle exec rake test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.vscode
HiGHS.log
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Ruby Algebraic Modeling System

RAMS is a library for formulating and solving [Mixed Integer Linear Programs](https://en.wikipedia.org/wiki/Integer_programming) in Ruby. Currently it supports the following solvers:
RAMS is a library for formulating and solving [Mixed Integer Linear Programs](https://en.wikipedia.org/wiki/Integer_programming) in Ruby. Currently it supports the following open source solvers:

* [CLP](https://www.coin-or.org/Clp/)
* [CBC](https://www.coin-or.org/Cbc/)
* [CPLEX](https://www-01.ibm.com/software/commerce/optimization/cplex-optimizer/)
* [GNU Linear Programming Kit](https://www.gnu.org/software/glpk/)
* [SCIP](http://scip.zib.de)
* [HiGHS](https://highs.dev/)
* [SCIP](https://www.scipopt.org/)

## Documentation

Expand Down
6 changes: 4 additions & 2 deletions docs/01-quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

## Installation

RAMS assumes you have the solver you're using in your `PATH`. The default solver is [GLPK]((https://www.gnu.org/software/glpk/)), but you can also use [CLP](https://www.coin-or.org/Clp/), [CBC](https://www.coin-or.org/Cbc/), [CPLEX](https://www-01.ibm.com/software/commerce/optimization/cplex-optimizer/),
and [SCIP](http://scip.zib.de).
RAMS assumes you have the solver you're using in your `PATH`. The default solver is [GLPK]((https://www.gnu.org/software/glpk/)), but you can also use [CLP](https://www.coin-or.org/Clp/), [CBC](https://www.coin-or.org/Cbc/), [HiGHS](https://highs.dev),
and [SCIP](https://scipopt.org).

First make sure you have the latest RAMS installed.

Expand All @@ -14,11 +14,13 @@ gem install rams
Now install GLPK or whatever solver you wish.

### Ubuntu

```
sudo apt-get install glpk-utils
```

### Mac OSX

```
brew install glpk
```
Expand Down
6 changes: 5 additions & 1 deletion docs/02-modeling-primitives.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ By default, a continuous variable has a lower bound of `0` and an upper bound of
```ruby
puts "#{m.variables.values.map { |x| [x.low, x.high ]}}"
```

```
[[0.0, nil], [0.0, nil], [0.0, nil]]
```
Expand All @@ -33,11 +34,12 @@ To set a variable's lower bound to negative infinity, pass a `low: inf` keyword
x4 = m.variable(type: :integer, low: nil, high: 10)
```

The binary variables may appear to have an upper bound of positive infinity, but that becomes `1` when it is written to the solver. To see a model the way it is passed to a solver, use the `to_lp` method. This returns the model in [LP format](http://lpsolve.sourceforge.net/5.0/CPLEX-format.htm). Note that the variable names are different in the `to_lp` output.
The binary variables may appear to have an upper bound of positive infinity, but that becomes `1` when it is written to the solver. To see a model the way it is passed to a solver, use the `to_lp` method. This returns the model in [LP format](https://lpsolve.sourceforge.net/5.0/CPLEX-format.htm). Note that the variable names are different in the `to_lp` output.

```ruby
puts m.to_lp
```

```
max
obj: 0 v1
Expand Down Expand Up @@ -75,6 +77,7 @@ puts <<-HERE
#{c3.lhs[x2]} * x2 + #{c3.lhs[x3]} #{c3.sense} #{c3.rhs}
HERE
```

```
2.0 * x1 + 0.5 * x2 <= 5.0
1.0 * x2 + 1.0 * x3 + 1.0 * x4 >= 2.0
Expand All @@ -101,6 +104,7 @@ x = #{[x1, x2, x3, x4].map { |x| solution[x] }}
y = #{[c1, c2, c3].map { |c| solution.dual[c] }}
HERE
```

```
z = 10.0
x = [2.0, 2.0, 1.0, -1.0]
Expand Down
19 changes: 17 additions & 2 deletions docs/03-solver-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ If you want to switch to a different solver, install that solver onto your syste
```ruby
m.solver = :cbc # or
m.solver = :clp # or
m.solver = :cplex # or
m.solver = :glpk # or
m.solver = :highs # or
m.solver = :scip
```

Expand All @@ -38,8 +38,8 @@ By default, RAMS assumes that solvers are available in your system's PATH with t

- `RAMS_SOLVER_PATH_CBC` - Override path for CBC (defaults to `coin.cbc`)
- `RAMS_SOLVER_PATH_CLP` - Override path for CLP (defaults to `clp`)
- `RAMS_SOLVER_PATH_CPLEX` - Override path for CPLEX (defaults to `cplex`)
- `RAMS_SOLVER_PATH_GLPK` - Override path for GLPK (defaults to `glpsol`)
- `RAMS_SOLVER_PATH_HIGHS` - Override path for HiGHS (defaults to `highs`)
- `RAMS_SOLVER_PATH_SCIP` - Override path for SCIP (defaults to `scip`)

For example, if you have GLPK installed in a custom location:
Expand All @@ -54,6 +54,12 @@ Or if you want to use a specific version of CBC:
export RAMS_SOLVER_PATH_CBC=/usr/local/bin/cbc-2.10
```

Or if you have HiGHS installed in a custom location:

```bash
export RAMS_SOLVER_PATH_HIGHS=/opt/highs/bin/highs
```

These environment variables are particularly useful when you have multiple versions of solvers installed or when solvers are installed in non-standard locations.

## Solver Arguments
Expand All @@ -64,6 +70,7 @@ Additional solver arguments can be passed as though they are command line flags.
m.args = ['--dfs', '--bib']
m.solve
```

```
GLPSOL: GLPK LP/MIP Solver, v4.60
Parameter(s) specified in the command line:
Expand All @@ -89,4 +96,12 @@ m.args = ['-c', 'set presolving maxrounds 0']
m.solve
```

Similarly, if you are using HiGHS, you can set a time limit or choose a specific algorithm:

```ruby
m.solver = :highs
m.args = ['--time_limit', '10', '--solver', 'simplex']
m.solve
```

Every solver has different options, so check the manual to see what command line flags are available to you.
4 changes: 2 additions & 2 deletions lib/rams/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
require_relative 'formatters/lp'
require_relative 'solvers/cbc'
require_relative 'solvers/clp'
require_relative 'solvers/cplex'
require_relative 'solvers/glpk'
require_relative 'solvers/highs'
require_relative 'solvers/scip'
require_relative 'variable'

Expand Down Expand Up @@ -38,8 +38,8 @@ class Model
SOLVERS = {
cbc: RAMS::Solvers::CBC.new,
clp: RAMS::Solvers::CLP.new,
cplex: RAMS::Solvers::CPLEX.new,
glpk: RAMS::Solvers::GLPK.new,
highs: RAMS::Solvers::HiGHS.new,
scip: RAMS::Solvers::SCIP.new
}.freeze

Expand Down
55 changes: 0 additions & 55 deletions lib/rams/solvers/cplex.rb

This file was deleted.

94 changes: 94 additions & 0 deletions lib/rams/solvers/highs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
require_relative 'solver'

module RAMS
module Solvers
# Interface to HiGHS solver
class HiGHS < Solver
def solver_command(model_path, solution_path, args)
[solver_executable('highs', 'highs'), model_path, '--solution_file', solution_path] + args
end

private

def parse_status(_model, lines)
status_idx = lines.index { |l| l =~ /^Model status/ }
return :undefined unless status_idx

# Status is on the next line after "Model status"
status = lines[status_idx + 1]
return :undefined unless status

return :optimal if status =~ /optimal/i
return :feasible if status =~ /^feasible/i
return :infeasible if status =~ /infeasible/i
return :unbounded if status =~ /unbounded/i
:undefined
end

def parse_objective(_model, lines)
objective_line = lines.find { |l| l =~ /^Objective/ }
return nil unless objective_line
objective_line.split.last.to_f
end

def parse_primal(model, lines)
# Find the primal section
start_idx = lines.index { |l| l =~ /^# Columns/ }
return {} unless start_idx

num_columns = lines[start_idx].split.last.to_i
primal_values = {}

# Parse variable values
(1..num_columns).each do |i|
line = lines[start_idx + i]
next unless line

parts = line.split
next unless parts.length >= 2

var_name = parts[0]
var_value = parts[1].to_f
variable = model.variables[var_name]
primal_values[variable] = var_value if variable
end

primal_values
end

def parse_dual(model, lines)
# Find the dual section
dual_start_idx = lines.index { |l| l =~ /^# Dual solution values/ }
return {} unless dual_start_idx


# Check if dual solution is available
lines = lines[dual_start_idx..-1]
return {} if lines[dual_start_idx + 1] =~ /^None/

# Find the rows section within dual values
rows_idx = lines.index { |l| l =~ /^# Rows/ }
return {} unless rows_idx

num_rows = lines[rows_idx].split.last.to_i
dual_values = {}

# Parse constraint dual values
(1..num_rows).each do |i|
line = lines[rows_idx + i]
next unless line

parts = line.split
next unless parts.length >= 2

constraint_name = parts[0]
dual_value = parts[1].to_f
constraint = model.constraints[constraint_name]
dual_values[constraint] = dual_value if constraint
end

dual_values
end
end
end
end
2 changes: 1 addition & 1 deletion rams.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)

Gem::Specification.new do |spec|
spec.name = 'rams'
spec.version = '0.1.7'
spec.version = '0.1.8'
spec.authors = ["Ryan J. O'Neil"]
spec.email = ['ryanjoneil@gmail.com']
spec.summary = 'Ruby Algebraic Modeling System'
Expand Down
Loading
Loading