From de6c7310ecc5f519e491c67641bb189e5d5725ac Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Tue, 13 Oct 2026 18:44:43 -0500 Subject: [PATCH 01/20] refactor(test): type erased real-space fixture consumer annotation Replace the tuple[object, ...] annotation on the sacc_galaxy_xis_lens0_lens0_real consumer with the actual (Sacc, float64, float64) contract matching its producer. --- tests/likelihood/gauss_family/statistic/test_two_point.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/likelihood/gauss_family/statistic/test_two_point.py b/tests/likelihood/gauss_family/statistic/test_two_point.py index d285e4b88..b42b34640 100644 --- a/tests/likelihood/gauss_family/statistic/test_two_point.py +++ b/tests/likelihood/gauss_family/statistic/test_two_point.py @@ -7,6 +7,7 @@ from unittest.mock import MagicMock import numpy as np +import numpy.typing as npt import pyccl import pytest import sacc @@ -794,7 +795,9 @@ def test_two_point_src0_src0_no_window_aiw( def test_two_point_src0_src0_real_aiw( - sacc_galaxy_xis_lens0_lens0_real: tuple[object, ...], + sacc_galaxy_xis_lens0_lens0_real: tuple[ + sacc.Sacc, npt.NDArray[np.float64], npt.NDArray[np.float64] + ], apply_interp_when: tp.ApplyInterpolationWhen, ) -> None: sacc_data, _, _ = sacc_galaxy_xis_lens0_lens0_real From 6d55bf5523d02e2c69067ef8a547a4c89458df64 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Tue, 13 Oct 2026 18:52:05 -0500 Subject: [PATCH 02/20] refactor(test): document real-space TwoPoint fixture test --- .../gauss_family/statistic/test_two_point.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/likelihood/gauss_family/statistic/test_two_point.py b/tests/likelihood/gauss_family/statistic/test_two_point.py index b42b34640..0146e1585 100644 --- a/tests/likelihood/gauss_family/statistic/test_two_point.py +++ b/tests/likelihood/gauss_family/statistic/test_two_point.py @@ -800,6 +800,16 @@ def test_two_point_src0_src0_real_aiw( ], apply_interp_when: tp.ApplyInterpolationWhen, ) -> None: + """Check a real-space lens0-lens0 TwoPoint computes a finite theory vector. + + Reads the SACC data from the real-space fixture and computes the theory + vector. The cell count matches ``ells_for_xi`` only when the REAL + interpolation flag is set. The redshift and angular arrays are unused. + + :param sacc_galaxy_xis_lens0_lens0_real: Tuple of SACC data, redshift + coordinates, and angular coordinates from the fixture. + :param apply_interp_when: When to apply interpolation. + """ sacc_data, _, _ = sacc_galaxy_xis_lens0_lens0_real lens0 = WeakLensing(sacc_tracer="lens0") From f6e36a06b5e76054cd7f0e7e4ee09e6f0fe2fb3d Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Fri, 16 Oct 2026 20:20:32 -0500 Subject: [PATCH 03/20] refactor(test): Annotate SACC test fixtures with typed NDArray returns Replace bare np.ndarray tuple annotations with npt.NDArray[np.float64] in the two-point SACC metadata fixtures and their consuming tests. Convert the three-tracer fixture return annotations from parenthesized single-line forms to multi-line tuple forms. Add docstrings describing the fixture and test contracts, replacing the copied "without window functions" fixture descriptions. --- .../metadata/test_metadata_two_point_sacc.py | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/tests/metadata/test_metadata_two_point_sacc.py b/tests/metadata/test_metadata_two_point_sacc.py index af69a5412..dad45a619 100644 --- a/tests/metadata/test_metadata_two_point_sacc.py +++ b/tests/metadata/test_metadata_two_point_sacc.py @@ -66,8 +66,12 @@ @pytest.fixture(name="sacc_galaxy_src0_src0_invalid_data_type") def fixture_sacc_galaxy_src0_src0_invalid_data_type( recwarn: pytest.WarningsRecorder, -) -> tuple[sacc.Sacc, np.ndarray, np.ndarray]: - """Fixture for a SACC data without window functions.""" +) -> tuple[sacc.Sacc, npt.NDArray[np.float64], npt.NDArray[np.float64]]: + """Build unwindowed SACC data with an intentionally invalid data type. + + :param recwarn: Recorder for SACC's unknown-data-type warning. + :returns: SACC data, redshift coordinates, and source number density. + """ sacc_data = sacc.Sacc() z = np.linspace(0, 1.0, 50) + 0.05 @@ -92,10 +96,18 @@ def fixture_sacc_galaxy_src0_src0_invalid_data_type( @pytest.fixture(name="sacc_galaxy_xis_three_tracers") -def fixture_sacc_galaxy_xis_three_tracers() -> ( - tuple[sacc.Sacc, np.ndarray, np.ndarray, np.ndarray, np.ndarray] -): - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_xis_three_tracers() -> tuple[ + sacc.Sacc, + npt.NDArray[np.float64], + npt.NDArray[np.float64], + npt.NDArray[np.float64], + npt.NDArray[np.float64], +]: + """Build unwindowed real-space SACC data with a three-tracer correlation. + + :returns: SACC data, redshift coordinates, and number densities for src0, + lens0, and lens1, in that order. + """ sacc_data = sacc.Sacc() z = np.linspace(0, 1.0, 50) + 0.05 @@ -125,10 +137,18 @@ def fixture_sacc_galaxy_xis_three_tracers() -> ( @pytest.fixture(name="sacc_galaxy_cells_three_tracers") -def fixture_sacc_galaxy_cells_three_tracers() -> ( - tuple[sacc.Sacc, np.ndarray, np.ndarray, np.ndarray, np.ndarray] -): - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_cells_three_tracers() -> tuple[ + sacc.Sacc, + npt.NDArray[np.float64], + npt.NDArray[np.float64], + npt.NDArray[np.float64], + npt.NDArray[np.float64], +]: + """Build unwindowed harmonic SACC data with a three-tracer correlation. + + :returns: SACC data, redshift coordinates, and number densities for src0, + lens0, and lens1, in that order. + """ sacc_data = sacc.Sacc() z = np.linspace(0, 1.0, 50) + 0.05 @@ -365,8 +385,15 @@ def test_extract_all_trace_xis_src0_lens0( def test_extract_all_tracers_invalid_data_type( - sacc_galaxy_src0_src0_invalid_data_type: tuple[sacc.Sacc, np.ndarray, np.ndarray], + sacc_galaxy_src0_src0_invalid_data_type: tuple[ + sacc.Sacc, npt.NDArray[np.float64], npt.NDArray[np.float64] + ], ) -> None: + """Reject a tracer whose only data points have an unsupported data type. + + :param sacc_galaxy_src0_src0_invalid_data_type: Invalid SACC data, redshift + coordinates, and source number density. + """ sacc_data, _, _ = sacc_galaxy_src0_src0_invalid_data_type assert sacc_data is not None with pytest.raises( @@ -662,6 +689,11 @@ def test_extract_all_data_types_three_tracers_reals( npt.NDArray[np.float64], ], ) -> None: + """Reject real-space metadata for a correlation containing three tracers. + + :param sacc_galaxy_xis_three_tracers: SACC data, redshift coordinates, and + number densities for src0, lens0, and lens1. + """ sacc_data, _, _, _, _ = sacc_galaxy_xis_three_tracers with pytest.raises( @@ -683,6 +715,11 @@ def test_extract_all_data_types_three_tracers_harmonics( npt.NDArray[np.float64], ], ) -> None: + """Reject harmonic metadata for a correlation containing three tracers. + + :param sacc_galaxy_cells_three_tracers: SACC data, redshift coordinates, and + number densities for src0, lens0, and lens1. + """ sacc_data, _, _, _, _ = sacc_galaxy_cells_three_tracers with pytest.raises( From b5d4bef21a2dd36475faaeee09192641c927c339 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Fri, 16 Oct 2026 20:56:56 -0500 Subject: [PATCH 04/20] refactor(tests): type harmonic SACC fixture with dataclass records Replace the tuple-based sacc_galaxy_cells fixture return with SaccGalaxyHarmonicFixture, which holds sacc_data, distributions, and spectra as frozen dataclasses (SampledRedshiftDistribution and HarmonicAngularPowerSpectrum). Update consumers to read named fields instead of positional indices. Add docstrings to the affected tests. Extend test_extract_all_data_harmonic_no_cov to run against a copy and assert that the shared fixture's covariance is left unchanged. --- tests/_types.py | 45 ++- tests/conftest.py | 49 ++- .../metadata/test_metadata_two_point_sacc.py | 331 ++++++++---------- .../metadata/test_metadata_two_point_types.py | 103 +++--- 4 files changed, 247 insertions(+), 281 deletions(-) diff --git a/tests/_types.py b/tests/_types.py index 6e3b8eafa..9de4b8189 100644 --- a/tests/_types.py +++ b/tests/_types.py @@ -1,5 +1,10 @@ -"""Shared types for test fixtures.""" +"""Shared types for test fixtures. +Frozen records bind named fields, but do not freeze nested arrays, maps, or SACC. +Array annotations specify dtype, not semantic role, shape, or physical units. +""" + +from dataclasses import dataclass from typing import TypeAlias, TypedDict from numcosmo_py import Nc @@ -9,6 +14,44 @@ from firecrown.metadata_types import TracerNames +@dataclass(frozen=True, kw_only=True) +class SampledRedshiftDistribution: + """Independent sampled redshift coordinates and unnormalized number density. + + :param z: Sampled redshift coordinates. + :param dndz: Number-density values on the redshift grid; not normalized. + """ + + z: npt.NDArray[np.float64] + dndz: npt.NDArray[np.float64] + + +@dataclass(frozen=True, kw_only=True) +class HarmonicAngularPowerSpectrum: + """Independent unwindowed harmonic coordinates and measured cells. + + :param ells: Integer multipoles for the measurements. + :param cells: Measured angular power spectrum values, not theory predictions. + """ + + ells: npt.NDArray[np.int64] + cells: npt.NDArray[np.float64] + + +@dataclass(frozen=True, kw_only=True) +class SaccGalaxyHarmonicFixture: + """Harmonic SACC data with independently generated named expectations. + + :param sacc_data: SACC data used by production extraction and construction. + :param distributions: Redshift distributions keyed by tracer name. + :param spectra: Measured spectra keyed by ordered tracer names and data type. + """ + + sacc_data: sacc.Sacc + distributions: dict[str, SampledRedshiftDistribution] + spectra: dict[tuple[TracerNames, str], HarmonicAngularPowerSpectrum] + + RedshiftDistribution: TypeAlias = tuple[ npt.NDArray[np.float64], npt.NDArray[np.float64] ] diff --git a/tests/conftest.py b/tests/conftest.py index 09ac02881..1b91704ca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,8 +44,11 @@ from firecrown.updatable import ParamsMap, get_default_params_map from firecrown.utils import upper_triangle_indices from tests._types import ( + HarmonicAngularPowerSpectrum, RedshiftDistribution, + SaccGalaxyHarmonicFixture, SaccGalaxyWindowedFixture, + SampledRedshiftDistribution, WindowedAngularPowerSpectrum, ) @@ -808,17 +811,14 @@ def fixture_sacc_galaxy_xis_src0_lens0() -> tuple[ @pytest.fixture(name="sacc_galaxy_cells", scope="module") -def fixture_sacc_galaxy_cells() -> tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]] - ], -]: - """Fixture for a SACC data without window functions. +def fixture_sacc_galaxy_cells() -> SaccGalaxyHarmonicFixture: + """Build unwindowed harmonic SACC data and independent named expectations. - Module-scoped because SACC objects are expensive to create and are - read-only in tests. + Module-scoped because SACC objects are expensive to create. Consumers must + copy SACC before mutation; expected grids remain shared between records. + + :returns: SACC data, sampled redshift distributions by tracer name, and + measured cells by ordered tracer pair and data type. """ sacc_data = sacc.Sacc() @@ -828,29 +828,26 @@ def fixture_sacc_galaxy_cells() -> tuple[ src_bins_centers = np.linspace(0.25, 0.75, 5) lens_bins_centers = np.linspace(0.1, 0.6, 5) - tracers: dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]] = {} - tracer_pairs: dict[ - tuple[TracerNames, str], tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]] - ] = {} + tracers: dict[str, SampledRedshiftDistribution] = {} + tracer_pairs: dict[tuple[TracerNames, str], HarmonicAngularPowerSpectrum] = {} for i, mn in enumerate(src_bins_centers): - dndz = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) + dndz: npt.NDArray[np.float64] = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", f"src{i}", z, dndz) - tracers[f"src{i}"] = (z, dndz) + tracers[f"src{i}"] = SampledRedshiftDistribution(z=z, dndz=dndz) for i, mn in enumerate(lens_bins_centers): dndz = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", f"lens{i}", z, dndz) - tracers[f"lens{i}"] = (z, dndz) + tracers[f"lens{i}"] = SampledRedshiftDistribution(z=z, dndz=dndz) dv = [] for i, j in upper_triangle_indices(len(src_bins_centers)): - Cells = np.array(np.random.normal(size=ells.shape[0])) + Cells: npt.NDArray[np.float64] = np.array(np.random.normal(size=ells.shape[0])) sacc_data.add_ell_cl("galaxy_shear_cl_ee", f"src{i}", f"src{j}", ells, Cells) tracer_pairs[(TracerNames(f"src{i}", f"src{j}"), "galaxy_shear_cl_ee")] = ( - ells, - Cells, + HarmonicAngularPowerSpectrum(ells=ells, cells=Cells) ) dv.append(Cells) @@ -858,8 +855,7 @@ def fixture_sacc_galaxy_cells() -> tuple[ Cells = np.array(np.random.normal(size=ells.shape[0])) sacc_data.add_ell_cl("galaxy_density_cl", f"lens{i}", f"lens{j}", ells, Cells) tracer_pairs[(TracerNames(f"lens{i}", f"lens{j}"), "galaxy_density_cl")] = ( - ells, - Cells, + HarmonicAngularPowerSpectrum(ells=ells, cells=Cells) ) dv.append(Cells) @@ -870,10 +866,7 @@ def fixture_sacc_galaxy_cells() -> tuple[ ) tracer_pairs[ (TracerNames(f"src{i}", f"lens{j}"), "galaxy_shearDensity_cl_e") - ] = ( - ells, - Cells, - ) + ] = HarmonicAngularPowerSpectrum(ells=ells, cells=Cells) dv.append(Cells) delta_v = np.concatenate(dv, axis=0) @@ -881,7 +874,9 @@ def fixture_sacc_galaxy_cells() -> tuple[ sacc_data.add_covariance(cov) - return sacc_data, tracers, tracer_pairs + return SaccGalaxyHarmonicFixture( + sacc_data=sacc_data, distributions=tracers, spectra=tracer_pairs + ) @pytest.fixture(name="sacc_galaxy_cwindows", scope="module") diff --git a/tests/metadata/test_metadata_two_point_sacc.py b/tests/metadata/test_metadata_two_point_sacc.py index dad45a619..89d2d4fe2 100644 --- a/tests/metadata/test_metadata_two_point_sacc.py +++ b/tests/metadata/test_metadata_two_point_sacc.py @@ -58,7 +58,7 @@ _type_to_sacc_string_real, ) from firecrown.updatable import ParamsMap -from tests._types import SaccGalaxyWindowedFixture +from tests._types import SaccGalaxyHarmonicFixture, SaccGalaxyWindowedFixture # pylint: disable=too-many-lines @@ -178,23 +178,21 @@ def fixture_sacc_galaxy_cells_three_tracers() -> tuple[ def test_extract_all_tracers_cells( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, tracers, _ = sacc_galaxy_cells + """Extract harmonic tracer distributions matching independent expectations. + + :param sacc_galaxy_cells: Harmonic SACC data and named expected distributions. + """ + sacc_data = sacc_galaxy_cells.sacc_data + tracers = sacc_galaxy_cells.distributions assert sacc_data is not None all_tracers = extract_all_tracers_tomographic_bins(sacc_data) for tracer in all_tracers: orig_tracer = tracers[tracer.bin_name] - assert_array_equal(tracer.z, orig_tracer[0]) - assert_array_equal(tracer.dndz, orig_tracer[1]) + assert_array_equal(tracer.z, orig_tracer.z) + assert_array_equal(tracer.dndz, orig_tracer.dndz) def test_extract_all_tracers_xis( @@ -582,16 +580,14 @@ def test_extract_all_tracers_projected_fields_map_tracer_without_data() -> None: def test_extract_all_metadata_index_harmonics( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_cells + """Extract harmonic indices for every expected tracer pair and data type. + + :param sacc_galaxy_cells: Harmonic SACC data and expected spectra. + """ + sacc_data = sacc_galaxy_cells.sacc_data + tracer_pairs = sacc_galaxy_cells.spectra all_data = extract_all_harmonic_metadata_indices(sacc_data) assert len(all_data) == len(tracer_pairs) @@ -602,16 +598,14 @@ def test_extract_all_metadata_index_harmonics( def test_extract_all_metadata_index_harmonics_by_type( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_cells + """Filter harmonic metadata indices to the requested shear data type. + + :param sacc_galaxy_cells: Harmonic SACC data and expected spectra. + """ + sacc_data = sacc_galaxy_cells.sacc_data + tracer_pairs = sacc_galaxy_cells.spectra all_data = extract_all_harmonic_metadata_indices( sacc_data, allowed_data_type=["galaxy_shear_cl_ee"] @@ -767,16 +761,14 @@ def test_extract_all_photoz_bin_combinations_reals( def test_extract_all_photoz_bin_combinations_harmonics( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_cells + """Recover expected harmonic bin combinations and their SACC names. + + :param sacc_galaxy_cells: Harmonic SACC data and expected spectra. + """ + sacc_data = sacc_galaxy_cells.sacc_data + tracer_pairs = sacc_galaxy_cells.spectra # We build all possible combinations of tracers all_bin_combs = extract_all_photoz_bin_combinations(sacc_data) @@ -803,16 +795,13 @@ def test_extract_all_photoz_bin_combinations_harmonics( def test_make_harmonics( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_cells + """Construct harmonic metadata from extracted bin combinations and multipoles. + + :param sacc_galaxy_cells: Harmonic SACC data used to extract bin combinations. + """ + sacc_data = sacc_galaxy_cells.sacc_data ells = np.unique(np.logspace(1, 3, 10).astype(np.int64)) all_bin_combs = extract_all_photoz_bin_combinations(sacc_data) @@ -852,16 +841,14 @@ def test_make_reals( @pytest.mark.slow def test_extract_all_harmonic_data( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_cells + """Extract consistent harmonic measurements matching independent expectations. + + :param sacc_galaxy_cells: Harmonic SACC data and named expected spectra. + """ + sacc_data = sacc_galaxy_cells.sacc_data + tracer_pairs = sacc_galaxy_cells.spectra two_point_harmonics = extract_all_harmonic_data(sacc_data) assert len(two_point_harmonics) == len(tracer_pairs) @@ -872,9 +859,9 @@ def test_extract_all_harmonic_data( tracer_names = TracerNames(metadata.XY.x.bin_name, metadata.XY.y.bin_name) assert (tracer_names, metadata.get_sacc_name()) in tracer_pairs - ells, Cell = tracer_pairs[(tracer_names, metadata.get_sacc_name())] - assert_array_equal(metadata.ells, ells) - assert_array_equal(two_point.data, Cell) + spectrum = tracer_pairs[(tracer_names, metadata.get_sacc_name())] + assert_array_equal(metadata.ells, spectrum.ells) + assert_array_equal(two_point.data, spectrum.cells) check_two_point_consistence_harmonic(two_point_harmonics) @@ -935,17 +922,16 @@ def test_extract_all_real_data( def test_constructor_harmonic_metadata( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, tp_factory: TwoPointFactory, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_cells + """Construct TwoPoint statistics from extracted harmonic metadata. + + :param sacc_galaxy_cells: Harmonic SACC data and expected spectra. + :param tp_factory: Factory for the statistics' sources. + """ + sacc_data = sacc_galaxy_cells.sacc_data + tracer_pairs = sacc_galaxy_cells.spectra two_point_harmonics = extract_all_harmonic_metadata(sacc_data) @@ -957,22 +943,21 @@ def test_constructor_harmonic_metadata( tracer_pairs_key = (two_point.sacc_tracers, two_point.sacc_data_type) assert tracer_pairs_key in tracer_pairs assert two_point.ells is not None - assert_array_equal(two_point.ells, tracer_pairs[tracer_pairs_key][0]) + assert_array_equal(two_point.ells, tracer_pairs[tracer_pairs_key].ells) @pytest.mark.slow def test_constructor_harmonic_data( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, tp_factory: TwoPointFactory, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_cells + """Construct TwoPoint statistics matching independently generated measurements. + + :param sacc_galaxy_cells: Harmonic SACC data and expected spectra. + :param tp_factory: Factory for the statistics' sources. + """ + sacc_data = sacc_galaxy_cells.sacc_data + tracer_pairs = sacc_galaxy_cells.spectra two_point_harmonics = extract_all_harmonic_data(sacc_data) @@ -985,9 +970,9 @@ def test_constructor_harmonic_data( tracer_pairs_key = (two_point.sacc_tracers, two_point.sacc_data_type) assert tracer_pairs_key in tracer_pairs assert two_point.ells is not None - assert_array_equal(two_point.ells, tracer_pairs[tracer_pairs_key][0]) + assert_array_equal(two_point.ells, tracer_pairs[tracer_pairs_key].ells) assert_array_equal( - two_point.get_data_vector(), tracer_pairs[tracer_pairs_key][1] + two_point.get_data_vector(), tracer_pairs[tracer_pairs_key].cells ) @@ -1288,18 +1273,17 @@ def test_constructor_reals_data( def test_compare_constructors_harmonic( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, tp_factory: TwoPointFactory, tools_with_vanilla_cosmology: ModelingTools, ) -> None: - sacc_data, _, _ = sacc_galaxy_cells + """Compare harmonic measurement construction with the original SACC read path. + + :param sacc_galaxy_cells: Harmonic SACC data for both construction paths. + :param tp_factory: Factory for the statistics' sources. + :param tools_with_vanilla_cosmology: Tools for comparing theory vectors. + """ + sacc_data = sacc_galaxy_cells.sacc_data two_point_harmonics = extract_all_harmonic_data(sacc_data) @@ -1452,16 +1436,17 @@ def test_compare_constructors_reals( def test_extract_all_data_harmonic_no_cov( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_cells + """Reject missing covariance without mutating the shared harmonic fixture. + + :param sacc_galaxy_cells: Shared harmonic SACC data and independent expectations. + """ + shared_sacc = sacc_galaxy_cells.sacc_data + shared_covariance = shared_sacc.covariance + assert shared_covariance is not None + expected_covariance = shared_covariance.dense.copy() + sacc_data = shared_sacc.copy() sacc_data.covariance = None with pytest.raises( ValueError, @@ -1469,6 +1454,10 @@ def test_extract_all_data_harmonic_no_cov( ): _ = extract_all_harmonic_data(sacc_data) + assert sacc_data.covariance is None + assert shared_sacc.covariance is shared_covariance + assert_array_equal(shared_sacc.covariance.dense, expected_covariance) + def test_extract_all_data_real_no_cov( sacc_galaxy_xis: tuple[ @@ -2278,17 +2267,13 @@ def test_make_all_photoz_bin_combinations_with_cmb_empty() -> None: def test_extract_all_photoz_bin_combinations_with_selector( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Test extract_all_photoz_bin_combinations with bin_pair_selector.""" - sacc_data, _, _ = sacc_galaxy_cells + """Test extract_all_photoz_bin_combinations with bin_pair_selector. + + :param sacc_galaxy_cells: Harmonic SACC data to filter by bin pair. + """ + sacc_data = sacc_galaxy_cells.sacc_data # Without selector - should get all combinations all_combinations = extract_all_photoz_bin_combinations(sacc_data) @@ -2309,17 +2294,13 @@ def test_extract_all_photoz_bin_combinations_with_selector( def test_extract_all_photoz_bin_combinations_with_source_selector( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Test extract_all_photoz_bin_combinations with source bin selector.""" - sacc_data, _, _ = sacc_galaxy_cells + """Test extract_all_photoz_bin_combinations with source bin selector. + + :param sacc_galaxy_cells: Harmonic SACC data to filter to source measurements. + """ + sacc_data = sacc_galaxy_cells.sacc_data # With source selector - should only get source (shear) measurements source_selector = SourceBinPairSelector() @@ -2337,17 +2318,13 @@ def test_extract_all_photoz_bin_combinations_with_source_selector( def test_extract_all_photoz_bin_combinations_with_lens_selector( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Test extract_all_photoz_bin_combinations with lens bin selector.""" - sacc_data, _, _ = sacc_galaxy_cells + """Test extract_all_photoz_bin_combinations with lens bin selector. + + :param sacc_galaxy_cells: Harmonic SACC data to filter to lens measurements. + """ + sacc_data = sacc_galaxy_cells.sacc_data # With lens selector - should only get lens (counts) measurements lens_selector = LensBinPairSelector() @@ -2364,17 +2341,13 @@ def test_extract_all_photoz_bin_combinations_with_lens_selector( def test_extract_all_harmonic_metadata_with_selector( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Test extract_all_harmonic_metadata with bin_pair_selector.""" - sacc_data, _, _ = sacc_galaxy_cells + """Test extract_all_harmonic_metadata with bin_pair_selector. + + :param sacc_galaxy_cells: Harmonic SACC data to filter by bin pair. + """ + sacc_data = sacc_galaxy_cells.sacc_data # Without selector - should get all metadata all_metadata = extract_all_harmonic_metadata(sacc_data) @@ -2400,17 +2373,13 @@ def test_extract_all_harmonic_metadata_with_selector( def test_extract_all_harmonic_metadata_with_source_selector( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Test extract_all_harmonic_metadata with source bin selector.""" - sacc_data, _, _ = sacc_galaxy_cells + """Test extract_all_harmonic_metadata with source bin selector. + + :param sacc_galaxy_cells: Harmonic SACC data to filter to source measurements. + """ + sacc_data = sacc_galaxy_cells.sacc_data # With source selector - should only get source (shear) measurements source_selector = SourceBinPairSelector() @@ -2426,17 +2395,13 @@ def test_extract_all_harmonic_metadata_with_source_selector( def test_extract_all_harmonic_metadata_with_combined_selector( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Test extract_all_harmonic_metadata with combined selectors.""" - sacc_data, _, _ = sacc_galaxy_cells + """Test extract_all_harmonic_metadata with combined selectors. + + :param sacc_galaxy_cells: Harmonic SACC data to filter by combined selectors. + """ + sacc_data = sacc_galaxy_cells.sacc_data # Combine auto-name and auto-measurement selectors combined_selector = AutoNameBinPairSelector() & AutoMeasurementBinPairSelector() @@ -2544,17 +2509,13 @@ def test_extract_all_real_metadata_with_combined_selector( def test_extract_all_harmonic_metadata_with_not_selector( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Test extract_all_harmonic_metadata with NOT selector.""" - sacc_data, _, _ = sacc_galaxy_cells + """Test extract_all_harmonic_metadata with NOT selector. + + :param sacc_galaxy_cells: Harmonic SACC data to filter by an inverted selector. + """ + sacc_data = sacc_galaxy_cells.sacc_data # Get all metadata all_metadata = extract_all_harmonic_metadata(sacc_data) @@ -2621,17 +2582,13 @@ def test_extract_all_real_metadata_with_or_selector( def test_extract_with_selector_empty_result( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Test that selector can result in empty extraction.""" - sacc_data, _, _ = sacc_galaxy_cells + """Test that selector can result in empty extraction. + + :param sacc_galaxy_cells: Harmonic SACC data to filter to nonexistent bins. + """ + sacc_data = sacc_galaxy_cells.sacc_data # Create selector for non-existent bin names impossible_selector = NamedBinPairSelector(names=[("nonexistent1", "nonexistent2")]) @@ -2649,17 +2606,13 @@ def test_extract_with_selector_empty_result( def test_extract_with_selector_preserves_data_quality( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Test that using selector doesn't affect data quality of extracted metadata.""" - sacc_data, _, _ = sacc_galaxy_cells + """Test that using selector doesn't affect data quality of extracted metadata. + + :param sacc_galaxy_cells: Harmonic SACC data for filtered and full extraction. + """ + sacc_data = sacc_galaxy_cells.sacc_data # Extract without selector all_metadata = extract_all_harmonic_metadata(sacc_data) diff --git a/tests/metadata/test_metadata_two_point_types.py b/tests/metadata/test_metadata_two_point_types.py index 53c4b3e40..274c9f86e 100644 --- a/tests/metadata/test_metadata_two_point_types.py +++ b/tests/metadata/test_metadata_two_point_types.py @@ -62,7 +62,7 @@ from firecrown.metadata_types._sacc_type_string import ( _type_to_sacc_string_real as real, ) -from tests._types import SaccGalaxyWindowedFixture +from tests._types import SaccGalaxyHarmonicFixture, SaccGalaxyWindowedFixture def test_order_enums() -> None: @@ -197,16 +197,13 @@ def test_measurement_is_compatible() -> None: def test_extract_all_tracers_types_cells( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_cells + """Verify harmonic tracers have the expected lens and source measurements. + + :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + """ + sacc_data = sacc_galaxy_cells.sacc_data tracers, _ = extract_all_measured_types(sacc_data) @@ -288,16 +285,13 @@ def test_extract_all_tracers_types_reals_inverted( def test_extract_all_tracers_types_cells_include_maybe( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_cells + """Verify repeated harmonic measured-type extraction returns equal results. + + :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + """ + sacc_data = sacc_galaxy_cells.sacc_data assert extract_all_measured_types(sacc_data) == extract_all_measured_types( sacc_data @@ -412,16 +406,13 @@ def test_check_two_point_consistence_real_harmonic( def test_check_two_point_consistence_harmonic_mixing_cov( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_cells + """Verify harmonic consistency rejects measurements with mixed covariance names. + + :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + """ + sacc_data = sacc_galaxy_cells.sacc_data tpms = extract_all_harmonic_data(sacc_data) @@ -463,16 +454,13 @@ def test_check_two_point_consistence_real_mixing_cov( def test_check_two_point_consistence_harmonic_non_unique_indices( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_cells + """Verify harmonic consistency rejects repeated indices within a measurement. + + :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + """ + sacc_data = sacc_galaxy_cells.sacc_data tpms = extract_all_harmonic_data(sacc_data) @@ -512,16 +500,13 @@ def test_check_two_point_consistence_real_non_unique_indices( def test_check_two_point_consistence_harmonic_indices_overlap( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_cells + """Verify harmonic consistency rejects indices shared between measurements. + + :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + """ + sacc_data = sacc_galaxy_cells.sacc_data tpms = extract_all_harmonic_data(sacc_data) @@ -719,21 +704,16 @@ def test_measurements_types_multiple_galaxy_and_cmb() -> None: def test_extract_all_measured_mixed_types_with_genuinely_mixed_tracers( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: """Test extract_all_measured_types with allow_mixed_types=True on mixed tracers. This test verifies that when allow_mixed_types=True, the function permits tracers to have multiple measurement types without raising an error. + + :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. """ - sacc_data, _, _ = sacc_galaxy_cells + sacc_data = sacc_galaxy_cells.sacc_data # This should NOT raise an error even though src/lens tracers may have # mixed types from different measurement combinations @@ -928,22 +908,17 @@ def test_extract_all_measured_mixed_types_multiple_measurements_same_tracer() -> def test_extract_all_measured_types_allow_mixed_types_vs_false( - sacc_galaxy_cells: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: """Test the difference between allow_mixed_types=True and False. This test verifies that with allow_mixed_types=False, we may get an error (after potential auto-correction attempts), while with allow_mixed_types=True, we get results without error. + + :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. """ - sacc_data, _, _ = sacc_galaxy_cells + sacc_data = sacc_galaxy_cells.sacc_data # Try with allow_mixed_types=True - should work without error result_true = extract_all_measured_types(sacc_data, allow_mixed_types=True) From ab184b1bcfc6b0727ee5ccb4ecc4990c8cdbf87c Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Fri, 16 Oct 2026 21:59:58 -0500 Subject: [PATCH 05/20] refactor(tests): type real SACC fixtures with named dataclasses Replace the tuple-based `sacc_galaxy_xis` and `sacc_galaxy_xis_inverted` fixtures with `SaccGalaxyRealFixture`, which exposes `sacc_data`, `distributions`, and `spectra`. Each measured spectrum is a `RealAngularCorrelation` with `thetas` and `xis` fields instead of a positional pair. Tests now access fields by name and their docstrings describe the expected inputs. test_extract_all_data_real_no_cov now copies the shared SACC object before clearing its covariance. It also asserts that the shared fixture's covariance is unchanged, so the module-scoped fixture is not mutated for later tests. --- tests/_types.py | 26 ++ tests/conftest.py | 102 +++---- .../metadata/test_metadata_two_point_sacc.py | 255 ++++++++---------- .../metadata/test_metadata_two_point_types.py | 96 +++---- 4 files changed, 227 insertions(+), 252 deletions(-) diff --git a/tests/_types.py b/tests/_types.py index 9de4b8189..47406d4ab 100644 --- a/tests/_types.py +++ b/tests/_types.py @@ -52,6 +52,32 @@ class SaccGalaxyHarmonicFixture: spectra: dict[tuple[TracerNames, str], HarmonicAngularPowerSpectrum] +@dataclass(frozen=True, kw_only=True) +class RealAngularCorrelation: + """Independent unwindowed angular coordinates and measured real-space xis. + + :param thetas: Angular coordinates for the measurements; no unit is enforced. + :param xis: Measured correlations, not theory predictions or number densities. + """ + + thetas: npt.NDArray[np.float64] + xis: npt.NDArray[np.float64] + + +@dataclass(frozen=True, kw_only=True) +class SaccGalaxyRealFixture: + """Real SACC data with independently generated named expectations. + + :param sacc_data: SACC data used by production extraction and construction. + :param distributions: Redshift distributions keyed by tracer name. + :param spectra: Measured correlations keyed by ordered tracer names and data type. + """ + + sacc_data: sacc.Sacc + distributions: dict[str, SampledRedshiftDistribution] + spectra: dict[tuple[TracerNames, str], RealAngularCorrelation] + + RedshiftDistribution: TypeAlias = tuple[ npt.NDArray[np.float64], npt.NDArray[np.float64] ] diff --git a/tests/conftest.py b/tests/conftest.py index 1b91704ca..c95103e5b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,8 +45,10 @@ from firecrown.utils import upper_triangle_indices from tests._types import ( HarmonicAngularPowerSpectrum, + RealAngularCorrelation, RedshiftDistribution, SaccGalaxyHarmonicFixture, + SaccGalaxyRealFixture, SaccGalaxyWindowedFixture, SampledRedshiftDistribution, WindowedAngularPowerSpectrum, @@ -982,18 +984,14 @@ def fixture_sacc_galaxy_cwindows() -> SaccGalaxyWindowedFixture: @pytest.fixture(name="sacc_galaxy_xis", scope="module") -def fixture_sacc_galaxy_xis() -> tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], -]: - """Fixture for a SACC data without window functions. +def fixture_sacc_galaxy_xis() -> SaccGalaxyRealFixture: + """Build unwindowed real SACC data and independent named expectations. - Module-scoped because SACC objects are expensive to create and are - read-only in tests. + Module-scoped because SACC objects are expensive to create. Consumers must + copy SACC before mutation; expected grids remain shared between records. + + :returns: SACC data, sampled redshift distributions by tracer name, and + measured xis by ordered tracer pair and data type. """ z = (np.linspace(0, 1.0, 256) + 0.05).astype(np.float64) @@ -1004,29 +1002,26 @@ def fixture_sacc_galaxy_xis() -> tuple[ src_bins_centers = np.linspace(0.25, 0.75, 5) lens_bins_centers = np.linspace(0.1, 0.6, 5) - tracers: dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]] = {} - tracer_pairs: dict[ - tuple[TracerNames, str], tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]] - ] = {} + tracers: dict[str, SampledRedshiftDistribution] = {} + tracer_pairs: dict[tuple[TracerNames, str], RealAngularCorrelation] = {} for i, mn in enumerate(src_bins_centers): - dndz = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) + dndz: npt.NDArray[np.float64] = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", f"src{i}", z, dndz) - tracers[f"src{i}"] = (z, dndz) + tracers[f"src{i}"] = SampledRedshiftDistribution(z=z, dndz=dndz) for i, mn in enumerate(lens_bins_centers): dndz = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", f"lens{i}", z, dndz) - tracers[f"lens{i}"] = (z, dndz) + tracers[f"lens{i}"] = SampledRedshiftDistribution(z=z, dndz=dndz) dv = [] for i, j in upper_triangle_indices(len(lens_bins_centers)): - xis = np.array(np.random.normal(size=thetas.shape[0])) + xis: npt.NDArray[np.float64] = np.array(np.random.normal(size=thetas.shape[0])) sacc_data.add_theta_xi("galaxy_density_xi", f"lens{i}", f"lens{j}", thetas, xis) tracer_pairs[(TracerNames(f"lens{i}", f"lens{j}"), "galaxy_density_xi")] = ( - thetas, - xis, + RealAngularCorrelation(thetas=thetas, xis=xis) ) dv.append(xis) @@ -1037,10 +1032,7 @@ def fixture_sacc_galaxy_xis() -> tuple[ ) tracer_pairs[ (TracerNames(f"src{i}", f"lens{j}"), "galaxy_shearDensity_xi_t") - ] = ( - thetas, - xis, - ) + ] = RealAngularCorrelation(thetas=thetas, xis=xis) dv.append(xis) for i, j in upper_triangle_indices(len(src_bins_centers)): @@ -1049,8 +1041,7 @@ def fixture_sacc_galaxy_xis() -> tuple[ "galaxy_shear_xi_minus", f"src{i}", f"src{j}", thetas, xis ) tracer_pairs[(TracerNames(f"src{i}", f"src{j}"), "galaxy_shear_xi_minus")] = ( - thetas, - xis, + RealAngularCorrelation(thetas=thetas, xis=xis) ) dv.append(xis) for i, j in upper_triangle_indices(len(src_bins_centers)): @@ -1059,8 +1050,7 @@ def fixture_sacc_galaxy_xis() -> tuple[ "galaxy_shear_xi_plus", f"src{i}", f"src{j}", thetas, xis ) tracer_pairs[(TracerNames(f"src{i}", f"src{j}"), "galaxy_shear_xi_plus")] = ( - thetas, - xis, + RealAngularCorrelation(thetas=thetas, xis=xis) ) dv.append(xis) @@ -1069,19 +1059,21 @@ def fixture_sacc_galaxy_xis() -> tuple[ sacc_data.add_covariance(cov) - return sacc_data, tracers, tracer_pairs + return SaccGalaxyRealFixture( + sacc_data=sacc_data, distributions=tracers, spectra=tracer_pairs + ) @pytest.fixture(name="sacc_galaxy_xis_inverted") -def fixture_sacc_galaxy_xis_inverted() -> tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], -]: - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_xis_inverted() -> SaccGalaxyRealFixture: + """Build inverted real SACC data and independent named expectations. + + Preserve the intentionally reversed tracer conventions for correction tests. + Expected grids remain shared between records. + + :returns: SACC data, sampled redshift distributions by tracer name, and + measured xis by the original inverted tracer-pair and data-type keys. + """ z = (np.linspace(0, 1.0, 256) + 0.05).astype(np.float64) thetas = np.linspace(0.0, 2.0 * np.pi, 20, dtype=np.float64) @@ -1091,29 +1083,26 @@ def fixture_sacc_galaxy_xis_inverted() -> tuple[ src_bins_centers = np.linspace(0.25, 0.75, 5) lens_bins_centers = np.linspace(0.1, 0.6, 5) - tracers: dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]] = {} - tracer_pairs: dict[ - tuple[TracerNames, str], tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]] - ] = {} + tracers: dict[str, SampledRedshiftDistribution] = {} + tracer_pairs: dict[tuple[TracerNames, str], RealAngularCorrelation] = {} for i, mn in enumerate(src_bins_centers): - dndz = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) + dndz: npt.NDArray[np.float64] = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", f"src{i}", z, dndz) - tracers[f"src{i}"] = (z, dndz) + tracers[f"src{i}"] = SampledRedshiftDistribution(z=z, dndz=dndz) for i, mn in enumerate(lens_bins_centers): dndz = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", f"lens{i}", z, dndz) - tracers[f"lens{i}"] = (z, dndz) + tracers[f"lens{i}"] = SampledRedshiftDistribution(z=z, dndz=dndz) dv = [] for i, j in upper_triangle_indices(len(lens_bins_centers)): - xis = np.array(np.random.normal(size=thetas.shape[0])) + xis: npt.NDArray[np.float64] = np.array(np.random.normal(size=thetas.shape[0])) sacc_data.add_theta_xi("galaxy_density_xi", f"lens{j}", f"lens{i}", thetas, xis) tracer_pairs[(TracerNames(f"lens{j}", f"lens{i}"), "galaxy_density_xi")] = ( - thetas, - xis, + RealAngularCorrelation(thetas=thetas, xis=xis) ) dv.append(xis) @@ -1124,10 +1113,7 @@ def fixture_sacc_galaxy_xis_inverted() -> tuple[ ) tracer_pairs[ (TracerNames(f"lens{j}", f"src{i}"), "galaxy_shearDensity_xi_t") - ] = ( - thetas, - xis, - ) + ] = RealAngularCorrelation(thetas=thetas, xis=xis) dv.append(xis) for i, j in upper_triangle_indices(len(src_bins_centers)): @@ -1136,8 +1122,7 @@ def fixture_sacc_galaxy_xis_inverted() -> tuple[ "galaxy_shear_xi_minus", f"src{j}", f"src{i}", thetas, xis ) tracer_pairs[(TracerNames(f"src{j}", f"src{i}"), "galaxy_shear_xi_minus")] = ( - thetas, - xis, + RealAngularCorrelation(thetas=thetas, xis=xis) ) dv.append(xis) for i, j in upper_triangle_indices(len(src_bins_centers)): @@ -1146,8 +1131,7 @@ def fixture_sacc_galaxy_xis_inverted() -> tuple[ "galaxy_shear_xi_plus", f"src{j}", f"src{i}", thetas, xis ) tracer_pairs[(TracerNames(f"src{j}", f"src{i}"), "galaxy_shear_xi_plus")] = ( - thetas, - xis, + RealAngularCorrelation(thetas=thetas, xis=xis) ) dv.append(xis) @@ -1156,7 +1140,9 @@ def fixture_sacc_galaxy_xis_inverted() -> tuple[ sacc_data.add_covariance(cov) - return sacc_data, tracers, tracer_pairs + return SaccGalaxyRealFixture( + sacc_data=sacc_data, distributions=tracers, spectra=tracer_pairs + ) @pytest.fixture(name="sacc_galaxy_cells_order_convention") diff --git a/tests/metadata/test_metadata_two_point_sacc.py b/tests/metadata/test_metadata_two_point_sacc.py index 89d2d4fe2..aff205801 100644 --- a/tests/metadata/test_metadata_two_point_sacc.py +++ b/tests/metadata/test_metadata_two_point_sacc.py @@ -58,7 +58,11 @@ _type_to_sacc_string_real, ) from firecrown.updatable import ParamsMap -from tests._types import SaccGalaxyHarmonicFixture, SaccGalaxyWindowedFixture +from tests._types import ( + SaccGalaxyHarmonicFixture, + SaccGalaxyRealFixture, + SaccGalaxyWindowedFixture, +) # pylint: disable=too-many-lines @@ -196,23 +200,21 @@ def test_extract_all_tracers_cells( def test_extract_all_tracers_xis( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, tracers, _ = sacc_galaxy_xis + """Extract real tracer distributions matching independent expectations. + + :param sacc_galaxy_xis: Real SACC data and named expected distributions. + """ + sacc_data = sacc_galaxy_xis.sacc_data + tracers = sacc_galaxy_xis.distributions assert sacc_data is not None all_tracers = extract_all_tracers_tomographic_bins(sacc_data) for tracer in all_tracers: orig_tracer = tracers[tracer.bin_name] - assert_array_equal(tracer.z, orig_tracer[0]) - assert_array_equal(tracer.dndz, orig_tracer[1]) + assert_array_equal(tracer.z, orig_tracer.z) + assert_array_equal(tracer.dndz, orig_tracer.dndz) def test_extract_all_tracers_cells_src0_src0( @@ -618,16 +620,14 @@ def test_extract_all_metadata_index_harmonics_by_type( def test_extract_all_metadata_index_reals( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_xis + """Extract real metadata indices for every independently generated spectrum. + + :param sacc_galaxy_xis: Real SACC data and expected spectra. + """ + sacc_data = sacc_galaxy_xis.sacc_data + tracer_pairs = sacc_galaxy_xis.spectra all_data = extract_all_real_metadata_indices(sacc_data) assert len(all_data) == len(tracer_pairs) @@ -638,16 +638,14 @@ def test_extract_all_metadata_index_reals( def test_extract_all_metadata_index_reals_by_type( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_xis + """Filter real metadata indices to the requested density data type. + + :param sacc_galaxy_xis: Real SACC data and expected spectra. + """ + sacc_data = sacc_galaxy_xis.sacc_data + tracer_pairs = sacc_galaxy_xis.spectra all_data = extract_all_real_metadata_indices( sacc_data, allowed_data_type=["galaxy_density_xi"] @@ -727,16 +725,14 @@ def test_extract_all_data_types_three_tracers_harmonics( def test_extract_all_photoz_bin_combinations_reals( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_xis + """Recover expected real bin combinations and their SACC names. + + :param sacc_galaxy_xis: Real SACC data and expected spectra. + """ + sacc_data = sacc_galaxy_xis.sacc_data + tracer_pairs = sacc_galaxy_xis.spectra # We build all possible combinations of tracers all_bin_combs = extract_all_photoz_bin_combinations(sacc_data) @@ -816,16 +812,13 @@ def test_make_harmonics( def test_make_reals( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_xis + """Construct real metadata from extracted bin combinations and angular grids. + + :param sacc_galaxy_xis: Real SACC data used to extract bin combinations. + """ + sacc_data = sacc_galaxy_xis.sacc_data thetas = np.linspace(0.0, 2.0 * np.pi, 20, dtype=np.float64) all_bin_combs = extract_all_photoz_bin_combinations(sacc_data) @@ -894,16 +887,14 @@ def test_extract_all_harmonic_with_window_data( @pytest.mark.slow def test_extract_all_real_data( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_xis + """Extract consistent real measurements matching independent expectations. + + :param sacc_galaxy_xis: Real SACC data and named expected spectra. + """ + sacc_data = sacc_galaxy_xis.sacc_data + tracer_pairs = sacc_galaxy_xis.spectra two_point_xis = extract_all_real_data(sacc_data) assert len(two_point_xis) == len(tracer_pairs) @@ -914,9 +905,9 @@ def test_extract_all_real_data( tracer_names = TracerNames(metadata.XY.x.bin_name, metadata.XY.y.bin_name) assert (tracer_names, metadata.get_sacc_name()) in tracer_pairs - thetas, xi = tracer_pairs[(tracer_names, metadata.get_sacc_name())] - assert_array_equal(metadata.thetas, thetas) - assert_array_equal(two_point.data, xi) + spectrum = tracer_pairs[(tracer_names, metadata.get_sacc_name())] + assert_array_equal(metadata.thetas, spectrum.thetas) + assert_array_equal(two_point.data, spectrum.xis) check_two_point_consistence_real(two_point_xis) @@ -1216,17 +1207,16 @@ def test_constructor_harmonic_with_window_data( def test_constructor_reals_metadata( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, tp_factory: TwoPointFactory, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_xis + """Construct TwoPoint statistics from extracted real metadata. + + :param sacc_galaxy_xis: Real SACC data and expected spectra. + :param tp_factory: Factory for the statistics' sources. + """ + sacc_data = sacc_galaxy_xis.sacc_data + tracer_pairs = sacc_galaxy_xis.spectra two_point_reals = extract_all_real_metadata(sacc_data) @@ -1238,22 +1228,21 @@ def test_constructor_reals_metadata( tracer_pairs_key = (two_point.sacc_tracers, two_point.sacc_data_type) assert tracer_pairs_key in tracer_pairs assert two_point.thetas is not None - assert_array_equal(two_point.thetas, tracer_pairs[tracer_pairs_key][0]) + assert_array_equal(two_point.thetas, tracer_pairs[tracer_pairs_key].thetas) @pytest.mark.slow def test_constructor_reals_data( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, tp_factory: TwoPointFactory, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_xis + """Construct TwoPoint statistics matching independently generated real data. + + :param sacc_galaxy_xis: Real SACC data and expected spectra. + :param tp_factory: Factory for the statistics' sources. + """ + sacc_data = sacc_galaxy_xis.sacc_data + tracer_pairs = sacc_galaxy_xis.spectra two_point_reals = extract_all_real_data(sacc_data) @@ -1266,9 +1255,9 @@ def test_constructor_reals_data( tracer_pairs_key = (two_point.sacc_tracers, two_point.sacc_data_type) assert tracer_pairs_key in tracer_pairs assert two_point.thetas is not None - assert_array_equal(two_point.thetas, tracer_pairs[tracer_pairs_key][0]) + assert_array_equal(two_point.thetas, tracer_pairs[tracer_pairs_key].thetas) assert_array_equal( - two_point.get_data_vector(), tracer_pairs[tracer_pairs_key][1] + two_point.get_data_vector(), tracer_pairs[tracer_pairs_key].xis ) @@ -1381,18 +1370,17 @@ def test_compare_constructors_harmonic_with_window( def test_compare_constructors_reals( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, tp_factory: TwoPointFactory, tools_with_vanilla_cosmology: ModelingTools, ) -> None: - sacc_data, _, _ = sacc_galaxy_xis + """Compare real measurement construction with the original SACC read path. + + :param sacc_galaxy_xis: Real SACC data for both construction paths. + :param tp_factory: Factory for the statistics' sources. + :param tools_with_vanilla_cosmology: Tools for comparing theory vectors. + """ + sacc_data = sacc_galaxy_xis.sacc_data two_point_xis = extract_all_real_data(sacc_data) @@ -1460,16 +1448,17 @@ def test_extract_all_data_harmonic_no_cov( def test_extract_all_data_real_no_cov( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_xis + """Reject missing covariance without mutating the shared real fixture. + + :param sacc_galaxy_xis: Shared real SACC data and independent expectations. + """ + shared_sacc = sacc_galaxy_xis.sacc_data + shared_covariance = shared_sacc.covariance + assert shared_covariance is not None + expected_covariance = shared_covariance.dense.copy() + sacc_data = shared_sacc.copy() sacc_data.covariance = None with pytest.raises( ValueError, @@ -1477,6 +1466,10 @@ def test_extract_all_data_real_no_cov( ): _ = extract_all_real_data(sacc_data) + assert sacc_data.covariance is None + assert shared_sacc.covariance is shared_covariance + assert_array_equal(shared_sacc.covariance.dense, expected_covariance) + def test_make_all_photoz_bin_combinations_with_cmb_basic() -> None: """Test basic functionality of make_all_photoz_bin_combinations_with_cmb.""" @@ -2418,17 +2411,13 @@ def test_extract_all_harmonic_metadata_with_combined_selector( def test_extract_all_real_metadata_with_selector( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - """Test extract_all_real_metadata with bin_pair_selector.""" - sacc_data, _, _ = sacc_galaxy_xis + """Test extract_all_real_metadata with bin_pair_selector. + + :param sacc_galaxy_xis: Real SACC data to filter by auto-correlation. + """ + sacc_data = sacc_galaxy_xis.sacc_data # Without selector - should get all metadata all_metadata = extract_all_real_metadata(sacc_data) @@ -2454,17 +2443,13 @@ def test_extract_all_real_metadata_with_selector( def test_extract_all_real_metadata_with_lens_selector( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - """Test extract_all_real_metadata with lens bin selector.""" - sacc_data, _, _ = sacc_galaxy_xis + """Test extract_all_real_metadata with lens bin selector. + + :param sacc_galaxy_xis: Real SACC data to filter by lens measurements. + """ + sacc_data = sacc_galaxy_xis.sacc_data # With lens selector - should only get lens (counts) measurements lens_selector = LensBinPairSelector() @@ -2481,17 +2466,13 @@ def test_extract_all_real_metadata_with_lens_selector( def test_extract_all_real_metadata_with_combined_selector( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - """Test extract_all_real_metadata with combined selectors.""" - sacc_data, _, _ = sacc_galaxy_xis + """Test extract_all_real_metadata with combined selectors. + + :param sacc_galaxy_xis: Real SACC data to filter by combined selectors. + """ + sacc_data = sacc_galaxy_xis.sacc_data # Combine auto-name and lens selectors combined_selector = AutoNameBinPairSelector() & LensBinPairSelector() @@ -2541,17 +2522,13 @@ def test_extract_all_harmonic_metadata_with_not_selector( def test_extract_all_real_metadata_with_or_selector( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - """Test extract_all_real_metadata with OR selector.""" - sacc_data, _, _ = sacc_galaxy_xis + """Test extract_all_real_metadata with OR selector. + + :param sacc_galaxy_xis: Real SACC data to filter by alternative selectors. + """ + sacc_data = sacc_galaxy_xis.sacc_data # Get all tracers to find specific bin names tracers = extract_all_tracers_tomographic_bins(sacc_data) diff --git a/tests/metadata/test_metadata_two_point_types.py b/tests/metadata/test_metadata_two_point_types.py index 274c9f86e..bb540cde5 100644 --- a/tests/metadata/test_metadata_two_point_types.py +++ b/tests/metadata/test_metadata_two_point_types.py @@ -62,7 +62,11 @@ from firecrown.metadata_types._sacc_type_string import ( _type_to_sacc_string_real as real, ) -from tests._types import SaccGalaxyHarmonicFixture, SaccGalaxyWindowedFixture +from tests._types import ( + SaccGalaxyHarmonicFixture, + SaccGalaxyRealFixture, + SaccGalaxyWindowedFixture, +) def test_order_enums() -> None: @@ -233,16 +237,13 @@ def test_extract_all_tracers_types_cwindows( def test_extract_all_tracers_types_reals( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_xis + """Verify real tracers retain their lens and source measurement meanings. + + :param sacc_galaxy_xis: Real SACC data with named independent expectations. + """ + sacc_data = sacc_galaxy_xis.sacc_data tracers, _ = extract_all_measured_types(sacc_data) @@ -259,16 +260,13 @@ def test_extract_all_tracers_types_reals( def test_extract_all_tracers_types_reals_inverted( - sacc_galaxy_xis_inverted: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis_inverted: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_xis_inverted + """Correct inverted real tracer conventions while preserving measurement types. + + :param sacc_galaxy_xis_inverted: Inverted real SACC data and named expectations. + """ + sacc_data = sacc_galaxy_xis_inverted.sacc_data with pytest.warns(DeprecationWarning, match="AUTO-CORRECTION PERFORMED"): tracers, _ = extract_all_measured_types(sacc_data) @@ -309,16 +307,13 @@ def test_extract_all_tracers_types_cwindows_include_maybe( def test_extract_all_tracers_types_xi_thetas_include_maybe( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_xis + """Verify repeated real measured-type extraction returns equal results. + + :param sacc_galaxy_xis: Real SACC data with named independent expectations. + """ + sacc_data = sacc_galaxy_xis.sacc_data assert extract_all_measured_types(sacc_data) == extract_all_measured_types( sacc_data @@ -429,16 +424,13 @@ def test_check_two_point_consistence_harmonic_mixing_cov( def test_check_two_point_consistence_real_mixing_cov( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_xis + """Verify real consistency rejects measurements with mixed covariance names. + + :param sacc_galaxy_xis: Real SACC data with named independent expectations. + """ + sacc_data = sacc_galaxy_xis.sacc_data tpms = extract_all_real_data(sacc_data) tpms[0] = replace(tpms[0], covariance_name="wrong_cov_name") @@ -476,16 +468,13 @@ def test_check_two_point_consistence_harmonic_non_unique_indices( def test_check_two_point_consistence_real_non_unique_indices( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_xis + """Verify real consistency rejects repeated indices within a measurement. + + :param sacc_galaxy_xis: Real SACC data with named independent expectations. + """ + sacc_data = sacc_galaxy_xis.sacc_data tpms = extract_all_real_data(sacc_data) new_indices = tpms[0].indices @@ -522,16 +511,13 @@ def test_check_two_point_consistence_harmonic_indices_overlap( def test_check_two_point_consistence_real_indices_overlap( - sacc_galaxy_xis: tuple[ - sacc.Sacc, - dict[str, tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]], - dict[ - tuple[TracerNames, str], - tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]], - ], - ], + sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_xis + """Verify real consistency rejects indices shared between measurements. + + :param sacc_galaxy_xis: Real SACC data with named independent expectations. + """ + sacc_data = sacc_galaxy_xis.sacc_data tpms = extract_all_real_data(sacc_data) From 416e6145125fefc9e0582667d3ce119de4865eca Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Fri, 16 Oct 2026 22:56:17 -0500 Subject: [PATCH 06/20] refactor(test): type windowed SACC fixture with dataclasses Replace the tuple-based `SaccGalaxyWindowedFixture` and `WindowedAngularPowerSpectrum` aliases in tests/_types.py with frozen, keyword-only dataclasses. The fixture now exposes `sacc_data`, `distributions`, and `spectra` fields, and each spectrum exposes `support_ells`, `bandpowers`, and `window` instead of positional unpacking. Update conftest.py to build the new records, and update the metadata, types, and Gaussian likelihood tests to read the named fields. Test docstrings now describe the fixture contract. Behavior of the tests is unchanged. --- tests/_types.py | 47 ++++++++----- tests/conftest.py | 44 +++++++------ .../gauss_family/test_const_gaussian.py | 14 +++- .../gauss_family/test_const_gaussianPM.py | 14 +++- .../metadata/test_metadata_two_point_sacc.py | 66 ++++++++++++++----- .../metadata/test_metadata_two_point_types.py | 12 +++- 6 files changed, 139 insertions(+), 58 deletions(-) diff --git a/tests/_types.py b/tests/_types.py index 47406d4ab..48a09ba6f 100644 --- a/tests/_types.py +++ b/tests/_types.py @@ -78,22 +78,37 @@ class SaccGalaxyRealFixture: spectra: dict[tuple[TracerNames, str], RealAngularCorrelation] -RedshiftDistribution: TypeAlias = tuple[ - npt.NDArray[np.float64], npt.NDArray[np.float64] -] -"""Sampled redshift distribution as (z, dndz), without a normalization guarantee.""" - -WindowedAngularPowerSpectrum: TypeAlias = tuple[ - npt.NDArray[np.int64], npt.NDArray[np.float64], sacc.BandpowerWindow -] -"""Window ell grid, measured bandpowers, and their bandpower window.""" - -SaccGalaxyWindowedFixture: TypeAlias = tuple[ - sacc.Sacc, - dict[str, RedshiftDistribution], - dict[tuple[TracerNames, str], WindowedAngularPowerSpectrum], -] -"""SACC data, distributions by tracer name, and spectra by tracer pair/data type.""" +@dataclass(frozen=True, kw_only=True) +class WindowedAngularPowerSpectrum: + """Independent window support, measured bandpowers, and their shared window. + + Support multipoles need not match the number of measured bandpowers. + SACC observation labels are weighted support sums, not the support grid + or normalized band centers. + + :param support_ells: Integer multipoles on which the window is defined. + :param bandpowers: Measured values, one per window column, not predictions. + :param window: SACC BandpowerWindow shared with the fixture's data points; + its values are support multipoles and its weights are unnormalized. + """ + + support_ells: npt.NDArray[np.int64] + bandpowers: npt.NDArray[np.float64] + window: sacc.BandpowerWindow + + +@dataclass(frozen=True, kw_only=True) +class SaccGalaxyWindowedFixture: + """Windowed SACC data with independently generated named expectations. + + :param sacc_data: SACC data used by production extraction and construction. + :param distributions: Redshift distributions keyed by tracer name. + :param spectra: Windowed spectra keyed by ordered tracer names and data type. + """ + + sacc_data: sacc.Sacc + distributions: dict[str, SampledRedshiftDistribution] + spectra: dict[tuple[TracerNames, str], WindowedAngularPowerSpectrum] class NumCosmoCosmologyFixture(TypedDict): diff --git a/tests/conftest.py b/tests/conftest.py index c95103e5b..8a51ade19 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,7 +46,6 @@ from tests._types import ( HarmonicAngularPowerSpectrum, RealAngularCorrelation, - RedshiftDistribution, SaccGalaxyHarmonicFixture, SaccGalaxyRealFixture, SaccGalaxyWindowedFixture, @@ -883,10 +882,15 @@ def fixture_sacc_galaxy_cells() -> SaccGalaxyHarmonicFixture: @pytest.fixture(name="sacc_galaxy_cwindows", scope="module") def fixture_sacc_galaxy_cwindows() -> SaccGalaxyWindowedFixture: - """Fixture for a SACC data with window functions. + """Build windowed SACC data and independent named expectations. - Module-scoped because SACC objects are expensive to create and are - read-only in tests. + Module-scoped because SACC objects are expensive to create. Expected grids + remain shared between records, and windows are shared with SACC data points. + Ten support multipoles yield five measured bandpowers; SACC observation + labels are unnormalized weighted sums of the support multipoles. + + :returns: SACC data, sampled redshift distributions by tracer name, and + windowed spectra by ordered tracer pair and data type. """ sacc_data = sacc.Sacc() @@ -897,18 +901,18 @@ def fixture_sacc_galaxy_cwindows() -> SaccGalaxyWindowedFixture: src_bins_centers = np.linspace(0.25, 0.75, 5) lens_bins_centers = np.linspace(0.1, 0.6, 5) - tracers: dict[str, RedshiftDistribution] = {} + tracers: dict[str, SampledRedshiftDistribution] = {} tracer_pairs: dict[tuple[TracerNames, str], WindowedAngularPowerSpectrum] = {} for i, mn in enumerate(src_bins_centers): - dndz = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) + dndz: npt.NDArray[np.float64] = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", f"src{i}", z, dndz) - tracers[f"src{i}"] = (z, dndz) + tracers[f"src{i}"] = SampledRedshiftDistribution(z=z, dndz=dndz) for i, mn in enumerate(lens_bins_centers): dndz = np.exp(-0.5 * (z - mn) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", f"lens{i}", z, dndz) - tracers[f"lens{i}"] = (z, dndz) + tracers[f"lens{i}"] = SampledRedshiftDistribution(z=z, dndz=dndz) for i, j in upper_triangle_indices(len(src_bins_centers)): weights = ( @@ -917,7 +921,7 @@ def fixture_sacc_galaxy_cwindows() -> SaccGalaxyWindowedFixture: + np.eye(ells.shape[0], nobs, k=-5, dtype=np.float64) ) window = sacc.BandpowerWindow(ells, weights) - Cells = np.random.normal(size=nobs) + Cells: npt.NDArray[np.float64] = np.random.normal(size=nobs) sacc_data.add_ell_cl( "galaxy_shear_cl_ee", f"src{i}", @@ -927,9 +931,9 @@ def fixture_sacc_galaxy_cwindows() -> SaccGalaxyWindowedFixture: window=window, ) tracer_pairs[(TracerNames(f"src{i}", f"src{j}"), "galaxy_shear_cl_ee")] = ( - ells, - Cells, - window, + WindowedAngularPowerSpectrum( + support_ells=ells, bandpowers=Cells, window=window + ) ) for i, j in upper_triangle_indices(len(lens_bins_centers)): @@ -949,9 +953,9 @@ def fixture_sacc_galaxy_cwindows() -> SaccGalaxyWindowedFixture: window=window, ) tracer_pairs[(TracerNames(f"lens{i}", f"lens{j}"), "galaxy_density_cl")] = ( - ells, - Cells, - window, + WindowedAngularPowerSpectrum( + support_ells=ells, bandpowers=Cells, window=window + ) ) for i, j in product(range(len(src_bins_centers)), range(len(lens_bins_centers))): @@ -972,15 +976,15 @@ def fixture_sacc_galaxy_cwindows() -> SaccGalaxyWindowedFixture: ) tracer_pairs[ (TracerNames(f"src{i}", f"lens{j}"), "galaxy_shearDensity_cl_e") - ] = ( - ells, - Cells, - window, + ] = WindowedAngularPowerSpectrum( + support_ells=ells, bandpowers=Cells, window=window ) sacc_data.add_covariance(np.identity(len(sacc_data)) * 0.01) - return sacc_data, tracers, tracer_pairs + return SaccGalaxyWindowedFixture( + sacc_data=sacc_data, distributions=tracers, spectra=tracer_pairs + ) @pytest.fixture(name="sacc_galaxy_xis", scope="module") diff --git a/tests/likelihood/gauss_family/test_const_gaussian.py b/tests/likelihood/gauss_family/test_const_gaussian.py index 927d7fdb7..1ed671f94 100644 --- a/tests/likelihood/gauss_family/test_const_gaussian.py +++ b/tests/likelihood/gauss_family/test_const_gaussian.py @@ -522,7 +522,12 @@ def test_create_ready( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, tp_factory: TwoPointFactory, ) -> None: - sacc_data, _, _ = sacc_galaxy_cwindows + """Create a ready Gaussian likelihood from extracted windowed measurements. + + :param sacc_galaxy_cwindows: Windowed SACC data for measurement extraction. + :param tp_factory: Factory for the statistics' sources. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data two_point_harmonics = extract_all_harmonic_data(sacc_data) two_points = TwoPoint.from_measurement(two_point_harmonics, tp_factory) @@ -537,7 +542,12 @@ def test_create_ready_wrong_size( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, tp_factory: TwoPointFactory, ) -> None: - sacc_data, _, _ = sacc_galaxy_cwindows + """Reject a covariance too small for the extracted windowed measurements. + + :param sacc_galaxy_cwindows: Windowed SACC data for measurement extraction. + :param tp_factory: Factory for the statistics' sources. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data two_point_harmonics = extract_all_harmonic_data(sacc_data) two_points = TwoPoint.from_measurement(two_point_harmonics, tp_factory) diff --git a/tests/likelihood/gauss_family/test_const_gaussianPM.py b/tests/likelihood/gauss_family/test_const_gaussianPM.py index 39555533e..4892af974 100644 --- a/tests/likelihood/gauss_family/test_const_gaussianPM.py +++ b/tests/likelihood/gauss_family/test_const_gaussianPM.py @@ -583,7 +583,12 @@ def test_create_ready( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, tp_factory: TwoPointFactory, ) -> None: - sacc_data, _, _ = sacc_galaxy_cwindows + """Create a ready point-mass likelihood from extracted windowed measurements. + + :param sacc_galaxy_cwindows: Windowed SACC data for measurement extraction. + :param tp_factory: Factory for the statistics' sources. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data two_point_harmonics = extract_all_harmonic_data(sacc_data) two_points = TwoPoint.from_measurement(two_point_harmonics, tp_factory) @@ -598,7 +603,12 @@ def test_create_ready_wrong_size( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, tp_factory: TwoPointFactory, ) -> None: - sacc_data, _, _ = sacc_galaxy_cwindows + """Reject a covariance too small for the extracted windowed measurements. + + :param sacc_galaxy_cwindows: Windowed SACC data for measurement extraction. + :param tp_factory: Factory for the statistics' sources. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data two_point_harmonics = extract_all_harmonic_data(sacc_data) two_points = TwoPoint.from_measurement(two_point_harmonics, tp_factory) diff --git a/tests/metadata/test_metadata_two_point_sacc.py b/tests/metadata/test_metadata_two_point_sacc.py index aff205801..84c57a26f 100644 --- a/tests/metadata/test_metadata_two_point_sacc.py +++ b/tests/metadata/test_metadata_two_point_sacc.py @@ -863,7 +863,12 @@ def test_extract_all_harmonic_data( def test_extract_all_harmonic_with_window_data( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_cwindows + """Extract windowed measurements matching independent support and bandpowers. + + :param sacc_galaxy_cwindows: Windowed SACC data and named expected spectra. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data + tracer_pairs = sacc_galaxy_cwindows.spectra two_point_harmonics = extract_all_harmonic_data(sacc_data) assert len(two_point_harmonics) == len(tracer_pairs) @@ -874,9 +879,10 @@ def test_extract_all_harmonic_with_window_data( tracer_names = TracerNames(metadata.XY.x.bin_name, metadata.XY.y.bin_name) assert (tracer_names, metadata.get_sacc_name()) in tracer_pairs - ells, Cell, window = tracer_pairs[(tracer_names, metadata.get_sacc_name())] - assert_array_equal(metadata.ells, ells) - assert_array_equal(two_point.data, Cell) + spectrum = tracer_pairs[(tracer_names, metadata.get_sacc_name())] + window = spectrum.window + assert_array_equal(metadata.ells, spectrum.support_ells) + assert_array_equal(two_point.data, spectrum.bandpowers) assert metadata.window is not None assert_array_equal(metadata.window, window.weight / window.weight.sum(axis=0)) @@ -971,7 +977,13 @@ def test_constructor_harmonic_with_window_metadata( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, tp_factory: TwoPointFactory, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_cwindows + """Construct TwoPoint statistics from extracted windowed harmonic metadata. + + :param sacc_galaxy_cwindows: Windowed SACC data and independent windows. + :param tp_factory: Factory for the statistics' sources. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data + tracer_pairs = sacc_galaxy_cwindows.spectra two_point_harmonics = extract_all_harmonic_metadata(sacc_data) @@ -982,7 +994,7 @@ def test_constructor_harmonic_with_window_metadata( for two_point in two_points_new: tracer_pairs_key = (two_point.sacc_tracers, two_point.sacc_data_type) assert tracer_pairs_key in tracer_pairs - _, _, window = tracer_pairs[tracer_pairs_key] + window = tracer_pairs[tracer_pairs_key].window assert two_point.ells is not None assert two_point.window is not None assert_array_equal( @@ -995,8 +1007,11 @@ def test_constructor_harmonic_with_window_metadata( def test_extract_all_harmonic_metadata_normalize_true( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, ) -> None: - """Test that normalize=True normalizes windows in extract_all_harmonic_metadata.""" - sacc_data, _, _ = sacc_galaxy_cwindows + """Test that normalize=True normalizes windows in extract_all_harmonic_metadata. + + :param sacc_galaxy_cwindows: Windowed SACC data with unnormalized weights. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data two_point_harmonics = extract_all_harmonic_metadata(sacc_data, normalize=True) @@ -1012,8 +1027,11 @@ def test_extract_all_harmonic_metadata_normalize_true( def test_extract_all_harmonic_metadata_normalize_false( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, ) -> None: - """Test that normalize=False keeps windows unnormalized.""" - sacc_data, _, _ = sacc_galaxy_cwindows + """Test that normalize=False keeps windows unnormalized. + + :param sacc_galaxy_cwindows: Windowed SACC data with unnormalized weights. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data two_point_harmonics = extract_all_harmonic_metadata(sacc_data, normalize=False) @@ -1033,8 +1051,11 @@ def test_extract_all_harmonic_metadata_normalize_false( def test_extract_all_harmonic_metadata_default_normalizes( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, ) -> None: - """Test that the default behavior (no normalize parameter) normalizes windows.""" - sacc_data, _, _ = sacc_galaxy_cwindows + """Test that the default behavior (no normalize parameter) normalizes windows. + + :param sacc_galaxy_cwindows: Windowed SACC data with unnormalized weights. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data two_point_harmonics = extract_all_harmonic_metadata(sacc_data) @@ -1183,7 +1204,13 @@ def test_constructor_harmonic_with_window_data( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, tp_factory: TwoPointFactory, ) -> None: - sacc_data, _, tracer_pairs = sacc_galaxy_cwindows + """Construct TwoPoint statistics matching independent bandpowers and windows. + + :param sacc_galaxy_cwindows: Windowed SACC data and named expected spectra. + :param tp_factory: Factory for the statistics' sources. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data + tracer_pairs = sacc_galaxy_cwindows.spectra two_point_harmonics = extract_all_harmonic_data(sacc_data) @@ -1195,9 +1222,10 @@ def test_constructor_harmonic_with_window_data( for two_point in two_points_new: tracer_pairs_key = (two_point.sacc_tracers, two_point.sacc_data_type) assert tracer_pairs_key in tracer_pairs - _, Cell, window = tracer_pairs[tracer_pairs_key] + spectrum = tracer_pairs[tracer_pairs_key] + window = spectrum.window assert two_point.ells is not None - assert_array_equal(two_point.get_data_vector(), Cell) + assert_array_equal(two_point.get_data_vector(), spectrum.bandpowers) assert two_point.window is not None assert_array_equal( two_point.window, @@ -1320,7 +1348,13 @@ def test_compare_constructors_harmonic_with_window( tp_factory: TwoPointFactory, tools_with_vanilla_cosmology: ModelingTools, ) -> None: - sacc_data, _, _ = sacc_galaxy_cwindows + """Compare windowed measurement construction with the original SACC read path. + + :param sacc_galaxy_cwindows: Windowed SACC data for both construction paths. + :param tp_factory: Factory for the statistics' sources. + :param tools_with_vanilla_cosmology: Tools for comparing theory vectors. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data two_point_harmonics = extract_all_harmonic_data(sacc_data) diff --git a/tests/metadata/test_metadata_two_point_types.py b/tests/metadata/test_metadata_two_point_types.py index bb540cde5..2411b4a2c 100644 --- a/tests/metadata/test_metadata_two_point_types.py +++ b/tests/metadata/test_metadata_two_point_types.py @@ -223,7 +223,11 @@ def test_extract_all_tracers_types_cells( def test_extract_all_tracers_types_cwindows( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_cwindows + """Verify windowed tracers retain their lens and source measurement meanings. + + :param sacc_galaxy_cwindows: Windowed SACC data with named expectations. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data tracers, _ = extract_all_measured_types(sacc_data) @@ -299,7 +303,11 @@ def test_extract_all_tracers_types_cells_include_maybe( def test_extract_all_tracers_types_cwindows_include_maybe( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, ) -> None: - sacc_data, _, _ = sacc_galaxy_cwindows + """Verify repeated windowed measured-type extraction returns equal results. + + :param sacc_galaxy_cwindows: Windowed SACC data with named expectations. + """ + sacc_data = sacc_galaxy_cwindows.sacc_data assert extract_all_measured_types(sacc_data) == extract_all_measured_types( sacc_data From 88978ac00297aa89ac4a39a08ea578f7a66da3d8 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 17 Oct 2026 01:13:25 -0500 Subject: [PATCH 07/20] refactor(test): Use HarmonicWindowFunction dataclass in window fixture Replace the tuple returned by the window_1 fixture with a frozen, keyword-only HarmonicWindowFunction dataclass holding ell_grid, weights, and band_labels. The two_point_cwindow and optimized_two_point_cwindow fixtures now read named fields instead of positional tuple indices, so the mapping onto TwoPointHarmonic's ells, window, and window_ells arguments is explicit. The dataclass docstring notes that its dtype annotations do not check weight-matrix shape or physical units. --- tests/conftest.py | 70 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8a51ade19..401766734 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ # pylint: disable=too-many-lines import ast import sys +from dataclasses import dataclass from itertools import product from typing import Callable, assert_never @@ -391,16 +392,40 @@ def fixture_all_real_bins() -> list[TomographicBin]: ] +@dataclass(frozen=True, kw_only=True) +class HarmonicWindowFunction: + """Harmonic window with its ell grid, weight matrix, and band labels. + + The weight matrix has one row per ell in the grid and one column per band. + Named fields and dtype annotations do not check weight-matrix dimensions, + shapes, same-dtype swaps between arrays, or physical units. + + :param ell_grid: Integer multipoles on which the window weights are defined. + :param weights: Weight matrix with one row per ell and one column per band. + :param band_labels: Float labels, one per weight column; TwoPointHarmonic checks + their one-dimensional shape and count, not their values or physical meaning. + """ + + ell_grid: npt.NDArray[np.int64] + weights: npt.NDArray[np.float64] + band_labels: npt.NDArray[np.float64] + + @pytest.fixture(name="window_1") -def fixture_window_1() -> ( - tuple[npt.NDArray[np.int64], npt.NDArray[np.float64], npt.NDArray[np.float64]] -): - """Generate a Window object with 100 ells.""" +def fixture_window_1() -> HarmonicWindowFunction: + """Generate a harmonic window with 100 ells and four bands. + + :returns: The ell grid, weight matrix, and band labels for the window. + """ ells = np.array(np.linspace(0, 100, 100), dtype=np.int64) weights = np.ones(400).reshape(-1, 4) - window_ells = np.array([0, 1, 2, 3], dtype=np.float64) + band_labels = np.array([0, 1, 2, 3], dtype=np.float64) - return ells, weights, window_ells + return HarmonicWindowFunction( + ell_grid=ells, + weights=weights, + band_labels=band_labels, + ) @pytest.fixture(name="harmonic_two_point_xy") @@ -435,17 +460,20 @@ def fixture_real_two_point_xy( @pytest.fixture(name="two_point_cwindow") def fixture_two_point_cwindow( - window_1: tuple[ - npt.NDArray[np.int64], npt.NDArray[np.float64], npt.NDArray[np.float64] - ], + window_1: HarmonicWindowFunction, harmonic_two_point_xy: TwoPointXY, ) -> TwoPointHarmonic: - """Generate a TwoPointXY object with 100 ells.""" + """Generate a TwoPointHarmonic with 100 ells and four bands. + + :param window_1: Harmonic window supplying the ell grid, weights, and labels. + :param harmonic_two_point_xy: Harmonic TwoPointXY metadata for the measurement. + :returns: A TwoPointHarmonic carrying the windowed ell grid. + """ two_point = TwoPointHarmonic( XY=harmonic_two_point_xy, - ells=window_1[0], - window=window_1[1], - window_ells=window_1[2], + ells=window_1.ell_grid, + window=window_1.weights, + window_ells=window_1.band_labels, ) return two_point @@ -1516,20 +1544,22 @@ def fixture_optimized_harmonic_two_point_xy( # Optimized versions of dependent fixtures @pytest.fixture(name="optimized_two_point_cwindow") def fixture_optimized_two_point_cwindow( - window_1: tuple[ - npt.NDArray[np.int64], npt.NDArray[np.float64], npt.NDArray[np.float64] - ], + window_1: HarmonicWindowFunction, optimized_harmonic_two_point_xy: TwoPointXY, ) -> TwoPointHarmonic: - """Generate a TwoPointXY object with zero skipped tests. + """Generate a TwoPointHarmonic using compatible measurement pairs. Uses optimized fixtures that pre-filter for valid measurement pairs. + + :param window_1: Harmonic window supplying the ell grid, weights, and labels. + :param optimized_harmonic_two_point_xy: Pre-filtered harmonic TwoPointXY metadata. + :returns: A TwoPointHarmonic carrying the windowed ell grid. """ two_point = TwoPointHarmonic( XY=optimized_harmonic_two_point_xy, - ells=window_1[0], - window=window_1[1], - window_ells=window_1[2], + ells=window_1.ell_grid, + window=window_1.weights, + window_ells=window_1.band_labels, ) return two_point From 1a0063323ea743a2cf9db9bc96fa9f3ec5baee31 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 17 Oct 2026 01:44:25 -0500 Subject: [PATCH 08/20] refactor(tests): use dataclass for scale cut window fixture Replace the positional tuple returned by the window function fixture with a frozen ScaleCutWindowFunction dataclass. Fields are accessed by name instead of index, so the weights, support multipoles, and bin centers can no longer be swapped silently. Test behavior is unchanged. --- tests/test_scale_cuts_with_window.py | 72 ++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/tests/test_scale_cuts_with_window.py b/tests/test_scale_cuts_with_window.py index 297df7c07..c499b4da7 100644 --- a/tests/test_scale_cuts_with_window.py +++ b/tests/test_scale_cuts_with_window.py @@ -1,5 +1,6 @@ """Test that the scale cuts with bandpower windows work correctly.""" +from dataclasses import dataclass from pathlib import Path import numpy as np @@ -23,18 +24,42 @@ from firecrown.utils import base_model_from_yaml, upper_triangle_indices +@dataclass(frozen=True, kw_only=True) +class ScaleCutWindowFunction: + """Window weights, support multipoles, and geometric bin centers. + + Frozen bindings leave the arrays mutable. Names and dtype annotations do not + enforce matrix dimensions, shapes, same-dtype array swaps, or physical units. + + :param weights: Column-normalized weights, one row per support multipole and + one column per bin. + :param support_ells: Integer multipoles on which the window is sampled. + :param bin_centers: Geometric means of adjacent bin edges, used as SACC + observation labels; not weighted means of the window support. + """ + + weights: npt.NDArray[np.float64] + support_ells: npt.NDArray[np.int64] + bin_centers: npt.NDArray[np.float64] + + @pytest.fixture(name="window_function_data", scope="module") -def fixture_window_function() -> ( - tuple[npt.NDArray[np.float64], npt.NDArray[np.int64], npt.NDArray[np.float64]] -): - """Generate window function data.""" +def fixture_window_function() -> ScaleCutWindowFunction: + """Generate a normalized window on 800 multipoles for five bins. + + :returns: Window weights, support multipoles, and geometric bin centers. + """ n_ell_bin = 5 ell_bin_edges = np.geomspace(10, 500, n_ell_bin + 1) - ell_bin_centers = np.sqrt(ell_bin_edges[:-1] * ell_bin_edges[1:]) + ell_bin_centers: npt.NDArray[np.float64] = np.sqrt( + ell_bin_edges[:-1] * ell_bin_edges[1:] + ) ell_bin_widths = np.diff(ell_bin_edges) - ell_window = np.arange(800) - window_function = np.zeros((ell_window.shape[0], n_ell_bin)) + ell_window: npt.NDArray[np.int64] = np.arange(800) + window_function: npt.NDArray[np.float64] = np.zeros( + (ell_window.shape[0], n_ell_bin) + ) for i, (mu_ell, sigma_ell) in enumerate(zip(ell_bin_centers, ell_bin_widths)): window_function[:, i] = np.exp( -0.5 * (ell_window - mu_ell) ** 2 / (sigma_ell) ** 2 @@ -42,14 +67,16 @@ def fixture_window_function() -> ( window_function = window_function / window_function.sum(axis=0, keepdims=True) - return window_function, ell_window, ell_bin_centers + return ScaleCutWindowFunction( + weights=window_function, + support_ells=ell_window, + bin_centers=ell_bin_centers, + ) @pytest.fixture(name="minimal_3x2pt_sacc", scope="module") def fixture_minimal_3x2pt_sacc( - window_function_data: tuple[ - npt.NDArray[np.float64], npt.NDArray[np.int64], npt.NDArray[np.float64] - ], + window_function_data: ScaleCutWindowFunction, ) -> tuple[ sacc.Sacc, dict[ @@ -63,8 +90,11 @@ def fixture_minimal_3x2pt_sacc( ], ccl.Cosmology, ]: - """Generate SACC data with window functions. - At the moment, this only adds cosmic shear data.""" + """Generate SACC data with window functions for cosmic shear only. + + :param window_function_data: Window weights, support, and observation centers. + :returns: SACC data, independent CCL bandpowers with window data, and cosmology. + """ sacc_data = sacc.Sacc() z = (np.linspace(0, 4.0, 256) + 0.05).astype(np.float64) @@ -84,27 +114,29 @@ def fixture_minimal_3x2pt_sacc( ) for i, j in upper_triangle_indices(len(src_bins_centers)): - window = sacc.BandpowerWindow(window_function_data[1], window_function_data[0]) + window = sacc.BandpowerWindow( + window_function_data.support_ells, window_function_data.weights + ) Cell = ccl.angular_cl( cosmo=cosmo, tracer1=ccl_tracers[f"src{i}"], tracer2=ccl_tracers[f"src{j}"], - ell=window_function_data[1], + ell=window_function_data.support_ells, ) - Cell_binned = window_function_data[0].T @ Cell + Cell_binned = window_function_data.weights.T @ Cell sacc_data.add_ell_cl( data_type="galaxy_shear_cl_ee", tracer1=f"src{i}", tracer2=f"src{j}", - ell=window_function_data[2], + ell=window_function_data.bin_centers, x=Cell_binned, window=window, ) ccl_cell[f"src{i}-src{j}"] = ( Cell_binned, - window_function_data[2], - window_function_data[1], - window_function_data[0], + window_function_data.bin_centers, + window_function_data.support_ells, + window_function_data.weights, ) sacc_data.add_covariance(np.identity(len(sacc_data)) * 0.01) From eb37c23bc1674d3483919637bd6405c999eaf8c7 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 17 Oct 2026 02:45:56 -0500 Subject: [PATCH 09/20] refactor(test): replace scale-cut fixture tuples with typed dataclasses Introduce ScaleCutBandpowerPrediction and ScaleCutSaccFixture so the minimal_3x2pt_sacc fixture returns named fields instead of positional tuples. The label and support tests now read spectrum.bandpowers and spectrum.window instead of unpacking four-element tuples. The window fields reference the shared window_function_data directly rather than copying its arrays. Add explicit type annotations to the fixture locals and update the test docstrings to document their parameters. --- tests/test_scale_cuts_with_window.py | 135 +++++++++++++++------------ 1 file changed, 76 insertions(+), 59 deletions(-) diff --git a/tests/test_scale_cuts_with_window.py b/tests/test_scale_cuts_with_window.py index c499b4da7..b8a6cbd7e 100644 --- a/tests/test_scale_cuts_with_window.py +++ b/tests/test_scale_cuts_with_window.py @@ -74,22 +74,41 @@ def fixture_window_function() -> ScaleCutWindowFunction: ) +@dataclass(frozen=True, kw_only=True) +class ScaleCutBandpowerPrediction: + """Independent CCL bandpower predictions and their scale-cut window context. + + :param bandpowers: CCL predictions projected through the normalized window, + one value per bin, also used as the fixture's SACC measurements. + :param window: Shared normalized weights, support multipoles, and geometric + bin centers; frozen bindings do not freeze its arrays or check shapes. + """ + + bandpowers: npt.NDArray[np.float64] + window: ScaleCutWindowFunction + + +@dataclass(frozen=True, kw_only=True) +class ScaleCutSaccFixture: + """Cosmic-shear SACC data with independent predictions and their cosmology. + + Frozen bindings leave SACC, the spectrum mapping, and nested arrays mutable. + Names and dtype annotations do not check same-dtype swaps, shapes, or units. + + :param sacc_data: Windowed SACC measurements for production likelihood loading. + :param spectra: Independent CCL predictions keyed by ordered tracer-pair names. + :param cosmology: Cosmology used to generate the CCL tracers and predictions. + """ + + sacc_data: sacc.Sacc + spectra: dict[str, ScaleCutBandpowerPrediction] + cosmology: ccl.Cosmology + + @pytest.fixture(name="minimal_3x2pt_sacc", scope="module") def fixture_minimal_3x2pt_sacc( window_function_data: ScaleCutWindowFunction, -) -> tuple[ - sacc.Sacc, - dict[ - str, - tuple[ - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.int64], - npt.NDArray[np.float64], - ], - ], - ccl.Cosmology, -]: +) -> ScaleCutSaccFixture: """Generate SACC data with window functions for cosmic shear only. :param window_function_data: Window weights, support, and observation centers. @@ -101,12 +120,12 @@ def fixture_minimal_3x2pt_sacc( src_bins_centers = np.linspace(0.25, 0.75, 2) - ccl_tracers = {} - ccl_cell = {} + ccl_tracers: dict[str, ccl.WeakLensingTracer] = {} + ccl_cell: dict[str, ScaleCutBandpowerPrediction] = {} cosmo = ccl.CosmologyVanillaLCDM() for i, mn in enumerate(src_bins_centers): - dndz = np.exp(-0.5 * (z - mn) ** 2 / 0.05**2) + dndz: npt.NDArray[np.float64] = np.exp(-0.5 * (z - mn) ** 2 / 0.05**2) sacc_data.add_tracer("NZ", f"src{i}", z, dndz) ccl_tracers[f"src{i}"] = ccl.WeakLensingTracer( cosmo=cosmo, @@ -117,13 +136,13 @@ def fixture_minimal_3x2pt_sacc( window = sacc.BandpowerWindow( window_function_data.support_ells, window_function_data.weights ) - Cell = ccl.angular_cl( + Cell: npt.NDArray[np.float64] = ccl.angular_cl( cosmo=cosmo, tracer1=ccl_tracers[f"src{i}"], tracer2=ccl_tracers[f"src{j}"], ell=window_function_data.support_ells, ) - Cell_binned = window_function_data.weights.T @ Cell + Cell_binned: npt.NDArray[np.float64] = window_function_data.weights.T @ Cell sacc_data.add_ell_cl( data_type="galaxy_shear_cl_ee", tracer1=f"src{i}", @@ -132,16 +151,14 @@ def fixture_minimal_3x2pt_sacc( x=Cell_binned, window=window, ) - ccl_cell[f"src{i}-src{j}"] = ( - Cell_binned, - window_function_data.bin_centers, - window_function_data.support_ells, - window_function_data.weights, + ccl_cell[f"src{i}-src{j}"] = ScaleCutBandpowerPrediction( + bandpowers=Cell_binned, + window=window_function_data, ) sacc_data.add_covariance(np.identity(len(sacc_data)) * 0.01) - return sacc_data, ccl_cell, cosmo + return ScaleCutSaccFixture(sacc_data=sacc_data, spectra=ccl_cell, cosmology=cosmo) @pytest.fixture( @@ -169,24 +186,19 @@ def fixture_cut(request: pytest.FixtureRequest) -> tuple[str, str, int]: def test_scale_cuts_with_bandpower_window_label( - minimal_3x2pt_sacc: tuple[ - sacc.Sacc, - dict[ - str, - tuple[ - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.int64], - npt.NDArray[np.float64], - ], - ], - ccl.Cosmology, - ], + minimal_3x2pt_sacc: ScaleCutSaccFixture, cut: tuple[str, str, int], tmp_path: Path, ) -> None: - """Test that the scale cuts with bandpower windows work correctly.""" - sacc_data, ccl_cell, cosmo = minimal_3x2pt_sacc + """Test scale cuts selected by bandpower window labels. + + :param minimal_3x2pt_sacc: SACC data, independent CCL predictions, and cosmology. + :param cut: Ordered tracer names and upper multipole threshold. + :param tmp_path: Directory for the SACC file loaded by the likelihood factory. + """ + sacc_data = minimal_3x2pt_sacc.sacc_data + ccl_cell = minimal_3x2pt_sacc.spectra + cosmo = minimal_3x2pt_sacc.cosmology tp_factory = base_model_from_yaml(TwoPointFactory, two_point_yaml) @@ -236,8 +248,12 @@ def test_scale_cuts_with_bandpower_window_label( ccl_cell_cut_data_vector = np.concatenate( [ - cell if name != f"{cut[0]}-{cut[1]}" else cell[window_ell < cut[2]] - for name, (cell, window_ell, _, _) in ccl_cell.items() + ( + spectrum.bandpowers + if name != f"{cut[0]}-{cut[1]}" + else spectrum.bandpowers[spectrum.window.bin_centers < cut[2]] + ) + for name, spectrum in ccl_cell.items() ] ) # Check that the shapes match (i.e. that the correct data points are selected) @@ -255,25 +271,21 @@ def test_scale_cuts_with_bandpower_window_label( ], ) def test_scale_cuts_with_bandpower_window_support( - minimal_3x2pt_sacc: tuple[ - sacc.Sacc, - dict[ - str, - tuple[ - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.int64], - npt.NDArray[np.float64], - ], - ], - ccl.Cosmology, - ], + minimal_3x2pt_sacc: ScaleCutSaccFixture, cut: tuple[str, str, int], method: tuple[TwoPointFilterMethod, float], tmp_path: Path, ) -> None: - """Test that the scale cuts with bandpower windows work correctly.""" - sacc_data, ccl_cell, cosmo = minimal_3x2pt_sacc + """Test scale cuts selected by cumulative bandpower window support. + + :param minimal_3x2pt_sacc: SACC data, independent CCL predictions, and cosmology. + :param cut: Ordered tracer names and upper multipole threshold. + :param method: Filter method and required fraction of window support. + :param tmp_path: Directory for the SACC file loaded by the likelihood factory. + """ + sacc_data = minimal_3x2pt_sacc.sacc_data + ccl_cell = minimal_3x2pt_sacc.spectra + cosmo = minimal_3x2pt_sacc.cosmology tp_factory = base_model_from_yaml(TwoPointFactory, two_point_yaml) @@ -324,11 +336,16 @@ def test_scale_cuts_with_bandpower_window_support( ccl_cell_cut_data_vector = np.concatenate( [ ( - cell + spectrum.bandpowers if name != f"{cut[0]}-{cut[1]}" - else cell[window[ell < cut[2]].sum(axis=0) >= method[1]] + else spectrum.bandpowers[ + spectrum.window.weights[spectrum.window.support_ells < cut[2]].sum( + axis=0 + ) + >= method[1] + ] ) - for name, (cell, _, ell, window) in ccl_cell.items() + for name, spectrum in ccl_cell.items() ] ) # Check that the shapes match (i.e. that the correct data points are selected) From b90feb5de0b38bcfb62e850891a3f118de979974 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 17 Oct 2026 12:26:37 -0500 Subject: [PATCH 10/20] refactor(test): return typed fixture from harmonic cross-bin SACC tests Replace the tuple-returning cross-bin SACC fixtures with a SaccHarmonicCrossBinFixture dataclass. It holds the SACC data and a distributions mapping keyed by tracer name, so tests look up src0, src1, lens0, and lens1 by name instead of unpacking positional values. The fixtures in conftest.py and the tests that consume them in test_metadata_two_point.py and test_metadata_two_point_sacc.py are updated to match, and the affected tests now assert per-tracer z and dndz by name. --- tests/_types.py | 16 ++++ tests/conftest.py | 79 +++++++++++-------- tests/metadata/test_metadata_two_point.py | 12 ++- .../metadata/test_metadata_two_point_sacc.py | 63 +++++++-------- 4 files changed, 99 insertions(+), 71 deletions(-) diff --git a/tests/_types.py b/tests/_types.py index 48a09ba6f..e5222fcd2 100644 --- a/tests/_types.py +++ b/tests/_types.py @@ -26,6 +26,22 @@ class SampledRedshiftDistribution: dndz: npt.NDArray[np.float64] +@dataclass(frozen=True, kw_only=True) +class SaccHarmonicCrossBinFixture: + """Harmonic cross-bin SACC data and independent redshift expectations. + + Both tracer distributions share their original redshift coordinate array, + but retain distinct unnormalized number densities. No measurement metadata + is inferred here; consumers still exercise production extraction. + + :param sacc_data: Unwindowed harmonic SACC data for the two tracers. + :param distributions: Sampled redshift distributions keyed by tracer name. + """ + + sacc_data: sacc.Sacc + distributions: dict[str, SampledRedshiftDistribution] + + @dataclass(frozen=True, kw_only=True) class HarmonicAngularPowerSpectrum: """Independent unwindowed harmonic coordinates and measured cells. diff --git a/tests/conftest.py b/tests/conftest.py index 401766734..40082ab21 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -50,6 +50,7 @@ SaccGalaxyHarmonicFixture, SaccGalaxyRealFixture, SaccGalaxyWindowedFixture, + SaccHarmonicCrossBinFixture, SampledRedshiftDistribution, WindowedAngularPowerSpectrum, ) @@ -649,23 +650,21 @@ def fixture_sacc_galaxy_cells_src0_src0() -> tuple[sacc.Sacc, TomographicBin]: @pytest.fixture(name="sacc_galaxy_cells_src0_src1", scope="module") -def fixture_sacc_galaxy_cells_src0_src1() -> tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], -]: - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_cells_src0_src1() -> SaccHarmonicCrossBinFixture: + """Build source-source harmonic data and named redshift expectations. - z = np.linspace(0, 1.0, 256) + 0.05 + :returns: SACC data and src0/src1 distributions sharing the redshift grid. + """ + + z: npt.NDArray[np.float64] = np.linspace(0, 1.0, 256) + 0.05 ells = np.unique(np.logspace(1, 3, 10).astype(np.int64)) sacc_data = sacc.Sacc() - dndz0 = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) + dndz0: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "src0", z, dndz0) - dndz1 = np.exp(-0.5 * (z - 0.7) ** 2 / 0.05 / 0.05) + dndz1: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.7) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "src1", z, dndz1) Cells = np.random.normal(size=ells.shape[0]) @@ -674,7 +673,13 @@ def fixture_sacc_galaxy_cells_src0_src1() -> tuple[ cov = np.diag(np.ones_like(Cells) * 0.01) sacc_data.add_covariance(cov) - return sacc_data, z, dndz0, dndz1 + return SaccHarmonicCrossBinFixture( + sacc_data=sacc_data, + distributions={ + "src0": SampledRedshiftDistribution(z=z, dndz=dndz0), + "src1": SampledRedshiftDistribution(z=z, dndz=dndz1), + }, + ) @pytest.fixture(name="sacc_galaxy_cells_lens0_lens0", scope="module") @@ -704,21 +709,19 @@ def fixture_sacc_galaxy_cells_lens0_lens0() -> tuple[sacc.Sacc, TomographicBin]: @pytest.fixture(name="sacc_galaxy_cells_lens0_lens1") -def fixture_sacc_galaxy_cells_lens0_lens1() -> tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], -]: - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_cells_lens0_lens1() -> SaccHarmonicCrossBinFixture: + """Build lens-lens harmonic data and named redshift expectations. + + :returns: SACC data and lens0/lens1 distributions sharing the redshift grid. + """ sacc_data = sacc.Sacc() - z = np.linspace(0, 1.0, 256) + 0.05 + z: npt.NDArray[np.float64] = np.linspace(0, 1.0, 256) + 0.05 ells = np.unique(np.logspace(1, 3, 10).astype(np.int64)) - dndz0 = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) + dndz0: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens0", z, dndz0) - dndz1 = np.exp(-0.5 * (z - 0.6) ** 2 / 0.05 / 0.05) + dndz1: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.6) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens1", z, dndz1) Cells = np.random.normal(size=ells.shape[0]) @@ -727,7 +730,13 @@ def fixture_sacc_galaxy_cells_lens0_lens1() -> tuple[ cov = np.diag(np.ones_like(Cells) * 0.01) sacc_data.add_covariance(cov) - return sacc_data, z, dndz0, dndz1 + return SaccHarmonicCrossBinFixture( + sacc_data=sacc_data, + distributions={ + "lens0": SampledRedshiftDistribution(z=z, dndz=dndz0), + "lens1": SampledRedshiftDistribution(z=z, dndz=dndz1), + }, + ) @pytest.fixture(name="sacc_galaxy_xis_lens0_lens0") @@ -784,22 +793,20 @@ def fixture_sacc_galaxy_xis_lens0_lens1() -> tuple[ @pytest.fixture(name="sacc_galaxy_cells_src0_lens0") -def fixture_sacc_galaxy_cells_src0_lens0() -> tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], -]: - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_cells_src0_lens0() -> SaccHarmonicCrossBinFixture: + """Build source-lens harmonic data and named redshift expectations. + + :returns: SACC data and src0/lens0 distributions sharing the redshift grid. + """ sacc_data = sacc.Sacc() - z = np.linspace(0, 1.0, 256) + 0.05 + z: npt.NDArray[np.float64] = np.linspace(0, 1.0, 256) + 0.05 ells = np.unique(np.logspace(1, 3, 10).astype(np.int64)) - dndz0 = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) + dndz0: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "src0", z, dndz0) - dndz1 = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) + dndz1: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens0", z, dndz1) Cells = np.random.normal(size=ells.shape[0]) @@ -808,7 +815,13 @@ def fixture_sacc_galaxy_cells_src0_lens0() -> tuple[ cov = np.diag(np.ones_like(Cells) * 0.01) sacc_data.add_covariance(cov) - return sacc_data, z, dndz0, dndz1 + return SaccHarmonicCrossBinFixture( + sacc_data=sacc_data, + distributions={ + "src0": SampledRedshiftDistribution(z=z, dndz=dndz0), + "lens0": SampledRedshiftDistribution(z=z, dndz=dndz1), + }, + ) @pytest.fixture(name="sacc_galaxy_xis_src0_lens0") diff --git a/tests/metadata/test_metadata_two_point.py b/tests/metadata/test_metadata_two_point.py index bc08e4958..a1cbddeae 100644 --- a/tests/metadata/test_metadata_two_point.py +++ b/tests/metadata/test_metadata_two_point.py @@ -46,6 +46,7 @@ from firecrown.metadata_types._sacc_type_string import ( _type_to_sacc_string_real as real, ) +from tests._types import SaccHarmonicCrossBinFixture def test_inferred_galaxy_z_dist() -> None: @@ -1340,20 +1341,17 @@ def test_extract_all_real_metadata_indices_multiple_combinations() -> None: def test_extract_all_harmonic_metadata_indices_no_swap( - sacc_galaxy_cells_src0_lens0: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_cells_src0_lens0: SaccHarmonicCrossBinFixture, ) -> None: """Test extract_all_harmonic_metadata_indices with no tracer swap needed. This test verifies that when tracers are in the correct order according to the SACC convention (measurement type order matches tracer order), no swap is performed. + + :param sacc_galaxy_cells_src0_lens0: Source-lens harmonic SACC expectations. """ - sacc_data, _, _, _ = sacc_galaxy_cells_src0_lens0 + sacc_data = sacc_galaxy_cells_src0_lens0.sacc_data indices = extract_all_harmonic_metadata_indices(sacc_data) diff --git a/tests/metadata/test_metadata_two_point_sacc.py b/tests/metadata/test_metadata_two_point_sacc.py index 84c57a26f..f9c84c605 100644 --- a/tests/metadata/test_metadata_two_point_sacc.py +++ b/tests/metadata/test_metadata_two_point_sacc.py @@ -62,6 +62,7 @@ SaccGalaxyHarmonicFixture, SaccGalaxyRealFixture, SaccGalaxyWindowedFixture, + SaccHarmonicCrossBinFixture, ) # pylint: disable=too-many-lines @@ -234,26 +235,26 @@ def test_extract_all_tracers_cells_src0_src0( def test_extract_all_tracers_cells_src0_src1( - sacc_galaxy_cells_src0_src1: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_cells_src0_src1: SaccHarmonicCrossBinFixture, ) -> None: - sacc_data, z, dndz0, dndz1 = sacc_galaxy_cells_src0_src1 + """Extract both source distributions without changing their harmonic roles. + + :param sacc_galaxy_cells_src0_src1: Source-source SACC data and expectations. + """ + sacc_data = sacc_galaxy_cells_src0_src1.sacc_data + distributions = sacc_galaxy_cells_src0_src1.distributions assert sacc_data is not None all_tracers = extract_all_tracers_tomographic_bins(sacc_data) assert len(all_tracers) == 2 for tracer in all_tracers: - assert_array_equal(tracer.z, z) + assert_array_equal(tracer.z, distributions[tracer.bin_name].z) assert tracer.measurements == {Galaxies.SHEAR_E} if tracer.bin_name == "src0": - assert_array_equal(tracer.dndz, dndz0) + assert_array_equal(tracer.dndz, distributions["src0"].dndz) elif tracer.bin_name == "src1": - assert_array_equal(tracer.dndz, dndz1) + assert_array_equal(tracer.dndz, distributions["src1"].dndz) def test_extract_all_tracers_cells_lens0_lens0( @@ -273,26 +274,26 @@ def test_extract_all_tracers_cells_lens0_lens0( def test_extract_all_tracers_cells_lens0_lens1( - sacc_galaxy_cells_lens0_lens1: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_cells_lens0_lens1: SaccHarmonicCrossBinFixture, ) -> None: - sacc_data, z, dndz0, dndz1 = sacc_galaxy_cells_lens0_lens1 + """Extract both lens distributions without changing their harmonic roles. + + :param sacc_galaxy_cells_lens0_lens1: Lens-lens SACC data and expectations. + """ + sacc_data = sacc_galaxy_cells_lens0_lens1.sacc_data + distributions = sacc_galaxy_cells_lens0_lens1.distributions assert sacc_data is not None all_tracers = extract_all_tracers_tomographic_bins(sacc_data) assert len(all_tracers) == 2 for tracer in all_tracers: - assert_array_equal(tracer.z, z) + assert_array_equal(tracer.z, distributions[tracer.bin_name].z) assert tracer.measurements == {Galaxies.COUNTS} if tracer.bin_name == "lens0": - assert_array_equal(tracer.dndz, dndz0) + assert_array_equal(tracer.dndz, distributions["lens0"].dndz) elif tracer.bin_name == "lens1": - assert_array_equal(tracer.dndz, dndz1) + assert_array_equal(tracer.dndz, distributions["lens1"].dndz) def test_extract_all_tracers_xis_lens0_lens0( @@ -335,14 +336,14 @@ def test_extract_all_tracers_xis_lens0_lens1( def test_extract_all_trace_cells_src0_lens0( - sacc_galaxy_cells_src0_lens0: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_cells_src0_lens0: SaccHarmonicCrossBinFixture, ) -> None: - sacc_data, z, dndz0, dndz1 = sacc_galaxy_cells_src0_lens0 + """Extract source and lens distributions with distinct harmonic roles. + + :param sacc_galaxy_cells_src0_lens0: Source-lens SACC data and expectations. + """ + sacc_data = sacc_galaxy_cells_src0_lens0.sacc_data + distributions = sacc_galaxy_cells_src0_lens0.distributions assert sacc_data is not None all_tracers = extract_all_tracers_tomographic_bins(sacc_data) @@ -350,12 +351,12 @@ def test_extract_all_trace_cells_src0_lens0( for tracer in all_tracers: if tracer.bin_name == "src0": - assert_array_equal(tracer.z, z) - assert_array_equal(tracer.dndz, dndz0) + assert_array_equal(tracer.z, distributions["src0"].z) + assert_array_equal(tracer.dndz, distributions["src0"].dndz) assert tracer.measurements == {Galaxies.SHEAR_E} elif tracer.bin_name == "lens0": - assert_array_equal(tracer.z, z) - assert_array_equal(tracer.dndz, dndz1) + assert_array_equal(tracer.z, distributions["lens0"].z) + assert_array_equal(tracer.dndz, distributions["lens0"].dndz) assert tracer.measurements == {Galaxies.COUNTS} From 8d494f0368feacdcffcf45c752d77601a2c89f27 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 17 Oct 2026 18:06:40 -0500 Subject: [PATCH 11/20] refactor(test): type real-space SACC fixtures with dataclass Replace tuple-returning real-space SACC fixtures with a typed SaccRealCrossBinFixture that exposes sacc_data and named per-tracer distributions. Update the consuming tests to read these fields and drop the now-unused numpy.typing imports. --- tests/_types.py | 16 ++++++ tests/conftest.py | 53 +++++++++++-------- tests/metadata/test_metadata_two_point.py | 14 ++--- .../metadata/test_metadata_two_point_sacc.py | 43 +++++++-------- .../metadata/test_metadata_two_point_types.py | 13 ++--- tests/test_bug_413.py | 16 +++--- 6 files changed, 86 insertions(+), 69 deletions(-) diff --git a/tests/_types.py b/tests/_types.py index e5222fcd2..1ae0230c5 100644 --- a/tests/_types.py +++ b/tests/_types.py @@ -42,6 +42,22 @@ class SaccHarmonicCrossBinFixture: distributions: dict[str, SampledRedshiftDistribution] +@dataclass(frozen=True, kw_only=True) +class SaccRealCrossBinFixture: + """Real cross-bin SACC data and independent redshift expectations. + + Both tracer distributions share their original redshift coordinate array, + but retain distinct unnormalized number densities. No measurement metadata + is inferred here; consumers still exercise production extraction. + + :param sacc_data: Unwindowed real-space SACC data for the two tracers. + :param distributions: Sampled redshift distributions keyed by tracer name. + """ + + sacc_data: sacc.Sacc + distributions: dict[str, SampledRedshiftDistribution] + + @dataclass(frozen=True, kw_only=True) class HarmonicAngularPowerSpectrum: """Independent unwindowed harmonic coordinates and measured cells. diff --git a/tests/conftest.py b/tests/conftest.py index 40082ab21..227264e93 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,6 +51,7 @@ SaccGalaxyRealFixture, SaccGalaxyWindowedFixture, SaccHarmonicCrossBinFixture, + SaccRealCrossBinFixture, SampledRedshiftDistribution, WindowedAngularPowerSpectrum, ) @@ -766,21 +767,19 @@ def fixture_sacc_galaxy_xis_lens0_lens0() -> tuple[sacc.Sacc, TomographicBin]: @pytest.fixture(name="sacc_galaxy_xis_lens0_lens1") -def fixture_sacc_galaxy_xis_lens0_lens1() -> tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], -]: - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_xis_lens0_lens1() -> SaccRealCrossBinFixture: + """Build lens-lens real data and named redshift expectations. + + :returns: SACC data and lens0/lens1 distributions sharing the redshift grid. + """ sacc_data = sacc.Sacc() - z = np.linspace(0, 1.0, 256) + 0.05 + z: npt.NDArray[np.float64] = np.linspace(0, 1.0, 256) + 0.05 thetas = np.linspace(0.0, 2.0 * np.pi, 20) - dndz0 = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) + dndz0: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens0", z, dndz0) - dndz1 = np.exp(-0.5 * (z - 0.6) ** 2 / 0.05 / 0.05) + dndz1: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.6) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens1", z, dndz1) xis = np.random.normal(size=thetas.shape[0]) @@ -789,7 +788,13 @@ def fixture_sacc_galaxy_xis_lens0_lens1() -> tuple[ cov = np.diag(np.ones_like(xis) * 0.01) sacc_data.add_covariance(cov) - return sacc_data, z, dndz0, dndz1 + return SaccRealCrossBinFixture( + sacc_data=sacc_data, + distributions={ + "lens0": SampledRedshiftDistribution(z=z, dndz=dndz0), + "lens1": SampledRedshiftDistribution(z=z, dndz=dndz1), + }, + ) @pytest.fixture(name="sacc_galaxy_cells_src0_lens0") @@ -825,22 +830,20 @@ def fixture_sacc_galaxy_cells_src0_lens0() -> SaccHarmonicCrossBinFixture: @pytest.fixture(name="sacc_galaxy_xis_src0_lens0") -def fixture_sacc_galaxy_xis_src0_lens0() -> tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], -]: - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_xis_src0_lens0() -> SaccRealCrossBinFixture: + """Build source-lens real data and named redshift expectations. + + :returns: SACC data and src0/lens0 distributions sharing the redshift grid. + """ sacc_data = sacc.Sacc() - z = np.linspace(0, 1.0, 256) + 0.05 + z: npt.NDArray[np.float64] = np.linspace(0, 1.0, 256) + 0.05 thetas = np.linspace(0.0, 2.0 * np.pi, 20) - dndz0 = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) + dndz0: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "src0", z, dndz0) - dndz1 = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) + dndz1: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens0", z, dndz1) xis = np.random.normal(size=thetas.shape[0]) @@ -849,7 +852,13 @@ def fixture_sacc_galaxy_xis_src0_lens0() -> tuple[ cov = np.diag(np.ones_like(xis) * 0.01) sacc_data.add_covariance(cov) - return sacc_data, z, dndz0, dndz1 + return SaccRealCrossBinFixture( + sacc_data=sacc_data, + distributions={ + "src0": SampledRedshiftDistribution(z=z, dndz=dndz0), + "lens0": SampledRedshiftDistribution(z=z, dndz=dndz1), + }, + ) @pytest.fixture(name="sacc_galaxy_cells", scope="module") diff --git a/tests/metadata/test_metadata_two_point.py b/tests/metadata/test_metadata_two_point.py index a1cbddeae..b5fc7a8e4 100644 --- a/tests/metadata/test_metadata_two_point.py +++ b/tests/metadata/test_metadata_two_point.py @@ -5,7 +5,6 @@ import dataclasses import numpy as np -import numpy.typing as npt import pytest import sacc from numpy.testing import assert_array_equal @@ -46,7 +45,7 @@ from firecrown.metadata_types._sacc_type_string import ( _type_to_sacc_string_real as real, ) -from tests._types import SaccHarmonicCrossBinFixture +from tests._types import SaccHarmonicCrossBinFixture, SaccRealCrossBinFixture def test_inferred_galaxy_z_dist() -> None: @@ -1136,20 +1135,17 @@ def test_make_two_point_xy_sacc_convention_explanation() -> None: def test_extract_all_real_metadata_indices_no_swap( - sacc_galaxy_xis_src0_lens0: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_xis_src0_lens0: SaccRealCrossBinFixture, ) -> None: """Test extract_all_real_metadata_indices with no tracer swap needed. This test verifies that when tracers are in the correct order according to the SACC convention (measurement type order matches tracer order), no swap is performed. + + :param sacc_galaxy_xis_src0_lens0: Source-lens real SACC data and expectations. """ - sacc_data, _, _, _ = sacc_galaxy_xis_src0_lens0 + sacc_data = sacc_galaxy_xis_src0_lens0.sacc_data indices = extract_all_real_metadata_indices(sacc_data) diff --git a/tests/metadata/test_metadata_two_point_sacc.py b/tests/metadata/test_metadata_two_point_sacc.py index f9c84c605..d07ba5139 100644 --- a/tests/metadata/test_metadata_two_point_sacc.py +++ b/tests/metadata/test_metadata_two_point_sacc.py @@ -63,6 +63,7 @@ SaccGalaxyRealFixture, SaccGalaxyWindowedFixture, SaccHarmonicCrossBinFixture, + SaccRealCrossBinFixture, ) # pylint: disable=too-many-lines @@ -313,26 +314,26 @@ def test_extract_all_tracers_xis_lens0_lens0( def test_extract_all_tracers_xis_lens0_lens1( - sacc_galaxy_xis_lens0_lens1: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_xis_lens0_lens1: SaccRealCrossBinFixture, ) -> None: - sacc_data, z, dndz0, dndz1 = sacc_galaxy_xis_lens0_lens1 + """Extract both lens distributions without changing their real-space roles. + + :param sacc_galaxy_xis_lens0_lens1: Lens-lens SACC data and expectations. + """ + sacc_data = sacc_galaxy_xis_lens0_lens1.sacc_data + distributions = sacc_galaxy_xis_lens0_lens1.distributions assert sacc_data is not None all_tracers = extract_all_tracers_tomographic_bins(sacc_data) assert len(all_tracers) == 2 for tracer in all_tracers: - assert_array_equal(tracer.z, z) + assert_array_equal(tracer.z, distributions[tracer.bin_name].z) assert tracer.measurements == {Galaxies.COUNTS} if tracer.bin_name == "lens0": - assert_array_equal(tracer.dndz, dndz0) + assert_array_equal(tracer.dndz, distributions["lens0"].dndz) elif tracer.bin_name == "lens1": - assert_array_equal(tracer.dndz, dndz1) + assert_array_equal(tracer.dndz, distributions["lens1"].dndz) def test_extract_all_trace_cells_src0_lens0( @@ -361,14 +362,14 @@ def test_extract_all_trace_cells_src0_lens0( def test_extract_all_trace_xis_src0_lens0( - sacc_galaxy_xis_src0_lens0: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_xis_src0_lens0: SaccRealCrossBinFixture, ) -> None: - sacc_data, z, dndz0, dndz1 = sacc_galaxy_xis_src0_lens0 + """Extract source and lens distributions with distinct real-space roles. + + :param sacc_galaxy_xis_src0_lens0: Source-lens SACC data and expectations. + """ + sacc_data = sacc_galaxy_xis_src0_lens0.sacc_data + distributions = sacc_galaxy_xis_src0_lens0.distributions assert sacc_data is not None all_tracers = extract_all_tracers_tomographic_bins(sacc_data) @@ -376,12 +377,12 @@ def test_extract_all_trace_xis_src0_lens0( for tracer in all_tracers: if tracer.bin_name == "src0": - assert_array_equal(tracer.z, z) - assert_array_equal(tracer.dndz, dndz0) + assert_array_equal(tracer.z, distributions["src0"].z) + assert_array_equal(tracer.dndz, distributions["src0"].dndz) assert tracer.measurements == {Galaxies.SHEAR_T} elif tracer.bin_name == "lens0": - assert_array_equal(tracer.z, z) - assert_array_equal(tracer.dndz, dndz1) + assert_array_equal(tracer.z, distributions["lens0"].z) + assert_array_equal(tracer.dndz, distributions["lens0"].dndz) assert tracer.measurements == {Galaxies.COUNTS} diff --git a/tests/metadata/test_metadata_two_point_types.py b/tests/metadata/test_metadata_two_point_types.py index 2411b4a2c..355076c01 100644 --- a/tests/metadata/test_metadata_two_point_types.py +++ b/tests/metadata/test_metadata_two_point_types.py @@ -9,7 +9,6 @@ from unittest.mock import MagicMock import numpy as np -import numpy.typing as npt import pytest import sacc import sacc_name_mapping as snm @@ -66,6 +65,7 @@ SaccGalaxyHarmonicFixture, SaccGalaxyRealFixture, SaccGalaxyWindowedFixture, + SaccRealCrossBinFixture, ) @@ -753,19 +753,16 @@ def test_extract_all_measured_mixed_types_density_only( def test_extract_all_measured_mixed_types_mixed_real_space( - sacc_galaxy_xis_src0_lens0: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_xis_src0_lens0: SaccRealCrossBinFixture, ) -> None: """Test extract_all_measured_types with allow_mixed_types=True on real-space data. This test verifies behavior with real-space data (xi correlations) when mixed types are allowed. + + :param sacc_galaxy_xis_src0_lens0: Source-lens real SACC data and expectations. """ - sacc_data, _, _, _ = sacc_galaxy_xis_src0_lens0 + sacc_data = sacc_galaxy_xis_src0_lens0.sacc_data result, _ = extract_all_measured_types(sacc_data, allow_mixed_types=True) diff --git a/tests/test_bug_413.py b/tests/test_bug_413.py index 40c44b724..9a7d64a44 100644 --- a/tests/test_bug_413.py +++ b/tests/test_bug_413.py @@ -2,8 +2,6 @@ from typing import Tuple -import numpy as np -import numpy.typing as npt import pyccl import sacc @@ -16,6 +14,7 @@ from firecrown.modeling_tools import ModelingTools from firecrown.models.two_point import calculate_pk from firecrown.updatable import get_default_params_map +from tests._types import SaccRealCrossBinFixture def make_twopoint_with_optional_systematics( @@ -79,17 +78,16 @@ def make_twopoint_with_optional_systematics( def test_calculate_pk( - sacc_galaxy_xis_lens0_lens1: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_xis_lens0_lens1: SaccRealCrossBinFixture, ) -> None: + """Preserve power-spectrum symmetry when only one source has PT systematics. + + :param sacc_galaxy_xis_lens0_lens1: Lens-lens real SACC data and expectations. + """ # Set up a TwoPoint object in which the first source has a PT systematic # and the second source doe not have a PT systematic. - sacc_data, _, _, _ = sacc_galaxy_xis_lens0_lens1 + sacc_data = sacc_galaxy_xis_lens0_lens1.sacc_data a, b, _, tools = make_twopoint_with_optional_systematics(sacc_data, True, False) From ca29473caa938ec8085bb70ac2be14ee26dd5ce2 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 17 Oct 2026 19:00:33 -0500 Subject: [PATCH 12/20] refactor (test): Return named fixtures for three-tracer SACC tests Replace the positional tuples returned by the real-space and harmonic three-tracer fixtures with frozen keyword-only dataclasses. Each fixture now exposes `sacc_data` and a `distributions` mapping keyed by tracer name (src0, lens0, lens1), built from SampledRedshiftDistribution. Update the two three-tracer rejection tests to read `sacc_data` from the named fixture. Add explicit numpy array type annotations to the redshift and dN/dz arrays in both fixtures. --- .../metadata/test_metadata_two_point_sacc.py | 118 +++++++++++------- 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/tests/metadata/test_metadata_two_point_sacc.py b/tests/metadata/test_metadata_two_point_sacc.py index d07ba5139..8a1160cbc 100644 --- a/tests/metadata/test_metadata_two_point_sacc.py +++ b/tests/metadata/test_metadata_two_point_sacc.py @@ -5,6 +5,7 @@ import re import warnings +from dataclasses import dataclass import numpy as np import numpy.typing as npt @@ -59,6 +60,7 @@ ) from firecrown.updatable import ParamsMap from tests._types import ( + SampledRedshiftDistribution, SaccGalaxyHarmonicFixture, SaccGalaxyRealFixture, SaccGalaxyWindowedFixture, @@ -69,6 +71,36 @@ # pylint: disable=too-many-lines +@dataclass(frozen=True, kw_only=True) +class SaccRealThreeTracerFixture: + """Real SACC data with an intentionally invalid three-tracer correlation. + + Frozen bindings leave SACC, mappings, and arrays mutable. Array dtypes do not + enforce semantic roles, shapes, units, or a shared redshift grid. + + :param sacc_data: SACC data retaining the three-tracer rejection input. + :param distributions: Independent sampled distributions keyed by tracer name. + """ + + sacc_data: sacc.Sacc + distributions: dict[str, SampledRedshiftDistribution] + + +@dataclass(frozen=True, kw_only=True) +class SaccHarmonicThreeTracerFixture: + """Harmonic SACC data with an intentionally invalid three-tracer correlation. + + Frozen bindings leave SACC, mappings, and arrays mutable. Array dtypes do not + enforce semantic roles, shapes, units, or a shared redshift grid. + + :param sacc_data: SACC data retaining the three-tracer rejection input. + :param distributions: Independent sampled distributions keyed by tracer name. + """ + + sacc_data: sacc.Sacc + distributions: dict[str, SampledRedshiftDistribution] + + @pytest.fixture(name="sacc_galaxy_src0_src0_invalid_data_type") def fixture_sacc_galaxy_src0_src0_invalid_data_type( recwarn: pytest.WarningsRecorder, @@ -102,30 +134,24 @@ def fixture_sacc_galaxy_src0_src0_invalid_data_type( @pytest.fixture(name="sacc_galaxy_xis_three_tracers") -def fixture_sacc_galaxy_xis_three_tracers() -> tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], -]: +def fixture_sacc_galaxy_xis_three_tracers() -> SaccRealThreeTracerFixture: """Build unwindowed real-space SACC data with a three-tracer correlation. - :returns: SACC data, redshift coordinates, and number densities for src0, - lens0, and lens1, in that order. + :returns: SACC data and independent src0/lens0/lens1 distributions sharing + their original redshift grid and retaining distinct number densities. """ sacc_data = sacc.Sacc() - z = np.linspace(0, 1.0, 50) + 0.05 + z: npt.NDArray[np.float64] = np.linspace(0, 1.0, 50) + 0.05 thetas = np.linspace(0.0, 2.0 * np.pi, 20) - dndz0 = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) + dndz0: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "src0", z, dndz0) - dndz1 = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) + dndz1: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens0", z, dndz1) - dndz2 = np.exp(-0.5 * (z - 0.9) ** 2 / 0.05 / 0.05) + dndz2: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.9) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens1", z, dndz2) # We have to coerce the return type of np.random.normal to np.array, @@ -139,34 +165,35 @@ def fixture_sacc_galaxy_xis_three_tracers() -> tuple[ cov = np.diag(np.ones_like(xis) * 0.01) sacc_data.add_covariance(cov) - return sacc_data, z, dndz0, dndz1, dndz2 + return SaccRealThreeTracerFixture( + sacc_data=sacc_data, + distributions={ + "src0": SampledRedshiftDistribution(z=z, dndz=dndz0), + "lens0": SampledRedshiftDistribution(z=z, dndz=dndz1), + "lens1": SampledRedshiftDistribution(z=z, dndz=dndz2), + }, + ) @pytest.fixture(name="sacc_galaxy_cells_three_tracers") -def fixture_sacc_galaxy_cells_three_tracers() -> tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], -]: +def fixture_sacc_galaxy_cells_three_tracers() -> SaccHarmonicThreeTracerFixture: """Build unwindowed harmonic SACC data with a three-tracer correlation. - :returns: SACC data, redshift coordinates, and number densities for src0, - lens0, and lens1, in that order. + :returns: SACC data and independent src0/lens0/lens1 distributions sharing + their original redshift grid and retaining distinct number densities. """ sacc_data = sacc.Sacc() - z = np.linspace(0, 1.0, 50) + 0.05 + z: npt.NDArray[np.float64] = np.linspace(0, 1.0, 50) + 0.05 ells = np.unique(np.logspace(1, 3, 10).astype(np.int64)) - dndz0 = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) + dndz0: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "src0", z, dndz0) - dndz1 = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) + dndz1: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.1) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens0", z, dndz1) - dndz2 = np.exp(-0.5 * (z - 0.9) ** 2 / 0.05 / 0.05) + dndz2: npt.NDArray[np.float64] = np.exp(-0.5 * (z - 0.9) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens1", z, dndz2) # We have to coerce the return type of np.random.normal to np.array, @@ -180,7 +207,14 @@ def fixture_sacc_galaxy_cells_three_tracers() -> tuple[ cov = np.diag(np.ones_like(Cells) * 0.01) sacc_data.add_covariance(cov) - return sacc_data, z, dndz0, dndz1, dndz2 + return SaccHarmonicThreeTracerFixture( + sacc_data=sacc_data, + distributions={ + "src0": SampledRedshiftDistribution(z=z, dndz=dndz0), + "lens0": SampledRedshiftDistribution(z=z, dndz=dndz1), + "lens1": SampledRedshiftDistribution(z=z, dndz=dndz2), + }, + ) def test_extract_all_tracers_cells( @@ -675,20 +709,14 @@ def test_extract_no_window( def test_extract_all_data_types_three_tracers_reals( - sacc_galaxy_xis_three_tracers: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_xis_three_tracers: SaccRealThreeTracerFixture, ) -> None: """Reject real-space metadata for a correlation containing three tracers. - :param sacc_galaxy_xis_three_tracers: SACC data, redshift coordinates, and - number densities for src0, lens0, and lens1. + :param sacc_galaxy_xis_three_tracers: Real SACC data and named independent + redshift distributions for src0, lens0, and lens1. """ - sacc_data, _, _, _, _ = sacc_galaxy_xis_three_tracers + sacc_data = sacc_galaxy_xis_three_tracers.sacc_data with pytest.raises( ValueError, @@ -701,20 +729,14 @@ def test_extract_all_data_types_three_tracers_reals( def test_extract_all_data_types_three_tracers_harmonics( - sacc_galaxy_cells_three_tracers: tuple[ - sacc.Sacc, - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - npt.NDArray[np.float64], - ], + sacc_galaxy_cells_three_tracers: SaccHarmonicThreeTracerFixture, ) -> None: """Reject harmonic metadata for a correlation containing three tracers. - :param sacc_galaxy_cells_three_tracers: SACC data, redshift coordinates, and - number densities for src0, lens0, and lens1. + :param sacc_galaxy_cells_three_tracers: Harmonic SACC data and named independent + redshift distributions for src0, lens0, and lens1. """ - sacc_data, _, _, _, _ = sacc_galaxy_cells_three_tracers + sacc_data = sacc_galaxy_cells_three_tracers.sacc_data with pytest.raises( ValueError, From beea667793ba977707b3cf33c65c5158f6268d9b Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 17 Oct 2026 19:49:33 -0500 Subject: [PATCH 13/20] refactor(test): type sync deps requirement cases with dataclass --- tests/test_sync_deps_cli.py | 78 +++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/tests/test_sync_deps_cli.py b/tests/test_sync_deps_cli.py index 0b93e5219..11ee11161 100644 --- a/tests/test_sync_deps_cli.py +++ b/tests/test_sync_deps_cli.py @@ -2,6 +2,7 @@ from __future__ import annotations +from dataclasses import dataclass import hashlib from pathlib import Path import shutil @@ -20,6 +21,24 @@ ] +@dataclass(frozen=True, kw_only=True) +class RequirementCase: + """Identify a manifest requirement and its selected lock route. + + Field names clarify roles but cannot statically distinguish swapped strings. + + :param group: Manifest group containing the requirement. + :param identity: Logical manifest identity, independent of the locked name. + :param locked_name: Package name selected in the lock for the given manager. + :param manager: Installation manager for that locked selection. + """ + + group: str + identity: str + locked_name: str + manager: str + + def _run_cli(root: Path, *args: str) -> subprocess.CompletedProcess[str]: """Run the copied production script against the isolated repository. @@ -439,18 +458,45 @@ def test_cli_check_rejects_wrong_python_identity( @pytest.mark.parametrize( "requirement", [ - ("runtime", "sample", "sample-conda", "conda"), - ("runtime", "pip-runtime", "pip_runtime", "pip"), - ("workarounds", "workaround", "workaround-conda", "conda"), - ("workarounds", "pip-workaround", "pip_workaround", "pip"), - ("devenv", "dev-tool", "dev-tool-conda", "conda"), - ("devenv", "pip-dev", "pip_dev", "pip"), + RequirementCase( + group="runtime", + identity="sample", + locked_name="sample-conda", + manager="conda", + ), + RequirementCase( + group="runtime", + identity="pip-runtime", + locked_name="pip_runtime", + manager="pip", + ), + RequirementCase( + group="workarounds", + identity="workaround", + locked_name="workaround-conda", + manager="conda", + ), + RequirementCase( + group="workarounds", + identity="pip-workaround", + locked_name="pip_workaround", + manager="pip", + ), + RequirementCase( + group="devenv", + identity="dev-tool", + locked_name="dev-tool-conda", + manager="conda", + ), + RequirementCase( + group="devenv", identity="pip-dev", locked_name="pip_dev", manager="pip" + ), ], ) def test_cli_check_validates_each_requirement_in_each_environment( locked_repository: Path, environment: tuple[str, str], - requirement: tuple[str, str, str, str], + requirement: RequirementCase, ) -> None: """Compatible selections elsewhere cannot mask one incompatible selection. @@ -459,11 +505,10 @@ def test_cli_check_validates_each_requirement_in_each_environment( :param requirement: Group, logical identity, locked name and manager to constrain. """ python, platform = environment - group, identity, name, manager = requirement manifest_path = locked_repository / "dependencies.yaml" manifest = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) - for entry in manifest[group]: - if entry["name"] == identity: + for entry in manifest[requirement.group]: + if entry["name"] == requirement.identity: entry["version"] = ">=1,<2" manifest_path.write_text( yaml.safe_dump(manifest, sort_keys=False), encoding="utf-8" @@ -473,12 +518,21 @@ def test_cli_check_validates_each_requirement_in_each_environment( compatible = _run_cli(locked_repository, "--check") assert compatible.returncode == 0, compatible.stderr assert _snapshot(locked_repository) == before - _set_locked_version(locked_repository, environment, name, manager, "0.9") + _set_locked_version( + locked_repository, + environment, + requirement.locked_name, + requirement.manager, + "0.9", + ) before = _snapshot(locked_repository) incompatible = _run_cli(locked_repository, "--check") assert incompatible.returncode == 1 assert f"Python {python} / {platform}" in incompatible.stderr - assert f"{group} requirement {identity} ({manager}:{name})" in incompatible.stderr + assert ( + f"{requirement.group} requirement {requirement.identity} " + f"({requirement.manager}:{requirement.locked_name})" + ) in incompatible.stderr assert "locked 0.9, requires >=1,<2" in incompatible.stderr assert _snapshot(locked_repository) == before From 9dca0160877dcd16672a26b7d90e56262f901987 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 17 Oct 2026 20:26:12 -0500 Subject: [PATCH 14/20] refactor(test): annotate CCL calculator fixtures with types Annotate BACKGROUND with CCLCalculatorArgs and the PK_LINEAR and PK_NONLIN fragments with dict[str, PowerSpec], importing PowerSpec. The partial fragments keep the open mapping because no existing schema covers a single calculator-argument key. --- tests/test_modeling_tools_ccl_factory.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_modeling_tools_ccl_factory.py b/tests/test_modeling_tools_ccl_factory.py index af276c416..421a7cc04 100644 --- a/tests/test_modeling_tools_ccl_factory.py +++ b/tests/test_modeling_tools_ccl_factory.py @@ -21,6 +21,7 @@ CCLSplineParams, ModelingTools, MuSigmaModel, + PowerSpec, PoweSpecAmplitudeParameter, ) from firecrown.updatable import ParamsMap, get_default_params_map @@ -160,15 +161,17 @@ def fixture_ccl_spline_params( * (1 / (1 + (K_GRID / (0.1 * 0.05)) ** (3 * 0.96)) * A_GRID) ) -BACKGROUND = { +BACKGROUND: CCLCalculatorArgs = { "background": {"a": A_ARRAY, "chi": CHI_ARRAY, "h_over_h0": H_OVER_H0_ARRAY} } -PK_LINEAR = { +# Partial fragments: no existing schema describes a single calculator-argument +# key, and a new partial variant is out of scope, so keep the open mapping. +PK_LINEAR: dict[str, PowerSpec] = { "pk_linear": {"k": K_ARRAY, "a": A_ARRAY, "delta_matter:delta_matter": PK_ARRAY} } -PK_NONLIN = { +PK_NONLIN: dict[str, PowerSpec] = { "pk_nonlin": { "k": np.linspace(0.1, 1.0, 100), "a": np.linspace(0.1, 1.0, 100), From 7df37df4905ed7b6bd6b70164684b3a94c8470ca Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 17 Oct 2026 22:52:09 -0500 Subject: [PATCH 15/20] refactor(likelihood): use explicit np.float64 dtypes Replace np.double annotations and builtin float dtypes with np.float64. These are equivalent aliases, so behavior is unchanged. --- firecrown/likelihood/_gaussfamily.py | 4 ++-- firecrown/likelihood/_gaussian_pointmass.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/firecrown/likelihood/_gaussfamily.py b/firecrown/likelihood/_gaussfamily.py index 7bd11e94b..a02068dfa 100644 --- a/firecrown/likelihood/_gaussfamily.py +++ b/firecrown/likelihood/_gaussfamily.py @@ -170,8 +170,8 @@ def __init__( self.cholesky: None | npt.NDArray[np.float64] = None self.inv_cov: None | npt.NDArray[np.float64] = None self.cov_index_map: None | dict[int, int] = None - self.theory_vector: None | npt.NDArray[np.double] = None - self.data_vector: None | npt.NDArray[np.double] = None + self.theory_vector: None | npt.NDArray[np.float64] = None + self.data_vector: None | npt.NDArray[np.float64] = None self._use_cholesky_for_chisq: bool = use_cholesky @classmethod diff --git a/firecrown/likelihood/_gaussian_pointmass.py b/firecrown/likelihood/_gaussian_pointmass.py index cf1027881..399bac6af 100644 --- a/firecrown/likelihood/_gaussian_pointmass.py +++ b/firecrown/likelihood/_gaussian_pointmass.py @@ -399,10 +399,10 @@ def _prepare_integrand(self, cosmo: pyccl.Cosmology) -> np.ndarray: # Cosmology calculations sc_valid = pyccl.sigma_critical(cosmo, a_lens=aL_all, a_source=aS_all) - sc = np.full(mask.shape, np.nan, dtype=float) + sc = np.full(mask.shape, np.nan, dtype=np.float64) sc[mask] = sc_valid D_l_valid = pyccl.angular_diameter_distance(cosmo, a_l)[z_l > 0] - D_l = np.full(z_l.shape, np.nan, dtype=float) + D_l = np.full(z_l.shape, np.nan, dtype=np.float64) D_l[z_l > 0] = D_l_valid D_l = D_l[:, None] integrand = 1.0 / sc / (D_l * D_l) @@ -448,7 +448,7 @@ def _build_V(self, betas: np.ndarray) -> np.ndarray: V = np.zeros((len(row_src_idx), len(lens_tracers))) valid = (row_lens_idx >= 0) & (row_src_idx >= 0) - beta_rows = np.zeros(valid.sum(), dtype=float) + beta_rows = np.zeros(valid.sum(), dtype=np.float64) beta_rows = betas[row_lens_idx[valid], row_src_idx[valid]] V[valid, row_lens_idx[valid]] = beta_rows / (theta[valid] ** 2) return V From 0632c88d68b2e00a155ef688e667477da2dfc5f7 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Tue, 27 Oct 2026 17:26:11 -0500 Subject: [PATCH 16/20] refactor: return typed SACC fixture dataclasses from tests Replace tuple-returning SACC test fixtures with named frozen dataclasses (SaccGalaxyHarmonicFixture, SaccGalaxyRealFixture, SaccGalaxyWindowedFixture, SaccGalaxyDistributionFixture, SaccInvalidDataTypeFixture). Tests access sacc_data, distributions, and spectra by name instead of unpacking tuples. Relabel fixture expectations as synthetic observations rather than measured values, and define "Independent SACC expectation" and "Synthetic SACC observation" in GLOSSARY.md. Behavior of the existing tests is unchanged. --- GLOSSARY.md | 12 +- tests/_types.py | 38 ++-- tests/conftest.py | 165 ++++++++++++------ .../statistic/source/test_source.py | 60 ++++--- .../gauss_family/statistic/test_two_point.py | 52 +++--- .../metadata/test_metadata_two_point_sacc.py | 76 +++++--- .../metadata/test_metadata_two_point_types.py | 9 +- 7 files changed, 255 insertions(+), 157 deletions(-) diff --git a/GLOSSARY.md b/GLOSSARY.md index 701ef3e26..599a5c683 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -1,6 +1,6 @@ -# Firecrown API, Dependency, and Release Language +# Firecrown Language -Terms used when assessing Firecrown dependencies and the compatibility of releases and pull requests. +Terms used when assessing Firecrown dependencies, release compatibility, and scientific test expectations. ## Language @@ -33,3 +33,11 @@ An advisory, technically non-blocking sample of environment rebuildability for a **API policy verdict**: The target-dependent interpretation of a completed static public API comparison: no detected breaking changes, informational breaking changes on `master`, or rejection of detected breaking changes on a support target. The verdict is distinct from whether analysis and reporting succeeded; missing analysis or reporting is not evidence of compatibility, and no detected breaks does not establish complete behavioral compatibility. + +## SACC Test Language + +**Independent SACC expectation**: +A reference redshift distribution or measured angular spectrum specified alongside SACC data, rather than reconstructed through Firecrown's production extraction or theory calculations. Independence describes the reference's origin, not whether its underlying arrays share storage with the SACC data. + +**Synthetic SACC observation**: +A supplied test value for a recognized SACC observable, such as an angular power spectrum, angular correlation, or windowed bandpower. It is neither a survey measurement nor necessarily a theory prediction or physically plausible realization; a value with an unknown data-type label has no established observable interpretation. diff --git a/tests/_types.py b/tests/_types.py index 1ae0230c5..ca093f610 100644 --- a/tests/_types.py +++ b/tests/_types.py @@ -5,7 +5,7 @@ """ from dataclasses import dataclass -from typing import TypeAlias, TypedDict +from typing import TypedDict from numcosmo_py import Nc import numpy as np @@ -14,6 +14,7 @@ from firecrown.metadata_types import TracerNames + @dataclass(frozen=True, kw_only=True) class SampledRedshiftDistribution: """Independent sampled redshift coordinates and unnormalized number density. @@ -26,6 +27,18 @@ class SampledRedshiftDistribution: dndz: npt.NDArray[np.float64] +@dataclass(frozen=True, kw_only=True) +class SaccGalaxyDistributionFixture: + """SACC tracer data and independently sampled redshift distributions. + + :param sacc_data: SACC data used by production extraction and construction. + :param distributions: Redshift distributions keyed by tracer name. + """ + + sacc_data: sacc.Sacc + distributions: dict[str, SampledRedshiftDistribution] + + @dataclass(frozen=True, kw_only=True) class SaccHarmonicCrossBinFixture: """Harmonic cross-bin SACC data and independent redshift expectations. @@ -60,10 +73,10 @@ class SaccRealCrossBinFixture: @dataclass(frozen=True, kw_only=True) class HarmonicAngularPowerSpectrum: - """Independent unwindowed harmonic coordinates and measured cells. + """Independent unwindowed harmonic coordinates and synthetic observations. - :param ells: Integer multipoles for the measurements. - :param cells: Measured angular power spectrum values, not theory predictions. + :param ells: Integer multipoles for the synthetic observations. + :param cells: Synthetic observed values, not survey data or theory predictions. """ ells: npt.NDArray[np.int64] @@ -76,7 +89,7 @@ class SaccGalaxyHarmonicFixture: :param sacc_data: SACC data used by production extraction and construction. :param distributions: Redshift distributions keyed by tracer name. - :param spectra: Measured spectra keyed by ordered tracer names and data type. + :param spectra: Synthetic spectra keyed by ordered tracer names and data type. """ sacc_data: sacc.Sacc @@ -86,10 +99,10 @@ class SaccGalaxyHarmonicFixture: @dataclass(frozen=True, kw_only=True) class RealAngularCorrelation: - """Independent unwindowed angular coordinates and measured real-space xis. + """Independent unwindowed angular coordinates and synthetic observations. - :param thetas: Angular coordinates for the measurements; no unit is enforced. - :param xis: Measured correlations, not theory predictions or number densities. + :param thetas: Angular coordinates for the observations; no unit is enforced. + :param xis: Synthetic correlations, not survey data or theory predictions. """ thetas: npt.NDArray[np.float64] @@ -102,7 +115,7 @@ class SaccGalaxyRealFixture: :param sacc_data: SACC data used by production extraction and construction. :param distributions: Redshift distributions keyed by tracer name. - :param spectra: Measured correlations keyed by ordered tracer names and data type. + :param spectra: Synthetic correlations keyed by ordered tracer names and data type. """ sacc_data: sacc.Sacc @@ -112,14 +125,15 @@ class SaccGalaxyRealFixture: @dataclass(frozen=True, kw_only=True) class WindowedAngularPowerSpectrum: - """Independent window support, measured bandpowers, and their shared window. + """Independent window support, synthetic bandpowers, and their shared window. - Support multipoles need not match the number of measured bandpowers. + Support multipoles need not match the number of bandpowers. SACC observation labels are weighted support sums, not the support grid or normalized band centers. :param support_ells: Integer multipoles on which the window is defined. - :param bandpowers: Measured values, one per window column, not predictions. + :param bandpowers: Synthetic observed values, not survey data or predictions; + one per window column. :param window: SACC BandpowerWindow shared with the fixture's data points; its values are support multipoles and its weights are unnormalized. """ diff --git a/tests/conftest.py b/tests/conftest.py index 227264e93..017a67d1f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,6 +47,7 @@ from tests._types import ( HarmonicAngularPowerSpectrum, RealAngularCorrelation, + SaccGalaxyDistributionFixture, SaccGalaxyHarmonicFixture, SaccGalaxyRealFixture, SaccGalaxyWindowedFixture, @@ -625,8 +626,11 @@ def fixture_cluster_sacc_data() -> sacc.Sacc: @pytest.fixture(name="sacc_galaxy_cells_src0_src0", scope="module") -def fixture_sacc_galaxy_cells_src0_src0() -> tuple[sacc.Sacc, TomographicBin]: - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_cells_src0_src0() -> SaccGalaxyHarmonicFixture: + """Build unwindowed source-source SACC data and named expectations. + + :returns: SACC data, the copied source distribution, and its synthetic spectrum. + """ sacc_data = sacc.Sacc() z = np.linspace(0, 1.0, 256) + 0.05 @@ -635,19 +639,23 @@ def fixture_sacc_galaxy_cells_src0_src0() -> tuple[sacc.Sacc, TomographicBin]: dndz = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "src0", z, dndz) - Cells = np.random.normal(size=ells.shape[0]) + Cells: npt.NDArray[np.float64] = np.asarray(np.random.normal(size=ells.shape[0])) sacc_data.add_ell_cl("galaxy_shear_cl_ee", "src0", "src0", ells, Cells) cov = np.diag(np.ones_like(Cells) * 0.01) sacc_data.add_covariance(cov) - expected_bin = TomographicBin( - bin_name="src0", - z=z.copy(), - dndz=dndz.copy(), - measurements={Galaxies.SHEAR_E}, + return SaccGalaxyHarmonicFixture( + sacc_data=sacc_data, + distributions={ + "src0": SampledRedshiftDistribution(z=z.copy(), dndz=dndz.copy()) + }, + spectra={ + (TracerNames("src0", "src0"), "galaxy_shear_cl_ee"): ( + HarmonicAngularPowerSpectrum(ells=ells, cells=Cells) + ) + }, ) - return sacc_data, expected_bin @pytest.fixture(name="sacc_galaxy_cells_src0_src1", scope="module") @@ -684,8 +692,11 @@ def fixture_sacc_galaxy_cells_src0_src1() -> SaccHarmonicCrossBinFixture: @pytest.fixture(name="sacc_galaxy_cells_lens0_lens0", scope="module") -def fixture_sacc_galaxy_cells_lens0_lens0() -> tuple[sacc.Sacc, TomographicBin]: - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_cells_lens0_lens0() -> SaccGalaxyHarmonicFixture: + """Build unwindowed lens-lens SACC data and named expectations. + + :returns: SACC data, the copied lens distribution, and its synthetic spectrum. + """ sacc_data = sacc.Sacc() z = np.linspace(0, 1.0, 256) + 0.05 @@ -694,19 +705,23 @@ def fixture_sacc_galaxy_cells_lens0_lens0() -> tuple[sacc.Sacc, TomographicBin]: dndz = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens0", z, dndz) - Cells = np.random.normal(size=ells.shape[0]) + Cells: npt.NDArray[np.float64] = np.asarray(np.random.normal(size=ells.shape[0])) sacc_data.add_ell_cl("galaxy_density_cl", "lens0", "lens0", ells, Cells) cov = np.diag(np.ones_like(Cells) * 0.01) sacc_data.add_covariance(cov) - expected_bin = TomographicBin( - bin_name="lens0", - z=z.copy(), - dndz=dndz.copy(), - measurements={Galaxies.COUNTS}, + return SaccGalaxyHarmonicFixture( + sacc_data=sacc_data, + distributions={ + "lens0": SampledRedshiftDistribution(z=z.copy(), dndz=dndz.copy()) + }, + spectra={ + (TracerNames("lens0", "lens0"), "galaxy_density_cl"): ( + HarmonicAngularPowerSpectrum(ells=ells, cells=Cells) + ) + }, ) - return sacc_data, expected_bin @pytest.fixture(name="sacc_galaxy_cells_lens0_lens1") @@ -741,8 +756,11 @@ def fixture_sacc_galaxy_cells_lens0_lens1() -> SaccHarmonicCrossBinFixture: @pytest.fixture(name="sacc_galaxy_xis_lens0_lens0") -def fixture_sacc_galaxy_xis_lens0_lens0() -> tuple[sacc.Sacc, TomographicBin]: - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_xis_lens0_lens0() -> SaccGalaxyRealFixture: + """Build unwindowed lens-lens real SACC data and named expectations. + + :returns: SACC data, the copied lens distribution, and its synthetic correlation. + """ sacc_data = sacc.Sacc() z = np.linspace(0, 1.0, 256) + 0.05 @@ -751,19 +769,23 @@ def fixture_sacc_galaxy_xis_lens0_lens0() -> tuple[sacc.Sacc, TomographicBin]: dndz = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens0", z, dndz) - xis = np.random.normal(size=thetas.shape[0]) + xis: npt.NDArray[np.float64] = np.asarray(np.random.normal(size=thetas.shape[0])) sacc_data.add_theta_xi("galaxy_density_xi", "lens0", "lens0", thetas, xis) cov = np.diag(np.ones_like(xis) * 0.01) sacc_data.add_covariance(cov) - expected_bin = TomographicBin( - bin_name="lens0", - z=z.copy(), - dndz=dndz.copy(), - measurements={Galaxies.COUNTS}, + return SaccGalaxyRealFixture( + sacc_data=sacc_data, + distributions={ + "lens0": SampledRedshiftDistribution(z=z.copy(), dndz=dndz.copy()) + }, + spectra={ + (TracerNames("lens0", "lens0"), "galaxy_density_xi"): ( + RealAngularCorrelation(thetas=thetas, xis=xis) + ) + }, ) - return sacc_data, expected_bin @pytest.fixture(name="sacc_galaxy_xis_lens0_lens1") @@ -1220,8 +1242,11 @@ def fixture_sacc_galaxy_cells_order_convention() -> sacc.Sacc: @pytest.fixture(name="sacc_galaxy_cells_src0_src0_no_data") -def fixture_sacc_galaxy_cells_src0_src0_no_data() -> tuple[sacc.Sacc, TomographicBin]: - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_cells_src0_src0_no_data() -> SaccGalaxyDistributionFixture: + """Build source SACC tracer data without two-point observations. + + :returns: SACC data and the copied source distribution. + """ sacc_data = sacc.Sacc() z = np.linspace(0, 1.0, 256) + 0.05 @@ -1229,13 +1254,12 @@ def fixture_sacc_galaxy_cells_src0_src0_no_data() -> tuple[sacc.Sacc, Tomographi dndz = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "src0", z, dndz) - expected_bin = TomographicBin( - bin_name="src0", - z=z.copy(), - dndz=dndz.copy(), - measurements={Galaxies.SHEAR_E}, + return SaccGalaxyDistributionFixture( + sacc_data=sacc_data, + distributions={ + "src0": SampledRedshiftDistribution(z=z.copy(), dndz=dndz.copy()) + }, ) - return sacc_data, expected_bin @pytest.fixture(name="sacc_galaxy_lens0_no_two_point_data") @@ -1260,8 +1284,11 @@ def fixture_sacc_galaxy_lens0_no_two_point_data() -> sacc.Sacc: @pytest.fixture(name="sacc_galaxy_cells_src0_src0_window") -def fixture_sacc_galaxy_cells_src0_src0_window() -> tuple[sacc.Sacc, TomographicBin]: - """Fixture for a SACC data with a window function.""" +def fixture_sacc_galaxy_cells_src0_src0_window() -> SaccGalaxyWindowedFixture: + """Build windowed source-source SACC data and named expectations. + + :returns: SACC data, the copied source distribution, and its synthetic bandpowers. + """ sacc_data = sacc.Sacc() z = np.linspace(0, 1.0, 256) + 0.05 @@ -1273,7 +1300,7 @@ def fixture_sacc_galaxy_cells_src0_src0_window() -> tuple[sacc.Sacc, Tomographic # Making a diagonal window function window = sacc.BandpowerWindow(ells, np.diag(np.ones_like(ells))) - Cells = np.random.normal(size=ells.shape[0]) + Cells: npt.NDArray[np.float64] = np.asarray(np.random.normal(size=ells.shape[0])) sacc_data.add_ell_cl( "galaxy_shear_cl_ee", "src0", "src0", ells, Cells, window=window ) @@ -1281,18 +1308,27 @@ def fixture_sacc_galaxy_cells_src0_src0_window() -> tuple[sacc.Sacc, Tomographic cov = np.diag(np.ones_like(Cells) * 0.01) sacc_data.add_covariance(cov) - expected_bin = TomographicBin( - bin_name="src0", - z=z.copy(), - dndz=dndz.copy(), - measurements={Galaxies.SHEAR_E}, + return SaccGalaxyWindowedFixture( + sacc_data=sacc_data, + distributions={ + "src0": SampledRedshiftDistribution(z=z.copy(), dndz=dndz.copy()) + }, + spectra={ + (TracerNames("src0", "src0"), "galaxy_shear_cl_ee"): ( + WindowedAngularPowerSpectrum( + support_ells=ells, bandpowers=Cells, window=window + ) + ) + }, ) - return sacc_data, expected_bin @pytest.fixture(name="sacc_galaxy_cells_src0_src0_no_window") -def fixture_sacc_galaxy_cells_src0_src0_no_window() -> tuple[sacc.Sacc, TomographicBin]: - """Fixture for a SACC data without a window function.""" +def fixture_sacc_galaxy_cells_src0_src0_no_window() -> SaccGalaxyHarmonicFixture: + """Build unwindowed source-source SACC data and named expectations. + + :returns: SACC data, the copied source distribution, and its synthetic spectrum. + """ sacc_data = sacc.Sacc() z = np.linspace(0, 1.0, 256) + 0.05 @@ -1307,20 +1343,25 @@ def fixture_sacc_galaxy_cells_src0_src0_no_window() -> tuple[sacc.Sacc, Tomograp cov = np.diag(np.ones_like(Cells) * 0.01) sacc_data.add_covariance(cov) - expected_bin = TomographicBin( - bin_name="src0", - z=z.copy(), - dndz=dndz.copy(), - measurements={Galaxies.SHEAR_E}, + return SaccGalaxyHarmonicFixture( + sacc_data=sacc_data, + distributions={ + "src0": SampledRedshiftDistribution(z=z.copy(), dndz=dndz.copy()) + }, + spectra={ + (TracerNames("src0", "src0"), "galaxy_shear_cl_ee"): ( + HarmonicAngularPowerSpectrum(ells=ells, cells=Cells) + ) + }, ) - return sacc_data, expected_bin @pytest.fixture(name="sacc_galaxy_xis_lens0_lens0_real") -def fixture_sacc_galaxy_xis_lens0_lens0_real() -> ( - tuple[sacc.Sacc, np.ndarray, np.ndarray] -): - """Fixture for a SACC data without window functions.""" +def fixture_sacc_galaxy_xis_lens0_lens0_real() -> SaccGalaxyRealFixture: + """Build unwindowed lens-lens real SACC data and named expectations. + + :returns: SACC data, the lens distribution, and its synthetic correlation. + """ sacc_data = sacc.Sacc() z = np.linspace(0, 1.0, 256) + 0.05 @@ -1329,13 +1370,21 @@ def fixture_sacc_galaxy_xis_lens0_lens0_real() -> ( dndz = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "lens0", z, dndz) - xis = np.random.normal(size=thetas.shape[0]) + xis: npt.NDArray[np.float64] = np.asarray(np.random.normal(size=thetas.shape[0])) sacc_data.add_theta_xi("galaxy_density_xi", "lens0", "lens0", thetas, xis) cov = np.diag(np.ones_like(xis) * 0.01) sacc_data.add_covariance(cov) - return sacc_data, z, thetas + return SaccGalaxyRealFixture( + sacc_data=sacc_data, + distributions={"lens0": SampledRedshiftDistribution(z=z, dndz=dndz)}, + spectra={ + (TracerNames("lens0", "lens0"), "galaxy_density_xi"): ( + RealAngularCorrelation(thetas=thetas, xis=xis) + ) + }, + ) @pytest.fixture(name="wl_factory") diff --git a/tests/likelihood/gauss_family/statistic/source/test_source.py b/tests/likelihood/gauss_family/statistic/source/test_source.py index 624f5b64b..a7a196720 100644 --- a/tests/likelihood/gauss_family/statistic/source/test_source.py +++ b/tests/likelihood/gauss_family/statistic/source/test_source.py @@ -30,9 +30,9 @@ from firecrown.likelihood.number_counts._args import NumberCountsArgs from firecrown.likelihood.weak_lensing import WeakLensingArgs from firecrown.metadata_functions import extract_all_tracers_tomographic_bins -from firecrown.metadata_types import TomographicBin from firecrown.modeling_tools import ModelingTools from firecrown.updatable import ParamsMap, get_default_params +from tests._types import SaccGalaxyHarmonicFixture @pytest.fixture( @@ -188,16 +188,17 @@ def test_trivial_source_select_field() -> None: def test_weak_lensing_source_init( - sacc_galaxy_cells_src0_src0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0: SaccGalaxyHarmonicFixture, tools_with_vanilla_cosmology: ModelingTools, ) -> None: - sacc_data, expected_bin = sacc_galaxy_cells_src0_src0 + sacc_data = sacc_galaxy_cells_src0_src0.sacc_data + expected_distribution = sacc_galaxy_cells_src0_src0.distributions["src0"] source = wl.WeakLensing(sacc_tracer="src0") source.read(sacc_data) - assert_allclose(source.tracer_args.z, expected_bin.z) - assert_allclose(source.tracer_args.dndz, expected_bin.dndz) + assert_allclose(source.tracer_args.z, expected_distribution.z) + assert_allclose(source.tracer_args.dndz, expected_distribution.dndz) source.update(ParamsMap()) ts = source.get_tracers(tools_with_vanilla_cosmology) assert len(ts) == 1 @@ -207,9 +208,9 @@ def test_weak_lensing_source_init( def test_weak_lensing_source_create_ready( - sacc_galaxy_cells_src0_src0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_src0_src0 + sacc_data = sacc_galaxy_cells_src0_src0.sacc_data all_tracers = extract_all_tracers_tomographic_bins(sacc_data) src0 = next((obj for obj in all_tracers if obj.bin_name == "src0"), None) @@ -225,9 +226,9 @@ def test_weak_lensing_source_create_ready( def test_weak_lensing_source_factory( - sacc_galaxy_cells_src0_src0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_src0_src0 + sacc_data = sacc_galaxy_cells_src0_src0.sacc_data all_tracers = extract_all_tracers_tomographic_bins(sacc_data) src0 = next((obj for obj in all_tracers if obj.bin_name == "src0"), None) @@ -244,9 +245,9 @@ def test_weak_lensing_source_factory( def test_weak_lensing_source_factory_cache( - sacc_galaxy_cells_src0_src0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_src0_src0 + sacc_data = sacc_galaxy_cells_src0_src0.sacc_data all_tracers = extract_all_tracers_tomographic_bins(sacc_data) src0 = next((obj for obj in all_tracers if obj.bin_name == "src0"), None) @@ -260,10 +261,10 @@ def test_weak_lensing_source_factory_cache( @pytest.mark.parametrize("include_z_dependence", [True, False]) def test_weak_lensing_source_factory_global_systematics( - sacc_galaxy_cells_src0_src0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0: SaccGalaxyHarmonicFixture, include_z_dependence: bool, ) -> None: - sacc_data, _ = sacc_galaxy_cells_src0_src0 + sacc_data = sacc_galaxy_cells_src0_src0.sacc_data all_tracers = extract_all_tracers_tomographic_bins(sacc_data) src0 = next((obj for obj in all_tracers if obj.bin_name == "src0"), None) @@ -293,9 +294,9 @@ def test_weak_lensing_source_factory_global_systematics( def test_weak_lensing_source_init_wrong_name( - sacc_galaxy_cells_src0_src0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_src0_src0 + sacc_data = sacc_galaxy_cells_src0_src0.sacc_data source = wl.WeakLensing(sacc_tracer="src10") with pytest.raises(KeyError, match="src10"): @@ -303,17 +304,18 @@ def test_weak_lensing_source_init_wrong_name( def test_number_counts_source_init( - sacc_galaxy_cells_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_lens0_lens0: SaccGalaxyHarmonicFixture, tools_with_vanilla_cosmology: ModelingTools, ) -> None: - sacc_data, expected_bin = sacc_galaxy_cells_lens0_lens0 + sacc_data = sacc_galaxy_cells_lens0_lens0.sacc_data + expected_distribution = sacc_galaxy_cells_lens0_lens0.distributions["lens0"] source = nc.NumberCounts(sacc_tracer="lens0") source.read(sacc_data) - assert_allclose(source.tracer_args.z, expected_bin.z) - assert_allclose(source.tracer_args.dndz, expected_bin.dndz) + assert_allclose(source.tracer_args.z, expected_distribution.z) + assert_allclose(source.tracer_args.dndz, expected_distribution.dndz) source.update(ParamsMap(lens0_bias=1.1)) dp = source.get_derived_parameters() assert dp is not None @@ -323,9 +325,9 @@ def test_number_counts_source_init( def test_number_counts_source_create_ready( - sacc_galaxy_cells_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_lens0_lens0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_lens0_lens0 + sacc_data = sacc_galaxy_cells_lens0_lens0.sacc_data all_tracers = extract_all_tracers_tomographic_bins(sacc_data) lens0 = next((obj for obj in all_tracers if obj.bin_name == "lens0"), None) @@ -341,9 +343,9 @@ def test_number_counts_source_create_ready( def test_number_counts_source_factory( - sacc_galaxy_cells_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_lens0_lens0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_lens0_lens0 + sacc_data = sacc_galaxy_cells_lens0_lens0.sacc_data all_tracers = extract_all_tracers_tomographic_bins(sacc_data) lens0 = next((obj for obj in all_tracers if obj.bin_name == "lens0"), None) @@ -360,9 +362,9 @@ def test_number_counts_source_factory( def test_number_counts_source_factory_cache( - sacc_galaxy_cells_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_lens0_lens0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_lens0_lens0 + sacc_data = sacc_galaxy_cells_lens0_lens0.sacc_data all_tracers = extract_all_tracers_tomographic_bins(sacc_data) lens0 = next((obj for obj in all_tracers if obj.bin_name == "lens0"), None) @@ -375,9 +377,9 @@ def test_number_counts_source_factory_cache( def test_number_counts_source_factory_global_systematics( - sacc_galaxy_cells_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_lens0_lens0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_lens0_lens0 + sacc_data = sacc_galaxy_cells_lens0_lens0.sacc_data all_tracers = extract_all_tracers_tomographic_bins(sacc_data) lens0 = next((obj for obj in all_tracers if obj.bin_name == "lens0"), None) @@ -403,9 +405,9 @@ def test_number_counts_source_factory_global_systematics( def test_number_counts_source_init_wrong_name( - sacc_galaxy_cells_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_lens0_lens0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_lens0_lens0 + sacc_data = sacc_galaxy_cells_lens0_lens0.sacc_data source = nc.NumberCounts(sacc_tracer="lens10") with pytest.raises(KeyError, match="lens10"): diff --git a/tests/likelihood/gauss_family/statistic/test_two_point.py b/tests/likelihood/gauss_family/statistic/test_two_point.py index 0146e1585..ae7d49891 100644 --- a/tests/likelihood/gauss_family/statistic/test_two_point.py +++ b/tests/likelihood/gauss_family/statistic/test_two_point.py @@ -7,7 +7,6 @@ from unittest.mock import MagicMock import numpy as np -import numpy.typing as npt import pyccl import pytest import sacc @@ -49,6 +48,12 @@ from firecrown.modeling_tools import ModelingTools from firecrown.models.two_point import TwoPointTheory from firecrown.updatable import ParamsMap, get_default_params_map +from tests._types import ( + SaccGalaxyDistributionFixture, + SaccGalaxyHarmonicFixture, + SaccGalaxyRealFixture, + SaccGalaxyWindowedFixture, +) @pytest.fixture(name="include_rsd", params=[True, False], ids=["rsd", "no_rsd"]) @@ -157,15 +162,15 @@ def test_tracer_names() -> None: def test_two_point_src0_src0_window_parameterized( window_fixture: str, - sacc_galaxy_cells_src0_src0_window: tuple[sacc.Sacc, TomographicBin], - sacc_galaxy_cells_src0_src0_no_window: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0_window: SaccGalaxyWindowedFixture, + sacc_galaxy_cells_src0_src0_no_window: SaccGalaxyHarmonicFixture, ) -> None: """Test TwoPoint theory calculations are repeatable for both window scenarios.""" if window_fixture == "with_window": - sacc_data, _ = sacc_galaxy_cells_src0_src0_window + sacc_data = sacc_galaxy_cells_src0_src0_window.sacc_data expected_window = True else: # no_window - sacc_data, _ = sacc_galaxy_cells_src0_src0_no_window + sacc_data = sacc_galaxy_cells_src0_src0_no_window.sacc_data expected_window = False src0 = WeakLensing(sacc_tracer="src0") @@ -216,11 +221,11 @@ def test_two_point_generate_ell_or_theta() -> None: def test_two_point_src0_src0_no_data_binning( - sacc_galaxy_cells_src0_src0_no_data: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0_no_data: SaccGalaxyDistributionFixture, binning_type: str, ) -> None: """Test TwoPoint with different binning types when no data is present.""" - sacc_data, _ = sacc_galaxy_cells_src0_src0_no_data + sacc_data = sacc_galaxy_cells_src0_src0_no_data.sacc_data src0 = WeakLensing(sacc_tracer="src0") @@ -345,9 +350,9 @@ def test_two_point_theory_construction() -> None: def test_two_point_lens0_lens0_config( - sacc_galaxy_xis_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_xis_lens0_lens0: SaccGalaxyRealFixture, ) -> None: - sacc_data, _ = sacc_galaxy_xis_lens0_lens0 + sacc_data = sacc_galaxy_xis_lens0_lens0.sacc_data src0 = NumberCounts(sacc_tracer="lens0") @@ -379,9 +384,9 @@ def test_two_point_lens0_lens0_config( def test_two_point_src0_src0_no_data_error( - sacc_galaxy_cells_src0_src0_no_data: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0_no_data: SaccGalaxyDistributionFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_src0_src0_no_data + sacc_data = sacc_galaxy_cells_src0_src0_no_data.sacc_data src0 = WeakLensing(sacc_tracer="src0") @@ -419,9 +424,9 @@ def test_two_point_lens0_lens0_requires_theta_config_without_measurements( def test_two_point_src0_src0_data_and_conf_warn( - sacc_galaxy_cells_src0_src0_window: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0_window: SaccGalaxyWindowedFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_src0_src0_window + sacc_data = sacc_galaxy_cells_src0_src0_window.sacc_data src0 = WeakLensing(sacc_tracer="src0") @@ -444,9 +449,9 @@ def test_two_point_src0_src0_data_and_conf_warn( def test_two_point_lens0_lens0_data_and_conf_warn( - sacc_galaxy_xis_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_xis_lens0_lens0: SaccGalaxyRealFixture, ) -> None: - sacc_data, _ = sacc_galaxy_xis_lens0_lens0 + sacc_data = sacc_galaxy_xis_lens0_lens0.sacc_data src0 = NumberCounts(sacc_tracer="lens0") @@ -727,12 +732,12 @@ def test_apply_interp_when_serialization_error() -> None: def test_two_point_src0_src0_window_aiw( - sacc_galaxy_cells_src0_src0_window: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0_window: SaccGalaxyWindowedFixture, apply_interp_when: tp.ApplyInterpolationWhen, ) -> None: """This test also makes sure that TwoPoint theory calculations are repeatable.""" - sacc_data, _ = sacc_galaxy_cells_src0_src0_window + sacc_data = sacc_galaxy_cells_src0_src0_window.sacc_data src0 = WeakLensing(sacc_tracer="src0") @@ -761,10 +766,10 @@ def test_two_point_src0_src0_window_aiw( def test_two_point_src0_src0_no_window_aiw( - sacc_galaxy_cells_src0_src0_no_window: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0_no_window: SaccGalaxyHarmonicFixture, apply_interp_when: tp.ApplyInterpolationWhen, ) -> None: - sacc_data, _ = sacc_galaxy_cells_src0_src0_no_window + sacc_data = sacc_galaxy_cells_src0_src0_no_window.sacc_data src0 = WeakLensing(sacc_tracer="src0") @@ -795,9 +800,7 @@ def test_two_point_src0_src0_no_window_aiw( def test_two_point_src0_src0_real_aiw( - sacc_galaxy_xis_lens0_lens0_real: tuple[ - sacc.Sacc, npt.NDArray[np.float64], npt.NDArray[np.float64] - ], + sacc_galaxy_xis_lens0_lens0_real: SaccGalaxyRealFixture, apply_interp_when: tp.ApplyInterpolationWhen, ) -> None: """Check a real-space lens0-lens0 TwoPoint computes a finite theory vector. @@ -806,11 +809,10 @@ def test_two_point_src0_src0_real_aiw( vector. The cell count matches ``ells_for_xi`` only when the REAL interpolation flag is set. The redshift and angular arrays are unused. - :param sacc_galaxy_xis_lens0_lens0_real: Tuple of SACC data, redshift - coordinates, and angular coordinates from the fixture. + :param sacc_galaxy_xis_lens0_lens0_real: Real-space SACC fixture. :param apply_interp_when: When to apply interpolation. """ - sacc_data, _, _ = sacc_galaxy_xis_lens0_lens0_real + sacc_data = sacc_galaxy_xis_lens0_lens0_real.sacc_data lens0 = WeakLensing(sacc_tracer="lens0") statistic = tp.TwoPoint( diff --git a/tests/metadata/test_metadata_two_point_sacc.py b/tests/metadata/test_metadata_two_point_sacc.py index 8a1160cbc..9204232ac 100644 --- a/tests/metadata/test_metadata_two_point_sacc.py +++ b/tests/metadata/test_metadata_two_point_sacc.py @@ -101,14 +101,33 @@ class SaccHarmonicThreeTracerFixture: distributions: dict[str, SampledRedshiftDistribution] +@dataclass(frozen=True, kw_only=True) +class SaccInvalidDataTypeFixture: + """SACC data and raw inputs for an intentionally invalid data type. + + Frozen bindings leave SACC, mappings, and arrays mutable. The raw values + have no asserted physical spectrum interpretation. + + :param sacc_data: SACC data retaining the unsupported data type. + :param distributions: Sampled redshift distributions keyed by tracer name. + :param ells: Integer multipole coordinates used for the data points. + :param values: Raw values used for the invalid data points. + """ + + sacc_data: sacc.Sacc + distributions: dict[str, SampledRedshiftDistribution] + ells: npt.NDArray[np.int64] + values: npt.NDArray[np.float64] + + @pytest.fixture(name="sacc_galaxy_src0_src0_invalid_data_type") def fixture_sacc_galaxy_src0_src0_invalid_data_type( recwarn: pytest.WarningsRecorder, -) -> tuple[sacc.Sacc, npt.NDArray[np.float64], npt.NDArray[np.float64]]: +) -> SaccInvalidDataTypeFixture: """Build unwindowed SACC data with an intentionally invalid data type. :param recwarn: Recorder for SACC's unknown-data-type warning. - :returns: SACC data, redshift coordinates, and source number density. + :returns: Invalid SACC data, sampled distribution, multipoles, and raw values. """ sacc_data = sacc.Sacc() @@ -118,8 +137,8 @@ def fixture_sacc_galaxy_src0_src0_invalid_data_type( dndz = np.exp(-0.5 * (z - 0.5) ** 2 / 0.05 / 0.05) sacc_data.add_tracer("NZ", "src0", z, dndz) - Cells = np.random.normal(size=ells.shape[0]) - sacc_data.add_ell_cl("this_type_is_invalid", "src0", "src0", ells, Cells) + values: npt.NDArray[np.float64] = np.asarray(np.random.normal(size=ells.shape[0])) + sacc_data.add_ell_cl("this_type_is_invalid", "src0", "src0", ells, values) warning = next(iter(recwarn), None) if warning is not None: assert isinstance(warning.message, UserWarning) @@ -127,10 +146,15 @@ def fixture_sacc_galaxy_src0_src0_invalid_data_type( r"Unknown data_type value this_type_is_invalid\.", str(warning.message) ) - cov = np.diag(np.ones_like(Cells) * 0.01) + cov = np.diag(np.ones_like(values) * 0.01) sacc_data.add_covariance(cov) - return sacc_data, z, dndz + return SaccInvalidDataTypeFixture( + sacc_data=sacc_data, + distributions={"src0": SampledRedshiftDistribution(z=z, dndz=dndz)}, + ells=ells, + values=values, + ) @pytest.fixture(name="sacc_galaxy_xis_three_tracers") @@ -254,17 +278,18 @@ def test_extract_all_tracers_xis( def test_extract_all_tracers_cells_src0_src0( - sacc_galaxy_cells_src0_src0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, expected_bin = sacc_galaxy_cells_src0_src0 + sacc_data = sacc_galaxy_cells_src0_src0.sacc_data + expected_distribution = sacc_galaxy_cells_src0_src0.distributions["src0"] assert sacc_data is not None all_tracers = extract_all_tracers_tomographic_bins(sacc_data) assert len(all_tracers) == 1 for tracer in all_tracers: - assert_array_equal(tracer.z, expected_bin.z) - assert_array_equal(tracer.dndz, expected_bin.dndz) + assert_array_equal(tracer.z, expected_distribution.z) + assert_array_equal(tracer.dndz, expected_distribution.dndz) assert tracer.bin_name == "src0" assert tracer.measurements == {Galaxies.SHEAR_E} @@ -293,17 +318,18 @@ def test_extract_all_tracers_cells_src0_src1( def test_extract_all_tracers_cells_lens0_lens0( - sacc_galaxy_cells_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_lens0_lens0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, expected_bin = sacc_galaxy_cells_lens0_lens0 + sacc_data = sacc_galaxy_cells_lens0_lens0.sacc_data + expected_distribution = sacc_galaxy_cells_lens0_lens0.distributions["lens0"] assert sacc_data is not None all_tracers = extract_all_tracers_tomographic_bins(sacc_data) assert len(all_tracers) == 1 for tracer in all_tracers: - assert_array_equal(tracer.z, expected_bin.z) - assert_array_equal(tracer.dndz, expected_bin.dndz) + assert_array_equal(tracer.z, expected_distribution.z) + assert_array_equal(tracer.dndz, expected_distribution.dndz) assert tracer.bin_name == "lens0" assert tracer.measurements == {Galaxies.COUNTS} @@ -332,17 +358,18 @@ def test_extract_all_tracers_cells_lens0_lens1( def test_extract_all_tracers_xis_lens0_lens0( - sacc_galaxy_xis_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_xis_lens0_lens0: SaccGalaxyRealFixture, ) -> None: - sacc_data, expected_bin = sacc_galaxy_xis_lens0_lens0 + sacc_data = sacc_galaxy_xis_lens0_lens0.sacc_data + expected_distribution = sacc_galaxy_xis_lens0_lens0.distributions["lens0"] assert sacc_data is not None all_tracers = extract_all_tracers_tomographic_bins(sacc_data) assert len(all_tracers) == 1 for tracer in all_tracers: - assert_array_equal(tracer.z, expected_bin.z) - assert_array_equal(tracer.dndz, expected_bin.dndz) + assert_array_equal(tracer.z, expected_distribution.z) + assert_array_equal(tracer.dndz, expected_distribution.dndz) assert tracer.bin_name == "lens0" assert tracer.measurements == {Galaxies.COUNTS} @@ -421,16 +448,13 @@ def test_extract_all_trace_xis_src0_lens0( def test_extract_all_tracers_invalid_data_type( - sacc_galaxy_src0_src0_invalid_data_type: tuple[ - sacc.Sacc, npt.NDArray[np.float64], npt.NDArray[np.float64] - ], + sacc_galaxy_src0_src0_invalid_data_type: SaccInvalidDataTypeFixture, ) -> None: """Reject a tracer whose only data points have an unsupported data type. - :param sacc_galaxy_src0_src0_invalid_data_type: Invalid SACC data, redshift - coordinates, and source number density. + :param sacc_galaxy_src0_src0_invalid_data_type: Invalid SACC data and raw inputs. """ - sacc_data, _, _ = sacc_galaxy_src0_src0_invalid_data_type + sacc_data = sacc_galaxy_src0_src0_invalid_data_type.sacc_data assert sacc_data is not None with pytest.raises( ValueError, match="Tracer src0 does not have data points associated with it." @@ -694,9 +718,9 @@ def test_extract_all_metadata_index_reals_by_type( def test_extract_no_window( - sacc_galaxy_cells_src0_src0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0: SaccGalaxyHarmonicFixture, ) -> None: - sacc_data, _ = sacc_galaxy_cells_src0_src0 + sacc_data = sacc_galaxy_cells_src0_src0.sacc_data indices = np.array([0, 1, 2], dtype=np.int64) with warnings.catch_warnings(): diff --git a/tests/metadata/test_metadata_two_point_types.py b/tests/metadata/test_metadata_two_point_types.py index 355076c01..b5ecf3374 100644 --- a/tests/metadata/test_metadata_two_point_types.py +++ b/tests/metadata/test_metadata_two_point_types.py @@ -33,7 +33,6 @@ Clusters, Galaxies, Measurement, - TomographicBin, TracerNames, TwoPointHarmonic, TwoPointReal, @@ -721,14 +720,14 @@ def test_extract_all_measured_mixed_types_with_genuinely_mixed_tracers( def test_extract_all_measured_mixed_types_galaxy_only( - sacc_galaxy_cells_src0_src0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_src0_src0: SaccGalaxyHarmonicFixture, ) -> None: """Test extract_all_measured_types with allow_mixed_types=True on single type. This test verifies that tracers with only one measurement type work correctly with allow_mixed_types=True. """ - sacc_data, _ = sacc_galaxy_cells_src0_src0 + sacc_data = sacc_galaxy_cells_src0_src0.sacc_data result, _ = extract_all_measured_types(sacc_data, allow_mixed_types=True) @@ -737,14 +736,14 @@ def test_extract_all_measured_mixed_types_galaxy_only( def test_extract_all_measured_mixed_types_density_only( - sacc_galaxy_cells_lens0_lens0: tuple[sacc.Sacc, TomographicBin], + sacc_galaxy_cells_lens0_lens0: SaccGalaxyHarmonicFixture, ) -> None: """Test extract_all_measured_types with allow_mixed_types=True on density. Verifies that galaxy density (COUNTS) measurements work correctly with allow_mixed_types=True. """ - sacc_data, _ = sacc_galaxy_cells_lens0_lens0 + sacc_data = sacc_galaxy_cells_lens0_lens0.sacc_data result, _ = extract_all_measured_types(sacc_data, allow_mixed_types=True) From c3986f12574f64f9d7a1d3db24fb978ce4d22e88 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Wed, 28 Oct 2026 07:54:14 -0500 Subject: [PATCH 17/20] docs(test): reword fixture docstrings and drop independence claims Replace "independent" wording in test fixture docstrings with "input", "expected", or "named expectations", and remove notes about frozen bindings, dtype annotations, and shape or unit checks. No executable code changes. --- tests/_types.py | 25 +++++----- tests/conftest.py | 15 ++---- .../metadata/test_metadata_two_point_sacc.py | 47 ++++++++----------- .../metadata/test_metadata_two_point_types.py | 24 +++++----- tests/test_scale_cuts_with_window.py | 20 +++----- 5 files changed, 55 insertions(+), 76 deletions(-) diff --git a/tests/_types.py b/tests/_types.py index ca093f610..851f26e41 100644 --- a/tests/_types.py +++ b/tests/_types.py @@ -1,7 +1,6 @@ """Shared types for test fixtures. -Frozen records bind named fields, but do not freeze nested arrays, maps, or SACC. -Array annotations specify dtype, not semantic role, shape, or physical units. +Expected arrays retain the inputs supplied to SACC, or copies of those inputs. """ from dataclasses import dataclass @@ -17,7 +16,7 @@ @dataclass(frozen=True, kw_only=True) class SampledRedshiftDistribution: - """Independent sampled redshift coordinates and unnormalized number density. + """Sampled redshift coordinates and unnormalized number density. :param z: Sampled redshift coordinates. :param dndz: Number-density values on the redshift grid; not normalized. @@ -29,7 +28,7 @@ class SampledRedshiftDistribution: @dataclass(frozen=True, kw_only=True) class SaccGalaxyDistributionFixture: - """SACC tracer data and independently sampled redshift distributions. + """SACC tracer data and its sampled redshift distributions. :param sacc_data: SACC data used by production extraction and construction. :param distributions: Redshift distributions keyed by tracer name. @@ -41,7 +40,7 @@ class SaccGalaxyDistributionFixture: @dataclass(frozen=True, kw_only=True) class SaccHarmonicCrossBinFixture: - """Harmonic cross-bin SACC data and independent redshift expectations. + """Harmonic cross-bin SACC data and expected redshift distributions. Both tracer distributions share their original redshift coordinate array, but retain distinct unnormalized number densities. No measurement metadata @@ -57,7 +56,7 @@ class SaccHarmonicCrossBinFixture: @dataclass(frozen=True, kw_only=True) class SaccRealCrossBinFixture: - """Real cross-bin SACC data and independent redshift expectations. + """Real cross-bin SACC data and expected redshift distributions. Both tracer distributions share their original redshift coordinate array, but retain distinct unnormalized number densities. No measurement metadata @@ -73,7 +72,7 @@ class SaccRealCrossBinFixture: @dataclass(frozen=True, kw_only=True) class HarmonicAngularPowerSpectrum: - """Independent unwindowed harmonic coordinates and synthetic observations. + """Unwindowed harmonic coordinates and synthetic observations. :param ells: Integer multipoles for the synthetic observations. :param cells: Synthetic observed values, not survey data or theory predictions. @@ -85,7 +84,7 @@ class HarmonicAngularPowerSpectrum: @dataclass(frozen=True, kw_only=True) class SaccGalaxyHarmonicFixture: - """Harmonic SACC data with independently generated named expectations. + """Harmonic SACC data with named expected distributions and spectra. :param sacc_data: SACC data used by production extraction and construction. :param distributions: Redshift distributions keyed by tracer name. @@ -99,9 +98,9 @@ class SaccGalaxyHarmonicFixture: @dataclass(frozen=True, kw_only=True) class RealAngularCorrelation: - """Independent unwindowed angular coordinates and synthetic observations. + """Unwindowed angular coordinates and synthetic observations. - :param thetas: Angular coordinates for the observations; no unit is enforced. + :param thetas: Angular coordinates for the observations. :param xis: Synthetic correlations, not survey data or theory predictions. """ @@ -111,7 +110,7 @@ class RealAngularCorrelation: @dataclass(frozen=True, kw_only=True) class SaccGalaxyRealFixture: - """Real SACC data with independently generated named expectations. + """Real SACC data with named expected distributions and correlations. :param sacc_data: SACC data used by production extraction and construction. :param distributions: Redshift distributions keyed by tracer name. @@ -125,7 +124,7 @@ class SaccGalaxyRealFixture: @dataclass(frozen=True, kw_only=True) class WindowedAngularPowerSpectrum: - """Independent window support, synthetic bandpowers, and their shared window. + """Window support, synthetic bandpowers, and their shared window. Support multipoles need not match the number of bandpowers. SACC observation labels are weighted support sums, not the support grid @@ -145,7 +144,7 @@ class WindowedAngularPowerSpectrum: @dataclass(frozen=True, kw_only=True) class SaccGalaxyWindowedFixture: - """Windowed SACC data with independently generated named expectations. + """Windowed SACC data with named expected distributions and spectra. :param sacc_data: SACC data used by production extraction and construction. :param distributions: Redshift distributions keyed by tracer name. diff --git a/tests/conftest.py b/tests/conftest.py index 017a67d1f..736915555 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -399,14 +399,9 @@ def fixture_all_real_bins() -> list[TomographicBin]: class HarmonicWindowFunction: """Harmonic window with its ell grid, weight matrix, and band labels. - The weight matrix has one row per ell in the grid and one column per band. - Named fields and dtype annotations do not check weight-matrix dimensions, - shapes, same-dtype swaps between arrays, or physical units. - :param ell_grid: Integer multipoles on which the window weights are defined. :param weights: Weight matrix with one row per ell and one column per band. - :param band_labels: Float labels, one per weight column; TwoPointHarmonic checks - their one-dimensional shape and count, not their values or physical meaning. + :param band_labels: Float labels, one per weight column. """ ell_grid: npt.NDArray[np.int64] @@ -885,7 +880,7 @@ def fixture_sacc_galaxy_xis_src0_lens0() -> SaccRealCrossBinFixture: @pytest.fixture(name="sacc_galaxy_cells", scope="module") def fixture_sacc_galaxy_cells() -> SaccGalaxyHarmonicFixture: - """Build unwindowed harmonic SACC data and independent named expectations. + """Build unwindowed harmonic SACC data and named expectations. Module-scoped because SACC objects are expensive to create. Consumers must copy SACC before mutation; expected grids remain shared between records. @@ -954,7 +949,7 @@ def fixture_sacc_galaxy_cells() -> SaccGalaxyHarmonicFixture: @pytest.fixture(name="sacc_galaxy_cwindows", scope="module") def fixture_sacc_galaxy_cwindows() -> SaccGalaxyWindowedFixture: - """Build windowed SACC data and independent named expectations. + """Build windowed SACC data and named expectations. Module-scoped because SACC objects are expensive to create. Expected grids remain shared between records, and windows are shared with SACC data points. @@ -1061,7 +1056,7 @@ def fixture_sacc_galaxy_cwindows() -> SaccGalaxyWindowedFixture: @pytest.fixture(name="sacc_galaxy_xis", scope="module") def fixture_sacc_galaxy_xis() -> SaccGalaxyRealFixture: - """Build unwindowed real SACC data and independent named expectations. + """Build unwindowed real SACC data and named expectations. Module-scoped because SACC objects are expensive to create. Consumers must copy SACC before mutation; expected grids remain shared between records. @@ -1142,7 +1137,7 @@ def fixture_sacc_galaxy_xis() -> SaccGalaxyRealFixture: @pytest.fixture(name="sacc_galaxy_xis_inverted") def fixture_sacc_galaxy_xis_inverted() -> SaccGalaxyRealFixture: - """Build inverted real SACC data and independent named expectations. + """Build inverted real SACC data and named expectations. Preserve the intentionally reversed tracer conventions for correction tests. Expected grids remain shared between records. diff --git a/tests/metadata/test_metadata_two_point_sacc.py b/tests/metadata/test_metadata_two_point_sacc.py index 9204232ac..dafea81e2 100644 --- a/tests/metadata/test_metadata_two_point_sacc.py +++ b/tests/metadata/test_metadata_two_point_sacc.py @@ -75,11 +75,8 @@ class SaccRealThreeTracerFixture: """Real SACC data with an intentionally invalid three-tracer correlation. - Frozen bindings leave SACC, mappings, and arrays mutable. Array dtypes do not - enforce semantic roles, shapes, units, or a shared redshift grid. - :param sacc_data: SACC data retaining the three-tracer rejection input. - :param distributions: Independent sampled distributions keyed by tracer name. + :param distributions: Input redshift distributions keyed by tracer name. """ sacc_data: sacc.Sacc @@ -90,11 +87,8 @@ class SaccRealThreeTracerFixture: class SaccHarmonicThreeTracerFixture: """Harmonic SACC data with an intentionally invalid three-tracer correlation. - Frozen bindings leave SACC, mappings, and arrays mutable. Array dtypes do not - enforce semantic roles, shapes, units, or a shared redshift grid. - :param sacc_data: SACC data retaining the three-tracer rejection input. - :param distributions: Independent sampled distributions keyed by tracer name. + :param distributions: Input redshift distributions keyed by tracer name. """ sacc_data: sacc.Sacc @@ -105,13 +99,10 @@ class SaccHarmonicThreeTracerFixture: class SaccInvalidDataTypeFixture: """SACC data and raw inputs for an intentionally invalid data type. - Frozen bindings leave SACC, mappings, and arrays mutable. The raw values - have no asserted physical spectrum interpretation. - :param sacc_data: SACC data retaining the unsupported data type. :param distributions: Sampled redshift distributions keyed by tracer name. :param ells: Integer multipole coordinates used for the data points. - :param values: Raw values used for the invalid data points. + :param values: Raw values for the invalid data points, with no physical meaning. """ sacc_data: sacc.Sacc @@ -161,7 +152,7 @@ def fixture_sacc_galaxy_src0_src0_invalid_data_type( def fixture_sacc_galaxy_xis_three_tracers() -> SaccRealThreeTracerFixture: """Build unwindowed real-space SACC data with a three-tracer correlation. - :returns: SACC data and independent src0/lens0/lens1 distributions sharing + :returns: SACC data and input src0/lens0/lens1 distributions sharing their original redshift grid and retaining distinct number densities. """ sacc_data = sacc.Sacc() @@ -203,7 +194,7 @@ def fixture_sacc_galaxy_xis_three_tracers() -> SaccRealThreeTracerFixture: def fixture_sacc_galaxy_cells_three_tracers() -> SaccHarmonicThreeTracerFixture: """Build unwindowed harmonic SACC data with a three-tracer correlation. - :returns: SACC data and independent src0/lens0/lens1 distributions sharing + :returns: SACC data and input src0/lens0/lens1 distributions sharing their original redshift grid and retaining distinct number densities. """ sacc_data = sacc.Sacc() @@ -244,7 +235,7 @@ def fixture_sacc_galaxy_cells_three_tracers() -> SaccHarmonicThreeTracerFixture: def test_extract_all_tracers_cells( sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Extract harmonic tracer distributions matching independent expectations. + """Extract harmonic tracer distributions matching the fixture inputs. :param sacc_galaxy_cells: Harmonic SACC data and named expected distributions. """ @@ -262,7 +253,7 @@ def test_extract_all_tracers_cells( def test_extract_all_tracers_xis( sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - """Extract real tracer distributions matching independent expectations. + """Extract real tracer distributions matching the fixture inputs. :param sacc_galaxy_xis: Real SACC data and named expected distributions. """ @@ -682,7 +673,7 @@ def test_extract_all_metadata_index_harmonics_by_type( def test_extract_all_metadata_index_reals( sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - """Extract real metadata indices for every independently generated spectrum. + """Extract real metadata indices for every input spectrum. :param sacc_galaxy_xis: Real SACC data and expected spectra. """ @@ -737,7 +728,7 @@ def test_extract_all_data_types_three_tracers_reals( ) -> None: """Reject real-space metadata for a correlation containing three tracers. - :param sacc_galaxy_xis_three_tracers: Real SACC data and named independent + :param sacc_galaxy_xis_three_tracers: Real SACC data and input redshift distributions for src0, lens0, and lens1. """ sacc_data = sacc_galaxy_xis_three_tracers.sacc_data @@ -757,7 +748,7 @@ def test_extract_all_data_types_three_tracers_harmonics( ) -> None: """Reject harmonic metadata for a correlation containing three tracers. - :param sacc_galaxy_cells_three_tracers: Harmonic SACC data and named independent + :param sacc_galaxy_cells_three_tracers: Harmonic SACC data and input redshift distributions for src0, lens0, and lens1. """ sacc_data = sacc_galaxy_cells_three_tracers.sacc_data @@ -884,7 +875,7 @@ def test_make_reals( def test_extract_all_harmonic_data( sacc_galaxy_cells: SaccGalaxyHarmonicFixture, ) -> None: - """Extract consistent harmonic measurements matching independent expectations. + """Extract consistent harmonic measurements matching the fixture inputs. :param sacc_galaxy_cells: Harmonic SACC data and named expected spectra. """ @@ -911,7 +902,7 @@ def test_extract_all_harmonic_data( def test_extract_all_harmonic_with_window_data( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, ) -> None: - """Extract windowed measurements matching independent support and bandpowers. + """Extract windowed measurements matching the input support and bandpowers. :param sacc_galaxy_cwindows: Windowed SACC data and named expected spectra. """ @@ -943,7 +934,7 @@ def test_extract_all_harmonic_with_window_data( def test_extract_all_real_data( sacc_galaxy_xis: SaccGalaxyRealFixture, ) -> None: - """Extract consistent real measurements matching independent expectations. + """Extract consistent real measurements matching the fixture inputs. :param sacc_galaxy_xis: Real SACC data and named expected spectra. """ @@ -996,7 +987,7 @@ def test_constructor_harmonic_data( sacc_galaxy_cells: SaccGalaxyHarmonicFixture, tp_factory: TwoPointFactory, ) -> None: - """Construct TwoPoint statistics matching independently generated measurements. + """Construct TwoPoint statistics matching the input harmonic measurements. :param sacc_galaxy_cells: Harmonic SACC data and expected spectra. :param tp_factory: Factory for the statistics' sources. @@ -1027,7 +1018,7 @@ def test_constructor_harmonic_with_window_metadata( ) -> None: """Construct TwoPoint statistics from extracted windowed harmonic metadata. - :param sacc_galaxy_cwindows: Windowed SACC data and independent windows. + :param sacc_galaxy_cwindows: Windowed SACC data and its input windows. :param tp_factory: Factory for the statistics' sources. """ sacc_data = sacc_galaxy_cwindows.sacc_data @@ -1252,7 +1243,7 @@ def test_constructor_harmonic_with_window_data( sacc_galaxy_cwindows: SaccGalaxyWindowedFixture, tp_factory: TwoPointFactory, ) -> None: - """Construct TwoPoint statistics matching independent bandpowers and windows. + """Construct TwoPoint statistics matching the input bandpowers and windows. :param sacc_galaxy_cwindows: Windowed SACC data and named expected spectra. :param tp_factory: Factory for the statistics' sources. @@ -1312,7 +1303,7 @@ def test_constructor_reals_data( sacc_galaxy_xis: SaccGalaxyRealFixture, tp_factory: TwoPointFactory, ) -> None: - """Construct TwoPoint statistics matching independently generated real data. + """Construct TwoPoint statistics matching the input real measurements. :param sacc_galaxy_xis: Real SACC data and expected spectra. :param tp_factory: Factory for the statistics' sources. @@ -1510,7 +1501,7 @@ def test_extract_all_data_harmonic_no_cov( ) -> None: """Reject missing covariance without mutating the shared harmonic fixture. - :param sacc_galaxy_cells: Shared harmonic SACC data and independent expectations. + :param sacc_galaxy_cells: Shared harmonic SACC data and named expectations. """ shared_sacc = sacc_galaxy_cells.sacc_data shared_covariance = shared_sacc.covariance @@ -1534,7 +1525,7 @@ def test_extract_all_data_real_no_cov( ) -> None: """Reject missing covariance without mutating the shared real fixture. - :param sacc_galaxy_xis: Shared real SACC data and independent expectations. + :param sacc_galaxy_xis: Shared real SACC data and named expectations. """ shared_sacc = sacc_galaxy_xis.sacc_data shared_covariance = shared_sacc.covariance diff --git a/tests/metadata/test_metadata_two_point_types.py b/tests/metadata/test_metadata_two_point_types.py index b5ecf3374..aa0ff8e3e 100644 --- a/tests/metadata/test_metadata_two_point_types.py +++ b/tests/metadata/test_metadata_two_point_types.py @@ -204,7 +204,7 @@ def test_extract_all_tracers_types_cells( ) -> None: """Verify harmonic tracers have the expected lens and source measurements. - :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + :param sacc_galaxy_cells: Harmonic SACC data with named expectations. """ sacc_data = sacc_galaxy_cells.sacc_data @@ -244,7 +244,7 @@ def test_extract_all_tracers_types_reals( ) -> None: """Verify real tracers retain their lens and source measurement meanings. - :param sacc_galaxy_xis: Real SACC data with named independent expectations. + :param sacc_galaxy_xis: Real SACC data with named expectations. """ sacc_data = sacc_galaxy_xis.sacc_data @@ -290,7 +290,7 @@ def test_extract_all_tracers_types_cells_include_maybe( ) -> None: """Verify repeated harmonic measured-type extraction returns equal results. - :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + :param sacc_galaxy_cells: Harmonic SACC data with named expectations. """ sacc_data = sacc_galaxy_cells.sacc_data @@ -318,7 +318,7 @@ def test_extract_all_tracers_types_xi_thetas_include_maybe( ) -> None: """Verify repeated real measured-type extraction returns equal results. - :param sacc_galaxy_xis: Real SACC data with named independent expectations. + :param sacc_galaxy_xis: Real SACC data with named expectations. """ sacc_data = sacc_galaxy_xis.sacc_data @@ -412,7 +412,7 @@ def test_check_two_point_consistence_harmonic_mixing_cov( ) -> None: """Verify harmonic consistency rejects measurements with mixed covariance names. - :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + :param sacc_galaxy_cells: Harmonic SACC data with named expectations. """ sacc_data = sacc_galaxy_cells.sacc_data @@ -435,7 +435,7 @@ def test_check_two_point_consistence_real_mixing_cov( ) -> None: """Verify real consistency rejects measurements with mixed covariance names. - :param sacc_galaxy_xis: Real SACC data with named independent expectations. + :param sacc_galaxy_xis: Real SACC data with named expectations. """ sacc_data = sacc_galaxy_xis.sacc_data @@ -457,7 +457,7 @@ def test_check_two_point_consistence_harmonic_non_unique_indices( ) -> None: """Verify harmonic consistency rejects repeated indices within a measurement. - :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + :param sacc_galaxy_cells: Harmonic SACC data with named expectations. """ sacc_data = sacc_galaxy_cells.sacc_data @@ -479,7 +479,7 @@ def test_check_two_point_consistence_real_non_unique_indices( ) -> None: """Verify real consistency rejects repeated indices within a measurement. - :param sacc_galaxy_xis: Real SACC data with named independent expectations. + :param sacc_galaxy_xis: Real SACC data with named expectations. """ sacc_data = sacc_galaxy_xis.sacc_data @@ -500,7 +500,7 @@ def test_check_two_point_consistence_harmonic_indices_overlap( ) -> None: """Verify harmonic consistency rejects indices shared between measurements. - :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + :param sacc_galaxy_cells: Harmonic SACC data with named expectations. """ sacc_data = sacc_galaxy_cells.sacc_data @@ -522,7 +522,7 @@ def test_check_two_point_consistence_real_indices_overlap( ) -> None: """Verify real consistency rejects indices shared between measurements. - :param sacc_galaxy_xis: Real SACC data with named independent expectations. + :param sacc_galaxy_xis: Real SACC data with named expectations. """ sacc_data = sacc_galaxy_xis.sacc_data @@ -704,7 +704,7 @@ def test_extract_all_measured_mixed_types_with_genuinely_mixed_tracers( This test verifies that when allow_mixed_types=True, the function permits tracers to have multiple measurement types without raising an error. - :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + :param sacc_galaxy_cells: Harmonic SACC data with named expectations. """ sacc_data = sacc_galaxy_cells.sacc_data @@ -906,7 +906,7 @@ def test_extract_all_measured_types_allow_mixed_types_vs_false( an error (after potential auto-correction attempts), while with allow_mixed_types=True, we get results without error. - :param sacc_galaxy_cells: Harmonic SACC data with named independent expectations. + :param sacc_galaxy_cells: Harmonic SACC data with named expectations. """ sacc_data = sacc_galaxy_cells.sacc_data diff --git a/tests/test_scale_cuts_with_window.py b/tests/test_scale_cuts_with_window.py index b8a6cbd7e..c41f307c1 100644 --- a/tests/test_scale_cuts_with_window.py +++ b/tests/test_scale_cuts_with_window.py @@ -28,9 +28,6 @@ class ScaleCutWindowFunction: """Window weights, support multipoles, and geometric bin centers. - Frozen bindings leave the arrays mutable. Names and dtype annotations do not - enforce matrix dimensions, shapes, same-dtype array swaps, or physical units. - :param weights: Column-normalized weights, one row per support multipole and one column per bin. :param support_ells: Integer multipoles on which the window is sampled. @@ -76,12 +73,12 @@ def fixture_window_function() -> ScaleCutWindowFunction: @dataclass(frozen=True, kw_only=True) class ScaleCutBandpowerPrediction: - """Independent CCL bandpower predictions and their scale-cut window context. + """CCL bandpowers used as SACC measurements and their scale-cut window. :param bandpowers: CCL predictions projected through the normalized window, one value per bin, also used as the fixture's SACC measurements. :param window: Shared normalized weights, support multipoles, and geometric - bin centers; frozen bindings do not freeze its arrays or check shapes. + bin centers. """ bandpowers: npt.NDArray[np.float64] @@ -90,13 +87,10 @@ class ScaleCutBandpowerPrediction: @dataclass(frozen=True, kw_only=True) class ScaleCutSaccFixture: - """Cosmic-shear SACC data with independent predictions and their cosmology. - - Frozen bindings leave SACC, the spectrum mapping, and nested arrays mutable. - Names and dtype annotations do not check same-dtype swaps, shapes, or units. + """Cosmic-shear SACC data with its input CCL bandpowers and cosmology. :param sacc_data: Windowed SACC measurements for production likelihood loading. - :param spectra: Independent CCL predictions keyed by ordered tracer-pair names. + :param spectra: Input CCL bandpowers keyed by ordered tracer-pair names. :param cosmology: Cosmology used to generate the CCL tracers and predictions. """ @@ -112,7 +106,7 @@ def fixture_minimal_3x2pt_sacc( """Generate SACC data with window functions for cosmic shear only. :param window_function_data: Window weights, support, and observation centers. - :returns: SACC data, independent CCL bandpowers with window data, and cosmology. + :returns: SACC data, input CCL bandpowers with window data, and cosmology. """ sacc_data = sacc.Sacc() @@ -192,7 +186,7 @@ def test_scale_cuts_with_bandpower_window_label( ) -> None: """Test scale cuts selected by bandpower window labels. - :param minimal_3x2pt_sacc: SACC data, independent CCL predictions, and cosmology. + :param minimal_3x2pt_sacc: SACC data, input CCL bandpowers, and cosmology. :param cut: Ordered tracer names and upper multipole threshold. :param tmp_path: Directory for the SACC file loaded by the likelihood factory. """ @@ -278,7 +272,7 @@ def test_scale_cuts_with_bandpower_window_support( ) -> None: """Test scale cuts selected by cumulative bandpower window support. - :param minimal_3x2pt_sacc: SACC data, independent CCL predictions, and cosmology. + :param minimal_3x2pt_sacc: SACC data, input CCL bandpowers, and cosmology. :param cut: Ordered tracer names and upper multipole threshold. :param method: Filter method and required fraction of window support. :param tmp_path: Directory for the SACC file loaded by the likelihood factory. From 1017e122fe92bae3a2219238ce0369c5b77dc709 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 31 Oct 2026 14:26:59 -0500 Subject: [PATCH 18/20] fix: remove redundant allocation of beta_rows --- firecrown/likelihood/_gaussian_pointmass.py | 1 - 1 file changed, 1 deletion(-) diff --git a/firecrown/likelihood/_gaussian_pointmass.py b/firecrown/likelihood/_gaussian_pointmass.py index 399bac6af..8086113bf 100644 --- a/firecrown/likelihood/_gaussian_pointmass.py +++ b/firecrown/likelihood/_gaussian_pointmass.py @@ -448,7 +448,6 @@ def _build_V(self, betas: np.ndarray) -> np.ndarray: V = np.zeros((len(row_src_idx), len(lens_tracers))) valid = (row_lens_idx >= 0) & (row_src_idx >= 0) - beta_rows = np.zeros(valid.sum(), dtype=np.float64) beta_rows = betas[row_lens_idx[valid], row_src_idx[valid]] V[valid, row_lens_idx[valid]] = beta_rows / (theta[valid] ** 2) return V From c842299bd5f16d8127962085f67e89325fa2cd23 Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 31 Oct 2026 14:33:52 -0500 Subject: [PATCH 19/20] docs(test): explain distinct SACC fixture record types --- tests/_types.py | 3 +++ tests/metadata/test_metadata_two_point_sacc.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/_types.py b/tests/_types.py index 851f26e41..666adabbb 100644 --- a/tests/_types.py +++ b/tests/_types.py @@ -38,6 +38,9 @@ class SaccGalaxyDistributionFixture: distributions: dict[str, SampledRedshiftDistribution] +# Harmonic and real-space cross-bin records have distinct types because they +# represent different physical observables. Their shared fields can evolve +# independently as fixture values become more physically realistic. @dataclass(frozen=True, kw_only=True) class SaccHarmonicCrossBinFixture: """Harmonic cross-bin SACC data and expected redshift distributions. diff --git a/tests/metadata/test_metadata_two_point_sacc.py b/tests/metadata/test_metadata_two_point_sacc.py index dafea81e2..4bcf10e3b 100644 --- a/tests/metadata/test_metadata_two_point_sacc.py +++ b/tests/metadata/test_metadata_two_point_sacc.py @@ -71,6 +71,9 @@ # pylint: disable=too-many-lines +# These rejection fixtures retain distinct harmonic and real-space types so +# their values can evolve to reflect the respective physical observables while +# preserving the intentionally invalid three-tracer inputs. @dataclass(frozen=True, kw_only=True) class SaccRealThreeTracerFixture: """Real SACC data with an intentionally invalid three-tracer correlation. From f897ce9b4d244402bd704836eebc86e2afe8553c Mon Sep 17 00:00:00 2001 From: Marc Paterno Date: Sat, 31 Oct 2026 14:40:25 -0500 Subject: [PATCH 20/20] clarify: rename independent SACC expectation to fixture expectation Rename the glossary term "Independent SACC expectation" to "SACC fixture expectation" and rewrite its definition. The new definition describes the reference as retaining the inputs supplied to SACC, or copies of them, for comparison with extracted results. It drops the earlier requirement that the reference be independent of Firecrown's production extraction and theory calculations, and the note that independence concerns origin rather than shared array storage. This is an intentional terminology change, not a correction of an error. --- GLOSSARY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GLOSSARY.md b/GLOSSARY.md index 599a5c683..c28a61ae6 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -36,8 +36,8 @@ The target-dependent interpretation of a completed static public API comparison: ## SACC Test Language -**Independent SACC expectation**: -A reference redshift distribution or measured angular spectrum specified alongside SACC data, rather than reconstructed through Firecrown's production extraction or theory calculations. Independence describes the reference's origin, not whether its underlying arrays share storage with the SACC data. +**SACC fixture expectation**: +A reference redshift distribution or angular spectrum retaining the inputs supplied to SACC, or copies of those inputs, for comparison with extracted results. **Synthetic SACC observation**: A supplied test value for a recognized SACC observable, such as an angular power spectrum, angular correlation, or windowed bandpower. It is neither a survey measurement nor necessarily a theory prediction or physically plausible realization; a value with an unknown data-type label has no established observable interpretation.