Skip to main content

gam_model_kernels/
cubic_cell_kernel.rs

1use gam_math::probability::normal_cdf;
2use gam_runtime::resource::{ByteLruCache, ResidentBytes};
3use smallvec::{SmallVec, smallvec};
4use std::hash::{Hash, Hasher};
5use std::sync::Arc;
6use std::sync::atomic::{AtomicU64, Ordering};
7
8/// Typed errors raised by the de-nested cubic transport kernel.
9///
10/// Sibling families (`bernoulli_marginal_slope`, `survival_marginal_slope`,
11/// `marginal_slope_shared`) currently consume the kernel's public surface via
12/// `Result<_, String>`. To stay source-compatible, the kernel converts errors
13/// to `String` at the boundary via `From<CubicCellKernelError> for String` and
14/// keeps the public function signatures returning `Result<_, String>`.
15/// `Display` is exact-byte-equivalent to the previous `format!(...)` strings.
16#[derive(Clone, Debug)]
17pub enum CubicCellKernelError {
18    /// Interval probe / cell-bounds preconditions (ordered bounds, supported
19    /// infinity patterns, positive finite width).
20    InvalidInterval { reason: String },
21    /// Cell-shape / branch-classification failure: tail cells not affine,
22    /// finite cells with non-positive width, non-finite affine coefficients,
23    /// non-affine cell with infinite bounds, leading-coefficient degeneracy
24    /// in the moment recurrence, etc.
25    InvalidCellShape { reason: String },
26    /// Reduced moment vector (or polynomial-convolution scratch) is shorter
27    /// than the polynomial degree the leaf needs to evaluate.
28    InsufficientMoments { reason: String },
29    /// Bivariate-normal CDF domain validation (non-finite/non-infinite
30    /// argument, non-finite correlation).
31    BivariateNormalDomain { reason: String },
32}
33
34impl_reason_error_boilerplate! {
35    CubicCellKernelError {
36        InvalidInterval,
37        InvalidCellShape,
38        InsufficientMoments,
39        BivariateNormalDomain,
40    }
41}
42
43impl CubicCellKernelError {
44    #[inline]
45    fn invalid_interval(reason: impl Into<String>) -> Self {
46        CubicCellKernelError::InvalidInterval {
47            reason: reason.into(),
48        }
49    }
50    #[inline]
51    fn invalid_cell_shape(reason: impl Into<String>) -> Self {
52        CubicCellKernelError::InvalidCellShape {
53            reason: reason.into(),
54        }
55    }
56    #[inline]
57    fn insufficient_moments(reason: impl Into<String>) -> Self {
58        CubicCellKernelError::InsufficientMoments {
59            reason: reason.into(),
60        }
61    }
62    #[inline]
63    fn bivariate_normal_domain(reason: impl Into<String>) -> Self {
64        CubicCellKernelError::BivariateNormalDomain {
65            reason: reason.into(),
66        }
67    }
68}
69
70// De-nested cubic transport kernel.
71//
72// This module implements the de-nested flexible-link/score-warp model
73//
74//   eta(z) = a + b*z + b*delta_h(z) + delta_w(a + b*z)
75//
76// where delta_h is the score warp and delta_w is the link deviation.
77// This is not the literal nested composition L(a + b*H(z)); it is an
78// additive-correction model around the affine core a + b*z.
79//
80// On each partition cell, both deviations are cubic polynomials, so eta is
81// at most sextic in z and q(z) = 0.5*(z^2 + eta^2) is at most degree 12.
82// The integral of exp(-q(z)) is evaluated by transporting from the affine
83// anchor (c2=c3=0, where q is Gaussian and the integral reduces to BVN)
84// to the target non-affine cell via the polynomial moment recurrence.
85//
86// The partition covers (-∞, +∞) with:
87//   • two semi-infinite affine TAIL cells (outside all deviation support),
88//   • finitely many interior cells (each a sextic microcell).
89// Because tail cells have constant deviations (c2=c3=0), their bounds
90// are parameter-independent, so no Leibniz boundary-motion corrections
91// appear in the derivatives.
92//
93// Shared by bernoulli_marginal_slope and survival_marginal_slope families.
94
95#[derive(Clone, Copy, Debug, PartialEq)]
96pub struct LocalSpanCubic {
97    pub left: f64,
98    pub right: f64,
99    pub c0: f64,
100    pub c1: f64,
101    pub c2: f64,
102    pub c3: f64,
103}
104
105impl LocalSpanCubic {
106    #[inline]
107    pub fn evaluate(self, x: f64) -> f64 {
108        let t = x - self.left;
109        self.c0 + self.c1 * t + self.c2 * t * t + self.c3 * t * t * t
110    }
111
112    #[inline]
113    pub fn first_derivative(self, x: f64) -> f64 {
114        let t = x - self.left;
115        self.c1 + 2.0 * self.c2 * t + 3.0 * self.c3 * t * t
116    }
117
118    #[inline]
119    pub fn second_derivative(self, x: f64) -> f64 {
120        let t = x - self.left;
121        2.0 * self.c2 + 6.0 * self.c3 * t
122    }
123}
124
125pub const ANCHORED_DEVIATION_KERNEL: &str = "DenestedCubicTransport";
126
127const INV_TWO_PI: f64 = 1.0 / std::f64::consts::TAU;
128
129/// 384-point Gauss–Legendre nodes, re-exported for the GPU cubic-cell kernel
130/// (`src/gpu/cubic_cell/kernel_src.rs`) to embed as `__constant__` device
131/// memory. Linux-only because the kernel emitter is Linux-only.
132#[cfg(target_os = "linux")]
133pub const GL_NODES_FOR_GPU_KERNEL: &[f64; 384] = &GL_NODES;
134/// Companion weights to [`GL_NODES_FOR_GPU_KERNEL`].
135#[cfg(target_os = "linux")]
136pub const GL_WEIGHTS_FOR_GPU_KERNEL: &[f64; 384] = &GL_WEIGHTS;
137
138const GL_NODES: [f64; 384] = [
139    -9.999_804_411_726_474e-1,
140    -9.998_969_471_378_596e-1,
141    -9.997_467_408_113_523e-1,
142    -9.995_297_988_558_859e-1,
143    -9.992_461_316_671_845e-1,
144    -9.988_957_572_063_257e-1,
145    -9.984_786_985_384_589e-1,
146    -9.979_949_833_727_938e-1,
147    -9.974_446_439_389_107e-1,
148    -9.968_277_169_440_913e-1,
149    -9.961_442_435_551_087e-1,
150    -9.953_942_693_885_953e-1,
151    -9.945_778_445_047_068e-1,
152    -9.936_950_234_020_883e-1,
153    -9.927_458_650_133_153e-1,
154    -9.917_304_327_004_32e-1,
155    -9.906_487_942_504_061e-1,
156    -9.895_010_218_704_087e-1,
157    -9.882_871_921_828_699e-1,
158    -9.870_073_862_202_815e-1,
159    -9.856_616_894_197_333e-1,
160    -9.842_501_916_171_713e-1,
161    -9.827_729_870_413_743e-1,
162    -9.812_301_743_076_443e-1,
163    -9.796_218_564_112_101e-1,
164    -9.779_481_407_203_411e-1,
165    -9.762_091_389_691_724e-1,
166    -9.744_049_672_502_397e-1,
167    -9.725_357_460_067_257e-1,
168    -9.706_016_000_244_151e-1,
169    -9.686_026_584_233_628e-1,
170    -9.665_390_546_492_71e-1,
171    -9.644_109_264_645_802e-1,
172    -9.622_184_159_392_698e-1,
173    -9.599_616_694_413_742e-1,
174    -9.576_408_376_272_095e-1,
175    -9.552_560_754_313_16e-1,
176    -9.528_075_420_561_144e-1,
177    -9.502_954_009_612_771e-1,
178    -9.477_198_198_528_157e-1,
179    -9.450_809_706_718_851e-1,
180    -9.423_790_295_833_044e-1,
181    -9.396_141_769_637_963e-1,
182    -9.367_865_973_899_459e-1,
183    -9.338_964_796_258_775e-1,
184    -9.309_440_166_106_54e-1,
185    -9.279_294_054_453_956e-1,
186    -9.248_528_473_801_222e-1,
187    -9.217_145_478_003_181e-1,
188    -9.185_147_162_132_208e-1,
189    -9.152_535_662_338_34e-1,
190    -9.119_313_155_706_682e-1,
191    -9.085_481_860_112_055e-1,
192    -9.051_044_034_070_944e-1,
193    -9.016_001_976_590_722e-1,
194    -8.980_358_027_016_164e-1,
195    -8.944_114_564_873_288e-1,
196    -8.907_274_009_710_492e-1,
197    -8.869_838_820_937_034e-1,
198    -8.831_811_497_658_847e-1,
199    -8.793_194_578_511_7e-1,
200    -8.753_990_641_491_725e-1,
201    -8.714_202_303_783_312e-1,
202    -8.673_832_221_584_393e-1,
203    -8.632_883_089_929_12e-1,
204    -8.591_357_642_507_945e-1,
205    -8.549_258_651_485_127e-1,
206    -8.506_588_927_313_666e-1,
207    -8.463_351_318_547_683e-1,
208    -8.419_548_711_652_254e-1,
209    -8.375_184_030_810_715e-1,
210    -8.330_260_237_729_452e-1,
211    -8.284_780_331_440_178e-1,
212    -8.238_747_348_099_726e-1,
213    -8.192_164_360_787_36e-1,
214    -8.145_034_479_299_62e-1,
215    -8.097_360_849_942_72e-1,
216    -8.049_146_655_322_506e-1,
217    -8.000_395_114_131_988e-1,
218    -7.951_109_480_936_471e-1,
219    -7.901_293_045_956_28e-1,
220    -7.850_949_134_847_117e-1,
221    -7.800_081_108_478_04e-1,
222    -7.748_692_362_707_1e-1,
223    -7.696_786_328_154_644e-1,
224    -7.644_366_469_974_285e-1,
225    -7.591_436_287_621_58e-1,
226    -7.537_999_314_620_412e-1,
227    -7.484_059_118_327_094e-1,
228    -7.429_619_299_692_227e-1,
229    -7.374_683_493_020_299e-1,
230    -7.319_255_365_727_068e-1,
231    -7.263_338_618_094_733e-1,
232    -7.206_936_983_024_912e-1,
233    -7.150_054_225_789_432e-1,
234    -7.092_694_143_778_975e-1,
235    -7.034_860_566_249_567e-1,
236    -6.976_557_354_066_943e-1,
237    -6.917_788_399_448_808e-1,
238    -6.858_557_625_704_99e-1,
239    -6.798_868_986_975_534e-1,
240    -6.738_726_467_966_731e-1,
241    -6.678_134_083_685_102e-1,
242    -6.617_095_879_169_366e-1,
243    -6.555_615_929_220_4e-1,
244    -6.493_698_338_129_212e-1,
245    -6.431_347_239_402_948e-1,
246    -6.368_566_795_488_945e-1,
247    -6.305_361_197_496_849e-1,
248    -6.241_734_664_918_837e-1,
249    -6.177_691_445_347_913e-1,
250    -6.113_235_814_194_364e-1,
251    -6.048_372_074_400_329e-1,
252    -5.983_104_556_152_549e-1,
253    -5.917_437_616_593_286e-1,
254    -5.851_375_639_529_456e-1,
255    -5.784_923_035_139_965e-1,
256    -5.718_084_239_681_3e-1,
257    -5.650_863_715_191_369e-1,
258    -5.583_265_949_191_623e-1,
259    -5.515_295_454_387_482e-1,
260    -5.446_956_768_367_068e-1,
261    -5.378_254_453_298_289e-1,
262    -5.309_193_095_624_275e-1,
263    -5.239_777_305_757_194e-1,
264    -5.170_011_717_770_473e-1,
265    -5.099_900_989_089_429e-1,
266    -5.029_449_800_180_356e-1,
267    -4.958_662_854_238_058_4e-1,
268    -4.887_544_876_871_878e-1,
269    -4.816_100_615_790_221e-1,
270    -4.744_334_840_483_605_5e-1,
271    -4.672_252_341_906_264e-1,
272    -4.599_857_932_156_304e-1,
273    -4.527_156_444_154_463_7e-1,
274    -4.454_152_731_321_473_5e-1,
275    -4.380_851_667_254_05e-1,
276    -4.307_258_145_399_544_5e-1,
277    -4.233_377_078_729_265e-1,
278    -4.159_213_399_410_494e-1,
279    -4.084_772_058_477_228e-1,
280    -4.010_058_025_499_653e-1,
281    -3.935_076_288_252_386e-1,
282    -3.859_831_852_381_500_6e-1,
283    -3.784_329_741_070_358_6e-1,
284    -3.708_574_994_704_271e-1,
285    -3.632_572_670_534_011e-1,
286    -3.556_327_842_338_202e-1,
287    -3.479_845_600_084_600_6e-1,
288    -3.403_131_049_590_297e-1,
289    -3.326_189_312_180_866e-1,
290    -3.249_025_524_348_469_5e-1,
291    -3.171_644_837_408_958_4e-1,
292    -3.094_052_417_157_978e-1,
293    -3.016_253_443_526_109e-1,
294    -2.938_253_110_233_064_5e-1,
295    -2.860_056_624_440_967_5e-1,
296    -2.781_669_206_406_729e-1,
297    -2.703_096_089_133_553e-1,
298    -2.624_342_518_021_592_4e-1,
299    -2.545_413_750_517_773e-1,
300    -2.466_315_055_764_817_5e-1,
301    -2.387_051_714_249_486_3e-1,
302    -2.307_629_017_450_062e-1,
303    -2.228_052_267_483_099_4e-1,
304    -2.148_326_776_749_466_5e-1,
305    -2.068_457_867_579_697_5e-1,
306    -1.988_450_871_878_683_4e-1,
307    -1.908_311_130_769_724_5e-1,
308    -1.828_043_994_237_965_6e-1,
309    -1.747_654_820_773_241_2e-1,
310    -1.667_148_977_012_352_4e-1,
311    -1.586_531_837_380_799_3e-1,
312    -1.505_808_783_733_995e-1,
313    -1.424_985_204_997_981_4e-1,
314    -1.344_066_496_809_674_7e-1,
315    -1.263_058_061_156_663e-1,
316    -1.181_965_306_016_578_4e-1,
317    -1.100_793_644_996_070_4e-1,
318    -1.019_548_496_969_403_7e-1,
319    -9.382_352_857_167_028e-2,
320    -8.568_594_395_618_719e-2,
321    -7.754_263_910_102_077e-2,
322    -6.939_415_763_857_37e-2,
323    -6.124_104_354_682_962e-2,
324    -5.308_384_111_303_817_6e-2,
325    -4.492_309_489_737_94e-2,
326    -3.675_934_969_660_982e-2,
327    -2.859_315_050_769_284_7e-2,
328    -2.042_504_249_141_571e-2,
329    -1.225_557_093_599_553_8e-2,
330    -4.085_281_220_676_868e-3,
331    4.085_281_220_676_868e-3,
332    1.225_557_093_599_553_8e-2,
333    2.042_504_249_141_571e-2,
334    2.859_315_050_769_284_7e-2,
335    3.675_934_969_660_982e-2,
336    4.492_309_489_737_94e-2,
337    5.308_384_111_303_817_6e-2,
338    6.124_104_354_682_962e-2,
339    6.939_415_763_857_37e-2,
340    7.754_263_910_102_077e-2,
341    8.568_594_395_618_719e-2,
342    9.382_352_857_167_028e-2,
343    1.019_548_496_969_403_7e-1,
344    1.100_793_644_996_070_4e-1,
345    1.181_965_306_016_578_4e-1,
346    1.263_058_061_156_663e-1,
347    1.344_066_496_809_674_7e-1,
348    1.424_985_204_997_981_4e-1,
349    1.505_808_783_733_995e-1,
350    1.586_531_837_380_799_3e-1,
351    1.667_148_977_012_352_4e-1,
352    1.747_654_820_773_241_2e-1,
353    1.828_043_994_237_965_6e-1,
354    1.908_311_130_769_724_5e-1,
355    1.988_450_871_878_683_4e-1,
356    2.068_457_867_579_697_5e-1,
357    2.148_326_776_749_466_5e-1,
358    2.228_052_267_483_099_4e-1,
359    2.307_629_017_450_062e-1,
360    2.387_051_714_249_486_3e-1,
361    2.466_315_055_764_817_5e-1,
362    2.545_413_750_517_773e-1,
363    2.624_342_518_021_592_4e-1,
364    2.703_096_089_133_553e-1,
365    2.781_669_206_406_729e-1,
366    2.860_056_624_440_967_5e-1,
367    2.938_253_110_233_064_5e-1,
368    3.016_253_443_526_109e-1,
369    3.094_052_417_157_978e-1,
370    3.171_644_837_408_958_4e-1,
371    3.249_025_524_348_469_5e-1,
372    3.326_189_312_180_866e-1,
373    3.403_131_049_590_297e-1,
374    3.479_845_600_084_600_6e-1,
375    3.556_327_842_338_202e-1,
376    3.632_572_670_534_011e-1,
377    3.708_574_994_704_271e-1,
378    3.784_329_741_070_358_6e-1,
379    3.859_831_852_381_500_6e-1,
380    3.935_076_288_252_386e-1,
381    4.010_058_025_499_653e-1,
382    4.084_772_058_477_228e-1,
383    4.159_213_399_410_494e-1,
384    4.233_377_078_729_265e-1,
385    4.307_258_145_399_544_5e-1,
386    4.380_851_667_254_05e-1,
387    4.454_152_731_321_473_5e-1,
388    4.527_156_444_154_463_7e-1,
389    4.599_857_932_156_304e-1,
390    4.672_252_341_906_264e-1,
391    4.744_334_840_483_605_5e-1,
392    4.816_100_615_790_221e-1,
393    4.887_544_876_871_878e-1,
394    4.958_662_854_238_058_4e-1,
395    5.029_449_800_180_356e-1,
396    5.099_900_989_089_429e-1,
397    5.170_011_717_770_473e-1,
398    5.239_777_305_757_194e-1,
399    5.309_193_095_624_275e-1,
400    5.378_254_453_298_289e-1,
401    5.446_956_768_367_068e-1,
402    5.515_295_454_387_482e-1,
403    5.583_265_949_191_623e-1,
404    5.650_863_715_191_369e-1,
405    5.718_084_239_681_3e-1,
406    5.784_923_035_139_965e-1,
407    5.851_375_639_529_456e-1,
408    5.917_437_616_593_286e-1,
409    5.983_104_556_152_549e-1,
410    6.048_372_074_400_329e-1,
411    6.113_235_814_194_364e-1,
412    6.177_691_445_347_913e-1,
413    6.241_734_664_918_837e-1,
414    6.305_361_197_496_849e-1,
415    6.368_566_795_488_945e-1,
416    6.431_347_239_402_948e-1,
417    6.493_698_338_129_212e-1,
418    6.555_615_929_220_4e-1,
419    6.617_095_879_169_366e-1,
420    6.678_134_083_685_102e-1,
421    6.738_726_467_966_731e-1,
422    6.798_868_986_975_534e-1,
423    6.858_557_625_704_99e-1,
424    6.917_788_399_448_808e-1,
425    6.976_557_354_066_943e-1,
426    7.034_860_566_249_567e-1,
427    7.092_694_143_778_975e-1,
428    7.150_054_225_789_432e-1,
429    7.206_936_983_024_912e-1,
430    7.263_338_618_094_733e-1,
431    7.319_255_365_727_068e-1,
432    7.374_683_493_020_299e-1,
433    7.429_619_299_692_227e-1,
434    7.484_059_118_327_094e-1,
435    7.537_999_314_620_412e-1,
436    7.591_436_287_621_58e-1,
437    7.644_366_469_974_285e-1,
438    7.696_786_328_154_644e-1,
439    7.748_692_362_707_1e-1,
440    7.800_081_108_478_04e-1,
441    7.850_949_134_847_117e-1,
442    7.901_293_045_956_28e-1,
443    7.951_109_480_936_471e-1,
444    8.000_395_114_131_988e-1,
445    8.049_146_655_322_506e-1,
446    8.097_360_849_942_72e-1,
447    8.145_034_479_299_62e-1,
448    8.192_164_360_787_36e-1,
449    8.238_747_348_099_726e-1,
450    8.284_780_331_440_178e-1,
451    8.330_260_237_729_452e-1,
452    8.375_184_030_810_715e-1,
453    8.419_548_711_652_254e-1,
454    8.463_351_318_547_683e-1,
455    8.506_588_927_313_666e-1,
456    8.549_258_651_485_127e-1,
457    8.591_357_642_507_945e-1,
458    8.632_883_089_929_12e-1,
459    8.673_832_221_584_393e-1,
460    8.714_202_303_783_312e-1,
461    8.753_990_641_491_725e-1,
462    8.793_194_578_511_7e-1,
463    8.831_811_497_658_847e-1,
464    8.869_838_820_937_034e-1,
465    8.907_274_009_710_492e-1,
466    8.944_114_564_873_288e-1,
467    8.980_358_027_016_164e-1,
468    9.016_001_976_590_722e-1,
469    9.051_044_034_070_944e-1,
470    9.085_481_860_112_055e-1,
471    9.119_313_155_706_682e-1,
472    9.152_535_662_338_34e-1,
473    9.185_147_162_132_208e-1,
474    9.217_145_478_003_181e-1,
475    9.248_528_473_801_222e-1,
476    9.279_294_054_453_956e-1,
477    9.309_440_166_106_54e-1,
478    9.338_964_796_258_775e-1,
479    9.367_865_973_899_459e-1,
480    9.396_141_769_637_963e-1,
481    9.423_790_295_833_044e-1,
482    9.450_809_706_718_851e-1,
483    9.477_198_198_528_157e-1,
484    9.502_954_009_612_771e-1,
485    9.528_075_420_561_144e-1,
486    9.552_560_754_313_16e-1,
487    9.576_408_376_272_095e-1,
488    9.599_616_694_413_742e-1,
489    9.622_184_159_392_698e-1,
490    9.644_109_264_645_802e-1,
491    9.665_390_546_492_71e-1,
492    9.686_026_584_233_628e-1,
493    9.706_016_000_244_151e-1,
494    9.725_357_460_067_257e-1,
495    9.744_049_672_502_397e-1,
496    9.762_091_389_691_724e-1,
497    9.779_481_407_203_411e-1,
498    9.796_218_564_112_101e-1,
499    9.812_301_743_076_443e-1,
500    9.827_729_870_413_743e-1,
501    9.842_501_916_171_713e-1,
502    9.856_616_894_197_333e-1,
503    9.870_073_862_202_815e-1,
504    9.882_871_921_828_699e-1,
505    9.895_010_218_704_087e-1,
506    9.906_487_942_504_061e-1,
507    9.917_304_327_004_32e-1,
508    9.927_458_650_133_153e-1,
509    9.936_950_234_020_883e-1,
510    9.945_778_445_047_068e-1,
511    9.953_942_693_885_953e-1,
512    9.961_442_435_551_087e-1,
513    9.968_277_169_440_913e-1,
514    9.974_446_439_389_107e-1,
515    9.979_949_833_727_938e-1,
516    9.984_786_985_384_589e-1,
517    9.988_957_572_063_257e-1,
518    9.992_461_316_671_845e-1,
519    9.995_297_988_558_859e-1,
520    9.997_467_408_113_523e-1,
521    9.998_969_471_378_596e-1,
522    9.999_804_411_726_474e-1,
523];
524const GL_WEIGHTS: [f64; 384] = [
525    5.019_410_348_676_869_6e-5,
526    1.168_390_665_730_266_3e-4,
527    1.835_749_193_551_655_8e-4,
528    2.503_070_890_844_105e-4,
529    3.170_242_698_112_815e-4,
530    3.837_208_020_912_921_4e-4,
531    4.503_919_137_716_827e-4,
532    5.170_330_453_491_649e-4,
533    5.836_397_042_630_135e-4,
534    6.502_074_240_969_948e-4,
535    7.167_317_509_947_801e-4,
536    7.832_082_385_905_168e-4,
537    8.496_324_460_039_209e-4,
538    9.159_999_370_632_641e-4,
539    9.823_062_800_663_463e-4,
540    1.048_547_047_793_689_5e-3,
541    1.114_717_817_647_310_6e-3,
542    1.180_814_171_855_922e-3,
543    1.246_831_697_715_441_5e-3,
544    1.312_765_987_850_66e-3,
545    1.378_612_640_487_646_8e-3,
546    1.444_367_259_734_736e-3,
547    1.510_025_455_865_810_3e-3,
548    1.575_582_845_607_936_8e-3,
549    1.641_035_052_429_271_5e-3,
550    1.706_377_706_828_447_1e-3,
551    1.771_606_446_623_834_7e-3,
552    1.836_716_917_243_567_5e-3,
553    1.901_704_772_014_899_2e-3,
554    1.966_565_672_453_437e-3,
555    2.031_295_288_552_398_4e-3,
556    2.095_889_299_071_020_6e-3,
557    2.160_343_391_822_734_3e-3,
558    2.224_653_263_962_713e-3,
559    2.288_814_622_274_955e-3,
560    2.352_823_183_458_769e-3,
561    2.416_674_674_414_340_5e-3,
562    2.480_364_832_528_265_6e-3,
563    2.543_889_405_957_74e-3,
564    2.607_244_153_914_452e-3,
565    2.670_424_846_947_554e-3,
566    2.733_427_267_226_093_3e-3,
567    2.796_247_208_820_428e-3,
568    2.858_880_477_983_06e-3,
569    2.921_322_893_428_515_3e-3,
570    2.983_570_286_612_554_5e-3,
571    3.045_618_502_010_327_8e-3,
572    3.107_463_397_393_755_5e-3,
573    3.169_100_844_108_32e-3,
574    3.230_526_727_348_174e-3,
575    3.291_736_946_431_361e-3,
576    3.352_727_415_073_250_3e-3,
577    3.413_494_061_659_418_4e-3,
578    3.474_032_829_517_317e-3,
579    3.534_339_677_187_348_4e-3,
580    3.594_410_578_692_452e-3,
581    3.654_241_523_806_987e-3,
582    3.713_828_518_324_312_5e-3,
583    3.773_167_584_323_583_5e-3,
584    3.832_254_760_435_171e-3,
585    3.891_086_102_105_193_4e-3,
586    3.949_657_681_858_895e-3,
587    4.007_965_589_562_678e-3,
588    4.066_005_932_685_269e-3,
589    4.123_774_836_557_6e-3,
590    4.181_268_444_631_281e-3,
591    4.238_482_918_736_289e-3,
592    4.295_414_439_336_925e-3,
593    4.352_059_205_787_275e-3,
594    4.408_413_436_584_285e-3,
595    4.464_473_369_620_78e-3,
596    4.520_235_262_436_235e-3,
597    4.575_695_392_466_791e-3,
598    4.630_850_057_293_894e-3,
599    4.685_695_574_891_041e-3,
600    4.740_228_283_870_022e-3,
601    4.794_444_543_725_102e-3,
602    4.848_340_735_076_109e-3,
603    4.901_913_259_910_197e-3,
604    4.955_158_541_821_682_4e-3,
605    5.008_073_026_251_332e-3,
606    5.060_653_180_723_101_4e-3,
607    5.112_895_495_080_397e-3,
608    5.164_796_481_720_011e-3,
609    5.216_352_675_825_451e-3,
610    5.267_560_635_597_735e-3,
611    5.318_416_942_485_385e-3,
612    5.368_918_201_412_827e-3,
613    5.419_061_041_006_627e-3,
614    5.468_842_113_820_941e-3,
615    5.518_258_096_560_71e-3,
616    5.567_305_690_303_767e-3,
617    5.615_981_620_720_803e-3,
618    5.664_282_638_294_182e-3,
619    5.712_205_518_534_655e-3,
620    5.759_747_062_196_925_5e-3,
621    5.806_904_095_492_818e-3,
622    5.853_673_470_303_617_4e-3,
623    5.900_052_064_389_824e-3,
624    5.946_036_781_599_814e-3,
625    5.991_624_552_076_468e-3,
626    6.036_812_332_462_087e-3,
627    6.081_597_106_101_673e-3,
628    6.125_975_883_244_196e-3,
629    6.169_945_701_242_237e-3,
630    6.213_503_624_749_591e-3,
631    6.256_646_745_917_723e-3,
632    6.299_372_184_589_237e-3,
633    6.341_677_088_490_664e-3,
634    6.383_558_633_422_572e-3,
635    6.425_014_023_448_273e-3,
636    6.466_040_491_080_434e-3,
637    6.506_635_297_465_724e-3,
638    6.546_795_732_567_842_5e-3,
639    6.586_519_115_348_261e-3,
640    6.625_802_793_945_317e-3,
641    6.664_644_145_851_14e-3,
642    6.703_040_578_086_941e-3,
643    6.740_989_527_375_895e-3,
644    6.778_488_460_314_126e-3,
645    6.815_534_873_540_5e-3,
646    6.852_126_293_902_878e-3,
647    6.888_260_278_623_754e-3,
648    6.923_934_415_463_31e-3,
649    6.959_146_322_880_146_5e-3,
650    6.993_893_650_190_702e-3,
651    7.028_174_077_725_734e-3,
652    7.061_985_316_985_506e-3,
653    7.095_325_110_792_439e-3,
654    7.128_191_233_441_844e-3,
655    7.160_581_490_850_321e-3,
656    7.192_493_720_702_486e-3,
657    7.223_925_792_595_309e-3,
658    7.254_875_608_179_984e-3,
659    7.285_341_101_302_512e-3,
660    7.315_320_238_141_324_5e-3,
661    7.344_811_017_343_063e-3,
662    7.373_811_470_156_258e-3,
663    7.402_319_660_562_818e-3,
664    7.430_333_685_407_178e-3,
665    7.457_851_674_523_319e-3,
666    7.484_871_790_859_79e-3,
667    7.511_392_230_602_079e-3,
668    7.537_411_223_293_362e-3,
669    7.562_927_031_952_382e-3,
670    7.587_937_953_189_561_5e-3,
671    7.612_442_317_320_796e-3,
672    7.636_438_488_478_739e-3,
673    7.659_924_864_722_064e-3,
674    7.682_899_878_142_539e-3,
675    7.705_361_994_969_524e-3,
676    7.727_309_715_672_44e-3,
677    7.748_741_575_060_914e-3,
678    7.769_656_142_382_462e-3,
679    7.790_052_021_418_226e-3,
680    7.809_927_850_575_903e-3,
681    7.829_282_302_980_82e-3,
682    7.848_114_086_564_56e-3,
683    7.866_421_944_151_094e-3,
684    7.884_204_653_540_665e-3,
685    7.901_461_027_591_6e-3,
686    7.918_189_914_299_318e-3,
687    7.934_390_196_873_448e-3,
688    7.950_060_793_812_204e-3,
689    7.965_200_658_974_709e-3,
690    7.979_808_781_650_77e-3,
691    7.993_884_186_628_266e-3,
692    8.007_425_934_258_548e-3,
693    8.020_433_120_518_866e-3,
694    8.032_904_877_072_8e-3,
695    8.044_840_371_328_26e-3,
696    8.056_238_806_493_175e-3,
697    8.067_099_421_628_42e-3,
698    8.077_421_491_698_82e-3,
699    8.087_204_327_621_594e-3,
700    8.096_447_276_312_202e-3,
701    8.105_149_720_727_933e-3,
702    8.113_311_079_909_208e-3,
703    8.120_930_809_018_415e-3,
704    8.128_008_399_376_085e-3,
705    8.134_543_378_495_033e-3,
706    8.140_535_310_111_77e-3,
707    8.145_983_794_215_77e-3,
708    8.150_888_467_075_875e-3,
709    8.155_249_001_265_092e-3,
710    8.159_065_105_681_899e-3,
711    8.162_336_525_570_1e-3,
712    8.165_063_042_535_465e-3,
713    8.167_244_474_560_707e-3,
714    8.168_880_676_017_344e-3,
715    8.169_971_537_675_47e-3,
716    8.170_516_986_711_104e-3,
717    8.170_516_986_711_104e-3,
718    8.169_971_537_675_47e-3,
719    8.168_880_676_017_344e-3,
720    8.167_244_474_560_707e-3,
721    8.165_063_042_535_465e-3,
722    8.162_336_525_570_1e-3,
723    8.159_065_105_681_899e-3,
724    8.155_249_001_265_092e-3,
725    8.150_888_467_075_875e-3,
726    8.145_983_794_215_77e-3,
727    8.140_535_310_111_77e-3,
728    8.134_543_378_495_033e-3,
729    8.128_008_399_376_085e-3,
730    8.120_930_809_018_415e-3,
731    8.113_311_079_909_208e-3,
732    8.105_149_720_727_933e-3,
733    8.096_447_276_312_202e-3,
734    8.087_204_327_621_594e-3,
735    8.077_421_491_698_82e-3,
736    8.067_099_421_628_42e-3,
737    8.056_238_806_493_175e-3,
738    8.044_840_371_328_26e-3,
739    8.032_904_877_072_8e-3,
740    8.020_433_120_518_866e-3,
741    8.007_425_934_258_548e-3,
742    7.993_884_186_628_266e-3,
743    7.979_808_781_650_77e-3,
744    7.965_200_658_974_709e-3,
745    7.950_060_793_812_204e-3,
746    7.934_390_196_873_448e-3,
747    7.918_189_914_299_318e-3,
748    7.901_461_027_591_6e-3,
749    7.884_204_653_540_665e-3,
750    7.866_421_944_151_094e-3,
751    7.848_114_086_564_56e-3,
752    7.829_282_302_980_82e-3,
753    7.809_927_850_575_903e-3,
754    7.790_052_021_418_226e-3,
755    7.769_656_142_382_462e-3,
756    7.748_741_575_060_914e-3,
757    7.727_309_715_672_44e-3,
758    7.705_361_994_969_524e-3,
759    7.682_899_878_142_539e-3,
760    7.659_924_864_722_064e-3,
761    7.636_438_488_478_739e-3,
762    7.612_442_317_320_796e-3,
763    7.587_937_953_189_561_5e-3,
764    7.562_927_031_952_382e-3,
765    7.537_411_223_293_362e-3,
766    7.511_392_230_602_079e-3,
767    7.484_871_790_859_79e-3,
768    7.457_851_674_523_319e-3,
769    7.430_333_685_407_178e-3,
770    7.402_319_660_562_818e-3,
771    7.373_811_470_156_258e-3,
772    7.344_811_017_343_063e-3,
773    7.315_320_238_141_324_5e-3,
774    7.285_341_101_302_512e-3,
775    7.254_875_608_179_984e-3,
776    7.223_925_792_595_309e-3,
777    7.192_493_720_702_486e-3,
778    7.160_581_490_850_321e-3,
779    7.128_191_233_441_844e-3,
780    7.095_325_110_792_439e-3,
781    7.061_985_316_985_506e-3,
782    7.028_174_077_725_734e-3,
783    6.993_893_650_190_702e-3,
784    6.959_146_322_880_146_5e-3,
785    6.923_934_415_463_31e-3,
786    6.888_260_278_623_754e-3,
787    6.852_126_293_902_878e-3,
788    6.815_534_873_540_5e-3,
789    6.778_488_460_314_126e-3,
790    6.740_989_527_375_895e-3,
791    6.703_040_578_086_941e-3,
792    6.664_644_145_851_14e-3,
793    6.625_802_793_945_317e-3,
794    6.586_519_115_348_261e-3,
795    6.546_795_732_567_842_5e-3,
796    6.506_635_297_465_724e-3,
797    6.466_040_491_080_434e-3,
798    6.425_014_023_448_273e-3,
799    6.383_558_633_422_572e-3,
800    6.341_677_088_490_664e-3,
801    6.299_372_184_589_237e-3,
802    6.256_646_745_917_723e-3,
803    6.213_503_624_749_591e-3,
804    6.169_945_701_242_237e-3,
805    6.125_975_883_244_196e-3,
806    6.081_597_106_101_673e-3,
807    6.036_812_332_462_087e-3,
808    5.991_624_552_076_468e-3,
809    5.946_036_781_599_814e-3,
810    5.900_052_064_389_824e-3,
811    5.853_673_470_303_617_4e-3,
812    5.806_904_095_492_818e-3,
813    5.759_747_062_196_925_5e-3,
814    5.712_205_518_534_655e-3,
815    5.664_282_638_294_182e-3,
816    5.615_981_620_720_803e-3,
817    5.567_305_690_303_767e-3,
818    5.518_258_096_560_71e-3,
819    5.468_842_113_820_941e-3,
820    5.419_061_041_006_627e-3,
821    5.368_918_201_412_827e-3,
822    5.318_416_942_485_385e-3,
823    5.267_560_635_597_735e-3,
824    5.216_352_675_825_451e-3,
825    5.164_796_481_720_011e-3,
826    5.112_895_495_080_397e-3,
827    5.060_653_180_723_101_4e-3,
828    5.008_073_026_251_332e-3,
829    4.955_158_541_821_682_4e-3,
830    4.901_913_259_910_197e-3,
831    4.848_340_735_076_109e-3,
832    4.794_444_543_725_102e-3,
833    4.740_228_283_870_022e-3,
834    4.685_695_574_891_041e-3,
835    4.630_850_057_293_894e-3,
836    4.575_695_392_466_791e-3,
837    4.520_235_262_436_235e-3,
838    4.464_473_369_620_78e-3,
839    4.408_413_436_584_285e-3,
840    4.352_059_205_787_275e-3,
841    4.295_414_439_336_925e-3,
842    4.238_482_918_736_289e-3,
843    4.181_268_444_631_281e-3,
844    4.123_774_836_557_6e-3,
845    4.066_005_932_685_269e-3,
846    4.007_965_589_562_678e-3,
847    3.949_657_681_858_895e-3,
848    3.891_086_102_105_193_4e-3,
849    3.832_254_760_435_171e-3,
850    3.773_167_584_323_583_5e-3,
851    3.713_828_518_324_312_5e-3,
852    3.654_241_523_806_987e-3,
853    3.594_410_578_692_452e-3,
854    3.534_339_677_187_348_4e-3,
855    3.474_032_829_517_317e-3,
856    3.413_494_061_659_418_4e-3,
857    3.352_727_415_073_250_3e-3,
858    3.291_736_946_431_361e-3,
859    3.230_526_727_348_174e-3,
860    3.169_100_844_108_32e-3,
861    3.107_463_397_393_755_5e-3,
862    3.045_618_502_010_327_8e-3,
863    2.983_570_286_612_554_5e-3,
864    2.921_322_893_428_515_3e-3,
865    2.858_880_477_983_06e-3,
866    2.796_247_208_820_428e-3,
867    2.733_427_267_226_093_3e-3,
868    2.670_424_846_947_554e-3,
869    2.607_244_153_914_452e-3,
870    2.543_889_405_957_74e-3,
871    2.480_364_832_528_265_6e-3,
872    2.416_674_674_414_340_5e-3,
873    2.352_823_183_458_769e-3,
874    2.288_814_622_274_955e-3,
875    2.224_653_263_962_713e-3,
876    2.160_343_391_822_734_3e-3,
877    2.095_889_299_071_020_6e-3,
878    2.031_295_288_552_398_4e-3,
879    1.966_565_672_453_437e-3,
880    1.901_704_772_014_899_2e-3,
881    1.836_716_917_243_567_5e-3,
882    1.771_606_446_623_834_7e-3,
883    1.706_377_706_828_447_1e-3,
884    1.641_035_052_429_271_5e-3,
885    1.575_582_845_607_936_8e-3,
886    1.510_025_455_865_810_3e-3,
887    1.444_367_259_734_736e-3,
888    1.378_612_640_487_646_8e-3,
889    1.312_765_987_850_66e-3,
890    1.246_831_697_715_441_5e-3,
891    1.180_814_171_855_922e-3,
892    1.114_717_817_647_310_6e-3,
893    1.048_547_047_793_689_5e-3,
894    9.823_062_800_663_463e-4,
895    9.159_999_370_632_641e-4,
896    8.496_324_460_039_209e-4,
897    7.832_082_385_905_168e-4,
898    7.167_317_509_947_801e-4,
899    6.502_074_240_969_948e-4,
900    5.836_397_042_630_135e-4,
901    5.170_330_453_491_649e-4,
902    4.503_919_137_716_827e-4,
903    3.837_208_020_912_921_4e-4,
904    3.170_242_698_112_815e-4,
905    2.503_070_890_844_105e-4,
906    1.835_749_193_551_655_8e-4,
907    1.168_390_665_730_266_3e-4,
908    5.019_410_348_676_869_6e-5,
909];
910
911#[derive(Clone, Copy, Debug, Eq, PartialEq)]
912pub enum ExactCellBranch {
913    Affine,
914    Quartic,
915    Sextic,
916}
917
918#[derive(Clone, Copy, Debug, PartialEq)]
919pub struct DenestedCubicCell {
920    pub left: f64,
921    pub right: f64,
922    pub c0: f64,
923    pub c1: f64,
924    pub c2: f64,
925    pub c3: f64,
926}
927
928impl DenestedCubicCell {
929    #[inline]
930    pub fn eta(self, z: f64) -> f64 {
931        self.c0 + self.c1 * z + self.c2 * z * z + self.c3 * z * z * z
932    }
933
934    #[inline]
935    pub fn q(self, z: f64) -> f64 {
936        let eta = self.eta(z);
937        0.5 * (z * z + eta * eta)
938    }
939}
940
941#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
942pub struct CellMomentFingerprint {
943    pub hash: u64,
944    bins: [u64; 6],
945}
946
947#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
948pub struct CellMomentCacheKey {
949    pub fingerprint: CellMomentFingerprint,
950    pub max_degree: usize,
951}
952
953#[derive(Clone, Copy, Debug, Default, PartialEq)]
954pub struct CellMomentDedupStats {
955    pub lookups: u64,
956    pub hits: u64,
957    pub misses: u64,
958}
959
960impl CellMomentDedupStats {
961    #[inline]
962    pub fn hit_rate(self) -> f64 {
963        if self.lookups == 0 {
964            0.0
965        } else {
966            self.hits as f64 / self.lookups as f64
967        }
968    }
969}
970
971#[inline]
972fn splitmix64(x: u64) -> u64 {
973    gam_linalg::utils::splitmix64_hash(x)
974}
975
976#[inline]
977fn mix_fingerprint_words(words: &[u64]) -> u64 {
978    let mut h = 0xcbf2_9ce4_8422_2325u64;
979    for &word in words {
980        h ^= splitmix64(word);
981        h = h.wrapping_mul(0x100_0000_01b3);
982    }
983    h
984}
985
986#[inline]
987fn quantized_cell_word(x: f64, epsilon: f64) -> u64 {
988    if epsilon == 0.0 || !epsilon.is_finite() || epsilon < 0.0 || !x.is_finite() {
989        return x.to_bits();
990    }
991    (x / epsilon).round().to_bits()
992}
993
994/// Returns a deterministic geometric fingerprint for a de-nested cubic cell.
995///
996/// With `epsilon == 0.0`, each coordinate is represented by its exact IEEE-754
997/// bit pattern, so equal fingerprints imply bit-equal `(left, right, c0, c1,
998/// c2, c3)` tuples.  With `epsilon > 0`, finite coordinates are binned to the
999/// nearest multiple of `epsilon`; callers should treat this as an approximate
1000/// cache key and validate the resulting model error for their data.
1001pub fn cell_moment_fingerprint(cell: DenestedCubicCell, epsilon: f64) -> CellMomentFingerprint {
1002    let bins = [
1003        quantized_cell_word(cell.left, epsilon),
1004        quantized_cell_word(cell.right, epsilon),
1005        quantized_cell_word(cell.c0, epsilon),
1006        quantized_cell_word(cell.c1, epsilon),
1007        quantized_cell_word(cell.c2, epsilon),
1008        quantized_cell_word(cell.c3, epsilon),
1009    ];
1010    CellMomentFingerprint {
1011        hash: mix_fingerprint_words(&bins),
1012        bins,
1013    }
1014}
1015
1016#[inline]
1017pub fn cell_moment_cache_key(
1018    cell: DenestedCubicCell,
1019    max_degree: usize,
1020    epsilon: f64,
1021) -> CellMomentCacheKey {
1022    CellMomentCacheKey {
1023        fingerprint: cell_moment_fingerprint(cell, epsilon),
1024        max_degree,
1025    }
1026}
1027
1028#[derive(Clone, Copy, Debug, PartialEq)]
1029pub struct DenestedPartitionCell {
1030    pub cell: DenestedCubicCell,
1031    pub score_span: LocalSpanCubic,
1032    pub link_span: LocalSpanCubic,
1033    /// Provenance of the cell's boundaries: a fixed z location (score break
1034    /// or ±∞ tail) or a link-knot crossing `z = (τ - a)/b`. Together with
1035    /// `(score_span, link_span)` this identifies the cell's two-parameter
1036    /// family in `(a, b)` across rows (see
1037    /// [`crate::cell_moment_family`]).
1038    pub left_edge: PartitionEdge,
1039    pub right_edge: PartitionEdge,
1040}
1041
1042impl DenestedPartitionCell {}
1043
1044/// Provenance of one boundary of a denested partition cell.
1045#[derive(Clone, Copy, Debug, PartialEq)]
1046pub enum PartitionEdge {
1047    /// A z location independent of the row scalars: a score-spline break,
1048    /// or ±∞ for tail cells.
1049    Fixed(f64),
1050    /// A link-knot crossing: the boundary sits at `z = (τ - a)/b` for the
1051    /// row's `(a, b)`.
1052    Crossing { tau: f64 },
1053}
1054
1055impl PartitionEdge {
1056    /// The boundary's z location at the row scalars `(a, b)`.
1057    #[inline]
1058    pub fn z_at(self, a: f64, b: f64) -> f64 {
1059        match self {
1060            Self::Fixed(z) => z,
1061            Self::Crossing { tau } => (tau - a) / b,
1062        }
1063    }
1064}
1065
1066#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
1067struct TailCellMomentCacheKey {
1068    c0_bits: u64,
1069    c1_bits: u64,
1070    endpoint_bits: u64,
1071    side: i8,
1072    max_degree: usize,
1073}
1074
1075const TAIL_CELL_MOMENT_CACHE_MAX_BYTES: usize = 64 * 1024 * 1024;
1076const TAIL_CELL_MOMENT_CACHE_MAX_ENTRIES: usize = 262_144;
1077
1078#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1079pub struct TailCellMomentCacheStats {
1080    pub hits: usize,
1081    pub misses: usize,
1082    pub entries: usize,
1083}
1084
1085impl TailCellMomentCacheStats {
1086    #[inline]
1087    pub fn requests(self) -> usize {
1088        self.hits + self.misses
1089    }
1090
1091    #[inline]
1092    pub fn hit_rate(self) -> f64 {
1093        let requests = self.requests();
1094        if requests == 0 {
1095            0.0
1096        } else {
1097            self.hits as f64 / requests as f64
1098        }
1099    }
1100}
1101
1102/// Affine-tail cell-moment memo.
1103///
1104/// Stand-alone instances (`TailCellMomentCache::new()`) are useful when a
1105/// caller needs deterministic hit/miss bookkeeping that is not polluted by
1106/// concurrent traffic on the global memo. The production path uses the
1107/// global instance behind [`evaluate_cell_moments`].
1108///
1109/// All methods take `&self`: the LRU is internally synchronized (sharded for
1110/// the concurrent global memo) and the counters are atomics, so the global
1111/// instance needs no outer `Mutex`. The previous `OnceLock<Mutex<…>>` wrapper
1112/// serialized every tail-cell evaluation across all rayon workers of the
1113/// marginal-slope exact-cache build — the same contention class the sharded
1114/// per-family cell-moment LRU fix removed.
1115#[derive(Debug)]
1116pub struct TailCellMomentCache {
1117    moments: ByteLruCache<TailCellMomentCacheKey, CellMomentState>,
1118    in_flight: std::sync::Mutex<
1119        std::collections::HashMap<
1120            TailCellMomentCacheKey,
1121            Arc<std::sync::OnceLock<Result<CellMomentState, String>>>,
1122        >,
1123    >,
1124    hits: std::sync::atomic::AtomicUsize,
1125    misses: std::sync::atomic::AtomicUsize,
1126}
1127
1128impl Default for TailCellMomentCache {
1129    fn default() -> Self {
1130        // Tail-cell entries are small (a short moment vector), so sharding
1131        // the byte/entry budgets is harmless; size the shard count off the
1132        // worker pool exactly like the per-family cell-moment LRU.
1133        let shard_count = std::thread::available_parallelism()
1134            .map(|workers| workers.get().saturating_mul(8))
1135            .unwrap_or(32)
1136            .clamp(8, 256);
1137        Self {
1138            moments: ByteLruCache::with_max_entries_sharded(
1139                TAIL_CELL_MOMENT_CACHE_MAX_BYTES,
1140                TAIL_CELL_MOMENT_CACHE_MAX_ENTRIES,
1141                shard_count,
1142            ),
1143            in_flight: std::sync::Mutex::new(std::collections::HashMap::new()),
1144            hits: std::sync::atomic::AtomicUsize::new(0),
1145            misses: std::sync::atomic::AtomicUsize::new(0),
1146        }
1147    }
1148}
1149
1150impl TailCellMomentCache {
1151    /// Construct an empty cache. Hits/misses start at zero.
1152    #[inline]
1153    pub fn new() -> Self {
1154        Self::default()
1155    }
1156
1157    /// Reset the cache to its empty state. Existing entries are dropped and
1158    /// the hit/miss counters are zeroed.
1159    #[inline]
1160    pub fn clear(&self) {
1161        self.moments.clear();
1162        self.in_flight
1163            .lock()
1164            .unwrap_or_else(|p| p.into_inner())
1165            .clear();
1166        self.hits.store(0, std::sync::atomic::Ordering::Relaxed);
1167        self.misses.store(0, std::sync::atomic::Ordering::Relaxed);
1168    }
1169
1170    /// Snapshot of the cache's current usage stats.
1171    #[inline]
1172    pub fn stats(&self) -> TailCellMomentCacheStats {
1173        TailCellMomentCacheStats {
1174            hits: self.hits.load(std::sync::atomic::Ordering::Relaxed),
1175            misses: self.misses.load(std::sync::atomic::Ordering::Relaxed),
1176            entries: self.moments.len(),
1177        }
1178    }
1179
1180    /// Look up `cell` at `max_degree`, computing and inserting the result on
1181    /// miss. Cells outside the affine-tail keyset bypass the cache and run
1182    /// the uncached evaluator directly without touching the counters.
1183    ///
1184    /// Stat semantics: every request served from an existing resident entry,
1185    /// or from a concurrently published entry for the same key, increments
1186    /// `hits`; a **miss** is counted only for the caller that actually
1187    /// computes a cold key. The compute happens outside the LRU shard lock,
1188    /// but an in-flight table coalesces same-key cold races so followers reuse
1189    /// the leader's published value instead of duplicating work.
1190    pub fn evaluate(
1191        &self,
1192        cell: DenestedCubicCell,
1193        max_degree: usize,
1194    ) -> Result<CellMomentState, String> {
1195        let Some(key) = tail_cell_cache_key(cell, max_degree) else {
1196            return evaluate_cell_moments_uncached(cell, max_degree);
1197        };
1198        if let Some(state) = self.moments.get(&key) {
1199            self.hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1200            return Ok(state);
1201        }
1202
1203        let (slot, leader) = {
1204            let mut in_flight = self.in_flight.lock().unwrap_or_else(|p| p.into_inner());
1205            if let Some(slot) = in_flight.get(&key) {
1206                (Arc::clone(slot), false)
1207            } else {
1208                let slot = Arc::new(std::sync::OnceLock::new());
1209                in_flight.insert(key, Arc::clone(&slot));
1210                (slot, true)
1211            }
1212        };
1213
1214        if !leader {
1215            let state = slot.wait().clone()?;
1216            self.hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1217            return Ok(state);
1218        }
1219
1220        let state = evaluate_cell_moments_uncached(cell, max_degree);
1221        if let Ok(state) = &state {
1222            self.moments.insert(key, state.clone());
1223            self.hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1224        }
1225        self.misses
1226            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1227        if let Err(existing_state) = slot.set(state.clone()) {
1228            std::mem::drop(existing_state);
1229        }
1230        self.in_flight
1231            .lock()
1232            .unwrap_or_else(|p| p.into_inner())
1233            .remove(&key);
1234        state
1235    }
1236}
1237
1238static TAIL_CELL_MOMENT_CACHE: std::sync::OnceLock<TailCellMomentCache> =
1239    std::sync::OnceLock::new();
1240static TAIL_CELL_MOMENT_CACHE_ENABLED: std::sync::atomic::AtomicBool =
1241    std::sync::atomic::AtomicBool::new(true);
1242
1243fn tail_cell_moment_cache() -> &'static TailCellMomentCache {
1244    TAIL_CELL_MOMENT_CACHE.get_or_init(TailCellMomentCache::default)
1245}
1246
1247#[inline]
1248fn tail_cell_cache_key(
1249    cell: DenestedCubicCell,
1250    max_degree: usize,
1251) -> Option<TailCellMomentCacheKey> {
1252    if cell.c2 != 0.0 || cell.c3 != 0.0 {
1253        return None;
1254    }
1255    match (!cell.left.is_finite(), !cell.right.is_finite()) {
1256        (true, false) if cell.right.is_finite() => Some(TailCellMomentCacheKey {
1257            c0_bits: cell.c0.to_bits(),
1258            c1_bits: cell.c1.to_bits(),
1259            endpoint_bits: cell.right.to_bits(),
1260            side: -1,
1261            max_degree,
1262        }),
1263        (false, true) if cell.left.is_finite() => Some(TailCellMomentCacheKey {
1264            c0_bits: cell.c0.to_bits(),
1265            c1_bits: cell.c1.to_bits(),
1266            endpoint_bits: cell.left.to_bits(),
1267            side: 1,
1268            max_degree,
1269        }),
1270        _ => None,
1271    }
1272}
1273
1274pub fn set_tail_cell_moment_cache_enabled(enabled: bool) {
1275    TAIL_CELL_MOMENT_CACHE_ENABLED.store(enabled, std::sync::atomic::Ordering::Relaxed);
1276}
1277
1278pub fn reset_tail_cell_moment_cache() {
1279    tail_cell_moment_cache().clear();
1280}
1281
1282pub fn tail_cell_moment_cache_stats() -> TailCellMomentCacheStats {
1283    tail_cell_moment_cache().stats()
1284}
1285
1286#[derive(Clone, Copy, Debug, Eq)]
1287pub struct CellFingerprint {
1288    c0: u64,
1289    c1: u64,
1290    c2: u64,
1291    c3: u64,
1292    left: u64,
1293    right: u64,
1294}
1295
1296impl CellFingerprint {
1297    #[inline]
1298    pub fn new(cell: DenestedCubicCell) -> Self {
1299        Self {
1300            c0: cell.c0.to_bits(),
1301            c1: cell.c1.to_bits(),
1302            c2: cell.c2.to_bits(),
1303            c3: cell.c3.to_bits(),
1304            left: cell.left.to_bits(),
1305            right: cell.right.to_bits(),
1306        }
1307    }
1308}
1309
1310impl PartialEq for CellFingerprint {
1311    #[inline]
1312    fn eq(&self, other: &Self) -> bool {
1313        self.c0 == other.c0
1314            && self.c1 == other.c1
1315            && self.c2 == other.c2
1316            && self.c3 == other.c3
1317            && self.left == other.left
1318            && self.right == other.right
1319    }
1320}
1321
1322impl Hash for CellFingerprint {
1323    #[inline]
1324    fn hash<H: Hasher>(&self, state: &mut H) {
1325        self.c0.hash(state);
1326        self.c1.hash(state);
1327        self.c2.hash(state);
1328        self.c3.hash(state);
1329        self.left.hash(state);
1330        self.right.hash(state);
1331    }
1332}
1333
1334#[derive(Clone, Debug, Default, PartialEq)]
1335pub struct CachedCellMoments {
1336    /// Regular (value) cell moments, populated by
1337    /// `evaluate_cell_moments_cached`. None when only derivative moments
1338    /// have been cached for this cell. Wrapped in `Arc` so `ByteLruCache`
1339    /// returns lookups through cheap refcount bumps instead of deep-cloning
1340    /// the inline `SmallVec<[f64; 10]>` (which spills on every degree-`>= 10`
1341    /// request) on every hot-path LRU hit.
1342    state: Option<Arc<CellMomentState>>,
1343    /// Derivative moments, populated by
1344    /// `evaluate_cell_derivative_moments_cached`. None when only value
1345    /// moments have been cached for this cell. Both variants share the
1346    /// same `CellFingerprint` key so derivative-only callers do not evict
1347    /// pre-cached value entries and vice versa. Same `Arc` wrapping rationale
1348    /// as `state` above.
1349    derivative_state: Option<Arc<CellDerivativeMomentState>>,
1350}
1351
1352impl CachedCellMoments {
1353    #[inline]
1354    pub fn new(state: Arc<CellMomentState>) -> Self {
1355        Self {
1356            state: Some(state),
1357            derivative_state: None,
1358        }
1359    }
1360
1361    #[inline]
1362    pub fn new_derivative(state: Arc<CellDerivativeMomentState>) -> Self {
1363        Self {
1364            state: None,
1365            derivative_state: Some(state),
1366        }
1367    }
1368
1369    #[inline]
1370    pub fn state_for_degree(&self, max_degree: usize) -> Option<CellMomentState> {
1371        let state = self.state.as_ref()?;
1372        if state.moments.len().saturating_sub(1) < max_degree {
1373            return None;
1374        }
1375        // Cached `Arc<CellMomentState>` is shared across LRU hits, so we
1376        // cannot reuse the inner vector in place. Clone the underlying state
1377        // and (rarely) truncate down to the requested degree to honour the
1378        // public moment-length contract.
1379        let mut state = (**state).clone();
1380        state.moments.truncate(max_degree + 1);
1381        Some(state)
1382    }
1383
1384    #[inline]
1385    pub fn derivative_state_for_degree(
1386        &self,
1387        max_degree: usize,
1388    ) -> Option<CellDerivativeMomentState> {
1389        let state = self.derivative_state.as_ref()?;
1390        if state.moments.len().saturating_sub(1) < max_degree {
1391            return None;
1392        }
1393        // See `state_for_degree`: shared `Arc` forces an inner clone here.
1394        let mut state = (**state).clone();
1395        state.moments.truncate(max_degree + 1);
1396        Some(state)
1397    }
1398
1399    #[inline]
1400    pub fn with_value(mut self, state: Arc<CellMomentState>) -> Self {
1401        self.state = Some(state);
1402        self
1403    }
1404
1405    #[inline]
1406    pub fn with_derivative(mut self, state: Arc<CellDerivativeMomentState>) -> Self {
1407        self.derivative_state = Some(state);
1408        self
1409    }
1410}
1411
1412impl ResidentBytes for CachedCellMoments {
1413    fn resident_bytes(&self) -> usize {
1414        let value_bytes = self
1415            .state
1416            .as_ref()
1417            .map_or(0, |state| state.resident_bytes());
1418        let derivative_bytes = self
1419            .derivative_state
1420            .as_ref()
1421            .map_or(0, |state| state.resident_bytes());
1422        std::mem::size_of::<Self>()
1423            .saturating_add(value_bytes)
1424            .saturating_add(derivative_bytes)
1425    }
1426}
1427
1428#[derive(Debug, Default)]
1429pub struct CellMomentCacheStats {
1430    hits: AtomicU64,
1431    misses: AtomicU64,
1432}
1433
1434impl CellMomentCacheStats {
1435    #[inline]
1436    pub fn snapshot(&self) -> (u64, u64) {
1437        (
1438            self.hits.load(Ordering::Relaxed),
1439            self.misses.load(Ordering::Relaxed),
1440        )
1441    }
1442
1443    #[inline]
1444    pub fn hit_rate_delta(&self, before: (u64, u64)) -> (u64, u64, f64) {
1445        let (hits, misses) = self.snapshot();
1446        let dh = hits.saturating_sub(before.0);
1447        let dm = misses.saturating_sub(before.1);
1448        let total = dh + dm;
1449        let rate = if total == 0 {
1450            0.0
1451        } else {
1452            dh as f64 / total as f64
1453        };
1454        (dh, dm, rate)
1455    }
1456}
1457
1458pub type CellMomentLruCache = ByteLruCache<CellFingerprint, CachedCellMoments>;
1459
1460pub const CELL_MOMENT_INLINE_CAPACITY: usize = 10;
1461
1462pub type CellMomentVec = SmallVec<[f64; CELL_MOMENT_INLINE_CAPACITY]>;
1463
1464#[derive(Clone, Debug, PartialEq)]
1465pub struct CellMomentState {
1466    pub branch: ExactCellBranch,
1467    pub value: f64,
1468    pub moments: CellMomentVec,
1469}
1470
1471impl ResidentBytes for CellMomentState {
1472    fn resident_bytes(&self) -> usize {
1473        let spilled_bytes = if self.moments.spilled() {
1474            self.moments
1475                .capacity()
1476                .saturating_mul(std::mem::size_of::<f64>())
1477        } else {
1478            0
1479        };
1480        std::mem::size_of::<Self>().saturating_add(spilled_bytes)
1481    }
1482}
1483
1484#[derive(Clone, Debug, PartialEq)]
1485pub struct CellDerivativeMomentState {
1486    pub branch: ExactCellBranch,
1487    pub moments: CellMomentVec,
1488}
1489
1490impl ResidentBytes for CellDerivativeMomentState {
1491    fn resident_bytes(&self) -> usize {
1492        let spilled_bytes = if self.moments.spilled() {
1493            self.moments
1494                .capacity()
1495                .saturating_mul(std::mem::size_of::<f64>())
1496        } else {
1497            0
1498        };
1499        std::mem::size_of::<Self>().saturating_add(spilled_bytes)
1500    }
1501}
1502
1503#[derive(Clone, Copy, Debug, PartialEq)]
1504pub struct CellMomentStateRef<'a> {
1505    pub branch: ExactCellBranch,
1506    pub value: f64,
1507    pub moments: &'a [f64],
1508}
1509
1510#[derive(Clone, Debug)]
1511pub struct CellMomentScratch {
1512    moments: Vec<f64>,
1513}
1514
1515impl Default for CellMomentScratch {
1516    fn default() -> Self {
1517        // Pre-size to the codebase's max moment degree so steady-state
1518        // `prepare_moments` calls never reallocate. Calls with `len`
1519        // exceeding this still reserve lazily.
1520        Self {
1521            moments: Vec::with_capacity(MAX_AFFINE_ANCHOR_DEGREE + 1),
1522        }
1523    }
1524}
1525
1526impl CellMomentScratch {
1527    pub fn new() -> Self {
1528        Self::default()
1529    }
1530
1531    pub fn with_capacity(max_degree: usize) -> Self {
1532        Self {
1533            moments: Vec::with_capacity(max_degree + 1),
1534        }
1535    }
1536
1537    #[inline]
1538    fn prepare_moments(&mut self, len: usize) -> &mut [f64] {
1539        if self.moments.capacity() < len {
1540            CELL_MOMENT_REALLOCS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1541            self.moments.reserve(len - self.moments.capacity());
1542        }
1543        // Grow monotonically: shorter requests should not truncate the backing
1544        // storage and then zero the old tail when a later request grows again.
1545        // Only the active prefix is scratch for this evaluation.
1546        if self.moments.len() < len {
1547            self.moments.resize(len, 0.0);
1548        }
1549        let out = &mut self.moments[..len];
1550        out.fill(0.0);
1551        out
1552    }
1553}
1554
1555/// Counter for moment-buffer reallocations in `prepare_moments`. Production
1556/// code increments this on every buffer growth; the test mod inspects it to
1557/// assert the steady-state hot loop allocates exactly once per row buffer.
1558pub(crate) static CELL_MOMENT_REALLOCS: std::sync::atomic::AtomicUsize =
1559    std::sync::atomic::AtomicUsize::new(0);
1560
1561/// Canonical 20-point Gauss–Legendre nodes on [-1, 1] (Abramowitz & Stegun
1562/// 25.4), tabulated to f64 precision. Used here for the Drezner–Wesolowsky
1563/// bivariate normal CDF representation — 20 points give >30-digit accuracy for
1564/// the smooth arcsin-transformed integrand, ensuring the BVN value is exact to
1565/// f64 precision for all (h, k, ρ) — and shared with the cubic-cell B-spline
1566/// moment parity gate in [`crate::gpu_kernels::cubic_bspline_moments`].
1567pub const GL20_NODES: [f64; 20] = [
1568    -0.993_128_599_185_094_9,
1569    -0.963_971_927_277_913_8,
1570    -0.912_234_428_251_326,
1571    -0.839_116_971_822_218_8,
1572    -0.746_331_906_460_150_8,
1573    -0.636_053_680_726_515,
1574    -0.510_867_001_950_827_1,
1575    -0.373_706_088_715_419_6,
1576    -0.227_785_851_141_645_1,
1577    -0.076_526_521_133_497_33,
1578    0.076_526_521_133_497_33,
1579    0.227_785_851_141_645_1,
1580    0.373_706_088_715_419_6,
1581    0.510_867_001_950_827_1,
1582    0.636_053_680_726_515,
1583    0.746_331_906_460_150_8,
1584    0.839_116_971_822_218_8,
1585    0.912_234_428_251_326,
1586    0.963_971_927_277_913_8,
1587    0.993_128_599_185_094_9,
1588];
1589
1590/// Companion weights to [`GL20_NODES`]. Symmetric, summing to 2.
1591pub const GL20_WEIGHTS: [f64; 20] = [
1592    0.017_614_007_139_152_12,
1593    0.040_601_429_800_386_94,
1594    0.062_672_048_334_109_06,
1595    0.083_276_741_576_704_75,
1596    0.101_930_119_817_240_4,
1597    0.118_194_531_961_518_4,
1598    0.131_688_638_449_176_6,
1599    0.142_096_109_318_382_1,
1600    0.149_172_986_472_603_7,
1601    0.152_753_387_130_725_9,
1602    0.152_753_387_130_725_9,
1603    0.149_172_986_472_603_7,
1604    0.142_096_109_318_382_1,
1605    0.131_688_638_449_176_6,
1606    0.118_194_531_961_518_4,
1607    0.101_930_119_817_240_4,
1608    0.083_276_741_576_704_75,
1609    0.062_672_048_334_109_06,
1610    0.040_601_429_800_386_94,
1611    0.017_614_007_139_152_12,
1612];
1613
1614/// Provenance-tagged breakpoint dedup: sorts ascending and merges entries
1615/// coinciding within 1e-12, but when a fixed score break and a link-knot
1616/// crossing coincide (the kink configuration), the surviving entry keeps
1617/// the `Fixed` tag — a deterministic choice; the z location is identical
1618/// either way.
1619fn dedup_sorted_tagged_breakpoints(points: &mut Vec<(f64, PartitionEdge)>) {
1620    points.sort_by(|lhs, rhs| {
1621        lhs.0
1622            .partial_cmp(&rhs.0)
1623            .unwrap_or(std::cmp::Ordering::Equal)
1624    });
1625    points.dedup_by(|lhs, rhs| {
1626        let coincide = if lhs.0 == rhs.0 {
1627            true
1628        } else if lhs.0.is_finite() && rhs.0.is_finite() {
1629            (lhs.0 - rhs.0).abs() <= 1e-12
1630        } else {
1631            false
1632        };
1633        if coincide && matches!(lhs.1, PartitionEdge::Fixed(_)) {
1634            // `dedup_by` keeps `rhs` (the earlier element) — propagate the
1635            // Fixed tag onto the survivor.
1636            rhs.1 = lhs.1;
1637        }
1638        coincide
1639    });
1640}
1641
1642#[inline]
1643pub fn interval_probe_point(left: f64, right: f64) -> Result<f64, String> {
1644    if !(left < right) {
1645        return Err(CubicCellKernelError::invalid_interval(format!(
1646            "interval probe requires ordered bounds, got [{left}, {right}]"
1647        ))
1648        .into());
1649    }
1650    if left.is_finite() && right.is_finite() {
1651        Ok(0.5 * (left + right))
1652    } else if left == f64::NEG_INFINITY && right == f64::INFINITY {
1653        Ok(0.0)
1654    } else if left == f64::NEG_INFINITY && right.is_finite() {
1655        Ok(right - 1.0)
1656    } else if left.is_finite() && right == f64::INFINITY {
1657        Ok(left + 1.0)
1658    } else {
1659        Err(CubicCellKernelError::invalid_interval(format!(
1660            "interval probe requires finite bounds or full infinities, got [{left}, {right}]"
1661        ))
1662        .into())
1663    }
1664}
1665
1666#[inline]
1667pub fn quartic_qprime_coefficients(c0: f64, c1: f64, c2: f64) -> [f64; 4] {
1668    [
1669        c0 * c1,
1670        1.0 + c1 * c1 + 2.0 * c0 * c2,
1671        3.0 * c1 * c2,
1672        2.0 * c2 * c2,
1673    ]
1674}
1675
1676#[inline]
1677pub fn sextic_qprime_coefficients(c0: f64, c1: f64, c2: f64, c3: f64) -> [f64; 6] {
1678    [
1679        c0 * c1,
1680        1.0 + c1 * c1 + 2.0 * c0 * c2,
1681        3.0 * c0 * c3 + 3.0 * c1 * c2,
1682        4.0 * c1 * c3 + 2.0 * c2 * c2,
1683        5.0 * c2 * c3,
1684        3.0 * c3 * c3,
1685    ]
1686}
1687
1688/// Boundary term `right^n · exp(−q(right)) − left^n · exp(−q(left))` used by
1689/// the moment recurrences. Takes precomputed `left^n` and `right^n` so callers
1690/// can roll the powers across a recurrence — each iteration becomes one
1691/// multiply instead of a fresh `powi(n)`.
1692#[inline]
1693fn moment_boundary_term_with_powers(
1694    cell: DenestedCubicCell,
1695    left_pow_n: f64,
1696    right_pow_n: f64,
1697) -> f64 {
1698    let left_term = if cell.left.is_infinite() {
1699        0.0
1700    } else {
1701        left_pow_n * (-cell.q(cell.left)).exp()
1702    };
1703    let right_term = if cell.right.is_infinite() {
1704        0.0
1705    } else {
1706        right_pow_n * (-cell.q(cell.right)).exp()
1707    };
1708    right_term - left_term
1709}
1710
1711#[inline]
1712fn base_moments_match_direct(base: &[f64], direct: &[f64]) -> bool {
1713    base.iter()
1714        .zip(direct.iter())
1715        .all(|(&lhs, &rhs)| (lhs - rhs).abs() <= 1e-10 * (1.0 + lhs.abs().max(rhs.abs())))
1716}
1717
1718#[inline]
1719fn direct_non_affine_moments_if_base_matches(
1720    cell: DenestedCubicCell,
1721    base: &[f64],
1722    max_degree: usize,
1723) -> Option<Vec<f64>> {
1724    if !cell.left.is_finite() || !cell.right.is_finite() {
1725        return None;
1726    }
1727    // When the supplied base moments are the actual moments of this fixed
1728    // finite cell, prefer the same quadrature-backed evaluator used by the
1729    // public non-affine moment path.  The algebraic raising recurrence is kept
1730    // below for callers that intentionally pass symbolic or otherwise
1731    // non-cell-consistent bases, but repeatedly dividing by the quartic/sextic
1732    // leading coefficient can amplify harmless base-roundoff into high-order
1733    // moment error.
1734    let (moments, _) = evaluate_non_affine_cell_simd::<false>(cell, max_degree);
1735    if base_moments_match_direct(base, &moments) {
1736        Some(moments.into_vec())
1737    } else {
1738        None
1739    }
1740}
1741
1742pub fn reduce_quartic_moments(
1743    cell: DenestedCubicCell,
1744    base_m0_m2: [f64; 3],
1745    max_degree: usize,
1746) -> Result<Vec<f64>, String> {
1747    if max_degree <= 2 {
1748        return Ok(base_m0_m2[..=max_degree].to_vec());
1749    }
1750    if let Some(moments) = direct_non_affine_moments_if_base_matches(cell, &base_m0_m2, max_degree)
1751    {
1752        return Ok(moments);
1753    }
1754    let d = quartic_qprime_coefficients(cell.c0, cell.c1, cell.c2);
1755    let lead = d[3];
1756    if !lead.is_finite() || lead.abs() <= 1e-18 {
1757        return Err(CubicCellKernelError::invalid_cell_shape(format!(
1758            "quartic moment reduction requires nonzero leading coefficient, got {lead:.3e}"
1759        ))
1760        .into());
1761    }
1762    let mut moments = vec![0.0; max_degree + 1];
1763    moments[0] = base_m0_m2[0];
1764    moments[1] = base_m0_m2[1];
1765    moments[2] = base_m0_m2[2];
1766    // Roll left^n / right^n across the recurrence rather than calling
1767    // `powi(n)` each iteration. Skip the multiply when an endpoint is
1768    // infinite — the boundary helper ignores the power in that case, and
1769    // ∞·0 would produce a NaN we'd then have to mask off anyway.
1770    let left_finite = cell.left.is_finite();
1771    let right_finite = cell.right.is_finite();
1772    let mut left_pow_n = if left_finite { 1.0 } else { 0.0 };
1773    let mut right_pow_n = if right_finite { 1.0 } else { 0.0 };
1774    for n in 0..=(max_degree - 3) {
1775        let b_n = moment_boundary_term_with_powers(cell, left_pow_n, right_pow_n);
1776        let mut numer = if n == 0 {
1777            0.0
1778        } else {
1779            (n as f64) * moments[n - 1]
1780        };
1781        for j in 0..=2 {
1782            numer -= d[j] * moments[n + j];
1783        }
1784        numer -= b_n;
1785        moments[n + 3] = numer / lead;
1786        if left_finite {
1787            left_pow_n *= cell.left;
1788        }
1789        if right_finite {
1790            right_pow_n *= cell.right;
1791        }
1792    }
1793    Ok(moments)
1794}
1795
1796pub fn reduce_sextic_moments(
1797    cell: DenestedCubicCell,
1798    base_m0_m4: [f64; 5],
1799    max_degree: usize,
1800) -> Result<Vec<f64>, String> {
1801    if max_degree <= 4 {
1802        return Ok(base_m0_m4[..=max_degree].to_vec());
1803    }
1804    if let Some(moments) = direct_non_affine_moments_if_base_matches(cell, &base_m0_m4, max_degree)
1805    {
1806        return Ok(moments);
1807    }
1808    let d = sextic_qprime_coefficients(cell.c0, cell.c1, cell.c2, cell.c3);
1809    let lead = d[5];
1810    if !lead.is_finite() {
1811        return Err(CubicCellKernelError::invalid_cell_shape(format!(
1812            "sextic moment reduction encountered non-finite leading coefficient: {lead:.3e}"
1813        ))
1814        .into());
1815    }
1816    let recurrence_scale = d[..5]
1817        .iter()
1818        .fold(1.0_f64, |scale, coefficient| scale.max(coefficient.abs()));
1819    if lead.abs() <= f64::EPSILON * recurrence_scale {
1820        // Dividing the recurrence by an unresolved leading coefficient is
1821        // ill-conditioned. Preserve the exact cubic and use the canonical
1822        // fixed-rule transport; lowering its degree would change the model.
1823        return evaluate_non_affine_cell_state(cell, ExactCellBranch::Sextic, max_degree)
1824            .map(|state| state.moments.into_vec());
1825    }
1826    let mut moments = vec![0.0; max_degree + 1];
1827    for (idx, value) in base_m0_m4.into_iter().enumerate() {
1828        moments[idx] = value;
1829    }
1830    let left_finite = cell.left.is_finite();
1831    let right_finite = cell.right.is_finite();
1832    let mut left_pow_n = if left_finite { 1.0 } else { 0.0 };
1833    let mut right_pow_n = if right_finite { 1.0 } else { 0.0 };
1834    for n in 0..=(max_degree - 5) {
1835        let b_n = moment_boundary_term_with_powers(cell, left_pow_n, right_pow_n);
1836        let mut numer = if n == 0 {
1837            0.0
1838        } else {
1839            (n as f64) * moments[n - 1]
1840        };
1841        for j in 0..=4 {
1842            numer -= d[j] * moments[n + j];
1843        }
1844        numer -= b_n;
1845        moments[n + 5] = numer / lead;
1846        if left_finite {
1847            left_pow_n *= cell.left;
1848        }
1849        if right_finite {
1850            right_pow_n *= cell.right;
1851        }
1852    }
1853    Ok(moments)
1854}
1855
1856#[inline]
1857pub fn cell_first_derivative_from_moments(
1858    derivative_coefficients: &[f64],
1859    moments: &[f64],
1860) -> Result<f64, String> {
1861    let value = moment_dot_with_coefficients(derivative_coefficients, moments, "first derivative")?;
1862    Ok(value * INV_TWO_PI)
1863}
1864
1865/// Maximum moment index (i.e. `max_degree` passed to
1866/// `evaluate_cell_moments`) required to evaluate
1867/// `cell_first_derivative_from_moments(derivative_coefficients, moments)`.
1868///
1869/// Callers must request at least `cell_first_derivative_required_max_degree(
1870/// derivative_coefficients)` so the moment dot is well-defined; #321 was
1871/// caused by hardcoding a smaller value at one call site.
1872#[inline]
1873pub fn cell_first_derivative_required_max_degree(derivative_coefficients: &[f64]) -> usize {
1874    derivative_coefficients.len().saturating_sub(1)
1875}
1876
1877/// Maximum moment index required by `cell_second_derivative_from_moments`.
1878///
1879/// Mirrors the kernel's internal `needed = max(second_deg, product_deg) + 1`
1880/// computation, but returned as `max_degree` (i.e. `needed - 1`) so it lines
1881/// up with the `evaluate_cell_moments(cell, max_degree)` argument convention.
1882/// The contraction folds an inner cubic `eta` (always degree 3) with the two
1883/// first-coefficient slices and the second-coefficient slice; the +3 below is
1884/// the cubic-cell eta polynomial.
1885#[inline]
1886pub fn cell_second_derivative_required_max_degree(
1887    first_coefficients_r: &[f64],
1888    first_coefficients_s: &[f64],
1889    second_coefficients_rs: &[f64],
1890) -> usize {
1891    let second_degree = second_coefficients_rs.len().saturating_sub(1);
1892    let product_degree = first_coefficients_r.len().saturating_sub(1)
1893        + first_coefficients_s.len().saturating_sub(1)
1894        + 3;
1895    second_degree.max(product_degree)
1896}
1897
1898#[inline]
1899pub fn cell_polynomial_integral_from_moments(
1900    polynomial_coefficients: &[f64],
1901    moments: &[f64],
1902    label: &str,
1903) -> Result<f64, String> {
1904    let value = moment_dot_with_coefficients(polynomial_coefficients, moments, label)?;
1905    Ok(value * INV_TWO_PI)
1906}
1907
1908#[inline]
1909pub fn cell_second_derivative_from_moments(
1910    cell: DenestedCubicCell,
1911    first_coefficients_r: &[f64],
1912    first_coefficients_s: &[f64],
1913    second_coefficients_rs: &[f64],
1914    moments: &[f64],
1915) -> Result<f64, String> {
1916    let second_degree = second_coefficients_rs.len().saturating_sub(1);
1917    let product_degree = first_coefficients_r.len().saturating_sub(1)
1918        + first_coefficients_s.len().saturating_sub(1)
1919        + 3;
1920    let needed = second_degree.max(product_degree) + 1;
1921    if needed > moments.len() {
1922        return Err(CubicCellKernelError::insufficient_moments(format!(
1923            "insufficient reduced moments for second derivative: need {}, have {}",
1924            needed,
1925            moments.len()
1926        ))
1927        .into());
1928    }
1929    let second_term = moment_dot_with_coefficients_unchecked(second_coefficients_rs, moments);
1930    // Fold `Σ_{e,i,j} eta[e]·r[i]·s[j]·moments[e+i+j]` into a single dot
1931    // against `moments`. Convolving `eta ⊗ r ⊗ s` first turns the original
1932    // `len(eta)·len(r)·len(s)` triple loop (typically 4·4·4 = 64 mul-adds
1933    // per call) into `len(eta)·len(r) + (len(eta)+len(r)-1)·len(s) +
1934    // len(out)` ≈ 16 + 28 + 10 = 54 mul-adds, with the inner loops now in
1935    // straight-line FMA-friendly form.
1936    let cubic = [cell.c0, cell.c1, cell.c2, cell.c3];
1937    // Capacity bound: cubic (4) + first_r (≤MAX) + first_s (≤MAX) - 2.
1938    // First-coefficient slices are passed in as `[f64; 4]` from every
1939    // production caller; sizing to 32 covers any realistic test input.
1940    const SCRATCH: usize = 32;
1941    let mut eta_r = [0.0_f64; SCRATCH];
1942    let mut eta_rs = [0.0_f64; SCRATCH];
1943    let er_len = poly_conv_into(&cubic, first_coefficients_r, &mut eta_r);
1944    let ers_len = poly_conv_into(&eta_r[..er_len], first_coefficients_s, &mut eta_rs);
1945    let mut eta_term = 0.0;
1946    for k in 0..ers_len {
1947        eta_term = eta_rs[k].mul_add(moments[k], eta_term);
1948    }
1949    Ok((second_term - eta_term) * INV_TWO_PI)
1950}
1951
1952/// Pointwise value of the cell second-derivative integrand
1953/// `(∂²/∂r∂s) exp(-q(z))/2π` at a single `z`, evaluated from the SAME
1954/// `(r, s, rs)` coefficient polynomials the moment reduction
1955/// [`cell_second_derivative_from_moments`] integrates:
1956///
1957/// ```text
1958///   F_rs(z) = ( c_rs(z) - η(z)·c_r(z)·c_s(z) ) · exp(-q(z)) · 1/2π ,
1959/// ```
1960///
1961/// with `c_•(z) = Σ_k coeff_•[k]·zᵏ`, `η(z)` the cell cubic, and
1962/// `q(z) = ½(z² + η(z)²)`. This is the integrand whose `[cell.left,
1963/// cell.right]` integral the from-moments form returns — needed for the
1964/// Leibniz boundary term when a cell edge (a link-knot crossing
1965/// `z=(τ-a)/b`) moves with a parameter (the slope `b`): the directional
1966/// derivative of `∫_{z_L}^{z_R} F_rs dz` picks up
1967/// `F_rs(z_R)·z_R'(dir) - F_rs(z_L)·z_L'(dir)` on top of the fixed-domain
1968/// part. Coefficient sign convention matches the simpson reference
1969/// (`numeric_ab`): pass the ACTUAL derivative-coefficient polynomials
1970/// `∂c/∂r` etc. (not the negated `neg_dc_d•` the moment path consumes).
1971#[inline]
1972pub fn cell_second_derivative_boundary_integrand(
1973    cell: DenestedCubicCell,
1974    first_coefficients_r: &[f64],
1975    first_coefficients_s: &[f64],
1976    second_coefficients_rs: &[f64],
1977    z: f64,
1978) -> f64 {
1979    let eta = cell.eta(z);
1980    let c_r = poly_eval_at(first_coefficients_r, z);
1981    let c_s = poly_eval_at(first_coefficients_s, z);
1982    let c_rs = poly_eval_at(second_coefficients_rs, z);
1983    (c_rs - eta * c_r * c_s) * (-cell.q(z)).exp() * INV_TWO_PI
1984}
1985
1986/// Pointwise value of the cell third-derivative integrand
1987/// `(∂³/∂r∂s∂t) exp(-q(z))/2π` at a single `z`, evaluated from the same
1988/// `(r, s, t, rs, rt, st, rst)` coefficient polynomials that
1989/// [`cell_third_derivative_from_moments`] integrates:
1990///
1991/// ```text
1992/// F_rst(z) = (
1993///     c_rst(z)
1994///   - η(z)·(c_rs(z)c_t(z) + c_rt(z)c_s(z) + c_st(z)c_r(z))
1995///   + (η(z)² - 1)·c_r(z)c_s(z)c_t(z)
1996/// ) · exp(-q(z)) · 1/2π .
1997/// ```
1998///
1999/// This is the boundary value for differentiating an already-third-order
2000/// fixed-domain integral with respect to a moving edge. The sign convention is
2001/// intentionally identical to [`cell_third_derivative_from_moments`]: callers
2002/// must pass the coefficient slices in the convention of the integral they are
2003/// differentiating. In particular, survival/probit paths that integrate the
2004/// jointly negated cell and coefficient slices must evaluate this boundary
2005/// integrand with the same joint negation; evaluating an un-negated boundary for
2006/// a negated fixed-domain integral flips the sign of this odd-order integrand.
2007#[inline]
2008pub fn cell_third_derivative_boundary_integrand(
2009    cell: DenestedCubicCell,
2010    first_coefficients_r: &[f64],
2011    first_coefficients_s: &[f64],
2012    first_coefficients_t: &[f64],
2013    second_coefficients_rs: &[f64],
2014    second_coefficients_rt: &[f64],
2015    second_coefficients_st: &[f64],
2016    third_coefficients_rst: &[f64],
2017    z: f64,
2018) -> f64 {
2019    let eta = cell.eta(z);
2020    let c_r = poly_eval_at(first_coefficients_r, z);
2021    let c_s = poly_eval_at(first_coefficients_s, z);
2022    let c_t = poly_eval_at(first_coefficients_t, z);
2023    let c_rs = poly_eval_at(second_coefficients_rs, z);
2024    let c_rt = poly_eval_at(second_coefficients_rt, z);
2025    let c_st = poly_eval_at(second_coefficients_st, z);
2026    let c_rst = poly_eval_at(third_coefficients_rst, z);
2027    let amplitude =
2028        c_rst - eta * (c_rs * c_t + c_rt * c_s + c_st * c_r) + (eta * eta - 1.0) * c_r * c_s * c_t;
2029    amplitude * (-cell.q(z)).exp() * INV_TWO_PI
2030}
2031
2032/// Pointwise value of the density-weighted integrand `g(z)·exp(-q(z))/2π` at a
2033/// single `z`, for an arbitrary integrand polynomial `g`.
2034///
2035/// This is the boundary value needed for the moving-domain (Leibniz) term of a
2036/// density-normalization integral `∫ g(z)·exp(-q(z))/2π dz` whose cell edge is a
2037/// link-knot crossing `z=(τ-a)/b` that moves with a parameter direction: the
2038/// directional derivative of the integral picks up
2039/// `g(z_R)·w(z_R)·z_R'(dir) - g(z_L)·w(z_L)·z_L'(dir)` on top of the
2040/// fixed-domain part, with `w(z)=exp(-q(z))/2π` the same weight the moment
2041/// reductions integrate. Unlike the Hessian-integral boundary term (which is
2042/// shared by adjacent cells and cancels across each interior knot), the
2043/// ln-density integrand `D_t`/`D_t,uv` carries a non-shared `g`, so this
2044/// Leibniz term does NOT cancel and must be added (gam#932/#979).
2045pub fn cell_density_boundary_integrand(cell: DenestedCubicCell, g: &[f64], z: f64) -> f64 {
2046    poly_eval_at(g, z) * (-cell.q(z)).exp() * INV_TWO_PI
2047}
2048
2049/// Horner evaluation of `Σ_k coefficients[k]·zᵏ`.
2050#[inline]
2051fn poly_eval_at(coefficients: &[f64], z: f64) -> f64 {
2052    let mut acc = 0.0_f64;
2053    for &c in coefficients.iter().rev() {
2054        acc = acc.mul_add(z, c);
2055    }
2056    acc
2057}
2058
2059#[inline]
2060fn moment_dot_with_coefficients(
2061    coefficients: &[f64],
2062    moments: &[f64],
2063    label: &str,
2064) -> Result<f64, String> {
2065    if coefficients.len() > moments.len() {
2066        return Err(CubicCellKernelError::insufficient_moments(format!(
2067            "insufficient reduced moments for {label}: need {}, have {}",
2068            coefficients.len(),
2069            moments.len()
2070        ))
2071        .into());
2072    }
2073    Ok(moment_dot_with_coefficients_unchecked(
2074        coefficients,
2075        moments,
2076    ))
2077}
2078
2079#[inline]
2080fn moment_dot_with_coefficients_unchecked(coefficients: &[f64], moments: &[f64]) -> f64 {
2081    let mut acc = 0.0;
2082    for (idx, &coeff) in coefficients.iter().enumerate() {
2083        acc = coeff.mul_add(moments[idx], acc);
2084    }
2085    acc
2086}
2087
2088/// Convolve two polynomial coefficient slices into a fixed-capacity output
2089/// buffer. Returns the populated length (`lhs.len() + rhs.len() - 1` when
2090/// both are non-empty). The buffer's tail (beyond the returned length) is
2091/// not zeroed; callers must use only the returned prefix.
2092///
2093/// Used by the multi-derivative reductions to fold `eta · r · s · …` triple
2094/// and quadruple sums into a single moment dot, eliminating the
2095/// `O(deg^3)`/`O(deg^4)` inner-loop work that dominated the
2096/// `cell_*_derivative_from_moments` hot leaves on large-scale fits.
2097#[inline]
2098fn poly_conv_into(lhs: &[f64], rhs: &[f64], out: &mut [f64]) -> usize {
2099    if lhs.is_empty() || rhs.is_empty() {
2100        return 0;
2101    }
2102    let len = lhs.len() + rhs.len() - 1;
2103    assert!(out.len() >= len);
2104    for slot in out[..len].iter_mut() {
2105        *slot = 0.0;
2106    }
2107    for (i, &lv) in lhs.iter().enumerate() {
2108        for (j, &rv) in rhs.iter().enumerate() {
2109            out[i + j] = lv.mul_add(rv, out[i + j]);
2110        }
2111    }
2112    len
2113}
2114
2115#[inline]
2116fn require_moments_degree(
2117    required_degree: usize,
2118    moments: &[f64],
2119    label: &str,
2120) -> Result<(), String> {
2121    if required_degree >= moments.len() {
2122        return Err(CubicCellKernelError::insufficient_moments(format!(
2123            "insufficient reduced moments for {label}: need {}, have {}",
2124            required_degree + 1,
2125            moments.len()
2126        ))
2127        .into());
2128    }
2129    Ok::<(), _>(())
2130}
2131
2132#[inline]
2133fn require_scratch_capacity(
2134    required_len: usize,
2135    capacity: usize,
2136    label: &str,
2137) -> Result<(), String> {
2138    if required_len > capacity {
2139        return Err(CubicCellKernelError::insufficient_moments(format!(
2140            "{label} polynomial convolution scratch too small: need {required_len}, have {capacity}"
2141        ))
2142        .into());
2143    }
2144    Ok::<(), _>(())
2145}
2146
2147#[inline]
2148fn convolution_chain_len(lengths: &[usize]) -> usize {
2149    if lengths.is_empty() || lengths.contains(&0) {
2150        0
2151    } else {
2152        lengths.iter().sum::<usize>() - (lengths.len() - 1)
2153    }
2154}
2155
2156#[inline]
2157fn first_coefficients_degree(label: &str, coefficients: &[f64]) -> Result<usize, String> {
2158    coefficients
2159        .len()
2160        .checked_sub(1)
2161        .ok_or_else(|| format!("{label} first-derivative coefficients must be non-empty"))
2162}
2163
2164#[inline]
2165pub fn cell_third_derivative_from_moments(
2166    cell: DenestedCubicCell,
2167    first_coefficients_r: &[f64],
2168    first_coefficients_s: &[f64],
2169    first_coefficients_t: &[f64],
2170    second_coefficients_rs: &[f64],
2171    second_coefficients_rt: &[f64],
2172    second_coefficients_st: &[f64],
2173    third_coefficients_rst: &[f64],
2174    moments: &[f64],
2175) -> Result<f64, String> {
2176    let eta = [cell.c0, cell.c1, cell.c2, cell.c3];
2177    let r_degree = first_coefficients_degree("r", first_coefficients_r)?;
2178    let s_degree = first_coefficients_degree("s", first_coefficients_s)?;
2179    let t_degree = first_coefficients_degree("t", first_coefficients_t)?;
2180    let second_sum_degree = [
2181        second_coefficients_rs.len() + first_coefficients_t.len(),
2182        second_coefficients_rt.len() + first_coefficients_s.len(),
2183        second_coefficients_st.len() + first_coefficients_r.len(),
2184    ]
2185    .into_iter()
2186    .max()
2187    .unwrap_or(0)
2188    .saturating_sub(1);
2189    let triple_product_degree = r_degree + s_degree + t_degree;
2190    let needed = (third_coefficients_rst.len().saturating_sub(1))
2191        .max(3 + second_sum_degree)
2192        .max(6 + triple_product_degree);
2193    require_moments_degree(needed, moments, "third derivative")?;
2194
2195    let third_term = moment_dot_with_coefficients_unchecked(third_coefficients_rst, moments);
2196
2197    // This is a deliberately serial leaf kernel: each call performs only a
2198    // handful of fixed-size polynomial convolutions, so Rayon fan-out belongs
2199    // at the surrounding row/cell batch level rather than inside this hot path.
2200    const SCRATCH: usize = 32;
2201    let max_linear_conv_len = [
2202        convolution_chain_len(&[
2203            eta.len(),
2204            second_coefficients_rs.len(),
2205            first_coefficients_t.len(),
2206        ]),
2207        convolution_chain_len(&[
2208            eta.len(),
2209            second_coefficients_rt.len(),
2210            first_coefficients_s.len(),
2211        ]),
2212        convolution_chain_len(&[
2213            eta.len(),
2214            second_coefficients_st.len(),
2215            first_coefficients_r.len(),
2216        ]),
2217    ]
2218    .into_iter()
2219    .max()
2220    .unwrap_or(0);
2221    let max_cubic_conv_len = convolution_chain_len(&[
2222        7,
2223        first_coefficients_r.len(),
2224        first_coefficients_s.len(),
2225        first_coefficients_t.len(),
2226    ]);
2227    require_scratch_capacity(
2228        max_linear_conv_len.max(max_cubic_conv_len),
2229        SCRATCH,
2230        "third derivative",
2231    )?;
2232    let mut buf_a = [0.0_f64; SCRATCH];
2233    let mut buf_b = [0.0_f64; SCRATCH];
2234
2235    // eta_second_term = Σ over (rs⊗t, rt⊗s, st⊗r) of eta⊗product · moments.
2236    // Fold each of the three triple sums into a single moment dot.
2237    let mut eta_second_term = 0.0;
2238    let conv_dot = |first: &[f64],
2239                    second: &[f64],
2240                    buf_a: &mut [f64; SCRATCH],
2241                    buf_b: &mut [f64; SCRATCH]|
2242     -> f64 {
2243        let m = poly_conv_into(first, second, buf_a);
2244        let n = poly_conv_into(&eta, &buf_a[..m], buf_b);
2245        let mut acc = 0.0;
2246        for k in 0..n {
2247            acc = buf_b[k].mul_add(moments[k], acc);
2248        }
2249        acc
2250    };
2251    eta_second_term += conv_dot(
2252        second_coefficients_rs,
2253        first_coefficients_t,
2254        &mut buf_a,
2255        &mut buf_b,
2256    );
2257    eta_second_term += conv_dot(
2258        second_coefficients_rt,
2259        first_coefficients_s,
2260        &mut buf_a,
2261        &mut buf_b,
2262    );
2263    eta_second_term += conv_dot(
2264        second_coefficients_st,
2265        first_coefficients_r,
2266        &mut buf_a,
2267        &mut buf_b,
2268    );
2269
2270    // cubic_coeff_term = Σ_{e,i,j,k} (eta·eta − 1)[e] · r[i] · s[j] · t[k] · moments[e+i+j+k].
2271    // Convolve r⊗s, then ⊗t, then ⊗(eta·eta − 1), giving a single dot.
2272    let mut eta_sq_minus_one = [0.0_f64; 7];
2273    for (i, &eta_i) in eta.iter().enumerate() {
2274        for (j, &eta_j) in eta.iter().enumerate() {
2275            eta_sq_minus_one[i + j] = eta_i.mul_add(eta_j, eta_sq_minus_one[i + j]);
2276        }
2277    }
2278    eta_sq_minus_one[0] -= 1.0;
2279
2280    let rs_len = poly_conv_into(first_coefficients_r, first_coefficients_s, &mut buf_a);
2281    let rst_len = poly_conv_into(&buf_a[..rs_len], first_coefficients_t, &mut buf_b);
2282    // buf_a now reused for (eta_sq_minus_one ⊗ rst).
2283    let final_len = poly_conv_into(&eta_sq_minus_one, &buf_b[..rst_len], &mut buf_a);
2284    let mut cubic_coeff_term = 0.0;
2285    for k in 0..final_len {
2286        cubic_coeff_term = buf_a[k].mul_add(moments[k], cubic_coeff_term);
2287    }
2288
2289    Ok((third_term - eta_second_term + cubic_coeff_term) * INV_TWO_PI)
2290}
2291
2292#[inline]
2293pub fn cell_fourth_derivative_from_moments(
2294    cell: DenestedCubicCell,
2295    first_coefficients_r: &[f64],
2296    first_coefficients_s: &[f64],
2297    first_coefficients_t: &[f64],
2298    first_coefficients_u: &[f64],
2299    second_coefficients_rs: &[f64],
2300    second_coefficients_rt: &[f64],
2301    second_coefficients_ru: &[f64],
2302    second_coefficients_st: &[f64],
2303    second_coefficients_su: &[f64],
2304    second_coefficients_tu: &[f64],
2305    third_coefficients_rst: &[f64],
2306    third_coefficients_rsu: &[f64],
2307    third_coefficients_rtu: &[f64],
2308    third_coefficients_stu: &[f64],
2309    fourth_coefficients_rstu: &[f64],
2310    moments: &[f64],
2311) -> Result<f64, String> {
2312    let eta = [cell.c0, cell.c1, cell.c2, cell.c3];
2313    let r_degree = first_coefficients_degree("r", first_coefficients_r)?;
2314    let s_degree = first_coefficients_degree("s", first_coefficients_s)?;
2315    let t_degree = first_coefficients_degree("t", first_coefficients_t)?;
2316    let u_degree = first_coefficients_degree("u", first_coefficients_u)?;
2317    let linear_sum_degree = [
2318        third_coefficients_rst.len() + first_coefficients_u.len(),
2319        third_coefficients_rsu.len() + first_coefficients_t.len(),
2320        third_coefficients_rtu.len() + first_coefficients_s.len(),
2321        third_coefficients_stu.len() + first_coefficients_r.len(),
2322        second_coefficients_rs.len() + second_coefficients_tu.len(),
2323        second_coefficients_rt.len() + second_coefficients_su.len(),
2324        second_coefficients_ru.len() + second_coefficients_st.len(),
2325    ]
2326    .into_iter()
2327    .max()
2328    .unwrap_or(0)
2329    .saturating_sub(1);
2330    let quad_sum_degree = [
2331        second_coefficients_rs.len() + first_coefficients_t.len() + first_coefficients_u.len(),
2332        second_coefficients_rt.len() + first_coefficients_s.len() + first_coefficients_u.len(),
2333        second_coefficients_ru.len() + first_coefficients_s.len() + first_coefficients_t.len(),
2334        second_coefficients_st.len() + first_coefficients_r.len() + first_coefficients_u.len(),
2335        second_coefficients_su.len() + first_coefficients_r.len() + first_coefficients_t.len(),
2336        second_coefficients_tu.len() + first_coefficients_r.len() + first_coefficients_s.len(),
2337    ]
2338    .into_iter()
2339    .max()
2340    .unwrap_or(0)
2341    .saturating_sub(2);
2342    let quartic_product_degree = r_degree + s_degree + t_degree + u_degree;
2343    let needed = (fourth_coefficients_rstu.len().saturating_sub(1))
2344        .max(3 + linear_sum_degree)
2345        .max(6 + quad_sum_degree)
2346        .max(9 + quartic_product_degree);
2347    require_moments_degree(needed, moments, "fourth derivative")?;
2348
2349    let fourth_term = moment_dot_with_coefficients_unchecked(fourth_coefficients_rstu, moments);
2350
2351    // This is a deliberately serial leaf kernel: each call performs only a
2352    // handful of fixed-size polynomial convolutions, so Rayon fan-out belongs
2353    // at the surrounding row/cell batch level rather than inside this hot path.
2354    const SCRATCH: usize = 32;
2355    let max_linear_conv_len = [
2356        convolution_chain_len(&[
2357            eta.len(),
2358            third_coefficients_rst.len(),
2359            first_coefficients_u.len(),
2360        ]),
2361        convolution_chain_len(&[
2362            eta.len(),
2363            third_coefficients_rsu.len(),
2364            first_coefficients_t.len(),
2365        ]),
2366        convolution_chain_len(&[
2367            eta.len(),
2368            third_coefficients_rtu.len(),
2369            first_coefficients_s.len(),
2370        ]),
2371        convolution_chain_len(&[
2372            eta.len(),
2373            third_coefficients_stu.len(),
2374            first_coefficients_r.len(),
2375        ]),
2376        convolution_chain_len(&[
2377            eta.len(),
2378            second_coefficients_rs.len(),
2379            second_coefficients_tu.len(),
2380        ]),
2381        convolution_chain_len(&[
2382            eta.len(),
2383            second_coefficients_rt.len(),
2384            second_coefficients_su.len(),
2385        ]),
2386        convolution_chain_len(&[
2387            eta.len(),
2388            second_coefficients_ru.len(),
2389            second_coefficients_st.len(),
2390        ]),
2391    ]
2392    .into_iter()
2393    .max()
2394    .unwrap_or(0);
2395    let max_quad_conv_len = [
2396        convolution_chain_len(&[
2397            7,
2398            second_coefficients_rs.len(),
2399            first_coefficients_t.len(),
2400            first_coefficients_u.len(),
2401        ]),
2402        convolution_chain_len(&[
2403            7,
2404            second_coefficients_rt.len(),
2405            first_coefficients_s.len(),
2406            first_coefficients_u.len(),
2407        ]),
2408        convolution_chain_len(&[
2409            7,
2410            second_coefficients_ru.len(),
2411            first_coefficients_s.len(),
2412            first_coefficients_t.len(),
2413        ]),
2414        convolution_chain_len(&[
2415            7,
2416            second_coefficients_st.len(),
2417            first_coefficients_r.len(),
2418            first_coefficients_u.len(),
2419        ]),
2420        convolution_chain_len(&[
2421            7,
2422            second_coefficients_su.len(),
2423            first_coefficients_r.len(),
2424            first_coefficients_t.len(),
2425        ]),
2426        convolution_chain_len(&[
2427            7,
2428            second_coefficients_tu.len(),
2429            first_coefficients_r.len(),
2430            first_coefficients_s.len(),
2431        ]),
2432    ]
2433    .into_iter()
2434    .max()
2435    .unwrap_or(0);
2436    let max_quartic_conv_len = convolution_chain_len(&[
2437        10,
2438        first_coefficients_r.len(),
2439        first_coefficients_s.len(),
2440        first_coefficients_t.len(),
2441        first_coefficients_u.len(),
2442    ]);
2443    require_scratch_capacity(
2444        max_linear_conv_len
2445            .max(max_quad_conv_len)
2446            .max(max_quartic_conv_len),
2447        SCRATCH,
2448        "fourth derivative",
2449    )?;
2450    let mut buf_a = [0.0_f64; SCRATCH];
2451    let mut buf_b = [0.0_f64; SCRATCH];
2452
2453    // eta_linear_term = Σ over seven (rst⊗u, rsu⊗t, rtu⊗s, stu⊗r, rs⊗tu,
2454    // rt⊗su, ru⊗st) of eta⊗product · moments. Fold each triple sum into
2455    // a single moment dot.
2456    let conv_eta_dot = |first: &[f64],
2457                        second: &[f64],
2458                        buf_a: &mut [f64; SCRATCH],
2459                        buf_b: &mut [f64; SCRATCH]|
2460     -> f64 {
2461        let m = poly_conv_into(first, second, buf_a);
2462        let n = poly_conv_into(&eta, &buf_a[..m], buf_b);
2463        let mut acc = 0.0;
2464        for k in 0..n {
2465            acc = buf_b[k].mul_add(moments[k], acc);
2466        }
2467        acc
2468    };
2469    let mut eta_linear_term = 0.0;
2470    eta_linear_term += conv_eta_dot(
2471        third_coefficients_rst,
2472        first_coefficients_u,
2473        &mut buf_a,
2474        &mut buf_b,
2475    );
2476    eta_linear_term += conv_eta_dot(
2477        third_coefficients_rsu,
2478        first_coefficients_t,
2479        &mut buf_a,
2480        &mut buf_b,
2481    );
2482    eta_linear_term += conv_eta_dot(
2483        third_coefficients_rtu,
2484        first_coefficients_s,
2485        &mut buf_a,
2486        &mut buf_b,
2487    );
2488    eta_linear_term += conv_eta_dot(
2489        third_coefficients_stu,
2490        first_coefficients_r,
2491        &mut buf_a,
2492        &mut buf_b,
2493    );
2494    eta_linear_term += conv_eta_dot(
2495        second_coefficients_rs,
2496        second_coefficients_tu,
2497        &mut buf_a,
2498        &mut buf_b,
2499    );
2500    eta_linear_term += conv_eta_dot(
2501        second_coefficients_rt,
2502        second_coefficients_su,
2503        &mut buf_a,
2504        &mut buf_b,
2505    );
2506    eta_linear_term += conv_eta_dot(
2507        second_coefficients_ru,
2508        second_coefficients_st,
2509        &mut buf_a,
2510        &mut buf_b,
2511    );
2512
2513    let mut eta_sq_minus_one = [0.0_f64; 7];
2514    for (i, &eta_i) in eta.iter().enumerate() {
2515        for (j, &eta_j) in eta.iter().enumerate() {
2516            eta_sq_minus_one[i + j] = eta_i.mul_add(eta_j, eta_sq_minus_one[i + j]);
2517        }
2518    }
2519    eta_sq_minus_one[0] -= 1.0;
2520
2521    // quad_coeff_term: six (eta²−1)⊗A⊗B⊗C · moments sums, where the (A,B,C)
2522    // factors are: (rs,t,u), (rt,s,u), (ru,s,t), (st,r,u), (su,r,t), (tu,r,s).
2523    let mut buf_c = [0.0_f64; SCRATCH];
2524    let conv_weighted_triple_dot = |weight: &[f64],
2525                                    a: &[f64],
2526                                    b: &[f64],
2527                                    c: &[f64],
2528                                    buf_a: &mut [f64; SCRATCH],
2529                                    buf_b: &mut [f64; SCRATCH],
2530                                    buf_c: &mut [f64; SCRATCH]|
2531     -> f64 {
2532        let ab_len = poly_conv_into(a, b, buf_a);
2533        let abc_len = poly_conv_into(&buf_a[..ab_len], c, buf_b);
2534        let final_len = poly_conv_into(weight, &buf_b[..abc_len], buf_c);
2535        let mut acc = 0.0;
2536        for k in 0..final_len {
2537            acc = buf_c[k].mul_add(moments[k], acc);
2538        }
2539        acc
2540    };
2541    let mut quad_coeff_term = 0.0;
2542    quad_coeff_term += conv_weighted_triple_dot(
2543        &eta_sq_minus_one,
2544        second_coefficients_rs,
2545        first_coefficients_t,
2546        first_coefficients_u,
2547        &mut buf_a,
2548        &mut buf_b,
2549        &mut buf_c,
2550    );
2551    quad_coeff_term += conv_weighted_triple_dot(
2552        &eta_sq_minus_one,
2553        second_coefficients_rt,
2554        first_coefficients_s,
2555        first_coefficients_u,
2556        &mut buf_a,
2557        &mut buf_b,
2558        &mut buf_c,
2559    );
2560    quad_coeff_term += conv_weighted_triple_dot(
2561        &eta_sq_minus_one,
2562        second_coefficients_ru,
2563        first_coefficients_s,
2564        first_coefficients_t,
2565        &mut buf_a,
2566        &mut buf_b,
2567        &mut buf_c,
2568    );
2569    quad_coeff_term += conv_weighted_triple_dot(
2570        &eta_sq_minus_one,
2571        second_coefficients_st,
2572        first_coefficients_r,
2573        first_coefficients_u,
2574        &mut buf_a,
2575        &mut buf_b,
2576        &mut buf_c,
2577    );
2578    quad_coeff_term += conv_weighted_triple_dot(
2579        &eta_sq_minus_one,
2580        second_coefficients_su,
2581        first_coefficients_r,
2582        first_coefficients_t,
2583        &mut buf_a,
2584        &mut buf_b,
2585        &mut buf_c,
2586    );
2587    quad_coeff_term += conv_weighted_triple_dot(
2588        &eta_sq_minus_one,
2589        second_coefficients_tu,
2590        first_coefficients_r,
2591        first_coefficients_s,
2592        &mut buf_a,
2593        &mut buf_b,
2594        &mut buf_c,
2595    );
2596
2597    // cubic_weight = 3·eta − eta³ (same as the prior expansion: eta_sq*eta
2598    // negated, plus the 3·eta linear correction).
2599    let mut eta_sq = [0.0_f64; 7];
2600    for (i, &eta_i) in eta.iter().enumerate() {
2601        for (j, &eta_j) in eta.iter().enumerate() {
2602            eta_sq[i + j] = eta_i.mul_add(eta_j, eta_sq[i + j]);
2603        }
2604    }
2605    let mut cubic_weight = [0.0_f64; 10];
2606    for (i, &eta_sq_i) in eta_sq.iter().enumerate() {
2607        for (j, &eta_j) in eta.iter().enumerate() {
2608            cubic_weight[i + j] = (-eta_sq_i).mul_add(eta_j, cubic_weight[i + j]);
2609        }
2610    }
2611    for (idx, &eta_coeff) in eta.iter().enumerate() {
2612        cubic_weight[idx] += 3.0 * eta_coeff;
2613    }
2614
2615    // quartic_coeff_term: cubic_weight ⊗ r ⊗ s ⊗ t ⊗ u · moments. The
2616    // original quintuple loop did 10·4·4·4·4 = 2560 mul-adds per call;
2617    // four sequential convolutions plus one moment dot drop this to
2618    // ~16+28+40+52+16 ≈ 152 mul-adds.
2619    let rs_len = poly_conv_into(first_coefficients_r, first_coefficients_s, &mut buf_a);
2620    let rst_len = poly_conv_into(&buf_a[..rs_len], first_coefficients_t, &mut buf_b);
2621    let rstu_len = poly_conv_into(&buf_b[..rst_len], first_coefficients_u, &mut buf_a);
2622    let final_len = poly_conv_into(&cubic_weight, &buf_a[..rstu_len], &mut buf_b);
2623    let mut quartic_coeff_term = 0.0;
2624    for k in 0..final_len {
2625        quartic_coeff_term = buf_b[k].mul_add(moments[k], quartic_coeff_term);
2626    }
2627
2628    Ok((fourth_term - eta_linear_term + quad_coeff_term + quartic_coeff_term) * INV_TWO_PI)
2629}
2630
2631#[inline]
2632pub fn global_cubic_from_local(span: LocalSpanCubic) -> (f64, f64, f64, f64) {
2633    let left = span.left;
2634    let q0 = span.c0 - span.c1 * left + span.c2 * left * left - span.c3 * left * left * left;
2635    let q1 = span.c1 - 2.0 * span.c2 * left + 3.0 * span.c3 * left * left;
2636    let q2 = span.c2 - 3.0 * span.c3 * left;
2637    let q3 = span.c3;
2638    (q0, q1, q2, q3)
2639}
2640
2641/// Return the cubic polynomial coefficients (in `z`) of
2642/// `f(z) = link_span.evaluate(a + b*z)`.
2643///
2644/// `link_span.evaluate` is a cubic in its argument, so `f(z)` is also a cubic
2645/// in `z` and can be written exactly as
2646///
2647/// ```text
2648///     f(z) = d0 + d1·z + d2·z² + d3·z³
2649/// ```
2650///
2651/// where `(d0, d1, d2, d3)` are the values returned by this function. These
2652/// are **polynomial coefficients**, *not* derivatives of `f` at `z = 0`. The
2653/// relationship to Taylor derivatives is
2654///
2655/// ```text
2656///     d_k = f^(k)(0) / k!
2657/// ```
2658///
2659/// so `d0 = f(0)`, `d1 = f'(0)`, `d2 = ½·f''(0)`, `d3 = ⅙·f'''(0)`. Callers
2660/// such as [`denested_cell_coefficients`] and [`link_basis_cell_coefficients`]
2661/// rely on the polynomial-coefficient convention, since they propagate the
2662/// values directly as the `(c0, c1, c2, c3)` slots of a downstream polynomial
2663/// in `z`.
2664#[inline]
2665pub fn transformed_link_cubic(link_span: LocalSpanCubic, a: f64, b: f64) -> (f64, f64, f64, f64) {
2666    let shift = a - link_span.left;
2667    let d0 = link_span.c0
2668        + link_span.c1 * shift
2669        + link_span.c2 * shift * shift
2670        + link_span.c3 * shift * shift * shift;
2671    let d1 = b * (link_span.c1 + 2.0 * link_span.c2 * shift + 3.0 * link_span.c3 * shift * shift);
2672    let d2 = b * b * (link_span.c2 + 3.0 * link_span.c3 * shift);
2673    let d3 = link_span.c3 * b * b * b;
2674    (d0, d1, d2, d3)
2675}
2676
2677#[inline]
2678pub fn denested_cell_coefficients(
2679    score_span: LocalSpanCubic,
2680    link_span: LocalSpanCubic,
2681    a: f64,
2682    b: f64,
2683) -> [f64; 4] {
2684    let (h0, h1, h2, h3) = global_cubic_from_local(score_span);
2685    let (d0, d1, d2, d3) = transformed_link_cubic(link_span, a, b);
2686    [a + b * h0 + d0, b + b * h1 + d1, b * h2 + d2, b * h3 + d3]
2687}
2688
2689#[inline]
2690pub fn denested_cell_coefficient_partials(
2691    score_span: LocalSpanCubic,
2692    link_span: LocalSpanCubic,
2693    a: f64,
2694    b: f64,
2695) -> ([f64; 4], [f64; 4]) {
2696    let (h0, h1, h2, h3) = global_cubic_from_local(score_span);
2697    let shift = a - link_span.left;
2698    let alpha1 = link_span.c1;
2699    let alpha2 = link_span.c2;
2700    let alpha3 = link_span.c3;
2701    let dc_da = [
2702        1.0 + alpha1 + 2.0 * alpha2 * shift + 3.0 * alpha3 * shift * shift,
2703        b * (2.0 * alpha2 + 6.0 * alpha3 * shift),
2704        3.0 * alpha3 * b * b,
2705        0.0,
2706    ];
2707    let dc_db = [
2708        h0,
2709        1.0 + h1 + alpha1 + 2.0 * alpha2 * shift + 3.0 * alpha3 * shift * shift,
2710        h2 + 2.0 * b * (alpha2 + 3.0 * alpha3 * shift),
2711        h3 + 3.0 * alpha3 * b * b,
2712    ];
2713    (dc_da, dc_db)
2714}
2715
2716#[inline]
2717fn link_cubic_second_partials(
2718    link_span: LocalSpanCubic,
2719    a: f64,
2720    b: f64,
2721) -> ([f64; 4], [f64; 4], [f64; 4]) {
2722    let shift = a - link_span.left;
2723    let alpha2 = link_span.c2;
2724    let alpha3 = link_span.c3;
2725    let dc_daa = [
2726        2.0 * alpha2 + 6.0 * alpha3 * shift,
2727        6.0 * alpha3 * b,
2728        0.0,
2729        0.0,
2730    ];
2731    let dc_dab = [
2732        0.0,
2733        2.0 * alpha2 + 6.0 * alpha3 * shift,
2734        6.0 * alpha3 * b,
2735        0.0,
2736    ];
2737    let dc_dbb = [
2738        0.0,
2739        0.0,
2740        2.0 * (alpha2 + 3.0 * alpha3 * shift),
2741        6.0 * alpha3 * b,
2742    ];
2743    (dc_daa, dc_dab, dc_dbb)
2744}
2745
2746#[inline]
2747pub fn denested_cell_second_partials(
2748    score_span: LocalSpanCubic,
2749    link_span: LocalSpanCubic,
2750    a: f64,
2751    b: f64,
2752) -> ([f64; 4], [f64; 4], [f64; 4]) {
2753    let score_left = score_span.left;
2754    if !score_left.is_finite() {
2755        return ([f64::NAN; 4], [f64::NAN; 4], [f64::NAN; 4]);
2756    }
2757    link_cubic_second_partials(link_span, a, b)
2758}
2759
2760#[inline]
2761fn link_cubic_third_partials(
2762    link_span: LocalSpanCubic,
2763) -> ([f64; 4], [f64; 4], [f64; 4], [f64; 4]) {
2764    let alpha3 = link_span.c3;
2765    (
2766        [6.0 * alpha3, 0.0, 0.0, 0.0],
2767        [0.0, 6.0 * alpha3, 0.0, 0.0],
2768        [0.0, 0.0, 6.0 * alpha3, 0.0],
2769        [0.0, 0.0, 0.0, 6.0 * alpha3],
2770    )
2771}
2772
2773#[inline]
2774pub fn denested_cell_third_partials(
2775    link_span: LocalSpanCubic,
2776) -> ([f64; 4], [f64; 4], [f64; 4], [f64; 4]) {
2777    link_cubic_third_partials(link_span)
2778}
2779
2780#[inline]
2781pub fn score_basis_cell_coefficients(score_basis_span: LocalSpanCubic, b: f64) -> [f64; 4] {
2782    let (h0, h1, h2, h3) = global_cubic_from_local(score_basis_span);
2783    [b * h0, b * h1, b * h2, b * h3]
2784}
2785
2786#[inline]
2787pub fn link_basis_cell_coefficients(link_basis_span: LocalSpanCubic, a: f64, b: f64) -> [f64; 4] {
2788    let (d0, d1, d2, d3) = transformed_link_cubic(link_basis_span, a, b);
2789    [d0, d1, d2, d3]
2790}
2791
2792#[inline]
2793pub fn link_basis_cell_coefficient_partials(
2794    link_basis_span: LocalSpanCubic,
2795    a: f64,
2796    b: f64,
2797) -> ([f64; 4], [f64; 4]) {
2798    let shift = a - link_basis_span.left;
2799    let alpha1 = link_basis_span.c1;
2800    let alpha2 = link_basis_span.c2;
2801    let alpha3 = link_basis_span.c3;
2802    let dc_da = [
2803        alpha1 + 2.0 * alpha2 * shift + 3.0 * alpha3 * shift * shift,
2804        b * (2.0 * alpha2 + 6.0 * alpha3 * shift),
2805        3.0 * alpha3 * b * b,
2806        0.0,
2807    ];
2808    let dc_db = [
2809        0.0,
2810        alpha1 + 2.0 * alpha2 * shift + 3.0 * alpha3 * shift * shift,
2811        2.0 * b * (alpha2 + 3.0 * alpha3 * shift),
2812        3.0 * alpha3 * b * b,
2813    ];
2814    (dc_da, dc_db)
2815}
2816
2817#[inline]
2818pub fn link_basis_cell_second_partials(
2819    link_basis_span: LocalSpanCubic,
2820    a: f64,
2821    b: f64,
2822) -> ([f64; 4], [f64; 4], [f64; 4]) {
2823    link_cubic_second_partials(link_basis_span, a, b)
2824}
2825
2826#[inline]
2827pub fn link_basis_cell_third_partials(
2828    link_basis_span: LocalSpanCubic,
2829) -> ([f64; 4], [f64; 4], [f64; 4], [f64; 4]) {
2830    link_cubic_third_partials(link_basis_span)
2831}
2832
2833pub fn build_denested_partition_cells<FS, FL>(
2834    a: f64,
2835    b: f64,
2836    score_breaks: &[f64],
2837    link_breaks: &[f64],
2838    score_span_at: FS,
2839    link_span_at: FL,
2840) -> Result<Vec<DenestedPartitionCell>, String>
2841where
2842    FS: FnMut(f64) -> Result<LocalSpanCubic, String>,
2843    FL: FnMut(f64) -> Result<LocalSpanCubic, String>,
2844{
2845    build_denested_partition_cells_with_tails(
2846        a,
2847        b,
2848        score_breaks,
2849        link_breaks,
2850        score_span_at,
2851        link_span_at,
2852    )
2853}
2854
2855/// Build a partition covering `(-∞, +∞)` with parameter-independent outer
2856/// bounds.  Interior cells use the same finite-cell polynomial algebra.
2857/// The two tail cells are guaranteed affine (c2=c3=0) because both
2858/// deviations saturate to constants outside their knot support.
2859///
2860/// The tail cells' score/link spans come from the same closures evaluated
2861/// at a representative point in the tail region — the closures must return
2862/// constant (c1=c2=c3=0) cubics for points outside support.
2863pub fn build_denested_partition_cells_with_tails<FS, FL>(
2864    a: f64,
2865    b: f64,
2866    score_breaks: &[f64],
2867    link_breaks: &[f64],
2868    mut score_span_at: FS,
2869    mut link_span_at: FL,
2870) -> Result<Vec<DenestedPartitionCell>, String>
2871where
2872    FS: FnMut(f64) -> Result<LocalSpanCubic, String>,
2873    FL: FnMut(f64) -> Result<LocalSpanCubic, String>,
2874{
2875    // Collect all INTERNAL split points (finite), each tagged with its
2876    // provenance: a fixed score break or a link-knot crossing. Provenance
2877    // identifies the cell's `(a, b)` family for the Chebyshev moment-family
2878    // layer; the z coordinates alone cannot distinguish the two kinds.
2879    let mut split_points: Vec<(f64, PartitionEdge)> = score_breaks
2880        .iter()
2881        .map(|&sigma| (sigma, PartitionEdge::Fixed(sigma)))
2882        .collect();
2883    if b.abs() > 1e-12 {
2884        for &tau in link_breaks {
2885            let z = (tau - a) / b;
2886            if z.is_finite() {
2887                split_points.push((z, PartitionEdge::Crossing { tau }));
2888            }
2889        }
2890    }
2891    dedup_sorted_tagged_breakpoints(&mut split_points);
2892
2893    let mut out = Vec::new();
2894
2895    if split_points.is_empty() {
2896        let score_span = score_span_at(0.0)?;
2897        let link_span = link_span_at(a)?;
2898        let coeffs = denested_cell_coefficients(score_span, link_span, a, b);
2899        return Ok(vec![DenestedPartitionCell {
2900            cell: DenestedCubicCell {
2901                left: f64::NEG_INFINITY,
2902                right: f64::INFINITY,
2903                c0: coeffs[0],
2904                c1: coeffs[1],
2905                c2: 0.0,
2906                c3: 0.0,
2907            },
2908            score_span,
2909            link_span,
2910            left_edge: PartitionEdge::Fixed(f64::NEG_INFINITY),
2911            right_edge: PartitionEdge::Fixed(f64::INFINITY),
2912        }]);
2913    }
2914
2915    // ── Left tail cell: (-∞, leftmost_split] ──
2916    let (leftmost, leftmost_edge) = split_points[0];
2917    // Evaluate spans at a point just left of the leftmost split.  The
2918    // closures return constant tail cubics for this region.
2919    let left_probe = interval_probe_point(f64::NEG_INFINITY, leftmost)?;
2920    let left_score_span = score_span_at(left_probe)?;
2921    let left_link_span = link_span_at(a + b * left_probe)?;
2922    let left_coeffs = denested_cell_coefficients(left_score_span, left_link_span, a, b);
2923    if left_coeffs[2] != 0.0 || left_coeffs[3] != 0.0 {
2924        return Err(CubicCellKernelError::invalid_cell_shape(format!(
2925            "left tail cell must be affine (deviations constant outside support), \
2926             got c2={:.3e}, c3={:.3e}",
2927            left_coeffs[2], left_coeffs[3]
2928        ))
2929        .into());
2930    }
2931    out.push(DenestedPartitionCell {
2932        cell: DenestedCubicCell {
2933            left: f64::NEG_INFINITY,
2934            right: leftmost,
2935            c0: left_coeffs[0],
2936            c1: left_coeffs[1],
2937            c2: 0.0,
2938            c3: 0.0,
2939        },
2940        score_span: left_score_span,
2941        link_span: left_link_span,
2942        left_edge: PartitionEdge::Fixed(f64::NEG_INFINITY),
2943        right_edge: leftmost_edge,
2944    });
2945
2946    // ── Interior cells (all finite) ──
2947    for window in split_points.windows(2) {
2948        let (left, left_edge) = window[0];
2949        let (right, right_edge) = window[1];
2950        if !left.is_finite() || !right.is_finite() || right - left <= 1e-12 {
2951            continue;
2952        }
2953        let mid = interval_probe_point(left, right)?;
2954        let score_span = score_span_at(mid)?;
2955        let link_span = link_span_at(a + b * mid)?;
2956        let coeffs = denested_cell_coefficients(score_span, link_span, a, b);
2957        out.push(DenestedPartitionCell {
2958            cell: DenestedCubicCell {
2959                left,
2960                right,
2961                c0: coeffs[0],
2962                c1: coeffs[1],
2963                c2: coeffs[2],
2964                c3: coeffs[3],
2965            },
2966            score_span,
2967            link_span,
2968            left_edge,
2969            right_edge,
2970        });
2971    }
2972
2973    // ── Right tail cell: [rightmost_split, +∞) ──
2974    let (rightmost, rightmost_edge) = *split_points.last().unwrap();
2975    let right_probe = interval_probe_point(rightmost, f64::INFINITY)?;
2976    let right_score_span = score_span_at(right_probe)?;
2977    let right_link_span = link_span_at(a + b * right_probe)?;
2978    let right_coeffs = denested_cell_coefficients(right_score_span, right_link_span, a, b);
2979    if right_coeffs[2] != 0.0 || right_coeffs[3] != 0.0 {
2980        return Err(CubicCellKernelError::invalid_cell_shape(format!(
2981            "right tail cell must be affine (deviations constant outside support), \
2982             got c2={:.3e}, c3={:.3e}",
2983            right_coeffs[2], right_coeffs[3]
2984        ))
2985        .into());
2986    }
2987    out.push(DenestedPartitionCell {
2988        cell: DenestedCubicCell {
2989            left: rightmost,
2990            right: f64::INFINITY,
2991            c0: right_coeffs[0],
2992            c1: right_coeffs[1],
2993            c2: 0.0,
2994            c3: 0.0,
2995        },
2996        score_span: right_score_span,
2997        link_span: right_link_span,
2998        left_edge: rightmost_edge,
2999        right_edge: PartitionEdge::Fixed(f64::INFINITY),
3000    });
3001
3002    Ok(out)
3003}
3004
3005#[inline]
3006pub fn branch_cell(cell: DenestedCubicCell) -> Result<ExactCellBranch, String> {
3007    validate_cell_inputs(cell)?;
3008    if !cell.left.is_finite() || !cell.right.is_finite() {
3009        if cell.c2 == 0.0 && cell.c3 == 0.0 {
3010            return Ok(ExactCellBranch::Affine);
3011        }
3012        return Err(CubicCellKernelError::invalid_cell_shape(format!(
3013            "non-affine cells require finite bounds, got [{}, {}] with c2={:.6e}, c3={:.6e}",
3014            cell.left, cell.right, cell.c2, cell.c3
3015        ))
3016        .into());
3017    }
3018    if cell.right <= cell.left {
3019        return Err(CubicCellKernelError::invalid_cell_shape(format!(
3020            "finite cell must have left < right, got [{}, {}]",
3021            cell.left, cell.right
3022        ))
3023        .into());
3024    }
3025    // These are exact polynomial classes, not approximation bands. Numerical
3026    // conditioning is handled inside the evaluator without erasing terms.
3027    if cell.c2 == 0.0 && cell.c3 == 0.0 {
3028        Ok(ExactCellBranch::Affine)
3029    } else if cell.c3 == 0.0 {
3030        Ok(ExactCellBranch::Quartic)
3031    } else {
3032        Ok(ExactCellBranch::Sextic)
3033    }
3034}
3035
3036#[inline]
3037fn validate_bvn_args(h: f64, k: f64, rho: f64) -> Result<(), String> {
3038    if !h.is_finite() && !h.is_infinite() {
3039        return Err(CubicCellKernelError::bivariate_normal_domain(
3040            "bivariate normal cdf requires finite or infinite h",
3041        )
3042        .into());
3043    }
3044    if !k.is_finite() && !k.is_infinite() {
3045        return Err(CubicCellKernelError::bivariate_normal_domain(
3046            "bivariate normal cdf requires finite or infinite k",
3047        )
3048        .into());
3049    }
3050    if !rho.is_finite() {
3051        return Err(CubicCellKernelError::bivariate_normal_domain(format!(
3052            "bivariate normal cdf requires finite correlation, got {rho}"
3053        ))
3054        .into());
3055    }
3056    Ok::<(), _>(())
3057}
3058
3059#[inline]
3060fn bvn_gl_sum(h: f64, k: f64, rho_clamped: f64, asr: f64) -> f64 {
3061    // The Drezner-Wesolowsky arcsin representation is integrated with the
3062    // same 20-point Gauss-Legendre rule as before, but mirrored node pairs are
3063    // evaluated with one sin_cos for the half-angle offset rather than two
3064    // independent sin calls.  This preserves the quadrature rule (and hence
3065    // the accuracy envelope) while reducing the transcendental work in the
3066    // dominant finite-bound path from 20 sin calls to 11 sin/cos evaluations.
3067    if rho_clamped == 0.0 {
3068        return 0.0;
3069    }
3070    let hs = 0.5 * (h * h + k * k);
3071    let hk = h * k;
3072    let half_asr = 0.5 * asr;
3073    let (sin_mid, cos_mid) = half_asr.sin_cos();
3074    let mut sum = 0.0;
3075    for i in 0..10 {
3076        let node = GL20_NODES[i].abs();
3077        let weight = GL20_WEIGHTS[i];
3078        let (sin_delta, cos_delta) = (half_asr * node).sin_cos();
3079
3080        let sn_lo = sin_mid * cos_delta - cos_mid * sin_delta;
3081        let one_minus_lo = 1.0 - sn_lo * sn_lo;
3082        let expo_lo = ((sn_lo * hk) - hs) / one_minus_lo;
3083
3084        let sn_hi = sin_mid * cos_delta + cos_mid * sin_delta;
3085        let one_minus_hi = 1.0 - sn_hi * sn_hi;
3086        let expo_hi = ((sn_hi * hk) - hs) / one_minus_hi;
3087
3088        sum += weight * (expo_lo.exp() + expo_hi.exp());
3089    }
3090    sum
3091}
3092
3093pub fn bivariate_normal_cdf(h: f64, k: f64, rho: f64) -> Result<f64, String> {
3094    validate_bvn_args(h, k, rho)?;
3095    if h == f64::NEG_INFINITY || k == f64::NEG_INFINITY {
3096        return Ok(0.0);
3097    }
3098    if h == f64::INFINITY {
3099        return Ok(normal_cdf(k));
3100    }
3101    if k == f64::INFINITY {
3102        return Ok(normal_cdf(h));
3103    }
3104
3105    let rho_clamped = rho.clamp(-1.0, 1.0);
3106    if rho_clamped >= 1.0 - 1e-12 {
3107        return Ok(normal_cdf(h.min(k)));
3108    }
3109    if rho_clamped <= -1.0 + 1e-12 {
3110        return Ok((normal_cdf(h) - normal_cdf(-k)).clamp(0.0, 1.0));
3111    }
3112    if rho_clamped == 0.0 {
3113        return Ok((normal_cdf(h) * normal_cdf(k)).clamp(0.0, 1.0));
3114    }
3115    if h == 0.0 && k == 0.0 {
3116        return Ok((0.25 + rho_clamped.asin() / std::f64::consts::TAU).clamp(0.0, 1.0));
3117    }
3118
3119    let asr = rho_clamped.asin();
3120    let sum = bvn_gl_sum(h, k, rho_clamped, asr);
3121    Ok((normal_cdf(h) * normal_cdf(k) + asr * sum / (4.0 * std::f64::consts::PI)).clamp(0.0, 1.0))
3122}
3123
3124#[inline]
3125fn bvn_gl_sum_interval(h: f64, left: f64, right: f64, rho_clamped: f64, asr: f64) -> f64 {
3126    if rho_clamped == 0.0 {
3127        return 0.0;
3128    }
3129    let h2 = h * h;
3130    let right_hs = 0.5 * (h2 + right * right);
3131    let left_hs = 0.5 * (h2 + left * left);
3132    let half_asr = 0.5 * asr;
3133    let (sin_mid, cos_mid) = half_asr.sin_cos();
3134    let mut sum = 0.0;
3135    for i in 0..10 {
3136        let node = GL20_NODES[i].abs();
3137        let weight = GL20_WEIGHTS[i];
3138        let (sin_delta, cos_delta) = (half_asr * node).sin_cos();
3139
3140        let sn_lo = sin_mid * cos_delta - cos_mid * sin_delta;
3141        let one_minus_lo = 1.0 - sn_lo * sn_lo;
3142        let lo_right = (((sn_lo * h * right) - right_hs) / one_minus_lo).exp();
3143        let lo_left = (((sn_lo * h * left) - left_hs) / one_minus_lo).exp();
3144
3145        let sn_hi = sin_mid * cos_delta + cos_mid * sin_delta;
3146        let one_minus_hi = 1.0 - sn_hi * sn_hi;
3147        let hi_right = (((sn_hi * h * right) - right_hs) / one_minus_hi).exp();
3148        let hi_left = (((sn_hi * h * left) - left_hs) / one_minus_hi).exp();
3149
3150        sum += weight * ((lo_right - lo_left) + (hi_right - hi_left));
3151    }
3152    sum
3153}
3154
3155fn bivariate_normal_cdf_interval(h: f64, left: f64, right: f64, rho: f64) -> Result<f64, String> {
3156    if right <= left {
3157        return Ok(0.0);
3158    }
3159    if left == f64::NEG_INFINITY && right == f64::INFINITY {
3160        return Ok(normal_cdf(h));
3161    }
3162    if !left.is_finite() || !right.is_finite() {
3163        let upper = bivariate_normal_cdf(h, right, rho)?;
3164        let lower = bivariate_normal_cdf(h, left, rho)?;
3165        return Ok((upper - lower).clamp(0.0, 1.0));
3166    }
3167    validate_bvn_args(h, left, rho)?;
3168    validate_bvn_args(h, right, rho)?;
3169    if h == f64::NEG_INFINITY {
3170        return Ok(0.0);
3171    }
3172    if h == f64::INFINITY {
3173        return Ok((normal_cdf(right) - normal_cdf(left)).clamp(0.0, 1.0));
3174    }
3175
3176    let rho_clamped = rho.clamp(-1.0, 1.0);
3177    if rho_clamped >= 1.0 - 1e-12 || rho_clamped <= -1.0 + 1e-12 {
3178        let upper = bivariate_normal_cdf(h, right, rho_clamped)?;
3179        let lower = bivariate_normal_cdf(h, left, rho_clamped)?;
3180        return Ok((upper - lower).clamp(0.0, 1.0));
3181    }
3182
3183    let cdf_h = normal_cdf(h);
3184    let normal_part = cdf_h * (normal_cdf(right) - normal_cdf(left));
3185    if rho_clamped == 0.0 {
3186        return Ok(normal_part.clamp(0.0, 1.0));
3187    }
3188    let asr = rho_clamped.asin();
3189    let sum = bvn_gl_sum_interval(h, left, right, rho_clamped, asr);
3190    Ok((normal_part + asr * sum / (4.0 * std::f64::consts::PI)).clamp(0.0, 1.0))
3191}
3192
3193fn exp_neg_half_square(x: f64) -> f64 {
3194    if x.is_infinite() {
3195        0.0
3196    } else {
3197        (-0.5 * x * x).exp()
3198    }
3199}
3200
3201/// Zeroth truncated standard-normal moment `T_0(a, b) = ∫_a^b e^(−z²/2) dz
3202/// = √(2π)·(Φ(b) − Φ(a))`, evaluated without catastrophic cancellation in
3203/// either tail.
3204///
3205/// Writing `T_0 = √(π/2)·[erf(b/√2) − erf(a/√2)]`, the naive form collapses
3206/// to `0.0` whenever both endpoints lie in the *same* far tail: `erf`
3207/// saturates at the IEEE-754 values `±1.0` for `|x| ≳ 8.3·√2`, so the
3208/// difference of two saturated values is exactly zero even though the
3209/// integral is a strictly positive number well inside the f64 normal range
3210/// (e.g. `∫_{-12}^{-10} ≈ 1.9e-23`). The fix is to reduce the erf difference
3211/// to complementary tail probabilities — `erfc` is evaluated with a dedicated
3212/// tail series, *not* as `1 − erf` — and to pick, by the sign of the
3213/// endpoints, the algebraically-equivalent form whose terms do not cancel
3214/// against one another:
3215///
3216/// ```text
3217/// both ≥ 0 (upper tail):  erf(b/√2) − erf(a/√2) = erfc(a/√2) − erfc(b/√2)
3218/// both ≤ 0 (lower tail):  erf(b/√2) − erf(a/√2) = erfc(−b/√2) − erfc(−a/√2)
3219/// straddling zero:        erf(b/√2) − erf(a/√2)
3220///                        = erf(b/√2) + erf(−a/√2)       near the anchor
3221///                        = 2 − erfc(b/√2) − erfc(−a/√2) otherwise
3222/// ```
3223///
3224/// In each branch every `erfc` argument is `≥ 0`, so the terms are small
3225/// positive tail values, while narrow straddling intervals add two
3226/// non-negative `erf` masses measured outward from the anchor. That avoids
3227/// the `2 − erfc(b/√2) − erfc(−a/√2)` cancellation when both erfc terms round
3228/// to `1.0`, but keeps the erfc-tail form for ordinary/full-line straddling
3229/// intervals. No large quantities cancel and full f64 precision survives down
3230/// to the underflow boundary in either tail and around the affine anchor.
3231///
3232/// Uses `libm::erfc` (msun double-precision implementation, ≤ 1 ulp) rather
3233/// than `statrs::function::erf::erfc` (a 6-term rational approximation that
3234/// carries ~3·10⁻¹¹ relative error around `|x| ≈ 1/√2` — see the existing
3235/// `libm::erfc` consumer at `inference::polya_gamma_core::normal_cdf`). That
3236/// statrs error propagates directly into `T_0`, then through every higher
3237/// moment `T_n` (the recurrence `T_n = a^{n-1}e^{-a²/2} − b^{n-1}e^{-b²/2}
3238/// + (n-1)·T_{n-2}` walks `T_0` up two steps at a time), then through every
3239/// affine-cell moment via `affine_anchor_moment_vector` (whose `out[n]` is a
3240/// linear combination of `T_0..=T_n`), and is the dominant source of error
3241/// in the affine-cell branch of the cubic-cell substrate (CPU/GPU parity
3242/// reference for transformation-normal, bernoulli-marginal-slope, and the
3243/// BMS flex-row higher-derivative reuse path).
3244fn truncated_gaussian_zeroth_moment(a: f64, b: f64) -> f64 {
3245    let inv_sqrt2 = 1.0 / std::f64::consts::SQRT_2;
3246    let za = a * inv_sqrt2;
3247    let zb = b * inv_sqrt2;
3248    let erf_diff = if za >= 0.0 {
3249        libm::erfc(za) - libm::erfc(zb)
3250    } else if zb <= 0.0 {
3251        libm::erfc(-zb) - libm::erfc(-za)
3252    } else if zb <= 0.5 && -za <= 0.5 {
3253        // Near the affine anchor, erfc(zb) and erfc(-za) are both close to
3254        // one; subtracting them from 2.0 can round a tiny but representable
3255        // cell mass to zero. The equivalent erf sum adds small positive
3256        // quantities directly.
3257        libm::erf(zb) + libm::erf(-za)
3258    } else {
3259        2.0 - libm::erfc(zb) - libm::erfc(-za)
3260    };
3261    // √(2π)·½ = √(π/2).
3262    (std::f64::consts::PI / 2.0).sqrt() * erf_diff
3263}
3264
3265/// Fill `out[0..=max_degree]` with the raw truncated standard-normal moments
3266///
3267/// ```text
3268/// T_n(a, b) = ∫_a^b z^n exp(-z²/2) dz
3269/// ```
3270///
3271/// using the integration-by-parts recurrence
3272///
3273/// ```text
3274/// T_0(a, b) = √(2π) (Φ(b) − Φ(a))
3275/// T_1(a, b) = exp(−a²/2) − exp(−b²/2)
3276/// T_n(a, b) = a^(n−1) e^{−a²/2} − b^(n−1) e^{−b²/2} + (n−1) T_{n−2}(a, b)
3277/// ```
3278///
3279/// Computed in one forward sweep so each call evaluates `erf` and
3280/// `exp(−x²/2)` exactly twice (once at `a`, once at `b`) regardless of the
3281/// requested degree. The naive form — calling `T_n` recursively for each
3282/// `n = 0..=max_degree` — re-evaluated `erf`/`exp` about `max_degree²/4`
3283/// times per affine cell, which dominated the wall time of the
3284/// transformation-normal and bernoulli-marginal-slope inner solves with
3285/// `max_degree = 64` (the transport order's required degree budget).
3286fn fill_truncated_gaussian_moments(a: f64, b: f64, out: &mut [f64]) {
3287    if out.is_empty() {
3288        return;
3289    }
3290    out[0] = truncated_gaussian_zeroth_moment(a, b);
3291    if out.len() == 1 {
3292        return;
3293    }
3294    let ea = exp_neg_half_square(a);
3295    let eb = exp_neg_half_square(b);
3296    out[1] = ea - eb;
3297    if out.len() == 2 {
3298        return;
3299    }
3300    let a_finite = a.is_finite();
3301    let b_finite = b.is_finite();
3302    // For n in 2..=max_degree we need a^{n-1} e^{-a²/2} (resp. b). Carry the
3303    // running powers a^{n-1}, b^{n-1} forward by a single multiply per step.
3304    // Infinite endpoints contribute 0 (the integrand decays at the rate of
3305    // exp(−x²/2)), matching the prior `is_infinite` branch in the recursive
3306    // implementation; we still update the running power so the iteration
3307    // stays branchless when both endpoints are finite.
3308    let mut a_pow_n_minus_1 = a; // a^1, used at n = 2
3309    let mut b_pow_n_minus_1 = b;
3310    for n in 2..out.len() {
3311        let left = if a_finite { a_pow_n_minus_1 * ea } else { 0.0 };
3312        let right = if b_finite { b_pow_n_minus_1 * eb } else { 0.0 };
3313        out[n] = left - right + (n as f64 - 1.0) * out[n - 2];
3314        a_pow_n_minus_1 *= a;
3315        b_pow_n_minus_1 *= b;
3316    }
3317}
3318
3319/// Stack-array bound for `affine_anchor_moment_vector_into`. Public callers
3320/// use up to ~24 (largest is the bernoulli-margslope outer-step degree-21
3321/// reduction); 64 leaves comfortable headroom without growing the per-call
3322/// stack footprint meaningfully.
3323const MAX_AFFINE_ANCHOR_DEGREE: usize = 64;
3324
3325pub fn affine_anchor_moment_vector(
3326    alpha: f64,
3327    beta: f64,
3328    left: f64,
3329    right: f64,
3330    max_degree: usize,
3331) -> Vec<f64> {
3332    let mut out = vec![0.0; max_degree + 1];
3333    affine_anchor_moment_vector_into(alpha, beta, left, right, max_degree, &mut out);
3334    out
3335}
3336
3337fn affine_anchor_moment_vector_into(
3338    alpha: f64,
3339    beta: f64,
3340    left: f64,
3341    right: f64,
3342    max_degree: usize,
3343    out: &mut [f64],
3344) {
3345    assert_eq!(out.len(), max_degree + 1);
3346    let s = (1.0 + beta * beta).sqrt();
3347    let mu = -alpha * beta / (1.0 + beta * beta);
3348    let y_left = if left.is_infinite() {
3349        if left.is_sign_positive() {
3350            f64::INFINITY
3351        } else {
3352            f64::NEG_INFINITY
3353        }
3354    } else {
3355        s * (left - mu)
3356    };
3357    let y_right = if right.is_infinite() {
3358        if right.is_sign_positive() {
3359            f64::INFINITY
3360        } else {
3361            f64::NEG_INFINITY
3362        }
3363    } else {
3364        s * (right - mu)
3365    };
3366    let anchor = (-alpha * alpha / (2.0 * s * s)).exp() / s;
3367    assert!(
3368        max_degree <= MAX_AFFINE_ANCHOR_DEGREE,
3369        "affine_anchor_moment_vector max_degree {} exceeds compile-time bound {}",
3370        max_degree,
3371        MAX_AFFINE_ANCHOR_DEGREE
3372    );
3373    let mut t = [0.0_f64; MAX_AFFINE_ANCHOR_DEGREE + 1];
3374    fill_truncated_gaussian_moments(y_left, y_right, &mut t[..=max_degree]);
3375    // Build mu^k and s^{-k} tables once. The inner sum is the binomial
3376    // expansion of the affine change-of-variables, and computing the
3377    // binomial coefficient via Pascal's row recurrence + carrying mu/s
3378    // powers eliminates the per-(n, k) `powi` and binomial calls that
3379    // otherwise dominated the inner loop at large `max_degree`.
3380    let mut mu_pow = [1.0_f64; MAX_AFFINE_ANCHOR_DEGREE + 1];
3381    for k in 1..=max_degree {
3382        mu_pow[k] = mu_pow[k - 1] * mu;
3383    }
3384    let inv_s = 1.0 / s;
3385    let mut inv_s_pow = [1.0_f64; MAX_AFFINE_ANCHOR_DEGREE + 1];
3386    for k in 1..=max_degree {
3387        inv_s_pow[k] = inv_s_pow[k - 1] * inv_s;
3388    }
3389    out.fill(0.0);
3390    for n in 0..=max_degree {
3391        let mut acc = 0.0;
3392        // C(n, k+1) = C(n, k) · (n − k) / (k + 1).
3393        let mut binom = 1.0;
3394        for k in 0..=n {
3395            let term = binom * mu_pow[n - k] * inv_s_pow[k];
3396            acc = term.mul_add(t[k], acc);
3397            if k < n {
3398                binom = binom * (n - k) as f64 / (k + 1) as f64;
3399            }
3400        }
3401        out[n] = anchor * acc;
3402    }
3403}
3404
3405fn affine_value_from_moment_primitive(
3406    alpha: f64,
3407    beta: f64,
3408    left: f64,
3409    right: f64,
3410) -> Result<f64, String> {
3411    // Exact formula via bivariate normal CDF.
3412    //
3413    // V(α,β,l,r) = ∫_l^r Φ(α+βz)φ(z)dz
3414    //            = P(U ≤ α+βZ, l ≤ Z ≤ r)    where U,Z iid N(0,1)
3415    //            = Φ₂(h, r; ρ) − Φ₂(h, l; ρ)
3416    //
3417    // with h = α/√(1+β²) and ρ = −β/√(1+β²).
3418    //
3419    // This is exact to floating-point precision via the high-accuracy
3420    // Drezner-Wesolowsky BVN routine, replacing the previous fixed 20-point
3421    // Gauss-Legendre numerical integration of the derivative primitive.
3422    let s = (1.0 + beta * beta).sqrt();
3423    let h = alpha / s;
3424    let rho = -beta / s;
3425    bivariate_normal_cdf_interval(h, left, right, rho)
3426}
3427
3428fn validate_cell_inputs(cell: DenestedCubicCell) -> Result<(), String> {
3429    for (name, value) in [
3430        ("c0", cell.c0),
3431        ("c1", cell.c1),
3432        ("c2", cell.c2),
3433        ("c3", cell.c3),
3434    ] {
3435        if !value.is_finite() {
3436            return Err(CubicCellKernelError::invalid_cell_shape(format!(
3437                "cell coefficient {name} must be finite, got {value}"
3438            ))
3439            .into());
3440        }
3441    }
3442    if cell.left.is_nan() || cell.right.is_nan() || cell.left >= cell.right {
3443        return Err(CubicCellKernelError::invalid_cell_shape(format!(
3444            "cell bounds must satisfy left < right without NaN, got [{}, {}]",
3445            cell.left, cell.right
3446        ))
3447        .into());
3448    }
3449    Ok(())
3450}
3451
3452fn validate_affine_cell_inputs(cell: DenestedCubicCell, max_degree: usize) -> Result<(), String> {
3453    validate_cell_inputs(cell)?;
3454    if cell.c2 != 0.0 || cell.c3 != 0.0 {
3455        return Err(CubicCellKernelError::invalid_cell_shape(format!(
3456            "affine cell requires c2=c3=0 exactly, got c2={:.6e}, c3={:.6e}",
3457            cell.c2, cell.c3
3458        ))
3459        .into());
3460    }
3461    if max_degree > MAX_AFFINE_ANCHOR_DEGREE {
3462        return Err(CubicCellKernelError::invalid_cell_shape(format!(
3463            "affine cell moment degree {max_degree} exceeds supported maximum {MAX_AFFINE_ANCHOR_DEGREE}"
3464        ))
3465        .into());
3466    }
3467    Ok(())
3468}
3469
3470/// Evaluate an affine cell (c2=c3=0) with a value/moment-consistent primitive.
3471///
3472/// Value and moments are now generated from the same affine moment primitive.
3473/// The zero-moment derivative is exact, and `value` is reconstructed by
3474/// integrating `d value / d alpha = INV_TWO_PI * moments[0]` over `alpha`
3475/// on a transformed semi-infinite domain.
3476pub fn evaluate_affine_cell_state(
3477    cell: DenestedCubicCell,
3478    max_degree: usize,
3479) -> Result<CellMomentState, String> {
3480    validate_affine_cell_inputs(cell, max_degree)?;
3481    let alpha = cell.c0;
3482    let beta = cell.c1;
3483    let value = affine_value_from_moment_primitive(alpha, beta, cell.left, cell.right)?;
3484    let moments = affine_anchor_moment_vector(alpha, beta, cell.left, cell.right, max_degree);
3485    Ok(CellMomentState {
3486        branch: ExactCellBranch::Affine,
3487        value,
3488        moments: moments.into(),
3489    })
3490}
3491
3492fn evaluate_affine_cell_derivative_state(
3493    cell: DenestedCubicCell,
3494    max_degree: usize,
3495) -> Result<CellDerivativeMomentState, String> {
3496    validate_affine_cell_inputs(cell, max_degree)?;
3497    let alpha = cell.c0;
3498    let beta = cell.c1;
3499    let moments = affine_anchor_moment_vector(alpha, beta, cell.left, cell.right, max_degree);
3500    Ok(CellDerivativeMomentState {
3501        branch: ExactCellBranch::Affine,
3502        moments: moments.into(),
3503    })
3504}
3505
3506/// Accumulate `mw * z^k` into `moments[k]` for k=0..moments.len(). The
3507/// "unrolled4" name is historical — this is the plain scalar accumulator
3508/// that the SIMD outer loop calls per lane. Moment counts are small enough
3509/// (max_degree + 1 <= ~10) that explicit 4-way unrolling does not measurably
3510/// improve throughput over the iterator path; the wide::f64x4::exp savings
3511/// in the SIMD outer dominate the kernel's runtime.
3512#[inline]
3513fn accumulate_moments_unrolled4(moments: &mut [f64], mw: f64, z: f64) {
3514    let mut z_pow = 1.0_f64;
3515    for slot in moments.iter_mut() {
3516        *slot = mw.mul_add(z_pow, *slot);
3517        z_pow *= z;
3518    }
3519}
3520
3521// Shared SIMD Gauss-Legendre core for non-affine cells. The const generic
3522// `COMPUTE_VALUE` selects whether the cell value integral
3523// `∫ φ(η(z)) · exp(-½z²) dz / √(2π)` is accumulated alongside the moments.
3524// Monomorphization collapses the const-generic branches at compile time, so
3525// `COMPUTE_VALUE = false` emits the moment-only path verbatim.
3526//
3527// Single source of truth for the moment SIMD lane ordering, the Horner-with-FMA
3528// pattern for η(z), the `0.5 * (z² + η²)` quadratic-form evaluation order, the
3529// unscaled per-node GL moment weights, the post-loop half-width fold, and the
3530// per-lane `accumulate_moments_unrolled4` call. The previous duplicated code paths
3531// drifted by 1 ULP whenever any of these details diverged; here both paths
3532// share the same instructions, eliminating an entire class of regressions
3533// where a tweak to the quadrature order or the FMA pattern would silently
3534// re-introduce divergence between the value- and derivative-only callers.
3535//
3536// Gauss-Legendre on [left, right] converges geometrically for the analytic
3537// integrand exp(-q(z)) with quartic/sextic q on a bounded cell; the prior
3538// adaptive transport path expanded basis_moments via the forward 3-/5-step
3539// recurrences in reduce_quartic/sextic_moments, which amplify roundoff by
3540// (1/lead)^n with lead = 2c2²/3c3² and overflow to NaN for small c2/c3 cells
3541// that arise naturally in production.
3542//
3543// The fixed 384-node rule that replaced the transport path is accurate but
3544// pays ~384 exp evaluations per cell unconditionally. Production cells are
3545// narrow spline-knot subdivisions where a 12- or 24-node rule is already
3546// converged to machine precision, and the flex marginal-slope row calculus
3547// evaluates O(100) such cells per row across n=10⁵–10⁶ rows per criterion
3548// evaluation — the fixed rule was the dominant cost of the whole fit (#979).
3549// `evaluate_non_affine_cell_simd` therefore walks a progressive ladder of
3550// rules (12, 24, 48, 96, 192, 384 nodes) and returns as soon as two
3551// consecutive rules agree to `NON_AFFINE_LADDER_RTOL` relative to the moment
3552// vector's own scale. Unlike the old fixed rule — whose error was real but
3553// uncertified — every accepted ladder result carries an embedded two-rule
3554// agreement certificate; a cell that never certifies falls through to the
3555// same 384-node answer the fixed rule produced.
3556//
3557// SIMD path: process 4 GL nodes per outer iteration, batching the two scalar
3558// `exp` calls into single 4-wide `wide::f64x4::exp` invocations. All ladder
3559// rule sizes are divisible by 4, so no scalar tail is needed for the GL
3560// sweep. The inner moment accumulation is then run scalar per-lane but with
3561// a 4-way unrolled slab over the moment slots to break the `z_pow *= z`
3562// serial dependency chain.
3563#[inline(always)]
3564fn evaluate_non_affine_cell_with_rule<const COMPUTE_VALUE: bool>(
3565    cell: DenestedCubicCell,
3566    max_degree: usize,
3567    gl_nodes: &[f64],
3568    gl_weights: &[f64],
3569) -> (CellMomentVec, f64) {
3570    let mut moments: CellMomentVec = smallvec![0.0_f64; max_degree + 1];
3571    let mut value_integral = 0.0_f64;
3572    let center = 0.5 * (cell.left + cell.right);
3573    let half_width = 0.5 * (cell.right - cell.left);
3574    let c0 = cell.c0;
3575    let c1 = cell.c1;
3576    let c2 = cell.c2;
3577    let c3 = cell.c3;
3578    let moments_slice: &mut [f64] = &mut moments;
3579    assert_eq!(gl_nodes.len(), gl_weights.len());
3580    use wide::f64x4;
3581    let center_v = f64x4::splat(center);
3582    let half_width_v = f64x4::splat(half_width);
3583    let c0_v = f64x4::splat(c0);
3584    let c1_v = f64x4::splat(c1);
3585    let c2_v = f64x4::splat(c2);
3586    let c3_v = f64x4::splat(c3);
3587    let neg_half_v = f64x4::splat(-0.5);
3588    let n_total = gl_nodes.len();
3589    let n_simd = n_total - (n_total % 4);
3590    let mut i = 0;
3591    while i < n_simd {
3592        let node_v = f64x4::from([
3593            gl_nodes[i],
3594            gl_nodes[i + 1],
3595            gl_nodes[i + 2],
3596            gl_nodes[i + 3],
3597        ]);
3598        let weight_v = f64x4::from([
3599            gl_weights[i],
3600            gl_weights[i + 1],
3601            gl_weights[i + 2],
3602            gl_weights[i + 3],
3603        ]);
3604        let z_v = half_width_v.mul_add(node_v, center_v);
3605        // Horner: ((c3*z + c2)*z + c1)*z + c0
3606        let eta_v = c3_v
3607            .mul_add(z_v, c2_v)
3608            .mul_add(z_v, c1_v)
3609            .mul_add(z_v, c0_v);
3610        let z2_v = z_v * z_v;
3611        let neg_q_v = neg_half_v * (z2_v + eta_v * eta_v);
3612        let exp_negq_v = neg_q_v.exp();
3613        let moment_weight_v = weight_v * exp_negq_v;
3614        let z_arr = z_v.to_array();
3615        let mw_arr = moment_weight_v.to_array();
3616        if COMPUTE_VALUE {
3617            for lane in 0..4 {
3618                let z = z_arr[lane];
3619                let mw = mw_arr[lane];
3620                accumulate_moments_unrolled4(moments_slice, mw, z);
3621                // The value integrand carries Φ(η)'s erfc, whose systematic
3622                // per-z error is ~1e-13. To honor the cell-value accuracy
3623                // contract the value term must be assembled bit-for-bit like
3624                // the scalar reference: a non-fused node map
3625                // `z_ref = center + half_width·node`, the expanded
3626                // `η = c0 + c1·z + c2·z² + c3·z³` (NOT the SIMD Horner-FMA used
3627                // for the moments), the unscaled GL weight, a scalar `exp(-½z²)`,
3628                // and a plain `+=`. The SIMD `z_v`/`eta_v` above (fused) feed
3629                // ONLY the moments and are left untouched. Any single ULP slip
3630                // here (FMA node map, Horner η, per-term half_width, SIMD exp,
3631                // FMA accumulation) drifts the 384-node sum by ~1.4e-13 and
3632                // breaks the contract.
3633                let node = gl_nodes[i + lane];
3634                let weight = gl_weights[i + lane];
3635                let z_ref = center + half_width * node;
3636                let eta_ref = c0 + c1 * z_ref + c2 * z_ref * z_ref + c3 * z_ref * z_ref * z_ref;
3637                value_integral += weight * (-0.5 * z_ref * z_ref).exp() * normal_cdf(eta_ref);
3638            }
3639        } else {
3640            for lane in 0..4 {
3641                let z = z_arr[lane];
3642                let mw = mw_arr[lane];
3643                accumulate_moments_unrolled4(moments_slice, mw, z);
3644            }
3645        }
3646        i += 4;
3647    }
3648    while i < n_total {
3649        let node = gl_nodes[i];
3650        let weight = gl_weights[i];
3651        let z = center + half_width * node;
3652        let eta = c3.mul_add(z, c2).mul_add(z, c1).mul_add(z, c0);
3653        let q = 0.5 * (z * z + eta * eta);
3654        let moment_weight = weight * (-q).exp();
3655        accumulate_moments_unrolled4(moments_slice, moment_weight, z);
3656        if COMPUTE_VALUE {
3657            // Bit-for-bit the reference value structure (see SIMD branch): the
3658            // node map `z = center + half_width·node` here already matches the
3659            // reference (non-fused), but η must use the expanded reference form
3660            // rather than the moment path's Horner-FMA.
3661            let eta_ref = c0 + c1 * z + c2 * z * z + c3 * z * z * z;
3662            value_integral += weight * (-0.5 * z * z).exp() * normal_cdf(eta_ref);
3663        }
3664        i += 1;
3665    }
3666    // Apply the cell half-width to both moment and value integrals ONCE at the
3667    // end, mirroring the prefold reference. Folding half_width per-term changes
3668    // f64 rounding enough to show up at the 1e-13 contract.
3669    for moment in moments_slice.iter_mut() {
3670        *moment *= half_width;
3671    }
3672    let value = if COMPUTE_VALUE {
3673        value_integral * half_width
3674    } else {
3675        value_integral
3676    };
3677    (moments, value)
3678}
3679
3680/// Relative agreement threshold for the progressive non-affine quadrature
3681/// ladder: two consecutive Gauss-Legendre rules must agree on every moment
3682/// slot to this tolerance relative to the moment vector's own max magnitude
3683/// before the finer rule's result
3684/// is accepted. Gauss-Legendre error decays geometrically in the node count
3685/// for the analytic integrand `exp(-q(z))`, so agreement between an n-node
3686/// and a 2n-node rule certifies that both are converged: the coarse rule's
3687/// true error is bounded by the observed difference plus the (much smaller)
3688/// fine-rule error.
3689///
3690/// History (#979): a roundoff-floor relaxation of this test (accept when
3691/// successive rungs agree to `≈ n·ε·scale` rather than the bare `3e-15`) was
3692/// tried to let smooth cells certify below the terminal 384-node rung. It was
3693/// reverted: the value-bearing path carries `∫ φ(z)·Φ(η(z)) dz`, and `Φ`'s
3694/// `erfc` implementation has a *systematic per-z* error of order `1e-13` that
3695/// each rung's node set samples differently. Only the exact 384-node rule
3696/// reproduces the reference's erfc-noise realization, so any sub-384 rung
3697/// drifts from the 384 value by `≈ 1e-13` — a drift that is NOT truncation,
3698/// does NOT shrink with rung, and is NOT bounded by rung-to-rung agreement.
3699/// The moment ladder remains independent of the value integral so value- and
3700/// derivative-only evaluators keep returning bit-identical moments. The scalar
3701/// value now evaluates on the terminal 384-node rule directly, preserving the
3702/// `non_affine_cell_state_matches_prefold_reference_to_1e_minus_13` value
3703/// contract without forcing every derivative-moment caller to use the terminal
3704/// rung.
3705const NON_AFFINE_LADDER_RTOL: f64 = 1e-15;
3706
3707/// Node counts of the progressive ladder below the 384-node terminal rung.
3708/// All divisible by 4 so the SIMD sweep needs no scalar tail.
3709const NON_AFFINE_LADDER_RUNGS: [usize; 5] = [12, 24, 48, 96, 192];
3710
3711/// Runtime-generated Gauss-Legendre rules for the ladder rungs, computed
3712/// once per process by Newton iteration on the Legendre polynomial roots
3713/// (standard `gauleg`: cosine initial guess, 3-4 Newton steps to machine
3714/// precision). The terminal 384-node rung reuses the compile-time
3715/// `GL_NODES`/`GL_WEIGHTS` tables, which also remain the single source for
3716/// the GPU kernel.
3717fn non_affine_ladder_rules() -> &'static [(Vec<f64>, Vec<f64>)] {
3718    static RULES: std::sync::OnceLock<Vec<(Vec<f64>, Vec<f64>)>> = std::sync::OnceLock::new();
3719    RULES.get_or_init(|| {
3720        NON_AFFINE_LADDER_RUNGS
3721            .iter()
3722            .map(|&n| gauss_legendre_rule(n))
3723            .collect()
3724    })
3725}
3726
3727/// Nodes and weights of the `n`-point Gauss-Legendre rule on `[-1, 1]`;
3728/// the canonical implementation lives in `gam-math` (previously
3729/// triplicated across gam-terms / gam-model-kernels / gam-models).
3730use gam_math::special::gauss_legendre as gauss_legendre_rule;
3731
3732/// Two-rule agreement certificate for the progressive ladder. `true` when
3733/// every MOMENT slot agrees to `NON_AFFINE_LADDER_RTOL` relative to the fine
3734/// result's max magnitude. Non-finite results never certify, so they fall
3735/// through to the terminal 384-node rung and reproduce the fixed rule's
3736/// behavior exactly.
3737///
3738/// The decision is deliberately moment-only and independent of whether the
3739/// caller also computed the cell value: the value- and derivative-only
3740/// evaluators MUST select the same ladder rung so they accumulate the moment
3741/// vector over the same nodes and return bit-identical moments (the
3742/// `derivative_moment_evaluator_matches_value_evaluator_moments` invariant).
3743/// Value-bearing callers evaluate the scalar cell probability separately on
3744/// the terminal 384-node rule; this certificate governs only the reusable
3745/// derivative moment vector.
3746fn non_affine_ladder_converged(coarse: &CellMomentVec, fine: &CellMomentVec) -> bool {
3747    let mut scale = 0.0_f64;
3748    let mut err = 0.0_f64;
3749    for (&c, &f) in coarse.iter().zip(fine.iter()) {
3750        scale = scale.max(f.abs());
3751        err = err.max((c - f).abs());
3752    }
3753    if !(scale.is_finite() && err.is_finite()) {
3754        return false;
3755    }
3756    err <= NON_AFFINE_LADDER_RTOL * scale
3757}
3758
3759/// Per-rung certification histogram for the non-affine ladder, indexed by the
3760/// rung that certified (`NON_AFFINE_LADDER_RUNGS[i]` at index `i`), with the
3761/// final slot counting cells that fell through to the terminal 384-node rule.
3762/// Incremented once per non-affine cell evaluation; the BMS exact-cache build
3763/// logs the distribution so the ladder's real cost (early-certify win vs.
3764/// terminal-fallthrough cost) is observable on every large-scale fit rather
3765/// than assumed. `+1` length for the terminal bucket.
3766pub(crate) static NON_AFFINE_LADDER_CERT_COUNTS: [AtomicU64; NON_AFFINE_LADDER_RUNGS.len() + 1] = [
3767    AtomicU64::new(0),
3768    AtomicU64::new(0),
3769    AtomicU64::new(0),
3770    AtomicU64::new(0),
3771    AtomicU64::new(0),
3772    AtomicU64::new(0),
3773];
3774
3775/// Snapshot the ladder certification histogram as `(rung_node_count, count)`
3776/// pairs plus the terminal-fallthrough count, for logging/inspection.
3777pub fn non_affine_ladder_cert_histogram() -> (Vec<(usize, u64)>, u64) {
3778    let per_rung = NON_AFFINE_LADDER_RUNGS
3779        .iter()
3780        .enumerate()
3781        .map(|(i, &n)| (n, NON_AFFINE_LADDER_CERT_COUNTS[i].load(Ordering::Relaxed)))
3782        .collect();
3783    let terminal =
3784        NON_AFFINE_LADDER_CERT_COUNTS[NON_AFFINE_LADDER_RUNGS.len()].load(Ordering::Relaxed);
3785    (per_rung, terminal)
3786}
3787
3788/// Progressive-ladder evaluation of a non-affine cell: walk the rule ladder
3789/// from 12 nodes upward and return the first result certified by two-rule
3790/// agreement; a cell that never certifies returns the terminal 384-node
3791/// result, byte-identical to the previous fixed-rule implementation.
3792#[inline]
3793fn evaluate_non_affine_cell_simd<const COMPUTE_VALUE: bool>(
3794    cell: DenestedCubicCell,
3795    max_degree: usize,
3796) -> (CellMomentVec, f64) {
3797    let mut prev: Option<(CellMomentVec, f64)> = None;
3798    for (i, (nodes, weights)) in non_affine_ladder_rules().iter().enumerate() {
3799        let cur =
3800            evaluate_non_affine_cell_with_rule::<COMPUTE_VALUE>(cell, max_degree, nodes, weights);
3801        if let Some(prev) = prev.as_ref()
3802            && non_affine_ladder_converged(&prev.0, &cur.0)
3803        {
3804            NON_AFFINE_LADDER_CERT_COUNTS[i].fetch_add(1, Ordering::Relaxed);
3805            return cur;
3806        }
3807        prev = Some(cur);
3808    }
3809    NON_AFFINE_LADDER_CERT_COUNTS[NON_AFFINE_LADDER_RUNGS.len()].fetch_add(1, Ordering::Relaxed);
3810    evaluate_non_affine_cell_with_rule::<COMPUTE_VALUE>(cell, max_degree, &GL_NODES, &GL_WEIGHTS)
3811}
3812
3813/// Value-only evaluation of a non-affine cell on the terminal 384-node rule.
3814///
3815/// Returns the cell probability integral `∫ exp(-½z²)·Φ(η(z)) dz` (pre the
3816/// `1/√τ` normalization) computed bit-for-bit like the value branch of
3817/// [`evaluate_non_affine_cell_with_rule`]: the non-fused node map
3818/// `z = center + half_width·node`, the expanded (non-Horner)
3819/// `η = c0 + c1·z + c2·z² + c3·z³`, the unscaled GL weight, a scalar
3820/// `exp(-½z²)`, a plain `+=` in ascending node order, and a single trailing
3821/// `·half_width`. The terminal rule has 384 nodes (divisible by 4), so the
3822/// general kernel's value path never takes its scalar tail — this loop walks
3823/// the same nodes in the same order and therefore reproduces the reference
3824/// erfc-noise realization the `1e-13` value contract pins down.
3825///
3826/// Computing this through `evaluate_non_affine_cell_with_rule::<true>` at
3827/// `max_degree = 0` would additionally run the 4-wide SIMD `exp(-q)` moment
3828/// sweep and a moment accumulation on every node only to discard the moment
3829/// vector. The survival marginal-slope fit evaluates a value per non-affine
3830/// partition cell, so that discarded moment work is the dominant waste in the
3831/// per-cell pass; this evaluator does only the work the value needs.
3832fn evaluate_non_affine_cell_value_terminal(cell: DenestedCubicCell) -> f64 {
3833    let center = 0.5 * (cell.left + cell.right);
3834    let half_width = 0.5 * (cell.right - cell.left);
3835    let c0 = cell.c0;
3836    let c1 = cell.c1;
3837    let c2 = cell.c2;
3838    let c3 = cell.c3;
3839    let mut value_integral = 0.0_f64;
3840    for (&node, &weight) in GL_NODES.iter().zip(GL_WEIGHTS.iter()) {
3841        let z = center + half_width * node;
3842        let eta = c0 + c1 * z + c2 * z * z + c3 * z * z * z;
3843        value_integral += weight * (-0.5 * z * z).exp() * normal_cdf(eta);
3844    }
3845    value_integral * half_width
3846}
3847
3848fn evaluate_non_affine_cell_state(
3849    cell: DenestedCubicCell,
3850    branch: ExactCellBranch,
3851    max_degree: usize,
3852) -> Result<CellMomentState, String> {
3853    let (moments, _) = evaluate_non_affine_cell_simd::<false>(cell, max_degree);
3854    let value_integral = evaluate_non_affine_cell_value_terminal(cell);
3855    // Reference structure: `value_integral * half_width / sqrt(TAU)`. The
3856    // half_width factor is already applied inside the rule evaluator, so divide
3857    // by sqrt(TAU) here (a true division, NOT multiply-by-reciprocal) to
3858    // reproduce the reference's final rounding bit-for-bit.
3859    Ok(CellMomentState {
3860        branch,
3861        value: value_integral / (std::f64::consts::TAU).sqrt(),
3862        moments,
3863    })
3864}
3865
3866fn evaluate_non_affine_cell_derivative_state(
3867    cell: DenestedCubicCell,
3868    branch: ExactCellBranch,
3869    max_degree: usize,
3870) -> Result<CellDerivativeMomentState, String> {
3871    let (moments, _) = evaluate_non_affine_cell_simd::<false>(cell, max_degree);
3872    Ok(CellDerivativeMomentState { branch, moments })
3873}
3874
3875/// De-nested cubic cell evaluator.
3876///
3877/// Affine cells use the closed-form affine anchor; non-affine cells (Quartic
3878/// and Sextic branches) are evaluated in a single pass over a fixed
3879/// high-order Gauss-Legendre rule on `[left, right]`.
3880pub fn evaluate_cell_moments(
3881    cell: DenestedCubicCell,
3882    max_degree: usize,
3883) -> Result<CellMomentState, String> {
3884    if !TAIL_CELL_MOMENT_CACHE_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
3885        return evaluate_cell_moments_uncached(cell, max_degree);
3886    }
3887    tail_cell_moment_cache().evaluate(cell, max_degree)
3888}
3889
3890/// Evaluate cell moments without consulting the global affine-tail memo.
3891///
3892/// This is retained for regression tests and before/after microbenchmarks;
3893/// production callers should use [`evaluate_cell_moments`].
3894pub fn evaluate_cell_moments_uncached(
3895    cell: DenestedCubicCell,
3896    max_degree: usize,
3897) -> Result<CellMomentState, String> {
3898    evaluate_cell_state_dispatched(
3899        cell,
3900        max_degree,
3901        evaluate_affine_cell_state,
3902        evaluate_non_affine_cell_state,
3903    )
3904}
3905
3906/// Evaluate only the moment vector needed by derivative contractions.
3907///
3908/// This deliberately does not compute the cell probability value
3909/// `∫ φ(z) Φ(η(z)) dz`. Derivative contractions consume
3910/// `∫ z^k exp(-q(z)) dz` moments only, so keeping the value out of the return
3911/// type prevents this cheaper evaluator from satisfying value-bearing calls.
3912pub fn evaluate_cell_derivative_moments_uncached(
3913    cell: DenestedCubicCell,
3914    max_degree: usize,
3915) -> Result<CellDerivativeMomentState, String> {
3916    evaluate_cell_state_dispatched(
3917        cell,
3918        max_degree,
3919        evaluate_affine_cell_derivative_state,
3920        evaluate_non_affine_cell_derivative_state,
3921    )
3922}
3923
3924/// Shared branch dispatch for the value-bearing and derivative-only cell
3925/// evaluators. Both walk the same decision tree (semi-infinite tail → must
3926/// be affine; finite cell → branch-by-coefficients with the sextic
3927/// degenerate-lowering path), differing only in which pair of
3928/// `(affine, non_affine)` evaluator helpers to delegate to.  The two helpers
3929/// are passed as `fn` pointers so the dispatch monomorphizes per `S` and
3930/// keeps the existing pre-condition errors / unreachable branch handling
3931/// in lockstep across both evaluators.
3932fn evaluate_cell_state_dispatched<S>(
3933    cell: DenestedCubicCell,
3934    max_degree: usize,
3935    affine: fn(DenestedCubicCell, usize) -> Result<S, String>,
3936    non_affine: fn(DenestedCubicCell, ExactCellBranch, usize) -> Result<S, String>,
3937) -> Result<S, String> {
3938    validate_cell_inputs(cell)?;
3939    let left_inf = !cell.left.is_finite();
3940    let right_inf = !cell.right.is_finite();
3941    if left_inf || right_inf {
3942        // Semi-infinite tail cells must be affine: the deviation saturates
3943        // to a constant outside support, so c2=c3=0.  Both the BVN CDF
3944        // and the truncated-Gaussian moment vector handle infinite bounds.
3945        if cell.c2 != 0.0 || cell.c3 != 0.0 {
3946            return Err(CubicCellKernelError::invalid_cell_shape(format!(
3947                "semi-infinite cell [{}, {}] must be affine (c2=c3=0), got c2={:.3e}, c3={:.3e}",
3948                cell.left, cell.right, cell.c2, cell.c3
3949            ))
3950            .into());
3951        }
3952        return affine(cell, max_degree);
3953    }
3954    if cell.right <= cell.left {
3955        return Err(CubicCellKernelError::invalid_cell_shape(format!(
3956            "finite cell must have left < right, got [{}, {}]",
3957            cell.left, cell.right
3958        ))
3959        .into());
3960    }
3961    let branch = branch_cell(cell)?;
3962    if branch == ExactCellBranch::Affine {
3963        return affine(cell, max_degree);
3964    }
3965    non_affine(cell, branch, max_degree)
3966}
3967
3968/// Evaluate a de-nested cubic cell through a fit-lifetime byte-limited LRU cache.
3969///
3970/// The fingerprint is an exact bit-cast of `(c0, c1, c2, c3, left, right)`, so
3971/// eviction and reuse cannot alias nearby-but-different cells.  A cached entry
3972/// computed to a higher degree may satisfy a lower-degree request by truncating
3973/// the moment vector, preserving the public [`evaluate_cell_moments`] contract.
3974pub fn evaluate_cell_moments_cached(
3975    cell: DenestedCubicCell,
3976    max_degree: usize,
3977    cache: &CellMomentLruCache,
3978    stats: Option<&CellMomentCacheStats>,
3979) -> Result<CellMomentState, String> {
3980    // Affine cells (every rigid-path cell and every tail cell) evaluate
3981    // through the closed-form anchor — cheaper than a single LRU probe. The
3982    // LRU exists only to amortize the EXPENSIVE non-affine transport across
3983    // recurring cells; at large n the row scalars `(a, b)` are unique per
3984    // row, so affine cells never recur and routing them through the sharded
3985    // mutex was pure cost (320k lock+insert+evict ops per gradient eval, ~0%
3986    // hit — the dominant cost of the rigid n=320k fit, #979). Bypass the
3987    // cache entirely for them.
3988    if matches!(branch_cell(cell), Ok(ExactCellBranch::Affine)) {
3989        if let Some(stats) = stats {
3990            stats.misses.fetch_add(1, Ordering::Relaxed);
3991        }
3992        return evaluate_cell_moments_uncached(cell, max_degree);
3993    }
3994    let key = CellFingerprint::new(cell);
3995    let existing_derivative = match cache.get(&key) {
3996        Some(cached) => {
3997            if let Some(state) = cached.state_for_degree(max_degree) {
3998                if let Some(stats) = stats {
3999                    stats.hits.fetch_add(1, Ordering::Relaxed);
4000                }
4001                return Ok(state);
4002            }
4003            // `cached.derivative_state` is `Option<Arc<_>>`; `.clone()` here
4004            // is the cheap refcount bump the audit-39 fix targets, not a
4005            // full moment-vector deep clone.
4006            cached.derivative_state.clone()
4007        }
4008        None => None,
4009    };
4010    if let Some(stats) = stats {
4011        stats.misses.fetch_add(1, Ordering::Relaxed);
4012    }
4013    let state = evaluate_cell_moments(cell, max_degree)?;
4014    // Wrap the freshly-computed state in `Arc` once, share it with the cache
4015    // through `Arc::clone`, and return the underlying value by unwrapping the
4016    // unique-reference (caller-side) `Arc`. This replaces the prior
4017    // `state.clone()` deep copy at the insert site.
4018    let shared = Arc::new(state);
4019    let mut entry = CachedCellMoments::new(Arc::clone(&shared));
4020    if let Some(derivative) = existing_derivative {
4021        entry = entry.with_derivative(derivative);
4022    }
4023    cache.insert(key, entry);
4024    Ok(Arc::try_unwrap(shared).unwrap_or_else(|a| (*a).clone()))
4025}
4026
4027/// Derivative-moment counterpart to [`evaluate_cell_moments_cached`]. Shares
4028/// the value-moment LRU by storing both moment kinds in a single
4029/// [`CachedCellMoments`] entry keyed on the cell fingerprint — derivative
4030/// insertions preserve any pre-existing value state and vice versa, so the
4031/// two callers never evict each other's work.
4032pub fn evaluate_cell_derivative_moments_cached(
4033    cell: DenestedCubicCell,
4034    max_degree: usize,
4035    cache: &CellMomentLruCache,
4036    stats: Option<&CellMomentCacheStats>,
4037) -> Result<CellDerivativeMomentState, String> {
4038    // Affine cells bypass the LRU — see `evaluate_cell_moments_cached` for
4039    // why the sharded-mutex memo is pure overhead on the closed-form affine
4040    // path at large n (#979).
4041    if matches!(branch_cell(cell), Ok(ExactCellBranch::Affine)) {
4042        if let Some(stats) = stats {
4043            stats.misses.fetch_add(1, Ordering::Relaxed);
4044        }
4045        return evaluate_cell_derivative_moments_uncached(cell, max_degree);
4046    }
4047    let key = CellFingerprint::new(cell);
4048    let existing_value = match cache.get(&key) {
4049        Some(cached) => {
4050            if let Some(state) = cached.derivative_state_for_degree(max_degree) {
4051                if let Some(stats) = stats {
4052                    stats.hits.fetch_add(1, Ordering::Relaxed);
4053                }
4054                return Ok(state);
4055            }
4056            // `cached.state` is `Option<Arc<_>>`; `.clone()` here is the cheap
4057            // refcount bump the audit-39 fix targets, not a full moment-vector
4058            // deep clone.
4059            cached.state.clone()
4060        }
4061        None => None,
4062    };
4063    if let Some(stats) = stats {
4064        stats.misses.fetch_add(1, Ordering::Relaxed);
4065    }
4066    let state = evaluate_cell_derivative_moments_uncached(cell, max_degree)?;
4067    // Wrap the freshly-computed state in `Arc` once, share it with the cache
4068    // through `Arc::clone`, and return the underlying value by unwrapping the
4069    // unique-reference (caller-side) `Arc`. This replaces the prior
4070    // `state.clone()` deep copy at the insert site.
4071    let shared = Arc::new(state);
4072    let mut entry = CachedCellMoments::new_derivative(Arc::clone(&shared));
4073    if let Some(value) = existing_value {
4074        entry = entry.with_value(value);
4075    }
4076    cache.insert(key, entry);
4077    Ok(Arc::try_unwrap(shared).unwrap_or_else(|a| (*a).clone()))
4078}
4079
4080/// Scratch-backed variant of [`evaluate_cell_moments`].
4081///
4082/// Reuses the supplied [`CellMomentScratch`] for the returned moments slice,
4083/// so repeated calls with the same scratch (and a sufficient initial capacity)
4084/// avoid per-call `Vec` allocations on the hot inner-PIRLS row-intercept
4085/// solver path. Internal transport allocations are unchanged.
4086pub fn evaluate_cell_moments_with_scratch<'a>(
4087    cell: DenestedCubicCell,
4088    max_degree: usize,
4089    scratch: &'a mut CellMomentScratch,
4090) -> Result<CellMomentStateRef<'a>, String> {
4091    let state = evaluate_cell_moments(cell, max_degree)?;
4092    let out = scratch.prepare_moments(max_degree + 1);
4093    out.copy_from_slice(&state.moments);
4094    Ok(CellMomentStateRef {
4095        branch: state.branch,
4096        value: state.value,
4097        moments: out,
4098    })
4099}
4100
4101#[cfg(test)]
4102mod tests {
4103    use super::*;
4104    use gam_math::probability::normal_pdf;
4105
4106    #[inline]
4107    pub(super) fn polynomial_value(coefficients: &[f64], z: f64) -> f64 {
4108        coefficients
4109            .iter()
4110            .rev()
4111            .fold(0.0, |acc, &coeff| acc * z + coeff)
4112    }
4113
4114    fn reset_cell_moment_test_reallocs() {
4115        super::CELL_MOMENT_REALLOCS.store(0, std::sync::atomic::Ordering::Relaxed);
4116    }
4117
4118    fn cell_moment_test_reallocs() -> usize {
4119        super::CELL_MOMENT_REALLOCS.load(std::sync::atomic::Ordering::Relaxed)
4120    }
4121
4122    fn assert_close_rel(label: &str, actual: f64, expected: f64, tol: f64) {
4123        let denom = expected.abs().max(1.0);
4124        let rel = (actual - expected).abs() / denom;
4125        assert!(
4126            rel <= tol,
4127            "{label}: actual={actual:.17e} expected={expected:.17e} rel={rel:.3e} tol={tol:.3e}"
4128        );
4129    }
4130
4131    // The link-basis cell coefficient `transformed_link_cubic(span, a, b)` is, in
4132    // each of its four output components, a polynomial of TOTAL degree exactly 3 in
4133    // (a, b):
4134    //   d0 = c0 + c1·s + c2·s² + c3·s³            (s = a − left; deg 3 in a)
4135    //   d1 = b·(c1 + 2c2·s + 3c3·s²)              (a²·b → total deg 3)
4136    //   d2 = b²·(c2 + 3c3·s)                       (a·b² → total deg 3)
4137    //   d3 = c3·b³                                 (b³  → total deg 3)
4138    // Therefore EVERY 4th-order total (a,b)-partial (∂⁴/∂aⁱ∂b^{4−i}) is identically
4139    // zero, while the 3rd-order partials (∂³/∂aⁱ∂b^{3−i}) are the highest nonzero
4140    // ones. This is the exact algebraic fact the bidirectional flex jet relies on:
4141    // a "second mixed derivative of a third-a-partial" slot, etc., demands a 4th
4142    // total (a,b)-partial and must be hard-zero — substituting a (nonzero) 3rd
4143    // partial there is a bug. This test certifies BOTH facts by central FD so the
4144    // hard-coded `0.0` fixes are provably correct and provably necessary.
4145    #[test]
4146    fn link_basis_cell_fourth_ab_partials_vanish_third_are_nonzero() {
4147        let span = LocalSpanCubic {
4148            left: -0.4,
4149            right: 1.6,
4150            c0: 0.37,
4151            c1: -0.81,
4152            c2: 0.53,
4153            c3: -0.29,
4154        };
4155        let a0 = 0.23_f64;
4156        let b0 = 0.61_f64;
4157        let h = 1e-2_f64;
4158
4159        // Generic central-difference stencils per derivative order.
4160        let stencil = |order: usize| -> &'static [(i64, f64)] {
4161            match order {
4162                0 => &[(0, 1.0)],
4163                1 => &[(-1, -0.5), (1, 0.5)],
4164                2 => &[(-1, 1.0), (0, -2.0), (1, 1.0)],
4165                3 => &[(-2, -0.5), (-1, 1.0), (1, -1.0), (2, 0.5)],
4166                4 => &[(-2, 1.0), (-1, -4.0), (0, 6.0), (1, -4.0), (2, 1.0)],
4167                _ => &[(0, 1.0)],
4168            }
4169        };
4170        // FD of component `k` of the cell coefficient: ∂^{na+nb}/∂a^{na}∂b^{nb}.
4171        let fd = |k: usize, na: usize, nb: usize| -> f64 {
4172            let mut acc = 0.0;
4173            for &(ia, wa) in stencil(na) {
4174                for &(ib, wb) in stencil(nb) {
4175                    let a = a0 + (ia as f64) * h;
4176                    let b = b0 + (ib as f64) * h;
4177                    acc += wa * wb * link_basis_cell_coefficients(span, a, b)[k];
4178                }
4179            }
4180            acc / h.powi((na + nb) as i32)
4181        };
4182
4183        let (p3_aaa, p3_aab, p3_abb, p3_bbb) = link_basis_cell_third_partials(span);
4184
4185        // (1) The analytic 3rd partials match FD (within FD truncation) — and at
4186        // least one is appreciably nonzero, so these are real signal that a wrong
4187        // slot would inject.
4188        let mut max_third = 0.0_f64;
4189        for k in 0..4 {
4190            for (label, (na, nb), analytic) in [
4191                ("aaa", (3usize, 0usize), p3_aaa[k]),
4192                ("aab", (2, 1), p3_aab[k]),
4193                ("abb", (1, 2), p3_abb[k]),
4194                ("bbb", (0, 3), p3_bbb[k]),
4195            ] {
4196                let got = fd(k, na, nb);
4197                assert!(
4198                    (got - analytic).abs() <= 1e-4 + 1e-3 * analytic.abs(),
4199                    "3rd partial {label}[{k}] analytic {analytic:+.6e} vs FD {got:+.6e}"
4200                );
4201                max_third = max_third.max(analytic.abs());
4202            }
4203        }
4204        assert!(
4205            max_third > 1e-1,
4206            "expected an appreciable nonzero 3rd (a,b)-partial; max |analytic| = {max_third:.3e}"
4207        );
4208
4209        // (2) EVERY 4th-order total (a,b)-partial vanishes (degree-3 polynomial),
4210        // certifying that the hard-coded `0.0` in the bidirectional d12 slots is the
4211        // mathematically required value, not an approximation.
4212        for k in 0..4 {
4213            for (na, nb) in [(4usize, 0usize), (3, 1), (2, 2), (1, 3), (0, 4)] {
4214                let got = fd(k, na, nb);
4215                assert!(
4216                    got.abs() <= 1e-2,
4217                    "4th (a,b)-partial ∂^{na}_a∂^{nb}_b of cell coeff[{k}] must vanish, FD = {got:+.6e}"
4218                );
4219            }
4220        }
4221    }
4222
4223    #[test]
4224    fn non_affine_cell_state_grid_matches_public_cell_moments_reference() {
4225        let cells = [
4226            DenestedCubicCell {
4227                left: -1.25,
4228                right: -0.2,
4229                c0: -0.35,
4230                c1: 0.85,
4231                c2: 0.04,
4232                c3: -0.015,
4233            },
4234            DenestedCubicCell {
4235                left: -0.2,
4236                right: 0.55,
4237                c0: 0.12,
4238                c1: -0.65,
4239                c2: -0.025,
4240                c3: 0.02,
4241            },
4242            DenestedCubicCell {
4243                left: 0.55,
4244                right: 1.6,
4245                c0: 0.42,
4246                c1: 0.35,
4247                c2: 0.018,
4248                c3: 0.012,
4249            },
4250        ];
4251        for cell in cells {
4252            let branch = branch_cell(cell).expect("branch");
4253            assert_ne!(branch, ExactCellBranch::Affine);
4254            for max_degree in [0usize, 2, 4, 9, 16] {
4255                let direct = evaluate_non_affine_cell_state(cell, branch, max_degree)
4256                    .expect("direct non-affine transport");
4257                let public = evaluate_cell_moments(cell, max_degree).expect("public evaluator");
4258                assert_eq!(direct.branch, public.branch);
4259                assert_eq!(direct.moments.len(), public.moments.len());
4260                let value_scale = direct.value.abs().max(public.value.abs()).max(1.0);
4261                assert!(
4262                    (direct.value - public.value).abs() <= 1e-10 * value_scale,
4263                    "value mismatch for {cell:?} degree {max_degree}: direct={} public={}",
4264                    direct.value,
4265                    public.value
4266                );
4267                for (degree, (lhs, rhs)) in
4268                    direct.moments.iter().zip(public.moments.iter()).enumerate()
4269                {
4270                    let scale = lhs.abs().max(rhs.abs()).max(1.0);
4271                    assert!(
4272                        (lhs - rhs).abs() <= 1e-10 * scale,
4273                        "moment {degree} mismatch for {cell:?} degree {max_degree}: {lhs} vs {rhs}"
4274                    );
4275                }
4276            }
4277        }
4278    }
4279
4280    #[test]
4281    fn affine_tail_cell_memo_matches_uncached_grid_and_records_hits() {
4282        // Use a dedicated local cache so the test's hit/miss/entry counters
4283        // are not perturbed by concurrent tests that drive the shared
4284        // global memo through `evaluate_cell_moments`. Asserting on the
4285        // global counters made this test race-flaky when the suite ran in
4286        // parallel.
4287        let cache = TailCellMomentCache::new();
4288        let c0s = [-2.0, -0.25, 0.0, 1.5];
4289        let c1s = [-1.2, -0.05, 0.0, 0.8];
4290        let endpoints = [-4.0, -1.0, 0.0, 2.5, 6.0];
4291        let degrees = [0_usize, 4, 9, 16, 24];
4292
4293        for &c0 in &c0s {
4294            for &c1 in &c1s {
4295                for &endpoint in &endpoints {
4296                    for &max_degree in &degrees {
4297                        for &(left, right) in
4298                            &[(f64::NEG_INFINITY, endpoint), (endpoint, f64::INFINITY)]
4299                        {
4300                            let cell = DenestedCubicCell {
4301                                left,
4302                                right,
4303                                c0,
4304                                c1,
4305                                c2: 0.0,
4306                                c3: 0.0,
4307                            };
4308                            let expected = evaluate_cell_moments_uncached(cell, max_degree)
4309                                .expect("uncached affine tail moments");
4310                            let actual = cache
4311                                .evaluate(cell, max_degree)
4312                                .expect("cached affine tail moments miss");
4313                            let repeat = cache
4314                                .evaluate(cell, max_degree)
4315                                .expect("cached affine tail moments hit");
4316                            assert_eq!(actual.branch, expected.branch);
4317                            assert_eq!(repeat.branch, expected.branch);
4318                            assert_close_rel(
4319                                "tail value miss",
4320                                actual.value,
4321                                expected.value,
4322                                1e-14,
4323                            );
4324                            assert_close_rel("tail value hit", repeat.value, expected.value, 1e-14);
4325                            assert_eq!(actual.moments.len(), expected.moments.len());
4326                            assert_eq!(repeat.moments.len(), expected.moments.len());
4327                            for (idx, ((a, r), e)) in actual
4328                                .moments
4329                                .iter()
4330                                .zip(repeat.moments.iter())
4331                                .zip(expected.moments.iter())
4332                                .enumerate()
4333                            {
4334                                assert_close_rel(
4335                                    &format!("tail moment miss[{idx}]"),
4336                                    *a,
4337                                    *e,
4338                                    1e-14,
4339                                );
4340                                assert_close_rel(&format!("tail moment hit[{idx}]"), *r, *e, 1e-14);
4341                            }
4342                        }
4343                    }
4344                }
4345            }
4346        }
4347
4348        let stats = cache.stats();
4349        assert_eq!(stats.misses, stats.entries);
4350        assert!(
4351            stats.hits >= stats.misses,
4352            "expected repeat hits: {stats:?}"
4353        );
4354        assert!(
4355            stats.hit_rate() >= 0.5,
4356            "unexpected low hit rate: {stats:?}"
4357        );
4358    }
4359
4360    fn reference_bivariate_normal_cdf_20(h: f64, k: f64, rho: f64) -> f64 {
4361        if h == f64::NEG_INFINITY || k == f64::NEG_INFINITY {
4362            return 0.0;
4363        }
4364        if h == f64::INFINITY {
4365            return normal_cdf(k);
4366        }
4367        if k == f64::INFINITY {
4368            return normal_cdf(h);
4369        }
4370        let rho_clamped = rho.clamp(-1.0, 1.0);
4371        if rho_clamped >= 1.0 - 1e-12 {
4372            return normal_cdf(h.min(k));
4373        }
4374        if rho_clamped <= -1.0 + 1e-12 {
4375            return (normal_cdf(h) - normal_cdf(-k)).clamp(0.0, 1.0);
4376        }
4377
4378        let hs = 0.5 * (h * h + k * k);
4379        let asr = rho_clamped.asin();
4380        let mut sum = 0.0;
4381        for (&node, &weight) in GL20_NODES.iter().zip(GL20_WEIGHTS.iter()) {
4382            let sn = (0.5 * asr * (node + 1.0)).sin();
4383            let one_minus = 1.0 - sn * sn;
4384            let expo = ((sn * h * k) - hs) / one_minus;
4385            sum += weight * expo.exp();
4386        }
4387        (normal_cdf(h) * normal_cdf(k) + asr * sum / (4.0 * std::f64::consts::PI)).clamp(0.0, 1.0)
4388    }
4389
4390    #[test]
4391    fn non_affine_cell_state_reference_grid_matches_public_moments() {
4392        let c0s = [-0.4, 0.0, 0.35];
4393        let c1s = [-0.8, 0.25, 1.1];
4394        let c2s = [-0.12, 0.08];
4395        let c3s = [-0.04, 0.03];
4396        let intervals = [(-1.25, -0.2), (-0.5, 0.75), (0.1, 1.4)];
4397        let degrees = [3usize, 6, 9, 12];
4398
4399        for &c0 in &c0s {
4400            for &c1 in &c1s {
4401                for &c2 in &c2s {
4402                    for &c3 in &c3s {
4403                        for &(left, right) in &intervals {
4404                            let cell = DenestedCubicCell {
4405                                left,
4406                                right,
4407                                c0,
4408                                c1,
4409                                c2,
4410                                c3,
4411                            };
4412                            let branch = branch_cell(cell).expect("branch");
4413                            assert_ne!(branch, ExactCellBranch::Affine);
4414                            for &degree in &degrees {
4415                                let direct = evaluate_non_affine_cell_state(cell, branch, degree)
4416                                    .expect("direct non-affine state");
4417                                let public = evaluate_cell_moments(cell, degree)
4418                                    .expect("public non-affine state");
4419                                assert_eq!(direct.branch, public.branch);
4420                                let value_scale =
4421                                    direct.value.abs().max(public.value.abs()).max(1.0);
4422                                assert!(
4423                                    (direct.value - public.value).abs() / value_scale <= 1.0e-15,
4424                                    "value mismatch for {cell:?}, degree {degree}: direct={:.17e}, public={:.17e}",
4425                                    direct.value,
4426                                    public.value
4427                                );
4428                                assert_eq!(direct.moments.len(), public.moments.len());
4429                                for (idx, (&a, &b)) in
4430                                    direct.moments.iter().zip(public.moments.iter()).enumerate()
4431                                {
4432                                    let scale = a.abs().max(b.abs()).max(1.0);
4433                                    assert!(
4434                                        (a - b).abs() / scale <= 1.0e-15,
4435                                        "moment {idx} mismatch for {cell:?}, degree {degree}: direct={a:.17e}, public={b:.17e}"
4436                                    );
4437                                }
4438                            }
4439                        }
4440                    }
4441                }
4442            }
4443        }
4444    }
4445
4446    #[test]
4447    fn bivariate_normal_cdf_matches_reference_grid_to_1e_minus_10() {
4448        let hs = [-8.0, -5.0, -3.0, -1.5, -0.5, 0.0, 0.25, 1.0, 2.5, 5.0, 8.0];
4449        let ks = [-8.0, -4.0, -2.0, -0.75, 0.0, 0.4, 1.25, 3.0, 6.0, 8.0];
4450        let rhos = [
4451            -0.999_999_999_999,
4452            -0.999,
4453            -0.95,
4454            -0.7,
4455            -0.3,
4456            -1.0e-12,
4457            0.0,
4458            1.0e-12,
4459            0.3,
4460            0.7,
4461            0.95,
4462            0.999,
4463            0.999_999_999_999,
4464        ];
4465        for &h in &hs {
4466            for &k in &ks {
4467                for &rho in &rhos {
4468                    let actual = bivariate_normal_cdf(h, k, rho).expect("bvn");
4469                    let expected = reference_bivariate_normal_cdf_20(h, k, rho);
4470                    let scale = expected.abs().max(1.0e-300);
4471                    let rel = (actual - expected).abs() / scale;
4472                    assert!(
4473                        rel < 1.0e-10 || (actual - expected).abs() < 1.0e-14,
4474                        "h={h} k={k} rho={rho} actual={actual:.17e} expected={expected:.17e} rel={rel:.3e}"
4475                    );
4476                }
4477            }
4478        }
4479    }
4480
4481    #[test]
4482    fn bivariate_normal_cdf_matches_reference_lcg_property_samples() {
4483        let mut seed = 0x5eed_cafe_f00d_u64;
4484        let mut next_unit = || {
4485            seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
4486            ((seed >> 11) as f64) * (1.0 / ((1_u64 << 53) as f64))
4487        };
4488        for _ in 0..4096 {
4489            let h = -8.0 + 16.0 * next_unit();
4490            let k = -8.0 + 16.0 * next_unit();
4491            let rho = -0.999 + 1.998 * next_unit();
4492            let actual = bivariate_normal_cdf(h, k, rho).expect("bvn");
4493            let expected = reference_bivariate_normal_cdf_20(h, k, rho);
4494            let scale = expected.abs().max(1.0e-300);
4495            let rel = (actual - expected).abs() / scale;
4496            assert!(
4497                rel < 1.0e-10 || (actual - expected).abs() < 1.0e-14,
4498                "h={h} k={k} rho={rho} actual={actual:.17e} expected={expected:.17e} rel={rel:.3e}"
4499            );
4500        }
4501    }
4502
4503    #[test]
4504    fn affine_bvn_interval_primitive_matches_two_cdf_difference() {
4505        let hs = [-6.0, -2.0, -0.25, 0.0, 0.8, 3.0, 6.0];
4506        let bounds = [
4507            (-5.0, -2.0),
4508            (-3.0, -0.1),
4509            (-1.0, 0.0),
4510            (-0.25, 0.75),
4511            (0.2, 3.5),
4512            (2.0, 7.0),
4513        ];
4514        let rhos = [-0.98, -0.8, -0.25, 0.0, 0.25, 0.8, 0.98];
4515        for &h in &hs {
4516            for &(left, right) in &bounds {
4517                for &rho in &rhos {
4518                    let actual =
4519                        bivariate_normal_cdf_interval(h, left, right, rho).expect("interval");
4520                    let expected = (reference_bivariate_normal_cdf_20(h, right, rho)
4521                        - reference_bivariate_normal_cdf_20(h, left, rho))
4522                    .clamp(0.0, 1.0);
4523                    let scale = expected.abs().max(1.0e-300);
4524                    let rel = (actual - expected).abs() / scale;
4525                    assert!(
4526                        rel < 1.0e-10 || (actual - expected).abs() < 1.0e-12,
4527                        "h={h} left={left} right={right} rho={rho} actual={actual:.17e} expected={expected:.17e} rel={rel:.3e}"
4528                    );
4529                }
4530            }
4531        }
4532    }
4533
4534    fn simpson_integral<F>(left: f64, right: f64, steps: usize, f: F) -> f64
4535    where
4536        F: Fn(f64) -> f64,
4537    {
4538        let n = if steps.is_multiple_of(2) {
4539            steps
4540        } else {
4541            steps + 1
4542        };
4543        let h = (right - left) / n as f64;
4544        let mut acc = f(left) + f(right);
4545        for k in 1..n {
4546            let x = left + h * k as f64;
4547            let w = if k % 2 == 0 { 2.0 } else { 4.0 };
4548            acc += w * f(x);
4549        }
4550        acc * h / 3.0
4551    }
4552
4553    #[test]
4554    fn global_transform_preserves_local_span_polynomial() {
4555        let span = LocalSpanCubic {
4556            left: -1.2,
4557            right: 0.8,
4558            c0: 0.3,
4559            c1: -0.25,
4560            c2: 0.11,
4561            c3: -0.04,
4562        };
4563        let (g0, g1, g2, g3) = global_cubic_from_local(span);
4564        for &x in &[-1.2, -0.7, -0.1, 0.4, 0.8] {
4565            let local = span.evaluate(x);
4566            let global = g0 + g1 * x + g2 * x * x + g3 * x * x * x;
4567            assert!((local - global).abs() < 1e-12);
4568        }
4569    }
4570
4571    #[test]
4572    fn bivariate_normal_cdf_independent_factorizes() {
4573        let h = -0.35;
4574        let k = 0.8;
4575        let out = bivariate_normal_cdf(h, k, 0.0).expect("bvn");
4576        let target = normal_cdf(h) * normal_cdf(k);
4577        assert!((out - target).abs() < 1e-12);
4578    }
4579
4580    #[test]
4581    fn evaluate_affine_cell_state_matches_numeric_integrals() {
4582        let cell = DenestedCubicCell {
4583            left: -0.9,
4584            right: 0.8,
4585            c0: 0.15,
4586            c1: -0.35,
4587            c2: 0.0,
4588            c3: 0.0,
4589        };
4590        let state = evaluate_affine_cell_state(cell, 6).expect("affine cell");
4591        let value_numeric = simpson_integral(cell.left, cell.right, 4000, |z| {
4592            super::normal_cdf(cell.eta(z)) * normal_pdf(z)
4593        });
4594        assert_eq!(state.branch, ExactCellBranch::Affine);
4595        assert!((state.value - value_numeric).abs() < 1e-9);
4596        for degree in 0..=6 {
4597            let target = simpson_integral(cell.left, cell.right, 4000, |z| {
4598                z.powi(degree as i32) * (-cell.q(z)).exp()
4599            });
4600            assert!((state.moments[degree] - target).abs() < 1e-9);
4601        }
4602    }
4603
4604    /// #2293 regression at the exact failure boundary: the affine primitive
4605    /// must propagate a BVN-domain error instead of substituting the plausible
4606    /// probability `0.0`. This calls the private primitive directly so the
4607    /// public cell validator below cannot intercept the malformed state first;
4608    /// restoring `unwrap_or(0.0)` would make these cases return `Ok(0.0)` and
4609    /// fail this test.
4610    #[test]
4611    fn affine_value_primitive_propagates_bvn_errors_2293() {
4612        for (case, result) in [
4613            (
4614                "non-finite standardized threshold",
4615                affine_value_from_moment_primitive(f64::NAN, -0.35, -0.9, 0.8),
4616            ),
4617            (
4618                "non-finite integration bound",
4619                affine_value_from_moment_primitive(0.15, -0.35, f64::NAN, 0.8),
4620            ),
4621        ] {
4622            let error = result.expect_err(case);
4623            assert!(!error.is_empty(), "{case} must retain its BVN diagnostic");
4624        }
4625    }
4626
4627    /// Public evaluators must reject malformed cells at their validation
4628    /// boundary. This is intentionally separate from
4629    /// `affine_value_primitive_propagates_bvn_errors_2293`, which bypasses that
4630    /// boundary to pin the internal `Result` propagation itself.
4631    #[test]
4632    fn affine_cell_errors_are_never_substituted_with_probability_zero_2293() {
4633        let base = DenestedCubicCell {
4634            left: -0.9,
4635            right: 0.8,
4636            c0: 0.15,
4637            c1: -0.35,
4638            c2: 0.0,
4639            c3: 0.0,
4640        };
4641        for (field, invalid) in [
4642            ("c0", f64::NAN),
4643            ("c0", f64::INFINITY),
4644            ("c1", f64::NEG_INFINITY),
4645            ("c2", f64::NAN),
4646            ("c3", f64::INFINITY),
4647        ] {
4648            let cell = match field {
4649                "c0" => DenestedCubicCell {
4650                    c0: invalid,
4651                    ..base
4652                },
4653                "c1" => DenestedCubicCell {
4654                    c1: invalid,
4655                    ..base
4656                },
4657                "c2" => DenestedCubicCell {
4658                    c2: invalid,
4659                    ..base
4660                },
4661                "c3" => DenestedCubicCell {
4662                    c3: invalid,
4663                    ..base
4664                },
4665                _ => unreachable!(),
4666            };
4667            assert!(evaluate_affine_cell_state(cell, 3).is_err());
4668            assert!(evaluate_cell_moments_uncached(cell, 3).is_err());
4669        }
4670        for cell in [
4671            DenestedCubicCell {
4672                left: f64::NAN,
4673                ..base
4674            },
4675            DenestedCubicCell {
4676                right: f64::NAN,
4677                ..base
4678            },
4679            DenestedCubicCell {
4680                left: 1.0,
4681                right: 0.0,
4682                ..base
4683            },
4684        ] {
4685            assert!(evaluate_affine_cell_state(cell, 3).is_err());
4686            assert!(evaluate_cell_moments_uncached(cell, 3).is_err());
4687        }
4688    }
4689
4690    #[test]
4691    fn semi_infinite_cells_require_structurally_affine_coefficients_2293() {
4692        let tiny_curvature = 5.0e-11;
4693        for cell in [
4694            DenestedCubicCell {
4695                left: f64::NEG_INFINITY,
4696                right: 0.5,
4697                c0: 0.2,
4698                c1: -0.1,
4699                c2: tiny_curvature,
4700                c3: 0.0,
4701            },
4702            DenestedCubicCell {
4703                left: -0.5,
4704                right: f64::INFINITY,
4705                c0: 0.2,
4706                c1: -0.1,
4707                c2: 0.0,
4708                c3: -tiny_curvature,
4709            },
4710        ] {
4711            assert!(branch_cell(cell).is_err());
4712            assert!(evaluate_cell_moments_uncached(cell, 3).is_err());
4713            assert!(tail_cell_cache_key(cell, 3).is_none());
4714        }
4715    }
4716
4717    #[test]
4718    fn large_affine_anchor_cannot_hide_finite_cell_curvature_2321() {
4719        let cell = DenestedCubicCell {
4720            left: -1.0,
4721            right: 1.0,
4722            c0: 1.0e8,
4723            c1: -2.0e7,
4724            c2: -7.895_512e-3,
4725            c3: -2.973_499e-3,
4726        };
4727
4728        assert_eq!(branch_cell(cell).unwrap(), ExactCellBranch::Sextic);
4729        assert_ne!(
4730            evaluate_cell_moments_uncached(cell, 9).unwrap().branch,
4731            ExactCellBranch::Affine
4732        );
4733    }
4734
4735    #[test]
4736    fn affine_cell_value_matches_zero_moment_derivative() {
4737        let cell = DenestedCubicCell {
4738            left: -1.1,
4739            right: 0.7,
4740            c0: 0.23,
4741            c1: -0.41,
4742            c2: 0.0,
4743            c3: 0.0,
4744        };
4745        let h = 1e-6;
4746        let plus = evaluate_affine_cell_state(
4747            DenestedCubicCell {
4748                c0: cell.c0 + h,
4749                ..cell
4750            },
4751            0,
4752        )
4753        .expect("affine plus");
4754        let minus = evaluate_affine_cell_state(
4755            DenestedCubicCell {
4756                c0: cell.c0 - h,
4757                ..cell
4758            },
4759            0,
4760        )
4761        .expect("affine minus");
4762        let center = evaluate_affine_cell_state(cell, 0).expect("affine center");
4763        let d_value = (plus.value - minus.value) / (2.0 * h);
4764        let target = INV_TWO_PI * center.moments[0];
4765        assert!((d_value - target).abs() < 1e-8);
4766    }
4767
4768    #[test]
4769    fn coefficient_partials_match_exact_span_derivatives() {
4770        let score_span = LocalSpanCubic {
4771            left: -0.75,
4772            right: 0.25,
4773            c0: 0.08,
4774            c1: -0.03,
4775            c2: 0.02,
4776            c3: -0.01,
4777        };
4778        let link_span = LocalSpanCubic {
4779            left: -0.6,
4780            right: 0.9,
4781            c0: -0.05,
4782            c1: 0.04,
4783            c2: -0.02,
4784            c3: 0.015,
4785        };
4786        let a = 0.3;
4787        let b = -0.7;
4788        let (dc_da, dc_db) = denested_cell_coefficient_partials(score_span, link_span, a, b);
4789        for &z in &[-0.75, -0.4, -0.1, 0.2] {
4790            let u = a + b * z;
4791            let eta_a = 1.0 + link_span.first_derivative(u);
4792            let eta_b = z + score_span.evaluate(z) + z * link_span.first_derivative(u);
4793            assert!((polynomial_value(&dc_da, z) - eta_a).abs() < 1e-12);
4794            assert!((polynomial_value(&dc_db, z) - eta_b).abs() < 1e-12);
4795        }
4796    }
4797
4798    #[test]
4799    fn second_coefficient_partials_match_exact_span_derivatives() {
4800        let score_span = LocalSpanCubic {
4801            left: -0.75,
4802            right: 0.25,
4803            c0: 0.08,
4804            c1: -0.03,
4805            c2: 0.02,
4806            c3: -0.01,
4807        };
4808        let link_span = LocalSpanCubic {
4809            left: -0.6,
4810            right: 0.9,
4811            c0: -0.05,
4812            c1: 0.04,
4813            c2: -0.02,
4814            c3: 0.015,
4815        };
4816        let a = 0.3;
4817        let b = -0.7;
4818        let second_partials = denested_cell_second_partials(score_span, link_span, a, b);
4819        let dc_daa = second_partials.0;
4820        let dc_dab = second_partials.1;
4821        let dc_dbb = second_partials.2;
4822        for &z in &[-0.75, -0.4, -0.1, 0.2] {
4823            let u = a + b * z;
4824            let eta_aa = link_span.second_derivative(u);
4825            let eta_ab = z * link_span.second_derivative(u);
4826            let eta_bb = z * z * link_span.second_derivative(u);
4827            assert!((polynomial_value(&dc_daa, z) - eta_aa).abs() < 1e-12);
4828            assert!((polynomial_value(&dc_dab, z) - eta_ab).abs() < 1e-12);
4829            assert!((polynomial_value(&dc_dbb, z) - eta_bb).abs() < 1e-12);
4830        }
4831    }
4832
4833    #[test]
4834    fn higher_derivative_moment_helpers_reject_empty_first_coefficients() {
4835        let cell = DenestedCubicCell {
4836            left: -1.0,
4837            right: 1.0,
4838            c0: 0.0,
4839            c1: 1.0,
4840            c2: 0.0,
4841            c3: 0.0,
4842        };
4843        let moments = [1.0; 16];
4844
4845        let third_err = cell_third_derivative_from_moments(
4846            cell,
4847            &[],
4848            &[1.0],
4849            &[1.0],
4850            &[],
4851            &[],
4852            &[],
4853            &[],
4854            &moments,
4855        )
4856        .expect_err("empty first coefficients should be rejected");
4857        assert!(third_err.contains("r first-derivative coefficients must be non-empty"));
4858
4859        let fourth_err = cell_fourth_derivative_from_moments(
4860            cell,
4861            &[1.0],
4862            &[],
4863            &[1.0],
4864            &[1.0],
4865            &[],
4866            &[],
4867            &[],
4868            &[],
4869            &[],
4870            &[],
4871            &[],
4872            &[],
4873            &[],
4874            &[],
4875            &[],
4876            &moments,
4877        )
4878        .expect_err("empty first coefficients should be rejected");
4879        assert!(fourth_err.contains("s first-derivative coefficients must be non-empty"));
4880    }
4881
4882    #[test]
4883    fn fourth_derivative_rejects_overlong_scratch_convolutions() {
4884        let cell = DenestedCubicCell {
4885            left: -1.0,
4886            right: 1.0,
4887            c0: 0.0,
4888            c1: 1.0,
4889            c2: 0.0,
4890            c3: 0.0,
4891        };
4892        let long_first = [1.0; 10];
4893        let zero = [0.0; 1];
4894        let moments = [1.0; 64];
4895
4896        let err = cell_fourth_derivative_from_moments(
4897            cell,
4898            &long_first,
4899            &long_first,
4900            &long_first,
4901            &long_first,
4902            &zero,
4903            &zero,
4904            &zero,
4905            &zero,
4906            &zero,
4907            &zero,
4908            &zero,
4909            &zero,
4910            &zero,
4911            &zero,
4912            &zero,
4913            &moments,
4914        )
4915        .expect_err("oversized convolution should be rejected before writing scratch");
4916        assert!(err.contains("fourth derivative polynomial convolution scratch too small"));
4917    }
4918
4919    #[test]
4920    fn score_and_link_basis_cell_coefficients_match_direct_construction() {
4921        let score_basis_span = LocalSpanCubic {
4922            left: -0.7,
4923            right: 0.4,
4924            c0: 0.2,
4925            c1: -0.04,
4926            c2: 0.03,
4927            c3: -0.01,
4928        };
4929        let link_basis_span = LocalSpanCubic {
4930            left: -0.5,
4931            right: 1.1,
4932            c0: -0.03,
4933            c1: 0.05,
4934            c2: -0.02,
4935            c3: 0.01,
4936        };
4937        let a = 0.25;
4938        let b = -0.8;
4939        let score_coeffs = score_basis_cell_coefficients(score_basis_span, b);
4940        let link_coeffs = link_basis_cell_coefficients(link_basis_span, a, b);
4941        for &z in &[-0.7, -0.1, 0.2, 0.4] {
4942            let score_poly = polynomial_value(&score_coeffs, z);
4943            let link_poly = polynomial_value(&link_coeffs, z);
4944            assert!((score_poly - b * score_basis_span.evaluate(z)).abs() < 1e-12);
4945            assert!((link_poly - link_basis_span.evaluate(a + b * z)).abs() < 1e-12);
4946        }
4947    }
4948
4949    #[test]
4950    fn link_basis_partials_match_exact_span_derivatives() {
4951        let link_basis_span = LocalSpanCubic {
4952            left: -0.5,
4953            right: 1.1,
4954            c0: -0.03,
4955            c1: 0.05,
4956            c2: -0.02,
4957            c3: 0.01,
4958        };
4959        let a = 0.25;
4960        let b = -0.8;
4961        let (dc_da, dc_db) = link_basis_cell_coefficient_partials(link_basis_span, a, b);
4962        let (dc_daa, dc_dab, dc_dbb) = link_basis_cell_second_partials(link_basis_span, a, b);
4963        for &z in &[-0.6, -0.2, 0.15, 0.5] {
4964            let u = a + b * z;
4965            let eta_a = link_basis_span.first_derivative(u);
4966            let eta_b = z * link_basis_span.first_derivative(u);
4967            let eta_aa = link_basis_span.second_derivative(u);
4968            let eta_ab = z * link_basis_span.second_derivative(u);
4969            let eta_bb = z * z * link_basis_span.second_derivative(u);
4970            assert!((polynomial_value(&dc_da, z) - eta_a).abs() < 1e-12);
4971            assert!((polynomial_value(&dc_db, z) - eta_b).abs() < 1e-12);
4972            assert!((polynomial_value(&dc_daa, z) - eta_aa).abs() < 1e-12);
4973            assert!((polynomial_value(&dc_dab, z) - eta_ab).abs() < 1e-12);
4974            assert!((polynomial_value(&dc_dbb, z) - eta_bb).abs() < 1e-12);
4975        }
4976    }
4977
4978    #[test]
4979    fn denested_third_partials_match_exact_span_derivatives() {
4980        let link_span = LocalSpanCubic {
4981            left: -0.6,
4982            right: 0.9,
4983            c0: -0.05,
4984            c1: 0.04,
4985            c2: -0.02,
4986            c3: 0.015,
4987        };
4988        let (dc_daaa, dc_daab, dc_dabb, dc_dbbb) = denested_cell_third_partials(link_span);
4989        let link_third = 6.0 * link_span.c3;
4990        for &z in &[-0.75, -0.4, -0.1, 0.2] {
4991            let eta_aaa = link_third;
4992            let eta_aab = z * link_third;
4993            let eta_abb = z * z * link_third;
4994            let eta_bbb = z * z * z * link_third;
4995            assert!((polynomial_value(&dc_daaa, z) - eta_aaa).abs() < 1e-12);
4996            assert!((polynomial_value(&dc_daab, z) - eta_aab).abs() < 1e-12);
4997            assert!((polynomial_value(&dc_dabb, z) - eta_abb).abs() < 1e-12);
4998            assert!((polynomial_value(&dc_dbbb, z) - eta_bbb).abs() < 1e-12);
4999        }
5000    }
5001
5002    #[test]
5003    fn link_basis_third_partials_match_exact_span_derivatives() {
5004        let link_basis_span = LocalSpanCubic {
5005            left: -0.5,
5006            right: 1.1,
5007            c0: -0.03,
5008            c1: 0.05,
5009            c2: -0.02,
5010            c3: 0.01,
5011        };
5012        let (dc_daaa, dc_daab, dc_dabb, dc_dbbb) = link_basis_cell_third_partials(link_basis_span);
5013        let link_third = 6.0 * link_basis_span.c3;
5014        for &z in &[-0.6, -0.2, 0.15, 0.5] {
5015            let eta_aaa = link_third;
5016            let eta_aab = z * link_third;
5017            let eta_abb = z * z * link_third;
5018            let eta_bbb = z * z * z * link_third;
5019            assert!((polynomial_value(&dc_daaa, z) - eta_aaa).abs() < 1e-12);
5020            assert!((polynomial_value(&dc_daab, z) - eta_aab).abs() < 1e-12);
5021            assert!((polynomial_value(&dc_dabb, z) - eta_abb).abs() < 1e-12);
5022            assert!((polynomial_value(&dc_dbbb, z) - eta_bbb).abs() < 1e-12);
5023        }
5024    }
5025
5026    #[test]
5027    fn branch_selection_uses_exact_polynomial_degree() {
5028        let affine = DenestedCubicCell {
5029            left: -1.0,
5030            right: 1.0,
5031            c0: 0.1,
5032            c1: -0.4,
5033            c2: 0.0,
5034            c3: 0.0,
5035        };
5036        let quartic = DenestedCubicCell {
5037            c2: 2e-4,
5038            c3: 0.0,
5039            ..affine
5040        };
5041        let sextic = DenestedCubicCell {
5042            c2: 2e-4,
5043            c3: -1e-13,
5044            ..affine
5045        };
5046        assert_eq!(branch_cell(affine).unwrap(), ExactCellBranch::Affine);
5047        assert_eq!(branch_cell(quartic).unwrap(), ExactCellBranch::Quartic);
5048        assert_eq!(branch_cell(sextic).unwrap(), ExactCellBranch::Sextic);
5049    }
5050
5051    #[test]
5052    fn affine_anchor_moments_match_whole_line_closed_forms() {
5053        let out = affine_anchor_moment_vector(0.0, 0.0, f64::NEG_INFINITY, f64::INFINITY, 4);
5054        // `affine_anchor_moment_vector` returns the RAW substrate moments
5055        // `T_n = ∫ z^n exp(-½z²) dz` (the cubic-cell `∫ z^n exp(-q) dz`
5056        // convention that every production consumer and the GPU parity path
5057        // share; the `1/√(2π)` is folded in downstream via `INV_TWO_PI`). At
5058        // the affine identity the anchor is the *unnormalized* standard normal,
5059        // so M0 = M2 = √(2π) and M1 = 0 — the normalized {1, 0, 1} moments
5060        // scaled by the whole-line mass √(2π).
5061        let sqrt_2pi = (2.0 * std::f64::consts::PI).sqrt();
5062        assert!((out[0] - sqrt_2pi).abs() < 1e-12);
5063        assert!(out[1].abs() < 1e-12);
5064        assert!((out[2] - sqrt_2pi).abs() < 1e-12);
5065    }
5066
5067    #[test]
5068    fn affine_anchor_moments_match_shifted_gaussian_whole_line() {
5069        let alpha = 0.7;
5070        let beta = -0.4;
5071        let out = affine_anchor_moment_vector(alpha, beta, f64::NEG_INFINITY, f64::INFINITY, 4);
5072        let s = (1.0 + beta * beta).sqrt();
5073        let mu = -alpha * beta / (1.0 + beta * beta);
5074        // RAW (unnormalized) whole-line moments of the affine anchor
5075        // `exp(-½(alpha + beta·z)²)·exp(-½z²)`, an unnormalized Gaussian with
5076        // mean `mu` and variance `1/s²`. Its raw moments carry the `√(2π)` mass
5077        // factor: M0 = √(2π)·scale, M1 = √(2π)·scale·mu,
5078        // M2 = √(2π)·scale·(mu² + 1/s²), where the anchor amplitude
5079        // `scale = exp(-alpha² / 2s²) / s`.
5080        let scale = (-alpha * alpha / (2.0 * s * s)).exp() / s;
5081        let sqrt_2pi = (2.0 * std::f64::consts::PI).sqrt();
5082        assert!((out[0] - scale * sqrt_2pi).abs() < 1e-12);
5083        assert!((out[1] - scale * sqrt_2pi * mu).abs() < 1e-12);
5084        assert!((out[2] - scale * sqrt_2pi * (mu * mu + 1.0 / (s * s))).abs() < 1e-10);
5085    }
5086
5087    #[test]
5088    fn quartic_recurrence_reduces_higher_moments() {
5089        let cell = DenestedCubicCell {
5090            left: -1.0,
5091            right: 0.9,
5092            c0: 0.2,
5093            c1: -0.3,
5094            c2: 0.18,
5095            c3: 0.0,
5096        };
5097        let exact = |k: usize| {
5098            simpson_integral(cell.left, cell.right, 2000, |z| {
5099                z.powi(k as i32) * (-cell.q(z)).exp()
5100            })
5101        };
5102        let reduced = reduce_quartic_moments(cell, [exact(0), exact(1), exact(2)], 6)
5103            .expect("quartic reduction");
5104        for k in 0..=6 {
5105            let target = exact(k);
5106            assert!(
5107                (reduced[k] - target).abs() < 1e-7,
5108                "quartic reduced moment M{k} mismatch: {} vs {}",
5109                reduced[k],
5110                target
5111            );
5112        }
5113    }
5114
5115    #[test]
5116    fn sextic_recurrence_reduces_higher_moments() {
5117        let cell = DenestedCubicCell {
5118            left: -0.8,
5119            right: 0.7,
5120            c0: -0.1,
5121            c1: 0.25,
5122            c2: -0.14,
5123            c3: 0.22,
5124        };
5125        let exact = |k: usize| {
5126            simpson_integral(cell.left, cell.right, 3000, |z| {
5127                z.powi(k as i32) * (-cell.q(z)).exp()
5128            })
5129        };
5130        let reduced =
5131            reduce_sextic_moments(cell, [exact(0), exact(1), exact(2), exact(3), exact(4)], 9)
5132                .expect("sextic reduction");
5133        for k in 0..=9 {
5134            let target = exact(k);
5135            assert!(
5136                (reduced[k] - target).abs() < 1e-7,
5137                "sextic reduced moment M{k} mismatch: {} vs {}",
5138                reduced[k],
5139                target
5140            );
5141        }
5142    }
5143
5144    #[test]
5145    fn ill_conditioned_sextic_recurrence_preserves_the_exact_cubic() {
5146        let cell = DenestedCubicCell {
5147            left: -1.0,
5148            right: 1.0,
5149            c0: 0.0,
5150            c1: 0.0,
5151            c2: 0.1,
5152            c3: 2.0e-10,
5153        };
5154        assert_eq!(branch_cell(cell).unwrap(), ExactCellBranch::Sextic);
5155
5156        let state = evaluate_cell_moments(cell, 9).expect("degenerate sextic cell");
5157        let reduced = reduce_sextic_moments(cell, [0.0; 5], 9)
5158            .expect("ill-conditioned recurrence must use exact transport");
5159        assert_eq!(reduced.as_slice(), state.moments.as_slice());
5160        let affine = evaluate_affine_cell_state(
5161            DenestedCubicCell {
5162                c2: 0.0,
5163                c3: 0.0,
5164                ..cell
5165            },
5166            9,
5167        )
5168        .expect("affine cell");
5169
5170        assert_eq!(state.branch, ExactCellBranch::Sextic);
5171        assert!(
5172            (state.moments[0] - affine.moments[0]).abs() > 1e-4,
5173            "degenerate sextic handling must not drop the nonzero c2 term"
5174        );
5175    }
5176
5177    #[test]
5178    fn moment_reduced_first_and_second_derivatives_match_numeric_integrals() {
5179        let cell = DenestedCubicCell {
5180            left: -0.9,
5181            right: 0.6,
5182            c0: 0.15,
5183            c1: -0.2,
5184            c2: 0.08,
5185            c3: 0.17,
5186        };
5187        let moments = reduce_sextic_moments(
5188            cell,
5189            [
5190                simpson_integral(cell.left, cell.right, 3000, |z| (-cell.q(z)).exp()),
5191                simpson_integral(cell.left, cell.right, 3000, |z| z * (-cell.q(z)).exp()),
5192                simpson_integral(cell.left, cell.right, 3000, |z| z * z * (-cell.q(z)).exp()),
5193                simpson_integral(cell.left, cell.right, 3000, |z| {
5194                    z.powi(3) * (-cell.q(z)).exp()
5195                }),
5196                simpson_integral(cell.left, cell.right, 3000, |z| {
5197                    z.powi(4) * (-cell.q(z)).exp()
5198                }),
5199            ],
5200            9,
5201        )
5202        .expect("reduced moments");
5203
5204        let r = [0.7, -0.1, 0.3];
5205        let s = [0.2, 0.5];
5206        let second = [0.4, -0.2, 0.1];
5207        let exact_first = cell_first_derivative_from_moments(&r, &moments).expect("first");
5208        let exact_second =
5209            cell_second_derivative_from_moments(cell, &r, &s, &second, &moments).expect("second");
5210
5211        let numeric_first = simpson_integral(cell.left, cell.right, 3000, |z| {
5212            polynomial_value(&r, z) * (-cell.q(z)).exp() / (2.0 * std::f64::consts::PI)
5213        });
5214        let numeric_second = simpson_integral(cell.left, cell.right, 3000, |z| {
5215            let eta = cell.eta(z);
5216            (polynomial_value(&second, z) - eta * polynomial_value(&r, z) * polynomial_value(&s, z))
5217                * (-cell.q(z)).exp()
5218                / (2.0 * std::f64::consts::PI)
5219        });
5220
5221        assert!((exact_first - numeric_first).abs() < 1e-7);
5222        assert!((exact_second - numeric_second).abs() < 1e-7);
5223    }
5224
5225    #[test]
5226    fn moment_reduced_third_derivative_matches_numeric_integral() {
5227        let cell = DenestedCubicCell {
5228            left: -0.85,
5229            right: 0.7,
5230            c0: -0.12,
5231            c1: 0.18,
5232            c2: 0.09,
5233            c3: -0.11,
5234        };
5235        let moments = evaluate_cell_moments(cell, 12).expect("cell moments");
5236        let r = [0.35, -0.12, 0.08];
5237        let s = [0.17, 0.09];
5238        let t = [-0.21, 0.14, -0.04];
5239        let rs = [0.11, -0.07, 0.05];
5240        let rt = [-0.06, 0.03];
5241        let st = [0.08, -0.02, 0.01];
5242        let rst = [0.04, -0.05, 0.02];
5243
5244        let exact_third = cell_third_derivative_from_moments(
5245            cell,
5246            &r,
5247            &s,
5248            &t,
5249            &rs,
5250            &rt,
5251            &st,
5252            &rst,
5253            &moments.moments,
5254        )
5255        .expect("third derivative");
5256        let numeric_third = simpson_integral(cell.left, cell.right, 4000, |z| {
5257            let eta = cell.eta(z);
5258            let rz = polynomial_value(&r, z);
5259            let sz = polynomial_value(&s, z);
5260            let tz = polynomial_value(&t, z);
5261            let rsz = polynomial_value(&rs, z);
5262            let rtz = polynomial_value(&rt, z);
5263            let stz = polynomial_value(&st, z);
5264            let rstz = polynomial_value(&rst, z);
5265            (rstz - eta * (rsz * tz + rtz * sz + stz * rz) + (eta * eta - 1.0) * rz * sz * tz)
5266                * (-cell.q(z)).exp()
5267                / (2.0 * std::f64::consts::PI)
5268        });
5269
5270        assert!((exact_third - numeric_third).abs() < 1e-7);
5271    }
5272
5273    #[test]
5274    fn moment_reduced_fourth_derivative_matches_numeric_integral() {
5275        let cell = DenestedCubicCell {
5276            left: -0.8,
5277            right: 0.65,
5278            c0: 0.11,
5279            c1: -0.22,
5280            c2: 0.07,
5281            c3: 0.13,
5282        };
5283        let moments = evaluate_cell_moments(cell, 16).expect("cell moments");
5284        let r = [0.21, -0.13, 0.06];
5285        let s = [-0.18, 0.04];
5286        let t = [0.09, 0.07, -0.03];
5287        let u = [-0.14, 0.05];
5288        let rs = [0.08, -0.03, 0.02];
5289        let rt = [-0.05, 0.01];
5290        let ru = [0.04, -0.02, 0.01];
5291        let st = [0.03, 0.02];
5292        let su = [-0.02, 0.05, -0.01];
5293        let tu = [0.07, -0.04];
5294        let rst = [0.03, -0.01, 0.02];
5295        let rsu = [-0.02, 0.04];
5296        let rtu = [0.01, 0.02, -0.01];
5297        let stu = [-0.03, 0.02];
5298        let rstu = [0.02, -0.01, 0.01];
5299
5300        let exact_fourth = cell_fourth_derivative_from_moments(
5301            cell,
5302            &r,
5303            &s,
5304            &t,
5305            &u,
5306            &rs,
5307            &rt,
5308            &ru,
5309            &st,
5310            &su,
5311            &tu,
5312            &rst,
5313            &rsu,
5314            &rtu,
5315            &stu,
5316            &rstu,
5317            &moments.moments,
5318        )
5319        .expect("fourth derivative");
5320        let numeric_fourth = simpson_integral(cell.left, cell.right, 5000, |z| {
5321            let eta = cell.eta(z);
5322            let rz = polynomial_value(&r, z);
5323            let sz = polynomial_value(&s, z);
5324            let tz = polynomial_value(&t, z);
5325            let uz = polynomial_value(&u, z);
5326            let rsz = polynomial_value(&rs, z);
5327            let rtz = polynomial_value(&rt, z);
5328            let ruz = polynomial_value(&ru, z);
5329            let stz = polynomial_value(&st, z);
5330            let suz = polynomial_value(&su, z);
5331            let tuz = polynomial_value(&tu, z);
5332            let rstz = polynomial_value(&rst, z);
5333            let rsuz = polynomial_value(&rsu, z);
5334            let rtuz = polynomial_value(&rtu, z);
5335            let stuz = polynomial_value(&stu, z);
5336            let rstuz = polynomial_value(&rstu, z);
5337            let linear =
5338                rstz * uz + rsuz * tz + rtuz * sz + stuz * rz + rsz * tuz + rtz * suz + ruz * stz;
5339            let quadratic = rsz * tz * uz
5340                + rtz * sz * uz
5341                + ruz * sz * tz
5342                + stz * rz * uz
5343                + suz * rz * tz
5344                + tuz * rz * sz;
5345            let quartic = rz * sz * tz * uz;
5346            (rstuz - eta * linear
5347                + (eta * eta - 1.0) * quadratic
5348                + (-eta * eta * eta + 3.0 * eta) * quartic)
5349                * (-cell.q(z)).exp()
5350                / (2.0 * std::f64::consts::PI)
5351        });
5352
5353        assert!((exact_fourth - numeric_fourth).abs() < 2e-7);
5354    }
5355
5356    #[test]
5357    fn denested_cell_parameter_derivatives_match_exact_integrands() {
5358        let score_span = LocalSpanCubic {
5359            left: -0.75,
5360            right: 0.25,
5361            c0: 0.08,
5362            c1: -0.03,
5363            c2: 0.02,
5364            c3: -0.01,
5365        };
5366        let link_span = LocalSpanCubic {
5367            left: -0.6,
5368            right: 0.9,
5369            c0: -0.05,
5370            c1: 0.04,
5371            c2: -0.02,
5372            c3: 0.015,
5373        };
5374        let a = 0.3;
5375        let b = -0.7;
5376        let coeffs = denested_cell_coefficients(score_span, link_span, a, b);
5377        let cell = DenestedCubicCell {
5378            left: score_span.left,
5379            right: score_span.right,
5380            c0: coeffs[0],
5381            c1: coeffs[1],
5382            c2: coeffs[2],
5383            c3: coeffs[3],
5384        };
5385        let state = evaluate_cell_moments(cell, 24).expect("cell moments");
5386        let (dc_da, dc_db) = denested_cell_coefficient_partials(score_span, link_span, a, b);
5387        let (dc_daa, dc_dab, dc_dbb) = denested_cell_second_partials(score_span, link_span, a, b);
5388        let (dc_daaa, dc_daab, dc_dabb, dc_dbbb) = denested_cell_third_partials(link_span);
5389        let zero = [0.0; 4];
5390        let link_third = 6.0 * link_span.c3;
5391
5392        let eta_a = |z: f64| 1.0 + link_span.first_derivative(a + b * z);
5393        let eta_b = |z: f64| z + score_span.evaluate(z) + z * link_span.first_derivative(a + b * z);
5394        let eta_aa = |z: f64| link_span.second_derivative(a + b * z);
5395        let eta_ab = |z: f64| z * link_span.second_derivative(a + b * z);
5396        let eta_bb = |z: f64| z * z * link_span.second_derivative(a + b * z);
5397        let eta_aaa = |z: f64| link_third + 0.0 * z;
5398        let eta_aab = |z: f64| z * link_third;
5399        let eta_abb = |z: f64| z * z * link_third;
5400        let eta_bbb = |z: f64| z * z * z * link_third;
5401
5402        let exact_a = cell_first_derivative_from_moments(&dc_da, &state.moments).expect("a");
5403        let exact_b = cell_first_derivative_from_moments(&dc_db, &state.moments).expect("b");
5404        let exact_aa =
5405            cell_second_derivative_from_moments(cell, &dc_da, &dc_da, &dc_daa, &state.moments)
5406                .expect("aa");
5407        let exact_ab =
5408            cell_second_derivative_from_moments(cell, &dc_da, &dc_db, &dc_dab, &state.moments)
5409                .expect("ab");
5410        let exact_bb =
5411            cell_second_derivative_from_moments(cell, &dc_db, &dc_db, &dc_dbb, &state.moments)
5412                .expect("bb");
5413        let exact_aaa = cell_third_derivative_from_moments(
5414            cell,
5415            &dc_da,
5416            &dc_da,
5417            &dc_da,
5418            &dc_daa,
5419            &dc_daa,
5420            &dc_daa,
5421            &dc_daaa,
5422            &state.moments,
5423        )
5424        .expect("aaa");
5425        let exact_aab = cell_third_derivative_from_moments(
5426            cell,
5427            &dc_da,
5428            &dc_da,
5429            &dc_db,
5430            &dc_daa,
5431            &dc_dab,
5432            &dc_dab,
5433            &dc_daab,
5434            &state.moments,
5435        )
5436        .expect("aab");
5437        let exact_abb = cell_third_derivative_from_moments(
5438            cell,
5439            &dc_da,
5440            &dc_db,
5441            &dc_db,
5442            &dc_dab,
5443            &dc_dab,
5444            &dc_dbb,
5445            &dc_dabb,
5446            &state.moments,
5447        )
5448        .expect("abb");
5449        let exact_bbb = cell_third_derivative_from_moments(
5450            cell,
5451            &dc_db,
5452            &dc_db,
5453            &dc_db,
5454            &dc_dbb,
5455            &dc_dbb,
5456            &dc_dbb,
5457            &dc_dbbb,
5458            &state.moments,
5459        )
5460        .expect("bbb");
5461        let exact_aaaa = cell_fourth_derivative_from_moments(
5462            cell,
5463            &dc_da,
5464            &dc_da,
5465            &dc_da,
5466            &dc_da,
5467            &dc_daa,
5468            &dc_daa,
5469            &dc_daa,
5470            &dc_daa,
5471            &dc_daa,
5472            &dc_daa,
5473            &dc_daaa,
5474            &dc_daaa,
5475            &dc_daaa,
5476            &dc_daaa,
5477            &zero,
5478            &state.moments,
5479        )
5480        .expect("aaaa");
5481        let exact_aaab = cell_fourth_derivative_from_moments(
5482            cell,
5483            &dc_da,
5484            &dc_da,
5485            &dc_da,
5486            &dc_db,
5487            &dc_daa,
5488            &dc_daa,
5489            &dc_dab,
5490            &dc_daa,
5491            &dc_dab,
5492            &dc_dab,
5493            &dc_daaa,
5494            &dc_daab,
5495            &dc_daab,
5496            &dc_daab,
5497            &zero,
5498            &state.moments,
5499        )
5500        .expect("aaab");
5501        let exact_aabb = cell_fourth_derivative_from_moments(
5502            cell,
5503            &dc_da,
5504            &dc_da,
5505            &dc_db,
5506            &dc_db,
5507            &dc_daa,
5508            &dc_dab,
5509            &dc_dab,
5510            &dc_dab,
5511            &dc_dab,
5512            &dc_dbb,
5513            &dc_daab,
5514            &dc_daab,
5515            &dc_dabb,
5516            &dc_dabb,
5517            &zero,
5518            &state.moments,
5519        )
5520        .expect("aabb");
5521        let exact_abbb = cell_fourth_derivative_from_moments(
5522            cell,
5523            &dc_da,
5524            &dc_db,
5525            &dc_db,
5526            &dc_db,
5527            &dc_dab,
5528            &dc_dab,
5529            &dc_dab,
5530            &dc_dbb,
5531            &dc_dbb,
5532            &dc_dbb,
5533            &dc_dabb,
5534            &dc_dabb,
5535            &dc_dabb,
5536            &dc_dbbb,
5537            &zero,
5538            &state.moments,
5539        )
5540        .expect("abbb");
5541        let exact_bbbb = cell_fourth_derivative_from_moments(
5542            cell,
5543            &dc_db,
5544            &dc_db,
5545            &dc_db,
5546            &dc_db,
5547            &dc_dbb,
5548            &dc_dbb,
5549            &dc_dbb,
5550            &dc_dbb,
5551            &dc_dbb,
5552            &dc_dbb,
5553            &dc_dbbb,
5554            &dc_dbbb,
5555            &dc_dbbb,
5556            &dc_dbbb,
5557            &zero,
5558            &state.moments,
5559        )
5560        .expect("bbbb");
5561
5562        let numeric_a = simpson_integral(cell.left, cell.right, 5000, |z| {
5563            eta_a(z) * (-cell.q(z)).exp() * INV_TWO_PI
5564        });
5565        let numeric_b = simpson_integral(cell.left, cell.right, 5000, |z| {
5566            eta_b(z) * (-cell.q(z)).exp() * INV_TWO_PI
5567        });
5568        let numeric_aa = simpson_integral(cell.left, cell.right, 5000, |z| {
5569            (eta_aa(z) - cell.eta(z) * eta_a(z) * eta_a(z)) * (-cell.q(z)).exp() * INV_TWO_PI
5570        });
5571        let numeric_ab = simpson_integral(cell.left, cell.right, 5000, |z| {
5572            (eta_ab(z) - cell.eta(z) * eta_a(z) * eta_b(z)) * (-cell.q(z)).exp() * INV_TWO_PI
5573        });
5574        let numeric_bb = simpson_integral(cell.left, cell.right, 5000, |z| {
5575            (eta_bb(z) - cell.eta(z) * eta_b(z) * eta_b(z)) * (-cell.q(z)).exp() * INV_TWO_PI
5576        });
5577        let numeric_aaa = simpson_integral(cell.left, cell.right, 5000, |z| {
5578            let eta = cell.eta(z);
5579            (eta_aaa(z) - 3.0 * eta * eta_aa(z) * eta_a(z) + (eta * eta - 1.0) * eta_a(z).powi(3))
5580                * (-cell.q(z)).exp()
5581                * INV_TWO_PI
5582        });
5583        let numeric_aab = simpson_integral(cell.left, cell.right, 5000, |z| {
5584            let eta = cell.eta(z);
5585            let a_z = eta_a(z);
5586            let b_z = eta_b(z);
5587            (eta_aab(z) - eta * (eta_aa(z) * b_z + 2.0 * eta_ab(z) * a_z)
5588                + (eta * eta - 1.0) * a_z * a_z * b_z)
5589                * (-cell.q(z)).exp()
5590                * INV_TWO_PI
5591        });
5592        let numeric_abb = simpson_integral(cell.left, cell.right, 5000, |z| {
5593            let eta = cell.eta(z);
5594            let a_z = eta_a(z);
5595            let b_z = eta_b(z);
5596            (eta_abb(z) - eta * (2.0 * eta_ab(z) * b_z + eta_bb(z) * a_z)
5597                + (eta * eta - 1.0) * a_z * b_z * b_z)
5598                * (-cell.q(z)).exp()
5599                * INV_TWO_PI
5600        });
5601        let numeric_bbb = simpson_integral(cell.left, cell.right, 5000, |z| {
5602            let eta = cell.eta(z);
5603            (eta_bbb(z) - 3.0 * eta * eta_bb(z) * eta_b(z) + (eta * eta - 1.0) * eta_b(z).powi(3))
5604                * (-cell.q(z)).exp()
5605                * INV_TWO_PI
5606        });
5607        let numeric_aaaa = simpson_integral(cell.left, cell.right, 5000, |z| {
5608            let eta = cell.eta(z);
5609            let eta_a_z = eta_a(z);
5610            let eta_aa_z = eta_aa(z);
5611            let eta_aaa_z = eta_aaa(z);
5612            (-eta * (4.0 * eta_aaa_z * eta_a_z + 3.0 * eta_aa_z * eta_aa_z)
5613                + (eta * eta - 1.0) * (6.0 * eta_aa_z * eta_a_z * eta_a_z)
5614                + (-eta * eta * eta + 3.0 * eta) * eta_a_z.powi(4))
5615                * (-cell.q(z)).exp()
5616                * INV_TWO_PI
5617        });
5618        let numeric_aaab = simpson_integral(cell.left, cell.right, 5000, |z| {
5619            let eta = cell.eta(z);
5620            let a_z = eta_a(z);
5621            let b_z = eta_b(z);
5622            let aa_z = eta_aa(z);
5623            let ab_z = eta_ab(z);
5624            let aaa_z = eta_aaa(z);
5625            let aab_z = eta_aab(z);
5626            (-eta * (aaa_z * b_z + 3.0 * aab_z * a_z + 3.0 * aa_z * ab_z)
5627                + (eta * eta - 1.0) * (3.0 * aa_z * a_z * b_z + 3.0 * ab_z * a_z * a_z)
5628                + (-eta * eta * eta + 3.0 * eta) * a_z.powi(3) * b_z)
5629                * (-cell.q(z)).exp()
5630                * INV_TWO_PI
5631        });
5632        let numeric_aabb = simpson_integral(cell.left, cell.right, 5000, |z| {
5633            let eta = cell.eta(z);
5634            let a_z = eta_a(z);
5635            let b_z = eta_b(z);
5636            let aa_z = eta_aa(z);
5637            let ab_z = eta_ab(z);
5638            let bb_z = eta_bb(z);
5639            let aab_z = eta_aab(z);
5640            let abb_z = eta_abb(z);
5641            (-eta * (2.0 * aab_z * b_z + 2.0 * abb_z * a_z + aa_z * bb_z + 2.0 * ab_z * ab_z)
5642                + (eta * eta - 1.0)
5643                    * (aa_z * b_z * b_z + 4.0 * ab_z * a_z * b_z + bb_z * a_z * a_z)
5644                + (-eta * eta * eta + 3.0 * eta) * a_z * a_z * b_z * b_z)
5645                * (-cell.q(z)).exp()
5646                * INV_TWO_PI
5647        });
5648        let numeric_abbb = simpson_integral(cell.left, cell.right, 5000, |z| {
5649            let eta = cell.eta(z);
5650            let a_z = eta_a(z);
5651            let b_z = eta_b(z);
5652            let ab_z = eta_ab(z);
5653            let bb_z = eta_bb(z);
5654            let abb_z = eta_abb(z);
5655            let bbb_z = eta_bbb(z);
5656            (-eta * (3.0 * abb_z * b_z + bbb_z * a_z + 3.0 * ab_z * bb_z)
5657                + (eta * eta - 1.0) * (3.0 * ab_z * b_z * b_z + 3.0 * bb_z * a_z * b_z)
5658                + (-eta * eta * eta + 3.0 * eta) * a_z * b_z.powi(3))
5659                * (-cell.q(z)).exp()
5660                * INV_TWO_PI
5661        });
5662        let numeric_bbbb = simpson_integral(cell.left, cell.right, 5000, |z| {
5663            let eta = cell.eta(z);
5664            let eta_b_z = eta_b(z);
5665            let eta_bb_z = eta_bb(z);
5666            let eta_bbb_z = eta_bbb(z);
5667            (-eta * (4.0 * eta_bbb_z * eta_b_z + 3.0 * eta_bb_z * eta_bb_z)
5668                + (eta * eta - 1.0) * (6.0 * eta_bb_z * eta_b_z * eta_b_z)
5669                + (-eta * eta * eta + 3.0 * eta) * eta_b_z.powi(4))
5670                * (-cell.q(z)).exp()
5671                * INV_TWO_PI
5672        });
5673
5674        assert!((exact_a - numeric_a).abs() < 1e-8);
5675        assert!((exact_b - numeric_b).abs() < 1e-8);
5676        assert!((exact_aa - numeric_aa).abs() < 1e-8);
5677        assert!((exact_ab - numeric_ab).abs() < 1e-8);
5678        assert!((exact_bb - numeric_bb).abs() < 1e-8);
5679        assert!((exact_aaa - numeric_aaa).abs() < 2e-7);
5680        assert!((exact_aab - numeric_aab).abs() < 2e-7);
5681        assert!((exact_abb - numeric_abb).abs() < 2e-7);
5682        assert!((exact_bbb - numeric_bbb).abs() < 2e-7);
5683        assert!((exact_aaaa - numeric_aaaa).abs() < 2e-6);
5684        assert!((exact_aaab - numeric_aaab).abs() < 2e-6);
5685        assert!((exact_aabb - numeric_aabb).abs() < 2e-6);
5686        assert!((exact_abbb - numeric_abbb).abs() < 2e-6);
5687        assert!((exact_bbbb - numeric_bbbb).abs() < 2e-6);
5688    }
5689
5690    #[test]
5691    fn link_basis_cell_derivatives_match_exact_integrands() {
5692        let score_span = LocalSpanCubic {
5693            left: -0.75,
5694            right: 0.25,
5695            c0: 0.08,
5696            c1: -0.03,
5697            c2: 0.02,
5698            c3: -0.01,
5699        };
5700        let link_span = LocalSpanCubic {
5701            left: -0.6,
5702            right: 0.9,
5703            c0: -0.05,
5704            c1: 0.04,
5705            c2: -0.02,
5706            c3: 0.015,
5707        };
5708        let link_basis_span = LocalSpanCubic {
5709            left: -0.6,
5710            right: 0.9,
5711            c0: 0.02,
5712            c1: -0.01,
5713            c2: 0.03,
5714            c3: -0.02,
5715        };
5716        let a = 0.3;
5717        let b = -0.7;
5718        let coeffs = denested_cell_coefficients(score_span, link_span, a, b);
5719        let cell = DenestedCubicCell {
5720            left: score_span.left,
5721            right: score_span.right,
5722            c0: coeffs[0],
5723            c1: coeffs[1],
5724            c2: coeffs[2],
5725            c3: coeffs[3],
5726        };
5727        let state = evaluate_cell_moments(cell, 24).expect("cell moments");
5728        let (dc_da, dc_db) = denested_cell_coefficient_partials(score_span, link_span, a, b);
5729        let second_partials = denested_cell_second_partials(score_span, link_span, a, b);
5730        let dc_daa = second_partials.0;
5731        let dc_dab = second_partials.1;
5732        let dc_dbb = second_partials.2;
5733        let denested_third = denested_cell_third_partials(link_span);
5734        let dc_daaa = denested_third.0;
5735        let dc_dbbb = denested_third.3;
5736
5737        let coeff_w = link_basis_cell_coefficients(link_basis_span, a, b);
5738        let (coeff_aw, coeff_bw) = link_basis_cell_coefficient_partials(link_basis_span, a, b);
5739        let (coeff_aaw, coeff_abw, coeff_bbw) =
5740            link_basis_cell_second_partials(link_basis_span, a, b);
5741        let link_basis_third = link_basis_cell_third_partials(link_basis_span);
5742        let coeff_aaaw = link_basis_third.0;
5743        let coeff_bbbw = link_basis_third.3;
5744        let zero = [0.0; 4];
5745        let basis_third = 6.0 * link_basis_span.c3;
5746
5747        let eta_a = |z: f64| 1.0 + link_span.first_derivative(a + b * z);
5748        let eta_b = |z: f64| z + score_span.evaluate(z) + z * link_span.first_derivative(a + b * z);
5749        let eta_aa = |z: f64| link_span.second_derivative(a + b * z);
5750        let eta_ab = |z: f64| z * link_span.second_derivative(a + b * z);
5751        let eta_bb = |z: f64| z * z * link_span.second_derivative(a + b * z);
5752        let eta_w = |z: f64| link_basis_span.evaluate(a + b * z);
5753        let eta_aw = |z: f64| link_basis_span.first_derivative(a + b * z);
5754        let eta_bw = |z: f64| z * link_basis_span.first_derivative(a + b * z);
5755        let eta_aaw = |z: f64| link_basis_span.second_derivative(a + b * z);
5756        let eta_abw = |z: f64| z * link_basis_span.second_derivative(a + b * z);
5757        let eta_bbw = |z: f64| z * z * link_basis_span.second_derivative(a + b * z);
5758        let eta_aaaw = |z: f64| basis_third + 0.0 * z;
5759        let eta_bbbw = |z: f64| z * z * z * basis_third;
5760
5761        let exact_w = cell_first_derivative_from_moments(&coeff_w, &state.moments).expect("w");
5762        let exact_aw =
5763            cell_second_derivative_from_moments(cell, &dc_da, &coeff_w, &coeff_aw, &state.moments)
5764                .expect("aw");
5765        let exact_bw =
5766            cell_second_derivative_from_moments(cell, &dc_db, &coeff_w, &coeff_bw, &state.moments)
5767                .expect("bw");
5768        let exact_ww =
5769            cell_second_derivative_from_moments(cell, &coeff_w, &coeff_w, &zero, &state.moments)
5770                .expect("ww");
5771        let exact_aaw = cell_third_derivative_from_moments(
5772            cell,
5773            &dc_da,
5774            &dc_da,
5775            &coeff_w,
5776            &dc_daa,
5777            &coeff_aw,
5778            &coeff_aw,
5779            &coeff_aaw,
5780            &state.moments,
5781        )
5782        .expect("aaw");
5783        let exact_abw = cell_third_derivative_from_moments(
5784            cell,
5785            &dc_da,
5786            &dc_db,
5787            &coeff_w,
5788            &dc_dab,
5789            &coeff_aw,
5790            &coeff_bw,
5791            &coeff_abw,
5792            &state.moments,
5793        )
5794        .expect("abw");
5795        let exact_bbw = cell_third_derivative_from_moments(
5796            cell,
5797            &dc_db,
5798            &dc_db,
5799            &coeff_w,
5800            &dc_dbb,
5801            &coeff_bw,
5802            &coeff_bw,
5803            &coeff_bbw,
5804            &state.moments,
5805        )
5806        .expect("bbw");
5807        let exact_www = cell_third_derivative_from_moments(
5808            cell,
5809            &coeff_w,
5810            &coeff_w,
5811            &coeff_w,
5812            &zero,
5813            &zero,
5814            &zero,
5815            &zero,
5816            &state.moments,
5817        )
5818        .expect("www");
5819        let exact_aaaw = cell_fourth_derivative_from_moments(
5820            cell,
5821            &dc_da,
5822            &dc_da,
5823            &dc_da,
5824            &coeff_w,
5825            &dc_daa,
5826            &dc_daa,
5827            &coeff_aw,
5828            &dc_daa,
5829            &coeff_aw,
5830            &coeff_aw,
5831            &dc_daaa,
5832            &coeff_aaw,
5833            &coeff_aaw,
5834            &coeff_aaw,
5835            &coeff_aaaw,
5836            &state.moments,
5837        )
5838        .expect("aaaw");
5839        let exact_aaww = cell_fourth_derivative_from_moments(
5840            cell,
5841            &dc_da,
5842            &dc_da,
5843            &coeff_w,
5844            &coeff_w,
5845            &dc_daa,
5846            &coeff_aw,
5847            &coeff_aw,
5848            &coeff_aw,
5849            &coeff_aw,
5850            &zero,
5851            &coeff_aaw,
5852            &coeff_aaw,
5853            &zero,
5854            &zero,
5855            &zero,
5856            &state.moments,
5857        )
5858        .expect("aaww");
5859        let exact_abww = cell_fourth_derivative_from_moments(
5860            cell,
5861            &dc_da,
5862            &dc_db,
5863            &coeff_w,
5864            &coeff_w,
5865            &dc_dab,
5866            &coeff_aw,
5867            &coeff_aw,
5868            &coeff_bw,
5869            &coeff_bw,
5870            &zero,
5871            &coeff_abw,
5872            &coeff_abw,
5873            &zero,
5874            &zero,
5875            &zero,
5876            &state.moments,
5877        )
5878        .expect("abww");
5879        let exact_bbww = cell_fourth_derivative_from_moments(
5880            cell,
5881            &dc_db,
5882            &dc_db,
5883            &coeff_w,
5884            &coeff_w,
5885            &dc_dbb,
5886            &coeff_bw,
5887            &coeff_bw,
5888            &coeff_bw,
5889            &coeff_bw,
5890            &zero,
5891            &coeff_bbw,
5892            &coeff_bbw,
5893            &zero,
5894            &zero,
5895            &zero,
5896            &state.moments,
5897        )
5898        .expect("bbww");
5899        let exact_bbbw = cell_fourth_derivative_from_moments(
5900            cell,
5901            &dc_db,
5902            &dc_db,
5903            &dc_db,
5904            &coeff_w,
5905            &dc_dbb,
5906            &dc_dbb,
5907            &coeff_bw,
5908            &dc_dbb,
5909            &coeff_bw,
5910            &coeff_bw,
5911            &dc_dbbb,
5912            &coeff_bbw,
5913            &coeff_bbw,
5914            &coeff_bbw,
5915            &coeff_bbbw,
5916            &state.moments,
5917        )
5918        .expect("bbbw");
5919        let exact_wwww = cell_fourth_derivative_from_moments(
5920            cell,
5921            &coeff_w,
5922            &coeff_w,
5923            &coeff_w,
5924            &coeff_w,
5925            &zero,
5926            &zero,
5927            &zero,
5928            &zero,
5929            &zero,
5930            &zero,
5931            &zero,
5932            &zero,
5933            &zero,
5934            &zero,
5935            &zero,
5936            &state.moments,
5937        )
5938        .expect("wwww");
5939
5940        let numeric_w = simpson_integral(cell.left, cell.right, 5000, |z| {
5941            eta_w(z) * (-cell.q(z)).exp() * INV_TWO_PI
5942        });
5943        let numeric_aw = simpson_integral(cell.left, cell.right, 5000, |z| {
5944            (eta_aw(z) - cell.eta(z) * eta_a(z) * eta_w(z)) * (-cell.q(z)).exp() * INV_TWO_PI
5945        });
5946        let numeric_bw = simpson_integral(cell.left, cell.right, 5000, |z| {
5947            (eta_bw(z) - cell.eta(z) * eta_b(z) * eta_w(z)) * (-cell.q(z)).exp() * INV_TWO_PI
5948        });
5949        let numeric_ww = simpson_integral(cell.left, cell.right, 5000, |z| {
5950            (-cell.eta(z) * eta_w(z) * eta_w(z)) * (-cell.q(z)).exp() * INV_TWO_PI
5951        });
5952        let numeric_aaw = simpson_integral(cell.left, cell.right, 5000, |z| {
5953            let eta = cell.eta(z);
5954            let w_z = eta_w(z);
5955            let a_z = eta_a(z);
5956            (eta_aaw(z) - eta * (eta_aa(z) * w_z + 2.0 * eta_aw(z) * a_z)
5957                + (eta * eta - 1.0) * a_z * a_z * w_z)
5958                * (-cell.q(z)).exp()
5959                * INV_TWO_PI
5960        });
5961        let numeric_abw = simpson_integral(cell.left, cell.right, 5000, |z| {
5962            let eta = cell.eta(z);
5963            let w_z = eta_w(z);
5964            let a_z = eta_a(z);
5965            let b_z = eta_b(z);
5966            (eta_abw(z) - eta * (eta_ab(z) * w_z + eta_aw(z) * b_z + eta_bw(z) * a_z)
5967                + (eta * eta - 1.0) * a_z * b_z * w_z)
5968                * (-cell.q(z)).exp()
5969                * INV_TWO_PI
5970        });
5971        let numeric_bbw = simpson_integral(cell.left, cell.right, 5000, |z| {
5972            let eta = cell.eta(z);
5973            let w_z = eta_w(z);
5974            let b_z = eta_b(z);
5975            (eta_bbw(z) - eta * (eta_bb(z) * w_z + 2.0 * eta_bw(z) * b_z)
5976                + (eta * eta - 1.0) * b_z * b_z * w_z)
5977                * (-cell.q(z)).exp()
5978                * INV_TWO_PI
5979        });
5980        let numeric_www = simpson_integral(cell.left, cell.right, 5000, |z| {
5981            let eta = cell.eta(z);
5982            let w_z = eta_w(z);
5983            ((eta * eta - 1.0) * w_z * w_z * w_z) * (-cell.q(z)).exp() * INV_TWO_PI
5984        });
5985        let numeric_aaaw = simpson_integral(cell.left, cell.right, 5000, |z| {
5986            let eta = cell.eta(z);
5987            let a_z = eta_a(z);
5988            let w_z = eta_w(z);
5989            let aa_z = eta_aa(z);
5990            let aw_z = eta_aw(z);
5991            (eta_aaaw(z)
5992                - eta * ((dc_daaa[0] + 0.0 * z) * w_z + 3.0 * eta_aaw(z) * a_z + 3.0 * aa_z * aw_z)
5993                + (eta * eta - 1.0) * (3.0 * aa_z * a_z * w_z + 3.0 * aw_z * a_z * a_z)
5994                + (-eta * eta * eta + 3.0 * eta) * a_z * a_z * a_z * w_z)
5995                * (-cell.q(z)).exp()
5996                * INV_TWO_PI
5997        });
5998        let numeric_aaww = simpson_integral(cell.left, cell.right, 5000, |z| {
5999            let eta = cell.eta(z);
6000            let a_z = eta_a(z);
6001            let w_z = eta_w(z);
6002            let aw_z = eta_aw(z);
6003            (-(2.0 * eta * (eta_aaw(z) * w_z + aw_z * aw_z))
6004                + (eta * eta - 1.0) * (eta_aa(z) * w_z * w_z + 4.0 * aw_z * a_z * w_z)
6005                + (-eta * eta * eta + 3.0 * eta) * a_z * a_z * w_z * w_z)
6006                * (-cell.q(z)).exp()
6007                * INV_TWO_PI
6008        });
6009        let numeric_abww = simpson_integral(cell.left, cell.right, 5000, |z| {
6010            let eta = cell.eta(z);
6011            let a_z = eta_a(z);
6012            let b_z = eta_b(z);
6013            let w_z = eta_w(z);
6014            let aw_z = eta_aw(z);
6015            let bw_z = eta_bw(z);
6016            (-(2.0 * eta * (eta_abw(z) * w_z + aw_z * bw_z))
6017                + (eta * eta - 1.0)
6018                    * (eta_ab(z) * w_z * w_z + 2.0 * aw_z * b_z * w_z + 2.0 * bw_z * a_z * w_z)
6019                + (-eta * eta * eta + 3.0 * eta) * a_z * b_z * w_z * w_z)
6020                * (-cell.q(z)).exp()
6021                * INV_TWO_PI
6022        });
6023        let numeric_bbww = simpson_integral(cell.left, cell.right, 5000, |z| {
6024            let eta = cell.eta(z);
6025            let b_z = eta_b(z);
6026            let w_z = eta_w(z);
6027            let bw_z = eta_bw(z);
6028            (-(2.0 * eta * (eta_bbw(z) * w_z + bw_z * bw_z))
6029                + (eta * eta - 1.0) * (eta_bb(z) * w_z * w_z + 4.0 * bw_z * b_z * w_z)
6030                + (-eta * eta * eta + 3.0 * eta) * b_z * b_z * w_z * w_z)
6031                * (-cell.q(z)).exp()
6032                * INV_TWO_PI
6033        });
6034        let numeric_bbbw = simpson_integral(cell.left, cell.right, 5000, |z| {
6035            let eta = cell.eta(z);
6036            let b_z = eta_b(z);
6037            let w_z = eta_w(z);
6038            let bb_z = eta_bb(z);
6039            let bw_z = eta_bw(z);
6040            (eta_bbbw(z)
6041                - eta
6042                    * ((dc_dbbb[3] * z * z * z) * w_z + 3.0 * eta_bbw(z) * b_z + 3.0 * bb_z * bw_z)
6043                + (eta * eta - 1.0) * (3.0 * bb_z * b_z * w_z + 3.0 * bw_z * b_z * b_z)
6044                + (-eta * eta * eta + 3.0 * eta) * b_z * b_z * b_z * w_z)
6045                * (-cell.q(z)).exp()
6046                * INV_TWO_PI
6047        });
6048        let numeric_wwww = simpson_integral(cell.left, cell.right, 5000, |z| {
6049            let eta = cell.eta(z);
6050            let w_z = eta_w(z);
6051            ((-eta * eta * eta + 3.0 * eta) * w_z * w_z * w_z * w_z)
6052                * (-cell.q(z)).exp()
6053                * INV_TWO_PI
6054        });
6055
6056        assert!((exact_w - numeric_w).abs() < 1e-8);
6057        assert!((exact_aw - numeric_aw).abs() < 1e-7);
6058        assert!((exact_bw - numeric_bw).abs() < 1e-7);
6059        assert!((exact_ww - numeric_ww).abs() < 1e-7);
6060        assert!((exact_aaw - numeric_aaw).abs() < 2e-6);
6061        assert!((exact_abw - numeric_abw).abs() < 2e-6);
6062        assert!((exact_bbw - numeric_bbw).abs() < 2e-6);
6063        assert!((exact_www - numeric_www).abs() < 2e-6);
6064        assert!((exact_aaaw - numeric_aaaw).abs() < 3e-6);
6065        assert!((exact_aaww - numeric_aaww).abs() < 3e-6);
6066        assert!((exact_abww - numeric_abww).abs() < 3e-6);
6067        assert!((exact_bbww - numeric_bbww).abs() < 3e-6);
6068        assert!((exact_bbbw - numeric_bbbw).abs() < 3e-6);
6069        assert!((exact_wwww - numeric_wwww).abs() < 3e-6);
6070    }
6071
6072    #[test]
6073    fn score_basis_cell_derivatives_match_exact_integrands() {
6074        let score_span = LocalSpanCubic {
6075            left: -0.75,
6076            right: 0.25,
6077            c0: 0.08,
6078            c1: -0.03,
6079            c2: 0.02,
6080            c3: -0.01,
6081        };
6082        let score_basis_span = LocalSpanCubic {
6083            left: -0.75,
6084            right: 0.25,
6085            c0: -0.04,
6086            c1: 0.06,
6087            c2: -0.01,
6088            c3: 0.02,
6089        };
6090        let link_span = LocalSpanCubic {
6091            left: -0.6,
6092            right: 0.9,
6093            c0: -0.05,
6094            c1: 0.04,
6095            c2: -0.02,
6096            c3: 0.015,
6097        };
6098        let a = 0.3;
6099        let b = -0.7;
6100        let coeffs = denested_cell_coefficients(score_span, link_span, a, b);
6101        let cell = DenestedCubicCell {
6102            left: score_span.left,
6103            right: score_span.right,
6104            c0: coeffs[0],
6105            c1: coeffs[1],
6106            c2: coeffs[2],
6107            c3: coeffs[3],
6108        };
6109        let state = evaluate_cell_moments(cell, 24).expect("cell moments");
6110        let (dc_da, dc_db) = denested_cell_coefficient_partials(score_span, link_span, a, b);
6111        let second_partials = denested_cell_second_partials(score_span, link_span, a, b);
6112        let dc_daa = second_partials.0;
6113        let dc_dab = second_partials.1;
6114        let dc_dbb = second_partials.2;
6115        let denested_third = denested_cell_third_partials(link_span);
6116        let dc_dbbb = denested_third.3;
6117
6118        let coeff_h = score_basis_cell_coefficients(score_basis_span, b);
6119        let coeff_bh = score_basis_cell_coefficients(score_basis_span, 1.0);
6120        let zero = [0.0; 4];
6121
6122        let eta_a = |z: f64| 1.0 + link_span.first_derivative(a + b * z);
6123        let eta_b = |z: f64| z + score_span.evaluate(z) + z * link_span.first_derivative(a + b * z);
6124        let eta_ab = |z: f64| z * link_span.second_derivative(a + b * z);
6125        let eta_bb = |z: f64| z * z * link_span.second_derivative(a + b * z);
6126        let eta_h = |z: f64| b * score_basis_span.evaluate(z);
6127        let eta_bh = |z: f64| score_basis_span.evaluate(z);
6128
6129        let exact_h = cell_first_derivative_from_moments(&coeff_h, &state.moments).expect("h");
6130        let exact_ah =
6131            cell_second_derivative_from_moments(cell, &dc_da, &coeff_h, &zero, &state.moments)
6132                .expect("ah");
6133        let exact_bh =
6134            cell_second_derivative_from_moments(cell, &dc_db, &coeff_h, &coeff_bh, &state.moments)
6135                .expect("bh");
6136        let exact_hh =
6137            cell_second_derivative_from_moments(cell, &coeff_h, &coeff_h, &zero, &state.moments)
6138                .expect("hh");
6139        let exact_abh = cell_third_derivative_from_moments(
6140            cell,
6141            &dc_da,
6142            &dc_db,
6143            &coeff_h,
6144            &dc_dab,
6145            &zero,
6146            &coeff_bh,
6147            &zero,
6148            &state.moments,
6149        )
6150        .expect("abh");
6151        let exact_bbh = cell_third_derivative_from_moments(
6152            cell,
6153            &dc_db,
6154            &dc_db,
6155            &coeff_h,
6156            &dc_dbb,
6157            &coeff_bh,
6158            &coeff_bh,
6159            &zero,
6160            &state.moments,
6161        )
6162        .expect("bbh");
6163        let exact_bhh = cell_third_derivative_from_moments(
6164            cell,
6165            &dc_db,
6166            &coeff_h,
6167            &coeff_h,
6168            &coeff_bh,
6169            &coeff_bh,
6170            &zero,
6171            &zero,
6172            &state.moments,
6173        )
6174        .expect("bhh");
6175        let exact_hhh = cell_third_derivative_from_moments(
6176            cell,
6177            &coeff_h,
6178            &coeff_h,
6179            &coeff_h,
6180            &zero,
6181            &zero,
6182            &zero,
6183            &zero,
6184            &state.moments,
6185        )
6186        .expect("hhh");
6187        let exact_bbbh = cell_fourth_derivative_from_moments(
6188            cell,
6189            &dc_db,
6190            &dc_db,
6191            &dc_db,
6192            &coeff_h,
6193            &dc_dbb,
6194            &dc_dbb,
6195            &coeff_bh,
6196            &dc_dbb,
6197            &coeff_bh,
6198            &coeff_bh,
6199            &dc_dbbb,
6200            &zero,
6201            &zero,
6202            &zero,
6203            &zero,
6204            &state.moments,
6205        )
6206        .expect("bbbh");
6207        let exact_aahh = cell_fourth_derivative_from_moments(
6208            cell,
6209            &dc_da,
6210            &dc_da,
6211            &coeff_h,
6212            &coeff_h,
6213            &dc_daa,
6214            &zero,
6215            &zero,
6216            &zero,
6217            &zero,
6218            &zero,
6219            &zero,
6220            &zero,
6221            &zero,
6222            &zero,
6223            &zero,
6224            &state.moments,
6225        )
6226        .expect("aahh");
6227        let exact_abhh = cell_fourth_derivative_from_moments(
6228            cell,
6229            &dc_da,
6230            &dc_db,
6231            &coeff_h,
6232            &coeff_h,
6233            &dc_dab,
6234            &zero,
6235            &zero,
6236            &coeff_bh,
6237            &coeff_bh,
6238            &zero,
6239            &zero,
6240            &zero,
6241            &zero,
6242            &zero,
6243            &zero,
6244            &state.moments,
6245        )
6246        .expect("abhh");
6247        let exact_bbhh = cell_fourth_derivative_from_moments(
6248            cell,
6249            &dc_db,
6250            &dc_db,
6251            &coeff_h,
6252            &coeff_h,
6253            &dc_dbb,
6254            &coeff_bh,
6255            &coeff_bh,
6256            &coeff_bh,
6257            &coeff_bh,
6258            &zero,
6259            &zero,
6260            &zero,
6261            &zero,
6262            &zero,
6263            &zero,
6264            &state.moments,
6265        )
6266        .expect("bbhh");
6267        let exact_bhhh = cell_fourth_derivative_from_moments(
6268            cell,
6269            &dc_db,
6270            &coeff_h,
6271            &coeff_h,
6272            &coeff_h,
6273            &coeff_bh,
6274            &coeff_bh,
6275            &coeff_bh,
6276            &zero,
6277            &zero,
6278            &zero,
6279            &zero,
6280            &zero,
6281            &zero,
6282            &zero,
6283            &zero,
6284            &state.moments,
6285        )
6286        .expect("bhhh");
6287        let exact_hhhh = cell_fourth_derivative_from_moments(
6288            cell,
6289            &coeff_h,
6290            &coeff_h,
6291            &coeff_h,
6292            &coeff_h,
6293            &zero,
6294            &zero,
6295            &zero,
6296            &zero,
6297            &zero,
6298            &zero,
6299            &zero,
6300            &zero,
6301            &zero,
6302            &zero,
6303            &zero,
6304            &state.moments,
6305        )
6306        .expect("hhhh");
6307
6308        let numeric_h = simpson_integral(cell.left, cell.right, 5000, |z| {
6309            eta_h(z) * (-cell.q(z)).exp() * INV_TWO_PI
6310        });
6311        let numeric_ah = simpson_integral(cell.left, cell.right, 5000, |z| {
6312            (-cell.eta(z) * eta_a(z) * eta_h(z)) * (-cell.q(z)).exp() * INV_TWO_PI
6313        });
6314        let numeric_bh = simpson_integral(cell.left, cell.right, 5000, |z| {
6315            (eta_bh(z) - cell.eta(z) * eta_b(z) * eta_h(z)) * (-cell.q(z)).exp() * INV_TWO_PI
6316        });
6317        let numeric_hh = simpson_integral(cell.left, cell.right, 5000, |z| {
6318            (-cell.eta(z) * eta_h(z) * eta_h(z)) * (-cell.q(z)).exp() * INV_TWO_PI
6319        });
6320        let numeric_abh = simpson_integral(cell.left, cell.right, 5000, |z| {
6321            let eta = cell.eta(z);
6322            (-(eta * (eta_ab(z) * eta_h(z) + eta_bh(z) * eta_a(z)))
6323                + (eta * eta - 1.0) * eta_a(z) * eta_b(z) * eta_h(z))
6324                * (-cell.q(z)).exp()
6325                * INV_TWO_PI
6326        });
6327        let numeric_bbh = simpson_integral(cell.left, cell.right, 5000, |z| {
6328            let eta = cell.eta(z);
6329            (-(eta * (eta_bb(z) * eta_h(z) + 2.0 * eta_bh(z) * eta_b(z)))
6330                + (eta * eta - 1.0) * eta_b(z) * eta_b(z) * eta_h(z))
6331                * (-cell.q(z)).exp()
6332                * INV_TWO_PI
6333        });
6334        let numeric_bhh = simpson_integral(cell.left, cell.right, 5000, |z| {
6335            let eta = cell.eta(z);
6336            (-(2.0 * eta * eta_bh(z) * eta_h(z))
6337                + (eta * eta - 1.0) * eta_b(z) * eta_h(z) * eta_h(z))
6338                * (-cell.q(z)).exp()
6339                * INV_TWO_PI
6340        });
6341        let numeric_hhh = simpson_integral(cell.left, cell.right, 5000, |z| {
6342            let eta = cell.eta(z);
6343            ((eta * eta - 1.0) * eta_h(z) * eta_h(z) * eta_h(z)) * (-cell.q(z)).exp() * INV_TWO_PI
6344        });
6345        let numeric_bbbh = simpson_integral(cell.left, cell.right, 5000, |z| {
6346            let eta = cell.eta(z);
6347            let b_z = eta_b(z);
6348            let h_z = eta_h(z);
6349            let bb_z = eta_bb(z);
6350            let bh_z = eta_bh(z);
6351            (-(eta * ((dc_dbbb[3] * z * z * z) * h_z + 3.0 * bb_z * bh_z))
6352                + (eta * eta - 1.0) * (3.0 * bb_z * b_z * h_z + 3.0 * bh_z * b_z * b_z)
6353                + (-eta * eta * eta + 3.0 * eta) * b_z * b_z * b_z * h_z)
6354                * (-cell.q(z)).exp()
6355                * INV_TWO_PI
6356        });
6357        let numeric_aahh = simpson_integral(cell.left, cell.right, 5000, |z| {
6358            let eta = cell.eta(z);
6359            let a_z = eta_a(z);
6360            let h_z = eta_h(z);
6361            ((eta * eta - 1.0) * polynomial_value(&dc_daa, z) * h_z * h_z
6362                + (-eta * eta * eta + 3.0 * eta) * a_z * a_z * h_z * h_z)
6363                * (-cell.q(z)).exp()
6364                * INV_TWO_PI
6365        });
6366        let numeric_abhh = simpson_integral(cell.left, cell.right, 5000, |z| {
6367            let eta = cell.eta(z);
6368            let a_z = eta_a(z);
6369            let b_z = eta_b(z);
6370            let h_z = eta_h(z);
6371            ((eta * eta - 1.0) * (eta_ab(z) * h_z * h_z + 2.0 * eta_bh(z) * a_z * h_z)
6372                + (-eta * eta * eta + 3.0 * eta) * a_z * b_z * h_z * h_z)
6373                * (-cell.q(z)).exp()
6374                * INV_TWO_PI
6375        });
6376        let numeric_bbhh = simpson_integral(cell.left, cell.right, 5000, |z| {
6377            let eta = cell.eta(z);
6378            let b_z = eta_b(z);
6379            let h_z = eta_h(z);
6380            let bh_z = eta_bh(z);
6381            (-(2.0 * eta * bh_z * bh_z)
6382                + (eta * eta - 1.0) * (eta_bb(z) * h_z * h_z + 4.0 * bh_z * b_z * h_z)
6383                + (-eta * eta * eta + 3.0 * eta) * b_z * b_z * h_z * h_z)
6384                * (-cell.q(z)).exp()
6385                * INV_TWO_PI
6386        });
6387        let numeric_bhhh = simpson_integral(cell.left, cell.right, 5000, |z| {
6388            let eta = cell.eta(z);
6389            let h_z = eta_h(z);
6390            (-(eta * (3.0 * eta_bh(z) * h_z * h_z))
6391                + (eta * eta - 1.0) * (3.0 * eta_bh(z) * h_z * h_z)
6392                + (-eta * eta * eta + 3.0 * eta) * eta_b(z) * h_z * h_z * h_z)
6393                * (-cell.q(z)).exp()
6394                * INV_TWO_PI
6395        });
6396        let numeric_hhhh = simpson_integral(cell.left, cell.right, 5000, |z| {
6397            let eta = cell.eta(z);
6398            let h_z = eta_h(z);
6399            ((-eta * eta * eta + 3.0 * eta) * h_z * h_z * h_z * h_z)
6400                * (-cell.q(z)).exp()
6401                * INV_TWO_PI
6402        });
6403
6404        assert!((exact_h - numeric_h).abs() < 1e-8);
6405        assert!((exact_ah - numeric_ah).abs() < 1e-7);
6406        assert!((exact_bh - numeric_bh).abs() < 1e-7);
6407        assert!((exact_hh - numeric_hh).abs() < 1e-7);
6408        assert!((exact_abh - numeric_abh).abs() < 2e-6);
6409        assert!((exact_bbh - numeric_bbh).abs() < 2e-6);
6410        assert!((exact_bhh - numeric_bhh).abs() < 2e-6);
6411        assert!((exact_hhh - numeric_hhh).abs() < 2e-6);
6412        assert!((exact_bbbh - numeric_bbbh).abs() < 3e-6);
6413        assert!((exact_aahh - numeric_aahh).abs() < 3e-6);
6414        assert!((exact_abhh - numeric_abhh).abs() < 3e-6);
6415        assert!((exact_bbhh - numeric_bbhh).abs() < 3e-6);
6416        assert!((exact_bhhh - numeric_bhhh).abs() < 3e-6);
6417        assert!((exact_hhhh - numeric_hhhh).abs() < 3e-6);
6418    }
6419
6420    #[test]
6421    fn cross_basis_cell_derivatives_match_exact_integrands() {
6422        let score_span = LocalSpanCubic {
6423            left: -0.75,
6424            right: 0.25,
6425            c0: 0.08,
6426            c1: -0.03,
6427            c2: 0.02,
6428            c3: -0.01,
6429        };
6430        let score_basis_span = LocalSpanCubic {
6431            left: -0.75,
6432            right: 0.25,
6433            c0: -0.04,
6434            c1: 0.06,
6435            c2: -0.01,
6436            c3: 0.02,
6437        };
6438        let link_span = LocalSpanCubic {
6439            left: -0.6,
6440            right: 0.9,
6441            c0: -0.05,
6442            c1: 0.04,
6443            c2: -0.02,
6444            c3: 0.015,
6445        };
6446        let link_basis_span = LocalSpanCubic {
6447            left: -0.6,
6448            right: 0.9,
6449            c0: 0.02,
6450            c1: -0.01,
6451            c2: 0.03,
6452            c3: -0.02,
6453        };
6454        let a = 0.3;
6455        let b = -0.7;
6456        let coeffs = denested_cell_coefficients(score_span, link_span, a, b);
6457        let cell = DenestedCubicCell {
6458            left: score_span.left,
6459            right: score_span.right,
6460            c0: coeffs[0],
6461            c1: coeffs[1],
6462            c2: coeffs[2],
6463            c3: coeffs[3],
6464        };
6465        let state = evaluate_cell_moments(cell, 24).expect("cell moments");
6466        let (dc_da, dc_db) = denested_cell_coefficient_partials(score_span, link_span, a, b);
6467        let (dc_daa, dc_dab, _) = denested_cell_second_partials(score_span, link_span, a, b);
6468
6469        let coeff_h = score_basis_cell_coefficients(score_basis_span, b);
6470        let coeff_bh = score_basis_cell_coefficients(score_basis_span, 1.0);
6471        let coeff_w = link_basis_cell_coefficients(link_basis_span, a, b);
6472        let (coeff_aw, coeff_bw) = link_basis_cell_coefficient_partials(link_basis_span, a, b);
6473        let (coeff_aaw, coeff_abw, _) = link_basis_cell_second_partials(link_basis_span, a, b);
6474        let zero = [0.0; 4];
6475
6476        let eta_a = |z: f64| 1.0 + link_span.first_derivative(a + b * z);
6477        let eta_b = |z: f64| z + score_span.evaluate(z) + z * link_span.first_derivative(a + b * z);
6478        let eta_h = |z: f64| b * score_basis_span.evaluate(z);
6479        let eta_bh = |z: f64| score_basis_span.evaluate(z);
6480        let eta_w = |z: f64| link_basis_span.evaluate(a + b * z);
6481        let eta_ab = |z: f64| z * link_span.second_derivative(a + b * z);
6482        let eta_aw = |z: f64| link_basis_span.first_derivative(a + b * z);
6483        let eta_bw = |z: f64| z * link_basis_span.first_derivative(a + b * z);
6484
6485        let exact_hw =
6486            cell_second_derivative_from_moments(cell, &coeff_h, &coeff_w, &zero, &state.moments)
6487                .expect("hw");
6488        let exact_ahw = cell_third_derivative_from_moments(
6489            cell,
6490            &dc_da,
6491            &coeff_h,
6492            &coeff_w,
6493            &zero,
6494            &coeff_aw,
6495            &zero,
6496            &zero,
6497            &state.moments,
6498        )
6499        .expect("ahw");
6500        let exact_bhw = cell_third_derivative_from_moments(
6501            cell,
6502            &dc_db,
6503            &coeff_h,
6504            &coeff_w,
6505            &coeff_bh,
6506            &coeff_bw,
6507            &zero,
6508            &zero,
6509            &state.moments,
6510        )
6511        .expect("bhw");
6512        let exact_hhw = cell_third_derivative_from_moments(
6513            cell,
6514            &coeff_h,
6515            &coeff_h,
6516            &coeff_w,
6517            &zero,
6518            &zero,
6519            &zero,
6520            &zero,
6521            &state.moments,
6522        )
6523        .expect("hhw");
6524        let exact_hww = cell_third_derivative_from_moments(
6525            cell,
6526            &coeff_h,
6527            &coeff_w,
6528            &coeff_w,
6529            &zero,
6530            &zero,
6531            &zero,
6532            &zero,
6533            &state.moments,
6534        )
6535        .expect("hww");
6536        let exact_aahw = cell_fourth_derivative_from_moments(
6537            cell,
6538            &dc_da,
6539            &dc_da,
6540            &coeff_h,
6541            &coeff_w,
6542            &dc_daa,
6543            &zero,
6544            &coeff_aw,
6545            &zero,
6546            &coeff_aw,
6547            &zero,
6548            &zero,
6549            &coeff_aaw,
6550            &zero,
6551            &zero,
6552            &zero,
6553            &state.moments,
6554        )
6555        .expect("aahw");
6556        let exact_hhww = cell_fourth_derivative_from_moments(
6557            cell,
6558            &coeff_h,
6559            &coeff_h,
6560            &coeff_w,
6561            &coeff_w,
6562            &zero,
6563            &zero,
6564            &zero,
6565            &zero,
6566            &zero,
6567            &zero,
6568            &zero,
6569            &zero,
6570            &zero,
6571            &zero,
6572            &zero,
6573            &state.moments,
6574        )
6575        .expect("hhww");
6576        let exact_hhhw = cell_fourth_derivative_from_moments(
6577            cell,
6578            &coeff_h,
6579            &coeff_h,
6580            &coeff_h,
6581            &coeff_w,
6582            &zero,
6583            &zero,
6584            &zero,
6585            &zero,
6586            &zero,
6587            &zero,
6588            &zero,
6589            &zero,
6590            &zero,
6591            &zero,
6592            &zero,
6593            &state.moments,
6594        )
6595        .expect("hhhw");
6596        let exact_abhw = cell_fourth_derivative_from_moments(
6597            cell,
6598            &dc_da,
6599            &dc_db,
6600            &coeff_h,
6601            &coeff_w,
6602            &dc_dab,
6603            &zero,
6604            &coeff_aw,
6605            &coeff_bh,
6606            &coeff_bw,
6607            &zero,
6608            &zero,
6609            &coeff_abw,
6610            &zero,
6611            &zero,
6612            &zero,
6613            &state.moments,
6614        )
6615        .expect("abhw");
6616        let exact_ahww = cell_fourth_derivative_from_moments(
6617            cell,
6618            &dc_da,
6619            &coeff_h,
6620            &coeff_w,
6621            &coeff_w,
6622            &zero,
6623            &coeff_aw,
6624            &coeff_aw,
6625            &zero,
6626            &zero,
6627            &zero,
6628            &zero,
6629            &zero,
6630            &zero,
6631            &zero,
6632            &zero,
6633            &state.moments,
6634        )
6635        .expect("ahww");
6636        let exact_bhww = cell_fourth_derivative_from_moments(
6637            cell,
6638            &dc_db,
6639            &coeff_h,
6640            &coeff_w,
6641            &coeff_w,
6642            &coeff_bh,
6643            &coeff_bw,
6644            &coeff_bw,
6645            &zero,
6646            &zero,
6647            &zero,
6648            &zero,
6649            &zero,
6650            &zero,
6651            &zero,
6652            &zero,
6653            &state.moments,
6654        )
6655        .expect("bhww");
6656        let exact_hwww = cell_fourth_derivative_from_moments(
6657            cell,
6658            &coeff_h,
6659            &coeff_w,
6660            &coeff_w,
6661            &coeff_w,
6662            &zero,
6663            &zero,
6664            &zero,
6665            &zero,
6666            &zero,
6667            &zero,
6668            &zero,
6669            &zero,
6670            &zero,
6671            &zero,
6672            &zero,
6673            &state.moments,
6674        )
6675        .expect("hwww");
6676
6677        let numeric_hw = simpson_integral(cell.left, cell.right, 5000, |z| {
6678            (-cell.eta(z) * eta_h(z) * eta_w(z)) * (-cell.q(z)).exp() * INV_TWO_PI
6679        });
6680        let numeric_ahw = simpson_integral(cell.left, cell.right, 5000, |z| {
6681            let eta = cell.eta(z);
6682            (-(eta * eta_aw(z) * eta_h(z)) + (eta * eta - 1.0) * eta_a(z) * eta_h(z) * eta_w(z))
6683                * (-cell.q(z)).exp()
6684                * INV_TWO_PI
6685        });
6686        let numeric_bhw = simpson_integral(cell.left, cell.right, 5000, |z| {
6687            let eta = cell.eta(z);
6688            (-(eta * (eta_bh(z) * eta_w(z) + eta_bw(z) * eta_h(z)))
6689                + (eta * eta - 1.0) * eta_b(z) * eta_h(z) * eta_w(z))
6690                * (-cell.q(z)).exp()
6691                * INV_TWO_PI
6692        });
6693        let numeric_hhw = simpson_integral(cell.left, cell.right, 5000, |z| {
6694            let eta = cell.eta(z);
6695            ((eta * eta - 1.0) * eta_h(z) * eta_h(z) * eta_w(z)) * (-cell.q(z)).exp() * INV_TWO_PI
6696        });
6697        let numeric_hww = simpson_integral(cell.left, cell.right, 5000, |z| {
6698            let eta = cell.eta(z);
6699            ((eta * eta - 1.0) * eta_h(z) * eta_w(z) * eta_w(z)) * (-cell.q(z)).exp() * INV_TWO_PI
6700        });
6701        let numeric_aahw = simpson_integral(cell.left, cell.right, 5000, |z| {
6702            let eta = cell.eta(z);
6703            (-(eta * polynomial_value(&coeff_aaw, z) * eta_h(z))
6704                + (eta * eta - 1.0)
6705                    * (polynomial_value(&dc_daa, z) * eta_h(z) * eta_w(z)
6706                        + 2.0 * eta_aw(z) * eta_a(z) * eta_h(z))
6707                + (-eta * eta * eta + 3.0 * eta) * eta_a(z) * eta_a(z) * eta_h(z) * eta_w(z))
6708                * (-cell.q(z)).exp()
6709                * INV_TWO_PI
6710        });
6711        let numeric_hhww = simpson_integral(cell.left, cell.right, 5000, |z| {
6712            let eta = cell.eta(z);
6713            ((-eta * eta * eta + 3.0 * eta) * eta_h(z) * eta_h(z) * eta_w(z) * eta_w(z))
6714                * (-cell.q(z)).exp()
6715                * INV_TWO_PI
6716        });
6717        let numeric_hhhw = simpson_integral(cell.left, cell.right, 5000, |z| {
6718            let eta = cell.eta(z);
6719            ((-eta * eta * eta + 3.0 * eta) * eta_h(z) * eta_h(z) * eta_h(z) * eta_w(z))
6720                * (-cell.q(z)).exp()
6721                * INV_TWO_PI
6722        });
6723        let numeric_abhw = simpson_integral(cell.left, cell.right, 5000, |z| {
6724            let eta = cell.eta(z);
6725            (-(eta * polynomial_value(&coeff_abw, z) * eta_h(z) + eta * eta_aw(z) * eta_bh(z))
6726                + (eta * eta - 1.0)
6727                    * (eta_ab(z) * eta_h(z) * eta_w(z)
6728                        + eta_aw(z) * eta_b(z) * eta_h(z)
6729                        + eta_bh(z) * eta_a(z) * eta_w(z)
6730                        + eta_bw(z) * eta_a(z) * eta_h(z))
6731                + (-eta * eta * eta + 3.0 * eta) * eta_a(z) * eta_b(z) * eta_h(z) * eta_w(z))
6732                * (-cell.q(z)).exp()
6733                * INV_TWO_PI
6734        });
6735        let numeric_ahww = simpson_integral(cell.left, cell.right, 5000, |z| {
6736            let eta = cell.eta(z);
6737            (2.0 * (eta * eta - 1.0) * eta_aw(z) * eta_h(z) * eta_w(z)
6738                + (-eta * eta * eta + 3.0 * eta) * eta_a(z) * eta_h(z) * eta_w(z) * eta_w(z))
6739                * (-cell.q(z)).exp()
6740                * INV_TWO_PI
6741        });
6742        let numeric_bhww = simpson_integral(cell.left, cell.right, 5000, |z| {
6743            let eta = cell.eta(z);
6744            let h_z = eta_h(z);
6745            let w_z = eta_w(z);
6746            ((eta * eta - 1.0) * (eta_bh(z) * w_z * w_z + 2.0 * eta_bw(z) * h_z * w_z)
6747                + (-eta * eta * eta + 3.0 * eta) * eta_b(z) * h_z * w_z * w_z)
6748                * (-cell.q(z)).exp()
6749                * INV_TWO_PI
6750        });
6751        let numeric_hwww = simpson_integral(cell.left, cell.right, 5000, |z| {
6752            let eta = cell.eta(z);
6753            ((-eta * eta * eta + 3.0 * eta) * eta_h(z) * eta_w(z) * eta_w(z) * eta_w(z))
6754                * (-cell.q(z)).exp()
6755                * INV_TWO_PI
6756        });
6757
6758        assert!((exact_hw - numeric_hw).abs() < 1e-7);
6759        assert!((exact_ahw - numeric_ahw).abs() < 2e-6);
6760        assert!((exact_bhw - numeric_bhw).abs() < 2e-6);
6761        assert!((exact_hhw - numeric_hhw).abs() < 2e-6);
6762        assert!((exact_hww - numeric_hww).abs() < 2e-6);
6763        assert!((exact_aahw - numeric_aahw).abs() < 3e-6);
6764        assert!((exact_hhww - numeric_hhww).abs() < 3e-6);
6765        assert!((exact_hhhw - numeric_hhhw).abs() < 3e-6);
6766        assert!((exact_abhw - numeric_abhw).abs() < 3e-6);
6767        assert!((exact_ahww - numeric_ahww).abs() < 3e-6);
6768        assert!((exact_bhww - numeric_bhww).abs() < 3e-6);
6769        assert!((exact_hwww - numeric_hwww).abs() < 3e-6);
6770    }
6771
6772    #[test]
6773    fn cell_moment_scratch_reuses_buffers_under_margslope_like_pressure() {
6774        let cells = [
6775            DenestedCubicCell {
6776                left: -1.2,
6777                right: -0.35,
6778                c0: 0.18,
6779                c1: 0.72,
6780                c2: -0.045,
6781                c3: 0.018,
6782            },
6783            DenestedCubicCell {
6784                left: -0.35,
6785                right: 0.48,
6786                c0: -0.08,
6787                c1: 0.91,
6788                c2: 0.038,
6789                c3: -0.014,
6790            },
6791            DenestedCubicCell {
6792                left: 0.48,
6793                right: 1.4,
6794                c0: 0.11,
6795                c1: 0.83,
6796                c2: 0.022,
6797                c3: 0.012,
6798            },
6799        ];
6800        let mut scratch = CellMomentScratch::with_capacity(MAX_AFFINE_ANCHOR_DEGREE);
6801        for cell in cells {
6802            let baseline = evaluate_cell_moments(cell, 9).expect("baseline moments");
6803            let scratch_state =
6804                evaluate_cell_moments_with_scratch(cell, 9, &mut scratch).expect("scratch moments");
6805            assert_eq!(baseline.branch, scratch_state.branch);
6806            assert!((baseline.value - scratch_state.value).abs() <= 1e-10);
6807            assert_eq!(baseline.moments.len(), scratch_state.moments.len());
6808            for (lhs, rhs) in baseline.moments.iter().zip(scratch_state.moments.iter()) {
6809                assert!((lhs - rhs).abs() <= 1e-10, "{lhs} vs {rhs}");
6810            }
6811        }
6812
6813        reset_cell_moment_test_reallocs();
6814        let mut checksum = 0.0;
6815        for i in 0..5_000 {
6816            let cell = cells[i % cells.len()];
6817            let state = evaluate_cell_moments_with_scratch(cell, 9, &mut scratch)
6818                .expect("scratch moments under repeated pressure");
6819            checksum += state.value + state.moments[0] * 1e-12;
6820        }
6821        assert!(checksum.is_finite());
6822        assert_eq!(
6823            cell_moment_test_reallocs(),
6824            0,
6825            "scratch-backed inner cell-moment calls should not grow Vec buffers"
6826        );
6827    }
6828
6829    #[test]
6830    fn evaluate_cell_moments_matches_numeric_integrals() {
6831        let cell = DenestedCubicCell {
6832            left: -0.9,
6833            right: 0.8,
6834            c0: 0.15,
6835            c1: -0.35,
6836            c2: 0.11,
6837            c3: -0.07,
6838        };
6839        let state = evaluate_cell_moments(cell, 6).expect("cell moments");
6840        let value_numeric = simpson_integral(cell.left, cell.right, 4000, |z| {
6841            super::normal_cdf(cell.eta(z)) * normal_pdf(z)
6842        });
6843        assert!((state.value - value_numeric).abs() < 1e-9);
6844        for degree in 0..=6 {
6845            let target = simpson_integral(cell.left, cell.right, 4000, |z| {
6846                z.powi(degree as i32) * (-cell.q(z)).exp()
6847            });
6848            assert!((state.moments[degree] - target).abs() < 1e-9);
6849        }
6850    }
6851
6852    #[test]
6853    fn partition_builder_moves_link_preimages_with_intercept() {
6854        let score_breaks = [-2.0, -1.0, 0.0, 1.0, 2.0];
6855        let link_breaks = [-1.5, -0.5, 0.5, 1.5];
6856        let score_span = |z: f64| {
6857            let left = if z < -1.0 {
6858                -2.0
6859            } else if z < 0.0 {
6860                -1.0
6861            } else if z < 1.0 {
6862                0.0
6863            } else {
6864                1.0
6865            };
6866            Ok(LocalSpanCubic {
6867                left,
6868                right: left + 1.0,
6869                c0: 0.1,
6870                c1: 0.2,
6871                c2: 0.0,
6872                c3: 0.0,
6873            })
6874        };
6875        let link_span = |u: f64| {
6876            let left = if u < -0.5 {
6877                -1.5
6878            } else if u < 0.5 {
6879                -0.5
6880            } else {
6881                0.5
6882            };
6883            Ok(LocalSpanCubic {
6884                left,
6885                right: left + 1.0,
6886                c0: -0.05,
6887                c1: 0.1,
6888                c2: 0.0,
6889                c3: 0.0,
6890            })
6891        };
6892        let cells_a0 = build_denested_partition_cells(
6893            0.25,
6894            0.9,
6895            &score_breaks,
6896            &link_breaks,
6897            score_span,
6898            link_span,
6899        )
6900        .expect("cells a0");
6901        let cells_a1 = build_denested_partition_cells(
6902            0.55,
6903            0.9,
6904            &score_breaks,
6905            &link_breaks,
6906            score_span,
6907            link_span,
6908        )
6909        .expect("cells a1");
6910        assert!(cells_a0.len() >= score_breaks.len() - 1);
6911        assert!(
6912            cells_a0
6913                .windows(2)
6914                .all(|w| (w[0].cell.right - w[1].cell.left).abs() <= 1e-12)
6915        );
6916        assert!(
6917            cells_a0
6918                .iter()
6919                .zip(cells_a1.iter())
6920                .any(|(lhs, rhs)| (lhs.cell.left - rhs.cell.left).abs() > 1e-10)
6921        );
6922        assert!(cells_a0.first().unwrap().cell.left.is_infinite());
6923        assert!(cells_a0.last().unwrap().cell.right.is_infinite());
6924    }
6925
6926    #[test]
6927    fn partition_builder_without_breaks_returns_single_global_cell() {
6928        let cells = build_denested_partition_cells_with_tails(
6929            0.3,
6930            -0.4,
6931            &[],
6932            &[],
6933            |z| {
6934                if z.is_nan() {
6935                    return Err("probe z is NaN".to_string());
6936                }
6937                Ok(LocalSpanCubic {
6938                    left: 0.0,
6939                    right: 1.0,
6940                    c0: 0.0,
6941                    c1: 0.0,
6942                    c2: 0.0,
6943                    c3: 0.0,
6944                })
6945            },
6946            |u| {
6947                if u.is_nan() {
6948                    return Err("probe u is NaN".to_string());
6949                }
6950                Ok(LocalSpanCubic {
6951                    left: 0.0,
6952                    right: 1.0,
6953                    c0: 0.0,
6954                    c1: 0.0,
6955                    c2: 0.0,
6956                    c3: 0.0,
6957                })
6958            },
6959        )
6960        .expect("global cell");
6961        assert_eq!(cells.len(), 1);
6962        assert_eq!(cells[0].cell.left, f64::NEG_INFINITY);
6963        assert_eq!(cells[0].cell.right, f64::INFINITY);
6964        assert!(cells[0].cell.c2.abs() < 1e-12);
6965        assert!(cells[0].cell.c3.abs() < 1e-12);
6966    }
6967
6968    #[test]
6969    fn polynomial_integral_helper_matches_moment_sum() {
6970        let cell = DenestedCubicCell {
6971            left: -1.5,
6972            right: 1.25,
6973            c0: 0.2,
6974            c1: -0.4,
6975            c2: 0.15,
6976            c3: 0.03,
6977        };
6978        let state = evaluate_cell_moments(cell, 8).expect("cell moments");
6979        let coeffs = [1.5, -0.25, 0.75, 0.1];
6980        let expected = INV_TWO_PI
6981            * coeffs
6982                .iter()
6983                .enumerate()
6984                .map(|(idx, coeff)| coeff * state.moments[idx])
6985                .sum::<f64>();
6986        let got = cell_polynomial_integral_from_moments(&coeffs, &state.moments, "test poly")
6987            .expect("poly integral");
6988        assert!((got - expected).abs() < 1e-14);
6989    }
6990
6991    #[test]
6992    fn batched_cell_moment_max_degree_matches_direct_non_affine_grid() {
6993        let cells = [
6994            DenestedCubicCell {
6995                left: -2.0,
6996                right: -0.25,
6997                c0: -0.7,
6998                c1: 0.8,
6999                c2: 0.015,
7000                c3: -0.004,
7001            },
7002            DenestedCubicCell {
7003                left: -0.5,
7004                right: 0.75,
7005                c0: 0.2,
7006                c1: -0.35,
7007                c2: -0.025,
7008                c3: 0.0,
7009            },
7010            DenestedCubicCell {
7011                left: 0.1,
7012                right: 1.6,
7013                c0: 0.4,
7014                c1: 0.25,
7015                c2: 0.01,
7016                c3: 0.006,
7017            },
7018            DenestedCubicCell {
7019                left: -1.25,
7020                right: 2.25,
7021                c0: -0.1,
7022                c1: 0.55,
7023                c2: -0.012,
7024                c3: 0.003,
7025            },
7026        ];
7027        for cell in cells {
7028            let branch = branch_cell(cell).expect("branch");
7029            if branch == ExactCellBranch::Affine {
7030                continue;
7031            }
7032            let batched =
7033                evaluate_non_affine_cell_state(cell, branch, 21).expect("degree-21 state");
7034            for degree in [9usize, 15, 21] {
7035                let direct =
7036                    evaluate_non_affine_cell_state(cell, branch, degree).expect("direct state");
7037                assert_eq!(batched.branch, direct.branch);
7038                let denom = direct.value.abs().max(1.0);
7039                assert!(((batched.value - direct.value).abs() / denom) < 1e-10);
7040                for k in 0..=degree {
7041                    let denom = direct.moments[k].abs().max(1.0);
7042                    let rel = (batched.moments[k] - direct.moments[k]).abs() / denom;
7043                    assert!(
7044                        rel < 1e-10,
7045                        "cell={cell:?} degree={degree} moment={k} rel={rel:e}"
7046                    );
7047                }
7048            }
7049        }
7050    }
7051
7052    #[test]
7053    fn derivative_moment_evaluator_matches_value_evaluator_moments() {
7054        let cells = [
7055            DenestedCubicCell {
7056                left: -2.0,
7057                right: -0.4,
7058                c0: 0.15,
7059                c1: -0.8,
7060                c2: 0.0,
7061                c3: 0.0,
7062            },
7063            DenestedCubicCell {
7064                left: -0.75,
7065                right: 1.4,
7066                c0: -0.25,
7067                c1: 0.6,
7068                c2: 0.12,
7069                c3: 0.0,
7070            },
7071            DenestedCubicCell {
7072                left: -1.1,
7073                right: 0.9,
7074                c0: 0.35,
7075                c1: -0.3,
7076                c2: 0.05,
7077                c3: -0.015,
7078            },
7079        ];
7080        for cell in cells {
7081            for degree in [4usize, 9, 15, 21] {
7082                let full = evaluate_cell_moments_uncached(cell, degree).expect("full moments");
7083                let derivative = evaluate_cell_derivative_moments_uncached(cell, degree)
7084                    .expect("derivative moments");
7085                assert_eq!(full.branch, derivative.branch);
7086                assert_eq!(full.moments.len(), derivative.moments.len());
7087                for k in 0..full.moments.len() {
7088                    assert_eq!(full.moments[k].to_bits(), derivative.moments[k].to_bits());
7089                }
7090            }
7091        }
7092    }
7093
7094    #[test]
7095    fn cell_moment_lru_matches_uncached_non_affine_grid() {
7096        let cache = CellMomentLruCache::new(16 * 1024 * 1024);
7097        let stats = CellMomentCacheStats::default();
7098        let c0s = [-0.75, 0.0, 0.5];
7099        let c1s = [-1.2, 0.25, 1.1];
7100        let c2s = [-0.18, 0.07];
7101        let c3s = [0.0, 0.025];
7102        let bounds = [(-2.0, -0.5), (-0.25, 1.5)];
7103        let degrees = [4usize, 9, 15, 21];
7104        for &c0 in &c0s {
7105            for &c1 in &c1s {
7106                for &c2 in &c2s {
7107                    for &c3 in &c3s {
7108                        for &(left, right) in &bounds {
7109                            for &max_degree in &degrees {
7110                                let cell = DenestedCubicCell {
7111                                    left,
7112                                    right,
7113                                    c0,
7114                                    c1,
7115                                    c2,
7116                                    c3,
7117                                };
7118                                let branch = branch_cell(cell).expect("branch");
7119                                if branch == ExactCellBranch::Affine {
7120                                    continue;
7121                                }
7122                                let expected =
7123                                    evaluate_non_affine_cell_state(cell, branch, max_degree)
7124                                        .expect("uncached non-affine moments");
7125                                let got = evaluate_cell_moments_cached(
7126                                    cell,
7127                                    max_degree,
7128                                    &cache,
7129                                    Some(&stats),
7130                                )
7131                                .expect("cached moments");
7132                                assert_eq!(got.branch, expected.branch);
7133                                assert_eq!(got.moments.len(), max_degree + 1);
7134                                let denom = expected.value.abs().max(1.0);
7135                                assert!(
7136                                    ((got.value - expected.value).abs() / denom) < 1e-10,
7137                                    "value mismatch for {cell:?} degree {max_degree}: got {} expected {}",
7138                                    got.value,
7139                                    expected.value
7140                                );
7141                                for (idx, (&lhs, &rhs)) in
7142                                    got.moments.iter().zip(expected.moments.iter()).enumerate()
7143                                {
7144                                    let denom = rhs.abs().max(1.0);
7145                                    assert!(
7146                                        ((lhs - rhs).abs() / denom) < 1e-10,
7147                                        "moment {idx} mismatch for {cell:?} degree {max_degree}: got {lhs} expected {rhs}"
7148                                    );
7149                                }
7150                                let warm = evaluate_cell_moments_cached(
7151                                    cell,
7152                                    max_degree,
7153                                    &cache,
7154                                    Some(&stats),
7155                                )
7156                                .expect("warm cached moments");
7157                                assert_eq!(warm, got);
7158                            }
7159                        }
7160                    }
7161                }
7162            }
7163        }
7164        let (hits, misses) = stats.snapshot();
7165        assert!(hits > 0, "expected warm LRU hits");
7166        assert!(misses > 0, "expected cold LRU misses");
7167    }
7168
7169    #[test]
7170    fn cell_moment_fingerprint_exact_cache_matches_current_evaluator() {
7171        let cells = [
7172            DenestedCubicCell {
7173                left: -1.75,
7174                right: -0.25,
7175                c0: 0.15,
7176                c1: -0.35,
7177                c2: 0.08,
7178                c3: -0.015,
7179            },
7180            DenestedCubicCell {
7181                left: -0.5,
7182                right: 0.8,
7183                c0: -0.2,
7184                c1: 0.45,
7185                c2: -0.12,
7186                c3: 0.025,
7187            },
7188            DenestedCubicCell {
7189                left: 0.1,
7190                right: 1.6,
7191                c0: 0.05,
7192                c1: 0.2,
7193                c2: 0.03,
7194                c3: 0.004,
7195            },
7196        ];
7197        let mut cache = std::collections::HashMap::new();
7198        for max_degree in [0usize, 3, 4, 9, 16] {
7199            for cell in cells {
7200                let baseline = evaluate_cell_moments(cell, max_degree).expect("baseline moments");
7201                let key = cell_moment_cache_key(cell, max_degree, 0.0);
7202                let cached = cache.entry(key).or_insert_with(|| {
7203                    evaluate_cell_moments(cell, max_degree).expect("cached moments")
7204                });
7205                assert_eq!(baseline.branch, cached.branch);
7206                assert_eq!(baseline.value.to_bits(), cached.value.to_bits());
7207                assert_eq!(baseline.moments.len(), cached.moments.len());
7208                for (lhs, rhs) in baseline.moments.iter().zip(cached.moments.iter()) {
7209                    assert_eq!(lhs.to_bits(), rhs.to_bits());
7210                }
7211            }
7212        }
7213    }
7214
7215    #[test]
7216    fn fuzzy_cell_moment_fingerprint_error_scales_with_epsilon() {
7217        for epsilon in [1e-8, 1e-6] {
7218            let base = DenestedCubicCell {
7219                left: -1.25,
7220                right: 1.1,
7221                c0: 0.1,
7222                c1: -0.25,
7223                c2: 0.04,
7224                c3: -0.006,
7225            };
7226            let perturbed = DenestedCubicCell {
7227                left: base.left + 0.001 * epsilon,
7228                right: base.right - 0.001 * epsilon,
7229                c0: base.c0 + 0.001 * epsilon,
7230                c1: base.c1 - 0.001 * epsilon,
7231                c2: base.c2 + 0.001 * epsilon,
7232                c3: base.c3 - 0.001 * epsilon,
7233            };
7234            assert_eq!(
7235                cell_moment_cache_key(base, 9, epsilon),
7236                cell_moment_cache_key(perturbed, 9, epsilon)
7237            );
7238            let lhs = evaluate_cell_moments(base, 9).expect("base moments");
7239            let rhs = evaluate_cell_moments(perturbed, 9).expect("perturbed moments");
7240            let max_rel = lhs
7241                .moments
7242                .iter()
7243                .zip(rhs.moments.iter())
7244                .map(|(a, b)| (a - b).abs() / a.abs().max(b.abs()).max(1.0))
7245                .fold(0.0_f64, f64::max);
7246            assert!(
7247                max_rel <= 10.0 * epsilon,
7248                "epsilon={epsilon:.1e} max_rel={max_rel:.3e}"
7249            );
7250        }
7251    }
7252
7253    /// Locks in numerical equivalence of the optimized
7254    /// `evaluate_non_affine_cell_state` against an inline reference
7255    /// implementation that mirrors the prior pre-fold structure
7256    /// (separate `cell.eta(z)` / `cell.q(z)` calls; post-loop
7257    /// `* half_width`; trailing `value_integral * half_width / sqrt(TAU)`).
7258    /// Any drift larger than 1e-13 relative would indicate the hot-path
7259    /// rewrite changed the math.
7260    #[test]
7261    fn non_affine_cell_state_matches_prefold_reference_to_1e_minus_13() {
7262        // Reference: byte-for-byte the structure of the previous
7263        // implementation. Kept local to this test to avoid leaking a second
7264        // public surface.
7265        fn reference(
7266            cell: DenestedCubicCell,
7267            branch: ExactCellBranch,
7268            max_degree: usize,
7269        ) -> CellMomentState {
7270            let mut moments: CellMomentVec = smallvec![0.0_f64; max_degree + 1];
7271            let mut value_integral = 0.0_f64;
7272            let center = 0.5 * (cell.left + cell.right);
7273            let half_width = 0.5 * (cell.right - cell.left);
7274            for (&node, &weight) in GL_NODES.iter().zip(GL_WEIGHTS.iter()) {
7275                let z = center + half_width * node;
7276                let eta = cell.eta(z);
7277                let moment_weight = weight * (-cell.q(z)).exp();
7278                let mut z_pow = 1.0_f64;
7279                for moment in &mut moments {
7280                    *moment = moment_weight.mul_add(z_pow, *moment);
7281                    z_pow *= z;
7282                }
7283                value_integral += weight * (-0.5 * z * z).exp() * normal_cdf(eta);
7284            }
7285            for moment in &mut moments {
7286                *moment *= half_width;
7287            }
7288            CellMomentState {
7289                branch,
7290                value: value_integral * half_width / (std::f64::consts::TAU).sqrt(),
7291                moments,
7292            }
7293        }
7294
7295        // Hand-rolled inputs that cross both Quartic and Sextic branches and
7296        // exercise positive/negative coefficients, asymmetric intervals, and
7297        // a wide degree range (matches survival_marginal_slope's degree=9
7298        // production call as well as the bernoulli outer-step degree=24).
7299        let cells = [
7300            DenestedCubicCell {
7301                left: -1.25,
7302                right: -0.2,
7303                c0: -0.35,
7304                c1: 0.85,
7305                c2: 0.04,
7306                c3: -0.015,
7307            },
7308            DenestedCubicCell {
7309                left: -0.2,
7310                right: 0.55,
7311                c0: 0.12,
7312                c1: -0.65,
7313                c2: -0.025,
7314                c3: 0.02,
7315            },
7316            DenestedCubicCell {
7317                left: 0.55,
7318                right: 1.6,
7319                c0: 0.42,
7320                c1: 0.35,
7321                c2: 0.018,
7322                c3: 0.012,
7323            },
7324            DenestedCubicCell {
7325                left: -3.0,
7326                right: -1.0,
7327                c0: 1.7,
7328                c1: -0.4,
7329                c2: 0.11,
7330                c3: -0.07,
7331            },
7332        ];
7333        let degrees = [0_usize, 4, 9, 16, 24];
7334        for cell in cells {
7335            let branch = branch_cell(cell).expect("branch");
7336            assert_ne!(branch, ExactCellBranch::Affine);
7337            for max_degree in degrees {
7338                let actual = evaluate_non_affine_cell_state(cell, branch, max_degree)
7339                    .expect("optimized non-affine");
7340                let expected = reference(cell, branch, max_degree);
7341                assert_eq!(actual.branch, expected.branch);
7342                assert_eq!(actual.moments.len(), expected.moments.len());
7343                let denom_v = expected.value.abs().max(1.0);
7344                let rel_v = (actual.value - expected.value).abs() / denom_v;
7345                let actual_v = actual.value;
7346                let expected_v = expected.value;
7347                assert!(
7348                    rel_v <= 1e-13,
7349                    "value rel mismatch for {cell:?} degree {max_degree}: \
7350                     actual={actual_v:.17e} expected={expected_v:.17e} rel={rel_v:.3e}"
7351                );
7352                for (k, (lhs, rhs)) in actual
7353                    .moments
7354                    .iter()
7355                    .zip(expected.moments.iter())
7356                    .enumerate()
7357                {
7358                    let denom = rhs.abs().max(1.0);
7359                    let rel = (lhs - rhs).abs() / denom;
7360                    assert!(
7361                        rel <= 1e-13,
7362                        "moment {k} rel mismatch for {cell:?} degree {max_degree}: \
7363                         actual={lhs:.17e} expected={rhs:.17e} rel={rel:.3e}"
7364                    );
7365                }
7366
7367                // Also lock in the derivative-state path on the same
7368                // inputs so the (parallel) edit there can't drift.
7369                let actual_deriv =
7370                    evaluate_non_affine_cell_derivative_state(cell, branch, max_degree)
7371                        .expect("optimized derivative");
7372                for (k, (lhs, rhs)) in actual_deriv
7373                    .moments
7374                    .iter()
7375                    .zip(expected.moments.iter())
7376                    .enumerate()
7377                {
7378                    let denom = rhs.abs().max(1.0);
7379                    let rel = (lhs - rhs).abs() / denom;
7380                    assert!(
7381                        rel <= 1e-13,
7382                        "deriv moment {k} rel mismatch for {cell:?} degree {max_degree}: \
7383                         actual={lhs:.17e} expected={rhs:.17e} rel={rel:.3e}"
7384                    );
7385                }
7386            }
7387        }
7388    }
7389
7390    /// DECISIVE: the third-derivative kernel must equal the FD of the
7391    /// second-derivative kernel w.r.t. a parameter that perturbs `eta`,
7392    /// RE-EVALUATING the moments at each step (the moments depend on `eta`
7393    /// via the `exp(-q)` weight). This isolates the kernel from all survival
7394    /// partition/cross machinery (gam#979 f_uv_dir localization).
7395    #[test]
7396    fn third_derivative_kernel_matches_fd_of_second_with_eta_perturbation() {
7397        // A finite, non-affine cell.
7398        let base = DenestedCubicCell {
7399            left: -0.6,
7400            right: 0.9,
7401            c0: 0.30,
7402            c1: 0.45,
7403            c2: -0.20,
7404            c3: 0.12,
7405        };
7406        // Synthetic parameter directions as cubic-in-z perturbations of eta:
7407        //   eta_u = ∂eta/∂u, eta_v = ∂eta/∂v, eta_t = ∂eta/∂t (the dir).
7408        let eta_u = [0.11_f64, -0.07, 0.05, 0.02];
7409        let eta_v = [-0.09_f64, 0.13, -0.04, 0.03];
7410        let eta_t = [0.17_f64, 0.06, -0.10, 0.04]; // the "b-like" direction
7411        // Second crosses ∂²eta/∂{·}{·} (pick small non-zero cubics).
7412        let eta_uv = [0.02_f64, 0.01, -0.015, 0.005];
7413        let eta_ut = [-0.01_f64, 0.02, 0.007, -0.003];
7414        let eta_vt = [0.015_f64, -0.008, 0.01, 0.004];
7415        // Third cross ∂³eta/∂u∂v∂t.
7416        let eta_uvt = [0.003_f64, -0.002, 0.001, 0.0005];
7417
7418        let neg = |a: &[f64; 4]| a.map(|v| -v);
7419        let max_degree = 15usize;
7420
7421        // f_uv(s) where param s shifts eta by s·(eta_t + ½ s²... ) — here we
7422        // build the cell at eta + s·eta_t + s²·eta_vt-style is NOT needed; we
7423        // only need the t-direction to first order for ∂/∂t. To FD ∂(f_uv)/∂t
7424        // we perturb eta along eta_t AND carry the s-dependence of the u,v
7425        // crosses: eta_u(s)=eta_u + s·eta_ut, eta_v(s)=eta_v + s·eta_vt,
7426        // eta_uv(s)=eta_uv + s·eta_uvt. The cell cubic shifts by s·eta_t.
7427        let f_uv_at = |s: f64| -> f64 {
7428            let cell_s = DenestedCubicCell {
7429                c0: base.c0 + s * eta_t[0],
7430                c1: base.c1 + s * eta_t[1],
7431                c2: base.c2 + s * eta_t[2],
7432                c3: base.c3 + s * eta_t[3],
7433                ..base
7434            };
7435            // Moments MUST be recomputed at the perturbed eta.
7436            let st = evaluate_cell_moments(cell_s, max_degree).unwrap();
7437            let neg_cell = DenestedCubicCell {
7438                c0: -cell_s.c0,
7439                c1: -cell_s.c1,
7440                c2: -cell_s.c2,
7441                c3: -cell_s.c3,
7442                ..cell_s
7443            };
7444            let u_s = [
7445                eta_u[0] + s * eta_ut[0],
7446                eta_u[1] + s * eta_ut[1],
7447                eta_u[2] + s * eta_ut[2],
7448                eta_u[3] + s * eta_ut[3],
7449            ];
7450            let v_s = [
7451                eta_v[0] + s * eta_vt[0],
7452                eta_v[1] + s * eta_vt[1],
7453                eta_v[2] + s * eta_vt[2],
7454                eta_v[3] + s * eta_vt[3],
7455            ];
7456            let uv_s = [
7457                eta_uv[0] + s * eta_uvt[0],
7458                eta_uv[1] + s * eta_uvt[1],
7459                eta_uv[2] + s * eta_uvt[2],
7460                eta_uv[3] + s * eta_uvt[3],
7461            ];
7462            cell_second_derivative_from_moments(
7463                neg_cell,
7464                &neg(&u_s),
7465                &neg(&v_s),
7466                &neg(&uv_s),
7467                &st.moments,
7468            )
7469            .unwrap()
7470        };
7471
7472        let h = 1e-5;
7473        let fd = (f_uv_at(h) - f_uv_at(-h)) / (2.0 * h);
7474
7475        // Analytic third via the kernel (negated cell + negated crosses, as the
7476        // survival path does).
7477        let st0 = evaluate_cell_moments(base, max_degree).unwrap();
7478        let neg_cell0 = DenestedCubicCell {
7479            c0: -base.c0,
7480            c1: -base.c1,
7481            c2: -base.c2,
7482            c3: -base.c3,
7483            ..base
7484        };
7485        let analytic = cell_third_derivative_from_moments(
7486            neg_cell0,
7487            &neg(&eta_u),
7488            &neg(&eta_v),
7489            &neg(&eta_t),
7490            &neg(&eta_uv),
7491            &neg(&eta_ut),
7492            &neg(&eta_vt),
7493            &neg(&eta_uvt),
7494            &st0.moments,
7495        )
7496        .unwrap();
7497
7498        let denom = fd.abs().max(1e-3);
7499        let rel = (analytic - fd).abs() / denom;
7500        assert!(
7501            rel <= 1e-5,
7502            "third kernel vs FD-of-second mismatch: analytic={analytic:.12e} fd={fd:.12e} rel={rel:.3e}"
7503        );
7504    }
7505
7506    #[test]
7507    fn moving_shared_edge_second_integral_derivative_has_leibniz_jump_sign() {
7508        let edge0 = 0.2_f64;
7509        let edge_velocity = -0.37_f64;
7510
7511        let left_eta = [0.22_f64, -0.18, 0.09, 0.03];
7512        let right_eta = [-0.11_f64, 0.26, -0.04, 0.02];
7513        let left_r = [0.08_f64, -0.05, 0.03, 0.01];
7514        let left_s = [-0.06_f64, 0.04, 0.02, -0.015];
7515        let left_rs = [0.025_f64, -0.012, 0.006, 0.004];
7516        let right_r = [-0.03_f64, 0.07, -0.02, 0.012];
7517        let right_s = [0.05_f64, -0.025, 0.018, 0.007];
7518        let right_rs = [-0.018_f64, 0.014, -0.005, 0.003];
7519
7520        let integral_at = |shift: f64| -> f64 {
7521            let edge = edge0 + edge_velocity * shift;
7522            let left = DenestedCubicCell {
7523                left: -0.7,
7524                right: edge,
7525                c0: left_eta[0],
7526                c1: left_eta[1],
7527                c2: left_eta[2],
7528                c3: left_eta[3],
7529            };
7530            let right = DenestedCubicCell {
7531                left: edge,
7532                right: 1.1,
7533                c0: right_eta[0],
7534                c1: right_eta[1],
7535                c2: right_eta[2],
7536                c3: right_eta[3],
7537            };
7538            let left_state = evaluate_cell_moments(left, 12).expect("left moments");
7539            let right_state = evaluate_cell_moments(right, 12).expect("right moments");
7540            cell_second_derivative_from_moments(
7541                left,
7542                &left_r,
7543                &left_s,
7544                &left_rs,
7545                &left_state.moments,
7546            )
7547            .expect("left second")
7548                + cell_second_derivative_from_moments(
7549                    right,
7550                    &right_r,
7551                    &right_s,
7552                    &right_rs,
7553                    &right_state.moments,
7554                )
7555                .expect("right second")
7556        };
7557
7558        let h = 1e-5;
7559        let fd = (integral_at(h) - integral_at(-h)) / (2.0 * h);
7560
7561        let left = DenestedCubicCell {
7562            left: -0.7,
7563            right: edge0,
7564            c0: left_eta[0],
7565            c1: left_eta[1],
7566            c2: left_eta[2],
7567            c3: left_eta[3],
7568        };
7569        let right = DenestedCubicCell {
7570            left: edge0,
7571            right: 1.1,
7572            c0: right_eta[0],
7573            c1: right_eta[1],
7574            c2: right_eta[2],
7575            c3: right_eta[3],
7576        };
7577        let f_left =
7578            cell_second_derivative_boundary_integrand(left, &left_r, &left_s, &left_rs, edge0);
7579        let f_right =
7580            cell_second_derivative_boundary_integrand(right, &right_r, &right_s, &right_rs, edge0);
7581        let analytic = edge_velocity * (f_left - f_right);
7582
7583        let denom = analytic.abs().max(1e-8);
7584        let rel = (fd - analytic).abs() / denom;
7585        assert!(
7586            rel <= 5e-8,
7587            "moving edge sign mismatch: fd={fd:.12e} analytic={analytic:.12e} rel={rel:.3e}"
7588        );
7589    }
7590
7591    #[test]
7592    fn moving_shared_edge_second_integral_mixed_derivative_has_full_leibniz_terms() {
7593        let edge0 = -0.15_f64;
7594        let edge_d1 = 0.31_f64;
7595        let edge_d2 = -0.27_f64;
7596        let edge_d12 = 0.19_f64;
7597
7598        let left_eta = [0.16_f64, -0.21, 0.07, -0.025];
7599        let right_eta = [-0.09_f64, 0.18, -0.055, 0.018];
7600        let left_r = [0.075_f64, -0.045, 0.018, 0.009];
7601        let left_s = [-0.052_f64, 0.033, 0.014, -0.011];
7602        let left_rs = [0.021_f64, -0.009, 0.005, 0.0025];
7603        let right_r = [-0.028_f64, 0.063, -0.017, 0.010];
7604        let right_s = [0.047_f64, -0.023, 0.016, 0.006];
7605        let right_rs = [-0.015_f64, 0.012, -0.004, 0.002];
7606
7607        let integral_at = |s1: f64, s2: f64| -> f64 {
7608            let edge = edge0 + edge_d1 * s1 + edge_d2 * s2 + edge_d12 * s1 * s2;
7609            let left = DenestedCubicCell {
7610                left: -0.8,
7611                right: edge,
7612                c0: left_eta[0],
7613                c1: left_eta[1],
7614                c2: left_eta[2],
7615                c3: left_eta[3],
7616            };
7617            let right = DenestedCubicCell {
7618                left: edge,
7619                right: 0.9,
7620                c0: right_eta[0],
7621                c1: right_eta[1],
7622                c2: right_eta[2],
7623                c3: right_eta[3],
7624            };
7625            let left_state = evaluate_cell_moments(left, 12).expect("left moments");
7626            let right_state = evaluate_cell_moments(right, 12).expect("right moments");
7627            cell_second_derivative_from_moments(
7628                left,
7629                &left_r,
7630                &left_s,
7631                &left_rs,
7632                &left_state.moments,
7633            )
7634            .expect("left second")
7635                + cell_second_derivative_from_moments(
7636                    right,
7637                    &right_r,
7638                    &right_s,
7639                    &right_rs,
7640                    &right_state.moments,
7641                )
7642                .expect("right second")
7643        };
7644
7645        let h = 2e-4;
7646        let fd = (integral_at(h, h) - integral_at(h, -h) - integral_at(-h, h)
7647            + integral_at(-h, -h))
7648            / (4.0 * h * h);
7649
7650        let left = DenestedCubicCell {
7651            left: -0.8,
7652            right: edge0,
7653            c0: left_eta[0],
7654            c1: left_eta[1],
7655            c2: left_eta[2],
7656            c3: left_eta[3],
7657        };
7658        let right = DenestedCubicCell {
7659            left: edge0,
7660            right: 0.9,
7661            c0: right_eta[0],
7662            c1: right_eta[1],
7663            c2: right_eta[2],
7664            c3: right_eta[3],
7665        };
7666
7667        let boundary_z_derivative =
7668            |cell: DenestedCubicCell, r: &[f64], s: &[f64], rs: &[f64]| -> f64 {
7669                let eta = cell.eta(edge0);
7670                let eta_z = cell.c1 + 2.0 * cell.c2 * edge0 + 3.0 * cell.c3 * edge0 * edge0;
7671                let cr = poly_eval_at(r, edge0);
7672                let cs = poly_eval_at(s, edge0);
7673                let crs = poly_eval_at(rs, edge0);
7674                let cr_z = r.iter().enumerate().skip(1).fold(0.0, |acc, (k, val)| {
7675                    acc + (k as f64) * val * edge0.powi(k as i32 - 1)
7676                });
7677                let cs_z = s.iter().enumerate().skip(1).fold(0.0, |acc, (k, val)| {
7678                    acc + (k as f64) * val * edge0.powi(k as i32 - 1)
7679                });
7680                let crs_z = rs.iter().enumerate().skip(1).fold(0.0, |acc, (k, val)| {
7681                    acc + (k as f64) * val * edge0.powi(k as i32 - 1)
7682                });
7683                let amp = crs - eta * cr * cs;
7684                let amp_z = crs_z - eta_z * cr * cs - eta * cr_z * cs - eta * cr * cs_z;
7685                let q_z = edge0 + eta * eta_z;
7686                (amp_z - amp * q_z) * (-cell.q(edge0)).exp() * INV_TWO_PI
7687            };
7688
7689        let f_left =
7690            cell_second_derivative_boundary_integrand(left, &left_r, &left_s, &left_rs, edge0);
7691        let f_right =
7692            cell_second_derivative_boundary_integrand(right, &right_r, &right_s, &right_rs, edge0);
7693        let fz_left = boundary_z_derivative(left, &left_r, &left_s, &left_rs);
7694        let fz_right = boundary_z_derivative(right, &right_r, &right_s, &right_rs);
7695        let analytic = edge_d12 * (f_left - f_right) + edge_d1 * edge_d2 * (fz_left - fz_right);
7696
7697        let denom = analytic.abs().max(1e-8);
7698        let rel = (fd - analytic).abs() / denom;
7699        assert!(
7700            rel <= 2e-7,
7701            "moving edge mixed term mismatch: fd={fd:.12e} analytic={analytic:.12e} rel={rel:.3e}"
7702        );
7703    }
7704
7705    // gam#1454 resolution. The reported defect ("survival flex directional
7706    // third[g,w0] wrong: candidate f_au_dir/f_aa_dir missing self-flux") posited
7707    // a MISSING third-order Leibniz self-flux at the moving link-knot crossings.
7708    // This regression establishes the two facts that, together, prove the
7709    // implicit-intercept third-order tower
7710    // (`row_primary_third_contracted_recompute*`) is CORRECT to add no such flux:
7711    //
7712    //   (1) The third-derivative integrand `F_rst` genuinely DOES jump across a
7713    //       C²-link knot — its third coefficient slice carries `c_rst ∝ 6·α₃`,
7714    //       and `α₃` (the spline's third `z`-derivative) is the one piece a C²
7715    //       cubic spline leaves discontinuous. So the jump is real and the
7716    //       `cell_third_derivative_boundary_integrand` flux formula is exact
7717    //       (verified by FD of a direct ∂/∂edge of the third-integral sum —
7718    //       a FOURTH-order scenario that pins the integrand, not the tower).
7719    //
7720    //   (2) Every boundary term in the Leibniz expansion of a THIRD derivative,
7721    //       however, evaluates an integrand of order ≤ 2 at the moving edge
7722    //       (one of the three differentiations is spent moving the boundary).
7723    //       The second-derivative integrand `F_rs` is CONTINUOUS across the same
7724    //       C² knot (its slices reach at most `α₂ + 3α₃·shift`, i.e. ½·η''(u*),
7725    //       which a C² spline keeps continuous). Hence the shared-edge flux
7726    //       `velocity·(F_rs^L − F_rs^R)` telescopes to ZERO, and the tower's
7727    //       third-order self-flux is a genuine no-op. The real residual lives in
7728    //       the interior implicit-intercept assembly, not at the boundary.
7729    #[test]
7730    fn third_order_self_flux_telescopes_but_third_integrand_jumps_at_c2_knot_1454() {
7731        let edge0 = 0.13_f64;
7732        let edge_velocity = -0.41_f64;
7733
7734        // Build η continuous to C² at edge0 but with a jump in the cubic (3rd
7735        // derivative) coefficient. Pick the left cubic freely; choose the right
7736        // cubic to match value+1st+2nd derivative at edge0, then perturb its c3.
7737        let left_eta = [0.18_f64, -0.12, 0.07, 0.04];
7738        let right_c3 = 0.04_f64 + 0.09; // α₃ jump across the knot.
7739        // Match η, η', η'' at edge0 for the right piece given its c3:
7740        //   η(z)  = c0 + c1 z + c2 z² + c3 z³
7741        //   η'(z) = c1 + 2 c2 z + 3 c3 z²
7742        //   η''(z)= 2 c2 + 6 c3 z
7743        // Solve right (c0,c1,c2) so the three values equal the left ones at edge0.
7744        let l0 = left_eta[0];
7745        let l1 = left_eta[1];
7746        let l2 = left_eta[2];
7747        let l3 = left_eta[3];
7748        let e = edge0;
7749        let eta_val = l0 + l1 * e + l2 * e * e + l3 * e * e * e;
7750        let eta_d1 = l1 + 2.0 * l2 * e + 3.0 * l3 * e * e;
7751        let eta_d2 = 2.0 * l2 + 6.0 * l3 * e;
7752        let rc2 = (eta_d2 - 6.0 * right_c3 * e) / 2.0;
7753        let rc1 = eta_d1 - 2.0 * rc2 * e - 3.0 * right_c3 * e * e;
7754        let rc0 = eta_val - rc1 * e - rc2 * e * e - right_c3 * e * e * e;
7755        let right_eta = [rc0, rc1, rc2, right_c3];
7756
7757        // Coefficient slices. The first/second slices we keep continuous at the
7758        // edge (mimicking c_r=1+η', c_rs∝η'' which a C² spline matches), so the
7759        // 2nd-order flux would cancel. The third-order slice `rst` carries the
7760        // jumping α₃ and is DIFFERENT across the edge — this is the term that
7761        // breaks cancellation.
7762        let common_r = [0.06_f64, -0.04, 0.02, 0.0];
7763        let common_s = [-0.05_f64, 0.03, 0.015, 0.0];
7764        let common_t = [0.08_f64, 0.05, -0.03, 0.0];
7765        let common_rs = [0.02_f64, -0.01, 0.005, 0.0];
7766        let common_rt = [-0.012_f64, 0.008, 0.004, 0.0];
7767        let common_st = [0.015_f64, -0.006, 0.003, 0.0];
7768        // rst ∝ 6·α₃ in the real path: left and right differ by the α₃ jump.
7769        let left_rst = [6.0 * l3, 0.0, 0.0, 0.0];
7770        let right_rst = [6.0 * right_c3, 0.0, 0.0, 0.0];
7771
7772        let max_degree = 15usize;
7773        let neg = |a: &[f64; 4]| a.map(|v| -v);
7774
7775        // The integral sum over the two cells sharing the moving edge, computed
7776        // via the fixed-domain moment reduction with the SURVIVAL/probit sign
7777        // convention (negated cell + negated coefficient slices), exactly as the
7778        // production `row_primary_third_contracted_recompute` path does.
7779        let integral_at = |shift: f64| -> f64 {
7780            let edge = edge0 + edge_velocity * shift;
7781            let left = DenestedCubicCell {
7782                left: -0.7,
7783                right: edge,
7784                c0: left_eta[0],
7785                c1: left_eta[1],
7786                c2: left_eta[2],
7787                c3: left_eta[3],
7788            };
7789            let right = DenestedCubicCell {
7790                left: edge,
7791                right: 1.0,
7792                c0: right_eta[0],
7793                c1: right_eta[1],
7794                c2: right_eta[2],
7795                c3: right_eta[3],
7796            };
7797            let lst = evaluate_cell_moments(left, max_degree).unwrap();
7798            let rst_m = evaluate_cell_moments(right, max_degree).unwrap();
7799            let neg_left = DenestedCubicCell {
7800                c0: -left.c0,
7801                c1: -left.c1,
7802                c2: -left.c2,
7803                c3: -left.c3,
7804                ..left
7805            };
7806            let neg_right = DenestedCubicCell {
7807                c0: -right.c0,
7808                c1: -right.c1,
7809                c2: -right.c2,
7810                c3: -right.c3,
7811                ..right
7812            };
7813            let li = cell_third_derivative_from_moments(
7814                neg_left,
7815                &neg(&common_r),
7816                &neg(&common_s),
7817                &neg(&common_t),
7818                &neg(&common_rs),
7819                &neg(&common_rt),
7820                &neg(&common_st),
7821                &neg(&left_rst),
7822                &lst.moments,
7823            )
7824            .unwrap();
7825            let ri = cell_third_derivative_from_moments(
7826                neg_right,
7827                &neg(&common_r),
7828                &neg(&common_s),
7829                &neg(&common_t),
7830                &neg(&common_rs),
7831                &neg(&common_rt),
7832                &neg(&common_st),
7833                &neg(&right_rst),
7834                &rst_m.moments,
7835            )
7836            .unwrap();
7837            li + ri
7838        };
7839
7840        let h = 1e-5;
7841        let fd = (integral_at(h) - integral_at(-h)) / (2.0 * h);
7842
7843        // Fixed-domain part: differentiate ONLY the integrands (domain frozen at
7844        // edge0). Its directional derivative is the analytic Leibniz flux alone,
7845        // since the integrand coefficients here are edge-independent:
7846        //   flux = velocity · ( F_rst^L(edge0) − F_rst^R(edge0) ).
7847        //
7848        // CONVENTION: the finite-difference `integral_at` above integrates the
7849        // SURVIVAL/probit sign convention — negated cell (η→−η) AND negated
7850        // coefficient slices — exactly as the production
7851        // `row_primary_third_contracted_recompute` path does. The Leibniz
7852        // boundary integrand must therefore be evaluated in that SAME negated
7853        // convention: the third-derivative integrand is ODD under the joint
7854        // (η→−η, coeff→−coeff) negation (its `rst`, `η·rs·t`, and `(η²−1)·r·s·t`
7855        // terms each flip sign an odd number of times), so evaluating the flux
7856        // with un-negated cells/coeffs yields exactly the opposite sign and the
7857        // Leibniz identity `fd = flux` fails as `fd = −flux`. (The
7858        // second-derivative sibling test `moving_shared_edge_second_integral_
7859        // derivative_has_leibniz_jump_sign` keeps BOTH sides un-negated and so
7860        // stays self-consistent; this test keeps BOTH sides negated.)
7861        let neg_eta = |eta: &[f64; 4]| [-eta[0], -eta[1], -eta[2], -eta[3]];
7862        let left_eta_neg = neg_eta(&left_eta);
7863        let right_eta_neg = neg_eta(&right_eta);
7864        let left0 = DenestedCubicCell {
7865            left: -0.7,
7866            right: edge0,
7867            c0: left_eta_neg[0],
7868            c1: left_eta_neg[1],
7869            c2: left_eta_neg[2],
7870            c3: left_eta_neg[3],
7871        };
7872        let right0 = DenestedCubicCell {
7873            left: edge0,
7874            right: 1.0,
7875            c0: right_eta_neg[0],
7876            c1: right_eta_neg[1],
7877            c2: right_eta_neg[2],
7878            c3: right_eta_neg[3],
7879        };
7880        let f_left = cell_third_derivative_boundary_integrand(
7881            left0,
7882            &neg(&common_r),
7883            &neg(&common_s),
7884            &neg(&common_t),
7885            &neg(&common_rs),
7886            &neg(&common_rt),
7887            &neg(&common_st),
7888            &neg(&left_rst),
7889            edge0,
7890        );
7891        let f_right = cell_third_derivative_boundary_integrand(
7892            right0,
7893            &neg(&common_r),
7894            &neg(&common_s),
7895            &neg(&common_t),
7896            &neg(&common_rs),
7897            &neg(&common_rt),
7898            &neg(&common_st),
7899            &neg(&right_rst),
7900            edge0,
7901        );
7902
7903        // The integrand DOES jump across this C² knot (the α₃ third-coefficient
7904        // term is the only discontinuous piece). Confirm the jump is genuine —
7905        // if it were zero the flux would be a no-op and #1454 would not exist.
7906        let jump = f_left - f_right;
7907        assert!(
7908            jump.abs() > 1e-4,
7909            "third-derivative integrand must jump across the C² knot (α₃ discontinuity); \
7910             got jump={jump:.3e}"
7911        );
7912
7913        let analytic_flux = edge_velocity * jump;
7914        let denom = fd.abs().max(1e-6);
7915        let rel = (fd - analytic_flux).abs() / denom;
7916        assert!(
7917            rel <= 1e-5,
7918            "moving-edge third-derivative flux mismatch (#1454): fd={fd:.12e} \
7919             analytic_flux={analytic_flux:.12e} rel={rel:.3e}"
7920        );
7921
7922        // ---- Fact (2): the SECOND-derivative integrand telescopes to zero. ----
7923        // A 3rd-derivative Leibniz boundary term spends one differentiation on
7924        // the moving edge and evaluates a ≤2nd-order integrand there. The
7925        // hardest such term is the slope-slope Hessian integrand `F_bb`, whose
7926        // coefficient slice is the link cubic's b-b partial
7927        //   dc_dbb(z) = [0, 0, 2(α₂ + 3 α₃·shift), 6 α₃·b]·(z⁰..z³)
7928        //             = z²·η''(u),  with u = a + b·z, shift = a − knot.
7929        // Across a C² knot α₂, α₃, and `shift` all jump, yet η''(u*) is
7930        // continuous — so the EVALUATED slice `c_bb(z*) = z*²·η''(u*)` matches on
7931        // both sides and `F_bb` is continuous. Build the two pieces' raw dc_dbb
7932        // decompositions from `link_cubic_second_partials` and confirm the
7933        // second-derivative integrand carries no jump (flux telescopes to 0).
7934        let a_row = 0.21_f64;
7935        let b_row = 1.37_f64;
7936        let knot = a_row + b_row * edge0; // u-location of the crossing.
7937        // Left/right link pieces: choose α₂,α₃ freely on the left; pick the
7938        // right piece's α₂ so η''(knot) is continuous given a jumped α₃.
7939        let left_link = LocalSpanCubic {
7940            left: knot - 0.6,
7941            right: knot + 0.6,
7942            c0: 0.0,
7943            c1: 0.0,
7944            c2: 0.08,
7945            c3: -0.05,
7946        };
7947        let right_alpha3 = -0.05_f64 + 0.11; // α₃ jump.
7948        // η''(knot) continuity:  2α₂ᴸ + 6α₃ᴸ·(knot−leftᴸ) = 2α₂ᴿ + 6α₃ᴿ·(knot−leftᴿ).
7949        let right_left_coord = knot - 0.4;
7950        let lhs = 2.0 * left_link.c2 + 6.0 * left_link.c3 * (knot - left_link.left);
7951        let right_alpha2 = (lhs - 6.0 * right_alpha3 * (knot - right_left_coord)) / 2.0;
7952        let right_link = LocalSpanCubic {
7953            left: right_left_coord,
7954            right: right_left_coord + 0.8,
7955            c0: 0.0,
7956            c1: 0.0,
7957            c2: right_alpha2,
7958            c3: right_alpha3,
7959        };
7960        let (_, _, dc_dbb_left) = link_cubic_second_partials(left_link, a_row, b_row);
7961        let (_, _, dc_dbb_right) = link_cubic_second_partials(right_link, a_row, b_row);
7962        // The per-coefficient arrays differ (α₃ jumped)...
7963        assert!(
7964            (dc_dbb_left[3] - dc_dbb_right[3]).abs() > 1e-3,
7965            "α₃ jump must make the raw dc_dbb coefficient arrays differ"
7966        );
7967        // ...but the EVALUATED second-order slice at the crossing matches, so the
7968        // F_bb boundary integrand carries no jump and the flux telescopes to 0.
7969        let c_bb_left = poly_eval_at(&dc_dbb_left, edge0);
7970        let c_bb_right = poly_eval_at(&dc_dbb_right, edge0);
7971        assert!(
7972            (c_bb_left - c_bb_right).abs() <= 1e-12,
7973            "second-derivative slope-slope integrand must be CONTINUOUS across the \
7974             C² knot (telescoping self-flux): left={c_bb_left:.15e} right={c_bb_right:.15e}"
7975        );
7976    }
7977}