EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Divergences

How far apart two distributions are. More...

Classes

struct  Emrys::MonteCarlo
 Method that estimates a divergence from count draws. More...
struct  Emrys::MonteCarloEstimate< Scalar >
 An estimate from MonteCarlo draws, with its standard error. More...
class  Emrys::GroundMetric< Space_, Scalar_ >
 A weighting of a space's differences, built by tag path: the ground metric of wasserstein(). More...
struct  Emrys::CollapseBounds< Scalar >
 The bracket \( \max(B - H(\hat w), 0) \le \mathrm{KL}(p \parallel q) \le B \) on the divergence of a distribution from its collapse, which kullbackLeiblerCollapseBounds() returns. More...

Functions

template<typename P, typename Q>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::kullbackLeibler (P const &p, Q const &q)
template<DiscreteSpace Space, typename Scalar>
Scalar Emrys::kullbackLeibler (Categorical< Space, Scalar > const &p, Categorical< Space, Scalar > const &q)
template<typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G>
MonteCarloEstimate< typename P::Scalar > Emrys::kullbackLeibler (MonteCarlo const &method, P const &p, Q const &q, G &rng)
template<ExecutionPolicy Policy, typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G>
MonteCarloEstimate< typename P::Scalar > Emrys::kullbackLeibler (Policy &&policy, MonteCarlo const &method, P const &p, Q const &q, G &rng)
 The MonteCarlo kullbackLeibler() under policy: the draws and \( \log p \) at each are taken as Sequential takes them and stored, and \( \log q \) at each is a for-each over them, so the estimate is Sequential's (see Execution policies).
template<typename Space, typename Scalar, typename Allocator>
CollapseBounds< Scalar > Emrys::kullbackLeiblerCollapseBounds (GaussianMixture< Space, Scalar, Allocator > const &p)
template<typename Space, typename ModeSpace, typename Scalar, typename Allocator>
CollapseBounds< Scalar > Emrys::kullbackLeiblerCollapseBounds (HybridGaussian< Space, ModeSpace, Scalar, Allocator > const &p)
template<typename Space, typename ModeSpace, typename Scalar, typename ComponentAllocator, typename Allocator>
CollapseBounds< Scalar > Emrys::kullbackLeiblerCollapseBounds (Mixture< HybridGaussian< Space, ModeSpace, Scalar, ComponentAllocator >, Allocator > const &p)
template<typename P, typename Q>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::bhattacharyya (P const &p, Q const &q)
template<DiscreteSpace Space, typename Scalar>
Scalar Emrys::bhattacharyya (Categorical< Space, Scalar > const &p, Categorical< Space, Scalar > const &q)
template<typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G>
MonteCarloEstimate< typename P::Scalar > Emrys::bhattacharyya (MonteCarlo const &method, P const &p, Q const &q, G &rng)
template<ExecutionPolicy Policy, typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G>
MonteCarloEstimate< typename P::Scalar > Emrys::bhattacharyya (Policy &&policy, MonteCarlo const &method, P const &p, Q const &q, G &rng)
 The MonteCarlo bhattacharyya() under policy, as the MonteCarlo kullbackLeibler() runs under one.
template<typename P, typename Q>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::hellinger (P const &p, Q const &q)
template<DiscreteSpace Space, typename Scalar>
Scalar Emrys::hellinger (Categorical< Space, Scalar > const &p, Categorical< Space, Scalar > const &q)
template<typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G>
MonteCarloEstimate< typename P::Scalar > Emrys::hellinger (MonteCarlo const &method, P const &p, Q const &q, G &rng)
template<ExecutionPolicy Policy, typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G>
MonteCarloEstimate< typename P::Scalar > Emrys::hellinger (Policy &&policy, MonteCarlo const &method, P const &p, Q const &q, G &rng)
 The MonteCarlo hellinger() under policy, as the MonteCarlo kullbackLeibler() runs under one.
template<DiscreteSpace Space, typename Scalar>
Scalar Emrys::jensenShannon (Categorical< Space, Scalar > const &p, Categorical< Space, Scalar > const &q)
template<typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G> && internal::MonteCarloComparable<Q, P, G>
MonteCarloEstimate< typename P::Scalar > Emrys::jensenShannon (MonteCarlo const &method, P const &p, Q const &q, G &rng)
template<ExecutionPolicy Policy, typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G> && internal::MonteCarloComparable<Q, P, G>
MonteCarloEstimate< typename P::Scalar > Emrys::jensenShannon (Policy &&policy, MonteCarlo const &method, P const &p, Q const &q, G &rng)
 The MonteCarlo jensenShannon() under policy, as the MonteCarlo kullbackLeibler() runs under one.
template<typename P, typename Q>
internal::NoClosedForm Emrys::jensenShannon (P const &, Q const &)
 jensenShannon() for a pair with no closed form: a compile-time error naming the estimator.
template<typename P, typename Q>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::wasserstein (P const &p, Q const &q)
template<typename P, typename Q, typename WeightsDerived>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::wasserstein (P const &p, Q const &q, Eigen::MatrixBase< WeightsDerived > const &weights)
template<typename P, typename Q>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::wasserstein (P const &p, Q const &q, GroundMetric< typename P::SpaceType, typename P::Scalar > const &metric)
 wasserstein() under the ground metric metric (see GroundMetric).
template<internal::HasMoments Distribution, typename TruthDerived>
auto Emrys::expectedSquaredError (Distribution const &distribution, Eigen::MatrixBase< TruthDerived > const &truth)
template<internal::HasMoments Distribution, typename TruthDerived, typename WeightsDerived>
auto Emrys::expectedSquaredError (Distribution const &distribution, Eigen::MatrixBase< TruthDerived > const &truth, Eigen::MatrixBase< WeightsDerived > const &weights)
 expectedSquaredError() under the ground metric \( \|W \delta\| \): \( \|W (\mu \boxminus x^*)\|^2 + \mathrm{tr}(W \Sigma W^T) \), with weights a matrix with a column per difference coordinate.
template<internal::HasMoments Distribution, typename TruthDerived>
auto Emrys::expectedSquaredError (Distribution const &distribution, Eigen::MatrixBase< TruthDerived > const &truth, GroundMetric< typename Distribution::SpaceType, typename Distribution::Scalar > const &metric)
 expectedSquaredError() under the ground metric metric (see GroundMetric).

Detailed Description

How far apart two distributions are.

Each function takes the two distributions in the order the quantity is written, \( D(p \parallel q) \) as f(p, q):

Function Quantity Range Symmetric
kullbackLeibler() \( \mathrm{KL}(p \parallel q) = E_p[\log p - \log q] \) \( [0, \infty] \) no
bhattacharyya() \( D_B = -\log \int \sqrt{p q} \) \( [0, \infty] \) yes
hellinger() \( H = \sqrt{1 - e^{-D_B}} \), a metric \( [0, 1] \) yes
jensenShannon() \( \tfrac12 [\mathrm{KL}(p \| m) + \mathrm{KL}(q \| m)] \) \( [0, \log 2] \) yes
wasserstein() \( W_2 \), the cost of moving \( p \) onto \( q \), a metric \( [0, \infty) \) yes

In jensenShannon(), \( m = \tfrac12 (p + q) \), the equal mixture of the two. Distances are returned as distances, not squared. Logarithms are natural, so the divergences are in nats.

kullbackLeiblerCollapseBounds() takes one distribution, a Gaussian mixture, a HybridGaussian, or a mixture of them, and brackets its kullbackLeibler() divergence from its moment-matched Gaussian in closed form: how much a report of its mean and covariance leaves out.

Closed forms exist between two Gaussian densities of one space (all but Jensen-Shannon, whose mixture has no closed-form entropy) and between two Categorical distributions (all but Wasserstein). The Gaussian densities are what toDensity() returns in any of the four forms, Gaussian, SqrtGaussian, InfoGaussian, and SqrtInfoGaussian, in any pair, so a square-root filter's state is compared with a covariance-form reference as it is. They take densities because they read a triangular root of each covariance, the cached Cholesky factor, the stored root, or the inverse of the precision's root, and the log-determinants; a GaussianMarginal is parameters, so copy() it first. Each result is clamped at zero, so rounding never gives a negative divergence; a distribution against itself gives exactly zero, except that wasserstein() gives about \( 10^{-8} \), the square root of a rounding residue in \( W_2^2 \). Between categoricals the Bhattacharyya coefficient is summed in log space, so one that underflows gives \( +\infty \).

Any other pair is estimated by passing a MonteCarlo method first, as transform() takes its method. A call without one for a pair that has no closed form fails to compile, with one error, a message that names the estimator. A ParticleSet has no density, so it is compared through its toDensity(), and the result is that kernel density's.

On a manifold, the closed forms compare covariances that live in tangent spaces at different means, without transporting one to the other: the approximation that the tangent-space mean of a mixture makes. It is accurate while the means are close, as two headings on either side of the seam are.

The divergences are unchanged by any invertible map applied to both distributions, so a weighting of the coordinates means nothing to them. Wasserstein, which measures in the space's units, takes one: see GroundMetric and expectedSquaredError().

The quantities are Kullback and Leibler (1951), Bhattacharyya (1943), the Hellinger distance, the Jensen-Shannon divergence of Lin (1991), and the Wasserstein metric; each function's comment gives the closed form it uses and where that comes from.

Function Documentation

◆ bhattacharyya() [1/3]

template<DiscreteSpace Space, typename Scalar>
Scalar Emrys::bhattacharyya ( Categorical< Space, Scalar > const & p,
Categorical< Space, Scalar > const & q )
nodiscard
Returns
the Bhattacharyya distance \( -\log \sum_i \sqrt{p_i q_i} \) between two categorical distributions: \( +\infty \) if they share no value.

◆ bhattacharyya() [2/3]

template<typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G>
MonteCarloEstimate< typename P::Scalar > Emrys::bhattacharyya ( MonteCarlo const & method,
P const & p,
Q const & q,
G & rng )
nodiscard
Returns
a MonteCarlo estimate of the Bhattacharyya distance: minus the log of the mean of \( \sqrt{q(x) / p(x)} \) over draws from p, which estimates \( \int \sqrt{p q} \). See kullbackLeibler(MonteCarlo const &, P const &, Q const &, G &) for what p and q may be.

◆ bhattacharyya() [3/3]

template<typename P, typename Q>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::bhattacharyya ( P const & p,
Q const & q )
nodiscard
Returns
the Bhattacharyya distance between two Gaussians,

\[ D_B = \tfrac18 (\mu_p \boxminus \mu_q)^T \Sigma^{-1} (\mu_p \boxminus \mu_q) + \tfrac12 \log \frac{|\Sigma|}{\sqrt{|\Sigma_p| |\Sigma_q|}}, \qquad \Sigma = \tfrac12 (\Sigma_p + \Sigma_q). \]

The first term measures how far apart the means are, the second how different the covariances are: the closed form for two Gaussians.

p and q are densities of one space in any of the four forms, in any pair, as in kullbackLeibler(). Between two Gaussians the average covariance is factorized; otherwise its root is one triangularization of \( [\,A_p \;\; A_q\,] / \sqrt 2 \), with \( A \) the roots that kullbackLeibler() reads, so no covariance is formed.

◆ expectedSquaredError()

template<internal::HasMoments Distribution, typename TruthDerived>
auto Emrys::expectedSquaredError ( Distribution const & distribution,
Eigen::MatrixBase< TruthDerived > const & truth )
nodiscard
Returns
the expected squared error of distribution about truth, a value of its space: \( E_p \|x \boxminus x^*\|^2 = \|\mu \boxminus x^*\|^2 + \mathrm{tr}\,\Sigma \), which is \( W_2^2 \) between \( p \) and a point mass at \( x^* \). It needs only moments(), so it is exact for any distribution that has them: a Gaussian, a mixture, a kernel density, a particle set, a hybrid state. Where moments() returns a std::expected, as a ParticleSet's does, so does this, with the same error.

Moments in a space that is not linear are tangent-space moments, so on a manifold this is the expected squared error to first order.

◆ hellinger() [1/3]

template<DiscreteSpace Space, typename Scalar>
Scalar Emrys::hellinger ( Categorical< Space, Scalar > const & p,
Categorical< Space, Scalar > const & q )
nodiscard
Returns
the Hellinger distance \( \sqrt{1 - \sum_i \sqrt{p_i q_i}} \) between two categorical distributions: 1 if they share no value.

◆ hellinger() [2/3]

template<typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G>
MonteCarloEstimate< typename P::Scalar > Emrys::hellinger ( MonteCarlo const & method,
P const & p,
Q const & q,
G & rng )
nodiscard
Returns
a MonteCarlo estimate of the Hellinger distance, from the same draws as bhattacharyya(MonteCarlo const &, P const &, Q const &, G &).

◆ hellinger() [3/3]

template<typename P, typename Q>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::hellinger ( P const & p,
Q const & q )
nodiscard
Returns
the Hellinger distance \( \sqrt{1 - e^{-D_B}} \) between two Gaussians, with \( D_B \) their bhattacharyya() distance: a metric, bounded by 1. p and q are densities of one space in any of the four forms, in any pair, as in kullbackLeibler().

◆ jensenShannon() [1/2]

template<DiscreteSpace Space, typename Scalar>
Scalar Emrys::jensenShannon ( Categorical< Space, Scalar > const & p,
Categorical< Space, Scalar > const & q )
nodiscard
Returns
the Jensen-Shannon divergence between two categorical distributions, \( \tfrac12 \mathrm{KL}(p \parallel m) + \tfrac12 \mathrm{KL}(q \parallel m) \) with \( m = \tfrac12 (p + q) \): \( \log 2 \) if they share no value. Its square root is a metric.

◆ jensenShannon() [2/2]

template<typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G> && internal::MonteCarloComparable<Q, P, G>
MonteCarloEstimate< typename P::Scalar > Emrys::jensenShannon ( MonteCarlo const & method,
P const & p,
Q const & q,
G & rng )
nodiscard
Returns
a MonteCarlo estimate of the Jensen-Shannon divergence: count draws from each of p and q, each evaluated under both. Both must be anything drawSampleWithLogDensity() takes, and both must be a density (HasLogDensity). This is the only form for two Gaussians, whose even mixture has no closed-form entropy.

◆ kullbackLeibler() [1/4]

template<DiscreteSpace Space, typename Scalar>
Scalar Emrys::kullbackLeibler ( Categorical< Space, Scalar > const & p,
Categorical< Space, Scalar > const & q )
nodiscard
Returns
\( \mathrm{KL}(p \parallel q) = \sum_i p_i (\log p_i - \log q_i) \) between two categorical distributions: \( +\infty \) where \( q \) gives zero probability to a value that \( p \) doesn't.

◆ kullbackLeibler() [2/4]

template<typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G>
MonteCarloEstimate< typename P::Scalar > Emrys::kullbackLeibler ( MonteCarlo const & method,
P const & p,
Q const & q,
G & rng )
nodiscard
Returns
a MonteCarlo estimate of \( \mathrm{KL}(p \parallel q) \): the mean of \( \log p(x) - \log q(x) \) over draws from p. p is anything drawSampleWithLogDensity() takes, such as a GaussianMixture or a KernelDensity, and q any density (HasLogDensity) of the same space. The estimate is \( +\infty \) if q is zero at a draw. The draws are taken one at a time and never stored, so a bounded space allocates nothing [allocation].

◆ kullbackLeibler() [3/4]

template<typename P, typename Q>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::kullbackLeibler ( P const & p,
Q const & q )
nodiscard
Returns
\( \mathrm{KL}(p \parallel q) = \tfrac12 \left[ \mathrm{tr}(\Sigma_q^{-1} \Sigma_p) + (\mu_p \boxminus \mu_q)^T \Sigma_q^{-1} (\mu_p \boxminus \mu_q) - d + \log|\Sigma_q| - \log|\Sigma_p| \right] \) between two Gaussians, with \( d \) the difference dimension: the closed form for two multivariate normals. The trace is \( \|A_q^{-1} A_p\|_F^2 \), by a triangular solve with roots \( A \) of the covariances, so no covariance is formed. p and q are densities of one space in any of the four forms, Gaussian, SqrtGaussian, InfoGaussian, or SqrtInfoGaussian, in any pair: the root is the cached Cholesky factor of a Gaussian, the stored root of a SqrtGaussian, and the inverse of the precision's root in the information forms.

◆ kullbackLeibler() [4/4]

template<ExecutionPolicy Policy, typename P, typename Q, std::uniform_random_bit_generator G>
requires internal::MonteCarloComparable<P, Q, G>
MonteCarloEstimate< typename P::Scalar > Emrys::kullbackLeibler ( Policy && policy,
MonteCarlo const & method,
P const & p,
Q const & q,
G & rng )
nodiscard

The MonteCarlo kullbackLeibler() under policy: the draws and \( \log p \) at each are taken as Sequential takes them and stored, and \( \log q \) at each is a for-each over them, so the estimate is Sequential's (see Execution policies).

The same form serves bhattacharyya(), hellinger(), and jensenShannon().

◆ kullbackLeiblerCollapseBounds() [1/3]

template<typename Space, typename Scalar, typename Allocator>
CollapseBounds< Scalar > Emrys::kullbackLeiblerCollapseBounds ( GaussianMixture< Space, Scalar, Allocator > const & p)
nodiscard
Returns
the bracket on \( \mathrm{KL}(p \parallel q) \) from the Gaussian mixture p to its collapse \( q \), the Gaussian with p's moments() that Mixture::collapsed() makes:

\[ \max\big(B - H(\hat w), 0\big) \le \mathrm{KL}(p \parallel q) \le B, \qquad B = \tfrac12 \Big[ \log|\Sigma| - \sum_i \hat w_i \log|\Sigma_i| \Big], \]

with \( \Sigma \) the mixture's covariance, \( \Sigma_i \) the components', \( \hat w_i \) the normalized weights, and \( H(\hat w) = -\sum_i \hat w_i \log \hat w_i \) their entropy, at most \( \log n \) for \( n \) components.

It says whether the mixture's mean and covariance, the ellipse a report draws, describe it. The upper end is zero when every component is the same Gaussian and grows as the components spread apart; by Pinsker's inequality, no probability read off \( q \), such as an ellipse's coverage, differs from the mixture's by more than \( \sqrt{B / 2} \). The lower end says how far from Gaussian the mixture is at least, so a monitor can tell a mixture far from its collapse from one that the upper end only fails to vouch for. The two are at most \( H(\hat w) \) apart, and the divergence approaches the lower end as the components separate. The upper end depends only on the distribution, but the lower end depends on how it is written: splitting a component into copies of itself raises \( H(\hat w) \) and so lowers the lower end, so prune or merge copies before reading it. The upper end is loose while the components overlap: for two equal unit-variance components at \( \pm 1 \), \( B = 0.35 \) where the divergence is 0.009.

A merge of two components leaves the mixture's covariance unchanged and replaces two terms of \( \sum_i \hat w_i \log|\Sigma_i| \) by one, so it lowers \( B \) by exactly its Runnalls cost: the upper end before a reduction less the upper end after it is what the reduction threw away, and \( B \) is the total cost of merging down to one component, in any order.

Since \( \log q \) is quadratic and \( q \) has \( p \)'s moments, \( \mathrm{KL}(p \parallel q) = H(q) - H(p) \), a difference of entropies. Conditioning on which component drew a value cannot raise its entropy, so \( H(p) \ge \sum_i \hat w_i H(g_i) \), which gives \( B \). Nor can the value's entropy exceed that of the pair, so \( H(p) \le \sum_i \hat w_i H(g_i) + H(\hat w) \), the upper bound of Huber et al. (2008), which gives the lower end. \( B \) is the cost Runnalls (2007) charges for merging two components, as MixtureParams::merged() says, taken over all of them at once.

It takes the view for the components' cached log-determinants, factorizes only \( \Sigma \), and allocates nothing on a bounded space. On a manifold, \( \Sigma \) is a tangent-space covariance, as moments() computes it, so the bracket holds to the same approximation as the closed forms.

See also
kullbackLeibler(MonteCarlo const &, P const &, Q const &, G &), which estimates the divergence itself.

◆ kullbackLeiblerCollapseBounds() [2/3]

template<typename Space, typename ModeSpace, typename Scalar, typename Allocator>
CollapseBounds< Scalar > Emrys::kullbackLeiblerCollapseBounds ( HybridGaussian< Space, ModeSpace, Scalar, Allocator > const & p)
nodiscard
Returns
the bracket on \( \mathrm{KL}(p \parallel q) \) from the hybrid state p, read over its continuous state, to the Gaussian \( q \) that Hybrid::collapsed() makes: the bracket of the mixture of its conditionals weighted by the mode probabilities (see kullbackLeiblerCollapseBounds(GaussianMixture const &)). It checks whether an ImmFilter's report of its state as one Gaussian describes the state.

◆ kullbackLeiblerCollapseBounds() [3/3]

template<typename Space, typename ModeSpace, typename Scalar, typename ComponentAllocator, typename Allocator>
CollapseBounds< Scalar > Emrys::kullbackLeiblerCollapseBounds ( Mixture< HybridGaussian< Space, ModeSpace, Scalar, ComponentAllocator >, Allocator > const & p)
nodiscard
Returns
the bracket on \( \mathrm{KL}(p \parallel q) \) from the mixture of hybrid states p, read over its continuous state, to the Gaussian \( q \) with its moments, which p.collapsed().collapsed() makes: the bracket of the mixture of every component's conditionals, each weighted by its component's weight times its mode's probability (see kullbackLeiblerCollapseBounds(GaussianMixture const &)). It checks whether a mixture of IMM states is described by one Gaussian.

◆ wasserstein() [1/2]

template<typename P, typename Q>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::wasserstein ( P const & p,
Q const & q )
nodiscard
Returns
the 2-Wasserstein distance between two Gaussians,

\[ W_2^2 = \|\mu_p \boxminus \mu_q\|^2 + \mathrm{tr}\,\Sigma_p + \mathrm{tr}\,\Sigma_q - 2 \, \mathrm{tr}\big( (\Sigma_q^{1/2} \Sigma_p \Sigma_q^{1/2})^{1/2} \big), \]

measured in the space's own units. The last trace is the nuclear norm of \( A_p^T A_q \), with \( A \) the roots of the covariances that kullbackLeibler() reads, so it takes one singular-value decomposition and no matrix square root. p and q are densities of one space in any of the four forms, in any pair, as in kullbackLeibler().
See also
GroundMetric, under which the overload that takes one measures

◆ wasserstein() [2/2]

template<typename P, typename Q, typename WeightsDerived>
requires internal::GaussianDensitiesOfOneSpace<P, Q>
P::Scalar Emrys::wasserstein ( P const & p,
Q const & q,
Eigen::MatrixBase< WeightsDerived > const & weights )
nodiscard
Returns
the 2-Wasserstein distance between two Gaussians under the ground metric \( \|W \delta\| \): their distance after the map \( W \), with a column per difference coordinate and any number of rows. A \( W \) of fewer rows, or with rows of zeros, measures only the combinations it names, so two different distributions can be at distance zero: under a singular \( W \) this is a pseudometric, and a threshold at zero does not tell them apart.