26 releases
Uses new Rust 2024
| new 0.3.0 | Aug 2, 2026 |
|---|---|
| 0.2.5 | Jul 22, 2026 |
| 0.1.22 | Feb 22, 2026 |
| 0.1.20 | Jul 19, 2025 |
| 0.1.9 | Nov 9, 2024 |
#338 in Math
785KB
18K
SLoC
ROOC
A mixed-integer linear optimization library and modeling language
Language documentation · Web platform
ROOC lets you build linear and mixed-integer models with a fluent Rust API or write them in the ROOC modeling language.
Quick start
use rooc::builder::any;
use rooc::{Microlp, ModelBuilder, constraint, vars};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut model = ModelBuilder::new();
vars! { model =>
make_a: bool;
make_b: bool;
make_c: bool;
material: int(0, 8);
};
let solution = model
.maximize(6.0 * make_a + 5.0 * make_b + 4.0 * make_c - material)
.with(constraint!(2.0 * make_a + 3.0 * make_b + make_c <= material))
.with(constraint!(make_a -> make_b))
.with(constraint!(any(vec![make_a, make_c])))
.solve_with(Microlp::new())?
.into_solution()?;
println!("objective = {}", solution.value());
println!("make_a = {:?}", solution.var_value(make_a));
println!("material = {:?}", solution.var_value(material));
Ok(())
}
Build a model
Variables
Declare variables with the vars! macro:
use rooc::{ModelBuilder, vars};
let mut model = ModelBuilder::new();
vars! { model =>
a: bool;
b: int(0, 10);
c: real;
d: real(-5.0, 5.0);
e: nonneg;
f: nonneg(0.0, 100.0);
xs[5]: bool;
};
Use xs[i] to access an indexed family. For computed names, use the methods directly:
let y = model.add_var("y", VariableType::integer_range(0, 3));
let zs = model.add_vars("z", 4, VariableType::bool());
Expressions and constraints
Use normal arithmetic and boolean operators with variables and numbers. sum, min, max, abs, all, and any are available from rooc::builder.
use rooc::builder::{abs, all, any, max, min, sum};
let total = sum(xs.iter().copied());
let high = max(vec![a, b]);
let low = min(vec![a, b]);
let distance = abs(c - 5.0);
let required = all(vec![a, b]);
let implication = a.implies(b);
Add constraints with constraint! and with:
model
.with(constraint!(2.0 * a + b <= 10.0))
.with(constraint!(cap: a + b == 1.0))
.with(constraint!(a -> b))
.with(constraint!(a <-> !b))
.with_all(vec![
constraint!(c <= 5.0),
constraint!(c >= 1.0),
]);
A constraint without a comparison asserts a boolean expression. Use with_all when a loop produces several constraints:
use rooc::{ModelBuilder, constraint, vars};
let mut model = ModelBuilder::new();
vars! { model => xs[3]: nonneg; }
for (x, capacity) in xs.iter().copied().zip([1.0, 2.0, 3.0]) {
model = model.with(constraint!(x <= capacity));
}
let model = model.satisfy();
Set an objective with maximize, minimize, or satisfy:
model.maximize(sum(xs.iter().copied()));
model.minimize(a + b);
model.satisfy();
The builder returns a new value after each call. Assign it back when you build constraints in a loop.
Solve a model
Built-in solvers
Pass a solver to solve_with:
| Solver | Use for |
|---|---|
Auto |
Safe general-purpose MILP default |
Microlp::new() |
Mixed-integer models and configurable MIP options |
Clarabel |
Continuous models |
The Rust crate enables microlp and clarabel by default. Every other solver
feature is opt-in. Only the default solvers are implemented entirely in Rust
and supported in WebAssembly builds.
| Cargo feature | Pure Rust | WASM | Optional capabilities | Scope | Prerequisite |
|---|---|---|---|---|---|
microlp |
Yes | Yes | MIP gap, time limit, best bound | LP + MILP | None |
clarabel |
Yes | Yes | Shadow prices | Continuous LP | None |
coin_cbc |
No | No | Initial solution, MIP gap, time limit, best bound | LP + MILP | Native CBC toolchain |
highs |
No | No | Initial solution, MIP gap, time limit, shadow prices | LP + MILP | Native HiGHS toolchain |
lpsolve |
No | No | Time limit | LP + MILP | Native C build |
scip |
No | No | Initial solution, MIP gap, time limit | LP + MILP | SCIP installation |
scip_bundled |
No | No | Initial solution, MIP gap, time limit | LP + MILP | Bundled native SCIP build |
lp-solvers |
No | No | None through AllSolvers |
LP + MILP | Solver executable on PATH |
cplex-rs |
No | No | Time limit | LP + MILP | IBM CPLEX installation |
Select an opt-in solver explicitly:
[dependencies]
rooc = { version = "0.3.0", default-features = false, features = ["highs"] }
For a native application that also needs the default solvers, combine the
features, for example features = ["microlp", "clarabel", "scip_bundled"].
Scip is the builder type for either scip or scip_bundled. The lpsolve
and cplex-rs features cannot be enabled together. Native-only features are
rejected for wasm32 and cannot be used by the browser/WebAssembly package.
Configured solvers expose only their implemented capabilities:
use rooc::Highs;
use std::time::Duration;
let solver = Highs::new()
.with_time_limit(Duration::from_secs(30))
.with_mip_gap(0.01)
.with_initial_solution([("x", 1.0)]);
Clarabel and HiGHS provide named shadow prices through the existing
DualValues solution capability. Reduced costs are not exposed.
use rooc::Auto;
let solution = model
.maximize(objective)
.solve_with(Auto)?;
Auto selects ROOC's safe general-purpose MILP default and uses Microlp for every supported model. Use Microlp::new() when you need MIP options:
use rooc::Microlp;
use std::time::Duration;
let solver = Microlp::new()
.with_mip_gap(0.0)
.with_time_limit(Duration::from_secs(5))
.with_node_limit(100_000);
Solving returns a SolveOutcome. If the solver reaches a MIP gap, time limit, or node limit
and the current assignment satisfies the bounds, it comes back as a
solution whose status is Feasible, while a search that did not yet fit into the bounds
yields Interrupted. The solver returns an Err for internal solver errors or when a
model is infeasible or unbounded.
use rooc::{SolveOutcome, SolutionStatus};
match model.maximize(objective).solve_with(solver)? {
SolveOutcome::Solution(solution) => match solution.status() {
SolutionStatus::Optimal => println!("proven optimal: {}", solution.value()),
SolutionStatus::Feasible => println!(
"best found {} (stopped because {}, bound {:?})",
solution.value(),
solution.termination_reason(),
solution.best_bound(),
),
},
SolveOutcome::Interrupted(interrupted) => {
println!("nothing found yet: {}", interrupted.termination_reason());
}
}
You can use into_solution() to convert the SolveOutcome into a Solution,
if it is available.
Read the solution
Read values back through the variables used to build the model:
solution.value();
solution.var_value(x);
solution.numeric_value(x);
solution.eval(&(2.0 * x + y));
Export a model
Call to_lp_format on a linearized model to create CPLEX LP text:
let lp_text = model
.maximize(3.0 * x + 2.0 * y + z)
.with(constraint!(cap: 2.0 * x + y + z <= 8.0))
.linearize()?
.to_lp_format();
Use the ROOC language
The language is useful for models that use data, iteration, or graphs:
use rooc::{RoocSolver, solve_milp_lp_problem};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let source = "max sum((value, i) in enumerate(values)) { value * x_i }
s.t.
sum((weight, i) in enumerate(weights)) { weight * x_i } <= capacity
where
let weights = [10, 60, 30, 40, 30, 20, 20, 2]
let values = [1, 10, 15, 40, 60, 90, 100, 15]
let capacity = 102
define
x_i as Boolean for i in 0..len(weights)";
let solver = RoocSolver::try_new(source.to_string())?;
let solution = solver.solve_using(solve_milp_lp_problem)?.into_solution()?;
println!("{}", solution);
Ok(())
}
The language supports collections, graphs, indexed constraints, boolean logic, and the abs { }, min { }, and max { } blocks.
max abs { x }
s.t.
-10 <= x
x <= 6
define
x as Real
ROOC derives the bounds needed to model these expressions from declarations and constraints. If a required finite bound is unavailable, compilation reports an error instead of guessing a Big-M value.
Build a linear model directly
If your application already has coefficient vectors, use LinearModel directly:
use rooc::{
Comparison, LinearModel, OptimizationType, VariableType, solve_real_lp_problem_clarabel,
};
let mut model = LinearModel::new();
model.add_variable("x1", VariableType::non_negative_real());
model.add_variable("x2", VariableType::real());
model.add_constraint(vec![1.0, 1.0], Comparison::LessOrEqual, 5.0);
model.set_objective(vec![1.0, 2.0], OptimizationType::Max);
// No limit is configured, so the search runs to proven optimality and the
// outcome is guaranteed to hold a solution.
let solution = solve_real_lp_problem_clarabel(&model)
.unwrap()
.into_solution()
.unwrap();
println!("{}", solution);
For complete language examples, see the language documentation.
License
ROOC is released under the MPL-2.0 license.
Dependencies
~8–19MB
~385K SLoC