From ae6733245341cb060392aa020ffb4e455097b12c Mon Sep 17 00:00:00 2001 From: premshaw04 Date: Tue, 4 Nov 2025 00:28:14 +0530 Subject: [PATCH 1/2] Add rng_fn to CAR/ICAR #7713 --- pymc/distributions/multivariate.py | 10 ++++++++++ pymc/distributions/shape_utils.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/pymc/distributions/multivariate.py b/pymc/distributions/multivariate.py index f76a98546e..7a01f667ef 100644 --- a/pymc/distributions/multivariate.py +++ b/pymc/distributions/multivariate.py @@ -67,6 +67,7 @@ ) from pymc.distributions.shape_utils import ( _change_dist_size, + build_icar_covariance, change_dist_size, get_support_shape, implicit_size_from_params, @@ -2452,6 +2453,15 @@ class ICAR(Continuous): rv_op = icar @classmethod + def ICAR(name, W, tau=1.0, mu=None, **kwargs): + W = pt.as_tensor_variable(W) + if mu is None: + mu = pt.zeros(W.shape[0]) + + cov = build_icar_covariance(W, tau) # python helper that does eig pseudo inverse + + return pm.MvNormal(name, mu=mu, cov=cov, method="eig", **kwargs) + def dist(cls, W, sigma=1, zero_sum_stdev=0.001, **kwargs): # Note: These checks are forcing W to be non-symbolic if not W.ndim == 2: diff --git a/pymc/distributions/shape_utils.py b/pymc/distributions/shape_utils.py index cdab3046b1..12b8d2c9c4 100644 --- a/pymc/distributions/shape_utils.py +++ b/pymc/distributions/shape_utils.py @@ -46,6 +46,16 @@ from pymc.pytensorf import PotentialShapeType +def build_icar_covariance(W, tau): + D = np.diag(W.sum(axis=1)) + Q = tau*(D-W) + vals, vecs = np.linalg.eigh(Q) + tol = np.max(vals)*1e-12 + inv_vals = np.where(vals > tol, 1/vals, 0.0) + cov = (vecs*inv_vals) @ vecs.T + cov = 0.5*(cov+cov.T) + return cov + def to_tuple(shape): """Convert ints, arrays, and Nones to tuples. From 6e09c0dfd915323312cd13637a56ab05b53bc7fe Mon Sep 17 00:00:00 2001 From: premshaw04 Date: Thu, 6 Nov 2025 04:17:52 +0530 Subject: [PATCH 2/2] Use MvNormal.dist in ICAR wrapper instead of direct RV constructor --- pymc/distributions/multivariate.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pymc/distributions/multivariate.py b/pymc/distributions/multivariate.py index 7a01f667ef..eab0128f54 100644 --- a/pymc/distributions/multivariate.py +++ b/pymc/distributions/multivariate.py @@ -2452,15 +2452,16 @@ class ICAR(Continuous): rv_op = icar - @classmethod - def ICAR(name, W, tau=1.0, mu=None, **kwargs): + + def ICAR(name, W, tau=1.0, mu=None, method="eig", **kwargs): W = pt.as_tensor_variable(W) + if mu is None: mu = pt.zeros(W.shape[0]) - cov = build_icar_covariance(W, tau) # python helper that does eig pseudo inverse + cov = build_icar_covariance(W, tau) # private python helper that gets eig pseudo inverse - return pm.MvNormal(name, mu=mu, cov=cov, method="eig", **kwargs) + return pm.MvNormal.dist(mu=mu, cov=cov, method=method, name=name, **kwargs) def dist(cls, W, sigma=1, zero_sum_stdev=0.001, **kwargs): # Note: These checks are forcing W to be non-symbolic