Metadata-Version: 2.4
Name: pyestimationtoolbox
Version: 0.1.0
Summary: Composable Gaussian state estimators and interacting multiple-model estimation
Author: Mohamed Elsherbiny
License-Expression: MIT
Project-URL: Repository, https://github.com/Mohamed-Elsherbiny/pyestimationtoolbox
Project-URL: Issues, https://github.com/Mohamed-Elsherbiny/pyestimationtoolbox/issues
Keywords: estimation,kalman-filter,ekf,imm,sensor-fusion
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Dynamic: license-file

# PyEstimationToolbox

PyEstimationToolbox is a small, composable Python library for Gaussian state
estimation. It provides:

- a linear Kalman filter (KF);
- an extended Kalman filter (EKF);
- a filter-agnostic interacting multiple-model (IMM) estimator;
- constant, callable, and continuous-time Markov transition models;
- time-varying process, measurement, noise, and Jacobian interfaces;
- masked measurements for missing or asynchronous sensor values.

The IMM depends on the `GaussianModeFilter` protocol rather than concrete KF or
EKF classes. New Gaussian filters can therefore be added without modifying the
IMM algorithm.

## Installation

```bash
python -m pip install .
```

For development:

```bash
python -m pip install -e ".[dev]"
pytest
```

## Minimal time-varying KF

```python
import numpy as np

from pyestimationtoolbox import (
    CallableLinearMeasurement,
    CallableLinearProcess,
    GaussianState,
    KalmanFilter,
    LinearMeasurementMatrices,
    LinearProcessMatrices,
    Measurement,
    StepContext,
)


def process(context: StepContext) -> LinearProcessMatrices:
    dt = context.dt
    return LinearProcessMatrices(
        transition=np.array([[1.0, dt], [0.0, 1.0]]),
        noise=np.diag([dt**3 / 3.0, dt]),
    )


def sensor(_context: StepContext) -> LinearMeasurementMatrices:
    return LinearMeasurementMatrices(
        observation=np.array([[1.0, 0.0]]),
        noise=np.array([[0.25]]),
    )


kf = KalmanFilter(
    process_model=CallableLinearProcess(process),
    measurement_model=CallableLinearMeasurement(sensor),
    initial_state=GaussianState(np.zeros(2), np.eye(2)),
)

context = StepContext(time=0.1, dt=0.1)
kf.predict(context)
update = kf.update(Measurement([1.2]), context)
print(update.posterior.mean)
```

## IMM composition

```python
from pyestimationtoolbox import ConstantTransition, IMMEstimator, IMMMode

imm = IMMEstimator(
    modes=[
        IMMMode("nominal", nominal_filter),
        IMMMode("maneuvering", maneuvering_filter),
    ],
    transition_model=ConstantTransition([[0.98, 0.02], [0.05, 0.95]]),
    initial_probabilities=[0.8, 0.2],
)

result = imm.step(measurement, context)
print(result.posterior.mean)
print(result.mode_probabilities)
```

The two mode filters may use different algorithms, such as a KF and an EKF,
provided both satisfy `GaussianModeFilter` and use the same common state
coordinates. See
[`examples/switching_sensor_models.py`](examples/switching_sensor_models.py)
for a complete mixed KF/EKF example with nonuniform sample times.

## Design contract

The first release deliberately supports Gaussian mode-conditioned filters with
a shared state dimension. A mode filter must expose its posterior, accept a
mixed Gaussian posterior, and return a measurement log-likelihood. This covers
KF, EKF, UKF, CKF, and square-root variants while keeping the IMM mathematically
well-defined.

The transition-matrix convention is:

```text
Pi[i, j] = P(mode at k is j | mode at k-1 was i)
```

Rows must therefore sum to one.

## License

MIT

