Information Filters and SRIF

Information filters represent uncertainty by the inverse of covariance rather than covariance itself. This approach offers superior numerical stability, natural handling of weak measurements, and efficient batch processing. This guide covers the information filter and the Square Root Information Filter (SRIF) as implemented in pytcl.dynamic_estimation.

When to Use Information Filters

Advantages over Kalman Filters:

  1. Numerical Stability: Works well with poorly-conditioned systems

    • No matrix inversion of covariance during the update (direct accumulation of information)

    • Square Root Information Filter is more robust than standard Kalman

  2. Weak Measurement Handling: Graceful degradation when measurements have high uncertainty

    • Kalman gain can become ill-conditioned with high \(\mathbf{R}\)

    • Information filter naturally handles weak or missing measurements

  3. Batch Processing: Natural for off-line data processing and smoothing

  4. Decentralized Systems: Multiple information sources combine trivially (additive information)

  5. Initial Uncertainty: Easy to handle unknown initial state (zero information)

When NOT to use:

  • You need real-time performance and computational resources are limited (Kalman is simpler)

  • Your system is well-conditioned and measurements are always present (standard Kalman is fine)

  • You need fast forward-only filtering of large data streams (Kalman is more standard)

Information Filter Fundamentals

Standard Kalman Filter represents uncertainty as covariance \(\mathbf{P}\):

\[\mathbf{P}_k = E[(\mathbf{x}_k - \hat{\mathbf{x}}_k)(\mathbf{x}_k - \hat{\mathbf{x}}_k)^T]\]

Information Filter represents uncertainty by the information matrix:

\[\mathbf{Y}_k = \mathbf{P}_k^{-1}\]

and the information vector:

\[\mathbf{y}_k = \mathbf{P}_k^{-1} \hat{\mathbf{x}}_k = \mathbf{Y}_k \hat{\mathbf{x}}_k\]

Recover the state estimate via:

\[\hat{\mathbf{x}}_k = \mathbf{Y}_k^{-1} \mathbf{y}_k\]

Information Gain:

When a measurement \(z_k\) arrives with covariance \(\mathbf{R}_k\), the information gain is:

\[\Delta \mathbf{Y}_k = \mathbf{H}_k^T \mathbf{R}_k^{-1} \mathbf{H}_k\]
\[\Delta \mathbf{y}_k = \mathbf{H}_k^T \mathbf{R}_k^{-1} z_k\]

The information is updated additively:

\[\mathbf{Y}_{k|k} = \mathbf{Y}_{k|k-1} + \Delta \mathbf{Y}_k, \qquad \mathbf{y}_{k|k} = \mathbf{y}_{k|k-1} + \Delta \mathbf{y}_k\]

Key Insight: Information matrices add directly (unlike covariances which combine via matrix algebra). This makes decentralized fusion trivial.

Working in Information Form

pytcl provides state_to_information and information_to_state to convert between the two representations:

import numpy as np

from pytcl.dynamic_estimation import (
    information_to_state,
    state_to_information,
)

x = np.array([1.0, 0.5])
P = np.eye(2) * 0.1

y, Y = state_to_information(x, P)
print(y)
# [10.  5.]
print(Y)
# [[10.  0.]
#  [ 0. 10.]]

x_back, P_back = information_to_state(y, Y)
print(x_back)
# [1.  0.5]

Prediction and Update Steps

The prediction step is more complex than in the Kalman filter because it requires inverting the information matrix. For the discrete linear system

\[\mathbf{x}_{k+1} = \mathbf{F}_k \mathbf{x}_k + \mathbf{w}_k, \quad \mathbf{w}_k \sim \mathcal{N}(0, \mathbf{Q}_k)\]

the predicted information is

\[\mathbf{Y}_{k+1|k} = [\mathbf{F}_k \mathbf{Y}_{k|k}^{-1} \mathbf{F}_k^T + \mathbf{Q}_k]^{-1}\]

while the measurement update is purely additive. information_filter_predict and information_filter_update implement both steps:

from pytcl.dynamic_estimation import (
    information_filter_predict,
    information_filter_update,
)

# Constant-velocity model, position-only measurement
dt = 0.1
F = np.array([[1.0, dt], [0.0, 1.0]])
Q = np.eye(2) * 0.01
H = np.array([[1.0, 0.0]])
R = np.array([[0.5]])

y_pred, Y_pred = information_filter_predict(y, Y, F, Q)

z = np.array([1.07])
y_upd, Y_upd = information_filter_update(y_pred, Y_pred, z, H, R)

x_upd, P_upd = information_to_state(y_upd, Y_upd)
print(x_upd)
# [1.05363339 0.50032733]
print(np.sqrt(np.diag(P_upd)))
# [0.30138795 0.33141565]

Filtering a Measurement Sequence

information_filter runs the full predict/update recursion over a list of measurements and returns an InformationFilterResult with both information-form (y_filt, Y_filt) and state-form (x_filt, P_filt) estimates. Use None in the measurement list for missing measurements – a skipped update is simply “no information added”.

from pytcl.dynamic_estimation import information_filter

# 1D position tracking with constant velocity (truth: pos = 0.1 * k)
rng = np.random.default_rng(42)
x0 = np.array([0.0, 1.0])  # pos, vel
P0 = np.eye(2) * 0.1

y0, Y0 = state_to_information(x0, P0)

measurements = [
    np.array([0.1 * (k + 1) + rng.normal(0.0, 0.5)]) for k in range(20)
]

result = information_filter(y0, Y0, measurements, F, Q, H, R)

for k in (0, 5, 10, 15, 19):
    x_k = result.x_filt[k]
    sig = np.sqrt(np.diag(result.P_filt[k]))
    print(
        f"k={k:2d}  x = [{x_k[0]:6.3f}, {x_k[1]:6.3f}]"
        f"  sigma = [{sig[0]:.3f}, {sig[1]:.3f}]"
    )
# k= 0  x = [ 0.128,  1.002]  sigma = [0.301, 0.331]
# k= 5  x = [ 0.411,  0.910]  sigma = [0.291, 0.385]
# k=10  x = [ 1.018,  0.996]  sigma = [0.306, 0.403]
# k=15  x = [ 1.654,  1.060]  sigma = [0.313, 0.403]
# k=19  x = [ 2.056,  1.049]  sigma = [0.315, 0.400]

Unknown Initial State

Information filters excel when the initial state is completely unknown: set \(\mathbf{Y}_0 = 0\) (zero information) and let measurements build up information. The information matrix is rank-deficient until enough independent measurements have constrained every state component:

y0_unknown = np.zeros(2)
Y0_unknown = np.zeros((2, 2))  # No prior information at all

result_u = information_filter(
    y0_unknown, Y0_unknown, measurements, F, Q, H, R
)

print(result_u.x_filt[0], np.linalg.matrix_rank(result_u.Y_filt[0]))
# [0.25235854 0.        ] 1
print(result_u.x_filt[2], np.linalg.matrix_rank(result_u.Y_filt[2]))
# [0.41570303 2.11459482] 2

After one position-only measurement the information matrix has rank 1 – position is constrained but velocity is not. By the third measurement the rank is full and a (still noisy) velocity estimate exists. A covariance-form Kalman filter cannot represent “no prior” exactly; it must approximate it with a large \(\mathbf{P}_0\).

Square Root Information Filter (SRIF)

Standard information filters invert matrices at each predict step, which can be numerically delicate. The SRIF instead carries the square root of the information matrix, \(\mathbf{R}\) with \(\mathbf{R}^T \mathbf{R} = \mathbf{Y}\), and the transformed information vector \(\mathbf{r} = \mathbf{R} \hat{\mathbf{x}}\).

The measurement update stacks the prior square root with the whitened measurement and triangularizes by QR decomposition:

\[\begin{split}\begin{bmatrix} \mathbf{R}_{k|k-1} & \mathbf{r}_{k|k-1} \\ \mathbf{R}_{z}^{-T} \mathbf{H}_k & \mathbf{R}_{z}^{-T} z_k \end{bmatrix} \xrightarrow{\text{QR}} \begin{bmatrix} \mathbf{R}_{k|k} & \mathbf{r}_{k|k} \\ 0 & e_k \end{bmatrix}\end{split}\]

where \(\mathbf{R}_z\) is a Cholesky factor of the measurement noise covariance. No information matrix is ever formed explicitly during the update, and the effective condition number is the square root of the full-matrix version.

pytcl ships srif_predict, srif_update, and the batch driver srif_filter (returning an SRIFResult with r_filt, R_filt, x_filt, P_filt). Note that the current srif_predict implementation propagates through covariance space rather than triangularizing the stacked square-root arrays directly – see its docstring for details.

from pytcl.dynamic_estimation import srif_filter

# SRIF initial condition: R0.T @ R0 = inv(P0), r0 = R0 @ x0
R0 = np.linalg.cholesky(np.linalg.inv(P0)).T
r0 = R0 @ x0

result_srif = srif_filter(r0, R0, measurements, F, Q, H, R)

diff = max(
    np.max(np.abs(a - b))
    for a, b in zip(result_srif.x_filt, result.x_filt)
)
print(f"max |x_srif - x_if| = {diff:.2e}")
# max |x_srif - x_if| = 1.33e-15

print(result_srif.x_filt[-1])
# [2.05631183 1.04852902]

On this well-conditioned toy problem both filters agree to machine precision; the SRIF’s advantage appears on poorly-conditioned problems (large dynamic range in state uncertainties, nearly-singular measurement geometry).

Related square-root variants. pytcl.dynamic_estimation also provides square-root and factorized forms of the covariance filter: srkf_predict / srkf_update / srkf_predict_update (square-root Kalman filter propagating a Cholesky factor of \(\mathbf{P}\)) and ud_factorize / ud_predict / ud_update / ud_reconstruct (Bierman-Thornton U-D factorization). These serve the same numerical-robustness goal from the covariance side.

Multi-Sensor Fusion

Because information is additive, independent sensors can be processed one at a time with information_filter_update – each scalar sensor contributes \(\mathbf{h}^T z / r\) to the information vector and \(\mathbf{h}^T \mathbf{h} / r\) to the information matrix:

# 2D localization with 3 independent scalar sensors
x0_fusion = np.array([0.0, 0.0])  # [x, y]
P0_fusion = np.eye(2) * 10.0

y_cur, Y_cur = state_to_information(x0_fusion, P0_fusion)

sensors = [
    # (measurement, measurement row, variance)
    (1.1, np.array([[1.0, 0.0]]), 0.5),  # x position
    (0.9, np.array([[0.0, 1.0]]), 0.5),  # y position
    (1.4, np.array([[1.0, 1.0]]) / np.sqrt(2.0), 0.8),  # diagonal range
]

for z_s, H_s, r_s in sensors:
    y_cur, Y_cur = information_filter_update(
        y_cur, Y_cur, np.array([z_s]), H_s, np.array([[r_s]])
    )

x_est, P_est = information_to_state(y_cur, Y_cur)
print(x_est)
# [1.06163716 0.87116097]
print(np.sqrt(np.diag(P_est)))
# [0.62237366 0.62237366]

For decentralized architectures where each node ships its information contribution to a fusion center, fuse_information adds a list of InformationState tuples in one call:

from pytcl.dynamic_estimation import InformationState, fuse_information

y_prior, Y_prior = state_to_information(x0_fusion, P0_fusion)

contributions = [InformationState(y=y_prior, Y=Y_prior)]
for z_s, H_s, r_s in sensors:
    contributions.append(
        InformationState(
            y=H_s.T @ np.array([z_s]) / r_s,
            Y=H_s.T @ H_s / r_s,
        )
    )

fused = fuse_information(contributions)
x_fused, P_fused = information_to_state(fused.y, fused.Y)
print(x_fused)
# [1.06163716 0.87116097]

The one-shot fused result is identical to the sequential updates – addition is order-independent.

Comparison: Information Filter vs Kalman Filter

Numerical Behavior:

Aspect

Kalman Filter

Information Filter

State representation

x-hat, P (covariance)

y, Y (information)

Measurement update

Multiplicative (K)

Additive (Y, y)

Prediction step

Direct (P_pred)

Requires inversion

Weak measurement (R -> inf)

Kalman gain -> 0

Information -> 0

Unknown initial state

Approximate (large P)

Exact (Y = 0)

Numerical stability

Good

Better (SRIF)

Batch processing

Awkward

Natural

Decentralized fusion

Complex

Trivial (add info)

When Information Filter Excels:

  1. Unknown initial state: Set \(\mathbf{Y}_0 = 0\) (zero information)

  2. Intermittent measurements: Missing measurements = no information update (pass None)

  3. Batch smoothing: Natural representation for all-at-once processing

  4. Multi-source fusion: Information from all sensors adds directly

Diagnostics

The information matrix itself is the primary health indicator. Monitor its rank (are all states observable yet?) and condition number (is state recovery numerically safe?):

Y_final = result.Y_filt[-1]
rank = np.linalg.matrix_rank(Y_final)
cond = np.linalg.cond(Y_final)
print(f"rank {rank} of {Y_final.shape[0]}, condition number {cond:.2f}")
# rank 2 of 2, condition number 3.49

A rank-deficient \(\mathbf{Y}\) means some state directions are unconstrained and information_to_state cannot recover a unique estimate. A large condition number warns that the recovered covariance is unreliable – switch to the SRIF.

Tuning Guidelines

Choose Information Filter When:

  1. System is poorly conditioned (high aspect ratios in state/measurement spaces)

  2. Multiple sensors with different measurement rates/uncertainties

  3. Batch processing or offline smoothing

  4. Unknown or weak initial state

  5. Need numerical robustness

Parameter Selection:

  • Weak initial information: instead of exactly zero, \(\mathbf{Y}_0 = \epsilon \mathbf{I}\) with \(\epsilon = 10^{-6}\) to \(10^{-8}\) keeps early state recovery well-posed

  • SRIF vs Standard IF: use srif_filter whenever numerics are critical

  • Regularization: add \(\delta \mathbf{I}\) to prevent singularity:

    \[\mathbf{Y}_k \leftarrow \mathbf{Y}_k + \delta \mathbf{I}, \quad \delta = 10^{-10}\]

Common Pitfalls

  1. Singular Information Matrix: Measurements don’t constrain all states

    • Fix: Monitor rank, add weak priors, ensure observable states

  2. Numerical Inversion: Computing \(\mathbf{Y}^{-1}\) becomes ill-conditioned

    • Fix: Use the SRIF or regularize (add a small \(\delta \mathbf{I}\))

  3. Unbounded Information Accumulation: With negligible process noise, information grows without bound

    • Fix: Model realistic process noise; monitor condition numbers

  4. Costly Prediction Step: The predict step requires inversion, unlike the Kalman form

    • Fix: Hybrid approach (Kalman forward, information form for fusion/batch steps)

See Also

References:

  • Bierman (1977) – Factorization Methods for Discrete Sequential Estimation – Foundational SRIF work

  • Bar-Shalom, Li, Kirubarajan (2001) – Estimation with Applications to Tracking and Navigation

  • Kailath, Sayed, Hassibi (2000) – Linear Estimation – Comprehensive treatment with information filters

  • Dyer & McReynolds (1969) – Extension of Square Root Filtering to Include Process Noise – Original SRIF

  • Mutambara (1998) – Decentralized Estimation and Control for Multisensor Systems