Breakdown grid v2: paired six-arm estimator, honest bootstrap errors, degeneracy guards#804
Breakdown grid v2: paired six-arm estimator, honest bootstrap errors, degeneracy guards#804cailmdaley wants to merge 6 commits into
Conversation
Rebuild the resolution x noise breakdown harness for statistical rigor. v1
reported bare m = <g>/<R>/gamma - 1 with no uncertainty; at moderate/low S/N
the per-seed shear noise gives sigma_m ~ 0.5, so its S/N trends were unmeasured.
Producer (run_breakdown_grid.py):
- Six signed arms per seed -- (g1+,g1-), (g2+,g2-), (psf+,psf-) -- sharing a
bit-identical noise realisation and sub-pixel shift (same RandomState into
make_data; galsim .shear() draws no rng). The +/- difference cancels the
shared shape noise that dominated v1.
- Paired m1/m2 = (<g[+]> - <g[-]>)/(2 gamma Rbar) - 1; additive c1/c2 and
leakage alpha from the PSF-shear pair; legacy ratio-of-averages kept for v1
continuity. Bootstrap-over-seeds errors (B=1000) on every estimator.
- Degeneracy guard: |Rbar| < 5 se(Rbar) -> m null with degenerate:true rather
than dividing by a response consistent with zero.
- Seeds independent per cell (10_000_000 + cell_index*100_000 + s), never
recycled. Per-band seed counts default {high:100, moderate:400, low:1600}.
- --true-noise ablation: per-pixel true-inverse-variance weight path via
stamp.bkg_rms. Diagnostics: mean_s2n, mean_T_ratio, sigma_mad_est/noise_true
contamination ratio.
- ProcessPoolExecutor over cells (--jobs); per-cell .npz of raw per-(seed,arm)
arrays for refit-free re-analysis; provenance (ngmix/galsim/git/wall time).
- Emits breakdown_grid_v2.json and mirrors breakdown_grid.json so the status
page reader (build_status.py loads that name) stays green unchanged.
Plotter (plot_breakdown_grid.py):
- m1/m2/|c1| vs resolution, hue=band, error bars on every point, degenerate
cells as open hatched markers at 0, +/-5e-3 tolerance band. Same PNG name.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9qEVRCWEekT5ACNB3rjkq
…ve the resampling The paired estimators' bootstrap resampled each arm independently, which destroys the +/-gamma pairing inside every replicate and inflates the reported error to the unpaired level (read 0.23 at a moderate cell where the per-seed pairs give a 0.009 numerator term). One index array per replicate now feeds every quantity: numerator pairs, R denominators (replacing the analytic se(R) that treated the two arms' correlated R values as independent), and the legacy ratio. Verified on the r=0.37 moderate cell: m1 = -0.0005 +/- 0.0243, decomposing as 0.0087 (paired numerator) + 0.0264 (R-bar noise) with corr(dg, R_i) = +0.42 — the honest error is response-noise dominated at moderate S/N. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J9qEVRCWEekT5ACNB3rjkq
…n, epochs)
Three CLI knobs turn the clean-room v2 grid into production-settings
ablations, each defaulting to the bit-identical clean baseline:
- --centroid-source {hsm,wcs}: "wcs" is the production default since
31ae736. The harness fabricates the astrometry truth the path consumes:
a TAN WCS whose CD matrix IS the drawing jacobian, ra/dec evaluated
through that same WCS at the object's true pixel position (the toy
analogue of coadd (x,y) -> (ra,dec) -> epoch pixel).
- --wcs-g1/--wcs-g2/--wcs-theta-deg: shear/rotate the drawing WCS away
from a pure pixel scale (the esheldon/ngmix#72 sensitivity axis).
- --n-epochs (pre-existing) completes the parity set.
make_data grows wcs= and return_centers= kwargs, both default-off and
backward-compatible. Under a non-trivial WCS the sub-pixel shift is drawn
in PIXEL space (uniform +-0.5/axis): a world-space +-scale/2 box maps
through a sheared jacobian to a parallelogram exceeding +-0.5 pixel,
which silently breaks round-to-nearest-pixel centroid logic with
off-by-one-pixel centres. The legacy path keeps the exact legacy draw
lines: smoke-verified bit-identical (78/78 arrays) against the v2
production baseline npz.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjLSiytceGpbMfzZe6wqGa
Even stamps put the galsim centre on a half-integer; np.round() in the ngmix "wcs" centroid path then lands one pixel off for every object -- a systematic half-pixel origin error that would masquerade as a production pathology. Production VIGNETs are odd; assert it. Also soften the "exact by construction" round-trip claim (TAN residual ~5e-9 px). Fresh-eyes review of fb745d1: otherwise clean -- coordinate chain, rng-pairing contract, matrix orientation, pickle boundary, and all 14 make_data call sites verified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjLSiytceGpbMfzZe6wqGa
One BootPSFFailure raise out of ~1e5 fits killed the whole jac-realistic ablation grid (job 808259). Production treats bootstrap failures as flagged objects, so the harness now does the same: record flags=-1 / finite=False, let the pair-drop policy drop the seed, and let fail_frac record the rate -- a systematic failure mode still surfaces loudly there instead of fatally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjLSiytceGpbMfzZe6wqGa
plot_s4_ablations.py overlays ablation grids on the baseline (3 bands x m1/m2/c1, tolerance bands, dodged error bars) and prints per-arm max|m|/max|delta| significance tables. --paired recomputes delta-m1 per cell from the raw npz records with a shared bootstrap resample for bit-identical-image arms (the wcs-centroid ablation), cancelling shape noise to ~1e-6-level errors at high S/N. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjLSiytceGpbMfzZe6wqGa
|
Update: S4 production-parity ablations — the v2 PASS survives production settings. Three new parity knobs on the harness (
No |Δ|/σ ≥ 2 anywhere across ~300 arm×band×cell comparisons (the 1.7–1.9σ cluster in the two 2-epoch arms traces to a shared baseline fluctuation — both arms sit closer to zero than the baseline at that cell). Comparison figure + per-arm significance tables: Two notes on the diff since the original description: the test-plan line "no — Claude Fable on behalf of Cail |
Summary
Replaces the v1 breakdown-grid harness (16 unpaired seeds/cell, no uncertainties) with a statistically sound v2:
degenerate: trueinstead of dividing (the v1 grid printed m2 = −58.6 at the point-source/low-S/N corner from exactly this division).--true-noiseablation knob (routesprepare_ngmix_weightsonto the per-pixel true-inverse-variance path viastamp.bkg_rms).Why it matters
The v1 panel showed alarming S/N-dependent m (up to +0.31 at S/N≈50) and c trends. The v2 grid (24 cells, ~100k metacal fits, SLURM) shows they were statistical artifacts of the v1 harness: every resolved cell in every band is consistent with m = 0 (precision ~1e-3 high band, ~1e-2 moderate, ~1.5e-2 low), |c1| < 1e-3 and leakage α ≤ 0.2% everywhere, and R11 ≈ R22 on resolved rungs. The live
do_ngmix_metacalpath is healthy at survey-relevant S/N; the earlier panel was measuring its own noise floor.Test plan
src/changes — harness-side only (scripts/python/)— Claude Fable on behalf of Cail
🤖 Generated with Claude Code
https://claude.ai/code/session_01J9qEVRCWEekT5ACNB3rjkq