nstat.extras.validation.pykalman_bridge
Cross-validate nstat.DecodingAlgorithms.kalman_filter() against pykalman.
pykalman (https://github.com/pykalman/pykalman) is a BSD-3 pure-NumPy Kalman implementation, community-resurrected in 2026 (v0.11.2, Jan 2026; 1.3k stars). Because it’s NumPy-only (no JAX), it’s the lowest-friction cross-validation reference for the Gaussian-only Kalman path — perfect for CI parity tests.
This bridge addresses AUDIT D3 (the known smoother-index approximation
gap in DecodingAlgorithms.kalman_fixedIntervalSmoother()) by
providing an independent reference implementation users can call from
the same script that uses nstat’s Kalman primitives.
- Install:
pip install nstat-toolbox[test-parity] # or just: pip install pykalman
Functions
|
Fit a linear-Gaussian Kalman filter (+ smoother) in both nstat and pykalman. |
Classes
|
Side-by-side Kalman filter / smoother fit comparison. |
- class nstat.extras.validation.pykalman_bridge.KalmanComparison(nstat_filtered_means, pykalman_filtered_means, nstat_smoothed_means, pykalman_smoothed_means, filtered_inf_norm, smoothed_inf_norm)[source]
Bases:
objectSide-by-side Kalman filter / smoother fit comparison.
- Variables:
nstat_filtered_means (numpy.ndarray) – Filtered state means from
DecodingAlgorithms.kalman_filter(), shape(T, Dx).pykalman_filtered_means (numpy.ndarray) – Filtered state means from
pykalman.KalmanFilter.filter(), same shape and layout.nstat_smoothed_means (numpy.ndarray | None) – Smoothed state means from
DecodingAlgorithms.kalman_fixedIntervalSmoother()if computed (elseNone), shape(T, Dx).pykalman_smoothed_means (numpy.ndarray | None) – Smoothed state means from
pykalman.KalmanFilter.smooth(), same shape (elseNone).filtered_inf_norm (float) – \(\max_t \|\hat{x}_t^{nstat} - \hat{x}_t^{pykalman}\|_\infty\). Filtered means typically agree to ~1e-8 between any two correct implementations of the same linear-Gaussian model.
smoothed_inf_norm (float | None) – Same for smoothed means; reflects the RTS smoother agreement.
Noneif smoothing was not run.
- Parameters:
- assert_filtered_agree(atol=0.01)[source]
Assert filtered means agree within
atol.Default
atol=1e-2reflects the current empirical baseline on a 100×2 linear-Gaussian fixture (~2.6e-3, dominated by t=0 initialization convention differences between nstat and pykalman). Tighten to1e-8once nstat’s filter is patched to match the pykalman initialization (prior at t=0 vs posterior at t=0). Use this as a regression guard, not as a claim of exact agreement.- Parameters:
atol (float)
- Return type:
None
- assert_smoothed_agree(atol=0.001)[source]
Assert smoothed means agree within
atol.Default
atol=1e-3reflects the current empirical baseline on a 100×2 linear-Gaussian fixture (~1.6e-4) — dominated by the same t=0 initialization convention difference that drives the filter disagreement (~2.6e-3), since the RTS smoother is forward-filter-then-backward-smooth and inherits the filter’s boundary handling.Earlier versions of this bridge documented a ~0.4 unit smoother gap and attributed it to “AUDIT D3.” That was a misdiagnosis: the bridge was comparing nstat’s fixed-lag smoother (
kalman_fixedIntervalSmoother— Anderson & Moore augmented-state construction) against pykalman’s RTS smoother — fundamentally different algorithms. The fix was to callDecodingAlgorithms.kalman_smoother()(which is a proper RTS pass) and the disagreement collapsed from ~0.4 to ~1.6e-4.Tighten to
1e-8once nstat’s filter t=0 init convention is patched.- Parameters:
atol (float)
- Return type:
None
- nstat.extras.validation.pykalman_bridge.cross_validate_kalman(observations, transition_matrix, observation_matrix, transition_covariance, observation_covariance, initial_state_mean, initial_state_covariance, *, compute_smoother=True)[source]
Fit a linear-Gaussian Kalman filter (+ smoother) in both nstat and pykalman.
- Parameters:
observations (ndarray) – Observation time series, shape
(T, Dy).transition_matrix (ndarray) – State transition matrix \(A\), shape
(Dx, Dx).observation_matrix (ndarray) – Observation matrix \(C\), shape
(Dy, Dx).transition_covariance (ndarray) – Process noise covariance \(Q\), shape
(Dx, Dx).observation_covariance (ndarray) – Measurement noise covariance \(R\), shape
(Dy, Dy).initial_state_mean (ndarray) – Initial state estimate \(\hat{x}_0\), shape
(Dx,).initial_state_covariance (ndarray) – Initial state covariance \(P_0\), shape
(Dx, Dx).compute_smoother (bool) – If
True(default), also fit the RTS smoother in both libraries and populate the smoothed-means fields.
- Return type:
Notes
The bridge calls nstat with the Pythonic keyword signature (
observations=,transition=, …) — seeDecodingAlgorithms.kalman_filter()for the MATLAB-compatible positional alternative.