BUG: Cauchy and StudentT logcdf return -inf in the lower tail where the true value is finite
Describe the issue:
logcdf for Cauchy and StudentT drifts in the lower tail and eventually returns -inf,
where the true value is finite and nowhere near the limits of a float. In both cases it comes
from cancellation in the argument handed to log.
Cauchy(0, 1):
| value | pm.logcdf |
scipy | abs err |
|---|---|---|---|
| -1e12 | -28.7759535 | -28.7757510 | 2.0e-04 |
| -1e14 | -33.3868965 | -33.3809212 | 6.0e-03 |
| -1e15 | -35.8205098 | -35.6835063 | 1.4e-01 |
| -1e16 | -inf | -37.9860914 | |
| -1e20 | -inf | -47.1964317 |
StudentT(nu, 0, 1) — the -inf kicks in somewhere between -7e7 (nu=1) and -4e8 (nu=30),
and the last finite values before that are already off by up to ~2 nats:
| nu | value | pm.logcdf |
scipy |
|---|---|---|---|
| 1 | -1e8 | -inf | -19.5654106 |
| 2 | -1e8 | -37.4299478 | -37.5345087 |
| 2 | -1e9 | -inf | -42.1396789 |
| 5 | -1e8 | -91.8824720 | -89.8531475 |
| 5 | -1e9 | -inf | -101.366073 |
PyMC's own logp is still finite at those same points. For Cauchy(0, 1) at value=-1e16,
logp gives -74.83 while logcdf gives -inf: the density is finite but the cumulative
probability is reported as exactly zero, and those can't both be right.
This isn't just a precision complaint. logcdf is what pm.Censored and the truncated
distributions call, so a censored or truncated Cauchy/StudentT with a low bound ends up with
an -inf logp and the sampler is stuck.
Reproduceable code example:
import numpy as np
import pymc as pm
from scipy import stats
rv = pm.Cauchy.dist(alpha=0.0, beta=1.0)
xs = np.array([-1e12, -1e14, -1e15, -1e16, -1e20])
print("pymc :", pm.logcdf(rv, xs).eval())
print("scipy:", stats.cauchy(0, 1).logcdf(xs))
print("logp :", pm.logp(rv, xs).eval())
t = pm.StudentT.dist(nu=5.0, mu=0.0, sigma=1.0)
xs = np.array([-1e7, -1e8, -1e9])
print("pymc :", pm.logcdf(t, xs).eval())
print("scipy:", stats.t(5.0).logcdf(xs))Error message:
No exception is raised. logcdf simply returns -inf.PyMC version information:
Environment- PyMC version: 6.3.2+3.g3f3b1082e
- PyTensor version: 3.3.1
- Python version: 3.12.13
- OS: macOS 26.6.2 (arm64)
- Install method: pip, editable install from main
Context for the issue:
Cauchy.logcdf computes log(0.5 + arctan(z) / pi). As z goes to -inf, arctan(z) approaches -pi/2, so the argument is 0.5 - 0.5.
StudentT.logcdf computes x = (t + sqrt(t**2 + nu)) / (2 * sqrt(t**2 + nu)). For t << 0, t + sqrt(t**2 + nu) subtracts two nearly equal numbers, x underflows to zero, and betainc(a, b, 0) == 0.
Both have exact equivalents that avoid the cancellation. I have a patch with regression tests and will open a PR.
One scope note on StudentT: the rewrite removes the cancellation in x, which fixes the tail wherever betainc can still represent the result. For large nu far out in the tail, betainc(nu/2, nu/2, x) underflows on its own scipy.stats.t.logcdf returns -inf at the same points so that would need a log-space incomplete beta, which I'm leaving out of this.
Source: pymc-devs/pymc