Skip to main content

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