#linear-programming-solver #linear-programming #linear-optimization #milp

bin+lib rooc

A mixed integer linear programming modeling language to solve linear optimization models. Extensible, works in WASM and easy to use.

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

MPL-2.0 license

785KB
18K SLoC

ROOC

ROOC logo

A mixed-integer linear optimization library and modeling language

Crates.io

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