EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Mixture.h
Go to the documentation of this file.
1#pragma once
2
14
15#include <algorithm>
16#include <concepts>
17#include <expected>
18#include <functional>
19#include <iterator>
20#include <random>
21#include <span>
22#include <numeric>
23#include <optional>
24#include <ranges>
25#include <type_traits>
26#include <utility>
27#include <variant>
28#include <vector>
29
51
52namespace Emrys
53{
54
59{
60 Eigen::Index index;
62
64 friend bool
65 operator==(ComponentError const &, ComponentError const &) = default;
66};
67
71using MixtureError = std::variant<WeightError, ComponentError>;
72
77
78template <internal::ComponentOrInfoForm Component_, typename Allocator_>
79class Mixture;
80
81template <internal::ComponentOrInfoForm Component_, typename Allocator_>
82class MixtureParams;
83
84template <typename Source, typename Selection, typename SubSpace>
85class MixtureMarginal;
86
87namespace internal
88{
89struct MixtureLineageAccess;
90} // namespace internal
91
125template <RealScalar Scalar_, typename Allocator_ = DefaultAllocator>
126class MixtureLineage
127{
128public:
129
130 using Scalar = Scalar_;
131 using allocator_type = Allocator_;
132 using Index = Eigen::Index;
133
134 MixtureLineage() = default;
135
137 explicit MixtureLineage(Allocator_ const & allocator)
138 : m_logShares(ScalarAllocator(allocator))
139 {}
140
142 MixtureLineage(MixtureLineage const & other)
143 : MixtureLineage{other, other.get_allocator()}
144 {}
145
147 MixtureLineage(MixtureLineage const & other, Allocator_ const & allocator)
148 : m_logShares(other.m_logShares, ScalarAllocator(allocator))
149 , m_parentCount{other.m_parentCount}
150 , m_count{other.m_count}
151 {}
152
154 MixtureLineage(MixtureLineage && other) = default;
155
159 MixtureLineage(MixtureLineage && other, Allocator_ const & allocator)
160 : m_logShares(std::move(other.m_logShares), ScalarAllocator(allocator))
161 , m_parentCount{other.m_parentCount}
162 , m_count{other.m_count}
163 {}
164
167 operator=(MixtureLineage const & other) = default;
168
170 MixtureLineage &
171 operator=(MixtureLineage && other) = default;
172
173 ~MixtureLineage() = default;
174
176 [[nodiscard]] allocator_type
178 {
179 return allocator_type(m_logShares.get_allocator());
180 }
181
185 [[nodiscard]] Index
186 count() const
187 {
188 return m_count;
189 }
190
192 [[nodiscard]] Index
194 {
195 return m_parentCount;
196 }
197
199 [[nodiscard]] Index
200 size() const
201 {
202 return m_parentCount == 0 ? Index(0) : Index(m_logShares.size()) / m_parentCount;
203 }
204
206 [[nodiscard]] std::span<Scalar const>
208 {
209 EMRYS_FILTER_ASSERT(i >= 0 && i < size() && "the component has no row");
210 return {m_logShares.data() + i * m_parentCount, std::size_t(m_parentCount)};
211 }
212
214 [[nodiscard]] Scalar
215 logShare(Index i, Index parent) const
216 {
217 EMRYS_FILTER_ASSERT(parent >= 0 && parent < m_parentCount && "no such parent");
218 return logShares(i)[std::size_t(parent)];
219 }
220
228 template <typename Parents, typename Children>
229 void
230 carry(Eigen::MatrixBase<Parents> const & parents, Eigen::MatrixBase<Children> & children, Scalar orphan = 0) const
231 {
232 EMRYS_FILTER_ASSERT(
233 parents.size() == parentCount() && children.size() == size() && "one value per parent and per component"
234 );
235 Scalar const none = -Eigen::NumTraits<Scalar>::infinity();
236 for (Index i = 0; i < size(); ++i)
237 {
238 auto const shares = logShares(i);
239 Scalar const top = *std::ranges::max_element(shares);
240 if (top == none)
241 {
242 children(i) = orphan;
243 continue;
244 }
245 Scalar sum = 0;
246 Scalar total = 0;
247 for (Index parent = 0; parent < m_parentCount; ++parent)
248 {
249 Scalar const share = Eigen::numext::exp(Scalar(shares[std::size_t(parent)] - top));
250 sum += share * parents(parent);
251 total += share;
252 }
253 children(i) = sum / total;
254 }
255 }
256
257private:
258
260 template <internal::ComponentOrInfoForm, typename>
261 friend class MixtureParams;
262
263 friend struct internal::MixtureLineageAccess;
264
265 using ScalarAllocator = internal::ReBound<Allocator_, Scalar>;
266
268 [[nodiscard]] bool
269 tracking() const
270 {
271 return m_count > 0;
272 }
273
277 void
278 start(Index components, std::optional<Index> capacity)
279 {
280 ++m_count;
281 m_parentCount = components;
282 if (capacity)
283 {
284 m_logShares.reserve(std::size_t(*capacity * components));
285 }
286 m_logShares.assign(std::size_t(components * components), -Eigen::NumTraits<Scalar>::infinity());
287 for (Index i = 0; i < components; ++i)
288 {
289 m_logShares[std::size_t(i * components + i)] = Scalar(0);
290 }
291 }
292
294 void
295 reset()
296 {
297 m_logShares.clear();
298 m_parentCount = 0;
299 m_count = 0;
300 }
301
303 void
304 clearRows()
305 {
306 m_logShares.clear();
307 }
308
310 void
311 pushOrphan()
312 {
313 if (tracking())
314 {
315 m_logShares.resize(m_logShares.size() + std::size_t(m_parentCount), -Eigen::NumTraits<Scalar>::infinity());
316 }
317 }
318
320 void
321 push(std::span<Scalar const> row)
322 {
323 if (tracking())
324 {
325 EMRYS_FILTER_ASSERT(Index(row.size()) == m_parentCount && "the row must have a share per parent");
326 m_logShares.insert(m_logShares.end(), row.begin(), row.end());
327 }
328 }
329
331 void
332 pushCopyOf(Index i)
333 {
334 if (tracking())
335 {
336 std::size_t const end = m_logShares.size();
337 m_logShares.resize(end + std::size_t(m_parentCount));
338 std::copy_n(
339 m_logShares.begin() + i * m_parentCount,
340 m_parentCount,
341 m_logShares.begin() + std::ptrdiff_t(end)
342 );
343 }
344 }
345
347 void
348 moveRow(Index from, Index to)
349 {
350 if (tracking() && from != to)
351 {
352 std::copy_n(
353 m_logShares.begin() + from * m_parentCount,
354 m_parentCount,
355 m_logShares.begin() + to * m_parentCount
356 );
357 }
358 }
359
361 void
362 truncate(Index rows)
363 {
364 if (tracking())
365 {
366 m_logShares.resize(std::size_t(rows * m_parentCount));
367 }
368 }
369
371 void
372 removeAt(Index i)
373 {
374 if (tracking())
375 {
376 moveRow(size() - 1, i);
377 truncate(size() - 1);
378 }
379 }
380
384 void
385 merge(Index keep, Index absorb, Scalar logKeep, Scalar logAbsorb)
386 {
387 if (!tracking())
388 {
389 return;
390 }
391 auto total = LogSumExp<Scalar>{};
392 total.add(logKeep);
393 total.add(logAbsorb);
394 Scalar const logTotal = total.value();
395 if (!isFinite(logTotal))
396 {
397 return;
398 }
399 Scalar * const kept = m_logShares.data() + keep * m_parentCount;
400 Scalar const * const absorbed = m_logShares.data() + absorb * m_parentCount;
401 for (Index n = 0; n < m_parentCount; ++n)
402 {
403 auto share = LogSumExp<Scalar>{};
404 share.add(kept[n] + logKeep - logTotal);
405 share.add(absorbed[n] + logAbsorb - logTotal);
406 kept[n] = share.value();
407 }
408 }
409
411 void
412 swap(MixtureLineage & other)
413 {
414 m_logShares.swap(other.m_logShares);
415 std::swap(m_parentCount, other.m_parentCount);
416 std::swap(m_count, other.m_count);
417 }
418
419 internal::Buffer<Scalar, Allocator_> m_logShares;
420 Index m_parentCount = 0;
421 Index m_count = 0;
422};
423
424namespace internal
425{
426
430struct MixtureLineageAccess
431{
433 template <typename Scalar, typename ToAllocator, typename FromAllocator>
434 static void
435 assign(MixtureLineage<Scalar, ToAllocator> & to, MixtureLineage<Scalar, FromAllocator> const & from)
436 {
437 to.m_logShares.assign(from.m_logShares.begin(), from.m_logShares.end());
438 to.m_parentCount = from.m_parentCount;
439 to.m_count = from.m_count;
440 }
441
443 template <typename MixtureType>
444 static void
445 copy(MixtureType & to, MixtureType const & from)
446 {
447 to.m_lineage = from.m_lineage;
448 }
449
451 template <typename MixtureType>
452 static void
453 startFrom(MixtureType & to, MixtureType const & from)
454 {
455 EMRYS_FILTER_ASSERT(to.empty() && "the mixture must have no components");
456 to.m_lineage.m_logShares.clear();
457 to.m_lineage.m_parentCount = from.m_lineage.m_parentCount;
458 to.m_lineage.m_count = from.m_lineage.m_count;
459 }
460
462 template <typename MixtureType>
463 static void
464 inherit(MixtureType & to, MixtureType const & from, Eigen::Index i)
465 {
466 if (from.m_lineage.tracking())
467 {
468 to.m_lineage.truncate(to.size() - 1);
469 to.m_lineage.push(from.m_lineage.logShares(i));
470 }
471 }
472};
473
477template <typename Source, typename Target, typename Given, typename Derived, typename TargetSpace, typename GivenSpace>
478[[nodiscard]] auto
479conditionalMixtureIn(
480 Source const & source,
481 Target const & target,
482 Given const & given,
483 Eigen::MatrixBase<Derived> const & values,
484 TargetSpace const & targetSpace,
485 GivenSpace const & givenSpace
486)
487{
488 EMRYS_FILTER_ASSERT(distinctIndices(target.values, given.values) && "target and given indices must be distinct");
489 using Params = MixtureParams<Gaussian<TargetSpace, typename Source::Scalar>, typename Source::allocator_type>;
490 using Result = std::expected<Params, GaussianError>;
491 auto result = Params{targetSpace, source.get_allocator()};
492 result.reserve(source.size());
493 for (auto const & entry : source)
494 {
495 auto conditioned = conditionMomentsWithEvidence(
496 entry.component.mean(),
497 entry.component.covarianceLower(),
498 target,
499 given,
500 values,
501 givenSpace,
502 targetSpace
503 );
504 if (!conditioned)
505 {
506 return Result{std::unexpect, GaussianError::NotPositiveDefinite};
507 }
508 result.add(
509 entry.logWeight + conditioned->logLikelihood,
510 std::move(conditioned->moments.mean),
511 std::move(conditioned->moments.cov)
512 );
513 }
514 return Result{std::move(result)};
515}
516
518template <typename GivenPath, typename Source, typename Derived>
519[[nodiscard]] auto
520tagConditionalMixture(Source const & source, Eigen::MatrixBase<Derived> const & values)
521{
522 return tagConditional<GivenPath>(
523 source.space(),
524 [&](auto const & target, auto const & given, auto const & targetSpace, auto const & givenSpace) {
525 return conditionalMixtureIn(source, target, given, values, targetSpace, givenSpace);
526 }
527 );
528}
529
530} // namespace internal
531
577template <internal::ComponentOrInfoForm Component_, typename Allocator_ = DefaultAllocator>
578class MixtureParams
579{
580 static_assert(
582 "a Gaussian in an information form is not a mixture component, having no collapse; convert each "
583 "with toGaussian() or toSqrt() and hold a Gaussian or a SqrtGaussian (decisions.md [gaussian-forms])"
584 );
585
586public:
587
589 using SpaceType = typename ComponentType::SpaceType;
590 using allocator_type = Allocator_;
591 using MomentsType = typename ComponentType::MomentsType;
592 using Scalar = typename ComponentType::Scalar;
593 using Index = Eigen::Index;
594 using VectorType = typename MomentsType::VectorType;
595 using DifferenceVectorType = typename MomentsType::DifferenceVectorType;
596 using MatrixType = typename MomentsType::MatrixType;
599
601 static constexpr int ValueDimensionAtCompileTime = ComponentType::ValueDimensionAtCompileTime;
602
604 static constexpr int MaxValueDimensionAtCompileTime = ComponentType::MaxValueDimensionAtCompileTime;
605
607 static constexpr int DifferenceDimensionAtCompileTime = ComponentType::DifferenceDimensionAtCompileTime;
608
610 static constexpr int MaxDifferenceDimensionAtCompileTime = ComponentType::MaxDifferenceDimensionAtCompileTime;
611
612 static constexpr bool IsParams = true;
613
618 struct Entry : internal::AllocatorTypeOf<ComponentType>
619 {
622 : logWeight{weight}
623 , component{std::move(held)}
624 {}
625
627 template <typename A>
628 requires(std::is_constructible_v<ComponentType, ComponentType const &, A const &>)
629 Entry(Entry const & other, A const & allocator)
630 : logWeight{other.logWeight}
631 , component{other.component, allocator}
632 {}
633
635 template <typename A>
636 requires(std::is_constructible_v<ComponentType, ComponentType &&, A const &>)
637 Entry(Entry && other, A const & allocator)
638 : logWeight{other.logWeight}
639 , component{std::move(other.component), allocator}
640 {}
641
642 Entry(Entry const &) = default;
643 Entry(Entry &&) = default;
644
646 Entry &
647 operator=(Entry const &) = default;
648
650 Entry &
651 operator=(Entry &&) = default;
652
653 ~Entry() = default;
654
657 };
658
659 using const_iterator = typename internal::Buffer<Entry, Allocator_>::const_iterator;
660
664 template <bool ReturnRemovedWeight>
665 using RemovalResult = std::conditional_t<ReturnRemovedWeight, std::expected<Scalar, WeightError>, void>;
666
673 {
674 public:
675
676 MutableEntry(MutableEntry const &) = delete;
677
680 operator=(MutableEntry const &) = delete;
681
683 [[nodiscard]] Scalar
684 logWeight() const
685 {
686 return m_entry.logWeight;
687 }
688
690 [[nodiscard]] Index
691 index() const
692 {
693 return m_index;
694 }
695
697 [[nodiscard]] ComponentType const &
698 component() const
699 {
700 return m_entry.component;
701 }
702
706 template <typename C>
707 requires std::same_as<std::remove_cvref_t<C>, ComponentType>
708 void
710 {
711 EMRYS_FILTER_ASSERT(
712 component.space() == m_entry.component.space() && "the component must be in the mixture's space"
713 );
714 MixtureParams::assignComponent(m_entry, std::forward<C>(component));
715 }
716
720 template <typename F>
721 requires std::invocable<F &, ComponentType &>
722 decltype(auto)
724 {
725 return internal::keepingSpace(m_entry.component, f);
726 }
727
729 [[nodiscard]] MomentsType const &
730 moments() const
731 requires(isGaussian<ComponentType>)
732 {
733 return component().moments();
734 }
735
737 [[nodiscard]] VectorType const &
738 mean() const
739 requires(isGaussian<ComponentType>)
740 {
741 return component().mean();
742 }
743
745 [[nodiscard]] MatrixType const &
747 requires(isGaussian<ComponentType>)
748 {
749 return component().covarianceLower();
750 }
751
753 template <MatrixExpression Mean>
755 void
756 setMean(Mean && mean)
757 {
758 m_entry.component.setMean(std::forward<Mean>(mean));
759 }
760
762 template <MatrixExpression Cov>
764 void
765 setCovariance(Cov && cov)
766 {
767 m_entry.component.setCovariance(std::forward<Cov>(cov));
768 }
769
771 void
774 {
775 MixtureParams::assignComponent(m_entry, std::move(moments));
776 }
777
778 private:
779
780 friend MixtureParams;
781
783 MutableEntry(Entry & entry, Index index)
784 : m_entry{entry}
785 , m_index{index}
786 {}
787
788 Entry & m_entry;
789 Index m_index;
790 };
791
792 MixtureParams() = default;
793
795 explicit MixtureParams(Allocator_ const & allocator)
796 : m_entries(EntryAllocator(allocator))
797 , m_weightScratch(ScalarAllocator(allocator))
798 , m_lineage(allocator)
799 {}
800
804 explicit MixtureParams(Capacity capacity, Allocator_ const & allocator = {})
806 : MixtureParams{allocator}
807 {
808 setCapacity(capacity.count);
809 }
810
812 explicit MixtureParams(Index valueDimension, Allocator_ const & allocator = {})
813 requires internal::SizedByLength<SpaceType>
814 : MixtureParams{allocator}
815 {
816 fitSpace(valueDimension);
817 }
818
820 explicit MixtureParams(SpaceType space, Allocator_ const & allocator = {})
821 : MixtureParams{allocator}
822 {
823 m_space = std::move(space);
824 }
825
827 MixtureParams(SpaceType space, Capacity capacity, Allocator_ const & allocator = {})
828 : MixtureParams{std::move(space), allocator}
829 {
830 setCapacity(capacity.count);
831 }
832
837 MixtureParams(SpaceType space, Index valueDimension, Allocator_ const & allocator = {})
838 : MixtureParams{std::move(space), allocator}
839 {
840 fitSpace(valueDimension);
841 }
842
847 MixtureParams(MixtureParams const & other)
848 : MixtureParams{other, other.get_allocator()}
849 {}
850
854 MixtureParams(MixtureParams const & other, Allocator_ const & allocator)
855 : MixtureParams{allocator}
856 {
857 m_space = other.m_space;
858 if (other.m_capacity)
859 {
860 setCapacity(*(other.m_capacity));
861 }
862 m_entries.assign(other.m_entries.begin(), other.m_entries.end());
863 }
864
866 MixtureParams(MixtureParams && other) noexcept
867 : m_entries{std::move(other.m_entries)}
868 , m_space{std::move(other.m_space)}
869 , m_capacity{other.m_capacity}
870 , m_weightScratch{std::move(other.m_weightScratch)}
871 , m_lineage{std::move(other.m_lineage)}
872 {
873 other.m_stamp.touchShape();
874 }
875
879 MixtureParams(MixtureParams && other, Allocator_ const & allocator)
880 : m_entries{std::move(other.m_entries), EntryAllocator(allocator)}
881 , m_space{std::move(other.m_space)}
882 , m_capacity{other.m_capacity}
883 , m_weightScratch(ScalarAllocator(allocator))
884 , m_lineage{std::move(other.m_lineage), allocator}
885 {
886 other.m_stamp.touchShape();
887 }
888
894 operator=(MixtureParams const & other)
895 {
896 m_stamp.touchShape();
897 if (this == &other)
898 {
899 return *this;
900 }
901 assertCapacity(other.size());
902 m_entries.assign(other.m_entries.begin(), other.m_entries.end());
903 m_space = other.m_space;
904 m_lineage.reset();
905 return *this;
906 }
907
914 operator=(MixtureParams && other) noexcept
915 {
916 m_stamp.touchShape();
917 other.m_stamp.touchShape();
918 if (this == &other)
919 {
920 return *this;
921 }
922 if (std::allocator_traits<Allocator_>::propagate_on_container_move_assignment::value
923 || get_allocator() == other.get_allocator())
924 {
925 if (get_allocator() != other.get_allocator())
926 {
927 // The entries take other's allocator, so the scratch does too.
928 m_weightScratch = std::move(other.m_weightScratch);
929 }
930 m_entries = std::move(other.m_entries);
931 m_capacity = other.m_capacity;
932 m_lineage = std::move(other.m_lineage);
933 }
934 else
935 {
936 // Copied into this mixture's storage, which keeps its capacity.
937 assertCapacity(other.size());
938 m_entries.assign(
939 std::make_move_iterator(other.m_entries.begin()),
940 std::make_move_iterator(other.m_entries.end())
941 );
942 m_lineage = other.m_lineage;
943 }
944 m_space = std::move(other.m_space);
945 return *this;
946 }
947
949 [[nodiscard]] allocator_type
951 {
952 return allocator_type(m_entries.get_allocator());
953 }
954
956 [[nodiscard]] Index
957 size() const
958 {
959 return Index(m_entries.size());
960 }
961
963 [[nodiscard]] bool
964 empty() const
965 {
966 return m_entries.empty();
967 }
968
970 [[nodiscard]] std::optional<Index>
971 capacity() const
972 {
973 return m_capacity;
974 }
975
979 void
981 {
982 EMRYS_FILTER_ASSERT(capacity >= size() && "the capacity is smaller than the mixture");
983 m_capacity = capacity;
984 reserveScratch(capacity);
985 }
986
988 [[nodiscard]] Index
990 {
991 return Emrys::valueDimension(m_space);
992 }
993
995 [[nodiscard]] Index
997 {
998 return Emrys::differenceDimension(m_space);
999 }
1000
1002 [[nodiscard]] SpaceType const &
1003 space() const
1004 {
1005 return m_space;
1006 }
1007
1012 void
1014 requires(isGaussian<ComponentType> && internal::SizedByLength<SpaceType>)
1015 {
1016 m_stamp.touchShape();
1017 internal::fitValueDimension(m_space, n);
1018 for (auto & entry : m_entries)
1019 {
1020 entry.component.resize(n);
1021 }
1022 }
1023
1029 template <typename Path, typename MeanDerived, typename CovDerived, typename CrossDerived>
1031 void
1033 Index element,
1034 Eigen::MatrixBase<MeanDerived> const & mean,
1035 Eigen::MatrixBase<CovDerived> const & cov,
1036 Eigen::MatrixBase<CrossDerived> const & cross
1037 )
1038 {
1039 m_stamp.touchShape();
1040 static_cast<void>(internal::growBlock<Path>(m_space, element, mean.size()));
1041 for (auto & entry : m_entries)
1042 {
1043 entry.component.template insert<Path>(element, mean, cov, cross);
1044 }
1045 }
1046
1048 template <typename Path, typename MeanDerived, typename CovDerived>
1050 void
1051 insert(Index element, Eigen::MatrixBase<MeanDerived> const & mean, Eigen::MatrixBase<CovDerived> const & cov)
1052 {
1053 using Cross = internal::MatrixOf<
1054 Scalar,
1055 Eigen::Dynamic,
1056 Eigen::Dynamic,
1057 CovDerived::MaxRowsAtCompileTime,
1058 MomentsType::MatrixType::MaxColsAtCompileTime>;
1059 insert<Path>(element, mean, cov, Cross::Zero(cov.rows(), differenceDimension()));
1060 }
1061
1065 template <typename Path>
1067 void
1068 erase(Index element, Index count = 1)
1069 {
1070 m_stamp.touchShape();
1071 static_cast<void>(internal::shrinkBlock<Path>(m_space, element, count));
1072 for (auto & entry : m_entries)
1073 {
1074 entry.component.template erase<Path>(element, count);
1075 }
1076 }
1077
1079 void
1081 {
1082 m_stamp.touchValues();
1083 assertCapacity(n);
1084 m_entries.reserve(std::size_t(n));
1085 reserveScratch(n);
1086 }
1087
1089 void
1091 {
1092 m_stamp.touchShape();
1093 m_entries.clear();
1094 m_lineage.clearRows();
1095 }
1096
1102 void
1103 swap(MixtureParams & other)
1104 {
1105 m_stamp.touchShape();
1106 other.m_stamp.touchShape();
1107 EMRYS_FILTER_ASSERT(get_allocator() == other.get_allocator() && "the mixtures must have equal allocators");
1108 assertCapacity(other.size());
1109 other.assertCapacity(size());
1110 m_entries.swap(other.m_entries);
1111 std::swap(m_space, other.m_space);
1112 m_lineage.swap(other.m_lineage);
1113 }
1114
1118 [[nodiscard]] LineageType const &
1119 lineage() const
1120 {
1121 return m_lineage;
1122 }
1123
1129 void
1131 {
1132 m_stamp.touchValues();
1133 m_lineage.start(size(), m_capacity);
1134 }
1135
1137 [[nodiscard]] const_iterator
1138 begin() const
1139 {
1140 return m_entries.cbegin();
1141 }
1142
1144 [[nodiscard]] const_iterator
1145 end() const
1146 {
1147 return m_entries.cend();
1148 }
1149
1154 [[nodiscard]] Entry const &
1156 {
1157 assertIndex(i);
1158 return at(i);
1159 }
1160
1164 Index
1165 add(Scalar logWeight, ComponentType component)
1166 {
1167 m_stamp.touchShape();
1168 EMRYS_FILTER_ASSERT(component.space() == m_space && "the component must be in the mixture's space");
1169 assertCapacity(size() + 1);
1170 m_entries.push_back(Entry{logWeight, std::move(component)});
1171 m_lineage.pushOrphan();
1172 return size() - 1;
1173 }
1174
1176 Index
1177 add(Scalar logWeight, MomentsType moments)
1179 {
1180 EMRYS_FILTER_ASSERT(
1181 moments.valueDimension() == valueDimension() && moments.differenceDimension() == differenceDimension()
1182 );
1183 return add(logWeight, ComponentType{m_space, std::move(moments)});
1184 }
1185
1189 template <MatrixExpression Mean, MatrixExpression Cov>
1191 Index
1192 add(Scalar logWeight, Mean && mean, Cov && cov)
1193 {
1194 return add(logWeight, MomentsType{std::forward<Mean>(mean), std::forward<Cov>(cov)});
1195 }
1196
1198 void
1200 {
1201 assertIndex(i);
1202 m_stamp.touchShape();
1203 removeAt(i);
1204 }
1205
1207 void
1209 {
1210 EMRYS_FILTER_ASSERT(component.space() == m_space && "the component must be in the mixture's space");
1211 assignComponent(mutableAt(i), std::move(component));
1212 }
1213
1215 void
1218 {
1219 assignComponent(mutableAt(i), std::move(moments));
1220 }
1221
1223 template <MatrixExpression Mean>
1225 void
1226 setMean(Index i, Mean && mean)
1227 {
1228 mutableAt(i).component.setMean(std::forward<Mean>(mean));
1229 }
1230
1232 template <MatrixExpression Cov>
1234 void
1235 setCovariance(Index i, Cov && cov)
1236 {
1237 mutableAt(i).component.setCovariance(std::forward<Cov>(cov));
1238 }
1239
1241 void
1243 {
1244 mutableAt(i).logWeight = logWeight;
1245 }
1246
1248 void
1250 {
1251 mutableAt(i).logWeight += logScale;
1252 }
1253
1255 void
1257 {
1258 m_stamp.touchValues();
1259 for (auto & entry : m_entries)
1260 {
1261 entry.logWeight += logScale;
1262 }
1263 }
1264
1268 [[nodiscard]] std::expected<Scalar, WeightError>
1270 {
1271 return internal::logTotalWeight<Scalar>(m_entries | std::views::transform(&Entry::logWeight));
1272 }
1273
1278 [[nodiscard]] std::expected<void, WeightError>
1280 {
1281 m_stamp.touchValues();
1282 auto const total = logTotalWeight();
1283 if (!total)
1284 {
1285 return std::unexpected{total.error()};
1286 }
1287 scaleWeights(-*total);
1288 return {};
1289 }
1290
1297 [[nodiscard]] std::expected<Scalar, WeightError>
1298 ess() const
1299 {
1300 return logTotalWeight().transform([this](Scalar total) {
1301 return essGiven(total);
1302 });
1303 }
1304
1311 [[nodiscard]] Index
1313 {
1314 EMRYS_FILTER_ASSERT(!empty() && "the most probable component of an empty mixture");
1315 return Index(std::ranges::max_element(m_entries, std::ranges::less{}, &Entry::logWeight) - m_entries.begin());
1316 }
1317
1324 template <std::ranges::input_range R>
1325 void
1326 assign(R && components)
1327 {
1328 clear();
1329 if constexpr (std::ranges::sized_range<R>)
1330 {
1331 reserve(Index(std::ranges::size(components)));
1332 }
1333 for (auto && [logWeight, component] : components)
1334 {
1335 add(logWeight, component);
1336 }
1337 }
1338
1342 void
1343 append(MixtureParams const & other)
1344 {
1345 m_stamp.touchShape();
1346 EMRYS_FILTER_ASSERT(other.m_space == m_space && "cannot append a mixture in a different space");
1347 std::size_t const n = other.m_entries.size();
1348 assertCapacity(size() + Index(n));
1349 m_entries.reserve(m_entries.size() + n);
1350 for (std::size_t i = 0; i < n; ++i)
1351 {
1352 m_entries.push_back(other.m_entries[i]);
1353 m_lineage.pushOrphan();
1354 }
1355 }
1356
1362 template <typename F>
1363 void
1364 forEach(F && f)
1365 {
1366 forEach(execution::sequential, std::forward<F>(f));
1367 }
1368
1373 template <ExecutionPolicy P, typename F>
1374 void
1375 forEach(P && policy, F && f)
1376 {
1377 static_assert(
1378 std::is_void_v<std::invoke_result_t<F &, MutableEntry &>>,
1379 "forEach() takes a callback that returns nothing; one that returns a log scale factor goes to reweight()"
1380 );
1381 visitEntries(std::forward<P>(policy), [&](MutableEntry & handle, Entry &) {
1382 std::invoke(f, handle);
1383 });
1384 }
1385
1389 template <typename F>
1390 void
1391 reweight(F && f)
1392 {
1393 reweight(execution::sequential, std::forward<F>(f));
1394 }
1395
1397 template <ExecutionPolicy P, typename F>
1398 void
1399 reweight(P && policy, F && f)
1400 {
1401 static_assert(
1402 std::floating_point<std::remove_cvref_t<std::invoke_result_t<F &, MutableEntry &>>>,
1403 "reweight() takes a callback that returns a log scale factor, a floating-point number"
1404 );
1405 visitEntries(std::forward<P>(policy), [&](MutableEntry & handle, Entry & entry) {
1406 entry.logWeight += Scalar(std::invoke(f, handle));
1407 });
1408 }
1409
1418 Index
1419 merge(Index keep, Index absorb)
1420 {
1421 return merge(keep, absorb, merged(keep, absorb));
1422 }
1423
1432 [[nodiscard]] ComponentType
1433 merged(Index first, Index second) const
1434 {
1435 assertIndex(first);
1436 assertIndex(second);
1437 Index const pair[2] = {first, second};
1438 EMRYS_FILTER_ASSERT(pair[0] != pair[1] && "cannot merge a component with itself");
1440 Index(2),
1441 [&](Index i) -> ComponentType const & {
1442 return at(pair[i]).component;
1443 },
1444 [&](Index i) {
1445 return at(pair[i]).logWeight;
1446 }
1447 );
1448 }
1449
1457 Index
1458 merge(Index keep, Index absorb, ComponentType component)
1459 {
1460 return mergeInto(keep, absorb, std::move(component));
1461 }
1462
1471 Index
1472 merge(Index keep, Index absorb, MomentsType moments)
1474 {
1475 return mergeInto(keep, absorb, std::move(moments));
1476 }
1477
1486 template <std::ranges::forward_range R>
1487 void
1488 split(Index i, R && children)
1489 {
1490 assertIndex(i);
1491 m_stamp.touchShape();
1492 EMRYS_FILTER_ASSERT(!std::ranges::empty(children));
1493 auto relativeTotal = LogSumExp<Scalar>{};
1494 for ([[maybe_unused]] auto && [logRelativeWeight, child] : children)
1495 {
1496 relativeTotal.add(logRelativeWeight);
1497 }
1498 Scalar const logTotal = relativeTotal.value();
1499 EMRYS_FILTER_ASSERT(isFinite(logTotal) && "relative weights must have a finite, nonzero sum");
1500 Scalar const logBase = at(i).logWeight - logTotal;
1501 bool first = true;
1502 for (auto && [logRelativeWeight, child] : children)
1503 {
1504 if (first)
1505 {
1506 at(i).logWeight = logBase + logRelativeWeight;
1507 assignComponent(at(i), child);
1508 first = false;
1509 }
1510 else
1511 {
1512 add(logBase + logRelativeWeight, child);
1513 m_lineage.truncate(size() - 1);
1514 m_lineage.pushCopyOf(i);
1515 }
1516 }
1517 }
1518
1526 template <bool ReturnRemovedWeight = false, typename Pred>
1527 requires(!ReturnRemovedWeight)
1528 void
1529 removeIf(Pred pred)
1530 {
1531 removeEntriesIf<false>(std::move(pred));
1532 }
1533
1537 template <bool ReturnRemovedWeight, typename Pred>
1538 requires ReturnRemovedWeight
1539 [[nodiscard]] std::expected<Scalar, WeightError>
1540 removeIf(Pred pred)
1541 {
1542 return removeEntriesIf<true>(std::move(pred));
1543 }
1544
1557 [[nodiscard]] std::expected<Scalar, WeightError>
1558 pruneBelow(Scalar logThreshold)
1559 {
1560 return pruneWith(internal::PruneBelowRule<Scalar>{logThreshold});
1561 }
1562
1573 [[nodiscard]] std::expected<Scalar, WeightError>
1574 pruneTail(Scalar logFraction)
1575 {
1576 return pruneWith(internal::PruneTailRule<Scalar>{logFraction});
1577 }
1578
1588 [[nodiscard]] std::expected<Scalar, WeightError>
1590 {
1591 return pruneWith(internal::PruneToSizeRule{maxSize});
1592 }
1593
1601 template <typename Path>
1602 [[nodiscard]] auto
1603 marginal() const &
1604 {
1605 return internal::tagMarginal<MixtureMarginal, Path>(*this);
1606 }
1607
1609 template <typename Path>
1610 void
1611 marginal() const && = delete;
1612
1614 template <typename Path>
1615 [[nodiscard]] auto
1616 marginal(Index element) const &
1617 {
1618 return internal::elementMarginal<MixtureMarginal, Path>(*this, element);
1619 }
1620
1622 template <typename Path>
1623 void
1624 marginal(Index element) const && = delete;
1625
1639 template <typename GivenPath, typename Derived>
1641 [[nodiscard]] auto
1642 conditional(Eigen::MatrixBase<Derived> const & values) const
1643 {
1644 return internal::tagConditionalMixture<GivenPath>(*this, values);
1645 }
1646
1661 [[nodiscard]] std::expected<void, MixtureError>
1663 {
1664 return factorizeComponents(*this, internal::NoRepair{});
1665 }
1666
1679 template <typename R>
1680 [[nodiscard]] std::expected<void, MixtureError>
1681 factorize(R const & repair)
1682 {
1683 m_stamp.touchValues();
1684 return factorizeComponents(*this, repair);
1685 }
1686
1691 [[nodiscard]] std::expected<DensityType, MixtureError>
1692 toDensity() const &
1693 {
1694 if (auto result = factorize(); !result)
1695 {
1696 return std::unexpected{result.error()};
1697 }
1698 return DensityType{*this};
1699 }
1700
1702 void
1703 toDensity() const && = delete;
1704
1710 template <typename R>
1711 [[nodiscard]] std::expected<DensityType, MixtureError>
1712 toDensity(R const & repair) &
1713 {
1714 if (auto result = factorize(repair); !result)
1715 {
1716 return std::unexpected{result.error()};
1717 }
1718 return DensityType{*this};
1719 }
1720
1722 template <typename R>
1723 void
1724 toDensity(R const &) && = delete;
1725
1726private:
1727
1728 friend DensityType;
1729
1731 [[nodiscard]] Scalar
1732 essGiven(Scalar total) const
1733 {
1734 Scalar sumOfSquares = 0;
1735 for (auto const & entry : m_entries)
1736 {
1737 sumOfSquares += Eigen::numext::abs2(Eigen::numext::exp(entry.logWeight - total));
1738 }
1739 return Scalar(1) / sumOfSquares;
1740 }
1741
1743 template <typename>
1744 friend class internal::ValuesCheck;
1745
1747 template <typename>
1748 friend class internal::ShapeCheck;
1749
1750 friend struct internal::MixtureLineageAccess;
1751
1756 template <typename Self, typename R>
1757 [[nodiscard]] static std::expected<void, MixtureError>
1758 factorizeComponents(Self & self, R const & repair)
1759 {
1760 constexpr bool Repairs = !std::is_same_v<R, internal::NoRepair>;
1761 auto const factorizeOne = [&](auto & component) {
1762 if constexpr (Repairs)
1763 {
1764 return component.factorize(repair);
1765 }
1766 else
1767 {
1768 return component.factorize();
1769 }
1770 };
1771 auto const total = self.logTotalWeight();
1772 if (!total)
1773 {
1774 return std::unexpected{MixtureError{total.error()}};
1775 }
1776 if constexpr (Repairs)
1777 {
1778 // Every repair is tried before any is kept, on a copy of a component that needs one,
1779 // so that a component that can't be repaired leaves the mixture as it was. Repairs are
1780 // rare, so the copies are too.
1781 for (Index i = 0; i < self.size(); ++i)
1782 {
1783 auto const & component = std::as_const(self.at(i).component);
1784 if (!component.factorize())
1785 {
1786 auto trial = ComponentType{component};
1787 if (auto result = factorizeOne(trial); !result)
1788 {
1789 return std::unexpected{MixtureError{ComponentError{i, result.error()}}};
1790 }
1791 }
1792 }
1793 }
1794 for (Index i = 0; i < self.size(); ++i)
1795 {
1796 if (auto result = factorizeOne(self.at(i).component); !result)
1797 {
1798 return std::unexpected{MixtureError{ComponentError{i, result.error()}}};
1799 }
1800 }
1801 // Unchanged weights write nothing, so factorizing parameters that are already factorized
1802 // only reads, as toDensity() from several threads needs (decisions.md [execution-policies]).
1803 if (self.m_logTotalWeight != *total)
1804 {
1805 self.m_logTotalWeight = *total;
1806 }
1807 return {};
1808 }
1809
1810 using EntryAllocator = internal::ReBound<Allocator_, Entry>;
1811 using ScalarAllocator = internal::ReBound<Allocator_, Scalar>;
1812
1814 void
1815 fitSpace(Index n)
1816 {
1817 internal::fitValueDimension(m_space, n);
1818 }
1819
1821 static void
1822 assignComponent(Entry & entry, MomentsType const & moments)
1824 {
1825 entry.component.setMean(moments.mean());
1826 entry.component.setCovariance(moments.covarianceLower());
1827 }
1828
1830 static void
1831 assignComponent(Entry & entry, MomentsType && moments)
1833 {
1834 entry.component.setMean(std::move(moments.mean()));
1835 entry.component.setCovariance(std::move(moments.covarianceLower()));
1836 }
1837
1839 template <bool ReturnRemovedWeight, typename Pred>
1841 removeEntriesIf(Pred pred)
1842 {
1843 m_stamp.touchShape();
1844 auto const guard = internal::CallbackGuard{m_stamp};
1845 auto const removeAtIndex = [&](Index i) {
1846 bool remove = false;
1847 if constexpr (std::is_invocable_v<Pred &, Index, Entry const &>)
1848 {
1849 remove = static_cast<bool>(std::invoke(pred, i, std::as_const(at(i))));
1850 }
1851 else
1852 {
1853 remove = static_cast<bool>(std::invoke(pred, std::as_const(at(i))));
1854 }
1855 guard.check();
1856 return remove;
1857 };
1858 if constexpr (ReturnRemovedWeight)
1859 {
1860 auto const total = logTotalWeight();
1861 if (!total)
1862 {
1863 return std::unexpected{total.error()};
1864 }
1865 return compact(removeAtIndex) - *total;
1866 }
1867 else
1868 {
1869 compact(removeAtIndex);
1870 }
1871 }
1872
1876 template <ExecutionPolicy P, typename Visit>
1877 void
1878 visitEntries(P && policy, Visit const & visit)
1879 {
1880 m_stamp.touchValues();
1881 auto const guard = internal::CallbackGuard{m_stamp};
1882 internal::forEachIndex(std::forward<P>(policy), size(), [&](Index i) {
1883 auto & entry = m_entries[std::size_t(i)];
1884 auto handle = MutableEntry{entry, i};
1885 visit(handle, entry);
1886 guard.check();
1887 });
1888 }
1889
1891 static void
1892 assignComponent(Entry & entry, ComponentType const & component)
1893 {
1894 entry.component = component;
1895 }
1896
1898 static void
1899 assignComponent(Entry & entry, ComponentType && component)
1900 {
1901 entry.component = std::move(component);
1902 }
1903
1907 template <typename Replacement>
1908 Index
1909 mergeInto(Index k, Index a, Replacement && replacement)
1910 {
1911 assertIndex(k);
1912 assertIndex(a);
1913 m_stamp.touchShape();
1914 EMRYS_FILTER_ASSERT(k != a && "cannot merge a component with itself");
1915 auto weight = LogSumExp<Scalar>{};
1916 weight.add(at(k).logWeight);
1917 weight.add(at(a).logWeight);
1918 m_lineage.merge(k, a, at(k).logWeight, at(a).logWeight);
1919 at(k).logWeight = weight.value();
1920 assignComponent(at(k), std::forward<Replacement>(replacement));
1921 bool const keepWasLast = k == size() - 1;
1922 removeAt(a);
1923 return keepWasLast ? a : k;
1924 }
1925
1927 void
1928 assertCapacity([[maybe_unused]] Index count) const
1929 {
1930 EMRYS_FILTER_ASSERT(
1931 (!m_capacity || count <= *m_capacity) && "the mixture would hold more components than its capacity"
1932 );
1933 }
1934
1936 void
1937 reserveScratch(Index count)
1938 {
1939 auto const n = std::size_t(count);
1940 m_entries.reserve(n);
1941 m_weightScratch.reserve(n);
1942 }
1943
1945 void
1946 assertIndex([[maybe_unused]] Index i) const
1947 {
1948 EMRYS_FILTER_ASSERT(i >= 0 && i < size() && "component index out of range");
1949 }
1950
1952 [[nodiscard]] Entry &
1953 at(Index i)
1954 {
1955 return m_entries[std::size_t(i)];
1956 }
1957
1959 [[nodiscard]] Entry const &
1960 at(Index i) const
1961 {
1962 return m_entries[std::size_t(i)];
1963 }
1964
1966 [[nodiscard]] Entry &
1967 mutableAt(Index i)
1968 {
1969 assertIndex(i);
1970 m_stamp.touchValues();
1971 return at(i);
1972 }
1973
1975 void
1976 removeAt(Index i)
1977 {
1978 if (i != size() - 1)
1979 {
1980 at(i) = std::move(m_entries.back());
1981 }
1982 m_entries.pop_back();
1983 m_lineage.removeAt(i);
1984 }
1985
1993 template <typename Rule>
1994 [[nodiscard]] std::expected<Scalar, WeightError>
1995 pruneWith(Rule const & rule)
1996 {
1997 auto const total = logTotalWeight();
1998 if (!total)
1999 {
2000 return std::unexpected{total.error()};
2001 }
2002 m_stamp.touchShape();
2003 auto cut = rule(
2004 size(),
2005 [&](Index i) {
2006 return at(i).logWeight;
2007 },
2008 *total,
2009 m_weightScratch
2010 );
2011 Scalar const removed = compact([&](Index i) {
2012 return cut.removes(at(i).logWeight);
2013 });
2014 // The kept components include one of positive weight, so their total is finite.
2016 return removed - *total;
2017 }
2018
2023 template <typename RemoveAtIndex>
2024 Scalar
2025 compact(RemoveAtIndex removeAtIndex)
2026 {
2027 auto removed = LogSumExp<Scalar>{};
2028 Index out = 0;
2029 for (Index i = 0; i < size(); ++i)
2030 {
2031 if (removeAtIndex(i))
2032 {
2033 removed.add(at(i).logWeight);
2034 continue;
2035 }
2036 if (out != i)
2037 {
2038 at(out) = std::move(at(i));
2039 m_lineage.moveRow(i, out);
2040 }
2041 ++out;
2042 }
2043 m_entries.erase(m_entries.begin() + out, m_entries.end());
2044 m_lineage.truncate(out);
2045 return removed.value();
2046 }
2047
2048 internal::Buffer<Entry, Allocator_> m_entries;
2050 std::optional<Index> m_capacity;
2051
2052 // Scratch, kept between calls: the weights pruneTail() and pruneToSize() order.
2053 internal::Buffer<Scalar, Allocator_> m_weightScratch;
2054
2055 LineageType m_lineage;
2056
2057 // Filled by the const factorize() for use by views.
2058 mutable Scalar m_logTotalWeight{};
2059
2060 internal::Stamp m_stamp;
2061};
2062
2071template <Space Space_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator>
2073
2088template <internal::ComponentOrInfoForm Component_, typename Allocator_ = DefaultAllocator>
2089class Mixture
2090{
2091public:
2092
2096 using allocator_type = Allocator_;
2097 using Entry = typename ParamsType::Entry;
2100 using Scalar = typename ParamsType::Scalar;
2101 using Index = typename ParamsType::Index;
2105
2108
2111
2114
2117
2118 static constexpr bool IsParams = false;
2119
2121 [[nodiscard]] Index
2122 size() const
2123 {
2124 return source().size();
2125 }
2126
2128 [[nodiscard]] Index
2130 {
2131 return source().valueDimension();
2132 }
2133
2135 [[nodiscard]] Index
2137 {
2138 return source().differenceDimension();
2139 }
2140
2142 [[nodiscard]] SpaceType const &
2143 space() const
2144 {
2145 return source().space();
2146 }
2147
2149 [[nodiscard]] allocator_type
2151 {
2152 return source().get_allocator();
2153 }
2154
2156 [[nodiscard]] const_iterator
2157 begin() const
2158 {
2159 return source().begin();
2160 }
2161
2163 [[nodiscard]] const_iterator
2164 end() const
2165 {
2166 return source().end();
2167 }
2168
2170 [[nodiscard]] typename ParamsType::Entry const &
2172 {
2173 return source()[i];
2174 }
2175
2177 [[nodiscard]] Scalar
2179 {
2180 return source().m_logTotalWeight;
2181 }
2182
2186 [[nodiscard]] Scalar
2187 ess() const
2188 {
2189 return source().essGiven(logTotalWeight());
2190 }
2191
2197 [[nodiscard]] Index
2199 {
2200 return source().mostProbable();
2201 }
2202
2208 [[nodiscard]] ComponentType
2210 {
2212 size(),
2213 [&](Index i) -> ComponentType const & {
2214 return (*this)[i].component;
2215 },
2216 [&](Index i) {
2217 return (*this)[i].logWeight;
2218 }
2219 );
2220 }
2221
2231 [[nodiscard]] MomentsType
2232 moments() const
2233 {
2235 }
2236
2244 template <typename Derived>
2245 [[nodiscard]] Scalar
2246 logDensity(Eigen::MatrixBase<Derived> const & x) const
2247 {
2248 static_assert(Derived::IsVectorAtCompileTime, "logDensity() expects a vector");
2249 EMRYS_FILTER_ASSERT(x.size() == valueDimension());
2250 auto sum = LogSumExp<Scalar>{};
2251 for (auto const & entry : *this)
2252 {
2253 if (entry.logWeight == -Eigen::NumTraits<Scalar>::infinity())
2254 {
2255 continue;
2256 }
2257 sum.add(entry.logWeight + ComponentTraits<ComponentType>::logDensity(entry.component, x));
2258 }
2259 return sum.value() - logTotalWeight();
2260 }
2261
2268 template <typename Derived>
2269 [[nodiscard]] Scalar
2270 density(Eigen::MatrixBase<Derived> const & x) const
2271 {
2272 return Eigen::numext::exp(logDensity(x));
2273 }
2274
2276 template <typename Path>
2277 [[nodiscard]] auto
2278 marginal() const
2279 {
2280 return internal::tagMarginal<MixtureMarginal, Path>(*this);
2281 }
2282
2284 template <typename Path>
2285 [[nodiscard]] auto
2286 marginal(Index element) const
2287 {
2288 return internal::elementMarginal<MixtureMarginal, Path>(*this, element);
2289 }
2290
2292 template <typename GivenPath, typename Derived>
2294 [[nodiscard]] auto
2295 conditional(Eigen::MatrixBase<Derived> const & values) const
2296 {
2297 return internal::tagConditionalMixture<GivenPath>(*this, values);
2298 }
2299
2300private:
2301
2302 friend ParamsType;
2303
2307 explicit Mixture(ParamsType const & source)
2308 : m_check{source}
2309 {}
2310
2312 [[nodiscard]] ParamsType const &
2313 source() const
2314 {
2315 return m_check.get();
2316 }
2317
2318 internal::ValuesCheck<ParamsType> m_check;
2319};
2320
2329template <Space Space_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator>
2331
2332namespace internal
2333{
2334
2340template <typename Source>
2341struct ElementsOf;
2342
2346template <Component Component_, typename Allocator_>
2347struct ElementsOf<MixtureParams<Component_, Allocator_>>
2348{
2350 template <typename Source>
2351 [[nodiscard]] static auto
2352 logWeight(Source const & source, Eigen::Index i)
2353 {
2354 return source[i].logWeight;
2355 }
2356
2358 template <typename Source>
2359 [[nodiscard]] static auto const &
2360 component(Source const & source, Eigen::Index i)
2361 {
2362 return source[i].component;
2363 }
2364
2366 template <typename ComponentParams>
2367 using CopyType = MixtureParams<Density<ComponentParams>, Allocator_>;
2368
2372 template <typename Source, typename Marginal>
2373 [[nodiscard]] static auto
2374 copy(Source const &, Marginal const & marginal, Allocator_ const & allocator)
2375 {
2376 auto result = CopyType<typename Marginal::ComponentMarginalType::ParamsType>{marginal.space(), allocator};
2377 result.reserve(marginal.size());
2378 for (auto const & element : marginal)
2379 {
2380 result.add(element.logWeight, element.component.copy());
2381 }
2382 return result;
2383 }
2384};
2385
2387template <Component Component_, typename Allocator_>
2388struct ElementsOf<Mixture<Component_, Allocator_>> : ElementsOf<MixtureParams<Component_, Allocator_>>
2389{};
2390
2391} // namespace internal
2392
2421template <typename Source, typename Selection, typename SubSpace>
2423{
2424public:
2425
2426 using Scalar = typename Source::Scalar;
2427 using Index = Eigen::Index;
2428
2431
2434
2437
2441
2442 using SpaceType = SubSpace;
2443 using allocator_type = typename Source::allocator_type;
2444 using ComponentType = typename Source::ComponentType;
2445
2447 using ResolvedType = typename internal::SelectionHolder<Source, Selection, SubSpace>::ResolvedType;
2448
2450 using IndexVectorType = decltype(ResolvedType::indices);
2451
2453 using DifferenceIndexVectorType = decltype(ResolvedType::differenceIndices);
2454
2457 ComponentType>::template MarginalType<internal::SharedSelection<ResolvedType>, SubSpace>;
2458
2465
2466 using value_type = Element;
2467 using const_iterator = internal::ElementIterator<MixtureMarginal>;
2469
2472 typename internal::ElementsOf<Source>::template CopyType<typename ComponentMarginalType::ParamsType>;
2473
2475 MixtureMarginal(Source const & source, Selection const & selection, SubSpace space)
2476 requires(!internal::isSharedSelection<Selection>)
2477 : m_holder{source}
2478 , m_selection{source, selection, std::move(space)}
2479 {}
2480
2482 MixtureMarginal(Source const & source, Selection shared)
2483 requires(internal::isSharedSelection<Selection>)
2484 : m_holder{source}
2485 , m_selection{shared}
2486 {}
2487
2489 [[nodiscard]] SubSpace const &
2490 space() const
2491 {
2492 return m_selection.get().space;
2493 }
2494
2496 [[nodiscard]] allocator_type
2498 {
2499 return source().get_allocator();
2500 }
2501
2503 [[nodiscard]] Index
2504 size() const
2505 {
2506 return source().size();
2507 }
2508
2510 [[nodiscard]] Index
2512 {
2513 return indices().size();
2514 }
2515
2517 [[nodiscard]] Index
2519 {
2520 return differenceIndices().size();
2521 }
2522
2524 [[nodiscard]] IndexVectorType const &
2525 indices() const
2526 {
2527 return m_selection.get().indices;
2528 }
2529
2531 [[nodiscard]] DifferenceIndexVectorType const &
2533 {
2534 return m_selection.get().differenceIndices;
2535 }
2536
2538 [[nodiscard]] Element
2540 {
2541 EMRYS_FILTER_ASSERT(i >= 0 && i < size() && "component index out of range");
2542 Source const & from = source();
2543 return Element{
2544 Elements::logWeight(from, i),
2546 Elements::component(from, i),
2547 internal::SharedSelection<ResolvedType>{&m_selection.get()}
2548 }
2549 };
2550 }
2551
2553 [[nodiscard]] const_iterator
2554 begin() const
2555 {
2556 return const_iterator{*this, 0};
2557 }
2558
2560 [[nodiscard]] const_iterator
2561 end() const
2562 {
2563 return const_iterator{*this, size()};
2564 }
2565
2570 [[nodiscard]] ParamsType
2571 copy() const
2572 {
2573 return copy(get_allocator());
2574 }
2575
2577 [[nodiscard]] ParamsType
2578 copy(allocator_type const & allocator) const
2579 {
2580 return Elements::copy(source(), *this, allocator);
2581 }
2582
2583private:
2584
2585 using Elements = internal::ElementsOf<Source>;
2586
2588 [[nodiscard]] Source const &
2589 source() const
2590 {
2591 return m_holder.get();
2592 }
2593
2594 internal::HolderFor<Source> m_holder;
2595 internal::SelectionHolder<Source, Selection, SubSpace> m_selection;
2596};
2597
2611template <
2612 typename Scalar,
2613 typename Space,
2614 typename Allocator,
2615 std::uniform_random_bit_generator G,
2616 typename R = resamplers::Multinomial>
2618[[nodiscard]] SampleSet<Space, Scalar>
2620 Mixture<Gaussian<Space, Scalar>, Allocator> const & mixture,
2621 Eigen::Index n,
2622 G & rng,
2623 R const & resampler = {}
2624)
2625{
2626 EMRYS_FILTER_ASSERT(n >= 0);
2627 using SampleSetType = SampleSet<Space, Scalar>;
2628 auto weights = internal::bufferOf<Scalar>(mixture.get_allocator());
2629 weights.reserve(std::size_t(mixture.size()));
2630 for (auto const & entry : mixture)
2631 {
2632 weights.push_back(Eigen::numext::exp(entry.logWeight - mixture.logTotalWeight()));
2633 }
2634 auto ancestors = internal::bufferOf<Eigen::Index>(mixture.get_allocator());
2635 ancestors.resize(std::size_t(n));
2636 resampler(std::span<Scalar const>{weights}, std::span<Eigen::Index>{ancestors}, rng);
2637
2638 using Normals = SampleSet<DifferenceSpace<Space>, Scalar>;
2639 Normals const noise = internal::standardNormal<Normals>(mixture.differenceDimension(), n, rng);
2640 auto samples = SampleSetType{mixture.valueDimension(), n};
2641 for (Eigen::Index j = 0; j < n; ++j)
2642 {
2643 auto const gaussian = mixture[ancestors[std::size_t(j)]].component.toDensity();
2644 samples.col(j) = Emrys::plus(
2645 mixture.space(),
2646 gaussian->mean(),
2647 internal::multiply(internal::asTriangular<Eigen::Lower>(gaussian->llt().matrixLLT()), noise.col(j))
2648 );
2649 }
2650 return samples;
2651}
2652
2659template <typename Scalar, typename Space, typename Allocator, std::uniform_random_bit_generator G>
2660[[nodiscard]] typename Moments<Space, Scalar>::VectorType
2661drawSample(Mixture<Gaussian<Space, Scalar>, Allocator> const & mixture, G & rng)
2662{
2663 Eigen::Index const picked = internal::drawIndex<Scalar>(
2664 mixture.size(),
2665 [&](Eigen::Index k) {
2666 return Eigen::numext::exp(Scalar(mixture[k].logWeight - mixture.logTotalWeight()));
2667 },
2668 rng
2669 );
2670 auto const gaussian = mixture[picked].component.toDensity();
2671 return drawSample(*gaussian, rng);
2672}
2673
2684template <
2685 typename Scalar,
2686 typename Space,
2687 typename Allocator,
2688 std::uniform_random_bit_generator G,
2689 typename R = resamplers::Multinomial>
2690 requires Resampler<R, Scalar, G>
2691[[nodiscard]] SamplesWithLogDensity<Space, Scalar>
2693 Mixture<Gaussian<Space, Scalar>, Allocator> const & mixture,
2694 Eigen::Index n,
2695 G & rng,
2696 R const & resampler = {}
2697)
2698{
2699 return drawSamplesWithLogDensity(execution::sequential, mixture, n, rng, resampler);
2700}
2701
2707template <
2709 typename Scalar,
2710 typename Space,
2711 typename Allocator,
2712 std::uniform_random_bit_generator G,
2713 typename R = resamplers::Multinomial>
2717 P && policy,
2718 Mixture<Gaussian<Space, Scalar>, Allocator> const & mixture,
2719 Eigen::Index n,
2720 G & rng,
2721 R const & resampler = {}
2722)
2723{
2724 auto result = SamplesWithLogDensity<Space, Scalar>{drawSamples(mixture, n, rng, resampler), {}};
2725 result.logDensity.resize(n);
2726 internal::forEachIndex(std::forward<P>(policy), n, [&](Eigen::Index j) {
2727 result.logDensity(j) = mixture.logDensity(result.samples.col(j));
2728 });
2729 return result;
2730}
2731
2737template <typename Scalar, typename Space, typename Allocator, std::uniform_random_bit_generator G>
2739drawSampleWithLogDensity(Mixture<Gaussian<Space, Scalar>, Allocator> const & mixture, G & rng)
2740{
2741 auto result = SampleWithLogDensity<Space, Scalar>{drawSample(mixture, rng), 0};
2742 result.logDensity = mixture.logDensity(result.sample);
2743 return result;
2744}
2745
2746} // namespace Emrys
2747
2748#ifndef EMRYS_FILTER_NO_IO
2749
2750namespace Emrys
2751{
2752
2753namespace internal
2754{
2755
2760template <typename Component_, typename Allocator_>
2761void
2762describe(std::ostream & stream, MixtureParams<Component_, Allocator_> const & mixture, Indent indent)
2763{
2764 using Scalar = typename MixtureParams<Component_, Allocator_>::Scalar;
2765 stream << indent << "mixture of ";
2766 printCount(stream, mixture.size(), "component");
2767 printDimension(stream, mixture.valueDimension(), mixture.differenceDimension());
2768 auto const logTotal = mixture.logTotalWeight();
2769 if (logTotal)
2770 {
2771 Params<Component_> const collapsed = ComponentTraits<Params<Component_>>::collapse(
2772 mixture.size(),
2773 [&](Eigen::Index i) -> Params<Component_> const & {
2774 return mixture[i].component;
2775 },
2776 [&](Eigen::Index i) {
2777 return mixture[i].logWeight;
2778 }
2779 );
2780 printMoments(stream, indent.deeper(), ComponentTraits<Params<Component_>>::moments(collapsed));
2781 }
2782 else
2783 {
2784 stream << '\n';
2785 describe(stream, logTotal.error(), indent.deeper());
2786 }
2787 Eigen::Index i = 0;
2788 for (auto const & entry : mixture)
2789 {
2790 stream << '\n' << indent.deeper() << "component " << i << ", ";
2791 if (logTotal)
2792 {
2793 stream << "weight " << Eigen::numext::exp(Scalar(entry.logWeight - *logTotal));
2794 }
2795 else
2796 {
2797 stream << "log-weight " << entry.logWeight;
2798 }
2799 stream << ":\n";
2800 describe(stream, entry.component, indent.deeper().deeper());
2801 ++i;
2802 }
2803}
2804
2805} // namespace internal
2806
2807} // namespace Emrys
2808
2809#endif // EMRYS_FILTER_NO_IO
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
What a container of several Gaussians needs of each one (decisions.md [component-first]): the Compone...
Spaces built from other spaces (decisions.md [sizes-and-dimensions], [several-resizable-blocks]): Com...
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
How a function that stores a matrix argument takes it (decisions.md [forwarded-matrices]): the concep...
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
What marginals and conditionals are built from: resolving Eigen index sets against a space; the share...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
Moments: the mean and covariance that a Gaussian carries and that a mixture or a sample set is moment...
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
Printing: operator<< and std::formatter for every distribution, every filter (which prints its name a...
The resamplers: function objects that draw ancestors in proportion to weights, which ParticleSet::res...
SampleSet: a matrix whose columns are samples in a space, which the samplers return and the unscented...
Drawing samples: SamplesWithLogDensity and SampleWithLogDensity, the Gaussian draws drawSamples(),...
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...
What parameters and their views share (decisions.md [view-stamp], [params-and-density]): Stamp,...
Weighted statistics over points, all in internal, which the containers of weighted elements (Gaussian...
Subspaces, and growing and shrinking blocks (decisions.md [several-resizable-blocks]).
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
The density of a Gaussian whose covariance has been factorized: a read-only view.
Definition Gaussian.h:1011
Streaming log-sum-exp: accumulates without overflow.
Definition Weights.h:46
Scalar value() const
Definition Weights.h:133
How each component of a Mixture descends from the components it had at its last startLineage(): each ...
Definition Mixture.h:127
MixtureLineage(MixtureLineage const &other, Allocator_ const &allocator)
Copies other into storage from allocator.
Definition Mixture.h:147
Scalar_ Scalar
the scalar type
Definition Mixture.h:130
std::span< Scalar const > logShares(Index i) const
Definition Mixture.h:207
Index count() const
Definition Mixture.h:186
Allocator_ allocator_type
the allocator the shares come from
Definition Mixture.h:131
MixtureLineage(MixtureLineage const &other)
Copies the record into storage from other's allocator (see Allocation).
Definition Mixture.h:142
void carry(Eigen::MatrixBase< Parents > const &parents, Eigen::MatrixBase< Children > &children, Scalar orphan=0) const
Carries a value per parent to the components, as a statistic kept per hypothesis follows them through...
Definition Mixture.h:230
MixtureLineage(MixtureLineage &&other)=default
Takes other's record.
Eigen::Index Index
Eigen's index type.
Definition Mixture.h:132
Scalar logShare(Index i, Index parent) const
Definition Mixture.h:215
MixtureLineage(MixtureLineage &&other, Allocator_ const &allocator)
Moves other into storage from allocator, taking its storage when the allocators are equal.
Definition Mixture.h:159
MixtureLineage(Allocator_ const &allocator)
An empty record, with no mark, whose shares come from allocator.
Definition Mixture.h:137
MixtureLineage & operator=(MixtureLineage const &other)=default
Copies other's record into this one's storage.
Index size() const
Definition Mixture.h:200
Index parentCount() const
Definition Mixture.h:193
allocator_type get_allocator() const
Definition Mixture.h:177
friend class MixtureParams
Definition Mixture.h:261
MixtureLineage & operator=(MixtureLineage &&other)=default
Takes other's record, as std::vector's move assignment does.
Read-only view of a subset of the coordinates of every component of a mixture or a hybrid,...
Definition Mixture.h:2423
typename Type::Scalar Scalar
Definition Mixture.h:2426
typename internal::ElementsOf< Type >::template CopyType< typename ComponentMarginalType::ParamsType > ParamsType
Definition Mixture.h:2471
const_iterator end() const
Definition Mixture.h:2561
Index differenceDimension() const
Definition Mixture.h:2518
Element value_type
Definition Mixture.h:2466
DifferenceIndexVectorType const & differenceIndices() const
Definition Mixture.h:2532
static constexpr int MaxDifferenceDimensionAtCompileTime
Definition Mixture.h:2439
const_iterator begin() const
Definition Mixture.h:2554
SubSpace const & space() const
Definition Mixture.h:2490
IndexVectorType const & indices() const
Definition Mixture.h:2525
typename Type::ComponentType ComponentType
Definition Mixture.h:2444
MixtureMarginal(Source const &source, Selection const &selection, SubSpace space)
Selects selection's coordinates, which form space.
Definition Mixture.h:2475
allocator_type get_allocator() const
Definition Mixture.h:2497
const_iterator iterator
Definition Mixture.h:2468
decltype(ResolvedType::indices) IndexVectorType
Definition Mixture.h:2450
ParamsType copy() const
Definition Mixture.h:2571
Element operator[](Index i) const
Definition Mixture.h:2539
typename Type::allocator_type allocator_type
Definition Mixture.h:2443
typename ComponentTraits< ComponentType >::template MarginalType< internal::SharedSelection< ResolvedType >, SubSpace > ComponentMarginalType
Definition Mixture.h:2456
static constexpr int MaxValueDimensionAtCompileTime
Definition Mixture.h:2433
decltype(ResolvedType::differenceIndices) DifferenceIndexVectorType
Definition Mixture.h:2453
typename internal::SelectionHolder< Type, Selection, SubSpace >::ResolvedType ResolvedType
Definition Mixture.h:2447
static constexpr int DifferenceDimensionAtCompileTime
Definition Mixture.h:2436
SubSpace SpaceType
Definition Mixture.h:2442
static constexpr int ValueDimensionAtCompileTime
Definition Mixture.h:2430
Eigen::Index Index
Definition Mixture.h:2427
internal::ElementIterator< MixtureMarginal > const_iterator
Definition Mixture.h:2467
MixtureMarginal(Source const &source, Selection shared)
Selects the coordinates of source that shared resolved, borrowing them.
Definition Mixture.h:2482
ParamsType copy(allocator_type const &allocator) const
copy() with storage from allocator.
Definition Mixture.h:2578
Index valueDimension() const
Definition Mixture.h:2511
Index size() const
Definition Mixture.h:2504
Writable access to one entry during forEach().
Definition Mixture.h:673
MatrixType const & covarianceLower() const
Definition Mixture.h:746
MomentsType const & moments() const
Definition Mixture.h:730
void setComponent(C &&component)
Replaces the entry's component with component, which must be in the mixture's space; see MixtureParam...
Definition Mixture.h:709
VectorType const & mean() const
Definition Mixture.h:738
Index index() const
Definition Mixture.h:691
Scalar logWeight() const
Definition Mixture.h:684
void setMean(Mean &&mean)
Sets the component's mean; see GaussianParams::setMean().
Definition Mixture.h:756
decltype(auto) updateComponent(F &&f)
Definition Mixture.h:723
MutableEntry(MutableEntry const &)=delete
lives only inside the callback
void setMoments(MomentsType moments)
Sets the component's mean and covariance to moments.
Definition Mixture.h:772
MutableEntry & operator=(MutableEntry const &)=delete
Lives only inside the callback.
void setCovariance(Cov &&cov)
Sets the component's covariance; see GaussianParams::setCovariance().
Definition Mixture.h:765
ComponentType const & component() const
Definition Mixture.h:698
A weighted sum of components of one type, all in one space.
Definition Mixture.h:579
Mixture< Component_, Allocator_ > DensityType
the view toDensity() returns
Definition Mixture.h:597
void setLogWeight(Index i, Scalar logWeight)
Definition Mixture.h:1242
Index differenceDimension() const
Definition Mixture.h:996
void split(Index i, R &&children)
Definition Mixture.h:1488
Index add(Scalar logWeight, ComponentType component)
Definition Mixture.h:1165
MixtureLineage< Scalar, Allocator_ > LineageType
what lineage() returns
Definition Mixture.h:598
void setCovariance(Index i, Cov &&cov)
Definition Mixture.h:1235
void reweight(P &&policy, F &&f)
Definition Mixture.h:1399
ComponentType merged(Index first, Index second) const
Definition Mixture.h:1433
MixtureParams(Index valueDimension, Allocator_ const &allocator={})
Definition Mixture.h:812
void setCapacity(Index capacity)
Reserves room for capacity components, and the scratch for that many, and makes holding more a precon...
Definition Mixture.h:980
std::expected< Scalar, WeightError > pruneTail(Scalar logFraction)
Definition Mixture.h:1574
void setComponent(Index i, ComponentType component)
Definition Mixture.h:1208
typename ComponentType::MomentsType MomentsType
a mean and covariance together
Definition Mixture.h:591
typename internal::Buffer< Entry, Allocator_ >::const_iterator const_iterator
iterates the entries
Definition Mixture.h:659
std::expected< void, MixtureError > factorize() const
Definition Mixture.h:1662
void insert(Index element, Eigen::MatrixBase< MeanDerived > const &mean, Eigen::MatrixBase< CovDerived > const &cov)
Definition Mixture.h:1051
std::expected< Scalar, WeightError > logTotalWeight() const
Definition Mixture.h:1269
Eigen::Index Index
Eigen's index type.
Definition Mixture.h:593
std::expected< DensityType, MixtureError > toDensity() const &
Definition Mixture.h:1692
std::expected< Scalar, WeightError > pruneBelow(Scalar logThreshold)
Definition Mixture.h:1558
Params< Component_ > ComponentType
a component
Definition Mixture.h:588
void append(MixtureParams const &other)
Definition Mixture.h:1343
std::conditional_t< ReturnRemovedWeight, std::expected< Scalar, WeightError >, void > RemovalResult
Result of removeIf(): the log of the removed fraction of the total weight, or nothing when ReturnRemo...
Definition Mixture.h:665
void swap(MixtureParams &other)
Definition Mixture.h:1103
MixtureParams(MixtureParams &&other, Allocator_ const &allocator)
Definition Mixture.h:879
typename ComponentType::SpaceType SpaceType
the components' space
Definition Mixture.h:589
void assign(R &&components)
Definition Mixture.h:1326
Index merge(Index keep, Index absorb)
Definition Mixture.h:1419
MixtureParams(SpaceType space, Capacity capacity, Allocator_ const &allocator={})
Definition Mixture.h:827
Entry const & operator[](Index i) const
Definition Mixture.h:1155
Index merge(Index keep, Index absorb, MomentsType moments)
Definition Mixture.h:1472
std::expected< void, MixtureError > factorize(R const &repair)
Definition Mixture.h:1681
MixtureParams(MixtureParams &&other) noexcept
Definition Mixture.h:866
typename ComponentType::Scalar Scalar
the scalar type
Definition Mixture.h:592
void resizeComponents(Index n)
Definition Mixture.h:1013
void setMean(Index i, Mean &&mean)
Definition Mixture.h:1226
MixtureParams(Allocator_ const &allocator)
Definition Mixture.h:795
Index merge(Index keep, Index absorb, ComponentType component)
Definition Mixture.h:1458
LineageType const & lineage() const
Definition Mixture.h:1119
Index add(Scalar logWeight, MomentsType moments)
Definition Mixture.h:1177
void forEach(P &&policy, F &&f)
Definition Mixture.h:1375
void setMoments(Index i, MomentsType moments)
Definition Mixture.h:1216
void insert(Index element, Eigen::MatrixBase< MeanDerived > const &mean, Eigen::MatrixBase< CovDerived > const &cov, Eigen::MatrixBase< CrossDerived > const &cross)
Definition Mixture.h:1032
typename MomentsType::DifferenceVectorType DifferenceVectorType
a difference
Definition Mixture.h:595
MixtureParams(MixtureParams const &other, Allocator_ const &allocator)
Definition Mixture.h:854
MixtureParams & operator=(MixtureParams &&other) noexcept
Definition Mixture.h:914
std::expected< void, WeightError > normalize()
Definition Mixture.h:1279
MixtureParams(MixtureParams const &other)
Definition Mixture.h:847
std::expected< Scalar, WeightError > ess() const
Definition Mixture.h:1298
std::expected< Scalar, WeightError > pruneToSize(Index maxSize)
Definition Mixture.h:1589
MixtureParams(SpaceType space, Index valueDimension, Allocator_ const &allocator={})
Definition Mixture.h:837
auto conditional(Eigen::MatrixBase< Derived > const &values) const
Definition Mixture.h:1642
typename MomentsType::MatrixType MatrixType
a matrix over differences
Definition Mixture.h:596
const_iterator begin() const
Definition Mixture.h:1138
MixtureParams(Capacity capacity, Allocator_ const &allocator={})
Definition Mixture.h:804
void erase(Index element, Index count=1)
Definition Mixture.h:1068
MixtureParams(SpaceType space, Allocator_ const &allocator={})
Definition Mixture.h:820
std::expected< Scalar, WeightError > removeIf(Pred pred)
Definition Mixture.h:1540
void scaleWeight(Index i, Scalar logScale)
Definition Mixture.h:1249
const_iterator end() const
Definition Mixture.h:1145
Allocator_ allocator_type
the allocator, re-bound per buffer
Definition Mixture.h:590
MixtureParams & operator=(MixtureParams const &other)
Definition Mixture.h:894
Index add(Scalar logWeight, Mean &&mean, Cov &&cov)
Definition Mixture.h:1192
typename MomentsType::VectorType VectorType
a value, such as a mean
Definition Mixture.h:594
void removeIf(Pred pred)
Definition Mixture.h:1529
void scaleWeights(Scalar logScale)
Definition Mixture.h:1256
The density of a Mixture whose components have been factorized: a read-only view.
Definition Mixture.h:2090
typename ParamsType::MatrixType MatrixType
Definition Mixture.h:2104
const_iterator end() const
Definition Mixture.h:2164
typename ParamsType::Scalar Scalar
Definition Mixture.h:2100
Scalar logTotalWeight() const
Definition Mixture.h:2178
Index mostProbable() const
Definition Mixture.h:2198
typename ParamsType::VectorType VectorType
Definition Mixture.h:2102
MomentsType moments() const
Definition Mixture.h:2232
typename ParamsType::Index Index
Definition Mixture.h:2101
allocator_type get_allocator() const
Definition Mixture.h:2150
Index size() const
Definition Mixture.h:2122
static constexpr int MaxValueDimensionAtCompileTime
Definition Mixture.h:2110
static constexpr bool IsParams
Definition Mixture.h:2118
Scalar density(Eigen::MatrixBase< Derived > const &x) const
Definition Mixture.h:2270
SpaceType const & space() const
Definition Mixture.h:2143
auto marginal(Index element) const
marginal() of one repetition of a repeated block; see GaussianParams::marginal(element).
Definition Mixture.h:2286
static constexpr int MaxDifferenceDimensionAtCompileTime
Definition Mixture.h:2116
Index differenceDimension() const
Definition Mixture.h:2136
typename ParamsType::Entry Entry
Definition Mixture.h:2097
static constexpr int DifferenceDimensionAtCompileTime
Definition Mixture.h:2113
auto conditional(Eigen::MatrixBase< Derived > const &values) const
Definition Mixture.h:2295
static constexpr int ValueDimensionAtCompileTime
Definition Mixture.h:2107
Index valueDimension() const
Definition Mixture.h:2129
ParamsType::Entry const & operator[](Index i) const
Definition Mixture.h:2171
Allocator_ allocator_type
Definition Mixture.h:2096
typename ParamsType::const_iterator const_iterator
Definition Mixture.h:2098
typename ParamsType::MomentsType MomentsType
Definition Mixture.h:2099
const_iterator begin() const
Definition Mixture.h:2157
ComponentType collapsed() const
Definition Mixture.h:2209
typename ParamsType::SpaceType SpaceType
Definition Mixture.h:2095
MixtureParams< Component_, Allocator_ > ParamsType
Definition Mixture.h:2093
Scalar logDensity(Eigen::MatrixBase< Derived > const &x) const
Definition Mixture.h:2246
Scalar ess() const
Definition Mixture.h:2187
Params< Component_ > ComponentType
Definition Mixture.h:2094
auto marginal() const
marginal() of the subspace named by the tag path Path; see MixtureParams::marginal().
Definition Mixture.h:2278
typename ParamsType::DifferenceVectorType DifferenceVectorType
Definition Mixture.h:2103
ValueVector< Space_, Scalar > VectorType
a value, such as the mean
Definition Moments.h:74
The distribution that a container of several holds one of per element (decisions.md [component-first]...
Definition Component.h:299
An execution policy: execution::sequential, a policy of the standard library such as std::execution::...
Definition Execution.h:459
A space whose instance holds nothing, so that S{} is the only one there is: a space of fixed size,...
Definition Space.h:217
Requirements on a resampler for scalar type Scalar and random generator G.
Definition Resampling.h:61
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
bool isFinite(T const &x)
Definition Math.h:305
NumericError
Reasons a computation from parameters the caller gave can fail: a matrix that does not factorize or a...
Definition NumericError.h:30
@ NotPositiveDefinite
the covariance is not positive definite, so it has no Cholesky factor or inverse
Definition NumericError.h:31
Mixture< Gaussian< Space_, Scalar_ >, Allocator_ > GaussianMixture
The Mixture of a GaussianMixtureParams, the spelling for the common case (decisions....
Definition Mixture.h:2330
SampleWithLogDensity< Space, Scalar > drawSampleWithLogDensity(Gamma< Space, Scalar > const &gamma, G &rng)
Definition Gamma.h:546
Eigen::Matrix< Scalar, valueDimensionAtCompileTime< Space >, Eigen::Dynamic,(maxValueDimensionAtCompileTime< Space >==1 ? Eigen::RowMajor :Eigen::ColMajor), maxValueDimensionAtCompileTime< Space >, Eigen::Dynamic > SampleSet
A matrix whose columns are samples in Space (see Spaces), with the space's size and storage bound.
Definition SampleSet.h:33
MixtureParams< Gaussian< Space_, Scalar_ >, Allocator_ > GaussianMixtureParams
A Mixture of Gaussians in covariance form, the spelling for the common case (decisions....
Definition Mixture.h:2072
MixtureError GaussianMixtureError
MixtureError by the name it had when every mixture was a GaussianMixtureParams.
Definition Mixture.h:76
Eigen::Index drawSample(Categorical< Space, Scalar > const &distribution, G &rng)
Definition Categorical.h:1111
constexpr bool isGaussian
Whether C is a Gaussian in covariance form: the component for which a container offers the convenienc...
Definition Component.h:111
std::variant< WeightError, ComponentError > MixtureError
Reasons MixtureParams::toDensity() can fail.
Definition Mixture.h:71
std::vector< Eigen::Index > drawSamples(Categorical< Space, Scalar > const &distribution, Eigen::Index n, G &rng)
Definition Categorical.h:1087
SamplesWithLogDensity< Space, Scalar > drawSamplesWithLogDensity(Gamma< Space, Scalar > const &gamma, Eigen::Index n, G &rng)
Definition Gamma.h:529
typename ParamsOf< D >::type Params
The parameters of the density D: GaussianParams for a Gaussian, and D itself for a distribution whose...
Definition Params.h:42
constexpr Sequential sequential
The default execution policy: plain loops on the calling thread, in index order (Sequential).
Definition Execution.h:128
constexpr Keep keep()
Definition Reduction.h:371
GivenBlock< Space, P, Value > given(Space const &space, Value const &value)
Definition Measurement.h:917
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
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
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
The most elements a container will hold, reserved once: a mixture's components or a particle set's pa...
Definition Allocation.h:54
A mixture component that could not be factorized.
Definition Mixture.h:59
Eigen::Index index
position of the component in the mixture
Definition Mixture.h:60
NumericError error
why factorization failed: a Gaussian's covariance, or a Hybrid's mode weights
Definition Mixture.h:61
friend bool operator==(ComponentError const &, ComponentError const &)=default
Equal when the position and the error are.
What a container does with a component beyond the component's own members (decisions....
Definition Component.h:104
One component's log-weight, as in the source, and its marginal.
Definition Mixture.h:2461
Scalar logWeight
the component's log-weight
Definition Mixture.h:2462
ComponentMarginalType component
the component's marginal
Definition Mixture.h:2463
One weighted component.
Definition Mixture.h:619
Entry & operator=(Entry &&)=default
Moves the weight and the component.
Entry(Scalar weight, ComponentType held)
The component held with log-weight weight.
Definition Mixture.h:621
Entry & operator=(Entry const &)=default
Copies into the component's storage.
ComponentType component
the component, in the mixture's space
Definition Mixture.h:656
Entry(Entry &&other, A const &allocator)
Moves other with its component into storage from allocator.
Definition Mixture.h:637
Scalar logWeight
the log of the component's (unnormalized) weight
Definition Mixture.h:655
Entry(Entry const &other, A const &allocator)
Copies other with its component in storage from allocator.
Definition Mixture.h:629
Entry(Entry &&)=default
moves the weight and the component
Entry(Entry const &)=default
copies the weight and the component
One sample in Space, in a vector sized by the space, together with the log of the density it was draw...
Definition Sampling.h:50
Scalar logDensity
the log-density at sample
Definition Sampling.h:52
Samples in Space together with the log of the density they were drawn from, evaluated at each.
Definition Sampling.h:36
Draws each ancestor independently.
Definition Resampling.h:219