Skip to content

pm.LKJCorr breaks compute_log_prior/compute_log_likelihood on a value-variable naming mismatch, blocking psense for rlm-jc-001 #453

Description

@frankbuckley

Note

Drafted by an LLM-based AI tool (Claude Code/Opus 5).

Summary

pm.compute_log_prior and pm.compute_log_likelihood both refuse to run on any model containing a pm.LKJCorr random variable, because they match the trace's posterior variables against the model's transformed value variable names while the posterior (correctly) stores the constrained name. The mismatch is a naming seam, not an intractable density.

Today this costs exactly one fit — lrp-rlm-jc-001 is the only model that both uses LKJCorr and routes through the standard compute_log_* path — so it is the last outstanding item on #381's psense coverage after #452 (192 of 194 fits covered; lrp-rlm-mm-001 is the other, and is a genuine non-converged exemption). But it will bite any future LKJCorr model on the standard path, and #383 recommends moving more models to LKJCorr, so the seam is worth closing before it spreads.

Reproduction

import pymc as pm

with pm.Model() as m:
    L = pm.LKJCorr("measure_corr_chol", n=3, eta=2.0)

m.free_RVs    # [measure_corr_chol]
m.value_vars  # [measure_corr_chol_cholesky_corr__]

LKJCorr's default transform is a Cholesky-corr transform, so the value variable is <name>_cholesky_corr__. The posterior group stores the constrained draw under <name>. compute_log_prior derives the name it expects from the transform and finds no match:

exact match required for all data variable names, but
['eta_cell', 'sigma_subject', 'measure_corr_chol_cholesky', 'z_subject', 'kappa'] !=
['eta_cell', 'sigma_subject', 'kappa', 'measure_corr_chol', 'z_subject']
{'measure_corr_chol_cholesky', 'measure_corr_chol'} are not in both.

Observed on a real --config reporting fit of lrp-rlm-jc-001 on 2026-07-26. Both groups fail — the prior group as well as the likelihood group — so this is not the "likelihood is not evaluable" case that compute_log_likelihood_and_prior(strict=False) was written for. That argument lets the fit survive the failure; it does not repair it.

Why only one model is affected today

pm.LKJCorr appears three times in factories.py:

Line RV Model(s) Affected?
5641 trait_corr_chol lcf (longitudinal correlated-factor) No
5646 state_corr_chol_w{n} lcf No
6319 measure_corr_chol rlm-jc-001 (historical joint) Yes

lcf escapes because it builds its log_likelihood / log_prior groups by a bespoke path rather than calling compute_log_likelihood_and_prior — a design already documented in that function's docstring ("Families with a bespoke log-likelihood … build their groups their own way and do not call this"), and confirmed by lrp-rli-lcf-001's stored trace carrying both groups and a valid psense_summary.csv. fit_rlm_joint_growth is compute_loo=False, so the sampling stage attaches nothing and it must request the groups explicitly — which is where it meets the seam.

Note the choice of LKJCorr here is deliberate and should not be reverted: factories.py:6315-6318 records that LKJCholeskyCov was rejected in this correlation-only role because its unused per-outcome sd scales are unidentified nuisances that pollute the all-free-RV convergence gate. #383 makes the same argument for switching mm / rlm-mm to LKJCorr, which is precisely why this seam should be fixed rather than worked around by going back.

Options

  1. Give historical_joint a bespoke log-prior path, as lcf already has. Most tractable, has in-repo precedent, and touches one family. Downside: a second bespoke path to maintain, and it does not help the next LKJCorr model.
  2. Reconcile the name at the compute_log_* seam — pass the constrained-to-value mapping (model.rvs_to_values) or rename the trace variables into a throwaway copy before the call. Fixes every present and future LKJCorr model in one place. Needs care that the values passed are on the scale the log-density expects, which is the substantive risk: a silently wrong scale would produce plausible numbers rather than an error.
  3. Check whether a newer PyMC resolves it upstream before building anything. Worth ten minutes first — if it is fixed above 6.1, this becomes a version bump plus a regression test.

Option 3 first, then 2 if the seam is ours to close, with 1 as the fallback.

Acceptance criteria

  • Root cause confirmed as ours or upstream, with the PyMC version behaviour recorded.
  • lrp-rlm-jc-001 emits psense_summary.csv and psense.png at --config reporting, or the model is recorded as a documented exemption with the specific reason (matching the treatment lrp-rlm-mm-001 already gets under Emit power-scaling (psense) and prior-pushforward artifacts for all estimand-reporting families #381), not left silently absent.
  • If the fix is at the seam (option 2), a test asserts that a model containing an LKJCorr RV can have compute_log_prior run against its own posterior — the naming contract, pinned so a PyMC upgrade cannot silently reintroduce it.
  • If the fix changes which values reach the log-density, the resulting psense numbers are sanity-checked against a model whose psense already works (lrp-rli-lcf-001 is the natural comparator — same LKJCorr construction, working groups).
  • notes/202607261700-psense-coverage-backfill.md updated with the outcome; Emit power-scaling (psense) and prior-pushforward artifacts for all estimand-reporting families #381's coverage line moves from 192/194.
  • ruff check src/, npm run format:check, npm run spellcheck pass.

Related

Found while closing #381's refit gap in #452, which added the psense wiring to fit_rlm_joint_growth that surfaced this — before that the artefacts were simply absent with no error at all. Connected to #383 (which would move more models onto LKJCorr) and to the general LKJCholeskyCov-vs-LKJCorr reasoning recorded at factories.py:6315-6318 and in notes/202607101638-mm-001-convergence-reparameterisation.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions