diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index b9f0a74b8cc..55e4484dc3e 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -27,7 +27,6 @@ from pymc3.theanof import floatX from . import transforms -from pymc3.util import get_variable_name from .special import log_i0 from ..math import invlogit, logit, logdiffexp from .dist_math import ( @@ -239,15 +238,6 @@ def logp(self, value): return bound(-tt.log(upper - lower), value >= lower, value <= upper) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - lower = dist.lower - upper = dist.upper - name = r'\text{%s}' % name - return r'${} \sim \text{{Uniform}}(\mathit{{lower}}={},~\mathit{{upper}}={})$'.format( - name, get_variable_name(lower), get_variable_name(upper)) - def logcdf(self, value): """ Compute the log of the cumulative distribution function for Uniform distribution @@ -315,10 +305,6 @@ def logp(self, value): """ return tt.zeros_like(value) - def _repr_latex_(self, name=None, dist=None): - name = r'\text{%s}' % name - return r'${} \sim \text{{Flat}}()$'.format(name) - def logcdf(self, value): """ Compute the log of the cumulative distribution function for Flat distribution @@ -382,10 +368,6 @@ def logp(self, value): """ return bound(tt.zeros_like(value), value > 0) - def _repr_latex_(self, name=None, dist=None): - name = r'\text{%s}' % name - return r'${} \sim \text{{HalfFlat}}()$'.format(name) - def logcdf(self, value): """ Compute the log of the cumulative distribution function for HalfFlat distribution @@ -538,15 +520,8 @@ def logp(self, value): return bound((-tau * (value - mu)**2 + tt.log(tau / np.pi / 2.)) / 2., sigma > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - sigma = dist.sigma - mu = dist.mu - name = r'\text{%s}' % name - return r'${} \sim \text{{Normal}}(\mathit{{mu}}={},~\mathit{{sigma}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(sigma)) + def _distr_parameters_for_repr(self): + return ["mu", "sigma"] def logcdf(self, value): """ @@ -766,21 +741,8 @@ def _normalization(self): else: return normal_lcdf(mu, sigma, self.upper) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - name = r'\text{%s}' % name - return ( - r'${} \sim \text{{TruncatedNormal}}(' - r'\mathit{{mu}}={},~\mathit{{sigma}}={},a={},b={})$' - .format( - name, - get_variable_name(self.mu), - get_variable_name(self.sigma), - get_variable_name(self.lower), - get_variable_name(self.upper), - ) - ) + def _distr_parameters_for_repr(self): + return ["mu", "sigma", "lower", "upper"] class HalfNormal(PositiveContinuous): @@ -907,13 +869,8 @@ def logp(self, value): value >= 0, tau > 0, sigma > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - sigma = dist.sigma - name = r'\text{%s}' % name - return r'${} \sim \text{{HalfNormal}}(\mathit{{sigma}}={})$'.format(name, - get_variable_name(sigma)) + def _distr_parameters_for_repr(self): + return ["sigma"] def logcdf(self, value): """ @@ -1110,17 +1067,8 @@ def logp(self, value): value > 0, value - alpha > 0, mu > 0, lam > 0, alpha >= 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - lam = dist.lam - mu = dist.mu - alpha = dist.alpha - name = r'\text{%s}' % name - return r'${} \sim \text{{Wald}}(\mathit{{mu}}={},~\mathit{{lam}}={},~\mathit{{alpha}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(lam), - get_variable_name(alpha)) + def _distr_parameters_for_repr(self): + return ["mu", "lam", "alpha"] def logcdf(self, value): """ @@ -1351,15 +1299,8 @@ def logcdf(self, value): ) ) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - alpha = dist.alpha - beta = dist.beta - name = r'\text{%s}' % name - return r'${} \sim \text{{Beta}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name, - get_variable_name(alpha), - get_variable_name(beta)) + def _distr_parameters_for_repr(self): + return ["alpha", "beta"] class Kumaraswamy(UnitContinuous): R""" @@ -1467,16 +1408,6 @@ def logp(self, value): value >= 0, value <= 1, a > 0, b > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - a = dist.a - b = dist.b - name = r'\text{%s}' % name - return r'${} \sim \text{{Kumaraswamy}}(\mathit{{a}}={},~\mathit{{b}}={})$'.format(name, - get_variable_name(a), - get_variable_name(b)) - class Exponential(PositiveContinuous): R""" @@ -1565,14 +1496,6 @@ def logp(self, value): lam = self.lam return bound(tt.log(lam) - lam * value, value >= 0, lam > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - lam = dist.lam - name = r'\text{%s}' % name - return r'${} \sim \text{{Exponential}}(\mathit{{lam}}={})$'.format(name, - get_variable_name(lam)) - def logcdf(self, value): r""" Compute the log of cumulative distribution function for the Exponential distribution @@ -1700,16 +1623,6 @@ def logp(self, value): return -tt.log(2 * b) - abs(value - mu) / b - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - b = dist.b - mu = dist.mu - name = r'\text{%s}' % name - return r'${} \sim \text{{Laplace}}(\mathit{{mu}}={},~\mathit{{b}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(b)) - def logcdf(self, value): """ Compute the log of the cumulative distribution function for Laplace distribution @@ -1871,15 +1784,8 @@ def logp(self, value): - tt.log(value), tau > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - tau = dist.tau - mu = dist.mu - name = r'\text{%s}' % name - return r'${} \sim \text{{Lognormal}}(\mathit{{mu}}={},~\mathit{{tau}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(tau)) + def _distr_parameters_for_repr(self): + return ["mu", "tau"] def logcdf(self, value): """ @@ -2045,17 +1951,8 @@ def logp(self, value): - (nu + 1.0) / 2.0 * tt.log1p(lam * (value - mu)**2 / nu), lam > 0, nu > 0, sigma > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - nu = dist.nu - mu = dist.mu - lam = dist.lam - name = r'\text{%s}' % name - return r'${} \sim \text{{StudentT}}(\mathit{{nu}}={},~\mathit{{mu}}={},~\mathit{{lam}}={})$'.format(name, - get_variable_name(nu), - get_variable_name(mu), - get_variable_name(lam)) + def _distr_parameters_for_repr(self): + return ["nu", "mu", "lam"] def logcdf(self, value): """ @@ -2192,15 +2089,8 @@ def logp(self, value): - logpow(value, alpha + 1), value >= m, alpha > 0, m > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - alpha = dist.alpha - m = dist.m - name = r'\text{%s}' % name - return r'${} \sim \text{{Pareto}}(\mathit{{alpha}}={},~\mathit{{m}}={})$'.format(name, - get_variable_name(alpha), - get_variable_name(m)) + def _distr_parameters_for_repr(self): + return ["alpha", "m"] def logcdf(self, value): """ @@ -2330,16 +2220,6 @@ def logp(self, value): - tt.log1p(((value - alpha) / beta)**2), beta > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - alpha = dist.alpha - beta = dist.beta - name = r'\text{%s}' % name - return r'${} \sim \text{{Cauchy}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name, - get_variable_name(alpha), - get_variable_name(beta)) - def logcdf(self, value): """ Compute the log of the cumulative distribution function for Cauchy distribution @@ -2450,14 +2330,6 @@ def logp(self, value): - tt.log1p((value / beta)**2), value >= 0, beta > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - beta = dist.beta - name = r'\text{%s}' % name - return r'${} \sim \text{{HalfCauchy}}(\mathit{{beta}}={})$'.format(name, - get_variable_name(beta)) - def logcdf(self, value): """ Compute the log of the cumulative distribution function for HalfCauchy distribution @@ -2641,15 +2513,8 @@ def logcdf(self, value): alpha > 0, beta > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - beta = dist.beta - alpha = dist.alpha - name = r'\text{%s}' % name - return r'${} \sim \text{{Gamma}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name, - get_variable_name(alpha), - get_variable_name(beta)) + def _distr_parameters_for_repr(self): + return ["alpha", "beta"] class InverseGamma(PositiveContinuous): @@ -2791,15 +2656,8 @@ def logp(self, value): + logpow(value, -alpha - 1), value > 0, alpha > 0, beta > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - beta = dist.beta - alpha = dist.alpha - name = r'\text{%s}' % name - return r'${} \sim \text{{InverseGamma}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name, - get_variable_name(alpha), - get_variable_name(beta)) + def _distr_parameters_for_repr(self): + return ["alpha", "beta"] class ChiSquared(Gamma): @@ -2844,14 +2702,6 @@ def __init__(self, nu, *args, **kwargs): self.nu = nu = tt.as_tensor_variable(floatX(nu)) super().__init__(alpha=nu / 2., beta=0.5, *args, **kwargs) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - nu = dist.nu - name = r'\text{%s}' % name - return r'${} \sim \Chi^2(\mathit{{nu}}={})$'.format(name, - get_variable_name(nu)) - class Weibull(PositiveContinuous): R""" @@ -2959,16 +2809,6 @@ def logp(self, value): - (value / beta)**alpha, value >= 0, alpha > 0, beta > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - beta = dist.beta - alpha = dist.alpha - name = r'\text{%s}' % name - return r'${} \sim \text{{Weibull}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name, - get_variable_name(alpha), - get_variable_name(beta)) - def logcdf(self, value): r""" Compute the log of the cumulative distribution function for Weibull distribution @@ -3127,15 +2967,8 @@ def logp(self, value): - (nu + 1.0) / 2.0 * tt.log1p(value ** 2 / (nu * sigma**2)), sigma > 0, lam > 0, nu > 0, value >= 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - nu = dist.nu - sigma = dist.sigma - name = r'\text{%s}' % name - return r'${} \sim \text{{HalfStudentT}}(\mathit{{nu}}={},~\mathit{{sigma}}={})$'.format(name, - get_variable_name(nu), - get_variable_name(sigma)) + def _distr_parameters_for_repr(self): + return ["nu", "lam"] class ExGaussian(Continuous): @@ -3285,17 +3118,8 @@ def logp(self, value): return bound(lp, sigma > 0.0, nu > 0.0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - sigma = dist.sigma - mu = dist.mu - nu = dist.nu - name = r'\text{%s}' % name - return r'${} \sim \text{{ExGaussian}}(\mathit{{mu}}={},~\mathit{{sigma}}={},~\mathit{{nu}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(sigma), - get_variable_name(nu)) + def _distr_parameters_for_repr(self): + return ["mu", "sigma", "nu"] def logcdf(self, value): """ @@ -3429,16 +3253,8 @@ def logp(self, value): return bound(kappa * tt.cos(mu - value) - (tt.log(2 * np.pi) + log_i0(kappa)), kappa > 0, value >= -np.pi, value <= np.pi) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - kappa = dist.kappa - mu = dist.mu - name = r'\text{%s}' % name - return r'${} \sim \text{{VonMises}}(\mathit{{mu}}={},~\mathit{{kappa}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(kappa)) - + def _distr_parameters_for_repr(self): + return ["mu", "kappa"] class SkewNormal(Continuous): @@ -3572,17 +3388,8 @@ def logp(self, value): + tt.log(tau / np.pi / 2.)) / 2., tau > 0, sigma > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - sigma = dist.sigma - mu = dist.mu - alpha = dist.alpha - name = r'\text{%s}' % name - return r'${} \sim \text{{Skew-Normal}}(\mathit{{mu}}={},~\mathit{{sigma}}={},~\mathit{{alpha}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(sigma), - get_variable_name(alpha)) + def _distr_parameters_for_repr(self): + return ["mu", "sigma", "alpha"] class Triangular(BoundedContinuous): @@ -3708,18 +3515,6 @@ def logp(self, value): tt.log(2 * (upper - value) / ((upper - lower) * (upper - c))), np.inf))) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - lower = dist.lower - upper = dist.upper - c = dist.c - name = r'\text{%s}' % name - return r'${} \sim \text{{Triangular}}(\mathit{{c}}={},~\mathit{{lower}}={},~\mathit{{upper}}={})$'.format(name, - get_variable_name(c), - get_variable_name(lower), - get_variable_name(upper)) - def logcdf(self, value): """ Compute the log of the cumulative distribution function for Triangular distribution @@ -3853,16 +3648,6 @@ def logp(self, value): scaled = (value - self.mu) / self.beta return bound(-scaled - tt.exp(-scaled) - tt.log(self.beta), self.beta > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - beta = dist.beta - mu = dist.mu - name = r'\text{%s}' % name - return r'${} \sim \text{{Gumbel}}(\mathit{{mu}}={},~\mathit{{beta}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(beta)) - def logcdf(self, value): """ Compute the log of the cumulative distribution function for Gumbel distribution @@ -4031,6 +3816,9 @@ def logp(self, value): value > 0, ) + def _distr_parameters_for_repr(self): + return ["nu", "sigma"] + class Logistic(Continuous): R""" @@ -4129,16 +3917,6 @@ def random(self, point=None, size=None): dist_shape=self.shape, size=size) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - mu = dist.mu - s = dist.s - name = r'\text{%s}' % name - return r'${} \sim \text{{Logistic}}(\mathit{{mu}}={},~\mathit{{s}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(s)) - def logcdf(self, value): r""" Compute the log of the cumulative distribution function for Logistic distribution @@ -4280,15 +4058,8 @@ def logp(self, value): + 0.5 * tt.log(tau / (2. * np.pi)) - tt.log(value * (1 - value)), value > 0, value < 1, tau > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - sigma = dist.sigma - mu = dist.mu - name = r'\text{%s}' % name - return r'${} \sim \text{{LogitNormal}}(\mathit{{mu}}={},~\mathit{{sigma}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(sigma)) + def _distr_parameters_for_repr(self): + return ["mu", "sigma"] class Interpolated(BoundedContinuous): @@ -4392,6 +4163,9 @@ def logp(self, value): """ return tt.log(self.interp_op(value) / self.Z) + def _distr_parameters_for_repr(self): + return [] + class Moyal(Continuous): R""" @@ -4494,16 +4268,6 @@ def logp(self, value): - tt.log(self.sigma) - (1 / 2) * tt.log(2 * np.pi)), self.sigma > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - sigma = dist.sigma - mu = dist.mu - name = r'\text{%s}' % name - return r'${} \sim \text{{Moyal}}(\mathit{{mu}}={},~\mathit{{sigma}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(sigma)) - def logcdf(self, value): """ Compute the log of the cumulative distribution function for Moyal distribution diff --git a/pymc3/distributions/discrete.py b/pymc3/distributions/discrete.py index c5d43706f3b..090949c9053 100644 --- a/pymc3/distributions/discrete.py +++ b/pymc3/distributions/discrete.py @@ -17,7 +17,6 @@ from scipy import stats import warnings -from pymc3.util import get_variable_name from .dist_math import bound, factln, binomln, betaln, logpow, random_choice from .distribution import Discrete, draw_values, generate_samples from .shape_utils import broadcast_distribution_samples @@ -123,15 +122,6 @@ def logp(self, value): 0 <= value, value <= n, 0 <= p, p <= 1) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - n = dist.n - p = dist.p - name = r'\text{%s}' % name - return r'${} \sim \text{{Binomial}}(\mathit{{n}}={},~\mathit{{p}}={})$'.format(name, - get_variable_name(n), - get_variable_name(p)) class BetaBinomial(Discrete): R""" @@ -259,16 +249,6 @@ def logp(self, value): value >= 0, value <= self.n, alpha > 0, beta > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - alpha = dist.alpha - beta = dist.beta - name = r'\text{%s}' % name - return r'${} \sim \text{{BetaBinomial}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name, - get_variable_name(alpha), - get_variable_name(beta)) - class Bernoulli(Discrete): R"""Bernoulli log-likelihood @@ -371,13 +351,8 @@ def logp(self, value): value >= 0, value <= 1, p >= 0, p <= 1) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - p = dist.p - name = r'\text{%s}' % name - return r'${} \sim \text{{Bernoulli}}(\mathit{{p}}={})$'.format(name, - get_variable_name(p)) + def _distr_parameters_for_repr(self): + return ["p"] class DiscreteWeibull(Discrete): @@ -486,16 +461,6 @@ def random(self, point=None, size=None): dist_shape=self.shape, size=size) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - q = dist.q - beta = dist.beta - name = r'\text{%s}' % name - return r'${} \sim \text{{DiscreteWeibull}}(\mathit{{q}}={},~\mathit{{beta}}={})$'.format(name, - get_variable_name(q), - get_variable_name(beta)) - class Poisson(Discrete): R""" @@ -590,14 +555,6 @@ def logp(self, value): return tt.switch(tt.eq(mu, 0) * tt.eq(value, 0), 0, log_prob) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - mu = dist.mu - name = r'\text{%s}' % name - return r'${} \sim \text{{Poisson}}(\mathit{{mu}}={})$'.format(name, - get_variable_name(mu)) - class NegativeBinomial(Discrete): R""" @@ -717,16 +674,6 @@ def logp(self, value): Poisson.dist(self.mu).logp(value), negbinom) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - mu = dist.mu - alpha = dist.alpha - name = r'\text{%s}' % name - return r'${} \sim \text{{NegativeBinomial}}(\mathit{{mu}}={},~\mathit{{alpha}}={})$'.format(name, - get_variable_name(mu), - get_variable_name(alpha)) - class Geometric(Discrete): R""" @@ -810,14 +757,6 @@ def logp(self, value): return bound(tt.log(p) + logpow(1 - p, value - 1), 0 <= p, p <= 1, value >= 1) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - p = dist.p - name = r'\text{%s}' % name - return r'${} \sim \text{{Geometric}}(\mathit{{p}}={})$'.format(name, - get_variable_name(p)) - class DiscreteUniform(Discrete): R""" @@ -913,16 +852,6 @@ def logp(self, value): return bound(-tt.log(upper - lower + 1), lower <= value, value <= upper) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - lower = dist.lower - upper = dist.upper - name = r'\text{%s}' % name - return r'${} \sim \text{{DiscreteUniform}}(\mathit{{lower}}={},~\mathit{{upper}}={})$'.format(name, - get_variable_name(lower), - get_variable_name(upper)) - class Categorical(Discrete): R""" @@ -1044,14 +973,6 @@ def logp(self, value): return bound(a, value >= 0, value <= (k - 1), tt.all(p_ >= 0, axis=-1), tt.all(p <= 1, axis=-1)) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - p = dist.p - name = r'\text{%s}' % name - return r'${} \sim \text{{Categorical}}(\mathit{{p}}={})$'.format(name, - get_variable_name(p)) - class Constant(Discrete): r""" @@ -1112,12 +1033,6 @@ def logp(self, value): c = self.c return bound(0, tt.eq(value, c)) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - name = r'\text{%s}' % name - return r'${} \sim \text{{Constant}}()$'.format(name) - ConstantDist = Constant @@ -1231,16 +1146,6 @@ def logp(self, value): 0 <= psi, psi <= 1, 0 <= theta) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - theta = dist.theta - psi = dist.psi - name = r'\text{%s}' % name - return r'${} \sim \text{{ZeroInflatedPoisson}}(\mathit{{theta}}={},~\mathit{{psi}}={})$'.format(name, - get_variable_name(theta), - get_variable_name(psi)) - class ZeroInflatedBinomial(Discrete): R""" @@ -1354,22 +1259,6 @@ def logp(self, value): 0 <= psi, psi <= 1, 0 <= p, p <= 1) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - n = dist.n - p = dist.p - psi = dist.psi - - name_n = get_variable_name(n) - name_p = get_variable_name(p) - name_psi = get_variable_name(psi) - name = r'\text{%s}' % name - return (r'${} \sim \text{{ZeroInflatedBinomial}}' - r'(\mathit{{n}}={},~\mathit{{p}}={},~' - r'\mathit{{psi}}={})$' - .format(name, name_n, name_p, name_psi)) - class ZeroInflatedNegativeBinomial(Discrete): R""" @@ -1523,22 +1412,6 @@ def logp(self, value): 0 <= psi, psi <= 1, mu > 0, alpha > 0) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - mu = dist.mu - alpha = dist.alpha - psi = dist.psi - - name_mu = get_variable_name(mu) - name_alpha = get_variable_name(alpha) - name_psi = get_variable_name(psi) - name = r'\text{%s}' % name - return (r'${} \sim \text{{ZeroInflatedNegativeBinomial}}' - r'(\mathit{{mu}}={},~\mathit{{alpha}}={},~' - r'\mathit{{psi}}={})$' - .format(name, name_mu, name_alpha, name_psi)) - class OrderedLogistic(Categorical): R""" @@ -1619,12 +1492,3 @@ def __init__(self, eta, cutpoints, *args, **kwargs): p = p_cum[..., 1:] - p_cum[..., :-1] super().__init__(p=p, *args, **kwargs) - - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - name_eta = get_variable_name(dist.eta) - name_cutpoints = get_variable_name(dist.cutpoints) - return (r'${} \sim \text{{OrderedLogistic}}' - r'(\mathit{{eta}}={}, \mathit{{cutpoints}}={}$' - .format(name, name_eta, name_cutpoints)) diff --git a/pymc3/distributions/distribution.py b/pymc3/distributions/distribution.py index 2bdf9d88c09..3d3cd002eb1 100644 --- a/pymc3/distributions/distribution.py +++ b/pymc3/distributions/distribution.py @@ -15,6 +15,7 @@ import numbers import contextvars import dill +import inspect from typing import TYPE_CHECKING if TYPE_CHECKING: from typing import Optional, Callable @@ -22,6 +23,7 @@ import numpy as np import theano.tensor as tt from theano import function +from ..util import get_repr_for_variable import theano from ..memoize import memoize from ..model import ( @@ -135,9 +137,46 @@ def getattr_value(self, val): return val - def _repr_latex_(self, name=None, dist=None): + def _distr_parameters_for_repr(self): + """Return the names of the parameters for this distribution (e.g. "mu" + and "sigma" for Normal). Used in generating string (and LaTeX etc.) + representations of Distribution objects. By default based on inspection + of __init__, but can be overwritten if necessary (e.g. to avoid including + "sd" and "tau"). + """ + return inspect.getfullargspec(self.__init__).args[1:] + + def _distr_name_for_repr(self): + return self.__class__.__name__ + + def _str_repr(self, name=None, dist=None, formatting='plain'): + """Generate string representation for this distribution, optionally + including LaTeX markup (formatting='latex'). + """ + if dist is None: + dist = self + if name is None: + name = '[unnamed]' + + param_names = self._distr_parameters_for_repr() + param_values = [get_repr_for_variable(getattr(dist, x), formatting=formatting) + for x in param_names] + + if formatting == "latex": + param_string = ",~".join([r"\mathit{{{name}}}={value}".format(name=name, + value=value) for name, value in zip(param_names, param_values)]) + return r"$\text{{{var_name}}} \sim \text{{{distr_name}}}({params})$".format(var_name=name, + distr_name=dist._distr_name_for_repr(), params=param_string) + else: + # 'plain' is default option + param_string = ", ".join(["{name}={value}".format(name=name, + value=value) for name, value in zip(param_names, param_values)]) + return "{var_name} ~ {distr_name}({params})".format(var_name=name, + distr_name=dist._distr_name_for_repr(), params=param_string) + + def _repr_latex_(self, **kwargs): """Magic method name for IPython to use for LaTeX formatting.""" - return None + return self._str_repr(formatting="latex", **kwargs) def logp_nojac(self, *args, **kwargs): """Return the logp, but do not include a jacobian term for transforms. @@ -200,6 +239,9 @@ def logp(self, x): """ return tt.zeros_like(x) + def _distr_parameters_for_repr(self): + return [] + class Discrete(Distribution): """Base class for discrete distributions""" @@ -501,6 +543,9 @@ def random(self, point=None, size=None, **kwargs): "Define a custom random method and pass it as kwarg random" ) + def _distr_parameters_for_repr(self): + return [] + class _DrawValuesContext(metaclass=ContextMeta, context_class='_DrawValuesContext'): """ A context manager class used while drawing values with draw_values diff --git a/pymc3/distributions/mixture.py b/pymc3/distributions/mixture.py index a041c3bc159..3169aabe52c 100644 --- a/pymc3/distributions/mixture.py +++ b/pymc3/distributions/mixture.py @@ -18,7 +18,6 @@ import theano.tensor as tt import warnings -from pymc3.util import get_variable_name from ..math import logsumexp from .dist_math import bound, random_choice from .distribution import (Discrete, Distribution, draw_values, @@ -578,6 +577,8 @@ def random(self, point=None, size=None): samples = np.reshape(samples, size + dist_shape) return samples + def _distr_parameters_for_repr(self): + return [] class NormalMixture(Mixture): R""" @@ -627,14 +628,5 @@ def __init__(self, w, mu, sigma=None, tau=None, sd=None, comp_shape=(), *args, * super().__init__(w, Normal.dist(mu, sigma=sigma, shape=comp_shape), *args, **kwargs) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - mu = dist.mu - w = dist.w - sigma = dist.sigma - name = r'\text{%s}' % name - return r'${} \sim \text{{NormalMixture}}(\mathit{{w}}={},~\mathit{{mu}}={},~\mathit{{sigma}}={})$'.format(name, - get_variable_name(w), - get_variable_name(mu), - get_variable_name(sigma)) + def _distr_parameters_for_repr(self): + return ["w", "mu", "sigma"] diff --git a/pymc3/distributions/multivariate.py b/pymc3/distributions/multivariate.py index d3c76d5df33..aeea0b39c6b 100755 --- a/pymc3/distributions/multivariate.py +++ b/pymc3/distributions/multivariate.py @@ -30,7 +30,6 @@ from pymc3.theanof import floatX from . import transforms -from pymc3.util import get_variable_name from .distribution import (Continuous, Discrete, draw_values, generate_samples, _DrawValuesContext) from ..model import Deterministic @@ -151,18 +150,11 @@ def _quaddist_tau(self, delta): logdet = -tt.sum(tt.log(diag)) return quaddist, logdet, ok - def _repr_cov_params(self, dist=None): - if dist is None: - dist = self - if self._cov_type == 'chol': - chol = get_variable_name(self.chol_cov) - return r'\mathit{{chol}}={}'.format(chol) - elif self._cov_type == 'cov': - cov = get_variable_name(self.cov) - return r'\mathit{{cov}}={}'.format(cov) - elif self._cov_type == 'tau': - tau = get_variable_name(self.tau) - return r'\mathit{{tau}}={}'.format(tau) + def _cov_param_for_repr(self): + if self._cov_type == "chol": + return "chol_cov" + else: + return self._cov_type class MvNormal(_QuadFormBase): @@ -330,14 +322,8 @@ def logp(self, value): norm = - 0.5 * k * pm.floatX(np.log(2 * np.pi)) return bound(norm - 0.5 * quaddist - logdet, ok) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - mu = dist.mu - name_mu = get_variable_name(mu) - return (r'${} \sim \text{{MvNormal}}' - r'(\mathit{{mu}}={}, {})$' - .format(name, name_mu, self._repr_cov_params(dist))) + def _distr_parameters_for_repr(self): + return ["mu", self._cov_param_for_repr()] class MvStudentT(_QuadFormBase): @@ -448,17 +434,8 @@ def logp(self, value): inner = - (self.nu + k) / 2. * tt.log1p(quaddist / self.nu) return bound(norm + inner - logdet, ok) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - mu = dist.mu - nu = dist.nu - name_nu = get_variable_name(nu) - name_mu = get_variable_name(mu) - return (r'${} \sim \text{{MvStudentT}}' - r'(\mathit{{nu}}={}, \mathit{{mu}}={}, ' - r'{})$' - .format(name, name_nu, name_mu, self._repr_cov_params(dist))) + def _distr_parameters_for_repr(self): + return ["mu", "nu", self._cov_param_for_repr()] class Dirichlet(Continuous): @@ -580,12 +557,8 @@ def logp(self, value): np.logical_not(a.broadcastable), tt.all(a > 0), broadcast_conditions=False) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - a = dist.a - return r'${} \sim \text{{Dirichlet}}(\mathit{{a}}={})$'.format(name, - get_variable_name(a)) + def _distr_parameters_for_repr(self): + return ["a"] class Multinomial(Discrete): @@ -735,15 +708,6 @@ def logp(self, x): broadcast_conditions=False ) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - n = dist.n - p = dist.p - return r'${} \sim \text{{Multinomial}}(\mathit{{n}}={}, \mathit{{p}}={})$'.format(name, - get_variable_name(n), - get_variable_name(p)) - def posdef(AA): try: @@ -901,14 +865,6 @@ def logp(self, X): broadcast_conditions=False ) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - nu = dist.nu - V = dist.V - return r'${} \sim \text{{Wishart}}(\mathit{{nu}}={}, \mathit{{V}}={})$'.format(name, - get_variable_name(nu), - get_variable_name(V)) def WishartBartlett(name, S, nu, is_cholesky=False, return_cholesky=False, testval=None): R""" diff --git a/pymc3/distributions/simulator.py b/pymc3/distributions/simulator.py index 5320f3be9d0..e0e8e456fdc 100644 --- a/pymc3/distributions/simulator.py +++ b/pymc3/distributions/simulator.py @@ -34,7 +34,7 @@ def __init__( ): """ This class stores a function defined by the user in Python language. - + function: function Python function defined by the user. params: list @@ -53,7 +53,7 @@ def __init__( If a callable is based it should return a number or a 1d numpy array. epsilon: float Standard deviation of the gaussian_kernel. - *args and **kwargs: + *args and **kwargs: Arguments and keywords arguments that the function takes. """ @@ -115,7 +115,7 @@ def random(self, point=None, size=None): else: return np.array([self.function(*params) for _ in range(size)]) - def _repr_latex_(self, name=None, dist=None): + def _str_repr(self, name=None, dist=None, formatting="plain"): if dist is None: dist = self name = name @@ -123,7 +123,12 @@ def _repr_latex_(self, name=None, dist=None): params = ", ".join([var.name for var in dist.params]) sum_stat = self.sum_stat.__name__ if hasattr(self.sum_stat, "__call__") else self.sum_stat distance = self.distance.__name__ - return f"$\\text{{{name}}} \sim \\text{{Simulator}}(\\text{{{function}}}({params}), \\text{{{distance}}}, \\text{{{sum_stat}}})$" + + if formatting == "latex": + return f"$\\text{{{name}}} \sim \\text{{Simulator}}(\\text{{{function}}}({params}), \\text{{{distance}}}, \\text{{{sum_stat}}})$" + else: + return f"{name} ~ Simulator({function}({params}), {distance}, {sum_stat})" + def identity(x): @@ -138,7 +143,7 @@ def gaussian_kernel(epsilon, obs_data, sim_data): def wasserstein(epsilon, obs_data, sim_data): """Wasserstein distance function. - + We are assuming obs_data and sim_data are already sorted! """ return np.mean(np.abs((obs_data - sim_data) / epsilon)) @@ -146,7 +151,7 @@ def wasserstein(epsilon, obs_data, sim_data): def energy(epsilon, obs_data, sim_data): """Energy distance function. - + We are assuming obs_data and sim_data are already sorted! """ return 1.4142 * np.mean(((obs_data - sim_data) / epsilon) ** 2) ** 0.5 diff --git a/pymc3/distributions/timeseries.py b/pymc3/distributions/timeseries.py index 4443661a741..3956550d82d 100644 --- a/pymc3/distributions/timeseries.py +++ b/pymc3/distributions/timeseries.py @@ -19,7 +19,6 @@ from theano import scan import numpy as np -from pymc3.util import get_variable_name from .continuous import get_tau_sigma, Normal, Flat from .shape_utils import to_tuple from . import multivariate @@ -80,16 +79,6 @@ def logp(self, x): innov_like = Normal.dist(k * x_im1, tau=tau_e).logp(x_i) return boundary(x[0]) + tt.sum(innov_like) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - k = dist.k - tau_e = dist.tau_e - name = r"\text{%s}" % name - return r"${} \sim \text{{AR1}}(\mathit{{k}}={},~\mathit{{tau_e}}={})$".format( - name, get_variable_name(k), get_variable_name(tau_e) - ) - class AR(distribution.Continuous): r""" @@ -327,15 +316,8 @@ def _random(self, sigma, mu, size, sample_shape): data = data - data[0] return data - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - mu = dist.mu - sigma = dist.sigma - name = r"\text{%s}" % name - return r"${} \sim \text{{GaussianRandomWalk}}(\mathit{{mu}}={},~\mathit{{sigma}}={})$".format( - name, get_variable_name(mu), get_variable_name(sigma) - ) + def _distr_parameters_for_repr(self): + return ["mu", "sigma"] class GARCH11(distribution.Continuous): @@ -401,19 +383,8 @@ def logp(self, x): vol = self.get_volatility(x) return tt.sum(Normal.dist(0.0, sigma=vol).logp(x)) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - omega = dist.omega - alpha_1 = dist.alpha_1 - beta_1 = dist.beta_1 - name = r"\text{%s}" % name - return r"${} \sim \text{GARCH}(1,~1,~\mathit{{omega}}={},~\mathit{{alpha_1}}={},~\mathit{{beta_1}}={})$".format( - name, - get_variable_name(omega), - get_variable_name(alpha_1), - get_variable_name(beta_1), - ) + def _distr_parameters_for_repr(self): + return ["omega", "alpha_1", "beta_1"] class EulerMaruyama(distribution.Continuous): @@ -455,14 +426,8 @@ def logp(self, x): sd = tt.sqrt(self.dt) * g return tt.sum(Normal.dist(mu=mu, sigma=sd).logp(x[1:])) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - dt = dist.dt - name = r"\text{%s}" % name - return r"${} \sim \text{EulerMaruyama}(\mathit{{dt}}={})$".format( - name, get_variable_name(dt) - ) + def _distr_parameters_for_repr(self): + return ["dt"] class MvGaussianRandomWalk(distribution.Continuous): @@ -525,15 +490,8 @@ def logp(self, x): return self.init.logp_sum(x[0]) + self.innov.logp_sum(x_i - x_im1) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - mu = dist.innov.mu - cov = dist.innov.cov - name = r"\text{%s}" % name - return r"${} \sim \text{MvGaussianRandomWalk}(\mathit{{mu}}={},~\mathit{{cov}}={})$".format( - name, get_variable_name(mu), get_variable_name(cov) - ) + def _distr_parameters_for_repr(self): + return ["mu", "cov"] class MvStudentTRandomWalk(MvGaussianRandomWalk): @@ -560,13 +518,5 @@ def __init__(self, nu, *args, **kwargs): self.nu = tt.as_tensor_variable(nu) self.innov = multivariate.MvStudentT.dist(self.nu, None, *self.innovArgs) - def _repr_latex_(self, name=None, dist=None): - if dist is None: - dist = self - nu = dist.innov.nu - mu = dist.innov.mu - cov = dist.innov.cov - name = r"\text{%s}" % name - return r"${} \sim \text{MvStudentTRandomWalk}(\mathit{{nu}}={},~\mathit{{mu}}={},~\mathit{{cov}}={})$".format( - name, get_variable_name(nu), get_variable_name(mu), get_variable_name(cov) - ) + def _distr_parameters_for_repr(self): + return ["nu", "mu", "cov"] diff --git a/pymc3/distributions/transforms.py b/pymc3/distributions/transforms.py index 48f1d27fc3e..305c092285d 100644 --- a/pymc3/distributions/transforms.py +++ b/pymc3/distributions/transforms.py @@ -196,6 +196,14 @@ def logp_nojac(self, x): """ return self.dist.logp(self.transform_used.backward(x)) + def _repr_latex_(self, **kwargs): + # prevent TransformedDistributions from ending up in LaTeX representations + # of models + return None + + def _distr_parameters_for_repr(self): + return [] + transform = Transform diff --git a/pymc3/model.py b/pymc3/model.py index ccd123b7a0a..cae3e12ca53 100644 --- a/pymc3/model.py +++ b/pymc3/model.py @@ -64,6 +64,24 @@ class PyMC3Variable(TensorVariable): def __rmatmul__(self, other): return tt.dot(other, self) + def _str_repr(self, name=None, dist=None, formatting="plain"): + if getattr(self, "distribution", None) is None: + if formatting == "latex": + return None + else: + return super().__str__() + + if name is None and hasattr(self, 'name'): + name = self.name + if dist is None and hasattr(self, 'distribution'): + dist = self.distribution + return self.distribution._str_repr(name=name, dist=dist, formatting=formatting) + + def _repr_latex_(self, **kwargs): + return self._str_repr(formatting="latex", **kwargs) + + __latex__ = _repr_latex_ + class InstanceMethod: """Class for hiding references to instance methods so they can be pickled. @@ -1326,20 +1344,32 @@ def check_test_point(self, test_point=None, round_vals=2): name="Log-probability of test_point", ) - def _repr_latex_(self, name=None, dist=None): - tex_vars = [] - for rv in itertools.chain(self.unobserved_RVs, self.observed_RVs): - rv_tex = rv.__latex__() - if rv_tex is not None: - array_rv = rv_tex.replace(r"\sim", r"&\sim &").strip("$") - tex_vars.append(array_rv) - return r"""$$ - \begin{{array}}{{rcl}} - {} - \end{{array}} - $$""".format( - "\\\\".join(tex_vars) - ) + def _str_repr(self, formatting="plain", **kwargs): + all_rv = itertools.chain(self.unobserved_RVs, self.observed_RVs) + + if formatting == "latex": + rv_reprs = [rv.__latex__() for rv in all_rv] + rv_reprs = [rv_repr.replace(r"\sim", r"&\sim &").strip("$") + for rv_repr in rv_reprs if rv_repr is not None] + return r"""$$ + \begin{{array}}{{rcl}} + {} + \end{{array}} + $$""".format( + "\\\\".join(rv_reprs)) + else: + rv_reprs = [rv.__str__() for rv in all_rv] + rv_reprs = [rv_repr for rv_repr in rv_reprs if not 'TransformedDistribution()' in rv_repr] + # align vars on their ~ + names = [s[:s.index('~')-1] for s in rv_reprs] + distrs = [s[s.index('~')+2:] for s in rv_reprs] + maxlen = str(max(len(x) for x in names)) + rv_reprs = [('{name:>' + maxlen + '} ~ {distr}').format(name=n, distr=d) + for n, d in zip(names, distrs)] + return "\n".join(rv_reprs) + + def _repr_latex_(self, **kwargs): + return self._str_repr(formatting="latex", **kwargs) __latex__ = _repr_latex_ @@ -1607,17 +1637,6 @@ def __init__( wrapper=InstanceMethod, ) - def _repr_latex_(self, name=None, dist=None): - if self.distribution is None: - return None - if name is None: - name = self.name - if dist is None: - dist = self.distribution - return self.distribution._repr_latex_(name=name, dist=dist) - - __latex__ = _repr_latex_ - @property def init_value(self): """Convenience attribute to return tag.test_value""" @@ -1752,17 +1771,6 @@ def __init__( self.tag.test_value = theano.compile.view_op(data).tag.test_value self.scaling = _get_scaling(total_size, data.shape, data.ndim) - def _repr_latex_(self, name=None, dist=None): - if self.distribution is None: - return None - if name is None: - name = self.name - if dist is None: - dist = self.distribution - return self.distribution._repr_latex_(name=name, dist=dist) - - __latex__ = _repr_latex_ - @property def init_value(self): """Convenience attribute to return tag.test_value""" @@ -1822,27 +1830,28 @@ def __ne__(self, other): return not self == other -def _walk_up_rv(rv): +def _walk_up_rv(rv, formatting='plain'): """Walk up theano graph to get inputs for deterministic RV.""" all_rvs = [] parents = list(itertools.chain(*[j.inputs for j in rv.get_parents()])) if parents: for parent in parents: - all_rvs.extend(_walk_up_rv(parent)) + all_rvs.extend(_walk_up_rv(parent, formatting=formatting)) else: - if rv.name: - all_rvs.append(r"\text{%s}" % rv.name) - else: - all_rvs.append(r"\text{Constant}") + name = rv.name if rv.name else "Constant" + fmt = r"\text{{{name}}}" if formatting == "latex" else "{name}" + all_rvs.append(fmt.format(name=name)) return all_rvs -def _latex_repr_rv(rv): +def _repr_deterministic_rv(rv, formatting='plain'): """Make latex string for a Deterministic variable""" - return r"$\text{%s} \sim \text{Deterministic}(%s)$" % ( - rv.name, - r",~".join(_walk_up_rv(rv)), - ) + if formatting == 'latex': + return r"$\text{{{name}}} \sim \text{{Deterministic}}({args})$".format( + name=rv.name, args=r",~".join(_walk_up_rv(rv, formatting=formatting))) + else: + return "{name} ~ Deterministic({args})".format( + name=rv.name, args=", ".join(_walk_up_rv(rv, formatting=formatting))) def Deterministic(name, var, model=None, dims=None): @@ -1861,7 +1870,7 @@ def Deterministic(name, var, model=None, dims=None): var = var.copy(model.name_for(name)) model.deterministics.append(var) model.add_random_variable(var, dims) - var._repr_latex_ = functools.partial(_latex_repr_rv, var) + var._repr_latex_ = functools.partial(_repr_deterministic_rv, var, formatting='latex') var.__latex__ = var._repr_latex_ return var @@ -1940,17 +1949,6 @@ def __init__( wrapper=InstanceMethod, ) - def _repr_latex_(self, name=None, dist=None): - if self.distribution is None: - return None - if name is None: - name = self.name - if dist is None: - dist = self.distribution - return self.distribution._repr_latex_(name=name, dist=dist) - - __latex__ = _repr_latex_ - @property def init_value(self): """Convenience attribute to return tag.test_value""" diff --git a/pymc3/tests/test_distributions.py b/pymc3/tests/test_distributions.py index a52ff63a4a3..fbfb74ab195 100644 --- a/pymc3/tests/test_distributions.py +++ b/pymc3/tests/test_distributions.py @@ -1805,7 +1805,7 @@ def setup_class(self): r"$\text{sigma} \sim \text{HalfNormal}(\mathit{sigma}=1.0)$", r"$\text{mu} \sim \text{Deterministic}(\text{alpha},~\text{Constant},~\text{beta})$", r"$\text{beta} \sim \text{Normal}(\mathit{mu}=0.0,~\mathit{sigma}=10.0)$", - r"$Z \sim \text{MvNormal}(\mathit{mu}=array, \mathit{chol}=array)$", + r"$\text{Z} \sim \text{MvNormal}(\mathit{mu}=array,~\mathit{chol_cov}=array)$", r"$\text{Y_obs} \sim \text{Normal}(\mathit{mu}=\text{mu},~\mathit{sigma}=f(\text{sigma}))$", ) diff --git a/pymc3/util.py b/pymc3/util.py index ce3782ada18..46e35d595cf 100644 --- a/pymc3/util.py +++ b/pymc3/util.py @@ -124,26 +124,33 @@ def get_default_varnames(var_iterator, include_transformed): return [var for var in var_iterator if not is_transformed_name(str(var))] -def get_variable_name(variable): - r"""Returns the variable data type if it is a constant, otherwise - returns the argument name. +def get_repr_for_variable(variable, formatting="plain"): + """Build a human-readable string representation for a variable. """ name = variable.name if name is None: if hasattr(variable, "get_parents"): try: names = [ - get_variable_name(item) for item in variable.get_parents()[0].inputs + get_repr_for_variable(item, formatting=formatting) + for item in variable.get_parents()[0].inputs ] # do not escape_latex these, since it is not idempotent - return "f(%s)" % ",~".join([n for n in names if isinstance(n, str)]) + if formatting == "latex": + return "f({args})".format(args=",~".join([n for n in names if isinstance(n, str)])) + else: + return "f({args})".format(args=", ".join([n for n in names if isinstance(n, str)])) except IndexError: pass value = variable.eval() - if not value.shape: + if not value.shape or value.shape == (1,): return asscalar(value) return "array" - return r"\text{%s}" % name + + if formatting == "latex": + return r"\text{{{name}}}".format(name=name) + else: + return name def update_start_vals(a, b, model):