pub fn run_loop<P, S, So>(
problem: &mut Problem<P>,
state: S,
solver: &mut So,
criteria: &mut [Box<dyn TerminationCriterion<S>>],
max_iter: u64,
) -> Result<OptimizationResult<S>, So::Error>Expand description
Drive a solver to completion against a shared Problem wrapper.
Executor is a thin owning wrapper over this. Composed solvers
(e.g. CG inside CMA, NM inside DE) call run_loop directly so the
inner solver shares the outer’s wrapper: inner cost and gradient
calls bump the same EvalCounts as outer calls, so the eval
aggregation contract (CONTRIBUTING.md “Solver composition” rule 1) is
satisfied automatically for same-problem inners. For composed
solvers driving an inner against an adapter problem (e.g.
LogBarrier), construct a
fresh Problem::new(adapter), pass &mut into run_loop, then
fold the inner wrapper’s EvalCounts back into the outer’s via
EvalCounts::add on Problem::counts_mut.
The inner state’s State::cost_evals (mirrored via
CountsMirror) reflects only per-run work: run_loop takes
a baseline snapshot of Problem::counts at entry, and the state
mirror computes the delta against that. Nested run_loop calls
against the same wrapper therefore see clean per-call counters.
Semantics match Executor::run: each criterion is
reset at
entry, so a criteria vector reused across calls (as an
InnerExecutor does) sees fresh
per-run state. Then init is called once, then on each iteration
framework criteria are checked in insertion order before
the solver’s own terminate hook, before stepping. max_iter is
checked against state.iter() and exits with TerminationReason::MaxIter.
next_iter may also report a mid-iter termination via its return tuple;
in that case the iteration counter is left untouched so the final
state.iter() still reflects the last fully completed iteration.