EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Divergence.h
Go to the documentation of this file.
1#pragma once
2
11
12#include <concepts>
13#include <expected>
14#include <limits>
15#include <random>
16#include <type_traits>
17#include <utility>
18
19#include <Eigen/Cholesky>
20#include <Eigen/SVD>
21
40
41namespace Emrys
42{
43
99
108{
109 Eigen::Index count;
110};
111
115template <typename Scalar = double>
117{
118 Scalar value;
120 Eigen::Index count;
121};
122
123namespace internal
124{
125
129template <typename Indices>
130[[nodiscard]] Eigen::Index
131indexCount(Indices const & indices)
132{
133 if constexpr (std::is_integral_v<Indices>)
134 {
135 return 1;
136 }
137 else
138 {
139 return Eigen::Index(indices.size());
140 }
141}
142
144template <typename Indices>
145[[nodiscard]] Eigen::Index
146indexAt(Indices const & indices, Eigen::Index k)
147{
148 if constexpr (std::is_integral_v<Indices>)
149 {
150 return Eigen::Index(indices);
151 }
152 else
153 {
154 return Eigen::Index(indices[k]);
155 }
156}
157
158} // namespace internal
159
189template <Space Space_, RealScalar Scalar_ = double>
191{
192public:
193
194 using SpaceType = Space_;
195 using Scalar = Scalar_;
196 using Index = Eigen::Index;
198
200 explicit GroundMetric(Space_ space)
201 : m_matrix{MatrixType::Identity(Emrys::differenceDimension(space), Emrys::differenceDimension(space))}
202 , m_space{std::move(space)}
203 {}
204
209 template <typename Path>
210 void
211 scale(Scalar factor)
212 {
213 auto const rows = diffIdx<Path>(m_space);
214 for (Index k = 0; k < internal::indexCount(rows); ++k)
215 {
216 m_matrix.row(internal::indexAt(rows, k)) *= factor;
217 }
218 }
219
225 template <typename Into, typename From>
226 void
228 {
229 auto const into = diffIdx<Into>(m_space);
230 auto const from = diffIdx<From>(m_space);
231 EMRYS_FILTER_ASSERT(
232 internal::indexCount(into) == internal::indexCount(from)
233 && "combined blocks must have the same difference dimension"
234 );
235 MatrixType const before = m_matrix;
236 for (Index k = 0; k < internal::indexCount(into); ++k)
237 {
238 m_matrix.row(internal::indexAt(into, k)) += factor * before.row(internal::indexAt(from, k));
239 }
240 }
241
243 [[nodiscard]] MatrixType const &
244 matrix() const
245 {
246 return m_matrix;
247 }
248
250 [[nodiscard]] Space_ const &
251 space() const
252 {
253 return m_space;
254 }
255
256private:
257
258 MatrixType m_matrix;
259 EMRYS_FILTER_NO_UNIQUE_ADDRESS Space_ m_space;
260};
261
262namespace internal
263{
264
266template <typename>
267inline constexpr bool alwaysFalse = false;
268
274struct NoClosedForm
275{
277 template <typename T>
278 operator T() const;
279};
280
282inline void
283assertSameDimensions(auto const & p, auto const & q)
284{
285 EMRYS_FILTER_ASSERT(
286 p.valueDimension() == q.valueDimension() && p.differenceDimension() == q.differenceDimension()
287 && "the distributions must have the same dimensions"
288 );
289}
290
296template <typename Scalar>
297class RunningMean
298{
299public:
300
302 void
303 add(Scalar x)
304 {
305 if (x == Eigen::NumTraits<Scalar>::infinity())
306 {
307 m_infinite = true;
308 }
309 if (m_infinite)
310 {
311 return;
312 }
313 ++m_count;
314 Scalar const delta = x - m_mean;
315 m_mean += delta / Scalar(m_count);
316 m_sumOfSquares += delta * (x - m_mean);
317 }
318
320 [[nodiscard]] bool
321 infinite() const
322 {
323 return m_infinite;
324 }
325
327 [[nodiscard]] Scalar
328 mean() const
329 {
330 return m_infinite ? Eigen::NumTraits<Scalar>::infinity() : m_mean;
331 }
332
334 [[nodiscard]] Scalar
335 standardError() const
336 {
337 return m_infinite ? Eigen::NumTraits<Scalar>::infinity()
338 : Eigen::numext::sqrt(m_sumOfSquares / Scalar(m_count - 1) / Scalar(m_count));
339 }
340
341private:
342
343 Eigen::Index m_count = 0;
344 Scalar m_mean = 0;
345 Scalar m_sumOfSquares = 0;
346 bool m_infinite = false;
347};
348
350template <typename Scalar>
351[[nodiscard]] Scalar
352infinity()
353{
354 return Eigen::NumTraits<Scalar>::infinity();
355}
356
358template <typename Scalar>
359[[nodiscard]] Scalar
360logEvenMixture(Scalar a, Scalar b)
361{
362 auto sum = LogSumExp<Scalar>{};
363 sum.add(a);
364 sum.add(b);
365 return sum.value() - Eigen::numext::log(Scalar(2));
366}
367
369template <typename Scalar>
370[[nodiscard]] Scalar
371hellingerFromBhattacharyya(Scalar distance)
372{
373 return Eigen::numext::sqrt(Eigen::numext::maxi(-Eigen::numext::expm1(-distance), Scalar(0)));
374}
375
383template <typename Scalar, typename DifferenceDerived, typename FirstDerived, typename SecondDerived>
384[[nodiscard]] Scalar
385buresWasserstein(
386 Eigen::MatrixBase<DifferenceDerived> const & difference,
387 Eigen::MatrixBase<FirstDerived> const & first,
388 Eigen::MatrixBase<SecondDerived> const & second
389)
390{
391 using Cross = MatrixOf<
392 Scalar,
393 FirstDerived::ColsAtCompileTime,
394 SecondDerived::ColsAtCompileTime,
395 FirstDerived::MaxColsAtCompileTime,
396 SecondDerived::MaxColsAtCompileTime>;
397 Cross const cross = multiply(first.transpose(), second);
398 Scalar const nuclearNorm = Eigen::JacobiSVD<Cross>{cross}.singularValues().sum();
399 Scalar const squared =
400 difference.squaredNorm() + first.squaredNorm() + second.squaredNorm() - Scalar(2) * nuclearNorm;
401 return Eigen::numext::sqrt(Eigen::numext::maxi(squared, Scalar(0)));
402}
403
407template <typename D>
408inline constexpr bool isGaussianDensity = false;
409
411template <typename Space, typename Scalar>
412inline constexpr bool isGaussianDensity<Gaussian<Space, Scalar>> = true;
413
415template <typename Params>
416inline constexpr bool isGaussianDensity<FormGaussian<Params>> = true;
417
421template <typename P, typename Q>
422concept GaussianDensitiesOfOneSpace =
423 isGaussianDensity<P> && isGaussianDensity<Q> && std::same_as<typename P::SpaceType, typename Q::SpaceType>
424 && std::same_as<typename P::Scalar, typename Q::Scalar>;
425
429template <typename D>
430inline constexpr bool isCovarianceDensity = std::same_as<D, Gaussian<typename D::SpaceType, typename D::Scalar>>;
431
435template <typename D>
436inline constexpr int covarianceRootTriangle = D::ParamsType::IsInfoForm ? Eigen::Upper : Eigen::Lower;
437
444template <typename D>
445[[nodiscard]] typename D::MatrixType
446covarianceRootOf(D const & d)
447{
448 using MatrixType = typename D::MatrixType;
449 if constexpr (isCovarianceDensity<D>)
450 {
451 return MatrixType{d.llt().matrixL()};
452 }
453 else if constexpr (D::ParamsType::IsInfoForm)
454 {
455 Eigen::Index const n = d.differenceDimension();
456 return d.colorNormals(MatrixType::Identity(n, n));
457 }
458 else
459 {
460 return d.root();
461 }
462}
463
468template <typename D, typename Derived>
469void
470whitenInPlace(D const & d, Eigen::MatrixBase<Derived> & matrix)
471{
472 if constexpr (isCovarianceDensity<D>)
473 {
474 solveInPlace(d.llt().matrixL(), matrix);
475 }
476 else if constexpr (D::ParamsType::IsInfoForm)
477 {
478 auto const root = covarianceRootOf(d);
479 solveInPlace(root.template triangularView<Eigen::Upper>(), matrix);
480 }
481 else
482 {
483 solveInPlace(d.root().template triangularView<Eigen::Lower>(), matrix);
484 }
485}
486
491template <typename P, typename Q>
492[[nodiscard]] typename P::MatrixType
493averageFactor(P const & p, Q const & q)
494{
495 using Scalar = typename P::Scalar;
496 using MatrixType = typename P::MatrixType;
497 constexpr int Rows = MatrixType::RowsAtCompileTime;
498 constexpr int MaxRows = MatrixType::MaxRowsAtCompileTime;
499 using Stack = MatrixOf<
500 Scalar,
501 Rows,
502 Rows == Eigen::Dynamic ? Eigen::Dynamic : 2 * Rows,
503 MaxRows,
504 MaxRows == Eigen::Dynamic ? Eigen::Dynamic : 2 * MaxRows>;
505 Eigen::Index const n = p.differenceDimension();
506 auto stack = Stack{n, 2 * n};
507 Scalar const half = Eigen::numext::sqrt(Scalar(0.5));
508 stack.leftCols(n) = half * covarianceRootOf(p);
509 stack.rightCols(n) = half * covarianceRootOf(q);
510 return lowerTriangularRoot(stack);
511}
512
514template <typename P, typename G>
515concept SampleWithLogDensityFrom = requires(P const & p, G & rng) { drawSampleWithLogDensity(p, rng); };
516
518template <typename P, typename Q, typename G>
519concept MonteCarloComparable =
520 SampleWithLogDensityFrom<P, G> && HasLogDensity<Q> && std::same_as<typename P::SpaceType, typename Q::SpaceType>
521 && std::same_as<typename P::Scalar, typename Q::Scalar>;
522
532template <typename Scalar, typename Policy, typename P, typename G, typename TermOf>
533[[nodiscard]] RunningMean<Scalar>
534meanOverDraws(Policy && policy, P const & p, Eigen::Index count, G & rng, TermOf const & termOf)
535{
536 EMRYS_FILTER_ASSERT(count >= 2 && "a Monte Carlo estimate needs at least two draws");
537 auto terms = RunningMean<Scalar>{};
538 if constexpr (isSequential<Policy>)
539 {
540 for (Eigen::Index j = 0; j < count && !terms.infinite(); ++j)
541 {
542 auto const draw = drawSampleWithLogDensity(p, rng);
543 terms.add(termOf(draw.sample, draw.logDensity));
544 }
545 }
546 else
547 {
548 auto samples = SampleSet<typename P::SpaceType, Scalar>{p.valueDimension(), count};
549 auto logP = Eigen::Array<Scalar, Eigen::Dynamic, 1>{count};
550 for (Eigen::Index j = 0; j < count; ++j)
551 {
552 auto const draw = drawSampleWithLogDensity(p, rng);
553 samples.col(j) = draw.sample;
554 logP(j) = draw.logDensity;
555 }
556 auto values = Eigen::Array<Scalar, Eigen::Dynamic, 1>{count};
557 forEachIndex(std::forward<Policy>(policy), count, [&](Eigen::Index j) {
558 values(j) = termOf(samples.col(j), logP(j));
559 });
560 for (Eigen::Index j = 0; j < count && !terms.infinite(); ++j)
561 {
562 terms.add(values(j));
563 }
564 }
565 return terms;
566}
567
571template <typename Scalar, typename Policy, typename P, typename Q, typename G>
572[[nodiscard]] RunningMean<Scalar>
573bhattacharyyaCoefficientTerms(Policy && policy, MonteCarlo const & method, P const & p, Q const & q, G & rng)
574{
575 return meanOverDraws<Scalar>(
576 std::forward<Policy>(policy),
577 p,
578 method.count,
579 rng,
580 [&](auto const & sample, Scalar logP) {
581 return Eigen::numext::exp(Scalar(0.5) * (q.logDensity(sample) - logP));
582 }
583 );
584}
585
590template <typename Scalar, typename Policy, typename P, typename Q, typename G>
591[[nodiscard]] RunningMean<Scalar>
592mixtureDivergenceTerms(Policy && policy, MonteCarlo const & method, P const & p, Q const & q, G & rng)
593{
594 return meanOverDraws<Scalar>(
595 std::forward<Policy>(policy),
596 p,
597 method.count,
598 rng,
599 [&](auto const & sample, Scalar logP) {
600 return Scalar(logP - logEvenMixture(logP, Scalar(q.logDensity(sample))));
601 }
602 );
603}
604
606template <typename Space, typename MomentsType, typename TruthDerived>
607[[nodiscard]] typename MomentsType::Scalar
608expectedSquaredErrorOf(Space const & space, MomentsType const & moments, Eigen::MatrixBase<TruthDerived> const & truth)
609{
610 EMRYS_FILTER_ASSERT(truth.size() == moments.valueDimension() && "the truth must be a value of the space");
611 typename MomentsType::DifferenceVectorType const error = Emrys::minus(space, moments.mean(), truth);
612 return error.squaredNorm() + moments.covarianceLower().trace();
613}
614
616template <typename Space, typename MomentsType, typename TruthDerived, typename WeightsDerived>
617[[nodiscard]] typename MomentsType::Scalar
618expectedSquaredErrorOf(
619 Space const & space,
620 MomentsType const & moments,
621 Eigen::MatrixBase<TruthDerived> const & truth,
622 Eigen::MatrixBase<WeightsDerived> const & weights
623)
624{
625 EMRYS_FILTER_ASSERT(truth.size() == moments.valueDimension() && "the truth must be a value of the space");
626 EMRYS_FILTER_ASSERT(
627 weights.cols() == moments.differenceDimension() && "the weights must have a column per difference coordinate"
628 );
629 typename MomentsType::DifferenceVectorType const error = Emrys::minus(space, moments.mean(), truth);
630 // tr(W Σ W^T) is the sum of the entries of (W Σ) ∘ W.
631 return multiply(weights, error).squaredNorm()
632 + multiply(weights, moments.covarianceLower()).cwiseProduct(weights).sum();
633}
634
638template <typename Distribution, typename Compute>
639[[nodiscard]] auto
640fromMoments(Distribution const & distribution, Compute compute)
641{
642 using Result = std::remove_cvref_t<decltype(distribution.moments())>;
643 if constexpr (isExpected<Result>)
644 {
645 using Value = decltype(compute(std::declval<typename Result::value_type const &>()));
646 using Expected = std::expected<Value, typename Result::error_type>;
647 Result const moments = distribution.moments();
648 if (!moments)
649 {
650 return Expected{std::unexpect, moments.error()};
651 }
652 return Expected{compute(*moments)};
653 }
654 else
655 {
656 return compute(distribution.moments());
657 }
658}
659
660} // namespace internal
661
675template <typename P, typename Q>
676 requires internal::GaussianDensitiesOfOneSpace<P, Q>
677[[nodiscard]] typename P::Scalar
678kullbackLeibler(P const & p, Q const & q)
679{
680 using Scalar = typename P::Scalar;
681 internal::assertSameDimensions(p, q);
682 typename P::MatrixType whitened = internal::covarianceRootOf(p);
683 internal::whitenInPlace(q, whitened);
684 Scalar const divergence = Scalar(0.5)
685 * (whitened.squaredNorm() + q.squaredMahalanobisDistance(p.mean())
686 - Scalar(p.differenceDimension()) + q.logDeterminant() - p.logDeterminant());
687 return Eigen::numext::maxi(divergence, Scalar(0));
688}
689
695template <DiscreteSpace Space, typename Scalar>
696[[nodiscard]] Scalar
698{
699 EMRYS_FILTER_ASSERT(p.cardinality() == q.cardinality() && "the distributions must have the same values");
700 auto const logP = p.logProbabilities();
701 auto const logQ = q.logProbabilities();
702 Scalar divergence = 0;
703 for (Eigen::Index i = 0; i < p.cardinality(); ++i)
704 {
705 if (logP(i) == -internal::infinity<Scalar>())
706 {
707 continue;
708 }
709 if (logQ(i) == -internal::infinity<Scalar>())
710 {
711 return internal::infinity<Scalar>();
712 }
713 divergence += Eigen::numext::exp(logP(i)) * (logP(i) - logQ(i));
714 }
715 return Eigen::numext::maxi(divergence, Scalar(0));
716}
717
726template <typename P, typename Q, std::uniform_random_bit_generator G>
727 requires internal::MonteCarloComparable<P, Q, G>
728[[nodiscard]] MonteCarloEstimate<typename P::Scalar>
729kullbackLeibler(MonteCarlo const & method, P const & p, Q const & q, G & rng)
730{
731 return kullbackLeibler(execution::sequential, method, p, q, rng);
732}
733
740template <ExecutionPolicy Policy, typename P, typename Q, std::uniform_random_bit_generator G>
741 requires internal::MonteCarloComparable<P, Q, G>
742[[nodiscard]] MonteCarloEstimate<typename P::Scalar>
743kullbackLeibler(Policy && policy, MonteCarlo const & method, P const & p, Q const & q, G & rng)
744{
745 using Scalar = typename P::Scalar;
746 auto const terms = internal::meanOverDraws<Scalar>(
747 std::forward<Policy>(policy),
748 p,
749 method.count,
750 rng,
751 [&](auto const & sample, Scalar logP) {
752 Scalar const logQ = q.logDensity(sample);
753 return logQ == -internal::infinity<Scalar>() ? internal::infinity<Scalar>() : Scalar(logP - logQ);
754 }
755 );
756 return {terms.mean(), terms.standardError(), method.count};
757}
758
762template <typename P, typename Q>
763[[nodiscard]] internal::NoClosedForm
764kullbackLeibler(P const &, Q const &)
765{
766 static_assert(
767 internal::alwaysFalse<P>,
768 "kullbackLeibler(p, q) has a closed form only between two Gaussian densities of one space, in any of the four "
769 "forms (call toDensity() on the parameters, and copy() a GaussianMarginal first), or two Categorical "
770 "views of one space; "
771 "estimate it otherwise with kullbackLeibler(MonteCarlo{count}, p, q, rng)"
772 );
773 return {};
774}
775
780template <typename Scalar = double>
782{
783 Scalar lower;
784 Scalar upper;
785};
786
787namespace internal
788{
789
796template <typename Scalar, typename CovarianceDerived, typename ForEach>
797[[nodiscard]] CollapseBounds<Scalar>
798collapseBounds(Eigen::MatrixBase<CovarianceDerived> const & covarianceLower, ForEach forEach)
799{
800 using MatrixType = typename CovarianceDerived::PlainObject;
801 auto const llt = Eigen::LLT<MatrixType>{covarianceLower};
802 EMRYS_FILTER_ASSERT(llt.info() == Eigen::Success && "the mixture's covariance must be positive definite");
803 Scalar componentLogDeterminants = 0;
804 Scalar weightEntropy = 0;
805 forEach([&](Scalar const logWeight, auto const & gaussian) {
806 if (logWeight > -Eigen::NumTraits<Scalar>::infinity())
807 {
808 Scalar const weight = Eigen::numext::exp(logWeight);
809 componentLogDeterminants += weight * FactorizedGaussian::logDeterminant(gaussian);
810 weightEntropy -= weight * logWeight;
811 }
812 });
813 Scalar const logDeterminant = Scalar(2) * llt.matrixLLT().diagonal().array().log().sum();
814 Scalar const upper = Eigen::numext::maxi(Scalar(0.5) * (logDeterminant - componentLogDeterminants), Scalar(0));
815 return CollapseBounds<Scalar>{Eigen::numext::maxi(upper - weightEntropy, Scalar(0)), upper};
816}
817
818} // namespace internal
819
865template <typename Space, typename Scalar, typename Allocator>
866[[nodiscard]] CollapseBounds<Scalar>
868{
869 auto const moments = p.moments();
870 return internal::collapseBounds<Scalar>(moments.covarianceLower(), [&](auto const visit) {
871 for (auto const & entry : p)
872 {
873 visit(entry.logWeight - p.logTotalWeight(), entry.component);
874 }
875 });
876}
877
885template <typename Space, typename ModeSpace, typename Scalar, typename Allocator>
886[[nodiscard]] CollapseBounds<Scalar>
888{
889 auto const moments = p.moments();
890 return internal::collapseBounds<Scalar>(moments.covarianceLower(), [&](auto const visit) {
891 auto const logProbabilities = internal::logProbabilitiesOf(p.modes());
892 for (Eigen::Index m = 0; m < p.size(); ++m)
893 {
894 visit(Scalar(logProbabilities(m)), p.conditional(m));
895 }
896 });
897}
898
907template <typename Space, typename ModeSpace, typename Scalar, typename ComponentAllocator, typename Allocator>
908[[nodiscard]] CollapseBounds<Scalar>
911)
912{
913 auto const moments = p.moments();
914 return internal::collapseBounds<Scalar>(moments.covarianceLower(), [&](auto const visit) {
915 for (auto const & entry : p)
916 {
917 Scalar const logWeight = entry.logWeight - p.logTotalWeight();
918 auto const logProbabilities = internal::logProbabilitiesOf(entry.component.modes());
919 for (Eigen::Index m = 0; m < entry.component.size(); ++m)
920 {
921 visit(Scalar(logWeight + logProbabilities(m)), entry.component.conditional(m));
922 }
923 }
924 });
925}
926
941template <typename P, typename Q>
942 requires internal::GaussianDensitiesOfOneSpace<P, Q>
943[[nodiscard]] typename P::Scalar
944bhattacharyya(P const & p, Q const & q)
945{
946 using Scalar = typename P::Scalar;
947 using MatrixType = typename P::MatrixType;
948 internal::assertSameDimensions(p, q);
949 typename P::DifferenceVectorType difference = Emrys::minus(p.space(), p.mean(), q.mean());
950 Scalar logDeterminant{};
951 if constexpr (internal::isCovarianceDensity<P> && internal::isCovarianceDensity<Q>)
952 {
953 MatrixType const average = Scalar(0.5) * (p.covarianceLower() + q.covarianceLower());
954 auto const llt = Eigen::LLT<MatrixType>{average};
955 EMRYS_FILTER_ASSERT(llt.info() == Eigen::Success && "the average covariance must be positive definite");
956 llt.matrixL().solveInPlace(difference);
957 logDeterminant = Scalar(2) * llt.matrixLLT().diagonal().array().log().sum();
958 }
959 else
960 {
961 MatrixType const average = internal::averageFactor(p, q);
962 EMRYS_FILTER_ASSERT(
963 (average.diagonal().array() != Scalar(0)).all() && "the average covariance must be positive definite"
964 );
965 average.template triangularView<Eigen::Lower>().solveInPlace(difference);
966 logDeterminant = Scalar(2) * average.diagonal().array().abs().log().sum();
967 }
968 Scalar const distance = Scalar(0.125) * difference.squaredNorm()
969 + Scalar(0.5) * (logDeterminant - Scalar(0.5) * (p.logDeterminant() + q.logDeterminant()));
970 return Eigen::numext::maxi(distance, Scalar(0));
971}
972
977template <DiscreteSpace Space, typename Scalar>
978[[nodiscard]] Scalar
980{
981 EMRYS_FILTER_ASSERT(p.cardinality() == q.cardinality() && "the distributions must have the same values");
982 auto logCoefficient = LogSumExp<Scalar>{};
983 for (Eigen::Index i = 0; i < p.cardinality(); ++i)
984 {
985 logCoefficient.add(Scalar(0.5) * (p.logProbability(i) + q.logProbability(i)));
986 }
987 return Eigen::numext::maxi(-logCoefficient.value(), Scalar(0));
988}
989
995template <typename P, typename Q, std::uniform_random_bit_generator G>
996 requires internal::MonteCarloComparable<P, Q, G>
997[[nodiscard]] MonteCarloEstimate<typename P::Scalar>
998bhattacharyya(MonteCarlo const & method, P const & p, Q const & q, G & rng)
999{
1000 return bhattacharyya(execution::sequential, method, p, q, rng);
1001}
1002
1007template <ExecutionPolicy Policy, typename P, typename Q, std::uniform_random_bit_generator G>
1008 requires internal::MonteCarloComparable<P, Q, G>
1009[[nodiscard]] MonteCarloEstimate<typename P::Scalar>
1010bhattacharyya(Policy && policy, MonteCarlo const & method, P const & p, Q const & q, G & rng)
1011{
1012 using Scalar = typename P::Scalar;
1013 auto const terms = internal::bhattacharyyaCoefficientTerms<Scalar>(std::forward<Policy>(policy), method, p, q, rng);
1014 return {-Eigen::numext::log(terms.mean()), terms.standardError() / terms.mean(), method.count};
1015}
1016
1020template <typename P, typename Q>
1021[[nodiscard]] internal::NoClosedForm
1022bhattacharyya(P const &, Q const &)
1023{
1024 static_assert(
1025 internal::alwaysFalse<P>,
1026 "bhattacharyya(p, q) has a closed form only between two Gaussian densities of one space, in any of the four "
1027 "forms (call toDensity() on the parameters, and copy() a GaussianMarginal first), or two Categorical "
1028 "views of one space; "
1029 "estimate it otherwise with bhattacharyya(MonteCarlo{count}, p, q, rng)"
1030 );
1031 return {};
1032}
1033
1039template <typename P, typename Q>
1040 requires internal::GaussianDensitiesOfOneSpace<P, Q>
1041[[nodiscard]] typename P::Scalar
1042hellinger(P const & p, Q const & q)
1043{
1044 return internal::hellingerFromBhattacharyya(bhattacharyya(p, q));
1045}
1046
1051template <DiscreteSpace Space, typename Scalar>
1052[[nodiscard]] Scalar
1054{
1055 return internal::hellingerFromBhattacharyya(bhattacharyya(p, q));
1056}
1057
1062template <typename P, typename Q, std::uniform_random_bit_generator G>
1063 requires internal::MonteCarloComparable<P, Q, G>
1064[[nodiscard]] MonteCarloEstimate<typename P::Scalar>
1065hellinger(MonteCarlo const & method, P const & p, Q const & q, G & rng)
1066{
1067 return hellinger(execution::sequential, method, p, q, rng);
1068}
1069
1073template <ExecutionPolicy Policy, typename P, typename Q, std::uniform_random_bit_generator G>
1074 requires internal::MonteCarloComparable<P, Q, G>
1075[[nodiscard]] MonteCarloEstimate<typename P::Scalar>
1076hellinger(Policy && policy, MonteCarlo const & method, P const & p, Q const & q, G & rng)
1077{
1078 using Scalar = typename P::Scalar;
1079 auto const terms = internal::bhattacharyyaCoefficientTerms<Scalar>(std::forward<Policy>(policy), method, p, q, rng);
1080 Scalar const distance = Eigen::numext::sqrt(Eigen::numext::maxi(Scalar(1) - terms.mean(), Scalar(0)));
1081 // dH/dc = -1 / (2H); at H = 0 the first-order error vanishes, and H is of the order of the
1082 // square root of the coefficient's error.
1083 Scalar const standardError = distance > Scalar(0) ? terms.standardError() / (Scalar(2) * distance)
1084 : Eigen::numext::sqrt(terms.standardError());
1085 return {distance, standardError, method.count};
1086}
1087
1091template <typename P, typename Q>
1092[[nodiscard]] internal::NoClosedForm
1093hellinger(P const &, Q const &)
1094{
1095 static_assert(
1096 internal::alwaysFalse<P>,
1097 "hellinger(p, q) has a closed form only between two Gaussian densities of one space, in any of the four "
1098 "forms (call toDensity() on the parameters, and copy() a GaussianMarginal first), or two Categorical "
1099 "views of one space; "
1100 "estimate it otherwise with hellinger(MonteCarlo{count}, p, q, rng)"
1101 );
1102 return {};
1103}
1104
1111template <DiscreteSpace Space, typename Scalar>
1112[[nodiscard]] Scalar
1114{
1115 EMRYS_FILTER_ASSERT(p.cardinality() == q.cardinality() && "the distributions must have the same values");
1116 Scalar divergence = 0;
1117 for (Eigen::Index i = 0; i < p.cardinality(); ++i)
1118 {
1119 Scalar const logP = p.logProbability(i);
1120 Scalar const logQ = q.logProbability(i);
1121 Scalar const logMixture = internal::logEvenMixture(logP, logQ);
1122 if (logP != -internal::infinity<Scalar>())
1123 {
1124 divergence += Eigen::numext::exp(logP) * (logP - logMixture);
1125 }
1126 if (logQ != -internal::infinity<Scalar>())
1127 {
1128 divergence += Eigen::numext::exp(logQ) * (logQ - logMixture);
1129 }
1130 }
1131 Scalar const bound = Eigen::numext::log(Scalar(2));
1132 return Eigen::numext::mini(Eigen::numext::maxi(Scalar(0.5) * divergence, Scalar(0)), bound);
1133}
1134
1141template <typename P, typename Q, std::uniform_random_bit_generator G>
1142 requires internal::MonteCarloComparable<P, Q, G> && internal::MonteCarloComparable<Q, P, G>
1143[[nodiscard]] MonteCarloEstimate<typename P::Scalar>
1144jensenShannon(MonteCarlo const & method, P const & p, Q const & q, G & rng)
1145{
1146 return jensenShannon(execution::sequential, method, p, q, rng);
1147}
1148
1153template <ExecutionPolicy Policy, typename P, typename Q, std::uniform_random_bit_generator G>
1154 requires internal::MonteCarloComparable<P, Q, G> && internal::MonteCarloComparable<Q, P, G>
1155[[nodiscard]] MonteCarloEstimate<typename P::Scalar>
1156jensenShannon(Policy && policy, MonteCarlo const & method, P const & p, Q const & q, G & rng)
1157{
1158 using Scalar = typename P::Scalar;
1159 auto const fromP = internal::mixtureDivergenceTerms<Scalar>(policy, method, p, q, rng);
1160 auto const fromQ = internal::mixtureDivergenceTerms<Scalar>(policy, method, q, p, rng);
1161 Scalar const standardError =
1162 Scalar(0.5)
1163 * Eigen::numext::sqrt(
1164 fromP.standardError() * fromP.standardError() + fromQ.standardError() * fromQ.standardError()
1165 );
1166 return {Scalar(0.5) * (fromP.mean() + fromQ.mean()), standardError, method.count};
1167}
1168
1172template <typename P, typename Q>
1173[[nodiscard]] internal::NoClosedForm
1174jensenShannon(P const &, Q const &)
1175{
1176 static_assert(
1177 internal::alwaysFalse<P>,
1178 "jensenShannon(p, q) has a closed form only between two Categorical views of one space; between Gaussians "
1179 "or anything else, estimate it with jensenShannon(MonteCarlo{count}, p, q, rng)"
1180 );
1181 return {};
1182}
1183
1195template <typename P, typename Q>
1196 requires internal::GaussianDensitiesOfOneSpace<P, Q>
1197[[nodiscard]] typename P::Scalar
1198wasserstein(P const & p, Q const & q)
1199{
1200 using MatrixType = typename P::MatrixType;
1201 internal::assertSameDimensions(p, q);
1202 typename P::DifferenceVectorType const difference = Emrys::minus(p.space(), p.mean(), q.mean());
1203 MatrixType const first = internal::covarianceRootOf(p);
1204 MatrixType const second = internal::covarianceRootOf(q);
1205 return internal::buresWasserstein<typename P::Scalar>(difference, first, second);
1206}
1207
1215template <typename P, typename Q, typename WeightsDerived>
1216 requires internal::GaussianDensitiesOfOneSpace<P, Q>
1217[[nodiscard]] typename P::Scalar
1218wasserstein(P const & p, Q const & q, Eigen::MatrixBase<WeightsDerived> const & weights)
1219{
1220 using Scalar = typename P::Scalar;
1221 using DensityType = P;
1222 using Measured =
1223 internal::MatrixOf<Scalar, WeightsDerived::RowsAtCompileTime, 1, WeightsDerived::MaxRowsAtCompileTime, 1>;
1224 using Factor = internal::MatrixOf<
1225 Scalar,
1226 WeightsDerived::RowsAtCompileTime,
1227 DensityType::DifferenceDimensionAtCompileTime,
1228 WeightsDerived::MaxRowsAtCompileTime,
1229 DensityType::MaxDifferenceDimensionAtCompileTime>;
1230 internal::assertSameDimensions(p, q);
1231 EMRYS_FILTER_ASSERT(
1232 weights.cols() == p.differenceDimension() && "the weights must have a column per difference coordinate"
1233 );
1234 typename DensityType::DifferenceVectorType const difference = Emrys::minus(p.space(), p.mean(), q.mean());
1235 Measured const measured = internal::multiply(weights, difference);
1236 Factor const first = internal::multiply(
1237 weights,
1238 internal::asTriangular<internal::covarianceRootTriangle<P>>(internal::covarianceRootOf(p))
1239 );
1240 Factor const second = internal::multiply(
1241 weights,
1242 internal::asTriangular<internal::covarianceRootTriangle<Q>>(internal::covarianceRootOf(q))
1243 );
1244 return internal::buresWasserstein<Scalar>(measured, first, second);
1245}
1246
1250template <typename P, typename Q>
1251 requires internal::GaussianDensitiesOfOneSpace<P, Q>
1252[[nodiscard]] typename P::Scalar
1254{
1255 return wasserstein(p, q, metric.matrix());
1256}
1257
1261template <typename P, typename Q>
1262[[nodiscard]] internal::NoClosedForm
1263wasserstein(P const &, Q const &)
1264{
1265 static_assert(
1266 internal::alwaysFalse<P>,
1267 "wasserstein(p, q) has a closed form only between two Gaussian densities of one space, in any of the "
1268 "four forms (call toDensity() on the parameters, and copy() a GaussianMarginal first); between "
1269 "mixtures or particle sets it needs an optimal-transport solver, "
1270 "which the library does not have. expectedSquaredError(p, truth) gives the distance to a point"
1271 );
1272 return {};
1273}
1274
1286template <internal::HasMoments Distribution, typename TruthDerived>
1287[[nodiscard]] auto
1288expectedSquaredError(Distribution const & distribution, Eigen::MatrixBase<TruthDerived> const & truth)
1289{
1290 return internal::fromMoments(distribution, [&](auto const & moments) {
1291 return internal::expectedSquaredErrorOf(distribution.space(), moments, truth);
1292 });
1293}
1294
1300template <internal::HasMoments Distribution, typename TruthDerived, typename WeightsDerived>
1301[[nodiscard]] auto
1303 Distribution const & distribution,
1304 Eigen::MatrixBase<TruthDerived> const & truth,
1305 Eigen::MatrixBase<WeightsDerived> const & weights
1306)
1307{
1308 return internal::fromMoments(distribution, [&](auto const & moments) {
1309 return internal::expectedSquaredErrorOf(distribution.space(), moments, truth, weights);
1310 });
1311}
1312
1316template <internal::HasMoments Distribution, typename TruthDerived>
1317[[nodiscard]] auto
1319 Distribution const & distribution,
1320 Eigen::MatrixBase<TruthDerived> const & truth,
1322)
1323{
1324 return expectedSquaredError(distribution, truth, metric.matrix());
1325}
1326
1327} // 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...
What the four forms of the Gaussian share (decisions.md [gaussian-forms], [form-densities]): the Gaus...
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
The hybrid state of a discrete mode and a component per mode (decisions.md [imm], [component-first]):...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
The mixture: the parameters MixtureParams of weighted components of one type (decisions....
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'...
Product: the product of independent densities, each on its own block of a space, built with on<Path>(...
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...
The square-root form of the Gaussian (decisions.md [gaussian-forms]): its parameters SqrtGaussianPara...
Tag paths and the index utilities (decisions.md [tag-addressing], [index-names]): TagPath and TagPath...
Triangularization: the triangular factor of a stacked matrix by Householder reflections,...
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
The probabilities of a CategoricalParams whose weights define a distribution: a read-only view.
Definition Categorical.h:601
auto logProbabilities() const
Definition Categorical.h:632
Eigen::Index cardinality() const
Definition Categorical.h:623
Scalar logProbability(Eigen::Index value) const
Definition Categorical.h:648
A weighting of a space's differences, built by tag path: the ground metric of wasserstein().
Definition Divergence.h:191
void combine(Scalar factor)
Adds factor times the measured coordinates of the block named by From to those of Into,...
Definition Divergence.h:227
void scale(Scalar factor)
Multiplies the measured coordinates of the block named by Path (a tag or a TagPath) by factor.
Definition Divergence.h:211
typename Moments< Space_, Scalar_ >::MatrixType MatrixType
's type, square over differences
Definition Divergence.h:197
Space_ SpaceType
the space whose differences are measured
Definition Divergence.h:194
Eigen::Index Index
Eigen's index type.
Definition Divergence.h:196
MatrixType const & matrix() const
Definition Divergence.h:244
Scalar_ Scalar
the scalar type
Definition Divergence.h:195
GroundMetric(Space_ space)
The identity on space: each difference measured by its Euclidean norm.
Definition Divergence.h:200
Space_ const & space() const
Definition Divergence.h:251
MomentsType moments() const
Definition Hybrid.h:632
Streaming log-sum-exp: accumulates without overflow.
Definition Weights.h:46
The density of a Mixture whose components have been factorized: a read-only view.
Definition Mixture.h:2090
MomentsType moments() const
Definition Mixture.h:2232
DifferenceMatrix< Space_, Space_, Scalar > MatrixType
a matrix over differences, such as the covariance
Definition Moments.h:76
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
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
Hybrid< Gaussian< Space_, Scalar_ >, ModeSpace_, Allocator_ > HybridGaussian
The Hybrid of Gaussians in covariance form, the spelling for the common case (decisions....
Definition Hybrid.h:742
auto expectedSquaredError(Distribution const &distribution, Eigen::MatrixBase< TruthDerived > const &truth)
Definition Divergence.h:1288
Scalar jensenShannon(Categorical< Space, Scalar > const &p, Categorical< Space, Scalar > const &q)
Definition Divergence.h:1113
P::Scalar bhattacharyya(P const &p, Q const &q)
Definition Divergence.h:944
P::Scalar kullbackLeibler(P const &p, Q const &q)
Definition Divergence.h:678
P::Scalar hellinger(P const &p, Q const &q)
Definition Divergence.h:1042
P::Scalar wasserstein(P const &p, Q const &q)
Definition Divergence.h:1198
CollapseBounds< Scalar > kullbackLeiblerCollapseBounds(GaussianMixture< Space, Scalar, Allocator > const &p)
Definition Divergence.h:867
constexpr Sequential sequential
The default execution policy: plain loops on the calling thread, in index order (Sequential).
Definition Execution.h:128
Sample< std::remove_cvref_t< G > > sample(G &&function)
Definition Policies.h:665
constexpr auto diffIdx()
idx<S, Path>() of the differences of the subspace named by Path: its rows of a covariance.
Definition TagPath.h:669
auto minus(S const &space, Eigen::MatrixBase< ADerived > const &a, Eigen::MatrixBase< BDerived > const &b)
Definition Space.h:715
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
The bracket on the divergence of a distribution from its collapse, which kullbackLeiblerCollapseBoun...
Definition Divergence.h:782
Scalar upper
: the divergence is at most this
Definition Divergence.h:784
Scalar lower
: the divergence is at least this
Definition Divergence.h:783
An estimate from MonteCarlo draws, with its standard error.
Definition Divergence.h:117
Scalar standardError
its standard error, by the delta method for a function of a mean
Definition Divergence.h:119
Scalar value
the estimate
Definition Divergence.h:118
Eigen::Index count
draws taken from each distribution sampled
Definition Divergence.h:120
Method that estimates a divergence from count draws.
Definition Divergence.h:108
Eigen::Index count
draws from each distribution sampled, at least two
Definition Divergence.h:109