Skip to content

Commit 089a40d

Browse files
cailmdaleyclaude
andcommitted
fix: NaMaster linear binning lmax mismatch and add fine pseudo-Cl rule
- Fix linear binning to respect actual lmax (2*nside-1) instead of using from_nside_linear which assumes 3*nside-1. This was causing ValueError when bins lmax (3071) didn't match fields lmax (2047). - Add fine_pseudo_cl Snakemake rule for generating finely-binned pseudo-Cls with configurable ell_step for accurate C_ℓ → COSEBIS conversion. - Add generate_fine_pseudo_cl.py script for the new rule. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c6550b7 commit 089a40d

File tree

4 files changed

+219
-29
lines changed

4 files changed

+219
-29
lines changed

cosmo_inference/notebooks/2D_bmodes_paper_workflow/rules/bmodes.smk

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,56 @@ localrules:
151151
bmodes_all,
152152
bmodes_pdf,
153153
# Note: plot_eb and plot_cosebis removed from localrules - they now run on compute nodes
154+
155+
156+
# Output directory for cosmo_val results
157+
COSMO_VAL_OUTPUT = "/n17data/cdaley/unions/pure_eb/code/sp_validation/notebooks/cosmo_val/output"
158+
CAT_CONFIG = "/n17data/cdaley/unions/pure_eb/code/sp_validation/notebooks/cosmo_val/cat_config.yaml"
159+
160+
161+
rule fine_pseudo_cl:
162+
"""Generate finely-binned pseudo-Cls for accurate C_ℓ → COSEBIS conversion.
163+
164+
Uses linear binning with configurable ell_step (default 1 for single-ell bins).
165+
"""
166+
output:
167+
pseudo_cl=f"{COSMO_VAL_OUTPUT}/pseudo_cl_{{version}}_ellstep={{ell_step}}.fits",
168+
pseudo_cl_cov=f"{COSMO_VAL_OUTPUT}/pseudo_cl_cov_{{version}}_ellstep={{ell_step}}.fits",
169+
params:
170+
version="{version}",
171+
ell_step=lambda w: int(w.ell_step),
172+
cat_config=CAT_CONFIG,
173+
nside=1024,
174+
npatch=1,
175+
resources:
176+
mem_mb=32000,
177+
runtime=120, # minutes
178+
threads: 48
179+
script:
180+
"../scripts/generate_fine_pseudo_cl.py"
181+
182+
183+
rule fine_pseudo_cl_fiducial:
184+
"""Generate finely-binned pseudo-Cls for fiducial version with ell_step=1."""
185+
input:
186+
rules.fine_pseudo_cl.output.pseudo_cl.format(
187+
version=config["fiducial"]["version"],
188+
ell_step=1,
189+
),
190+
rules.fine_pseudo_cl.output.pseudo_cl_cov.format(
191+
version=config["fiducial"]["version"],
192+
ell_step=1,
193+
),
194+
195+
196+
rule fine_pseudo_cl_all:
197+
"""Generate finely-binned pseudo-Cls for all versions."""
198+
input:
199+
[
200+
rules.fine_pseudo_cl.output.pseudo_cl.format(version=ver, ell_step=1)
201+
for ver in bmodes_versions()
202+
],
203+
[
204+
rules.fine_pseudo_cl.output.pseudo_cl_cov.format(version=ver, ell_step=1)
205+
for ver in bmodes_versions()
206+
],

cosmo_inference/notebooks/2D_bmodes_paper_workflow/rules/epistemics_v2.smk

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ rule cl_version_comparison:
199199
f"{CONFIG_DIR}/cl_fiducial.md",
200200
],
201201
config=f"{CONFIG_DIR}/config.yaml",
202-
cl_fiducial_evidence=f"{CLAIMS_DIR}/cl_fiducial/evidence.json",
202+
cl_fiducial_evidence=rules.cl_data_vector.output.evidence,
203203
pseudo_cl=[
204204
f"{SASHA_COSMO_VAL_OUTPUT}/pseudo_cl_{ver}.fits"
205205
for ver in config["versions"]
@@ -231,7 +231,7 @@ rule pure_eb_pte_matrix:
231231
f"{CONFIG_DIR}/2d_plots.md",
232232
],
233233
config=f"{CONFIG_DIR}/config.yaml",
234-
pure_eb_evidence=f"{CLAIMS_DIR}/pure_eb_data_vector/evidence.json",
234+
pure_eb_evidence=rules.pure_eb_data_vector.output.evidence,
235235
# Pre-computed PTE matrices (from calculate_pure_eb_ptes.py)
236236
pte_data=f"results/paper_plots/intermediate/{config['fiducial']['version']}_pure_eb_ptes.npz",
237237
output:
@@ -424,12 +424,13 @@ rule harmonic_config_cosebis_comparison:
424424
input:
425425
spec=f"{CONFIG_DIR}/harmonic_config_cosebis_comparison.md",
426426
config=f"{CONFIG_DIR}/config.yaml",
427+
# Finely-binned pseudo-Cls (nellbins=1000, linear Δℓ=16) for accurate COSEBIS conversion
427428
pseudo_cls=[
428-
f"{SASHA_COSMO_VAL_OUTPUT}/pseudo_cl_{ver}.fits"
429+
f"{COSMO_VAL_OUTPUT}/pseudo_cl_{ver}_nellbins=1000.fits"
429430
for ver in config["versions"]
430431
],
431432
pseudo_cls_cov=[
432-
f"{SASHA_COSMO_VAL_OUTPUT}/pseudo_cl_cov_{ver}.fits"
433+
f"{COSMO_VAL_OUTPUT}/pseudo_cl_cov_{ver}_nellbins=1000.fits"
433434
for ver in config["versions"]
434435
],
435436
cov_integration=[
@@ -474,9 +475,9 @@ rule xi_cosmology_paper:
474475
"""
475476
input:
476477
spec=f"{CONFIG_DIR}/xi_cosmology_paper.md",
477-
cosebis_evidence=f"{CLAIMS_DIR}/cosebis_version_comparison/evidence.json",
478-
pure_eb_evidence=f"{CLAIMS_DIR}/pure_eb_data_vector/evidence.json",
479-
covariance_evidence=f"{CLAIMS_DIR}/covariance_blind_consistency/evidence.json",
478+
cosebis_evidence=rules.cosebis_version_comparison.output.evidence,
479+
pure_eb_evidence=rules.pure_eb_data_vector.output.evidence,
480+
covariance_evidence=rules.covariance_blind_consistency.output.evidence,
480481
output:
481482
macros="docs/unions_release/unions_2d_shear_xi/claims_macros.tex",
482483
evidence=f"{CLAIMS_DIR}/xi_cosmology_paper/evidence.json",
@@ -489,11 +490,11 @@ rule xi_cosmology_paper:
489490
rule paper_macros:
490491
"""Generate LaTeX macros and tables for B-modes paper (Daley et al.)."""
491492
input:
492-
cosebis_evidence=f"{CLAIMS_DIR}/cosebis_version_comparison/evidence.json",
493-
pure_eb_evidence=f"{CLAIMS_DIR}/pure_eb_data_vector/evidence.json",
493+
cosebis_evidence=rules.cosebis_version_comparison.output.evidence,
494+
pure_eb_evidence=rules.pure_eb_data_vector.output.evidence,
494495
# PTE composite evidence for table generation
495-
config_space_pte=f"{CLAIMS_DIR}/config_space_pte_matrices/evidence.json",
496-
harmonic_space_pte=f"{CLAIMS_DIR}/harmonic_space_pte_matrices/evidence.json",
496+
config_space_pte=rules.config_space_pte_matrices.output.evidence,
497+
harmonic_space_pte=rules.harmonic_space_pte_matrices.output.evidence,
497498
output:
498499
bmodes="docs/unions_release/unions_bmodes/claims_macros.tex",
499500
pte_table_results="docs/unions_release/unions_bmodes/pte_table_results.tex",
@@ -536,17 +537,35 @@ rule all_method_specs:
536537
rule bmodes_paper_spec:
537538
"""Generate evidence.json for bmodes_paper spec.
538539
539-
Dependencies parsed from markdown 'Depends on:' line.
540+
Dependencies include both evidence files and figure outputs to ensure
541+
dashboard regenerates all stale plots.
540542
"""
541543
input:
542544
spec=f"{CONFIG_DIR}/bmodes_paper.md",
543-
# Explicit upstream evidence (from Depends on: line in spec)
544-
pure_eb_covariance=f"{CLAIMS_DIR}/pure_eb_covariance/evidence.json",
545-
pure_eb_data_vector=f"{CLAIMS_DIR}/pure_eb_data_vector/evidence.json",
546-
cosebis_version_comparison=f"{CLAIMS_DIR}/cosebis_version_comparison/evidence.json",
547-
cl_fiducial=f"{CLAIMS_DIR}/cl_fiducial/evidence.json",
548-
config_space_pte=f"{CLAIMS_DIR}/config_space_pte_matrices/evidence.json",
549-
harmonic_space_pte=f"{CLAIMS_DIR}/harmonic_space_pte_matrices/evidence.json",
545+
# Upstream evidence (using rules.X.output for single source of truth)
546+
pure_eb_covariance=rules.pure_eb_covariance.output.evidence,
547+
pure_eb_data_vector=rules.pure_eb_data_vector.output.evidence,
548+
cosebis_version_comparison=rules.cosebis_version_comparison.output.evidence,
549+
cl_fiducial=rules.cl_data_vector.output.evidence,
550+
config_space_pte=rules.config_space_pte_matrices.output.evidence,
551+
harmonic_space_pte=rules.harmonic_space_pte_matrices.output.evidence,
552+
# Figure dependencies from bmodes.smk (ensures dashboard triggers regeneration)
553+
plot_eb_xis=expand(
554+
"/n17data/cdaley/unions/pure_eb/code/sp_validation/notebooks/cosmo_val/output/{version}_eb_minsep={min_sep}_maxsep={max_sep}_nbins={nbins}_minsepint={min_sep_int}_maxsepint={max_sep_int}_nbinsint={nbins_int}_npatch={npatch}_varmethod=semi-analytic_xis.png",
555+
version=config["versions"],
556+
min_sep=config["fiducial"]["min_sep"],
557+
max_sep=config["fiducial"]["max_sep"],
558+
nbins=config["fiducial"]["nbins"],
559+
min_sep_int=config["fiducial"]["min_sep_int"],
560+
max_sep_int=config["fiducial"]["max_sep_int"],
561+
nbins_int=config["fiducial"]["nbins_int"],
562+
npatch=config["fiducial"]["npatch"],
563+
),
564+
plot_cosebis_modes=expand(
565+
"/n17data/cdaley/unions/pure_eb/code/sp_validation/notebooks/cosmo_val/output/{version}_cosebis_minsep=" + str(config['fiducial']['min_sep_int']) + "_maxsep=" + str(config['fiducial']['max_sep_int']) + "_nbins=" + str(config['fiducial']['nbins_int']) + "_npatch={npatch}_varmethod=analytic_nmodes=" + str(config['fiducial']['nmodes']) + "_scalecut=" + str(config['fiducial']['fiducial_min_scale']) + "-" + str(config['fiducial']['fiducial_max_scale']) + "_cosebis.png",
566+
version=config["versions"],
567+
npatch=config["fiducial"]["npatch"],
568+
),
550569
output:
551570
evidence=f"{CLAIMS_DIR}/bmodes_paper/evidence.json",
552571
script:
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""Generate finely-binned pseudo-Cls for COSEBIS comparison.
2+
3+
Uses linear binning with configurable ell_step for accurate C_ℓ → COSEBIS conversion.
4+
Designed to be called by Snakemake or standalone.
5+
"""
6+
7+
import os
8+
import sys
9+
10+
from astropy.io import fits
11+
import numpy as np
12+
13+
from sp_validation.cosmo_val import CosmologyValidation
14+
15+
16+
def generate_pseudo_cl(
17+
version: str,
18+
ell_step: int,
19+
output_cl: str,
20+
output_cov: str,
21+
cat_config: str,
22+
nside: int = 1024,
23+
npatch: int = 1,
24+
):
25+
"""Generate pseudo-Cl and covariance with linear binning.
26+
27+
Parameters
28+
----------
29+
version : str
30+
Catalog version (e.g., "SP_v1.4.6_leak_corr")
31+
ell_step : int
32+
Width of ell bins (1 for single-ell bins)
33+
output_cl : str
34+
Output path for pseudo-Cl FITS file
35+
output_cov : str
36+
Output path for covariance FITS file
37+
cat_config : str
38+
Path to catalog configuration YAML
39+
nside : int
40+
HEALPix nside for map-based estimation
41+
npatch : int
42+
Number of jackknife patches
43+
"""
44+
output_dir = os.path.dirname(output_cl)
45+
os.makedirs(output_dir, exist_ok=True)
46+
47+
print(f"\n{'='*60}")
48+
print(f"Generating pseudo-Cl for {version} with ell_step={ell_step}")
49+
print(f"{'='*60}\n")
50+
51+
# Create CosmologyValidation with fine ell binning
52+
cv = CosmologyValidation(
53+
versions=[version],
54+
catalog_config=cat_config,
55+
output_dir=output_dir,
56+
binning="linear",
57+
ell_step=ell_step,
58+
nside=nside,
59+
cell_method="map",
60+
nrandom_cell=10,
61+
# Standard params (not used for pseudo-Cl but required)
62+
npatch=npatch,
63+
theta_min=1.0,
64+
theta_max=250.0,
65+
nbins=20,
66+
)
67+
68+
# Calculate pseudo-Cls
69+
cv.calculate_pseudo_cl()
70+
71+
# Rename to final output
72+
src_cl = os.path.join(output_dir, f"pseudo_cl_{version}.fits")
73+
if os.path.exists(src_cl) and src_cl != output_cl:
74+
with fits.open(src_cl) as hdul:
75+
data = hdul["PSEUDO_CELL"].data
76+
n_ell = len(data["ELL"])
77+
print(f"Generated pseudo-Cl with {n_ell} ell bins")
78+
print(f"ell range: [{data['ELL'].min():.1f}, {data['ELL'].max():.1f}]")
79+
os.rename(src_cl, output_cl)
80+
print(f"Saved to: {output_cl}")
81+
82+
# Calculate covariance
83+
print(f"\nCalculating covariance...")
84+
cv.calculate_pseudo_cl_eb_cov()
85+
86+
src_cov = os.path.join(output_dir, f"pseudo_cl_cov_{version}.fits")
87+
if os.path.exists(src_cov) and src_cov != output_cov:
88+
os.rename(src_cov, output_cov)
89+
print(f"Saved covariance to: {output_cov}")
90+
91+
92+
if __name__ == "__main__":
93+
# Check if running under Snakemake
94+
try:
95+
snakemake # noqa: F821
96+
except NameError:
97+
# Standalone usage
98+
print("Usage: Run via Snakemake rule 'fine_pseudo_cl'")
99+
sys.exit(1)
100+
101+
generate_pseudo_cl(
102+
version=snakemake.params.version, # noqa: F821
103+
ell_step=snakemake.params.ell_step, # noqa: F821
104+
output_cl=snakemake.output.pseudo_cl, # noqa: F821
105+
output_cov=snakemake.output.pseudo_cl_cov, # noqa: F821
106+
cat_config=snakemake.params.cat_config, # noqa: F821
107+
nside=snakemake.params.nside, # noqa: F821
108+
npatch=snakemake.params.npatch, # noqa: F821
109+
)

src/sp_validation/cosmo_val.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,11 +2637,14 @@ def calculate_pseudo_cl_eb_cov(self):
26372637
lmax = 2*self.nside
26382638
b_lmax = lmax - 1
26392639

2640+
ells = np.arange(lmin, lmax+1)
2641+
26402642
if self.binning == 'linear':
2641-
b = nmt.NmtBin.from_nside_linear(self.nside, self.ell_step)
2643+
# Linear bands of width ell_step, respecting actual lmax
2644+
bpws = (ells - lmin) // self.ell_step
2645+
bpws = np.minimum(bpws, bpws[-1]) # Ensure last bin captures all
2646+
b = nmt.NmtBin(ells=ells, bpws=bpws, lmax=b_lmax)
26422647
elif self.binning == 'powspace':
2643-
ells = np.arange(lmin, lmax+1)
2644-
26452648
start = np.power(lmin, self.power)
26462649
end = np.power(lmax, self.power)
26472650
bins_ell = np.power(np.linspace(start, end, self.n_ell_bins+1), 1/self.power)
@@ -3032,11 +3035,14 @@ def get_pseudo_cls_map(self, map, wsp=None):
30323035
lmax = 2*self.nside
30333036
b_lmax = lmax - 1
30343037

3038+
ells = np.arange(lmin, lmax+1)
3039+
30353040
if self.binning == 'linear':
3036-
b = nmt.NmtBin.from_nside_linear(self.nside, self.ell_step)
3041+
# Linear bands of width ell_step, respecting actual lmax
3042+
bpws = (ells - lmin) // self.ell_step
3043+
bpws = np.minimum(bpws, bpws[-1]) # Ensure last bin captures all
3044+
b = nmt.NmtBin(ells=ells, bpws=bpws, lmax=b_lmax)
30373045
elif self.binning == 'powspace':
3038-
ells = np.arange(lmin, lmax+1)
3039-
30403046
start = np.power(lmin, self.power)
30413047
end = np.power(lmax, self.power)
30423048
bins_ell = np.power(np.linspace(start, end, self.n_ell_bins+1), 1/self.power)
@@ -3053,7 +3059,7 @@ def get_pseudo_cls_map(self, map, wsp=None):
30533059
factor = -1 if self.pol_factor else 1
30543060

30553061
f_all = nmt.NmtField(mask=(map!=0), maps=[map.real, factor*map.imag], lmax=b_lmax)
3056-
3062+
30573063
if wsp is None:
30583064
wsp = nmt.NmtWorkspace.from_fields(f_all, f_all, b)
30593065

@@ -3071,11 +3077,14 @@ def get_pseudo_cls_catalog(self, catalog, params, wsp=None):
30713077
lmax = 2*self.nside
30723078
b_lmax = lmax - 1
30733079

3080+
ells = np.arange(lmin, lmax+1)
3081+
30743082
if self.binning == 'linear':
3075-
b = nmt.NmtBin.from_nside_linear(self.nside, self.ell_step)
3083+
# Linear bands of width ell_step, respecting actual lmax
3084+
bpws = (ells - lmin) // self.ell_step
3085+
bpws = np.minimum(bpws, bpws[-1]) # Ensure last bin captures all
3086+
b = nmt.NmtBin(ells=ells, bpws=bpws, lmax=b_lmax)
30763087
elif self.binning == 'powspace':
3077-
ells = np.arange(lmin, lmax+1)
3078-
30793088
start = np.power(lmin, self.power)
30803089
end = np.power(lmax, self.power)
30813090
bins_ell = np.power(np.linspace(start, end, self.n_ell_bins+1), 1/self.power)

0 commit comments

Comments
 (0)