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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
//! 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`]; 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 crateProblem;
use crateSolver;
use crate;
use crateTerminationCriterion;
/// Construct a fresh inner-solver [`State`] seeded at a point.
///
/// Implemented by solvers that can serve as the *inner* of a composed
/// solver — one that repeatedly minimizes a wrapped subproblem starting
/// from the current outer iterate (e.g.
/// [`BarrierMethod`](crate::solver::BarrierMethod) and
/// [`AugmentedLagrangianMethod`](crate::solver::AugmentedLagrangianMethod)
/// re-solving their barrier / augmented-Lagrangian subproblem at each
/// continuation step). The outer solver calls [`seed`](Self::seed) to
/// build a private inner state at the warm-start point, drives the inner
/// over it, then reads the refined iterate back via
/// [`State::param`].
///
/// [`seed`](Self::seed) uses the solver's *natural default scale*: there
/// is no outer step-size to track, so a simplex solver picks its own
/// default edge, a Hessian-history solver starts from the identity, and so
/// on. The CMA-flavored, step-size-scaled variant lives on the
/// [`MemeticInner`](crate::solver::MemeticInner) sub-trait, which extends
/// this one.
///
/// # Contract
///
/// **Implementor must:** return a state whose
/// [`State::param`] equals `x` (a fresh
/// seed, not a continuation of any previous solve), so the outer solver's
/// warm start is honored.
/// 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`] 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 [`Problem`] wrapper bumps
/// [`EvalCounts`](crate::core::problem::EvalCounts) on every
/// cost / gradient / residual / Jacobian / Hessian call, and the
/// executor mirrors the per-run delta onto the inner state via
/// [`CountsMirror`]. What the outer must do depends on which problem
/// the inner sees:
///
/// - **Same-problem inner** (the outer passes its own
/// `&mut Problem<P>` to [`run`](Self::run)): the inner's calls bump
/// the *same* wrapper as the outer's, so aggregation happens
/// transparently. No explicit roll-up; the outer state's
/// [`CountsMirror`] impl decides how the counts surface on its
/// [`State::cost_evals`] /
/// [`GradientState::gradient_evals`](crate::core::state::GradientState::gradient_evals).
/// - **Adapter-problem inner** (the outer builds a fresh
/// `Problem::new(adapter)` per outer iter — e.g. the barrier /
/// augmented-Lagrangian methods): after [`run`](Self::run) returns,
/// fold the inner wrapper's counts back into the outer's wrapper via
/// [`EvalCounts::add`](crate::core::problem::EvalCounts::add) on
/// [`Problem::counts_mut`]. Skipping this fold silently corrupts
/// `MaxCostEvals` budgets and the public `result.cost_evals()`.
///
/// See the [`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`] 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.