Skip to content

integrators: portfolio GMM dead-member fix (n_comp=None) + AV draw-density repair (bin-ordered head slice) - #33

Merged
oshaughnessy-junior merged 4 commits into
rift_O4dfrom
rift_O4d_portfolio_gmm_member_fix
Jul 23, 2026
Merged

integrators: portfolio GMM dead-member fix (n_comp=None) + AV draw-density repair (bin-ordered head slice)#33
oshaughnessy-junior merged 4 commits into
rift_O4dfrom
rift_O4d_portfolio_gmm_member_fix

Conversation

@oshaughnessy-junior

Copy link
Copy Markdown
Owner

Portfolio GMM dead-member fix + AV draw-density repair

Two coupled silent-degradation bugs found by the shape-recovery merge gate (test/expensive_before_merging/integrators/, merged in #31), fixed together because the first was masking the second.

Bug 1: portfolio GMM members never trained (dead member)

mcsamplerPortfolio.setup() forwards kwargs that lack n_comp; mcsamplerEnsemble.setup() defaulted n_comp=None; update_sampling_prior silently no-ops for n_comp=None. Net effect in default production wiring (--sampler-method portfolio without explicit GMM args): the GMM member never trained and every portfolio ran as AV-only — no error, no warning. Discovered by the PR #28 freeze-territory probe (the freeze machinery was governing a corpse).

Fix: setup() defaults n_comp=None → 1 with a loud notice (n_comp=0 remains the documented off-switch); update_sampling_prior warns once instead of silently skipping; plus an up-shape guard for bare (2,) bounds rows in uncorrelated groups (same guard _sample()/q-scoring already had) — without it, fixing Bug 1 alone turns the silent no-op into a crash in default (per-dimension) configuration.

Bug 2: AV draw_simplified head-sliced a bin-ordered cloud (density lie)

sample_from_bins emits points grouped in lexicographic bin order; draw_simplified returned rv[:n_to_get], i.e. only the first ~50–60% of the live-volume bins, while sampling_density (hence the portfolio balance-heuristic q_mix) claimed uniform coverage of all occupied bins. In any multi-member portfolio this systematically biased the recovered shape (pulls up to 0.64σ at d=2, dose-response with take-fraction; it also explains the +0.1-nat residual portfolio evidence bias seen in the #26 validation and the universal negative-pull signature on base). The dead GMM member of Bug 1 partially masked it — a live member re-weights exactly the region AV never populates.

Fix: random subsample (without replacement) instead of the head slice. Validation probe: pull 0.644 → 0.0013, lnZ bias −0.299 → −0.006, JS 0.081 → 1e-5, n_eff unchanged. AV standalone paths are untouched (draw_simplified serves portfolio members).

Validation

  • Regression test test/integrators/test_portfolio_gmm_member_trains.py: default-wired AV+GMM portfolio must train its GMM member; n_comp=0 must still disable. Both pass.
  • Differential shape gate (standard preset, base rift_O4d @ 841bcfe vs this branch, portfolio held strict): portfolio d2 5/6 IMPROVED(fail→pass) — evidence bias +0.02..+0.12 → |bias| ≤ 0.007 nats, pulls 0.03–0.16σ → ≤ 0.007σ; three more targets starved→pass at d4/d6; AV and GMM standalone rows unchanged. One flagged row (mix_d2_n1_s303) is a threshold-straddler: n_eff 3022→1502 trips the n_eff<0.5×base heuristic at 0.497 while every shape metric improves (bias +0.085→−0.002, pull 0.045→0.001) — waived.
  • Full artifacts: RIFT_roboto_paper analyses/integrator_shape_gate/gmm_member_fix_gate/ (gate JSONs + comparisons + the complete diagnosis/ probe ladder isolating Bug 2: jitter check, single-member portfolio controls, coverage instrumentation, monkey-patch validation).

Note for reviewers: the gate's two GMM d6_n3 verdict flips between rounds were shown to be OpenBLAS-pthreads jitter on n_eff≈100-marginal starved rows (bitwise-reproducible on an idle node), not code effects.

🤖 Generated with Claude Code

oshaughnessy-junior and others added 3 commits July 22, 2026 17:10
… invalid n_comp, fix uncorrelated-group bounds shape

Three related repairs to a silent-degradation chain found by the shape-gate
probes (2026-07-22):
1. mcsamplerEnsemble.setup(): n_comp=None (the default!) flowed into the
   integrator and disabled ALL GMM training downstream with no warning; in
   default portfolio wiring the GMM member never trained and 'portfolio'
   ran as AV-only. Now defaults to n_comp=1 with a loud notice; n_comp=0
   remains the explicit off-switch.
2. update_sampling_prior(): warn (once) instead of silently no-opping when
   n_comp is invalid for a group.
3. update_sampling_prior(): up-shape bare (2,) bounds rows for per-dimension
   (uncorrelated) groups before GMM.fit — same guard _sample() and the
   q-scoring path already had; latent until (1) because this line was never
   reached in default portfolio config, and it would have turned the silent
   no-op into a crash.

Regression test: test/integrators/test_portfolio_gmm_member_trains.py
(default-wired AV+GMM portfolio must train its GMM member; n_comp=0 must
still disable). Differential shape-recovery gate run attached to the PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ered cloud

sample_from_bins emits points grouped in lexicographic bin order, so
rv[:n_to_get] covered only the first ~half of the live volume while
sampling_density (hence portfolio q_mix) claimed uniform coverage of all
occupied bins -- a latent density lie that systematically biased shape
recovery in ANY multi-member portfolio (pulls up to 0.6 sigma at d2; the
previously-dead GMM member masked it).  Random subsample collapses the
gate-probe pull 0.644->0.0013, lnZ bias -0.299->-0.006, JS 0.081->1e-5.
Diagnosis: analyses/integrator_shape_gate/gmm_member_fix_gate/diagnosis/
(RIFT_roboto_paper).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@oshaughnessy-junior

Copy link
Copy Markdown
Owner Author

Auto-review finding: the new regression test cannot collect in a torch-free installation, because importing mcsamplerPortfolio eagerly loads the optional NF entry point and mcsamplerNFlow raises ModuleNotFoundError: No module named 'torch'.\n\nReproduction from this PR checkout:\n\nGW_SURROGATE='' RIFT_LOWLATENCY=1 PYTHONPATH=MonteCarloMarginalizeCode/Code pytest -q MonteCarloMarginalizeCode/Code/test/integrators/test_portfolio_gmm_member_trains.py\n\nThis is not a failure of the GMM regression itself: with entry-point discovery isolated, both the default-GMM-training and n_comp=0 checks pass. But #33 targets rift_O4d directly and does not contain the optional-plugin isolation from #28, so it cannot make this test reliably runnable in the production-style no-torch environment. Please either include that isolation here, make #33 explicitly depend on a merged equivalent, or arrange the test to avoid eager optional-NF discovery.

…ession test pytest-collectable

Review finding on #33: importing mcsamplerPortfolio eagerly load()s all
RIFT.integrator_plugins entry points, so a torch-free installation died with
ModuleNotFoundError before the new regression test could even collect.
Guard each pipeline.load() (skip + notice, matching the isolation #28
carries) and convert the regression script into two proper pytest test
functions. Verified: pytest -q passes (2 passed) with a torch-import
blocker on PYTHONPATH, reviewer's invocation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@oshaughnessy-junior

Copy link
Copy Markdown
Owner Author

Fixed in 8ca6368: guarded each pipeline.load() in the portfolio plugin discovery (skip-with-notice on unavailable optional deps, matching #28's isolation) and converted the regression script to proper pytest functions. Verified with the reported invocation plus a torch-import blocker on PYTHONPATH: 2 passed. The guard makes #33 independent of #28's merge order.

🤖 Generated with Claude Code

@oshaughnessy-junior
oshaughnessy-junior merged commit 4bac744 into rift_O4d Jul 23, 2026
19 checks passed
oshaughnessy-junior added a commit that referenced this pull request Jul 23, 2026
… by PR #33

PR #33 found two coupled bugs that invalidate the event-specific numbers in this document:
 * default-wired portfolio GMM members NEVER TRAINED (n_comp=None silently no-op'd), so the real-ILE
   portfolio runs here carried an untrained corpse member (found via this PR's freeze probe);
 * AV draw_simplified head-sliced a BIN-ORDERED cloud (~50-60% of live-volume bins) while
   sampling_density claimed uniform coverage of ALL bins -- a q_mix DENSITY LIE, which inflates the
   weight of any member drawing where AV never populates.

My earlier "the outliers are real heavy tails" conclusion was based on counting q_mix UNDERFLOWS
(zero found).  That test was correct but incomplete: it rules out a density underflow, not a density
LIE.  Lesson recorded: when a weight looks impossible, test whether the member actually DRAWS where
it claims density, not just for underflow.

The clipping study stands as a METHODOLOGICAL result (which quantities may be clipped and why -- those
arguments are analytic), but every S250114ax efficiency/ln Z figure needs re-measuring on top of #33.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
oshaughnessy-junior added a commit that referenced this pull request Jul 23, 2026
…in gate probe

Re-measured S250114ax on top of PR #33 (GMM members now actually train; AV draw_simplified no longer
misreports its density).  Result: the portfolio got WORSE when the GMM member came alive --
n_eff 2.1 (default) / 1.1 (adaptive) at 4M, vs 52.6 pre-#33 and 100.2 for standalone AV.  Pre-#33 the
GMM member was a corpse so the portfolio was effectively AV-only; now the allocation hands the live
GMM ~0.84 of the budget and the pooled n_eff collapses.

This isolates the DRAW-ALLOCATION pathology in clean form: never-freeze is working (AV updates every
chunk) but both allocation rules score by per-chunk n_ess, which sits at ~1 through VARAHA's slow
CUMULATIVE contraction, so a member that looks instantly good takes the budget.

New opt-in lever portfolio_varaha_min_frac / --portfolio-varaha-min-frac reserves a combined draw
fraction for VARAHA members, applied after either allocation rule.  It works mechanically (AV share
0.97 at floor 0.5, 0.85 at floor 0.7) but does NOT rescue this event: even a 3-15% GMM share still
poisons the pooled n_eff (~1-2).

Conclusion recorded: the missing capability is member EXCLUSION, not re-weighting -- any nonzero
share of a wrong member's draws enters q_mix and a handful of its samples dominate.  Honest
production guidance for AV-favorable high-SNR events: run standalone AV, not AV+GMM.  The
portfolio's demonstrated value stays the correlated regime (Benchmark 3).

Also adds test/expensive_before_merging/integrators/probe_portfolio_optin_flags.py: the
TESTING.md-required flag-ON probe for the opt-in features, reusing the merge-gate suite as a library
(identical targets/metrics/thresholds) and running in-process because the gate's spawn workers would
not carry a monkey-patch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
oshaughnessy-junior added a commit that referenced this pull request Jul 23, 2026
…get (post-#33); de-circularize credit

Post-#33 S250114ax, warm, same budget -- clipping the proposal-fit input is the lever that works:

  run                          Neff>=5    Neff>=10   final@4M
  standalone AV (reference)    0.695M     1.374M     100.2
  portfolio, default             --         --         2.1
  portfolio + defensive 0.05     --         --         2.5
  portfolio + PROPOSAL-FIT CLIP 0.420M     0.590M      14.4

n_eff=5 1.7x faster than standalone AV and n_eff=10 2.3x faster.  The real O4 event configs run
--n-eff 10, so AT THE TARGET THAT SHIPS the clipped AV+GMM portfolio beats standalone AV on this
event.  It plateaus at ~14 rather than reaching AV's 100, so AV alone still wins the stress target.

This only became visible after #33: pre-#33 the GMM member was a corpse, so there was nothing to
poison and clipping did nothing.  Lever ordering on this event: clip (2.1->14.4) >> defensive mixture
(2.1->2.5) ~ VARAHA draw floor (no rescue) > adaptive allocation (worse, 1.1).

Also de-circularizes the 'credit' signal: the raw MIS share frac_m*q_m/q_mix scales with frac_m, so a
member accrued credit BECAUSE it was dominant (a 0.95-share GMM scored 6e-4 vs AV 9e-10 and starved
AV to the floor -- the same circularity n_ess has).  Now normalized by frac_m => "integral explained
per unit allocation", which is allocation-invariant and is what an allocation rule must compare.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
oshaughnessy-junior added a commit that referenced this pull request Jul 23, 2026
…ment

Gate (PR #31 requirement), base rift_O4d @4bac7444 vs this branch, both incl. #33, run on an idle
node: COMPARE_EXIT=0, 0 blocking regressions, base and PR identical in aggregate (strict 8/8,
warn-only 5/5, starved 45/45), 22 of 23 portfolio rows bitwise identical to base.

The one remaining difference is mix_d2_n3_s303 (n_eff 736->517, bias -0.0060->-0.0064): the row where
never-freeze actually engages.  Both PASS comfortably, but it quantifies the trade -- never-freeze
buys starvation-immunity and costs ~30% n_eff where freezing would have been harmless.

Also hardens probe_portfolio_optin_flags.py to export CUDA_VISIBLE_DEVICES/OMP_NUM_THREADS and put
the checkout on sys.path itself.  Library-mode use of the suite does NOT inherit what
run_shape_recovery.sh exports; without it you import the INSTALLED RIFT and get confident, meaningless
numbers (this bit me once -- caught only because the absolute n_eff did not match the gate's).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
oshaughnessy-junior added a commit that referenced this pull request Jul 24, 2026
…es NOT generalise

4 typical O4 events, warm, --n-eff 30, real SEOBNRv5PHM (cuda128 container on idle Blackwell nodes):

  event      no-clip lnZ(neff)  clip lnZ(neff)  dlnZ   neff_ratio
  S231026ab  17.54 (28.9)       17.49 (29.8)    -0.05  x1.03
  S240426s   29.74 (31.2)       29.56 (30.3)    -0.18  x0.97
  S240513ei  83.76 (3.1)        83.83 (1.3)     +0.07  x0.42
  S240703ad  41.89 (3.3)        42.27 (5.0)     +0.38  x1.53

Clipping's dramatic S250114ax result (2.3x at the production target) is SPECIFIC to that event's
extreme heavy-tailed pathology.  On typical events it is a near-noop; on the two under-converged hard
events it is a wash within the n_eff~1-5 scatter.  ln Z agrees everywhere (|dlnZ| <= 0.38, MC error)
-- the portfolio replicates the AV integral with or without clipping.  Confirms opt-in/default-off was
the right call.  Closes the study's last open question.

Also confirmed en route: the cuda128/cc90-120 container runs SEOBNRv5PHM+cupy on Blackwell (CC 12.0),
matching the A100/cuda118 result -- the path that let this run bypass the saturated A100 condor pool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@oshaughnessy-junior
oshaughnessy-junior deleted the rift_O4d_portfolio_gmm_member_fix branch August 1, 2026 10:32
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.

1 participant