kipe.roukf module
The reduced-order unscented Kalman filter (ROUKF): its linear algebra.
This module is the filter itself, on plain arrays: it knows no forward solver, no measurements
and no files, so it can be tested on its own. Running it over a study (propagating the sigma
points with the solver, computing the innovations, outer iterations, output) is done in
kipe.estimation, which calls initial_state(), sample() and update().
The method of [MC11], in the formulation and notation of [NAB26]. The filter estimates the
parameters \(\theta \in \mathbb{R}^p\); the model state \(\chi\) (x in the
code; \(n\) entries on this MPI rank) is corrected along, but has no uncertainty of its
own at the start (reduced order: \(L_\chi^0 = 0\)). The uncertainty is carried by the
factors \(L_\chi\) (\(n \times p\)), \(L_\theta\) and \(U\) (both
\(p \times p\)), with the parameter covariance
The sigma points are the estimates perturbed along a fixed SigmaPointStencil
\(I_{(*)}\), whose \(r\) columns \(I_{(i)} \in \mathbb{R}^p\) all have the weight
\(\alpha = 1/r\), with the weighted mean
and \(Y_{(*)}\) the matrix collecting the vectors \(Y_{(i)}\) column-wise.
The estimate and its uncertainty factors form the FilterState. One assimilation step
consists of four stages. Propagation and innovations involve the forward solver and the
measurements and are done in kipe.estimation; sampling and correction are done here.
[NAB26] counts the means of the propagated sigma points to the prediction; here they start
the correction, since they need no solver.
Sampling (
sample()): with \(C\) the lower Cholesky factor of \(U^{-1}\), the sigma points are\[\hat{\chi}_{(i)} = \hat{\chi}_+ + L_\chi C I_{(i)}, \qquad \hat{\theta}_{(i)} = \hat{\theta}_+ + L_\theta C I_{(i)}.\]Some publications, e.g., [Ber12] and [NB22], write \(C^T\) here, with \(C\) a Cholesky factor of \(U^{-1}\): that holds for the upper factor (\(C^T C = U^{-1}\), e.g., MATLAB’s
chol), not for the lower one used here (\(C C^T = U^{-1}\), numpy’scholesky).Propagation: each sigma point is propagated with the forward model to the next measurement time (
kipe.estimation), the parameters stay constant.Innovation: per sigma point, with the spatial sampler and measurement model (
kipe.estimation),\[\Gamma_{(i)} = Z - \mathcal{H}(\hat{\chi}_{(i)}).\]Correction (
update()): the a priori estimates (mean()of the propagated sigma points), theirsensitivity(), thegain()and the corrected (a posteriori) estimates,\[ \begin{align}\begin{aligned}\hat{\chi}_- = E_\alpha(\hat{\chi}_{(*)}), \qquad \hat{\theta}_- = E_\alpha(\hat{\theta}_{(*)}),\\L_\chi = \alpha \hat{\chi}_{(*)} I_{(*)}^T, \qquad L_\theta = \alpha \hat{\theta}_{(*)} I_{(*)}^T, \qquad L_\Gamma = \alpha \Gamma_{(*)} I_{(*)}^T,\\U = P_\alpha + L_\Gamma^T W^{-1} L_\Gamma, \qquad M = U^{-1} L_\Gamma^T W^{-1} E_\alpha(\Gamma_{(*)}),\\\hat{\chi}_+ = \hat{\chi}_- - L_\chi M, \qquad \hat{\theta}_+ = \hat{\theta}_- - L_\theta M.\end{aligned}\end{align} \]
Here the innovations are passed weighted with the noise, \(W^{-1/2} \Gamma_{(i)}\), so
\(W\) does not appear in the code. States are rank-local vectors: the state fields,
concatenated. All functions are pure; only gain() communicates (two small sums over
the ranks).
Moireau, P., & Chapelle, D. (2011). Reduced-order Unscented Kalman Filtering with application to parameter identification in large-dimensional systems. ESAIM: Control, Optimisation and Calculus of Variations, 17(2), 380-405.
Bertoglio, C. (2012). Forward and Inverse Problems in Fluid-Structure Interaction. Application to Hemodynamics (PhD thesis). Université Pierre et Marie Curie - Paris VI.
Nolte, D., & Bertoglio, C. (2022). Inverse problems in blood flow modeling: A review. International Journal for Numerical Methods in Biomedical Engineering, 38(8), e3613. https://doi.org/10.1002/cnm.3613
- class kipe.roukf.SigmaPointStencil(points: NDArray_f64, alpha: float)[source]
Bases:
objectThe stencil \(I_{(*)}\) along which the sigma points are sampled, and its weight.
- points: NDArray_f64
The stencil points \(I_{(i)}\), one per column, shape
(p, r).
- alpha: float
The weight \(\alpha = 1/r\) of every stencil point.
- class kipe.roukf.FilterState(x: NDArray_f64, theta: NDArray_f64, L_x: NDArray_f64, L_theta: NDArray_f64, U_inv: NDArray_f64)[source]
Bases:
objectThe estimate and the factors that carry its uncertainty.
- x: NDArray_f64
State estimate \(\hat{\chi}_+\), shape
(n,).
- theta: NDArray_f64
Parameter estimate \(\hat{\theta}_+\), shape
(p,).
- L_x: NDArray_f64
State sensitivity \(L_\chi\), shape
(n, p).
- L_theta: NDArray_f64
Parameter sensitivity \(L_\theta\), shape
(p, p).
- U_inv: NDArray_f64
\(U^{-1}\), shape
(p, p); zero forunique.
- kipe.roukf.sigma_point_stencil(sigma_points: SigmaPoints, p: int) SigmaPointStencil[source]
Construct the sigma-point stencil of the given kind, after [MC11].
simplex: \(p + 1\) points on a regular simplex; \(P_\alpha = I\).canonical: \(2p\) points \(\pm\sqrt{p}\,e_j\); \(P_\alpha = I\).unique: one point at the origin: no spread, hence no correction. A sanity check that runs the whole pipeline with fixed parameters.
Note
[MC11] also list
star, the canonical points and the origin. Its weights are not given; for \(P_\alpha = I\) and weights summing to one, the origin must have weight zero, so it equalscanonical. Equal weights \(\alpha=1/(2p + 1)\) would give \(P_\alpha = \frac{2p}{2p + 1} I\): the sigma points would represent too small a covariance, at every step.- Parameters:
sigma_points – kind of stencil
p – number of parameters
- Returns:
the stencil
- kipe.roukf.initial_state(x0: NDArray_f64, theta0: NDArray_f64, stddev_theta: NDArray_f64, stencil: SigmaPointStencil) FilterState[source]
Return the initial filter state.
As in [NAB26], the initial factors are
\[L_\chi^0 = 0, \qquad L_\theta^0 = \sqrt{P_\theta^0}, \qquad U^0 = P_\alpha,\]with \(\sqrt{P_\theta^0}\) the Cholesky factor of the diagonal initial covariance, so that \(P_\theta^0 = L_\theta^0 (U^0)^{-1} (L_\theta^0)^T\) for stencils with \(P_\alpha = I\). This differs from [MC11] and [Ber12], which use the inconsistent \((U^0)^{-1} = (P_\theta^0)^{-1/2}\), \(L_\theta^0 = I\).
- Parameters:
x0 – initial state, shape
(n,); known, without uncertainty of its owntheta0 – initial parameter estimate, shape
(p,)stddev_theta – initial standard deviations of \(\theta\), shape
(p,)stencil – the sigma-point stencil
- Returns:
the initial filter state
- kipe.roukf.sample(state: FilterState, stencil: SigmaPointStencil) tuple[NDArray_f64, NDArray_f64][source]
Sample the sigma points around the estimate.
\[\hat{\chi}_{(i)} = \hat{\chi}_+ + L_\chi C I_{(i)}, \qquad \hat{\theta}_{(i)} = \hat{\theta}_+ + L_\theta C I_{(i)},\]with \(C\) the lower Cholesky factor of \(U^{-1}\).
Some publications, e.g., [Ber12] and [NB22], write \(C^T\) here, with \(C\) a Cholesky factor of \(U^{-1}\): that holds for the upper factor (\(C^T C = U^{-1}\), e.g., MATLAB’s
chol), not for the lower one used here (\(C C^T = U^{-1}\), numpy’scholesky).- Parameters:
state – the filter state, around whose estimate the sigma points are sampled
stencil – the sigma-point stencil
- Returns:
sigma-point states, one per column, shape
(n, r)sigma-point parameters, one per column, shape
(p, r)
- kipe.roukf.mean(ensemble: NDArray_f64, stencil: SigmaPointStencil) NDArray_f64[source]
Return the weighted mean \(E_\alpha(Y_{(*)}) = \alpha \sum_i Y_{(i)}\).
- Parameters:
ensemble – one sigma point per column, each of size k (e.g., n for states, p for parameters), shape
(k, r)stencil – the sigma-point stencil
- Returns:
the mean, shape
(k,)
- kipe.roukf.sensitivity(ensemble: NDArray_f64, stencil: SigmaPointStencil) NDArray_f64[source]
Return the sensitivity \(\alpha Y_{(*)} I_{(*)}^T\) of the sigma points.
- Parameters:
ensemble – one sigma point per column, each of size k (e.g., n for states, p for parameters), shape
(k, r)stencil – the sigma-point stencil
- Returns:
the sensitivity, shape
(k, p)
- kipe.roukf.gain(Gamma: NDArray_f64, stencil: SigmaPointStencil, comm: mpi4py.MPI.Comm) tuple[NDArray_f64, NDArray_f64][source]
Compute \(U^{-1}\) and the correction factor \(M\) from the innovations.
\[L_\Gamma = \alpha \Gamma_{(*)} I_{(*)}^T, \qquad U = P_\alpha + L_\Gamma^T L_\Gamma, \qquad M = U^{-1} L_\Gamma^T E_\alpha(\Gamma_{(*)}),\]for innovations weighted with the noise (so \(W = I\)). The products \(L_\Gamma^T L_\Gamma\) and \(L_\Gamma^T E_\alpha(\Gamma_{(*)})\) are sums over the innovation entries, summed over the ranks; each entry must be held by exactly one rank (see
kipe.sampling.SpatialSampler.global_indices).- Parameters:
Gamma – innovations, weighted with the noise, one sigma point per column; the m innovation entries held by this rank (possibly none), shape
(m, r)stencil – the sigma-point stencil
comm – communicator over which the innovation entries are distributed
- Returns:
\(U^{-1}\), shape
(p, p); zero forunique\(M\), shape
(p,)
- kipe.roukf.update(x_sigma: NDArray_f64, theta_sigma: NDArray_f64, Gamma: NDArray_f64, stencil: SigmaPointStencil, comm: mpi4py.MPI.Comm) FilterState[source]
Return the corrected filter state from the propagated sigma points and their innovations.
The correction stage of the assimilation step: the a priori estimates (
mean()), the sensitivities (sensitivity()), thegain()and the corrected estimates.- Parameters:
x_sigma – the propagated sigma-point states, one per column, shape
(n, r)theta_sigma – the sigma-point parameters, one per column, shape
(p, r)Gamma – their innovations, weighted with the noise, see
gain()stencil – the sigma-point stencil
comm – communicator over which the state and innovation entries are distributed
- Returns:
the corrected filter state