From b30d6a9ac807148fbce23692be891a0352451662 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 24 Oct 2026 02:09:04 +0200 Subject: [PATCH 01/10] Refuse blinded tomography at direct pseudo-Cl producers --- src/sp_validation/cosmo_val/pseudo_cl.py | 4 ++++ src/sp_validation/tests/test_cosmo_val.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/sp_validation/cosmo_val/pseudo_cl.py b/src/sp_validation/cosmo_val/pseudo_cl.py index a728d3eb..361369ac 100644 --- a/src/sp_validation/cosmo_val/pseudo_cl.py +++ b/src/sp_validation/cosmo_val/pseudo_cl.py @@ -213,6 +213,8 @@ def calculate_pseudo_cl(self, compute_tomography=True, out_path=None): self.print_done("Done pseudo-Cl's") def calculate_pseudo_cl_map(self, ver, nside, out_path, tomo_bin_a, tomo_bin_b): + if (tomo_bin_a, tomo_bin_b) != ("all", "all"): + self._refuse_blinded_tomography(ver) assert (tomo_bin_a == "all" and tomo_bin_b == "all") or ( isinstance(tomo_bin_a, (int, np.integer)) and isinstance(tomo_bin_b, (int, np.integer)) @@ -312,6 +314,8 @@ def calculate_pseudo_cl_map(self, ver, nside, out_path, tomo_bin_a, tomo_bin_b): ) def calculate_pseudo_cl_catalog(self, ver, out_path, tomo_bin_a, tomo_bin_b): + if (tomo_bin_a, tomo_bin_b) != ("all", "all"): + self._refuse_blinded_tomography(ver) assert (tomo_bin_a == "all" and tomo_bin_b == "all") or ( isinstance(tomo_bin_a, (int, np.integer)) and isinstance(tomo_bin_b, (int, np.integer)) diff --git a/src/sp_validation/tests/test_cosmo_val.py b/src/sp_validation/tests/test_cosmo_val.py index 25cf0865..1eab2a1e 100644 --- a/src/sp_validation/tests/test_cosmo_val.py +++ b/src/sp_validation/tests/test_cosmo_val.py @@ -649,6 +649,14 @@ def test_a_blinded_catalogue_refuses_tomographic_signal(self, tmp_path): cv.calculate_2pcf_version(version, compute_tomography=True) with pytest.raises(blinding.BlindingError, match="tomographic"): cv.calculate_pseudo_cl(compute_tomography=True) + # Direct producers must refuse before opening even a missing catalogue. + cv.cc[version]["shear"]["path"] = str(tmp_path / "missing.fits") + out = tmp_path / "tomographic.fits" + with pytest.raises(blinding.BlindingError, match="tomographic"): + cv.calculate_pseudo_cl_map(version, 16, out, 1, 2) + with pytest.raises(blinding.BlindingError, match="tomographic"): + cv.calculate_pseudo_cl_catalog(version, out, 1, 2) + assert not out.exists() def test_treecorr_runs_on_the_cpus_the_process_holds(self, tmp_path): """By default TreeCorr takes the process's CPU affinity, not the node's count.""" From f25a3dd38fdd52a980545e8558dabcfdf2170a63 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 24 Oct 2026 02:14:43 +0200 Subject: [PATCH 02/10] Check catalogue blind stamps before consuming SACC parts --- src/sp_validation/cosmo_val/real_space.py | 1 + src/sp_validation/sacc_io.py | 16 ++-- .../tests/test_blind_consumers.py | 73 +++++++++++++++++++ src/sp_validation/tests/test_cosmo_val.py | 23 ++++++ workflow/rules/cosmo_val.smk | 4 + workflow/scripts/assemble_sacc.py | 4 +- workflow/scripts/cv_cosebis.py | 1 + workflow/scripts/cv_plot_pseudo_cl.py | 1 + workflow/scripts/cv_pure_eb.py | 1 + workflow/scripts/cv_runner.py | 4 +- workflow/scripts/cv_summarize_bmodes.py | 1 + 11 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 src/sp_validation/tests/test_blind_consumers.py diff --git a/src/sp_validation/cosmo_val/real_space.py b/src/sp_validation/cosmo_val/real_space.py index 8678b828..a486e8b9 100644 --- a/src/sp_validation/cosmo_val/real_space.py +++ b/src/sp_validation/cosmo_val/real_space.py @@ -81,6 +81,7 @@ def calculate_2pcf_version( ggs = {f"tomo_bin_{b1}_tomo_bin_{b2}": None for b1, b2 in tomo_bin_pairs} held = self.xi_parts.get((ver, grid)) if reuse else None if ALL_PAIR in ggs and held is not None: + sacc_io.expect(held, blind) ggs[ALL_PAIR] = sacc_io.xi_correlation(held) to_compute = [ (b1, b2) diff --git a/src/sp_validation/sacc_io.py b/src/sp_validation/sacc_io.py index 2c2e7919..7817ef78 100644 --- a/src/sp_validation/sacc_io.py +++ b/src/sp_validation/sacc_io.py @@ -983,6 +983,15 @@ def stamp(s): return s.metadata.get(STAMP_KEY, "none") +def expect(part, blind): + """Refuse a part from a different blind before consuming its signal.""" + name = blind if isinstance(blind, str) else blind.name + if stamp(part) != name: + raise ValueError( + f"part is stamped {stamp(part)}, but the catalogue is declared under {name}" + ) + + def seal(s, blind, theory=None): """What :func:`save` writes for a birth, kept in memory: a copy of ``s`` concealed under ``blind`` with ``theory`` and stamped with its name.""" @@ -1023,13 +1032,10 @@ def save(s, path, *, blind=None, theory=None, derived_from=None): if len(stamps) != 1: raise ValueError(f"input parts carry different blinds: {sorted(stamps)}") (name,) = stamps - if blind is not None and blind.name != name: - raise ValueError( - f"parts are stamped {name}, but the catalogue is declared under " - f"{blind.name}" - ) out = s.copy() out.metadata[STAMP_KEY] = name + if blind is not None: + expect(out, blind) elif blind is not None: out = seal(s, blind, theory) else: diff --git a/src/sp_validation/tests/test_blind_consumers.py b/src/sp_validation/tests/test_blind_consumers.py new file mode 100644 index 00000000..ca004237 --- /dev/null +++ b/src/sp_validation/tests/test_blind_consumers.py @@ -0,0 +1,73 @@ +"""Check parts against the declared blind before derivations or reporting.""" + +import runpy +from pathlib import Path +from types import SimpleNamespace + +import pytest + +from sp_validation import blinding, sacc_io + +SCRIPTS = Path(__file__).resolve().parents[3] / "workflow" / "scripts" + + +@pytest.fixture +def part_path(tmp_path): + part = sacc_io.new_sacc({0: ([0.1, 1.0], [1.0, 1.0])}) + sacc_io.add_xi(part, (0, 0), [10.0], [1.0], [0.5], grid="integration") + path = tmp_path / "xi.sacc" + sacc_io.save(part, path, blind=blinding.NONE) + return path + + +@pytest.mark.parametrize("expected", ["none", "toy"]) +def test_reporting_parts(part_path, monkeypatch, expected): + monkeypatch.syspath_prepend(str(SCRIPTS)) + from cv_runner import take_reporting_parts + + cv = SimpleNamespace( + versions=["v"], xi_parts={}, blind=lambda v: blinding.Blind(expected) + ) + if expected == "toy": + with pytest.raises(ValueError, match="stamped none.*toy"): + take_reporting_parts(cv, [part_path]) + assert not cv.xi_parts + else: + take_reporting_parts(cv, [part_path]) + assert sacc_io.stamp(cv.xi_parts["v", "reporting"]) == "none" + + +@pytest.mark.parametrize("script, input_key, reader", [ + ("cv_cosebis.py", "xi", "get_xi"), + ("cv_pure_eb.py", "xi_integration", "xi_correlation"), +]) +@pytest.mark.parametrize("expected", ["none", "toy"]) +def test_derived_script_checks_before_reading_signal( + part_path, monkeypatch, script, input_key, reader, expected +): + monkeypatch.syspath_prepend(str(SCRIPTS)) + import cv_runner + + monkeypatch.setattr(cv_runner, "_unbuffer_streams", lambda: None) + + class ReachedSignal(Exception): + pass + + def read_signal(*args, **kwargs): + raise ReachedSignal + + monkeypatch.setattr(sacc_io, reader, read_signal) + smk = SimpleNamespace( + params={"version": "v", "blind": expected, "fiducial_scale_cut": [1, 100]}, + input={input_key: part_path}, + ) + error = ReachedSignal if expected == "none" else ValueError + with pytest.raises(error): + runpy.run_path(str(SCRIPTS / script), init_globals={"snakemake": smk}) + assert not list(part_path.parent.glob("*.npz")) + assert not list(part_path.parent.glob("*.png")) + + +def test_expect_names_both_stamps(part_path): + with pytest.raises(ValueError, match="stamped none.*toy"): + sacc_io.expect(sacc_io.load(part_path), blinding.Blind("toy")) diff --git a/src/sp_validation/tests/test_cosmo_val.py b/src/sp_validation/tests/test_cosmo_val.py index 1eab2a1e..426f4a86 100644 --- a/src/sp_validation/tests/test_cosmo_val.py +++ b/src/sp_validation/tests/test_cosmo_val.py @@ -658,6 +658,29 @@ def test_a_blinded_catalogue_refuses_tomographic_signal(self, tmp_path): cv.calculate_pseudo_cl_catalog(version, out, 1, 2) assert not out.exists() + @pytest.mark.parametrize("stamp", ["none", "toy"]) + def test_held_xi_matches_declared_blind(self, tmp_path, stamp): + from sp_validation import blinding, sacc_io + + params, version = self._write_synthetic_catalogs(tmp_path) + config = yaml.safe_load(open(params["catalog_config"])) + config["paths"]["blinds"] = str(tmp_path / "blinds") + config[version]["blind"] = "toy" + with open(params["catalog_config"], "w") as f: + yaml.dump(config, f) + blinding.init("toy", config) + cv = CosmologyValidation(versions=[version], npatch=1, **params) + part = sacc_io.new_sacc({0: ([0.1, 1.0], [1.0, 1.0])}) + sacc_io.add_xi(part, (0, 0), [10.0], [1.0], [0.5], grid="reporting") + part.metadata["blind"] = stamp + cv.xi_parts[version, "reporting"] = part + if stamp == "none": + with pytest.raises(ValueError, match="stamped none.*toy"): + cv.calculate_2pcf_version(version) + else: + result = cv.calculate_2pcf_version(version) + np.testing.assert_array_equal(result["tomo_bin_all_tomo_bin_all"].xip, [1.0]) + def test_treecorr_runs_on_the_cpus_the_process_holds(self, tmp_path): """By default TreeCorr takes the process's CPU affinity, not the node's count.""" import treecorr diff --git a/workflow/rules/cosmo_val.smk b/workflow/rules/cosmo_val.smk index 81d305d1..08526643 100644 --- a/workflow/rules/cosmo_val.smk +++ b/workflow/rules/cosmo_val.smk @@ -361,6 +361,7 @@ rule cv_plot_pseudo_cl: **cv_pseudo_cl_figures(), params: versions=CV_VERSIONS, + blinds=[blind_of(v) for v in CV_VERSIONS], # Style is per catalogue, so the derived variants take their parent's. markers=[CATALOG_CONFIG[base_version(v)]["marker"] for v in CV_VERSIONS], colours=[CATALOG_CONFIG[base_version(v)]["colour"] for v in CV_VERSIONS], @@ -390,6 +391,7 @@ rule cv_pure_eb: **cv_pure_eb_figures("{version}"), params: version="{version}", + blind=lambda w: blind_of(w.version), min_sep=CV["theta_min"], max_sep=CV["theta_max"], nbins=CV["nbins"], @@ -417,6 +419,7 @@ rule cv_cosebis: **cv_cosebis_figures("{version}"), params: version="{version}", + blind=lambda w: blind_of(w.version), min_sep=XI_GRIDS["integration"]["min_sep"], max_sep=XI_GRIDS["integration"]["max_sep"], nbins=XI_GRIDS["integration"]["nbins"], @@ -448,6 +451,7 @@ rule cv_summarize_bmodes: summary_json=str(COSMO_VAL / "bmode_summary.json"), params: versions=CV_VERSIONS, + blinds=[blind_of(v) for v in CV_VERSIONS], fiducial_scale_cut=CV["fiducial_scale_cut"], include_pseudo_cl=CV.get("include_pseudo_cl", False), resources: diff --git a/workflow/scripts/assemble_sacc.py b/workflow/scripts/assemble_sacc.py index d756bce2..f6efca0c 100644 --- a/workflow/scripts/assemble_sacc.py +++ b/workflow/scripts/assemble_sacc.py @@ -136,7 +136,9 @@ def assemble_sacc( path = part_paths.get(name) if path is None: continue - parts.append(_attach_cov(sacc_io.load(path), name, xi_cov, pseudo_cl_cov)) + part = sacc_io.load(path) + sacc_io.expect(part, blind) + parts.append(_attach_cov(part, name, xi_cov, pseudo_cl_cov)) if not parts: raise ValueError(f"no parts found for {version}: {part_paths}") s = sacc_io.save( diff --git a/workflow/scripts/cv_cosebis.py b/workflow/scripts/cv_cosebis.py index ee9d8aef..4e691332 100644 --- a/workflow/scripts/cv_cosebis.py +++ b/workflow/scripts/cv_cosebis.py @@ -28,6 +28,7 @@ fiducial_scale_cut = tuple(p["fiducial_scale_cut"]) part = sacc_io.load(snakemake.input["xi"]) +sacc_io.expect(part, p["blind"]) theta, xip, xim = sacc_io.get_xi(part, (0, 0), grid="integration") edges = log_bin_edges(p["min_sep"], p["max_sep"], p["nbins"]) diff --git a/workflow/scripts/cv_plot_pseudo_cl.py b/workflow/scripts/cv_plot_pseudo_cl.py index f9aeeda9..e993125d 100644 --- a/workflow/scripts/cv_plot_pseudo_cl.py +++ b/workflow/scripts/cv_plot_pseudo_cl.py @@ -19,6 +19,7 @@ spectra = {} for i, version in enumerate(p["versions"]): part = sacc_io.load(snakemake.input["pseudo_cl"][i]) + sacc_io.expect(part, p["blinds"][i]) ell, ee, bb, eb, _window = sacc_io.get_pseudo_cl(part, (0, 0)) with fits.open(snakemake.input["pseudo_cl_cov"][i]) as hdul: covs = { diff --git a/workflow/scripts/cv_pure_eb.py b/workflow/scripts/cv_pure_eb.py index 3b582699..fea8c99f 100644 --- a/workflow/scripts/cv_pure_eb.py +++ b/workflow/scripts/cv_pure_eb.py @@ -30,6 +30,7 @@ fiducial_scale_cut = tuple(p["fiducial_scale_cut"]) part = sacc_io.load(snakemake.input["xi_integration"]) +sacc_io.expect(part, p["blind"]) gg_int = sacc_io.xi_correlation(part, grid="integration") # A part stores bin centres; the edges come from the grid it was measured on. grid = p["integration"] diff --git a/workflow/scripts/cv_runner.py b/workflow/scripts/cv_runner.py index bb67cdfb..8bdbc2a5 100644 --- a/workflow/scripts/cv_runner.py +++ b/workflow/scripts/cv_runner.py @@ -41,7 +41,9 @@ def take_reporting_parts(cv, paths): from sp_validation import sacc_io for version, path in zip(cv.versions, paths, strict=True): - cv.xi_parts[version, "reporting"] = sacc_io.load(path) + part = sacc_io.load(path) + sacc_io.expect(part, cv.blind(version)) + cv.xi_parts[version, "reporting"] = part def verify_outputs(snakemake): diff --git a/workflow/scripts/cv_summarize_bmodes.py b/workflow/scripts/cv_summarize_bmodes.py index 3f9d9e82..8d1051f5 100644 --- a/workflow/scripts/cv_summarize_bmodes.py +++ b/workflow/scripts/cv_summarize_bmodes.py @@ -49,6 +49,7 @@ from astropy.io import fits part = sacc_io.load(snakemake.input["pseudo_cl"][i]) + sacc_io.expect(part, p["blinds"][i]) _ell, _ee, bb, _eb, _window = sacc_io.get_pseudo_cl(part, (0, 0)) with fits.open(snakemake.input["pseudo_cl_cov"][i]) as hdul: cov_bb = np.asarray(hdul["COVAR_BB_BB"].data, float) From 9346c480d7417b475b860f43c399300bb40c1973 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 24 Oct 2026 02:15:13 +0200 Subject: [PATCH 03/10] Resolve physical shear paths for same-file blind declarations --- src/sp_validation/blinding.py | 2 +- src/sp_validation/tests/test_blinding.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/sp_validation/blinding.py b/src/sp_validation/blinding.py index 09c49718..619b060a 100644 --- a/src/sp_validation/blinding.py +++ b/src/sp_validation/blinding.py @@ -77,7 +77,7 @@ def _shear_file(entry): shear = entry.get("shear") if isinstance(entry, dict) else None if not isinstance(shear, dict) or "path" not in shear: return None - return os.path.normpath(os.path.join(str(entry.get("subdir", "")), shear["path"])) + return os.path.realpath(os.path.join(str(entry.get("subdir", "")), shear["path"])) def blind_of(catalogues, entry): diff --git a/src/sp_validation/tests/test_blinding.py b/src/sp_validation/tests/test_blinding.py index d148e88b..2592050b 100644 --- a/src/sp_validation/tests/test_blinding.py +++ b/src/sp_validation/tests/test_blinding.py @@ -92,7 +92,7 @@ def test_seal_refuses_a_sacc_already_stamped(): sio.seal(sealed, bd.NONE) -def test_entries_reading_one_file_declare_one_blind(): +def test_entries_reading_one_file_declare_one_blind(tmp_path): catalogues = { "paths": {"blinds": "/blinds"}, "Y3": {"blind": "y3", "subdir": "/d", "shear": {"path": "y3.fits"}}, @@ -102,3 +102,14 @@ def test_entries_reading_one_file_declare_one_blind(): catalogues["TWIN"]["shear"]["path"] = "/d/sub/../y3.fits" with pytest.raises(ValueError, match="different blinds"): bd.blind_of(catalogues, "TWIN") + + physical = tmp_path / "y3.fits" + physical.touch() + alias = tmp_path / "alias.fits" + alias.symlink_to(physical) + catalogues["Y3"]["shear"]["path"] = str(physical) + catalogues["TWIN"]["shear"]["path"] = str(alias) + with pytest.raises(ValueError, match="different blinds"): + bd.blind_of(catalogues, "TWIN") + catalogues["TWIN"]["blind"] = "y3" + assert bd.blind_of(catalogues, "TWIN") == "y3" From 97cb124f6f340b0b7ab1bea5e6e24427497a9708 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 24 Oct 2026 02:21:11 +0200 Subject: [PATCH 04/10] Test blind determinism, envelope, multi-tracer shifts and secret-free outputs --- src/sp_validation/tests/test_blinding.py | 88 +++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/src/sp_validation/tests/test_blinding.py b/src/sp_validation/tests/test_blinding.py index 2592050b..ef7739bf 100644 --- a/src/sp_validation/tests/test_blinding.py +++ b/src/sp_validation/tests/test_blinding.py @@ -1,7 +1,7 @@ """What a blind shifts, and what the catalogue config may declare. -The shifts run the default theory, :func:`sp_validation.blinding.shear`, under -a blind whose hidden point raises S8 above the fiducial. +Small SACCs exercise the default theory and Smokescreen's parameter draw. +The ``up`` fixture raises S8 above the fiducial for the E/B response tests. """ import json @@ -86,6 +86,90 @@ def test_pure_eb_under_a_blind_shifts_e_and_not_b(up): assert np.max(np.abs(response[2 * n : 4 * n])) < 0.1 +def _small_xi(): + s = _sacc() + theta = np.array([10.0, 50.0]) + sio.add_xi(s, (0, 0), theta, 0 * theta, 0 * theta, grid="reporting") + return s + + +def test_distinct_blinds_and_determinism(tmp_path): + catalogues = {"paths": {"blinds": str(tmp_path / "blinds")}} + a, b = (bd.init(name, catalogues) for name in ("a", "b")) + s = _small_xi() + first = bd.conceal(s, a).mean + np.testing.assert_array_equal(first, bd.conceal(s, a).mean) + assert not np.array_equal(first, bd.conceal(s, b).mean) + np.testing.assert_array_equal(s.mean, np.zeros(len(s.mean))) + + +def test_smokescreen_draw_stays_in_envelope(tmp_path): + from smokescreen.datavector import concealing_factor + + blind = bd.init("envelope", {"paths": {"blinds": str(tmp_path)}}) + record = blind.record() + keys = ("S8", "Omega_m") + fid = np.array([record["fiducial"][k] for k in keys]) + envelope = np.array([record["envelope"][k] for k in keys]) + for seed in [record["seed"], *range(1000)]: + # An identity theory observes the actual draw, without cosmology work. + delta = concealing_factor( + record["fiducial"], record["envelope"], seed=seed, + theory_fn=lambda p: np.array([p[k] for k in keys]), + ) + hidden = fid + delta + assert np.all(hidden >= fid - envelope) + assert np.all(hidden <= fid + envelope) + + +def test_multitracer_shift_matches_single_pair_theories(up): + from smokescreen.datavector import draw_param_shifts + + record = up.record() + shifts = draw_param_shifts(record["envelope"], record["seed"]) + fid = record["fiducial"] + hidden = {k: value + shifts.get(k, 0.0) for k, value in fid.items()} + z = np.linspace(0.0, 3.0, 100) + s = sio.new_sacc({ + i: (z, np.exp(-0.5 * ((z - centre) / 0.2) ** 2)) + for i, centre in enumerate((0.5, 1.1)) + }) + pairs = ((0, 0), (0, 1), (1, 1)) + for pair in pairs: + theta = np.array([10.0, 50.0]) + sio.add_xi(s, pair, theta, 0 * theta, 0 * theta, grid="reporting") + ell = np.array([50.0, 100.0, 200.0]) + sio.add_pseudo_cl( + s, pair, ell, 0 * ell, 0 * ell, 0 * ell, + window_ells=ell, window_weights=np.eye(3), + ) + shifted = bd.conceal(s, up) + for a, b in pairs: + rows = s.indices(tracers=(f"source_{a}", f"source_{b}")) + single = s.copy() + single.keep_indices(rows) + expected = bd.shear(hidden, single) - bd.shear(fid, single) + np.testing.assert_allclose(shifted.mean[rows] - s.mean[rows], expected, + rtol=1e-12, atol=0) + for kind in (sio.XI_PLUS, sio.XI_MINUS, sio.CL_EE): + assert np.any(expected[single.indices(kind)] != 0) + + +def test_saved_blind_is_secret_free(tmp_path, capfd): + s = _small_xi() + path = tmp_path / "sealed.sacc" + blind = bd.init("secret-free", {"paths": {"blinds": str(tmp_path / "blinds")}}) + written = sio.save(s, path, blind=blind) + seed = blind.record()["seed"] + captured = capfd.readouterr() + assert seed.encode() not in path.read_bytes() + assert seed not in captured.out + captured.err + assert written.metadata == {**s.metadata, "blind": blind.name} + loaded = sio.load(path) + assert loaded.metadata == written.metadata + np.testing.assert_array_equal(loaded.mean, written.mean) + + def test_seal_refuses_a_sacc_already_stamped(): sealed = sio.seal(_sacc(), bd.NONE) with pytest.raises(ValueError, match="already carries a blind stamp"): From 5a7da36efb77b401f5a635212d52b4ba950b5863 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 24 Oct 2026 02:52:51 +0200 Subject: [PATCH 05/10] Validate blind stamps in SACC loads, including paper-workflow readers --- papers/bmodes/rules/figures.smk | 12 ++++ .../scripts/bb_covariance_nz_independence.py | 10 +++- papers/bmodes/scripts/cl_data_vector.py | 23 ++++++-- .../bmodes/scripts/cl_version_comparison.py | 16 +++++- .../scripts/compute_cosebis_pte_single.py | 16 +++++- papers/bmodes/scripts/cosebis_data_vector.py | 17 ++++-- .../scripts/cosebis_version_comparison.py | 17 +++++- .../harmonic_config_cosebis_comparison.py | 18 ++++-- .../scripts/harmonic_space_pte_matrices.py | 20 ++++++- .../scripts/plot_cosebis_filter_overlay.py | 8 ++- papers/bmodes/scripts/pseudo_cl_io.py | 4 +- papers/bmodes/scripts/pure_eb_modes.py | 11 +++- src/sp_validation/sacc_io.py | 9 ++- .../tests/test_blind_consumers.py | 44 ++++++++++++++- src/sp_validation/tests/test_sacc_writers.py | 5 +- workflow/scripts/assemble_sacc.py | 3 +- workflow/scripts/cv_cosebis.py | 3 +- workflow/scripts/cv_plot_pseudo_cl.py | 3 +- workflow/scripts/cv_pure_eb.py | 3 +- workflow/scripts/cv_runner.py | 3 +- workflow/scripts/cv_summarize_bmodes.py | 3 +- workflow/tests/test_bmodes_dag.py | 56 +++++++++++++++++++ 22 files changed, 252 insertions(+), 52 deletions(-) create mode 100644 workflow/tests/test_bmodes_dag.py diff --git a/papers/bmodes/rules/figures.smk b/papers/bmodes/rules/figures.smk index 9506b81e..c7a0b4ee 100644 --- a/papers/bmodes/rules/figures.smk +++ b/papers/bmodes/rules/figures.smk @@ -138,6 +138,8 @@ rule cosebis_version_comparison: params: version_labels=VERSION_LABELS, versions=VERSIONS_LEAK_CORR, + blinds={v: blind_of(v) for v in VERSIONS_LEAK_CORR}, + cov_base_dir=str(COSMO_INFERENCE / "data/covariance"), output: evidence=f"{TAPESTRY_DIR}/cosebis_version_comparison/evidence.json", figure_stacked=f"{TAPESTRY_DIR}/cosebis_version_comparison/figure_stacked.png", @@ -163,6 +165,7 @@ rule cosebis_data_vector: **{f"cov_{ver}": _cov_integration_path(ver) for ver in VERSIONS_ALL_FOR_PLOTS}, params: cov_base_dir=str(COSMO_INFERENCE / "data/covariance"), + blinds={v: blind_of(v) for v in VERSIONS_ALL_FOR_PLOTS}, output: evidence=f"{TAPESTRY_DIR}/cosebis_data_vector/evidence.json", paper_figure=f"{PAPER_FIGURES_DIR}/cosebis_data_vector.pdf", @@ -184,6 +187,7 @@ rule pure_eb_modes: "results/paper_plots/intermediate/{version}_pure_eb.npz", params: **FIDUCIAL_BINNING, + blind=lambda w: blind_of(w.version), resources: mem_mb=8000, runtime=20, @@ -285,6 +289,7 @@ rule cl_data_vector: **{f"pseudo_cl_{ver}": _pseudo_cl_path(ver) for ver in VERSIONS_ALL_FOR_PLOTS}, **{f"pseudo_cl_cov_{ver}": _pseudo_cl_cov_path(ver) for ver in VERSIONS_ALL_FOR_PLOTS}, params: + blinds={v: blind_of(v) for v in VERSIONS_ALL_FOR_PLOTS}, ell_min_cut=config["cl"]["fiducial_ell_min"], ell_max_cut=config["cl"]["fiducial_ell_max"], output: @@ -305,6 +310,7 @@ rule cl_version_comparison: params: version_labels=VERSION_LABELS, versions=VERSIONS_LEAK_CORR, + blinds={v: blind_of(v) for v in VERSIONS_LEAK_CORR}, ell_min_cut=config["cl"]["fiducial_ell_min"], ell_max_cut=config["cl"]["fiducial_ell_max"], output: @@ -328,6 +334,7 @@ rule compute_cosebis_pte: pte_json=f"{TAPESTRY_DIR}/cosebis_pte_matrix/pte_values/{{version}}/pte_{{i_min}}_{{i_max}}.json", params: nmodes=FIDUCIAL["nmodes"], + blind=lambda w: blind_of(w.version), wildcard_constraints: i_min=r"\d{3}", i_max=r"\d{3}", @@ -387,6 +394,7 @@ rule harmonic_space_pte_matrices: pseudo_cl_cov=[_pseudo_cl_cov_path(ver) for ver in VERSIONS_CONFIG_SPACE_PTES], params: version_labels=VERSION_LABELS, + blinds={v: blind_of(v) for v in VERSIONS_CONFIG_SPACE_PTES}, output: evidence=f"{TAPESTRY_DIR}/harmonic_space_pte_matrices/evidence.json", figure_fiducial=f"{TAPESTRY_DIR}/harmonic_space_pte_matrices/figure_fiducial.png", @@ -424,6 +432,7 @@ rule bb_covariance_nz_independence: # Pseudo-Cl data vector for ell bin centers pseudo_cl=_pseudo_cl_path(MOCK_VERSION), params: + blind=blind_of(MOCK_VERSION), nmodes=FIDUCIAL["nmodes"], theta_min=config["cosebis"]["theta_min"], theta_max=config["cosebis"]["theta_max"], @@ -464,6 +473,7 @@ rule harmonic_config_cosebis_comparison: **{f"xi_{ver}": _xi_integration_path(ver) for ver in VERSIONS_ALL_FOR_PLOTS}, **{f"cov_{ver}": _cov_integration_path(ver) for ver in VERSIONS_ALL_FOR_PLOTS}, params: + blinds={v: blind_of(v) for v in VERSIONS_ALL_FOR_PLOTS}, scale_cut=lambda wildcards: _COSEBIS_ANGULAR_RANGES[wildcards.angular_range], output: evidence=f"{TAPESTRY_DIR}/harmonic_config_cosebis_comparison_{{angular_range}}/evidence.json", @@ -487,6 +497,8 @@ rule cosebis_filter_overlay: input: pseudo_cl=_pseudo_cl_path(FIDUCIAL_VERSION), pseudo_cl_cov=_pseudo_cl_cov_path(FIDUCIAL_VERSION), + params: + blind=blind_of(FIDUCIAL_VERSION), output: evidence=f"{TAPESTRY_DIR}/cosebis_filter_overlay/evidence.json", figure=f"{TAPESTRY_DIR}/cosebis_filter_overlay/figure.png", diff --git a/papers/bmodes/scripts/bb_covariance_nz_independence.py b/papers/bmodes/scripts/bb_covariance_nz_independence.py index 1e4cdb52..a1b95d53 100644 --- a/papers/bmodes/scripts/bb_covariance_nz_independence.py +++ b/papers/bmodes/scripts/bb_covariance_nz_independence.py @@ -75,6 +75,7 @@ def load_cosebis_diagonals( min_sep_int, max_sep_int, nbins_int, + blind, ): """Compute COSEBIS covariance diagonals from config-space covariance. @@ -82,7 +83,7 @@ def load_cosebis_diagonals( Returns E_n and B_n covariance diagonals. """ # Load fine-binned 2PCF (need the binning info for COSEBIS calculation) - gg = sacc_io.xi_correlation(sacc_io.load(xi_integration_path)) + gg = sacc_io.xi_correlation(sacc_io.load(xi_integration_path, blind=blind)) # Compute COSEBIS with this realisation's covariance results = calculate_cosebis( @@ -247,6 +248,7 @@ def main( theta_max, figure_path, evidence_path, + blind, ): """Run the BB-covariance n(z)-independence cross-check. @@ -284,12 +286,13 @@ def main( min_sep_int, max_sep_int, nbins_int, + blind, ) for label, path in cov_integration_paths.items() } # Ell bin centers from the pseudo-Cl SACC part - ell_eff = load_pseudo_cl_data(pseudo_cl_path)["ELL"] + ell_eff = load_pseudo_cl_data(pseudo_cl_path, blind)["ELL"] def ratios_to_reference(data, modes): return { @@ -436,6 +439,7 @@ def _from_snakemake(smk): theta_max=smk.params.theta_max, figure_path=smk.output.figure, evidence_path=smk.output.evidence, + blind=smk.params["blind"], ) @@ -477,6 +481,7 @@ def _from_cli(argv=None): help="COSMO_INFERENCE data/covariance dir (Gaussian integration covariances)", ) ap.add_argument("--out", required=True, help="Output directory (lc {output})") + ap.add_argument("--blind", required=True) a = ap.parse_args(argv) with open(a.config) as f: @@ -530,6 +535,7 @@ def _from_cli(argv=None): theta_max=config["cosebis"]["theta_max"], figure_path=str(out_dir / "figure.png"), evidence_path=str(out_dir / "evidence.json"), + blind=a.blind, ) diff --git a/papers/bmodes/scripts/cl_data_vector.py b/papers/bmodes/scripts/cl_data_vector.py index 6576cd7c..85c77f30 100644 --- a/papers/bmodes/scripts/cl_data_vector.py +++ b/papers/bmodes/scripts/cl_data_vector.py @@ -44,9 +44,9 @@ def _compute_pte_with_cuts(data, covariance, ell, ell_min, ell_max): return pte, chi2, dof -def _load_pseudo_cl_data(pseudo_cl_path, pseudo_cl_cov_path): +def _load_pseudo_cl_data(pseudo_cl_path, pseudo_cl_cov_path, blind): """Load pseudo-Cl data from SACC and covariance from FITS.""" - data = load_pseudo_cl_data(pseudo_cl_path) + data = load_pseudo_cl_data(pseudo_cl_path, blind) ell = data["ELL"] cl_eb = data["EB"] @@ -203,6 +203,8 @@ def main( fiducial_version=None, fiducial_pseudo_cl_path=None, fiducial_pseudo_cl_cov_path=None, + *, + blinds, ): version = config["fiducial"]["version"] version_labels = config["plotting"]["version_labels"] @@ -232,7 +234,7 @@ def main( fiducial_pseudo_cl_cov_path, ) ell, cl_bb, cl_eb, cov_bb, cov_eb, sigma_bb, sigma_eb = _load_pseudo_cl_data( - pseudo_cl_path, pseudo_cl_cov_path + pseudo_cl_path, pseudo_cl_cov_path, blinds[ver] ) # Create figure with appropriate title @@ -271,7 +273,7 @@ def main( fiducial_pseudo_cl_cov_path, ) ell, cl_bb, cl_eb, cov_bb, cov_eb, sigma_bb, sigma_eb = _load_pseudo_cl_data( - pseudo_cl_path, pseudo_cl_cov_path + pseudo_cl_path, pseudo_cl_cov_path, blinds[version] ) # Compute PTEs (null tests) using full ell range @@ -358,6 +360,8 @@ def _from_cli(argv=None): "(lc output). Requires --fiducial-version and --fiducial-pseudo-cl-path." ), ) + ap.add_argument("--blinds", type=json.loads, required=True, + help="JSON mapping of catalogue versions to declared blinds") a = ap.parse_args(argv) with open(a.config) as f: config = yaml.safe_load(f) @@ -368,8 +372,17 @@ def _from_cli(argv=None): fiducial_version=a.fiducial_version, fiducial_pseudo_cl_path=a.fiducial_pseudo_cl_path, fiducial_pseudo_cl_cov_path=a.fiducial_pseudo_cl_cov_path, + blinds=a.blinds, ) if __name__ == "__main__": - _from_cli() + if "snakemake" in globals(): + smk = snakemake # noqa: F821 + version = smk.config["fiducial"]["version"] + main( + smk.config, Path(smk.input[f"pseudo_cl_{version}"]).parent, + Path(smk.output["evidence"]).parent, blinds=smk.params["blinds"], + ) + else: + _from_cli() diff --git a/papers/bmodes/scripts/cl_version_comparison.py b/papers/bmodes/scripts/cl_version_comparison.py index d16659f9..4fba731f 100644 --- a/papers/bmodes/scripts/cl_version_comparison.py +++ b/papers/bmodes/scripts/cl_version_comparison.py @@ -76,6 +76,8 @@ def main( fiducial_version=None, fiducial_pseudo_cl_path=None, fiducial_pseudo_cl_cov_path=None, + *, + blinds, ): # Scale cuts (Guerrini et al. fiducial ell range) ell_min_cut = int(config["cl"]["fiducial_ell_min"]) @@ -128,7 +130,7 @@ def main( fiducial_pseudo_cl_cov_path, ) - data = load_pseudo_cl_data(pseudo_cl_path) + data = load_pseudo_cl_data(pseudo_cl_path, blinds[version]) ell = data["ELL"] cl_bb = data["BB"] cl_eb = data["EB"] @@ -403,6 +405,8 @@ def _from_cli(argv=None): "(lc output). Requires --fiducial-version and --fiducial-pseudo-cl-path." ), ) + ap.add_argument("--blinds", type=json.loads, required=True, + help="JSON mapping of catalogue versions to declared blinds") a = ap.parse_args(argv) with open(a.config) as f: config = yaml.safe_load(f) @@ -413,8 +417,16 @@ def _from_cli(argv=None): fiducial_version=a.fiducial_version, fiducial_pseudo_cl_path=a.fiducial_pseudo_cl_path, fiducial_pseudo_cl_cov_path=a.fiducial_pseudo_cl_cov_path, + blinds=a.blinds, ) if __name__ == "__main__": - _from_cli() + if "snakemake" in globals(): + smk = snakemake # noqa: F821 + main( + smk.config, Path(smk.input["pseudo_cl"][0]).parent, + Path(smk.output["evidence"]).parent, blinds=smk.params["blinds"], + ) + else: + _from_cli() diff --git a/papers/bmodes/scripts/compute_cosebis_pte_single.py b/papers/bmodes/scripts/compute_cosebis_pte_single.py index d80f9eee..6828e856 100644 --- a/papers/bmodes/scripts/compute_cosebis_pte_single.py +++ b/papers/bmodes/scripts/compute_cosebis_pte_single.py @@ -79,7 +79,7 @@ def _compute_pair(gg, cov_path, nmodes, theta_min, theta_max): } -def main(config, xi_integration, cov_integration, out_dir, version=None): +def main(config, xi_integration, cov_integration, out_dir, version=None, *, blind): t_start = time.time() fid = config["fiducial"] # Version tags only the output filename + provenance record; the theta @@ -89,7 +89,7 @@ def main(config, xi_integration, cov_integration, out_dir, version=None): version = version if version is not None else fid["version"] nmodes = int(fid["nmodes"]) # 20 for full computation - gg = sacc_io.xi_correlation(sacc_io.load(xi_integration)) + gg = sacc_io.xi_correlation(sacc_io.load(xi_integration, blind=blind)) # Reporting theta grid (nbins+1 = 21 edges): geomspace(1', 250', 21) theta_grid = np.geomspace(fid["min_sep"], fid["max_sep"], fid["nbins"] + 1) @@ -183,6 +183,7 @@ def _from_cli(argv=None): required=True, help="Fiducial 1000-bin Gaussian covariance (processed .txt)", ) + ap.add_argument("--blind", required=True) ap.add_argument("--out", required=True, help="Output directory (lc {output})") ap.add_argument( "--version", @@ -199,8 +200,17 @@ def _from_cli(argv=None): a.cov_integration, a.out, version=a.version, + blind=a.blind, ) if __name__ == "__main__": - _from_cli() + if "snakemake" in globals(): + smk = snakemake # noqa: F821 + main( + smk.config, smk.input["xi_integration"], smk.input["cov_integration"], + Path(smk.output["pte_json"]).parent, version=smk.wildcards.version, + blind=smk.params["blind"], + ) + else: + _from_cli() diff --git a/papers/bmodes/scripts/cosebis_data_vector.py b/papers/bmodes/scripts/cosebis_data_vector.py index ea431569..0737d7f7 100644 --- a/papers/bmodes/scripts/cosebis_data_vector.py +++ b/papers/bmodes/scripts/cosebis_data_vector.py @@ -135,7 +135,7 @@ def _create_single_panel_bmode_figure(datasets, nmodes, scale_cuts, title=None): return fig -def main(config, xi_integration, cov_integration, out_dir): +def main(config, xi_integration, cov_integration, out_dir, *, blind): version = config["fiducial"]["version"] nmodes = config["fiducial"]["nmodes"] @@ -157,7 +157,7 @@ def main(config, xi_integration, cov_integration, out_dir): out_dir.mkdir(parents=True, exist_ok=True) # Load the fiducial fine-binned 2PCF once (integration grid) - gg = sacc_io.xi_correlation(sacc_io.load(xi_integration)) + gg = sacc_io.xi_correlation(sacc_io.load(xi_integration, blind=blind)) # Compute the E_n / B_n data vectors + T^T C_xi T mode covariance at both # scale cuts. Same calculate_cosebis call as the original per-version loop. @@ -232,12 +232,21 @@ def _from_cli(argv=None): required=True, help="Fiducial 1000-bin Gaussian covariance (processed .txt) for the T^T C_xi T transform", ) + ap.add_argument("--blind", required=True) ap.add_argument("--out", required=True, help="Output directory (lc {output})") a = ap.parse_args(argv) with open(a.config) as f: config = yaml.safe_load(f) - main(config, a.xi_integration, a.cov_integration, a.out) + main(config, a.xi_integration, a.cov_integration, a.out, blind=a.blind) if __name__ == "__main__": - _from_cli() + if "snakemake" in globals(): + smk = snakemake # noqa: F821 + version = smk.config["fiducial"]["version"] + main( + smk.config, smk.input[f"xi_{version}"], smk.input[f"cov_{version}"], + Path(smk.output["evidence"]).parent, blind=smk.params["blinds"][version], + ) + else: + _from_cli() diff --git a/papers/bmodes/scripts/cosebis_version_comparison.py b/papers/bmodes/scripts/cosebis_version_comparison.py index eca3deaa..46f5395d 100644 --- a/papers/bmodes/scripts/cosebis_version_comparison.py +++ b/papers/bmodes/scripts/cosebis_version_comparison.py @@ -164,6 +164,8 @@ def main( fiducial_version=None, fiducial_xi_path=None, fiducial_cov_path=None, + *, + blinds, ): # Leak-corrected versions (matches VERSIONS_LEAK_CORR in figures.smk) versions = [v for v in config["versions"] if "_leak_corr" in v and "_ecut" not in v] @@ -247,7 +249,7 @@ def main( marker = MARKER_STYLES[i] if i < len(MARKER_STYLES) else "o" fillstyle = "full" - gg = sacc_io.xi_correlation(sacc_io.load(xi_path)) + gg = sacc_io.xi_correlation(sacc_io.load(xi_path, blind=blinds[version])) results = calculate_cosebis( gg, @@ -384,6 +386,8 @@ def _from_cli(argv=None): help="Explicit path to the fiducial version's Gaussian integration " "covariance (e.g. lc output), overriding the canonical-tree path pattern", ) + ap.add_argument("--blinds", type=json.loads, required=True, + help="JSON mapping of catalogue versions to declared blinds") a = ap.parse_args(argv) with open(a.config) as f: config = yaml.safe_load(f) @@ -395,8 +399,17 @@ def _from_cli(argv=None): fiducial_version=a.fiducial_version, fiducial_xi_path=a.fiducial_xi_path, fiducial_cov_path=a.fiducial_cov_path, + blinds=a.blinds, ) if __name__ == "__main__": - _from_cli() + if "snakemake" in globals(): + smk = snakemake # noqa: F821 + main( + smk.config, Path(smk.input["xi_integration"][0]).parent, + smk.params["cov_base_dir"], Path(smk.output["evidence"]).parent, + blinds=smk.params["blinds"], + ) + else: + _from_cli() diff --git a/papers/bmodes/scripts/harmonic_config_cosebis_comparison.py b/papers/bmodes/scripts/harmonic_config_cosebis_comparison.py index 39cadec3..f36490bc 100644 --- a/papers/bmodes/scripts/harmonic_config_cosebis_comparison.py +++ b/papers/bmodes/scripts/harmonic_config_cosebis_comparison.py @@ -105,10 +105,10 @@ def _build_transforms(cosebis_obj, ell): def _compute_harmonic_cosebis( - pseudo_cl_path, pseudo_cov_path, nmodes, theta_min, theta_max + pseudo_cl_path, pseudo_cov_path, nmodes, theta_min, theta_max, blind ): """Compute COSEBIS from pseudo-C_ell and propagate covariance.""" - data = load_pseudo_cl_data(pseudo_cl_path) + data = load_pseudo_cl_data(pseudo_cl_path, blind) ell = np.asarray(data["ELL"], dtype=float) cl_ee = np.asarray(data["EE"], dtype=float) cl_bb = np.asarray(data["BB"], dtype=float) @@ -134,12 +134,12 @@ def _compute_harmonic_cosebis( return ce_harm, cb_harm, cov_harmonic -def _compute_config_cosebis(xi_path, cov_path, nmodes, scale_cut, config): +def _compute_config_cosebis(xi_path, cov_path, nmodes, scale_cut, config, blind): """Compute COSEBIS from fine-binned xi_pm for a single scale cut. Returns (En, Bn, cov). """ - gg = sacc_io.xi_correlation(sacc_io.load(xi_path)) + gg = sacc_io.xi_correlation(sacc_io.load(xi_path, blind=blind)) results = calculate_cosebis( gg, nmodes=nmodes, scale_cuts=[scale_cut], cov_path=cov_path @@ -500,7 +500,7 @@ def _make_version_comparison_figure( return fig -def main(config, inputs, scale_cut, output_dir, paper_figure_name=None): +def main(config, inputs, scale_cut, output_dir, paper_figure_name=None, *, blinds): """Harmonic-vs-config COSEBI cross-check for one angular range. ``inputs`` is a dict mirroring ``snakemake.input``: per-version keys @@ -571,6 +571,7 @@ def main(config, inputs, scale_cut, output_dir, paper_figure_name=None): nmodes, scale_cut, config, + blinds[ver], ) # Harmonic-space @@ -580,6 +581,7 @@ def main(config, inputs, scale_cut, output_dir, paper_figure_name=None): nmodes, scale_cut[0], scale_cut[1], + blinds[ver], ) harm = { "En": ce_h, @@ -628,6 +630,7 @@ def main(config, inputs, scale_cut, output_dir, paper_figure_name=None): nmodes, other_cut, config, + blinds[ver], ) ce_h_other, cb_h_other, cov_h_other = _compute_harmonic_cosebis( pseudo_cl_paths[pseudo_cl_key], @@ -635,6 +638,7 @@ def main(config, inputs, scale_cut, output_dir, paper_figure_name=None): nmodes, other_cut[0], other_cut[1], + blinds[ver], ) harm_other = { "En": ce_h_other, @@ -819,6 +823,7 @@ def _from_snakemake(smk): config=smk.config, inputs=dict(smk.input.items()), scale_cut=tuple(smk.params.scale_cut), + blinds=smk.params["blinds"], output_dir=Path(smk.output["evidence"]).parent, paper_figure_name=( Path(smk.output["paper_figure"]).name @@ -896,6 +901,8 @@ def _from_cli(argv=None): "input always reads from the old tree, even for the fiducial version." ), ) + ap.add_argument("--blinds", type=json.loads, required=True, + help="JSON mapping of catalogue versions to declared blinds") a = ap.parse_args(argv) with open(a.config) as f: @@ -956,6 +963,7 @@ def _from_cli(argv=None): scale_cut=scale_cut, output_dir=a.out, paper_figure_name=f"harmonic_config_cosebis_{a.angular_range}.pdf", + blinds=a.blinds, ) diff --git a/papers/bmodes/scripts/harmonic_space_pte_matrices.py b/papers/bmodes/scripts/harmonic_space_pte_matrices.py index 577c561f..46c0b3e8 100644 --- a/papers/bmodes/scripts/harmonic_space_pte_matrices.py +++ b/papers/bmodes/scripts/harmonic_space_pte_matrices.py @@ -38,7 +38,8 @@ def _pseudo_cl_cov(results_dir, ver, nbins=32): def compute_pte_matrix( - pseudo_cl_path, pseudo_cl_cov_path, fiducial_ell_min=None, fiducial_ell_max=None + pseudo_cl_path, pseudo_cl_cov_path, fiducial_ell_min=None, fiducial_ell_max=None, + *, blind, ): """Compute PTE matrix for all ell-bin cut combinations. @@ -63,7 +64,7 @@ def compute_pte_matrix( Summary statistics. """ # Load pseudo-Cl data - data = load_pseudo_cl_data(pseudo_cl_path) + data = load_pseudo_cl_data(pseudo_cl_path, blind) ell = data["ELL"] cl_bb = data["BB"] n_ell = len(ell) @@ -348,6 +349,8 @@ def main( fiducial_version_override=None, fiducial_pseudo_cl_path=None, fiducial_pseudo_cl_cov_path=None, + *, + blinds, ): versions = [v for v in config["versions"] if "_ecut" not in v] fiducial_version = config["fiducial"]["version"] @@ -404,6 +407,7 @@ def main( version_to_cov[version], fiducial_ell_min=fiducial_ell_min, fiducial_ell_max=fiducial_ell_max, + blind=blinds[version], ) all_stats[version] = stats @@ -545,6 +549,8 @@ def _from_cli(argv=None): help="Explicit path to fiducial pseudo-Cl covariance FITS produced by lc " "(overrides pattern reconstruction for --fiducial-version)", ) + ap.add_argument("--blinds", type=json.loads, required=True, + help="JSON mapping of catalogue versions to declared blinds") a = ap.parse_args(argv) with open(a.config) as f: config = yaml.safe_load(f) @@ -555,8 +561,16 @@ def _from_cli(argv=None): fiducial_version_override=a.fiducial_version, fiducial_pseudo_cl_path=a.fiducial_pseudo_cl_path, fiducial_pseudo_cl_cov_path=a.fiducial_pseudo_cl_cov_path, + blinds=a.blinds, ) if __name__ == "__main__": - _from_cli() + if "snakemake" in globals(): + smk = snakemake # noqa: F821 + main( + smk.config, Path(smk.input["pseudo_cl"][0]).parent, + Path(smk.output["evidence"]).parent, blinds=smk.params["blinds"], + ) + else: + _from_cli() diff --git a/papers/bmodes/scripts/plot_cosebis_filter_overlay.py b/papers/bmodes/scripts/plot_cosebis_filter_overlay.py index 5f071bb6..04cd485a 100644 --- a/papers/bmodes/scripts/plot_cosebis_filter_overlay.py +++ b/papers/bmodes/scripts/plot_cosebis_filter_overlay.py @@ -26,9 +26,9 @@ plt.style.use(PAPER_MPLSTYLE) -def load_bb_data(pseudo_cl_path, pseudo_cov_path): +def load_bb_data(pseudo_cl_path, pseudo_cov_path, blind): """Load BB bandpower data and errorbars.""" - data = load_pseudo_cl_data(pseudo_cl_path) + data = load_pseudo_cl_data(pseudo_cl_path, blind) ell = np.asarray(data["ELL"], dtype=float) bb = np.asarray(data["BB"], dtype=float) @@ -170,7 +170,9 @@ def main(): theta_max_fid = config["fiducial"]["fiducial_max_scale"] nmodes = 6 - ell_32, bb, sigma_bb = load_bb_data(pseudo_cl_path, pseudo_cov_path) + ell_32, bb, sigma_bb = load_bb_data( + pseudo_cl_path, pseudo_cov_path, snakemake.params["blind"] # noqa: F821 + ) ell_dense = np.logspace(np.log10(8), np.log10(2500), 2000) diff --git a/papers/bmodes/scripts/pseudo_cl_io.py b/papers/bmodes/scripts/pseudo_cl_io.py index b9497105..a242a779 100644 --- a/papers/bmodes/scripts/pseudo_cl_io.py +++ b/papers/bmodes/scripts/pseudo_cl_io.py @@ -3,12 +3,12 @@ from sp_validation import sacc_io -def load_pseudo_cl_data(path): +def load_pseudo_cl_data(path, blind): """Return a pseudo-Cℓ part's spectra as ``{"ELL", "EE", "EB", "BB"}`` arrays. The bandpower covariance is a separate FITS product (``pseudo_cl_cov``). """ - ell, ee, bb, eb, _window = sacc_io.get_pseudo_cl(sacc_io.load(str(path)), (0, 0)) + ell, ee, bb, eb, _window = sacc_io.get_pseudo_cl(sacc_io.load(str(path), blind=blind), (0, 0)) missing = [name for name, values in (("BB", bb), ("EB", eb)) if values is None] if missing: raise ValueError( diff --git a/papers/bmodes/scripts/pure_eb_modes.py b/papers/bmodes/scripts/pure_eb_modes.py index a9c0898d..57ac71fb 100644 --- a/papers/bmodes/scripts/pure_eb_modes.py +++ b/papers/bmodes/scripts/pure_eb_modes.py @@ -25,9 +25,9 @@ from sp_validation.sacc_io import PURE_KEYS -def _load_xi(path): +def _load_xi(path, blind): """``(meanr, xip, xim, weight)`` from a ξ± SACC part.""" - gg = sacc_io.xi_correlation(sacc_io.load(path)) + gg = sacc_io.xi_correlation(sacc_io.load(path, blind=blind)) return gg.meanr, gg.xip, gg.xim, gg.weight @@ -41,9 +41,11 @@ def pure_eb_modes( max_sep_int, nbins_int, out_path, + *, + blind, ): results = calculate_pure_eb_correlation( - *_load_xi(xi_integration), + *_load_xi(xi_integration, blind), np.geomspace(min_sep_int, max_sep_int, nbins_int + 1), np.loadtxt(cov_integration), np.geomspace(min_sep, max_sep, nbins + 1), @@ -76,11 +78,13 @@ def _from_snakemake(smk): p["max_sep_int"], p["nbins_int"], smk.output[0], + blind=p["blind"], ) def _from_cli(argv=None): ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + ap.add_argument("--blind", required=True) ap.add_argument("--xi-integration", required=True) ap.add_argument("--cov-integration", required=True) ap.add_argument("--min-sep", type=float, default=1.0) @@ -101,6 +105,7 @@ def _from_cli(argv=None): a.max_sep_int, a.nbins_int, a.out, + blind=a.blind, ) diff --git a/src/sp_validation/sacc_io.py b/src/sp_validation/sacc_io.py index 7817ef78..c4bdc5c7 100644 --- a/src/sp_validation/sacc_io.py +++ b/src/sp_validation/sacc_io.py @@ -1044,9 +1044,12 @@ def save(s, path, *, blind=None, theory=None, derived_from=None): return out -def load(path): - """Load the SACC at ``path``.""" - return sacc.Sacc.load_fits(str(path)) +def load(path, blind=None): + """Load a SACC, checking its stamp against ``blind`` when supplied.""" + part = sacc.Sacc.load_fits(str(path)) + if blind is not None: + expect(part, blind) + return part # ============================================================================= diff --git a/src/sp_validation/tests/test_blind_consumers.py b/src/sp_validation/tests/test_blind_consumers.py index ca004237..335e4d90 100644 --- a/src/sp_validation/tests/test_blind_consumers.py +++ b/src/sp_validation/tests/test_blind_consumers.py @@ -68,6 +68,46 @@ def read_signal(*args, **kwargs): assert not list(part_path.parent.glob("*.png")) -def test_expect_names_both_stamps(part_path): +@pytest.mark.parametrize("as_name", [False, True]) +@pytest.mark.parametrize("stamp", ["none", "toy"]) +def test_load_checks_expected_blind(part_path, as_name, stamp): + part = sacc_io.load(part_path) + part.metadata["blind"] = stamp + part.save_fits(str(part_path), overwrite=True) + expected = "toy" if as_name else blinding.Blind("toy") + if stamp == "none": + with pytest.raises(ValueError, match="stamped none.*toy"): + sacc_io.load(part_path, blind=expected) + else: + assert sacc_io.stamp(sacc_io.load(part_path, blind=expected)) == "toy" + + +@pytest.mark.parametrize("script, function, args", [ + ("pure_eb_modes.py", "_load_xi", ("xi", "toy")), + ("cl_data_vector.py", "_load_pseudo_cl_data", ("cl", "cov", "toy")), + ("pseudo_cl_io.py", "load_pseudo_cl_data", ("cl", "toy")), + ("plot_cosebis_filter_overlay.py", "load_bb_data", ("cl", "cov", "toy")), + ("harmonic_space_pte_matrices.py", "compute_pte_matrix", ("cl", "cov")), + ("harmonic_config_cosebis_comparison.py", "_compute_harmonic_cosebis", + ("cl", "cov", 6, 1, 100, "toy")), + ("harmonic_config_cosebis_comparison.py", "_compute_config_cosebis", + ("xi", "cov", 6, (1, 100), {}, "toy")), + ("bb_covariance_nz_independence.py", "load_cosebis_diagonals", + ("xi", "cov", 6, 1, 100, 0.1, 300, 100, "toy")), +]) +def test_paper_readers_check_before_signal( + part_path, monkeypatch, script, function, args +): + paper_scripts = SCRIPTS.parents[1] / "papers" / "bmodes" / "scripts" + monkeypatch.syspath_prepend(str(paper_scripts)) + module = runpy.run_path(str(paper_scripts / script)) + load = sacc_io.load + + def load_input(path, blind=None): + assert blind == "toy" + return load(part_path, blind=blind) + + monkeypatch.setattr(sacc_io, "load", load_input) + kwargs = {"blind": "toy"} if function == "compute_pte_matrix" else {} with pytest.raises(ValueError, match="stamped none.*toy"): - sacc_io.expect(sacc_io.load(part_path), blinding.Blind("toy")) + module[function](*args, **kwargs) diff --git a/src/sp_validation/tests/test_sacc_writers.py b/src/sp_validation/tests/test_sacc_writers.py index a3eb6618..8823ceda 100644 --- a/src/sp_validation/tests/test_sacc_writers.py +++ b/src/sp_validation/tests/test_sacc_writers.py @@ -146,7 +146,10 @@ def get_bandpower_windows(self): path = tmp_path / "pseudo_cl.sacc" sio.save(part, str(path), blind=NONE) - data = _paper_pseudo_cl_reader()(path) + reader = _paper_pseudo_cl_reader() + with pytest.raises(ValueError, match="stamped none.*toy"): + reader(path, "toy") + data = reader(path, "none") assert np.array_equal(data["ELL"], ell) assert np.array_equal(data["EE"], cl_all[0]) assert np.array_equal(data["EB"], cl_all[1]) diff --git a/workflow/scripts/assemble_sacc.py b/workflow/scripts/assemble_sacc.py index f6efca0c..034720aa 100644 --- a/workflow/scripts/assemble_sacc.py +++ b/workflow/scripts/assemble_sacc.py @@ -136,8 +136,7 @@ def assemble_sacc( path = part_paths.get(name) if path is None: continue - part = sacc_io.load(path) - sacc_io.expect(part, blind) + part = sacc_io.load(path, blind=blind) parts.append(_attach_cov(part, name, xi_cov, pseudo_cl_cov)) if not parts: raise ValueError(f"no parts found for {version}: {part_paths}") diff --git a/workflow/scripts/cv_cosebis.py b/workflow/scripts/cv_cosebis.py index 4e691332..c7176f82 100644 --- a/workflow/scripts/cv_cosebis.py +++ b/workflow/scripts/cv_cosebis.py @@ -27,8 +27,7 @@ version = p["version"] fiducial_scale_cut = tuple(p["fiducial_scale_cut"]) -part = sacc_io.load(snakemake.input["xi"]) -sacc_io.expect(part, p["blind"]) +part = sacc_io.load(snakemake.input["xi"], blind=p["blind"]) theta, xip, xim = sacc_io.get_xi(part, (0, 0), grid="integration") edges = log_bin_edges(p["min_sep"], p["max_sep"], p["nbins"]) diff --git a/workflow/scripts/cv_plot_pseudo_cl.py b/workflow/scripts/cv_plot_pseudo_cl.py index e993125d..dc1b32a1 100644 --- a/workflow/scripts/cv_plot_pseudo_cl.py +++ b/workflow/scripts/cv_plot_pseudo_cl.py @@ -18,8 +18,7 @@ spectra = {} for i, version in enumerate(p["versions"]): - part = sacc_io.load(snakemake.input["pseudo_cl"][i]) - sacc_io.expect(part, p["blinds"][i]) + part = sacc_io.load(snakemake.input["pseudo_cl"][i], blind=p["blinds"][i]) ell, ee, bb, eb, _window = sacc_io.get_pseudo_cl(part, (0, 0)) with fits.open(snakemake.input["pseudo_cl_cov"][i]) as hdul: covs = { diff --git a/workflow/scripts/cv_pure_eb.py b/workflow/scripts/cv_pure_eb.py index fea8c99f..25ee3f72 100644 --- a/workflow/scripts/cv_pure_eb.py +++ b/workflow/scripts/cv_pure_eb.py @@ -29,8 +29,7 @@ version = p["version"] fiducial_scale_cut = tuple(p["fiducial_scale_cut"]) -part = sacc_io.load(snakemake.input["xi_integration"]) -sacc_io.expect(part, p["blind"]) +part = sacc_io.load(snakemake.input["xi_integration"], blind=p["blind"]) gg_int = sacc_io.xi_correlation(part, grid="integration") # A part stores bin centres; the edges come from the grid it was measured on. grid = p["integration"] diff --git a/workflow/scripts/cv_runner.py b/workflow/scripts/cv_runner.py index 8bdbc2a5..b4ffcea2 100644 --- a/workflow/scripts/cv_runner.py +++ b/workflow/scripts/cv_runner.py @@ -41,8 +41,7 @@ def take_reporting_parts(cv, paths): from sp_validation import sacc_io for version, path in zip(cv.versions, paths, strict=True): - part = sacc_io.load(path) - sacc_io.expect(part, cv.blind(version)) + part = sacc_io.load(path, blind=cv.blind(version)) cv.xi_parts[version, "reporting"] = part diff --git a/workflow/scripts/cv_summarize_bmodes.py b/workflow/scripts/cv_summarize_bmodes.py index 8d1051f5..802de42c 100644 --- a/workflow/scripts/cv_summarize_bmodes.py +++ b/workflow/scripts/cv_summarize_bmodes.py @@ -48,8 +48,7 @@ if p["include_pseudo_cl"]: from astropy.io import fits - part = sacc_io.load(snakemake.input["pseudo_cl"][i]) - sacc_io.expect(part, p["blinds"][i]) + part = sacc_io.load(snakemake.input["pseudo_cl"][i], blind=p["blinds"][i]) _ell, _ee, bb, _eb, _window = sacc_io.get_pseudo_cl(part, (0, 0)) with fits.open(snakemake.input["pseudo_cl_cov"][i]) as hdul: cov_bb = np.asarray(hdul["COVAR_BB_BB"].data, float) diff --git a/workflow/tests/test_bmodes_dag.py b/workflow/tests/test_bmodes_dag.py new file mode 100644 index 00000000..89b7a61d --- /dev/null +++ b/workflow/tests/test_bmodes_dag.py @@ -0,0 +1,56 @@ +"""Paper readers accept catalogue-policy params under the host launcher.""" + +import shutil +from pathlib import Path + +import yaml +from conftest import REPO, VERSIONS, parse_jobs + + +def test_bmodes_readers_resolve(toy): + paper = toy.root / "papers" / "bmodes" + shutil.copytree(REPO / "papers" / "bmodes", paper, + ignore=shutil.ignore_patterns("results", ".snakemake", "__pycache__")) + shutil.copytree(toy.rundir / "results" / "cosmology", paper / "results" / "cosmology") + path = paper / "config" / "config.yaml" + config = yaml.safe_load(path.read_text()) + config["versions"] = list(VERSIONS) + fid = config["fiducial"] + fid.update(version=VERSIONS[1], mock_version=VERSIONS[0], + nz_realisations={"A": VERSIONS[1]}) + config["plotting"]["version_labels"] = {VERSIONS[1]: "Toy"} + path.write_text(yaml.safe_dump(config)) + + # Supply input parts/covariances: this tests ingestion rules, not measurement. + for version in VERSIONS: + paths = [toy.cosmo_val / ( + f"{version}_xi_minsep={fid['min_sep_int']}_maxsep={fid['max_sep_int']}" + f"_nbins={fid['nbins_int']}_npatch=1.sacc" + )] + paths.append(Path(toy.common.covariance_path( + version, "g", fid["min_sep_int"], fid["max_sep_int"], fid["nbins_int"], + "_masked", + ))) + for bins in (32, 96): + paths.extend([ + toy.cosmo_val / f"pseudo_cl_{version}_powspace_nbins={bins}.sacc", + toy.cosmo_val / f"pseudo_cl_cov_{version}_powspace_nbins={bins}.fits", + ]) + for item in paths: + item.parent.mkdir(parents=True, exist_ok=True) + item.touch() + + rules = ["cosebis_version_comparison", "cosebis_data_vector", "cl_data_vector", + "cl_version_comparison", "harmonic_space_pte_matrices", + "cosebis_filter_overlay"] + result = toy.snakemake( + "-n", *rules, + f"results/paper_plots/intermediate/{VERSIONS[1]}_pure_eb.npz", + f"results/tapestry/cosebis_pte_matrix/pte_values/{VERSIONS[1]}/pte_000_006.json", + "results/tapestry/harmonic_config_cosebis_comparison_full/evidence.json", + cwd=paper, + ) + assert result.returncode == 0, result.stdout + scheduled = {job.rule for job in parse_jobs(result.stdout)} + assert set(rules + ["pure_eb_modes", "compute_cosebis_pte", + "harmonic_config_cosebis_comparison"]) <= scheduled From be6cba7bc2f226a9466cebc547a0b685174ee640 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 24 Oct 2026 02:54:16 +0200 Subject: [PATCH 06/10] Observe actual CCL tracer pairs in the multi-tracer blind test --- src/sp_validation/tests/test_blinding.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/sp_validation/tests/test_blinding.py b/src/sp_validation/tests/test_blinding.py index ef7739bf..41e4140f 100644 --- a/src/sp_validation/tests/test_blinding.py +++ b/src/sp_validation/tests/test_blinding.py @@ -122,7 +122,8 @@ def test_smokescreen_draw_stays_in_envelope(tmp_path): assert np.all(hidden <= fid + envelope) -def test_multitracer_shift_matches_single_pair_theories(up): +def test_multitracer_shift_matches_single_pair_theories(up, monkeypatch): + import pyccl as ccl from smokescreen.datavector import draw_param_shifts record = up.record() @@ -143,7 +144,27 @@ def test_multitracer_shift_matches_single_pair_theories(up): s, pair, ell, 0 * ell, 0 * ell, 0 * ell, window_ells=ell, window_weights=np.eye(3), ) + # Observe CCL's actual inputs independently of blinding's pair selection. + make_tracer, angular_cl = ccl.WeakLensingTracer, ccl.angular_cl + nz_by_tracer, seen = {}, [] + + def tracer(*args, **kwargs): + result = make_tracer(*args, **kwargs) + nz_by_tracer[id(result)] = np.asarray(kwargs["dndz"][1]).copy() + return result + + def spectrum(cosmo, a, b, ell, **kwargs): + seen.append((nz_by_tracer[id(a)], nz_by_tracer[id(b)])) + return angular_cl(cosmo, a, b, ell, **kwargs) + + monkeypatch.setattr(ccl, "WeakLensingTracer", tracer) + monkeypatch.setattr(ccl, "angular_cl", spectrum) shifted = bd.conceal(s, up) + for a, b in pairs: + nz_a, nz_b = s.tracers[f"source_{a}"].nz, s.tracers[f"source_{b}"].nz + # Each pair reaches angular_cl at fiducial and hidden points, for ξ and Cℓ. + assert sum(np.array_equal(x, nz_a) and np.array_equal(y, nz_b) + for x, y in seen) == 4 for a, b in pairs: rows = s.indices(tracers=(f"source_{a}", f"source_{b}")) single = s.copy() From 622a136a12ff1cc737f620ee41cd991b73db32b1 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 24 Oct 2026 02:59:33 +0200 Subject: [PATCH 07/10] Reject hidden cosmology values in saved outputs and captured logs --- src/sp_validation/tests/test_blinding.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/sp_validation/tests/test_blinding.py b/src/sp_validation/tests/test_blinding.py index 41e4140f..481e0181 100644 --- a/src/sp_validation/tests/test_blinding.py +++ b/src/sp_validation/tests/test_blinding.py @@ -180,11 +180,23 @@ def test_saved_blind_is_secret_free(tmp_path, capfd): s = _small_xi() path = tmp_path / "sealed.sacc" blind = bd.init("secret-free", {"paths": {"blinds": str(tmp_path / "blinds")}}) - written = sio.save(s, path, blind=blind) - seed = blind.record()["seed"] + params_seen = [] + + def theory(params, vector): + params_seen.append(dict(params)) + return bd.shear(params, vector) + + written = sio.save(s, path, blind=blind, theory=theory) + record = blind.record() + hidden = next(p for p in params_seen if p != record["fiducial"]) captured = capfd.readouterr() - assert seed.encode() not in path.read_bytes() - assert seed not in captured.out + captured.err + secrets = [record["seed"]] + for key in ("S8", "Omega_m"): + value = hidden[key] + secrets.extend((repr(value), f"{value:.4f}", f"{value:.3f}")) + for secret in secrets: + assert secret.encode() not in path.read_bytes() + assert secret not in captured.out + captured.err assert written.metadata == {**s.metadata, "blind": blind.name} loaded = sio.load(path) assert loaded.metadata == written.metadata From 1e9c706b95dbb1252980deaf1da0fb93d982afbd Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 24 Oct 2026 03:11:34 +0200 Subject: [PATCH 08/10] Keep the blind gate to the cosmo_val path; Paper II readers stay as they were papers/bmodes runs only on public (blind: none) catalogues, so threading a blind through its readers and adding Snakemake adapters is out of scope here. Co-Authored-By: Claude Opus 5.5 --- papers/bmodes/rules/figures.smk | 12 ---- .../scripts/bb_covariance_nz_independence.py | 10 +--- papers/bmodes/scripts/cl_data_vector.py | 23 ++------ .../bmodes/scripts/cl_version_comparison.py | 16 +----- .../scripts/compute_cosebis_pte_single.py | 16 +----- papers/bmodes/scripts/cosebis_data_vector.py | 17 ++---- .../scripts/cosebis_version_comparison.py | 17 +----- .../harmonic_config_cosebis_comparison.py | 18 ++---- .../scripts/harmonic_space_pte_matrices.py | 20 +------ .../scripts/plot_cosebis_filter_overlay.py | 8 +-- papers/bmodes/scripts/pseudo_cl_io.py | 4 +- papers/bmodes/scripts/pure_eb_modes.py | 11 +--- .../tests/test_blind_consumers.py | 31 ---------- src/sp_validation/tests/test_sacc_writers.py | 5 +- workflow/tests/test_bmodes_dag.py | 56 ------------------- 15 files changed, 35 insertions(+), 229 deletions(-) delete mode 100644 workflow/tests/test_bmodes_dag.py diff --git a/papers/bmodes/rules/figures.smk b/papers/bmodes/rules/figures.smk index c7a0b4ee..9506b81e 100644 --- a/papers/bmodes/rules/figures.smk +++ b/papers/bmodes/rules/figures.smk @@ -138,8 +138,6 @@ rule cosebis_version_comparison: params: version_labels=VERSION_LABELS, versions=VERSIONS_LEAK_CORR, - blinds={v: blind_of(v) for v in VERSIONS_LEAK_CORR}, - cov_base_dir=str(COSMO_INFERENCE / "data/covariance"), output: evidence=f"{TAPESTRY_DIR}/cosebis_version_comparison/evidence.json", figure_stacked=f"{TAPESTRY_DIR}/cosebis_version_comparison/figure_stacked.png", @@ -165,7 +163,6 @@ rule cosebis_data_vector: **{f"cov_{ver}": _cov_integration_path(ver) for ver in VERSIONS_ALL_FOR_PLOTS}, params: cov_base_dir=str(COSMO_INFERENCE / "data/covariance"), - blinds={v: blind_of(v) for v in VERSIONS_ALL_FOR_PLOTS}, output: evidence=f"{TAPESTRY_DIR}/cosebis_data_vector/evidence.json", paper_figure=f"{PAPER_FIGURES_DIR}/cosebis_data_vector.pdf", @@ -187,7 +184,6 @@ rule pure_eb_modes: "results/paper_plots/intermediate/{version}_pure_eb.npz", params: **FIDUCIAL_BINNING, - blind=lambda w: blind_of(w.version), resources: mem_mb=8000, runtime=20, @@ -289,7 +285,6 @@ rule cl_data_vector: **{f"pseudo_cl_{ver}": _pseudo_cl_path(ver) for ver in VERSIONS_ALL_FOR_PLOTS}, **{f"pseudo_cl_cov_{ver}": _pseudo_cl_cov_path(ver) for ver in VERSIONS_ALL_FOR_PLOTS}, params: - blinds={v: blind_of(v) for v in VERSIONS_ALL_FOR_PLOTS}, ell_min_cut=config["cl"]["fiducial_ell_min"], ell_max_cut=config["cl"]["fiducial_ell_max"], output: @@ -310,7 +305,6 @@ rule cl_version_comparison: params: version_labels=VERSION_LABELS, versions=VERSIONS_LEAK_CORR, - blinds={v: blind_of(v) for v in VERSIONS_LEAK_CORR}, ell_min_cut=config["cl"]["fiducial_ell_min"], ell_max_cut=config["cl"]["fiducial_ell_max"], output: @@ -334,7 +328,6 @@ rule compute_cosebis_pte: pte_json=f"{TAPESTRY_DIR}/cosebis_pte_matrix/pte_values/{{version}}/pte_{{i_min}}_{{i_max}}.json", params: nmodes=FIDUCIAL["nmodes"], - blind=lambda w: blind_of(w.version), wildcard_constraints: i_min=r"\d{3}", i_max=r"\d{3}", @@ -394,7 +387,6 @@ rule harmonic_space_pte_matrices: pseudo_cl_cov=[_pseudo_cl_cov_path(ver) for ver in VERSIONS_CONFIG_SPACE_PTES], params: version_labels=VERSION_LABELS, - blinds={v: blind_of(v) for v in VERSIONS_CONFIG_SPACE_PTES}, output: evidence=f"{TAPESTRY_DIR}/harmonic_space_pte_matrices/evidence.json", figure_fiducial=f"{TAPESTRY_DIR}/harmonic_space_pte_matrices/figure_fiducial.png", @@ -432,7 +424,6 @@ rule bb_covariance_nz_independence: # Pseudo-Cl data vector for ell bin centers pseudo_cl=_pseudo_cl_path(MOCK_VERSION), params: - blind=blind_of(MOCK_VERSION), nmodes=FIDUCIAL["nmodes"], theta_min=config["cosebis"]["theta_min"], theta_max=config["cosebis"]["theta_max"], @@ -473,7 +464,6 @@ rule harmonic_config_cosebis_comparison: **{f"xi_{ver}": _xi_integration_path(ver) for ver in VERSIONS_ALL_FOR_PLOTS}, **{f"cov_{ver}": _cov_integration_path(ver) for ver in VERSIONS_ALL_FOR_PLOTS}, params: - blinds={v: blind_of(v) for v in VERSIONS_ALL_FOR_PLOTS}, scale_cut=lambda wildcards: _COSEBIS_ANGULAR_RANGES[wildcards.angular_range], output: evidence=f"{TAPESTRY_DIR}/harmonic_config_cosebis_comparison_{{angular_range}}/evidence.json", @@ -497,8 +487,6 @@ rule cosebis_filter_overlay: input: pseudo_cl=_pseudo_cl_path(FIDUCIAL_VERSION), pseudo_cl_cov=_pseudo_cl_cov_path(FIDUCIAL_VERSION), - params: - blind=blind_of(FIDUCIAL_VERSION), output: evidence=f"{TAPESTRY_DIR}/cosebis_filter_overlay/evidence.json", figure=f"{TAPESTRY_DIR}/cosebis_filter_overlay/figure.png", diff --git a/papers/bmodes/scripts/bb_covariance_nz_independence.py b/papers/bmodes/scripts/bb_covariance_nz_independence.py index a1b95d53..1e4cdb52 100644 --- a/papers/bmodes/scripts/bb_covariance_nz_independence.py +++ b/papers/bmodes/scripts/bb_covariance_nz_independence.py @@ -75,7 +75,6 @@ def load_cosebis_diagonals( min_sep_int, max_sep_int, nbins_int, - blind, ): """Compute COSEBIS covariance diagonals from config-space covariance. @@ -83,7 +82,7 @@ def load_cosebis_diagonals( Returns E_n and B_n covariance diagonals. """ # Load fine-binned 2PCF (need the binning info for COSEBIS calculation) - gg = sacc_io.xi_correlation(sacc_io.load(xi_integration_path, blind=blind)) + gg = sacc_io.xi_correlation(sacc_io.load(xi_integration_path)) # Compute COSEBIS with this realisation's covariance results = calculate_cosebis( @@ -248,7 +247,6 @@ def main( theta_max, figure_path, evidence_path, - blind, ): """Run the BB-covariance n(z)-independence cross-check. @@ -286,13 +284,12 @@ def main( min_sep_int, max_sep_int, nbins_int, - blind, ) for label, path in cov_integration_paths.items() } # Ell bin centers from the pseudo-Cl SACC part - ell_eff = load_pseudo_cl_data(pseudo_cl_path, blind)["ELL"] + ell_eff = load_pseudo_cl_data(pseudo_cl_path)["ELL"] def ratios_to_reference(data, modes): return { @@ -439,7 +436,6 @@ def _from_snakemake(smk): theta_max=smk.params.theta_max, figure_path=smk.output.figure, evidence_path=smk.output.evidence, - blind=smk.params["blind"], ) @@ -481,7 +477,6 @@ def _from_cli(argv=None): help="COSMO_INFERENCE data/covariance dir (Gaussian integration covariances)", ) ap.add_argument("--out", required=True, help="Output directory (lc {output})") - ap.add_argument("--blind", required=True) a = ap.parse_args(argv) with open(a.config) as f: @@ -535,7 +530,6 @@ def _from_cli(argv=None): theta_max=config["cosebis"]["theta_max"], figure_path=str(out_dir / "figure.png"), evidence_path=str(out_dir / "evidence.json"), - blind=a.blind, ) diff --git a/papers/bmodes/scripts/cl_data_vector.py b/papers/bmodes/scripts/cl_data_vector.py index 85c77f30..6576cd7c 100644 --- a/papers/bmodes/scripts/cl_data_vector.py +++ b/papers/bmodes/scripts/cl_data_vector.py @@ -44,9 +44,9 @@ def _compute_pte_with_cuts(data, covariance, ell, ell_min, ell_max): return pte, chi2, dof -def _load_pseudo_cl_data(pseudo_cl_path, pseudo_cl_cov_path, blind): +def _load_pseudo_cl_data(pseudo_cl_path, pseudo_cl_cov_path): """Load pseudo-Cl data from SACC and covariance from FITS.""" - data = load_pseudo_cl_data(pseudo_cl_path, blind) + data = load_pseudo_cl_data(pseudo_cl_path) ell = data["ELL"] cl_eb = data["EB"] @@ -203,8 +203,6 @@ def main( fiducial_version=None, fiducial_pseudo_cl_path=None, fiducial_pseudo_cl_cov_path=None, - *, - blinds, ): version = config["fiducial"]["version"] version_labels = config["plotting"]["version_labels"] @@ -234,7 +232,7 @@ def main( fiducial_pseudo_cl_cov_path, ) ell, cl_bb, cl_eb, cov_bb, cov_eb, sigma_bb, sigma_eb = _load_pseudo_cl_data( - pseudo_cl_path, pseudo_cl_cov_path, blinds[ver] + pseudo_cl_path, pseudo_cl_cov_path ) # Create figure with appropriate title @@ -273,7 +271,7 @@ def main( fiducial_pseudo_cl_cov_path, ) ell, cl_bb, cl_eb, cov_bb, cov_eb, sigma_bb, sigma_eb = _load_pseudo_cl_data( - pseudo_cl_path, pseudo_cl_cov_path, blinds[version] + pseudo_cl_path, pseudo_cl_cov_path ) # Compute PTEs (null tests) using full ell range @@ -360,8 +358,6 @@ def _from_cli(argv=None): "(lc output). Requires --fiducial-version and --fiducial-pseudo-cl-path." ), ) - ap.add_argument("--blinds", type=json.loads, required=True, - help="JSON mapping of catalogue versions to declared blinds") a = ap.parse_args(argv) with open(a.config) as f: config = yaml.safe_load(f) @@ -372,17 +368,8 @@ def _from_cli(argv=None): fiducial_version=a.fiducial_version, fiducial_pseudo_cl_path=a.fiducial_pseudo_cl_path, fiducial_pseudo_cl_cov_path=a.fiducial_pseudo_cl_cov_path, - blinds=a.blinds, ) if __name__ == "__main__": - if "snakemake" in globals(): - smk = snakemake # noqa: F821 - version = smk.config["fiducial"]["version"] - main( - smk.config, Path(smk.input[f"pseudo_cl_{version}"]).parent, - Path(smk.output["evidence"]).parent, blinds=smk.params["blinds"], - ) - else: - _from_cli() + _from_cli() diff --git a/papers/bmodes/scripts/cl_version_comparison.py b/papers/bmodes/scripts/cl_version_comparison.py index 4fba731f..d16659f9 100644 --- a/papers/bmodes/scripts/cl_version_comparison.py +++ b/papers/bmodes/scripts/cl_version_comparison.py @@ -76,8 +76,6 @@ def main( fiducial_version=None, fiducial_pseudo_cl_path=None, fiducial_pseudo_cl_cov_path=None, - *, - blinds, ): # Scale cuts (Guerrini et al. fiducial ell range) ell_min_cut = int(config["cl"]["fiducial_ell_min"]) @@ -130,7 +128,7 @@ def main( fiducial_pseudo_cl_cov_path, ) - data = load_pseudo_cl_data(pseudo_cl_path, blinds[version]) + data = load_pseudo_cl_data(pseudo_cl_path) ell = data["ELL"] cl_bb = data["BB"] cl_eb = data["EB"] @@ -405,8 +403,6 @@ def _from_cli(argv=None): "(lc output). Requires --fiducial-version and --fiducial-pseudo-cl-path." ), ) - ap.add_argument("--blinds", type=json.loads, required=True, - help="JSON mapping of catalogue versions to declared blinds") a = ap.parse_args(argv) with open(a.config) as f: config = yaml.safe_load(f) @@ -417,16 +413,8 @@ def _from_cli(argv=None): fiducial_version=a.fiducial_version, fiducial_pseudo_cl_path=a.fiducial_pseudo_cl_path, fiducial_pseudo_cl_cov_path=a.fiducial_pseudo_cl_cov_path, - blinds=a.blinds, ) if __name__ == "__main__": - if "snakemake" in globals(): - smk = snakemake # noqa: F821 - main( - smk.config, Path(smk.input["pseudo_cl"][0]).parent, - Path(smk.output["evidence"]).parent, blinds=smk.params["blinds"], - ) - else: - _from_cli() + _from_cli() diff --git a/papers/bmodes/scripts/compute_cosebis_pte_single.py b/papers/bmodes/scripts/compute_cosebis_pte_single.py index 6828e856..d80f9eee 100644 --- a/papers/bmodes/scripts/compute_cosebis_pte_single.py +++ b/papers/bmodes/scripts/compute_cosebis_pte_single.py @@ -79,7 +79,7 @@ def _compute_pair(gg, cov_path, nmodes, theta_min, theta_max): } -def main(config, xi_integration, cov_integration, out_dir, version=None, *, blind): +def main(config, xi_integration, cov_integration, out_dir, version=None): t_start = time.time() fid = config["fiducial"] # Version tags only the output filename + provenance record; the theta @@ -89,7 +89,7 @@ def main(config, xi_integration, cov_integration, out_dir, version=None, *, blin version = version if version is not None else fid["version"] nmodes = int(fid["nmodes"]) # 20 for full computation - gg = sacc_io.xi_correlation(sacc_io.load(xi_integration, blind=blind)) + gg = sacc_io.xi_correlation(sacc_io.load(xi_integration)) # Reporting theta grid (nbins+1 = 21 edges): geomspace(1', 250', 21) theta_grid = np.geomspace(fid["min_sep"], fid["max_sep"], fid["nbins"] + 1) @@ -183,7 +183,6 @@ def _from_cli(argv=None): required=True, help="Fiducial 1000-bin Gaussian covariance (processed .txt)", ) - ap.add_argument("--blind", required=True) ap.add_argument("--out", required=True, help="Output directory (lc {output})") ap.add_argument( "--version", @@ -200,17 +199,8 @@ def _from_cli(argv=None): a.cov_integration, a.out, version=a.version, - blind=a.blind, ) if __name__ == "__main__": - if "snakemake" in globals(): - smk = snakemake # noqa: F821 - main( - smk.config, smk.input["xi_integration"], smk.input["cov_integration"], - Path(smk.output["pte_json"]).parent, version=smk.wildcards.version, - blind=smk.params["blind"], - ) - else: - _from_cli() + _from_cli() diff --git a/papers/bmodes/scripts/cosebis_data_vector.py b/papers/bmodes/scripts/cosebis_data_vector.py index 0737d7f7..ea431569 100644 --- a/papers/bmodes/scripts/cosebis_data_vector.py +++ b/papers/bmodes/scripts/cosebis_data_vector.py @@ -135,7 +135,7 @@ def _create_single_panel_bmode_figure(datasets, nmodes, scale_cuts, title=None): return fig -def main(config, xi_integration, cov_integration, out_dir, *, blind): +def main(config, xi_integration, cov_integration, out_dir): version = config["fiducial"]["version"] nmodes = config["fiducial"]["nmodes"] @@ -157,7 +157,7 @@ def main(config, xi_integration, cov_integration, out_dir, *, blind): out_dir.mkdir(parents=True, exist_ok=True) # Load the fiducial fine-binned 2PCF once (integration grid) - gg = sacc_io.xi_correlation(sacc_io.load(xi_integration, blind=blind)) + gg = sacc_io.xi_correlation(sacc_io.load(xi_integration)) # Compute the E_n / B_n data vectors + T^T C_xi T mode covariance at both # scale cuts. Same calculate_cosebis call as the original per-version loop. @@ -232,21 +232,12 @@ def _from_cli(argv=None): required=True, help="Fiducial 1000-bin Gaussian covariance (processed .txt) for the T^T C_xi T transform", ) - ap.add_argument("--blind", required=True) ap.add_argument("--out", required=True, help="Output directory (lc {output})") a = ap.parse_args(argv) with open(a.config) as f: config = yaml.safe_load(f) - main(config, a.xi_integration, a.cov_integration, a.out, blind=a.blind) + main(config, a.xi_integration, a.cov_integration, a.out) if __name__ == "__main__": - if "snakemake" in globals(): - smk = snakemake # noqa: F821 - version = smk.config["fiducial"]["version"] - main( - smk.config, smk.input[f"xi_{version}"], smk.input[f"cov_{version}"], - Path(smk.output["evidence"]).parent, blind=smk.params["blinds"][version], - ) - else: - _from_cli() + _from_cli() diff --git a/papers/bmodes/scripts/cosebis_version_comparison.py b/papers/bmodes/scripts/cosebis_version_comparison.py index 46f5395d..eca3deaa 100644 --- a/papers/bmodes/scripts/cosebis_version_comparison.py +++ b/papers/bmodes/scripts/cosebis_version_comparison.py @@ -164,8 +164,6 @@ def main( fiducial_version=None, fiducial_xi_path=None, fiducial_cov_path=None, - *, - blinds, ): # Leak-corrected versions (matches VERSIONS_LEAK_CORR in figures.smk) versions = [v for v in config["versions"] if "_leak_corr" in v and "_ecut" not in v] @@ -249,7 +247,7 @@ def main( marker = MARKER_STYLES[i] if i < len(MARKER_STYLES) else "o" fillstyle = "full" - gg = sacc_io.xi_correlation(sacc_io.load(xi_path, blind=blinds[version])) + gg = sacc_io.xi_correlation(sacc_io.load(xi_path)) results = calculate_cosebis( gg, @@ -386,8 +384,6 @@ def _from_cli(argv=None): help="Explicit path to the fiducial version's Gaussian integration " "covariance (e.g. lc output), overriding the canonical-tree path pattern", ) - ap.add_argument("--blinds", type=json.loads, required=True, - help="JSON mapping of catalogue versions to declared blinds") a = ap.parse_args(argv) with open(a.config) as f: config = yaml.safe_load(f) @@ -399,17 +395,8 @@ def _from_cli(argv=None): fiducial_version=a.fiducial_version, fiducial_xi_path=a.fiducial_xi_path, fiducial_cov_path=a.fiducial_cov_path, - blinds=a.blinds, ) if __name__ == "__main__": - if "snakemake" in globals(): - smk = snakemake # noqa: F821 - main( - smk.config, Path(smk.input["xi_integration"][0]).parent, - smk.params["cov_base_dir"], Path(smk.output["evidence"]).parent, - blinds=smk.params["blinds"], - ) - else: - _from_cli() + _from_cli() diff --git a/papers/bmodes/scripts/harmonic_config_cosebis_comparison.py b/papers/bmodes/scripts/harmonic_config_cosebis_comparison.py index f36490bc..39cadec3 100644 --- a/papers/bmodes/scripts/harmonic_config_cosebis_comparison.py +++ b/papers/bmodes/scripts/harmonic_config_cosebis_comparison.py @@ -105,10 +105,10 @@ def _build_transforms(cosebis_obj, ell): def _compute_harmonic_cosebis( - pseudo_cl_path, pseudo_cov_path, nmodes, theta_min, theta_max, blind + pseudo_cl_path, pseudo_cov_path, nmodes, theta_min, theta_max ): """Compute COSEBIS from pseudo-C_ell and propagate covariance.""" - data = load_pseudo_cl_data(pseudo_cl_path, blind) + data = load_pseudo_cl_data(pseudo_cl_path) ell = np.asarray(data["ELL"], dtype=float) cl_ee = np.asarray(data["EE"], dtype=float) cl_bb = np.asarray(data["BB"], dtype=float) @@ -134,12 +134,12 @@ def _compute_harmonic_cosebis( return ce_harm, cb_harm, cov_harmonic -def _compute_config_cosebis(xi_path, cov_path, nmodes, scale_cut, config, blind): +def _compute_config_cosebis(xi_path, cov_path, nmodes, scale_cut, config): """Compute COSEBIS from fine-binned xi_pm for a single scale cut. Returns (En, Bn, cov). """ - gg = sacc_io.xi_correlation(sacc_io.load(xi_path, blind=blind)) + gg = sacc_io.xi_correlation(sacc_io.load(xi_path)) results = calculate_cosebis( gg, nmodes=nmodes, scale_cuts=[scale_cut], cov_path=cov_path @@ -500,7 +500,7 @@ def _make_version_comparison_figure( return fig -def main(config, inputs, scale_cut, output_dir, paper_figure_name=None, *, blinds): +def main(config, inputs, scale_cut, output_dir, paper_figure_name=None): """Harmonic-vs-config COSEBI cross-check for one angular range. ``inputs`` is a dict mirroring ``snakemake.input``: per-version keys @@ -571,7 +571,6 @@ def main(config, inputs, scale_cut, output_dir, paper_figure_name=None, *, blind nmodes, scale_cut, config, - blinds[ver], ) # Harmonic-space @@ -581,7 +580,6 @@ def main(config, inputs, scale_cut, output_dir, paper_figure_name=None, *, blind nmodes, scale_cut[0], scale_cut[1], - blinds[ver], ) harm = { "En": ce_h, @@ -630,7 +628,6 @@ def main(config, inputs, scale_cut, output_dir, paper_figure_name=None, *, blind nmodes, other_cut, config, - blinds[ver], ) ce_h_other, cb_h_other, cov_h_other = _compute_harmonic_cosebis( pseudo_cl_paths[pseudo_cl_key], @@ -638,7 +635,6 @@ def main(config, inputs, scale_cut, output_dir, paper_figure_name=None, *, blind nmodes, other_cut[0], other_cut[1], - blinds[ver], ) harm_other = { "En": ce_h_other, @@ -823,7 +819,6 @@ def _from_snakemake(smk): config=smk.config, inputs=dict(smk.input.items()), scale_cut=tuple(smk.params.scale_cut), - blinds=smk.params["blinds"], output_dir=Path(smk.output["evidence"]).parent, paper_figure_name=( Path(smk.output["paper_figure"]).name @@ -901,8 +896,6 @@ def _from_cli(argv=None): "input always reads from the old tree, even for the fiducial version." ), ) - ap.add_argument("--blinds", type=json.loads, required=True, - help="JSON mapping of catalogue versions to declared blinds") a = ap.parse_args(argv) with open(a.config) as f: @@ -963,7 +956,6 @@ def _from_cli(argv=None): scale_cut=scale_cut, output_dir=a.out, paper_figure_name=f"harmonic_config_cosebis_{a.angular_range}.pdf", - blinds=a.blinds, ) diff --git a/papers/bmodes/scripts/harmonic_space_pte_matrices.py b/papers/bmodes/scripts/harmonic_space_pte_matrices.py index 46c0b3e8..577c561f 100644 --- a/papers/bmodes/scripts/harmonic_space_pte_matrices.py +++ b/papers/bmodes/scripts/harmonic_space_pte_matrices.py @@ -38,8 +38,7 @@ def _pseudo_cl_cov(results_dir, ver, nbins=32): def compute_pte_matrix( - pseudo_cl_path, pseudo_cl_cov_path, fiducial_ell_min=None, fiducial_ell_max=None, - *, blind, + pseudo_cl_path, pseudo_cl_cov_path, fiducial_ell_min=None, fiducial_ell_max=None ): """Compute PTE matrix for all ell-bin cut combinations. @@ -64,7 +63,7 @@ def compute_pte_matrix( Summary statistics. """ # Load pseudo-Cl data - data = load_pseudo_cl_data(pseudo_cl_path, blind) + data = load_pseudo_cl_data(pseudo_cl_path) ell = data["ELL"] cl_bb = data["BB"] n_ell = len(ell) @@ -349,8 +348,6 @@ def main( fiducial_version_override=None, fiducial_pseudo_cl_path=None, fiducial_pseudo_cl_cov_path=None, - *, - blinds, ): versions = [v for v in config["versions"] if "_ecut" not in v] fiducial_version = config["fiducial"]["version"] @@ -407,7 +404,6 @@ def main( version_to_cov[version], fiducial_ell_min=fiducial_ell_min, fiducial_ell_max=fiducial_ell_max, - blind=blinds[version], ) all_stats[version] = stats @@ -549,8 +545,6 @@ def _from_cli(argv=None): help="Explicit path to fiducial pseudo-Cl covariance FITS produced by lc " "(overrides pattern reconstruction for --fiducial-version)", ) - ap.add_argument("--blinds", type=json.loads, required=True, - help="JSON mapping of catalogue versions to declared blinds") a = ap.parse_args(argv) with open(a.config) as f: config = yaml.safe_load(f) @@ -561,16 +555,8 @@ def _from_cli(argv=None): fiducial_version_override=a.fiducial_version, fiducial_pseudo_cl_path=a.fiducial_pseudo_cl_path, fiducial_pseudo_cl_cov_path=a.fiducial_pseudo_cl_cov_path, - blinds=a.blinds, ) if __name__ == "__main__": - if "snakemake" in globals(): - smk = snakemake # noqa: F821 - main( - smk.config, Path(smk.input["pseudo_cl"][0]).parent, - Path(smk.output["evidence"]).parent, blinds=smk.params["blinds"], - ) - else: - _from_cli() + _from_cli() diff --git a/papers/bmodes/scripts/plot_cosebis_filter_overlay.py b/papers/bmodes/scripts/plot_cosebis_filter_overlay.py index 04cd485a..5f071bb6 100644 --- a/papers/bmodes/scripts/plot_cosebis_filter_overlay.py +++ b/papers/bmodes/scripts/plot_cosebis_filter_overlay.py @@ -26,9 +26,9 @@ plt.style.use(PAPER_MPLSTYLE) -def load_bb_data(pseudo_cl_path, pseudo_cov_path, blind): +def load_bb_data(pseudo_cl_path, pseudo_cov_path): """Load BB bandpower data and errorbars.""" - data = load_pseudo_cl_data(pseudo_cl_path, blind) + data = load_pseudo_cl_data(pseudo_cl_path) ell = np.asarray(data["ELL"], dtype=float) bb = np.asarray(data["BB"], dtype=float) @@ -170,9 +170,7 @@ def main(): theta_max_fid = config["fiducial"]["fiducial_max_scale"] nmodes = 6 - ell_32, bb, sigma_bb = load_bb_data( - pseudo_cl_path, pseudo_cov_path, snakemake.params["blind"] # noqa: F821 - ) + ell_32, bb, sigma_bb = load_bb_data(pseudo_cl_path, pseudo_cov_path) ell_dense = np.logspace(np.log10(8), np.log10(2500), 2000) diff --git a/papers/bmodes/scripts/pseudo_cl_io.py b/papers/bmodes/scripts/pseudo_cl_io.py index a242a779..b9497105 100644 --- a/papers/bmodes/scripts/pseudo_cl_io.py +++ b/papers/bmodes/scripts/pseudo_cl_io.py @@ -3,12 +3,12 @@ from sp_validation import sacc_io -def load_pseudo_cl_data(path, blind): +def load_pseudo_cl_data(path): """Return a pseudo-Cℓ part's spectra as ``{"ELL", "EE", "EB", "BB"}`` arrays. The bandpower covariance is a separate FITS product (``pseudo_cl_cov``). """ - ell, ee, bb, eb, _window = sacc_io.get_pseudo_cl(sacc_io.load(str(path), blind=blind), (0, 0)) + ell, ee, bb, eb, _window = sacc_io.get_pseudo_cl(sacc_io.load(str(path)), (0, 0)) missing = [name for name, values in (("BB", bb), ("EB", eb)) if values is None] if missing: raise ValueError( diff --git a/papers/bmodes/scripts/pure_eb_modes.py b/papers/bmodes/scripts/pure_eb_modes.py index 57ac71fb..a9c0898d 100644 --- a/papers/bmodes/scripts/pure_eb_modes.py +++ b/papers/bmodes/scripts/pure_eb_modes.py @@ -25,9 +25,9 @@ from sp_validation.sacc_io import PURE_KEYS -def _load_xi(path, blind): +def _load_xi(path): """``(meanr, xip, xim, weight)`` from a ξ± SACC part.""" - gg = sacc_io.xi_correlation(sacc_io.load(path, blind=blind)) + gg = sacc_io.xi_correlation(sacc_io.load(path)) return gg.meanr, gg.xip, gg.xim, gg.weight @@ -41,11 +41,9 @@ def pure_eb_modes( max_sep_int, nbins_int, out_path, - *, - blind, ): results = calculate_pure_eb_correlation( - *_load_xi(xi_integration, blind), + *_load_xi(xi_integration), np.geomspace(min_sep_int, max_sep_int, nbins_int + 1), np.loadtxt(cov_integration), np.geomspace(min_sep, max_sep, nbins + 1), @@ -78,13 +76,11 @@ def _from_snakemake(smk): p["max_sep_int"], p["nbins_int"], smk.output[0], - blind=p["blind"], ) def _from_cli(argv=None): ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) - ap.add_argument("--blind", required=True) ap.add_argument("--xi-integration", required=True) ap.add_argument("--cov-integration", required=True) ap.add_argument("--min-sep", type=float, default=1.0) @@ -105,7 +101,6 @@ def _from_cli(argv=None): a.max_sep_int, a.nbins_int, a.out, - blind=a.blind, ) diff --git a/src/sp_validation/tests/test_blind_consumers.py b/src/sp_validation/tests/test_blind_consumers.py index 335e4d90..dcf2a8f1 100644 --- a/src/sp_validation/tests/test_blind_consumers.py +++ b/src/sp_validation/tests/test_blind_consumers.py @@ -80,34 +80,3 @@ def test_load_checks_expected_blind(part_path, as_name, stamp): sacc_io.load(part_path, blind=expected) else: assert sacc_io.stamp(sacc_io.load(part_path, blind=expected)) == "toy" - - -@pytest.mark.parametrize("script, function, args", [ - ("pure_eb_modes.py", "_load_xi", ("xi", "toy")), - ("cl_data_vector.py", "_load_pseudo_cl_data", ("cl", "cov", "toy")), - ("pseudo_cl_io.py", "load_pseudo_cl_data", ("cl", "toy")), - ("plot_cosebis_filter_overlay.py", "load_bb_data", ("cl", "cov", "toy")), - ("harmonic_space_pte_matrices.py", "compute_pte_matrix", ("cl", "cov")), - ("harmonic_config_cosebis_comparison.py", "_compute_harmonic_cosebis", - ("cl", "cov", 6, 1, 100, "toy")), - ("harmonic_config_cosebis_comparison.py", "_compute_config_cosebis", - ("xi", "cov", 6, (1, 100), {}, "toy")), - ("bb_covariance_nz_independence.py", "load_cosebis_diagonals", - ("xi", "cov", 6, 1, 100, 0.1, 300, 100, "toy")), -]) -def test_paper_readers_check_before_signal( - part_path, monkeypatch, script, function, args -): - paper_scripts = SCRIPTS.parents[1] / "papers" / "bmodes" / "scripts" - monkeypatch.syspath_prepend(str(paper_scripts)) - module = runpy.run_path(str(paper_scripts / script)) - load = sacc_io.load - - def load_input(path, blind=None): - assert blind == "toy" - return load(part_path, blind=blind) - - monkeypatch.setattr(sacc_io, "load", load_input) - kwargs = {"blind": "toy"} if function == "compute_pte_matrix" else {} - with pytest.raises(ValueError, match="stamped none.*toy"): - module[function](*args, **kwargs) diff --git a/src/sp_validation/tests/test_sacc_writers.py b/src/sp_validation/tests/test_sacc_writers.py index 8823ceda..a3eb6618 100644 --- a/src/sp_validation/tests/test_sacc_writers.py +++ b/src/sp_validation/tests/test_sacc_writers.py @@ -146,10 +146,7 @@ def get_bandpower_windows(self): path = tmp_path / "pseudo_cl.sacc" sio.save(part, str(path), blind=NONE) - reader = _paper_pseudo_cl_reader() - with pytest.raises(ValueError, match="stamped none.*toy"): - reader(path, "toy") - data = reader(path, "none") + data = _paper_pseudo_cl_reader()(path) assert np.array_equal(data["ELL"], ell) assert np.array_equal(data["EE"], cl_all[0]) assert np.array_equal(data["EB"], cl_all[1]) diff --git a/workflow/tests/test_bmodes_dag.py b/workflow/tests/test_bmodes_dag.py deleted file mode 100644 index 89b7a61d..00000000 --- a/workflow/tests/test_bmodes_dag.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Paper readers accept catalogue-policy params under the host launcher.""" - -import shutil -from pathlib import Path - -import yaml -from conftest import REPO, VERSIONS, parse_jobs - - -def test_bmodes_readers_resolve(toy): - paper = toy.root / "papers" / "bmodes" - shutil.copytree(REPO / "papers" / "bmodes", paper, - ignore=shutil.ignore_patterns("results", ".snakemake", "__pycache__")) - shutil.copytree(toy.rundir / "results" / "cosmology", paper / "results" / "cosmology") - path = paper / "config" / "config.yaml" - config = yaml.safe_load(path.read_text()) - config["versions"] = list(VERSIONS) - fid = config["fiducial"] - fid.update(version=VERSIONS[1], mock_version=VERSIONS[0], - nz_realisations={"A": VERSIONS[1]}) - config["plotting"]["version_labels"] = {VERSIONS[1]: "Toy"} - path.write_text(yaml.safe_dump(config)) - - # Supply input parts/covariances: this tests ingestion rules, not measurement. - for version in VERSIONS: - paths = [toy.cosmo_val / ( - f"{version}_xi_minsep={fid['min_sep_int']}_maxsep={fid['max_sep_int']}" - f"_nbins={fid['nbins_int']}_npatch=1.sacc" - )] - paths.append(Path(toy.common.covariance_path( - version, "g", fid["min_sep_int"], fid["max_sep_int"], fid["nbins_int"], - "_masked", - ))) - for bins in (32, 96): - paths.extend([ - toy.cosmo_val / f"pseudo_cl_{version}_powspace_nbins={bins}.sacc", - toy.cosmo_val / f"pseudo_cl_cov_{version}_powspace_nbins={bins}.fits", - ]) - for item in paths: - item.parent.mkdir(parents=True, exist_ok=True) - item.touch() - - rules = ["cosebis_version_comparison", "cosebis_data_vector", "cl_data_vector", - "cl_version_comparison", "harmonic_space_pte_matrices", - "cosebis_filter_overlay"] - result = toy.snakemake( - "-n", *rules, - f"results/paper_plots/intermediate/{VERSIONS[1]}_pure_eb.npz", - f"results/tapestry/cosebis_pte_matrix/pte_values/{VERSIONS[1]}/pte_000_006.json", - "results/tapestry/harmonic_config_cosebis_comparison_full/evidence.json", - cwd=paper, - ) - assert result.returncode == 0, result.stdout - scheduled = {job.rule for job in parse_jobs(result.stdout)} - assert set(rules + ["pure_eb_modes", "compute_cosebis_pte", - "harmonic_config_cosebis_comparison"]) <= scheduled From 4fbfa8d6f0c0a4b03ba4be9ae7d3d4f35e743fa3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 24 Oct 2026 01:12:34 +0000 Subject: [PATCH 09/10] ruff autofix (format + safe lint fixes) Pushed by the lint gate. --- .../tests/test_blind_consumers.py | 11 +++--- src/sp_validation/tests/test_blinding.py | 35 +++++++++++++------ src/sp_validation/tests/test_cosmo_val.py | 4 ++- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/sp_validation/tests/test_blind_consumers.py b/src/sp_validation/tests/test_blind_consumers.py index dcf2a8f1..3d3b7a8d 100644 --- a/src/sp_validation/tests/test_blind_consumers.py +++ b/src/sp_validation/tests/test_blind_consumers.py @@ -37,10 +37,13 @@ def test_reporting_parts(part_path, monkeypatch, expected): assert sacc_io.stamp(cv.xi_parts["v", "reporting"]) == "none" -@pytest.mark.parametrize("script, input_key, reader", [ - ("cv_cosebis.py", "xi", "get_xi"), - ("cv_pure_eb.py", "xi_integration", "xi_correlation"), -]) +@pytest.mark.parametrize( + "script, input_key, reader", + [ + ("cv_cosebis.py", "xi", "get_xi"), + ("cv_pure_eb.py", "xi_integration", "xi_correlation"), + ], +) @pytest.mark.parametrize("expected", ["none", "toy"]) def test_derived_script_checks_before_reading_signal( part_path, monkeypatch, script, input_key, reader, expected diff --git a/src/sp_validation/tests/test_blinding.py b/src/sp_validation/tests/test_blinding.py index 481e0181..79f5647c 100644 --- a/src/sp_validation/tests/test_blinding.py +++ b/src/sp_validation/tests/test_blinding.py @@ -114,7 +114,9 @@ def test_smokescreen_draw_stays_in_envelope(tmp_path): for seed in [record["seed"], *range(1000)]: # An identity theory observes the actual draw, without cosmology work. delta = concealing_factor( - record["fiducial"], record["envelope"], seed=seed, + record["fiducial"], + record["envelope"], + seed=seed, theory_fn=lambda p: np.array([p[k] for k in keys]), ) hidden = fid + delta @@ -131,18 +133,26 @@ def test_multitracer_shift_matches_single_pair_theories(up, monkeypatch): fid = record["fiducial"] hidden = {k: value + shifts.get(k, 0.0) for k, value in fid.items()} z = np.linspace(0.0, 3.0, 100) - s = sio.new_sacc({ - i: (z, np.exp(-0.5 * ((z - centre) / 0.2) ** 2)) - for i, centre in enumerate((0.5, 1.1)) - }) + s = sio.new_sacc( + { + i: (z, np.exp(-0.5 * ((z - centre) / 0.2) ** 2)) + for i, centre in enumerate((0.5, 1.1)) + } + ) pairs = ((0, 0), (0, 1), (1, 1)) for pair in pairs: theta = np.array([10.0, 50.0]) sio.add_xi(s, pair, theta, 0 * theta, 0 * theta, grid="reporting") ell = np.array([50.0, 100.0, 200.0]) sio.add_pseudo_cl( - s, pair, ell, 0 * ell, 0 * ell, 0 * ell, - window_ells=ell, window_weights=np.eye(3), + s, + pair, + ell, + 0 * ell, + 0 * ell, + 0 * ell, + window_ells=ell, + window_weights=np.eye(3), ) # Observe CCL's actual inputs independently of blinding's pair selection. make_tracer, angular_cl = ccl.WeakLensingTracer, ccl.angular_cl @@ -163,15 +173,18 @@ def spectrum(cosmo, a, b, ell, **kwargs): for a, b in pairs: nz_a, nz_b = s.tracers[f"source_{a}"].nz, s.tracers[f"source_{b}"].nz # Each pair reaches angular_cl at fiducial and hidden points, for ξ and Cℓ. - assert sum(np.array_equal(x, nz_a) and np.array_equal(y, nz_b) - for x, y in seen) == 4 + assert ( + sum(np.array_equal(x, nz_a) and np.array_equal(y, nz_b) for x, y in seen) + == 4 + ) for a, b in pairs: rows = s.indices(tracers=(f"source_{a}", f"source_{b}")) single = s.copy() single.keep_indices(rows) expected = bd.shear(hidden, single) - bd.shear(fid, single) - np.testing.assert_allclose(shifted.mean[rows] - s.mean[rows], expected, - rtol=1e-12, atol=0) + np.testing.assert_allclose( + shifted.mean[rows] - s.mean[rows], expected, rtol=1e-12, atol=0 + ) for kind in (sio.XI_PLUS, sio.XI_MINUS, sio.CL_EE): assert np.any(expected[single.indices(kind)] != 0) diff --git a/src/sp_validation/tests/test_cosmo_val.py b/src/sp_validation/tests/test_cosmo_val.py index 426f4a86..9d4ca719 100644 --- a/src/sp_validation/tests/test_cosmo_val.py +++ b/src/sp_validation/tests/test_cosmo_val.py @@ -679,7 +679,9 @@ def test_held_xi_matches_declared_blind(self, tmp_path, stamp): cv.calculate_2pcf_version(version) else: result = cv.calculate_2pcf_version(version) - np.testing.assert_array_equal(result["tomo_bin_all_tomo_bin_all"].xip, [1.0]) + np.testing.assert_array_equal( + result["tomo_bin_all_tomo_bin_all"].xip, [1.0] + ) def test_treecorr_runs_on_the_cpus_the_process_holds(self, tmp_path): """By default TreeCorr takes the process's CPU affinity, not the node's count.""" From 5c5e7a34dd3690e01e4e642a0bc540cf2eaf9b56 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Sat, 24 Oct 2026 17:02:01 +0200 Subject: [PATCH 10/10] Run CI on the lint autofix head The lint gate's autofix commit is pushed with GITHUB_TOKEN, which triggers no workflows. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ay8NWFocUojrbRVfYoC5nH