Repository navigation
fix(state_space): avoid overflow in the Miller Bessel recurrence (TruncatedPeriodic zeros/NaNs) - #827
Merged
thomaspinder merged 2 commits intoNov 10, 2026
Conversation
The downward recurrence in _stable_scaled_ive formed unnormalised values that
grow like (2 k_max / c)^k_max and overflow for small c = 1/(4 l^2) and high
truncation orders, so TruncatedPeriodic and TruncatedPeriodicSDE got zero or
NaN variances. In float32 this already happens for the default kernel.
Run the recurrence on the ratios I_k / I_{k-1} = c / (2k + c r_{k+1}), which
stay in (0, 1), and take cumulative products scaled by i0e(c).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checklist
uv run poe formatbefore committing (ruff format+ruff checkon the changed files).Description
_stable_scaled_ivecomputesĨ_k(c) = e^{-c} I_k(c), withc = 1/(4ℓ²), forTruncatedPeriodicandTruncatedPeriodicSDE. Forc <= truncation_orderit uses Miller's downward recurrence. That recurrence works on unnormalised values, starting from1atk_max = 2K + 20, and only rescales at the end. Those values grow like(2 k_max / c)^{k_max}, so they overflow toinf. The rescalei0e(c) / ratios[0]is then0, and every returned value is0orNaN.Where the overflow happens, compared with
scipy.special.ive:float32: the default kernel already fails.
TruncatedPeriodic(lengthscale=1.0, variance=1.0, period=1.0)usestruncation_order=6, soc = 0.25:With
truncation_order=6, it fails for every lengthscale above about 0.4.float64:
truncation_orderExample:
_stable_scaled_ive(1.8e-3, 25)returns all zeros, but the trueĨ_0is0.998.The existing tests stop at
c = 1e-3withtruncation_order = 20, which is just inside the safe region.Fix
The recurrence now runs on the ratios
r_k = I_k / I_{k-1}, fromr_{k_max+1} = 0:This is the same Miller recurrence
I_{k-1} = (2k/c) I_k + I_{k+1}, divided through byI_k. It has the same starting pointk_max = 2K + 20and the same truncation as before. The result isĨ_k = i0e(c) · r_1 ⋯ r_k. Each ratio is in(0, 1), so no step can overflow. Very small values underflow to zero, which is correct. The function stays jit- and grad-friendly (lax.scanpluscumprod). The other two branches are unchanged.Tests
New tests in
tests/test_state_space/test_stable_bessel.py:test_stable_scaled_ive_small_c_high_order_does_not_overflow: float64, the(c, K)pairs in the overflow region, values against scipy, and finite gradients.test_stable_scaled_ive_float32: float32 inputs, including the default kernel'sc = 0.25.Results:
main, 9 of the new cases fail.tests/test_state_spacepasses: 354 passed, 1 skipped.cin[1e-8, 1e4]forKin {1, 3, 6, 12, 25} againstscipy.special.ive. Onmain, the failures were the overflow cases above. With the fix, the only differences left come from the unchanged forward-recurrence branch, atcjust aboveK = 25(relative error 3e-7 on the highest orders).A CHANGELOG entry is added under Unreleased → Fixed.
Issue Number: N/A