# trellis-runner
## Trellis
Trellis is a generic execution engine for iterative numerical algorithms.
Rather than implementing optimisation loops, integration loops or search loops
directly, a procedure describes a single iteration of an algorithm while
Trellis manages execution, convergence, termination, observation and
checkpointing.
The library separates the concerns of:
- **algorithm implementation** (`Procedure`)
- **problem definition** (the problem supplied to the procedure)
- **algorithm state** (`UserState`)
- **execution control** (policies)
- **instrumentation** (observers)
This separation allows algorithms to focus solely on their numerical method,
while Trellis provides a reusable execution framework.
### Execution model
Every Trellis calculation follows the same execution model:
```
Problem
│
▼
┌───────────┐
│ Procedure │
└─────┬─────┘
│
updates state
│
▼
┌───────────┐
│ UserState │
└─────┬─────┘
│
emits Progress events
│
┌──────────┴──────────┐
▼ ▼
Engine Policies Observers
│
▼
Engine Actions
```
The principal abstractions are:
- **Problem** — the specific problem instance being solved.
- **Procedure** — performs a single iteration of the algorithm.
- **UserState** — stores the evolving state of the computation and reports
progress.
- **Policies** — inspect progress and control execution.
- **Observers** — inspect progress without affecting execution.
### A simple example
A calculation is configured using the builder returned by
[`GenerateBuilder::build_for`]:
```rust
use trellis_runner::{
GenerateBuilder, MaxIterationPolicy, RelativeTolerancePolicy,
CancellationGuard, Procedure, Progress, UserState,
};
struct Problem;
#[derive(Default)]
struct State {
value: f64,
}
impl UserState for State {
type Float = f64;
fn progress(&self) -> Progress<Self::Float> {
Progress::Measure(self.value)
}
}
struct Solver;
impl Procedure<Problem> for Solver {
const NAME: &'static str = "Example";
type State = State;
type Output = ();
fn step(
&self,
_: &mut Problem,
_: &mut State,
_: CancellationGuard<'_>,
) {}
fn finalise(
&self,
_: &mut Problem,
_: &State,
) {}
}
let engine = Solver
.build_for(Problem)
.with_initial_state(State::default())
.and_policy(RelativeTolerancePolicy::new(1e-8, 10))
.and_policy(MaxIterationPolicy::new(10_000))
.finalise();
let result = engine.run();
```
The builder configures the execution environment rather than the numerical
algorithm itself.
### Procedures
A [`Procedure`] implements the numerical algorithm.
Each call to `step()` performs a single iteration of the algorithm, while
`finalise()` converts the final algorithm state into the value returned to the
caller.
Both infallible and fallible procedures are supported.
### User state
[`UserState`] stores the evolving state of the computation.
In addition to algorithm-specific data, it reports progress to the engine via
[`Progress`], allowing policies and observers to monitor execution.
States implementing [`Snapshotable`] can additionally participate in
checkpointing, allowing long-running computations to be resumed.
### Policies
Policies control solver execution.
During a run, the engine collects progress emitted by the procedure and
passes it to one or more policies. Policies inspect this information and
decide whether the solver should:
- continue running,
- terminate successfully,
- terminate early,
- request a checkpoint,
- or perform another engine action.
Policies influence execution.
Observers do not.
```
Progress ──► Policy ──► Engine Action
│
└────► Observer
```
Multiple policies may be attached simultaneously.
The engine stops as soon as any policy requests termination.
Custom policies can be created implementing the [`EnginePolicy`] trait.
#### Built-in policies
| `MaxIterationPolicy` | Stops after a fixed number of iterations. |
| `TimeoutPolicy` | Stops after a maximum wall-clock duration. |
| `AbsoluteTolerancePolicy` | Stops when the mean absolute error over a rolling window falls below a tolerance. |
| `RelativeTolerancePolicy` | Stops when the mean relative error over a rolling window falls below a tolerance. |
| `TargetValuePolicy` | Stops when the mean distance to a target value remains below a tolerance. |
| `NoProgressPolicy` | Stops when no meaningful improvement has been observed for a specified number of iterations. |
| `StagnationPolicy` | Stops when improvement over a rolling window falls below a relative threshold. |
| `CheckpointPolicy` | Requests periodic checkpoint generation. |
### Observers
Observers receive every event emitted by the engine but never influence
execution.
Typical applications include:
- structured logging,
- tracing,
- CSV export,
- plotting,
- metrics collection,
- progress reporting,
- custom visualisation.
### Checkpointing
User states implementing [`Snapshotable`] may be checkpointed during
execution.
Checkpoints may be requested by policies or generated manually, allowing
interrupted computations to be resumed.
### Extending Trellis
Trellis is designed to be extended through traits.
Most applications only need to implement:
- [`Procedure`] to define the numerical algorithm,
- [`UserState`] to store algorithm state,
- [`EnginePolicy`] for custom stopping criteria,
- [`Observe`] for custom instrumentation.
These components compose naturally, allowing new algorithms, policies and
observers to be combined without modifying the execution engine itself.
License: MIT