Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/ISSUE_TEMPLATE/numerical_issue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: Numerical issue
description: Report instability, invalid covariance, inconsistent likelihoods, or backend numerical divergence
title: "Numerical issue: "
labels: ["numerical", "bug"]
---

## Summary

## Minimal reproducer

```python
# Include imports, backend selection, seed, and data.
```

## Expected behavior

## Observed behavior

## Backend and environment

Run `pyrecest info` and paste the output.

## Notes

Mention whether the issue involves covariance symmetry/PSD, Cholesky failures,
particle degeneracy, assignment ambiguity, or backend dtype/device behavior.
45 changes: 45 additions & 0 deletions .github/workflows/docs-examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Documentation examples

permissions:
contents: read

on:
pull_request:
branches:
- "**"
workflow_dispatch:

jobs:
doc-examples:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"

- name: Install package for documentation examples
run: |
python -m pip install --upgrade pip
python -m pip install poetry
poetry env use python
poetry install --with dev --extras "healpy_support"

- name: Run README Python examples
run: poetry run python scripts/run_doc_examples.py README.md --fail-fast
env:
PYTHONPATH: ${{ github.workspace }}/src

- name: Generate compatibility dashboard preview
run: poetry run python scripts/generate_compatibility_dashboard.py --output compatibility-dashboard.md
env:
PYTHONPATH: ${{ github.workspace }}/src

- name: Upload dashboard preview
uses: actions/upload-artifact@v7
with:
name: compatibility-dashboard-preview
path: compatibility-dashboard.md
33 changes: 33 additions & 0 deletions .github/workflows/release-notes-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Release notes preview

permissions:
contents: read

on:
pull_request:
branches:
- "**"
workflow_dispatch:

jobs:
release-notes-preview:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"

- name: Generate release notes from PR branch commits
run: python scripts/generate_release_notes.py "origin/${{ github.base_ref || 'main' }}..HEAD" --output release-notes-preview.md

- name: Upload release notes preview
uses: actions/upload-artifact@v7
with:
name: release-notes-preview
path: release-notes-preview.md
71 changes: 60 additions & 11 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,20 @@ jobs:

- name: Install documentation dependencies
run: |
retry() {
for attempt in 1 2 3; do
"$@" && return 0
echo "Command failed on attempt ${attempt}; retrying..."
sleep $((attempt * 20))
done

"$@"
}

python -m pip install --upgrade pip
python -m pip install poetry
retry python -m pip install poetry
poetry env use python
poetry install --only docs --no-root
retry poetry install --only docs --no-root

- name: Build documentation
run: poetry run mkdocs build --strict
Expand Down Expand Up @@ -74,7 +84,18 @@ jobs:

- name: Build distributions and validate local release metadata
run: |
python -m pip install --upgrade pip build twine
retry() {
for attempt in 1 2 3; do
"$@" && return 0
echo "Command failed on attempt ${attempt}; retrying..."
sleep $((attempt * 20))
done

"$@"
}

python -m pip install --upgrade pip
retry python -m pip install build twine
python scripts/check_release_consistency.py --local-only
python -m build
python -m twine check dist/*
Expand Down Expand Up @@ -156,18 +177,32 @@ jobs:

- name: Install dependencies
run: |
retry() {
for attempt in 1 2 3; do
"$@" && return 0
echo "Command failed on attempt ${attempt}; retrying..."
sleep $((attempt * 20))
done

"$@"
}

python -m pip install --upgrade pip
python -m pip install poetry
retry python -m pip install poetry
poetry env use python
case "${{ matrix.backend }}" in
numpy)
poetry install --with dev --extras "healpy_support"
retry poetry install --with dev --extras "healpy_support"
;;
pytorch)
poetry install --with dev --extras "healpy_support pytorch_support"
retry poetry install --with dev --extras "healpy_support"
retry poetry run python -m pip install \
--index-url https://download.pytorch.org/whl/cpu \
--extra-index-url https://pypi.org/simple \
"torch>=2.4,<3.0"
;;
jax)
poetry install --with dev --extras "healpy_support jax_support"
retry poetry install --with dev --extras "healpy_support jax_support"
;;
*)
echo "Unsupported backend: ${{ matrix.backend }}" >&2
Expand Down Expand Up @@ -246,18 +281,32 @@ jobs:

- name: Install dependencies
run: |
retry() {
for attempt in 1 2 3; do
"$@" && return 0
echo "Command failed on attempt ${attempt}; retrying..."
sleep $((attempt * 20))
done

"$@"
}

python -m pip install --upgrade pip
python -m pip install poetry
retry python -m pip install poetry
poetry env use python
case "${{ matrix.backend }}" in
numpy)
poetry install --with dev --extras "healpy_support"
retry poetry install --with dev --extras "healpy_support"
;;
pytorch)
poetry install --with dev --extras "healpy_support pytorch_support"
retry poetry install --with dev --extras "healpy_support"
retry poetry run python -m pip install \
--index-url https://download.pytorch.org/whl/cpu \
--extra-index-url https://pypi.org/simple \
"torch>=2.4,<3.0"
;;
jax)
poetry install --with dev --extras "healpy_support jax_support"
retry poetry install --with dev --extras "healpy_support jax_support"
;;
*)
echo "Unsupported backend: ${{ matrix.backend }}" >&2
Expand Down
4 changes: 3 additions & 1 deletion .secretlintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
megalinter-reports
/github/workspace/github_conf/branch_protection_rules.json
/github/workspace/github_conf/branch_protection_rules.json
*.isorted
**/*.isorted
12 changes: 12 additions & 0 deletions asv.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 1,
"project": "PyRecEst",
"project_url": "https://github.com/FlorianPfaff/PyRecEst",
"repo": ".",
"branches": ["main"],
"pythons": ["3.11", "3.12", "3.13"],
"benchmark_dir": "benchmarks/asv_benchmarks",
"env_dir": ".asv/env",
"results_dir": ".asv/results",
"html_dir": ".asv/html"
}
17 changes: 17 additions & 0 deletions benchmarks/asv_benchmarks/bench_kalman.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from pyrecest.backend import array, diag
from pyrecest.filters import KalmanFilter


class KalmanFilterBenchmarks:
def setup(self):
self.system_matrix = array([[1.0, 1.0], [0.0, 1.0]])
self.measurement_matrix = array([[1.0, 0.0]])
self.system_noise_cov = diag(array([0.05, 0.01]))
self.measurement_noise_cov = array([[0.25]])
self.measurement = array([1.0])

def time_predict_update_loop(self):
filt = KalmanFilter((array([0.0, 1.0]), diag(array([1.0, 1.0]))))
for _ in range(100):
filt.predict_linear(self.system_matrix, self.system_noise_cov)
filt.update_linear(self.measurement, self.measurement_matrix, self.measurement_noise_cov)
55 changes: 37 additions & 18 deletions docs/backend-api-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,47 @@ python scripts/render_backend_api_matrix.py

## Support Levels

| Level | Meaning |
|---------------|-------------------------------------------------------------------------------------------------------------------------------|
| `supported` | Intended to preserve backend semantics for the listed API. |
| `partial` | Numerically useful, but with documented limitations such as SciPy bridges, CPU copies, or missing gradient/device guarantees. |
| `unsupported` | Should raise a clear `NotImplementedError` or be documented as unavailable for the backend. |
| Level | Meaning |
| ------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `supported` | Intended to preserve backend semantics for the listed API. |
| `bridged` | Works by crossing into another numerical stack, usually NumPy/SciPy; do not assume device, dtype, or gradient preservation. |
| `partial` | Numerically useful, but with documented limitations such as missing modes or mixed native/bridged implementation. |
| `unsupported` | Should raise a clear `NotImplementedError` or be documented as unavailable for the backend. |

## Public API Rows

| API | NumPy | PyTorch | JAX | Notes |
|--------------------------------|-----------|-------------|-------------|--------------------------------------------------------------------------------------------------------------|
| `KalmanFilter` | supported | supported | supported | Linear Gaussian operations are the portable baseline. |
| `UKFOnManifolds` | supported | partial | unsupported | JAX exclusions are currently explicit. |
| `SphericalHarmonicsEOTTracker` | supported | unsupported | unsupported | Depends on spherical harmonics and SciPy-adjacent functionality. |
| `GaussianDistribution` | supported | supported | supported | Basic construction and portable operations should stay backend portable. |
| `LinearDiracDistribution` | supported | supported | supported | Used by conversion and particle-style workflows. |
| `UnscentedKalmanFilter` | supported | partial | partial | Portable for backend-compatible model functions; advanced paths may still bridge through NumPy/SciPy. |
| `EuclideanParticleFilter` | supported | partial | partial | Particle operations are portable where sampling and resampling helpers preserve backend semantics. |
| `DistributionConversion` | supported | partial | partial | Euclidean particle/Gaussian conversions are portable; grid, Fourier, and manifold routes are route-specific. |
| `MultiBernoulliTracker` | supported | partial | unsupported | Tracking workflows rely on assignment and measurement-set utilities that are currently NumPy-oriented. |
| `PointSetRegistration` | supported | partial | unsupported | Registration utilities may copy through NumPy/SciPy and should not be assumed differentiable. |
| `EvaluationUtilities` | supported | partial | partial | Plotting, assignment, and summaries remain partly NumPy/SciPy oriented. |
| API | NumPy | PyTorch | JAX | Notes |
| ------------------------------ | --------- | ----------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `BackendFacade` | supported | partial | partial | Facade names are importable across backends, but some functions are bridged or explicitly unsupported. |
| `DistributionConversion` | supported | partial | partial | Euclidean particle/Gaussian conversions are portable; grid, Fourier, and manifold routes are route-specific. |
| `EuclideanParticleFilter` | supported | partial | partial | Particle operations are portable where sampling and resampling helpers preserve backend semantics. |
| `EvaluationUtilities` | supported | bridged | bridged | Some plotting, assignment, and summary operations remain NumPy/SciPy oriented and may not preserve device or gradient semantics. |
| `GaussianDistribution` | supported | supported | supported | Basic construction, moment access, and portable operations should remain backend portable. |
| `KalmanFilter` | supported | supported | supported | Linear Gaussian operations are part of the portable baseline. |
| `LinearDiracDistribution` | supported | supported | supported | Used by representation conversion and particle-style workflows. |
| `MultiBernoulliTracker` | supported | partial | unsupported | Tracking workflows rely on assignment and measurement-set utilities that are currently NumPy-oriented. |
| `PointSetRegistration` | supported | partial | unsupported | Registration utilities may copy through NumPy/SciPy and should not be assumed differentiable. |
| `SphericalHarmonicsEOTTracker` | supported | unsupported | unsupported | Depends on spherical harmonics and SciPy-adjacent functionality. |
| `UKFOnManifolds` | supported | partial | unsupported | The current implementation documents explicit JAX exclusions for predict/update. |
| `UnscentedKalmanFilter` | supported | partial | partial | Portable for backend-compatible model functions; advanced paths may still bridge through NumPy/SciPy. |

When adding a new public API, add a row to the matrix, update docs if the row is
user-facing, and add a focused backend test if the API is expected to be
portable.

## Runtime Access

Use the public helper when examples or downstream packages need to inspect
backend support without duplicating the table:

```python
from pyrecest import get_backend_support

assert get_backend_support("KalmanFilter", backend="jax") == "supported"
```

The CLI can also render the matrix:

```bash
pyrecest backends --format markdown
```
15 changes: 15 additions & 0 deletions docs/change-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Change Management

Broad improvements are easier to review and debug when split into independent
change sets. Prefer a series of focused PRs over one all-encompassing PR.

## Review Checklist

- The public API and stability category are clear.
- Backend behavior is declared as supported, bridged, partial, or unsupported.
- Numerical validation policy is explicit.
- User-facing examples or docs were updated.
- A small regression or scenario test protects the behavior.
- Release notes can describe the user-visible change in one sentence.

When a change touches multiple rows in this checklist, consider splitting it.
22 changes: 22 additions & 0 deletions docs/compatibility-dashboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Compatibility Dashboard

This page is a checked-in snapshot of the compatibility dimensions that should
also be generated in CI with `scripts/generate_compatibility_dashboard.py`.

## Python Versions

PyRecEst declares support for Python `>=3.11,<3.15`.

## Backend Support

Run the following command to generate the current public backend API matrix:

```bash
pyrecest backends --format markdown
```

## Scenario Coverage

At minimum, keep one linear-Gaussian scenario executable as a release smoke test.
As the scenario zoo grows, this page should list which scenarios run under each
backend and Python version.
25 changes: 25 additions & 0 deletions docs/flagship-example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Flagship Example: Multi-target Tracking With Clutter

A flagship example should demonstrate why PyRecEst is useful beyond a minimal
Kalman-filter snippet. The recommended flagship is a compact multi-target
tracking scenario with missed detections and clutter.

## What It Should Show

1. A clear generative model for target motion and measurements.
2. A reusable transition model and measurement model.
3. Gating and association diagnostics.
4. Track lifecycle behavior across several time steps.
5. A plot or tabular summary of estimates, missed detections, and false tracks.
6. Backend notes explaining whether the workflow is NumPy-only, bridged, or
portable.

## Acceptance Criteria

- The example runs from the repository root.
- It has a deterministic seed or golden expected output.
- It is small enough for CI smoke testing.
- It links to the backend API matrix and scenario-zoo entry.

This page is intentionally a specification first. The example implementation can
be expanded incrementally without changing the acceptance criteria.
Loading