EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ > Class Template Reference

A particle filter: a weighted set of state points, moved by sampling the process model and weighted by the measurement likelihood. More...

#include <EmrysFilter/Filters/ParticleFilter.h>

Inheritance diagram for Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >:

Public Types

using Scalar = Scalar_
 the scalar type
using SpaceType = Space_
 the state space
using ModelType = Model_
 the process model
using StateType = ParticleSet<Space_, Scalar_, Allocator_>
 the state the filter owns
using allocator_type = Allocator_
 the state's allocator

Public Member Functions

 ParticleFilter (std::type_identity_t< Model_ > model, StateType state, std::type_identity_t< Upkeep_ > upkeep={})
 A filter with process model model, initial particles state, which must not be empty, and upkeep upkeep, which maintain() runs.
allocator_type get_allocator () const
StateType const & state () const
StateType & state ()
void setState (StateType state)
 Replaces the particles, as when reinitializing, with a set that has at least one and whose space the process model's writes partition, as the constructor's must.
Model_ const & model () const
Model_ & model ()
template<std::uniform_random_bit_generator G, typename... Inputs>
requires (!isFixedStep<Model_>)
void predict (G &rng, TimeStep< Scalar_ > auto const &dt, Inputs const &... inputs)
 Moves each particle forward by dt, any TimeStep (see Time steps), through the process model and adds an independent draw of the process noise \( Q \), drawn with rng.
template<ExecutionPolicy P, std::uniform_random_bit_generator G, typename... Inputs>
requires (!isFixedStep<Model_>)
void predict (P &&policy, G &rng, TimeStep< Scalar_ > auto const &dt, Inputs const &... inputs)
 predict(rng, dt, inputs...) under policy: the draws are taken first, as the sequential prediction takes them, and the particles then move as a for-each, so the result is Sequential's (see Execution policies).
template<std::uniform_random_bit_generator G, typename... Inputs>
requires (isFixedStep<Model_> && !internal::leadsWithSteps<Inputs...>)
void predict (G &rng, Inputs const &... inputs)
 Moves each particle forward by the fixed-step model's step, as predict(rng, dt, inputs...) does, with the model's cached factor of \( Q \) (FixedStepModel::noiseRoot()).
template<ExecutionPolicy P, std::uniform_random_bit_generator G, typename... Inputs>
requires (isFixedStep<Model_> && !internal::leadsWithSteps<Inputs...>)
void predict (P &&policy, G &rng, Inputs const &... inputs)
 predict(rng, inputs...) with a fixed-step model, under policy, as predict(policy, rng, dt, inputs...) runs.
template<std::uniform_random_bit_generator G, typename... Inputs>
requires isFixedStep<Model_>
void predict (G &rng, Steps steps, Inputs const &... inputs)
 Moves each particle forward by steps of the fixed-step model, as that many calls of predict(rng, inputs...) do, with inputs at every step and the draws in the same order.
template<ExecutionPolicy P, std::uniform_random_bit_generator G, typename... Inputs>
requires isFixedStep<Model_>
void predict (P &&policy, G &rng, Steps steps, Inputs const &... inputs)
 predict(rng, steps, inputs...) under policy, as that many calls of predict(policy, rng, inputs...) run.
template<internal::WeighsPoints M>
std::expected< ParticleFilterUpdate< Scalar_ >, WeightError > update (M const &measurement)
 Multiplies each particle's weight by the observation's likelihood there, \( p(z \mid x_i) \), and normalizes the weights.
template<ExecutionPolicy P, internal::WeighsPoints M>
std::expected< ParticleFilterUpdate< Scalar_ >, WeightError > update (P &&policy, M const &measurement)
 update() under policy: the likelihoods are a for-each over the particles and the evidence and the total weight reductions, which match Sequential's to rounding (see Execution policies).

Detailed Description

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
class Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >

A particle filter: a weighted set of state points, moved by sampling the process model and weighted by the measurement likelihood.

predict() moves each particle through the process model and adds a draw of its process noise, so it needs no transform method and the model may be as nonlinear as it likes. update() multiplies each particle's weight by \( p(z \mid x_i) \) from logLikelihoods(), which any measurement with additive noise provides, including mixture noise, and a model with its own logLikelihood(g), such as one known only by its likelihood, supplies itself (decisions.md [likelihood-measurements]).

The filter never resamples on its own. Upkeep, such as resampling when the effective sample size drops, is applied by the caller to state(), when it fits the problem: once after the last update before a prediction, not after each of several updates at one time.

With Resample applied by the caller this is the bootstrap filter of Gordon, Salmond, and Smith (1993), the sequential importance resampling filter of Arulampalam et al. (2002), §IV.B, with the transition prior as the proposal.

Both steps also take an execution policy first (see Execution policies). A prediction draws every particle's standard normals before it moves any, in the order a sequential prediction draws them, so the particles then move as a for-each and match Sequential's from the same generator. The normals come from std::normal_distribution, so a seed fixes the run within one standard library only (decisions.md [random-draws]). An update's likelihoods are a for-each and its evidence a reduction.

Under Sequential a prediction draws one particle's normals at a time, in a vector sized by the noise and the uncertain inputs, so it takes no scratch, and an update's likelihoods go into scratch from the state's allocator, reused across updates. On a bounded space its steps therefore allocate nothing under Sequential once it has run one at a steady number of particles (see Allocation). Under another policy the draws go into scratch from the state's allocator too, reserved for the noise and reused, which the first prediction with an uncertain input grows.

auto const upkeep = upkeep::whenEss(std::less{}, 0.5, upkeep::resample(rng), upkeep::roughen(rng));
filter.predict(rng, dt);
filter.update(fix);
filter.update(range);
upkeep(filter.state()); // resampled and roughened if the ESS is below half
filter.predict(std::execution::par, rng, dt); // the same, on every core
ParticleFilter(std::type_identity_t< Model_ > model, StateType state, std::type_identity_t< Upkeep_ > upkeep={})
A filter with process model model, initial particles state, which must not be empty,...
Definition ParticleFilter.h:266
Kinematic< Scalar, Position, Velocity > ConstantVelocity
Constant velocity (white-noise acceleration): position moves by velocity times .
Definition ProcessModel.h:1152
constexpr Resample< G, R > resample(G &generator, R resampler={})
Definition Upkeep.h:494
WhenEss< Compare, internal::DeducedScalar< Value >, Actions... > whenEss(Compare compare, Value fraction, Actions... actions)
Definition Upkeep.h:805
constexpr Roughen< G, internal::DeducedScalar< Value > > roughen(G &generator, Value scale=1)
Definition Upkeep.h:694

The filter can hold its upkeep, given last to the constructor, and run it where the caller calls maintain():

auto filter = ParticleFilter{model, initial, upkeep::whenEss(std::less{}, 0.5, upkeep::resample(rng),
filter.predict(rng, dt);
filter.update(fix);
(void)filter.maintain(); // the same as filter.upkeep()(filter.state())
Model_ const & model() const
Definition ParticleFilter.h:325
Template Parameters
Space_the state space (see Spaces)
Model_the process model, a ProcessModel or a FixedStepModel, which partition the space; with a FixedStepModel, predict() takes no time step
Scalar_scalar type, double by default
Allocator_the particles' allocator (see Allocation)
Upkeep_the upkeep maintain() runs on the particles (see Upkeep), upkeep::None unless given one
See also
ParticleSet, WhenEss, Resample, Filters

Constructor & Destructor Documentation

◆ ParticleFilter()

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::ParticleFilter ( std::type_identity_t< Model_ > model,
StateType state,
std::type_identity_t< Upkeep_ > upkeep = {} )
inline

A filter with process model model, initial particles state, which must not be empty, and upkeep upkeep, which maintain() runs.

Its scratch comes from the state's allocator, with room for the state's capacity if it has one. The model is not deduced from here, so that the deduction guide below, which binds fixedStep()'s result to the state's space, deduces the filter's arguments.

Member Function Documentation

◆ get_allocator()

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
allocator_type Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::get_allocator ( ) const
inlinenodiscard
Returns
the allocator of the particles and scratch

◆ model() [1/2]

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
Model_ & Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::model ( )
inlinenodiscard
Returns
the process model, to change its parameters

◆ model() [2/2]

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
Model_ const & Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::model ( ) const
inlinenodiscard
Returns
the process model

◆ predict() [1/2]

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
template<std::uniform_random_bit_generator G, typename... Inputs>
requires (!isFixedStep<Model_>)
void Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::predict ( G & rng,
TimeStep< Scalar_ > auto const & dt,
Inputs const &... inputs )
inline

Moves each particle forward by dt, any TimeStep (see Time steps), through the process model and adds an independent draw of the process noise \( Q \), drawn with rng.

inputs reach the model as in Emrys::predict(), except that each uncertain input is drawn once per particle rather than carried as a covariance. The weights are unchanged.

\( Q \) may be singular, as for a Constant model, whose coordinates then move only as the dynamics move them. Each particle's noise is drawn before its uncertain inputs, which are drawn in order.

◆ predict() [2/2]

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
template<ExecutionPolicy P, std::uniform_random_bit_generator G, typename... Inputs>
requires (!isFixedStep<Model_>)
void Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::predict ( P && policy,
G & rng,
TimeStep< Scalar_ > auto const & dt,
Inputs const &... inputs )
inline

predict(rng, dt, inputs...) under policy: the draws are taken first, as the sequential prediction takes them, and the particles then move as a for-each, so the result is Sequential's (see Execution policies).

The process model's propagate() then runs for several particles at once.

◆ setState()

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
void Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::setState ( StateType state)
inline

Replaces the particles, as when reinitializing, with a set that has at least one and whose space the process model's writes partition, as the constructor's must.

The scratch follows the new set's allocator and capacity.

◆ state() [1/2]

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
StateType & Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::state ( )
inlinenodiscard
Returns
the current particles, to change where they are between steps, as upkeep such as resampling does. The set must keep its space, its allocator, and at least one particle; setState() replaces it with another, and checks it.

◆ state() [2/2]

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
StateType const & Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::state ( ) const
inlinenodiscard
Returns
the current particles

◆ update() [1/2]

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
template<internal::WeighsPoints M>
std::expected< ParticleFilterUpdate< Scalar_ >, WeightError > Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::update ( M const & measurement)
inlinenodiscard

Multiplies each particle's weight by the observation's likelihood there, \( p(z \mid x_i) \), and normalizes the weights.

If no particle can explain the observation, the weights are unchanged and the measurement is rejected, with log-likelihood \( -\infty \) (decisions.md [update-outcomes]).

Returns
the observation's log-likelihood under the particles and whether it was accepted (see ParticleFilterUpdate), or the WeightError of prior weights that fail to define a distribution, as weights set to zero by the caller do, with the state unchanged

◆ update() [2/2]

template<Space Space_, typename Model_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator, typename Upkeep_ = upkeep::None>
template<ExecutionPolicy P, internal::WeighsPoints M>
std::expected< ParticleFilterUpdate< Scalar_ >, WeightError > Emrys::ParticleFilter< Space_, Model_, Scalar_, Allocator_, Upkeep_ >::update ( P && policy,
M const & measurement )
inlinenodiscard

update() under policy: the likelihoods are a for-each over the particles and the evidence and the total weight reductions, which match Sequential's to rounding (see Execution policies).

The measurement's measure() then runs for several particles at once.


The documentation for this class was generated from the following file: