Skip to content

Smokescreen blinding: per-catalogue custody, blind drawn once, parts sealed at birth - #253

Draft
cailmdaley wants to merge 181 commits into
developfrom
feat/sacc-6-blinding
Draft

cailmdaley wants to merge 181 commits into
developfrom
feat/sacc-6-blinding

Conversation

@cailmdaley

@cailmdaley cailmdaley commented Jul 10, 2026 •

Copy link
Copy Markdown
Collaborator

This PR blinds the data vector with the UNIONS Smokescreen fork. For a blinded catalogue, ξ± and pseudo-Cℓ_EE are shifted in memory by t(hidden) − t(fiducial), a secret cosmology's theory minus the fiducial's, before they are first written, so true values never reach disk. It guards against accidents, not adversaries.

Goals: blinding declared once per catalogue and enforced everywhere; signal born shifted, and everything derived from it blinded; the inference's theory route; any new statistic blindable without a PR; a reveal that is a reviewed change. Non-goals: adversarial security, seed encryption, maps and peak counts.

Design

  • Custody: a catalogue's blinding status: none, mock, or a blind's name.
  • Blind: a named secret seed, from which the fork draws the hidden cosmology uniformly within S8 ±0.075, Ωm ±0.1 of the fiducial.
  • Commitment: a public hash of the blind's record, naming it without revealing it.
  • Stamp: the custody token (none, mock or <name>:<commitment>) in a SACC's metadata. A part is one measurement's SACC file.
cat_config: blind: y3 ─ launch ─► rule params carry "y3:<commitment>"
measure ─► sacc_io.save(custody=): shift, stamp ─► part
COSEBIs, pure-E/B, {version}.sacc ◄─ save(derived_from=parts): inherit the one stamp
sacc_io.load refuses unstamped files

Key decisions

  • Custody is one field per catalogue. Every cosmo_val/cat_config.yaml entry must declare blind: (_leak_corr/_seed<N> inherit it), so a new catalogue can't run until someone decides. At launch and in CosmologyValidation, entries reading one shear file must declare one blind (the repo config wins over a personal copy), and a blinded catalogue is shown only beside mocks or its own blind, since any other overlay displays the shift.
  • A blind is one file outside git, <paths.blinds>/<name>.blind.json: seed, envelope, fiducial, draw scheme. The seed is unencrypted; registry access is blind access. A registry inside a git worktree is refused unless it is a private repo's root.
  • Calculate, then save. A measurement function computes its signal, saves (or seals) it under the catalogue's custody in the same function, and returns the sealed part, so raw signal never leaves it; calculate_2pcf is the pattern. sacc_io.save is the only SACC writer: a birth is shifted if blinded, then stamped; a derivation inherits its inputs' one stamp and cannot add shiftable rows. Rule params carry the token, so a flip reruns exactly what it touches.
  • One table of standard estimators. blinding.STANDARD declares each estimator cosmo_val computes, once, with its rule: ξ± and Cℓ_EE shifted by the default theory; Cℓ_BB/EB unshifted; COSEBIs and pure-E/B derived from ξ±, so they inherit its shift; ρ/τ signal-free. The default theory is pyccl on cs_util.cosmo.get_cosmo's CAMB HMCode2020-feedback route, as in inference; remaining differences (Planck18 centre, N_eff, kmax, IA off) are second order in a shift.
  • Anything else is shifted by a theory you pass: sacc_io.save(s, path, custody=c, theory={data_type: f}), with f(params, s, rows) -> values and params a plain mapping, so an emulator works (it may also replace a standard type's default). Under a blind, a type neither standard nor given a function is refused: a new statistic fails closed and needs no registry edit.

Rejected: custody in two places, e.g. a list of what each blind covers (copies drift); an encrypted seed (unattended jobs need the key beside it); a bespoke blinding cosmology (a third fiducial).

Using it

spv-container exec python -m sp_validation.blinding init y3   # prints name and commitment only

Set blind: y3 on the entry (and every entry reading its file) in a reviewed PR, then launch. A new statistic, ΔΣ say, is a function that calculates, then saves:

def delta_sigma_theory(params, s, rows):              # CCL, an emulator, ...
    return my_delta_sigma(theory.cosmology(params), theory.tag(s, rows, "rp"))

def measure_delta_sigma(cv, ver, out):
    rp, ds = my_estimator(cv, ver)                    # raw signal, local to this function
    s = sacc_io.new_sacc(cv.sacc_nz(ver), cv.sacc_metadata(ver))
    for r, v in zip(rp, ds):
        s.add_data_point(DELTA_SIGMA, ("source_0", "source_0"), v, rp=r)
    return sacc_io.save(s, out, custody=cv.custody(ver),
                        theory={DELTA_SIGMA: delta_sigma_theory})   # shifted here

Reveal = flip + rerun: copy the blinded outputs aside, set blind: none in a reviewed PR, and rerun; nothing to decrypt or subtract.

What else changes for users

  • sacc_io.save takes custody= or derived_from=, not type=. load(allow_unblinded=) and the workflow's cosmo_val.type are gone.
  • calculate_2pcf returns the sealed ξ± part, which sacc_io.xi_correlation reads like a GGCorrelation; the ξ± .txt dumps are gone.
  • ⟨M_ap²⟩ is computed from the part (TreeCorr's calculateMapSq to 1e-12) and kept in cv.map2, not written.
  • Interactive jackknife pure-E/B propagates the part's covariance through the kernel rather than jackknifing the modes (variance ratio to TreeCorr's on SP_v1.4.6.3: median 0.97–1.14 for E/B, 1.48 ambiguous).
  • scripts/xip_xim.py refuses a blinded shear file.

Open questions for the collaboration

  1. Where does the blind live? A candide path (default /n17data/UNIONS/WL/blinds, not yet created) or a private repo such as UNIONS-WL/blinds.
  2. Who may draw and hold a blind?
  3. v1.6.x: every entry is none for now. Has v1.6.x already been seen unblinded? If so, blinding it protects little.
  4. Reveal criteria (PRD: SACC data format + Smokescreen blinding #241 §7).
  5. Envelope and fiducial: keep S8 ±0.075, Ωm ±0.1? Planck18 or the inference centre as fiducial?
  6. Maps and peak counts: can't be shifted. Do they wait for the reveal?
  7. Nuisance parameters: Smokescreen shifts S8 and Ωm but not nuisance parameters (e.g. galaxy bias once γt returns). We believe this is second order; worth checking.

Testing

  • Container suite (with slow): 341 passed, 2 xfailed (GLASS maps, unrelated; noisy pure-E/B, below).
  • Host workflow tests: 14 passed, 1 skipped (SLURM smoke): paper DAG dry runs, launches that must fail closed, a flip rerunning exactly its ξ parts, an apptainer toy run under a fresh blind.
  • Custom types: with a theory function, shifted and assemblable; without, refused.
  • B-modes under a blind (synthetic ξ± with SP_v1.4.6.3 shape noise, each envelope corner): COSEBIs B_n move by the transform's response to the shift, ≤ 0.034σ; pure-E/B ξ_B ≤ 0.005σ noise-free. On noisy input ξ_B moves 0.1–1.2σ in isolated bins (strict xfail): pure-E/B is not additive on noisy ξ±, blinded or not; root cause open.

Follow-ups

  • Smokescreen: move SACC row planning into the fork, so conceal becomes a thin call.
  • cs_util#80: get_cosmo drops falsy values (mnu=0 becomes 0.06).
  • Inference outputs carry the custody token once it moves to SACC.

Issues

Closes #252.

Part of #280: the default theory's settings were matched by reading cosmosis_pipeline_A_*.ini; the numerical CosmoSIS comparison is not done.

Issue texts to update: #252 (no encryption, escrow or unblind; one theory per data type); #241 §4 (draw in S8/Ωm; mock custody replaces type/concealed); #241 §5 (the commitment hashes the whole record; reveal is a rerun); #241 §6 (cross-check in #280).

— Claude (Opus) on behalf of Cail.

🤖 Generated with Claude Code

@cailmdaley cailmdaley changed the title Smokescreen blinding: seeded data-vector blind, hash-commitment custody, blinded-COSEBIs derivation (PR 6) Smokescreen-fork blinding: seeded master-SACC blind, hash-commitment custody, CAMB↔CCL cross-check (PR 6) Jul 10, 2026
@cailmdaley
cailmdaley force-pushed the feat/sacc-6-blinding branch from 971767b to 11b8895 Compare July 10, 2026 22:52
@cailmdaley
cailmdaley changed the base branch from feat/sacc-5-firecrown-likelihood to feat/sacc-2-sacc-io July 10, 2026 22:52
@cailmdaley cailmdaley changed the title Smokescreen-fork blinding: seeded master-SACC blind, hash-commitment custody, CAMB↔CCL cross-check (PR 6) Smokescreen-fork blinding: per-part-at-birth blind, hash-commitment custody, CAMB↔CCL cross-check (PR 6) Jul 11, 2026
cailmdaley added a commit that referenced this pull request Jul 11, 2026
…ll image from lock (#266)

* felt: fork-implementation — #243 green, #253 rework under fix; fork PRs reviewed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf

* felt: fork-implementation — constitution absorbs #241 re-rulings (one-file layout, per-part blinding at birth)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf

* deps: declare cosmo_numba + numba, adopt committed uv.lock, install image from lock

Make sp_validation's environment reproducible so an image build can never
re-resolve numpy past numba's ceiling — the drift that silently upgraded numpy
to 2.5 and broke numba/ngmix.

- Declare cosmo-numba (aguinot/cosmo-numba@main; not on PyPI) — the numba
  B-mode kernels b_modes.py imports. main carries numpy-2 FFT support via its
  rocket-fft dep and declares its own deps, so numba's numpy window reaches the
  resolver. Also pin numba directly: the one load-bearing constraint, made
  visible and resilient to cosmo-numba's dep metadata (which has emptied out
  between refs).
- Declare the other imported-but-undeclared deps the audit found: matplotlib,
  pandas, pyyaml (core, src/); fitsio (glass extra); a new `workflow` extra for
  snakemake + mpi4py. cv_runner and unions_wl left undeclared (no resolvable
  source) with a NOTE.
- requires-python and ruff target -> 3.12 (the container's Python; cosmo-numba's
  floor).
- Commit uv.lock (un-ignored) as the SSOT; scope it to Linux via [tool.uv].
  numpy resolves to 2.4.6, inside numba 0.66's <2.5 window.
- Dockerfile installs from the lock: `uv sync --frozen --inexact` into the base
  image's /app/.venv (--inexact keeps the ShapePipe stack). Drops the ad-hoc
  snakemake layer and the cs_util `--upgrade` workaround — the lock pins
  cs_util 0.2.2 (with cs_util.size), decoupling us from the base image's cs_util.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vtw1qcgTrQ6YuMvxwYzNup

* felt: uv-lock-cosmo-numba — @main resolution, install model, cosmo-numba gotcha finding

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vtw1qcgTrQ6YuMvxwYzNup

* test: don't load base image's stale pytest-pydocstyle/pycodestyle plugins

uv sync installs a newer pytest than the base ShapePipe image's
pytest-pydocstyle/pytest-pycodestyle (>=2.4) expect; their pytest_collect_file
hooks use the removed `path` arg and crash collection. sp_validation lints with
ruff, so disable both via addopts (`-p no:`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vtw1qcgTrQ6YuMvxwYzNup

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
cailmdaley and others added 7 commits July 16, 2026 02:17
Add sp_validation.sacc_io: the writer/reader layer for the two-file SACC
layout that becomes the package's standard data-product format.

  {version}.sacc       analysis vector — NZ tracers, coarse xi+/-, pseudo-Cl
                       (EE/BB/EB) with a shared BandpowerWindow, COSEBIs,
                       pure E/B, rho/tau PSF diagnostics; one FullCovariance
                       assembled block-diagonally (zero cross-blocks).
  {version}_xi_fine    COSEBIs/pure-EB integration input — same NZ tracers,
                       fine-grid xi+/-, DiagonalCovariance from TreeCorr
                       varxip/varxim.

Covariance order is point-insertion order (SACC preserves it bitwise
through FITS). Writers insert in the canonical order — xi+ then xi-, Cl
(ee, bb, eb), COSEBIs (all En then all Bn), pure E/B in _EB_KEYS order
(xip_E, xim_E, xip_B, xim_B, xip_amb, xim_amb, matching
b_modes.calculate_eb_statistics), rho, then tau — but readers never assume
global order: every getter resolves indices through s.indices(dtype,
tracers, **tags). assemble_covariance validates that blocks are contiguous,
ascending and tile the data vector exactly, failing loud otherwise.

Custom data types (pure E/B, rho, tau) all parse under
sacc.parse_data_type_name. Tag filters are plain kwargs; the tags={...}
form silently selects nothing and is never used.

Test suite (test_sacc_io.py, all synthetic and fast): per-writer
round-trips (arrays/tags/windows/NZ bitwise), covariance block alignment
and zero cross-blocks, assemble_covariance failure modes, DiagonalCovariance
round-trip, extract() sub-covariance alignment, a tomographic multi-pair
case, reader/writer mirroring on a mixed file, and the end-to-end two-file
layout. 20 passed.

Co-Authored-By: Claude Opus <noreply@anthropic.com>
Fresh-eyes review caught a correctness bug: readers re-sorted selections by
theta/ell/n, but covariance blocks and bandpower windows stay in insertion
order. On a non-ascending grid the reader output silently desynchronised
from its covariance, and get_pseudo_cl returned sorted cl arrays against
unsorted window columns — internally inconsistent within one return tuple.

Fix by construction, not by sort:
  - Writers validate their grids. add_xi/add_pure_eb/add_rho/add_tau require
    strictly ascending theta; add_pseudo_cl requires strictly ascending
    ell_eff (add_cosebis is inherently safe — it enumerates the mode index).
    Out-of-order grids raise a loud ValueError naming the argument.
  - Readers drop the sort entirely and return in s.indices (insertion) order,
    so every getter is covariance- and window-aligned for ANY file, and
    ascending for canonical files. The _sorted_* helpers are replaced by
    plain insertion-order accessors (_mean/_tag).

Also:
  - _pair normalises (i, j) -> sorted, so get_xi(s, (1, 0)) addresses the same
    symmetric shear-shear pair as (0, 1) instead of a silent empty read.
  - Module docstring documents the tomographic ξ covariance ordering:
    insertion is pair-major ([pair0 xip; pair0 xim; pair1 xip; …]), supplied
    to assemble_covariance as one contiguous block matching add_xi call order;
    type-major converters (DES 2pt-FITS) permute explicitly via s.indices.
  - extract() docstring states tracers takes SACC names, not integer bins.

New tests (26 total, was 20): writers reject non-ascending theta/ell;
(1, 0) == (0, 1) round-trip; a 3-pair tomographic ξ covariance assembled as
one contiguous pair-major block with per-pair sub-blocks recovered via
extract(); get_pseudo_cl window column j <-> ell_eff[j] via window_ind tags.

Co-Authored-By: Claude Opus <noreply@anthropic.com>
The two-file split (analysis + {version}_xi_fine.sacc) was premised on a
10000-bin fine grid; the production operating point (Paper II B-modes) is
1000 bins, where a dense per-pair fine covariance block is ~32 MB and the
CosmoCov integration-binning covariance — which feeds pure-E/B and COSEBIs
error propagation — has a natural home as a BlockDiagonal block alongside
the analysis blocks. Layout test replaced with the one-file end-to-end
case (dense fine block, extract() sub-covariance alignment, zero
cross-blocks) plus a varxi-diagonal fallback test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019R5eiy11Lihkgn4MKufXSp
…date_statistic

PRD #241 §4 (Mocks vs data): save() requires type='data'|'mock' and stamps
it into metadata; load() raises on type='data' files lacking the
concealed=True blinding stamp, so skipping the blind can never silently
expose real data. Mocks load freely. allow_unblinded=True is the loud
escape hatch reserved for the blinding/unblinding tooling.

merge() wraps sacc.concatenate_data_sets thinly: per-statistic files
combine in order, shared tracers stored once, covariance block-diagonal
(library-enforced all-or-none), metadata union with loud conflicts.
update_statistic() is the value-only merge-back for the
extract -> conceal -> merge blinding flow: matches each sub point by
(data_type, tracers, tags) and overwrites the value, leaving order and
covariance untouched.

Tests: all four data/mock x concealed/unconcealed quadrants, the escape
hatch, stamp validation, merge (points, covariance, metadata conflict,
mixed-covariance failure) and update_statistic (values-only, unique-match).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QWF72ofwJh6ekgnCt9Xx6C
…comments

- grid tag values: coarse -> "reporting", fine -> "integration" (descriptive,
  not relative); tag kept because both grids share data type and tracer pair,
  so the tag is the sole disambiguator. Swept module docstrings and tests.
- Module docstring now spells out why insertion order is load-bearing:
  covariance row/column i refers to the i-th inserted data point.
- new_sacc: comment that psf_stars rides in the same tracer list as the NZ
  tracers only because a Sacc has one flat tracer namespace (bookkeeping,
  not physics).
- source_name/_pair helpers kept (4 and 8 call sites) with one-line
  justifications of the conventions they centralize.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QWF72ofwJh6ekgnCt9Xx6C
- Factor the theta-tagged insertion loop into _add_theta_series (add_rho,
  add_tau, add_pure_eb); add_pure_eb zips PURE_TYPES.values() against its
  signature order, and PURE_KEYS is derived from PURE_TYPES instead of
  restating it.
- Factor the (theta, plus, minus) read pattern into _get_pm (get_xi,
  get_rho, get_tau).
- add_xi hoists the optional-tag None-filtering out of the point loop;
  extract and merge lose their throwaway mutable dicts; get_cosebis
  builds its scale-cut tags in one expression.
- Tests: shared _add_xi default-ξ builder and _xi_block/_cl_block/
  _cosebi_block canonical index-block helpers replace ~60 lines of
  copy-pasted setup; test_readers_on_mixed_file builds on
  _multi_statistic_sacc.

Behaviour unchanged; 41/41 tests green in the container.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QWF72ofwJh6ekgnCt9Xx6C
@cailmdaley
cailmdaley force-pushed the feat/sacc-2-sacc-io branch from 55cb97d to 4489dd4 Compare July 16, 2026 00:39
cailmdaley and others added 5 commits July 16, 2026 11:05
Adversarial review (pass 2) findings:

- Sacc.indices returns an EMPTY array (warning only) on an unmatched
  selection; every reader, extract(), and covariance-block selector now
  funnels through a shared _indices guard that raises instead — a typo'd
  tag or non-bitwise-identical float scale cut can no longer propagate
  empty arrays downstream (assemble_covariance previously died with an
  opaque IndexError on the same path).
- get_cosebis(scale_cut=None) on a file carrying several scale cuts
  silently concatenated them; now raises and asks for an explicit cut.
- update_statistic let two sub points claim the same target point
  (last-write-wins); now raises.
- merge: the library keeps the FIRST input's tracer on a name clash with
  no equality check; shared tracers are now verified identical (z, nz)
  across inputs before concatenation.

7 regression tests; 48 total.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzUt7VbtXwr2SCHUdiQTyt
…abulary

Rebuild the per-part-at-birth blinding stack on top of feat/sacc-2-sacc-io.
sacc_io.py is now PR-2's canonical module verbatim + a single appended
gather(): the terminal assembly delegates its tracer/point/covariance
assembly to PR-2's merge() and adds only the blind-custody call
(assert_consistent_blind) and shared-stamp write.

The fail-closed load gate lives in sacc_io.load() per the PRD ("sacc_io fails
closed on load"); the blinding tooling uses allow_unblinded=True explicitly
where it must read a not-yet-blinded real vector (blind_part). save() now
carries the required type= (inherited from each part's provenance). Grid
vocabulary swept coarse->reporting, fine->integration across blinding.py,
blinding_theory.py, b_modes.py, blind_data_vector.py, and the blinding tests.

_extract_block/_set_values stay index-based (a code comment says why):
Smokescreen's ConcealDataVector aligns theory_fn output to the sub-SACC mean
by row position, so the block must be carved/written by contiguous integer
index, not by PR-2's tag-matching extract()/update_statistic().

Escrow/custody/CAMB<->CCL machinery is preserved exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzUt7VbtXwr2SCHUdiQTyt
…tion

The branch previously carried a stale vendored copy of sacc_io.py /
test_sacc_io.py from before PR2's review rounds. Rebuilt directly on
feat/sacc-2-sacc-io (8bd3817) so the canonical module is inherited,
bringing over the cosmo_val + Snakemake born-as-SACC migration work
from the old tip unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzUt7VbtXwr2SCHUdiQTyt
…nfig

Three integration-drift fixes surfaced by the reconciled base:

- test_ac6_ac8 / test_ac8_dotted: the end-to-end fixtures now pass
  type="mock" to sio.save (PR-2 requires the provenance stamp); the parts
  are mocks, blind_part re-saves inheriting that type.
- test_ac13: the CosmoSIS halofit config moved to
  cosmo_inference/cosmosis_config/templates/ on develop; point the AC13
  assertion at the new path (token unchanged: mead2020_feedback).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzUt7VbtXwr2SCHUdiQTyt
…rename

Sweep the migration code onto PR2's canonical vocabulary and contracts:

- grid='coarse'/'fine' -> 'reporting'/'integration' everywhere (writer
  calls, readers, tests), including the internal DAG intermediates:
  {version}_xi_coarse* -> _xi_reporting*, _xi_fine -> _xi_integration,
  the xi_coarse/xi_fine Snakemake output keys, CANONICAL part names,
  cv_xi_reporting_sacc, write_xi_integration_sacc.
- save(s, path) -> save(s, path, type=...): 'data' at every production
  writer (the cosmo_val pipeline measures the real UNIONS catalogues;
  GLASS mocks do not flow through these writers), 'mock' for synthetic
  test fixtures. assemble_sacc propagates its parts' type stamp rather
  than hardcoding, so mock parts assemble into a mock analysis file.
- load(path) -> fail-closed load: pipeline-internal readbacks of
  freshly written pre-blind data parts pass allow_unblinded=True
  (blinding is a downstream Smokescreen step); mock fixtures load freely.

Readers raising on unmatched selections needed no call-site changes:
every get_* reads a statistic guaranteed present in the file just
written or assembled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzUt7VbtXwr2SCHUdiQTyt
@cailmdaley
cailmdaley force-pushed the feat/sacc-6-blinding branch from 4b058f7 to c09b063 Compare July 16, 2026 09:55
cailmdaley and others added 6 commits July 18, 2026 11:46
Consistency follows tagging semantics: the grid tag declares which
binning a set of points lives on, so all same-length theta arrays under
one tag value must be bitwise identical (sacc never validates angles,
and grids diverging at floating-point level choke CosmoSIS downstream).
Different lengths within a tag group pass (scale-cut subsets); grids
under different tag values are unconstrained (reporting vs integration
differ by design).

The rho/tau/pure-EB writers now tag their points grid="reporting" by
default (overridable via grid=), so they join xi's consistency group
and no untagged group appears in our own files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MN9VazXKHUHQg16kiG7Ufk
…seudo-Cl grid="reporting"

The theta consistency guard generalizes to both angular domains: theta
and ell points are grouped separately by grid tag value, and within a
tag value all same-length grids must be bitwise identical. Two ell
series sharing a grid must also carry equal bandpower windows (window
ells and weight matrix); series without windows skip that check.

add_pseudo_cl now stamps grid="reporting" on every point by default
(overridable), joining the merge guard's consistency groups; since
sacc's add_ell_cl accepts no extra tags, its per-point insertion
(ell + shared window + window_ind) is inlined.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MN9VazXKHUHQg16kiG7Ufk
assemble_covariance now passes sacc.add_covariance a list of per-block
matrices instead of a dense zero-filled N×N array. sacc.BaseCovariance.make
turns a list into a BlockDiagonalCovariance (one FITS table per block, Σ
block² on disk), so cross-blocks are zero and implicit rather than
materialized. Validation (contiguous/ascending indices, no gap/overlap,
square blocks matching their index span) is unchanged.

Every existing consumer already read through the polymorphic .dense
property, so only the type assertions needed updating (FullCovariance ->
BlockDiagonalCovariance). Added a test that merging two files that already
carry a BlockDiagonalCovariance (assembled via assemble_covariance) stays
block-diagonal through merge and a save/load round-trip. Updated the module
docstring's storage-cost discussion accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pure_eb.py's calculate_pure_eb/plot_pure_eb defaulted nbins_int=100;
cosebis.py's calculate_cosebis already defaulted to 1000, and every
production config (papers/bmodes, papers/cosmo_val fiducial/pure_eb
blocks) already overrides to 1000. This aligns the function-signature
default with what every caller actually uses; config plumbing is
untouched, so any explicit override still wins.

The papers/cosmo_val/config/config.yaml cosebis.nbins_int (currently
2000, production numerics) is deliberately left unchanged — see report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Writers no longer force components an analysis may not have computed:
add_pseudo_cl's BB/EB, add_cosebis's Bn, and add_pure_eb's B/ambiguous
blocks now default to None and are simply not written when omitted
(add_pure_eb still requires each +/- pair together). Structural
arguments (grids, tracer/bin identifiers, the value for a component
you ARE adding) remain required with no default.

Composite readers (get_pseudo_cl, get_cosebis, get_pure_eb) return
None / omit the key for an absent optional component instead of
raising, via a new _mean_optional helper; a selection naming a missing
component explicitly (s.indices, _mean, extract) still fails loud, per
the existing empty-selection guard. merge() and assemble_covariance
work unchanged on files with only a subset of components.

Documents the optionality contract in the module docstring, and adds
partial-file round-trip, merge, and explicit-selection-fails-loud
tests.

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

cosebis.nbins_int was 2000; every other integration-grid entry in this
config (pure_eb, the fiducial block) is already 1000, and cosebis.py's
own function defaults are 1000. Unify.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base automatically changed from feat/sacc-2-sacc-io to develop July 21, 2026 12:24
cailmdaley and others added 2 commits July 21, 2026 14:28
Fold the integration-grid ξ± into the single terminal {version}.sacc, and
make part loading fail closed on unblinded real data.

Integration ξ± (grid='integration') is no longer its own terminal product.
The xi_highres part is now gathered by rule assemble_sacc into {version}.sacc
as tagged points, next to the reporting ξ± block. It is fiducial-only (the
10k-bin MPI run emits only the fiducial part), so it joins the fiducial
version's terminal file alone. assemble_sacc.py adds xi_integration to
CANONICAL; its own DiagonalCovariance passes straight through.

assemble_sacc.py no longer loads every part with allow_unblinded=True. The
run type (data|mock, from config, default data) gates it: mock runs load
freely, data runs fail closed unless a part carries the concealed=True stamp.
This is the seam for PR #253's blind-at-birth — a concealed data part then
assembles with allow_unblinded=False untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaL7prmKUHwJQcDyW3LoxD
Like SP_v1.4.11.3, its covariance was built from nz_SP_v1.4.6_A.txt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJSfyQjoQXjPKsEfkGhZLj
cailmdaley and others added 2 commits September 28, 2026 05:41
…o feat/sacc-6-blinding

# Conflicts:
#	papers/bmodes/scripts/gather_pure_eb_chunks.py
#	papers/bmodes/scripts/run_pure_eb_sweep.sh
#	src/sp_validation/tests/test_cosmo_val.py
#	workflow/common.py
CCL's total Omega_m is then exactly the blind axis, and a test says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJSfyQjoQXjPKsEfkGhZLj
@cailmdaley
cailmdaley changed the base branch from develop to refactor/scrub-legacy-blind September 28, 2026 03:47
cailmdaley and others added 12 commits September 28, 2026 05:52
A script: job unpickles the host's snakemake object with whichever snakemake
it imports first, and the image's site-packages precede the host's appended
sys.path. The image carried its own (the workflow extra, and the base image's
jupyter extra), so the host had to match its version exactly. The Dockerfile
now uninstalls every snakemake* package after the sync and the workflow extra
drops snakemake; the job then reads the pickle with the package that wrote it.

The launch check keeps only the Python minor (check_host_python): the host's
snakemake and its compiled dependencies load into the image's interpreter.
The README install line, CI's DAG job and the test harness no longer pin a
Snakemake version. The container smoke job now reports which snakemake
unpickled its object and asserts it is the host's version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJSfyQjoQXjPKsEfkGhZLj
Drops the candide LD_LIBRARY_PATH to /softs/openmpi: /softs is not bound, so
the path does not exist inside the container, and no containerized rule uses
MPI. Drops the default profile's --bind /home (apptainer mounts $HOME already).
States the real reasons for the rest: rerun-triggers leaves out software-env
because it hashes the per-person image path; shared-fs-usage leaves out
source-cache because jobs would be handed the launch's node-local cache path;
slurm_account because the executor's guess fails on candide; retries and
kept logs for fan-outs and their printed output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJSfyQjoQXjPKsEfkGhZLj
Takes the baseline's Python-only host check: the launch test covers a Python
minor and a registry tag, no Snakemake version. Restores the baseline's
launch-environment tests (image under ~/.cache, no launch cache, the candide
profile's job bound, image-sims check), which are not about blinding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJSfyQjoQXjPKsEfkGhZLj
It guards the baseline's image resolution, not blinding, and runs on any host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJSfyQjoQXjPKsEfkGhZLj
Apptainer binds $HOME, so a job read the launcher's own matplotlibrc, and a
LaTeX preamble there the image cannot typeset stopped every figure rule.
common.py points each job's MATPLOTLIBRC (through APPTAINERENV_, past
--cleanenv) at an empty workflow/matplotlibrc. The container smoke job reports
the matplotlibrc it would read and the test asserts it is the repo's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJSfyQjoQXjPKsEfkGhZLj
workflow.common runs in the host Snakemake with no sp_validation installed,
and loads container.py by path; container.py imports only the standard
library, since it also runs as the spv-container CLI before any image exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJSfyQjoQXjPKsEfkGhZLj
…ainer from mounting it

A job's home is its working directory, so a checkout or output tree under
the launching user's home was invisible to the job: the toy run's jobs
imported the image's sp_validation instead of the checkout's src/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJSfyQjoQXjPKsEfkGhZLj
@cailmdaley
cailmdaley changed the base branch from refactor/scrub-legacy-blind to develop September 28, 2026 21:46
cailmdaley and others added 10 commits September 28, 2026 23:52
The branch's own change (ddeb904..e34b46a) is laid onto develop, which
now carries #357, #358 and #359 as squashes, and each cut there holds:

* workflow/common.py: no image_python/check_host_python and no
  APPTAINERENV_MATPLOTLIBRC. base_version is custody's base catalogue;
  variants share their base's footprint and plotting style.
* workflow/README.md: develop's shorter output-roots paragraph, plus how a
  collaborator names their own COSMO_INFERENCE and how patch centres are drawn.
* workflow/tests: no fake image and no container= launch argument; the
  image-Python, image-under-home, profile-bounds and image-sims launch tests
  stay cut. test_one_integration_grid stays and reads the forced listing; the
  toy keeps its covariances map for it. The candide paper dry-runs target
  bmodes' `paper` and write into a tree of their own with stand-in centres.
  The candide toy run no longer plants a user matplotlibrc.
* test_cosmo_val: the pure-E/B test checks the jackknife against TreeCorr's,
  without a transform count.
* papers/bmodes: the ξ± parts replace the text dumps in rules/figures.smk
  and bb_covariance_nz_independence.py; the deleted sweep scripts and
  cosebis_binning_comparison stay deleted.
* No CONTRACTS files: the blind registry's layout lives in blinding.py's
  docstring, and custody.py's says it is stdlib-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_calculate_2pcf_does_not_depend_on_thread_count guards the min_top pin in
treecorr_config: calculate_2pcf's ξ± part, measured on 4 and on 48 threads from
fresh Catalogs split at the same persisted patch centres, must agree below
1e-6σ at production binning. Without the pin it moves by 0.038σ.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
calculate_2pcf splits at the output tree's {ver}_patches_npatch=N.dat and
writes it when missing, as develop does, now through a temporary file and
os.replace so a concurrent reader never sees a torn file. The hand-drawn
per-base centres go: patch_centers.py, write_patch_centers, the
patch_centers_sha256 part key, rule xi's patches input (common.patches_path
and patches_input), run_2pcf's patch_centers, and their README section, tests
and toy-run step. So do common._plain and the affinity num_threads default.
All three live on feat/jackknife-patch-centres, which seeds the k-means so a
fresh tree, a racing job and a variant split alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tside git

Every cat_config entry declares `blind: none | mock | <name>`, and a missing
declaration is refused. Entries reading one shear file declare one blind, with
the repository config authoritative for the files it names, and a version set
may show a blinded catalogue only beside mocks and its own blind (checked at
launch and in CosmologyValidation). The registry is `paths.blinds`, refused
inside a git worktree unless it is that worktree's root; `*.blind.json` is
gitignored. A blind is `<name>.blind.json`: seed, envelope, fiducial and draw
scheme, written once at 0440, with a commitment over the whole canonical
record. Jobs take custody from their params token (`CosmologyValidation(
custody=)`, `assemble_sacc`), and `open_blind` checks the record against the
token's commitment and the fork's draw scheme.

`Blind` holds a name and a path; `_hidden()` returns a mapping whose repr is
`<hidden cosmology>`, and a theory failure at the hidden point is reported by
exception type alone. `conceal` refuses a zero or non-finite shift.

Gone: `bases`/`share`/`base:` and the twin checks, Fernet and the key,
BlindingConfig and its digest, init staging, `confirm`, `verify`, `reveal`,
`audit`, the `cosmo_val.type` refusal and the `cryptography` dependency.
`xip_xim.py` refuses a blinded shear file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`theory.py` maps each SACC data type to f(params, s, rows): ξ±, Cℓ_EE and
γt default to CCL through cs_util.cosmo.get_cosmo on the CAMB
HMCode2020-feedback route the CosmoSIS inference runs, and `predict` groups
rows by function, tracer pair and window. `conceal` shifts
`sacc_io.shiftable` rows by predict(hidden) − predict(fiducial), refuses a
shiftable type without a theory and a (type, pair) left unmoved, and
silences the theory's output at the hidden point.

γt joins SHIFTABLE and γ× UNSHIFTED; γt around stars or randoms is a null
and stays unshifted. Source tracers carry quantity galaxy_shear, and
`add_lens`/`add_gamma_t` write lens samples and γt. The fiducial is
cs_util's Planck18 with logT_AGN 7.5, no IA and unit lens bias.
`blinding_theory.py` and its bespoke cosmology are gone; tests shift with an
analytic toy theory, and one slow test runs the CCL defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_blinding_bmodes seals the default theory's ξ± on the production grids,
noise-free and with one shape-noise draw, under a blind at each envelope
corner. COSEBIs B_n move by exactly the transform's response to the E-mode
shift, under a tenth of σ. Pure-E/B ξ_B does the same on noise-free input.
On noisy input it is a strict xfail: cosmo_numba's adaptive quadrature does
not converge on a noisy 1000-bin ξ±, so the kernel is not additive there.

The pure-E/B transform pin on committed ξ± is back. test_dag checks that
flipping a catalogue's declaration reruns its parts, as a params change. The
toy run launches once under a blind drawn up front and checks its parts'
stamps. The secrecy test also looks for Ω_c, and at logs, warnings and
str/format. The README custody section is shorter and states what a blind
does to each B-mode statistic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GJJyGbKSuEjjX9m2zdv6yi
…ests

- CosmologyValidation refuses a custody token whose blind differs from the
  catalogue config's declaration.
- A SACC's custody stamp is its token, under one metadata key; the 2pt-FITS
  converter reads only stamped SACCs.
- Default theory: CCL's ξ± Hankel transform extrapolates to ℓ = 10⁷; γt
  takes each lens sample's bias from its tracer (add_lens(bias=)), not the
  blind's fiducial; a map-based pseudo-Cℓ window includes pw²(ℓ).
- Tests: explicit seal/derivation cases replace hypothesis (dropped from the
  test extra); pure-E/B additivity asserted at the quadrature floor, the
  noisy xfail states the measured 0.4σ; ⟨M_ap²⟩ pinned to TreeCorr; a
  token round-trip replaces the host/job test; develop's assemble and
  unnamed-output DAG tests restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…xel-window's

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…custody

The scaffolding removal, the private COSMO_INFERENCE paragraph and the
non-custody docstrings are chore/cosmo-val-cleanup's; run_rho_tau passes its
rule's custody token and nothing else changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y you pass

blinding.STANDARD declares each estimator cosmo_val computes and its rule
once: ξ± and Cℓ_EE shifted by the default PyCCL theory, Cℓ_BB/EB unshifted,
COSEBIs and pure-E/B derived from ξ±, ρ/τ signal-free. Any other data type
is shifted when its birth passes a function,
sacc_io.save(s, path, custody=c, theory={data_type: f}), and is refused
under a blind without one; its sealed rows pass later derivations as copies.

γt leaves until cosmo_val calculates it: GAMMA_T/GAMMA_X, add_lens,
add_gamma_t, the lens bias, source_lens and the null-lens exemption go, with
their tests. The secrecy test goes; Blind still holds only a name and path.
The README and CLAUDE.md state the calculate-then-save rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GJJyGbKSuEjjX9m2zdv6yi

This branch has not been deployed

No deployments
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.

Smokescreen blinding wiring (fork protocol, three theory backends, custody)

1 participant