EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Likelihood.h
Go to the documentation of this file.
1#pragma once
2
9
10#include <array>
11#include <concepts>
12#include <cstddef>
13#include <expected>
14#include <optional>
15#include <span>
16#include <tuple>
17#include <type_traits>
18#include <utility>
19#include <variant>
20#include <vector>
21
22#include <Eigen/Cholesky>
23
42
43namespace Emrys
44{
45
46namespace internal
47{
48
56template <typename Scalar, typename Noise, typename Residual, typename Omega>
57[[nodiscard]] Scalar
58widenedLogDensity(Noise const & noise, Residual const & residual, Omega const & omega)
59{
60 if (!allFinite(omega))
61 {
62 return Eigen::NumTraits<Scalar>::quiet_NaN();
63 }
64 auto const component = [&](auto const & gaussian) {
65 using Gaussian = std::remove_cvref_t<decltype(gaussian)>;
66 using Widened = GaussianParams<typename Gaussian::SpaceType, Scalar>;
67 using Matrix = typename Widened::MatrixType;
68 Matrix cov = symmetricFromLower<Matrix>(gaussian.covarianceLower());
69 cov += omega;
70 auto const widened = Widened{
71 gaussian.space(),
72 typename Widened::VectorType{gaussian.mean().template cast<Scalar>()},
73 std::move(cov)
74 };
75 auto const density = widened.toDensity();
76 EMRYS_FILTER_ASSERT(density && "the measurement noise must factorize");
77 return Scalar(density->logDensity(residual));
78 };
79 if constexpr (isGaussianMixture<Noise>)
80 {
81 auto const total = noise.logTotalWeight();
82 EMRYS_FILTER_ASSERT(total && "the noise mixture's weights must be valid");
83 auto sum = LogSumExp<Scalar>{};
84 for (auto const & entry : noise)
85 {
86 sum.add(Scalar(entry.logWeight) - Scalar(*total) + component(entry.component));
87 }
88 return sum.value();
89 }
90 else
91 {
92 return component(noise);
93 }
94}
95
103template <ExecutionPolicy P, typename R, typename RowsOf, typename Scalar, typename Scratch>
104void
105fillBatchedLogLikelihoods(
106 P && policy,
107 R const & resolved,
108 Eigen::Index count,
109 RowsOf const & rowsOf,
110 Eigen::Index start,
111 Scalar * result,
112 Scratch & scratch
113)
114{
115 using InMatrix = typename R::template InMatrix<Scalar>;
116 Eigen::Index const rows = valueDimension(resolved.in());
117 scratch.resize(std::size_t(rows * count));
118 forEachChunk(std::forward<P>(policy), count, [&](Eigen::Index begin, Eigen::Index length) {
119 auto x = Eigen::Map<InMatrix>{scratch.data() + rows * begin, rows, length};
120 resolved.template gatherColumnsFromRows<Scalar>(
121 x,
122 [&](Eigen::Index j) {
123 return rowsOf(begin + j);
124 },
125 start
126 );
127 resolved.batchedLogLikelihoods(
128 Eigen::Map<InMatrix const>{x.data(), rows, length},
129 Eigen::Map<Eigen::Matrix<Scalar, 1, Eigen::Dynamic>>{result + begin, length}
130 );
131 });
132}
133
146template <ExecutionPolicy P, WeighsPoints M, typename Space, typename Derived, typename Result, typename Scratch>
147void
148fillLogLikelihoods(
149 P && policy,
150 M const & measurement,
151 Space const & space,
152 Eigen::MatrixBase<Derived> const & states,
153 Result && result,
154 Scratch & scratch
155)
156{
157 using Scalar = typename Derived::Scalar;
158 EMRYS_FILTER_ASSERT(states.rows() == valueDimension(space) && "the states do not fit the space");
159 EMRYS_FILTER_ASSERT(result.size() == states.cols() && "give one entry per state");
160 static_assert(
161 !hasConsider<M>,
162 "consider() is a Kalman update's; a filter that weights points moves no block it does not condition "
163 "(decisions.md [consider-states])"
164 );
165 auto const resolved = resolveMeasurement(measurement, space);
166 using R = std::remove_cvref_t<decltype(resolved)>;
167 if constexpr (R::BatchedLikelihood)
168 {
169 fillBatchedLogLikelihoods(
170 std::forward<P>(policy),
171 resolved,
172 states.cols(),
173 [&](Eigen::Index j) {
174 return states.col(j);
175 },
176 Eigen::Index{0},
177 result.data(),
178 scratch
179 );
180 }
181 else if constexpr (R::HasLogLikelihood)
182 {
183 forEachIndex(std::forward<P>(policy), states.cols(), [&](Eigen::Index j) {
184 result(j) = Scalar(resolved.logLikelihoodAt(states.col(j)));
185 });
186 }
187 else
188 {
189 static_assert(
190 R::HasAdditiveNoise,
191 "a measurement model with no additive noise has no likelihood of its own at a point; give it "
192 "logLikelihood(g), or name the inputs it is affine in with affineIn() beside additiveNoise()"
193 );
194 static_assert(
195 R::inputsAffine(),
196 "the likelihood at a point of noise that passes through the function is exact only where the model is "
197 "affine in it, so name the input in affineIn(), or give the model logLikelihood(g) (decisions.md "
198 "[likelihood-measurements])"
199 );
200 auto const & space_ = resolved.space();
201 if constexpr (R::InputCount == 0 && R::BatchedFunction)
202 {
203 // The function once per batch, its outputs after its inputs in the scratch.
204 auto const & noise = resolved.noise().distribution();
205 auto const density = noise.toDensity();
206 EMRYS_FILTER_ASSERT(density && "the measurement noise must factorize");
207 using InMatrix = typename R::template InMatrix<Scalar>;
208 using OutMatrix = typename R::template OutMatrix<Scalar>;
209 Eigen::Index const count = states.cols();
210 Eigen::Index const inRows = valueDimension(resolved.in());
211 Eigen::Index const outRows = valueDimension(space_);
212 scratch.resize(std::size_t((inRows + outRows) * count));
213 forEachChunk(std::forward<P>(policy), count, [&](Eigen::Index begin, Eigen::Index length) {
214 auto x = Eigen::Map<InMatrix>{scratch.data() + inRows * begin, inRows, length};
215 auto y = Eigen::Map<OutMatrix>{scratch.data() + inRows * count + outRows * begin, outRows, length};
216 resolved.template gatherColumnsFromRows<Scalar>(
217 x,
218 [&](Eigen::Index j) {
219 return states.col(begin + j);
220 },
221 Eigen::Index{0}
222 );
223 resolved.evaluateBatch(Eigen::Map<InMatrix const>{x.data(), inRows, length}, y);
224 for (Eigen::Index j = 0; j < length; ++j)
225 {
226 result(begin + j) = density->logDensity(observedResidual<Scalar>(resolved, space_, y.col(j)));
227 }
228 });
229 }
230 else if constexpr (R::InputCount == 0)
231 {
232 auto const & noise = resolved.noise().distribution();
233 auto const density = noise.toDensity();
234 EMRYS_FILTER_ASSERT(density && "the measurement noise must factorize");
235 forEachIndex(std::forward<P>(policy), states.cols(), [&](Eigen::Index j) {
236 auto const predicted = valueOf(resolved.evaluateAtState(states.col(j))).eval();
237 result(j) = density->logDensity(observedResidual<Scalar>(resolved, space_, predicted));
238 });
239 }
240 else if constexpr (R::BatchedFunction)
241 {
242 // The function and its Jacobian in the inputs once per batch, each input at its mean, the
243 // inputs' covariance carried through that Jacobian at each point: what the linear method
244 // gives below, exact where the function is affine in them.
245 using Out = typename R::Out;
246 using InMatrix = typename R::template InMatrix<Scalar>;
247 using OutMatrix = typename R::template OutMatrix<Scalar>;
248 using JacobianMatrix = MatrixOf<
249 Scalar,
251 Eigen::Dynamic,
253 Eigen::Dynamic>;
254 using Omega = typename Moments<Out, Scalar>::MatrixType;
255 auto const inputs = resolved.inputs();
256 Eigen::Index w = 0;
257 std::apply(
258 [&](auto const &... input) {
259 ((w += inputOf(input).differenceDimension()), ...);
260 },
261 inputs
262 );
263 Eigen::Index const count = states.cols();
264 Eigen::Index const inRows = valueDimension(resolved.in());
265 Eigen::Index const outRows = valueDimension(space_);
266 Eigen::Index const m = differenceDimension(space_);
267 scratch.resize(std::size_t((inRows + outRows + m * w) * count));
268 Scalar * const outputs = scratch.data() + inRows * count;
269 Scalar * const jacobianData = outputs + outRows * count;
270 forEachChunk(std::forward<P>(policy), count, [&](Eigen::Index begin, Eigen::Index length) {
271 auto x = Eigen::Map<InMatrix>{scratch.data() + inRows * begin, inRows, length};
272 auto y = Eigen::Map<OutMatrix>{outputs + outRows * begin, outRows, length};
273 auto jacobians = Eigen::Map<JacobianMatrix>{jacobianData + m * w * begin, m, w * length};
274 resolved.template gatherColumnsFromRows<Scalar>(
275 x,
276 [&](Eigen::Index j) {
277 return states.col(begin + j);
278 },
279 Eigen::Index{0}
280 );
281 resolved.evaluateBatchWithInputJacobians(
282 Eigen::Map<InMatrix const>{x.data(), inRows, length},
283 y,
284 jacobians
285 );
286 for (Eigen::Index j = 0; j < length; ++j)
287 {
288 Omega omega = Omega::Zero(m, m);
289 Eigen::Index offset = j * w;
290 std::apply(
291 [&](auto const &... argument) {
292 (
293 [&](auto const & input) {
294 using Input = std::remove_cvref_t<decltype(input)>;
295 using Cov = MatrixOf<
296 Scalar,
297 Input::DifferenceDimensionAtCompileTime,
298 Input::DifferenceDimensionAtCompileTime,
299 Input::MaxDifferenceDimensionAtCompileTime,
300 Input::MaxDifferenceDimensionAtCompileTime>;
301 Eigen::Index const d = input.differenceDimension();
302 auto const block =
303 middleColsOf<Input::DifferenceDimensionAtCompileTime>(jacobians, offset, d);
304 Cov const cov = symmetricFromLower<Cov>(covarianceLowerOf(input));
305 omega.noalias() += multiply(multiply(block, cov), block.transpose());
306 offset += d;
307 }(inputOf(argument)),
308 ...
309 );
310 },
311 inputs
312 );
313 omega = (Scalar(0.5) * (omega + omega.transpose())).eval();
314 result(begin + j) = widenedLogDensity<Scalar>(
315 resolved.noise().distribution(),
316 observedResidual<Scalar>(resolved, space_, y.col(j)),
317 omega
318 );
319 }
320 });
321 }
322 else
323 {
324 // The inputs' covariance carried through the function's Jacobian at the point, by the linear
325 // method, which is exact where the function is affine in them.
326 auto const & reads = resolved.reads();
327 using Reads = typename R::Reads;
328 using ReadPoint = PointInput<typename Reads::JointSpace, Scalar>;
329 auto const f = ResolvedFunction{resolved};
330 forEachIndex(std::forward<P>(policy), states.cols(), [&](Eigen::Index j) {
331 auto const read =
332 ReadPoint{reads.space(), typename ReadPoint::VectorType{states.col(j)(reads.indices())}};
333 result(j) = linearizeMeasurement(
334 resolved,
336 f,
337 read,
338 [&](auto const & problem, auto const & linearized) {
339 using Problem = std::remove_cvref_t<decltype(problem)>;
340 using Omega = typename Problem::OutputMatrix;
341 Omega const omega = inputsCovariance<Omega>(resolved, problem, linearized.jacobian);
342 return widenedLogDensity<Scalar>(
343 resolved.noise().distribution(),
344 observedResidual<Scalar>(resolved, space_, linearized.mean),
345 omega
346 );
347 }
348 );
349 });
350 }
351 }
352}
353
355template <ExecutionPolicy P, WeighsPoints M, typename Space, typename Derived, typename Result>
356void
357fillLogLikelihoods(
358 P && policy,
359 M const & measurement,
360 Space const & space,
361 Eigen::MatrixBase<Derived> const & states,
362 Result && result
363)
364{
365 auto scratch = Buffer<typename Derived::Scalar, DefaultAllocator>();
366 fillLogLikelihoods(std::forward<P>(policy), measurement, space, states, std::forward<Result>(result), scratch);
367}
368
370template <WeighsPoints M, typename Space, typename Derived, typename Result>
371void
372fillLogLikelihoods(
373 M const & measurement,
374 Space const & space,
375 Eigen::MatrixBase<Derived> const & states,
376 Result && result
377)
378{
379 fillLogLikelihoods(execution::sequential, measurement, space, states, std::forward<Result>(result));
380}
381
382} // namespace internal
383
406template <internal::WeighsPoints M, typename Space, typename Derived>
407[[nodiscard]] Eigen::Array<typename Derived::Scalar, Eigen::Dynamic, 1>
408logLikelihoods(M const & measurement, Space const & space, Eigen::MatrixBase<Derived> const & states)
409{
410 return logLikelihoods(execution::sequential, measurement, space, states);
411}
412
418template <ExecutionPolicy P, internal::WeighsPoints M, typename Space, typename Derived>
419[[nodiscard]] Eigen::Array<typename Derived::Scalar, Eigen::Dynamic, 1>
420logLikelihoods(P && policy, M const & measurement, Space const & space, Eigen::MatrixBase<Derived> const & states)
421{
422 auto result = Eigen::Array<typename Derived::Scalar, Eigen::Dynamic, 1>{states.cols()};
423 internal::fillLogLikelihoods(std::forward<P>(policy), measurement, space, states, result);
424 return result;
425}
426
431template <internal::WeighsPoints M, typename Space, typename Derived>
432[[nodiscard]] typename Derived::Scalar
433logLikelihood(M const & measurement, Space const & space, Eigen::MatrixBase<Derived> const & x)
434{
435 EMRYS_FILTER_ASSERT(x.cols() == 1 && "the state point must be a column vector");
436 return logLikelihoods(measurement, space, x)(0);
437}
438
443template <FixedSizeSpace S, internal::WeighsPoints M, typename Derived>
444 requires Space<S>
445[[nodiscard]] Eigen::Array<typename Derived::Scalar, Eigen::Dynamic, 1>
446logLikelihoods(M const & measurement, Eigen::MatrixBase<Derived> const & states)
447{
448 return logLikelihoods(execution::sequential, measurement, S{}, states);
449}
450
455template <FixedSizeSpace S, ExecutionPolicy P, internal::WeighsPoints M, typename Derived>
456 requires Space<S>
457[[nodiscard]] Eigen::Array<typename Derived::Scalar, Eigen::Dynamic, 1>
458logLikelihoods(P && policy, M const & measurement, Eigen::MatrixBase<Derived> const & states)
459{
460 return logLikelihoods(std::forward<P>(policy), measurement, S{}, states);
461}
462
467template <FixedSizeSpace S, internal::WeighsPoints M, typename Derived>
468 requires Space<S>
469[[nodiscard]] typename Derived::Scalar
470logLikelihood(M const & measurement, Eigen::MatrixBase<Derived> const & x)
471{
472 return logLikelihood(measurement, S{}, x);
473}
474
475} // namespace Emrys
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
Spaces built from other spaces (decisions.md [sizes-and-dimensions], [several-resizable-blocks]): Com...
The coordinate types (decisions.md [no-bounded-coordinates]): Linear, plain coordinates,...
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
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.
Measurement models and their observations (decisions.md [model-policies], [sensors],...
How an update reads a measurement, all internal (decisions.md [model-policies], [measurement-space-fu...
The mixture: the parameters MixtureParams of weighted components of one type (decisions....
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
Product: the product of independent densities, each on its own block of a space, built with on<Path>(...
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...
Subspaces, and growing and shrinking blocks (decisions.md [several-resizable-blocks]).
Tag paths and the index utilities (decisions.md [tag-addressing], [index-names]): TagPath and TagPath...
Propagating a distribution through a function (decisions.md [method-per-measurement],...
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
DifferenceMatrix< Space_, Space_, Scalar > MatrixType
a matrix over differences, such as the covariance
Definition Moments.h:76
A type usable as the space of a distribution.
Definition Space.h:186
bool allFinite(Eigen::DenseBase< Derived > const &x)
Definition Math.h:354
constexpr Sequential sequential
The default execution policy: plain loops on the calling thread, in index order (Sequential).
Definition Execution.h:128
Derived::Scalar logLikelihood(M const &measurement, Space const &space, Eigen::MatrixBase< Derived > const &x)
Definition Likelihood.h:433
Eigen::Array< typename Derived::Scalar, Eigen::Dynamic, 1 > logLikelihoods(M const &measurement, Space const &space, Eigen::MatrixBase< Derived > const &states)
Definition Likelihood.h:408
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
auto block(Space auto const &space, X &&x)
Definition TagPath.h:979
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
constexpr int valueDimensionAtCompileTime
The value dimension of Space at compile time, or Eigen::Dynamic if it is resizable.
Definition Space.h:251
constexpr int maxValueDimensionAtCompileTime
The largest valid value dimension of Space, or Eigen::Dynamic if it is unbounded.
Definition Space.h:258
constexpr Linearized linearized()
Definition Methods.h:333