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
//! Opt-in native model and trial observations for least-squares solvers.
//!
//! Observations belong to the solver, and getters perform no problem callbacks.
//! Fresh initialization resets the sequence. Exact continuation retains it.
//! Each native decision clears the previous batch, so consume observations
//! between steps. An execution-control stop can leave the previous batch intact;
//! sequence numbers distinguish new observations from retained ones.
//!
//! Construction failures and individual subproblem attempts are recorded even
//! when no trial callback follows. Pre-trial events leave `accepted` absent.
//! Failed callbacks use `callback_failed`; an accepted trial whose subsequent
//! Jacobian fails uses `accepted_jacobian_failed` and remains unpublished.
//! Missing operands are absent. Non-finite operands are retained as native
//! scalar values, so readers must not interpret availability as validity.
use crate::core::math::ComponentZip;
use crate::{ConvergenceEvidence, Scalar};
/// One enabled, disabled, or unavailable native convergence comparison.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct LeastSquaresCheck<F: Scalar = f64> {
/// Stable predicate name within the producing solver.
pub name: &'static str,
/// `None` means the criterion is disabled at this observation stage,
/// except for the structural `no_free_parameters` check.
pub tolerance: Option<F>,
/// The solver's actual comparison result; absent for disabled criteria.
pub passed: Option<bool>,
/// Native operands, including failing comparisons when available.
pub evidence: Option<ConvergenceEvidence<F>>,
}
/// A native model check or attempted residual trial, before publication.
///
/// `accepted` records the numerical acceptance decision. A later callback error
/// can prevent publication, so it does not by itself identify a returned point.
/// Missing trial cost means the residual callback did not complete. Native
/// steps can differ from subtraction of rounded callback coordinates.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct LeastSquaresObservation<F: Scalar = f64> {
/// Native operation: `check`, `construction`, `subproblem`, `selection`,
/// `damping_search`, or `trial`. Only `trial` dispatches a residual callback.
pub event: &'static str,
/// Native outcome or failure reason. A solved subproblem is not convergence.
pub outcome: Option<&'static str>,
/// Damping after this operation, including retry and acceptance updates.
pub damping_after: Option<F>,
/// Native step norm when computed, including non-finite arithmetic.
/// LM uses `sqrt(h^T D h)`. Bounded LM uses its Coleman-Li weighted
/// norm (including the feasible fraction on trial rows). TRF uses
/// Euclidean length in scaled free-coordinate units.
pub step_norm: Option<F>,
/// Named native arithmetic operands. Non-finite values remain observable.
pub arithmetic: Vec<(&'static str, F)>,
/// Monotone sequence within one fresh solve.
pub sequence: u64,
/// Whether a trial residual callback was attempted.
pub trial: bool,
/// Objective at the retained base iterate.
pub base_cost: F,
/// Native objective computed from a completed trial residual.
pub trial_cost: Option<F>,
/// Actual model decrease, including the bounded LM curvature correction.
pub actual_reduction: Option<F>,
/// Native predicted model decrease.
pub predicted_reduction: Option<F>,
/// Native safeguarded acceptance ratio.
pub gain_ratio: Option<F>,
/// Native acceptance decision, absent before a completed trial callback.
pub accepted: Option<bool>,
/// Damping used to compute this step, if applicable.
pub damping: Option<F>,
/// Radius before the trial, if applicable.
pub radius_before: Option<F>,
/// Radius after the acceptance update, if applicable.
pub radius_after: Option<F>,
/// Actual native step: LM's `h`, bounded LM's `alpha*h`, or TRF's
/// rounded step in scaled free-coordinate units.
pub step: Vec<F>,
/// Model gradient in the same units as `step`.
pub gradient: Vec<F>,
/// LM's monotone Marquardt diagonal, or bounded LM's squared scaling.
pub diagonal: Vec<F>,
/// TRF's free-coordinate scaling; empty for LM and bounded LM.
pub coordinate_scale: Vec<F>,
/// TRF's free-coordinate indices; empty for LM and bounded LM.
pub free: Vec<usize>,
/// Coleman-Li curvature diagonal, if applicable.
pub curvature: Vec<F>,
/// Number of damped model solves used to obtain this trial, or one for
/// an individual `subproblem` event. These are overlapping views: sum only
/// subproblem events for total work. TRF counts subproblem calls,
/// not iterations inside its secular search.
pub model_solves: u32,
/// Comparisons at this native observation stage.
pub checks: Vec<LeastSquaresCheck<F>>,
}
impl<F: Scalar> LeastSquaresObservation<F> {
pub(crate) fn model(cost: F) -> Self {
Self {
event: "check",
outcome: None,
damping_after: None,
step_norm: None,
arithmetic: Vec::new(),
sequence: 0,
trial: false,
base_cost: cost,
trial_cost: None,
actual_reduction: None,
predicted_reduction: None,
gain_ratio: None,
accepted: None,
damping: None,
radius_before: None,
radius_after: None,
step: Vec::new(),
gradient: Vec::new(),
diagonal: Vec::new(),
coordinate_scale: Vec::new(),
free: Vec::new(),
curvature: Vec::new(),
model_solves: 0,
checks: Vec::new(),
}
}
}
/// Read the most recent native least-squares decision batch without callbacks.
pub trait LeastSquaresDiagnostics<F: Scalar = f64> {
/// Empty when recording is disabled or no native decision has run.
fn least_squares_observations(&self) -> &[LeastSquaresObservation<F>];
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Trace<F: Scalar> {
next: u64,
pub(crate) observations: Vec<LeastSquaresObservation<F>>,
}
impl<F: Scalar> Trace<F> {
pub(crate) fn event(
&mut self,
cost: F,
event: &'static str,
outcome: &'static str,
) -> usize {
let mut row = LeastSquaresObservation::model(cost);
row.event = event;
row.outcome = Some(outcome);
self.push(row)
}
pub(crate) fn clear(&mut self) {
self.observations.clear();
}
pub(crate) fn reset(&mut self) {
self.clear();
self.next = 0;
}
pub(crate) fn push(
&mut self,
mut observation: LeastSquaresObservation<F>,
) -> usize {
self.next += 1;
observation.sequence = self.next;
self.observations.push(observation);
self.observations.len() - 1
}
}
pub(crate) fn vector<V: ComponentZip<F>, F: Scalar>(value: &V) -> Vec<F> {
let mut result = Vec::new();
value.all_zip(value, |x, _| {
result.push(x);
true
});
result
}