Skip to main content

Crate oximo

Crate oximo 

Source
Expand description
oximo logo Examples crates version CI

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

§Building models

§Variables

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)

§Constraints and objectives

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/max

§Indexed variables

variable!(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);

§Summing over sets

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]);

§Rule-style constraints

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);

§Index sets

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 keep their constructors.
let sparse = Set::from_ints([0, 2, 4, 8]);

§Nonlinear expressions

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));

§Solving

All backends implement the Solver trait:

pub trait Solver {
    fn solve(&mut self, model: &Model, opts: &Self::Options) -> Result<SolverResult, SolverError>;
}

§Features

FeatureWhat it addsDefault
highsHiGHS - LP/MILP/QP solver (bundled, no install)yes
ioMPS and LP file writersyes
gurobiGurobi - LP/MILP/QP/MIQP/NLP/MINLP solver (requires licensed install)no
gamsGAMS bridge - LP/MILP/QP/MIQP/NLP/MINLP depending on solverno
baronBARON - LP/MILP/QP/MIQP/NLP/MINLP solver (requires licensed install)no

§HiGHS (default)

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))?;

§Gurobi

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))?;

§GAMS

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())?;

§BARON

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())?;

§Reading results

For a quick, model-aware summary, print result.report(&m). For programmatic access:

let result = Highs.solve(&m, &HighsOptions::default())?;

match result.status {
    SolverStatus::Optimal => println!("optimal: {}", result.objective().unwrap()),
    SolverStatus::Infeasible => println!("infeasible"),
    SolverStatus::TimeLimit => 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);
}

§Model export

With the io feature (default), you can export models to MPS, LP and NL format for inspection or use with external solvers.

§Requirements

  • Gurobi feature: Gurobi, GUROBI_HOME set, valid license
  • GAMS feature: GAMS on PATH, valid license
  • BARON feature: BARON on PATH, valid license

§License

MIT OR Apache-2.0

Re-exports§

pub use oximo_core as core;
pub use oximo_expr as expr;
pub use oximo_solver as solver;
pub use oximo_io as io;

Modules§

prelude
Glob-import target. Brings the modeling and solver surface into scope.
solvers
Concrete solver backends, gated by cargo features.

Structs§

HighsOptions
HiGHS-specific solver options.

Enums§

HighsMethod
HiGHS LP / root-relaxation algorithm.
HighsPresolve
HiGHS presolve options.