Skip to content

Prior.create_variable(xdist=True) fails compile_logp for centered priors with nested Prior parameters that have dims #673

@ErikRingen

Description

@ErikRingen

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:

  1. xdist=True
  2. Outer Prior has non-empty dims
  3. At least one parameter is a nested Prior whose dims is non-empty (dims=() or dims=None on the inner doesn't trigger it)
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions