Skip to main content

Kernel Koopman Kalman Filter (KKF)

Tests PyPI version License: MIT Python 3.8+

A Python library for nonlinear state estimation using kernel-based Extended Dynamic Mode Decomposition (kEDMD) and Koopman operator theory.

What is KKF?

The Kalman filter is optimal for linear systems but degrades when the dynamics are strongly nonlinear. KKF lifts the state into a feature space where the dynamics are approximately linear, runs a Kalman filter there, and maps the estimate back. The pieces are:

  • Koopman operator theory — represent nonlinear dynamics as a linear operator acting on observables.
  • Kernel methods — build the feature space from a kernel (RBF, Matérn, ...).
  • kEDMD — estimate the linear operator from samples of the dynamics.
  • Kalman filtering — propagate the mean and covariance in feature space.

Features

  • Kernel Extended Dynamic Mode Decomposition (kEDMD)
  • A Kalman filter that operates in the lifted feature space
  • Works with arbitrary user-supplied dynamics f and observation g
  • Choice of kernel through scikit-learn's gaussian_process.kernels
  • Posterior covariance in both state and feature space, with optional kernel-parameter optimization
  • Type hints throughout, and unit/integration tests run in CI

Installation

pip install kkf

From source, for development:

git clone https://github.com/diegoolguinw/kkf.git
cd kkf
pip install -e ".[dev]"

Optional dependency groups:

pip install kkf[viz]    # plotting for the examples
pip install kkf[dev]    # tests and linters
pip install kkf[docs]   # documentation build
pip install kkf[all]    # everything

Quick Start

The example below estimates the states of an SIR (Susceptible-Infected-Recovered) epidemic model from noisy observations of the infected fraction. A runnable version is in examples/02_sir_epidemic_model.py.

import numpy as np
from scipy import stats
from sklearn.gaussian_process.kernels import Matern

from kkf import DynamicalSystem, KoopmanOperator, apply_koopman_kalman_filter

# SIR dynamics (discrete time)
beta, gamma = 0.12, 0.04

def f(x):  # state transition
    return x + np.array([
        -beta * x[0] * x[1],
        beta * x[0] * x[1] - gamma * x[1],
        gamma * x[1],
    ])

def g(x):  # observe the infected fraction
    return np.array([x[1]])

# Dimensions and noise model
nx, ny, n_features = 3, 1, 50
X_dist = stats.dirichlet(alpha=np.ones(nx))
dyn_dist = stats.multivariate_normal(mean=np.zeros(nx), cov=1e-5 * np.eye(nx))
obs_dist = stats.multivariate_normal(mean=np.zeros(ny), cov=1e-3 * np.eye(ny))

system = DynamicalSystem(nx, ny, f, g, X_dist, dyn_dist, obs_dist, discrete_time=True)

# Synthetic observations of the infected fraction
np.random.seed(42)
n_timesteps = 100
y_obs = np.random.randn(n_timesteps, ny) * 0.1 + 0.1

# Build the Koopman operator and run the filter
kernel = Matern(length_scale=1.0, nu=0.5)
koopman_op = KoopmanOperator(kernel, system)

x0_prior = np.array([0.8, 0.15, 0.05])
initial_prior = stats.multivariate_normal(mean=x0_prior, cov=0.1 * np.eye(nx))

solution = apply_koopman_kalman_filter(
    koopman_operator=koopman_op,
    observations=y_obs,
    initial_distribution=initial_prior,
    n_features=n_features,
    optimize=False,
    noise_samples=100,
)

# Posterior estimates and covariances at each step
print(solution.x_plus.shape)    # (n_timesteps, nx)
print(solution.Px_plus.shape)   # (n_timesteps, nx, nx)

solution.x_plus / solution.x_minus are the posterior / prior state estimates, and solution.Px_plus / solution.Px_minus the corresponding covariances.

Examples

The examples/ directory contains three runnable scripts:

Run any of them with, e.g., python examples/02_sir_epidemic_model.py. See examples/README.md for more.

API at a glance

from kkf import (
    DynamicalSystem,
    KoopmanOperator,
    KoopmanKalmanFilterSolution,
    apply_koopman_kalman_filter,
    compute_initial_covariance,
    compute_dynamics_covariance,
    compute_observation_covariance,
)

The algorithm proceeds in three steps:

  1. Lift the state into feature space using the kernel.
  2. Approximate the linear dynamics in that space with kEDMD.
  3. Filter with a Kalman filter in feature space, then project the estimate and covariance back to the state coordinates.

Main arguments to apply_koopman_kalman_filter:

Parameter Description Default
koopman_operator A KoopmanOperator built from your kernel and system required
observations Measurements, shape (n_timesteps, n_outputs) required
initial_distribution Prior over the initial state (a scipy.stats distribution) required
n_features Dimension of the lifted feature space required
optimize Optimize kernel parameters during fitting False
n_restarts_optimizer Optimizer restarts (used only when optimize=True) 10
noise_samples Monte Carlo samples for covariance propagation 100

The kernel is not passed here — choose it when constructing the KoopmanOperator (see Common variations below).

Requirements

  • Python 3.8+
  • NumPy ≥ 1.20
  • SciPy ≥ 1.7
  • scikit-learn ≥ 1.0
  • Matplotlib ≥ 3.5 (optional, only for the example plots)

Common variations

Different kernels:

from sklearn.gaussian_process.kernels import (
    RBF, Matern, ExpSineSquared, ConstantKernel,
)

kernel = RBF(length_scale=1.0)
kernel = Matern(length_scale=1.0, nu=2.5)
kernel = ExpSineSquared(length_scale=1.0, periodicity=1.0)
kernel = ConstantKernel(1.0) * Matern(nu=1.5)

Optimizing the kernel parameters:

solution = apply_koopman_kalman_filter(
    koopman_operator=koopman_op,
    observations=y_obs,
    initial_distribution=initial_prior,
    n_features=50,
    optimize=True,
    n_restarts_optimizer=20,
)

Confidence intervals from the posterior covariance:

std = np.sqrt(np.diagonal(solution.Px_plus, axis1=1, axis2=2))
ci_lower = solution.x_plus - 1.96 * std
ci_upper = solution.x_plus + 1.96 * std

Testing

pytest tests/                 # all tests
pytest tests/ -v              # verbose
pytest tests/ -m "not slow"   # skip slow tests
pytest tests/ --cov=kkf --cov-report=html

Contributing

Contributions are welcome — see CONTRIBUTING.md. This project follows the Contributor Code of Conduct.

Citation

@software{kkf2024,
  title  = {KKF: Kernel Koopman Kalman Filter},
  author = {Olguin-Wende, Diego},
  year   = {2024},
  url    = {https://github.com/diegoolguinw/kkf}
}

License

MIT — see LICENSE.

Contact

Metadata

Release files for kkf 1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kkf 1.0
File Size Uploaded Provenance Actions
kkf-1.0.tar.gz 21.7 kB With provenance Details for kkf-1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for kkf 1.0
File Interpreter ABI Platform Actions
kkf-1.0-py3-none-any.whl Python 3 none any Details for kkf-1.0-py3-none-any.whl

Total release size: 37.4 kB

Release files / kkf-1.0.tar.gz

Download URL kkf-1.0.tar.gz
Size 21.7 kB
Tags Source
SHA-256 checksum
How to use checksums
06ff0ed2ff79123fae8e35b030773af5dd10a8f63deb24db39ec4ffb0ce6d56e
BLAKE2b-256 checksum
How to use checksums
4af0fd4544d1956280d2d250ab364e8855f07ece958c0d17ea66604ea9017073
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 1, 2026.

Transparency log

Release files / kkf-1.0-py3-none-any.whl

Download URL kkf-1.0-py3-none-any.whl
Size 15.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8d7e49383aa95bb19f1f6e3b671502da0608b90139281592bc229fade88030a6
BLAKE2b-256 checksum
How to use checksums
1647d97b8a79e00bc6a73e1a9a321d28d4fdd4e97604a0782d190007b4c441fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 1, 2026.

Transparency log

Security for kkf 1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for kkf-1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page