EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
SwitchingModel.h
Go to the documentation of this file.
1#pragma once
2
12
13#include <algorithm>
14#include <array>
15#include <cstddef>
16#include <tuple>
17#include <type_traits>
18#include <utility>
19
28
29namespace Emrys
30{
31
48template <typename Model_, typename Method_>
50{
51 static_assert(
53 "a fixed-step model carries its own method; give the method to fixedStep() in place of withMethod()"
54 );
55 static_assert(internal::checkPredictionMethod<Method_>());
56
57public:
58
59 using ModelType = Model_;
60 using MethodType = Method_;
61
63 ModelWithMethod(Model_ model, Method_ method)
64 : m_model{std::move(model)}
65 , m_method{std::move(method)}
66 {}
67
69 [[nodiscard]] Model_ const &
70 model() const
71 {
72 return m_model;
73 }
74
76 [[nodiscard]] Model_ &
78 {
79 return m_model;
80 }
81
83 [[nodiscard]] Method_ const &
84 method() const
85 {
86 return m_method;
87 }
88
89private:
90
91 Model_ m_model;
92 EMRYS_FILTER_NO_UNIQUE_ADDRESS Method_ m_method;
93};
94
95namespace internal
96{
97
99template <typename Model>
100inline constexpr bool isModelWithMethod = false;
101
103template <typename Model_, typename Method_>
104inline constexpr bool isModelWithMethod<ModelWithMethod<Model_, Method_>> = true;
105
107
111template <typename Model>
112struct ModeModelOf
113{
114 using type = Model;
115};
116
118template <typename Model_, typename Method_>
119struct ModeModelOf<ModelWithMethod<Model_, Method_>>
120{
121 using type = Model_;
122};
123
125template <typename Model>
126using ModeModel = typename ModeModelOf<Model>::type;
127
129template <typename Entry>
130[[nodiscard]] auto &
131modeModel(Entry & entry)
132{
133 if constexpr (isModelWithMethod<std::remove_const_t<Entry>>)
134 {
135 return entry.model();
136 }
137 else
138 {
139 return entry;
140 }
141}
142
146template <typename Entry, typename Fallback>
147[[nodiscard]] auto const &
148modeMethod(Entry const & entry, Fallback const & fallback)
149{
150 if constexpr (isModelWithMethod<Entry> || isFixedStep<Entry>)
151 {
152 return entry.method();
153 }
154 else
155 {
156 return fallback;
157 }
158}
159
160} // namespace internal
161
167template <typename Model, TransformMethod Method>
169withMethod(Model model, Method method)
170{
171 return {std::move(model), std::move(method)};
172}
173
212template <DiscreteSpace ModeSpace_, RealScalar Scalar_, typename Path_, typename... Models>
214{
215 static_assert(sizeof...(Models) > 0, "a switching model needs a process model");
216
217 static_assert(
220 "a switching model's process models are all fixed-step or none are"
221 );
222
223public:
224
225 using Scalar = Scalar_;
226 using ModeSpaceType = ModeSpace_;
228
233 static constexpr bool Shared = sizeof...(Models) == 1 && std::is_same_v<Path_, ModeSpace_>;
234
238 SwitchingModel(ChainType chain, Models... models)
239 : m_chain{std::move(chain)}
240 , m_models{std::move(models)...}
241 {
242 if constexpr ((isFixedStep<Models> && ...))
243 {
244 EMRYS_FILTER_ASSERT(
245 std::apply(
246 [](auto const & first, auto const &... rest) {
247 return ((rest.timeStep() == first.timeStep()) && ...);
248 },
249 m_models
250 )
251 && "a switching model's fixed-step models must share one step"
252 );
253 }
254 if constexpr (!Shared)
255 {
256 using Axis = internal::PathTag<Path_>;
257 static_assert(
258 Axis::Cardinality == Eigen::Dynamic || Axis::Cardinality == int(sizeof...(Models)),
259 "give one process model per value of the axis that selects it, "
260 "or no path for one model shared by every mode"
261 );
262 EMRYS_FILTER_ASSERT(
263 selector().cardinality == Eigen::Index(sizeof...(Models))
264 && "give one process model per value of the axis that selects it, "
265 "or no path for one model shared by every mode"
266 );
267 }
268 }
269
274 template <typename... Others>
275 requires(
276 sizeof...(Others) == sizeof...(Models) && !(std::is_same_v<Others, Models> && ...)
277 && (std::is_convertible_v<Others const &, Models> && ...)
278 )
280 : SwitchingModel{other, std::index_sequence_for<Models...>{}}
281 {}
282
284 [[nodiscard]] ChainType const &
285 chain() const
286 {
287 return m_chain;
288 }
289
291 [[nodiscard]] ChainType &
293 {
294 return m_chain;
295 }
296
298 [[nodiscard]] ModeSpace_ const &
299 modeSpace() const
300 {
301 return m_chain.space();
302 }
303
305 template <std::size_t I>
306 [[nodiscard]] auto const &
307 model() const
308 {
309 return internal::modeModel(std::get<I>(m_models));
310 }
311
313 template <std::size_t I>
314 [[nodiscard]] auto &
316 {
317 return internal::modeModel(std::get<I>(m_models));
318 }
319
321 [[nodiscard]] std::size_t
322 modelIndex(Eigen::Index mode) const
323 {
324 EMRYS_FILTER_ASSERT(mode >= 0 && mode < modeSpace().cardinality() && "the mode is out of range");
325 if constexpr (Shared)
326 {
327 return 0;
328 }
329 else
330 {
331 auto const location = selector();
332 return std::size_t((mode / location.stride) % location.cardinality);
333 }
334 }
335
337 template <typename F>
338 decltype(auto)
339 visit(Eigen::Index mode, F && f) const
340 {
341 return visitEntry(mode, [&](auto const & entry) -> decltype(auto) {
342 return f(internal::modeModel(entry));
343 });
344 }
345
350 template <typename Fallback, typename F>
351 decltype(auto)
352 visit(Eigen::Index mode, Fallback const & fallback, F && f) const
353 {
354 return visitEntry(mode, [&](auto const & entry) -> decltype(auto) {
355 return f(internal::modeModel(entry), internal::modeMethod(entry, fallback));
356 });
357 }
358
362 template <typename Space>
363 [[nodiscard]] static consteval internal::StaticPartition
365 {
366 using internal::StaticPartition;
367 constexpr auto each = std::array<StaticPartition, sizeof...(Models)>{
368 internal::staticPartition<internal::ModeModel<Models>, Space>()...
369 };
370 if (std::ranges::find(each, StaticPartition::fails) != each.end())
371 {
372 return StaticPartition::fails;
373 }
374 return std::ranges::all_of(
375 each,
376 [](StaticPartition p) {
377 return p == StaticPartition::holds;
378 }
379 )
380 ? StaticPartition::holds
381 : StaticPartition::unknown;
382 }
383
385 template <typename Space>
386 [[nodiscard]] bool
387 partitions(Space const & space) const
388 {
389 return std::apply(
390 [&](auto const &... models) {
391 return (internal::modeModel(models).partitions(space) && ...);
392 },
393 m_models
394 );
395 }
396
397private:
398
402 template <typename F>
403 decltype(auto)
404 visitEntry(Eigen::Index mode, F const & f) const
405 {
406 std::size_t const index = modelIndex(mode);
407 return [&]<std::size_t... I>(std::index_sequence<I...>) -> decltype(auto) {
408 using Result = decltype(f(std::get<0>(m_models)));
409 if constexpr (std::is_void_v<Result>)
410 {
411 ((I == index ? (void)f(std::get<I>(m_models)) : void()), ...);
412 }
413 else
414 {
415 auto result = Result{};
416 ((I == index ? (void)(result = f(std::get<I>(m_models))) : void()), ...);
417 return result;
418 }
419 }(std::index_sequence_for<Models...>{});
420 }
421
423 template <typename Other, std::size_t... I>
424 SwitchingModel(Other const & other, std::index_sequence<I...>)
425 : SwitchingModel{other.chain(), Models(other.template model<I>())...}
426 {}
427
429 struct Selector
430 {
431 Eigen::Index stride;
432 Eigen::Index cardinality;
433 };
434
436 [[nodiscard]] Selector
437 selector() const
438 {
439 auto const [stride, axis] = internal::locateAxisByPath<Path_>(modeSpace());
440 return {stride, axis->cardinality()};
441 }
442
443 ChainType m_chain;
444 std::tuple<Models...> m_models;
445};
446
450template <typename ModeSpace_, typename Scalar_, typename Path_, typename... Models>
451inline constexpr bool isFixedStep<SwitchingModel<ModeSpace_, Scalar_, Path_, Models...>>{(isFixedStep<Models> && ...)};
452
453namespace internal
454{
455
460template <typename ModeSpace_, typename Scalar_, typename Path_, typename First, typename... Rest>
461struct DefaultMethodOf<SwitchingModel<ModeSpace_, Scalar_, Path_, First, Rest...>>
462{
464 using type = std::conditional_t<isFixedStep<First> || isUnboundStep<First>, ModelsMethod, methods::Linearized>;
465};
466
470template <typename ModeSpace_, typename Scalar_, typename Path_, typename... Models>
471[[nodiscard]] ModelsMethod
472predictionMethod(SwitchingModel<ModeSpace_, Scalar_, Path_, Models...> const &, ModelsMethod method)
473{
474 return method;
475}
476
478template <typename ModeSpace_, typename Scalar_, typename Path_, typename... Models, typename Space, typename Scalar>
479struct BoundStepOf<SwitchingModel<ModeSpace_, Scalar_, Path_, Models...>, Space, Scalar>
480{
481 using type = SwitchingModel<ModeSpace_, Scalar_, Path_, BoundStep<Models, Space, Scalar>...>;
482};
483
488template <typename ModeSpace_, typename Scalar_, typename Path_, typename... Models, typename Method>
489inline constexpr bool predictsBy<SwitchingModel<ModeSpace_, Scalar_, Path_, Models...>, Method>{
490 !isFixedStep<SwitchingModel<ModeSpace_, Scalar_, Path_, Models...>> || std::is_same_v<Method, ModelsMethod>
491};
492
493} // namespace internal
494
496template <typename ModeSpace, typename Scalar, typename... Models>
497SwitchingModel(MarkovChain<ModeSpace, Scalar>, Models...) -> SwitchingModel<ModeSpace, Scalar, ModeSpace, Models...>;
498
500
508template <typename P, typename ModeSpace, typename Scalar, typename... Models>
509[[nodiscard]] SwitchingModel<ModeSpace, Scalar, P, Models...>
511{
512 return {std::move(chain), std::move(models)...};
513}
514
540template <typename Path_, Measurement... Measurements>
542{
543 static_assert(sizeof...(Measurements) > 0, "a switching measurement needs a measurement");
544
545public:
546
547 using PathType = Path_;
548
549 static constexpr std::size_t Count = sizeof...(Measurements);
550
552 template <std::size_t I>
553 using MeasurementType = std::tuple_element_t<I, std::tuple<Measurements...>>;
554
556 explicit SwitchingMeasurement(Measurements... measurements)
557 : m_measurements{std::move(measurements)...}
558 {
559 using Axis = internal::PathTag<Path_>;
560 static_assert(
561 Axis::Cardinality == Eigen::Dynamic || Axis::Cardinality == int(sizeof...(Measurements)),
562 "give one measurement per value of the axis that selects it"
563 );
564 }
565
567 template <std::size_t I>
568 [[nodiscard]] MeasurementType<I> const &
570 {
571 return std::get<I>(m_measurements);
572 }
573
577 template <typename F>
578 decltype(auto)
579 visit(std::size_t value, F && f) const
580 {
581 EMRYS_FILTER_ASSERT(value < Count && "the switching measurement has no measurement for this value of the axis");
582 return visitFrom<0>(value, f);
583 }
584
585private:
586
588 template <std::size_t I, typename F>
589 decltype(auto)
590 visitFrom(std::size_t value, F & f) const
591 {
592 if constexpr (I + 1 == Count)
593 {
594 return f(std::get<I>(m_measurements));
595 }
596 else
597 {
598 if (value == I)
599 {
600 return f(std::get<I>(m_measurements));
601 }
602 return visitFrom<I + 1>(value, f);
603 }
604 }
605
606 std::tuple<Measurements...> m_measurements;
607};
608
616template <typename P, Measurement... Measurements>
617[[nodiscard]] SwitchingMeasurement<P, Measurements...>
618switchingMeasurement(Measurements... measurements)
619{
620 return SwitchingMeasurement<P, Measurements...>{std::move(measurements)...};
621}
622
623namespace internal
624{
625
627template <typename T>
628inline constexpr bool isSwitchingMeasurement = false;
629
631template <typename Path, typename... Measurements>
632inline constexpr bool isSwitchingMeasurement<SwitchingMeasurement<Path, Measurements...>> = true;
633
635
636} // namespace internal
637
638} // namespace Emrys
The distribution over a discrete space (decisions.md [discrete-spaces], [params-and-density]): Catego...
Discrete spaces (decisions.md [discrete-spaces]): the DiscreteSpace concept; Finite,...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
Measurement models and their observations (decisions.md [model-policies], [sensors],...
Process models as functions with reads and writes (decisions.md [model-policies], [continuous-time-no...
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]).
A Markov chain on a discrete space: the probability of moving from each value to each value in one st...
Definition Categorical.h:829
A process model and the prediction method that one mode of a SwitchingModel predicts it by,...
Definition SwitchingModel.h:50
Model_ const & model() const
Definition SwitchingModel.h:70
Model_ ModelType
the process model
Definition SwitchingModel.h:59
ModelWithMethod(Model_ model, Method_ method)
model, predicted by method.
Definition SwitchingModel.h:63
Method_ const & method() const
Definition SwitchingModel.h:84
Method_ MethodType
the prediction method
Definition SwitchingModel.h:60
Model_ & model()
Definition SwitchingModel.h:77
A measurement that switches between modes: one Measurement per value of a mode axis,...
Definition SwitchingModel.h:542
decltype(auto) visit(std::size_t value, F &&f) const
Definition SwitchingModel.h:579
static constexpr std::size_t Count
the number of measurements
Definition SwitchingModel.h:549
SwitchingMeasurement(Measurements... measurements)
measurements, one per value of the axis Path_, in order.
Definition SwitchingModel.h:556
std::tuple_element_t< I, std::tuple< Measurements... > > MeasurementType
Measurement I, for value I of the axis.
Definition SwitchingModel.h:553
Path_ PathType
the mode axis that selects the measurement
Definition SwitchingModel.h:547
MeasurementType< I > const & measurement() const
Definition SwitchingModel.h:569
A process model that switches between modes: a Markov chain on a discrete mode space,...
Definition SwitchingModel.h:214
SwitchingModel(SwitchingModel< ModeSpace_, Scalar_, Path_, Others... > const &other)
other with each of its models converted to this one's: how a filter binds a switching model of fixedS...
Definition SwitchingModel.h:279
decltype(auto) visit(Eigen::Index mode, F &&f) const
Definition SwitchingModel.h:339
MarkovChain< ModeSpace_, Scalar_ > ChainType
the chain's type
Definition SwitchingModel.h:227
ModeSpace_ ModeSpaceType
the discrete mode space
Definition SwitchingModel.h:226
auto & model()
Definition SwitchingModel.h:315
std::size_t modelIndex(Eigen::Index mode) const
Definition SwitchingModel.h:322
bool partitions(Space const &space) const
Definition SwitchingModel.h:387
auto const & model() const
Definition SwitchingModel.h:307
decltype(auto) visit(Eigen::Index mode, Fallback const &fallback, F &&f) const
Definition SwitchingModel.h:352
SwitchingModel(ChainType chain, Models... models)
The chain chain on the modes, and models, one per value of the axis Path_ (one shared by every mode i...
Definition SwitchingModel.h:238
static consteval internal::StaticPartition staticPartition()
Definition SwitchingModel.h:364
ChainType & chain()
Definition SwitchingModel.h:292
ModeSpace_ const & modeSpace() const
Definition SwitchingModel.h:299
Scalar_ Scalar
the scalar type of the chain's probabilities
Definition SwitchingModel.h:225
A type usable as the space of a distribution.
Definition Space.h:186
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
ModelWithMethod< Model, Method > withMethod(Model model, Method method)
Definition SwitchingModel.h:169
constexpr bool isFixedStep
Whether Model is a FixedStepModel, whose predictions take no time step.
Definition ProcessModel.h:2776
SwitchingMeasurement< P, Measurements... > switchingMeasurement(Measurements... measurements)
Definition SwitchingModel.h:618
SwitchingModel< ModeSpace, Scalar, P, Models... > switchingModel(MarkovChain< ModeSpace, Scalar > chain, Models... models)
Definition SwitchingModel.h:510
constexpr bool isUnboundStep
Whether Model is a fixed step not yet bound to a state space: an UnboundFixedStep,...
Definition ProcessModel.h:2792