Skip to main content

gam_terms/basis/
measure_jet_predict.rs

1//! Predict-side measure-jet honesty: the closed-form extrapolation variance
2//! from the frame notes (`docs/measure_jet_frame.md` §5).
3//!
4//! The current Gaussian representers decay off-support toward the parametric
5//! backbone with small posterior variance — confident reversion, which the
6//! honesty contract forbids. The structural fix prices ignorance off the web from the SAME
7//! fitted spectrum that smooths on it: every band level ℓ carries a fitted
8//! amplitude λ̂_ℓ (prior precision of the level's innovations), and a query
9//! that the level-ℓ kernel mass does not cover simply has an UNKNOWN level-ℓ
10//! innovation — prior variance λ̂_ℓ⁻¹, collected in full.
11//!
12//! # The formula (and its algebraic relation to §5)
13//!
14//! With `q̄_ℓ = (Σ_i m_i q_ℓ(c_i)) / (Σ_i m_i)` the web-averaged scale-ℓ
15//! support and `a_ℓ(x★) = min(q_ℓ(x★)/q̄_ℓ, 1)` the scale-correct on-web-ness
16//! weight in `[0, 1]`, let
17//! `ℓ★ = min{ℓ : q_ℓ(x★) ≥ coverage_floor · q̄_ℓ}` be the first covering
18//! level (ε★ = ε_{ℓ★}). Then, for per-level spectra,
19//!
20//! ```text
21//!   Var_extrap(x★) = Σ_{ℓ < ℓ★} λ̂_ℓ⁻¹  +  Σ_{ℓ ≥ ℓ★} (1 − a_ℓ(x★)) · λ̂_ℓ⁻¹
22//!                  = Σ_ℓ λ̂_ℓ⁻¹  −  Σ_{ℓ: ε_ℓ ≥ ε★} a_ℓ(x★) · λ̂_ℓ⁻¹ .
23//! ```
24//!
25//! The second line is the §5 statement: the total prior ignorance of the
26//! spectrum minus the part the query's coverage recovers — the recovered sum
27//! runs over the covered levels `ε_ℓ ≥ ε★` exactly as written in the charter.
28//! In fused mode the band has one precision, so the same coverage idea reduces
29//! to one charge: `λ_fused⁻¹` if no level clears its floor, otherwise
30//! `(1 − max_ℓ a_ℓ(x★)) · λ_fused⁻¹`.
31//! On-web queries (ε★ = ε_0, a_ℓ ≈ 1 everywhere) recover the full spectrum
32//! and pay ≈ 0 extra; far-off queries recover (almost) nothing and pay the
33//! full Σ_ℓ λ̂_ℓ⁻¹. Levels FINER than the first covering scale get no credit
34//! for stray sub-floor kernel mass: below ε★ the prediction is a jet
35//! extension, not an interpolation, so those innovations are charged as pure
36//! ignorance.
37//!
38//! # Never-covered convention
39//!
40//! If no band level clears the coverage floor (ε★ lies past the band), the
41//! covered set is EMPTY: in per-level mode every level contributes its full
42//! λ̂_ℓ⁻¹ and `Var_extrap = Σ_ℓ λ̂_ℓ⁻¹`; in fused mode the single band
43//! amplitude contributes once. The variance saturates at the spectrum's total
44//! prior ignorance instead of growing without bound, which is the honest
45//! statement: the model's coefficient prior is the only information it ever
46//! claimed about such a point.
47//!
48//! # Monotonicity (the distance-honesty theorem)
49//!
50//! Claim: if `q ≤ q′` pointwise (the support row of the farther query is
51//! nowhere larger), then `Var_extrap(q) ≥ Var_extrap(q′)`.
52//!
53//! Proof. `{ℓ : q_ℓ ≥ coverage_floor · q̄_ℓ} ⊆
54//! {ℓ : q′_ℓ ≥ coverage_floor · q̄_ℓ}` for the scale-specific floors, so
55//! `ℓ★(q) ≥ ℓ★(q′)`. Compare the per-level weights `w_ℓ`:
56//! - `ℓ < ℓ★(q′)`: both weights are 1;
57//! - `ℓ ≥ ℓ★(q)`: `w_ℓ(q) = 1 − a_ℓ(q) ≥ 1 − a_ℓ(q′) = w_ℓ(q′)`;
58//! - `ℓ★(q′) ≤ ℓ < ℓ★(q)`: `w_ℓ(q) = 1 ≥ 1 − a_ℓ(q′) = w_ℓ(q′)`.
59//! Every weight is no smaller and every `λ̂_ℓ⁻¹ > 0`, so the sum is no
60//! smaller. ∎
61//!
62//! Since the Gaussian kernel mass `q_ℓ(x★)` is pointwise nonincreasing as
63//! `x★` recedes from every center simultaneously, intervals widen
64//! monotonically with distance from the web. The ε★ gate introduces the only
65//! discontinuity, and it is bounded: a level crossing the floor changes its
66//! weight by at most `a_ℓ ≤ coverage_floor`, so the total jump is at most
67//! `coverage_floor · Σ_ℓ λ̂_ℓ⁻¹` and vanishes as the floor tightens.
68//!
69//! # Units
70//!
71//! The result is on the scale of physical `λ̂⁻¹`: callers must unnormalize the
72//! fitted Frobenius-normalized precision first (`λ_phys = λ_tilde / c`). Family
73//! dispersion scaling remains outside this pure spectrum-side kernel.
74
75use ndarray::ArrayView1;
76
77use super::BasisError;
78
79#[derive(Clone, Copy)]
80pub enum MeasureJetExtrapolationSpectrum<'a> {
81    /// One physical precision per band level.
82    PerLevel(&'a [f64]),
83    /// One physical precision for the fused band. It is charged once, with the
84    /// band's best coverage fraction.
85    Fused(f64),
86}
87
88/// Frame-note §5: closed-form extrapolation variance at a query — the price of
89/// ignorance off the web, read from the fitted spectrum. `ε★` = the first
90/// covering scale (smallest band scale at which the query's kernel mass
91/// clears `coverage_floor` × `q̄_ℓ`); levels finer than `ε★` contribute
92/// their full prior variance `λ̂_ℓ⁻¹`, levels from `ε★` up contribute the
93/// uncovered fraction `(1 − a_ℓ(x★)) · λ̂_ℓ⁻¹` with
94/// `a_ℓ(x★) = min(q_ℓ(x★)/q̄_ℓ, 1)` the smooth on-web-ness weight.
95/// Equivalently (see the module docs) the total prior ignorance
96/// `Σ_ℓ λ̂_ℓ⁻¹` minus the §5 coverage-recovered sum
97/// `Σ_{ℓ: ε_ℓ ≥ ε★} a_ℓ(x★)/λ̂_ℓ`. On-web queries (ε★ = ε_0, a ≈ 1) pay
98/// ≈ 0 extra; queries never covered by the band pay `Σ_ℓ λ̂_ℓ⁻¹` exactly —
99/// intervals widen monotonically with distance (theorem in the module docs).
100///
101/// Inputs: `support_row` = `q_ℓ(x★)` per band scale (one row of
102/// [`super::measure_jet_support_curve`]), `eps_band` the realized ascending
103/// band, `support_means` = `q̄_ℓ` per band scale, `spectrum` the physical
104/// precision spectrum, `coverage_floor` ∈ (0, 1) (e.g. 0.05).
105pub fn measure_jet_extrapolation_variance(
106    support_row: ArrayView1<'_, f64>,
107    eps_band: &[f64],
108    support_means: &[f64],
109    spectrum: MeasureJetExtrapolationSpectrum<'_>,
110    coverage_floor: f64,
111) -> Result<f64, BasisError> {
112    let n_levels = eps_band.len();
113    if n_levels == 0 {
114        crate::bail_invalid_basis!("measure-jet extrapolation variance needs a nonempty band");
115    }
116    if support_row.len() != n_levels || support_means.len() != n_levels {
117        crate::bail_dim_basis!(
118            "measure-jet extrapolation variance needs one support value and one support mean per \
119             band scale: {} support values, {} support means, {} scales",
120            support_row.len(),
121            support_means.len(),
122            n_levels
123        );
124    }
125    for (l, pair) in eps_band.windows(2).enumerate() {
126        if pair[1] <= pair[0] {
127            crate::bail_invalid_basis!(
128                "measure-jet band must be strictly ascending: eps[{l}] = {} vs eps[{}] = {}",
129                pair[0],
130                l + 1,
131                pair[1]
132            );
133        }
134    }
135    if eps_band.iter().any(|e| !(e.is_finite() && *e > 0.0)) {
136        crate::bail_invalid_basis!("measure-jet band scales must be finite and positive");
137    }
138    if support_row.iter().any(|q| !(q.is_finite() && *q >= 0.0)) {
139        crate::bail_invalid_basis!(
140            "measure-jet support row must be finite and nonnegative (kernel masses)"
141        );
142    }
143    if support_means.iter().any(|q| !(q.is_finite() && *q > 0.0)) {
144        crate::bail_invalid_basis!("measure-jet support means must be finite and positive");
145    }
146    if !(coverage_floor.is_finite() && coverage_floor > 0.0 && coverage_floor < 1.0) {
147        crate::bail_invalid_basis!(
148            "measure-jet coverage floor must lie strictly in (0, 1); got {coverage_floor}"
149        );
150    }
151    match spectrum {
152        MeasureJetExtrapolationSpectrum::PerLevel(lambda_hat) => {
153            if lambda_hat.len() != n_levels {
154                crate::bail_dim_basis!(
155                    "measure-jet per-level extrapolation variance needs one physical precision per \
156                     band scale: {} precisions, {} scales",
157                    lambda_hat.len(),
158                    n_levels
159                );
160            }
161            if lambda_hat.iter().any(|l| !(l.is_finite() && *l > 0.0)) {
162                crate::bail_invalid_basis!(
163                    "measure-jet per-scale amplitudes must be finite and positive (physical precisions)"
164                );
165            }
166            let first_covering = support_row
167                .iter()
168                .zip(support_means.iter())
169                .position(|(q, q_bar)| *q >= coverage_floor * *q_bar)
170                .unwrap_or(n_levels);
171            let mut variance = 0.0_f64;
172            for (l, ((&q, &q_bar), &lam)) in support_row
173                .iter()
174                .zip(support_means.iter())
175                .zip(lambda_hat.iter())
176                .enumerate()
177            {
178                let weight = if l < first_covering {
179                    1.0
180                } else {
181                    1.0 - (q / q_bar).min(1.0)
182                };
183                variance += weight / lam;
184            }
185            Ok(variance)
186        }
187        MeasureJetExtrapolationSpectrum::Fused(lambda_hat) => {
188            if !(lambda_hat.is_finite() && lambda_hat > 0.0) {
189                crate::bail_invalid_basis!(
190                    "measure-jet fused amplitude must be finite and positive (physical precision)"
191                );
192            }
193            let mut best_coverage = 0.0_f64;
194            let mut covered = false;
195            for (&q, &q_bar) in support_row.iter().zip(support_means.iter()) {
196                let coverage = (q / q_bar).min(1.0);
197                best_coverage = best_coverage.max(coverage);
198                if q >= coverage_floor * q_bar {
199                    covered = true;
200                }
201            }
202            let weight = if covered { 1.0 - best_coverage } else { 1.0 };
203            Ok(weight / lambda_hat)
204        }
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use ndarray::{Array1, arr1};
212
213    /// Shared deterministic fixture: a 5-level dyadic band with a
214    /// non-constant fitted spectrum.
215    pub(crate) fn band() -> Vec<f64> {
216        vec![0.05, 0.1, 0.2, 0.4, 0.8]
217    }
218
219    pub(crate) fn lambdas() -> Vec<f64> {
220        vec![40.0, 11.0, 3.5, 1.25, 0.6]
221    }
222
223    pub(crate) fn support_means(eps: &[f64]) -> Vec<f64> {
224        vec![TOTAL; eps.len()]
225    }
226
227    pub(crate) const FLOOR: f64 = 0.05;
228    pub(crate) const TOTAL: f64 = 1.0;
229
230    pub(crate) fn total_ignorance(lams: &[f64]) -> f64 {
231        lams.iter().map(|l| 1.0 / l).sum()
232    }
233
234    /// The exact single-unit-mass support curve at distance `d`:
235    /// q_ℓ(d) = total · exp(−d²/(2ε_ℓ²)) — the physical family the support
236    /// diagnostic produces for a one-center web.
237    pub(crate) fn support_at_distance(d: f64, eps: &[f64]) -> Array1<f64> {
238        Array1::from_iter(eps.iter().map(|e| TOTAL * (-d * d / (2.0 * e * e)).exp()))
239    }
240
241    /// (a) Monotone in distance: along the exact kernel-mass family the
242    /// support row is pointwise nonincreasing in d, so the variance must be
243    /// nondecreasing — including across every coverage-floor crossing in the
244    /// sweep.
245    #[test]
246    pub(crate) fn extrapolation_variance_is_monotone_in_distance() {
247        let eps = band();
248        let lams = lambdas();
249        let q_bar = support_means(&eps);
250        let mut prev = -1.0_f64;
251        // 0 → 6 in steps of 0.015: spans on-web through far-off, crossing
252        // the floor at every band level along the way.
253        for step in 0..400 {
254            let d = 0.015 * step as f64;
255            let row = support_at_distance(d, &eps);
256            let v = measure_jet_extrapolation_variance(
257                row.view(),
258                &eps,
259                &q_bar,
260                MeasureJetExtrapolationSpectrum::PerLevel(&lams),
261                FLOOR,
262            )
263            .expect("valid inputs");
264            assert!(
265                v >= prev,
266                "variance decreased with distance: variance({d:.3}) = {v:.12} < {prev:.12}"
267            );
268            prev = v;
269        }
270        // And the saturation: the far end of the sweep reaches the full
271        // prior ignorance (never-covered convention).
272        assert!(
273            (prev - total_ignorance(&lams)).abs() <= 1e-12,
274            "far-field variance must saturate at Σ 1/λ̂: got {prev}"
275        );
276    }
277
278    /// (a′) Pointwise domination, no geometric family assumed: a support row
279    /// that is pointwise smaller never yields smaller variance — exercised
280    /// on a NON-monotone-in-ℓ row pair as well.
281    #[test]
282    pub(crate) fn extrapolation_variance_is_monotone_under_pointwise_domination() {
283        let eps = band();
284        let lams = lambdas();
285        let q_bar = support_means(&eps);
286        let rows = [
287            arr1(&[0.9, 0.95, 0.99, 1.0, 1.0]),
288            arr1(&[0.02, 0.3, 0.06, 0.8, 0.97]),
289            arr1(&[0.0, 0.0, 0.04, 0.2, 0.6]),
290            arr1(&[0.04, 0.04, 0.04, 0.04, 0.049]),
291        ];
292        for row in &rows {
293            for shrink in [1.0, 0.9, 0.7, 0.3, 0.0] {
294                let smaller = row.mapv(|q| shrink * q);
295                let v_big = measure_jet_extrapolation_variance(
296                    row.view(),
297                    &eps,
298                    &q_bar,
299                    MeasureJetExtrapolationSpectrum::PerLevel(&lams),
300                    FLOOR,
301                )
302                .expect("valid inputs");
303                let v_small = measure_jet_extrapolation_variance(
304                    smaller.view(),
305                    &eps,
306                    &q_bar,
307                    MeasureJetExtrapolationSpectrum::PerLevel(&lams),
308                    FLOOR,
309                )
310                .expect("valid inputs");
311                assert!(
312                    v_small >= v_big,
313                    "pointwise-smaller support gave smaller variance: {v_small} < {v_big} \
314                     (row {row:?}, shrink {shrink})"
315                );
316            }
317        }
318    }
319
320    /// (b) On-web limit: full kernel mass at every scale prices ZERO extra
321    /// variance; near-full mass prices at most the uncovered fraction of the
322    /// total prior ignorance.
323    #[test]
324    pub(crate) fn extrapolation_variance_vanishes_on_web() {
325        let eps = band();
326        let lams = lambdas();
327        let q_bar = support_means(&eps);
328        let full = Array1::from_elem(eps.len(), TOTAL);
329        let v_full = measure_jet_extrapolation_variance(
330            full.view(),
331            &eps,
332            &q_bar,
333            MeasureJetExtrapolationSpectrum::PerLevel(&lams),
334            FLOOR,
335        )
336        .expect("valid inputs");
337        assert_eq!(v_full, 0.0, "full coverage must price zero extra variance");
338
339        let near = Array1::from_elem(eps.len(), 0.97 * TOTAL);
340        let v_near = measure_jet_extrapolation_variance(
341            near.view(),
342            &eps,
343            &q_bar,
344            MeasureJetExtrapolationSpectrum::PerLevel(&lams),
345            FLOOR,
346        )
347        .expect("valid inputs");
348        let budget = total_ignorance(&lams);
349        assert!(
350            v_near <= 0.05 * budget,
351            "near-full coverage must price a small fraction of Σ 1/λ̂: {v_near} vs budget {budget}"
352        );
353    }
354
355    /// (c) Off-web limit: zero support everywhere (never covered) collects
356    /// the spectrum's total prior ignorance Σ 1/λ̂ EXACTLY.
357    #[test]
358    pub(crate) fn extrapolation_variance_saturates_off_web() {
359        let eps = band();
360        let lams = lambdas();
361        let q_bar = support_means(&eps);
362        let zero = Array1::<f64>::zeros(eps.len());
363        let v = measure_jet_extrapolation_variance(
364            zero.view(),
365            &eps,
366            &q_bar,
367            MeasureJetExtrapolationSpectrum::PerLevel(&lams),
368            FLOOR,
369        )
370        .expect("valid inputs");
371        assert_eq!(
372            v,
373            total_ignorance(&lams),
374            "never-covered query must pay Σ 1/λ̂ exactly"
375        );
376    }
377
378    /// (d) Spectrum scaling: doubling every fitted amplitude halves the
379    /// variance — the λ̂⁻¹ pricing is exact, in every coverage regime.
380    #[test]
381    pub(crate) fn extrapolation_variance_halves_when_amplitudes_double() {
382        let eps = band();
383        let lams = lambdas();
384        let q_bar = support_means(&eps);
385        let doubled: Vec<f64> = lams.iter().map(|l| 2.0 * l).collect();
386        // Mixed regime: some levels below the floor, some covered partially,
387        // some fully — both weight branches exercised.
388        let rows = [
389            support_at_distance(0.35, &eps),
390            Array1::<f64>::zeros(eps.len()),
391            Array1::from_elem(eps.len(), 0.5),
392        ];
393        for row in &rows {
394            let v1 = measure_jet_extrapolation_variance(
395                row.view(),
396                &eps,
397                &q_bar,
398                MeasureJetExtrapolationSpectrum::PerLevel(&lams),
399                FLOOR,
400            )
401            .expect("valid inputs");
402            let v2 = measure_jet_extrapolation_variance(
403                row.view(),
404                &eps,
405                &q_bar,
406                MeasureJetExtrapolationSpectrum::PerLevel(&doubled),
407                FLOOR,
408            )
409            .expect("valid inputs");
410            assert!(
411                (2.0 * v2 - v1).abs() <= 1e-15 * v1.max(1.0),
412                "doubling λ̂ must halve the variance: {v1} vs 2×{v2}"
413            );
414        }
415    }
416
417    /// Convention pin: the ε★ gate. Sub-floor mass at every level is
418    /// never-covered (full Σ 1/λ̂, no credit for stray mass); the moment ONE
419    /// level clears the floor, that level and every coarser one switch to
420    /// the smooth uncovered-fraction weight while finer levels stay fully
421    /// charged.
422    #[test]
423    pub(crate) fn extrapolation_variance_gate_convention() {
424        let eps = band();
425        let lams = lambdas();
426        let q_bar = support_means(&eps);
427        let sub_floor = Array1::from_elem(eps.len(), 0.049 * TOTAL);
428        let v_sub = measure_jet_extrapolation_variance(
429            sub_floor.view(),
430            &eps,
431            &q_bar,
432            MeasureJetExtrapolationSpectrum::PerLevel(&lams),
433            FLOOR,
434        )
435        .expect("valid inputs");
436        assert_eq!(
437            v_sub,
438            total_ignorance(&lams),
439            "sub-floor mass earns no credit: full Σ 1/λ̂"
440        );
441
442        // Coverage exactly at the floor on the coarsest level only.
443        let mut at_floor = sub_floor.clone();
444        at_floor[eps.len() - 1] = FLOOR * TOTAL;
445        let v_floor = measure_jet_extrapolation_variance(
446            at_floor.view(),
447            &eps,
448            &q_bar,
449            MeasureJetExtrapolationSpectrum::PerLevel(&lams),
450            FLOOR,
451        )
452        .expect("valid inputs");
453        let expected: f64 = lams[..eps.len() - 1].iter().map(|l| 1.0 / l).sum::<f64>()
454            + (1.0 - FLOOR) / lams[eps.len() - 1];
455        assert!(
456            (v_floor - expected).abs() <= 1e-15,
457            "floor-clearing coarsest level must take weight 1 − a: {v_floor} vs {expected}"
458        );
459        // The gate's discontinuity is bounded by the documented
460        // coverage_floor · Σ 1/λ̂ budget.
461        assert!(
462            v_sub - v_floor <= FLOOR * total_ignorance(&lams) + 1e-15,
463            "gate jump exceeds the documented coverage_floor bound"
464        );
465    }
466
467    #[test]
468    pub(crate) fn fused_extrapolation_charges_single_band_amplitude_once() {
469        let eps = band();
470        let q_bar = support_means(&eps);
471        let lam = 2.5;
472        let zero = Array1::<f64>::zeros(eps.len());
473        let v_zero = measure_jet_extrapolation_variance(
474            zero.view(),
475            &eps,
476            &q_bar,
477            MeasureJetExtrapolationSpectrum::Fused(lam),
478            FLOOR,
479        )
480        .expect("valid inputs");
481        assert_eq!(
482            v_zero,
483            1.0 / lam,
484            "never-covered fused band must pay one amplitude, not one per level"
485        );
486
487        let covered = arr1(&[0.01, 0.2, 0.4, 0.75, 0.5]);
488        let v_covered = measure_jet_extrapolation_variance(
489            covered.view(),
490            &eps,
491            &q_bar,
492            MeasureJetExtrapolationSpectrum::Fused(lam),
493            FLOOR,
494        )
495        .expect("valid inputs");
496        let expected = (1.0 - 0.75) / lam;
497        assert!(
498            (v_covered - expected).abs() <= 1e-15,
499            "fused band must use the best covered level once: {v_covered} vs {expected}"
500        );
501    }
502}