GMM sampler: dim-group keys were in the wrong frame, and a dimension could be in no group at all - #359
Draft
oshaughnessy-junior wants to merge 1 commit into
Draft
GMM sampler: dim-group keys were in the wrong frame, and a dimension could be in no group at all#359oshaughnessy-junior wants to merge 1 commit into
oshaughnessy-junior wants to merge 1 commit into
Conversation
oshaughnessy-junior
deployed
to
private-review-dispatch-rift
September 16, 2026 21:49 — with
GitHub Actions
Active
oshaughnessy-junior
force-pushed
the
claude/confident-montalcini-933738
branch
from
September 16, 2026 21:56
29f3c08 to
74edc1f
Compare
oshaughnessy-junior
deployed
to
private-review-dispatch-rift
September 16, 2026 21:56 — with
GitHub Actions
Active
…could be in no group at all
A gmm_dict key is a tuple of DIMENSION indices, and mcsamplerEnsemble numbers its dimensions
differently depending on how it is driven:
standalone integrate(func, *args) builds raw_bounds from `args` -> dim i is args[i]
portfolio member the portfolio never calls member.integrate(); it calls
setup() (dim = len(params_ordered)) and then
draw_simplified(n, *params_ordered) -> dim i is
params_ordered[i]
Both ILE drivers built ONE key set, from sampler.params_ordered, and handed it to whichever
consumer was configured. That is right for a portfolio member and wrong for a standalone GMM,
with two separate consequences.
UNCOVERED DIMENSION -- wrong evidence. The three named groups (ra,dec), (distance,inclination),
(psi,phi_orb) cover six dimensions. A run with a seventh parameter (t_ref, i.e. no
--time-marginalization) leaves one out. MonteCarloEnsemble._sample() allocates sample_array
with xpy.empty and writes only the columns a dim-group names, so the uncovered column carried
UNINITIALIZED MEMORY into the integrand and into calc_pdf, with no matching factor in
sampling_prior_array. An unnormalized estimator. On base the uncovered parameter is `distance`
-- dimension 6 is the one left out, and _sample always reads indices in the args frame, where
args[6] is distance; after the frame fix the leftover needing its own group is t_ref.
WRONG PARAMETERS ADAPTED -- lost efficiency. Where the cover happens to be complete the
estimator stays unbiased and every group simply adapts the wrong parameters.
Zero-likelihood fixture, exact ln Z = 0, base 94f352a:
standalone GMM, 5 seeds base -28.7, -30.0, -30.0, -30.1, -136.2 -> -0.011
standalone GMM --time-marg base -0.017 -> -0.011
GMM --internal-gmm-correlate-all base +0.011 -> +0.011
portfolio AV,GMM, 4 seeds base -0.088 .. +0.576 -> -0.031 .. +0.022
AV --time-marg base -0.002 -> -0.002
The base standalone value is a LOTTERY, not a constant -- it is whatever the allocator returned,
so quoting one number invites a "reproduction" that lands 100 nats away. correlate-all is a
single full-dimension group, so the frame cannot matter and nothing moves.
The efficiency half, measured where both frames cover every dimension (6-dim --time-marg case,
narrow tilted sky ridge, 5 seeds, same sample budget): n_eff median 133 -> 241, across-seed ln Z
spread 0.087 -> 0.015 nats. Both frames agree on ln Z, as they must.
- both drivers: pick the frame by consumer (mcsamplerEnsemble.dim_group_frame) and fill any
leftover dimension (complete_dim_group_cover). integrate_likelihood_extrinsic_batchmode_lisa
had the same defect plus a third failure mode: under --distance-marginalization its keys ran
off the end of a shorter parameter list.
- the standalone driver validates the mapping at startup. analyze_event is wrapped in a bare
`except Exception` that writes an empty .dat and exits 0 under
--internal-make-empty-file-on-error, so a refusal raised from sampler.integrate() would look
like a job that merely found nothing.
- MonteCarloEnsemble: refuse a gmm_dict that does not partition range(d). Runs AFTER
validate_gmm_dict, which reports the more specific complaint (a seed that does not match its
group) and which callers exercise with a deliberately partial gmm_dict.
- mcsamplerEnsemble.integrate() named dim-group parameters from params_ordered while its keys
index args, so validate_gmm_dict's messages named the wrong parameter. Use curr_args.
CIP is unaffected: it builds keys from low_level_coord_names and integrates with
*low_level_coord_names, the same frame, and its default grouping covers every dimension. Its
--internal-correlate-parameters does accept OVERLAPPING blocks, which the new guard now refuses;
that configuration was double-counting a dimension before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oshaughnessy-junior
force-pushed
the
claude/confident-montalcini-933738
branch
from
September 16, 2026 22:44
74edc1f to
7723815
Compare
oshaughnessy-junior
deployed
to
private-review-dispatch-rift
September 16, 2026 22:44 — with
GitHub Actions
Active
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
--sampler-method GMMreturns a catastrophically wrong evidence. On a zero-likelihood fixturewhose exact answer is 0,
rift_O4dat 94f352a returns ln Z = −28.7, −30.0, −30.0, −30.1,−136.2 across five seeds. It finds the peak correctly, and reports sigma = 0.178 against that.
It is not a p_s normalization error. A
gmm_dictkey is a tuple of dimension indices, andmcsamplerEnsemblenumbers its dimensions differently depending on how it is driven:iisintegrate(func, *args)— standaloneargs[i]setup()+draw_simplified(n, *params_ordered)— portfolio memberparams_ordered[i]The portfolio never calls
member.integrate(). Both ILE drivers built one key set, fromsampler.params_ordered, and handed it to whichever consumer was configured — right for aportfolio member, wrong for a standalone GMM. Two separate consequences follow.
Uncovered dimension — wrong evidence. The three named groups cover six dimensions. A run
with a seventh parameter (t_ref, i.e. no
--time-marginalization) leaves one out.MonteCarloEnsemble._sample()allocatessample_arraywithxpy.emptyand writes only thecolumns a dim-group names, so the uncovered column carried uninitialized memory into the
integrand and into
calc_pdf, with no matching factor insampling_prior_array. The value iswhatever the allocator returned, which is why it is a lottery rather than a constant. On base the
uncovered parameter is
distance: dimension 6 is the one left out, and_samplealways readsindices in the args frame, where
args[6]is distance.Wrong parameters adapted — lost efficiency. Where the cover happens to be complete the
estimator stays unbiased and each group simply adapts the wrong parameters.
Measured
Zero-likelihood H1 fixture, exact ln Z = 0, base 94f352a.
--time-marginalization--internal-gmm-correlate-all--time-marginalization--internal-gmm-correlate-allis a single full-dimension group, so the frame cannot matter andnothing moves. The efficiency half, measured where both frames cover every dimension (6-dim
--time-marginalizationcase, narrow tilted sky ridge, 5 seeds, same sample budget):params_ordered(base)Both agree on ln Z, as they must.
Changes
mcsamplerEnsemble.dim_group_frame) and fill anyleftover dimension (
complete_dim_group_cover).integrate_likelihood_extrinsic_batchmode_lisahad the same defect plus a third failuremode: under
--distance-marginalizationits keys ran off the end of a shorter parameterlist. Fixed the same way.
analyze_eventis wrapped in a bareexcept Exceptionthat writes an empty.datand exits 0 under--internal-make-empty-file-on-error, so a refusal raised fromsampler.integrate()wouldhave looked like a job that merely found nothing.
MonteCarloEnsemblerefuses agmm_dictthat does not partitionrange(d). It runs aftervalidate_gmm_dict, which reports the more specific complaint and which callers exercise witha deliberately partial
gmm_dict.mcsamplerEnsemble.integrate()named dim-group parameters fromparams_orderedwhile its keysindex
args, sovalidate_gmm_dict's messages named the wrong parameter.Test
test/integrators/test_ensemble_dim_group_cover.py(14 tests), registered in.travis/test-integrate.sh. Analytic targets (E_prior[exp(A cos phi_orb)] = I0(A)) for thecover defect; the frame defect is pinned separately, because at 0.006–0.02 nats it sits an order
of magnitude below the evidence tolerance and no analytic assertion can see it.
Seven mutations in an isolated worktree, control clean each time: remove the guard; make
dim_group_frameignore its driver; drop each of the three guard call sites individually; makecomplete_dim_group_coverforgetn_comp/gmm_adapt; remove the empty-key check; makeintegrate()number dimensions byparams_ordered.Gates (ldas-grid, numpy backend, cupy does not import)
test-core-units.sh580 passed / 13 skipped / 0 failed ·test-lisa.sh275 passed ·test-posterior.shrc=0 ·test-integrate.sh1 failed —test_peak_local_runs_on_the_gpu_backend_and_matches_numpy, which fails identically on base94f352a on this host:
pytest.importorskip('cupy')does not skip under pytest 9.1.1 when cupyraises a chained
ImportErrorfor a missinglibcuda.so.1. Not reachable from this diff.CIP is unaffected
It builds keys from
low_level_coord_namesand integrates with*low_level_coord_names— thesame frame — and its default grouping covers every dimension. On identical ILE input, CIP GMM
2.06572 vs CIP AV 2.06558.
Behaviour change to note:
--internal-correlate-parametersaccepts overlapping blocks(e.g.
'mc,delta_mc mc,xi'), which the new guard now refuses. That configuration wasdouble-counting a dimension before, so the refusal is correct, but it turns a previously-running
job into a hard failure.
Not fixed, found in passing
--sampler-method AVwithout--time-marginalizationdies on a missingmcsamplerAdaptiveVolume.uniform_samp_vector..travis/test-integrate.shandtest-posterior.shwritecdf*.png,integral_result*.dat,output-ILE-samples*and friends into the repo root.🤖 Generated with Claude Code