Documentation
Termination
Control when the solver stops — time limits, step counts, score targets, and composites.
Termination conditions tell the solver when to stop searching. You can set termination globally or per-phase.
Omitted Phases Require an Effective Limit
With phases omitted, state-aware construction runs first. Default local
search is eligible only if top-level termination contains a time, work,
unimproved, or score limit. Missing termination or an empty [termination]
table does not enable it: those solves are construction-only. An explicit
local-search phase and its omitted selector defaults are a separate
configuration choice. A score target enables the default phase, but does not
guarantee that the target can be reached.
Termination Types
Time-Based
[termination]
seconds_spent_limit = 300 # Stop after 5 minutes
minutes_spent_limit sets the same budget in minutes. The two keys add up:
minutes_spent_limit = 1 with seconds_spent_limit = 30 is a 90-second limit.
The sum saturates instead of overflowing. A value of 0 is a zero time limit,
not “no limit”.
Step-Based
[termination]
step_count_limit = 100000 # Stop after 100k steps
Unimproved Time/Steps
Stop when no improvement has been found for a number of steps:
[termination]
unimproved_step_count_limit = 10000 # No improvement for 10k steps
or for a duration:
[termination]
unimproved_seconds_spent_limit = 60 # No improvement for 60s
No Hidden Time Limit
Nothing is added that the configuration does not set. A step, unimproved, or
score criterion without seconds_spent_limit or minutes_spent_limit runs
without a time limit: step_count_limit = 100000 alone stops after 100000
steps however long they take, and a best_score_limit that is never reached
runs until the phases end or the job is cancelled. Add a time limit when the
solve must also stop on the clock.
Score-Based
Stop when a target score is reached:
[termination]
best_score_limit = "0hard/-100soft" # Stop when feasible with soft ≥ -100
Composite Termination
Every configured criterion applies, combined with OR: the solve stops at
whichever is reached first. Phase-level termination tables combine their
criteria the same way.
[termination]
seconds_spent_limit = 300
best_score_limit = "0hard/0soft"
unimproved_step_count_limit = 10000
# Stops after 5 minutes, at a perfect score, or after 10k unimproved steps
The candidate trace header records the installed policy as
solverforge.execution_policy: one attribute per criterion
(configured_time_limit_ns, best_score_limit, step_count_limit,
unimproved_step_count_limit, unimproved_time_limit_ns, each none when
unset) and termination_composition, which names every installed criterion,
for example time_or_best_score_or_unimproved_step_count, or unbounded
when none is set.
A best_score_limit must parse as the model’s score type. One that does not
fails the job before any phase runs, naming the key:
[termination]
seconds_spent_limit = 300
best_score_limit = "0hard/-5sotf"
Per-Phase Termination
Each phase can have its own termination:
[[phases]]
type = "construction_heuristic"
# Runs until construction decisions finish; optional values may stay unassigned
[[phases]]
type = "local_search"
[phases.acceptor]
type = "late_acceptance"
late_acceptance_size = 400
[phases.termination]
unimproved_step_count_limit = 10000
An explicit phase’s termination counters start at that top-level phase boundary and are removed before the next phase begins. Score and unimproved limits observe committed step scores at the same boundary. Mandatory omitted construction does not install a phase-local overlay, so required completion remains governed by the retained lifecycle rather than an accidental internal cutoff.
A partitioned_search phase’s termination bounds its partitions instead: only
time and step count limits are accepted there. See
Partitioned And Custom Search.
Parallel Evaluation Budgets
In compiled acceptor-forager local search, move-count and score-calculation
limits count candidates consumed in canonical order. Parallel windows are
capped by the remaining in-phase evaluation budget. Trials evaluated ahead of
the decision point and discarded are reported as moves_speculative, not
added to moves_evaluated or score_calculations. A fixed work budget therefore
does not measure all physical speculative scoring work. See
Local Search.
Programmatic Termination
Use SolverManager::cancel(job_id) to stop a retained job from code:
use solverforge::SolverManager;
static MANAGER: SolverManager<Schedule> = SolverManager::new();
let (job_id, rx) = MANAGER.solve(problem).expect("solver job should start");
// Later...
MANAGER.cancel(job_id).expect("cancel should be accepted");
The job’s event stream then ends with SolverEvent::Cancelled.
To give one retained job a different solver-level termination, start it with
SolverManager::solve_with_override(solution, SolverConfigOverride::with_termination(termination));
see Runtime Overrides.
Configured limits remain binding during mandatory scalar and list construction. Their terminal result depends on whether the solution is structurally complete:
- after every mandatory list element, required assignment row, and non-optional
scalar slot is assigned, the terminal event is
SolverEvent::Completedand the terminal reason isSolverTerminalReason::TerminatedByConfig - if a limit fires first, the terminal event is
SolverEvent::Failed; noBestSolution, completed snapshot, or partial construction state is published
Local search cannot start until the mandatory completion gate passes. The gate is checked again before final publication, so a later phase cannot expose a solution that has become structurally incomplete.
Pause, cancellation, and config termination are polled inside large candidate work and around phase and terminal hooks. Paused time is removed from the active solve and phase timers, so resuming continues the remaining configured budget instead of charging the paused interval.
Extension State and Polling
Unimproved terminations own mutable observation state. Repeated checks on the same step do not count as extra unimproved steps.
Control and wall-clock checks are batched within candidate loops and are also performed at step, phase, and terminal boundaries. The local-search loop polls control every 64 candidate iterations, with evaluation checks on a 64-evaluation schedule. Construction also batches scans/pulls and polls around placements. These limits are cooperative stopping conditions, not hard real-time deadlines: a callback or one expensive candidate can run past the requested time before control settles. Phase limits with only step/score criteria use counters without reading the wall clock on each control poll.
See Also
- Configuration — TOML configuration format
- SolverManager — Programmatic solver control