Expand description
§oximo-core
Core modeling types for oximo: Model, Variable, Set, Constraint, Objective, Parameter, IndexedVar, Domain, and ModelKind.
Re-exports oximo-expr types (Expr, ExprArena, ExprId, ExprNode, UnaryOp, Children, ParamId, VarId) so downstream code does not need a separate oximo-expr import. End users typically depend on the umbrella oximo crate rather than this one directly.
§Usage
[dependencies]
oximo-core = "0.7.0"Or via the umbrella crate (recommended for end users):
[dependencies]
oximo = "0.7.0"§Quick example
use oximo_core::prelude::*;
let m = Model::new("transport");
// Scalar variables
variable!(m, x >= 0.0);
variable!(m, 0.0 <= y <= 10.0);
// Constraints (incl. a two-sided range, kept as one constraint)
constraint!(m, c1, x + 2.0 * y <= 14.0);
constraint!(m, c2, 3.0 * x - y >= 0.0);
constraint!(m, band, 1.0 <= x + y <= 12.0);
// Objective (or `objective!(m, Feasibility)` for a pure feasibility problem)
objective!(m, Max, 3.0 * x + 4.0 * y);
println!("kind = {:?}", m.kind()); // LP§Modeling API
The modeling surface is a set of macros: variable!, constraint!, objective!,
sum!, min!, max!, set!, and param!. Each expands to the underlying typed model operations,
so there is no runtime cost and full compile-time type/borrow checking is preserved.
Model uses interior mutability, so a macro can take &m, register
variables/constraints, and the variable!-introduced bindings (x, y, …) are
locals you can use immediately.
let m = Model::new("my_model");
variable!(m, x >= 0.0); // binds a local `x: Expr<'_>`
constraint!(m, cap, x <= 5.0); // uses x while holding &mNames are unique per registry. Registering a duplicate variable or constraint name panics.
§Accessors
m.num_variables() // usize
m.num_constraints() // algebraic + SOC + SOS + indicator constraints
m.variables() // Ref<'_, Vec<Variable>>
m.constraints() // ModelConstraints<'_>
m.constraints().algebraic() // typed algebraic registry
m.constraints().second_order_cones() // typed explicit-SOC registry
m.constraints().special_ordered_sets() // typed SOS registry
m.constraints().indicators() // typed indicator registry
m.arena() // ExprArenaSnapshot<'_>
m.kind() // ModelKind, cached, invalidated on change
m.try_objective() // Result<Objective, Error>
m.variable_id("x") // Option<VarId>
m.constraint_id("cap") // Option<ConstraintId> (algebraic)
m.soc_constraint_id("cone") // Option<SocConstraintId>
m.sos_constraint_id("choice") // Option<SosConstraintId>ModelConstraints::iter() visits every declared constraint. Its algebraic()
and second_order_cones() slices are convenient when both kinds are needed in
one scope. Typed views keep backend passes homogeneous while preserving the
same storage and typed IDs.
SOC-shaped quadratic constraints declared with constraint! remain algebraic
entries; only constraints declared with soc_constraint! or
add_soc_constraint appear in second_order_cones().
§Fixing and unfixing variables
m.fix_var(var_id, 3.0); // lb = ub = 3.0
m.unfix_var(var_id, 0.0, 10.0); // restore bounds§Variables
§Scalar variables
variable!(m, x); // free (-inf, +inf)
variable!(m, x >= 0.0); // lower bound only
variable!(m, 0.0 <= x <= 10.0); // both bounds
variable!(m, b, Bin); // binary {0, 1} (also Binary)
variable!(m, 0.0 <= n <= 100.0, Int); // general integer (also Integer)
variable!(m, s <= 10.0, SemiCont(2.0)); // semicontinuous: 0 or in [2, 10]
variable!(m, t <= 5.0, SemiInt(1.0)); // semi-integer: 0 or integer in [1, 5]
// Keyword args:
variable!(m, u, lb = 0.0, ub = 10.0); // same as `0.0 <= u <= 10.0`
variable!(m, v, lb = 0.0, domain = Int); // keyword domain (or a positional `Int`)
variable!(m, w, initial = 3.0); // warm start (scalar only)
variable!(m, p, fix = 5.0); // fixed to 5.0 (scalar only)§Indexed variables
Creates one scalar variable per key in a Set (or range), named base[key],
and binds an IndexedVar.
let i = Set::range(0..5);
variable!(m, 0.0 <= x[k in i] <= 10.0); // uniform bounds
variable!(m, y[k in i] >= 0.0, Int); // integer family
variable!(m, z[a in rows, b in cols], Bin); // multi-index (Cartesian product)
// Access by key (panics on missing key):
let expr = x[2]; // single key (usize / "name" / (a, b))
let e2 = z[a, b]; // inside the macros: multi-index sugar == z[(&a, &b)]
// Bounds may reference the index -> lowered to per-key bounds:
variable!(m, lower[k] <= w[k in i] <= upper[k]);
// Filtered family: keep only matching keys (no trivial elements built).
variable!(m, d[(i, j) in rc if i == j] >= 0.0);Large indexed variable, parameter, algebraic, range, SOC, and SOS families are prepared in parallel when the family is large enough, then registered serially in set iteration order.
§Domain
| Variant | Description |
|---|---|
Domain::Real | Any real number (default) |
Domain::Integer | Any integer |
Domain::Binary | 0 or 1 |
Domain::SemiContinuous { threshold } | 0 or any value >= threshold |
Domain::SemiInteger { threshold } | 0 or any integer >= threshold |
§Sets
Set is an ordered finite index set. Three variants:
let i = Set::range(0..5); // Range: i64 keys 0..5
let j = Set::strings(["a", "b", "c"]); // Strings
let k = Set::product(&i, &j); // Tuples: (0,"a"), (0,"b"), ...
let k = &i * &j; // Same via Mul operator
// From sparse ints:
let s = Set::from_ints([0, 2, 4, 8]);
// Filter:
let evens = i.filter(|k| k.as_i64().unwrap() % 2 == 0);§Constraints
==, <=, and >= are written directly, the macro intercepts the tokens, so
these are real constraint operators.
constraint!(m, name, lhs <= rhs); // named, also >= and ==
constraint!(m, lhs >= rhs); // anonymous (auto-named _c0, _c1, ...)
constraint!(m, band, 1.0 <= e <= 3.0); // two-sided range -> one constraint (expr bounds -> band_lo/band_hi)
constraint!(m, name = format!("c_{k}"), e == rhs); // computed run-time name§Indexed family over a set
// One constraint per key, auto-named supply[seattle], ...
constraint!(m, supply[p in plants], sum!(x[p, q] for q in markets) <= cap[p]);
// Multi-index family (multi-index access sugar: x[i, j]).
constraint!(m, flow[i in 0..n, j in 0..m], x[i, j] >= 0.0);
// Filtered family: only keys passing the guard.
constraint!(m, diag[(i, j) in rc if i == j], x[i, j] <= 1.0);Capture the macro result to query rows without reconstructing their names:
use oximo_core::prelude::*;
let m = Model::new("constraint_handles");
variable!(m, x[i in 0..4] >= 0.0);
let capacity: ConstraintHandle = constraint!(m, capacity, x[0] <= 10.0);
let cover: IndexedConstraint<usize> =
constraint!(m, cover[i in 0..4 if i % 2 == 0], x[i] >= 1.0);
assert!(cover.get(1).is_none()); // filtered out
for (i, cid) in cover.iter() {
assert_eq!(cover.get(i), Some(cid));
}
let bands: IndexedRangeConstraint<usize> =
constraint!(m, bands[i in 0..4], 1.0 <= x[i] <= 4.0);
let RangeConstraintHandles::Interval(cid) = bands.get(0).unwrap() else {
panic!("constant bounds and a linear body produce an interval row");
};get(key) returns copied model-bound handles; iter() yields typed (K, handle) pairs in domain
order for dense, sparse, string, tuple, and filtered domains.
Two-sided ranges return RangeConstraintHandles::Interval(handle) or
RangeConstraintHandles::Split { lower, upper }. Call .id() on an individual
handle when a backend-facing raw ConstraintId is required.
§Summation
sum!(body for k in domain) reads as sum_{k in domain} body. Nest with extra
clauses and filter with a trailing if:
constraint!(m, cap, sum!(weights[i] * x[i] for i in items) <= capacity);
objective!(m, Min, sum!(c[i, j] * x[i, j] for i in rows, j in cols));
let evens = sum!(x[i] for i in items if i % 2 == 0); // filteredInside constraint!, soc_constraint!, and objective!, sums inherit the
model’s expression context. Empty domains and filters produce the constant
expression 0, including in indexed families and nested sums.
For a standalone sum, pass the model explicitly when the domain or filter can be empty:
let total = sum!(m, x[i] for i in possibly_empty_items);
let filtered = sum!(m, x[i] for i in items if predicate(i));
// Both produce the constant expression 0 when no term is selected.§Special ordered sets
SOS1 and SOS2 constraints apply ordering weights to bare variables. SOS1 allows at most one nonzero member. SOS2 allows at most two adjacent nonzero members. Weights must be finite and unique within each set.
variable!(m, 0.0 <= choice_a <= 1.0);
variable!(m, 0.0 <= choice_b <= 1.0);
variable!(m, 0.0 <= choice_c <= 1.0);
let one_choice = sos_constraint!(m, one_choice, SOS1, [choice_a, choice_b, choice_c]);
sos_constraint!(m, adjacent_choice, SOS2, [choice_a, choice_b, choice_c]);
sos_constraint!(m, weighted, SOS2, [
(choice_a, 10.0), (choice_b, 20.0), (choice_c, 30.0),
]);The indexed form creates one set for every key in the binder. The binder is
required because the macro cannot infer an index domain from an IndexedVar:
choice[i in 0..n] produces choice[0], …, choice[n - 1]. To create one
SOS over a dynamically assembled collection, use
m.add_sos_constraint_auto_weights("choice", SosType::Sos1, members).
The single-constraint form returns a model-bound SosConstraintHandle.
Backends without native SOS support return an error.
Reformulate one constraint through its handle, or every active SOS in a model explicitly:
let transformed = one_choice.to_reformulated_model(SosReformulationOptions::default())?;
solver.solve(&transformed, &options)?;
let transformed = m.to_reformulated_sos_model(
SosReformulationOptions::default().with_fallback_big_m(1.0e6),
)?;
// Reformulate every active SOS in place when the native form is no longer needed.
let artifacts = m.reformulate_sos(
SosReformulationOptions::default().with_fallback_big_m(1.0e6),
)?;
solver.solve(&m, &options)?;The to_reformulated_* methods produce an independent model.
reformulate* methods modify the source model.
All forms preserve original IDs, append binary variables and linear rows, and retain the source SOS entries as inactive provenance. Finite variable bounds are used as member-specific Big-M values. An infinite bound is an error unless the caller explicitly supplies a positive finite fallback. A fallback that is too small can truncate the feasible region.
Prefer solving the original model with a backend that supports native SOS. Reformulate when the selected backend lacks that capability or when you intentionally need a MILP representation. In-place reformulation first validates every active set, then appends all generated artifacts without copying the source model.
Because the generated rows embed the current member bounds, those bounds cannot be changed on a reformulated model. Change bounds before reformulating, or produce a fresh reformulated copy after changing the source model.
§Indicator constraints
An indicator applies an affine row only when an existing binary variable has the selected value. The implication is one-way:
variable!(m, enabled, Binary);
variable!(m, x);
indicator_constraint!(m, capacity, enabled == 1 => x <= 10.0);
indicator_constraint!(m, shutdown, enabled == 0 => x == 0.0);
indicator_constraint!(m, band, enabled == 1 => -2.0 <= x <= 4.0);The trigger must be a bare binary variable from the same model and the consequent must be affine. Native support is available in Gurobi and MOSEK; GAMS requires an explicitly selected COPT, CPLEX, Gurobi, SCIP, or Xpress subsolver.
Backends without native indicator support reject active indicators. Convert them
explicitly with reformulate_indicators, or use to_reformulated_indicator_model
when changing the source model isn’t intended.
m.reformulate_indicators(IndicatorReformulationOptions::default())?;
highs.solve(&m, &options)?;§Second-order cone constraints
soc_constraint! registers ||terms||_2 <= bound. Every term and the bound
must be affine. The model classifies as SOCP/MISOCP.
soc_constraint!(m, cone, [x, y] <= t); // named -> SocConstraintHandle
soc_constraint!(m, [x - y, 2.0 * y] <= t + 1.0); // anonymous (auto-named _soc0, ...)
soc_constraint!(m, name = format!("c_{k}"), [x] <= t); // computed run-time name
soc_constraint!(m, risk[i in assets], [s[i] * w[i]] <= cap); // family: risk[key] per keyThe method form m.add_soc_constraint("cone", [x, y], t) is equivalent.
§Objectives
objective!(m, Min, cost_expr);
objective!(m, Max, revenue_expr);Nonlinear expressions use Expr methods such as sqrt, exp2, log1p,
atan2, min, and max. min!/max! provide the same indexed-domain
syntax as sum! and preserve deterministic child order.
§Parameters
param!(m, rate = 0.05); // binds a re-bindable `rate: Expr<'_>`
rate.set_param_value(0.07); // change between solves without rebuilding§Indexed parameters
Mirror indexed variables: one re-bindable scalar parameter per key, bound as an
IndexedParam. The right-hand side is evaluated per key and may reference the
index.
let items = Set::range(0..3);
param!(m, cost[i in items] = base_cost[i]); // one parameter per key
param!(m, w[(i, j) in rc] = weight(i, j)); // multi-index
param!(m, c[p in plants] = price[p]); // string-keyed (sparse)
let unit = cost[1]; // index for a param `Expr`
cost[1].set_param_value(9.0); // re-bind one entry via its handle
m.set_param_idx(&cost, 1, 9.0)?; // ...or by key on the model
m.param_value_idx(&cost, 1); // -> Some(9.0)§Model kind
Inferred automatically from variables and expressions, cached and invalidated
on change. The decision ladder runs top-down. Any integer/binary variable
picks the MI* variant of the row that matches:
| Kind (continuous/integer) | Conditions |
|---|---|
NLP/MINLP | Any nonlinear expression (degree > 2, transcendentals, division) |
QCP/MIQCP | Any quadratic constraint not recognized as a second-order cone |
SOCP/MISOCP | Second-order cones present (explicit or detected) |
QP/MIQP | Quadratic objective, linear constraints |
LP/MILP | Everything linear |
§License
MIT OR Apache-2.0
Re-exports§
pub use constraint::Constraint;pub use constraint::ConstraintExpr;pub use constraint::ConstraintHandle;pub use constraint::ConstraintId;pub use constraint::IntoRhs;pub use constraint::RangeConstraintHandles;pub use constraint::RangeConstraintIds;pub use constraint::Relate;pub use constraint::Sense;pub use display::ConstraintDisplay;pub use display::ExprDisplay;pub use display::IndicatorDisplay;pub use display::ObjectiveDisplay;pub use display::SocDisplay;pub use display::SosDisplay;pub use domain::Domain;pub use error::Error;pub use error::Result;pub use indexed::IndexedConstraint;pub use indexed::IndexedIndicatorConstraint;pub use indexed::IndexedParam;pub use indexed::IndexedRangeConstraint;pub use indexed::IndexedRangeIndicatorConstraint;pub use indexed::IndexedVar;pub use indicator::IndicatorConstraint;pub use indicator::IndicatorConstraintHandle;pub use indicator::IndicatorConstraintId;pub use indicator::RangeIndicatorConstraintHandles;pub use indicator::RangeIndicatorConstraintIds;pub use model::ConstraintRef;pub use model::IndexedVarBuilder;pub use model::Model;pub use model::ModelConstraints;pub use model::ModelKind;pub use model::display_index_key;pub use objective::Objective;pub use objective::ObjectiveSense;pub use param::Parameter;pub use reformulation::IndicatorReformulationArtifacts;pub use reformulation::IndicatorReformulationOptions;pub use reformulation::ReformulatedModel;pub use reformulation::ReformulationError;pub use reformulation::SosReformulationArtifacts;pub use reformulation::SosReformulationOptions;pub use set::Axis;pub use set::FromIndexKey;pub use set::FromIndexKeyRef;pub use set::IndexKey;pub use set::IndexKeyRef;pub use set::IndexTuple;pub use set::KeyCat;pub use set::ScalarKey;pub use set::Set;pub use set::SetIter;pub use soc::SocConstraint;pub use soc::SocConstraintHandle;pub use soc::SocConstraintId;pub use soc::SocForm;pub use sos::SosConstraint;pub use sos::SosConstraintHandle;pub use sos::SosConstraintId;pub use sos::SosMember;pub use sos::SosType;pub use sum::SumDomain;pub use var::VarBuilder;pub use var::Variable;pub use var::var_name;
Modules§
- constraint
- display
- Human-readable rendering of models, constraints, objectives, and expressions.
- domain
- error
- indexed
- indicator
- model
- objective
- param
- prelude
- reformulation
- Explicit, solver-independent model reformulations.
- set
- soc
- sos
- sum
- var
Macros§
- constraint
constraint!(model, [name|name[idx]], lhs <op> rhs), register a constraint, an auto-named anonymous constraint, or an indexed family of constraints.- indicator_
constraint - Register a native binary-triggered affine constraint.
indicator_constraint!(model, [name|name[idx]], binary == 0|1 => relation). - max
max!(body for pat in domain[, pat in domain ...][ if cond]), an indexed maximum.- min
min!(body for pat in domain[, pat in domain ...][ if cond]), an indexed minimum.- objective
objective!(model, Min|Max, expr), set the model objective and sense.objective!(model, Feasibility)(alsofeasibility/feas) declares a feasibility problem with no objective to optimize.- param
param!(model, name = value), declare a re-bindable scalar parameter and bind it to a local of the same name.- set
set!(name = domain), bind a local to an indexSet. A plain right side (0..5,a * b) is normalized to an owned set (a top-level*is a borrowing Cartesian product). Apat in domain[ if cond]comprehension builds (and optionally filters) the set. See the crate docs.- soc_
constraint soc_constraint!(model, [name|name = expr|name[idx]], [terms] <= bound), register the second-order cone constraint||terms||_2 <= bound(every term and the bound must be affine), an auto-named anonymous cone, or an indexed family of cones.- sos_
constraint sos_constraint!(model, [name|name[idx]], SOS1|SOS2, [var, ...]). Explicit weights can be supplied as[(var, weight), ...]. The short form assigns consecutive weights starting at one.- sum
sum!(body for pat in domain[, pat in domain ...]), algebraic summation, lowered to nestedsum_overfolds. To allow an empty domain (or an empty filter), anchor the sum to its model:sum!(model, body for pat in domain). Sums written insideconstraint!,soc_constraint!,objective!, or an anchored sum inherit its expression context automatically. Selected terms must belong to that model. Standalone unanchored sums require a first term.- variable
variable!(model, spec), declare a decision variable (or an indexed family) and bind it to a local of the same name. See the crate docs for the grammar.
Structs§
- Expr
- Lightweight handle to a node in an
ExprArenaCell. - Expr
Arena - Expr
Arena Cell - Parallel-safe owner used by
crate::Exprhandles. - Expr
Arena Snapshot - Immutable copy-on-write snapshot of an
ExprArenaCell. - Expr
Arena Write Guard - ExprId
- ModelId
- Stable identity shared by a model, its expression handles, and its results.
- Model
Mismatch Error - An expression, indexed family, model, or result came from another model.
- ParamId
- VarId
Enums§
- Eval
Error - Expr
Node - Here we use a linear fast-path:
sum(coeff * var) + constant. Built by the operator overloads when all children are linear, so LP/MILP construction never walks anAdd(Mul(Const, Var), ...)tree. - UnaryOp
- Unary scalar functions represented by
ExprNode::Unary.
Functions§
- describe_
nonlinear_ term - Render the first nonlinear summand of
idas a short infix string, resolving eachVarIdto a display name viaresolve. ReturnsNonewhenidis fully affine (no nonlinear residual). - dot
- Dot product of expressions with scalar coefficients:
sum_{i} c_i * e_i. - render_
expr - Render
idas a canonical infix string, resolving eachVarIdto a display name viaresolve. The linear part first, then any nonlinear residual summands. Signs are folded into the joins, and a value-less expression is rendered as0. - render_
linear_ terms - Render a
LinearTermssnapshot by: