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/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Dependency review

on:
pull_request:
branches:
- "**"

permissions:
contents: read
pull-requests: read

concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true

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

- name: Review dependency changes
uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
comment-summary-in-pr: always
53 changes: 52 additions & 1 deletion .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,50 @@ concurrency:
cancel-in-progress: true

jobs:
static:
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 static analysis tools
run: |
python -m pip install --upgrade pip
python -m pip install "ruff>=0.8,<1.0"

- name: Ruff allowlist
run: |
python -m ruff check \
scripts/check_benchmark_regression.py \
scripts/check_minimal_imports.py \
scripts/check_public_api_registry.py \
scripts/render_backend_api_matrix.py \
src/pyrecest/api_registry.py \
tests/test_api_registry.py \
tests/test_capability_matrix.py
python -m ruff format --check \
scripts/check_benchmark_regression.py \
scripts/check_minimal_imports.py \
scripts/check_public_api_registry.py \
scripts/render_backend_api_matrix.py \
src/pyrecest/api_registry.py \
tests/test_api_registry.py \
tests/test_capability_matrix.py

- name: Compile Python files
run: python -m compileall -q src scripts tests

- name: Check generated backend API matrix docs
run: python scripts/render_backend_api_matrix.py --check docs/backend-api-matrix.md

- name: Check generated public API registry docs
run: python scripts/check_public_api_registry.py --check docs/public-api-registry.md

ruff-ratchet:
runs-on: ubuntu-latest
steps:
Expand All @@ -32,4 +76,11 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install "ruff>=0.11,<0.15"
python -m ruff check src/pyrecest/_backend/capabilities.py src/pyrecest/cli.py src/pyrecest/filters/__init__.py scripts/generate_backend_api_matrix.py scripts/check_benchmark_results.py tests/test_backend_capabilities.py tests/test_filters_lazy_exports.py
python -m ruff check \
src/pyrecest/_backend/capabilities.py \
src/pyrecest/cli.py \
src/pyrecest/filters/__init__.py \
scripts/generate_backend_api_matrix.py \
scripts/check_benchmark_results.py \
tests/test_backend_capabilities.py \
tests/test_filters_lazy_exports.py
4 changes: 4 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,14 @@ jobs:
import pyrecest
print(f"Installed pyrecest {pyrecest.__version__}")
PY
python scripts/check_minimal_imports.py
python examples/basic/kalman_filter.py
python examples/basic/gaussian_multiplication.py
python scripts/run_scenario.py scenarios/linear_gaussian_cv_1d/config.toml --expected scenarios/linear_gaussian_cv_1d/expected.json
python benchmarks/basic_regressions.py --output benchmark-results.json
python scripts/check_benchmark_regression.py \
benchmark-results.json \
benchmarks/baselines/basic_regressions-linux-py313.json
python scripts/check_benchmark_results.py benchmark-results.json --baseline benchmarks/baselines/basic_regressions.json

- name: Upload benchmark result artifact
Expand Down
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ poetry run nox -s benchmarks
The PyTorch and JAX sessions require the corresponding optional extras. JAX
sessions set `JAX_ENABLE_X64=True` to match the main CI configuration.

## Quality Gates

Use CI for the full matrix, but run focused local checks for the changed surface
area before opening a pull request:

```bash
python -m compileall -q src scripts tests
PYTHONPATH=src python scripts/render_backend_api_matrix.py --check docs/backend-api-matrix.md
PYTHONPATH=src python scripts/check_public_api_registry.py --check docs/public-api-registry.md
python scripts/check_minimal_imports.py
```

Backend selection is process-global and import-time only. Set
`PYRECEST_BACKEND` before importing `pyrecest`, and use
`pyrecest.assert_backend(...)` or `pyrecest.warn_if_backend_env_changed()` in
Expand All @@ -48,6 +60,10 @@ scripts where accidental backend changes would be confusing.
`src/pyrecest/_backend/capabilities.py` when backend support changes.
- Run `python scripts/check_release_consistency.py --local-only` after changing
release metadata, citation metadata, or package metadata.
- Run `python scripts/render_backend_api_matrix.py --check docs/backend-api-matrix.md`
after changing backend capability metadata.
- Run `python scripts/check_public_api_registry.py --check docs/public-api-registry.md`
after adding, removing, stabilizing, deprecating, or reclassifying public APIs.
- Keep examples executable from the repository root.

## Adding or Changing Public APIs
Expand All @@ -62,6 +78,7 @@ guide page in the same pull request. Use the following decision order:
3. Add a focused backend contract test for any promised portable behavior.
4. Prefer `BackendNotSupportedError`, `ShapeError`, `DimensionMismatchError`, or
`NumericalStabilityError` for new user-facing failures.
5. Add or update the matching row in `src/pyrecest/api_registry.py`.

## Release Metadata

Expand Down
13 changes: 13 additions & 0 deletions benchmarks/baselines/basic_regressions-linux-py313.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"benchmarks": [
{
"name": "linear_kalman",
"iterations": 200,
"max_elapsed_seconds": 30.0,
"final_estimate": [
200.0,
1.0
]
}
]
}
7 changes: 5 additions & 2 deletions docs/backend-api-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ in CI so the user-facing matrix cannot silently drift from the executable metada

## Public API Rows

<!-- backend-api-matrix:start -->
| API | NumPy | PyTorch | JAX | Notes |
|--------------------------------|-----------|-------------|-------------|----------------------------------------------------------------------------------------------------------------------------------|
| `BackendFacade` | supported | partial | partial | Facade names are importable across backends, but some functions are bridged or explicitly unsupported. |
Expand All @@ -44,10 +45,12 @@ in CI so the user-facing matrix cannot silently drift from the executable metada
| `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. |
<!-- backend-api-matrix:end -->

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.
user-facing, add or update the generated table, and add a focused backend test
if the API is expected to be portable. CI checks that this table still reflects
`src/pyrecest/_backend/capabilities.py`.

## Runtime Access

Expand Down
12 changes: 12 additions & 0 deletions docs/install-footprint.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,17 @@ Use hyphenated extra names in public installation snippets. Pip normalizes
underscores and hyphens, but using one public spelling improves searchability and
matches the form displayed by package indexes.

## Migration Gates

Before moving an existing dependency out of the default installation, add or
update all of the following in the same pull request:

- an import smoke test for `python -m pip install .` without optional extras;
- one focused test or example for each API that should require the new extra;
- an explicit optional-dependency error message for users who call an API
without the required extra installed;
- a documentation row showing the dependency-to-extra mapping;
- a wheel-install smoke run in CI that exercises the minimal Euclidean baseline.

When an existing required dependency is moved to an extra, update the public API
registry and add a focused import test for every symbol whose dependency changed.
26 changes: 26 additions & 0 deletions docs/public-api-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ such as `pyrecest.filters`. It complements the backend API matrix: the registry
answers "what name is public?" and the matrix answers "which backend supports
that name?"

The machine-readable source is `src/pyrecest/api_registry.py`.

Run this check after adding, removing, stabilizing, deprecating, or reclassifying
user-facing APIs:

```bash
PYTHONPATH=src python scripts/check_public_api_registry.py --check docs/public-api-registry.md
```

## Rules

1. Prefer one canonical spelling for each public object.
Expand All @@ -23,3 +32,20 @@ tracker implementation. Add new filter symbols to `_FILTER_EXPORTS` in
Compatibility aliases such as mixed acronym/camelcase tracker names should stay
mapped to the same implementation module as their canonical form. New examples
and documentation should use the canonical spelling.

<!-- public-api-registry:start -->
| API | Module | Category | Backend contract | Notes |
|-----|--------|----------|------------------|-------|
| `BackendFacade` | `pyrecest.backend` | backend-specific | `BackendFacade` | Facade names are importable across backends, with bridged or unsupported functions documented in the backend matrix. |
| `DistributionConversion` | `pyrecest.distributions.conversion` | backend-specific | `DistributionConversion` | Euclidean Gaussian/particle routes are portable; grid, Fourier, and manifold routes are route-specific. |
| `EuclideanParticleFilter` | `pyrecest.filters` | backend-specific | `EuclideanParticleFilter` | Particle behavior depends on sampler and resampling support in the active backend. |
| `EvaluationUtilities` | `pyrecest.evaluation` | backend-specific | `EvaluationUtilities` | Plotting, assignment, summaries, and result helpers are only partly backend-portable. |
| `GaussianDistribution` | `pyrecest.distributions` | stable | `GaussianDistribution` | Basic construction, moment access, and portable operations are part of the core distribution API. |
| `KalmanFilter` | `pyrecest.filters` | stable | `KalmanFilter` | Linear Gaussian filtering is part of the portable baseline. |
| `LinearDiracDistribution` | `pyrecest.distributions` | stable | `LinearDiracDistribution` | Core particle-style representation used by conversion and filtering workflows. |
| `MultiBernoulliTracker` | `pyrecest.filters` | backend-specific | `MultiBernoulliTracker` | Tracking workflows rely on assignment and measurement-set utilities with NumPy-oriented paths. |
| `PointSetRegistration` | `pyrecest.utils` | backend-specific | `PointSetRegistration` | Registration helpers may bridge through NumPy/SciPy and are not guaranteed differentiable. |
| `SphericalHarmonicsEOTTracker` | `pyrecest.filters` | backend-specific | `SphericalHarmonicsEOTTracker` | Depends on spherical-harmonics and SciPy-adjacent functionality. |
| `UKFOnManifolds` | `pyrecest.filters` | backend-specific | `UKFOnManifolds` | Current predict/update paths explicitly exclude JAX. |
| `UnscentedKalmanFilter` | `pyrecest.filters` | backend-specific | `UnscentedKalmanFilter` | Portable for backend-compatible model functions; advanced paths may bridge through NumPy/SciPy. |
<!-- public-api-registry:end -->
72 changes: 72 additions & 0 deletions docs/quality-gates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Quality Gates

PyRecEst is a research library, but public releases should still protect users
from accidental backend, packaging, documentation, and security regressions.
The repository therefore separates broad exploratory checks from the checks that
should be safe to require on every pull request.

## Required Pull Request Checks

Recommended required checks for protected branches are:

| Check | Purpose |
|-------|---------|
| `Static analysis` | Runs the static baseline, compile checks, and generated-doc checks. |
| `Test workflow / docs` | Builds documentation with `mkdocs build --strict`. |
| `Test workflow / package` | Builds distributions, installs the wheel, and runs smoke examples. |
| `Test workflow / test` | Runs the backend matrix for NumPy, PyTorch, and JAX. |
| `CodeQL` | Scans the Python codebase for security issues. |
| `Dependency review` | Fails pull requests that introduce high-severity dependency advisories. |

Scheduled jobs may run larger or slower matrices, but the required checks should
remain small enough that contributors can iterate quickly.

## Backend Contract Changes

Any pull request that changes backend behavior should update the same source of
truth used by tests, documentation, and command-line inspection:

```text
src/pyrecest/_backend/capabilities.py
```

The generated backend API table in `docs/backend-api-matrix.md` must continue to
match that source. Public API category changes should also update
`src/pyrecest/api_registry.py` and `docs/public-api-registry.md`. Run these
commands locally after changing capability or public API registry rows:

```bash
PYTHONPATH=src python scripts/render_backend_api_matrix.py --check docs/backend-api-matrix.md
PYTHONPATH=src python scripts/check_public_api_registry.py --check docs/public-api-registry.md
```

If a public API is intended to be backend-portable, add a focused test that runs
on the relevant backend matrix. If it is intentionally backend-specific, add a
clear capability row and a user-facing failure mode.

## Static Analysis Baseline

The static workflow intentionally starts with an allowlist because parts of the
repository still contain historical re-export and backend-facade lint noise.
Expand the allowlist only when a module is clean under Ruff and mypy. Avoid
making the entire source tree required until the existing baseline is reconciled.

## Coverage And Benchmark Baselines

Coverage has a low initial floor so the project can enforce a real threshold
without blocking routine maintenance. Raise the threshold after adding tests to
backend contracts, representation conversion, filters, and tracker utilities.

Benchmarks should be deterministic and conservative. CI should fail on severe
slowdowns or numerical drift, not on small timing noise from shared runners.

## Dependency Footprint Changes

When moving dependencies into optional extras, keep the default installation
usable for the minimal Euclidean baseline:

- import `pyrecest`;
- construct a Gaussian distribution;
- run a linear Kalman predict/update cycle;
- build and install the wheel;
- keep error messages explicit for APIs that require optional extras.
21 changes: 21 additions & 0 deletions docs/stability-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ ideas by separating public APIs from experimental APIs.
| Deprecated | API still exists but emits `DeprecationWarning` and has a planned removal version. |
| Backend-specific | API is stable only for the backends listed in the backend API matrix. |

Tracked user-facing APIs are listed in the [public API registry](public-api-registry.md).
Keep that registry, backend capability metadata, and deprecation tests in sync
when API status changes.

## Public Symbol Registry

Public package namespaces should keep their exported symbols explicit. For lazy
Expand Down Expand Up @@ -46,6 +50,23 @@ Recommended cadence:
2. keep the warning for at least one additional minor release;
3. remove only in a major release unless the API was explicitly experimental.

## Executable Stability Checks

Public API changes should be visible in tests or generated metadata. For new
stable, backend-specific, or deprecated APIs, update the relevant rows in the
backend API matrix and add one of the following:

- a focused behavior test for the stable contract;
- a backend-contract test that verifies supported, partial, and unsupported
backends behave as documented;
- a deprecation test that asserts `DeprecationWarning` is emitted and that the
replacement is named in the warning message;
- an explicit `experimental` documentation note when the API is not yet covered
by the full deprecation cycle.

Treat undocumented package-level exports as accidental until they are covered by
this policy or moved under an experimental namespace.

## Runtime Metadata

Public APIs can expose stability metadata through `pyrecest.stability`:
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ nav:
- Diagnostics: diagnostics.md
- Numerical Contracts: numerical-contracts.md
- Shapes and Conventions: conventions.md
- Quality Gates: quality-gates.md
- Backend Compatibility: backend-compatibility.md
- Backend Support Matrix: backend-support.md
- Backend API Matrix: backend-api-matrix.md
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ omit = [
[tool.coverage.report]
show_missing = true
skip_covered = true
fail_under = 20
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
Expand Down
Loading
Loading