1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Composition adapter: drive an inner solver from inside an outer
//! solver's [`next_iter`](crate::core::solver::Solver::next_iter).
//!
//! [`InnerExecutor`] mirrors [`Executor`](crate::core::executor::Executor)'s
//! builder ergonomics (`max_iter`, `terminate_on`) but does *not* own the
//! problem — outer solvers store one as a field and call
//! [`InnerExecutor::run`] against the borrowed `&P` they receive in
//! `next_iter`. Internally [`InnerExecutor::run`] is exactly
//! [`run_loop`](crate::core::executor::run_loop); the wrapper just owns the
//! solver, the criteria vec, and the iteration budget so the same set of
//! settings can be reused across outer iters without re-allocating.
//!
//! See `AGENTS.md` "Solver composition" for the three load-bearing rules
//! (eval aggregation, criteria statelessness across calls, failure
//! routing) every outer solver must follow.
use crate;
use crateSolver;
use crateState;
use crateTerminationCriterion;
/// Pre-configured inner solver an outer solver drives once per outer
/// iteration.
///
/// Owns the inner solver, its termination criteria, and its `max_iter`
/// budget. The problem is supplied (borrowed) at [`run`](Self::run) time,
/// so the outer solver can pass the `&P` it receives in
/// [`next_iter`](crate::core::solver::Solver::next_iter) without taking
/// ownership.
///
/// Mirrors [`Executor`](crate::core::executor::Executor)'s builder API:
/// [`max_iter`](Self::max_iter) and [`terminate_on`](Self::terminate_on)
/// are chainable. The differences are (a) the problem isn't owned, and
/// (b) [`run`](Self::run) is reusable — the same `InnerExecutor` is
/// expected to be invoked many times across the outer's lifetime.
///
/// [`run_loop`](crate::core::executor::run_loop) stays as the lower-level
/// escape hatch for outer solvers that want to reconstruct criteria per
/// call.
///
/// # Composition contracts
///
/// Three rules outer solvers must follow when consuming the result of
/// [`run`](Self::run); see also `AGENTS.md` "Solver composition":
///
/// 1. **Eval aggregation.** The outer must roll the inner's
/// [`State::cost_evals`](crate::core::state::State::cost_evals) (and
/// [`GradientState::gradient_evals`](crate::core::state::GradientState::gradient_evals)
/// when both inner and outer states are
/// [`GradientState`](crate::core::state::GradientState)) into the
/// outer state via the `increment_*_evals` setters. Otherwise
/// `MaxCostEvals` budgets and the public `result.cost_evals()` lie.
/// See the [`Solver::next_iter`](crate::core::solver::Solver::next_iter)
/// contract for the canonical wording.
///
/// 2. **Criteria statelessness across calls.** Criteria registered with
/// [`terminate_on`](Self::terminate_on) live for the whole lifetime of
/// the `InnerExecutor` and are reused on every [`run`](Self::run)
/// call. They MUST be stateless across runs — fine for
/// [`MaxIter`](crate::core::termination::MaxIter),
/// [`GradientTolerance`](crate::core::termination::GradientTolerance),
/// and [`MaxCostEvals`](crate::core::termination::MaxCostEvals); *not*
/// fine for
/// [`MaxTime`](crate::core::termination::MaxTime), whose internal
/// `start` instant carries across calls and would fire prematurely on
/// later runs. If you need per-run criteria, build a fresh
/// `InnerExecutor` each call (or call
/// [`run_loop`](crate::core::executor::run_loop) directly with a
/// fresh `Vec`).
///
/// 3. **Failure routing.** [`run`](Self::run) returns a full
/// [`OptimizationResult`]; classify the reason. Use
/// [`TerminationReason::is_failure`](crate::core::termination::TerminationReason::is_failure)
/// to decide whether to bubble: `SolverFailed` should bubble via the
/// outer's mid-iter `Option<TerminationReason>` return; everything
/// else (`MaxIter`, `*Tolerance`, `SolverConverged`) is a "clean stop"
/// the outer can consume and continue past.