EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Indexing.h
Go to the documentation of this file.
1#pragma once
2
10
11#include <algorithm>
12#include <array>
13#include <cstddef>
14#include <optional>
15#include <type_traits>
16#include <utility>
17#include <vector>
18
19#include <Eigen/Cholesky>
20
29
30namespace Emrys::internal
31{
32
34template <int Size, int MaxSize>
35using IndexVector = Eigen::Matrix<Eigen::Index, Size, 1, Eigen::ColMajor, MaxSize, 1>;
36
38template <typename Indices, int SourceSize, int SourceMaxSize>
39struct IndexedSizeOf
40{
41private:
42
44 using Probe = Eigen::Matrix<int, SourceSize, 1, Eigen::ColMajor, SourceMaxSize, 1>;
45
47 using Expr = std::remove_cvref_t<decltype(std::declval<Probe const &>()(std::declval<Indices const &>()))>;
48
49 static_assert(Expr::ColsAtCompileTime == 1, "marginal and conditional indices must select a set, not one index");
50
51public:
52
53 static constexpr int size = Expr::RowsAtCompileTime;
54
57 static constexpr int maxSize = size != Eigen::Dynamic ? size
58 : Expr::MaxRowsAtCompileTime != Eigen::Dynamic ? int(Expr::MaxRowsAtCompileTime)
59 : SourceMaxSize;
60
61 using type = IndexVector<size, maxSize>;
62};
63
67template <typename Indices, int SourceSize, int SourceMaxSize>
68using IndexedSize = typename IndexedSizeOf<Indices, SourceSize, SourceMaxSize>::type;
69
74template <int SourceSize, int SourceMaxSize, typename Indices>
75[[nodiscard]] IndexedSize<Indices, SourceSize, SourceMaxSize>
76resolveIndices(Eigen::Index n, Indices const & indices)
77{
78 using Identity = Eigen::Matrix<Eigen::Index, SourceSize, 1, Eigen::ColMajor, SourceMaxSize, 1>;
79 Identity const identity = Identity::LinSpaced(n, 0, n - 1);
80 return identity(indices);
81}
82
84template <int Size, int MaxSize>
85[[nodiscard]] IndexVector<Size, MaxSize>
86consecutiveIndices(Eigen::Index start, Eigen::Index count)
87{
88 return IndexVector<Size, MaxSize>::LinSpaced(count, start, start + count - 1);
89}
90
94template <typename... Vectors>
95[[nodiscard]] bool
96distinctIndices(Vectors const &... indices)
97{
98 bool distinct = true;
99 std::size_t position = 0;
100 auto const seenBefore = [&](Eigen::Index index, std::size_t before) {
101 std::size_t k = 0;
102 bool seen = false;
103 auto const scan = [&](auto const & vector) {
104 for (Eigen::Index const other : vector)
105 {
106 if (k++ < before && other == index)
107 {
108 seen = true;
109 }
110 }
111 };
112 (scan(indices), ...);
113 return seen;
114 };
115 auto const check = [&](auto const & vector) {
116 for (Eigen::Index const index : vector)
117 {
118 if (seenBefore(index, position))
119 {
120 distinct = false;
121 }
122 ++position;
123 }
124 };
125 (check(indices), ...);
126 return distinct;
127}
128
133template <int SourceSize, int SourceMaxSize, int GivenSize, int GivenMaxSize>
134[[nodiscard]] auto
135complementIndices(Eigen::Index n, IndexVector<GivenSize, GivenMaxSize> const & given)
136{
137 auto taken = Eigen::Matrix<bool, SourceSize, 1, Eigen::ColMajor, SourceMaxSize, 1>{};
138 taken.setConstant(n, false);
139 for (Eigen::Index const i : given)
140 {
141 taken(i) = true;
142 }
143 constexpr int RestSize =
144 SourceSize != Eigen::Dynamic && GivenSize != Eigen::Dynamic ? SourceSize - GivenSize : Eigen::Dynamic;
145 auto result = IndexVector<RestSize, RestSize == Eigen::Dynamic ? SourceMaxSize : RestSize>{};
146 result.resize(n - given.size());
147 Eigen::Index out = 0;
148 for (Eigen::Index i = 0; i < n; ++i)
149 {
150 if (!taken(i))
151 {
152 result(out++) = i;
153 }
154 }
155 return result;
156}
157
162template <typename Values, typename Differences>
163struct Coordinates
164{
165 Values values;
166 Differences differences;
167};
168
172template <typename Space, typename ValueIndices, typename DifferenceIndices>
173[[nodiscard]] auto
174resolveCoordinates(Space const & space, ValueIndices const & values, DifferenceIndices const & differences)
175{
176 return Coordinates{
177 resolveIndices<valueDimensionAtCompileTime<Space>, maxValueDimensionAtCompileTime<Space>>(
178 valueDimension(space),
179 asIndexSet(values)
180 ),
181 resolveIndices<
184 };
185}
186
188template <typename Path, typename Space>
189[[nodiscard]] auto
190pathCoordinates(Space const & space)
191{
192 return resolveCoordinates(space, idx<Path>(space), diffIdx<Path>(space));
193}
194
199template <typename IndexVector_, typename DifferenceIndexVector_, typename SubSpace>
200struct ResolvedSelection
201{
202 IndexVector_ indices;
203 DifferenceIndexVector_ differenceIndices;
204 SubSpace space;
205};
206
208template <typename Resolved>
209struct SharedSelection
210{
211 Resolved const * resolved;
212};
213
215template <typename Selection>
216inline constexpr bool isSharedSelection = false;
217
219template <typename Resolved>
220inline constexpr bool isSharedSelection<SharedSelection<Resolved>> = true;
221
225template <typename Source, typename Selection, typename SubSpace>
226class SelectionHolder
227{
228public:
229
231 using ResolvedType = ResolvedSelection<
232 IndexedSize<
233 decltype(Selection::values),
234 Source::ValueDimensionAtCompileTime,
235 Source::MaxValueDimensionAtCompileTime>,
236 IndexedSize<
237 decltype(Selection::differences),
238 Source::DifferenceDimensionAtCompileTime,
239 Source::MaxDifferenceDimensionAtCompileTime>,
240 SubSpace>;
241
243 SelectionHolder(Source const & source, Selection const & selection, SubSpace space)
244 : m_resolved{
245 resolveIndices<Source::ValueDimensionAtCompileTime, Source::MaxValueDimensionAtCompileTime>(
246 source.valueDimension(),
247 selection.values
248 ),
249 resolveIndices<Source::DifferenceDimensionAtCompileTime, Source::MaxDifferenceDimensionAtCompileTime>(
250 source.differenceDimension(),
251 selection.differences
252 ),
253 std::move(space)
254 }
255 {
256 EMRYS_FILTER_ASSERT(
257 distinctIndices(m_resolved.indices) && distinctIndices(m_resolved.differenceIndices)
258 && "marginal indices must be distinct"
259 );
260 }
261
263 [[nodiscard]] ResolvedType const &
264 get() const
265 {
266 return m_resolved;
267 }
268
269private:
270
271 ResolvedType m_resolved;
272};
273
275template <typename Source, typename Resolved, typename SubSpace>
276class SelectionHolder<Source, SharedSelection<Resolved>, SubSpace>
277{
278public:
279
280 using ResolvedType = Resolved;
281
283 explicit SelectionHolder(SharedSelection<Resolved> shared)
284 : m_resolved{shared.resolved}
285 {}
286
288 [[nodiscard]] ResolvedType const &
289 get() const
290 {
291 return *m_resolved;
292 }
293
294private:
295
296 Resolved const * m_resolved;
297};
298
309template <typename GivenPath, typename Space, typename Condition>
310[[nodiscard]] auto
311tagConditional(Space const & space, Condition condition)
312{
313 constexpr int N = valueDimensionAtCompileTime<Space>;
314 constexpr int NMax = maxValueDimensionAtCompileTime<Space>;
317 Eigen::Index const n = valueDimension(space);
318 Eigen::Index const d = differenceDimension(space);
319 auto const given = pathCoordinates<GivenPath>(space);
320 auto const target =
321 Coordinates{complementIndices<N, NMax>(n, given.values), complementIndices<D, DMax>(d, given.differences)};
322 return condition(target, given, pathComplement<GivenPath>(space), pathSubspace<GivenPath>(space));
323}
324
330template <typename Path, typename Space, typename Make>
331[[nodiscard]] auto
332marginalOverPath(Space const & space, Make make)
333{
334 return make(pathCoordinates<Path>(space), pathSubspace<Path>(space));
335}
336
343template <typename Path, typename Space, typename Make>
344[[nodiscard]] auto
345marginalOverElement(Space const & space, Eigen::Index element, Make make)
346{
347 return make(
348 resolveCoordinates(space, idx<Path>(space, element), diffIdx<Path>(space, element)),
349 pathElementSubspace<Path>(space)
350 );
351}
352
354template <typename Matrix, typename Derived>
355void
356insertRows(Matrix & matrix, Eigen::Index at, Eigen::MatrixBase<Derived> const & rows)
357{
358 Eigen::Index const n = matrix.rows();
359 Eigen::Index const k = rows.rows();
360 EMRYS_FILTER_ASSERT(at >= 0 && at <= n && rows.cols() == matrix.cols());
361 auto result = Matrix{};
362 result.resize(n + k, matrix.cols());
363 result.topRows(at) = matrix.topRows(at);
364 result.middleRows(at, k) = rows;
365 result.bottomRows(n - at) = matrix.bottomRows(n - at);
366 matrix = std::move(result);
367}
368
370template <typename Matrix>
371void
372eraseRows(Matrix & matrix, Eigen::Index at, Eigen::Index count)
373{
374 Eigen::Index const n = matrix.rows();
375 EMRYS_FILTER_ASSERT(at >= 0 && count >= 0 && at + count <= n);
376 auto result = Matrix{};
377 result.resize(n - count, matrix.cols());
378 result.topRows(at) = matrix.topRows(at);
379 result.bottomRows(n - at - count) = matrix.bottomRows(n - at - count);
380 matrix = std::move(result);
381}
382
386template <typename Matrix, typename BlockDerived, typename CrossDerived>
387void
388insertSymmetric(
389 Matrix & matrix,
390 Eigen::Index at,
391 Eigen::MatrixBase<BlockDerived> const & block,
392 Eigen::MatrixBase<CrossDerived> const & cross
393)
394{
395 Eigen::Index const n = matrix.rows();
396 Eigen::Index const k = block.rows();
397 Eigen::Index const after = n - at;
398 EMRYS_FILTER_ASSERT(at >= 0 && at <= n && block.cols() == k && cross.rows() == k && cross.cols() == n);
399 auto result = Matrix{};
400 result.resize(n + k, n + k);
401 result.topLeftCorner(at, at) = matrix.topLeftCorner(at, at);
402 result.topRightCorner(at, after) = matrix.topRightCorner(at, after);
403 result.bottomLeftCorner(after, at) = matrix.bottomLeftCorner(after, at);
404 result.bottomRightCorner(after, after) = matrix.bottomRightCorner(after, after);
405 result.block(at, at, k, k) = block;
406 result.block(at, 0, k, at) = cross.leftCols(at);
407 result.block(at, at + k, k, after) = cross.rightCols(after);
408 result.block(0, at, at, k) = cross.leftCols(at).transpose();
409 result.block(at + k, at, after, k) = cross.rightCols(after).transpose();
410 matrix = std::move(result);
411}
412
414template <typename Matrix>
415void
416eraseSymmetric(Matrix & matrix, Eigen::Index at, Eigen::Index count)
417{
418 Eigen::Index const n = matrix.rows();
419 Eigen::Index const after = n - at - count;
420 EMRYS_FILTER_ASSERT(at >= 0 && count >= 0 && after >= 0);
421 auto result = Matrix{};
422 result.resize(n - count, n - count);
423 result.topLeftCorner(at, at) = matrix.topLeftCorner(at, at);
424 result.topRightCorner(at, after) = matrix.topRightCorner(at, after);
425 result.bottomLeftCorner(after, at) = matrix.bottomLeftCorner(after, at);
426 result.bottomRightCorner(after, after) = matrix.bottomRightCorner(after, after);
427 matrix = std::move(result);
428}
429
433template <typename Result, typename Derived>
434[[nodiscard]] Result
435symmetricFromLower(Eigen::MatrixBase<Derived> const & matrix)
436{
437 if constexpr (std::is_base_of_v<Eigen::PlainObjectBase<Derived>, Derived>)
438 {
439 Result result = matrix.template selfadjointView<Eigen::Lower>();
440 return result;
441 }
442 else
443 {
444 Result result = matrix;
445 result.template triangularView<Eigen::StrictlyUpper>() = result.transpose();
446 return result;
447 }
448}
449
455template <int Rows, int MaxRows, int Cols, int MaxCols, typename Derived>
456[[nodiscard]] auto
457lowerSymmetricBlock(
458 Eigen::MatrixBase<Derived> const & matrix,
459 IndexVector<Rows, MaxRows> const & rows,
460 IndexVector<Cols, MaxCols> const & cols
461)
462{
463 using Scalar = typename Derived::Scalar;
464 using Result = MatrixOf<Scalar, Rows, Cols, MaxRows, MaxCols>;
465 return Result::NullaryExpr(
466 rows.size(),
467 cols.size(),
468 [&matrix = matrix.derived(), &rows, &cols](Eigen::Index i, Eigen::Index j) {
469 Eigen::Index const r = rows(i);
470 Eigen::Index const c = cols(j);
471 return r >= c ? matrix(r, c) : matrix(c, r);
472 }
473 );
474}
475
481template <int Size, int MaxSize, typename Derived, typename Indices>
482[[nodiscard]] auto
483lowerSymmetricSelection(Eigen::MatrixBase<Derived> const & matrix, Indices const & indices)
484{
485 if constexpr (std::is_integral_v<Indices>)
486 {
487 return matrix.derived().coeff(indices, indices);
488 }
489 else
490 {
491 using Scalar = typename Derived::Scalar;
492 using Resolved = IndexedSize<Indices, Size, MaxSize>;
493 using Result = MatrixOf<
494 Scalar,
495 Resolved::RowsAtCompileTime,
496 Resolved::RowsAtCompileTime,
497 Resolved::MaxRowsAtCompileTime,
498 Resolved::MaxRowsAtCompileTime>;
499 Resolved resolved = resolveIndices<Size, MaxSize>(matrix.rows(), indices);
500 Eigen::Index const n = resolved.size();
501 return Result::NullaryExpr(
502 n,
503 n,
504 [&matrix = matrix.derived(), resolved = std::move(resolved)](Eigen::Index i, Eigen::Index j) {
505 Eigen::Index const r = resolved(i);
506 Eigen::Index const c = resolved(j);
507 return r >= c ? matrix(r, c) : matrix(c, r);
508 }
509 );
510 }
511}
512
517template <int Size, int MaxSize, typename Derived, typename Rows, typename Cols>
518[[nodiscard]] auto
519lowerSymmetricCross(Eigen::MatrixBase<Derived> const & matrix, Rows const & rows, Cols const & cols)
520{
521 if constexpr (std::is_integral_v<Rows> && std::is_integral_v<Cols>)
522 {
523 return rows >= cols ? matrix.derived().coeff(rows, cols) : matrix.derived().coeff(cols, rows);
524 }
525 else
526 {
527 auto const resolvedRows = resolveIndices<Size, MaxSize>(matrix.rows(), asIndexSet(rows));
528 auto const resolvedCols = resolveIndices<Size, MaxSize>(matrix.rows(), asIndexSet(cols));
529 using RowIndices = std::remove_cvref_t<decltype(resolvedRows)>;
530 using ColIndices = std::remove_cvref_t<decltype(resolvedCols)>;
531 using Result = MatrixOf<
532 typename Derived::Scalar,
533 RowIndices::RowsAtCompileTime,
534 ColIndices::RowsAtCompileTime,
535 RowIndices::MaxRowsAtCompileTime,
536 ColIndices::MaxRowsAtCompileTime>;
537 return Result{lowerSymmetricBlock(matrix, resolvedRows, resolvedCols)};
538 }
539}
540
542template <typename Scalar, typename TargetSpace>
543struct ConditionedMoments
544{
546 Eigen::Matrix<
547 Scalar,
549 1,
550 Eigen::ColMajor,
552 1>
553 mean;
554
556 MatrixOf<
557 Scalar,
562 cov;
563};
564
568template <typename Scalar, typename TargetSpace>
569struct ConditionedMomentsWithEvidence
570{
571 ConditionedMoments<Scalar, TargetSpace> moments;
572 Scalar logLikelihood;
573 Scalar squaredDistance;
574};
575
577template <typename Scalar, typename GivenSpace>
578using GivenMatrixOf = MatrixOf<
579 Scalar,
584
586template <typename Scalar, typename GivenSpace>
587using GivenVectorOf = Eigen::Matrix<
588 Scalar,
590 1,
591 Eigen::ColMajor,
593 1>;
594
599template <
600 typename Factorization,
601 typename MeanDerived,
602 typename CovDerived,
603 typename ResidualDerived,
604 typename Target,
605 typename Given,
606 typename TargetSpace>
607[[nodiscard]] ConditionedMoments<typename MeanDerived::Scalar, TargetSpace>
608conditionMomentsBy(
609 Factorization const & factorization,
610 Eigen::MatrixBase<MeanDerived> const & mean,
611 Eigen::MatrixBase<CovDerived> const & cov,
612 Target const & target,
613 Given const & given,
614 Eigen::MatrixBase<ResidualDerived> const & residual,
615 TargetSpace const & targetSpace
616)
617{
618 using Scalar = typename MeanDerived::Scalar;
619 constexpr int GivenSize = ResidualDerived::RowsAtCompileTime;
620 constexpr int GivenMaxSize = ResidualDerived::MaxRowsAtCompileTime;
623 using CrossMatrix = MatrixOf<Scalar, TargetSize, GivenSize, TargetMaxSize, GivenMaxSize>;
624
625 auto result = ConditionedMoments<Scalar, TargetSpace>{};
626 // With nothing to condition, only the likelihood remains; MSVC can't form an empty gain.
627 if constexpr (TargetSize != 0)
628 {
629 CrossMatrix const cross = lowerSymmetricBlock(cov, target.differences, given.differences);
630 CrossMatrix const gain = transposedSolve<CrossMatrix>(factorization, cross.transpose());
631 result.cov = lowerSymmetricBlock(cov, target.differences, target.differences);
632 result.cov.noalias() -= multiply(gain, cross.transpose());
633 if constexpr (hasIdentityReset<TargetSpace>)
634 {
635 result.mean = Emrys::plus(targetSpace, mean.derived()(target.values), multiply(gain, residual));
636 }
637 else
638 {
639 using TargetVector = decltype(result.mean);
640 using TargetDifference = Eigen::Matrix<Scalar, TargetSize, 1, Eigen::ColMajor, TargetMaxSize, 1>;
641 TargetVector const start = mean.derived()(target.values);
642 TargetDifference const step = multiply(gain, residual);
643 result.mean = Emrys::plus(targetSpace, start, step);
644 resetCovarianceAt(targetSpace, start, step, result.mean, result.cov);
645 }
646 }
647 return result;
648}
649
672template <
673 typename MeanDerived,
674 typename CovDerived,
675 typename ValuesDerived,
676 typename Target,
677 typename Given,
678 typename GivenSpace,
679 typename TargetSpace>
680[[nodiscard]] ConditionedMoments<typename MeanDerived::Scalar, TargetSpace>
681conditionMoments(
682 Eigen::MatrixBase<MeanDerived> const & mean,
683 Eigen::MatrixBase<CovDerived> const & cov,
684 Target const & target,
685 Given const & given,
686 Eigen::MatrixBase<ValuesDerived> const & values,
687 GivenSpace const & givenSpace,
688 TargetSpace const & targetSpace
689)
690{
691 using Scalar = typename MeanDerived::Scalar;
692 EMRYS_FILTER_ASSERT(values.size() == given.values.size());
693 GivenVectorOf<Scalar, GivenSpace> const residual = Emrys::minus(givenSpace, values, mean.derived()(given.values));
694 auto const ldlt =
695 Eigen::LDLT<GivenMatrixOf<Scalar, GivenSpace>>{lowerSymmetricBlock(cov, given.differences, given.differences)};
696 return conditionMomentsBy(ldlt, mean, cov, target, given, residual, targetSpace);
697}
698
707template <
708 typename MeanDerived,
709 typename CovDerived,
710 typename ValuesDerived,
711 typename Target,
712 typename Given,
713 typename GivenSpace,
714 typename TargetSpace>
715[[nodiscard]] std::optional<ConditionedMomentsWithEvidence<typename MeanDerived::Scalar, TargetSpace>>
716conditionMomentsWithEvidence(
717 Eigen::MatrixBase<MeanDerived> const & mean,
718 Eigen::MatrixBase<CovDerived> const & cov,
719 Target const & target,
720 Given const & given,
721 Eigen::MatrixBase<ValuesDerived> const & values,
722 GivenSpace const & givenSpace,
723 TargetSpace const & targetSpace
724)
725{
726 using Scalar = typename MeanDerived::Scalar;
727 using GivenVector = GivenVectorOf<Scalar, GivenSpace>;
728 EMRYS_FILTER_ASSERT(values.size() == given.values.size());
729 auto const llt =
730 Eigen::LLT<GivenMatrixOf<Scalar, GivenSpace>>{lowerSymmetricBlock(cov, given.differences, given.differences)};
731 if (llt.info() != Eigen::Success)
732 {
733 return std::nullopt;
734 }
735 GivenVector const residual = Emrys::minus(givenSpace, values, mean.derived()(given.values));
736 GivenVector whitened = residual;
737 llt.matrixL().solveInPlace(whitened);
738
739 Scalar const log2Pi = Emrys::log2Pi<Scalar>();
740 Scalar const logDeterminant = Scalar(2) * llt.matrixLLT().diagonal().array().log().sum();
741 Scalar const squaredDistance = whitened.squaredNorm();
742 return ConditionedMomentsWithEvidence<Scalar, TargetSpace>{
743 conditionMomentsBy(llt, mean, cov, target, given, residual, targetSpace),
744 Scalar(-0.5) * (Scalar(given.differences.size()) * log2Pi + logDeterminant + squaredDistance),
745 squaredDistance
746 };
747}
748
749} // namespace Emrys::internal
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
The covariance reset (decisions.md [covariance-reset]): hasIdentityReset<>, resetJacobian(),...
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...
Scalar log2Pi()
Definition Math.h:177
constexpr AxisIndex< Path, sizeof...(I)> at(I... indices)
Definition Discrete.h:1071
constexpr Shared shared()
Definition Batched.h:63
GivenBlock< Space, P, Value > given(Space const &space, Value const &value)
Definition Measurement.h:917
constexpr Residual< Inner > residual(Inner inner={})
Definition Resampling.h:544
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
auto plus(S const &space, Eigen::MatrixBase< XDerived > const &x, Eigen::MatrixBase< DDerived > const &d)
Definition Space.h:675
constexpr auto diffIdx()
idx<S, Path>() of the differences of the subspace named by Path: its rows of a covariance.
Definition TagPath.h:669
auto block(Space auto const &space, X &&x)
Definition TagPath.h:979
auto minus(S const &space, Eigen::MatrixBase< ADerived > const &a, Eigen::MatrixBase< BDerived > const &b)
Definition Space.h:715
constexpr bool hasIdentityReset
Whether the reset Jacobian of S is the identity, so that a covariance needs no change when its mean m...
Definition Reset.h:100
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
typename internal::DifferenceSpaceOf< S >::type DifferenceSpace
The space that differences of values in S live in: the type of its difference(), or S itself if it ha...
Definition Space.h:227
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 auto idx()
Definition TagPath.h:658
auto condition(Prior const &prior, OutputWithCross< typename Prior::SpaceType, OutputSpace, LinearizationForm, typename Prior::Scalar > const &predicted, Eigen::MatrixBase< Derived > const &observed, Form const &form={})
Definition Condition.h:676