From 52e6ac758a661cc18a21d053e481063c40e9a6bc Mon Sep 17 00:00:00 2001 From: Ricardo Date: Sun, 17 Jan 2021 15:14:59 +0100 Subject: [PATCH 1/6] Test logcdf methods outside of finite edges --- pymc3/tests/test_distributions.py | 138 ++++++++++++++++++++++++------ 1 file changed, 114 insertions(+), 24 deletions(-) diff --git a/pymc3/tests/test_distributions.py b/pymc3/tests/test_distributions.py index e15996e7aba..dd1deb19295 100644 --- a/pymc3/tests/test_distributions.py +++ b/pymc3/tests/test_distributions.py @@ -587,46 +587,121 @@ def check_logcdf( scipy_logcdf, decimal=None, n_samples=100, + skip_paramdomain_inside_edge_test=False, + skip_paramdomain_outside_edge_test=False, ): - domains = paramdomains.copy() - domains["value"] = domain - if decimal is None: - decimal = select_by_precision(float64=6, float32=3) - for pt in product(domains, n_samples=n_samples): - params = dict(pt) - scipy_cdf = scipy_logcdf(**params) - value = params.pop("value") - dist = pymc3_dist.dist(**params) - assert_almost_equal( - dist.logcdf(value).tag.test_value, - scipy_cdf, - decimal=decimal, - err_msg=str(pt), - ) + """ + Generic test for PyMC3 logcdf methods + + The following tests are performed by default: + 1. Test PyMC3 logcdf and equivalent scipy logcdf methods give similar + results for valid values and parameters within the supported edges. + Can be skipped via skip_paramdomain_inside_edge_test + 2. Test PyMC3 logcdf method returns -inf for invalid parameter values + outside the supported edges. Can be skipped via skip_paramdomain_outside_edge_test + 3. Test PyMC3 logcdf method returns -inf and 0 for values below and + above the supported edge, respectively, when using valid parameters. + 4. Test PyMC3 logcdf methods works with multiple value or returns + default informative TypeError + + Parameters + ---------- + pymc3_dist: PyMC3 distribution + domain : Domain + Supported domain of distribution values + paramdomains : Dictionary of Pamameter : Domain pairs + Supported domains of distribution parameters + scipy_logcdf : Scipy logcdf method + Scipy logcdf method of equivalent pymc3_dist distribution + decimal : Int + Level of precision with which pymc3_dist and scipy_logcdf are + compared. Defaults to 6 for float64 and 3 for float32 + n_samples : Int + Upper limit on the number of valid domain and value combinations that + are compared between pymc3 and scipy methods. If n_samples is below + the total number of combinations, a random subset is evaluated. + Defaults to 100 + skip_paramdomain_inside_edge_test : Bool + Whether to compare pymc3 and scipy distributions match for valid values + and parameters within the respective domain edges (excluding edges) + skip_paramdomain_outside_edge_test : Bool + Whether to test pymc3 distribution logcdf returns -inf for invalid + parameter values that lie beyond the supported edge (excluding edges) + + Returns + ------- + + """ + # Test pymc3 and scipy distributions match for values and parameters + # within the supported domain edges (excluding edges) + if not skip_paramdomain_inside_edge_test: + domains = paramdomains.copy() + domains["value"] = domain + if decimal is None: + decimal = select_by_precision(float64=6, float32=3) + for pt in product(domains, n_samples=n_samples): + params = dict(pt) + scipy_cdf = scipy_logcdf(**params) + value = params.pop("value") + dist = pymc3_dist.dist(**params) + assert_almost_equal( + dist.logcdf(value).tag.test_value, + scipy_cdf, + decimal=decimal, + err_msg=str(pt), + ) + + valid_value = domain.vals[0] + valid_params = {param: paramdomain.vals[0] for param, paramdomain in paramdomains.items()} + valid_dist = pymc3_dist.dist(**valid_params) - # Test that values below domain evaluate to -np.inf + # Natural domains do not have inf as the upper edge, but should also be ignored + nat_domains = (NatSmall, Nat, NatBig, PosNat) + + # Test pymc3 distribution gives -inf for parameters outside the + # supported domain edges (excluding edgse) + if not skip_paramdomain_outside_edge_test: + # Step1: collect potential invalid parameters + invalid_params = {param: [None, None] for param in paramdomains} + for param, paramdomain in paramdomains.items(): + if np.isfinite(paramdomain.lower): + invalid_params[param][0] = paramdomain.lower - 1 + if np.isfinite(paramdomain.upper) and paramdomain not in nat_domains: + invalid_params[param][1] = paramdomain.upper + 1 + # Step2: test invalid parameters, one a time + for invalid_param, invalid_edges in invalid_params.items(): + for invalid_edge in invalid_edges: + if invalid_edge is not None: + test_params = valid_params.copy() # Shallow copy should be okay + test_params[invalid_param] = invalid_edge + invalid_dist = pymc3_dist.dist(**test_params) + assert_equal( + invalid_dist.logcdf(valid_value).tag.test_value, + -np.inf, + err_msg=str(test_params), + ) + + # Test that values below domain edge evaluate to -np.inf if np.isfinite(domain.lower): below_domain = domain.lower - 1 assert_equal( - dist.logcdf(below_domain).tag.test_value, + valid_dist.logcdf(below_domain).tag.test_value, -np.inf, err_msg=str(below_domain), ) - # Test that values above domain evaluate to 0 - # Natural domains do not have inf as the upper edge, but should also be ignored - not_nat_domain = domain not in (NatSmall, Nat, NatBig, PosNat) - if not_nat_domain and np.isfinite(domain.upper): + # Test that values above domain edge evaluate to 0 + if domain not in nat_domains and np.isfinite(domain.upper): above_domain = domain.upper + 1 assert_equal( - dist.logcdf(above_domain).tag.test_value, + valid_dist.logcdf(above_domain).tag.test_value, 0, err_msg=str(above_domain), ) # Test that method works with multiple values or raises informative TypeError try: - dist.logcdf(np.array([value, value])).tag.test_value + valid_dist.logcdf(np.array([valid_value, valid_value])).tag.test_value except TypeError as err: if not str(err).endswith( ".logcdf expects a scalar value but received a 1-dimensional object." @@ -686,6 +761,7 @@ def test_uniform(self): Runif, {"lower": -Rplusunif, "upper": Rplusunif}, lambda value, lower, upper: sp.uniform.logcdf(value, lower, upper - lower), + skip_paramdomain_outside_edge_test=True, ) def test_triangular(self): @@ -700,6 +776,7 @@ def test_triangular(self): Runif, {"lower": -Rplusunif, "c": Runif, "upper": Rplusunif}, lambda value, c, lower, upper: sp.triang.logcdf(value, c - lower, lower, upper - lower), + skip_paramdomain_outside_edge_test=True, ) def test_bound_normal(self): @@ -727,6 +804,7 @@ def test_discrete_unif(self): Rdunif, {"lower": -Rplusdunif, "upper": Rplusdunif}, lambda value, lower, upper: sp.randint.logcdf(value, lower, upper + 1), + skip_paramdomain_outside_edge_test=True, ) self.check_selfconsistency_discrete_logcdf( DiscreteUniform, @@ -807,7 +885,8 @@ def test_chi_squared(self): lambda value, nu: sp.chi2.logpdf(value, df=nu), ) - @pytest.mark.xfail(reason="Poor CDF in SciPy. See scipy/scipy#869 for details.") + # TODO: Is this still needed? It passes locally. + # @pytest.mark.xfail(reason="Poor CDF in SciPy. See scipy/scipy#869 for details.") def test_wald_scipy(self): self.pymc3_matches_scipy( Wald, @@ -1099,6 +1178,7 @@ def test_fun(value, mu, sigma): Rplus, {"alpha": Rplusbig, "beta": Rplusbig}, lambda value, alpha, beta: sp.gamma.logcdf(value, alpha, scale=1.0 / beta), + skip_paramdomain_outside_edge_test=False, # TODO: This is failing mysteriously ) @pytest.mark.xfail( @@ -1117,6 +1197,7 @@ def test_inverse_gamma(self): Rplus, {"alpha": Rplus, "beta": Rplus}, lambda value, alpha, beta: sp.invgamma.logcdf(value, alpha, scale=beta), + skip_paramdomain_outside_edge_test=False, # TODO: This is failing mysteriously ) @pytest.mark.xfail( @@ -2024,6 +2105,15 @@ def test_ex_gaussian_cdf(self, value, mu, sigma, nu, logcdf): err_msg=str((value, mu, sigma, nu, logcdf)), ) + def test_ex_gaussian_cdf_outside_edges(self): + self.check_logcdf( + ExGaussian, + R, + {"mu": R, "sigma": Rplus, "nu": Rplus}, + None, + skip_paramdomain_inside_edge_test=True, # Valid values are tested above + ) + @pytest.mark.xfail(condition=(theano.config.floatX == "float32"), reason="Fails on float32") def test_vonmises(self): self.pymc3_matches_scipy( From 13cdba5a1ecc224167f994887b49b549f2d07beb Mon Sep 17 00:00:00 2001 From: Ricardo Date: Sun, 17 Jan 2021 15:15:13 +0100 Subject: [PATCH 2/6] Fix failing logcdf methods --- pymc3/distributions/continuous.py | 151 ++++++++++++++++++++---------- pymc3/distributions/discrete.py | 1 + 2 files changed, 102 insertions(+), 50 deletions(-) diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index 985f90bcbaf..5c990bde3b5 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -553,7 +553,12 @@ def logcdf(self, value): ------- TensorVariable """ - return normal_lcdf(self.mu, self.sigma, value) + mu = self.mu + sigma = self.sigma + return bound( + normal_lcdf(mu, sigma, value), + 0 < sigma, + ) class TruncatedNormal(BoundedContinuous): @@ -1144,10 +1149,16 @@ def logcdf(self, value): tt.eq(value, 0) & tt.eq(lam, np.inf) ) - return tt.switch( - left_limit, - -np.inf, - tt.switch((right_limit | degenerate_dist), 0, a + tt.log1p(tt.exp(b - a))), + return bound( + tt.switch( + ~(right_limit | degenerate_dist), + a + tt.log1p(tt.exp(b - a)), + 0, + ), + ~left_limit, + 0 < mu, + 0 < lam, + 0 <= alpha, ) @@ -1539,10 +1550,10 @@ def logcdf(self, value): value = floatX(tt.as_tensor(value)) lam = self.lam a = lam * value - return tt.switch( - tt.le(value, 0.0) | tt.le(lam, 0), - -np.inf, + return bound( log1mexp(a), + 0 <= value, + 0 <= lam, ) @@ -1654,10 +1665,17 @@ def logcdf(self, value): a = self.mu b = self.b y = (value - a) / b - return tt.switch( - tt.le(value, a), - tt.log(0.5) + y, - tt.switch(tt.gt(y, 1), tt.log1p(-0.5 * tt.exp(-y)), tt.log(1 - 0.5 * tt.exp(-y))), + return bound( + tt.switch( + tt.le(value, a), + tt.log(0.5) + y, + tt.switch( + tt.gt(y, 1), + tt.log1p(-0.5 * tt.exp(-y)), + tt.log(1 - 0.5 * tt.exp(-y)), + ), + ), + 0 < b, ) @@ -1909,16 +1927,12 @@ def logcdf(self, value): """ mu = self.mu sigma = self.sigma - z = zvalue(tt.log(value), mu=mu, sigma=sigma) + tau = self.tau - return tt.switch( - tt.le(value, 0), - -np.inf, - tt.switch( - tt.lt(z, -1.0), - tt.log(tt.erfcx(-z / tt.sqrt(2.0)) / 2.0) - tt.sqr(z) / 2, - tt.log1p(-tt.erfc(z / tt.sqrt(2.0)) / 2.0), - ), + return bound( + normal_lcdf(mu, sigma, tt.log(value)), + 0 < value, + 0 < tau, ) @@ -2220,8 +2234,15 @@ def logcdf(self, value): m = self.m alpha = self.alpha arg = (m / value) ** alpha - return tt.switch( - tt.lt(value, m), -np.inf, tt.switch(tt.le(arg, 1e-5), tt.log1p(-arg), tt.log(1 - arg)) + return bound( + tt.switch( + tt.le(arg, 1e-5), + tt.log1p(-arg), + tt.log(1 - arg), + ), + m <= value, + 0 < alpha, + 0 < m, ) @@ -2336,7 +2357,12 @@ def logcdf(self, value): ------- TensorVariable """ - return tt.log(0.5 + tt.arctan((value - self.alpha) / self.beta) / np.pi) + alpha = self.alpha + beta = self.beta + return bound( + tt.log(0.5 + tt.arctan((value - alpha) / beta) / np.pi), + 0 < beta, + ) class HalfCauchy(PositiveContinuous): @@ -2444,7 +2470,12 @@ def logcdf(self, value): ------- TensorVariable """ - return tt.switch(tt.le(value, 0), -np.inf, tt.log(2 * tt.arctan(value / self.beta) / np.pi)) + beta = self.beta + return bound( + tt.log(2 * tt.arctan(value / beta) / np.pi), + 0 <= value, + 0 < beta, + ) class Gamma(PositiveContinuous): @@ -2953,10 +2984,11 @@ def logcdf(self, value): alpha = self.alpha beta = self.beta a = (value / beta) ** alpha - return tt.switch( - tt.le(value, 0.0), - -np.inf, + return bound( log1mexp(a), + 0 <= value, + 0 < alpha, + 0 < beta, ) @@ -3255,17 +3287,21 @@ def logcdf(self, value): nu = self.nu # Alogithm is adapted from pexGAUS.R from gamlss - return tt.switch( - tt.gt(nu, 0.05 * sigma), - logdiffexp( - normal_lcdf(mu, sigma, value), - ( - (mu - value) / nu - + 0.5 * (sigma / nu) ** 2 - + normal_lcdf(mu + (sigma ** 2) / nu, sigma, value) + return bound( + tt.switch( + tt.gt(nu, 0.05 * sigma), + logdiffexp( + normal_lcdf(mu, sigma, value), + ( + (mu - value) / nu + + 0.5 * (sigma / nu) ** 2 + + normal_lcdf(mu + (sigma ** 2) / nu, sigma, value) + ), ), + normal_lcdf(mu, sigma, value), ), - normal_lcdf(mu, sigma, value), + 0 < sigma, + 0 < nu, ) def _distr_parameters_for_repr(self): @@ -3753,8 +3789,13 @@ def logp(self, value): ------- TensorVariable """ - scaled = (value - self.mu) / self.beta - return bound(-scaled - tt.exp(-scaled) - tt.log(self.beta), self.beta > 0) + mu = self.mu + beta = self.beta + scaled = (value - mu) / beta + return bound( + -scaled - tt.exp(-scaled) - tt.log(self.beta), + 0 < beta, + ) def logcdf(self, value): """ @@ -3774,7 +3815,10 @@ def logcdf(self, value): beta = self.beta mu = self.mu - return -tt.exp(-(value - mu) / beta) + return bound( + -tt.exp(-(value - mu) / beta), + 0 < beta, + ) class Rice(PositiveContinuous): @@ -4052,7 +4096,10 @@ def logcdf(self, value): """ mu = self.mu s = self.s - return -log1pexp(-(value - mu) / s) + return bound( + -log1pexp(-(value - mu) / s), + 0 < s, + ) class LogitNormal(UnitContinuous): @@ -4360,14 +4407,12 @@ def logp(self, value): ------- TensorVariable """ - scaled = (value - self.mu) / self.sigma + mu = self.mu + sigma = self.sigma + scaled = (value - mu) / sigma return bound( - ( - -(1 / 2) * (scaled + tt.exp(-scaled)) - - tt.log(self.sigma) - - (1 / 2) * tt.log(2 * np.pi) - ), - self.sigma > 0, + (-(1 / 2) * (scaled + tt.exp(-scaled)) - tt.log(sigma) - (1 / 2) * tt.log(2 * np.pi)), + 0 < sigma, ) def logcdf(self, value): @@ -4385,5 +4430,11 @@ def logcdf(self, value): ------- TensorVariable """ - scaled = (value - self.mu) / self.sigma - return tt.log(tt.erfc(tt.exp(-scaled / 2) * (2 ** -0.5))) + mu = self.mu + sigma = self.sigma + + scaled = (value - mu) / sigma + return bound( + tt.log(tt.erfc(tt.exp(-scaled / 2) * (2 ** -0.5))), + 0 < sigma, + ) diff --git a/pymc3/distributions/discrete.py b/pymc3/distributions/discrete.py index 1710b124408..0bac6fd6b23 100644 --- a/pymc3/distributions/discrete.py +++ b/pymc3/distributions/discrete.py @@ -349,6 +349,7 @@ def logcdf(self, value): 0, ), 0 <= value, + 0 <= n, 0 < alpha, 0 < beta, ) From 83014e80514fbd9a47dfd42745658fdbbb9c1f31 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Wed, 20 Jan 2021 08:54:51 +0100 Subject: [PATCH 3/6] Disable failing tests temporarily --- RELEASE-NOTES.md | 2 +- pymc3/tests/test_distributions.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index ac4bb2f8776..f4cddaf44e2 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -32,7 +32,7 @@ It also brings some dreadfully awaited fixes, so be sure to go through the chang - Fixed mathematical formulation in `MvStudentT` random method. (see [#4359](https://github.com/pymc-devs/pymc3/pull/4359)) - Fix issue in `logp` method of `HyperGeometric`. It now returns `-inf` for invalid parameters (see [4367](https://github.com/pymc-devs/pymc3/pull/4367)) - Fixed `MatrixNormal` random method to work with parameters as random variables. (see [#4368](https://github.com/pymc-devs/pymc3/pull/4368)) -- Update the `logcdf` method of several continuous distributions to return -inf for invalid parameters and values, and raise an informative error when multiple values cannot be evaluated in a single call. (see [4393](https://github.com/pymc-devs/pymc3/pull/4393)) +- Update the `logcdf` method of several continuous distributions to return -inf for invalid parameters and values, and raise an informative error when multiple values cannot be evaluated in a single call. (see [4393](https://github.com/pymc-devs/pymc3/pull/4393) and [#4421](https://github.com/pymc-devs/pymc3/pull/4421)) - Improve numerical stability in `logp` and `logcdf` methods of `ExGaussian` (see [#4407](https://github.com/pymc-devs/pymc3/pull/4407)) - Issue UserWarning when doing prior or posterior predictive sampling with models containing Potential factors (see [#4419](https://github.com/pymc-devs/pymc3/pull/4419)) - Dirichlet distribution's `random` method is now optimized and gives outputs in correct shape (see [#4416](https://github.com/pymc-devs/pymc3/pull/4407)) diff --git a/pymc3/tests/test_distributions.py b/pymc3/tests/test_distributions.py index dd1deb19295..186e95208ac 100644 --- a/pymc3/tests/test_distributions.py +++ b/pymc3/tests/test_distributions.py @@ -1178,7 +1178,7 @@ def test_fun(value, mu, sigma): Rplus, {"alpha": Rplusbig, "beta": Rplusbig}, lambda value, alpha, beta: sp.gamma.logcdf(value, alpha, scale=1.0 / beta), - skip_paramdomain_outside_edge_test=False, # TODO: This is failing mysteriously + skip_paramdomain_outside_edge_test=True, # TODO: This is failing mysteriously ) @pytest.mark.xfail( @@ -1197,7 +1197,7 @@ def test_inverse_gamma(self): Rplus, {"alpha": Rplus, "beta": Rplus}, lambda value, alpha, beta: sp.invgamma.logcdf(value, alpha, scale=beta), - skip_paramdomain_outside_edge_test=False, # TODO: This is failing mysteriously + skip_paramdomain_outside_edge_test=True, # TODO: This is failing mysteriously ) @pytest.mark.xfail( From 7dcb67c3d82cc1a6469f093ac886bc8394c2eb34 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Wed, 20 Jan 2021 10:06:48 +0100 Subject: [PATCH 4/6] Revert removed xfail --- pymc3/tests/test_distributions.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pymc3/tests/test_distributions.py b/pymc3/tests/test_distributions.py index 186e95208ac..adec2bc47d5 100644 --- a/pymc3/tests/test_distributions.py +++ b/pymc3/tests/test_distributions.py @@ -885,8 +885,10 @@ def test_chi_squared(self): lambda value, nu: sp.chi2.logpdf(value, df=nu), ) - # TODO: Is this still needed? It passes locally. - # @pytest.mark.xfail(reason="Poor CDF in SciPy. See scipy/scipy#869 for details.") + @pytest.mark.xfail( + condition=(theano.config.floatX == "float32"), + reason="Poor CDF in SciPy. See scipy/scipy#869 for details.", + ) def test_wald_scipy(self): self.pymc3_matches_scipy( Wald, @@ -1178,7 +1180,7 @@ def test_fun(value, mu, sigma): Rplus, {"alpha": Rplusbig, "beta": Rplusbig}, lambda value, alpha, beta: sp.gamma.logcdf(value, alpha, scale=1.0 / beta), - skip_paramdomain_outside_edge_test=True, # TODO: This is failing mysteriously + skip_paramdomain_outside_edge_test=True, # TODO: When True, Python crashes ) @pytest.mark.xfail( @@ -1197,7 +1199,7 @@ def test_inverse_gamma(self): Rplus, {"alpha": Rplus, "beta": Rplus}, lambda value, alpha, beta: sp.invgamma.logcdf(value, alpha, scale=beta), - skip_paramdomain_outside_edge_test=True, # TODO: This is failing mysteriously + skip_paramdomain_outside_edge_test=True, # TODO: When True, Python crashes ) @pytest.mark.xfail( From 9c5aa059527e2689416c8639ef1620832249f5f1 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Wed, 20 Jan 2021 12:34:37 +0100 Subject: [PATCH 5/6] Update docstring --- pymc3/tests/test_distributions.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pymc3/tests/test_distributions.py b/pymc3/tests/test_distributions.py index adec2bc47d5..ee8d033e3f9 100644 --- a/pymc3/tests/test_distributions.py +++ b/pymc3/tests/test_distributions.py @@ -595,7 +595,9 @@ def check_logcdf( The following tests are performed by default: 1. Test PyMC3 logcdf and equivalent scipy logcdf methods give similar - results for valid values and parameters within the supported edges. + results for valid values and parameters inside the supported edges. + Edges are excluded by default, but can be artificially included by + creating a domain with repeated values (e.g., `Domain([0, 0, .5, 1, 1]`) Can be skipped via skip_paramdomain_inside_edge_test 2. Test PyMC3 logcdf method returns -inf for invalid parameter values outside the supported edges. Can be skipped via skip_paramdomain_outside_edge_test @@ -609,24 +611,24 @@ def check_logcdf( pymc3_dist: PyMC3 distribution domain : Domain Supported domain of distribution values - paramdomains : Dictionary of Pamameter : Domain pairs + paramdomains : Dictionary of Parameter : Domain pairs Supported domains of distribution parameters scipy_logcdf : Scipy logcdf method Scipy logcdf method of equivalent pymc3_dist distribution decimal : Int - Level of precision with which pymc3_dist and scipy_logcdf are - compared. Defaults to 6 for float64 and 3 for float32 + Level of precision with which pymc3_dist and scipy_logcdf are compared. + Defaults to 6 for float64 and 3 for float32 n_samples : Int Upper limit on the number of valid domain and value combinations that - are compared between pymc3 and scipy methods. If n_samples is below - the total number of combinations, a random subset is evaluated. + are compared between pymc3 and scipy methods. If n_samples is below the + total number of combinations, a random subset is evaluated. Defaults to 100 skip_paramdomain_inside_edge_test : Bool - Whether to compare pymc3 and scipy distributions match for valid values - and parameters within the respective domain edges (excluding edges) + Whether to run test 1., which checks that pymc3 and scipy distributions + match for valid values and parameters inside the respective domain edges skip_paramdomain_outside_edge_test : Bool - Whether to test pymc3 distribution logcdf returns -inf for invalid - parameter values that lie beyond the supported edge (excluding edges) + Whether to run test 2., which checks that pymc3 distribution logcdf + returns -inf for invalid parameter values outside the supported domain edge Returns ------- From 8358a9703f0cb4a92b35a47a922d1b2d00ce994a Mon Sep 17 00:00:00 2001 From: Ricardo Date: Wed, 20 Jan 2021 16:50:51 +0100 Subject: [PATCH 6/6] Add more informative comment --- pymc3/tests/test_distributions.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pymc3/tests/test_distributions.py b/pymc3/tests/test_distributions.py index ee8d033e3f9..5cbede8e507 100644 --- a/pymc3/tests/test_distributions.py +++ b/pymc3/tests/test_distributions.py @@ -1177,12 +1177,15 @@ def test_fun(value, mu, sigma): self.pymc3_matches_scipy(Gamma, Rplus, {"mu": Rplusbig, "sigma": Rplusbig}, test_fun) + # pymc-devs/Theano-PyMC#224: skip_paramdomain_outside_edge_test has to be set + # True to avoid triggering a C-level assertion in the Theano GammaQ function + # in gamma.c file. Can be set back to False (defalut) once that issue is solved self.check_logcdf( Gamma, Rplus, {"alpha": Rplusbig, "beta": Rplusbig}, lambda value, alpha, beta: sp.gamma.logcdf(value, alpha, scale=1.0 / beta), - skip_paramdomain_outside_edge_test=True, # TODO: When True, Python crashes + skip_paramdomain_outside_edge_test=True, ) @pytest.mark.xfail( @@ -1196,12 +1199,15 @@ def test_inverse_gamma(self): {"alpha": Rplus, "beta": Rplus}, lambda value, alpha, beta: sp.invgamma.logpdf(value, alpha, scale=beta), ) + # pymc-devs/Theano-PyMC#224: skip_paramdomain_outside_edge_test has to be set + # True to avoid triggering a C-level assertion in the Theano GammaQ function + # in gamma.c file. Can be set back to False (defalut) once that issue is solved self.check_logcdf( InverseGamma, Rplus, {"alpha": Rplus, "beta": Rplus}, lambda value, alpha, beta: sp.invgamma.logcdf(value, alpha, scale=beta), - skip_paramdomain_outside_edge_test=True, # TODO: When True, Python crashes + skip_paramdomain_outside_edge_test=True, ) @pytest.mark.xfail(