Skip to content

Commit

Permalink
Restructure to add flexibility (#70)
Browse files Browse the repository at this point in the history
* before docs fix

* did some more work

* adding in

* airplane work

* syncing

* saving

* tests pass

* before trying onefunction

* after documenting bootstrap filter

* actually added bootstrap docs

* deprecated resamplers

* before swapping postprocess arg order

* more work on docs

* before readonly

* finished everything in journal

* small modification to profiling

* updated comment

* added stable and latest doc statuses

* moved problematic doctests to examples

* added examples in docs

* finished transfering notebooks

* added Distributions to project

* trying to add everything to docs project

* removed notebooks from test

* pakage develop POMDPs in docs

* finished sentence in pomdp example
  • Loading branch information
zsunberg authored Feb 10, 2025
1 parent cd9d264 commit 458c1fd
Show file tree
Hide file tree
Showing 38 changed files with 1,162 additions and 4,367 deletions.
1 change: 1 addition & 0 deletions .github/workflows/Docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
julia --project=docs -e '
using Pkg
Pkg.develop(PackageSpec(path=pwd()))
Pkg.develop("POMDPs")
Pkg.instantiate()'
- run: julia --project=docs docs/make.jl
env:
Expand Down
10 changes: 8 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
name = "ParticleFilters"
uuid = "c8b314e2-9260-5cf8-ae76-3be7461ca6d0"
repo = "https://github.com/JuliaPOMDP/ParticleFilters.jl"
version = "0.5.7"
version = "0.6.0"

[deps]
AliasTables = "66dad0bd-aa9a-41b7-9441-69ab47430ed8"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
POMDPLinter = "f3bd98c0-eb40-45e2-9eb1-f2763262d755"
POMDPTools = "7588e00f-9cae-40de-98dc-e0c70c48cdd7"
POMDPs = "a93abf59-7444-517b-a68a-c42f96afdd7d"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ReadOnlyArrays = "988b38a3-91fc-5605-94a2-ee2116b3bd83"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"

[compat]
AliasTables = "1.1.3"
Documenter = "1.8.0"
POMDPLinter = "0.1"
POMDPTools = "0.1, 1"
POMDPs = "0.9, 1"
ReadOnlyArrays = "0.2.0"
Statistics = "1"
StatsBase = "0.32, 0.33, 0.34"
julia = "1.1"

[extras]
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
Expand All @@ -38,4 +44,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
VegaLite = "112f6efa-9a02-5b7d-90c0-432ed331239a"

[targets]
test = ["DelimitedFiles", "Distributions", "InteractiveUtils", "LinearAlgebra", "Markdown", "Plots", "PlutoUI", "POMDPModels", "POMDPs", "POMDPTools", "Random", "Reel", "StaticArrays", "Test", "VegaLite"]
test = ["DelimitedFiles", "Distributions", "Documenter", "InteractiveUtils", "LinearAlgebra", "Markdown", "Plots", "PlutoUI", "POMDPModels", "POMDPs", "POMDPTools", "Random", "Reel", "StaticArrays", "Test", "VegaLite"]
39 changes: 4 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ParticleFilters

[![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://JuliaPOMDP.github.io/ParticleFilters.jl/latest)
[![Docs](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaPOMDP.github.io/ParticleFilters.jl/stable)
[![Docs - Latest](https://img.shields.io/badge/docs-latest-blue.svg)](https://JuliaPOMDP.github.io/ParticleFilters.jl/latest)
[![Build Status](https://github.com/JuliaPOMDP/ParticleFilters.jl/actions/workflows/CI.yml/badge.svg)](https://github.com/JuliaPOMDP/ParticleFilters.jl)
[![codecov.io](http://codecov.io/github/JuliaPOMDP/ParticleFilters.jl/coverage.svg?branch=master)](http://codecov.io/github/JuliaPOMDP/ParticleFilters.jl?branch=master)

Expand All @@ -10,40 +11,8 @@ This package provides some simple generic particle filters, and may serve as a t

# Installation

In Julia:

```julia
Pkg.add("ParticleFilters")
```

# Usage

Basic setup might look like this:
```julia
using ParticleFilters, Distributions

dynamics(x, u, rng) = x + u + randn(rng)
y_likelihood(x_previous, u, x, y) = pdf(Normal(), y - x)

model = ParticleFilterModel{Float64}(dynamics, y_likelihood)
pf = BootstrapFilter(model, 10)
```
Then the `update` function can be used to perform a particle filter update.
```julia
b = ParticleCollection([1.0, 2.0, 3.0, 4.0])
u = 1.0
y = 3.0

b_new = update(pf, b, u, y)
```

This is a very simple example and the framework can accommodate a variety of more complex use cases. More details can be found in the documentation linked to below.

There are [tutorials](https://juliapomdp.github.io/ParticleFilters.jl/latest/notebooks/) for three ways to use the particle filters:
1. As an [estimator for feedback control](https://juliapomdp.github.io/ParticleFilters.jl/latest/notebooks//Using-a-Particle-Filter-for-Feedback-Control.html),
2. to [filter time-series measurements](https://juliapomdp.github.io/ParticleFilters.jl/latest/notebooks/Filtering-a-Trajectory-or-Data-Series.html), and
3. as an [updater for POMDPs.jl](https://juliapomdp.github.io/ParticleFilters.jl/latest/notebooks/Using-a-Particle-Filter-with-POMDPs-jl.html).
Type `using ParticleFilters` in Julia, and you will be prompted to install.

# Documentation

https://JuliaPOMDP.github.io/ParticleFilters.jl/latest
https://JuliaPOMDP.github.io/ParticleFilters.jl/stable
14 changes: 13 additions & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
[deps]
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
POMDPModels = "355abbd5-f08e-5560-ac9e-8b5f2592a0ca"
POMDPTools = "7588e00f-9cae-40de-98dc-e0c70c48cdd7"
POMDPs = "a93abf59-7444-517b-a68a-c42f96afdd7d"
ParticleFilters = "c8b314e2-9260-5cf8-ae76-3be7461ca6d0"
PlutoSliderServer = "2fc8631c-6f24-4c5b-bca7-cbb509c42db4"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
VegaLite = "112f6efa-9a02-5b7d-90c0-432ed331239a"

[compat]
Documenter = "1.8"
POMDPModels = "0.4"
POMDPs = "1"
25 changes: 17 additions & 8 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
using Documenter, ParticleFilters, POMDPs, PlutoSliderServer
using Documenter, ParticleFilters, POMDPs

page_order = [
"index.md",
"bootstrap.md",
"beliefs.md",
"basic.md",
"depletion.md",
"sampling.md",
"example-filtering.md",
"example-feedback.md",
"example-pomdps.md",
]

makedocs(
modules=[ParticleFilters, POMDPs],
format=Documenter.HTML(),
sitename="ParticleFilters.jl",
warnonly=[:missing_docs, :cross_references],
remotes=Dict(dirname(dirname(pathof(POMDPs))) => (Remotes.GitHub("JuliaPOMDP", "POMDPs.jl"), "v1.0.0")) # Note: this is hard-coded to version 1.0.0 because I didn't know how to fix it. It should be updated in a future release, but it does not seem that important because it is only for source links.
)

PlutoSliderServer.export_directory(
"$(@__DIR__)/../notebooks";
Export_output_dir="$(@__DIR__)/build/notebooks"
warnonly = [:missing_docs, :cross_references],
pages = page_order,
)

deploydocs(
repo="github.com/JuliaPOMDP/ParticleFilters.jl.git",
target="build",
push_preview = true,
)
75 changes: 54 additions & 21 deletions docs/src/basic.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,68 @@
# Basic Particle Filter

## Update Steps
The [`BasicParticleFilter`](@ref) type is a flexible structure for building a particle filter. It simply contains functions that carry out each of the steps of a particle filter belief update.

The basic particle filtering step in ParticleFilters.jl is implemented in the [`update`](@ref) function, and consists of three steps:
The basic particle filtering step in ParticleFilters.jl is implemented in the [`update`](@ref) function, and consists of four steps:

1. Prediction (or propagation) - each state particle is simulated forward one step in time
2. Reweighting - an explicit measurement (observation) model is used to calculate a new weight
3. Resampling - a new collection of state particles is generated with particle frequencies proportional to the new weights
1. Preprocessing - before the prediction step, it may be useful to preprocess the belief, for example resampling if there are not enough distinct particles
2. Prediction (or propagation) - each state particle is simulated forward one step in time
3. Reweighting - an explicit measurement (observation) model is used to calculate a new weight
4. Postprocessing - after the reweighting step, it may be useful to postprocess the belief, for example detecting particle degeneracy and sampling new particles that are consistent with the observation

This is an example of [sequential importance resampling](https://en.wikipedia.org/wiki/Particle_filter#Sequential_Importance_Resampling_(SIR)) using the state transition distribution as the proposal distribution, and the [`BootstrapFilter`](@ref) constructor can be used to construct such a filter with a `model` that controls the prediction and reweighting steps, and a number of particles to create in the resampling phase.
In code, the update is written:
```julia
function update(up::BasicParticleFilter, b::AbstractParticleBelief, a, o)
bb = up.preprocess(b, a, o, up.rng)
particles = up.predict(bb, a, o, up.rng)
weights = up.reweight(bb, a, particles, o)
bp = WeightedParticleBelief(particles, weights)
return up.postprocess(bp, a, o, b, bb, up.rng)
end
```

!!! note
In the future, the steps in an `update` may change (for instance, the prediction and reweighting steps may be combined into a single function). However, we will maintain compatibility constructors so that code written for this 4-step process will continue to work.

A [`BasicParticleFilter`](@ref) is constructed by passing functions that implement each of the four steps. For example, a simple filter that adds Gaussian noise to each particle and reweights based on a Gaussian observation model is implemented in the following block. Note that there are no pre- or post-processing steps in this example.

```jldoctest basic; output=false, filter=r"BasicParticleFilter.*" => s"BasicParticleFilter"
using ParticleFilters, Distributions
preprocess(b, args...) = b
predict(b, a, o, rng) = particles(b) .+ a .+ randn(rng, n_particles(b))
reweight(b, a, particles, o) = weights(b) .* [pdf(Normal(p, 1.0), o) for p in particles]
postprocess(bp, args...) = bp
pf = BasicParticleFilter(preprocess, predict, reweight, postprocess)
# output
BasicParticleFilter()
A more flexible structure for building a particle filter is the [`BasicParticleFilter`](@ref). It contains three models, one for each step:
```

This filter can be used for an update as follows:

```jldoctest basic; output=false, filter=r"WeightedParticleBelief.*" => s"WeightedParticleBelief"
b = ParticleCollection([1.0, 2.0, 3.0])
a = 1.0
o = 2.0
1. The `predict_model` controls prediction through [`predict!`](@ref)
2. The `reweight_model` controls reweighting through [`reweight!`](@ref)
3. The `resampler` controls resampling through [`resample`](@ref)
bp = update(pf, b, a, o)
# output
WeightedParticleBelief()
```

ParticleFilters.jl contains implementations of these components that can be mixed and matched. In many cases the prediction and reweighting steps use the same model, for example a [`ParticleFilterModel`](@ref) or a [`POMDP`](https://github.com/JuliaPOMDP/POMDPs.jl).
In order to give access to additional information such as static dynamics parameters, consider using a [`callable object`](https://docs.julialang.org/en/v1/manual/methods/#Function-like-objects). For additional examples, and to re-use elements from the `BootstrapFilter`, see the [`BootstrapFilter`](@ref) source code.

To carry out the steps individually without the need for pre-allocating memory or doing a full [`update`](@ref) step, the [`predict`](@ref), [`reweight`](@ref), and [`resample`](@ref) functions are provided.
Some building blocks for constructing a `BasicParticleFilter` are provided in the `ParticleFilters` module. For example, the `BasicPredictor`, `BasicReweighter`, `POMDPPredictor`, and `POMDPReweighter` are not exported, but can be used to construct the `predict` and `reweight` functions for a `BasicParticleFilter`. The `check_particle_belief` function can be used as a postprocessing step, and `PostprocessChain` can be used to chain multiple postprocessing steps together. A function `(b, a, o, up.rng) -> ParticleCollection(low_variance_sample(b, 100, up.rng))` or `NormalizedESSConditionalResampler` can be used as a preprocessing step.

## Docstrings
## Reference

```@docs
BootstrapFilter
BasicParticleFilter
update
predict!
reweight!
resample
predict
reweight
particle_memory
```
20 changes: 18 additions & 2 deletions docs/src/beliefs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ ParticleFilters.jl provides two [types of particle beliefs](#Types-1). `Particle

Both are subtypes of `AbstractParticleBelief` and implement the same [particle belief interface](#Interface-1). For probability mass calculations (the [`pdf`](@ref) function), a dictionary containing the normalized sum of weights for all identical particles is created on the first call and cached for efficient future querying.

!!! warning
You should not access the fields of `ParticleCollection` or `WeightedParticleBelief` directly. Use the provided [interface](#Interface-1) instead to ensure that internal data structures are maintained correctly.

```@docs
ParticleCollection
WeightedParticleBelief
Expand All @@ -15,7 +18,7 @@ WeightedParticleBelief

### Standard POMDPs.jl Distribution Interface

The following functions from the [POMDPs.jl distributions interface](http://juliapomdp.github.io/POMDPs.jl/latest/interfaces.html#Distributions-1) provide basic ways of interacting with particle beliefs as distributions (click on each for documentation):
The following functions from the [POMDPs.jl distributions interface](http://juliapomdp.github.io/POMDPs.jl/latest/interfaces.html#Distributions-1) (a subset of the Distributions.jl interface) provide basic ways of interacting with particle beliefs as distributions (click on each for documentation):

- [`rand`](@ref)
- [`pdf`](@ref)
Expand All @@ -25,7 +28,7 @@ The following functions from the [POMDPs.jl distributions interface](http://juli

### Particle Interface

These functions provide access to the particles and weights in the beliefs (click on each for docstrings):
These functions provide *read only* access to the particles, weights, and other aspects of the beliefs (click on each for docstrings):

- [`n_particles`](@ref)
- [`particles`](@ref)
Expand All @@ -35,6 +38,14 @@ These functions provide access to the particles and weights in the beliefs (clic
- [`weight`](@ref)
- [`particle`](@ref)
- [`ParticleFilters.probdict`](@ref)
- [`effective_sample_size`](@ref)

To change the particles or weights in a belief, the following functions are provided:

- [`set_particle!`](@ref)
- [`set_weight!`](@ref)
- [`set_pair!`](@ref)
- [`push_pair!`](@ref)

### Interface Docstrings

Expand All @@ -52,6 +63,11 @@ weight_sum
weight
particle
ParticleFilters.probdict
effective_sample_size
set_particle!
set_weight!
set_pair!
push_pair!
```


55 changes: 55 additions & 0 deletions docs/src/bootstrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Bootstrap Filter

The [`BootstrapFilter`](@ref) is the simplest filter provided by the library and should be the starting point for most tasks.

## Quick Start

With a POMDPs.jl model, setup looks like this:
```jldoctest intro; output=false, filter=r"BasicParticleFilter.*" => s"BasicParticleFilter"
using ParticleFilters, POMDPModels
pomdp = TigerPOMDP()
pf = BootstrapFilter(pomdp, 10)
# output
BasicParticleFilter()
```

Without POMDPs.jl, setup looks like this:
```jldoctest intro; output=false, filter=r"BasicParticleFilter.*" => s"BasicParticleFilter"
using ParticleFilters, Distributions
dynamics(x, u, rng) = x + u + randn(rng)
y_likelihood(x_previous, u, x, y) = pdf(Normal(), y - x)
pf = BootstrapFilter(dynamics, y_likelihood, 10)
# output
BasicParticleFilter()
```

Once the filter has been created the [`update`](@ref) function can be used to perform a particle filter update.
```jldoctest intro; output=false, filter=r"WeightedParticleBelief.*" => s"WeightedParticleBelief"
b = ParticleCollection([1.0, 2.0, 3.0, 4.0])
u = 1.0
y = 3.0
b_new = update(pf, b, u, y)
# output
WeightedParticleBelief()
```

## More on the Bootstrap Filter

The [`BootstrapFilter`](@ref) is designed to be a sensible default choice for starting with particle filtering. The basic bootstrap filter approach was first described in "Novel approach to nonlinear / non-Gaussian Bayesian state estimation" by Gordon, Salmond, and Smith.
The version in this package first checks whether the normalized [effective sample size](@ref effective_sample_size) of the particle belief is above a threshold (the `resample_threshold` argument). If it is below the threshold, the belief is resampled using [`low_variance_sample`](@ref). The particles are then propagated through the dynamics model and weighted by the likelihood of the observation.

The `BootstrapFilter` offers a modest level of customization. The most common need for customization is recovering from particle depletion. For this case, use the `postprocess` keyword argument to specify a function that can be used to check for depletion and recover. See the [Handling Particle Depletion](@ref) section for more information about this task. If more customization is needed, users should use the [`BasicParticleFilter`](@ref).

```@docs
BootstrapFilter
```
Loading

2 comments on commit 458c1fd

@zsunberg
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/124741

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.6.0 -m "<description of version>" 458c1fdc44388eb80144d6576da8f87f134e86de
git push origin v0.6.0

Please sign in to comment.