Skip to content

Support marginalize of broadcasted normal-normal (one to many) - #715

Draft
ricardoV94 wants to merge 4 commits into
pymc-devs:mainfrom
ricardoV94:broadcast_normal_normal
Draft

Support marginalize of broadcasted normal-normal (one to many)#715
ricardoV94 wants to merge 4 commits into
pymc-devs:mainfrom
ricardoV94:broadcast_normal_normal

Conversation

@ricardoV94

Copy link
Copy Markdown
Member
  • Avoid duplicating shared RVs when unmarginalizing
  • Control for broadcasting in normal-normal marginalization
  • Support affine dependent means in normal-normal marginalization
  • Support broadcast normal-normal marginalization as MvNormal

local_unmarginalize wraps the unmarginalized RV in a ModelFreeRV and
graph_replaces it into the dependents. Because the wrapper re-references the
variable being replaced, graph_replace cloned the surrounding body, duplicating
any shared upstream RV wrapped in an already-built model variable (e.g. a
previously unmarginalized parent). Pin those opaque ModelValuedVar wrappers as
boundaries so their subgraphs are not cloned.
Register a pre-canonicalize pass ahead of the marginal rewrites (mirroring
pymc.logprob) so the structure detectors see flattened Add/Mul. normal-normal
now handles any affine dependent mean a + b*x (extracted by affine_coefficients),
not just x or x + c. Because marginalize now returns a canonical model,
equivalent_models gains an opt-in canonicalize= flag used by the round-trip test.
A latent broadcast into a wider dependent is shared across those
dependents, so the marginal is a correlated MvNormal rather than the
elementwise Normal. Classify each dependent dim with the dim-connection
analysis (stored as dims_connections on the op): dims where the latent
is broadcast become the MvNormal event (raveled and moved right), while
dims matched one-to-one stay independent batch dims. The conditional
mirrors this, summing each shared observation's evidence back onto the
latent.

The dim analysis is now the structural guard (it rejects couplings the
closed form can't express, e.g. x[None,:] + x[:,None]), so the affine
check accepts any DimShuffle of the latent.
@codecov-commenter

codecov-commenter commented Jul 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.64865% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 64.66%. Comparing base (86fac3c) to head (64fff18).
⚠️ Report is 23 commits behind head on main.

Files with missing lines Patch % Lines
pymc_extras/model/marginal/distributions/normal.py 98.24% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             main     #715       +/-   ##
===========================================
+ Coverage   51.60%   64.66%   +13.05%     
===========================================
  Files          73       92       +19     
  Lines        8003     8595      +592     
===========================================
+ Hits         4130     5558     +1428     
+ Misses       3873     3037      -836     
Files with missing lines Coverage Δ
pymc_extras/model/marginal/marginalize.py 96.62% <100.00%> (ø)
pymc_extras/model/marginal/rewrites.py 95.58% <100.00%> (ø)
pymc_extras/utils/model_equivalence.py 86.11% <100.00%> (+86.11%) ⬆️
pymc_extras/model/marginal/distributions/normal.py 97.22% <98.24%> (ø)

... and 9 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

zaxtax
zaxtax previously approved these changes Jul 20, 2026
@zaxtax
zaxtax self-requested a review July 20, 2026 09:24
@zaxtax
zaxtax dismissed their stale review July 20, 2026 09:25

Clicked on wrong PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants