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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
//! Read-only side effects fired around the iteration loop.
//!
//! An [`Observe`] implementation watches the run as it happens (logging,
//! progress reporting, recording a trajectory, streaming iterates to a UI)
//! without influencing it. Observers do **not** decide whether to stop; that
//! is the job of [`stop_when`](crate::Executor::stop_when).
//! The two extension points sit side-by-side on
//! [`Executor`](crate::core::executor::Executor):
//!
//! - [`stop_when`](crate::Executor::stop_when):
//! returns `Option<ApplicationStop>`; framework consumes the result and
//! stops the run if `Some`.
//! - [`Observe`]: returns `()`; the executor ignores any side effects on the
//! optimization itself. Failures must be handled inside the observer:
//! the trait returns no logging error for the executor to propagate.
//! Panics propagate normally. An observer may request a clean stop by
//! cancelling a cloned
//! [`CancellationToken`](crate::core::executor::CancellationToken); the
//! executor observes it before the next iteration.
//!
//! Like stopping hooks, observers bind on the minimum
//! [`State`](crate::core::state::State) shape they need (tenet 3): a logger
//! that just wants `iter`/`cost` impls `Observe<S: State>`; a gradient-norm
//! observer impls `Observe<S: GradientState>` and is rejected at compile time
//! when attached to a derivative-free run.
//!
//! # Solver diagnostics
//!
//! [`ObserveSolver<S, So>`](ObserveSolver) also borrows the concrete solver.
//! Register it with [`Executor::observe_solver_with`](crate::Executor::observe_solver_with),
//! or use [`Executor::observe_solver`](crate::Executor::observe_solver) for a
//! closure receiving `(&state, &solver, ObservationEvent)`. State observers
//! and solver observers share the lifecycle below, obey the same modes, and
//! fire in registration order, including when mixed.
//!
//! The solver retains ownership of models and workspace. Observation borrows
//! them without cloning or evaluating the problem. Getters retain their
//! individual availability and time semantics: annealing's
//! [`temperature`](crate::SimulatedAnnealing::temperature) describes the next
//! proposal, while SLSQP's [`stationarity`](crate::Slsqp::stationarity) and
//! [`multipliers`](crate::Slsqp::equality_multipliers) are optional and depend
//! on its current model. Callbacks must not evaluate the problem or mutate
//! solver machinery through interior mutability.
//!
//! Observations belong to the registered executor; they do not include every
//! iteration of an outer solver's inner solves. For applications that already
//! drive a [`Stepper`](crate::Stepper), [`Stepper::solver`](crate::Stepper::solver)
//! exposes the same shared borrow between steps. Retain the final solver with
//! [`Executor::run_with_solver`](crate::Executor::run_with_solver) when only
//! final diagnostics are needed.
//!
//! # Lifecycle
//!
//! Three hooks, called in this order during a run:
//!
//! 1. [`observe_init`](Observe::observe_init) fires once after
//! [`Solver::init`](crate::core::solver::Solver::init) returns and the
//! state's counter mirror is refreshed, before the first termination
//! check. A fresh run shows `iter() == 0`. Exact continuation skips solver
//! initialization and observes the restored iteration and solver instead.
//! 2. [`observe_iter`](Observe::observe_iter) fires after every successfully
//! completed iteration, after the iteration counter is incremented. On
//! the first call of a fresh run the state shows `iter() == 1`. Gated by
//! [`ObserverMode`].
//! 3. [`observe_final`](Observe::observe_final) fires once when the run
//! stops cleanly with a [`TerminationReport`]. The state shows the iter
//! count of the last fully-completed iteration.
//!
//! # Edge cases
//!
//! - **Stopping steps.** A step with `completed: false` leaves the iteration
//! count unchanged and fires only the final hook. A stopping step with
//! `completed: true` increments the count and fires the gated iteration hook
//! followed by the final hook. Both receive the same coherent progress.
//! - **Hard error from the problem.** If `next_iter` returns `Err(_)`, the
//! state is consumed by the failing call and there's nothing to observe;
//! [`observe_final`](Observe::observe_final) does *not* fire and the
//! error propagates out of
//! [`Executor::run`](crate::core::executor::Executor::run) /
//! [`Stepper::step`](crate::core::executor::Stepper::step). Observers
//! that need to react to hard aborts should track liveness themselves.
//! - **Reading the reason from inside observers.** The argument to
//! [`observe_final`](Observe::observe_final) is the
//! [`TerminationReport`]; state types do not carry it.
//!
//! # Starter observers
//!
//! Two zero-dependency observers ship in core, both binding on the minimum
//! [`State`](crate::core::state::State) shape:
//!
//! - [`Report`]: prints a one-line progress report (iter / cost / best) to
//! stderr on each fire.
//! - [`History`]: records the `(iter, cost, best_cost)` trajectory for later
//! plotting or analysis.
//!
//! Heavier integrations (tracing, slog, a TUI) live in satellite crates; a
//! `serde`-gated `CheckpointWriter` (non-wasm) snapshots the state to disk for
//! warm-starting a later run. Solver-aware exact checkpoints live in the
//! sibling [`checkpoint`](crate::core::checkpoint) module.
use crateTerminationReport;
pub use ;
pub use History;
pub use Report;
pub use ;
/// Hooks fired around the iteration loop. See the
/// [module docs](self) for lifecycle and edge cases.
///
/// All three methods default to no-ops, so an implementor fills in only what
/// they need. Bind on the minimum state shape required (`S: State`,
/// `S: GradientState`, `S: SimplexState`, …) so a mismatch with the solver is
/// a compile error rather than a runtime no-op.
/// Read-only observation of progress and solver-owned diagnostics.
///
/// Register an implementation with
/// [`Executor::observe_solver_with`](crate::Executor::observe_solver_with).
/// For a closure, use [`Executor::observe_solver`](crate::Executor::observe_solver).
/// Bind only on the state and solver capabilities the observer needs; neither
/// argument must implement `Clone` or serialization, and both may borrow data.
/// References passed to a callback cannot be retained beyond that callback.
///
/// All hooks default to no-ops. Their ordering, modes, exact continuation, and
/// error behavior match [`Observe`]; see the [module documentation](self).
/// Observers must use existing diagnostic getters without evaluating the
/// problem or mutating solver machinery through interior mutability. Handle
/// logging errors internally. Panics propagate normally. A cloned
/// [`CancellationToken`](crate::CancellationToken) can request a clean stop.
///
/// # Example
///
/// A reusable logger can bind on the minimum state shape while accepting any
/// annealing neighbor and RNG type:
///
/// ```
/// use basin::{ObserveSolver, SimulatedAnnealing, State};
///
/// struct TemperatureLogger;
/// impl<S, N, R> ObserveSolver<S, SimulatedAnnealing<N, f64, R>>
/// for TemperatureLogger
/// where
/// S: State,
/// {
/// fn observe_iter(&mut self, state: &S, solver: &SimulatedAnnealing<N, f64, R>) {
/// println!("{}: next temperature = {}", state.iter(), solver.temperature());
/// }
/// }
/// ```
/// The boundary passed to an [`Executor::observe_solver`](crate::Executor::observe_solver)
/// callback. See the [observer lifecycle](self#lifecycle).
/// Per-registration policy for [`observe_iter`](Observe::observe_iter).
///
/// Every variant gates the iteration callback only: `observe_init` and
/// `observe_final` always fire. A user who wants to fully disable an observer
/// should simply not register it. The same policy applies to [`ObserveSolver`]
/// and [`ObservationEvent::Iter`] callbacks.