45template <
typename Input>
46[[nodiscard]]
decltype(
auto)
47meanOf(Input
const & input)
49 if constexpr (UncertainInput<Input>)
63template <
typename Scalar,
typename Allocator = DefaultAllocator>
64struct PointLikelihoods
67 explicit PointLikelihoods(Allocator
const & allocator = {})
68 : logLikelihoodData(ReBound<Allocator, Scalar>(allocator))
69 , nisData(ReBound<Allocator, Scalar>(allocator))
70 , errorData(ReBound<Allocator, std::optional<
GaussianError>>(allocator))
71 , batchData(ReBound<Allocator, Scalar>(allocator))
76 reject(Eigen::Index count, Eigen::Index size)
78 logLikelihoodData.assign(std::size_t(count), -Eigen::NumTraits<Scalar>::infinity());
79 nisData.assign(std::size_t(count), Eigen::NumTraits<Scalar>::quiet_NaN());
80 errorData.assign(std::size_t(count), std::nullopt);
81 innovationSize = size;
86 reserve(Eigen::Index count)
88 logLikelihoodData.reserve(std::size_t(count));
89 nisData.reserve(std::size_t(count));
90 errorData.reserve(std::size_t(count));
94 [[nodiscard]] std::expected<void, GaussianError>
97 for (
auto const & error : errorData)
101 return std::unexpected{*error};
108 [[nodiscard]] Eigen::Index
111 return Eigen::Index(logLikelihoodData.size());
118 return Eigen::Map<Eigen::Array<Scalar, Eigen::Dynamic, 1>>{logLikelihoodData.data(), size()};
123 logLikelihoods()
const
125 return Eigen::Map<Eigen::Array<Scalar, Eigen::Dynamic, 1>
const>{logLikelihoodData.data(), size()};
132 return Eigen::Map<Eigen::Array<Scalar, Eigen::Dynamic, 1>>{nisData.data(), size()};
139 return Eigen::Map<Eigen::Array<Scalar, Eigen::Dynamic, 1>
const>{nisData.data(), size()};
142 Buffer<Scalar, Allocator> logLikelihoodData;
143 Buffer<Scalar, Allocator> nisData;
144 Buffer<std::optional<GaussianError>, Allocator> errorData;
145 Buffer<Scalar, Allocator> batchData;
146 Eigen::Index innovationSize = 0;
152struct PointLikelihoodsAccess
155 template <
typename Result>
156 [[nodiscard]]
static auto &
157 points(Result & result)
159 return result.m_points;
163 template <
typename Result>
164 [[nodiscard]]
static auto &
165 cells(Result & result)
167 return result.m_cells;
190template <ExecutionPolicy P,
typename Po
ints,
typename Store, Measurement M,
typename Likelihoods>
191[[nodiscard]] std::expected<void, GaussianError>
194 Points
const & points,
195 Store
const & conditionals,
196 Eigen::Index reference,
197 M
const & measurement,
198 Likelihoods & result,
202 using Scalar =
typename Store::Scalar;
203 static_assert(Store::Shared);
205 PinnedMeasurement<typename Points::SpaceType, typename Points::PathType, M>{points.space(), measurement};
206 auto const & resolved = f.resolved();
207 using R = std::remove_cvref_t<
decltype(resolved)>;
210 "a Kalman update pushes the prior through the model by a method: observe(method, model, z)"
213 R::HasAdditiveNoise && !R::MixtureNoise && R::InputCount == 0,
214 "a shared covariance needs additive Gaussian noise and no uncertain input; use PerPointCovariance for mixture "
215 "noise or noise through the function"
217 static_assert(!isIterated<typename R::Method>,
"an update given a block does not support Iterated yet");
220 "an update given a block, as a Rao-Blackwellized filter's, does not consider blocks yet (decisions.md "
224 conditionals.size() == points.size() && conditionals.size() > 0
225 &&
"give one Gaussian per point, and at least one point"
227 auto const prior = conditionals.conditional(reference);
228 auto const predicted = withPoint(points, reference, [&](
auto const & pinned) {
229 return transformPrediction(resolved, resolved.method(), f,
withCross(prior), pinned);
231 auto const & y = predicted.output();
232 auto const & cross = predicted.crossCovariance();
233 auto const & noise = resolved.noise().distribution();
234 using InnovationMatrix =
typename std::remove_cvref_t<
decltype(y)>::MatrixType;
235 using InnovationVector =
typename std::remove_cvref_t<
decltype(y)>::DifferenceVectorType;
236 InnovationMatrix s = y.covarianceLower() + noise.covarianceLower();
237 auto const factored = innovationCholesky<typename R::InnovationRepair>(s);
240 return std::unexpected{factored.error()};
242 auto const & llt = *factored;
244 constexpr bool NothingLeft = std::remove_cvref_t<
decltype(cross)>::RowsAtCompileTime == 0;
245 auto const gain = [&] {
246 if constexpr (NothingLeft)
252 return transposedSolve<std::remove_cvref_t<
decltype(predicted.crossCovariance())>>(
254 predicted.crossCovariance().transpose()
258 Eigen::Index
const m = y.differenceDimension();
259 Scalar
const logNormalizer =
260 Scalar(-0.5) * (Scalar(m) *
log2Pi<Scalar>() + Scalar(2) * llt.matrixLLT().diagonal().array().log().sum());
262 result.reject(points.size(), m);
264 posterior.resizeLike(conditionals);
265 auto posteriorMeans = posterior.means();
266 forEachIndex(std::forward<P>(policy), points.size(), [&](Eigen::Index i) {
269 InnovationVector residual = observedResidual<Scalar>(
272 Emrys::plus(y.space(), valueOf(f(conditionals.mean(i), points.center(i))), noise.mean())
274 Scalar const nis = residual.dot(llt.solve(residual));
275 result.nisData[std::size_t(i)] = nis;
276 result.logLikelihoodData[std::size_t(i)] = logNormalizer - Scalar(0.5) * nis;
277 if constexpr (!NothingLeft)
279 posteriorMeans.col(i) = Emrys::plus(conditionals.space(), conditionals.mean(i), multiply(gain, residual));
282 if constexpr (!NothingLeft)
286 using Form = std::remove_cvref_t<
decltype(resolved.covarianceUpdate())>;
287 using CovMatrix = std::remove_cvref_t<
decltype(posterior.sharedCovariance())>;
288 CovMatrix
symmetric = prior.covarianceLower();
290 posterior.sharedCovariance() = posteriorCov(resolved.covarianceUpdate(), symmetric, gain, cross, s, [&] {
291 if constexpr (std::is_same_v<Form, updates::Joseph>)
293 auto linearization = statisticalLinearization(predicted);
294 using Linearization = decltype(linearization);
295 return JosephTerms<typename Linearization::JacobianMatrix, InnovationMatrix>{
296 std::move(linearization.jacobian),
297 InnovationMatrix{linearization.errorCov + noise.covarianceLower()}
302 return std::tuple<>{};
323template <ExecutionPolicy P,
typename Po
ints,
typename Store, Measurement M,
typename Policy,
typename Likelihoods>
324[[nodiscard]] std::expected<void, GaussianError>
326 P && executionPolicy,
327 Points
const & points,
328 Store
const & conditionals,
329 M
const & measurement,
330 Policy
const & policy,
331 Likelihoods & result,
335 using ConditionalType =
typename Store::ConditionalType;
336 static_assert(!Store::Shared);
338 conditionals.size() == points.size() && conditionals.size() > 0
339 &&
"give one Gaussian per point, and at least one point"
342 posterior.resizeLike(conditionals);
344 result.reject(points.size(), 0);
347 PinnedMeasurement<typename Points::SpaceType, typename Points::PathType, M>{points.space(), measurement};
348 forEachIndex(std::forward<P>(executionPolicy), points.size(), [&](Eigen::Index i) {
350 auto updated = withPoint(points, i, [&, i](auto const & pinned) {
351 return updateBranchesWith<true>(conditionals.conditional(i), f, pinned);
355 result.errorData[std::size_t(i)] = updated.error();
358 auto & measured = *updated;
361 result.innovationSize = measured.innovationSize();
364 ReductionPolicy<Policy, std::remove_cvref_t<
decltype(measured)>, ConditionalType>,
365 "a Rao-Blackwellized filter keeps one Gaussian per point, so its policy must keep one: "
366 "Collapse, Select, or a Gate over them"
369 using Scalar =
typename std::remove_cvref_t<
decltype(measured)>::Scalar;
370 auto out = OneComponentOutput<ConditionalType, Scalar>{posterior.conditional(i)};
371 reduceInto(policy, std::move(measured.branches), out);
374 posterior.conditional(i) = conditionals.conditional(i);
377 std::size_t best = 0;
378 for (std::size_t k = 1; k < measured.branches.size(); ++k)
380 if (measured.branches[k].logWeight > measured.branches[best].logWeight)
385 result.nisData[std::size_t(i)] = measured.branches[best].nis();
387 result.logLikelihoodData[std::size_t(i)] = measured.logLikelihood() + out.logWeight;
389 return result.firstError();
395template <
typename Pinned,
typename Read>
400 pathLiesIn<Read, Pinned>,
401 "logLikelihood() may read only the gridded or sampled block: with the rest conditioned on it, "
402 "the rest would no longer be Gaussian"
421template <ExecutionPolicy P,
typename Po
ints,
typename Store, WeighsPo
ints M,
typename Likelihoods>
423updateGivenLikelihood(
425 Points
const & points,
426 Store
const & conditionals,
427 M
const & measurement,
431 using Scalar =
typename Store::Scalar;
432 using Pinned =
typename Points::PathType;
433 auto const resolved = resolveMeasurement(measurement, points.space());
434 using R = std::remove_cvref_t<
decltype(resolved)>;
435 static_assert(R::HasLogLikelihood,
"a measurement weighted by its likelihood alone has logLikelihood(g)");
438 "consider() is a Kalman update's; a filter that weights points moves no block it does not condition "
439 "(decisions.md [consider-states])"
441 [&]<
typename... Paths>(TagPaths<Paths...> *) {
442 (assertReadsPinned<Pinned, Paths>(), ...);
443 }(
static_cast<typename R::StatePaths *
>(
nullptr));
445 conditionals.size() == points.size() && conditionals.size() > 0
446 &&
"give one Gaussian per point, and at least one point"
448 Eigen::Index
const start = points.pinnedRows().start;
449 result.reject(points.size(), 0);
450 if constexpr (R::BatchedLikelihood)
452 fillBatchedLogLikelihoods(
453 std::forward<P>(policy),
456 [&](Eigen::Index i) {
457 return points.center(i);
460 result.logLikelihoodData.data(),
466 forEachIndex(std::forward<P>(policy), points.size(), [&](Eigen::Index i) {
467 result.logLikelihoodData[std::size_t(i)] = Scalar(resolved.logLikelihoodAtRows(points.center(i), start));
483template <
typename Po
ints,
typename Store,
typename Likelihoods,
typename OneResult,
typename UpdateOne>
484[[nodiscard]] std::expected<void, NumericError>
486 Points
const & points,
488 Likelihoods & likelihoods,
489 OneResult & oneResult,
490 UpdateOne
const & updateOne
493 likelihoods.reject(points.size(), 0);
494 for (Eigen::Index i = 0; i < points.size(); ++i)
496 auto & hybrid = hybrids.conditional(i);
497 auto const updated = withPoint(points, i, [&](
auto const & pinned) {
498 return updateOne(hybrid, pinned, oneResult);
502 return std::unexpected{updated.error()};
506 likelihoods.innovationSize = oneResult.modes.front().innovationSize();
508 if (!oneResult.accepted)
512 likelihoods.logLikelihoodData[std::size_t(i)] = oneResult.logLikelihood();
514 Eigen::Index mode = 0;
515 hybrid.modes().logWeights().maxCoeff(&mode);
516 auto const & branches = oneResult.modes[std::size_t(mode)].branches;
517 std::size_t best = 0;
518 for (std::size_t k = 1; k < branches.size(); ++k)
520 if (branches[k].logWeight > branches[best].logWeight)
525 likelihoods.nisData[std::size_t(i)] = branches[best].nis();
533template <
typename Model>
534inline constexpr bool isSwitchingPointModel =
requires {
typename Model::ModeSpaceType; };
539template <
typename Model>
540using PointCovarianceFor = std::conditional_t<isSwitchingPointModel<Model>, PerPointCovariance, SharedCovariance>;
543template <
typename Model_,
typename Method_>
549 PointSteps() =
default;
552 template <
typename Allocator>
553 PointSteps(Model_ model, MethodFor<Model_, Method_> method, Allocator
const &)
554 : m_model{std::move(model)}
555 , m_method{predictionMethod(m_model, std::move(method))}
559 [[nodiscard]] Model_
const &
566 [[nodiscard]] Model_ &
573 [[nodiscard]] Method_
const &
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
What the Rao-Blackwellized states share (decisions.md [rao-blackwellized-core], [rb-particle-filter])...
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
The likelihoods that sample-based filters weight by (decisions.md [likelihood-measurements],...
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'...
Reducing an update's branches (decisions.md [weighted-branches], [reduction-policies],...
SampleSet: a matrix whose columns are samples in a space, which the samplers return and the unscented...
Drawing samples: SamplesWithLogDensity and SampleWithLogDensity, the Gaussian draws drawSamples(),...
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...
Weighted statistics over points, all in internal, which the containers of weighted elements (Gaussian...
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
Scalar log2Pi()
Definition Math.h:177
NumericError GaussianError
Reasons a Gaussian, in any of its forms (decisions.md [gaussian-forms]), can fail to give a density,...
Definition NumericError.h:45
constexpr auto symmetric(Fallbacks... fallbacks)
Definition Roots.h:1204