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
22 changes: 16 additions & 6 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
# See https://zensical.org/docs/publish-your-site/
name: Publish docs

on:
push:
branches:
- main

permissions:
contents: write
contents: read
pages: write
id-token: write

jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/configure-pages@v5
- uses: actions/checkout@v4
- name: Configure Git Credentials
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- uses: astral-sh/setup-uv@v5
- run: uv sync --package cuthbert --extra docs
- run: uv run mkdocs gh-deploy --force
- run: uv run zensical build --clean
- uses: actions/upload-pages-artifact@v4
with:
path: site
- uses: actions/deploy-pages@v4
id: deployment
30 changes: 21 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,7 @@ pre-commit install

3. **Add your code. Add your tests. Update the docs if needed.**

4. If you have made changes to the docs, check that they render nicely:
```bash
mkdocs serve
```

5. Make sure to run the linter, type checker, tests and check coverage:
4. Make sure to run the linter, type checker, tests and check coverage:
```bash
pre-commit run --all-files
python -m pytest --cov=cuthbert --cov-report term-missing
Expand All @@ -52,9 +47,26 @@ python -m pytest --cov=cuthbert --cov-report term-missing
(with Type Checking Mode: standard)
extensions for assistance with linting and type checking during development.

6. Commit your changes and push your new branch to your fork.
5. Commit your changes and push your new branch to your fork.

6. Open a [pull request on GitHub](https://github.com/state-space-models/cuthbert/pulls).


## Updating the docs

The documentation site is built with [Zensical](https://zensical.org/).

You can render locally with:
```bash
zensical serve
```
The live preview should be available at `http://localhost:8000`.

7. Open a [pull request on GitHub](https://github.com/state-space-models/cuthbert/pulls).
Pages under `docs/` often pull shared copy from repository `README.md` files via
[PyMdown Snippets](https://facelessuser.github.io/pymdown-extensions/extensions/snippets/)
(`--8<-- "path"` and named sections in HTML comments). See that guide for syntax and
options. We recommend using Snippets where possible, to avoid duplication,
with source text in an appropriate `README.md` file and then linked to in `docs/` files.


## Adding an example
Expand All @@ -75,7 +87,7 @@ To add an example, you can use the following steps:
```
Again, see the [`docs/quickstart.md`](https://github.com/state-space-models/cuthbert/blob/main/docs/quickstart.md) example for reference.

4. Add a reference to your new example in the [`docs/examples/index.md`](https://github.com/state-space-models/cuthbert/blob/main/docs/examples/index.md) file and the [`mkdocs.yml`](https://github.com/state-space-models/cuthbert/blob/main/mkdocs.yml) file.
4. Add a reference to your new example in the [`docs/examples/index.md`](https://github.com/state-space-models/cuthbert/blob/main/docs/examples/index.md) file and the [`zensical.toml`](https://github.com/state-space-models/cuthbert/blob/main/zensical.toml) file.
You may need to add any new dependencies to the [`pyproject.toml`](https://github.com/state-space-models/cuthbert/blob/main/pyproject.toml) file under `examples`.
5. Make sure your example ends with "Key Takeaways" and "Next Steps" sections,
linking to other documentation and examples where appropriate.
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--intro-start-->
<!-- --8<-- [start:intro] -->
<div align="center">
<img src="https://raw.githubusercontent.com/state-space-models/cuthbert/main/docs/assets/cuthbert.png" alt="cuthbert logo"></img>
</div>
Expand All @@ -16,10 +16,10 @@ A JAX library for state-space model inference
[![Discord](https://img.shields.io/badge/Discord-5865F2?logo=discord&logoColor=white&style=for-the-badge)](https://discord.gg/W9BA4Aj7Rx)
[![GitHub](https://img.shields.io/badge/GitHub-181717?logo=github&logoColor=white&style=for-the-badge)](https://github.com/state-space-models/cuthbert)
[![PyPI](https://img.shields.io/pypi/v/cuthbert?style=for-the-badge)](https://pypi.org/project/cuthbert/)
[![Docs](https://img.shields.io/badge/Docs-b6d7a8?logo=materialformkdocs&logoColor=black&style=for-the-badge)](https://state-space-models.github.io/cuthbert/)
<!--intro-end-->
[![Docs](https://img.shields.io/badge/Docs-b6d7a8?logo=readme&logoColor=black&style=for-the-badge)](https://state-space-models.github.io/cuthbert/)
<!-- --8<-- [end:intro] -->

<!--goals-start-->
<!-- --8<-- [start:goals] -->
### Goals
- Simple, flexible and performant interface for state-space model inference.
- Decoupling of model specification and inference. `cuthbert` is built to swap between
Expand All @@ -39,23 +39,23 @@ Kalman filtering (+ extended/unscented/ensemble), expectation-maximization and m
But can easily compose with [`dynamax`](https://github.com/probml/dynamax), [`distrax`](https://github.com/google-deepmind/distrax), [`numpyro`](https://github.com/pyro-ppl/numpyro) and [`pymc`](https://github.com/pymc-devs/pymc) in a similar way to how [`blackjax` does](https://blackjax-devs.github.io/blackjax/).
- ["SMC Samplers"](https://www.stats.ox.ac.uk/~doucet/delmoral_doucet_jasra_sequentialmontecarlosamplersJRSSB.pdf) which sample from a posterior
distribution which is not (necessarily) a state-space model - [`blackjax` is great for this](https://github.com/blackjax-devs/blackjax/tree/main/blackjax/smc).
<!--goals-end-->
<!-- --8<-- [end:goals] -->

<!--codebase-structure-start-->
<!-- --8<-- [start:codebase_structure] -->
### Codebase structure

The codebase is structured as follows:

- `cuthbert`: The main package with unified interface for filtering and smoothing.
- `cuthbertlib`: A collection of atomic, smaller-scoped tools useful for state-space model inference,
that represent the building blocks that power the main `cuthbert` package.
<!--codebase-structure-end-->
<!-- --8<-- [end:codebase_structure] -->
- `docs`: Source code for the documentation for `cuthbert` and `cuthbertlib`.
- `pkg`: Packaging configuration for publishing `cuthbert` and `cuthbertlib` to PyPI.
- `tests`: Tests for the `cuthbert` and `cuthbertlib` packages.


<!--installation-start-->
<!-- --8<-- [start:installation] -->
## Installation

`cuthbert` depends on JAX, so you'll need to [install JAX](https://docs.jax.dev/en/latest/installation.html) for the available hardware (CPU, GPU, or TPU).
Expand Down Expand Up @@ -95,9 +95,9 @@ pip install -e ./pkg/cuthbertlib
pip install -e "./pkg/cuthbert[tests]"
```

<!--installation-end-->
<!-- --8<-- [end:installation] -->

<!--ecosystem-start-->
<!-- --8<-- [start:ecosystem] -->
## Ecosystem
- `cuthbert` is built on top of [`jax`](https://github.com/google/jax) and composes
easily with other JAX packages, e.g. [`optax`](https://github.com/google-deepmind/optax)
Expand All @@ -117,7 +117,7 @@ mentioned [above](#non-goals).
are wonderful learning materials for state-space models and SMC.
`cuthbert` is more focused on performance and composability with the JAX ecosystem.
- Much of the code in `cuthbert` is built on work from [`sqrt-parallel-smoothers`](https://github.com/EEA-sensors/sqrt-parallel-smoothers), [`mocat`](https://github.com/SamDuffield/mocat) and [`abile`](https://github.com/SamDuffield/abile).
<!--ecosystem-end-->
<!-- --8<-- [end:ecosystem] -->

## Contributing

Expand Down
4 changes: 2 additions & 2 deletions cuthbert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This folder contains the code for the main `cuthbert` package.

<!--unified-interface-start-->
<!-- --8<-- [start:unified_interface] -->
All inference methods are implemented with the following unified interface:

```python
Expand Down Expand Up @@ -36,4 +36,4 @@ kalman_smoother = cuthbert.gaussian.kalman.build_smoother(get_dynamics_params)
filter_states = cuthbert.filter(kalman_filter, model_inputs)
smoother_states = cuthbert.smoother(kalman_smoother, filter_states, model_inputs)
```
<!--unified-interface-end-->
<!-- --8<-- [end:unified_interface] -->
4 changes: 2 additions & 2 deletions cuthbert/discrete/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Discrete Hidden Markov Models

<!--discrete-hidden-markov-models-start-->
<!-- --8<-- [start:discrete_hidden_markov_models] -->
`cuthbert` provides exact filtering and smoothing for state-space models with discrete states (aka hidden Markov models):

In this case, we assume the latent states $x_t$ are discrete, i.e. $x_t \in \{1, 2, \ldots, K\}$ and therefore distributions over a state can be stored as an array of normalized probabilities.

Observations are handled via a general log likelihood functions that can also be
stored as an array of log likelihoods $b_i = \log p(y_t \mid x_t = i)$ of size $K$.

<!--discrete-hidden-markov-models-end-->
<!-- --8<-- [end:discrete_hidden_markov_models] -->

The core atomic functions can be found in [`cuthbertlib.discrete`](../../cuthbertlib/discrete).

4 changes: 2 additions & 2 deletions cuthbert/gaussian/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The core atomic functions can be found in [`cuthbertlib.kalman`](../../cuthbertlib/kalman).

<!--gaussian-filters-and-smoothers-start-->
<!-- --8<-- [start:gaussian_filters_and_smoothers] -->
Gaussian filters in `cuthbert` provide filtering distributions of the form:

$$
Expand Down Expand Up @@ -58,4 +58,4 @@ G_{t|T} L_{t+1|T} & V_{t|T}
\end{pmatrix}.
$$

<!--gaussian-filters-and-smoothers-end-->
<!-- --8<-- [end:gaussian_filters_and_smoothers] -->
4 changes: 2 additions & 2 deletions cuthbert/smc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

The core atomic functions can be found in [`cuthbertlib.smc`](../../cuthbertlib/smc).

<!--feynman-kac-start-->
<!-- --8<-- [start:feynman_kac] -->
Sequential Monte Carlo methods provide particle approximations to general Feynman-Kac models:

$$
\mathbb{Q}_{t}(x_{0:t}) \propto \mathbb{M}_0(x_0) \, G_0(x_0) \prod_{s=1}^{t} M_s(x_s \mid x_{s-1}) \, G_s(x_{s-1}, x_s), \quad t \in \{ 0, \dots, T\},
$$

where $M_{s}$ are probability kernels and $G_{s}$ are positive and bounded functions.
<!--feynman-kac-end-->
<!-- --8<-- [end:feynman_kac] -->
22 changes: 0 additions & 22 deletions docs/README.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
# Discrete Hidden Markov Models

{%
include-markdown "../../cuthbert/discrete/README.md"
start="<!--discrete-hidden-markov-models-start-->"
end="<!--discrete-hidden-markov-models-end-->"
%}
--8<-- "cuthbert/discrete/README.md:discrete_hidden_markov_models"


The core atomic functions can be found in
[`cuthbertlib.discrete`](../cuthbertlib_api/discrete.md).
[`cuthbertlib.discrete`](../api_cuthbertlib/discrete.md).

::: cuthbert.discrete.filter
options:
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,8 @@ densities.
Note that the moments and Taylor methods can be considered variants of the extended
Kalman filter.

The core atomic functions can be found in [`cuthbertlib.kalman`](../../cuthbertlib_api/kalman.md).
The core atomic functions can be found in [`cuthbertlib.kalman`](../../api_cuthbertlib/kalman.md).

## Gaussian Filters and Smoothers in `cuthbert`

{%
include-markdown "../../../cuthbert/gaussian/README.md"
start="<!--gaussian-filters-and-smoothers-start-->"
end="<!--gaussian-filters-and-smoothers-end-->"
%}
--8<-- "cuthbert/gaussian/README.md:gaussian_filters_and_smoothers"
6 changes: 1 addition & 5 deletions docs/cuthbert_api/index.md → docs/api_cuthbert/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,4 @@

## Unified Interface

{%
include-markdown "../../cuthbert/README.md"
start="<!--unified-interface-start-->"
end="<!--unified-interface-end-->"
%}
--8<-- "cuthbert/README.md:unified_interface"
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

- [Particle filter](particle_filter.md) - flexible particle filter for general Feynman-Kac models.
- [Marginal particle filter](marginal_particle_filter.md) - $O(N^2)$ variant of the particle filter.
- [Backward sampler](backward_sampler.md) - flexible backward smoothing using approaches in [`cuthbertlib.smc.smoothing`](../../cuthbertlib_api/smc.md).
- [Backward sampler](backward_sampler.md) - flexible backward smoothing using approaches in [`cuthbertlib.smc.smoothing`](../../api_cuthbertlib/smc.md).

The core atomic functions can be found in [`cuthbertlib.smc`](../../cuthbertlib_api/smc.md).
The core atomic functions can be found in [`cuthbertlib.smc`](../../api_cuthbertlib/smc.md).

## Feynman-Kac Models

{%
include-markdown "../../../cuthbert/smc/README.md"
start="<!--feynman-kac-start-->"
end="<!--feynman-kac-end-->"
%}
--8<-- "cuthbert/smc/README.md:feynman_kac"
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{%
include-markdown "../../cuthbertlib/README.md"
%}
--8<-- "cuthbertlib/README.md"

- [Discrete hidden Markov models](discrete.md)
- [Kalman filtering and smoothing](kalman.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{%
include-markdown "../../cuthbertlib/kalman/README.md"
%}
--8<-- "cuthbertlib/kalman/README.md"

::: cuthbertlib.kalman.filtering

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{%
include-markdown "../../cuthbertlib/linalg/README.md"
%}
--8<-- "cuthbertlib/linalg/README.md"

::: cuthbertlib.linalg.tria

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{%
include-markdown "../../cuthbertlib/linearize/README.md"
%}
--8<-- "cuthbertlib/linearize/README.md"

::: cuthbertlib.linearize.log_density

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{%
include-markdown "../../cuthbertlib/quadrature/README.md"
%}
--8<-- "cuthbertlib/quadrature/README.md"

::: cuthbertlib.quadrature.cubature

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{%
include-markdown "../../cuthbertlib/resampling/README.md"
%}
--8<-- "cuthbertlib/resampling/README.md"

::: cuthbertlib.resampling.protocols
options:
Expand Down
4 changes: 1 addition & 3 deletions docs/cuthbertlib_api/smc.md → docs/api_cuthbertlib/smc.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{%
include-markdown "../../cuthbertlib/smc/README.md"
%}
--8<-- "cuthbertlib/smc/README.md"

::: cuthbertlib.smc.smoothing.protocols

Expand Down
3 changes: 3 additions & 0 deletions docs/api_cuthbertlib/stats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--8<-- "cuthbertlib/stats/README.md"

::: cuthbertlib.stats.multivariate_normal
File renamed without changes.
4 changes: 1 addition & 3 deletions docs/contributing.md
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
{%
include-markdown "../CONTRIBUTING.md"
%}
--8<-- "CONTRIBUTING.md"
5 changes: 0 additions & 5 deletions docs/cuthbertlib_api/stats.md

This file was deleted.

6 changes: 3 additions & 3 deletions docs/examples/diff_pf_resampling.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Differentiable Resampling

In the default implementation of a particle filter, particles are propagated through a Markov kernel $M(x_t \mid x_{t-1})$, reweighted according to a potential function $G_t(x_{t-1}, x_t)$, and potentially resampled. While [various resamplings strategies exist](../cuthbertlib_api/resampling.md), they are typically non-differentiable, leading to biased estimates of the marginal log-likelihood's gradient, $\nabla_{\theta} \log p(y_{1:T})$, if used directly within `jax.grad` autodifferentiation framework. This is unfortunate, as relying on these biased gradients of the marginal log-likelihood falsifies for parameter estimation tasks.
In the default implementation of a particle filter, particles are propagated through a Markov kernel $M(x_t \mid x_{t-1})$, reweighted according to a potential function $G_t(x_{t-1}, x_t)$, and potentially resampled. While [various resamplings strategies exist](../api_cuthbertlib/resampling.md), they are typically non-differentiable, leading to biased estimates of the marginal log-likelihood's gradient, $\nabla_{\theta} \log p(y_{1:T})$, if used directly within `jax.grad` autodifferentiation framework. This is unfortunate, as relying on these biased gradients of the marginal log-likelihood falsifies for parameter estimation tasks.

Because of this, many different approaches have been proposed to make the resampling step differentiable. A simple implementation is that of [Scibior and Wood (2021)](https://arxiv.org/abs/2106.10314), which implements a stop-gradient trick recovering classical estimates of the gradients of the marginal log-likelihood via automatic differentation.
Formally, it is equivalent to computing
Expand Down Expand Up @@ -568,8 +568,8 @@ Finally, we should be sure that the forward pass is not actually being modified

## Next Steps

- **More SMC docs**: See [`cuthbert.smc`](../cuthbert_api/smc/index.md) for particle filtering and smoothing APIs.
- **More resampling docs**: See [`cuthbertlib.resampling`](../cuthbertlib_api/resampling.md) for resampling APIs.
- **More SMC docs**: See [`cuthbert.smc`](../api_cuthbert/smc/index.md) for particle filtering and smoothing APIs.
- **More resampling docs**: See [`cuthbertlib.resampling`](../api_cuthbertlib/resampling.md) for resampling APIs.
- **More info on differentiable particle filters**: See the [PyDPF paper](https://arxiv.org/abs/2510.25693) for an overview of different differentiable particle filter algorithms in the literature.

<!--- entangled-tangle-block
Expand Down
Loading
Loading