Limits
A Limit is a condition checked once after every generation to decide whether the engine should keep going. Multiple limits can be active at once (via Limit::Combined in Rust, or by passing several to .limit(...)/run(...) in Python) — in every case, the engine stops as soon as any one limit trips.
| Limit | Stops when | Rust | Python |
|---|---|---|---|
| Generation | the generation counter reaches a target count | Limit::Generation(n) / .until_generation(n) |
rd.Limit.generations(n) |
| Seconds | cumulative engine time reaches a duration | Limit::Seconds(Duration) / .until_seconds(secs) / .until_duration(dur) |
rd.Limit.seconds(secs) |
| Score | the best score reaches or crosses a target (direction-aware per objective; for multi-objective, every component must pass) | Limit::Score(Score) / .until_score(score) |
rd.Limit.score(target) |
| Convergence | improvement over a sliding window drops to (or below) an epsilon | Limit::Convergence(window, epsilon, _) / .until_convergence(window, epsilon) |
rd.Limit.convergence(window, threshold) |
| Expr | a metric expression evaluates to true — see Expressions for the full recipe |
Limit::Expr(Expr) / .until_expr(expr) |
rd.Limit.expr(expr) |
| Combined | any one of a set of sub-limits trips | Limit::Combined(vec![...]) / .limit((a, b, c)) |
.limit(a, b, c) |
Metric-based limits
A predicate-based limit keyed to a named metric also exists (Limit::Metric/until_metric in Rust, rd.Limit.metric(...) in Python), but is intentionally left out of this page for now pending a closer look at its stop/continue polarity.
Each limit on its own
import radiate as rd
score_limit = rd.Limit.score(0.01)
generations_limit = rd.Limit.generations(100)
seconds_limit = rd.Limit.seconds(60)
# window and threshold for convergence - how close the scores must be over the
# window to consider convergence
convergence_limit = rd.Limit.convergence(window=50, threshold=0.01)
// Run until a score target is reached
let engine = build_engine();
let target_score = 0.01;
let result = engine.iter().until_score(target_score).last().unwrap();
// Run until a time limit is reached
let engine = build_engine();
let time_limit = Duration::from_secs(60);
let result = engine.iter().until_duration(time_limit).last().unwrap();
// Run until the score stops improving by more than `epsilon` over a sliding `window`
let engine = build_engine();
let window = 50;
let epsilon = 0.01; // how close the scores must be over the window to consider convergence
let result = engine
.iter()
.limit(Limit::Convergence(
window,
epsilon,
VecDeque::with_capacity(window),
))
.last()
.unwrap();
Combining limits
Combining several at once — the engine stops on whichever trips first:
Python requires at least one limit
Unlike Rust — where an unbounded .iter() or run(closure) is legal, since the closure or a break is itself the stop condition — Python's engine raises immediately if you call .run() or start iterating with no Limit attached anywhere. Always set at least one via .limit(...).