38static_assert(
Cumulative != Eigen::Dynamic,
"a cumulative window is not a dynamic one");
72template <
typename Derived,
typename Scalar>
80 [[nodiscard]] std::expected<Scalar, NumericError>
83 if (derived().count() == 0)
87 Scalar
const total = derived().sum();
92 return total / internal::scalarFromCount<Scalar>(derived().degreesOfFreedom());
99 [[nodiscard]] std::expected<std::pair<Scalar, Scalar>,
NumericError>
100 bounds(Scalar probability)
const
102 if (derived().count() == 0)
106 Scalar
const dof = internal::scalarFromCount<Scalar>(derived().degreesOfFreedom());
107 return std::pair<Scalar, Scalar>{
118 [[nodiscard]] std::expected<Consistency, NumericError>
119 verdict(Scalar probability)
const
121 auto const range = bounds(probability);
124 return std::unexpected{range.error()};
126 Scalar
const total = derived().sum();
131 if (total > range->second)
135 if (total < range->first)
145 [[nodiscard]] Derived
const &
148 return static_cast<Derived
const &
>(*this);
157template <
int Window,
int MaxWindow,
typename Scalar,
typename Allocator>
158class ChiSquaredWindow :
public ChiSquaredTest<ChiSquaredWindow<Window, MaxWindow, Scalar, Allocator>, Scalar>
160 static_assert(Window == Eigen::Dynamic || Window > 0,
"a window holds at least one statistic");
162 Window == Eigen::Dynamic ? MaxWindow == Eigen::Dynamic || MaxWindow > 0 : MaxWindow == Window,
163 "a window's bound is positive, and a fixed window is its own bound"
166 using Entry = std::pair<Scalar, Eigen::Index>;
167 static constexpr bool Inline = MaxWindow != Eigen::Dynamic;
168 using Entries = std::conditional_t<
170 std::array<Entry, std::size_t(Inline ? MaxWindow : 1)>,
171 internal::Buffer<Entry, Allocator>>;
172 using Length = std::conditional_t<
173 Window == Eigen::Dynamic,
175 std::integral_constant<Eigen::Index, Window == Eigen::Dynamic ? 1 : Window>>;
179 using allocator_type = Allocator;
183 requires(Window != Eigen::Dynamic)
189 ChiSquaredWindow(Eigen::Index window, Allocator
const & allocator)
190 requires(Window == Eigen::Dynamic)
192 , m_entries(entries(window, allocator))
194 EMRYS_FILTER_ASSERT(window > 0 &&
"the window must hold at least one statistic");
195 EMRYS_FILTER_ASSERT((!Inline || window <= MaxWindow) &&
"the window must be within its bound");
199 ChiSquaredWindow(ChiSquaredWindow
const & other, Allocator
const & allocator)
201 : m_window{other.m_window}
202 , m_entries(other.m_entries, internal::ReBound<Allocator, Entry>(allocator))
203 , m_count{other.m_count}
204 , m_next{other.m_next}
208 [[nodiscard]] Allocator
209 get_allocator() const
212 return Allocator(m_entries.get_allocator());
219 add(Scalar value, Eigen::Index dimension)
221 EMRYS_FILTER_ASSERT(dimension > 0 &&
"the statistic must have at least one degree of freedom");
222 m_entries[std::size_t(m_next)] = Entry{value, dimension};
223 m_next = m_next + 1 == window() ? 0 : m_next + 1;
224 m_count = std::min(m_count + 1, window());
236 [[nodiscard]] Eigen::Index
243 [[nodiscard]] Eigen::Index
254 for (Eigen::Index i = 0; i < m_count; ++i)
256 total += m_entries[std::size_t(i)].first;
262 [[nodiscard]] Eigen::Index
263 degreesOfFreedom()
const
265 Eigen::Index total = 0;
266 for (Eigen::Index i = 0; i < m_count; ++i)
268 total += m_entries[std::size_t(i)].second;
276 [[nodiscard]]
static Entries
277 entries(Eigen::Index window, Allocator
const & allocator)
279 if constexpr (Inline)
285 return Entries(std::size_t(window), Entry{}, internal::ReBound<Allocator, Entry>(allocator));
291 Eigen::Index m_count = 0;
292 Eigen::Index m_next = 0;
298template <
typename Scalar>
299class ChiSquaredSum :
public ChiSquaredTest<ChiSquaredSum<Scalar>, Scalar>
305 add(Scalar value, Eigen::Index dimension)
307 EMRYS_FILTER_ASSERT(dimension > 0 &&
"the statistic must have at least one degree of freedom");
309 m_degreesOfFreedom += dimension;
318 m_degreesOfFreedom = 0;
323 [[nodiscard]] Eigen::Index
337 [[nodiscard]] Eigen::Index
338 degreesOfFreedom()
const
340 return m_degreesOfFreedom;
346 Eigen::Index m_degreesOfFreedom = 0;
347 Eigen::Index m_count = 0;
353template <
int Window,
int MaxWindow,
typename Scalar,
typename Allocator>
354using ChiSquaredWindowFor = std::conditional_t<
356 ChiSquaredSum<Scalar>,
357 ChiSquaredWindow<Window, MaxWindow, Scalar, Allocator>>;
416 int Window = Eigen::Dynamic,
417 int MaxWindow = Window,
418 typename Scalar = double,
420class NisMonitor :
public internal::ChiSquaredWindowFor<Window, MaxWindow, Scalar, Allocator>
422 static_assert(Window !=
Cumulative || MaxWindow ==
Cumulative,
"a cumulative window has no bound");
424 using Base = internal::ChiSquaredWindowFor<Window, MaxWindow, Scalar, Allocator>;
430 requires(Window != Eigen::Dynamic)
436 explicit NisMonitor(Eigen::Index window, Allocator
const & allocator = {})
437 requires(Window == Eigen::Dynamic)
438 : Base{window, allocator}
443 requires(Window == Eigen::Dynamic && MaxWindow == Eigen::Dynamic)
444 : Base{other, allocator}
452 template <
typename Update>
453 requires requires(Update
const &
update) {
454 update.branches.front().nis();
461 update.branches.size() == 1 &&
"with several branches, pass the NIS to test with add(nis, dimension)"
485template <GaussianForm Estimate,
typename Derived>
486[[nodiscard]] std::expected<typename Estimate::Scalar, GaussianError>
487nees(Eigen::MatrixBase<Derived>
const & truth, Estimate
const & estimate)
489 EMRYS_FILTER_ASSERT(truth.size() == estimate.valueDimension() &&
"the true state must fit the estimate's space");
490 if constexpr (
requires { estimate.toDensity(); })
492 auto const density = estimate.toDensity();
495 return std::unexpected{density.error()};
497 return typename Estimate::Scalar(density->squaredMahalanobisDistance(truth));
504 return std::unexpected{params.error()};
506 return nees(truth, *params);
525template <
typename Path, GaussianForm Estimate,
typename Derived>
526 requires requires(Estimate
const & estimate) { estimate.template marginal<Path>(); }
527[[nodiscard]] std::expected<typename Estimate::Scalar, GaussianError>
528nees(Eigen::MatrixBase<Derived>
const & truth, Estimate
const & estimate)
530 EMRYS_FILTER_ASSERT(truth.size() == estimate.valueDimension() &&
"the true state must fit the estimate's space");
533 auto const marginal = estimate.template marginal<Path>();
534 if constexpr (internal::isExpected<std::remove_cvref_t<
decltype(marginal)>>)
538 return std::unexpected{marginal.error()};
593 int Window = Eigen::Dynamic,
594 int MaxWindow = Window,
595 typename Scalar = double,
597class NeesMonitor :
public internal::ChiSquaredWindowFor<Window, MaxWindow, Scalar, Allocator>
599 static_assert(Window !=
Cumulative || MaxWindow ==
Cumulative,
"a cumulative window has no bound");
601 using Base = internal::ChiSquaredWindowFor<Window, MaxWindow, Scalar, Allocator>;
607 requires(Window != Eigen::Dynamic)
613 explicit NeesMonitor(Eigen::Index window, Allocator
const & allocator = {})
614 requires(Window == Eigen::Dynamic)
615 : Base{window, allocator}
620 requires(Window == Eigen::Dynamic && MaxWindow == Eigen::Dynamic)
621 : Base{other, allocator}
631 template <GaussianForm Estimate,
typename Derived>
632 requires requires(Estimate
const & estimate, Eigen::MatrixBase<Derived>
const & truth) {
633 nees(truth, estimate);
636 add(Eigen::MatrixBase<Derived>
const & truth, Estimate
const & estimate)
638 auto const value =
nees(truth, estimate);
639 add(value ? Scalar(*value) : std::numeric_limits<Scalar>::quiet_NaN(), estimate.differenceDimension());
647 template <
typename Path,
typename Derived,
typename Estimate>
648 requires requires(Estimate
const & estimate, Eigen::MatrixBase<Derived>
const & truth) {
652 add(Eigen::MatrixBase<Derived>
const & truth, Estimate
const & estimate)
654 auto const value =
nees<Path>(truth, estimate);
655 add(value ? Scalar(*value) : std::numeric_limits<Scalar>::quiet_NaN(),
656 internal::differenceBlockRows<Path>(estimate.space()).size);
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
The chi-squared distribution's CDF and quantile, with no dependency outside Eigen,...
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]...
NumericError, the reasons a factorization or a normalization can fail, and its two names,...
Tag paths and the index utilities (decisions.md [tag-addressing], [index-names]): TagPath and TagPath...
NeesMonitor()=default
A monitor over the most recent Window estimates, or over every estimate for Cumulative.
NeesMonitor(NeesMonitor const &other, Allocator const &allocator)
Copies other into a window from allocator.
Definition Consistency.h:619
NeesMonitor(Eigen::Index window, Allocator const &allocator={})
A monitor over the most recent window estimates, whose window, if it has no bound,...
Definition Consistency.h:613
void add(Eigen::MatrixBase< Derived > const &truth, Estimate const &estimate)
Adds the estimate estimate of the true state truth: its nees(), with its difference dimension as the ...
Definition Consistency.h:636
void add(Eigen::MatrixBase< Derived > const &truth, Estimate const &estimate)
Adds the block of estimate named by the path Path at the same block of the true state truth: its nees...
Definition Consistency.h:652
void add(Update const &update)
Adds an update with a single branch, as from Gaussian noise: a MeasurementUpdate, or the FilterUpdate...
Definition Consistency.h:458
NisMonitor()=default
A monitor over the most recent Window updates, or over every update for Cumulative.
NisMonitor(Eigen::Index window, Allocator const &allocator={})
A monitor over the most recent window updates, whose window, if it has no bound, comes from allocator...
Definition Consistency.h:436
NisMonitor(NisMonitor const &other, Allocator const &allocator)
Copies other into a window from allocator.
Definition Consistency.h:442
std::allocator< std::byte > DefaultAllocator
The allocator a type with a run-time count uses unless given one.
Definition Allocation.h:47
Scalar chiSquaredQuantile(std::type_identity_t< Scalar > const &probability, std::type_identity_t< Scalar > const &dof)
Definition ChiSquared.h:148
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
bool isNaN(T const &x)
Definition Math.h:329
NumericError
Reasons a computation from parameters the caller gave can fail: a matrix that does not factorize or a...
Definition NumericError.h:30
@ NonFinite
a matrix, a factor, or a log-weight holds NaN or infinity (+inf, for a log-weight)
Definition NumericError.h:33
@ Empty
there are no weights
Definition NumericError.h:34
std::expected< GaussianParams< typename G::SpaceType, typename G::Scalar >, GaussianError > toGaussian(G const &form)
Definition GaussianForm.h:91
constexpr int Cumulative
The window of a NisMonitor or NeesMonitor that drops nothing: its test runs over every statistic adde...
Definition Consistency.h:36
Consistency
The verdict of a NisMonitor's or NeesMonitor's verdict(): where the sum of the window's statistics li...
Definition Consistency.h:56
std::expected< typename Estimate::Scalar, GaussianError > nees(Eigen::MatrixBase< Derived > const &truth, Estimate const &estimate)
Definition Consistency.h:487
@ Consistent
the sum lies within the bounds
Definition Consistency.h:57
@ Underconfident
the sum lies below the lower bound: the covariance overstates the errors
Definition Consistency.h:59
@ Overconfident
the sum lies above the upper bound: the covariance understates the errors
Definition Consistency.h:58
std::expected< CategoricalUpdate< Space, Scalar >, WeightError > update(CategoricalParams< Space, Scalar > const &prior, E const &emission)
Definition Emission.h:97
auto block(Space auto const &space, X &&x)
Definition TagPath.h:979