Summary
When a Prior with non-empty dims has another Prior (also with non-empty dims) as a parameter, create_variable(..., xdist=True) builds a model where sample_prior_predictive succeeds but model.compile_logp() raises because inner RV nodes remain in the logp graph. The bug affects the centered parameterization only; centered=False and xdist=False both sidestep it.
Reproducer
import pymc as pm
from pymc_extras.prior import Prior
prior = Prior(
"Normal",
mu=Prior("Normal", mu=0.0, sigma=0.1, dims=("brand",)),
sigma=Prior("HalfNormal", sigma=0.1, dims=("brand",)),
dims=("brand", "customer"),
)
with pm.Model(coords={"brand": ["a", "b"], "customer": ["x", "y"]}) as model:
prior.create_variable("beta", xdist=True)
model.compile_logp()
ValueError: Random variables detected in the logp graph:
{halfnormal_rv{"(),()->()"}.out, normal_rv{"(),()->()"}.out}.
Necessary conditions (from a sweep of 10 nesting variants)
All four must hold to trigger the failure:
xdist=True
- Outer
Prior has non-empty dims
- At least one parameter is a nested
Prior whose dims is non-empty (dims=() or dims=None on the inner doesn't trigger it)
- Outer
Prior.centered is True (the default)
What I verified
model.free_RVs contains beta_mu, beta_sigma, beta — inner RVs are registered.
- Inner RVs are reachable from the outer RV's graph (
pytensor.graph.traversal.ancestors([beta]) contains the registered objects).
sample_prior_predictive succeeds.
prior.create_variable("beta", xdist=False) on the same Prior produces a compile_logp-able model.
- Flipping the outer
Prior.centered=False (still xdist=True) also produces a compile_logp-able model.
- The failing path is
_create_centered_variable with xdist=True. _create_non_centered_variable has the analogous xdist=True branch but does not fail on the sweep.
Where this surfaces
pymc_marketing.mmm.fourier.FourierBase.apply hard-codes xdist=True:
beta = self.prior.create_variable(self.variable_name, xdist=True)
so a fourier component whose outer Prior is centered and has nested Prior parameters with dims will hit this (e.g. hierarchical seasonality with brand-level deviations on mu/sigma). Flat priors, non-centered outer priors, and nested priors with scalar (dims=()) inner priors are unaffected.
Environment
pymc-extras 0.10.0, pymc 5.28.2, pytensor 2.38.2, Python 3.12.
Summary
When a
Priorwith non-emptydimshas anotherPrior(also with non-emptydims) as a parameter,create_variable(..., xdist=True)builds a model wheresample_prior_predictivesucceeds butmodel.compile_logp()raises because inner RV nodes remain in the logp graph. The bug affects the centered parameterization only;centered=Falseandxdist=Falseboth sidestep it.Reproducer
Necessary conditions (from a sweep of 10 nesting variants)
All four must hold to trigger the failure:
xdist=TruePriorhas non-emptydimsPriorwhosedimsis non-empty (dims=()ordims=Noneon the inner doesn't trigger it)Prior.centeredisTrue(the default)What I verified
model.free_RVscontainsbeta_mu,beta_sigma,beta— inner RVs are registered.pytensor.graph.traversal.ancestors([beta])contains the registered objects).sample_prior_predictivesucceeds.prior.create_variable("beta", xdist=False)on the samePriorproduces acompile_logp-able model.Prior.centered=False(stillxdist=True) also produces acompile_logp-able model._create_centered_variablewithxdist=True._create_non_centered_variablehas the analogous xdist=True branch but does not fail on the sweep.Where this surfaces
pymc_marketing.mmm.fourier.FourierBase.applyhard-codesxdist=True:so a fourier component whose outer
Prioris centered and has nestedPriorparameters with dims will hit this (e.g. hierarchical seasonality with brand-level deviations onmu/sigma). Flat priors, non-centered outer priors, and nested priors with scalar (dims=()) inner priors are unaffected.Environment
pymc-extras 0.10.0, pymc 5.28.2, pytensor 2.38.2, Python 3.12.