EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Filters

The fifth layer (decisions.md [layered-headers]): a filter owns its process model and a state, predicts with predict(dt, inputs...), and updates with update(measurement) [filters-and-recursions]. More...

Topics

 Upkeep
 Upkeep keeps a filter's representation in bounds without meaning to change the distribution much: resampling particles whose weights have degenerated, roughening them after, pruning the improbable particles, components, or cells, or repairing a covariance that rounding has left not positive definite.

Namespaces

namespace  Emrys::reductions
 The reduction policies, each made by its factory (decisions.md [policy-factories], [reduction-policies]): collapse(), select(), keep(), and gate().

Concepts

concept  Emrys::KalmanState
 What a KalmanFilter holds and a KalmanRecursion acts on: a Component, such as a Gaussian, or a square-root or information form of the Gaussian (see GaussianForm, decisions.md [gaussian-forms]), whose steps FormSteps.h supplies.
concept  Emrys::Recursion
 A recursion: a filter without its state, acting in place on a state passed in (decisions.md [filters-and-recursions]).
concept  Emrys::RecursionOver
 A Recursion whose steps act on the parameters of the component Component_, the constraint a filter over that component puts on its recursion.
concept  Emrys::ReductionPolicy
 A reduction policy for a filter that keeps a State per update: it reduces an update's branches to at most one State (decisions.md [reduction-policies]).
concept  Emrys::MixtureReductionPolicy
 A reduction policy a MixtureFilter over Component_ takes for Update: one that reduces the branches into an output that holds several components (see ReductionPolicy), so that one prior component becomes several, as Keep makes it.

Classes

class  Emrys::AmmFilter< Component_, Model_, Method_, Allocator_, Upkeep_ >
 The autonomous multiple model (AMM) estimator: a bank of filters, one per mode, each running on its own, and the probability that each mode is the one in force, by Bayes' rule over their measurement likelihoods. More...
struct  Emrys::CategoricalPredictedJoint< Space, Scalar >
 One prediction of a HiddenMarkovFilter as a smoother needs it: the distribution the step started from, the prediction, and the matrix that moved one to the other. More...
class  Emrys::HiddenMarkovFilterUpdate< Scalar >
 What a hidden Markov filter's update did: the measurement's log-evidence under the prior, and whether it was accepted. More...
class  Emrys::HiddenMarkovFilter< Space_, Chain_, Scalar_, Upkeep_ >
 A hidden Markov model filter: a CategoricalParams state, the chain that moves it, and the log-evidence of the measurements so far. More...
struct  Emrys::ImmFilterUpdate< ModeUpdate, Scalar_, Allocator >
 What an IMM filter's update did: each mode's update before reduction, the observation's log-likelihood over every mode, and whether any mode accepted the measurement. More...
struct  Emrys::HybridPredictedJoint< Space_, ModeSpace_, Scalar_, Allocator_ >
 One IMM prediction as its smoother needs it: the mode probabilities the step started from, each mode's Gaussian predicted through its own model as a PredictedJoint, and the chain's transition matrix. More...
class  Emrys::ImmRecursion< Component_, Model_, Method_, Allocator_ >
 The interacting multiple model (IMM) filter's steps over a hybrid state, without the state: a switching process model, the method that predicts the modes whose models carry none of their own, and the scratch the modes are mixed and updated into, acting on a Hybrid passed in (decisions.md [filters-and-recursions]). More...
class  Emrys::ImmFilter< Component_, Model_, Method_, Allocator_, Upkeep_ >
 An interacting multiple model (IMM) filter: a hybrid state with one component per mode, and the ImmRecursion that mixes it through a switching process model and updates it per mode. More...
class  Emrys::InfoFilter< Space_, Model_, Method_, Scalar_, Upkeep_ >
 The information filter: a KalmanFilter whose state is an InfoGaussianParams, the precision and information vector about a reference (decisions.md [gaussian-forms]). More...
class  Emrys::KalmanRecursion< Component_, Model_, Method_ >
 The Kalman filter's steps over one component, without the state: a process model and a prediction method, acting on a component passed in (decisions.md [filters-and-recursions]). More...
class  Emrys::KalmanFilter< Component_, Model_, Method_, Recursion_, Upkeep_ >
 A Kalman filter: a Gaussian state and the KalmanRecursion that predicts it through a process model and conditions it on measurements. More...
struct  Emrys::MixtureFilterUpdate< ComponentUpdate, Scalar, Allocator >
 What a mixture filter's update did: each prior component's update before reduction, the observation's log-likelihood under the whole mixture, and whether any component accepted the measurement. More...
struct  Emrys::GaussianMixturePredictedJoint< Space_, Scalar_, Allocator_ >
 One prediction of a mixture of Gaussians as its smoother needs it: the weights the step started from, each component's prediction as a PredictedJoint, and how the components descend from those of the record before. More...
class  Emrys::MixtureFilter< Component_, Model_, Method_, Allocator_, Recursion_, Upkeep_ >
 A mixture filter: a Mixture state, a process model, and the method that predicts through it, each component run by the recursion its type names, or by the one given. More...
class  Emrys::GaussianMixtureFilter< Space_, Model_, Method_, Scalar_, Allocator_, Upkeep_ >
 The MixtureFilter over a GaussianMixtureParams, the spelling for the common case (decisions.md [filters-and-recursions]): each component a KalmanFilter's step, reweighted by its likelihood. More...
class  Emrys::ParticleFilterUpdate< Scalar >
 What a particle filter's update did: the observation's log-likelihood under the particles, and whether it was accepted. More...
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. More...
class  Emrys::RbParticleFilterUpdate< Scalar, Allocator >
 What a Rao-Blackwellized particle filter's update did, per particle and in total. More...
class  Emrys::RbParticleFilter< Space_, Path_, Model_, Covariance_, Method_, Scalar_, Allocator_, Upkeep_ >
 A Rao-Blackwellized particle filter: particles for one block of the state, each carrying a Kalman filter over the rest. More...
class  Emrys::RbPointMassFilterUpdate< Scalar, Allocator >
 What a Rao-Blackwellized point-mass filter's update did, per live cell and in total. More...
class  Emrys::RbPointMassFilter< Grid_, Model_, Covariance_, Method_, Upkeep_ >
 A Rao-Blackwellized point-mass filter: fixed points for one block of the state, such as a coarse position, each carrying a Kalman filter over the rest. More...
class  Emrys::SqrtInfoFilter< Space_, Model_, Method_, Scalar_, Upkeep_ >
 The square-root information filter: a KalmanFilter whose state is a SqrtInfoGaussianParams, the data array \( [\,R \;\; r\,] \) about a reference (decisions.md [gaussian-forms]). More...
class  Emrys::SqrtKalmanFilter< Space_, Model_, Method_, Scalar_, Upkeep_ >
 The square-root Kalman filter: a KalmanFilter whose state is a SqrtGaussianParams, a mean and a lower-triangular factor of the covariance (decisions.md [gaussian-forms]). More...
class  Emrys::NisMonitor< Window, MaxWindow, Scalar, Allocator >
 A chi-squared consistency check over a window of updates. More...
class  Emrys::NeesMonitor< Window, MaxWindow, Scalar, Allocator >
 A chi-squared consistency check of a filter's estimates against the true states, over a window of them. More...
struct  Emrys::reductions::Collapse
 Reduction policy that moment-matches an update's branches into one Gaussian: soft outlier rejection, as in the probabilistic data association of Bar-Shalom and Tse (1975). More...
struct  Emrys::reductions::Select
 Reduction policy that keeps the most probable branch: hard rejection by comparing the noise components with each other. More...
struct  Emrys::reductions::Keep
 Reduction policy that keeps every branch, each added to the filter's output with its own posterior probability (see ReductionPolicy). More...
struct  Emrys::NisThreshold< Scalar >
 A Gate's threshold given directly as a normalized innovation squared, rather than as a probability, in the scalar it is written in (NisThreshold{9.21f} is a float). More...
class  Emrys::reductions::Gate< Inner, Scalar_ >
 Reduction policy that rejects branches by a chi-squared test on their normalized innovation squared, then reduces the rest with Inner. More...

Typedefs

template<typename Grid_, typename Covariance_ = SharedCovariance, typename Upkeep_ = upkeep::None>
using Emrys::PointMassFilter = RbPointMassFilter<Grid_, NoProcessModel, Covariance_, methods::Linearized, Upkeep_>
 A Bayesian grid filter: an RbPointMassFilter whose points cover the whole state, so nothing is left Gaussian.

Enumerations

enum class  Emrys::Consistency { Consistency::Consistent , Consistency::Overconfident , Consistency::Underconfident }
 The verdict of a NisMonitor's or NeesMonitor's verdict(): where the sum of the window's statistics lies against the two-sided bounds() at a probability \( p \), which split \( 1 - p \) equally between the two tails. More...

Functions

template<GaussianForm Estimate, typename Derived>
std::expected< typename Estimate::Scalar, GaussianError > Emrys::nees (Eigen::MatrixBase< Derived > const &truth, Estimate const &estimate)
template<typename Path, GaussianForm Estimate, typename Derived>
requires requires(Estimate const & estimate) { estimate.template marginal<Path>(); }
std::expected< typename Estimate::Scalar, GaussianError > Emrys::nees (Eigen::MatrixBase< Derived > const &truth, Estimate const &estimate)
constexpr Collapse Emrys::reductions::collapse ()
constexpr Select Emrys::reductions::select ()
constexpr Keep Emrys::reductions::keep ()
template<typename Value, typename Inner = Collapse>
requires std::is_arithmetic_v<Value>
Gate< Inner, internal::DeducedScalar< Value > > Emrys::reductions::gate (Value probability, Inner inner={})
template<RealScalar Scalar, typename Inner = Collapse>
Gate< Inner, Scalar > Emrys::reductions::gate (NisThreshold< Scalar > threshold, Inner inner={})
template<typename State, typename Policy, typename Branches>
requires internal::BranchRange<Branches> && internal::ReducesInto< Policy, Branches, internal::OneComponentOutput<State,
internal::BranchScalarOf<Branches>, std::optional<State>>>
std::optional< State > Emrys::reduceToOne (Policy const &policy, Branches &&branches)

Variables

constexpr int Emrys::Cumulative = -2
 The window of a NisMonitor or NeesMonitor that drops nothing: its test runs over every statistic added since construction or reset(), and it keeps only their sum and their total degrees of freedom.

Detailed Description

The fifth layer (decisions.md [layered-headers]): a filter owns its process model and a state, predicts with predict(dt, inputs...), and updates with update(measurement) [filters-and-recursions].

A filter is named by the density of its state and holds the state's parameters, which each step changes in place (Parameters and densities). KalmanFilter.h is the shape the others follow, and over a form of the Gaussian it is the filter that InfoFilter.h, SqrtKalmanFilter.h, and SqrtInfoFilter.h spell [gaussian-forms]; MixtureFilter.h, ImmFilter.h, ParticleFilter.h, RbPointMassFilter.h, and RbParticleFilter.h differ from it in their state, with RbUpdate.h shared by the last two, while HiddenMarkovFilter.h has nothing left Gaussian [emissions]. AmmFilter.h spells the IMM on the identity chain [autonomous-multiple-model]. Upkeep between steps is the caller's [upkeep]: a filter holds the upkeep it was given, FilterUpkeep.h's, and runs it on its state when the caller calls maintain(). So is the record of the forward pass that the histories of Smoothers smooth [smoothing]. Recursion.h spells what KalmanFilter.h's and MixtureFilter.h's filters ask of the recursion that runs their steps, so that one of the user's own runs inside them [user-extension-points].

Most of the filters share one structure, a discrete variable and a Gaussian state conditioned on it, and differ in what the discrete part is and how they handle it:

Filter Discrete part Handled by
Kalman filter with mixture noise inlier or outlier, memoryless enumeration, collapsed each step
Gaussian mixture filter component index, unlabeled enumeration, reduced
IMM maneuver regime, Markov with transition matrix \( \Pi \) enumeration, mixed through \( \Pi \)
Rao-Blackwellized point mass fixed point of one block enumeration, no mixing
Rao-Blackwellized particle a sampled block sampling
Hidden Markov model the whole state, Markov through \( \Pi \) or a generator enumeration, nothing Gaussian

Memoryless mixture noise is the IMM's case where every row of \( \Pi \) is the same, and a persistent outlier process (bursts, a stuck sensor) is an IMM on the measurement side: the noise additiveNoise(mixture).byMode<Sensor>() (ModeNoise) names the mode axis whose values its components are, which an IMM whose mode space has that axis updates by, and which a filter with no mode space reads as memoryless mixture noise.

A filter owns a state distribution and a recursion: a process model and a prediction method, with any scratch the steps need, acting on a state passed in (decisions.md [filters-and-recursions]). It moves the state forward with predict() and conditions it on measurements with update(), which takes any measurement (see MeasurementModel): each measurement carries its own model, noise, and transform method, so one filter can take a position fix by an affine update and a range-bearing measurement by an unscented one. Different sensors are different measurement types; a stream that mixes them dispatches with std::visit over the user's own variant.

auto filter = KalmanFilter{ConstantVelocity<Position, Velocity>{0.1}, initial};
filter.predict(dt);
auto const updated = filter.update(fix); // GaussianParams noise: one branch
auto const robust = filter.update(robustFix); // mixture noise, collapsed
auto const selected = filter.update(robustFix, reductions::select()); // mixture noise, the most probable kept
auto const gated = filter.update(fix, reductions::gate(0.997)); // rejected when its NIS is improbable
if (!gated) { ... } // S was not positive definite (decisions.md [innovation-cholesky])
if (!gated->accepted) { ... }

An update gives one branch per measurement-noise component (see MeasurementUpdate). A filter whose state is poorer than that reduces them with a policy: Collapse moment-matches, Select keeps the most probable, and Gate drops branches that fail a chi-squared test before passing the rest to another policy. A policy returns the new state, or adds what it keeps to the filter's output; keeping nothing rejects the measurement and keeps the prior (see ReductionPolicy).

Every filter is parameterized by its component first [component-first], [filters-and-recursions]: KalmanFilter over a Gaussian, MixtureFilter over a Mixture of components, ImmFilter over a Hybrid of components. The recursion a component's steps run is the IMM's for a Hybrid and the Kalman one for any other, so a MixtureFilter whose components are hybrid states is a mixture of IMM filters.

Every filter's update reports its outcome in one shape [update-outcomes]. A part of the state whose branches the policy rejects gets probability zero; whether the zero is stored follows from the state, and when every part is rejected the state keeps its prior:

Filter A part rejected
KalmanFilter and the form filters (one part)
GaussianMixtureFilter the component is dropped
ImmFilter, AmmFilter the mode keeps its prior Gaussian with probability 0, which the chain can revive
RbPointMassFilter the cell is removed, since nothing moves probability between cells
RbParticleFilter the particle keeps its Gaussian with weight 0, until resampled
ParticleFilter, HiddenMarkovFilter (no policy); an observation nothing explains leaves the weights

Under the AMM's identity chain a mode of probability zero can't come back, which is what FloorModes is for [autonomous-multiple-model].

Features that only watch the filter stay outside it: a NisMonitor is fed the result of each update, so a filter without one pays nothing.

Every filter here is an implementation of one published in the literature, cited in its own documentation; Särkkä (2013) covers the Kalman family and the particle filter in one notation, and Bar-Shalom, Li, and Kirubarajan (2001) the tracking practice around them.

Read Smoothers next.

Typedef Documentation

◆ PointMassFilter

template<typename Grid_, typename Covariance_ = SharedCovariance, typename Upkeep_ = upkeep::None>
using Emrys::PointMassFilter = RbPointMassFilter<Grid_, NoProcessModel, Covariance_, methods::Linearized, Upkeep_>

A Bayesian grid filter: an RbPointMassFilter whose points cover the whole state, so nothing is left Gaussian.

The state is \( p(x) = \sum_c P(c) \, \delta(x - p_c) \) over the grid's cells, and an update multiplies each cell's probability by \( p(z \mid c) \). As in the Rao-Blackwellized filter, no probability passes between cells, so the filter suits a state that doesn't move, or moves little against the cell spacing, such as a receiver's position or a fixed bias. The grid's path names a block that is the whole state: regularGrid<Composite<Position>, Position>(...). Cells may have covariances about their centers as in PointGrid. Mixture noise and gates need PerPointCovariance, under which each cell's innovation is evaluated on its own. A measurement known only by its likelihood (a model with logLikelihood(g) and no function), whatever the shape of its error, weights the cells exactly under either form (decisions.md [likelihood-measurements]).

The point-mass filter of Bucy and Senne (1971) for a state that does not move, as Bergman (1999), chapter 5, uses it for a position on a terrain map.

auto filter = PointMassFilter{regularGrid<Composite<Position>, Position>(first, spacing, {200, 200})};
filter.update(range); // reads Position
filter.state().marginal<GridAxis<Position, 0>>();
std::expected< UpdateType, NumericError > update(M const &measurement)
Conditions each live cell's Gaussian on measurement given the cell's center, by the measurement's own...
Definition RbPointMassFilter.h:607
auto regularGrid(S space, Eigen::MatrixBase< FirstDerived > const &first, Eigen::MatrixBase< SpacingDerived > const &spacing, std::array< Eigen::Index, std::size_t(internal::PathTag< P >::FixedValueDimension)> const &counts, std::optional< typename Moments< internal::PathTag< P >, typename FirstDerived::Scalar >::MatrixType > cellCovariance={}, Allocator const &allocator={})
Definition GridGaussian.h:681
RbPointMassFilter< Grid_, NoProcessModel, Covariance_, methods::Linearized, Upkeep_ > PointMassFilter
A Bayesian grid filter: an RbPointMassFilter whose points cover the whole state, so nothing is left G...
Definition RbPointMassFilter.h:1085
The axis of a regular grid of points (see regularGrid()) along coordinate I of the block Block: a run...
Definition GridGaussian.h:68

Its upkeep, given last, is run by maintain():

upkeep::whenSize(std::less{}, 100, upkeep::subdivide(2)))};
(void)refining.update(range);
(void)refining.maintain(); // pruned, then refined if fewer than 100 cells are left
constexpr Subdivide subdivide(std::ptrdiff_t factor=2)
Definition Upkeep.h:1071
constexpr PruneBelow< internal::DeducedScalar< Value > > pruneBelow(Value probability)
Definition Upkeep.h:954
Sequence< Actions... > sequence(Actions... actions)
Definition Upkeep.h:879
WhenSize< Compare, Actions... > whenSize(Compare compare, std::ptrdiff_t limit, Actions... actions)
Definition Upkeep.h:852

Enumeration Type Documentation

◆ Consistency

enum class Emrys::Consistency
strong

The verdict of a NisMonitor's or NeesMonitor's verdict(): where the sum of the window's statistics lies against the two-sided bounds() at a probability \( p \), which split \( 1 - p \) equally between the two tails.

A consistent filter is found Overconfident with probability \( (1 - p) / 2 \), and Underconfident with the same probability; a one-sided test at level \( \alpha \) passes \( p = 1 - 2\alpha \) and reads one of the two.

The two differ in kind. Either way the filter combines its prior and the measurements by covariances that are not the true ones, and so less precisely than it could. An overconfident filter's covariance is smaller than its errors, so it reports its estimates as better than they are: an error, which can leave an estimate far from the truth and still trusted. An underconfident filter's covariance is larger than its errors, so its estimates are no worse than it reports: not an error, only the loss of precision, such as from measurement or process noise set larger than the system's.

Enumerator
Consistent 

the sum lies within the bounds

Overconfident 

the sum lies above the upper bound: the covariance understates the errors

Underconfident 

the sum lies below the lower bound: the covariance overstates the errors

Function Documentation

◆ collapse()

Collapse Emrys::reductions::collapse ( )
nodiscardconstexpr
Returns
the Collapse policy

◆ gate() [1/2]

template<RealScalar Scalar, typename Inner = Collapse>
Gate< Inner, Scalar > Emrys::reductions::gate ( NisThreshold< Scalar > threshold,
Inner inner = {} )
nodiscard
Returns
a Gate passing the branches whose NIS is at most threshold, reduced by inner (Collapse by default)

◆ gate() [2/2]

template<typename Value, typename Inner = Collapse>
requires std::is_arithmetic_v<Value>
Gate< Inner, internal::DeducedScalar< Value > > Emrys::reductions::gate ( Value probability,
Inner inner = {} )
nodiscard
Returns
a Gate passing the branches whose NIS is within the probability chi-squared quantile, reduced by inner (Collapse by default). It holds the probability in the scalar it is written in (gate(0.997f) holds a float; an integer stands for double). It computes the quantile on every update; see Gate for a threshold computed once.

◆ keep()

Keep Emrys::reductions::keep ( )
nodiscardconstexpr
Returns
the Keep policy

◆ nees() [1/2]

template<typename Path, GaussianForm Estimate, typename Derived>
requires requires(Estimate const & estimate) { estimate.template marginal<Path>(); }
std::expected< typename Estimate::Scalar, GaussianError > Emrys::nees ( Eigen::MatrixBase< Derived > const & truth,
Estimate const & estimate )
nodiscard
Returns
the nees() of the block of estimate named by the path Path (a tag or a TagPath), at the same block of the true state truth, which is a value of the whole state: the estimate's marginal<Path>() at the truth's block<Path>(), or the GaussianError of either. The degrees of freedom are the block's difference dimension, one for a block of one coordinate.

Testing a block apart is how to find which part of a state is inconsistent: a filter underconfident in a velocity its fixes don't measure passes on the position and fails on the whole state. Reading both by one path keeps the truth's entries matched to the estimate's, where an offset written by hand would follow the space's layout.

auto const positionNees = nees<Position>(truth, filter.state());
std::expected< typename Estimate::Scalar, GaussianError > nees(Eigen::MatrixBase< Derived > const &truth, Estimate const &estimate)
Definition Consistency.h:487

◆ nees() [2/2]

template<GaussianForm Estimate, typename Derived>
std::expected< typename Estimate::Scalar, GaussianError > Emrys::nees ( Eigen::MatrixBase< Derived > const & truth,
Estimate const & estimate )
nodiscard
Returns
the normalized estimation error squared (NEES) of estimate at the true state truth, \( (x \boxminus \hat x)^T P^{-1} (x \boxminus \hat x) \), or the GaussianError of a covariance that doesn't factorize.

estimate is a GaussianForm: a Gaussian in any of its forms, such as a Kalman filter's state, or the collapse of a mixture or a Rao-Blackwellized state, or a marginal of one; its difference dimension is the degrees of freedom. It is the squared Mahalanobis distance of the truth from the estimate, the density's squaredMahalanobisDistance(), with the error taken by \( \boxminus \) in the estimate's space, so a quaternion's is a rotation vector. A GaussianMarginal view, which has no density of its own, is copied into parameters first. For a consistent filter it is chi-squared with that dimension; see NeesMonitor for the test, and nees<Path>() for one block of the state.

The normalized state estimation error squared of Bar-Shalom, Li, and Kirubarajan (2001), as given in Chen et al. (2023), §III-A, equation (7).

◆ reduceToOne()

template<typename State, typename Policy, typename Branches>
requires internal::BranchRange<Branches> && internal::ReducesInto< Policy, Branches, internal::OneComponentOutput<State,
internal::BranchScalarOf<Branches>, std::optional<State>>>
std::optional< State > Emrys::reduceToOne ( Policy const & policy,
Branches && branches )
nodiscard
Returns
the one State that policy, a ReductionPolicy, keeps of branches, or nothing when it keeps none: how a recursion of the user's own runs the policy it is given, whichever of the two forms of ReductionPolicy it has (decisions.md [user-extension-points]). Posteriors are moved out of branches given as an rvalue.

◆ select()

Select Emrys::reductions::select ( )
nodiscardconstexpr
Returns
the Select policy

Variable Documentation

◆ Cumulative

int Emrys::Cumulative = -2
inlineconstexpr

The window of a NisMonitor or NeesMonitor that drops nothing: its test runs over every statistic added since construction or reset(), and it keeps only their sum and their total degrees of freedom.

Where Eigen::Dynamic is a window whose length is given at run time, this is one with no length at all.