Skip to content

portfolio: VARAHA never-freeze default + freeze/allocation/clipping policy (gate-validated) - #28

Merged
oshaughnessy-junior merged 17 commits into
rift_O4dfrom
rift_O4d_portfolio_freeze_tuning
Jul 23, 2026
Merged

portfolio: VARAHA never-freeze default + freeze/allocation/clipping policy (gate-validated)#28
oshaughnessy-junior merged 17 commits into
rift_O4dfrom
rift_O4d_portfolio_freeze_tuning

Conversation

@oshaughnessy-junior

@oshaughnessy-junior oshaughnessy-junior commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Portfolio freeze-policy tuning, plus the draw-allocation and weight-clipping levers it exposed.
Base retargeted to rift_O4d (PRs #26/#27 merged); merged with #33.

Problem

The balance heuristic gave a VARAHA/AV member weight ~0.01 before it had contracted (< portfolio_freeze_wt 0.05), so it was frozen from chunk 1 and never contracted — the portfolio rode a stalling GMM member. Grace/revive cannot fix it: a VARAHA member only contracts on the chunk it is updated, and only earns weight once contracted, so reviving 1/N of the time never escapes (measured: the revive sweep stays at n_eff 1–3).

What this changes

  • VARAHA members are freeze-exempt by default (portfolio_varaha_never_freeze) — they update every chunk past their breakpoint, like standalone AV. q_mix makes this unbiased for any weights, so it can only cost eval cycles, never bias.
  • CLI: --portfolio-grace-iters / --portfolio-revive-period / --portfolio-freeze-wt / --portfolio-varaha-never-freeze / --portfolio-varaha-can-freeze, threaded into sampler.setup.
  • Opt-in levers (all default OFF, so the default path is unaffected): adaptive-probe draw allocation (--portfolio-adaptive-alloc) with selectable quality signal (--portfolio-quality-signal {global,credit,ness}), proposal-fit weight clipping (--portfolio-weight-clip), VARAHA draw floor (--portfolio-varaha-min-frac), plateau revive (portfolio_plateau_revive).
  • Two bug fixes found by benchmarking: --sampler-portfolio AV,GMM now comma-splits as documented (it was silently a 1-member portfolio); portfolio plugin discovery tolerates missing optional deps (the NFtorch import made the whole portfolio unimportable in the production container — independently fixed in integrators: portfolio GMM dead-member fix (n_comp=None) + AV draw-density repair (bin-ordered head slice) #33, resolved to that version).

Validation (both TESTING.md requirements)

Shape-recovery merge gate (base rift_O4d @4bac7444 vs branch, both incl. #33, quiet 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.

Flag-ON probe (required because the default gate is bitwise-identical for opt-in code) — gate's own targets/metrics/evaluate(): 0 opt-in regressions, both features help.

target flags OFF adaptive_alloc weight_clip
d2_n1_s303 1502 3021 3037
d2_n3_s303 517 1058 816
d4_n1_s303 163 415 263
d4_n3_s303 7 (starved) 15 54

A regression the gate caught in this PR (fixed)

The first gate run showed ~13/20 portfolio rows losing n_eff (2–4×). It did not block (portfolio is warn-only) but this PR changes the portfolio default path, so it was attributed rather than waived:

  • never-freeze was NOT the cause (ratio 1.00 — it only engages where a member would actually be frozen);
  • the plateau-aware revive was, reproducing base exactly when disabled (25.9→53.5, 29.1→83.8, 64.0→102.1, 7.2→31.4, 37.3→61.9). Forcing updates of members the freeze schedule would have parked makes their proposals worse. Now defaults off.

Honest caveats

  • Never-freeze is not free: on the one gate row where it engages, n_eff 736→517 (~30%). It buys starvation-immunity and pays for it where freezing was harmless. Both PASS comfortably.
  • Clipping's starved-regime gain is n_eff-only: the 7→54 row also moves bias −0.147→−0.279, and at n_eff ≲ 50 the gate deems shape untestable — do not read it as a shape result.
  • S250114ax figures in earlier revisions of the design doc are superseded by integrators: portfolio GMM dead-member fix (n_comp=None) + AV draw-density repair (bin-ordered head slice) #33 (dead GMM member + AV draw_simplified density lie). Re-measured post-integrators: portfolio GMM dead-member fix (n_comp=None) + AV draw-density repair (bin-ordered head slice) #33: portfolio + proposal-fit clipping reaches n_eff=10 at 0.59M vs standalone AV's 1.374M (2.3× at the production --n-eff 10 target), though it plateaus ~14 vs AV's 100 at the stress target.
  • Adaptive allocation is opt-in for a reason: with the per-member n_ess signal it starves a slow-contracting AV on AV-favorable real events. The global/credit signals are provided as better-founded alternatives; credit is normalized per unit allocation to avoid the circularity that a dominant member accrues credit because it is dominant.

Follow-up (not blocking)

Post-#33 multi-event validation on real events via the container family + condor — blocked here only on hardware (the cuda118 image won't run on Blackwell; the busy node kills the suite via RLIMIT_NPROC). Defaults are unaffected by that work.

Design doc: RIFT/integrators/DESIGN_portfolio_freeze_policy.md.

🤖 Generated with Claude Code

…r-import fix

The portfolio balance heuristic gave a VARAHA/AV member weight ~0.01 before it had
contracted (< portfolio_freeze_wt 0.05), freezing it from chunk 1 so it never became the
workhorse; the portfolio then rode a stalling GMM member (S250114ax cold n_eff ~1.9 vs
standalone AV ~100).  Grace/revive (f2d51de) cannot fix this: a VARAHA member only
contracts on the chunk it is updated, and it only earns allocation weight once it has
contracted -- a feedback loop that reviving 1/N of the time never escapes (measured: the
revive sweep stays at n_eff 1-3).

Fix: make VARAHA/AV members freeze-EXEMPT by default (portfolio_varaha_never_freeze=True) --
update every chunk past their breakpoint, like standalone AV.  The balance-heuristic mixture
density q_mix keeps the estimate unbiased for any weights, so continuous VARAHA updates can
only cost a few selfish-draw evals, never bias.  Also add a plateau-aware revive (keep
updating any low-weight member whose own per-chunk n_ess is still climbing) and a per-member
n_ess history for diagnostics.

Wire CLI flags into the driver and thread them into sampler.setup:
  --portfolio-grace-iters / --portfolio-revive-period / --portfolio-freeze-wt
  --portfolio-varaha-never-freeze (default) / --portfolio-varaha-can-freeze (disable)
setup() now ignores None (unset) kwargs so the sampler keeps its defaults.

Two correctness fixes surfaced by benchmarking:
 * --sampler-portfolio is now comma-split ('AV,GMM' was one bogus member -> 1-member portfolio).
 * portfolio plugin discovery no longer aborts the whole import when a plugin's optional deps
   are missing: the NF plugin does `import torch`, absent in the production GPU container, which
   made mcsamplerPortfolio unimportable there -> mcsampler_Portfolio_ok=False -> every portfolio
   run died with a NameError.  Unusable plugins are now skipped, not fatal.

Benchmarks (GPU A100), see RIFT/integrators/DESIGN_portfolio_freeze_policy.md:
 * S250114ax (hard, rho~82): never-freeze rescues AV n_eff 3.4 (frozen) -> 53, tracking
   standalone AV through the useful range; grace/revive tuning stays at 1-3; standalone GMM
   is NaN-unusable (works only inside the portfolio).
 * 4 typical O4 events (in-container, real SEOBNRv5PHM): portfolio REPLICATES the standalone-AV
   ln Z within MC error on every event and AV becomes the in-portfolio workhorse; on
   AV-favorable events the portfolio reaches a given n_eff in more evals (it still spends a
   share on GMM) -- an efficiency limit set by the DRAW-ALLOCATION heuristic, not the freeze
   policy, which is now solved.

Harness: test/integrators/bench_portfolio_freeze.sh, bench_multi_event.py, run_multi_event.sh,
parse_neff_traj.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
oshaughnessy-junior and others added 4 commits July 21, 2026 18:53
Follow-on to the never-freeze fix.  With never-freeze the portfolio is unbiased and AV is no
longer starved of UPDATES, but the DRAW allocation still split ~half the budget to GMM even when
AV was winning, so on AV-favorable events the portfolio needed more evals than standalone AV, and
on hard events it under-converged.  Root cause is a draw catch-22 identical in structure to the
freeze bug: a member's per-chunk n_ess is suppressed while it has few draws, so the member that
should win is stuck under-observed and never earns more draws.

Fix (portfolio_adaptive_alloc, default on): decouple a per-member QUALITY estimate (EMA of n_ess,
updated ONLY from chunks where the member had a fair allocation) from the ALLOCATION
(quality^exponent above a small floor), and ROUND-ROBIN PROBE one member per probe_period chunks
at a raised share so a suppressed member gets a fair look and can prove itself.  q_mix keeps every
allocation unbiased, so this only trades efficiency, never correctness.  Knobs (setup + CLI):
portfolio_adaptive_alloc / --portfolio-static-alloc, portfolio_alloc_exponent (2.0) /
--portfolio-alloc-exponent, portfolio_probe_period (4) / --portfolio-probe-period,
portfolio_alloc_floor (0.05), portfolio_quality_decay (0.5), portfolio_probe_frac (0.6).

New test test/integrators/test_portfolio_adaptive_alloc.py (synthetic, GPU, no ILE), fixed budget,
standalone AV vs GMM vs portfolio:
  * CORRELATED (compound-symmetric Gaussian): full-cov GMM beats AV's axis-aligned bins
    (n_eff 390 vs 131); adaptive concentrates on GMM (weight 0.88) so the PORTFOLIO BEATS
    standalone AV (381 vs 131) -- the correlated-problem regime where beating AV is expected.
  * cold VARAHA/AV under-covers the Gaussian tails and is biased low (-0.6..-0.9); the portfolio
    stays unbiased (-0.05) because the covering GMM enters q_mix.
The portfolio is now a "best-of" (tracks whichever member wins) rather than a fixed compromise.

test_portfolio_balance_heuristic.py: pin portfolio_adaptive_alloc=False there (it isolates the
q_mix ESTIMATOR under a fixed pathological allocation; adaptive alloc would change that scenario).
Also add a per-integration override: integrate_log now honors portfolio_adaptive_alloc in kwargs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… real events)

Real-event check flipped this from default-on to default-off.  On S250114ax (a real high-SNR
AV-favorable event) adaptive allocation drove the true AV workhorse to the 0.05 floor and rode a
self-consistent-but-worse GMM member: measured n_eff 8 vs 53 for the legacy allocation -- a
regression.

Root cause: the quality signal is each member's per-chunk Kish n_ess, which rewards
SELF-CONSISTENCY, not integral coverage.  A warm GMM is instantly self-consistent (per-chunk
n_ess ~120) while a warm VARAHA/AV member's per-chunk n_ess is genuinely ~1 during its slow,
CUMULATIVE contraction (its value emerges over ~70 chunks).  So the signal ranks the wrong member
and the probe can't rescue AV (it still looks bad at high allocation until fully contracted).  A
correct default needs a GLOBAL-impact signal (a member's marginal contribution to the pooled q_mix
n_eff), not per-member self-n_ess -- future work.

So: portfolio_adaptive_alloc now defaults False; CLI flag flipped to --portfolio-adaptive-alloc
(enable) from --portfolio-static-alloc; the default portfolio keeps never-freeze + legacy n_ess
reweighting (the validated 53-on-S250114ax / replicates-AV behavior).

The MECHANISM is still sound when the signal is right: test_portfolio_adaptive_alloc.py now
broad-seeds the GMM member (so it reliably models the correlation instead of gambling on cold GMM
finding a thin ridge) and, opting in to adaptive, robustly shows across seeds that on a
compound-symmetric CORRELATED Gaussian the portfolio concentrates on GMM and BEATS standalone AV
(n_eff ~390 vs ~20), unbiased.  DESIGN doc updated with the regression and the honest verdict.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…daptive allocation

Replaces the per-member Kish n_ess quality signal with the right objective:
  g_m = 2*mean_w_m/S - mean_w2_m/Q      (S=sum w, Q=sum w^2 over ALL members' samples)
i.e. each member's MARGINAL GAIN IN POOLED n_eff PER SAMPLE, derived from n_eff = S^2/Q.  It
credits a member for the weight MASS it supplies and debits it for the weight VARIANCE it injects.
Selectable via portfolio_quality_signal / --portfolio-quality-signal ('global' default, 'ness'
legacy).  Also: first observation is adopted outright (the global signal has an arbitrary scale, so
EMA-ing from the placeholder would bias it), no fair-allocation gating needed for 'global' (it is
self-correcting at low allocation), and default alloc exponent 2.0 -> 1.0.

Two simpler candidates were implemented and measured first; both fail, and the reasons are recorded
in DESIGN_portfolio_freeze_policy.md:
 * Kish n_ess is SCALE-INVARIANT, so it cannot see whether a member carries any integral mass.
 * mean weight alone is BACKWARDS: a well-matched contracted AV correctly has small uniform
   weights while a broad GMM's rare outlier sets the max (S250114ax: AV 1e-40 vs GMM 2e-4).

Synthetic (test_portfolio_adaptive_alloc.py, 2 seeds): on a compound-symmetric CORRELATED Gaussian
the portfolio concentrates on the full-cov GMM and BEATS standalone AV (~375 vs ~6-61 n_eff),
unbiased; uncorrelated case also unbiased.

IMPORTANT NEGATIVE RESULT on the real S250114ax event: the global signal still ranks GMM first, and
the measured values say why -- GMM's g is exactly 1.053e-4 = 1/9500 = 1/n_GMM, the analytic
signature of ONE sample owning the whole estimator (g = 2/n - 1/n for the holder of the dominant
outlier), vs AV ~1e-73.  The chunk max is a catastrophic GMM outlier ~1e73x any AV weight (a draw
where q_mix ~ 0 but the target is nonzero).  So on that event ALLOCATION IS NOT THE BOTTLENECK --
unbounded member importance weights are; even floored at 5% the GMM member still injects outliers,
which is why the legacy allocation reached 53 and not AV's 100.  The next lever is weight bounding /
member exclusion (defensive component, clipping), a member-quality fix rather than an allocation
one.  Adaptive allocation therefore stays OPT-IN; the default remains never-freeze + legacy
reweighting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… mass + q_mix underflow guard

Adds portfolio_weight_clip / --portfolio-weight-clip C (default OFF): cap each importance weight at
tau = C*sqrt(n)*mean(w) (Ionides truncated IS).  The removed and total weight mass are accumulated
in log space and the induced ln Z bias is PRINTED every chunk, so the bias is never silent and is
recoverable.  Also adds a permanent q_mix UNDERFLOW counter.

Findings (all measured; see DESIGN_portfolio_freeze_policy.md):

1. The S250114ax outliers are REAL heavy tails, not the 1e-300 q_mix floor firing: an instrumented
   run counts ZERO underflows.  So clipping is a legitimate tool here, not a mask.

2. Synthetic ground truth (bench_weight_clip.py, analytic ln Z, 2 seeds): where weights are
   well-behaved clipping is a COMPLETE NO-OP at C=1,5,20 -- no n_eff gain, no bias, clip_frac ~0
   on both the uncorrelated and correlated targets.  The tracker also works as a bias ESTIMATOR
   (predicted -0.003 vs measured -0.006, within +/-0.003 seed noise).

3. Real S250114ax -- THE TRAP.  Clipping C=1 reaches n_eff=100 in 1.87M evals, 2x FASTER than
   standalone AV (3.64M), and reports a perfectly converged run -- but its ln Z is 1180.25 vs AV's
   trusted 1191.79, i.e. 11.5 nats LOW.  The tracker flagged it live (removed mass frac 0.87-0.98).
   n_eff STOPS BEING A VALIDITY CHECK the moment clipping engages.

4. Side finding refining Benchmark 2: even WITHOUT clipping the AV+GMM portfolio reads ln Z=1183.12
   on this event, 8.7 nats below AV.  Heavy-tailed IS is unbiased in expectation but realizes LOW in
   nearly every run, so in production it behaves like a bias.  "Portfolio replicates AV's ln Z"
   holds on the four TYPICAL events, NOT on S250114ax -- there the right move is to drop that member.

Verdict: clipping is a SAFETY VALVE and a DIAGNOSTIC, not a speedup -- it cannot manufacture n_eff
without discarding integral mass one-for-one.  Where it is safe it does nothing; where it does
something it is biasing.  The valuable artifact is the tracked clip_frac.

Guidance recorded for promoting this to the individual integrators (AV): never ship bare clipping
(needs the mass tracking + a refuse-to-clip gate), surface clip_frac as a first-class diagnostic
regardless, and require the FULL LVK PP campaign for deep AV weight changes -- a small ln Z bias
would pass a single-event n_eff check and only surface as PP miscalibration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
oshaughnessy-junior and others added 3 commits July 22, 2026 17:08
…iased by construction)

Restructures the opt-in weight clip so it can never bias the estimate.  RIFT already separates the
two uses of the per-sample weights; clipping now respects that split:
  * log_integrand -> the ESTIMATE (init_log/update_log -> ln Z; maxval -> n_eff): left UNCLIPPED.
  * log_weights   -> ADAPTATION only (per-member n_ess report, allocation signal, and
    member.update_sampling_prior training): clipped copy log_weights_adapt.
Adaptation only shapes proposals (like warm-starts/oracles/q_mix), so clipping it cannot bias ln Z.
This is strictly better than the alternative of DROPPING a chunk that clipped: dropping conditional
on "a big weight appeared" is data-dependent selection and biases ln Z low (it preferentially
discards the rare mass-carrying chunks).  The tracked withheld mass becomes a TAIL DIAGNOSTIC.

Measured:
 * Synthetic (analytic ln Z): estimator bias UNCHANGED at C=0,1,5 -- the falsifiable proof the
   estimate is untouched; no-op where weights are well-behaved.
 * S250114ax: adaptation-clip C=1 gives ln Z=1184.4 (matches the unclipped 1183.1, NOT the biased
   estimator-clip 1180.3), confirming unbiasedness -- but n_eff COLLAPSES to 1.2 (vs 52.6 unclipped)
   because 97% of the weight mass was withheld FROM ADAPTATION: on this event the mass-carrying
   samples ARE the signal the GMM must learn, so clipping them starves the proposal.  Clipping
   cannot fix a fundamentally heavy-tailed member; the right move there stays "drop the GMM member".

Prior estimator-side attempt kept in the design doc as the cautionary result (n_eff=100 at 1.87M,
2x faster than AV, but ln Z biased -11.5 nats -- n_eff stops being a validity check once the
estimator is clipped).

Verdict: adaptation-only clipping is the correct/unbiased form -- a safety valve against one
pathological weight wrecking a member's covariance fit / the allocation, and a no-op otherwise.  It
does NOT manufacture n_eff.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… n_ess-inflation regression)

The previous "adaptation-stream" clipping was too broad: it also clipped the weights used for the
per-member n_ess REPORT and the allocation signal.  Clipping FLATTENS weights, which INFLATES a
member's Kish n_ess, so the allocation perversely rewarded exactly the member whose weights had to be
clipped.  Measured on S250114ax (legacy alloc + clip C=1): the clipped GMM member's reported n_ess
jumped to 60-230, the allocation drove the true AV workhorse to the 1% floor, and n_eff stuck at ~1
(vs 53 unclipped) -- a regression, even though ln Z stayed unbiased.

Fix: the clipped copy log_weights_adapt now feeds ONLY member.update_sampling_prior (the GMM
covariance fit).  The estimator (ln Z, n_eff), the n_ess report, and the allocation signal all use
the TRUE unclipped weights.  A short S250114ax run now climbs n_eff normally again.

Net clipping scope, all measured:
 * estimator: NEVER (clipping it biased ln Z -11.5 nats on S250114ax).
 * n_ess report / allocation: NEVER (inflates the clipped member's n_ess -> starves AV).
 * GMM proposal fit: the one legitimate use (stops a single enormous weight making the covariance
   fit degenerate); a no-op where weights are well-behaved.

Also lands run_multi_event_clip.sh (typical-event clip safety harness).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 real events (in-container), proposal-fit clip C=1 vs no-clip: unbiased everywhere, n_eff
comparable-or-better, and it recovers the two hard events the earlier broad-scope bug had degraded
(S240703ad n_eff 1.1 -> 6.9 -- protecting the GMM covariance fit from outliers gave a better
proposal).  ln Z differences within the large MC error at these low n_eff.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@oshaughnessy-junior
oshaughnessy-junior changed the base branch from rift_O4d_adaptive_integrator to rift_O4d July 23, 2026 00:56
… 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
…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 and others added 4 commits July 22, 2026 18:29
…o_freeze_tuning

# Conflicts:
#	MonteCarloMarginalizeCode/Code/RIFT/integrators/mcsamplerPortfolio.py
…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>
Answers "we have other rules (q_mix) to score, not n_eff?".  The per-member score was Kish n_ess,
which -- although computed on q_mix-denominated weights -- is SCALE-INVARIANT and therefore blind to
whether a member carries any integral mass at all.  The mixture machinery supports a strictly better,
q_mix-native rule: balance-heuristic CREDIT ASSIGNMENT.  Each sample's contribution is owed to
members in proportion to their share of the mixture density there, so

    credit_m = sum_i [ frac_m q_m(x_i) / q_mix(x_i) ] * w_i        (per drawn sample)

This credits a member for COVERING WHERE THE INTEGRAND IS even if it drew few samples there --
precisely the signal a slow-contracting VARAHA member needs, and precisely what n_ess cannot see.

Implementation: the q_mix block already evaluates every member's q_m, so we retain frac_m*q_m per
member (self._chunk_mix_parts) and form the credit in the report block; no extra density evaluations.
Selectable via portfolio_quality_signal / --portfolio-quality-signal {global,credit,ness}; 'credit'
is zero-based like 'global' (only the legacy Kish signal is floored at 1).

Synthetic check (correlated compound Gaussian, adaptive allocation on): all three signals run and
stay unbiased (bias -0.0003 / -0.0011 / +0.0054 for ness/global/credit) at comparable n_eff.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…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

Copy link
Copy Markdown
Owner Author

Automated review of efb30e435efaa1bf4af91942f2be22e7baa2f602 against rift_O4d 4bac744405fb18696710ea6fbc886e63beae6673 found two merge blockers and one gate bug:

  1. The required standard CPU shape-recovery comparison, with portfolio promoted to strict, reports portfolio/mix_d6_n1_s202 as PASS -> STARVED: base n_eff=102.14, n_ess=1070.15, lnI-lnZ=-0.02644; head n_eff=63.95, n_ess=882.36, lnI-lnZ=-0.02663. The differential comparator exits 1 with one blocking regression. AV/GMM deltas were otherwise nonblocking or pre-existing.

  2. The committed test_portfolio_balance_heuristic.py --as-test fails reproducibly in two clean-head runs at its fixed seed. The new q_mix decoy result is lnI-lnZ=-1.159, outside its abs(bias) <= 0.20 gate; the normal control passes at +0.007 (old stratified result -2.808).

  3. probe_portfolio_optin_flags.py:105 treats PASS -> STARVED as acceptable (vs not in ("PASS", "STARVED")), contrary to RIFT/integrators/TESTING.md and compare_shape_results.py. This run did not trigger that hole: the 16-row opt-in probe exited 0 and all baseline-PASS rows stayed PASS.

Supporting checks are clean: GitHub MERGEABLE/CLEAN, all 19 Actions checks green, git merge-tree, git diff --check, touched Python compilation, shell syntax, ILE --help, optional-plugin isolation, and test_portfolio_adaptive_alloc.py --as-test all passed.

Please resolve/revalidate the PASS -> STARVED default-path regression, make the balance harness reliable, and make the opt-in probe block PASS -> STARVED; then rerun the full base/head shape comparison with portfolio strict.

oshaughnessy-junior and others added 3 commits July 23, 2026 05:08
…n_eff in the shape gate)

The shape-recovery merge gate (PR #31) flagged a portfolio regression on my branch: ~13 of 20
portfolio rows lost n_eff vs base, several by 2-4x.  It did not block (portfolio is warn-only,
strict=AV,GMM) but PR #28 changes the portfolio DEFAULT path, so it needed attribution.

Isolated on the gate's own targets, same checkout, one knob at a time:
  * never-freeze (my headline default): ratio 1.00 on every row -- NO effect on these targets.
    (It matters on the S250114ax freeze-out, not here.)  My leading hypothesis was wrong.
  * plateau-aware _climbing revive: SOLE cause.  plateau ON -> OFF reproduces gate base EXACTLY:
        d4_n1_s101 25.9 -> 53.5 (base 53.5)    d4_n3_s202 29.1 -> 83.8 (base 83.8)
        d6_n1_s202 64.0 -> 102.1 (base 102.1)  d6_n3_s202  7.2 -> 31.4 (base 31.4)
        d8_n1_s101 37.3 -> 61.9 (base 61.9)

Mechanism: forcing updates of members the freeze schedule would have parked makes their proposals
WORSE, not better -- the opposite of the intuition that motivated it.

Fix: portfolio_plateau_revive now defaults False (base-equivalent default path); the behaviour stays
available as an opt-in knob with the measurements recorded inline so nobody re-enables it blind.

Method note: the first isolation attempt was INVALID -- it put the suite's test dir on sys.path but
not the branch on PYTHONPATH, so it imported the INSTALLED RIFT (where the knob does not exist) and
reported "no effect" with n_eff nowhere near the gate's.  A valid isolation must reproduce the gate's
absolute numbers; these do, row for row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…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>
…eatures help)

TESTING.md requires opt-in changes be probed with the flag ON, since the default-path gate is
bitwise-identical for opt-in code and therefore proves nothing about it.  Probe uses the gate's own
targets/metrics/evaluate(), so PASS here is PASS by gate criteria.

  target       flagsOFF  adaptive_alloc  weight_clip
  d2_n1_s303       1502            3021         3037
  d2_n3_s303        517            1058          816
  d4_n1_s303        163             415          263
  d4_n3_s303          7(starved)     15           54

0 opt-in regressions; |bias| <= 0.024 on every PASS row.  Independently corroborates the S250114ax
result that proposal-fit clipping is the effective lever (worst row 7 -> 54).

Recorded caveat: on the starved row the bias grows (-0.147 -> -0.279).  At n_eff <~ 50 the shape is
untestable by the gate's own policy, so clipping's gains in the STARVED regime are unvalidated for
SHAPE even though n_eff improves -- do not read the starved-row n_eff jump as a shape result.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@oshaughnessy-junior oshaughnessy-junior changed the title portfolio: VARAHA never-freeze default + freeze-policy CLI + container-import fix portfolio: VARAHA never-freeze default + freeze/allocation/clipping policy (gate-validated) Jul 23, 2026
@oshaughnessy-junior
oshaughnessy-junior merged commit 9c40120 into rift_O4d Jul 23, 2026
19 checks passed
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