From 8641b1dbabcc423a2c309ab6f072e2c5f7e22d55 Mon Sep 17 00:00:00 2001 From: Farhan Reynaldo Date: Thu, 13 May 2021 12:15:11 +0700 Subject: [PATCH 1/9] refactor pareto --- pymc3/distributions/continuous.py | 40 ++++++++++-------------- pymc3/tests/test_distributions_random.py | 14 +++++++++ 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index a2b9bd4f47..3c1a45d7f1 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -1980,6 +1980,22 @@ def logcdf(self, value): ) +class ParetoRV(RandomVariable): + name = "pareto" + ndim_supp = 0 + ndims_params = [0, 0] + dtype = "int64" + _print_name = ("Pareto", "\\operatorname{Pareto}") + + @classmethod + def rng_fn(cls, rng, alpha, m, size): + u = rng.uniform(size=size) + return m * (1.0 - u) ** (-1.0 / alpha) + + +pareto = ParetoRV() + + class Pareto(Continuous): r""" Pareto log-likelihood. @@ -2039,30 +2055,6 @@ def dist( return super().dist([alpha, m], **kwargs) - def _random(self, alpha, m, size=None): - u = np.random.uniform(size=size) - return m * (1.0 - u) ** (-1.0 / alpha) - - def random(self, point=None, size=None): - """ - Draw random values from Pareto distribution. - - Parameters - ---------- - point: dict, optional - Dict of variable values on which random values are to be - conditioned (uses default point if not specified). - size: int, optional - Desired size of random sample (returns one sample if not - specified). - - Returns - ------- - array - """ - # alpha, m = draw_values([self.alpha, self.m], point=point, size=size) - # return generate_samples(self._random, alpha, m, dist_shape=self.shape, size=size) - def logp( value: Union[float, np.ndarray, TensorVariable], alpha: Union[float, np.ndarray, TensorVariable], diff --git a/pymc3/tests/test_distributions_random.py b/pymc3/tests/test_distributions_random.py index 82d4f4e091..5d1857e0cf 100644 --- a/pymc3/tests/test_distributions_random.py +++ b/pymc3/tests/test_distributions_random.py @@ -449,6 +449,20 @@ def seeded_discrete_weibul_rng_fn(self): ] +class TestPareto(BaseTestDistribution): + pymc_dist = pm.Pareto + pymc_dist_params = {"alpha": 3.0, "m": 2.0} + expected_rv_op_params = {"alpha": 3.0, "m": 2.0} + reference_dist_params = {"alpha": 3.0, "scale": 2.0} + size = 15 + reference_dist = seeded_numpy_distribution_builder("pareto") + tests_to_run = [ + "check_pymc_params_match_rv_op", + "check_pymc_draws_match_reference", + "check_rv_size", + ] + + class TestGumbel(BaseTestDistribution): pymc_dist = pm.Gumbel pymc_dist_params = {"mu": 1.5, "beta": 3.0} From 88108b7013d782dc322eba124752be819d441f8a Mon Sep 17 00:00:00 2001 From: Farhan Reynaldo Date: Thu, 13 May 2021 16:04:20 +0700 Subject: [PATCH 2/9] remove as available on aesara --- pymc3/distributions/continuous.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index 3c1a45d7f1..9f9d6cdcc9 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -1980,22 +1980,6 @@ def logcdf(self, value): ) -class ParetoRV(RandomVariable): - name = "pareto" - ndim_supp = 0 - ndims_params = [0, 0] - dtype = "int64" - _print_name = ("Pareto", "\\operatorname{Pareto}") - - @classmethod - def rng_fn(cls, rng, alpha, m, size): - u = rng.uniform(size=size) - return m * (1.0 - u) ** (-1.0 / alpha) - - -pareto = ParetoRV() - - class Pareto(Continuous): r""" Pareto log-likelihood. From 6ca3075a7d4071fa1069cbba9885ffbb96333d4a Mon Sep 17 00:00:00 2001 From: Farhan Reynaldo Date: Thu, 13 May 2021 16:04:29 +0700 Subject: [PATCH 3/9] refactor laplace --- pymc3/distributions/continuous.py | 49 ++++++++++-------------- pymc3/tests/test_distributions.py | 1 - pymc3/tests/test_distributions_random.py | 21 ++++++---- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index 9f9d6cdcc9..debe6a7064 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -1463,6 +1463,21 @@ def logcdf(value, mu): ) +class LaplaceRV(RandomVariable): + name = "laplace" + ndim_supp = 0 + ndims_params = [0, 0] + dtype = "floatX" + _print_name = ("Laplace", "\\operatorname{Laplace}") + + @classmethod + def rng_fn(cls, rng, mu, b, size=None): + return stats.laplace.rvs(mu, b, size=size, random_state=rng) + + +laplace = LaplaceRV() + + class Laplace(Continuous): r""" Laplace log-likelihood. @@ -1505,6 +1520,7 @@ class Laplace(Continuous): b: float Scale parameter (b > 0). """ + rv_op = laplace def __init__(self, mu, b, *args, **kwargs): super().__init__(*args, **kwargs) @@ -1515,27 +1531,7 @@ def __init__(self, mu, b, *args, **kwargs): assert_negative_support(b, "b", "Laplace") - def random(self, point=None, size=None): - """ - Draw random values from Laplace distribution. - - Parameters - ---------- - point: dict, optional - Dict of variable values on which random values are to be - conditioned (uses default point if not specified). - size: int, optional - Desired size of random sample (returns one sample if not - specified). - - Returns - ------- - array - """ - # mu, b = draw_values([self.mu, self.b], point=point, size=size) - # return generate_samples(np.random.laplace, mu, b, dist_shape=self.shape, size=size) - - def logp(self, value): + def logp(value, mu, b): """ Calculate log-probability of Laplace distribution at specified value. @@ -1549,12 +1545,9 @@ def logp(self, value): ------- TensorVariable """ - mu = self.mu - b = self.b - return -at.log(2 * b) - abs(value - mu) / b - def logcdf(self, value): + def logcdf(value, mu, b): """ Compute the log of the cumulative distribution function for Laplace distribution at the specified value. @@ -1569,12 +1562,10 @@ def logcdf(self, value): ------- TensorVariable """ - a = self.mu - b = self.b - y = (value - a) / b + y = (value - mu) / b return bound( at.switch( - at.le(value, a), + at.le(value, mu), at.log(0.5) + y, at.switch( at.gt(y, 1), diff --git a/pymc3/tests/test_distributions.py b/pymc3/tests/test_distributions.py index 63466b7902..ff23afd5a6 100644 --- a/pymc3/tests/test_distributions.py +++ b/pymc3/tests/test_distributions.py @@ -1250,7 +1250,6 @@ def test_negative_binomial_init_fail(self, mu, p, alpha, n, expected): with pytest.raises(ValueError, match=f"Incompatible parametrization. {expected}"): NegativeBinomial("x", mu=mu, p=p, alpha=alpha, n=n) - @pytest.mark.xfail(reason="Distribution not refactored yet") def test_laplace(self): self.check_logp( Laplace, diff --git a/pymc3/tests/test_distributions_random.py b/pymc3/tests/test_distributions_random.py index 5d1857e0cf..355b6fb4d5 100644 --- a/pymc3/tests/test_distributions_random.py +++ b/pymc3/tests/test_distributions_random.py @@ -277,12 +277,6 @@ class TestKumaraswamy(BaseTestCases.BaseTestCase): params = {"a": 1.0, "b": 1.0} -@pytest.mark.xfail(reason="This distribution has not been refactored for v4") -class TestLaplace(BaseTestCases.BaseTestCase): - distribution = pm.Laplace - params = {"mu": 1.0, "b": 1.0} - - @pytest.mark.xfail(reason="This distribution has not been refactored for v4") class TestAsymmetricLaplace(BaseTestCases.BaseTestCase): distribution = pm.AsymmetricLaplace @@ -463,6 +457,20 @@ class TestPareto(BaseTestDistribution): ] +class TestLaplace(BaseTestDistribution): + pymc_dist = pm.Laplace + pymc_dist_params = {"mu": 0.0, "b": 1.0} + expected_rv_op_params = {"mu": 0.0, "b": 1.0} + reference_dist_params = {"loc": 0.0, "scale": 1.0} + size = 15 + reference_dist = seeded_scipy_distribution_builder("laplace") + tests_to_run = [ + "check_pymc_params_match_rv_op", + "check_pymc_draws_match_reference", + "check_rv_size", + ] + + class TestGumbel(BaseTestDistribution): pymc_dist = pm.Gumbel pymc_dist_params = {"mu": 1.5, "beta": 3.0} @@ -1116,7 +1124,6 @@ def ref_rand(size, mu, lam, alpha): ref_rand=ref_rand, ) - @pytest.mark.xfail(reason="This distribution has not been refactored for v4") def test_laplace(self): def ref_rand(size, mu, b): return st.laplace.rvs(mu, b, size=size) From 2c54c44450cd859f6644b0a424aed2bdf43041e1 Mon Sep 17 00:00:00 2001 From: Farhan Reynaldo Date: Thu, 13 May 2021 16:13:01 +0700 Subject: [PATCH 4/9] create dist class method in laplace --- pymc3/distributions/continuous.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index debe6a7064..1ddb2d12b0 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -1522,14 +1522,13 @@ class Laplace(Continuous): """ rv_op = laplace - def __init__(self, mu, b, *args, **kwargs): - super().__init__(*args, **kwargs) - self.b = b = at.as_tensor_variable(floatX(b)) - self.mean = self.median = self.mode = self.mu = mu = at.as_tensor_variable(floatX(mu)) - - self.variance = 2 * self.b ** 2 + @classmethod + def dist(cls, mu, b, *args, **kwargs): + b = at.as_tensor_variable(floatX(b)) + mu = at.as_tensor_variable(floatX(mu)) assert_negative_support(b, "b", "Laplace") + return super().dist([mu, b], **kwargs) def logp(value, mu, b): """ From 3ba97680576330bee93fb27d800687f585b85dc9 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Thu, 13 May 2021 14:37:40 +0200 Subject: [PATCH 5/9] Reintroduce `Pareto` default transform --- pymc3/distributions/continuous.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index 1ddb2d12b0..03fb556922 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -152,10 +152,16 @@ def default_transform(cls): def transform_params(rv_var): _, _, _, *args = rv_var.owner.inputs - lower = args[cls.bound_args_indices[0]] - upper = args[cls.bound_args_indices[1]] + + lower, upper = None, None + if cls.bound_args_indices[0] is not None: + lower = args[cls.bound_args_indices[0]] + if cls.bound_args_indices[1] is not None: + upper = args[cls.bound_args_indices[1]] + lower = at.as_tensor_variable(lower) if lower is not None else None upper = at.as_tensor_variable(upper) if upper is not None else None + return lower, upper return transforms.interval(transform_params) @@ -1970,7 +1976,7 @@ def logcdf(self, value): ) -class Pareto(Continuous): +class Pareto(BoundedContinuous): r""" Pareto log-likelihood. @@ -2016,6 +2022,7 @@ class Pareto(Continuous): Scale parameter (m > 0). """ rv_op = pareto + bound_args_indices = (1, None) # lower-bounded by `m` @classmethod def dist( From 449570a3998d2e6714796327a46f37f3eca40549 Mon Sep 17 00:00:00 2001 From: Farhan Reynaldo Date: Thu, 13 May 2021 20:01:54 +0700 Subject: [PATCH 6/9] fix pareto reference dist params --- pymc3/tests/test_distributions_random.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymc3/tests/test_distributions_random.py b/pymc3/tests/test_distributions_random.py index 355b6fb4d5..72521b9c25 100644 --- a/pymc3/tests/test_distributions_random.py +++ b/pymc3/tests/test_distributions_random.py @@ -447,9 +447,9 @@ class TestPareto(BaseTestDistribution): pymc_dist = pm.Pareto pymc_dist_params = {"alpha": 3.0, "m": 2.0} expected_rv_op_params = {"alpha": 3.0, "m": 2.0} - reference_dist_params = {"alpha": 3.0, "scale": 2.0} + reference_dist_params = {"b": 3.0, "scale": 2.0} size = 15 - reference_dist = seeded_numpy_distribution_builder("pareto") + reference_dist = seeded_scipy_distribution_builder("pareto") tests_to_run = [ "check_pymc_params_match_rv_op", "check_pymc_draws_match_reference", From 37acea6365b891f6bad7a0363abb436d67dbffb3 Mon Sep 17 00:00:00 2001 From: Farhan Reynaldo Date: Thu, 13 May 2021 21:51:31 +0700 Subject: [PATCH 7/9] remove size param from laplace --- pymc3/tests/test_distributions_random.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pymc3/tests/test_distributions_random.py b/pymc3/tests/test_distributions_random.py index 72521b9c25..61108e05d1 100644 --- a/pymc3/tests/test_distributions_random.py +++ b/pymc3/tests/test_distributions_random.py @@ -462,7 +462,6 @@ class TestLaplace(BaseTestDistribution): pymc_dist_params = {"mu": 0.0, "b": 1.0} expected_rv_op_params = {"mu": 0.0, "b": 1.0} reference_dist_params = {"loc": 0.0, "scale": 1.0} - size = 15 reference_dist = seeded_scipy_distribution_builder("laplace") tests_to_run = [ "check_pymc_params_match_rv_op", From fbd21034514cc69dfe859a9fe2b32726ca8c5986 Mon Sep 17 00:00:00 2001 From: Farhan Reynaldo Date: Fri, 14 May 2021 09:07:43 +0700 Subject: [PATCH 8/9] use existing aesara laplace implementation --- pymc3/distributions/continuous.py | 18 ++---------------- pymc3/tests/test_distributions_random.py | 6 ------ 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index 03fb556922..638129244d 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -34,6 +34,7 @@ halfcauchy, halfnormal, invgamma, + laplace, logistic, lognormal, normal, @@ -1469,21 +1470,6 @@ def logcdf(value, mu): ) -class LaplaceRV(RandomVariable): - name = "laplace" - ndim_supp = 0 - ndims_params = [0, 0] - dtype = "floatX" - _print_name = ("Laplace", "\\operatorname{Laplace}") - - @classmethod - def rng_fn(cls, rng, mu, b, size=None): - return stats.laplace.rvs(mu, b, size=size, random_state=rng) - - -laplace = LaplaceRV() - - class Laplace(Continuous): r""" Laplace log-likelihood. @@ -1534,7 +1520,7 @@ def dist(cls, mu, b, *args, **kwargs): mu = at.as_tensor_variable(floatX(mu)) assert_negative_support(b, "b", "Laplace") - return super().dist([mu, b], **kwargs) + return super().dist([mu, b], *args, **kwargs) def logp(value, mu, b): """ diff --git a/pymc3/tests/test_distributions_random.py b/pymc3/tests/test_distributions_random.py index 61108e05d1..8cb3bcff2d 100644 --- a/pymc3/tests/test_distributions_random.py +++ b/pymc3/tests/test_distributions_random.py @@ -1123,12 +1123,6 @@ def ref_rand(size, mu, lam, alpha): ref_rand=ref_rand, ) - def test_laplace(self): - def ref_rand(size, mu, b): - return st.laplace.rvs(mu, b, size=size) - - pymc3_random(pm.Laplace, {"mu": R, "b": Rplus}, ref_rand=ref_rand) - @pytest.mark.xfail(reason="This distribution has not been refactored for v4") def test_laplace_asymmetric(self): def ref_rand(size, kappa, b, mu): From 9b443ebbacd2c6e399a769c79d4401137d18eeb6 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Fri, 14 May 2021 08:58:14 +0200 Subject: [PATCH 9/9] Remove size from Pareto random test --- pymc3/tests/test_distributions_random.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pymc3/tests/test_distributions_random.py b/pymc3/tests/test_distributions_random.py index 8cb3bcff2d..8ec1d71740 100644 --- a/pymc3/tests/test_distributions_random.py +++ b/pymc3/tests/test_distributions_random.py @@ -448,7 +448,6 @@ class TestPareto(BaseTestDistribution): pymc_dist_params = {"alpha": 3.0, "m": 2.0} expected_rv_op_params = {"alpha": 3.0, "m": 2.0} reference_dist_params = {"b": 3.0, "scale": 2.0} - size = 15 reference_dist = seeded_scipy_distribution_builder("pareto") tests_to_run = [ "check_pymc_params_match_rv_op",