From cf83a362f5d85f29eed0454516eb55bc9499ac99 Mon Sep 17 00:00:00 2001 From: notluquis Date: Mon, 27 Jul 2026 13:53:07 -0400 Subject: [PATCH 1/2] Fix numba CauchyRV loc/scale, and test it at scale != 1 The numba dispatch computed `(loc + z) / scale` instead of `loc + scale * z`, giving the sampled variable location `loc/scale` and scale `1/scale`. `rng_fn_scipy` and the JAX dispatch are both correct, so only the numba backend was affected -- but it is the default linker. `logp` is untouched, so NUTS posteriors were fine; everything that draws was not, including `sample_prior_predictive` on a `HalfCauchy` prior. The existing `cauchy` case in `test_unaligned_RandomVariable` passed `scale=1.0`, where `(loc + z) / 1` is algebraically identical to `loc + 1 * z`, so both the location and scale errors vanished together. Bumping that to `scale=3.0` makes the Cramer-von Mises check fail on the old code (p = 1.7e-08) and pass on the new. Closes #2308 --- pytensor/link/numba/dispatch/random.py | 2 +- tests/link/numba/test_random.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pytensor/link/numba/dispatch/random.py b/pytensor/link/numba/dispatch/random.py index 2f7f8b3700..cf80869870 100644 --- a/pytensor/link/numba/dispatch/random.py +++ b/pytensor/link/numba/dispatch/random.py @@ -137,7 +137,7 @@ def random_fn(rng, loc, scale): def numba_core_CauchyRV(op, node): @numba_basic.numba_njit def random(rng, loc, scale): - return (loc + rng.standard_cauchy()) / scale + return loc + scale * rng.standard_cauchy() return random diff --git a/tests/link/numba/test_random.py b/tests/link/numba/test_random.py index 252967a0e8..523f39e2e5 100644 --- a/tests/link/numba/test_random.py +++ b/tests/link/numba/test_random.py @@ -569,7 +569,7 @@ def test_aligned_RandomVariable(rv_op, dist_args, size): ), ( pt.dscalar(), - np.array(1.0, dtype=np.float64), + np.array(3.0, dtype=np.float64), ), ], (2,), From 7d62e6983dd9842ed74480acd0cb31d7347f25e9 Mon Sep 17 00:00:00 2001 From: notluquis Date: Mon, 27 Jul 2026 14:02:50 -0400 Subject: [PATCH 2/2] Also un-degenerate the gumbel test case `test_unaligned_RandomVariable` is the only test that checks these samplers against their distribution, and it has three cases. `cauchy` and `gumbel` both used `scale=1.0`; `t` already passes `scale=np.pi`. `GumbelRV` has a hand-written core too (`loc - scale * np.log(-np.log(U))`) and is correct, but at `scale=1` a `(loc - z) / scale`-style slip would be invisible there for the same algebraic reason. Bumped to `scale=4.0`. Verified by injecting exactly that slip: the test fails at p=7.2e-08 with it and passes without. Audited the other hand-written numba cores (`t`, `halfnormal`, `pareto`, `invgamma`, `gumbel`, `wald`, plus the default-path RVs) against scipy with non-degenerate parameters -- all match, so `cauchy` was the only actual bug. --- tests/link/numba/test_random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/link/numba/test_random.py b/tests/link/numba/test_random.py index 523f39e2e5..0b2d3a962c 100644 --- a/tests/link/numba/test_random.py +++ b/tests/link/numba/test_random.py @@ -585,7 +585,7 @@ def test_aligned_RandomVariable(rv_op, dist_args, size): ), ( pt.dscalar(), - np.array(1.0, dtype=np.float64), + np.array(4.0, dtype=np.float64), ), ], (2,),