EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Upkeep.h
Go to the documentation of this file.
1#pragma once
2
9
10#include <concepts>
11#include <cstddef>
12#include <expected>
13#include <optional>
14#include <random>
15#include <tuple>
16#include <type_traits>
17#include <utility>
18#include <variant>
19
33
34namespace Emrys
35{
36
149
150namespace internal
151{
152
156template <typename Actions, typename State>
157[[nodiscard]] std::expected<bool, WeightError>
158runActions(Actions const & actions, State & state)
159{
160 return std::apply(
161 [&](auto const &... action) {
162 auto changed = std::expected<bool, WeightError>{false};
163 auto const run = [&](auto const & next) {
164 if (!changed)
165 {
166 return;
167 }
168 std::expected<bool, WeightError> const result = next(state);
169 if (!result)
170 {
171 changed = std::unexpected{result.error()};
172 }
173 else
174 {
175 changed = *changed || *result;
176 }
177 };
178 (run(action), ...);
179 return changed;
180 },
181 actions
182 );
183}
184
188template <typename Removed>
189[[nodiscard]] std::expected<bool, WeightError>
190prunedAny(Removed const & removed)
191{
192 if constexpr (isExpected<Removed>)
193 {
194 using Scalar = typename Removed::value_type;
195 return removed.transform([](Scalar logRemoved) {
196 return logRemoved > -Eigen::NumTraits<Scalar>::infinity();
197 });
198 }
199 else
200 {
201 return removed > -Eigen::NumTraits<Removed>::infinity();
202 }
203}
204
208template <typename Result>
209[[nodiscard]] std::expected<void, NumericError>
210factorizeOutcome(Result const & result)
211{
212 if (result)
213 {
214 return {};
215 }
216 if constexpr (std::same_as<typename Result::error_type, NumericError>)
217 {
218 return std::unexpected{result.error()};
219 }
220 else
221 {
222 return std::unexpected{std::visit(
223 [](auto const & error) {
224 if constexpr (std::same_as<std::remove_cvref_t<decltype(error)>, NumericError>)
225 {
226 return error;
227 }
228 else
229 {
230 return NumericError(error.error);
231 }
232 },
233 result.error()
234 )};
235 }
236}
237
241template <typename Test, typename... Args>
242concept UpkeepTest = std::predicate<Test const &, Args...>
243 || (std::invocable<Test const &, Args...>
244 && std::same_as<std::invoke_result_t<Test const &, Args...>, std::expected<bool, WeightError>>);
245
247template <typename Result>
248[[nodiscard]] std::expected<bool, WeightError>
249testOutcome(Result const & result)
250{
251 if constexpr (std::same_as<Result, std::expected<bool, WeightError>>)
252 {
253 return result;
254 }
255 else
256 {
257 return bool(result);
258 }
259}
260
265template <typename Compare, RealScalar Scalar_, ExecutionPolicy Policy_ = execution::Sequential>
266 requires std::predicate<Compare const &, Scalar_, Scalar_>
267struct EssTest
268{
269 EMRYS_FILTER_NO_UNIQUE_ADDRESS Compare compare;
270 Scalar_ fraction;
271 EMRYS_FILTER_NO_UNIQUE_ADDRESS Policy_ policy{};
272
274 template <typename State>
275 requires requires(State const & state) {
276 state.ess();
277 state.size();
278 }
279 [[nodiscard]] std::expected<bool, WeightError>
280 operator()(State const & state) const
281 {
282 if constexpr (!internal::isSequential<Policy_> && requires { state.ess(policy); })
283 {
284 return holds(state.ess(policy), state.size());
285 }
286 else
287 {
288 return holds(state.ess(), state.size());
289 }
290 }
291
292private:
293
297 template <typename Ess, typename Index>
298 [[nodiscard]] std::expected<bool, WeightError>
299 holds(Ess const & ess, Index size) const
300 {
301 if (!ess)
302 {
303 return std::unexpected{ess.error()};
304 }
305 using Scalar = typename Ess::value_type;
306 return bool(compare(*ess, Scalar(Scalar(fraction) * scalarFromCount<Scalar>(size))));
307 }
308};
309
313template <typename Compare>
314 requires std::predicate<Compare const &, std::ptrdiff_t, std::ptrdiff_t>
315struct SizeTest
316{
317 EMRYS_FILTER_NO_UNIQUE_ADDRESS Compare compare;
318 std::ptrdiff_t limit;
319
321 template <typename State>
322 requires requires(State const & state) { state.size(); }
323 [[nodiscard]] bool
324 operator()(State const & state) const
325 {
326 return bool(compare(std::ptrdiff_t(state.size()), limit));
327 }
328};
329
334template <typename State, typename G, typename R, typename P>
335concept ResamplableBy = requires(State & state, G & rng, Eigen::Index newSize, R const & resampler, P const & policy) {
336 state.size();
337 requires(!isSequential<P>) || requires { state.resample(rng, newSize, resampler); };
338 requires isSequential<P> || requires { state.resample(policy, rng, newSize, resampler); };
339};
340
342struct Always
343{
345 template <typename State>
346 [[nodiscard]] constexpr bool
347 operator()(State const &) const
348 {
349 return true;
350 }
351};
352
353} // namespace internal
354
367template <typename T, typename State>
368concept UpkeepOperation = requires(T const & operation, State & state) {
369 { operation(state) } -> std::same_as<std::expected<bool, NumericError>>;
370};
371
379namespace upkeep
380{
381
397template <
398 std::uniform_random_bit_generator G,
399 typename Resampler_ = resamplers::Systematic,
402{
403public:
404
408 constexpr explicit Resample(
409 G & generator,
410 Resampler_ resampler = {},
411 std::optional<Eigen::Index> size = std::nullopt,
412 Policy_ policy = {}
413 )
414 : generator_{&generator}
415 , resampler_{std::move(resampler)}
416 , size_{size}
417 , policy_{std::move(policy)}
418 {}
419
421 template <typename State>
422 requires internal::ResamplableBy<State, G, Resampler_, Policy_>
423 [[nodiscard]] std::expected<bool, WeightError>
424 operator()(State & state) const
425 {
426 auto const resampled = [&] {
427 if constexpr (internal::isSequential<Policy_>)
428 {
429 return state.resample(*generator_, newSize(state), resampler_);
430 }
431 else
432 {
433 return state.resample(policy_, *generator_, newSize(state), resampler_);
434 }
435 }();
436 if (!resampled)
437 {
438 return std::unexpected{resampled.error()};
439 }
440 return true;
441 }
442
444 [[nodiscard]] constexpr G &
445 generator() const noexcept
446 {
447 return *generator_;
448 }
449
451 [[nodiscard]] constexpr Resampler_ const &
452 resampler() const noexcept
453 {
454 return resampler_;
455 }
456
458 [[nodiscard]] constexpr std::optional<Eigen::Index>
459 size() const noexcept
460 {
461 return size_;
462 }
463
465 [[nodiscard]] constexpr Policy_ const &
466 policy() const noexcept
467 {
468 return policy_;
469 }
470
471private:
472
474 template <typename State>
475 [[nodiscard]] Eigen::Index
476 newSize(State const & state) const
477 {
478 return size_ ? *size_ : Eigen::Index(state.size());
479 }
480
481 G * generator_;
482 EMRYS_FILTER_NO_UNIQUE_ADDRESS Resampler_ resampler_;
483 std::optional<Eigen::Index> size_;
484 EMRYS_FILTER_NO_UNIQUE_ADDRESS Policy_ policy_;
485};
486
491template <std::uniform_random_bit_generator G, typename R = resamplers::Systematic>
492 requires(!std::integral<R>)
493[[nodiscard]] constexpr Resample<G, R>
494resample(G & generator, R resampler = {})
495{
496 return Resample<G, R>{generator, std::move(resampler)};
497}
498
503template <std::uniform_random_bit_generator G, typename R = resamplers::Systematic>
504 requires(!std::integral<R>)
505[[nodiscard]] constexpr Resample<G, R>
506resample(G & generator, Eigen::Index size, R resampler = {})
507{
508 EMRYS_FILTER_ASSERT(size > 0 && "a resample needs a positive size");
509 return Resample<G, R>{generator, std::move(resampler), size};
510}
511
516template <ExecutionPolicy P, std::uniform_random_bit_generator G, typename R = resamplers::Systematic>
517 requires(!std::integral<R>)
518[[nodiscard]] constexpr Resample<G, R, std::remove_cvref_t<P>>
519resample(P && policy, G & generator, R resampler = {})
520{
521 return Resample<G, R, std::remove_cvref_t<P>>{
522 generator,
523 std::move(resampler),
524 std::nullopt,
525 std::forward<P>(policy)
526 };
527}
528
533template <ExecutionPolicy P, std::uniform_random_bit_generator G, typename R = resamplers::Systematic>
534 requires(!std::integral<R>)
535[[nodiscard]] constexpr Resample<G, R, std::remove_cvref_t<P>>
536resample(P && policy, G & generator, Eigen::Index size, R resampler = {})
537{
538 EMRYS_FILTER_ASSERT(size > 0 && "a resample needs a positive size");
539 return Resample<G, R, std::remove_cvref_t<P>>{generator, std::move(resampler), size, std::forward<P>(policy)};
540}
541
568template <
569 std::uniform_random_bit_generator G,
570 RealScalar Scalar_ = double,
571 ExecutionPolicy Policy_ = execution::Sequential>
573{
574public:
575
577 constexpr explicit Roughen(G & generator, Scalar_ scale = 1, Policy_ policy = {})
578 : generator_{&generator}
579 , scale_{scale}
580 , policy_{std::move(policy)}
581 {}
582
586 template <typename Space, typename Scalar, typename Allocator>
587 [[nodiscard]] std::expected<bool, WeightError>
589 {
591 using Vector = typename Set::VectorType;
592 using Difference = typename Set::DifferenceVectorType;
593 static_assert(std::floating_point<Scalar>, "sampling requires a standard floating-point scalar");
594 EMRYS_FILTER_ASSERT(scale_ >= Scalar_(0) && "the kernel scale must not be negative");
595 G & rng = *generator_;
596 if (scale_ == Scalar_(0))
597 {
598 return particles.logTotalWeight(policy_).transform([](Scalar) {
599 return false;
600 });
601 }
602 auto const bandwidth = particles.kdeBandwidth(policy_);
603 if (!bandwidth)
604 {
605 return std::unexpected{bandwidth.error()};
606 }
607 auto const kernel = internal::defaultRoot((Scalar(scale_) * *bandwidth).eval());
608 auto const & space = particles.space();
609 Eigen::Index const rows = particles.differenceDimension();
610 if constexpr (internal::isSequential<Policy_>)
611 {
612 // One distribution for every draw, which may keep a value between them, so that the draws
613 // are those of filling all the particles' normals at once, as another policy does.
614 auto normal = std::normal_distribution<Scalar>{};
615 auto z = Difference{};
616 z.resize(rows);
617 for (Eigen::Index i = 0; i < particles.size(); ++i)
618 {
619 internal::fillStandardNormal(z, rng, normal);
620 Vector const moved = Emrys::plus(space, particles.state(i), Difference{internal::multiply(kernel, z)});
621 particles.setState(i, moved);
622 }
623 }
624 else
625 {
626 using Normals = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
627 auto draws = internal::scratchVector<Scalar>(policy_, std::size_t(rows * particles.size()));
628 auto normals = Eigen::Map<Normals>{draws.data(), rows, particles.size()};
629 internal::fillStandardNormal(normals, rng);
630 particles.forEach(policy_, [&](auto & particle) {
631 Vector const moved =
632 Emrys::plus(space, particle.state(), Difference{kernel * normals.col(particle.index())});
633 particle.setState(moved);
634 });
635 }
636 return true;
637 }
638
644 template <typename State>
645 requires requires(State & state) { state.setParticleStates(state.particles().states()); }
646 [[nodiscard]] std::expected<bool, WeightError>
647 operator()(State & state) const
648 {
649 auto particles = state.particles();
650 std::expected<bool, WeightError> const roughened = (*this)(particles);
651 if (roughened && *roughened)
652 {
653 state.setParticleStates(particles.states());
654 }
655 return roughened;
656 }
657
659 [[nodiscard]] constexpr G &
660 generator() const noexcept
661 {
662 return *generator_;
663 }
664
666 [[nodiscard]] constexpr Scalar_
667 scale() const noexcept
668 {
669 return scale_;
670 }
671
673 [[nodiscard]] constexpr Policy_ const &
674 policy() const noexcept
675 {
676 return policy_;
677 }
678
679private:
680
681 G * generator_;
682 Scalar_ scale_;
683 EMRYS_FILTER_NO_UNIQUE_ADDRESS Policy_ policy_;
684};
685
691template <std::uniform_random_bit_generator G, typename Value = double>
692 requires std::is_arithmetic_v<Value>
693[[nodiscard]] constexpr Roughen<G, internal::DeducedScalar<Value>>
694roughen(G & generator, Value scale = 1)
695{
696 return Roughen<G, internal::DeducedScalar<Value>>{generator, internal::DeducedScalar<Value>(scale)};
697}
698
702template <ExecutionPolicy P, std::uniform_random_bit_generator G, typename Value = double>
703 requires std::is_arithmetic_v<Value>
704[[nodiscard]] constexpr Roughen<G, internal::DeducedScalar<Value>, std::remove_cvref_t<P>>
705roughen(P && policy, G & generator, Value scale = 1)
706{
707 return Roughen<G, internal::DeducedScalar<Value>, std::remove_cvref_t<P>>{
708 generator,
709 internal::DeducedScalar<Value>(scale),
710 std::forward<P>(policy)
711 };
712}
713
728template <typename Predicate, typename... Actions>
729struct When
730{
732 std::tuple<Actions...> actions;
733
735 When(Predicate test, Actions... toRun)
736 : predicate{std::move(test)}
737 , actions{std::move(toRun)...}
738 {}
739
743 template <typename State>
744 requires internal::UpkeepTest<Predicate, State const &> && (UpkeepOperation<Actions, State> && ...)
745 [[nodiscard]] std::expected<bool, WeightError>
746 operator()(State & state) const
747 {
748 std::expected<bool, WeightError> const holds = internal::testOutcome(predicate(std::as_const(state)));
749 if (!holds || !*holds)
750 {
751 return holds;
752 }
753 return internal::runActions(actions, state);
754 }
755};
756
760template <typename Predicate, typename... Actions>
761[[nodiscard]] When<Predicate, Actions...>
762when(Predicate predicate, Actions... actions)
763{
764 return When<Predicate, Actions...>{std::move(predicate), std::move(actions)...};
765}
766
793template <typename Compare, RealScalar Scalar_, typename... Actions>
795
801template <typename Compare, typename Value, typename... Actions>
802 requires(!ExecutionPolicy<Compare>) && std::is_arithmetic_v<Value>
803 && std::predicate<Compare const &, internal::DeducedScalar<Value>, internal::DeducedScalar<Value>>
804[[nodiscard]] WhenEss<Compare, internal::DeducedScalar<Value>, Actions...>
805whenEss(Compare compare, Value fraction, Actions... actions)
806{
807 using Test = internal::EssTest<Compare, internal::DeducedScalar<Value>>;
808 return when(Test{std::move(compare), internal::DeducedScalar<Value>(fraction)}, std::move(actions)...);
809}
810
815template <ExecutionPolicy P, typename Compare, typename Value, typename... Actions>
816 requires std::is_arithmetic_v<Value>
817 && std::predicate<Compare const &, internal::DeducedScalar<Value>, internal::DeducedScalar<Value>>
818[[nodiscard]] When<internal::EssTest<Compare, internal::DeducedScalar<Value>, std::remove_cvref_t<P>>, Actions...>
819whenEss(P && policy, Compare compare, Value fraction, Actions... actions)
820{
821 using Test = internal::EssTest<Compare, internal::DeducedScalar<Value>, std::remove_cvref_t<P>>;
822 return when(
823 Test{std::move(compare), internal::DeducedScalar<Value>(fraction), std::forward<P>(policy)},
824 std::move(actions)...
825 );
826}
827
842template <typename Compare, typename... Actions>
844
849template <typename Compare, typename... Actions>
850 requires std::predicate<Compare const &, std::ptrdiff_t, std::ptrdiff_t>
851[[nodiscard]] WhenSize<Compare, Actions...>
852whenSize(Compare compare, std::ptrdiff_t limit, Actions... actions)
853{
854 return when(internal::SizeTest<Compare>{std::move(compare), limit}, std::move(actions)...);
855}
856
871template <typename... Actions>
872using Sequence = When<internal::Always, Actions...>;
873
877template <typename... Actions>
878[[nodiscard]] Sequence<Actions...>
879sequence(Actions... actions)
880{
881 return when(internal::Always{}, std::move(actions)...);
882}
883
888struct None
889{
891 template <typename State>
892 [[nodiscard]] constexpr std::expected<bool, NumericError>
893 operator()(State &) const
894 {
895 return false;
896 }
897};
898
902[[nodiscard]] constexpr None
904{
905 return {};
906}
907
928template <RealScalar Scalar_ = double>
930{
931 Scalar_ probability;
932
934 template <typename State>
935 requires requires(State & state, typename State::Scalar logThreshold) { state.pruneBelow(logThreshold); }
936 [[nodiscard]] std::expected<bool, WeightError>
937 operator()(State & state) const
938 {
939 using Scalar = typename State::Scalar;
940 EMRYS_FILTER_ASSERT(
941 probability >= Scalar_(0) && probability < Scalar_(1) && "the threshold is a probability below one"
942 );
943 return internal::prunedAny(state.pruneBelow(Eigen::numext::log(Scalar(probability))));
944 }
945};
946
951template <typename Value>
952 requires std::is_arithmetic_v<Value>
953[[nodiscard]] constexpr PruneBelow<internal::DeducedScalar<Value>>
954pruneBelow(Value probability)
955{
956 return {internal::DeducedScalar<Value>(probability)};
957}
958
972template <RealScalar Scalar_ = double>
974{
975 Scalar_ fraction;
976
978 template <typename State>
979 requires requires(State & state, typename State::Scalar logFraction) { state.pruneTail(logFraction); }
980 [[nodiscard]] std::expected<bool, WeightError>
981 operator()(State & state) const
982 {
983 using Scalar = typename State::Scalar;
984 EMRYS_FILTER_ASSERT(
985 fraction >= Scalar_(0) && fraction < Scalar_(1) && "the fraction pruned is a probability below one"
986 );
987 return internal::prunedAny(state.pruneTail(Eigen::numext::log(Scalar(fraction))));
988 }
989};
990
996template <typename Value>
997 requires std::is_arithmetic_v<Value>
998[[nodiscard]] constexpr PruneTail<internal::DeducedScalar<Value>>
999pruneTail(Value fraction)
1000{
1001 return {internal::DeducedScalar<Value>(fraction)};
1002}
1003
1015{
1016 std::ptrdiff_t maxSize;
1017
1019 template <typename State>
1020 requires requires(State & state, Eigen::Index size) { state.pruneToSize(size); }
1021 [[nodiscard]] std::expected<bool, WeightError>
1022 operator()(State & state) const
1023 {
1024 return internal::prunedAny(state.pruneToSize(Eigen::Index(maxSize)));
1025 }
1026};
1027
1031[[nodiscard]] constexpr PruneToSize
1032pruneToSize(std::ptrdiff_t maxSize)
1033{
1034 return {maxSize};
1035}
1036
1052{
1053 std::ptrdiff_t factor = 2;
1054
1056 template <typename State>
1057 requires requires(State & state, Eigen::Index factor) { state.subdivide(factor); }
1058 [[nodiscard]] std::expected<bool, WeightError>
1059 operator()(State & state) const
1060 {
1061 state.subdivide(Eigen::Index(factor));
1062 return factor > 1;
1063 }
1064};
1065
1070[[nodiscard]] constexpr Subdivide
1071subdivide(std::ptrdiff_t factor = 2)
1072{
1073 return {factor};
1074}
1075
1104template <RealScalar Scalar_ = double>
1106{
1107 Scalar_ probability;
1108
1113 template <typename State>
1114 requires requires(State & state) {
1115 state.modes().logTotalWeight();
1116 state.modes().logWeights();
1117 state.setModes(state.modes());
1118 }
1119 [[nodiscard]] std::expected<bool, WeightError>
1120 operator()(State & state) const
1121 {
1122 using ModesType = std::remove_cvref_t<decltype(state.modes())>;
1123 auto floored = flooredLogWeights(state.modes());
1124 if (!floored)
1125 {
1126 return std::unexpected{floored.error()};
1127 }
1128 if (!*floored)
1129 {
1130 return false;
1131 }
1132 state.setModes(ModesType{state.modes().space(), **floored});
1133 return true;
1134 }
1135
1140 template <typename Space, typename Scalar>
1141 [[nodiscard]] std::expected<bool, WeightError>
1143 {
1144 auto floored = flooredLogWeights(categorical);
1145 if (!floored)
1146 {
1147 return std::unexpected{floored.error()};
1148 }
1149 if (!*floored)
1150 {
1151 return false;
1152 }
1153 categorical.setLogWeights(**floored);
1154 return true;
1155 }
1156
1162 template <typename Component, typename Allocator>
1163 requires requires(typename MixtureParams<Component, Allocator>::ComponentType & hybrid) {
1164 hybrid.setModes(hybrid.modes());
1165 }
1166 [[nodiscard]] std::expected<bool, WeightError>
1168 {
1169 // Every component's modes are checked before any is changed, so that a failure leaves the
1170 // mixture as it was, and a mixture with nothing to floor is not touched.
1172 Scalar const logFloor = Eigen::numext::log(Scalar(probability));
1173 bool any = false;
1174 for (Eigen::Index i = 0; i < mixture.size(); ++i)
1175 {
1176 auto const & modes = mixture[i].component.modes();
1177 auto const total = modes.logTotalWeight();
1178 if (!total)
1179 {
1180 return std::unexpected{total.error()};
1181 }
1182 any = any || ((modes.logWeights() - *total) < logFloor).any();
1183 }
1184 if (!any)
1185 {
1186 return false;
1187 }
1188 mixture.forEach([&](auto & entry) {
1189 entry.updateComponent([&](auto & hybrid) {
1190 (void)(*this)(hybrid);
1191 });
1192 });
1193 return true;
1194 }
1195
1196private:
1197
1202 template <typename Modes>
1203 [[nodiscard]] std::expected<std::optional<typename Modes::ArrayType>, WeightError>
1204 flooredLogWeights(Modes const & modes) const
1205 {
1206 using ArrayType = typename Modes::ArrayType;
1207 using Scalar = typename Modes::Scalar;
1208 auto const total = modes.logTotalWeight();
1209 if (!total)
1210 {
1211 return std::unexpected{total.error()};
1212 }
1213 ArrayType const logProbabilities = modes.logWeights() - *total;
1214 Eigen::Index const count = logProbabilities.size();
1215 EMRYS_FILTER_ASSERT(
1216 probability >= Scalar_(0) && Scalar(probability) * internal::scalarFromCount<Scalar>(count) <= Scalar(1)
1217 && "the floor is a probability no more than one over the number of modes"
1218 );
1219 Scalar const logFloor = Eigen::numext::log(Scalar(probability));
1220 // The floored modes are those below a threshold, which rises as each round floors more:
1221 // the modes above it are scaled by the rest of the mass over their own, and any that falls
1222 // below the floor lowers that scale.
1223 Scalar threshold = logFloor;
1224 Scalar scale = 0;
1225 Eigen::Index floored = (logProbabilities < threshold).count();
1226 if (floored == 0)
1227 {
1228 return std::optional<ArrayType>{};
1229 }
1230 while (true)
1231 {
1232 auto kept = LogSumExp<Scalar>{};
1233 for (Eigen::Index m = 0; m < count; ++m)
1234 {
1235 if (!(logProbabilities(m) < threshold))
1236 {
1237 kept.add(logProbabilities(m));
1238 }
1239 }
1240 scale = Eigen::numext::log(Scalar(Scalar(1) - Scalar(floored) * Scalar(probability))) - kept.value();
1241 threshold = logFloor - scale;
1242 Eigen::Index const next = (logProbabilities < threshold).count();
1243 if (next == floored)
1244 {
1245 break;
1246 }
1247 floored = next;
1248 }
1249 return std::optional<ArrayType>{
1250 (logProbabilities < threshold).select(ArrayType::Constant(count, logFloor), logProbabilities + scale)
1251 };
1252 }
1253};
1254
1259template <typename Value>
1260 requires std::is_arithmetic_v<Value>
1261[[nodiscard]] constexpr FloorModes<internal::DeducedScalar<Value>>
1262floorModes(Value probability)
1263{
1264 return {internal::DeducedScalar<Value>(probability)};
1265}
1266
1299template <Repair Repair_ = roots::Ridge<>>
1301{
1302 Repair_ repair{};
1303
1307 template <typename State>
1308 requires requires(State & state, Repair_ const & repair) {
1309 std::as_const(state).factorize();
1310 state.factorize(repair);
1311 }
1312 [[nodiscard]] std::expected<bool, NumericError>
1313 operator()(State & state) const
1314 {
1315 std::expected<void, NumericError> const factorized =
1316 internal::factorizeOutcome(std::as_const(state).factorize());
1317 if (factorized)
1318 {
1319 return false;
1320 }
1321 if (factorized.error() != NumericError::NotPositiveDefinite)
1322 {
1323 return std::unexpected{factorized.error()};
1324 }
1325 std::expected<void, NumericError> const repaired = internal::factorizeOutcome(state.factorize(repair));
1326 if (!repaired)
1327 {
1328 return std::unexpected{repaired.error()};
1329 }
1330 return true;
1331 }
1332};
1333
1338template <Repair R = roots::Ridge<>>
1339[[nodiscard]] constexpr RepairCovariance<R>
1340repairCovariance(R repair = {})
1341{
1342 return {std::move(repair)};
1343}
1344
1345} // namespace upkeep
1346
1347} // namespace Emrys
The distribution over a discrete space (decisions.md [discrete-spaces], [params-and-density]): Catego...
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
The macros every header includes first.
The mixture: the parameters MixtureParams of weighted components of one type (decisions....
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
NumericError, the reasons a factorization or a normalization can fail, and its two names,...
The particle set: ParticleSet, the weighted samples, with resampling, upkeep of weights and particles...
The resamplers: function objects that draw ancestors in proportion to weights, which ParticleSet::res...
Square roots of a covariance (decisions.md [sigma-point-axes]): the Axes and Repair concepts that a r...
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(),...
Weighted statistics over points, all in internal, which the containers of weighted elements (Gaussian...
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
The parameters of a probability distribution over a discrete space: one weight per value.
Definition Categorical.h:257
void setLogWeights(Eigen::DenseBase< Derived > const &logWeights)
Sets the log-weights to logWeights, one per value.
Definition Categorical.h:369
A weighted sum of components of one type, all in one space.
Definition Mixture.h:579
std::expected< Scalar, WeightError > logTotalWeight() const
Definition Mixture.h:1269
Params< Component_ > ComponentType
a component
Definition Mixture.h:588
Index size() const
Definition Mixture.h:957
void forEach(F &&f)
Calls f on each entry as a MutableEntry&, returning nothing; reweight() takes one that returns a log ...
Definition Mixture.h:1364
A set of weighted samples: the empirical distribution.
Definition ParticleSet.h:279
Index size() const
Definition ParticleSet.h:606
std::expected< MatrixType, WeightError > kdeBandwidth() const
Definition ParticleSet.h:699
Space_ const & space() const
Definition ParticleSet.h:652
Index differenceDimension() const
Definition ParticleSet.h:645
ConstStateType state(Index i) const
Definition ParticleSet.h:673
void forEach(F &&f)
Calls f on each particle as a MutableParticle&, returning nothing; reweight() takes one that returns ...
Definition ParticleSet.h:1077
std::expected< Scalar, WeightError > logTotalWeight() const
Definition ParticleSet.h:965
void setState(Index i, Eigen::MatrixBase< Derived > const &state)
Sets particle i's state, normalized by the space.
Definition ParticleSet.h:890
Action that resamples a set in proportion to its weights, drawing from the generator G with the resam...
Definition Upkeep.h:402
std::expected< bool, WeightError > operator()(State &state) const
Resamples state.
Definition Upkeep.h:424
constexpr Resampler_ const & resampler() const noexcept
Definition Upkeep.h:452
constexpr Resample(G &generator, Resampler_ resampler={}, std::optional< Eigen::Index > size=std::nullopt, Policy_ policy={})
Draws from generator with resampler under policy, to size particles if one is given and to the state'...
Definition Upkeep.h:408
constexpr G & generator() const noexcept
Definition Upkeep.h:445
constexpr Policy_ const & policy() const noexcept
Definition Upkeep.h:466
constexpr std::optional< Eigen::Index > size() const noexcept
Definition Upkeep.h:459
Action that moves each particle by an independent draw from a Gaussian kernel, to restore the diversi...
Definition Upkeep.h:573
constexpr G & generator() const noexcept
Definition Upkeep.h:660
std::expected< bool, WeightError > operator()(State &state) const
Roughens the particles of a Rao-Blackwellized state, such as a ParticleGaussianParams,...
Definition Upkeep.h:647
constexpr Roughen(G &generator, Scalar_ scale=1, Policy_ policy={})
Draws from generator a kernel of covariance scale times the bandwidth, under policy.
Definition Upkeep.h:577
std::expected< bool, WeightError > operator()(ParticleSet< Space, Scalar, Allocator > &particles) const
Moves each of particles by a draw from the kernel.
Definition Upkeep.h:588
constexpr Policy_ const & policy() const noexcept
Definition Upkeep.h:674
constexpr Scalar_ scale() const noexcept
Definition Upkeep.h:667
An execution policy: execution::sequential, a policy of the standard library such as std::execution::...
Definition Execution.h:459
A type usable as the scalar of a distribution: a real number type such as float, double,...
Definition Math.h:80
An upkeep operation on a State: an action or a trigger, the library's or the caller's own,...
Definition Upkeep.h:368
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
NumericError WeightError
Reasons a collection of log-weights cannot be normalized: Empty, ZeroSum, or NonFinite of NumericErro...
Definition NumericError.h:53
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
constexpr Select select()
Definition Reduction.h:334
auto plus(S const &space, Eigen::MatrixBase< XDerived > const &x, Eigen::MatrixBase< DDerived > const &d)
Definition Space.h:675
constexpr FloorModes< internal::DeducedScalar< Value > > floorModes(Value probability)
Definition Upkeep.h:1262
When< internal::Always, Actions... > Sequence
Runs its actions in order on the state every time: a When whose predicate always holds,...
Definition Upkeep.h:872
constexpr Subdivide subdivide(std::ptrdiff_t factor=2)
Definition Upkeep.h:1071
When< internal::SizeTest< Compare >, Actions... > WhenSize
Trigger that runs its actions when compare holds between a state's number of elements and a limit,...
Definition Upkeep.h:843
constexpr RepairCovariance< R > repairCovariance(R repair={})
Definition Upkeep.h:1340
constexpr Resample< G, R > resample(G &generator, R resampler={})
Definition Upkeep.h:494
constexpr PruneBelow< internal::DeducedScalar< Value > > pruneBelow(Value probability)
Definition Upkeep.h:954
When< Predicate, Actions... > when(Predicate predicate, Actions... actions)
Definition Upkeep.h:762
Sequence< Actions... > sequence(Actions... actions)
Definition Upkeep.h:879
constexpr PruneTail< internal::DeducedScalar< Value > > pruneTail(Value fraction)
Definition Upkeep.h:999
WhenSize< Compare, Actions... > whenSize(Compare compare, std::ptrdiff_t limit, Actions... actions)
Definition Upkeep.h:852
constexpr None none()
Definition Upkeep.h:903
constexpr PruneToSize pruneToSize(std::ptrdiff_t maxSize)
Definition Upkeep.h:1032
When< internal::EssTest< Compare, Scalar_ >, Actions... > WhenEss
Trigger that runs its actions when compare holds between a set's effective sample size and fraction o...
Definition Upkeep.h:794
WhenEss< Compare, internal::DeducedScalar< Value >, Actions... > whenEss(Compare compare, Value fraction, Actions... actions)
Definition Upkeep.h:805
constexpr Roughen< G, internal::DeducedScalar< Value > > roughen(G &generator, Value scale=1)
Definition Upkeep.h:694
The upkeep operations, each made by its factory (decisions.md [policy-factories], [upkeep]): the acti...
Definition Upkeep.h:380
The type of execution::sequential, the default execution policy: plain loops on the calling thread,...
Definition Execution.h:123
Uses one uniform offset for M evenly spaced positions in [0, 1).
Definition Resampling.h:351
Action that raises every mode less probable than probability to it, scaling the others down to keep t...
Definition Upkeep.h:1106
Scalar_ probability
no mode is left less probable than this
Definition Upkeep.h:1107
std::expected< bool, WeightError > operator()(MixtureParams< Component, Allocator > &mixture) const
Floors the mode probabilities of each component of mixture, a mixture of hybrids, and leaves the comp...
Definition Upkeep.h:1167
std::expected< bool, WeightError > operator()(CategoricalParams< Space, Scalar > &categorical) const
Floors the probabilities of categorical, the state of a HiddenMarkovFilter, as those of a hybrid's mo...
Definition Upkeep.h:1142
std::expected< bool, WeightError > operator()(State &state) const
Floors state's mode probabilities, read by modes() and written by setModes().
Definition Upkeep.h:1120
The upkeep a filter holds when it is given none: it takes any state, changes nothing,...
Definition Upkeep.h:889
constexpr std::expected< bool, NumericError > operator()(State &) const
Definition Upkeep.h:893
Action that removes every point whose normalized probability is below probability,...
Definition Upkeep.h:930
Scalar_ probability
points less probable than this are removed
Definition Upkeep.h:931
std::expected< bool, WeightError > operator()(State &state) const
Prunes state.
Definition Upkeep.h:937
Action that removes the least probable points, as many as can be while their combined probability sta...
Definition Upkeep.h:974
std::expected< bool, WeightError > operator()(State &state) const
Prunes state.
Definition Upkeep.h:981
Scalar_ fraction
the probability the points removed stay below
Definition Upkeep.h:975
Action that removes the least probable points until at most maxSize remain, renormalizing the rest.
Definition Upkeep.h:1015
std::expected< bool, WeightError > operator()(State &state) const
Prunes state.
Definition Upkeep.h:1022
std::ptrdiff_t maxSize
the most points left, at least one
Definition Upkeep.h:1016
Action that repairs every covariance of the state that is not positive definite, by the Repair Repair...
Definition Upkeep.h:1301
Repair_ repair
the repair, roots::ridge() by default
Definition Upkeep.h:1302
std::expected< bool, NumericError > operator()(State &state) const
Repairs state's covariances that are not positive definite.
Definition Upkeep.h:1313
Action that splits every point into factor points along each axis, sharing its probability equally.
Definition Upkeep.h:1052
std::ptrdiff_t factor
the number of new cells along each axis per old cell
Definition Upkeep.h:1053
std::expected< bool, WeightError > operator()(State &state) const
Subdivides state.
Definition Upkeep.h:1059
Trigger that runs its actions when its predicate holds for the state: the general trigger,...
Definition Upkeep.h:730
Predicate predicate
the test of the state
Definition Upkeep.h:731
std::tuple< Actions... > actions
run in order when the test holds
Definition Upkeep.h:732
When(Predicate test, Actions... toRun)
Runs toRun, in order, when test holds for the state.
Definition Upkeep.h:735
std::expected< bool, WeightError > operator()(State &state) const
Tests state and runs the actions on it if the test holds.
Definition Upkeep.h:746