EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
CramerRao.h
Go to the documentation of this file.
1#pragma once
2
9
10#include <expected>
11#include <type_traits>
12#include <utility>
13
14#include <Eigen/Cholesky>
15
31
32namespace Emrys
33{
34
35namespace internal
36{
37
41template <typename Matrix>
42struct InfoSums
43{
44 Matrix first;
45 Matrix second;
46 bool valid;
47};
48
50template <typename Matrix>
51[[nodiscard]] InfoSums<Matrix>
52addInfoSums(InfoSums<Matrix> a, InfoSums<Matrix> const & b)
53{
54 a.first += b.first;
55 a.second += b.second;
56 a.valid = a.valid && b.valid;
57 return a;
58}
59
60} // namespace internal
61
117template <Space Space_, RealScalar Scalar_ = double>
119{
120public:
121
122 using Scalar = Scalar_;
123 using SpaceType = Space_;
126
130 template <MatrixExpression Info>
132 : m_space{std::move(space)}
133 , m_info{std::forward<Info>(info)}
134 {
135 EMRYS_FILTER_ASSERT(
136 m_info.rows() == differenceDimension(m_space) && m_info.cols() == m_info.rows()
137 && "the information matrix must have a row and a column per difference coordinate"
138 );
139 }
140
145 [[nodiscard]] static std::expected<PosteriorCramerRaoBound, GaussianError>
147 {
148 auto const llt = Eigen::LLT<MatrixType>{internal::symmetricFromLower<MatrixType>(prior.covarianceLower())};
149 if (llt.info() != Eigen::Success)
150 {
151 return std::unexpected{GaussianError::NotPositiveDefinite};
152 }
153 MatrixType info = internal::inverseFrom<MatrixType>(llt);
154 return PosteriorCramerRaoBound{prior.space(), symmetric(std::move(info))};
155 }
156
158 [[nodiscard]] Space_ const &
159 space() const
160 {
161 return m_space;
162 }
163
165 [[nodiscard]] MatrixType const &
166 info() const
167 {
168 return m_info;
169 }
170
174 [[nodiscard]] std::expected<MatrixType, GaussianError>
176 {
177 auto const llt = Eigen::LLT<MatrixType>{m_info};
178 if (llt.info() != Eigen::Success)
179 {
180 return std::unexpected{GaussianError::Singular};
181 }
182 MatrixType result = internal::inverseFrom<MatrixType>(llt);
183 return symmetric(std::move(result));
184 }
185
196 template <ProcessModel Model, typename Truths, typename... Inputs>
197 requires(!ExecutionPolicy<Model>)
198 [[nodiscard]] std::expected<void, GaussianError>
200 Model const & model,
201 Eigen::MatrixBase<Truths> const & truths,
202 TimeStep<Scalar_> auto const & dt,
203 Inputs const &... inputs
204 )
205 {
206 return predict(execution::sequential, model, truths, dt, inputs...);
207 }
208
214 template <ExecutionPolicy P, ProcessModel Model, typename Truths, typename... Inputs>
215 [[nodiscard]] std::expected<void, GaussianError>
217 P && policy,
218 Model const & model,
219 Eigen::MatrixBase<Truths> const & truths,
220 TimeStep<Scalar_> auto const & dt,
221 Inputs const &... inputs
222 )
223 {
224 static_assert(
225 (!UncertainInput<Inputs> && ...),
226 "the bound takes a model's true inputs, as simulate() does; an uncertain input adds noise that the "
227 "transition density of the bound does not model (decisions.md [cramer-rao-bound])"
228 );
229 internal::assertPartitions(model, m_space);
230 Scalar_ const step = internal::toScalarTime<Scalar_>(dt);
231 auto const call = internal::modelWith(std::cref(model), inputs...);
232 auto const resolved = internal::resolveProcess(call, m_space);
233 using R = std::remove_cvref_t<decltype(resolved)>;
234 auto const noise = internal::symmetricFromLower<MatrixType>(resolved.template noise<Scalar_>(step));
235 if constexpr (R::Affine && R::ReturnsJacobian && R::InputCount == 0)
236 {
237 MatrixType const transition = resolved.template transition<Scalar_>(step);
238 return predictFrom(noise, [&](auto const & solveNoise) {
239 internal::InfoSums<MatrixType> sums{transition, MatrixType{}, true};
240 MatrixType const whitened = solveNoise(transition);
241 sums.second.noalias() = internal::multiply(whitened.transpose(), whitened);
242 return sums;
243 });
244 }
245 else
246 {
247 assertTruths(truths);
248 auto const f = [&](auto const & x) {
249 return resolved.propagate(x, step);
250 };
251 using Input = internal::PointInput<Space_, Scalar_>;
252 using Problem = internal::Transform<decltype(f) const, Space_, Input>;
253 Eigen::Index const count = truths.cols();
254 return predictFrom(noise, [&](auto const & solveNoise) {
255 return averageOver(
256 std::forward<P>(policy),
257 count,
258 [&](Eigen::Index i, internal::InfoSums<MatrixType> & sums) {
259 // The problem holds its inputs by reference, so the point is named first.
260 auto const point = Input{m_space, VectorType{truths.col(i)}};
261 auto const problem = Problem{f, m_space, point};
262 auto const linearized = internal::linearizeAt(methods::linearized(), problem);
263 MatrixType const jacobian = linearized.jacobian.leftCols(differenceDimension(m_space));
264 MatrixType const whitened = solveNoise(jacobian);
265 sums.first += jacobian;
266 sums.second.noalias() += internal::multiply(whitened.transpose(), whitened);
267 }
268 );
269 });
270 }
271 }
272
276 template <typename Model, typename Truths, typename... Inputs>
277 requires(
279 && requires(Model const & m) { m.model(); }
280 )
281 [[nodiscard]] std::expected<void, GaussianError>
282 predict(Model const & model, Eigen::MatrixBase<Truths> const & truths, Inputs const &... inputs)
283 {
284 return predict(execution::sequential, model.model(), truths, model.timeStep(), inputs...);
285 }
286
288 template <ExecutionPolicy P, typename Model, typename Truths, typename... Inputs>
289 requires((isFixedStep<Model> || isUnboundStep<Model>) && requires(Model const & m) { m.model(); })
290 [[nodiscard]] std::expected<void, GaussianError>
291 predict(P && policy, Model const & model, Eigen::MatrixBase<Truths> const & truths, Inputs const &... inputs)
292 {
293 return predict(std::forward<P>(policy), model.model(), truths, model.timeStep(), inputs...);
294 }
295
306 template <typename Truths, MeasurementModel M, typename... Args>
307 [[nodiscard]] std::expected<void, GaussianError>
308 update(Eigen::MatrixBase<Truths> const & truths, M const & model, Args const &... args)
309 {
310 return update(execution::sequential, truths, model, args...);
311 }
312
314 template <ExecutionPolicy P, typename Truths, MeasurementModel M, typename... Args>
315 [[nodiscard]] std::expected<void, GaussianError>
316 update(P && policy, Eigen::MatrixBase<Truths> const & truths, M const & model, Args const &... args)
317 {
318 static_assert(
319 (!UncertainInput<Args> && ...),
320 "the bound takes a model's true inputs, as simulate() does; an uncertain input adds noise that the "
321 "measurement density of the bound does not model (decisions.md [cramer-rao-bound])"
322 );
323 assertTruths(truths);
324 auto const observed = internal::modelWith(std::cref(model), args...);
325 auto const resolved = internal::resolveMeasurement(observed, m_space);
326 using R = std::remove_cvref_t<decltype(resolved)>;
327 static_assert(
328 R::HasAdditiveNoise && !R::MixtureNoise,
329 "the bound takes a measurement model with additive Gaussian noise, whose likelihood is the Gaussian of "
330 "Tichavsky et al. (1998), eq. (33) (decisions.md [cramer-rao-bound])"
331 );
332 auto const sums = averageOver(
333 std::forward<P>(policy),
334 truths.cols(),
335 [&](Eigen::Index i, internal::InfoSums<MatrixType> & sums) {
336 auto const linearization = internal::linearizeMeasurementAtPoint<Scalar_>(
337 m_space,
338 VectorType{truths.col(i)},
339 resolved,
341 );
342 using NoiseMatrix = typename std::remove_cvref_t<decltype(linearization)>::NoiseMatrix;
343 auto const llt = Eigen::LLT<NoiseMatrix>{linearization.noise};
344 if (llt.info() != Eigen::Success)
345 {
346 sums.valid = false;
347 return;
348 }
349 using JacobianMatrix = typename std::remove_cvref_t<decltype(linearization)>::JacobianMatrix;
350 JacobianMatrix whitened = linearization.jacobian;
351 internal::solveInPlace(llt.matrixL(), whitened);
352 sums.second.noalias() += internal::multiply(whitened.transpose(), whitened);
353 }
354 );
355 if (!sums.valid)
356 {
357 return std::unexpected{GaussianError::NotPositiveDefinite};
358 }
359 m_info += sums.second;
360 m_info = symmetric(std::move(m_info));
361 return {};
362 }
363
364private:
365
367 [[nodiscard]] static MatrixType
368 symmetric(MatrixType matrix)
369 {
370 MatrixType result = Scalar_(0.5) * (matrix + matrix.transpose());
371 return result;
372 }
373
375 template <typename Truths>
376 void
377 assertTruths(Eigen::MatrixBase<Truths> const & truths) const
378 {
379 EMRYS_FILTER_ASSERT(truths.cols() > 0 && "the bound takes at least one true state");
380 EMRYS_FILTER_ASSERT(truths.rows() == valueDimension(m_space) && "a true state does not fit the space");
381 }
382
386 template <ExecutionPolicy P, typename AddTerms>
387 [[nodiscard]] internal::InfoSums<MatrixType>
388 averageOver(P && policy, Eigen::Index count, AddTerms const & addTerms) const
389 {
390 Eigen::Index const n = differenceDimension(m_space);
391 auto sums = internal::reduceRanges(
392 std::forward<P>(policy),
393 count,
394 [&](Eigen::Index begin, Eigen::Index length) {
395 auto range = internal::InfoSums<MatrixType>{MatrixType::Zero(n, n), MatrixType::Zero(n, n), true};
396 for (Eigen::Index i = begin; i < begin + length; ++i)
397 {
398 addTerms(i, range);
399 }
400 return range;
401 },
402 internal::addInfoSums<MatrixType>
403 );
404 sums.first /= Scalar_(count);
405 sums.second /= Scalar_(count);
406 return sums;
407 }
408
414 template <typename Average>
415 [[nodiscard]] std::expected<void, GaussianError>
416 predictFrom(MatrixType const & noise, Average const & average)
417 {
418 auto const noiseLlt = Eigen::LLT<MatrixType>{noise};
419 if (noiseLlt.info() != Eigen::Success)
420 {
421 return std::unexpected{GaussianError::NotPositiveDefinite};
422 }
423 auto const solveNoise = [&](MatrixType const & a) {
424 MatrixType result = a;
425 internal::solveInPlace(noiseLlt.matrixL(), result);
426 return result;
427 };
428 internal::InfoSums<MatrixType> const sums = average(solveNoise);
429 auto const combinedLlt = Eigen::LLT<MatrixType>{MatrixType{m_info + sums.second}};
430 if (combinedLlt.info() != Eigen::Success)
431 {
432 return std::unexpected{GaussianError::Singular};
433 }
434 MatrixType const noiseInverse = internal::inverseFrom<MatrixType>(noiseLlt);
435 MatrixType gain = sums.first;
436 internal::solveInPlace(noiseLlt, gain);
437 MatrixType whitened = gain.transpose();
438 internal::solveInPlace(combinedLlt.matrixL(), whitened);
439 MatrixType info = noiseInverse;
440 info.noalias() -= internal::multiply(whitened.transpose(), whitened);
441 m_info = symmetric(std::move(info));
442 return {};
443 }
444
445 EMRYS_FILTER_NO_UNIQUE_ADDRESS Space_ m_space;
446 MatrixType m_info;
447};
448
449} // namespace Emrys
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
The prediction and update steps of the square-root and information forms of the Gaussian (decisions....
How a function that stores a matrix argument takes it (decisions.md [forwarded-matrices]): the concep...
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
What marginals and conditionals are built from: resolving Eigen index sets against a space; the share...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
The update of a Gaussian by a measurement (decisions.md [templated-measurements], [weighted-branches]...
Moments: the mean and covariance that a Gaussian carries and that a mixture or a sample set is moment...
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
Process models as functions with reads and writes (decisions.md [model-policies], [continuous-time-no...
Solving by a factorization of a small matrix: transposedSolve() gives , the shape of a Kalman gain an...
The Space concept and what every continuous space shares (decisions.md [sizes-and-dimensions]): the t...
Time steps (decisions.md [time-steps]): TimeTraits, which converts a time type to a scalar,...
Propagating a distribution through a function (decisions.md [method-per-measurement],...
A Gaussian distribution: a mean and covariance in a space.
Definition Gaussian.h:259
MatrixType const & covarianceLower() const
Definition Gaussian.h:543
Space_ const & space() const
Definition Gaussian.h:519
DifferenceMatrix< Space_, Space_, Scalar > MatrixType
a matrix over differences, such as the covariance
Definition Moments.h:76
ValueVector< Space_, Scalar > VectorType
a value, such as the mean
Definition Moments.h:74
std::expected< void, GaussianError > update(Eigen::MatrixBase< Truths > const &truths, M const &model, Args const &... args)
Adds the information that a measurement at the true states truths, a column each, carries: ,...
Definition CramerRao.h:308
typename Moments< Space_, Scalar_ >::VectorType VectorType
a state value
Definition CramerRao.h:124
std::expected< void, GaussianError > predict(Model const &model, Eigen::MatrixBase< Truths > const &truths, TimeStep< Scalar_ > auto const &dt, Inputs const &... inputs)
Moves the bound forward by dt, any TimeStep (see Time steps), through the process model model with th...
Definition CramerRao.h:199
Space_ SpaceType
the state space
Definition CramerRao.h:123
std::expected< void, GaussianError > predict(P &&policy, Model const &model, Eigen::MatrixBase< Truths > const &truths, Inputs const &... inputs)
predict(model, truths, inputs...) over a fixed-step model's step under policy.
Definition CramerRao.h:291
static std::expected< PosteriorCramerRaoBound, GaussianError > create(GaussianParams< Space_, Scalar_ > const &prior)
Definition CramerRao.h:146
PosteriorCramerRaoBound(Space_ space, Info &&info)
The bound in space with the information matrix information, symmetric and positive semidefinite over ...
Definition CramerRao.h:131
std::expected< void, GaussianError > predict(Model const &model, Eigen::MatrixBase< Truths > const &truths, Inputs const &... inputs)
predict() over the step of the fixed-step model model, or of fixedStep()'s result,...
Definition CramerRao.h:282
Scalar_ Scalar
the scalar type
Definition CramerRao.h:122
typename Moments< Space_, Scalar_ >::MatrixType MatrixType
, over differences
Definition CramerRao.h:125
Space_ const & space() const
Definition CramerRao.h:159
std::expected< void, GaussianError > update(P &&policy, Eigen::MatrixBase< Truths > const &truths, M const &model, Args const &... args)
update(truths, model, args...) under policy: a reduction over the columns, as in predict().
Definition CramerRao.h:316
MatrixType const & info() const
Definition CramerRao.h:166
std::expected< MatrixType, GaussianError > covariance() const
Definition CramerRao.h:175
std::expected< void, GaussianError > predict(P &&policy, Model const &model, Eigen::MatrixBase< Truths > const &truths, TimeStep< Scalar_ > auto const &dt, Inputs const &... inputs)
predict(model, truths, dt, inputs...) under policy: the Jacobians at the columns are taken as a reduc...
Definition CramerRao.h:216
An execution policy: execution::sequential, a policy of the standard library such as std::execution::...
Definition Execution.h:459
A measurement model: what measurementModel() returns, or a composition of such models (decisions....
Definition MeasurementModel.h:231
A process model: what processModel() returns, a built-in model, or a composition of process models (d...
Definition ProcessModel.h:2010
A step of time that converts to Scalar: Scalar itself, or any type with a TimeTraits,...
Definition Time.h:128
An argument of transform() that is uncertain: something with a mean, a space, and either a covariance...
Definition Linearization.h:43
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
@ NotPositiveDefinite
the covariance is not positive definite, so it has no Cholesky factor or inverse
Definition NumericError.h:31
@ Singular
the precision is not positive definite, so the mean and covariance are undefined
Definition NumericError.h:32
constexpr auto symmetric(Fallbacks... fallbacks)
Definition Roots.h:1204
constexpr Sequential sequential
The default execution policy: plain loops on the calling thread, in index order (Sequential).
Definition Execution.h:128
constexpr bool isFixedStep
Whether Model is a FixedStepModel, whose predictions take no time step.
Definition ProcessModel.h:2776
constexpr bool isUnboundStep
Whether Model is a fixed step not yet bound to a state space: an UnboundFixedStep,...
Definition ProcessModel.h:2792
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
constexpr Linearized linearized()
Definition Methods.h:333