diff --git a/pymc/distributions/continuous.py b/pymc/distributions/continuous.py index aa722787da..b83260f671 100644 --- a/pymc/distributions/continuous.py +++ b/pymc/distributions/continuous.py @@ -33,7 +33,7 @@ from pytensor.raise_op import Assert from pytensor.tensor import gammaln from pytensor.tensor.extra_ops import broadcast_shape -from pytensor.tensor.math import tanh +from pytensor.tensor.math import betaincinv, gammaincinv, tanh from pytensor.tensor.random.basic import ( BetaRV, _gamma, @@ -1227,6 +1227,16 @@ def logcdf(value, alpha, beta): msg="alpha > 0, beta > 0", ) + def icdf(value, alpha, beta): + res = betaincinv(alpha, beta, value) + res = check_icdf_value(res, value) + return check_icdf_parameters( + res, + alpha > 0, + beta > 0, + msg="alpha > 0, beta > 0", + ) + class KumaraswamyRV(RandomVariable): name = "kumaraswamy" @@ -1872,6 +1882,21 @@ def logcdf(value, nu, mu, sigma): msg="nu > 0, sigma > 0", ) + def icdf(value, nu, mu, sigma): + res = pt.switch( + pt.lt(value, 0.5), + -pt.sqrt(nu) * pt.sqrt((1.0 / betaincinv(nu * 0.5, 0.5, 2.0 * value)) - 1.0), + pt.sqrt(nu) * pt.sqrt((1.0 / betaincinv(nu * 0.5, 0.5, 2.0 * (1 - value))) - 1.0), + ) + res = mu + res * sigma + res = check_icdf_value(res, value) + return check_icdf_parameters( + res, + nu > 0, + sigma > 0, + msg="nu > 0, sigma > 0", + ) + class Pareto(BoundedContinuous): r""" @@ -2063,7 +2088,7 @@ def logcdf(value, alpha, beta): def icdf(value, alpha, beta): res = alpha + beta * pt.tan(np.pi * (value - 0.5)) res = check_icdf_value(res, value) - return check_parameters( + return check_icdf_parameters( res, beta > 0, msg="beta > 0", @@ -2147,7 +2172,7 @@ def logcdf(value, loc, beta): def icdf(value, loc, beta): res = loc + beta * pt.tan(np.pi * (value) / 2.0) res = check_icdf_value(res, value) - return check_parameters( + return check_icdf_parameters( res, beta > 0, msg="beta > 0", @@ -2272,6 +2297,16 @@ def logcdf(value, alpha, scale): ) return check_parameters(res, 0 < alpha, 0 < beta, msg="alpha > 0, beta > 0") + def icdf(value, alpha, scale): + res = scale * gammaincinv(alpha, value) + res = check_icdf_value(res, value) + return check_icdf_parameters( + res, + alpha > 0, + scale > 0, + msg="alpha > 0, beta > 0", + ) + class InverseGamma(PositiveContinuous): r""" @@ -2556,7 +2591,7 @@ def logp(value, alpha, beta): def icdf(value, alpha, beta): res = beta * (-pt.log(1 - value)) ** (1 / alpha) res = check_icdf_value(res, value) - return check_parameters( + return check_icdf_parameters( res, alpha > 0, beta > 0, @@ -3116,7 +3151,7 @@ def icdf(value, lower, c, upper): upper - np.sqrt((upper - lower) * (upper - c) * (1 - value)), ) res = check_icdf_value(res, value) - return check_parameters( + return check_icdf_parameters( res, lower <= c, c <= upper, @@ -3219,7 +3254,7 @@ def logcdf(value, mu, beta): def icdf(value, mu, beta): res = mu - beta * pt.log(-pt.log(value)) res = check_icdf_value(res, value) - return check_parameters( + return check_icdf_parameters( res, beta > 0, msg="beta > 0", @@ -3437,7 +3472,7 @@ def logcdf(value, mu, s): def icdf(value, mu, s): res = mu + s * pt.log(value / (1 - value)) res = check_icdf_value(res, value) - return check_parameters( + return check_icdf_parameters( res, s > 0, msg="s > 0", @@ -3787,7 +3822,7 @@ def logcdf(value, mu, sigma): def icdf(value, mu, sigma): res = sigma * -pt.log(2.0 * pt.erfcinv(value) ** 2) + mu res = check_icdf_value(res, value) - return check_parameters( + return check_icdf_parameters( res, sigma > 0, msg="sigma > 0", diff --git a/tests/distributions/test_continuous.py b/tests/distributions/test_continuous.py index c8119d1482..31809720a5 100644 --- a/tests/distributions/test_continuous.py +++ b/tests/distributions/test_continuous.py @@ -411,6 +411,13 @@ def test_beta_logcdf(self): lambda value, alpha, beta: st.beta.logcdf(value, alpha, beta), ) + def test_beta_icdf(self): + check_icdf( + pm.Beta, + {"alpha": Rplus, "beta": Rplus}, + lambda q, alpha, beta: st.beta.ppf(q, alpha, beta), + ) + def test_kumaraswamy(self): # Scipy does not have a built-in Kumaraswamy def scipy_log_pdf(value, a, b): @@ -557,6 +564,13 @@ def test_studentt_logcdf(self): lambda value, nu, mu, sigma: st.t.logcdf(value, nu, mu, sigma), ) + def test_studentt_icdf(self): + check_icdf( + pm.StudentT, + {"nu": Rplusbig, "mu": R, "sigma": Rplusbig}, + lambda q, nu, mu, sigma: st.t.ppf(q, nu, mu, sigma), + ) + def test_cauchy(self): check_logp( pm.Cauchy, @@ -623,6 +637,13 @@ def test_gamma_logcdf(self): lambda value, alpha, beta: st.gamma.logcdf(value, alpha, scale=1.0 / beta), ) + def test_gamma_icdf(self): + check_icdf( + pm.Gamma, + {"alpha": Rplusbig, "beta": Rplusbig}, + lambda q, alpha, beta: st.gamma.ppf(q, alpha, scale=1.0 / beta), + ) + def test_inverse_gamma_logp(self): check_logp( pm.InverseGamma,