diff --git a/pyproject.toml b/pyproject.toml index 288e9766..89fd5b2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,9 @@ mflike = [ "fgspectra (>=1.3.0); python_version < '3.13'", # from mflike "syslibrary (>=0.2.0); python_version < '3.13'", # from mflike ] +cosmocnc = [ + "cosmocnc (>=1.0)", +] docs = [ "sphinx (<9); python_version < '3.12'", # tensorflow 2.13 pins typing-extensions==4.5.0 "sphinx; python_version >= '3.12'", @@ -55,6 +58,7 @@ dev = [ "pre-commit", ] all = [ + "soliket[cosmocnc]", "soliket[emulator]", "soliket[pyccl]", "soliket[pyhalomodel]", @@ -128,3 +132,6 @@ exclude_lines = [ # Don't complain about IPython completion helper "def _ipython_key_completions_", ] + +[tool.uv.sources] +cosmocnc = { git = "https://github.com/ggalloni/cosmocnc.git", branch = "soliket_compatibility" } diff --git a/tests/conftest.py b/tests/conftest.py index bc74cb59..ea9a3837 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -67,6 +67,16 @@ def check_skip_mflike(): pytest.importorskip(modname="mflike", reason="Couldn't import 'mflike' module") +@pytest.fixture +def check_skip_cosmocnc(): + """ + Check if the cosmocnc module can be imported, otherwise skip the tests. + """ + pytest.importorskip( + modname="cosmocnc", reason="Couldn't import 'cosmocnc' module" + ) + + @pytest.fixture def install_planck_lite(): """ diff --git a/tests/likelihood_refs.yaml b/tests/likelihood_refs.yaml index bfed7e87..35cce91f 100644 --- a/tests/likelihood_refs.yaml +++ b/tests/likelihood_refs.yaml @@ -21,6 +21,27 @@ clusters: rtol: *rtol atol: *atol +cosmocnc_clusters: + value: 99492.945 + rtol: *rtol + atol: *atol +cosmocnc_clusters_binned: + value: 49505.109 + rtol: *rtol + atol: *atol +cosmocnc_clusters_unbinned_backward: + value: 99544.550 + rtol: *rtol + atol: *atol +cosmocnc_clusters_multi_obs: + value: 76839.435 + rtol: *rtol + atol: *atol +cosmocnc_clusters_stacked_lensing: + value: 98985.873 + rtol: *rtol + atol: *atol + cosmopower: value: -295.139 rtol: *rtol diff --git a/tests/test_clusters.py b/tests/test_clusters.py index 0da68e34..c78c11bc 100644 --- a/tests/test_clusters.py +++ b/tests/test_clusters.py @@ -6,6 +6,140 @@ import soliket.clusters.survey as survey from soliket.clusters import tinker + +def _get_cosmocnc_common_config(): + """Common config shared across all cosmocnc cluster tests.""" + import cosmocnc + + base = cosmocnc.path_to_cosmocnc + survey_sr = os.path.join(base, "surveys", "survey_sr_so_sim.py") + survey_cat = os.path.join(base, "surveys", "survey_cat_so_sim.py") + + cnc_common = { + "stop_at_error": True, + "survey_sr": survey_sr, + "survey_cat": survey_cat, + "cosmology_tool": "cobaya", + "power_spectrum_type": "cobaya", + "obs_select": "q_so_sim", + "cluster_catalogue": "SO_sim_0", + "n_points": 2048, + "n_z": 50, + "z_min": 0.01, + "z_max": 3.0, + "M_min": 5e13, + "M_max": 5e15, + "obs_select_min": 5.0, + "obs_select_max": 200.0, + "hmf_calc": "cnc", + "hmf_type": "Tinker08", + "mass_definition": "500c", + "hmf_type_deriv": "numerical", + "interp_tinker": "linear", + "cosmo_amplitude_parameter": "sigma_8", + "cosmo_param_density": "physical", + "cosmocnc_verbose": "none", + "number_cores_hmf": 1, + "number_cores_abundance": 1, + "number_cores_data": 1, + "number_cores_stacked": 1, + } + theory = { + "camb": { + "extra_args": { + "num_massive_neutrinos": 1, + "accurate_massive_neutrino_transfers": True, + "nonlinear": False, + "kmax": 50.0, + } + }, + } + params = { + "ombh2": 0.0224, + "omch2": 0.12, + "H0": 67.0, + "logA": 3.05, + "As": {"value": "lambda logA: 1e-10*np.exp(logA)"}, + "ns": 0.965, + "tau": 0.054, + "nnu": 3.046, + "mnu": 0.06, + "A_szifi": -4.3054, + "alpha_szifi": 1.12, + "sigma_lnq_szifi": 0.173, + "bias_sz": 0.8, + "dof": 0.0, + } + return cnc_common, theory, params + + +def _get_cosmocnc_clusters_info(mode="unbinned"): + """CNCLike-based cluster likelihood setup for various modes.""" + cnc_common, theory, params = _get_cosmocnc_common_config() + + if mode == "unbinned": + cnc_like = dict( + **cnc_common, + observables=[["q_so_sim"]], + data_lik_from_abundance=True, + likelihood_type="unbinned", + stacked_likelihood=False, + ) + elif mode == "binned": + cnc_like = dict( + **cnc_common, + observables=[["q_so_sim"]], + data_lik_from_abundance=True, + likelihood_type="binned", + stacked_likelihood=False, + ) + elif mode == "unbinned_backward": + cnc_like = dict( + **cnc_common, + observables=[["q_so_sim"]], + data_lik_from_abundance=False, + likelihood_type="unbinned", + stacked_likelihood=False, + ) + elif mode == "multi_obs": + cnc_like = dict( + **cnc_common, + observables=[["q_so_sim", "p_so_sim"]], + data_lik_from_abundance=True, + likelihood_type="unbinned", + stacked_likelihood=False, + ) + params.update({ + "bias_cmblens": 0.8, + "a_lens": 1.0, + "sigma_lnp": 0.2, + "corr_lnq_lnp": 0.0, + }) + elif mode == "stacked_lensing": + cnc_like = dict( + **cnc_common, + observables=[["q_so_sim"]], + data_lik_from_abundance=False, + likelihood_type="unbinned", + stacked_likelihood=True, + stacked_data=["p_so_sim_stacked"], + compute_stacked_cov=True, + ) + params.update({ + "bias_cmblens": 0.8, + "a_lens": 1.0, + "sigma_lnp": 0.2, + "corr_lnq_lnp": 0.0, + }) + + return { + "likelihood": {"cosmocnc.CNCLike": cnc_like}, + "theory": theory, + "params": params, + "sampler": {"evaluate": None}, + } + + clusters_like_and_theory = { "likelihood": {"soliket.ClusterLikelihood": {"stop_at_error": True}}, "theory": { @@ -159,3 +293,51 @@ def test_dn_dlogM_small_grid(): # Expect output shape (nM, nz) assert out.shape[0] == M.shape[0] assert out.shape[1] == z.shape[0] + + +# --- cosmocnc-based cluster tests (no pyccl required) --- + + +def test_cosmocnc_clusters_model(check_skip_cosmocnc): + info = _get_cosmocnc_clusters_info() + _ = get_model(info) + + +def test_cosmocnc_clusters_loglike(check_skip_cosmocnc, likelihood_refs): + ref = likelihood_refs["cosmocnc_clusters"] + info = _get_cosmocnc_clusters_info() + model = get_model(info) + lnl = model.loglikes({})[0] + assert np.isclose(lnl, ref["value"], rtol=ref["rtol"], atol=ref["atol"]) + + +def test_cosmocnc_clusters_binned(check_skip_cosmocnc, likelihood_refs): + ref = likelihood_refs["cosmocnc_clusters_binned"] + info = _get_cosmocnc_clusters_info(mode="binned") + model = get_model(info) + lnl = model.loglikes({})[0] + assert np.isclose(lnl, ref["value"], rtol=ref["rtol"], atol=ref["atol"]) + + +def test_cosmocnc_clusters_unbinned_backward(check_skip_cosmocnc, likelihood_refs): + ref = likelihood_refs["cosmocnc_clusters_unbinned_backward"] + info = _get_cosmocnc_clusters_info(mode="unbinned_backward") + model = get_model(info) + lnl = model.loglikes({})[0] + assert np.isclose(lnl, ref["value"], rtol=ref["rtol"], atol=ref["atol"]) + + +def test_cosmocnc_clusters_multi_obs(check_skip_cosmocnc, likelihood_refs): + ref = likelihood_refs["cosmocnc_clusters_multi_obs"] + info = _get_cosmocnc_clusters_info(mode="multi_obs") + model = get_model(info) + lnl = model.loglikes({})[0] + assert np.isclose(lnl, ref["value"], rtol=ref["rtol"], atol=ref["atol"]) + + +def test_cosmocnc_clusters_stacked_lensing(check_skip_cosmocnc, likelihood_refs): + ref = likelihood_refs["cosmocnc_clusters_stacked_lensing"] + info = _get_cosmocnc_clusters_info(mode="stacked_lensing") + model = get_model(info) + lnl = model.loglikes({})[0] + assert np.isclose(lnl, ref["value"], rtol=ref["rtol"], atol=ref["atol"]) diff --git a/uv.lock b/uv.lock index c3b731c3..4b4de5ea 100644 --- a/uv.lock +++ b/uv.lock @@ -971,6 +971,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, ] +[[package]] +name = "cosmocnc" +version = "1.0" +source = { git = "https://github.com/ggalloni/cosmocnc.git?branch=soliket_compatibility#9ebbfeb8bb77668cf121e926c822092e7f002d1c" } +dependencies = [ + { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "matplotlib", version = "3.10.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mcfit" }, + { name = "multiprocess" }, + { name = "numpy", version = "1.24.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and sys_platform != 'win32') or (python_full_version < '3.11' and sys_platform == 'win32')" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and sys_platform != 'win32') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] + [[package]] name = "cosmopower" version = "0.2.0" @@ -3062,6 +3078,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, ] +[[package]] +name = "mcfit" +version = "0.0.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, + { name = "numpy", version = "1.24.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and sys_platform != 'win32') or (python_full_version < '3.11' and sys_platform == 'win32')" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and sys_platform != 'win32') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/55/1758ae48b2724e4ad88207861c799b212a41ecd8c7d5b1adc667fa13da2b/mcfit-0.0.22.tar.gz", hash = "sha256:9de22881a6b5f7aa55fc137acda06b44570631ba197550f9b0f3fa99acac2438", size = 23479, upload-time = "2024-08-19T15:58:15.368Z" } + [[package]] name = "mflike" version = "1.0.2" @@ -3099,6 +3129,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] +[[package]] +name = "multiprocess" +version = "0.70.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/b6/10832f96b499690854e574360be342a282f5f7dba58eff791299ff6c0637/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:02e5c35d7d6cd2bdc89c1858867f7bde4012837411023a4696c148c1bdd7c80e", size = 135131, upload-time = "2026-01-19T06:47:20.479Z" }, + { url = "https://files.pythonhosted.org/packages/99/50/faef2d8106534b0dc4a0b772668a1a99682696ebf17d3c0f13f2ed6a656a/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:79576c02d1207ec405b00cabf2c643c36070800cca433860e14539df7818b2aa", size = 135131, upload-time = "2026-01-19T06:47:21.879Z" }, + { url = "https://files.pythonhosted.org/packages/94/b1/0b71d18b76bf423c2e8ee00b31db37d17297ab3b4db44e188692afdca628/multiprocess-0.70.19-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6b6d78d43a03b68014ca1f0b7937d965393a670c5de7c29026beb2258f2f896", size = 135134, upload-time = "2026-01-19T06:47:23.262Z" }, + { url = "https://files.pythonhosted.org/packages/7e/aa/714635c727dbfc251139226fa4eaf1b07f00dc12d9cd2eb25f931adaf873/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1bbf1b69af1cf64cd05f65337d9215b88079ec819cd0ea7bac4dab84e162efe7", size = 144743, upload-time = "2026-01-19T06:47:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e1/155f6abf5e6b5d9cef29b6d0167c180846157a4aca9b9bee1a217f67c959/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5be9ec7f0c1c49a4f4a6fd20d5dda4aeabc2d39a50f4ad53720f1cd02b3a7c2e", size = 144738, upload-time = "2026-01-19T06:47:26.636Z" }, + { url = "https://files.pythonhosted.org/packages/af/cb/f421c2869d75750a4f32301cc20c4b63fab6376e9a75c8e5e655bdeb3d9b/multiprocess-0.70.19-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1c3dce098845a0db43b32a0b76a228ca059a668071cfeaa0f40c36c0b1585d45", size = 144741, upload-time = "2026-01-19T06:47:27.985Z" }, + { url = "https://files.pythonhosted.org/packages/41/ab/ccd9652d32e79f8fc4235f64fbfb39c85583c96f4c2290ff7757cf213ccb/multiprocess-0.70.19-pp39-pypy39_pp73-macosx_10_13_arm64.whl", hash = "sha256:e5e7dc3e3e1732e88c07aaec17eeb9917f9ed1107d9e60d5ab985cdc14bac43a", size = 133529, upload-time = "2026-01-19T06:47:29.302Z" }, + { url = "https://files.pythonhosted.org/packages/d0/75/6e4fc33200ff67819b234150c594193109f4a4573106ee4e0f417b6f6e44/multiprocess-0.70.19-pp39-pypy39_pp73-macosx_10_13_x86_64.whl", hash = "sha256:e6c0674d34b8adac22533f6786576b3de4e396aaeda9e0c15378af9b8ada2702", size = 133652, upload-time = "2026-01-19T06:47:30.122Z" }, + { url = "https://files.pythonhosted.org/packages/4e/db/a5fcaad04fb7b2e1c043df6f00d2a34ba88861ef5eeb11b82a3c80f813bd/multiprocess-0.70.19-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d6db91ca6391eebc139c352f34578cea382df6bfa03d3b4146ed12b18b01cc14", size = 133529, upload-time = "2026-01-19T06:47:31.043Z" }, + { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948, upload-time = "2026-01-19T06:47:32.325Z" }, + { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457, upload-time = "2026-01-19T06:47:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281, upload-time = "2026-01-19T06:47:35.037Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/d2c27e03cb84251dfe7249b8e82923643c6d48fa4883b9476b025e7dc7eb/multiprocess-0.70.19-py313-none-any.whl", hash = "sha256:8d5eb4ec5017ba2fab4e34a747c6d2c2b6fecfe9e7236e77988db91580ada952", size = 156414, upload-time = "2026-01-19T06:47:35.915Z" }, + { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, +] + [[package]] name = "nbclient" version = "0.10.2" @@ -5003,6 +5058,7 @@ dependencies = [ [package.optional-dependencies] all = [ + { name = "cosmocnc" }, { name = "cosmopower", marker = "python_full_version < '3.12' and sys_platform != 'win32'" }, { name = "fgspectra", marker = "python_full_version < '3.13'" }, { name = "mflike", marker = "python_full_version < '3.13'" }, @@ -5012,6 +5068,9 @@ all = [ { name = "tensorflow", marker = "python_full_version < '3.12' and sys_platform != 'win32'" }, { name = "tensorflow-probability", marker = "python_full_version < '3.12' and sys_platform != 'win32'" }, ] +cosmocnc = [ + { name = "cosmocnc" }, +] dev = [ { name = "pre-commit", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pre-commit", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, @@ -5054,6 +5113,8 @@ requires-dist = [ { name = "astropy", specifier = ">=5.3.4" }, { name = "camb", specifier = ">=1.5" }, { name = "cobaya", specifier = ">=3.5.5" }, + { name = "cosmocnc", marker = "extra == 'all'", git = "https://github.com/ggalloni/cosmocnc.git?branch=soliket_compatibility" }, + { name = "cosmocnc", marker = "extra == 'cosmocnc'", git = "https://github.com/ggalloni/cosmocnc.git?branch=soliket_compatibility" }, { name = "cosmopower", marker = "python_full_version < '3.12' and sys_platform != 'win32' and extra == 'all'", specifier = ">=0.2.0" }, { name = "cosmopower", marker = "python_full_version < '3.12' and sys_platform != 'win32' and extra == 'emulator'", specifier = ">=0.2.0" }, { name = "fgspectra", marker = "python_full_version < '3.13' and extra == 'all'", specifier = ">=1.3.0" }, @@ -5086,7 +5147,7 @@ requires-dist = [ { name = "tensorflow-probability", marker = "python_full_version < '3.12' and sys_platform != 'win32' and extra == 'all'", specifier = ">=0.20.1" }, { name = "tensorflow-probability", marker = "python_full_version < '3.12' and sys_platform != 'win32' and extra == 'emulator'", specifier = ">=0.20.1" }, ] -provides-extras = ["all", "dev", "docs", "emulator", "jupyter", "mflike", "pyccl", "pyhalomodel"] +provides-extras = ["all", "cosmocnc", "dev", "docs", "emulator", "jupyter", "mflike", "pyccl", "pyhalomodel"] [[package]] name = "soupsieve"