oximo is a Rust algebraic modeling library for mathematical optimization. Build LP, MILP, QP/MIQP, NLP, and MINLP models with a concise macro API, then solve them with bundled or commercial solvers.
use oximo::prelude::*;
use oximo::solvers::Highs;
let m = Model::new("transport");
variable!(m, x >= 0.0);
variable!(m, 0.0 <= y <= 4.0);
constraint!(m, c1, x + 2.0 * y <= 14.0);
constraint!(m, c2, 3.0 * x >= y);
constraint!(m, c3, x <= y + 2.0);
objective!(m, Max, 3.0 * x + 4.0 * y);
let result = Highs.solve(&m, &HighsOptions::default())?;
println!("obj = {:?}", result.objective()); // 34.0
println!("x = {:?}", result.value_of(x)); // 6.0
println!("y = {:?}", result.value_of(y)); // 4.0
# Ok::<(), Box<dyn std::error::Error>>(())let m = Model::new("my_model");
variable!(m, x >= 0.0); // continuous, x >= 0
variable!(m, 0.0 <= y <= 10.0); // continuous, 0 <= y <= 10
variable!(m, z); // free (unbounded by default)
variable!(m, b, Bin); // binary {0, 1} (also Binary)
variable!(m, n >= 0.0, Int); // general integer (also Integer)
variable!(m, s <= 10.0, SemiCont(2.0)); // semicontinuous: 0 or in [2, 10] (SemiInt too)Bounds, domain, warm start, and fixing can also be given as keyword args after the name:
variable!(m, x, lb = 0.0, ub = 1.0); // same as `0.0 <= x <= 1.0`
variable!(m, n, lb = 0.0, domain = Int); // keyword domain
variable!(m, w, lb = 0.0, ub = 10.0, Int); // mixed with positional domain token
variable!(m, p, lb = 0.0, initial = 3.0); // warm start (scalar only)
variable!(m, q, fix = 5.0); // fixed to 5.0 (scalar only)Expressions are built with standard Rust operators. The macros let you write the
relational operators ==, <=, >= directly. Scalar multiplication, addition,
subtraction, and nonlinear operations (see Nonlinear Expressions) all work out of the box:
constraint!(m, cap, 2.0 * x + 3.0 * y <= 100.0);
constraint!(m, demand, x >= 5.0);
constraint!(m, balance, x - y == 0.0);
constraint!(m, band, 1.0 <= x + y <= 10.0); // two-sided range -> band_lo + band_hi
objective!(m, Min, 3.0 * x + 5.0 * y);
// or
objective!(m, Max, x + 2.0 * y); // also Minimize/min, Maximize/maxvariable!(m, x[k in set]) registers one scalar per key with auto-named entries
like x[seattle,nyc]. Bounds apply uniformly by default; a multi-index family
ranges over a Cartesian product.
let m = Model::new("transport");
variable!(m, x[r in routes] >= 0.0); // one var per route
variable!(m, y[k in items] >= 0.0, Int); // integer family
variable!(m, z[a in rows, b in cols], Bin); // multi-index (Cartesian product)
// Scalar lookup: any type that converts to IndexKey works.
let e1 = x[("seattle", "nyc")];
let e2 = z[a, b];
// Per-key bounds may reference the index
variable!(m, 0.0 <= w[(p, q) in routes] <= capacity_for(&p, &q));
variable!(m, v[k in items], lb = 0.0, ub = cap[k]);
// Filtered family: keep only matching keys (no trivial elements built).
variable!(m, d[(i, j) in rc if i == j] >= 0.0);sum!(body for k in set) reads as sum_{k in set} body.
// Single sum: sum_{i in items} weights[i] * x[i]
constraint!(m, cap, sum!(weights[i] * x[i] for i in items) <= capacity);
// Double sum, flat: sum_{(p,q) in P*M} c[p,q] * x[p,q]
let total_cost = sum!(c[p, q] * x[p, q] for p in plants, q in markets);
// Filtered sum.
let active = sum!(x[i] for i in 0..n if online[i]);The indexed form of constraint! emits one constraint per key, auto-named like
supply[seattle]. A trailing if filters the keys, and name = expr gives a
computed run-time name.
// Scalar set: one constraint per period.
let periods = Set::range(0..T);
constraint!(m, setup[t in periods], x[t] <= capacity * s[t]);
// Tuple set + inner sum builds the LHS expression (key types inferred).
constraint!(m, supply[p in plants], sum!(x[p, q] for q in markets) <= supply_of(&p));
// Filtered family: only the keys passing the guard are built.
constraint!(m, diag[(i, j) in arcs if i == j], x[i, j] <= 1.0);
// Computed run-time name.
constraint!(m, name = format!("bal_{p}"), inflow[p] - outflow[p] == 0.0);A Set is the modeling-layer container for an ordered, finite index set over
integers, strings, or tuples. Most domains need no explicit Set: an integer
range is already a domain (x[i in 0..5], sum!(.. for i in 0..n)). Reach for
Set when keys are strings, tuples, sparse, or a subset reused across statements.
The set! macro binds a named set. A plain right side is normalized to an owned
set, a pat in domain[ if cond] comprehension builds (and optionally filters)
one.
use oximo::prelude::*;
let plants = Set::strings(["seattle", "san-diego"]);
set!(items = 0..5); // range normalized to Set<usize>
set!(routes = plants * plants); // Cartesian product
// Comprehension: product domain + by-value `if`. These two are equivalent.
set!(arcs = (p, q) in &plants * &plants if p != q); // single tuple pattern
set!(arcs = i in plants, j in plants if i != j); // multi-bind product
// The typed filter is also a Set method (the receiver pins the key type):
let diag = (&plants * &plants).filter_typed(|(p, q)| p == q);
// Sparse/string leaf sets:
let sparse = Set::from_ints([0, 2, 4, 8]);Pow, Sin, Cos, Exp, Log, Abs, and bilinear products are first-class. The
model's kind (LP/MILP/QP/MIQP/NLP/MINLP) is inferred from the
expressions.
// Rosenbrock NLP
objective!(m, Min, (1.0 - x).powi(2) + 100.0 * (y - x.powi(2)).powi(2));
// Quadratic constraint
constraint!(m, disk, x.powi(2) + y.powi(2) <= 1.0);
// Transcendental utility (MINLP when any variable is integer/binary)
objective!(m, Max, sum!(u[i] * (1.0 + w[i] * x[i]).log() for i in items));All backends implement the Solver trait:
pub trait Solver {
fn solve(&mut self, model: &Model, opts: &Self::Options) -> Result<SolverResult, SolverError>;
}| Feature | What it adds | Default |
|---|---|---|
highs |
HiGHS - LP/MILP/QP solver (bundled, no install) | yes |
io |
MPS and LP file writers | yes |
gurobi |
Gurobi - LP/MILP/QP/MIQP/NLP/MINLP solver (requires licensed install) | no |
gams |
GAMS bridge - LP/MILP/QP/MIQP/NLP/MINLP depending on solver | no |
baron |
BARON - LP/MILP/QP/MIQP/NLP/MINLP solver (requires licensed install) | no |
No install required, HiGHS is compiled from source via the highs crate.
use oximo::prelude::*;
use oximo::solvers::Highs;
let result = Highs.solve(&m, &HighsOptions::default()
.time_limit(Duration::from_secs(60))
.threads(4)
.mip_gap(0.01)
.method(HighsMethod::Ipm))?;Requires a licensed Gurobi install and GUROBI_HOME set. See crates/oximo-gurobi/README.md.
use oximo::prelude::*;
use oximo::solvers::Gurobi;
let result = Gurobi.solve(&m, &GurobiOptions::default()
.time_limit(Duration::from_secs(120))
.mip_focus(1)
.seed(101))?;Requires GAMS on PATH. Supports solving models via GAMS solvers (CPLEX, BARON, etc.). See crates/oximo-gams/README.md.
use oximo::prelude::*;
use oximo::solvers::Gams;
let result = Gams.solve(&m, &GamsOptions::default())?;Requires a licensed BARON install on PATH. Global solver for nonconvex LP/MILP/QP/MIQP/NLP/MINLP. See crates/oximo-baron/README.md.
use oximo::prelude::*;
use oximo::solvers::Baron;
let result = Baron::new().solve(&m, &BaronOptions::default())?;For a quick, model-aware summary, print result.report(&m). For programmatic access:
let result = Highs.solve(&m, &HighsOptions::default())?;
match result.termination {
TerminationStatus::Optimal => {
// `objective()` is `Option` (a model may have no objective), so print it
// only when present.
if let Some(obj) = result.objective() {
println!("optimal: {obj}");
}
}
TerminationStatus::Infeasible => println!("infeasible"),
TerminationStatus::TimeLimit if result.has_solution() => {
println!("time limit, best = {:?}", result.objective());
}
_ => {}
}
// Variable values (best solution)
let x_val = result.value_of(x); // Option<f64>
// Constraint duals
let dual = result.dual_of(constraint_id); // Option<f64>
// Reduced costs, keyed by VarId
let rc = result.reduced_costs.get(&x.var_id().unwrap());
// Solution pools (e.g. Gurobi, BARON with .num_sol(n)): all points, best first
for i in 0..result.result_count() {
let point = result.solution(i).unwrap();
println!("objective {:?}", point.objective);
}With the io feature (default), you can export models to MPS, LP and NL format for inspection or use with external solvers.
| Crate | Role |
|---|---|
oximo |
Umbrella crate |
oximo-expr |
Arena-allocated expression tree |
oximo-core |
Model, Variable, Constraint, Objective, Set |
oximo-solver |
Solver trait, SolverResult, SolverOptions |
oximo-io |
MPS, LP and NL writers |
oximo-highs |
HiGHS backend |
oximo-gurobi |
Gurobi backend |
oximo-gams |
GAMS writer and backend |
oximo-baron |
BARON writer and backend |
- Gurobi feature: Gurobi,
GUROBI_HOMEset, valid license - GAMS feature: GAMS on
PATH, valid license - BARON feature: BARON on
PATH, valid license
MIT OR Apache-2.0