|
EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
|
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. | |
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.
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.
| 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.
Its upkeep, given last, is run by maintain():
|
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.
|
nodiscard |
|
nodiscard |
float; an integer stands for double). It computes the quantile on every update; see Gate for a threshold computed once.
|
nodiscard |
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.
|
nodiscard |
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).
|
nodiscard |
|
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.