Skip to main content

gam_terms/basis/
measure_jet_moments.rs

1//! Measure-jet frame data interface: per-cell frozen-weight polynomial
2//! moment tables with a binomial-shift merge monoid
3//! (`docs/measure_jet_frame.md`, §2 "Data interface: moments or
4//! nothing").
5//!
6//! This module aggregates caller-computed weights into order-0..2 coordinate
7//! moments. Those tables exactly determine polynomial couplings under the
8//! same frozen weights, including the local affine sufficient statistics used
9//! by `measure_jet_smooth.rs`. They do NOT exactly determine Gaussian
10//! transforms at moved kernel centers: support curves, Gaussian Gram entries,
11//! and Gaussian `XᵀWX` products need their own kernel pass or a separately
12//! controlled approximation. Truncation does NOT live here either: the caller
13//! computes the Gaussian weights `w_i` (mass × kernel profile, with whatever
14//! cutoff its explicit `e^{−ρ²/2}` tolerance budget licenses) and this module
15//! only aggregates what it is handed.
16//!
17//! # The monoid
18//!
19//! A table holds, per response channel `g` and per coordinate multi-index
20//! `α` with `|α| ≤ 2`, the centered moment `μ_α = Σ_i w_i g_i (x_i − c)^α`
21//! about the cell reference point `c`. The binomial shift
22//!
23//! ```text
24//!   μ′_α = Σ_{β ≤ α}  C(α, β) (c − c′)^{α−β} μ_β
25//! ```
26//!
27//! re-expresses the same frozen-weight polynomial table about any other
28//! center `c′` exactly as a finite polynomial identity. It does not move the
29//! Gaussian kernel center or recompute weights. Merging two tables with
30//! already-compatible frozen weights is therefore "recenter to a common
31//! reference, add componentwise":
32//! an associative, commutative monoid whose identity is the empty (all-zero)
33//! table at any center. Exact distributed fitting, exact online updates, and
34//! bit-reproducibility under sorted reduction are corollaries of that one
35//! algebraic fact ([`merge_moment_tables`] is a monoid homomorphism from
36//! disjoint row sets under union to tables under ⊕).
37//!
38//! # Determinism / bit-exactness convention (sorted reduction)
39//!
40//! Floating-point addition is commutative but not associative, so the monoid
41//! laws hold algebraically while bit-patterns depend on reduction ORDER.
42//! This module pins one order everywhere:
43//!
44//! - [`accumulate_moment_table`] splits rows into fixed-size chunks
45//!   ([`MEASURE_JET_MOMENT_CHUNK_ROWS`], never derived from thread count),
46//!   accumulates each chunk sequentially in row order, and folds the chunk
47//!   partials sequentially in chunk-index order — the sorted reduction. The
48//!   result is bit-identical across runs, machines, and rayon pool sizes.
49//! - [`recenter_moment_table`] evaluates the shift in ONE fixed expression
50//!   order (documented at the site).
51//! - [`merge_moment_tables`] canonically orients its operands by the
52//!   lexicographic total order on centers (`f64::total_cmp` per coordinate),
53//!   so `a ⊕ b` and `b ⊕ a` execute the SAME instruction stream and are
54//!   bit-identical for arbitrary inputs.
55//!
56//! Cross-GROUPING bit-identity — `(A⊕B)⊕C` vs `A⊕(B⊕C)` — additionally
57//! requires the moment arithmetic itself to be exact; the in-module tests
58//! pin it on dyadic lattices (integer coordinates/channels, dyadic weights),
59//! where every product and sum is exactly representable, and callers
60//! reducing many chunks get run-to-run determinism by folding in chunk-index
61//! order exactly as the accumulator does.
62//!
63//! # 1:1 contract with `assemble_weighted_forms`
64//!
65//! [`jet_sufficient_stats`] reproduces, in closed form from a stored table
66//! whose weights were computed for the same center and scale, exactly the
67//! local-fit quantities the current workhorse
68//! (`measure_jet_smooth.rs::assemble_weighted_forms`) computes from raw
69//! points per (center, scale) block: the kernel mass `q`, the dimensionless
70//! weighted feature mean `a_mean`, the dimensionless slope Gram
71//! `G = Φ̃ᵀWΦ̃/q`, the weighted channel mean `uᵀv`, and the exact-projection
72//! right-hand side `Bᵀv/q` — so the substrate can later replace that
73//! same-center point loop without changing a single number.
74
75use ndarray::{Array1, Array2};
76
77/// The local jet-fit sufficient statistics read off one table — exactly the
78/// per-block quantities `assemble_weighted_forms` (measure_jet_smooth.rs)
79/// computes from raw points when the table weights are frozen at the same
80/// center and scale, reproduced in closed form from stored moments.
81#[derive(Debug, Clone, PartialEq)]
82pub struct MeasureJetJetStats {
83    /// Kernel mass `q = Σ w_i` (unit-channel zeroth moment).
84    pub q: f64,
85    /// Weighted mean of the requested value channel: `uᵀv = m0[ch]/q`.
86    pub mean: f64,
87    /// Dimensionless slope Gram `G = Φ̃ᵀWΦ̃/q = m2[0]/(qε²) − ā·āᵀ` with
88    /// `ā = m1[0]/(qε)` (`Φ` rows are `(x_i − c)/ε`).
89    pub gram: Array2<f64>,
90    /// Local-fit right-hand side `Bᵀv/q = m1[ch]/(qε) − ā·(m0[ch]/q)` — the
91    /// vector the exact weighted affine projection consumes.
92    pub cross: Array1<f64>,
93}
94