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): 

Feedback

Examples

Templates

Documentation