gam_models/fit_orchestration/drivers/mod.rs
1// GAM fit-orchestration drivers, relocated from `gam-terms/src/smooth/`
2// (`design_construction.rs` + `spatial_optimization.rs`) up into `gam-models`
3// per #1521. They were `include!`d into `gam_terms::smooth` (one flat module
4// alongside `prelude.rs` + `term_specs.rs`); to preserve that single-module
5// flat namespace (and the heavy cross-references between the two files) byte
6// for byte, they are `include!`d here as well. The shared import surface that
7// `prelude.rs`/`term_specs.rs` used to provide is reconstructed below with the
8// relocated paths (families now resolve as `crate::*`, the solver as
9// `gam_solve::*`, basis/term machinery as `gam_terms::*`).
10use gam_terms::basis::{
11 BasisError, BasisMetadata, BasisPsiDerivativeResult, BasisPsiSecondDerivativeResult,
12 BasisWorkspace, CenterStrategy, MaternIdentifiability, PenaltySource,
13 build_constant_curvature_basis_kappa_derivatives,
14 build_matern_basis_log_kappa_aniso_derivatives, build_matern_basis_log_kappa_derivatives,
15 build_matern_collocation_operator_matrices, build_measure_jet_basis_psi_derivatives,
16 build_thin_plate_basis_log_kappa_derivatives, estimate_penalty_nullity,
17 initial_aniso_contrasts,
18};
19
20use gam_custom_family::{
21 BlockEffectiveJacobian, BlockGeometryDirectionalDerivative, BlockWorkingSet,
22 BlockwiseFitOptions, CustomFamily, CustomFamilyBlockPsiDerivative, CustomFamilyOwnedMode,
23 CustomFamilyWarmStart, ExactNewtonOuterObjective, FamilyEvaluation, FamilyLinearizationState,
24 ParameterBlockSpec, ParameterBlockState, PenaltyMatrix,
25 evaluate_custom_family_joint_hyper_efs_owned, evaluate_custom_family_joint_hyper_owned,
26 fit_custom_family, fit_custom_family_fixed_log_lambdas_from_owned_mode,
27};
28
29use gam_solve::estimate::{
30 EstimationError, ExternalOptimOptions, FitInference, FitOptions, FittedLinkState, PenaltySpec,
31 UnifiedFitResult, UnifiedFitResultParts, fit_gamwith_heuristic_lambdas,
32};
33
34use gam_solve::estimate::reml::DirectionalHyperParam;
35
36// #1521: `freeze_term_collection_from_design` relocated DOWN into gam_terms::smooth
37// (was an `include!`d `pub fn` in spatial_optimization.rs). Re-export here so the
38// `crate::fit_orchestration::drivers::freeze_term_collection_from_design` path used
39// by families + pyffi resolves unchanged.
40pub use gam_terms::smooth::freeze_term_collection_from_design;
41
42use gam_solve::mixture_link::{
43 inverse_link_jet_for_inverse_link, logit_inverse_link_jet5, state_from_beta_logisticspec,
44 state_from_sasspec, state_fromspec,
45};
46
47use gam_math::quantile::quantile_from_sorted;
48
49use gam_linalg::faer_ndarray::{fast_ab, fast_atb, fast_atv};
50
51use gam_linalg::matrix::{DesignBlock, DesignMatrix, RandomEffectOperator, SymmetricMatrix};
52
53use gam_problem::{ConstraintSet, ExactNewtonJointPsiTerms, LinearInequalityConstraints};
54
55use gam_spec::{
56 InverseLink, LatentCLogLogState, LikelihoodSpec, MixtureLinkState, ResponseFamily,
57 SasLinkState, StandardLink,
58};
59
60use gam_terms::smooth::penalty_priors::{
61 realize_keyed_penalty_block_gamma_priors, realize_penalty_block_gamma_priors,
62};
63
64use gam_terms::smooth::shape_constraints::{
65 linear_constraints_from_lower_bounds_global, merge_linear_constraints_global,
66 shape_lower_bounds_local,
67};
68
69// Every `pub` item that `gam_terms::smooth` exposes (the `term_specs.rs`
70// spec/design machinery, `SmoothError`, the `penalty_priors`/`structure_analysis`
71// re-exports, …). This reconstructs the sibling-module visibility the drivers
72// had while textually pasted inside `gam_terms::smooth`.
73use gam_terms::smooth::*;
74
75use ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis, s};
76
77use std::collections::BTreeSet;
78use std::ops::Range;
79use std::sync::atomic::AtomicUsize;
80use std::sync::{Arc, Mutex};
81
82// Fit-result carriers relocated out of `gam_terms::smooth::term_specs` with the
83// drivers (they hold a `gam_solve` `UnifiedFitResult` and are consumed only by
84// the drivers / the surrounding fit-orchestration layer).
85#[derive(Clone)]
86pub struct FittedTermCollection {
87 pub fit: UnifiedFitResult,
88 pub design: TermCollectionDesign,
89 pub adaptive_diagnostics: Option<AdaptiveRegularizationDiagnostics>,
90}
91
92#[derive(Clone, Copy, Debug, Default)]
93pub struct SpatialLengthScaleOptimizationTiming {
94 pub log_kappa_dim: usize,
95 pub cost_calls: usize,
96 pub cost_total_s: f64,
97 pub eval_calls: usize,
98 pub eval_total_s: f64,
99 pub efs_calls: usize,
100 pub efs_total_s: f64,
101 pub slow_path_resets: u64,
102 pub design_revision_delta: u64,
103 /// #1868 deterministic n-independence instrument: the number of length-`n`
104 /// row-element touches the Gaussian zero-iteration inner synthesis performed
105 /// on the #1033 n-free κ-trial *skip* path during this κ-optimisation phase
106 /// (excludes the one-time priming eval). The #1033 architectural invariant
107 /// requires each in-window trial to touch only k×k objects, so this MUST NOT
108 /// scale with `n`. A value that grows with `n` is exactly the #1868
109 /// O(n)-per-callback regression. This replaces the old noisy wall-clock
110 /// per-callback ratio with an exact, millisecond-fast integer gate.
111 pub nfree_skip_row_touches: u64,
112 pub nfree_miss_shape: u64,
113 pub nfree_miss_value: u64,
114 pub nfree_miss_gradient: u64,
115 pub nfree_miss_penalty: u64,
116 pub nfree_miss_revision: u64,
117 pub nfree_miss_second_order: u64,
118 pub nfree_miss_other: u64,
119 pub optim_total_s: f64,
120}
121
122impl SpatialLengthScaleOptimizationTiming {
123 pub fn trial_total_s(self) -> f64 {
124 self.cost_total_s + self.eval_total_s + self.efs_total_s
125 }
126}
127
128#[derive(Clone)]
129pub struct FittedTermCollectionWithSpec {
130 pub fit: UnifiedFitResult,
131 pub design: TermCollectionDesign,
132 pub resolvedspec: TermCollectionSpec,
133 pub adaptive_diagnostics: Option<AdaptiveRegularizationDiagnostics>,
134 pub kappa_timing: Option<SpatialLengthScaleOptimizationTiming>,
135}
136
137include!("design_construction.rs");
138include!("spatial_optimization.rs");
139
140#[cfg(test)]
141mod test_support {
142 use super::*;
143
144 /// Test-only default-policy constructor. Production callers must supply the
145 /// fit's intrinsic resource policy through `new_with_policy`; keeping this
146 /// adapter inside the test-support module prevents a permissive constructor
147 /// from entering the library surface.
148 pub(super) trait SingleBlockExactJointDesignCacheTestExt<'d>: Sized {
149 fn new(
150 data: ArrayView2<'d, f64>,
151 spec: TermCollectionSpec,
152 design: TermCollectionDesign,
153 spatial_terms: Vec<usize>,
154 rho_dim: usize,
155 dims_per_term: Vec<usize>,
156 ) -> Result<Self, String>;
157 }
158
159 impl<'d> SingleBlockExactJointDesignCacheTestExt<'d> for SingleBlockExactJointDesignCache<'d> {
160 fn new(
161 data: ArrayView2<'d, f64>,
162 spec: TermCollectionSpec,
163 design: TermCollectionDesign,
164 spatial_terms: Vec<usize>,
165 rho_dim: usize,
166 dims_per_term: Vec<usize>,
167 ) -> Result<Self, String> {
168 let policy = gam_runtime::resource::ResourcePolicy::default_library();
169 Self::new_with_policy(
170 data,
171 spec,
172 design,
173 spatial_terms,
174 rho_dim,
175 dims_per_term,
176 &policy,
177 )
178 }
179 }
180}
181
182// #901 re-home: the end-to-end iso-κ joint REML outer-gradient FD oracles on
183// real Duchon/Matérn smooths. Authored in the pre-#1521 monolith, orphaned out
184// of the build by #1601 (its private driver deps live HERE post-carve, not in
185// `gam_terms::smooth` where the `include!` was commented out). The file is a
186// self-contained `#[cfg(test)] mod`, so it adds nothing to the non-test build.
187include!("iso_kappa_reml_gradient_fd_tests.rs");
188// #901 re-home: the Matérn κ-optimizer convergence/monotone gates the issue
189// listed as stalling on the wrong projected-logdet gradient. Same #1601
190// orphaning story — driver deps live HERE post-carve. Self-contained
191// `#[cfg(test)] mod`, so it adds nothing to the non-test build.
192include!("spatial_length_scale_monotone_tests.rs");
193// #1264/#1033 re-home: the production ψ-Gram fast-path skip guard
194// (`reduced_basis_equal` soundness, β̂ vs streamed to 1e-6) and the #1033
195// forced-rotation frontier measurement. Same #1601 orphaning story as the two
196// siblings above — its private driver deps live HERE post-carve, and the
197// monolith `include!` in `gam_terms::smooth::tests` was commented out and never
198// relocated, so both guards compiled into NO binary. Self-contained
199// `#[cfg(test)] mod`, so it adds nothing to the non-test build.
200include!("psi_gram_tensor_fast_path_tests.rs");
201// #901 re-home: the custom-family ADAPTIVE-ψ projected-logdet REML
202// hypergradient + outer-Hessian FD oracle on a real `SpatialAdaptiveExactFamily`
203// — the half of #901 the engine fix (joint_jeffreys_information_depends_on_psi)
204// directly targets, plus the #426 unified-dispatch parity pin. Same #1601
205// orphaning story as the two oracles above; driver deps live HERE post-carve.
206// Self-contained `#[cfg(test)] mod`, so it adds nothing to the non-test build.
207include!("spatial_adaptive_hyper_fd_tests.rs");
208// #1274 re-home: the Matérn n-free penalty re-key topology/byte-identity gates.
209// Authored in the pre-#1521 monolith under `tests/src_modules/smooths/`, they
210// were orphaned by #1601 (the `gam_terms::smooth::tests` `include!` was
211// commented out and the body needs the gam-models-private
212// `FrozenTermCollectionIncrementalRealizer`), so the #1274 guard compiled
213// nowhere. Re-homed HERE where the private realizer lives; self-contained
214// `#[cfg(test)] mod`, so it adds nothing to the non-test build.
215include!("matern_nfree_rekey_topology_tests.rs");
216// #1601 relocation debt: the 88 design-assembly / constraint / IFT-cache
217// regression guards. Same orphaning story as the siblings above — their
218// `build_term_collection_design` / freeze / incremental-realizer / tensor+streamed
219// eval deps live HERE post-#1521 carve, but #1601 commented the include! out of
220// `gam_terms::smooth::tests` "for relocation" that never happened (the parked
221// `tests/src_modules/` tree was `mod`'d into no binary). Self-contained
222// `#[cfg(test)] mod`.
223include!("design_assembly_constraint_tests.rs");
224// #1601 relocation debt: the LAST of the three orphaned smooth test files — 48
225// adaptive / bounded / pure-Duchon / Charbonnier regression guards. Same story:
226// commented out of `gam_terms::smooth::tests` by #1601 "for relocation" and
227// parked in the `tests/src_modules/` tree that compiled into no binary. Re-homed
228// here where its `build_term_collection_design` / freeze / SAS-link-state /
229// joint-hyper FD deps resolve post-#1521 carve. Self-contained `#[cfg(test)] mod`.
230include!("adaptive_bounded_duchon_tests.rs");
231
232// #2425 Half-A instrumentation. Measurement-only probes (they print numbers and
233// assert only that the measurement completed), kept in-tree so the next lane
234// does not have to rebuild this crate to re-derive them. Self-contained
235// `#[cfg(test)] mod`, so it adds nothing to the non-test build.
236include!("zz_measure_2425_kappa_tests.rs");
237
238// #2450 criterion-identity instrumentation. A PAIRED A/B over `rho_prior` at
239// one SHA: same data, same seed, same spec, only the prior varies, so the
240// reported difference is the criterion own bias. Measurement-only, same
241// contract as the probe above. Self-contained `#[cfg(test)] mod`.
242include!("zz_measure_2450_rho_prior_criterion_tests.rs");