diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index bf6ce60ab5..3e2c13d2f5 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -38,6 +38,26 @@ def __init__(self, transform=transforms.logodds, *args, **kwargs): super(UnitContinuous, self).__init__( transform=transform, *args, **kwargs) +def assert_nonpos_support(var, label, distname): + # Checks for evidence of positive support for a variable + if var is None: + return + try: + # Transformed distribution + support = np.isfinite(var.transformed.distribution.dist.logp(0).tag.test_value) + except AttributeError: + try: + # Untransformed distribution + support = np.isfinite(var.distribution.logp(0).tag.test_value) + except AttributeError: + # Otherwise no direct evidence of non-positive support + support = False + + if np.any(support): + msg = "The variable specified for {0} has non-positive support for {1}, ".format(label, distname) + msg += "likely making it unsuitable for this parameter." + warnings.warn(msg) + def get_tau_sd(tau=None, sd=None): """ @@ -185,7 +205,7 @@ def __init__(self, *args, **kwargs): # called to display a warning we have to fetch the args and # kwargs manually. After a certain period we should revert # back to the old calling signature. - + if len(args) == 1: mu = args[0] sd = kwargs.pop('sd', None) @@ -205,6 +225,9 @@ def __init__(self, *args, **kwargs): self.mean = self.median = self.mode = self.mu = mu self.tau, self.sd = get_tau_sd(tau=tau, sd=sd) self.variance = 1. / self.tau + + assert_nonpos_support(sd, 'sd', 'Normal') + assert_nonpos_support(tau, 'tau', 'Normal') super(Normal, self).__init__(**kwargs) @@ -252,6 +275,9 @@ def __init__(self, sd=None, tau=None, *args, **kwargs): self.tau, self.sd = get_tau_sd(tau=tau, sd=sd) self.mean = tt.sqrt(2 / (np.pi * self.tau)) self.variance = (1. - 2 / np.pi) / self.tau + + assert_nonpos_support(tau, 'tau', 'HalfNormal') + assert_nonpos_support(sd, 'sd', 'HalfNormal') def random(self, point=None, size=None, repeat=None): sd = draw_values([self.sd], point=point) @@ -331,6 +357,10 @@ def __init__(self, mu=None, lam=None, phi=None, alpha=0., *args, **kwargs): self.mode = self.mu * (tt.sqrt(1. + (1.5 * self.mu / self.lam)**2) - 1.5 * self.mu / self.lam) + alpha self.variance = (self.mu**3) / self.lam + + assert_nonpos_support(phi, 'phi', 'Wald') + assert_nonpos_support(mu, 'mu', 'Wald') + assert_nonpos_support(lam, 'lam', 'Wald') def get_mu_lam_phi(self, mu, lam, phi): if mu is None: @@ -433,6 +463,9 @@ def __init__(self, alpha=None, beta=None, mu=None, sd=None, self.mean = alpha / (alpha + beta) self.variance = alpha * beta / ( (alpha + beta)**2 * (alpha + beta + 1)) + + assert_nonpos_support(alpha, 'alpha', 'Beta') + assert_nonpos_support(beta, 'beta', 'Beta') def get_alpha_beta(self, alpha=None, beta=None, mu=None, sd=None): if (alpha is not None) and (beta is not None): @@ -492,6 +525,8 @@ def __init__(self, lam, *args, **kwargs): self.mode = 0 self.variance = lam**-2 + + assert_nonpos_support(lam, 'lam', 'Exponential') def random(self, point=None, size=None, repeat=None): lam = draw_values([self.lam], point=point) @@ -533,6 +568,8 @@ def __init__(self, mu, b, *args, **kwargs): self.mean = self.median = self.mode = self.mu = mu self.variance = 2 * b**2 + + assert_nonpos_support(b, 'b', 'Laplace') def random(self, point=None, size=None, repeat=None): mu, b = draw_values([self.mu, self.b], point=point) @@ -586,6 +623,9 @@ def __init__(self, mu=0, sd=None, tau=None, *args, **kwargs): self.median = tt.exp(mu) self.mode = tt.exp(mu - 1. / self.tau) self.variance = (tt.exp(1. / self.tau) - 1) * tt.exp(2 * mu + 1. / self.tau) + + assert_nonpos_support(tau, 'tau', 'Lognormal') + assert_nonpos_support(sd, 'sd', 'Lognormal') def _random(self, mu, tau, size=None): samples = np.random.normal(size=size) @@ -644,6 +684,9 @@ def __init__(self, nu, mu=0, lam=None, sd=None, *args, **kwargs): self.variance = tt.switch((nu > 2) * 1, (1 / self.lam) * (nu / (nu - 2)), np.inf) + + assert_nonpos_support(lam, 'lam (sd)', 'StudentT') + assert_nonpos_support(nu, 'nu', 'StudentT') def random(self, point=None, size=None, repeat=None): nu, mu, lam = draw_values([self.nu, self.mu, self.lam], @@ -702,6 +745,10 @@ def __init__(self, alpha, m, *args, **kwargs): tt.gt(alpha, 2), (alpha * m**2) / ((alpha - 2.) * (alpha - 1.)**2), np.inf) + + assert_nonpos_support(alpha, 'alpha', 'Pareto') + assert_nonpos_support(m, 'm', 'Pareto') + def _random(self, alpha, m, size=None): u = np.random.uniform(size=size) @@ -752,6 +799,8 @@ def __init__(self, alpha, beta, *args, **kwargs): super(Cauchy, self).__init__(*args, **kwargs) self.median = self.mode = self.alpha = alpha self.beta = beta + + assert_nonpos_support(beta, 'beta', 'Cauchy') def _random(self, alpha, beta, size=None): u = np.random.uniform(size=size) @@ -798,6 +847,8 @@ def __init__(self, beta, *args, **kwargs): self.mode = 0 self.median = beta self.beta = beta + + assert_nonpos_support(beta, 'beta', 'HalfCauchy') def _random(self, beta, size=None): u = np.random.uniform(size=size) @@ -864,6 +915,9 @@ def __init__(self, alpha=None, beta=None, mu=None, sd=None, self.mean = alpha / beta self.mode = tt.maximum((alpha - 1) / beta, 0) self.variance = alpha / beta**2 + + assert_nonpos_support(alpha, 'alpha', 'Gamma') + assert_nonpos_support(beta, 'beta', 'Gamma') def get_alpha_beta(self, alpha=None, beta=None, mu=None, sd=None): if (alpha is not None) and (beta is not None): @@ -931,6 +985,8 @@ def __init__(self, alpha, beta=1, *args, **kwargs): self.variance = tt.switch(tt.gt(alpha, 2), (beta**2) / (alpha * (alpha - 1.)**2), np.inf) + assert_nonpos_support(alpha, 'alpha', 'InverseGamma') + assert_nonpos_support(beta, 'beta', 'InverseGamma') def _calculate_mean(self): m = self.beta / (self.alpha - 1.) @@ -1013,6 +1069,9 @@ def __init__(self, alpha, beta, *args, **kwargs): self.median = beta * tt.exp(gammaln(tt.log(2)))**(1. / alpha) self.variance = (beta**2) * \ tt.exp(gammaln(1 + 2. / alpha - self.mean**2)) + + assert_nonpos_support(alpha, 'alpha', 'Weibull') + assert_nonpos_support(beta, 'beta', 'Weibull') def random(self, point=None, size=None, repeat=None): alpha, beta = draw_values([self.alpha, self.beta], @@ -1199,6 +1258,9 @@ def __init__(self, mu, sigma, nu, *args, **kwargs): self.nu = nu self.mean = mu + nu self.variance = (sigma**2) + (nu**2) + + assert_nonpos_support(sigma, 'sigma', 'ExGaussian') + assert_nonpos_support(nu, 'nu', 'ExGaussian') def random(self, point=None, size=None, repeat=None): mu, sigma, nu = draw_values([self.mu, self.sigma, self.nu], @@ -1257,6 +1319,8 @@ def __init__(self, mu=0.0, kappa=None, transform='circular', if transform == 'circular': self.transform = transforms.Circular() + + assert_nonpos_support(kappa, 'kappa', 'VonMises') def random(self, point=None, size=None, repeat=None): mu, kappa = draw_values([self.mu, self.kappa], @@ -1312,6 +1376,9 @@ def __init__(self, mu=0.0, sd=None, tau=None, alpha=1, *args, **kwargs): self.alpha = alpha self.mean = mu + self.sd * (2 / np.pi)**0.5 * alpha / (1 + alpha**2)**0.5 self.variance = self.sd**2 * (1 - (2 * alpha**2) / ((1 + alpha**2) * np.pi)) + + assert_nonpos_support(tau, 'tau', 'SkewNormal') + assert_nonpos_support(sd, 'sd', 'SkewNormal') def random(self, point=None, size=None, repeat=None): mu, tau, sd, alpha = draw_values(