Image-simulation m-bias: extracted core + snakemake orchestration#225
Image-simulation m-bias: extracted core + snakemake orchestration#225cailmdaley wants to merge 16 commits into
Conversation
Bring the image-simulation shear-bias core over from the divergent
image_sims_val branch as a clean, self-contained unit:
- src/sp_validation/image_sims.py: ImageSimMBias reads the five
calibrated grid catalogues (reference + g1/g2 +-shear pairs), matches
each to the reference by RA/Dec, and returns m1/m2/c1/c2 with
bootstrap errors. Its only sp_validation dependency is
match_catalogs_radec.
- src/sp_validation/catalog.py: add match_catalogs_radec (nearest-
neighbour RA/Dec match within a threshold) alongside match_stars2 --
develop had no such helper, and image_sims_val's cat.py is not on
develop.
- scripts/compute_m_bias_image_sims.py: CLI driver; now casts numpy
scalars to plain floats before dumping so m_bias_results.yaml is
human-readable rather than !!python/object binary.
- config/calibration/mask_v1.X.{4,9}_im_sim.yaml: the image-sim
calibration/cut configs the calibrate step consumes.
- tests/test_image_sims.py: analytic synthetic-recovery test (injects a
known m/c, asserts exact recovery) plus a match_catalogs_radec check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcYFjEWXxyCtUGzQJ7AhLH
Add workflow/rules/image_sims.smk and a standalone entry point that drive the image-simulation validation chain end to end across two containers, the sp_validation-side half of UNIONS-WL/MultiBand_ImSim#1: ShapePipe container: im_pipeline (raw sim tiles -> per-tile cats), im_merge (create_final_cat -> final_cat_{sim}) sp_validation container: im_extract -> comprehensive, im_calibrate -> cut, im_mbias -> m_bias_results.yaml Design: - Native container pattern: every rule sets `container: None` and calls `apptainer exec` explicitly, since the two halves live in different images. Extract/calibrate use the sp_validation image's own self-consistent calibration environment; m-bias injects this branch's extracted core on PYTHONPATH. - Fully parameterised under config["image_sims"] -- container paths, repo roots, data roots, PSF dict, tile list, sim/calibration knobs -- so a fresh user drives it from config alone, no hard-coded clone layout (the "runnable by someone other than Fabian" gap). - Shell blocks avoid f-strings, so snakemake's {input}/{wildcards} placeholders interpolate correctly -- fixing the {input.tiles} proxy- repr bug from the original Snakefile. - params_im_sim.py derives the field name from the run-dir basename, so a single shared template serves every sim. - Included in the main workflow under `if "image_sims" in config`; standalone via workflow/image_sims/Snakefile. Verified on candide: DAG resolves (36 jobs, correct chaining); im_init and im_calibrate run in-container and produce a real cut catalogue; the extracted m-bias core produces a real m2/c2 from grid_2's calibrated ShapePipe catalogues. Full end-to-end from Martin's on-disk intermediates is blocked by data-vintage inconsistencies (see fiber), not the workflow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcYFjEWXxyCtUGzQJ7AhLH
… from scratch
im_init only staged params.py / mask / cfis, but ShapePipe's get_images_runner
resolves the raw tile+exposure images via $SP_DIR/input_tiles and
$SP_DIR/input_exp -- top-level symlinks that neither run_job nor any init
script creates. Without them im_pipeline can only run off pre-staged
intermediates, not from raw images, which defeats the "runnable from raw
SKiLLS" purpose of the workflow.
Stage input_tiles -> {input_sims_base}/{sim}/images/SP_tiles and input_exp ->
.../SP_exp in im_init, matching the ground-truth run-dir layout of Martin's
working image-sims runs. Verified: init produces resolving symlinks to the
real CFIS_simu_image-*.fits for all five grid_1 sims.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uTmBAtdP5Wvav2M76MgCA
… grammar
The image-sim merge->extract->calibrate chain had never been run end-to-end
on the post-#761 ngmix column grammar; doing so surfaced a cascade of
old-grammar and interface defects, all fixed here (validated on 1z2z_grid_1:
2345 -> 1684 post-cut objects, <e1>,<e2> ~ 0 for the no-shear reference):
- params_im_sim.py: migrate add_cols / add_cols_pre_cal / centers to the v2
grammar (NGMIX_ELL_* -> NGMIX_G{1,2}_*, NGMIX_Tpsf_* -> NGMIX_T_PSF_RECONV_*,
drop NGMIX_MOM_FAIL, keep NGMIX_MCAL_TYPES_FAIL; IMAFLAGS_ISO omitted -- the
sims run no imaging-flag masking).
- extract_info.py: guard every star-catalogue-dependent block under
`if star_cat_path`. The sims have no star catalogue (star_cat_path=None), so
the star match/metacal/PSF-leakage diagnostics -- all downstream of the
comprehensive-cat write -- must be skipped rather than crash on undefined
`ind_star`. Galaxy diagnostics preserved; mixed gal+star R histograms fall
back to galaxy-only.
- catalog_builders.py: make CalibrateCat.read_cat FITS-aware. The sims produce
a single per-run comprehensive catalogue (FITS, from write_shape_catalog),
not the joined multi-patch HDF5 the data path builds; read it directly with
dat_ext=None instead of forcing h5py.
- mask_v1.X.9_im_sim.yaml: v2 grammar (NGMIX_MOM_FAIL -> NGMIX_MCAL_TYPES_FAIL,
NGMIX_ELL_PSFo_NOSHEAR_{0,1} -> NGMIX_G{1,2}_PSF_ORIG_NOSHEAR) and .fits input.
- image_sims.smk: comprehensive cat is .fits (write_shape_catalog always writes
FITS); default extract/calibrate to the repo checkout and inject
PYTHONPATH={sp_validation_repo}/src on every SPV stage, so the branch code
(these fixes) wins over the lagging baked container.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D9atA9VPrg7VMC7oBArny9
im_init staged params.py by `cp {params.template}` where the template was a
snakemake `params:` value, not a tracked `input:`. Editing the template (e.g.
the ngmix-v2 grammar migration) therefore did NOT retrigger im_init, so run
dirs kept stale old-grammar params.py and extract died on
`no field of name NGMIX_Tpsf_NOSHEAR` -- only for sims whose run dir predated
the edit. Move template + mask_src to `input:` so a template change re-stages
into every run dir.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D9atA9VPrg7VMC7oBArny9
…tion)
The extracted ImageSimMBias._m_c_pair was UNPAIRED: it matched the +g and
-g sims each to the reference separately, meaned them independently, and
subtracted. Intrinsic shape (sigma_e ~ 0.3) never cancelled, so a single
tile gave sigma(m) ~ 1.5 -- unconstrained.
Restore the paired estimator the SKiLLS sims are built for: match the +g and
-g sims directly to each other (same galaxies, opposite input shear; verified
98.8% co-located to <0.05" with e-correlation 0.81), difference per object,
m = <(e_+ - e_-)/(2 g_in) - 1> , c = <(e_+ + e_-)/2> ,
and bootstrap the paired objects (one resample applied to both sims) so the
per-object cancellation carries into the error. Intrinsic shape cancels in m;
c is a sum so it stays shape-noise limited (as it must). This also restores
Martin's direct +g<->-g matching, which the extraction (10baa52) regressed,
and goes beyond it (his bootstrap still resampled the two means independently).
New test test_mbias_pool_cancels_shape_noise builds catalogues with realistic
intrinsic scatter and asserts sigma(m) sits far below the unpaired shape-noise
floor -- the property the pooling exists to deliver. Existing exact-recovery
test still passes to machine precision.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HmxPiKJEechqueT3YU1yrx
Fresh-eyes review notes on 4e38326: document that 1z2z is the unsheared reference (loaded for null-test diagnostics, not consumed by the +g/-g pool estimator) and that the RA/Dec match is nearest-neighbour, not a strict bijection -- effectively 1:1 on well-separated grid sims (~99% co-located to <0.05" on SKiLLS grid_1) but could dilute cancellation on denser fields. No behavioural change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HmxPiKJEechqueT3YU1yrx
|
Status update (PR description rewritten to match): the chain now runs end-to-end from raw SKiLLS images on a single consistent ngmix-v2 grammar vintage, and the m-bias estimator is fixed to a paired, shape-noise-cancelling form with a guard test. The gate then earned its keep: the first clean run returned m ≈ −1, and the diagnosis landed on two real bugs in the shared calibration path, now filed with full mechanism and evidence:
A multiplicative-only hand-diagnostic gives m₁ = +0.14 ± 0.10, m₂ = +0.27 ± 0.11 (grid_1, 1 tile), so the pipeline itself looks healthy. Once the calibration route is chosen, re-running — Fable, on behalf of Cail |
…mulations with constant shear
) Fabian's #227 verdict excludes shape weights from sim m-bias calibration (w_des is pathological on sims, N_eff ~1-5%; w_iv behaves but is also excluded). w_col: null in the workflow config now flows through the .smk untouched and lands here as unit weights. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UvQvyJTEwYcLCwDEgpWMJ6
… the branch layout Martin implemented the #226 fix (skip additive-bias subtraction for constant-shear sims) on his image_sims_val branch (e38ace9) against the old notebooks/ layout; #225 moved calibration to scripts/calibration/. This ports the identical semantics: get_calibrated_m_c gains additive_correction (default True, data path unchanged); the calibrate script reads it from the mask config's metacal: section; both im_sim mask configs set False. c/c_err still computed as diagnostics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UvQvyJTEwYcLCwDEgpWMJ6
) The w_des-weighted global R was measured on N_eff ~ 20-100 objects and differs from the unweighted mean by up to 17% (0.780 vs 0.669 on tile 233.293) — no weights anywhere in sim m-bias, per Fabian's #227 verdict. calibrate_comprehensive_cat reads the key fail-fast, so both im_sim mask configs now carry it explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UvQvyJTEwYcLCwDEgpWMJ6
…#225 branch His side trusted, ours vetted, new grammar wins: - image_sims.py: our paired pool estimator + paired bootstrap kept; his pair_match knob adopted (False = his independent-means estimator with independent bootstraps — the toggle switches estimator flavor, documented); his print_mean_ellipticities + catalog_name default adopted; grids_dir key. - compute_m_bias_image_sims.py: ours (workflow-integrated, convergence tracking); his diagnostics capability preserved via diagnostics_image_sims.py, taken wholesale. - catalog.py/io.py: his hdf5 write branch ported into our layout (+ the import os it needed and a duplicated match_catalogs_radec removed — both caught by the reproduction gate / pre-commit). - calibration.py: identical additive_correction semantics both sides; our signature (no shape_method). - mask configs: ours (new grammar, #226/#227 flags); his old-grammar versions preserved in his branch history. Verified: calibrate+mbias rerun on tile 233.293 reproduces the pre-merge m_bias_results.yaml BIT-EXACTLY (m1=+0.274362, m2=+0.231224, all 8 values). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UvQvyJTEwYcLCwDEgpWMJ6
🔴 ruff found lint / format issues@cailmdaley — these block the merge into
|
Closes #224
Closes #226
Big picture
To trust a shear catalogue, we measure the bias of the pipeline that produced it: run the full measurement chain on simulated images with known injected shear, and compare what comes out to what went in. The result is the multiplicative and additive bias (m, c) — the numbers that calibrate, or validate, the survey's shear.
Four roles, three owners:
basic_info.txt.The measurement is only as good as the agreement between these components about what was injected, how things are named, and what columns mean. So the design rule throughout: every such fact has exactly one home, and everything else reads it or fails loudly.
The chain
What's here
The campaign manifest. A rule at the head of the DAG parses
g_cosmicfrom every requested branch'sbasic_info.txt, derives the injected amplitude (cross-checked equal across the sheared branches, sign/axis checked against each branch's name), and writesmanifest.yamlinto the run root. The estimator takes the amplitude and branch map from the manifest alone; the manifest holds exactly the facts parsed frombasic_info.txt.One estimator.
ImageSimMBiaspair-matches the ±shear catalogues object-by-object so intrinsic shape noise cancels before averaging (a guard test locks the cancellation), and bootstraps the errors. Estimator variants are config points (pair_match,w_col,global_R_weight) of this one class. It consumes the calibrated shear columns, so the headline quantity is residual m: the bias remaining after the chain's own metacal calibration. Fiducial estimator config: pair-matched, unweighted (#227), with additive-bias subtraction disabled (#226 — on constant-shear sims the subtraction removes the signal itself).Fail-fast configuration. Science keys (
w_col,pair_match,match_radius_deg,n_bootstrap,bootstrap_seed,mask_config) are required — no defaults in code or rule files. Operational keys default visibly in the workflowconfig.yamland nowhere else. Run configs are schema-validated; unknown keys are errors. Tiles are given as atile_idslist. The image-sim mask config is an explicit overlay on the base mask, so the sims-vs-data differences are declared.One container. A single sp_validation image that can run ShapePipe — that's the one every rule uses.
One run command.
snakemake --profile candide …with a committed SLURM profile (partition, node excludes, per-rule resources,OMP_NUM_THREADS=1). Snakemake owns all scheduling: it fans out across branch × tile jobs, and ShapePipe's internal multiprocessing is simply each job's thread count; the whole chain runs MPI-free.Provenance. Beside each result: every resolved config value, the manifest hash, both repos' commits, the container tag. Derived tracking files are rebuilt each run.
Validation
The current reference measurement (one tile, fiducial campaign, fresh from raw images) is m1 = +0.0197 ± 0.0212, m2 = −0.0158 ± 0.0175, c1 = −0.0027 ± 0.0085, c2 = −0.0032 ± 0.0075 — consistent with zero at the percent level. The seeded bootstrap makes it reproducible, so it doubles as a regression check: refactors should leave it unchanged, and when a change moves it, the diff gets understood rather than waved through. The number itself will evolve as the analysis does. Synthetic-recovery tests (inject known m/c, assert recovery) cover the estimator itself.
Single-tile caveats, stated so the number isn't over-read: tile-to-tile variance is unmeasured at N=1; selection response is not yet accounted; the pair-matched estimator drops the few-percent tail of objects whose detection flips between branches.
Before merge
Out of scope
Sim-generator changes (tracked as MultiBand_ImSim issues); the multi-tile campaign and selection-response study; cosmology-validation workflow rules (untouched).
— Fable, on behalf of Cail