RAMS is a library for formulating and solving Mixed Integer Linear Programs in Ruby. Currently it supports the following open source solvers.
RAMS assumes you have the solver you're using in your PATH. The default solver
is HiGHS, but you can easily switch to any of the solvers listed above.
First make sure you have the latest RAMS installed.
gem install ramsNow install HiGHS or whatever solver you wish. The command below works on Fedora Linux. Consult the solver instructions for installation on your platform.
sudo dnf install highsYou are now ready to formulate and solve models. Try running a script like this.
require 'rams'
m = RAMS::Model.new
x1 = m.variable type: :binary
x2 = m.variable type: :binary
x3 = m.variable type: :binary
m.constrain(x1 + x2 + x3 <= 2)
m.constrain(x2 + x3 <= 1)
m.sense = :max
m.objective = 1 * x1 + 2 * x2 + 3 * x3
solution = m.solve
puts <<-HERE
objective: #{solution.objective}
x1 = #{solution[x1]}
x2 = #{solution[x2]}
x3 = #{solution[x3]}
HEREYou should get output along the lines of the following.
objective: 4.0
x1 = 1.0
x2 = 0.0
x3 = 1.0The first class you need to instantiate is RAMS::Model. Everything else is
created by interacting with instances of the Model class.
require 'rams'
m = RAMS::Model.newVariables can be continuous (the default), integer, or binary. They are associated with an individual model.
x1 = m.variable
x2 = m.variable type: :integer
x3 = m.variable type: :binaryBy default, a continuous variable has a lower bound of 0 and an upper bound of
nil, representing positive infinity.
puts "#{m.variables.values.map { |x| [x.low, x.high ]}}"[[0.0, nil], [0.0, nil], [0.0, nil]]To set a variable's lower bound to negative infinity, pass a low: inf keyword
argument to the Model.variable method. Similarly, upper bounds can be passed
in to Model.variable using the high keyword argument.
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. Note that the variable names are different in the to_lp output.
puts m.to_lpmax
obj: 0 v1
st
bounds
0.0 <= v1 <= +inf
0.0 <= v2 <= +inf
0.0 <= v3 <= 1.0
-inf <= v4 <= 10
general
v2
v4
binary
v3
endNow we're ready to add some constraints. These can be done using linear
inequalities and the Model.constrain method.
c1 = m.constrain(2*x1 + x2/2 <= 5)
c2 = m.constrain(x2 + x3 >= 2 - x4)
c3 = m.constrain(x2 == 2*x3)When an inequality is instantiated, all the variables are moved into its lhs
attribute, and the constant is stored in its rhs attribute. The sense of the
inequality is also available.
puts <<-HERE
#{c1.lhs[x1]} * x1 + #{c1.lhs[x2]} * x2 #{c1.sense} #{c1.rhs}
#{c2.lhs[x2]} * x2 + #{c2.lhs[x3]} * x3 + #{c2.lhs[x4]} * x4 #{c2.sense} #{c2.rhs}
#{c3.lhs[x2]} * x2 + #{c3.lhs[x3]} #{c3.sense} #{c3.rhs}
HERE2.0 * x1 + 0.5 * x2 <= 5.0
1.0 * x2 + 1.0 * x3 + 1.0 * x4 >= 2.0
1.0 * x2 + -2.0 == 0.0The objective sense is available through the sense attribute. :max is the
default. To minimize, set the sense to :min. Similarly, assign to the
objective attribute to set the objective function. RAMS defaults to no
objective function, or feasibility models. Explicitly setting the sense is
always a good idea.
m.objective = x1 + 2*x2 + 3*x3 - x4
m.sense = :maxTo get a model solution, simply call solve. The objective, primal variable
values, and dual prices can be accessed directly off of this object, along with
the solution status.
puts <<-HERE
z = #{solution.objective}
x = #{[x1, x2, x3, x4].map { |x| solution[x] }}
y = #{[c1, c2, c3].map { |c| solution.dual[c] }}
HEREz = 10.0
x = [2.0, 2.0, 1.0, -1.0]
y = [5.0, 0.0, 0.0]If you want to see what the solver is doing, simply set verbose to true on
the model before solving.
m.verbose = true
solution = m.solveThis should give you output line the following, depending on your model and solver. Note that the output is not streamed in real time, but merely printed after solving.
Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms
Set option log_file to "HiGHS.log"
Set option solution_file to "/tmp/20250624-180194-hu2ppg.lp.sol"
MIP 20250624-180194-hu2ppg has 49 rows; 40 cols; 144 nonzeros; 40 integer variables (40 binary)
[...snip...]If you want to switch to a different solver, install that solver onto your
system and change the solver attribute on your model.
m.solver = :cbc # or
m.solver = :clp # or
m.solver = :glpk # or
m.solver = :highs # or
m.solver = :scipBy default, RAMS assumes that solvers are available in your system's PATH with
their standard names. However, you can customize the path or name for any solver
using environment variables.
RAMS_SOLVER_PATH_CBC- Override path for CBC (defaults tocoin.cbc)RAMS_SOLVER_PATH_CLP- Override path for CLP (defaults toclp)RAMS_SOLVER_PATH_GLPK- Override path for GLPK (defaults toglpsol)RAMS_SOLVER_PATH_HIGHS- Override path for HiGHS (defaults tohighs)RAMS_SOLVER_PATH_SCIP- Override path for SCIP (defaults toscip)
For example, if you have GLPK installed in a custom location:
export RAMS_SOLVER_PATH_GLPK=/opt/glpk/bin/glpsolOr if you have HiGHS installed in a custom location:
export RAMS_SOLVER_PATH_HIGHS=/opt/highs/bin/highsThese environment variables are particularly useful when you have multiple versions of solvers installed or when solvers are installed in non-standard locations.
Additional solver arguments can be passed as though they are command line flags.
The following adds both --dfs and --bib arguments to the GLPK invocation.
m.solver = :glpk
m.args = ['--dfs', '--bib']
m.solveGLPSOL: GLPK LP/MIP Solver, v4.60
Parameter(s) specified in the command line:
--lp /var/folders/vj/t2g113b97mq1qzscqh7b8npc0000gn/T/20170126-46037-crkxuo.lp
--output /var/folders/vj/t2g113b97mq1qzscqh7b8npc0000gn/T/20170126-46037-crkxuo.lp.sol
--dfs --bib
Reading problem data from '/var/folders/vj/t2g113b97mq1qzscqh7b8npc0000gn/T/20170126-46037-crkxuo.lp'...
[...snip...]This can be used to do things like set time limits on finding solutions. For instance, we can do that with GLPK as follows.
m.solver = :glpk
m.args = ['--tmlim', '3']
m.solveFor a more interesting example, if you are using SCIP, you can turn off presolving using the following configuration. This can be useful since SCIP doesn't provide dual prices for constraints that have been presolved out of the problem formulation.
m.solver = :scip
m.args = ['-c', 'set presolving maxrounds 0']
m.solveSimilarly, if you are using HiGHS, you can set a time limit or choose a specific algorithm.
m.solver = :highs
m.args = ['--time_limit', '10', '--solver', 'simplex']
m.solveEvery solver has different options, so check the manual to see what command line flags are available to you.
More modeling examples are available in the examples folder. Happy modeling!