EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Space.h
Go to the documentation of this file.
1#pragma once
2
13
14#include <concepts>
15#include <type_traits>
16#include <utility>
17
20
21namespace Emrys
22{
23
24namespace internal
25{
26
28template <typename S>
29concept HasNormalize = requires(S const & space, Eigen::MatrixXd & block) { space.normalize(block.middleRows(0, 0)); };
30
34template <typename S>
35concept HasPlus = requires(S const & space, Eigen::MatrixXd & block) {
36 space.plus(block.middleRows(0, 0), block.middleRows(0, 0), block.middleRows(0, 0));
37};
38
42template <typename S>
43concept HasMinus = requires(S const & space, Eigen::MatrixXd & block) {
44 space.minus(block.middleRows(0, 0), block.middleRows(0, 0), block.middleRows(0, 0));
45};
46
51template <typename S>
52concept NearMissNormalize =
53 !HasNormalize<S> && requires(S & space, Eigen::MatrixXd & block) { space.normalize(block); };
54
58template <typename S>
59concept NearMissPlus = !HasPlus<S> && requires(S & space, Eigen::MatrixXd & block) { space.plus(block, block, block); };
60
64template <typename S>
65concept NearMissMinus =
66 !HasMinus<S> && requires(S & space, Eigen::MatrixXd & block) { space.minus(block, block, block); };
67
75template <typename S>
76concept NormalizeTakesBlocks = HasNormalize<S> || (!requires { &S::normalize; } && !NearMissNormalize<S>);
77
79template <typename S>
80concept PlusTakesBlocks = HasPlus<S> || (!requires { &S::plus; } && !NearMissPlus<S>);
81
83template <typename S>
84concept MinusTakesBlocks = HasMinus<S> || (!requires { &S::minus; } && !NearMissMinus<S>);
85
87template <typename S>
88concept HasDifference = requires(S const & space) { space.difference(); };
89
91template <typename S>
92struct DifferenceSpaceOf
93{
95 using type = S;
96};
97
99template <HasDifference S>
100struct DifferenceSpaceOf<S>
101{
103 using type = std::remove_cvref_t<decltype(std::declval<S const &>().difference())>;
104};
105
110template <typename D, typename S>
111concept DifferenceSpaceFor = std::regular<D> && requires(D const & d) {
112 { d.valueDimension() } -> std::convertible_to<Eigen::Index>;
113 typename std::integral_constant<int, D::FixedValueDimension>;
114 typename std::integral_constant<int, D::ValueDimensionStep>;
115 typename std::integral_constant<int, D::MaxValueDimension>;
116 requires(D::ValueDimensionStep == 0) == (S::ValueDimensionStep == 0);
117 requires(D::MaxValueDimension == Eigen::Dynamic) == (S::MaxValueDimension == Eigen::Dynamic);
118};
119
121template <typename S>
122inline constexpr bool hasEqualSizes = DifferenceSpaceOf<S>::type::FixedValueDimension == S::FixedValueDimension
123 && DifferenceSpaceOf<S>::type::ValueDimensionStep == S::ValueDimensionStep
124 && DifferenceSpaceOf<S>::type::MaxValueDimension == S::MaxValueDimension;
125
130template <typename S>
131inline constexpr int resizableBlockCount = S::ValueDimensionStep == 0 ? 0 : 1;
132
133} // namespace internal
134
185template <typename S>
186concept Space = requires(S const & space) {
187 typename std::integral_constant<int, S::FixedValueDimension>;
188 typename std::integral_constant<int, S::ValueDimensionStep>;
189 typename std::integral_constant<int, S::MaxValueDimension>;
190 requires S::FixedValueDimension >= 0 && S::ValueDimensionStep >= 0;
191 requires(
192 S::ValueDimensionStep == 0
193 ? S::MaxValueDimension == S::FixedValueDimension
194 : (S::MaxValueDimension == Eigen::Dynamic || S::MaxValueDimension >= S::FixedValueDimension)
195 );
196 requires std::regular<S>;
197 { space.valueDimension() } -> std::convertible_to<Eigen::Index>;
198 requires internal::NormalizeTakesBlocks<S> && internal::PlusTakesBlocks<S> && internal::MinusTakesBlocks<S>;
199 requires internal::NormalizeTakesBlocks<typename internal::DifferenceSpaceOf<S>::type>;
200 requires internal::HasDifference<S>
201 || (!internal::HasNormalize<S> && !internal::HasPlus<S> && !internal::HasMinus<S>);
202 requires internal::DifferenceSpaceFor<typename internal::DifferenceSpaceOf<S>::type, S>;
203 requires internal::HasPlus<S> == internal::HasMinus<S>;
204 requires internal::hasEqualSizes<S> || internal::HasPlus<S>;
205 requires S::ValueDimensionStep == 0 || internal::resizableBlockCount<S> > 1
206 || requires(S & resizable, Eigen::Index n) { resizable.resize(n); };
207};
208
216template <typename S>
217concept FixedSizeSpace = std::is_empty_v<S> && std::default_initializable<S>;
218
226template <Space S>
227using DifferenceSpace = typename internal::DifferenceSpaceOf<S>::type;
228
233template <Space S>
234[[nodiscard]] DifferenceSpace<S>
235differenceSpace(S const & space)
236{
237 if constexpr (internal::HasDifference<S>)
238 {
239 return space.difference();
240 }
241 else
242 {
243 return space;
244 }
245}
246
250template <typename Space>
251inline constexpr int valueDimensionAtCompileTime =
252 Space::ValueDimensionStep == 0 ? Space::FixedValueDimension : Eigen::Dynamic;
253
257template <typename Space>
258inline constexpr int maxValueDimensionAtCompileTime =
259 Space::ValueDimensionStep == 0 ? Space::FixedValueDimension : Space::MaxValueDimension;
260
270template <Space S>
271[[nodiscard]] Eigen::Index
272valueDimension(S const & space)
273{
274 return space.valueDimension();
275}
276
282template <Space S>
283[[nodiscard]] Eigen::Index
284differenceDimension(S const & space)
285{
286 if constexpr (internal::HasDifference<S>)
287 {
288 return space.difference().valueDimension();
289 }
290 else
291 {
292 return space.valueDimension();
293 }
294}
295
301template <Space S>
302inline constexpr bool hasPlainValues = !internal::HasNormalize<S> && !internal::HasPlus<S>;
303
310template <Space S>
311inline constexpr bool hasPlainDifferences =
312 !internal::HasNormalize<DifferenceSpace<S>> && !internal::HasPlus<S> && !internal::HasMinus<S>;
313
320template <Space S>
322
329template <Space S, typename Derived>
330void
331normalizeValues(S const & space, Eigen::MatrixBase<Derived> const & block)
332{
333 if constexpr (internal::HasNormalize<S>)
334 {
335 space.normalize(block);
336 }
337}
338
344template <Space S, typename Derived>
345void
346normalizeDifferences(S const & space, Eigen::MatrixBase<Derived> const & block)
347{
348 if constexpr (internal::HasNormalize<DifferenceSpace<S>>)
349 {
350 differenceSpace(space).normalize(block);
351 }
352}
353
358template <FixedSizeSpace S, typename Derived>
359 requires Space<S>
360void
361normalizeValues(Eigen::MatrixBase<Derived> const & block)
362{
364}
365
370template <FixedSizeSpace S, typename Derived>
371 requires Space<S>
372void
373normalizeDifferences(Eigen::MatrixBase<Derived> const & block)
374{
376}
377
387template <typename Derived>
388[[nodiscard]] Eigen::MatrixBase<Derived> &
389writable(Eigen::MatrixBase<Derived> const & block)
390{
391 return const_cast<Eigen::MatrixBase<Derived> &>(block);
392}
393
394namespace internal
395{
402template <double Lower, double Period, typename Scalar>
403[[nodiscard]] constexpr Scalar
404wrapValue(Scalar x)
405{
406 constexpr double upper = Lower + Period;
407 if (x >= Scalar(Lower) && x < Scalar(upper)) [[likely]]
408 {
409 return x;
410 }
411 auto y = Scalar{};
412 if (x >= Scalar(Lower - Period) && x < Scalar(upper + Period)) [[likely]]
413 {
414 // Not a conditional expression, whose branches are distinct expression types for an
415 // automatic differentiation scalar.
416 if (x < Scalar(Lower))
417 {
418 y = x + Scalar(Period);
419 }
420 else
421 {
422 y = x - Scalar(Period);
423 }
424 }
425 else
426 {
427 y = x - Scalar(Period) * floorOf<Scalar>((x - Scalar(Lower)) / Scalar(Period));
428 }
429 // Rounding can leave y an ulp or so below the lower bound: the quotient can round up to the next
430 // integer, and Lower - Period is itself rounded. One more period brings it back. Just below the
431 // lower bound, that sum, or the one above, can round up to the excluded upper bound. Written so
432 // that NaN passes through.
433 if (y < Scalar(Lower))
434 {
435 y += Scalar(Period);
436 }
437 return y >= Scalar(upper) ? Scalar(Lower) : y;
438}
439
441template <typename Scalar, int Rows, int Cols, int MaxRows, int MaxCols>
442using MatrixOf = Eigen::
443 Matrix<Scalar, Rows, Cols, (MaxRows == 1 && MaxCols != 1 ? Eigen::RowMajor : Eigen::ColMajor), MaxRows, MaxCols>;
444
448template <typename L, typename R>
449struct PairedColumns
450{
452 static constexpr bool broadcastLeft = L::ColsAtCompileTime == 1 && R::ColsAtCompileTime != 1;
453
455 static constexpr bool broadcastRight = R::ColsAtCompileTime == 1 && L::ColsAtCompileTime != 1;
456
458 static constexpr int cols = broadcastLeft ? int(R::ColsAtCompileTime) : int(L::ColsAtCompileTime);
459
461 static constexpr int maxCols = broadcastLeft ? int(R::MaxColsAtCompileTime) : int(L::MaxColsAtCompileTime);
462
464 template <typename Left, typename Right>
465 [[nodiscard]] static Eigen::Index
466 count(Left const & left, Right const & right)
467 {
468 EMRYS_FILTER_ASSERT(
469 (broadcastLeft || broadcastRight || left.cols() == right.cols())
470 && "the operands have different numbers of columns"
471 );
472 return broadcastLeft ? right.cols() : left.cols();
473 }
474};
475
477template <bool Broadcast, typename Derived>
478[[nodiscard]] decltype(auto)
479withColumns(Eigen::MatrixBase<Derived> const & m, Eigen::Index cols)
480{
481 if constexpr (Broadcast)
482 {
483 return m.derived().replicate(1, cols);
484 }
485 else
486 {
487 return m.derived();
488 }
489}
490
495template <typename Derived>
496[[nodiscard]] decltype(auto)
497inMemory(Eigen::MatrixBase<Derived> const & m)
498{
499 if constexpr ((Eigen::internal::traits<Derived>::Flags & Eigen::DirectAccessBit) != 0)
500 {
501 return m.derived();
502 }
503 else
504 {
505 return m.eval();
506 }
507}
508
512template <typename S, typename X, typename D, typename Out>
513void
514plusInto(
515 S const & space,
516 Eigen::MatrixBase<X> const & x,
517 Eigen::MatrixBase<D> const & d,
518 Eigen::MatrixBase<Out> const & out
519)
520{
521 if constexpr (HasPlus<S>)
522 {
523 space.plus(x, d, out);
524 }
525 else
526 {
527 writable(out) = x + d;
528 normalizeValues(space, out);
529 }
530}
531
535template <typename S, typename A, typename B, typename Out>
536void
537minusInto(
538 S const & space,
539 Eigen::MatrixBase<A> const & a,
540 Eigen::MatrixBase<B> const & b,
541 Eigen::MatrixBase<Out> const & out
542)
543{
544 if constexpr (HasMinus<S>)
545 {
546 space.minus(a, b, out);
547 }
548 else
549 {
550 writable(out) = a - b;
551 normalizeDifferences(space, out);
552 }
553}
554
558template <bool Difference, typename S>
559[[nodiscard]] Eigen::Index
560rowsOf(S const & space)
561{
562 if constexpr (Difference)
563 {
564 return differenceSpace(space).valueDimension();
565 }
566 else
567 {
568 return space.valueDimension();
569 }
570}
571
575template <bool Difference, typename S>
576inline constexpr int rowsAtCompileTime =
578
583template <int Rows, typename M>
584[[nodiscard]] auto
585middleRowsOf(M & m, Eigen::Index offset, Eigen::Index rows)
586{
587 if constexpr (Rows == Eigen::Dynamic)
588 {
589 return m.middleRows(offset, rows);
590 }
591 else
592 {
593 EMRYS_FILTER_ASSERT(rows == Rows && "the block's rows must be its compile-time size");
594 return m.template middleRows<Rows>(offset);
595 }
596}
597
601template <int Cols, typename M>
602[[nodiscard]] auto
603middleColsOf(M & m, Eigen::Index offset, Eigen::Index cols)
604{
605 if constexpr (Cols == Eigen::Dynamic)
606 {
607 return m.middleCols(offset, cols);
608 }
609 else
610 {
611 EMRYS_FILTER_ASSERT(cols == Cols && "the block's columns must be its compile-time size");
612 return m.template middleCols<Cols>(offset);
613 }
614}
615
619template <int Rows, int Cols, typename M>
620[[nodiscard]] auto
621blockOf(M & m, Eigen::Index row, Eigen::Index col, Eigen::Index rows, Eigen::Index cols)
622{
623 EMRYS_FILTER_ASSERT(
624 (Rows == Eigen::Dynamic || rows == Rows) && (Cols == Eigen::Dynamic || cols == Cols)
625 && "the block's sizes must be its compile-time sizes"
626 );
627 return m.template block<Rows, Cols>(row, col, rows, cols);
628}
629
630} // namespace internal
631
639template <Space S, typename Scalar = double>
641 Eigen::Matrix<Scalar, valueDimensionAtCompileTime<S>, 1, Eigen::ColMajor, maxValueDimensionAtCompileTime<S>, 1>;
642
649template <Space S, typename Scalar = double>
651
658template <Space Rows, Space Cols = Rows, typename Scalar = double>
659using DifferenceMatrix = internal::MatrixOf<
660 Scalar,
665
673template <Space S, typename XDerived, typename DDerived>
674[[nodiscard]] auto
675plus(S const & space, Eigen::MatrixBase<XDerived> const & x, Eigen::MatrixBase<DDerived> const & d)
676{
677 using Columns = internal::PairedColumns<XDerived, DDerived>;
678 // The result's rows are the space's where it knows them, so that an unbounded operand into a
679 // bounded space gives a bounded result.
680 constexpr int rows = valueDimensionAtCompileTime<S> != Eigen::Dynamic ? valueDimensionAtCompileTime<S>
681 : int(XDerived::RowsAtCompileTime);
682 constexpr int maxRows = maxValueDimensionAtCompileTime<S> != Eigen::Dynamic ? maxValueDimensionAtCompileTime<S>
683 : int(XDerived::MaxRowsAtCompileTime);
684 using Result = internal::MatrixOf<typename XDerived::Scalar, rows, Columns::cols, maxRows, Columns::maxCols>;
685 Eigen::Index const cols = Columns::count(x, d);
686 auto result = Result{};
687 result.resize(x.rows(), cols);
688 // Evaluated once where they are expressions, so a space's plus() can read them by block or by
689 // column without recomputing a product; a broadcast stays a lazy replicate of the evaluated operand.
690 auto const & xInMemory = internal::inMemory(x);
691 auto const & dInMemory = internal::inMemory(d);
692 auto const & paired = internal::withColumns<Columns::broadcastLeft>(xInMemory, cols);
693 auto const & offsets = internal::withColumns<Columns::broadcastRight>(dInMemory, cols);
694 if constexpr (internal::HasPlus<S>)
695 {
696 space.plus(paired, offsets, result);
697 }
698 else
699 {
700 result = paired + offsets;
701 normalizeValues(space, result);
702 }
703 return result;
704}
705
713template <Space S, typename ADerived, typename BDerived>
714[[nodiscard]] auto
715minus(S const & space, Eigen::MatrixBase<ADerived> const & a, Eigen::MatrixBase<BDerived> const & b)
716{
717 using Columns = internal::PairedColumns<ADerived, BDerived>;
718 // The result's rows are the difference space's where it knows them, so that an unbounded
719 // operand into a bounded space gives a bounded result, and the operands' rows otherwise, which
720 // is right only where the space's differences have the sizes of its values.
721 constexpr bool sameRows = internal::hasEqualSizes<S>;
722 constexpr int spaceRows = valueDimensionAtCompileTime<DifferenceSpace<S>>;
723 constexpr int spaceMaxRows = maxValueDimensionAtCompileTime<DifferenceSpace<S>>;
724 constexpr int rows = spaceRows != Eigen::Dynamic ? spaceRows
725 : sameRows ? int(ADerived::RowsAtCompileTime)
726 : Eigen::Dynamic;
727 constexpr int maxRows = spaceMaxRows != Eigen::Dynamic ? spaceMaxRows
728 : sameRows ? int(ADerived::MaxRowsAtCompileTime)
729 : Eigen::Dynamic;
730 using Result = internal::MatrixOf<typename ADerived::Scalar, rows, Columns::cols, maxRows, Columns::maxCols>;
731 Eigen::Index const cols = Columns::count(a, b);
732 auto result = Result{};
733 result.resize(sameRows ? a.rows() : differenceDimension(space), cols);
734 auto const & aInMemory = internal::inMemory(a);
735 auto const & bInMemory = internal::inMemory(b);
736 auto const & left = internal::withColumns<Columns::broadcastLeft>(aInMemory, cols);
737 auto const & right = internal::withColumns<Columns::broadcastRight>(bInMemory, cols);
738 if constexpr (internal::HasMinus<S>)
739 {
740 space.minus(left, right, result);
741 }
742 else
743 {
744 result = left - right;
745 normalizeDifferences(space, result);
746 }
747 return result;
748}
749
765template <Space S, typename XDerived, typename DDerived>
766[[nodiscard]] auto
767less(S const & space, Eigen::MatrixBase<XDerived> const & x, Eigen::MatrixBase<DDerived> const & d)
768{
769 return Emrys::plus(space, x, -d);
770}
771
776template <FixedSizeSpace S, typename XDerived, typename DDerived>
777 requires Space<S>
778[[nodiscard]] auto
779plus(Eigen::MatrixBase<XDerived> const & x, Eigen::MatrixBase<DDerived> const & d)
780{
781 return Emrys::plus(S{}, x, d);
782}
783
788template <FixedSizeSpace S, typename ADerived, typename BDerived>
789 requires Space<S>
790[[nodiscard]] auto
791minus(Eigen::MatrixBase<ADerived> const & a, Eigen::MatrixBase<BDerived> const & b)
792{
793 return Emrys::minus(S{}, a, b);
794}
795
800template <FixedSizeSpace S, typename XDerived, typename DDerived>
801 requires Space<S>
802[[nodiscard]] auto
803less(Eigen::MatrixBase<XDerived> const & x, Eigen::MatrixBase<DDerived> const & d)
804{
805 return Emrys::less(S{}, x, d);
806}
807
817template <typename Space>
818[[nodiscard]] constexpr bool
819allowsValueDimension(Eigen::Index n)
820{
821 if constexpr (Space::ValueDimensionStep == 0)
822 {
823 return n == Space::FixedValueDimension;
824 }
825 else
826 {
827 return n >= Space::FixedValueDimension && (n - Space::FixedValueDimension) % Space::ValueDimensionStep == 0
828 && (Space::MaxValueDimension == Eigen::Dynamic || n <= Space::MaxValueDimension);
829 }
830}
831
832namespace internal
833{
834
839template <typename Space>
840void
841fitValueDimension(Space & space, Eigen::Index n)
842{
843 EMRYS_FILTER_ASSERT(allowsValueDimension<Space>(n) && "the dimension does not fit the space");
844 if constexpr (requires { space.resize(n); })
845 {
846 if (space.valueDimension() != n)
847 {
848 space.resize(n);
849 }
850 EMRYS_FILTER_ASSERT(space.valueDimension() == n && "the space's resize() did not give it this dimension");
851 }
852 else
853 {
854 EMRYS_FILTER_ASSERT(space.valueDimension() == n && "the dimension does not fit the space");
855 }
856}
857
858} // namespace internal
859
860} // namespace Emrys
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
The matrix of a linear measurement model by its columns: a matrix per block of the state read,...
Definition MeasurementModel.h:396
A space whose instance holds nothing, so that S{} is the only one there is: a space of fixed size,...
Definition Space.h:217
A type usable as the space of a distribution.
Definition Space.h:186
Scalar floorOf(std::type_identity_t< Scalar > const &x)
Definition Math.h:277
auto less(S const &space, Eigen::MatrixBase< XDerived > const &x, Eigen::MatrixBase< DDerived > const &d)
Definition Space.h:767
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 bool allowsValueDimension(Eigen::Index n)
Definition Space.h:819
Eigen::MatrixBase< Derived > & writable(Eigen::MatrixBase< Derived > const &block)
block, writable: Eigen's idiom for a function that writes through an expression passed as const&.
Definition Space.h:389
DifferenceSpace< S > differenceSpace(S const &space)
Definition Space.h:235
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
ValueVector< DifferenceSpace< S >, Scalar > DifferenceVector
A difference in S as an Eigen column vector of Scalar, the type of minus() and of the offset plus() t...
Definition Space.h:650
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
void normalizeDifferences(S const &space, Eigen::MatrixBase< Derived > const &block)
Brings the differences in block into canonical form: normalizeValues() in the difference space of spa...
Definition Space.h:346
constexpr bool isLinear
Whether S is linear: its values and its differences are both plain vectors.
Definition Space.h:321
constexpr bool hasPlainValues
Whether the values of S are plain: it has neither normalize() nor plus(), so is x + δ.
Definition Space.h:302
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
void normalizeValues(S const &space, Eigen::MatrixBase< Derived > const &block)
Brings the values in block (rows are space's coordinates, columns samples) into canonical form,...
Definition Space.h:331
internal::MatrixOf< Scalar, valueDimensionAtCompileTime< DifferenceSpace< Rows > >, valueDimensionAtCompileTime< DifferenceSpace< Cols > >, maxValueDimensionAtCompileTime< DifferenceSpace< Rows > >, maxValueDimensionAtCompileTime< DifferenceSpace< Cols > > > DifferenceMatrix
An Eigen matrix of Scalar over the differences of Rows and Cols: a covariance, with one space for bot...
Definition Space.h:659
constexpr bool hasPlainDifferences
Whether the differences of S are plain: its difference space has no normalize(), and it has neither p...
Definition Space.h:311
Eigen::Matrix< Scalar, valueDimensionAtCompileTime< S >, 1, Eigen::ColMajor, maxValueDimensionAtCompileTime< S >, 1 > ValueVector
A value in S as an Eigen column vector of Scalar: a mean, a sample, or a true state.
Definition Space.h:640