Skip to content

Filter Diagnostics

Real-time monitoring and reporting for Kalman filter performance. Track NIS, NEES, innovation statistics, and covariance health during filter execution.

FilterDiagnostics

Collects filter metrics at each time step and generates summary reports.

Setup

from kalbee import KalmanFilter, FilterDiagnostics

# Create filter
kf = KalmanFilter(state, cov, F, Q, H, R)

# Create diagnostics (m=measurement dim, n=state dim)
diag = FilterDiagnostics(m=1, n=2, alpha=0.05)

Collecting Metrics

Call collect() after each predict-update cycle:

for z in measurements:
    kf.predict()
    kf.update(z)

    # Collect diagnostics (optionally provide ground truth for NEES)
    snapshot = diag.collect(kf, measurement=z, ground_truth=true_state)

    print(f"Step {snapshot.timestamp}: NIS={snapshot.nis:.3f}, "
          f"Cov trace={snapshot.state_cov_trace:.4f}")

Summary Report

summary = diag.summary()
print(summary)
# {
#     'num_steps': 200,
#     'nis_mean': 1.05,
#     'nis_std': 1.42,
#     'nis_expected': 1.0,
#     'cov_trace_final': 0.234,
#     'cov_trace_mean': 0.567,
#     'nis_test_passed': True,
#     'nis_test_p_value': 0.342,
# }

Consistency Check

consistency = diag.check_consistency()
print(consistency)
# {
#     'nis_consistent': True,
#     'nis_mean': 1.05,
#     'nis_in_range': True,
# }

Accessing History

# Get all innovations as (T, m) array
innovations = diag.get_innovations()

# Get NIS values as array
nis_values = diag.get_nis_values()

# Reset diagnostics
diag.reset()

FilterSnapshot

Each collect() call returns a FilterSnapshot dataclass:

@dataclass
class FilterSnapshot:
    timestamp: int
    state_mean: np.ndarray       # Current state estimate
    state_cov_trace: float       # Trace of covariance (overall uncertainty)
    innovation: Optional[np.ndarray]
    innovation_cov: Optional[np.ndarray]
    nis: Optional[float]         # Normalized Innovation Squared
    nees: Optional[float]        # Normalized Estimation Error Squared
    kalman_gain_norm: Optional[float]

Complete Example

import numpy as np
from kalbee import KalmanFilter, FilterDiagnostics

# Setup
state = np.zeros((2, 1))
cov = np.eye(2) * 10.0
F = np.array([[1, 1], [0, 1]])
Q = np.eye(2) * 0.01
H = np.array([[1, 0]])
R = np.array([[0.5]])

kf = KalmanFilter(state, cov, F, Q, H, R)
diag = FilterDiagnostics(m=1, n=2)

# Generate data
np.random.seed(42)
T = 100
true_states = []
measurements = []

for k in range(T):
    true_state = np.array([[k * 0.1], [0.1]])
    true_states.append(true_state)
    measurements.append(
        H @ true_state + np.random.randn(1, 1) * np.sqrt(R[0, 0])
    )

# Run filter with diagnostics
for k, z in enumerate(measurements):
    kf.predict()
    kf.update(z)
    snapshot = diag.collect(kf, ground_truth=true_states[k])

# Analyze results
summary = diag.summary()
print(f"Steps: {summary['num_steps']}")
print(f"Mean NIS: {summary['nis_mean']:.3f} (expected: {summary['nis_expected']:.0f})")
print(f"Covariance trace (final): {summary['cov_trace_final']:.4f}")
print(f"Consistent: {summary.get('nis_test_passed', 'N/A')}")