Skip to content

Add icdf functions for Lognormal, Half Cauchy and Half Normal distributions #6766

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 26, 2023
20 changes: 20 additions & 0 deletions pymc/distributions/continuous.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
from pytensor.tensor.var import TensorConstant

from pymc.logprob.abstract import _logcdf_helper, _logprob_helper
from pymc.logprob.basic import icdf

try:
from polyagamma import polyagamma_cdf, polyagamma_pdf, random_polyagamma
Expand Down Expand Up @@ -856,6 +857,11 @@ def logcdf(value, loc, sigma):
msg="sigma > 0",
)

def icdf(value, loc, sigma):
res = icdf(Normal.dist(loc, sigma), (value + 1.0) / 2.0)
res = check_icdf_value(res, value)
return res


class WaldRV(RandomVariable):
name = "wald"
Expand Down Expand Up @@ -1714,12 +1720,17 @@ def logcdf(value, mu, sigma):
-np.inf,
normal_lcdf(mu, sigma, pt.log(value)),
)

return check_parameters(
res,
sigma > 0,
msg="sigma > 0",
)

def icdf(value, mu, sigma):
res = pt.exp(icdf(Normal.dist(mu, sigma), value))
return res


Lognormal = LogNormal

Expand Down Expand Up @@ -2121,6 +2132,15 @@ def logcdf(value, loc, beta):
msg="beta > 0",
)

def icdf(value, loc, beta):
res = loc + beta * pt.tan(np.pi * (value) / 2.0)
res = check_icdf_value(res, value)
return check_parameters(
res,
beta > 0,
msg="beta > 0",
)


class Gamma(PositiveContinuous):
r"""
Expand Down
23 changes: 23 additions & 0 deletions tests/distributions/test_continuous.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ def test_half_normal(self):
{"sigma": Rplus},
lambda value, sigma: st.halfnorm.logcdf(value, scale=sigma),
)
check_icdf(
pm.HalfNormal,
{"sigma": Rplus},
lambda q, sigma: st.halfnorm.ppf(q, scale=sigma),
)

def test_chisquared_logp(self):
check_logp(
Expand Down Expand Up @@ -502,6 +507,21 @@ def test_lognormal(self):
{"mu": R, "sigma": Rplusbig},
lambda value, mu, sigma: st.lognorm.logcdf(value, sigma, 0, np.exp(mu)),
)
check_icdf(
pm.LogNormal,
{"mu": R, "tau": Rplusbig},
lambda q, mu, tau: floatX(st.lognorm.ppf(q, tau**-0.5, 0, np.exp(mu))),
)
# Because we exponentiate the normal quantile function, setting sigma >= 9.5
# return extreme values that results in relative errors above 4 digits
# we circumvent it by keeping it below or equal to 9.
custom_rplusbig = Domain([0, 0.5, 0.9, 0.99, 1, 1.5, 2, 9, np.inf])
check_icdf(
pm.LogNormal,
{"mu": R, "sigma": custom_rplusbig},
lambda q, mu, sigma: floatX(st.lognorm.ppf(q, sigma, 0, np.exp(mu))),
decimal=select_by_precision(float64=4, float32=3),
)

def test_studentt_logp(self):
check_logp(
Expand Down Expand Up @@ -567,6 +587,9 @@ def test_half_cauchy(self):
{"beta": Rplusbig},
lambda value, beta: st.halfcauchy.logcdf(value, scale=beta),
)
check_icdf(
pm.HalfCauchy, {"beta": Rplusbig}, lambda q, beta: st.halfcauchy.ppf(q, scale=beta)
)

def test_gamma_logp(self):
check_logp(
Expand Down