MixtureDistribution
A k-component mixture distribution with a (possibly heterogeneous)
kernel per component – the “just the math” parent class. Knows how
to evaluate pdf/cdf, build and use a numeric ppf (inverse CDF), and
save/load that ppf to disk. Doesn’t know or care how it was produced;
see FittedMixtureDistribution for the subclass that adds that.
Attributes
kernels : list[Kernel], length k
One kernel instance per component; may be different types (but
all must agree on .periodic).
params : list[tuple[float, float]], length k
(loc, scale-like) parameters per component, matching
kernels[j].param_names.
weights : np.ndarray, shape (k,)
__post_init__(self):
@property
k(self) -> 'int':
@property
periodic(self) -> 'bool':
component_pdfs(self, x):
Weighted per-component densities at x, shape (*, k). Useful
for plotting each colored component curve individually.
pdf(self, x):
Mixture density at x: sum of weighted per-component densities.
component_cdfs(self, x):
Weighted per-component CDFs at x, shape (*, k).
cdf(self, x):
Mixture CDF at x: sum of weighted per-component CDFs. Always
available in closed form (every kernel supplies .cdf via
scipy), even for kernels/mixtures with no closed-form .ppf.
Note: for a periodic mixture this is itself periodic –
cdf(x) == cdf(x + 2pi) exactly, since they’re the same physical
angle. That’s correct for density/mass questions (and matches
pdf’s periodicity), but it is *not usable as a monotonic
quantile map across a full period – see unwrapped_cdf for
that.
unwrapped_cdf(self, x, grid_size=2000, eps=1e-09, rebuild=False):
A monotonically increasing “unwrapped” CDF across one full
period (0 -> 1 as x sweeps from the period’s start to its end),
for periodic mixtures. Non-periodic mixtures just delegate to
the ordinary .cdf(), which is already monotonic.
Built from the same (q, x) PPF grid used by .ppf() (lazily
built on first call, exactly like .ppf()), inverted the other
way via interpolation – i.e. this and .ppf() are exact
inverses of each other by construction, which .cdf() is not
for a periodic mixture.
This exists for callers that need a proper quantile map spanning
a full period (e.g. truncated-quantile encoding schemes) rather
than the periodic .cdf(), which wraps back to its starting
value before reaching 1.
ppf(self, q, grid_size=2000, eps=1e-09, rebuild=False):
Inverse CDF, via grid-based linear interpolation over precomputed
(q, x) pairs – built lazily on first call (or forced with
rebuild=True), since a mixture’s CDF essentially never has a
closed-form inverse even when every component’s CDF does.
save_ppf_grid(self, path, grid_size=2000, eps=1e-09):
Save the ppf grid to an NPZ file: ppf_grid (q values),
ppf_values (corresponding x values), and metadata (a JSON
string describing each component’s kernel type, params, and
weight – enough to fully reconstruct this mixture via
MixtureDistribution.load_ppf_grid).
@classmethod
load_ppf_grid(cls, path) -> "'MixtureDistribution'":
Reconstruct a MixtureDistribution (kernels, params, weights,
and the precomputed ppf grid) from a file written by
save_ppf_grid. The result supports .pdf/.cdf/.ppf
immediately; .ppf uses the loaded grid without recomputing it.
__repr__(self):
__eq__(self, other):