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

The first layer (decisions.md [layered-headers]): what the rest of the library builds on and what depends on nothing but Eigen. More...

Topics

 Allocation
 Storage sized by a space never touches the heap when the space is fixed or bounded (see Spaces), and an unbounded space allocates through Eigen's ordinary heap path.
 Execution policies
 Where loops run (decisions.md [execution-policies]).
 Resamplers
 A resampler is a function object called as r(weights, ancestors, rng).
 Time steps
 What a step of time may be (decisions.md [time-steps]).

Concepts

concept  Emrys::MatrixExpression
 A dense matrix or matrix expression, possibly cv- or reference-qualified: what a const Eigen::MatrixBase<Derived>& parameter accepts.
concept  Emrys::DiagonalExpression
 A diagonal matrix or diagonal expression, possibly cv- or reference-qualified, such as v.asDiagonal() or an Eigen::DiagonalMatrix: what a const Eigen::DiagonalBase<Derived>& parameter accepts.
concept  Emrys::CovarianceExpression
 A covariance as a function that stores one takes it: a MatrixExpression, or a DiagonalExpression, whose diagonal holds the variances and whose other entries are zero, so that GaussianParams<State>{mean, variances.asDiagonal()} needs no toDenseMatrix().
concept  Emrys::RealScalar
 A type usable as the scalar of a distribution: a real number type such as float, double, or an automatic-differentiation scalar.

Classes

struct  std::formatter< T, char >
 Formats any type the library describes (Emrys::internal::Printable) as its operator<< prints it. More...
class  Emrys::LogSumExp< Scalar >
 Streaming log-sum-exp: accumulates \( \log \sum_i e^{x_i} \) without overflow. More...

Macros

#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
 Assertion used for programmer errors (misuse of the API).

Typedefs

using Emrys::WeightError = NumericError
 Reasons a collection of log-weights cannot be normalized: Empty, ZeroSum, or NonFinite of NumericError.

Enumerations

enum class  Emrys::NumericError {
  NumericError::NotPositiveDefinite , NumericError::Singular , NumericError::NonFinite , NumericError::Empty ,
  NumericError::ZeroSum
}
 Reasons a computation from parameters the caller gave can fail: a matrix that does not factorize or a set of log-weights that does not normalize. More...

Functions

template<typename Scalar>
Scalar Emrys::chiSquaredCdf (std::type_identity_t< Scalar > const &x, std::type_identity_t< Scalar > const &dof)
template<typename Scalar>
Scalar Emrys::chiSquaredQuantile (std::type_identity_t< Scalar > const &probability, std::type_identity_t< Scalar > const &dof)
template<typename Scalar>
Scalar Emrys::pi ()
template<typename Scalar>
Scalar Emrys::log2Pi ()
template<typename Scalar>
Scalar Emrys::logGamma (std::type_identity_t< Scalar > const &x)
template<typename Scalar>
Scalar Emrys::logOnePlus (std::type_identity_t< Scalar > const &x)
template<typename Scalar>
Scalar Emrys::floorOf (std::type_identity_t< Scalar > const &x)
template<typename T>
bool Emrys::isFinite (T const &x)
template<typename T>
bool Emrys::isNaN (T const &x)
template<typename Derived>
bool Emrys::allFinite (Eigen::DenseBase< Derived > const &x)
template<internal::Printable T>
std::ostream & Emrys::operator<< (std::ostream &stream, T const &value)
 Prints value, any type the library describes (internal::Printable): a distribution, a filter, by name with its state nested under it, or an error code.
template<typename M, typename Derived = std::remove_cvref_t<M>>
requires std::derived_from<Derived, Eigen::MatrixBase<Derived>> && (!std::is_const_v<std::remove_reference_t<M>>) &&
((Derived::Flags & Eigen::LvalueBit) != 0)
void Emrys::triangularize (M &&m)
 Reduces m to upper-triangular form in place by Householder reflections from the left, with a nonnegative diagonal.
template<typename Scalar, ExecutionPolicy P, typename LogAt>
Scalar Emrys::logSumExp (P &&policy, Eigen::Index count, LogAt const &logAt)
template<typename Scalar, typename LogAt>
Scalar Emrys::logSumExp (Eigen::Index count, LogAt const &logAt)
 logSumExp(policy, count, logAt) under execution::sequential.
template<ExecutionPolicy P, typename Derived>
Derived::Scalar Emrys::logSumExp (P &&policy, Eigen::ArrayBase< Derived > const &logs)
template<typename Derived>
Derived::Scalar Emrys::logSumExp (Eigen::ArrayBase< Derived > const &logs)
 logSumExp(policy, logs) under execution::sequential.

Variables

constexpr int Emrys::versionMajor = 0
 The library's version, as major.minor.patch; the same numbers as the CMake project's.
constexpr int Emrys::versionMinor = 1
 The minor version.
constexpr int Emrys::versionPatch = 0
 The patch version.

Detailed Description

The first layer (decisions.md [layered-headers]): what the rest of the library builds on and what depends on nothing but Eigen.

It holds the version (Version.h), the macros (Macros.h), the RealScalar concept and constants and scalar functions in any such scalar (Math.h), the concept that lets a function move a matrix argument it stores (Forwarding.h, [forwarded-matrices]), the allocator conventions and buffers (Allocation.h, [allocation]), the execution policies and the primitives that run loops over a count under them (Execution.h, [execution-policies]), the time steps a prediction takes and their conversion to a scalar (Time.h, [time-steps]), log-weights and their normalization (Weights.h), the resamplers (Resampling.h), the chi-squared distribution's CDF and quantile (ChiSquared.h), the triangularization that the square-root forms of the Gaussian are built on (Triangularization.h, [gaussian-forms]), the products that keep a fixed size on Eigen's fixed-size path (Multiply.h), the solve that gives a gain from a factorization of a small matrix one column at a time (Solve.h), the modification stamp that parameters keep and the checks by which their views read it (Stamp.h, [view-stamp]), and the operator<< and std::formatter that print every distribution and filter (Print.h), left out under EMRYS_FILTER_NO_IO. Read Version.h first, and Spaces next.

Macro Definition Documentation

◆ EMRYS_FILTER_NO_UNIQUE_ADDRESS

#define EMRYS_FILTER_NO_UNIQUE_ADDRESS

Assertion used for programmer errors (misuse of the API).

Defaults to eigen_assert, so it is controlled by the same switches as Eigen's own checks (NDEBUG, EIGEN_NO_DEBUG). Define it before including any EmrysFilter header to override, as an expression like assert() or as a statement such as do { ... } while (false): the library uses it only as a statement of its own.

Lets an empty member, such as the default Linear space, take no storage.

Typedef Documentation

◆ WeightError

Reasons a collection of log-weights cannot be normalized: Empty, ZeroSum, or NonFinite of NumericError.

See also
GaussianMixtureParams::logTotalWeight(), ParticleSet::logTotalWeight()

Enumeration Type Documentation

◆ NumericError

enum class Emrys::NumericError
strong

Reasons a computation from parameters the caller gave can fail: a matrix that does not factorize or a set of log-weights that does not normalize.

A function returns the ones its documentation names, through either name below; a step that can fail both ways, such as a mixture filter's update, may return any of them (decisions.md [update-outcomes]).

A Gaussian's errors name the matrix that failed: a covariance that is singular or indefinite, as a square-root factor with a zero on its diagonal gives, is NotPositiveDefinite; a precision that is, as a prior with no information has, is Singular.

See also
GaussianError, WeightError
Enumerator
NotPositiveDefinite 

the covariance is not positive definite, so it has no Cholesky factor or inverse

Singular 

the precision is not positive definite, so the mean and covariance are undefined

NonFinite 

a matrix, a factor, or a log-weight holds NaN or infinity (+inf, for a log-weight)

Empty 

there are no weights

ZeroSum 

every weight is zero (every log-weight is -inf)

Function Documentation

◆ allFinite()

template<typename Derived>
bool Emrys::allFinite ( Eigen::DenseBase< Derived > const & x)
nodiscard
Returns
whether every coefficient of x is finite.

In place of x.allFinite(), which applies numext::isfinite to each coefficient and so fails for a matrix of AutoDiffScalar. A floating-point matrix takes allFinite(), Eigen's vectorized test; any other tests each coefficient with isFinite().

◆ chiSquaredCdf()

template<typename Scalar>
Scalar Emrys::chiSquaredCdf ( std::type_identity_t< Scalar > const & x,
std::type_identity_t< Scalar > const & dof )
nodiscard
Returns
the chi-squared cumulative distribution function with dof degrees of freedom at x, \( P(\tfrac{k}{2}, \tfrac{x}{2}) \)

The chi-squared distribution is that of a sum of dof squared standard normals, which is what a normalized innovation squared is for a consistent filter (Bar-Shalom, Li, and Kirubarajan(2001); Chen et al. (2023), §III-A).

Scalar is not deduced, as for logGamma(): a call names it, as in chiSquaredCdf<Scalar>(squaredDistance, k), and computes in it throughout. In an autodiff scalar the derivative in x is the chi-squared density, to the precision of the series or the continued fraction. It is 0 at zero and below, 1 at infinity, and NaN at NaN.

◆ chiSquaredQuantile()

template<typename Scalar>
Scalar Emrys::chiSquaredQuantile ( std::type_identity_t< Scalar > const & probability,
std::type_identity_t< Scalar > const & dof )
nodiscard
Returns
the probability quantile of the chi-squared distribution with dof degrees of freedom: the NIS that a consistent filter's innovations stay under with that probability.

Solved by safeguarded Newton iteration on chiSquaredCdf(), to within four epsilons of Scalar: the root is bracketed by doubling, each Newton step uses the chi-squared density as the derivative, and a step that leaves the bracket, or shrinks less than half as fast as the step before last, is replaced by bisection (Press et al. (2007), rtsafe). A quantile far below one, as a small probability with a degree of freedom or less gives, is bracketed from below by squaring a ratio and bisected geometrically, so that it takes as few steps as one near one; a quantile below the smallest normal Scalar comes out at most that. Scalar is not deduced, as for chiSquaredCdf().

A probability outside (0, 1) is a programmer error. Where asserts are off it still returns, as the limits do: zero for a probability of zero or less, infinity for one or more, and NaN for a NaN probability or degrees of freedom that are not positive.

◆ floorOf()

template<typename Scalar>
Scalar Emrys::floorOf ( std::type_identity_t< Scalar > const & x)
nodiscard
Returns
\( \lfloor x \rfloor \), the largest integer not above x, as a Scalar.

In place of Eigen::numext::floor, which fails for AutoDiffScalar for want of its floor. A floating-point Scalar takes std::floor. An Eigen::AutoDiffScalar takes its value's, with zero derivatives: the floor is constant between integers, and a wrap such as Periodic's subtracts a whole number of periods, so its derivative is the identity's. Any other scalar goes through Eigen::numext::floor.

Scalar is not deduced, as for logGamma(): a call names it, as in floorOf<Scalar>(x).

◆ isFinite()

template<typename T>
bool Emrys::isFinite ( T const & x)
nodiscard
Returns
whether x is finite, neither infinite nor NaN.

In place of Eigen::numext::isfinite, which fails for AutoDiffScalar. A floating-point scalar takes std::isfinite, an Eigen::AutoDiffScalar (or an expression of them) asks of its value, since finiteness is a property of the value and not of the derivatives, and any other scalar goes through Eigen::numext::isfinite. It returns bool, so the argument is deduced.

◆ isNaN()

template<typename T>
bool Emrys::isNaN ( T const & x)
nodiscard
Returns
whether x is NaN.

In place of Eigen::numext::isnan, which fails for AutoDiffScalar; the dispatch is isFinite()'s.

◆ log2Pi()

template<typename Scalar>
Scalar Emrys::log2Pi ( )
nodiscard
Returns
\( \log 2\pi \) as a Scalar, the constant in every Gaussian normalizer.

A floating-point Scalar takes a literal, correctly rounded for float, double, and an 80-bit long double, so it is a constant on every compiler; std::log is not constexpr before C++26, and std::log(2 * pi) would be one ulp low in double, since \( \pi \) is rounded before the log. A scalar whose Eigen::NumTraits<Scalar>::Literal is floating point, such as Eigen's AutoDiffScalar, converts that literal type's, so its derivatives are zero. Any other scalar takes the log of twice pi() in its own precision, which a multiprecision type may set at run time, so nothing is cached; the product is held in a Scalar before the log because an expression type is one numext::log cannot take.

◆ logGamma()

template<typename Scalar>
Scalar Emrys::logGamma ( std::type_identity_t< Scalar > const & x)
nodiscard
Returns
\( \log \Gamma(x) \), the log of the gamma function, as a Scalar.

In place of Eigen::numext::lgamma, which supports only float and double, and std::lgamma, which AutoDiffScalar does not overload. A floating-point Scalar takes std::lgamma, long double included. An Eigen::AutoDiffScalar takes its value's and carries the derivatives by \( \tfrac{d}{dx} \log \Gamma(x) = \psi(x) \), with the digamma function \( \psi \) from Eigen::numext::digamma, so its value type must be float or double. Any other scalar calls its own lgamma, found by argument-dependent lookup. The Gamma and Student-t normalizers use it; their derivative with respect to a shape or the degrees of freedom runs through the digamma term.

Scalar is not deduced: std::type_identity_t keeps the argument out of deduction, so a call names it, as in logGamma<Scalar>(a * b). An argument that is an expression, as AutoDiffScalar arithmetic yields, then converts to a plain Scalar rather than becoming the return type.

◆ logOnePlus()

template<typename Scalar>
Scalar Emrys::logOnePlus ( std::type_identity_t< Scalar > const & x)
nodiscard
Returns
\( \log(1 + x) \) as a Scalar, accurate for small x.

In place of Eigen::numext::log1p, which fails for AutoDiffScalar for want of its log1p. A floating-point Scalar takes std::log1p. An Eigen::AutoDiffScalar takes its value's and carries the derivatives by \( \tfrac{d}{dx} \log(1 + x) = 1 / (1 + x) \), keeping the small-x accuracy that log(1 + x) would lose. Any other scalar goes through Eigen::numext::log1p.

Scalar is not deduced, as for logGamma(): a call names it, as in logOnePlus<Scalar>(x).

◆ logSumExp() [1/2]

template<ExecutionPolicy P, typename Derived>
Derived::Scalar Emrys::logSumExp ( P && policy,
Eigen::ArrayBase< Derived > const & logs )
nodiscard
Returns
\( \log \sum_i e^{x_i} \) over the elements of logs, in their scalar, -inf for none, under policy: a reduceRanges() that adds each range whole (LogSumExp::add() of an array) and adds the ranges' sums. Under Sequential the whole array is one range. Normalized weights are then (logs - logSumExp(logs)).exp().

◆ logSumExp() [2/2]

template<typename Scalar, ExecutionPolicy P, typename LogAt>
Scalar Emrys::logSumExp ( P && policy,
Eigen::Index count,
LogAt const & logAt )
nodiscard
Returns
\( \log \sum_i e^{x_i} \) over the count inputs logAt(i), each converted to Scalar, -inf for none, under policy: a reduceRanges() that streams each range into a LogSumExp and adds the ranges' sums. Under Sequential it adds the inputs in order, one at a time.

◆ operator<<()

template<internal::Printable T>
std::ostream & Emrys::operator<< ( std::ostream & stream,
T const & value )

Prints value, any type the library describes (internal::Printable): a distribution, a filter, by name with its state nested under it, or an error code.

See Print.h for what a description holds.

std::cout << filter << '\n';

◆ pi()

template<typename Scalar>
Scalar Emrys::pi ( )
nodiscard
Returns
\( \pi \) as a Scalar.

In place of std::numbers::pi_v<Scalar>, which is ill-formed for a scalar that is not floating point, and EIGEN_PI, which rounds through long double. A floating-point Scalar takes std::numbers::pi_v. A scalar whose Eigen::NumTraits<Scalar>::Literal is floating point, such as Eigen's AutoDiffScalar, converts pi_v of that literal type, so its derivatives are zero. Any other scalar, such as a multiprecision type whose literal is itself, computes \( 4 \arctan 1 \) in its own precision through Eigen::numext::atan.

◆ triangularize()

template<typename M, typename Derived = std::remove_cvref_t<M>>
requires std::derived_from<Derived, Eigen::MatrixBase<Derived>> && (!std::is_const_v<std::remove_reference_t<M>>) &&
((Derived::Flags & Eigen::LvalueBit) != 0)
void Emrys::triangularize ( M && m)

Reduces m to upper-triangular form in place by Householder reflections from the left, with a nonnegative diagonal.

On return m holds \( Q^T M \) for the input \( M \) and an orthogonal \( Q \) that is never formed, with zeros below the diagonal. Since \( Q \) is orthogonal, \( (Q^T M)^T (Q^T M) = M^T M \): the leading square block \( R \) of the result is an upper-triangular factor of \( M^T M \), which is its Cholesky factor when the diagonal is positive, without \( M^T M \) ever being formed. Columns appended to \( M \) are carried along: triangularizing \( [\,A \;\; b\,] \) gives \( [\,R \;\; Q^T b\,] \), which is how the square-root information form keeps its vector beside its factor. A rank-deficient \( M \) gives zeros on the diagonal rather than failing.

This is Householder QR (Golub and Van Loan (2013), as given in QR decomposition), with the reflections applied as they are made and then discarded, so the work is in m and a row of scratch the width of m, sized by its compile-time bound: a matrix with a bounded size allocates nothing [allocation]. It departs from the algorithm only in flipping the sign of each row whose diagonal element is negative, which keeps \( R^T R \) and makes the factor of a full-rank \( M \) unique.

m may be any writable Eigen matrix or expression, such as a block of a larger buffer passed as a temporary; a const one is rejected rather than written through.

Variable Documentation

◆ versionMinor

int Emrys::versionMinor = 1
inlineconstexpr

The minor version.

See also
versionMajor

◆ versionPatch

int Emrys::versionPatch = 0
inlineconstexpr

The patch version.

See also
versionMajor