From 63cc6e013b59e15274d374cc1bfa7c65adee414a Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Tue, 29 Sep 2026 11:11:15 +0200 Subject: [PATCH] =?UTF-8?q?Map-based=20pseudo-C=E2=84=93=20bandpower=20win?= =?UTF-8?q?dows=20include=20the=20pixel=20window=20pw=C2=B2(=E2=84=93)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bandpower_window_from_workspace(wsp, nside=) multiplies NaMaster's decoupling window by pw²(ℓ) for a spectrum of HEALPix maps at nside, so the SACC window maps a theory C_ℓ to the measured bandpower. The map-based cosmo_val path passes its nside through pseudo_cl_to_sacc_part and pseudo_cl_to_sacc; the catalogue-based path has no pixel window and passes none. With the blinding PR (#253) the Cℓ_EE shift is computed through the SACC window as written. On its own that is consistent (data and shift share the window); once both land, this fix corrects the data and the shift together. Co-Authored-By: Claude Opus 5.5 --- src/sp_validation/cosmo_val/pseudo_cl.py | 12 ++++++++---- src/sp_validation/cosmo_val/sacc_writers.py | 9 +++++---- src/sp_validation/pseudo_cl.py | 9 +++++++-- src/sp_validation/tests/test_sacc_writers.py | 10 ++++++++++ 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/sp_validation/cosmo_val/pseudo_cl.py b/src/sp_validation/cosmo_val/pseudo_cl.py index cf74efc6..eeffddaf 100644 --- a/src/sp_validation/cosmo_val/pseudo_cl.py +++ b/src/sp_validation/cosmo_val/pseudo_cl.py @@ -610,7 +610,7 @@ def calculate_pseudo_cl_map(self, ver, nside, out_path): cl_shear = cl_shear - cl_noise self.print_cyan("Saving pseudo-Cl's...") - self.pseudo_cl_to_sacc_part(ver, out_path, ell_eff, cl_shear, wsp) + self.pseudo_cl_to_sacc_part(ver, out_path, ell_eff, cl_shear, wsp, nside=nside) self._pseudo_cls[ver]["pseudo_cl"] = self._load_pseudo_cl_sacc(out_path) @@ -716,12 +716,15 @@ def apply_random_rotation(self, e1, e2, rng=None): """ return apply_random_rotation(e1, e2, rng) - def pseudo_cl_to_sacc_part(self, version, out_path, ell_eff, cl_all, wsp): + def pseudo_cl_to_sacc_part( + self, version, out_path, ell_eff, cl_all, wsp, nside=None + ): """Write the pseudo-Cl SACC part (EE/BB/EB + shared bandpower window). ``cl_all`` is NaMaster's decoupled ``(4, nbp)`` array (EE, EB, BE, BB); - the writer takes the shared bandpower window from ``wsp``. No covariance - is attached here. + the writer takes the shared bandpower window from ``wsp``, with the + pixel window of maps at ``nside`` for a map-based spectrum. No + covariance is attached here. """ s = pseudo_cl_to_sacc( self.sacc_nz(version), @@ -729,6 +732,7 @@ def pseudo_cl_to_sacc_part(self, version, out_path, ell_eff, cl_all, wsp): ell_eff, cl_all, wsp, + nside=nside, ) sacc_io.save(s, out_path, type="data") diff --git a/src/sp_validation/cosmo_val/sacc_writers.py b/src/sp_validation/cosmo_val/sacc_writers.py index 8b7f57b7..5976fe0a 100644 --- a/src/sp_validation/cosmo_val/sacc_writers.py +++ b/src/sp_validation/cosmo_val/sacc_writers.py @@ -68,14 +68,15 @@ def xi_to_sacc( return s -def pseudo_cl_to_sacc(nz, metadata, ell_eff, cl_all, wsp, covariance=None): +def pseudo_cl_to_sacc(nz, metadata, ell_eff, cl_all, wsp, covariance=None, nside=None): """One pseudo-Cℓ part: EE/BB/EB with the shared bandpower window. ``cl_all`` is NaMaster's decoupled ``(4, nbp)`` array (EE, EB, BE, BB); the - window comes from :func:`bandpower_window_from_workspace`. ``covariance``, - when given, is the dense ``[EE; BB; EB]``-ordered block matching insertion. + window comes from :func:`bandpower_window_from_workspace`, with the pixel + window of maps at ``nside`` when given. ``covariance``, when given, is the + dense ``[EE; BB; EB]``-ordered block matching insertion. """ - window_ells, window_weights = bandpower_window_from_workspace(wsp) + window_ells, window_weights = bandpower_window_from_workspace(wsp, nside) s = sio.new_sacc(nz, metadata) sio.add_pseudo_cl( s, diff --git a/src/sp_validation/pseudo_cl.py b/src/sp_validation/pseudo_cl.py index 34cbc68d..bdcc8d67 100644 --- a/src/sp_validation/pseudo_cl.py +++ b/src/sp_validation/pseudo_cl.py @@ -286,7 +286,7 @@ def get_pseudo_cls_catalog( _NMT_EE = 0 -def bandpower_window_from_workspace(wsp): +def bandpower_window_from_workspace(wsp, nside=None): """Extract the bandpower window matrix ``W`` for a spin-2×spin-2 workspace. NaMaster's ``get_bandpower_windows()`` returns a four-index array @@ -303,9 +303,14 @@ def bandpower_window_from_workspace(wsp): ``compute_coupled_cell``. window_weights : np.ndarray ``W`` of shape ``(n_ell, n_bpw)`` — one column per bandpower, the layout - :func:`sp_validation.sacc_io.add_pseudo_cl` expects. + :func:`sp_validation.sacc_io.add_pseudo_cl` expects. For a spectrum of + HEALPix maps at ``nside``, ``W`` includes the pixel window pw²(ℓ), so it + maps a C_ℓ to the measured bandpower. """ bpw = wsp.get_bandpower_windows() # (n_cl_out, n_bpw, n_cl_in, n_ell) diagonal = bpw[_NMT_EE, :, _NMT_EE, :] # (n_bpw, n_ell) window_ells = np.arange(diagonal.shape[1], dtype=float) + if nside is not None: + pixwin = hp.pixwin(nside, lmax=diagonal.shape[1] - 1) + diagonal = diagonal * pixwin**2 return window_ells, diagonal.T diff --git a/src/sp_validation/tests/test_sacc_writers.py b/src/sp_validation/tests/test_sacc_writers.py index c4963b33..792d8544 100644 --- a/src/sp_validation/tests/test_sacc_writers.py +++ b/src/sp_validation/tests/test_sacc_writers.py @@ -171,6 +171,16 @@ def test_pseudo_cl_to_sacc_real_namaster(tmp_path): assert np.array_equal(ee, cl_all[0]) and np.array_equal(bb, cl_all[3]) # window columns correspond to the bandpowers, one per ell_eff assert window.weight.shape[1] == len(ell_eff) + # a map-based spectrum's window carries the pixel window pw²(ℓ) + import healpy as hp + + mapped = sw.pseudo_cl_to_sacc({0: _nz()}, META, ell_eff, cl_all, wsp, nside=nside) + pw2 = hp.pixwin(nside, lmax=len(window.values) - 1) ** 2 + np.testing.assert_allclose( + mapped.get_bandpower_windows(mapped.indices(sio.CL_EE)).weight, + pw2[:, None] * window.weight, + rtol=1e-12, + ) def test_cosebis_to_sacc(tmp_path):