lsstdesc-crow 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lsstdesc_crow-1.0.2/LICENSE +26 -0
- lsstdesc_crow-1.0.2/PKG-INFO +29 -0
- lsstdesc_crow-1.0.2/README.md +2 -0
- lsstdesc_crow-1.0.2/crow/__init__.py +11 -0
- lsstdesc_crow-1.0.2/crow/cluster_modules/_clmm_patches.py +174 -0
- lsstdesc_crow-1.0.2/crow/cluster_modules/abundance.py +87 -0
- lsstdesc_crow-1.0.2/crow/cluster_modules/completeness_models.py +75 -0
- lsstdesc_crow-1.0.2/crow/cluster_modules/kernel.py +37 -0
- lsstdesc_crow-1.0.2/crow/cluster_modules/mass_proxy/__init__.py +8 -0
- lsstdesc_crow-1.0.2/crow/cluster_modules/mass_proxy/gaussian_protocol.py +79 -0
- lsstdesc_crow-1.0.2/crow/cluster_modules/mass_proxy/murata.py +109 -0
- lsstdesc_crow-1.0.2/crow/cluster_modules/parameters.py +56 -0
- lsstdesc_crow-1.0.2/crow/cluster_modules/purity_models.py +76 -0
- lsstdesc_crow-1.0.2/crow/cluster_modules/shear_profile.py +467 -0
- lsstdesc_crow-1.0.2/crow/integrator/__init__.py +1 -0
- lsstdesc_crow-1.0.2/crow/integrator/integrator.py +31 -0
- lsstdesc_crow-1.0.2/crow/integrator/numcosmo_integrator.py +94 -0
- lsstdesc_crow-1.0.2/crow/integrator/scipy_integrator.py +52 -0
- lsstdesc_crow-1.0.2/crow/properties.py +19 -0
- lsstdesc_crow-1.0.2/crow/recipes/__init__.py +1 -0
- lsstdesc_crow-1.0.2/crow/recipes/binned_exact.py +349 -0
- lsstdesc_crow-1.0.2/crow/recipes/binned_grid.py +404 -0
- lsstdesc_crow-1.0.2/crow/recipes/binned_parent.py +112 -0
- lsstdesc_crow-1.0.2/lsstdesc_crow.egg-info/PKG-INFO +29 -0
- lsstdesc_crow-1.0.2/lsstdesc_crow.egg-info/SOURCES.txt +37 -0
- lsstdesc_crow-1.0.2/lsstdesc_crow.egg-info/dependency_links.txt +1 -0
- lsstdesc_crow-1.0.2/lsstdesc_crow.egg-info/requires.txt +5 -0
- lsstdesc_crow-1.0.2/lsstdesc_crow.egg-info/top_level.txt +1 -0
- lsstdesc_crow-1.0.2/pyproject.toml +52 -0
- lsstdesc_crow-1.0.2/setup.cfg +4 -0
- lsstdesc_crow-1.0.2/tests/test_cluster_abundance.py +55 -0
- lsstdesc_crow-1.0.2/tests/test_cluster_integrators.py +50 -0
- lsstdesc_crow-1.0.2/tests/test_cluster_kernels.py +118 -0
- lsstdesc_crow-1.0.2/tests/test_cluster_mass_richness.py +277 -0
- lsstdesc_crow-1.0.2/tests/test_cluster_parameters.py +51 -0
- lsstdesc_crow-1.0.2/tests/test_cluster_shear_profile.py +325 -0
- lsstdesc_crow-1.0.2/tests/test_recipe_binned_counts.py +659 -0
- lsstdesc_crow-1.0.2/tests/test_recipe_binned_parent.py +59 -0
- lsstdesc_crow-1.0.2/tests/test_recipe_binned_shear_profile.py +492 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Copyright (c) 2018-2019, The LSST DESC clump Contributors
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
6
|
+
are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
* Neither the name of clump nor the names of its contributors may be used to
|
|
14
|
+
endorse or promote products derived from this software without specific
|
|
15
|
+
prior written permission.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
18
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
19
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
20
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
21
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
22
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
23
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
24
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
25
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
26
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lsstdesc-crow
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Cluster Reconstruction of Observables Workbench
|
|
5
|
+
Author: The LSST DESC CROW Contributors
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/LSSTDESC/crow
|
|
8
|
+
Project-URL: Repository, https://github.com/LSSTDESC/crow
|
|
9
|
+
Project-URL: Issues, https://github.com/LSSTDESC/crow/issues
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: clmm
|
|
22
|
+
Requires-Dist: numcosmo_py>=0.21.1
|
|
23
|
+
Requires-Dist: numpy>=2.0
|
|
24
|
+
Requires-Dist: pyccl>=2.8.0
|
|
25
|
+
Requires-Dist: scipy>=1.12
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# CROW
|
|
29
|
+
**C**luster **R**econstruction of **O**bservables **W**orkbench: **CROW**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Module that contains the cluster model classes."""
|
|
2
|
+
|
|
3
|
+
from .cluster_modules import completeness_models, kernel, mass_proxy, purity_models
|
|
4
|
+
from .cluster_modules.abundance import ClusterAbundance
|
|
5
|
+
from .cluster_modules.shear_profile import ClusterShearProfile
|
|
6
|
+
from .properties import ClusterProperty
|
|
7
|
+
from .recipes.binned_exact import ExactBinnedClusterRecipe
|
|
8
|
+
from .recipes.binned_grid import GridBinnedClusterRecipe
|
|
9
|
+
from .recipes.binned_parent import BinnedClusterRecipe
|
|
10
|
+
|
|
11
|
+
__version__ = "1.0.2"
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.integrate import quad, simpson
|
|
3
|
+
from scipy.interpolate import splev, splrep
|
|
4
|
+
from scipy.special import gamma, gammainc, jv
|
|
5
|
+
|
|
6
|
+
from crow.integrator.numcosmo_integrator import NumCosmoIntegrator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def numcosmo_miscentered_mean_surface_density( # pragma: no cover
|
|
10
|
+
r_proj, r_mis, integrand, norm, aux_args, extra_integral
|
|
11
|
+
):
|
|
12
|
+
"""
|
|
13
|
+
NumCosmo replacement for `integrate_azimuthially_miscentered_mean_surface_density`.
|
|
14
|
+
|
|
15
|
+
Integrates azimuthally and radially for the mean surface mass density kernel.
|
|
16
|
+
"""
|
|
17
|
+
integrator = NumCosmoIntegrator(
|
|
18
|
+
relative_tolerance=1e-6,
|
|
19
|
+
absolute_tolerance=1e-3,
|
|
20
|
+
)
|
|
21
|
+
integrand = np.vectorize(integrand)
|
|
22
|
+
r_proj = np.atleast_1d(r_proj)
|
|
23
|
+
r_lower = np.full_like(r_proj, 1e-6)
|
|
24
|
+
r_lower[1:] = r_proj[:-1]
|
|
25
|
+
|
|
26
|
+
results = []
|
|
27
|
+
args = (r_mis, *aux_args)
|
|
28
|
+
integrator.extra_args = np.array(args)
|
|
29
|
+
for r_low, r_high in zip(r_lower, r_proj):
|
|
30
|
+
if extra_integral:
|
|
31
|
+
integrator.integral_bounds = [
|
|
32
|
+
(r_low, r_high),
|
|
33
|
+
(1.0e-6, np.pi),
|
|
34
|
+
(0.0, np.inf),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
def integrand_numcosmo(int_args, extra_args):
|
|
38
|
+
r_local = int_args[:, 0]
|
|
39
|
+
theta = int_args[:, 1]
|
|
40
|
+
extra = int_args[:, 2]
|
|
41
|
+
return integrand(theta, r_local, extra, *extra_args)
|
|
42
|
+
|
|
43
|
+
else:
|
|
44
|
+
integrator.integral_bounds = [(r_low, r_high), (1.0e-6, np.pi)]
|
|
45
|
+
|
|
46
|
+
def integrand_numcosmo(int_args, extra_args):
|
|
47
|
+
r_local = int_args[:, 0]
|
|
48
|
+
theta = int_args[:, 1]
|
|
49
|
+
return integrand(theta, r_local, *extra_args)
|
|
50
|
+
|
|
51
|
+
res = integrator.integrate(integrand_numcosmo)
|
|
52
|
+
results.append(res)
|
|
53
|
+
|
|
54
|
+
results = np.array(results)
|
|
55
|
+
mean_surface_density = np.cumsum(results) * norm * 2 / np.pi / r_proj**2
|
|
56
|
+
if not np.iterable(r_proj):
|
|
57
|
+
return res[0] * norm * 2 / np.pi / r_proj**2
|
|
58
|
+
return mean_surface_density
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _eval_2halo_term_generic_orig( # pragma: no cover
|
|
62
|
+
self,
|
|
63
|
+
sph_harm_ord,
|
|
64
|
+
r_proj,
|
|
65
|
+
z_cl,
|
|
66
|
+
halobias=1.0,
|
|
67
|
+
logkbounds=(-5, 5),
|
|
68
|
+
ksteps=1000,
|
|
69
|
+
loglbounds=(0, 6),
|
|
70
|
+
lsteps=500,
|
|
71
|
+
):
|
|
72
|
+
"""eval excess surface density from the 2-halo term (original)"""
|
|
73
|
+
# pylint: disable=protected-access
|
|
74
|
+
da = self.cosmo.eval_da(z_cl)
|
|
75
|
+
rho_m = self.cosmo._get_rho_m(z_cl)
|
|
76
|
+
|
|
77
|
+
k_values = np.logspace(logkbounds[0], logkbounds[1], ksteps)
|
|
78
|
+
pk_values = self.cosmo._eval_linear_matter_powerspectrum(k_values, z_cl)
|
|
79
|
+
interp_pk = splrep(k_values, pk_values)
|
|
80
|
+
theta = r_proj / da
|
|
81
|
+
|
|
82
|
+
# calculate integral, units [Mpc]**-3
|
|
83
|
+
def __integrand__(l_value, theta):
|
|
84
|
+
k_value = l_value / ((1 + z_cl) * da)
|
|
85
|
+
return l_value * jv(sph_harm_ord, l_value * theta) * splev(k_value, interp_pk)
|
|
86
|
+
|
|
87
|
+
l_values = np.logspace(loglbounds[0], loglbounds[1], lsteps)
|
|
88
|
+
kernel = np.array([simpson(__integrand__(l_values, t), x=l_values) for t in theta])
|
|
89
|
+
return halobias * kernel * rho_m / (2 * np.pi * (1 + z_cl) ** 3 * da**2)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _eval_2halo_term_generic_new( # pragma: no cover
|
|
93
|
+
####################################################################################
|
|
94
|
+
# NOTE: This function is just a small optimization of the one implemented on CLMM #
|
|
95
|
+
# here just to benchmark the difference due the restructuration of the integration #
|
|
96
|
+
####################################################################################
|
|
97
|
+
self,
|
|
98
|
+
sph_harm_ord,
|
|
99
|
+
r_proj,
|
|
100
|
+
z_cl,
|
|
101
|
+
halobias=1.0,
|
|
102
|
+
logkbounds=(-5, 5),
|
|
103
|
+
ksteps=1000,
|
|
104
|
+
loglbounds=(0, 6),
|
|
105
|
+
lsteps=500,
|
|
106
|
+
):
|
|
107
|
+
"""eval excess surface density from the 2-halo term (updated integration)"""
|
|
108
|
+
# pylint: disable=protected-access
|
|
109
|
+
da = self.cosmo.eval_da(z_cl)
|
|
110
|
+
rho_m = self.cosmo._get_rho_m(z_cl)
|
|
111
|
+
theta = r_proj / da
|
|
112
|
+
|
|
113
|
+
# interp pk
|
|
114
|
+
_k_values = np.logspace(logkbounds[0], logkbounds[1], ksteps)
|
|
115
|
+
interp_pk = splrep(
|
|
116
|
+
_k_values, self.cosmo._eval_linear_matter_powerspectrum(_k_values, z_cl)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# integrate
|
|
120
|
+
l_values = np.logspace(loglbounds[0], loglbounds[1], lsteps)
|
|
121
|
+
kernel = simpson(
|
|
122
|
+
(
|
|
123
|
+
l_values
|
|
124
|
+
* jv(sph_harm_ord, l_values * theta)
|
|
125
|
+
* splev(l_values / ((1 + z_cl) * da), interp_pk)
|
|
126
|
+
),
|
|
127
|
+
x=l_values,
|
|
128
|
+
)
|
|
129
|
+
return [halobias * kernel * rho_m / (2 * np.pi * (1 + z_cl) ** 3 * da**2)]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _eval_2halo_term_generic_vec( # pragma: no cover
|
|
133
|
+
self,
|
|
134
|
+
sph_harm_ord,
|
|
135
|
+
r_proj,
|
|
136
|
+
z_cl,
|
|
137
|
+
halobias=1.0,
|
|
138
|
+
logkbounds=(-5, 5),
|
|
139
|
+
ksteps=1000,
|
|
140
|
+
loglbounds=(0, 6),
|
|
141
|
+
lsteps=500,
|
|
142
|
+
):
|
|
143
|
+
"""eval excess surface density from the 2-halo term (vectorized)"""
|
|
144
|
+
z_cl = np.atleast_1d(z_cl)
|
|
145
|
+
# pylint: disable=protected-access
|
|
146
|
+
da = self.cosmo.eval_da(z_cl)
|
|
147
|
+
rho_m = self.cosmo._get_rho_m(z_cl)
|
|
148
|
+
|
|
149
|
+
# (n_z, n_r)
|
|
150
|
+
theta = (r_proj / da[None, :]).T
|
|
151
|
+
|
|
152
|
+
# calculate integral, units [Mpc]**-3
|
|
153
|
+
l_values = np.logspace(loglbounds[0], loglbounds[1], lsteps)
|
|
154
|
+
|
|
155
|
+
# (n_l, n_z)
|
|
156
|
+
k_values = l_values[:, np.newaxis] / ((1 + z_cl) * da)
|
|
157
|
+
pk_values = np.zeros(k_values.shape)
|
|
158
|
+
for i in range(z_cl.size):
|
|
159
|
+
pk_values[:, i] = self.cosmo._eval_linear_matter_powerspectrum(
|
|
160
|
+
k_values[:, i], z_cl[i]
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# (n_l, n_z, n_r)
|
|
164
|
+
jv_values = jv(sph_harm_ord, l_values[:, None, None] * theta[None, :, :])
|
|
165
|
+
kernel = l_values[:, None, None] * pk_values[:, :, None] * jv_values
|
|
166
|
+
|
|
167
|
+
# (n_z, n_r)
|
|
168
|
+
integ = simpson(kernel, x=l_values, axis=0)
|
|
169
|
+
|
|
170
|
+
# (n_z, n_r)
|
|
171
|
+
out = halobias * integ * (rho_m / (2 * np.pi * (1 + z_cl) ** 3 * da**2))[:, None]
|
|
172
|
+
|
|
173
|
+
# (n_r, n_z)
|
|
174
|
+
return out.T
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""The module responsible for building the cluster abundance calculation.
|
|
2
|
+
|
|
3
|
+
The galaxy cluster abundance integral is a combination of both theoretical
|
|
4
|
+
and phenomenological predictions. This module contains the classes and
|
|
5
|
+
functions that produce those predictions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import numpy.typing as npt
|
|
10
|
+
import pyccl
|
|
11
|
+
import pyccl.background as bkg
|
|
12
|
+
from pyccl.cosmology import Cosmology
|
|
13
|
+
|
|
14
|
+
from .parameters import Parameters
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ClusterAbundance:
|
|
18
|
+
"""The class that calculates the predicted number counts of galaxy clusters.
|
|
19
|
+
|
|
20
|
+
The abundance is a function of a specific cosmology, a mass and redshift range,
|
|
21
|
+
an area on the sky, a halo mass function, as well as multiple kernels, where
|
|
22
|
+
each kernel represents a different distribution involved in the final cluster
|
|
23
|
+
abundance integrand.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def cosmo(self) -> Cosmology | None:
|
|
28
|
+
"""The cosmology used to predict the cluster number count."""
|
|
29
|
+
return self._cosmo
|
|
30
|
+
|
|
31
|
+
@cosmo.setter
|
|
32
|
+
def cosmo(self, cosmo: Cosmology) -> None:
|
|
33
|
+
"""Update the cluster abundance calculation with a new cosmology."""
|
|
34
|
+
self._cosmo = cosmo
|
|
35
|
+
self._hmf_cache: dict[tuple[float, float], float] = {}
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
cosmo: Cosmology,
|
|
40
|
+
halo_mass_function: pyccl.halos.MassFunc,
|
|
41
|
+
) -> None:
|
|
42
|
+
super().__init__()
|
|
43
|
+
self.cosmo = cosmo
|
|
44
|
+
self.halo_mass_function = halo_mass_function
|
|
45
|
+
self.parameters = Parameters({})
|
|
46
|
+
|
|
47
|
+
def comoving_volume(
|
|
48
|
+
self, z: npt.NDArray[np.float64], sky_area: float = 0
|
|
49
|
+
) -> npt.NDArray[np.float64]:
|
|
50
|
+
"""The differential comoving volume given area sky_area at redshift z.
|
|
51
|
+
|
|
52
|
+
:param sky_area: The area of the survey on the sky in square degrees.
|
|
53
|
+
"""
|
|
54
|
+
assert self.cosmo is not None
|
|
55
|
+
scale_factor = 1.0 / (1.0 + z)
|
|
56
|
+
angular_diam_dist = bkg.angular_diameter_distance(self.cosmo, scale_factor)
|
|
57
|
+
h_over_h0 = bkg.h_over_h0(self.cosmo, scale_factor)
|
|
58
|
+
|
|
59
|
+
dV = (
|
|
60
|
+
pyccl.physical_constants.CLIGHT_HMPC
|
|
61
|
+
* (angular_diam_dist**2)
|
|
62
|
+
* ((1.0 + z) ** 2)
|
|
63
|
+
/ (self.cosmo["h"] * h_over_h0)
|
|
64
|
+
)
|
|
65
|
+
assert isinstance(dV, np.ndarray)
|
|
66
|
+
|
|
67
|
+
sky_area_rad = sky_area * (np.pi / 180.0) ** 2
|
|
68
|
+
|
|
69
|
+
return np.array(dV * sky_area_rad, dtype=np.float64)
|
|
70
|
+
|
|
71
|
+
def mass_function(
|
|
72
|
+
self,
|
|
73
|
+
log_mass: npt.NDArray[np.float64],
|
|
74
|
+
z: npt.NDArray[np.float64],
|
|
75
|
+
) -> npt.NDArray[np.float64]:
|
|
76
|
+
"""The mass function at z and mass."""
|
|
77
|
+
scale_factor = 1.0 / (1.0 + z)
|
|
78
|
+
return_vals = []
|
|
79
|
+
|
|
80
|
+
for logm, a in zip(log_mass.astype(float), scale_factor.astype(float)):
|
|
81
|
+
val = self._hmf_cache.get((logm, a))
|
|
82
|
+
if val is None:
|
|
83
|
+
val = self.halo_mass_function(self.cosmo, 10**logm, a)
|
|
84
|
+
self._hmf_cache[(logm, a)] = val
|
|
85
|
+
return_vals.append(val)
|
|
86
|
+
|
|
87
|
+
return np.asarray(return_vals, dtype=np.float64)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""The cluster completeness module.
|
|
2
|
+
|
|
3
|
+
This module holds the classes that define the kernels that can be included
|
|
4
|
+
in the cluster abundance integrand.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import numpy.typing as npt
|
|
9
|
+
|
|
10
|
+
from .parameters import Parameters
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Completeness:
|
|
14
|
+
"""The completeness kernel for the numcosmo simulated survey.
|
|
15
|
+
|
|
16
|
+
This kernel will affect the integrand by accounting for the incompleteness
|
|
17
|
+
of a cluster selection.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def distribution(
|
|
24
|
+
self,
|
|
25
|
+
log_mass: npt.NDArray[np.float64],
|
|
26
|
+
z: npt.NDArray[np.float64],
|
|
27
|
+
) -> npt.NDArray[np.float64]:
|
|
28
|
+
"""Evaluates and returns the completeness contribution to the integrand."""
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
REDMAPPER_DEFAULT_PARAMETERS = {
|
|
33
|
+
"a_n": 0.38,
|
|
34
|
+
"b_n": 1.2634,
|
|
35
|
+
"a_logm_piv": 13.31,
|
|
36
|
+
"b_logm_piv": 0.2025,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CompletenessAguena16(Completeness):
|
|
41
|
+
"""The completeness kernel for the numcosmo simulated survey.
|
|
42
|
+
|
|
43
|
+
This kernel will affect the integrand by accounting for the incompleteness
|
|
44
|
+
of a cluster selection.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
):
|
|
50
|
+
self.parameters = Parameters({**REDMAPPER_DEFAULT_PARAMETERS})
|
|
51
|
+
|
|
52
|
+
def _mpiv(self, z: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
|
|
53
|
+
log_mpiv = self.parameters["a_logm_piv"] + self.parameters["b_logm_piv"] * (
|
|
54
|
+
1.0 + z
|
|
55
|
+
)
|
|
56
|
+
mpiv = 10.0**log_mpiv
|
|
57
|
+
return mpiv.astype(np.float64)
|
|
58
|
+
|
|
59
|
+
def _nc(self, z: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
|
|
60
|
+
nc = self.parameters["a_n"] + self.parameters["b_n"] * (1.0 + z)
|
|
61
|
+
assert isinstance(nc, np.ndarray)
|
|
62
|
+
return nc
|
|
63
|
+
|
|
64
|
+
def distribution(
|
|
65
|
+
self,
|
|
66
|
+
log_mass: npt.NDArray[np.float64],
|
|
67
|
+
z: npt.NDArray[np.float64],
|
|
68
|
+
) -> npt.NDArray[np.float64]:
|
|
69
|
+
"""Evaluates and returns the completeness contribution to the integrand."""
|
|
70
|
+
|
|
71
|
+
mass_norm_pow = (10.0**log_mass / self._mpiv(z)) ** self._nc(z)
|
|
72
|
+
|
|
73
|
+
completeness = mass_norm_pow / (mass_norm_pow + 1.0)
|
|
74
|
+
assert isinstance(completeness, np.ndarray)
|
|
75
|
+
return completeness
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""The cluster kernel module.
|
|
2
|
+
|
|
3
|
+
This module holds the classes that define the kernels that can be included
|
|
4
|
+
in the cluster abundance integrand.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import numpy.typing as npt
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TrueMass:
|
|
12
|
+
"""The true mass kernel.
|
|
13
|
+
|
|
14
|
+
Assuming we measure the true mass, this will always be 1.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def distribution(self) -> npt.NDArray[np.float64]:
|
|
18
|
+
"""Evaluates and returns the mass distribution contribution to the integrand.
|
|
19
|
+
|
|
20
|
+
We have set this to 1.0 (i.e. it does not affect the mass distribution)
|
|
21
|
+
"""
|
|
22
|
+
return np.atleast_1d(1.0)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SpectroscopicRedshift:
|
|
26
|
+
"""The spec-z kernel.
|
|
27
|
+
|
|
28
|
+
Assuming the spectroscopic redshift has no uncertainties, this is akin to
|
|
29
|
+
multiplying by 1.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def distribution(self) -> npt.NDArray[np.float64]:
|
|
33
|
+
"""Evaluates and returns the z distribution contribution to the integrand.
|
|
34
|
+
|
|
35
|
+
We have set this to 1.0 (i.e. it does not affect the redshift distribution)
|
|
36
|
+
"""
|
|
37
|
+
return np.atleast_1d(1.0)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""The mass richness kernel module.
|
|
2
|
+
|
|
3
|
+
This module holds the classes that define the mass richness relations
|
|
4
|
+
that can be included in the cluster abundance integrand. These are
|
|
5
|
+
implementations of Kernels.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .murata import MurataBinned, MurataUnbinned
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Gaussian class for mass richness distributiosn."""
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import numpy.typing as npt
|
|
7
|
+
from scipy import special
|
|
8
|
+
|
|
9
|
+
from crow.integrator.numcosmo_integrator import NumCosmoIntegrator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MassRichnessGaussian:
|
|
13
|
+
"""The representation of mass richness relations that are of a gaussian form."""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def get_ln_mass_proxy_mean(
|
|
17
|
+
self,
|
|
18
|
+
log_mass: npt.NDArray[np.float64],
|
|
19
|
+
z: npt.NDArray[np.float64],
|
|
20
|
+
) -> npt.NDArray[np.float64]:
|
|
21
|
+
"""Return observed quantity corrected by redshift and mass."""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def get_ln_mass_proxy_sigma(
|
|
25
|
+
self,
|
|
26
|
+
log_mass: npt.NDArray[np.float64],
|
|
27
|
+
z: npt.NDArray[np.float64],
|
|
28
|
+
) -> npt.NDArray[np.float64]:
|
|
29
|
+
"""Return observed scatter corrected by redshift and mass."""
|
|
30
|
+
|
|
31
|
+
def integrated_gaussian(
|
|
32
|
+
self,
|
|
33
|
+
log_mass: npt.NDArray[np.float64],
|
|
34
|
+
z: npt.NDArray[np.float64],
|
|
35
|
+
log_mass_proxy_limits: tuple[float, float],
|
|
36
|
+
) -> npt.NDArray[np.float64]:
|
|
37
|
+
ln_mass_proxy_mean = self.get_ln_mass_proxy_mean(log_mass, z)
|
|
38
|
+
ln_mass_proxy_sigma = self.get_ln_mass_proxy_sigma(log_mass, z)
|
|
39
|
+
|
|
40
|
+
x_min = (ln_mass_proxy_mean - log_mass_proxy_limits[0] * np.log(10.0)) / (
|
|
41
|
+
np.sqrt(2.0) * ln_mass_proxy_sigma
|
|
42
|
+
)
|
|
43
|
+
x_max = (ln_mass_proxy_mean - log_mass_proxy_limits[1] * np.log(10.0)) / (
|
|
44
|
+
np.sqrt(2.0) * ln_mass_proxy_sigma
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return_vals = np.empty_like(x_min)
|
|
48
|
+
mask1 = (x_max > 3.0) | (x_min < -3.0)
|
|
49
|
+
mask2 = ~mask1
|
|
50
|
+
|
|
51
|
+
# pylint: disable=no-member
|
|
52
|
+
return_vals[mask1] = (
|
|
53
|
+
-(special.erfc(x_min[mask1]) - special.erfc(x_max[mask1])) / 2.0
|
|
54
|
+
)
|
|
55
|
+
# pylint: disable=no-member
|
|
56
|
+
return_vals[mask2] = (
|
|
57
|
+
special.erf(x_min[mask2]) - special.erf(x_max[mask2])
|
|
58
|
+
) / 2.0
|
|
59
|
+
assert isinstance(return_vals, np.ndarray)
|
|
60
|
+
return return_vals
|
|
61
|
+
|
|
62
|
+
def gaussian_kernel(
|
|
63
|
+
self,
|
|
64
|
+
log_mass: npt.NDArray[np.float64],
|
|
65
|
+
z: npt.NDArray[np.float64],
|
|
66
|
+
log_mass_proxy: npt.NDArray[np.float64],
|
|
67
|
+
) -> npt.NDArray[np.float64]:
|
|
68
|
+
ln_mass_proxy_mean = self.get_ln_mass_proxy_mean(log_mass, z)
|
|
69
|
+
ln_mass_proxy_sigma = self.get_ln_mass_proxy_sigma(log_mass, z)
|
|
70
|
+
|
|
71
|
+
normalization = 1 / np.sqrt(2 * np.pi * ln_mass_proxy_sigma**2)
|
|
72
|
+
result = normalization * np.exp(
|
|
73
|
+
-0.5
|
|
74
|
+
* ((log_mass_proxy * np.log(10) - ln_mass_proxy_mean) / ln_mass_proxy_sigma)
|
|
75
|
+
** 2
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
assert isinstance(result, np.ndarray)
|
|
79
|
+
return result
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""The Murata et al. 19 mass richness kernel models."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
|
|
6
|
+
from ..parameters import Parameters
|
|
7
|
+
from .gaussian_protocol import MassRichnessGaussian
|
|
8
|
+
|
|
9
|
+
MURATA_DEFAULT_PARAMETERS = {
|
|
10
|
+
"mu0": 3.0,
|
|
11
|
+
"mu1": 0.8,
|
|
12
|
+
"mu2": -0.3,
|
|
13
|
+
"sigma0": 0.3,
|
|
14
|
+
"sigma1": 0.0,
|
|
15
|
+
"sigma2": 0.0,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MurataModel:
|
|
20
|
+
"""The mass richness modeling defined in Murata 19."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
pivot_log_mass: float,
|
|
25
|
+
pivot_redshift: float,
|
|
26
|
+
):
|
|
27
|
+
super().__init__()
|
|
28
|
+
self.pivot_redshift = pivot_redshift
|
|
29
|
+
self.pivot_ln_mass = pivot_log_mass * np.log(10.0) # ln(M)
|
|
30
|
+
self.log1p_pivot_redshift = np.log1p(self.pivot_redshift)
|
|
31
|
+
|
|
32
|
+
self.parameters = Parameters({**MURATA_DEFAULT_PARAMETERS})
|
|
33
|
+
|
|
34
|
+
# Verify this gets called last or first
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def observed_value(
|
|
38
|
+
p: tuple[float, float, float],
|
|
39
|
+
log_mass: npt.NDArray[np.float64],
|
|
40
|
+
z: npt.NDArray[np.float64],
|
|
41
|
+
pivot_ln_mass: float,
|
|
42
|
+
log1p_pivot_redshift: float,
|
|
43
|
+
) -> npt.NDArray[np.float64]:
|
|
44
|
+
"""Return observed quantity corrected by redshift and mass."""
|
|
45
|
+
ln_mass = log_mass * np.log(10)
|
|
46
|
+
delta_ln_mass = ln_mass - pivot_ln_mass
|
|
47
|
+
delta_z = np.log1p(z) - log1p_pivot_redshift
|
|
48
|
+
|
|
49
|
+
result = p[0] + p[1] * delta_ln_mass + p[2] * delta_z
|
|
50
|
+
assert isinstance(result, np.ndarray)
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
def get_ln_mass_proxy_mean(
|
|
54
|
+
self,
|
|
55
|
+
log_mass: npt.NDArray[np.float64],
|
|
56
|
+
z: npt.NDArray[np.float64],
|
|
57
|
+
) -> npt.NDArray[np.float64]:
|
|
58
|
+
"""Return observed quantity corrected by redshift and mass."""
|
|
59
|
+
return MurataModel.observed_value(
|
|
60
|
+
(self.parameters["mu0"], self.parameters["mu1"], self.parameters["mu2"]),
|
|
61
|
+
log_mass,
|
|
62
|
+
z,
|
|
63
|
+
self.pivot_ln_mass,
|
|
64
|
+
self.log1p_pivot_redshift,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def get_ln_mass_proxy_sigma(
|
|
68
|
+
self,
|
|
69
|
+
log_mass: npt.NDArray[np.float64],
|
|
70
|
+
z: npt.NDArray[np.float64],
|
|
71
|
+
) -> npt.NDArray[np.float64]:
|
|
72
|
+
"""Return observed scatter corrected by redshift and mass."""
|
|
73
|
+
return MurataModel.observed_value(
|
|
74
|
+
(
|
|
75
|
+
self.parameters["sigma0"],
|
|
76
|
+
self.parameters["sigma1"],
|
|
77
|
+
self.parameters["sigma2"],
|
|
78
|
+
),
|
|
79
|
+
log_mass,
|
|
80
|
+
z,
|
|
81
|
+
self.pivot_ln_mass,
|
|
82
|
+
self.log1p_pivot_redshift,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class MurataBinned(MurataModel, MassRichnessGaussian):
|
|
87
|
+
"""The mass richness relation defined in Murata 19 for a binned data vector."""
|
|
88
|
+
|
|
89
|
+
def distribution(
|
|
90
|
+
self,
|
|
91
|
+
log_mass: npt.NDArray[np.float64],
|
|
92
|
+
z: npt.NDArray[np.float64],
|
|
93
|
+
log_mass_proxy_limits: tuple[float, float],
|
|
94
|
+
) -> npt.NDArray[np.float64]:
|
|
95
|
+
"""Evaluates and returns the mass-richness contribution to the integrand."""
|
|
96
|
+
return self.integrated_gaussian(log_mass, z, log_mass_proxy_limits)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class MurataUnbinned(MurataModel, MassRichnessGaussian):
|
|
100
|
+
"""The mass richness relation defined in Murata 19 for a unbinned data vector."""
|
|
101
|
+
|
|
102
|
+
def distribution(
|
|
103
|
+
self,
|
|
104
|
+
log_mass: npt.NDArray[np.float64],
|
|
105
|
+
z: npt.NDArray[np.float64],
|
|
106
|
+
log_mass_proxy: npt.NDArray[np.float64],
|
|
107
|
+
) -> npt.NDArray[np.float64]:
|
|
108
|
+
"""Evaluates and returns the mass-richness contribution to the integrand."""
|
|
109
|
+
return self.gaussian_kernel(log_mass, z, log_mass_proxy)
|