diff --git a/preliz/__init__.py b/preliz/__init__.py
index d089553c..c4c67eb6 100644
--- a/preliz/__init__.py
+++ b/preliz/__init__.py
@@ -11,6 +11,7 @@
from preliz.distributions import *
from preliz.distributions.plot import plot
+from preliz.distributions.catalog import catalog
from preliz.predictive import *
from preliz.ppls import *
from preliz.unidimensional import *
diff --git a/preliz/distributions/__init__.py b/preliz/distributions/__init__.py
index 7138cd42..ca303a44 100644
--- a/preliz/distributions/__init__.py
+++ b/preliz/distributions/__init__.py
@@ -101,13 +101,11 @@
all_continuous_multivariate = [Dirichlet, MvNormal]
+all_modifiers = [Mixture, Truncated, Censored, Hurdle]
__all__ = ( # noqa: PLE0604
[s.__name__ for s in all_continuous]
+ [s.__name__ for s in all_discrete]
+ [s.__name__ for s in all_continuous_multivariate]
- + [Mixture.__name__]
- + [Truncated.__name__]
- + [Censored.__name__]
- + [Hurdle.__name__]
+ + [s.__name__ for s in all_modifiers]
)
diff --git a/preliz/distributions/asymmetric_laplace.py b/preliz/distributions/asymmetric_laplace.py
index b9b37eae..099f7677 100644
--- a/preliz/distributions/asymmetric_laplace.py
+++ b/preliz/distributions/asymmetric_laplace.py
@@ -62,6 +62,8 @@ class AsymmetricLaplace(Continuous):
Symmetry parameter (0 < q < 1).
"""
+ parametrizations = [("kappa", "mu", "b"), ("q", "mu", "b")]
+
def __init__(self, kappa=None, mu=None, b=None, q=None):
super().__init__()
self.support = (-pt.inf, pt.inf)
diff --git a/preliz/distributions/bernoulli.py b/preliz/distributions/bernoulli.py
index 087f4209..967f6bf3 100644
--- a/preliz/distributions/bernoulli.py
+++ b/preliz/distributions/bernoulli.py
@@ -47,6 +47,8 @@ class Bernoulli(Discrete):
Alternative log odds for the probability of success.
"""
+ parametrizations = [("p",), ("logit_p",)]
+
def __init__(self, p=None, logit_p=None):
super().__init__()
self.support = (0, 1)
diff --git a/preliz/distributions/beta.py b/preliz/distributions/beta.py
index 2e24f248..1cc20433 100644
--- a/preliz/distributions/beta.py
+++ b/preliz/distributions/beta.py
@@ -70,6 +70,8 @@ class Beta(Continuous):
concentration > 0
"""
+ parametrizations = [("alpha", "beta"), ("mu", "sigma"), ("mu", "nu")]
+
def __init__(self, alpha=None, beta=None, mu=None, sigma=None, nu=None):
super().__init__()
self.support = (0, 1)
diff --git a/preliz/distributions/catalog.py b/preliz/distributions/catalog.py
new file mode 100644
index 00000000..0e338c42
--- /dev/null
+++ b/preliz/distributions/catalog.py
@@ -0,0 +1,403 @@
+"""Distribution catalog for exploring and querying PreliZ distributions."""
+
+from sys import modules
+
+import numpy as np
+
+from preliz.distributions import (
+ all_continuous,
+ all_continuous_multivariate,
+ all_discrete,
+ all_modifiers,
+)
+from preliz.distributions.distributions import _format_support
+
+_GROUPS = {
+ "continuous": all_continuous,
+ "discrete": all_discrete,
+ "continuous_multivariate": all_continuous_multivariate,
+ "unbounded": [
+ "AsymmetricLaplace",
+ "Cauchy",
+ "ExGaussian",
+ "Gumbel",
+ "Laplace",
+ "Logistic",
+ "Moyal",
+ "Normal",
+ "SkewNormal",
+ "SkewStudentT",
+ "StudentT",
+ ],
+ "positive": [
+ "ChiSquared",
+ "Exponential",
+ "Gamma",
+ "HalfCauchy",
+ "HalfNormal",
+ "HalfStudentT",
+ "InverseGamma",
+ "LogLogistic",
+ "LogNormal",
+ "Pareto",
+ "Rice",
+ "Wald",
+ "Weibull",
+ ],
+ "bounded": [
+ "Beta",
+ "BetaScaled",
+ "Kumaraswamy",
+ "LogitNormal",
+ "Triangular",
+ "TruncatedNormal",
+ "Uniform",
+ "VonMises",
+ ],
+ "non_negative_continuous": [
+ "ChiSquared",
+ "Exponential",
+ "Gamma",
+ "HalfCauchy",
+ "HalfNormal",
+ "HalfStudentT",
+ "InverseGamma",
+ "LogLogistic",
+ "LogitNormal",
+ "LogNormal",
+ "Pareto",
+ "Rice",
+ "ScaledInverseChiSquared",
+ "Wald",
+ "Weibull",
+ ],
+ "non_negative_discrete": [
+ "Bernoulli",
+ "Binomial",
+ "DiscreteWeibull",
+ "Geometric",
+ "NegativeBinomial",
+ "Poisson",
+ "ZeroInflatedBinomial",
+ "ZeroInflatedNegativeBinomial",
+ "ZeroInflatedPoisson",
+ ],
+ "bounded_discrete": [
+ "BetaBinomial",
+ "Binomial",
+ "Categorical",
+ "DiscreteUniform",
+ "Hypergeometric",
+ "ZeroInflatedBinomial",
+ ],
+ "multivariate": [
+ "Dirichlet",
+ "MultivariateNormal",
+ ],
+ "symmetric": [
+ "Beta",
+ "BetaScaled",
+ "Cauchy",
+ "DiscreteUniform",
+ "Laplace",
+ "Logistic",
+ "MultivariateNormal",
+ "Normal",
+ "StudentT",
+ "Uniform",
+ "VonMises",
+ ],
+ "asymmetric": [
+ "AsymmetricLaplace",
+ "DiscreteWeibull",
+ "ExGaussian",
+ "Exponential",
+ "Gamma",
+ "Geometric",
+ "Gumbel",
+ "InverseGamma",
+ "Kumaraswamy",
+ "LogLogistic",
+ "LogitNormal",
+ "Moyal",
+ "Pareto",
+ "Rice",
+ "ScaledInverseChiSquared",
+ "SkewNormal",
+ "SkewStudentT",
+ "Wald",
+ "Weibull",
+ ],
+ "heavy_tailed": [
+ "Cauchy",
+ "HalfCauchy",
+ "HalfStudentT",
+ "InverseGamma",
+ "LogLogistic",
+ "LogNormal",
+ "Pareto",
+ "SkewStudentT",
+ "StudentT",
+ ],
+ "light_tailed": [
+ "AsymmetricLaplace",
+ "ChiSquared",
+ "ExGaussian",
+ "Exponential",
+ "Gamma",
+ "HalfNormal",
+ "Laplace",
+ "Logistic",
+ "Moyal",
+ "Normal",
+ "Rice",
+ "SkewNormal",
+ "Triangular",
+ "TruncatedNormal",
+ "Wald",
+ "Weibull",
+ ],
+ "zero_inflated": [
+ "ZeroInflatedBinomial",
+ "ZeroInflatedNegativeBinomial",
+ "ZeroInflatedPoisson",
+ ],
+ "extreme_value": [
+ "Gumbel",
+ "LogLogistic",
+ ],
+ "circular": [
+ "VonMises",
+ ],
+ "binary": [
+ "Bernoulli",
+ "Binomial",
+ ],
+ "count": [
+ "DiscreteWeibull",
+ "Geometric",
+ "NegativeBinomial",
+ "Poisson",
+ "ZeroInflatedNegativeBinomial",
+ "ZeroInflatedPoisson",
+ ],
+}
+
+
+def _get_dist_class(name):
+ return getattr(modules["preliz.distributions"], name)
+
+
+class DistributionCatalog:
+ """Registry for accessing PreliZ distributions.
+
+ Provides methods to list, filter, and inspect distributions.
+
+ Examples
+ --------
+ List all distributions::
+
+ >>> pz.catalog
+
+ Get instances by category::
+
+ >>> pz.catalog.get("continuous")
+ >>> pz.catalog.get("positive")
+
+ Get distribution names::
+
+ >>> pz.catalog.get(output="names")
+ >>> pz.catalog.get("discrete", output="names")
+
+ Get info about a specific distribution::
+
+ >>> pz.catalog.info("Gamma")
+
+ Filter distributions by properties::
+
+ >>> pz.catalog.find(kind="continuous", num_params=2)
+ """
+
+ def __repr__(self):
+ continuous_names = [d.__name__ for d in all_continuous]
+ discrete_names = [d.__name__ for d in all_discrete]
+ multivariate_names = [d.__name__ for d in all_continuous_multivariate]
+ modifiers = [d.__name__ for d in all_modifiers]
+
+ lines = ["PreliZ Distributions", "=" * 50]
+ lines.append(f"Continuous ({len(continuous_names)}):")
+ lines.append(" " + ", ".join(continuous_names))
+ lines.append(f"\nDiscrete ({len(discrete_names)}):")
+ lines.append(" " + ", ".join(discrete_names))
+ lines.append(f"\nMultivariate ({len(multivariate_names)}):")
+ lines.append(" " + ", ".join(multivariate_names))
+ lines.append(f"\nModifiers ({len(modifiers)}):")
+ lines.append(" " + ", ".join(modifiers))
+ return "\n".join(lines)
+
+ def _repr_html_(self):
+ continuous_names = [d.__name__ for d in all_continuous]
+ discrete_names = [d.__name__ for d in all_discrete]
+ multivariate_names = [d.__name__ for d in all_continuous_multivariate]
+ modifiers = [d.__name__ for d in all_modifiers]
+
+ html = ["
"]
+ html.append("PreliZ Distributions
")
+ html.append(f"Continuous ({len(continuous_names)}): ")
+ html.append(", ".join(continuous_names) + "
")
+ html.append(f"Discrete ({len(discrete_names)}): ")
+ html.append(", ".join(discrete_names) + "
")
+ html.append(f"Multivariate ({len(multivariate_names)}): ")
+ html.append(", ".join(multivariate_names) + "
")
+ html.append(f"Modifiers ({len(modifiers)}): ")
+ html.append(", ".join(modifiers))
+ html.append("
")
+
+ return "".join(html)
+
+ def get(self, category="continuous", output="instances"):
+ """Return a list of uninitialized PreliZ distribution instances by category.
+
+ Parameters
+ ----------
+ category : str
+ Category of distributions to return. One of:
+ - ``"continuous"``: All univariate continuous distributions.
+ - ``"discrete"``: All discrete distributions.
+ - ``"continuous_multivariate"``: All continuous multivariate distributions.
+ - ``"positive"``: Continuous distributions on the positive reals.
+ - ``"unbounded"``: Continuous distributions on the full real line.
+ - ``"bounded"``: Continuous distributions on a finite interval.
+ - ``"non_negative"``: All non-negative distributions.
+ - ``"non_negative_continuous"``: Continuous distributions on [0, inf).
+ - ``"non_negative_discrete"``: Discrete distributions on {0, 1, 2, ...}.
+ - ``"bounded_discrete"``: Discrete distributions on a finite interval.
+ - ``"multivariate"``: All multivariate distributions.
+ - ``"symmetric"``: Distributions that are symmetric.
+ - ``"asymmetric"``: Distributions with skewed shapes.
+ - ``"heavy_tailed"``: Distributions with slowly decaying tails.
+ - ``"light_tailed"``: Distributions with quickly decaying tails.
+ - ``"zero_inflated"``: Discrete distributions with extra zeros.
+ - ``"extreme_value"``: Distributions for extreme events.
+ - ``"circular"``: Distributions on a circular domain.
+ - ``"binary"``: Distributions for binary outcomes.
+ - ``"count"``: Discrete distributions for count data.
+
+ output : str
+ Whether to return distribution instances ("instances") or names ("names").
+ Defaults to "instances".
+
+ Returns
+ -------
+ list of PreliZ distribution instances
+ """
+ if output not in ["instances", "names"]:
+ raise ValueError("Invalid value for 'output'. Must be 'instances' or 'names'.")
+
+ if output == "instances":
+ group = _GROUPS.get(category)
+ if group is None:
+ raise ValueError(
+ f"Unknown category '{category}'. "
+ f"Must be one of: {', '.join(repr(k) for k in _GROUPS)}"
+ )
+ if category in ("continuous", "discrete", "continuous_multivariate"):
+ return [d() for d in group]
+ return [_get_dist_class(name)() for name in group]
+
+ else:
+ group = _GROUPS.get(category)
+ if group is None:
+ raise ValueError(
+ f"Unknown category '{category}'. "
+ f"Must be one of: {', '.join(repr(k) for k in _GROUPS)}"
+ )
+ if category in ("continuous", "discrete", "continuous_multivariate"):
+ return [d.__name__ for d in group]
+ return list(group)
+
+ def info(self, name):
+ """Return metadata about a distribution.
+
+ Parameters
+ ----------
+ name : str
+ Name of the distribution (e.g., "Gamma", "Normal").
+
+ Returns
+ -------
+ dict
+ Dictionary with keys: name, kind, param_names, params_support, support,
+ parametrizations.
+ """
+ dist_cls = _get_dist_class(name)
+ dist = dist_cls()
+ result = {
+ "name": name,
+ "kind": dist.kind,
+ "param_names": dist.param_names,
+ "params_support": _format_support(dist.params_support),
+ "support": _format_support(dist.support),
+ }
+ parametrizations = getattr(dist_cls, "parametrizations", None)
+ if parametrizations is not None:
+ result["parametrizations"] = parametrizations
+ return result
+
+ def find(self, kind=None, num_params=None, support=None):
+ """Find distributions matching given criteria.
+
+ Parameters
+ ----------
+ kind : str, optional
+ Filter by kind: "continuous", "discrete".
+ num_params : int, optional
+ Filter by number of parameters.
+ support : str, optional
+ Filter by support type: "positive", "bounded", "unbounded",
+ "non_negative".
+
+ Returns
+ -------
+ list of PreliZ distribution instances
+ """
+ results = []
+
+ if kind == "continuous":
+ candidates = all_continuous
+ elif kind == "discrete":
+ candidates = all_discrete
+ else:
+ candidates = all_continuous + all_discrete
+
+ for dist_cls in candidates:
+ dist = dist_cls()
+
+ if num_params is not None and len(dist.param_names) != num_params:
+ continue
+
+ if support is not None:
+ if not self._matches_support(dist, support):
+ continue
+
+ results.append(dist)
+
+ return results
+
+ @staticmethod
+ def _matches_support(dist, support_type):
+ lower, upper = dist.support
+ if lower is None or upper is None:
+ return False
+ if support_type == "positive":
+ return lower >= 0 and upper == np.inf
+ elif support_type == "bounded":
+ return lower != -np.inf and upper != np.inf
+ elif support_type == "unbounded":
+ return lower == -np.inf and upper == np.inf
+ elif support_type == "non_negative":
+ return lower >= 0
+ return True
+
+
+catalog = DistributionCatalog()
diff --git a/preliz/distributions/categorical.py b/preliz/distributions/categorical.py
index 2529bcb0..0ae6b90f 100644
--- a/preliz/distributions/categorical.py
+++ b/preliz/distributions/categorical.py
@@ -37,8 +37,11 @@ class Categorical(Discrete):
Alternative log odds for the probability of success.
"""
+ parametrizations = [("p",), ("logit_p",)]
+
def __init__(self, p=None, logit_p=None):
super().__init__()
+ self.support = (None, None)
self._parametrization(p, logit_p)
def _parametrization(self, p=None, logit_p=None):
diff --git a/preliz/distributions/distributions.py b/preliz/distributions/distributions.py
index baae5aab..ccc807e0 100644
--- a/preliz/distributions/distributions.py
+++ b/preliz/distributions/distributions.py
@@ -71,10 +71,47 @@ def __repr__(self):
def _repr_html_(self):
name = self._get_name()
+
if self.is_frozen:
desc = self._get_description()
- return f"{name}({desc})"
- return f"{name}"
+ summary = f"{name}({desc})"
+ else:
+ summary = f"{name}"
+
+ td_label = "style='text-align:right;padding-right:10px'"
+ rows = []
+ rows.append(f"| Kind | {self.kind} |
")
+
+ support = _format_support(self.support)
+ if isinstance(support, tuple):
+ support_str = f"({support[0]}, {support[1]})"
+ else:
+ support_str = str(support)
+ rows.append(f"| Support | {support_str} |
")
+
+ if self.is_frozen:
+ try:
+ rows.append(f"| Mean | {self.mean():.2g} |
")
+ except Exception:
+ pass
+ try:
+ rows.append(f"| Std | {self.std():.2g} |
")
+ except Exception:
+ pass
+
+ parametrizations = getattr(self.__class__, "parametrizations", None)
+ if parametrizations:
+ params_str = ", ".join(f"({', '.join(p)})" for p in parametrizations)
+ rows.append(
+ f"| Parametrizations | {params_str} |
"
+ )
+
+ html = f"{summary}
"
+ html += ""
+ html += "".join(rows)
+ html += "
"
+
+ return html
@property
def params_dict(self):
@@ -83,6 +120,36 @@ def params_dict(self):
else:
return None
+ def info(self):
+ """Return metadata about this distribution.
+
+ Returns
+ -------
+ dict
+ Dictionary with keys: name, kind, param_names, params, params_support,
+ support, is_frozen, parametrizations.
+ """
+ result = {
+ "name": self._get_name(),
+ "kind": self.kind,
+ "param_names": self.param_names,
+ "params_support": _format_support(self.params_support),
+ "support": _format_support(self.support),
+ }
+ if self.is_frozen:
+ result["params"] = self.params
+
+ parametrizations = getattr(self.__class__, "parametrizations", None)
+ if parametrizations is not None:
+ result["parametrizations"] = parametrizations
+ if self.is_frozen:
+ result["parametrization_values"] = {
+ params: tuple(getattr(self, name) for name in params)
+ for params in parametrizations
+ }
+
+ return result
+
def summary(self, mass=None, interval=None, fmt=".2f"):
"""
Namedtuple with the mean, median, sd, and lower and upper bounds.
@@ -1122,3 +1189,19 @@ def _discrete_xvals(lower_ep, upper_ep, n_points):
x_vals = np.linspace(lower_ep, upper_ep + 1, n_points, dtype=int)
return x_vals
+
+
+def _format_support_value(value):
+ if value == np.inf:
+ return "inf"
+ if value == -np.inf:
+ return "-inf"
+ if isinstance(value, float | np.floating) and 0 < abs(value) < 1e-10:
+ return 0
+ return value
+
+
+def _format_support(values):
+ if isinstance(values, tuple | list):
+ return tuple(_format_support(v) for v in values)
+ return _format_support_value(values)
diff --git a/preliz/distributions/exponential.py b/preliz/distributions/exponential.py
index 1c4ea164..27cf356c 100644
--- a/preliz/distributions/exponential.py
+++ b/preliz/distributions/exponential.py
@@ -48,6 +48,8 @@ class Exponential(Continuous):
Scale (scale > 0).
"""
+ parametrizations = [("lam",), ("scale",)]
+
def __init__(self, lam=None, scale=None):
super().__init__()
self.support = (0, np.inf)
diff --git a/preliz/distributions/gamma.py b/preliz/distributions/gamma.py
index 7fd10f20..67ad7092 100644
--- a/preliz/distributions/gamma.py
+++ b/preliz/distributions/gamma.py
@@ -65,6 +65,8 @@ class Gamma(Continuous):
"""
+ parametrizations = [("alpha", "beta"), ("mu", "sigma")]
+
def __init__(self, alpha=None, beta=None, mu=None, sigma=None):
super().__init__()
self.support = (0, np.inf)
diff --git a/preliz/distributions/halfnormal.py b/preliz/distributions/halfnormal.py
index db7aab25..86fb6da8 100644
--- a/preliz/distributions/halfnormal.py
+++ b/preliz/distributions/halfnormal.py
@@ -56,6 +56,8 @@ class HalfNormal(Continuous):
Precision :math:`\tau` (``tau`` > 0).
"""
+ parametrizations = [("sigma",), ("tau",)]
+
def __init__(self, sigma=None, tau=None):
super().__init__()
self.support = (0, np.inf)
diff --git a/preliz/distributions/halfstudentt.py b/preliz/distributions/halfstudentt.py
index 8b42665a..fcf797dd 100644
--- a/preliz/distributions/halfstudentt.py
+++ b/preliz/distributions/halfstudentt.py
@@ -72,6 +72,8 @@ class HalfStudentT(Continuous):
Scale parameter (lam > 0). Converges to the precision as nu increases.
"""
+ parametrizations = [("nu", "sigma"), ("nu", "lam")]
+
def __init__(self, nu=None, sigma=None, lam=None):
super().__init__()
self.support = (0, np.inf)
diff --git a/preliz/distributions/inversegamma.py b/preliz/distributions/inversegamma.py
index 93a7f2a0..fb64bae2 100644
--- a/preliz/distributions/inversegamma.py
+++ b/preliz/distributions/inversegamma.py
@@ -63,6 +63,8 @@ class InverseGamma(Continuous):
Standard deviation (sigma > 0)
"""
+ parametrizations = [("alpha", "beta"), ("mu", "sigma")]
+
def __init__(self, alpha=None, beta=None, mu=None, sigma=None):
super().__init__()
self.support = (0, np.inf)
diff --git a/preliz/distributions/logitnormal.py b/preliz/distributions/logitnormal.py
index 519ddd64..981cdee9 100644
--- a/preliz/distributions/logitnormal.py
+++ b/preliz/distributions/logitnormal.py
@@ -55,6 +55,8 @@ class LogitNormal(Continuous):
Scale parameter (tau > 0).
"""
+ parametrizations = [("mu", "sigma"), ("mu", "tau")]
+
def __init__(self, mu=None, sigma=None, tau=None):
super().__init__()
self.support = (0, 1)
diff --git a/preliz/distributions/negativebinomial.py b/preliz/distributions/negativebinomial.py
index 413ff374..14337500 100644
--- a/preliz/distributions/negativebinomial.py
+++ b/preliz/distributions/negativebinomial.py
@@ -72,6 +72,8 @@ class NegativeBinomial(Discrete):
Number of target success trials (n > 0)
"""
+ parametrizations = [("mu", "alpha"), ("p", "n")]
+
def __init__(self, mu=None, alpha=None, p=None, n=None):
super().__init__()
self.support = (0, np.inf)
diff --git a/preliz/distributions/normal.py b/preliz/distributions/normal.py
index 365250e0..b86ef1ab 100644
--- a/preliz/distributions/normal.py
+++ b/preliz/distributions/normal.py
@@ -61,6 +61,8 @@ class Normal(Continuous):
Precision (tau > 0).
"""
+ parametrizations = [("mu", "sigma"), ("mu", "tau")]
+
def __init__(self, mu=None, sigma=None, tau=None):
super().__init__()
self.support = (-pt.inf, pt.inf)
diff --git a/preliz/distributions/studentt.py b/preliz/distributions/studentt.py
index d29a31a3..9ea81a88 100644
--- a/preliz/distributions/studentt.py
+++ b/preliz/distributions/studentt.py
@@ -67,6 +67,8 @@ class StudentT(Continuous):
Scale parameter (lam > 0). Converges to the precision as nu increases.
"""
+ parametrizations = [("nu", "mu", "sigma"), ("nu", "mu", "lam")]
+
def __init__(self, nu=None, mu=None, sigma=None, lam=None):
super().__init__()
self.support = (-np.inf, np.inf)
diff --git a/preliz/distributions/wald.py b/preliz/distributions/wald.py
index 5153e3bd..db858738 100644
--- a/preliz/distributions/wald.py
+++ b/preliz/distributions/wald.py
@@ -56,6 +56,8 @@ class Wald(Continuous):
Shape parameter (phi > 0).
"""
+ parametrizations = [("mu", "lam"), ("mu", "phi"), ("lam", "phi")]
+
def __init__(self, mu=None, lam=None, phi=None):
super().__init__()
self.support = (0, np.inf)
diff --git a/preliz/tests/test_catalog.py b/preliz/tests/test_catalog.py
new file mode 100644
index 00000000..f6aadafe
--- /dev/null
+++ b/preliz/tests/test_catalog.py
@@ -0,0 +1,157 @@
+import pytest
+
+from preliz.distributions.catalog import catalog
+
+
+class TestCatalogGet:
+ def test_get_continuous_instances(self):
+ dists = catalog.get("continuous")
+ assert len(dists) > 0
+ assert all(hasattr(d, "pdf") for d in dists)
+ assert all(hasattr(d, "kind") for d in dists)
+
+ def test_get_discrete_instances(self):
+ dists = catalog.get("discrete")
+ assert len(dists) > 0
+ assert all(hasattr(d, "pmf") or hasattr(d, "pdf") for d in dists)
+
+ def test_get_continuous_names(self):
+ names = catalog.get("continuous", output="names")
+ assert len(names) > 0
+ assert all(isinstance(n, str) for n in names)
+ assert "Normal" in names
+ assert "Gamma" in names
+
+ def test_get_discrete_names(self):
+ names = catalog.get("discrete", output="names")
+ assert len(names) > 0
+ assert "Poisson" in names
+ assert "Binomial" in names
+
+ def test_get_by_support_category(self):
+ positive = catalog.get("positive")
+ assert len(positive) > 0
+
+ bounded = catalog.get("bounded")
+ assert len(bounded) > 0
+
+ unbounded = catalog.get("unbounded")
+ assert len(unbounded) > 0
+
+ def test_get_invalid_category(self):
+ with pytest.raises(ValueError, match="Unknown category"):
+ catalog.get("invalid_category")
+
+ def test_get_invalid_output(self):
+ with pytest.raises(ValueError, match="Invalid value for 'output'"):
+ catalog.get("continuous", output="invalid")
+
+
+class TestCatalogInfo:
+ def test_info_returns_dict(self):
+ info = catalog.info("Gamma")
+ assert isinstance(info, dict)
+
+ def test_info_has_required_keys(self):
+ info = catalog.info("Gamma")
+ assert "name" in info
+ assert "kind" in info
+ assert "param_names" in info
+ assert "params_support" in info
+ assert "support" in info
+
+ def test_info_gamma(self):
+ info = catalog.info("Gamma")
+ assert info["name"] == "Gamma"
+ assert info["kind"] == "continuous"
+ assert info["param_names"] == ("alpha", "beta")
+ assert "parametrizations" in info
+ assert ("alpha", "beta") in info["parametrizations"]
+ assert ("mu", "sigma") in info["parametrizations"]
+
+ def test_info_normal(self):
+ info = catalog.info("Normal")
+ assert info["name"] == "Normal"
+ assert info["kind"] == "continuous"
+ assert "parametrizations" in info
+
+ def test_info_poisson(self):
+ info = catalog.info("Poisson")
+ assert info["name"] == "Poisson"
+ assert info["kind"] == "discrete"
+ assert info["param_names"] == ("mu",)
+
+ def test_info_support_formatting(self):
+ info = catalog.info("Gamma")
+ assert info["support"] == (0, "inf")
+ assert info["params_support"] == ((0, "inf"), (0, "inf"))
+
+ info = catalog.info("Normal")
+ assert info["support"] == ("-inf", "inf")
+
+ def test_info_invalid_name(self):
+ with pytest.raises(AttributeError):
+ catalog.info("NonExistentDistribution")
+
+
+class TestCatalogFind:
+ def test_find_by_kind_continuous(self):
+ dists = catalog.find(kind="continuous")
+ assert len(dists) > 0
+ assert all(d.kind == "continuous" for d in dists)
+
+ def test_find_by_kind_discrete(self):
+ dists = catalog.find(kind="discrete")
+ assert len(dists) > 0
+ assert all(d.kind == "discrete" for d in dists)
+
+ def test_find_by_num_params(self):
+ dists = catalog.find(num_params=1)
+ assert len(dists) > 0
+ assert all(len(d.param_names) == 1 for d in dists)
+
+ dists = catalog.find(num_params=2)
+ assert len(dists) > 0
+ assert all(len(d.param_names) == 2 for d in dists)
+
+ def test_find_by_support_positive(self):
+ dists = catalog.find(support="positive")
+ assert len(dists) > 0
+ for d in dists:
+ lower, upper = d.support
+ assert lower >= 0
+ assert upper == float("inf")
+
+ def test_find_by_support_bounded(self):
+ dists = catalog.find(support="bounded")
+ assert len(dists) > 0
+ for d in dists:
+ lower, upper = d.support
+ assert lower != float("-inf")
+ assert upper != float("inf")
+
+ def test_find_combined_filters(self):
+ dists = catalog.find(kind="continuous", num_params=2)
+ assert len(dists) > 0
+ assert all(d.kind == "continuous" for d in dists)
+ assert all(len(d.param_names) == 2 for d in dists)
+
+ def test_find_no_filters(self):
+ dists = catalog.find()
+ assert len(dists) > 0
+
+
+class TestCatalogRepr:
+ def test_repr_contains_distributions(self):
+ repr_str = repr(catalog)
+ assert "PreliZ Distributions" in repr_str
+ assert "Continuous" in repr_str
+ assert "Discrete" in repr_str
+ assert "Normal" in repr_str
+ assert "Poisson" in repr_str
+
+ def test_repr_html_contains_distributions(self):
+ html = catalog._repr_html_()
+ assert "