# oximo-solver
Solver trait, result types, status codes, and shared option building blocks for [oximo](https://github.com/oximo-rs/oximo).
This crate defines the contract that backend crates implement. End users interact with concrete backends (`Highs`, `Gurobi`, `Gams`) exposed by the umbrella `oximo` crate, they do not depend on this crate directly unless they are writing a new backend.
## Usage
```toml
[dependencies]
oximo-solver = "0.7.0"
oximo-core = "0.7.0"
```
## `Solver` trait
```rust,ignore
pub trait Solver {
type Options;
fn name(&self) -> &str;
fn supports(&self, kind: ModelKind) -> bool;
fn solve(&mut self, model: &Model, opts: &Self::Options) -> Result<SolverResult, SolverError>;
}
```
Each backend defines its own `Options` type. Users get compile-time validation and LSP autocomplete on the options that actually apply to that backend.
## `SolverResult`
`termination` records why the solve stopped, while `primal_status` records whether a usable
point came back.
| `termination` | `TerminationStatus` | Why the solve stopped |
| `primal_status` | `PrimalStatus` | Whether a usable primal point is available |
| `solutions` | `Vec<SolutionPoint>` | Primal points, best first (empty if no solution) |
| `dual` | `FxHashMap<ConstraintId, f64>` | Constraint duals at the best point |
| `reduced_costs` | `FxHashMap<VarId, f64>` | Variable reduced costs at the best point |
| `best_bound` | `Option<f64>` | Dual/relaxation bound (branch-and-bound backends) |
| `gap` | `Option<f64>` | Relative optimality gap, when reported |
| `solve_time` | `Duration` | Wall time around the solve call |
| `iterations` | `u64` | Simplex iteration count (if reported) |
| `raw_log` | `Option<String>` | Solver stdout/stderr |
Each `SolutionPoint` holds the `primal` variable values (`FxHashMap<VarId, f64>`) and that point's `objective` (`Option<f64>`). Index `0` is the best/incumbent. Backends with solution pools return the extra points after it.
## Shared preparation and reconstruction
Create a fresh `prepare::LoweringContext` per model build, and pass it through
classification and emission. It freezes parameter values and exposes the original
model entities. `PreparedExpressions` can be shared with parallel writers.
Direct affine nodes borrow their coefficients. One-use streaming consumers can
explicitly bypass reuse admission. Before cache initialization, first-use compound
expressions return owned terms without locking or allocating shared storage.
Once initialized, cached entries are checked independently of reuse admission;
a small bounded history recognizes interleaved roots.
`require_linear`, `require_linear_once`, and `require_quadratic` take a lazy location closure, e.g.
`|| format!("constraint {:?}", row.name)`, which runs only on failure.
Quadratic extraction returns `Extracted<QuadraticTerms>` (owned or shared),
which dereferences to the original coefficient representation.
Adapters own capability checks, native row/column layouts, reformulation choices,
native status decoding, and evidence that points, duals and global bounds exist.
`reconstruct` shares coordinate restoration and result cleanup. `normalize_result`
removes incomplete/nonfinite points and clears uncertified multipliers. Discarding
the incumbent also clears its native gap, which does not describe a surviving
pool point.
## Result accessors
```rust,ignore
result.objective() // Option<f64>, best solution's objective
result.value_of(expr) // Result<Option<f64>, ModelMismatchError>
result.value(var_id) // Option<f64>, primal value by VarId (best solution)
result.dual_of(handle) // Result<Option<f64>, ModelMismatchError>
result.best() // Option<&SolutionPoint>, same as .solution(0)
result.solution(i) // Option<&SolutionPoint>, i-th pooled point
result.result_count() // usize, number of returned points
result.has_solution() // true when a usable primal point is available
result.report(&model) // Result<ModelReport, ModelMismatchError>
// Indexed variables
result.value_of_idx(&flow, "nyc") // Result<Option<f64>, ModelMismatchError>
result.values_of(&flow)? // Iterator<(&IndexKey, f64)>
|-------------------------|--------------|--------------------|
| `.time_limit(Duration)` | `time_limit` | `Option<Duration>` |
| `.threads(u32)` | `threads` | `Option<u32>` |
| `.verbose(bool)` | `verbose` | `Option<bool>` |
### Implementing `HasUniversal` for a new backend
```rust
use oximo_solver::{HasUniversal, UniversalOptions};
#[derive(Default)]
pub struct MyOptions {
universal: UniversalOptions,
// backend-specific fields ...
}
impl HasUniversal for MyOptions {
fn universal(&self) -> &UniversalOptions { &self.universal }
fn universal_mut(&mut self) -> &mut UniversalOptions { &mut self.universal }
}
```
## Writing a new backend
Mirror the layout of an existing backend crate (`oximo-highs`, `oximo-gurobi`, `oximo-gams`):
1. `lib.rs`: public struct + `impl Solver`. `supports()` declares which `ModelKind`s are handled. `solve()` delegates to `translate::solve`.
2. `options.rs`: converts `MyOptions` into the backend's native option calls.
3. `translate.rs`: `Model` -> backend conversion and result extraction.
Add an optional dep + feature in `oximo/Cargo.toml` and re-export the type under `oximo::solvers`.
## License
MIT OR Apache-2.0