EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Batched.h
Go to the documentation of this file.
1#pragma once
2
9
10#include <array>
11#include <cstddef>
12#include <tuple>
13#include <type_traits>
14#include <utility>
15
16#include <unsupported/Eigen/AutoDiff>
17
24
25namespace Emrys
26{
27
32namespace jacobians
33{
34
40{};
41
46struct Shared
47{};
48
52[[nodiscard]] constexpr PerColumn
54{
55 return {};
56}
57
62[[nodiscard]] constexpr Shared
64{
65 return {};
66}
67
68} // namespace jacobians
69
70namespace internal
71{
72
74struct NoJacobians
75{};
76
78template <typename T>
79inline constexpr bool isBatchedMark = false;
80
82template <>
83inline constexpr bool isBatchedMark<NoJacobians>{true};
84
85template <>
86inline constexpr bool isBatchedMark<jacobians::PerColumn>{true};
87
88template <>
89inline constexpr bool isBatchedMark<jacobians::Shared>{true};
90
92
93} // namespace internal
94
99template <typename T>
101 internal::isBatchedMark<std::remove_cvref_t<T>> && !std::is_same_v<std::remove_cvref_t<T>, internal::NoJacobians>;
102
114template <typename F, typename Mark = internal::NoJacobians>
116{
117 static_assert(internal::isBatchedMark<Mark>);
118
120 using FunctionType = F;
122 using MarkType = Mark;
123
125 static constexpr bool GivesJacobians = !std::is_same_v<Mark, internal::NoJacobians>;
126
128 static constexpr bool PerColumn = std::is_same_v<Mark, jacobians::PerColumn>;
129
131
139 template <typename In, typename Out, MatrixExpression X, typename... Rest>
140 [[nodiscard]] auto
141 operator()(In const & in, Out const & out, X const & x, Rest const &... rest) const;
142};
143
144namespace internal
145{
146
148template <typename T>
149inline constexpr bool isBatched = false;
150
152template <typename F, typename Mark>
153inline constexpr bool isBatched<Batched<F, Mark>>{true};
154
156
162template <typename F>
163inline constexpr bool takesBatches = isBatched<F> || requires { requires F::TakesBatches; };
164
168template <typename F>
169struct BatchMarkOf
170{
171 using type = typename F::MarkType;
172};
173
175template <typename F, typename Mark>
176struct BatchMarkOf<Batched<F, Mark>>
177{
178 using type = Mark;
179};
180
182
184template <typename F>
185using BatchMark = typename BatchMarkOf<F>::type;
186
190template <typename F, bool = takesBatches<F>>
191struct BatchMarkOrNoneOf
192{
193 using type = NoJacobians;
194};
195
197template <typename F>
198struct BatchMarkOrNoneOf<F, true>
199{
200 using type = BatchMark<F>;
201};
202
204
206template <typename F>
207using BatchMarkOrNone = typename BatchMarkOrNoneOf<F>::type;
208
212template <typename F>
213[[nodiscard]] auto const &
214batchBody(F const & f)
215{
216 if constexpr (isBatched<F>)
217 {
218 return f.function;
219 }
220 else
221 {
222 return f;
223 }
224}
225
227template <typename F>
228using BatchBody = std::remove_cvref_t<decltype(batchBody(std::declval<F const &>()))>;
229
230} // namespace internal
231
245template <typename F>
246 requires(!internal::isBatched<std::remove_cvref_t<F>> && !MatrixExpression<F>)
247[[nodiscard]] Batched<std::remove_cvref_t<F>>
248batched(F && function)
249{
250 return {std::forward<F>(function)};
251}
252
257template <typename F, JacobianMark Mark>
258 requires(!internal::isBatched<std::remove_cvref_t<F>> && !MatrixExpression<F>)
259[[nodiscard]] Batched<std::remove_cvref_t<F>, std::remove_cvref_t<Mark>>
260batched(F && function, Mark)
261{
262 return {std::forward<F>(function)};
263}
264
265namespace internal
266{
267
269template <typename Name, typename Blocks>
270inline constexpr std::size_t blockPosition = 0;
271
273template <typename Name, typename... Blocks>
274inline constexpr std::size_t blockPosition<Name, std::tuple<Blocks...>> = [] {
275 constexpr std::array<bool, sizeof...(Blocks) + 1> same{std::is_same_v<Name, Blocks>..., true};
276 std::size_t k = 0;
277 while (!same[k])
278 {
279 ++k;
280 }
281 return k;
282}();
283
285
289template <typename In>
290using InputBlocks = typename decltype([] {
291 if constexpr (IsComposite<In>)
292 {
293 return std::type_identity<CompositeBlocks<In>>{};
294 }
295 else
296 {
297 return std::type_identity<std::tuple<In>>{};
298 }
299}())::type;
300
304template <typename Name, typename In>
305[[nodiscard]] BlockRows
306inputBlockRows(In const & in)
307{
308 if constexpr (IsComposite<In>)
309 {
310 return differenceBlockRows<Name>(in);
311 }
312 else
313 {
314 return BlockRows{0, differenceDimension(in)};
315 }
316}
317
318} // namespace internal
319
340template <typename In, typename Out, typename Scalar_, typename Mark>
342{
344 using Blocks = internal::InputBlocks<In>;
345
346 static constexpr std::size_t BlockCount = std::tuple_size_v<Blocks>;
347
348public:
349
351 using Scalar = Scalar_;
352
354 using ValuesType = Eigen::Map<internal::MatrixOf<
355 Scalar_,
357 Eigen::Dynamic,
359 Eigen::Dynamic>>;
360
362 using JacobiansType = Eigen::Map<internal::MatrixOf<
363 Scalar_,
365 Eigen::Dynamic,
367 Eigen::Dynamic>>;
368
377 In const & in,
378 std::array<bool, BlockCount> const & wanted
379 )
380 : m_values{values}
381 , m_jacobians{jacobians}
382 , m_in{in}
383 {
384 Eigen::Index offset = 0;
385 [&]<std::size_t... K>(std::index_sequence<K...>) {
386 ((m_offsets[K] = wanted[K] ? offset : -1,
387 offset += wanted[K] ? internal::inputBlockRows<std::tuple_element_t<K, Blocks>>(in).size : 0),
388 ...);
389 }(std::make_index_sequence<BlockCount>{});
390 m_wantedColumns = offset;
391 EMRYS_FILTER_ASSERT(
392 m_jacobians.cols() == (std::is_same_v<Mark, jacobians::PerColumn> ? values.cols() : 1) * m_wantedColumns
393 && "the Jacobians' storage holds the wanted blocks' columns for each column, or once"
394 );
395 }
396
398 [[nodiscard]] ValuesType &
400 {
401 return m_values;
402 }
403
405 [[nodiscard]] ValuesType const &
406 values() const
407 {
408 return m_values;
409 }
410
414 template <typename Name>
415 [[nodiscard]] bool
417 {
418 checkName<Name>();
419 return m_offsets[internal::blockPosition<Name, Blocks>] >= 0;
420 }
421
425 [[nodiscard]] bool
427 {
428 return !std::is_same_v<Mark, internal::NoJacobians> && m_wantedColumns == differenceDimension(m_in);
429 }
430
434 [[nodiscard]] auto
435 jacobian(Eigen::Index j)
436 {
437 checkWhole();
438 static_assert(
439 !std::is_same_v<Mark, jacobians::Shared>,
440 "under jacobians::shared(), a batched function writes its whole Jacobian once for every column, jacobian()"
441 );
442 EMRYS_FILTER_ASSERT(j >= 0 && j < m_values.cols() && "the column must be one of the batch");
443 return wholeColumns(j * m_wantedColumns);
444 }
445
449 [[nodiscard]] auto
451 {
452 checkWhole();
453 static_assert(
454 !std::is_same_v<Mark, jacobians::PerColumn>,
455 "under jacobians::perColumn(), a batched function writes its whole Jacobian once per column, jacobian(j)"
456 );
457 return wholeColumns(0);
458 }
459
464 template <typename Name>
465 [[nodiscard]] auto
466 jacobian(Eigen::Index j)
467 {
468 checkJacobian<Name>();
469 static_assert(
470 !std::is_same_v<Mark, jacobians::Shared>,
471 "a batched function marked jacobians::shared() writes one Jacobian for every column, jacobian<Name>()"
472 );
473 EMRYS_FILTER_ASSERT(j >= 0 && j < m_values.cols() && "the column must be one of the batch");
474 return columnsOf<Name>(j * m_wantedColumns);
475 }
476
480 template <typename Name>
481 [[nodiscard]] auto
483 {
484 checkJacobian<Name>();
485 static_assert(
486 !std::is_same_v<Mark, jacobians::PerColumn>,
487 "a batched function marked jacobians::perColumn() writes one Jacobian per column, jacobian<Name>(j)"
488 );
489 return columnsOf<Name>(0);
490 }
491
497 template <typename Out2, typename In2>
499 renamed(In2 const & in)
500 {
502 static_assert(std::tuple_size_v<internal::InputBlocks<In2>> == BlockCount);
503 auto wanted = std::array<bool, BlockCount>{};
504 for (std::size_t k = 0; k < BlockCount; ++k)
505 {
506 wanted[k] = m_offsets[k] >= 0;
507 }
508 return Renamed{
509 typename Renamed::ValuesType{m_values.data(), m_values.rows(), m_values.cols()},
510 typename Renamed::JacobiansType{m_jacobians.data(), m_jacobians.rows(), m_jacobians.cols()},
511 in,
512 wanted
513 };
514 }
515
516private:
517
519 template <typename Name>
520 static consteval void
521 checkName()
522 {
523 static_assert(
524 internal::blockPosition<Name, Blocks> < BlockCount,
525 "a batched function writes a Jacobian only for a block of its input, by the name reads() gives it"
526 );
527 }
528
532 template <typename Name>
533 static consteval void
534 checkJacobian()
535 {
536 static_assert(
537 !std::is_same_v<Mark, internal::NoJacobians>,
538 "a batched function marked without Jacobians writes none: mark it batched(f, jacobians::perColumn()) "
539 "or batched(f, jacobians::shared())"
540 );
541 checkName<Name>();
542 }
543
547 template <typename Name>
548 [[nodiscard]] auto
549 columnsOf(Eigen::Index start)
550 {
551 Eigen::Index const offset = m_offsets[internal::blockPosition<Name, Blocks>];
552 EMRYS_FILTER_ASSERT(offset >= 0 && "write only the Jacobians wantsJacobian() says are wanted");
554 return internal::middleColsOf<Cols>(m_jacobians, start + offset, internal::inputBlockRows<Name>(m_in).size);
555 }
556
558 static consteval void
559 checkWhole()
560 {
561 static_assert(
562 !std::is_same_v<Mark, internal::NoJacobians>,
563 "a batched function marked without Jacobians writes none: mark it batched(f, jacobians::perColumn()) "
564 "or batched(f, jacobians::shared())"
565 );
566 }
567
569 [[nodiscard]] auto
570 wholeColumns(Eigen::Index start)
571 {
572 EMRYS_FILTER_ASSERT(
573 m_wantedColumns == differenceDimension(m_in)
574 && "write the whole Jacobian only when wantsJacobians() says every block is wanted"
575 );
577 return internal::middleColsOf<Cols>(m_jacobians, start, m_wantedColumns);
578 }
579
580 ValuesType m_values;
581 JacobiansType m_jacobians;
582 In const & m_in;
583 std::array<Eigen::Index, BlockCount> m_offsets{};
584 Eigen::Index m_wantedColumns = 0;
585};
586
587template <typename F, typename Mark>
588template <typename In, typename Out, MatrixExpression X, typename... Rest>
589auto
590Batched<F, Mark>::operator()(In const & in, Out const & out, X const & x, Rest const &... rest) const
591{
592 static_assert(
594 "a batched function returns nothing to size its output by, so its model names the output with writes() or "
595 "outputSpace()"
596 );
597 using T = typename X::Scalar;
598 using Output = BatchedOutput<In, Out, T, Mark>;
599 using Value = internal::MatrixOf<T, valueDimensionAtCompileTime<Out>, 1, maxValueDimensionAtCompileTime<Out>, 1>;
600 using Jacobian = internal::MatrixOf<
601 T,
606 EMRYS_FILTER_ASSERT(x.rows() == valueDimension(in) && x.cols() == 1 && "the point is a vector in the input space");
607 auto value = Value{Value::Zero(valueDimension(out))};
608 auto jacobian = Jacobian{};
609 auto wanted = std::array<bool, std::tuple_size_v<internal::InputBlocks<In>>>{};
610 if constexpr (GivesJacobians)
611 {
612 jacobian.setZero(differenceDimension(out), differenceDimension(in));
613 wanted.fill(true);
614 }
615 auto y = Output{
616 typename Output::ValuesType{value.data(), value.rows(), 1},
617 typename Output::JacobiansType{jacobian.data(), differenceDimension(out), GivesJacobians ? jacobian.cols() : 0},
618 in,
619 wanted
620 };
621 internal::callForm(function, in, out, x, rest..., y);
622 if constexpr (GivesJacobians)
623 {
624 return withJacobian(std::move(value), std::move(jacobian));
625 }
626 else
627 {
628 return value;
629 }
630}
631
632namespace internal
633{
634
640template <typename F, typename In, typename Out, typename X, typename Y, typename... Rest>
641void
642callBatched(F const & batched, In const & in, Out const & out, X const & x, Y & y, Rest const &... rest)
643{
644 static_assert(takesBatches<F>);
645 using T = typename X::Scalar;
647 EMRYS_FILTER_ASSERT(
648 x.rows() == valueDimension(in) && y.rows() == valueDimension(out) && y.cols() == x.cols()
649 && "the batch writes a column in the output space per column in the input space"
650 );
651 auto output = Output{
652 typename Output::ValuesType{y.data(), y.rows(), y.cols()},
653 typename Output::JacobiansType{nullptr, differenceDimension(out), 0},
654 in,
655 {}
656 };
657 callForm(batchBody(batched), in, out, x, rest..., output);
658}
659
663template <typename Name, typename In>
664[[nodiscard]] BlockRows
665inputValueBlockRows(In const & in)
666{
667 if constexpr (IsComposite<In>)
668 {
669 return blockRows<Name>(in);
670 }
671 else
672 {
673 return BlockRows{0, valueDimension(in)};
674 }
675}
676
681template <typename F, typename In, typename Out, typename Dual, typename... Rest>
682inline constexpr bool batchTakesDuals = functionForm<
683 F,
684 In,
685 Out,
686 MatrixOf<
687 Dual,
689 Eigen::Dynamic,
691 Eigen::Dynamic>,
692 Rest const &...,
693 BatchedOutput<In, Out, Dual, NoJacobians> &>
694 != 0;
695
710template <typename F, typename In, typename Out, typename X, std::size_t K, typename Y, typename J, typename... Rest>
711void
712callBatchedWithJacobians(
713 F const & batched,
714 In const & in,
715 Out const & out,
716 X const & x,
717 std::array<bool, K> const & wanted,
718 Y & y,
719 J & jacobians,
720 Rest const &... rest
721)
722{
723 static_assert(takesBatches<F>);
724 using Mark = BatchMark<F>;
725 using T = typename X::Scalar;
726 using Blocks = InputBlocks<In>;
727 static_assert(K == std::tuple_size_v<Blocks>);
728 Eigen::Index const count = x.cols();
729 Eigen::Index const m = differenceDimension(out);
730 Eigen::Index w = 0;
731 [&]<std::size_t... B>(std::index_sequence<B...>) {
732 ((w += wanted[B] ? inputBlockRows<std::tuple_element_t<B, Blocks>>(in).size : 0), ...);
733 }(std::make_index_sequence<K>{});
734 EMRYS_FILTER_ASSERT(
735 x.rows() == valueDimension(in) && y.rows() == valueDimension(out) && y.cols() == count && jacobians.rows() == m
736 && jacobians.cols() == w * count
737 && "the batch writes a value and a Jacobian over the wanted blocks per column of its input"
738 );
739 if constexpr (!std::is_same_v<Mark, NoJacobians>)
740 {
741 using Output = BatchedOutput<In, Out, T, Mark>;
742 constexpr bool PerColumn = std::is_same_v<Mark, jacobians::PerColumn>;
743 // A shared Jacobian is written once, beside the values, and then copied to every column.
744 using Shared = MatrixOf<
745 T,
747 Eigen::Dynamic,
750 auto shared = Shared{};
751 if constexpr (!PerColumn)
752 {
753 shared.resize(m, w);
754 }
755 auto output = Output{
756 typename Output::ValuesType{y.data(), y.rows(), count},
757 typename Output::JacobiansType{PerColumn ? jacobians.data() : shared.data(), m, PerColumn ? w * count : w},
758 in,
759 wanted
760 };
761 callForm(batchBody(batched), in, out, x, rest..., output);
762 if constexpr (!PerColumn)
763 {
764 for (Eigen::Index j = 0; j < count; ++j)
765 {
766 jacobians.middleCols(j * w, w) = shared;
767 }
768 }
769 }
770 else
771 {
772 using Derivatives = Eigen::
773 Matrix<T, Eigen::Dynamic, 1, Eigen::ColMajor, maxValueDimensionAtCompileTime<DifferenceSpace<In>>, 1>;
774 using Dual = Eigen::AutoDiffScalar<Derivatives>;
775 static_assert(
776 batchTakesDuals<BatchBody<F>, In, Out, Dual, Rest...>,
777 "a batched function whose Jacobian a method uses is differentiated in dual numbers, so it takes X as "
778 "auto const & and writes Y in X's scalar; or it writes its Jacobians, batched(f, jacobians::perColumn()) "
779 "or batched(f, jacobians::shared())"
780 );
781 using DualX = MatrixOf<
782 Dual,
784 X::ColsAtCompileTime,
786 X::MaxColsAtCompileTime>;
787 using DualY = MatrixOf<
788 Dual,
790 Y::ColsAtCompileTime,
792 Y::MaxColsAtCompileTime>;
793 auto xd = DualX{};
794 xd.resize(x.rows(), count);
795 for (Eigen::Index j = 0; j < count; ++j)
796 {
797 for (Eigen::Index r = 0; r < x.rows(); ++r)
798 {
799 xd(r, j) = Dual{x(r, j), Derivatives::Zero(w)};
800 }
801 }
802 Eigen::Index offset = 0;
803 [&]<std::size_t... B>(std::index_sequence<B...>) {
804 (
805 [&] {
806 using Name = std::tuple_element_t<B, Blocks>;
807 if (wanted[B])
808 {
809 BlockRows const values = inputValueBlockRows<Name>(in);
810 BlockRows const differences = inputBlockRows<Name>(in);
811 EMRYS_FILTER_ASSERT(
812 isLinear<Name> && values.size == differences.size
813 && "a block differentiated in a batch is linear, its values its differences"
814 );
815 for (Eigen::Index r = 0; r < values.size; ++r)
816 {
817 for (Eigen::Index j = 0; j < count; ++j)
818 {
819 xd(values.start + r, j).derivatives()(offset + r) = T(1);
820 }
821 }
822 offset += differences.size;
823 }
824 }(),
825 ...
826 );
827 }(std::make_index_sequence<K>{});
828 auto yd = DualY{};
829 yd.resize(y.rows(), count);
830 callBatched(batched, in, out, xd, yd, rest...);
831 using Deviation = MatrixOf<
832 Dual,
834 1,
836 1>;
837 for (Eigen::Index j = 0; j < count; ++j)
838 {
839 for (Eigen::Index r = 0; r < y.rows(); ++r)
840 {
841 y(r, j) = yd(r, j).value();
842 }
843 Deviation const deviation = [&] {
844 if constexpr (HasMinus<Out>)
845 {
846 return Deviation{Emrys::minus(out, yd.col(j), y.col(j).template cast<Dual>())};
847 }
848 else
849 {
850 return Deviation{yd.col(j)};
851 }
852 }();
853 for (Eigen::Index r = 0; r < m; ++r)
854 {
855 // An output that does not depend on the wanted blocks may carry no derivatives.
856 if (deviation(r).derivatives().size() > 0)
857 {
858 jacobians.block(r, j * w, 1, w) = deviation(r).derivatives().transpose();
859 }
860 else
861 {
862 jacobians.block(r, j * w, 1, w).setZero();
863 }
864 }
865 }
866 }
867}
868
869} // namespace internal
870
871} // namespace Emrys
Spaces built from other spaces (decisions.md [sizes-and-dimensions], [several-resizable-blocks]): Com...
How a function that stores a matrix argument takes it (decisions.md [forwarded-matrices]): the concep...
The macros every header includes first.
The transform methods, as policies made by their factories (decisions.md [policy-factories],...
The Space concept and what every continuous space shares (decisions.md [sizes-and-dimensions]): the t...
Tag paths and the index utilities (decisions.md [tag-addressing], [index-names]): TagPath and TagPath...
The output of a batched function (decisions.md [batched-functions]): its values, a column per point,...
Definition Batched.h:342
ValuesType & values()
Definition Batched.h:399
auto jacobian()
Definition Batched.h:482
BatchedOutput(ValuesType values, JacobiansType jacobians, In const &in, std::array< bool, BlockCount > const &wanted)
The output over the values values and the Jacobians' storage jacobians, of the function's input in,...
Definition Batched.h:374
bool wantsJacobians() const
Definition Batched.h:426
Eigen::Map< internal::MatrixOf< Scalar_, valueDimensionAtCompileTime< DifferenceSpace< Out > >, Eigen::Dynamic, maxValueDimensionAtCompileTime< DifferenceSpace< Out > >, Eigen::Dynamic > > JacobiansType
The Jacobians' storage: the blocks wanted side by side, for each column or once.
Definition Batched.h:362
ValuesType const & values() const
Definition Batched.h:406
BatchedOutput< In2, Out2, Scalar_, Mark > renamed(In2 const &in)
Definition Batched.h:499
Eigen::Map< internal::MatrixOf< Scalar_, valueDimensionAtCompileTime< Out >, Eigen::Dynamic, maxValueDimensionAtCompileTime< Out >, Eigen::Dynamic > > ValuesType
The values, a column per point.
Definition Batched.h:354
auto jacobian()
Definition Batched.h:450
bool wantsJacobian() const
Definition Batched.h:416
Scalar_ Scalar
the scalar type
Definition Batched.h:351
auto jacobian(Eigen::Index j)
Definition Batched.h:466
auto jacobian(Eigen::Index j)
Definition Batched.h:435
Whether T is a mark of the Jacobians a batched function writes, made by a factory in Emrys::jacobians...
Definition Batched.h:100
A dense matrix or matrix expression, possibly cv- or reference-qualified: what a const Eigen::MatrixB...
Definition Forwarding.h:39
A type usable as the space of a distribution.
Definition Space.h:186
constexpr Shared shared()
Definition Batched.h:63
constexpr PerColumn perColumn()
Definition Batched.h:53
Batched< std::remove_cvref_t< F > > batched(F &&function)
Definition Batched.h:248
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
auto minus(S const &space, Eigen::MatrixBase< ADerived > const &a, Eigen::MatrixBase< BDerived > const &b)
Definition Space.h:715
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
constexpr bool isLinear
Whether S is linear: its values and its differences are both plain vectors.
Definition Space.h:321
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
WithJacobian< internal::PlainResult< std::remove_cvref_t< Value > >, internal::PlainResult< std::remove_cvref_t< Jacobian > > > withJacobian(Value &&value, Jacobian &&jacobian)
Definition Methods.h:222
The marks of the Jacobians a batched function writes beside its values (decisions....
Definition Batched.h:33
A function that takes every point at once, as batched() marks it (decisions.md [batched-functions]): ...
Definition Batched.h:116
F FunctionType
the function
Definition Batched.h:120
static constexpr bool GivesJacobians
whether the function writes Jacobians
Definition Batched.h:125
static constexpr bool PerColumn
whether it writes one Jacobian per column
Definition Batched.h:128
Mark MarkType
the Jacobians it writes
Definition Batched.h:122
auto operator()(In const &in, Out const &out, X const &x, Rest const &... rest) const
Definition Batched.h:590
F function
the function
Definition Batched.h:130
The mark that a batched function writes one Jacobian per column, by block of its input: Y....
Definition Batched.h:40
The mark that a batched function writes one Jacobian for every column, by block of its input: Y....
Definition Batched.h:47