diff --git a/src/sp_validation/cosmo_val/pure_eb.py b/src/sp_validation/cosmo_val/pure_eb.py index 095dd83e..272f154e 100644 --- a/src/sp_validation/cosmo_val/pure_eb.py +++ b/src/sp_validation/cosmo_val/pure_eb.py @@ -82,7 +82,12 @@ def calculate_pure_eb( Returns ------- dict - A dictionary containing the following keys: + Mapping of ``"tomo_bin_{b1}_tomo_bin_{b2}"`` to that bin pair's pure + E/B results, following the keys of :meth:`calculate_2pcf_version`. + With ``compute_tomography=False`` the single key is + ``"tomo_bin_all_tomo_bin_all"``. + + Each value is a dictionary containing the following keys: - "xip_E": Pure E-mode correlation function for xi+. - "xim_E": Pure E-mode correlation function for xi-. @@ -164,6 +169,7 @@ def plot_pure_eb( max_sep_int=300, nbins_int=1000, npatch=None, + compute_tomography=False, # LG: Hardcoded to False for now var_method="jackknife", cov_path_int=None, cosmo_cov=None, @@ -272,20 +278,31 @@ def plot_pure_eb( ) # Get or calculate results for this version - version_results = results_list[idx] or self.calculate_pure_eb( - version, - min_sep=min_sep, - max_sep=max_sep, - nbins=nbins, - min_sep_int=min_sep_int, - max_sep_int=max_sep_int, - nbins_int=nbins_int, - npatch=npatch, - var_method=var_method, - cov_path_int=cov_path_int, - cosmo_cov=cosmo_cov, - n_samples=n_samples, - ) + version_results = results_list[idx] + if version_results is None: + version_results_tomo = self.calculate_pure_eb( + version, + min_sep=min_sep, + max_sep=max_sep, + nbins=nbins, + min_sep_int=min_sep_int, + max_sep_int=max_sep_int, + nbins_int=nbins_int, + compute_tomography=False, # LG: Hardcoded to False for now + npatch=npatch, + var_method=var_method, + cov_path_int=cov_path_int, + cosmo_cov=cosmo_cov, + n_samples=n_samples, + ) + version_results = version_results_tomo[ + "tomo_bin_all_tomo_bin_all" + ] # LG: Extract non-tomographic results + elif ( + isinstance(version_results, dict) + and "tomo_bin_all_tomo_bin_all" in version_results + ): + version_results = version_results["tomo_bin_all_tomo_bin_all"] # Calculate E/B statistics for all bin combinations version_results = calculate_eb_statistics( diff --git a/src/sp_validation/cosmo_val/real_space.py b/src/sp_validation/cosmo_val/real_space.py index 96b7a077..2ed03238 100644 --- a/src/sp_validation/cosmo_val/real_space.py +++ b/src/sp_validation/cosmo_val/real_space.py @@ -7,12 +7,33 @@ """ import os +import re import matplotlib.pyplot as plt import numpy as np import treecorr from astropy.io import fits +from .. import sacc_io + +# calculate_2pcf_version keys its results "tomo_bin_{b1}_tomo_bin_{b2}"; this +# reads the bin pair back out ("all" for the non-tomographic single pair). +_PAIR_KEY = re.compile(r"tomo_bin_(.+?)_tomo_bin_(.+)") + + +def _sacc_bin_index(ggs): + """Map each catalog tomographic bin id to its 0-based SACC bin index. + + The catalog numbers its bins however the ``tomo_bin_col`` column does + (1-based in practice, and the single id ``"all"`` for a non-tomographic + run), while SACC tracers are ``source_{i}`` counted from zero. The map is + built from the ids actually present, in ascending order, so it never + assumes the catalog starts at 1 or numbers its bins without gaps. + """ + ids = {b for key in ggs for b in _PAIR_KEY.fullmatch(key).groups()} + order = sorted(ids, key=lambda b: 0 if b == "all" else int(b)) + return {b: i for i, b in enumerate(order)} + class RealSpaceMixin: def calculate_2pcf_version( @@ -71,8 +92,6 @@ def calculate_2pcf_version( ggs = {f"tomo_bin_{b1}_tomo_bin_{b2}": None for b1, b2 in tomo_bin_pairs} - # LG TO-DO: Change to sacc_io method - patch_file = self._output_path(f"{ver}_patches_npatch={npatch}.dat") cat_gal = fits.getdata(self.cc[ver]["shear"]["path"]) @@ -130,8 +149,8 @@ def calculate_2pcf_version( def calculate_2pcf( self, - npatch=None, compute_tomography=False, + npatch=None, **treecorr_config, ): """ @@ -159,15 +178,123 @@ def calculate_2pcf( for ver in self.versions: self.cat_ggs[ver] = self.calculate_2pcf_version( ver, - npatch=npatch, compute_tomography=compute_tomography, + npatch=npatch, **treecorr_config, ) - # LG TO-DO: No longer writing out text file, change to sacc_io method - return self.cat_ggs + def save_2pcf_sacc( + self, + ver, + sacc_path, + ggs=None, + *, + type, + grid="reporting", + metadata=None, + ): + """Write the measured ξ± for ``ver`` to ``sacc_path`` as a SACC file. + + Serialises the TreeCorr output of :meth:`calculate_2pcf_version` (or of + :meth:`calculate_2pcf`, via ``self.cat_ggs``) into the standard layout + of :mod:`sp_validation.sacc_io`: the ``source_{i}`` n(z) tracers, one + ξ+/ξ− block per tomographic bin pair, and the covariance. + + Insertion order is load-bearing. ``sacc_io.add_xi`` writes one bin pair + as ``[ξ+; ξ−]``, so the data vector is *pair-major*, and the covariance + is tied to it by position alone. Both the ξ insertion and the + covariance therefore iterate the same ``pairs`` list, sorted by SACC + bin index — never recomputed independently. + + The covariance follows what the measurement can support, read off the + correlations themselves (``var_method``) rather than off ``npatch``, + which the caller may have overridden per call: + + - jackknife (npatch > 1): ``treecorr.estimate_multi_cov`` over the + correlations in ``pairs`` order. TreeCorr concatenates each one as + ``[ξ+; ξ−]``, which is exactly the pair-major insertion order, so + the result is one contiguous block spanning every ξ point — + including the cross-pair covariance, which a per-pair ``gg.cov`` + would drop. + - shot noise (npatch == 1): the ``varxip``/``varxim`` diagonal, stored + as a SACC ``DiagonalCovariance``. + + Parameters + ---------- + ver : str + Catalog version, used for the n(z) lookup and stamped as metadata. + sacc_path : str + Path to the SACC product. + ggs : dict, optional + ``{"tomo_bin_{b1}_tomo_bin_{b2}": treecorr.GGCorrelation}`` as + returned by :meth:`calculate_2pcf_version`. Defaults to + ``self.cat_ggs[ver]``, i.e. the last :meth:`calculate_2pcf` run. + type : {'data', 'mock'} + Provenance of the input catalog, required by ``sacc_io.save``. + No default: only the caller knows whether it ran on a mock, and + ``sacc_io.load`` refuses unblinded ``type='data'`` files. + grid : str, optional + The ``grid`` tag on every point (default ``'reporting'``). Use + ``'integration'`` for the fine grid COSEBIs / pure-EB integrate + over, which shares this data type and tracer pair and is told + apart by nothing else. + metadata : dict, optional + Extra key/value pairs stored on the file, merged over the + version/npatch/blind stamped here. + + Returns + ------- + sacc.Sacc + The written data set. + """ + ggs = self.cat_ggs[ver] if ggs is None else ggs + if not ggs: + raise ValueError(f"{ver}: no ξ± measurements to write") + + index = _sacc_bin_index(ggs) + pairs = sorted( + ggs, key=lambda key: [index[b] for b in _PAIR_KEY.fullmatch(key).groups()] + ) + + z, *nz_cols = self.get_redshift(ver) + if len(nz_cols) != len(index): + raise ValueError( + f"{ver}: the n(z) file has {len(nz_cols)} distribution " + f"column(s) but the measurement covers {len(index)} " + f"tomographic bin(s) — every source bin needs its own n(z)" + ) + + s = sacc_io.new_sacc( + [(z, nz) for nz in nz_cols], + metadata={ + "version": ver, + "npatch": int(ggs[pairs[0]].npatch1), + **({"blind": self.blind} if self.blind is not None else {}), + **(metadata or {}), + }, + ) + + for key in pairs: + gg = ggs[key] + bins = tuple(index[b] for b in _PAIR_KEY.fullmatch(key).groups()) + sacc_io.add_xi( + s, + bins, + gg.meanr, + gg.xip, + gg.xim, + grid=grid, + theta_nom=gg.rnom, + npairs=gg.npairs, + weight=gg.weight, + ) + + sacc_io.save(s, sacc_path, type=type) + self.print_done(f"Wrote ξ± SACC for {ver} to {sacc_path}.") + return s + def calculate_aperture_mass_dispersion( self, theta_min=0.3, diff --git a/src/sp_validation/tests/test_cosmo_val.py b/src/sp_validation/tests/test_cosmo_val.py index 9536cbee..de7f6236 100644 --- a/src/sp_validation/tests/test_cosmo_val.py +++ b/src/sp_validation/tests/test_cosmo_val.py @@ -631,6 +631,13 @@ def test_calculate_pure_eb_runs_on_synthetic_catalog(self, tmp_path): nbins_int=600, ) + # calculate_pure_eb keys its results by tomographic bin pair, mirroring + # calculate_2pcf_version. Non-tomographic (compute_tomography=False, the + # default) means exactly one pair -- the all-galaxy auto-correlation -- + # so assert that and unwrap it to the per-bin mode dict. + assert list(results) == ["tomo_bin_all_tomo_bin_all"] + results = results["tomo_bin_all_tomo_bin_all"] + # Reference mode vectors from the seeded synthetic catalog + Schneider # transform. Deterministic (full-sample treecorr, no RNG); regenerate by # running calculate_pure_eb with the setup above and printing repr() of diff --git a/src/sp_validation/tests/test_glass_mock.py b/src/sp_validation/tests/test_glass_mock.py index a056318b..56a26444 100644 --- a/src/sp_validation/tests/test_glass_mock.py +++ b/src/sp_validation/tests/test_glass_mock.py @@ -40,7 +40,23 @@ camb = pytest.importorskip("camb") -HAVE_GLASS = importlib.util.find_spec("glass") is not None +def _have(module): + """True if ``module`` is importable. False if it or a parent is missing.""" + try: + return importlib.util.find_spec(module) is not None + except ModuleNotFoundError: + # find_spec imports the parent package, which raises if it is absent. + return False + + +HAVE_GLASS = _have("glass") + +# The map path needs more than GLASS itself: build_shells and +# Cosmology_from_camb import ``cosmology.compat.camb``, which ships with the +# ``cosmology`` API package rather than with GLASS. Gate on it separately so a +# missing dependency reports as a skip instead of masquerading as the (real, +# but different) glass/cosmology API incompatibility tracked in the xfail below. +HAVE_COSMOLOGY_CAMB = _have("cosmology.compat.camb") REFERENCE = Path(__file__).parent / "data" / "glass_mock_camb_reference.npz" @@ -131,6 +147,10 @@ def test_config_change_breaks_reference(): @pytest.mark.skipif(not HAVE_GLASS, reason="GLASS not installed in this image") +@pytest.mark.skipif( + not HAVE_COSMOLOGY_CAMB, + reason="cosmology.compat.camb not installed in this image", +) @pytest.mark.xfail( reason=( "glass_mock map path is incompatible with the installed glass/cosmology " diff --git a/workflow/common.py b/workflow/common.py index 3df3413b..ea2b1cb1 100644 --- a/workflow/common.py +++ b/workflow/common.py @@ -5,19 +5,13 @@ import re from pathlib import Path -# Output roots are env-overridable so a reproduction run can write into a -# fresh tree without clobbering (or silently reusing) prior products. -COSMO_VAL = Path( - os.environ.get( - "COSMO_VAL", "/n17data/cdaley/unions/code/sp_validation/cosmo_val/output" - ) -) +SP_VALIDATION = Path(__file__).resolve().parents[1] +COSMO_VAL = Path(os.environ.get("COSMO_VAL", SP_VALIDATION / "results/cosmo_val")) COSMO_INFERENCE = Path( - os.environ.get( - "COSMO_INFERENCE", "/n17data/cdaley/unions/code/sp_validation/cosmo_inference" - ) + os.environ.get("COSMO_INFERENCE", SP_VALIDATION / "cosmo_inference") ) -CAT_CONFIG = "/n17data/cdaley/unions/code/sp_validation/cosmo_val/cat_config.yaml" +CAT_CONFIG = SP_VALIDATION / "cosmo_val/cat_config.yaml" + BLINDS = ["A", "B", "C"] BLOCK_PAIRS = [("++", "1"), ("--", "2"), ("+-", "3")] @@ -34,12 +28,14 @@ "version": r"SP_v[\d.]+(_w_iv)?(_ecut\d+)?(_leak_corr)?", "blind": r"[ABC]", "nbins": r"\d+", + "npatch": r"\d+", "min_sep": r"[0-9.]+", "max_sep": r"[0-9.]+", "gaussian": r"(g|ng)", "block_pm": r"(\+\+|--|\+-)", "block_i": r"[123]", "mask_suffix": r"(_masked)?", + "tomo_suffix": r"(_tomo)?", "mock_id": r"\d{5}", "nside": r"\d+", } diff --git a/workflow/rules/cosmo_val.smk b/workflow/rules/cosmo_val.smk index 704b4fb0..a5650135 100644 --- a/workflow/rules/cosmo_val.smk +++ b/workflow/rules/cosmo_val.smk @@ -45,7 +45,7 @@ CV_BINNING = ( f"_nbins={CV['nbins']}_npatch={CV['npatch']}" ) - +# LG: DEPRACATED, xi_pm vectors no longer written to txt files. def cv_xi_txt(version): """Path to the 2pcf data vector calculate_2pcf writes for a version. @@ -238,8 +238,9 @@ rule cv_additive_bias: "../scripts/cv_additive_bias.py" +# LG: DEPRACATED since we do not plot unblinded data vectors. rule cv_plot_2pcf: - """n_pairs / xi± overlay across versions.""" + """Tomographic xi± across versions.""" input: xi=[cv_xi_txt(v) for v in CV_VERSIONS], output: @@ -251,11 +252,15 @@ rule cv_plot_2pcf: script: "../scripts/cv_plot_2pcf.py" - +# LG: DEPRACATED since we do not plot unblinded data vectors. rule cv_ratio_xi_sys_xi: """Ratio of PSF systematics (xi_psf_sys) to the cosmic-shear signal (xi+).""" input: - xi=[cv_xi_txt(v) for v in CV_VERSIONS], + # The xi txt this used to declare is written by nothing since + # calculate_2pcf stopped emitting it; cv_xi_txt is gone with it. The + # rule recomputes xi_psf_sys itself, so the entry is simply dropped — + # left in place it is a parse-time NameError that takes the whole + # workflow down, deprecated or not. rho=[cv_rho_stats(v) for v in CV_VERSIONS], tau=[cv_tau_stats(v) for v in CV_VERSIONS], output: @@ -294,8 +299,6 @@ rule cv_pseudo_cl: rule cv_pure_eb: """Pure E/B-mode decomposition for one version (config-space).""" - input: - xi=lambda w: cv_xi_txt(w.version), output: npz=cv_pure_eb_npz("{version}"), params: @@ -316,8 +319,9 @@ rule cv_pure_eb: rule cv_cosebis: """COSEBIs E/B decomposition for one version (config-space, fine binning).""" - input: - xi=lambda w: cv_xi_txt(w.version), + # No xi input, for the same reason as cv_pure_eb: calculate_cosebis runs + # its own TreeCorr over the fine integration binning, and the xi txt it + # used to declare is no longer written by anything. output: npz=cv_cosebis_npz("{version}"), params: diff --git a/workflow/rules/twopoint.smk b/workflow/rules/twopoint.smk index 22c09db2..72c10247 100644 --- a/workflow/rules/twopoint.smk +++ b/workflow/rules/twopoint.smk @@ -1,21 +1,21 @@ # Two-point data-vector rules: xi, rho/tau, and pseudo-Cl products. - +# LG: currently calculates the unblinded 2PCF. Don't look! rule xi: - input: - catalog=get_shear_catalog, + """ξ±(θ) two-point correlation for one catalog version, as a SACC file.""" output: - str(COSMO_VAL / "{version}_xi_minsep={min_sep}_maxsep={max_sep}_nbins={nbins}_npatch={npatch}.txt"), - str(COSMO_VAL / "xi_plus_{version}_minsep={min_sep}_maxsep={max_sep}_nbins={nbins}_npatch={npatch}.fits"), - str(COSMO_VAL / "xi_minus_{version}_minsep={min_sep}_maxsep={max_sep}_nbins={nbins}_npatch={npatch}.fits"), + sacc=str(COSMO_VAL / "xi_{version}_minsep={min_sep}_maxsep={max_sep}_nbins={nbins}_npatch={npatch}{tomo_suffix}.sacc"), threads: 24 params: ver="{version}", + compute_tomography=lambda w: w.tomo_suffix == "_tomo", + npatch="{npatch}", min_sep="{min_sep}", max_sep="{max_sep}", nbins="{nbins}", - npatch="{npatch}", - fits=False, + cat_config=CAT_CONFIG, + sacc_path=output.sacc, + data_type="data" resources: mem_mb=30000, disk_mb=20000, diff --git a/workflow/scripts/cv_cosebis.py b/workflow/scripts/cv_cosebis.py index 182cda99..3facd067 100644 --- a/workflow/scripts/cv_cosebis.py +++ b/workflow/scripts/cv_cosebis.py @@ -2,9 +2,13 @@ Compute + plot rule (per version). plot_cosebis calls calculate_cosebis over a fine integration binning (the 2000-bin TreeCorr is the dominant cost) and -evaluates the configured scale cuts. Writes the {version}_eb_..._data.npz -COSEBIs data product (declared output) plus figures, and the per-version -COSEBIs PTE that cv_summarize_bmodes collects. +evaluates the configured scale cuts. The TreeCorr run is its own, so this rule +declares no xi input. Writes the {version}_eb_..._data.npz COSEBIs data product +(declared output) plus figures, and the per-version COSEBIs PTE that +cv_summarize_bmodes collects. + +Non-tomographic: plot_cosebis passes compute_tomography=False and then reads +the single "tomo_bin_all_tomo_bin_all" entry back out of the result. """ from cv_runner import _unbuffer_streams, make_cv, verify_outputs diff --git a/workflow/scripts/cv_plot_2pcf.py b/workflow/scripts/cv_plot_2pcf.py index 9d5c8900..c004f38b 100644 --- a/workflow/scripts/cv_plot_2pcf.py +++ b/workflow/scripts/cv_plot_2pcf.py @@ -9,6 +9,7 @@ from cv_runner import _unbuffer_streams, make_cv, touch_sentinels from snakemake.script import snakemake +# LG : This is now DEPRECATED, NOT TO BE USED, since we are no longer plotting unblinded data vectors. _unbuffer_streams() cv = make_cv(snakemake) cv.plot_2pcf() diff --git a/workflow/scripts/cv_pure_eb.py b/workflow/scripts/cv_pure_eb.py index d15a763f..ff34c1ea 100644 --- a/workflow/scripts/cv_pure_eb.py +++ b/workflow/scripts/cv_pure_eb.py @@ -1,11 +1,14 @@ """Rule cv_pure_eb: pure E/B-mode decomposition for one version. Compute + plot rule (per version). plot_pure_eb calls calculate_pure_eb, which -runs two TreeCorr correlations (reporting + integration binning); the reporting -binning reuses the cv_2pcf data vector via calculate_2pcf's skip-if-exists -path. Writes the {version}_eb_..._data.npz data product (declared output) plus -companion figures, and the per-version E/B PTEs that cv_summarize_bmodes -collects. +runs two TreeCorr correlations of its own (reporting + integration binning) — +it reads no data vector from disk, so this rule declares no xi input. Writes +the {version}_eb_..._data.npz data product (declared output) plus companion +figures, and the per-version E/B PTEs that cv_summarize_bmodes collects. + +Non-tomographic: plot_pure_eb forwards a hardcoded compute_tomography=False to +calculate_pure_eb and then reads the single "tomo_bin_all_tomo_bin_all" entry +back out of the result, so the measurement runs on the single "all" bin pair. """ from cv_runner import _unbuffer_streams, make_cv, verify_outputs diff --git a/workflow/scripts/cv_ratio_xi_sys_xi.py b/workflow/scripts/cv_ratio_xi_sys_xi.py index ba737d7e..810d5578 100644 --- a/workflow/scripts/cv_ratio_xi_sys_xi.py +++ b/workflow/scripts/cv_ratio_xi_sys_xi.py @@ -10,6 +10,7 @@ from cv_runner import _unbuffer_streams, make_cv, verify_outputs from snakemake.script import snakemake +# LG : This is now DEPRECATED, NOT TO BE USED, since we are no longer plotting unblinded data vectors. _unbuffer_streams() cv = make_cv(snakemake) cv.plot_ratio_xi_sys_xi(offset=snakemake.params.get("offset", 0.1)) diff --git a/workflow/scripts/run_2pcf.py b/workflow/scripts/run_2pcf.py index 2e1ccabf..58622538 100644 --- a/workflow/scripts/run_2pcf.py +++ b/workflow/scripts/run_2pcf.py @@ -10,12 +10,18 @@ --ver SP_v1.4.6.3_leak_corr \ --min-sep 1.0 --max-sep 250.0 --nbins 20 --npatch 1 \ --cat-config /path/to/cosmo_val/cat_config.yaml \ - --out + --out --type mock -The measurement itself is unchanged — ``CosmologyValidation.calculate_2pcf`` -does the TreeCorr work and writes the ``.txt`` dump plus ξ+/ξ- FITS files into -``output_dir``. ``output_dir`` is passed explicitly (rather than via the -``COSMO_VAL`` env hook) so lc can point each run at its own ``{output}`` tree. +``CosmologyValidation.calculate_2pcf`` does the TreeCorr work and +``save_2pcf_sacc`` writes the result as a SACC file. Both live on the class: +serialising needs the version's n(z) and its tomographic bin map, so this +script only names the file — from the ``sacc_name`` param under Snakemake, from +``--sacc`` on the CLI (defaulting to the untagged ``xi_{ver}.sacc``, since each +lc recipe gets its own output tree). The *directory* is not this script's to +choose: ``save_2pcf_sacc`` joins the name onto the version's output directory, +so the SACC lands beside every other product of the run. Under Snakemake that +directory comes from the catalog config; on the CLI ``--out`` overrides it, so +lc can point each run at its own ``{output}`` tree. """ import argparse @@ -25,52 +31,63 @@ def run_2pcf( ver, + compute_tomography, + npatch, min_sep, max_sep, nbins, - npatch, cat_config, output_dir, - save_fits=True, + sacc_path, + data_type="data", ): - """Measure ξ±(θ) for ``ver`` and write it under ``output_dir``. + """Measure ξ±(θ) for ``ver`` and write it as the SACC file ``sacc_name``. Parameters mirror the TreeCorr reporting/integration grids: ``min_sep`` / ``max_sep`` in arcmin, ``nbins`` logarithmic bins, ``npatch`` spatial patches (1 for the paper fiducial). ``cat_config`` is an absolute path to the catalog configuration; ``output_dir`` overrides - ``cat_config['paths']['output']`` so products land where lc expects. + ``cat_config['paths']['output']`` so products land where lc expects, and + is left ``None`` under Snakemake so the directory comes from the catalog + config. ``sacc_name`` is a bare filename — ``save_2pcf_sacc`` joins it onto + whichever of the two that resolves to. + + ``data_type`` is the ``'data'``/``'mock'`` provenance stamp SACC requires. + It defaults to ``'data'`` because that is the fail-safe direction: a mock + mislabelled as data is merely refused by ``sacc_io.load`` until it carries + a blinding stamp, whereas real data mislabelled as a mock would load + unblinded. Callers running on mocks pass ``'mock'`` explicitly. """ cv = CosmologyValidation( versions=[ver], catalog_config=cat_config, output_dir=output_dir, ) - return cv.calculate_2pcf( - ver=ver, + ggs = cv.calculate_2pcf( + compute_tomography=compute_tomography, npatch=npatch, - save_fits=save_fits, min_sep=min_sep, max_sep=max_sep, nbins=nbins, ) + # calculate_2pcf keys its result by version; save_2pcf_sacc takes the + # per-version {pair: GGCorrelation} dict. + cv.save_2pcf_sacc(ver, sacc_path, ggs[ver], type=data_type) def _from_snakemake(smk): p = smk.params run_2pcf( ver=p["ver"], + compute_tomography=str(p["compute_tomography"]).lower() == "true", + npatch=int(p["npatch"]), min_sep=float(p["min_sep"]), max_sep=float(p["max_sep"]), nbins=int(p["nbins"]), - npatch=int(p["npatch"]), - # cat_config / output_dir were previously resolved via an os.chdir into - # the cosmo_val dir + the COSMO_VAL env var; expose them as optional - # params so the rule can pass them explicitly, falling back to the - # class defaults (./cat_config.yaml, COSMO_VAL env) otherwise. cat_config=p.get("cat_config", "./cat_config.yaml"), - output_dir=p.get("output_dir", None), - save_fits=True, + output_dir=None, + sacc_path=p["sacc_path"], + data_type=p.get("data_type", "data"), ) @@ -83,6 +100,11 @@ def _from_cli(argv=None): required=True, help="Catalog version key in cat_config, e.g. SP_v1.4.6.3_leak_corr", ) + ap.add_argument( + "--compute-tomography", + action="store_true", + help="Compute tomographic 2PCF (default: non-tomographic)", + ) ap.add_argument( "--min-sep", type=float, required=True, help="Min separation [arcmin]" ) @@ -97,17 +119,30 @@ def _from_cli(argv=None): "--cat-config", required=True, help="Absolute path to cat_config.yaml" ) ap.add_argument("--out", required=True, help="Output directory (lc {output})") - ap.add_argument("--no-fits", action="store_true", help="Skip ξ+/ξ- FITS export") + ap.add_argument( + "--sacc", + required=True, + help="Path to SACC file, written under --out (default: xi_{ver}.sacc)", + ) + ap.add_argument( + "--type", + choices=("data", "mock"), + default="data", + help="Catalog provenance stamped on the SACC file, default=%(default)s", + ) a = ap.parse_args(argv) + run_2pcf( ver=a.ver, + compute_tomography=a.compute_tomography, min_sep=a.min_sep, max_sep=a.max_sep, nbins=a.nbins, npatch=a.npatch, cat_config=a.cat_config, output_dir=a.out, - save_fits=not a.no_fits, + sacc_path=a.sacc, + data_type=a.type, )