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::Completed and the terminal reason is SolverTerminalReason::TerminatedByConfig
  • if a limit fires first, the terminal event is SolverEvent::Failed; no BestSolution, 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