Skip to content

Commit bc6ec88

Browse files
authored
ENH: Halos from COLOSSUS joint mass-redshift distributions (#452)
1 parent 1aa09dc commit bc6ec88

File tree

4 files changed

+225
-3
lines changed

4 files changed

+225
-3
lines changed

docs/halos/index.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
Dark Matter Halos (`skypy.halos`)
33
*********************************
44

5-
.. automodule:: skypy.halos
6-
75
You can reproduce figure 2 in Sheth and Tormen 1999
86
and plot the collapse functions for different halo models. For this, you can use
97
a python script, for example.
@@ -91,6 +89,12 @@ file and run the pipeline, for example.
9189
plt.show()
9290

9391

92+
Halos (`skypy.halos`)
93+
=====================
94+
95+
.. automodule:: skypy.halos
96+
97+
9498
Abundance Matching (`skypy.halos.abundance_matching`)
9599
=====================================================
96100

skypy/halos/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1-
"""
1+
"""Halos module.
2+
23
This module contains methods that model the properties of dark matter halo
34
populations.
5+
6+
Models
7+
======
8+
.. autosummary::
9+
:nosignatures:
10+
:toctree: ../api/
11+
12+
colossus_mf
413
"""
514

15+
__all__ = [
16+
'colossus_mf',
17+
]
18+
619
from . import abundance_matching # noqa F401,F403
720
from . import mass # noqa F401,F403
821
from . import quenching # noqa F401,F403
22+
from ._colossus import colossus_mf # noqa F401,F403

skypy/halos/_colossus.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
66
"""
77

8+
from astropy.cosmology import z_at_value
9+
from astropy import units
810
import numpy as np
911
from scipy import integrate
12+
from skypy.galaxies.redshift import redshifts_from_comoving_density
1013

1114
__all__ = [
1215
'colossus_mass_sampler',
@@ -72,3 +75,131 @@ def colossus_mass_sampler(redshift, model, mdef, m_min, m_max,
7275
n_uniform = np.random.uniform(size=size)
7376
masssample = np.interp(n_uniform, CDF, m)
7477
return masssample
78+
79+
80+
@units.quantity_input(sky_area=units.sr)
81+
def colossus_mf_redshift(redshift, model, mdef, m_min, m_max, sky_area,
82+
cosmology, sigma8, ns, resolution=1000, noise=True):
83+
r'''Sample redshifts from a COLOSSUS halo mass function.
84+
85+
Sample the redshifts of dark matter halos following a mass function
86+
implemented in COLOSSUS [1]_ within given mass and redshift ranges and for
87+
a given area of the sky.
88+
89+
Parameters
90+
----------
91+
redshift : array_like
92+
Input redshift grid on which the mass function is evaluated. Halos are
93+
sampled over this redshift range.
94+
model : string
95+
Mass function model which is available in colossus.
96+
mdef : str
97+
Halo mass definition for spherical overdensities used by colossus.
98+
m_min, m_max : float
99+
Lower and upper bounds for the halo mass in units of Solar mass, Msun.
100+
sky_area : `~astropy.units.Quantity`
101+
Sky area over which halos are sampled. Must be in units of solid angle.
102+
cosmology : Cosmology
103+
Cosmology object to convert comoving density.
104+
sigma8 : float
105+
Cosmology parameter, amplitude of the (linear) power spectrum on the
106+
scale of 8 Mpc/h.
107+
ns : float
108+
Cosmology parameter, spectral index of scalar perturbation power
109+
spectrum.
110+
noise : bool, optional
111+
Poisson-sample the number of halos. Default is `True`.
112+
113+
Returns
114+
-------
115+
redshifts : array_like
116+
Redshifts of the halo sample.
117+
118+
References
119+
----------
120+
.. [1] Diemer B., 2018, ApJS, 239, 35
121+
122+
'''
123+
from colossus.cosmology.cosmology import fromAstropy
124+
from colossus.lss.mass_function import massFunction
125+
126+
# Set the cosmology in COLOSSUS
127+
fromAstropy(cosmology, sigma8, ns)
128+
129+
# Integrate the mass function to get the number density of halos at each redshift
130+
def dndlnM(lnm, z):
131+
return massFunction(np.exp(lnm), z, 'M', 'dndlnM', mdef, model)
132+
lnmmin = np.log(m_min*cosmology.h)
133+
lnmmax = np.log(m_max*cosmology.h)
134+
density = [integrate.quad(dndlnM, lnmmin, lnmmax, args=(z))[0] for z in redshift]
135+
density = np.array(density) * np.power(cosmology.h, 3)
136+
137+
# Sample halo redshifts and assign to bins
138+
return redshifts_from_comoving_density(redshift, density, sky_area, cosmology, noise)
139+
140+
141+
@units.quantity_input(sky_area=units.sr)
142+
def colossus_mf(redshift, model, mdef, m_min, m_max, sky_area, cosmology,
143+
sigma8, ns, resolution=1000, noise=True):
144+
r'''Sample halo redshifts and masses from a COLOSSUS mass function.
145+
146+
Sample the redshifts and masses of dark matter halos following a mass
147+
function implemented in COLOSSUS [1]_ within given mass and redshift ranges
148+
and for a given area of the sky.
149+
150+
Parameters
151+
----------
152+
redshift : array_like
153+
Defines the edges of a set of redshift bins for which the mass function
154+
is evaluated. Must be a monotonically-increasing one-dimensional array
155+
of values. Halo redshifts are sampled between the minimum and maximum
156+
values in this array.
157+
model : string
158+
Mass function model which is available in colossus.
159+
mdef : str
160+
Halo mass definition for spherical overdensities used by colossus.
161+
m_min, m_max : float
162+
Lower and upper bounds for the halo mass in units of Solar mass, Msun.
163+
sky_area : `~astropy.units.Quantity`
164+
Sky area over which halos are sampled. Must be in units of solid angle.
165+
cosmology : Cosmology
166+
Cosmology object to calculate comoving densities.
167+
sigma8 : float
168+
Cosmology parameter, amplitude of the (linear) power spectrum on the
169+
scale of 8 Mpc/h.
170+
ns : float
171+
Cosmology parameter, spectral index of scalar perturbation power
172+
spectrum.
173+
noise : bool, optional
174+
Poisson-sample the number of halos. Default is `True`.
175+
176+
Returns
177+
-------
178+
redshift, mass : tuple of array_like
179+
Redshifts and masses of the halo sample.
180+
181+
References
182+
----------
183+
.. [1] Diemer B., 2018, ApJS, 239, 35
184+
185+
'''
186+
187+
# Sample halo redshifts and assign to bins
188+
z = colossus_mf_redshift(redshift, model, mdef, m_min, m_max, sky_area,
189+
cosmology, sigma8, ns, resolution, noise)
190+
redshift_bin = np.digitize(z, redshift)
191+
192+
# Calculate the redshift at the centre of each bin
193+
comoving_distance = cosmology.comoving_distance(redshift)
194+
d_mid = 0.5 * (comoving_distance[:-1] + comoving_distance[1:])
195+
z_mid = [z_at_value(cosmology.comoving_distance, d) for d in d_mid]
196+
197+
# Sample halo masses in each redshift bin
198+
m = np.empty_like(z)
199+
for i, zm in enumerate(z_mid):
200+
mask = redshift_bin == i + 1
201+
size = np.count_nonzero(mask)
202+
m[mask] = colossus_mass_sampler(zm, model, mdef, m_min, m_max,
203+
cosmology, sigma8, ns, size, resolution)
204+
205+
return z, m

skypy/halos/tests/test_colossus.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,76 @@ def test_colossus_mass_sampler():
3232
D, p = kstest(sample, lambda t: np.interp(t, m, CDF))
3333

3434
assert p > 0.01, 'D = {}, p = {}'.format(D, p)
35+
36+
37+
@pytest.mark.skipif(not HAS_COLOSSUS, reason='test requires colossus')
38+
@pytest.mark.flaky
39+
def test_colossus_mf_redshift():
40+
41+
from skypy.halos._colossus import colossus_mf_redshift
42+
from astropy.cosmology import Planck15
43+
from astropy import units
44+
from scipy import integrate
45+
from colossus.lss.mass_function import massFunction
46+
47+
# Parameters
48+
redshift = np.linspace(0., 2., 100)
49+
model, mdef = 'despali16', '500c'
50+
m_min, m_max = 1e+12, 1e+16
51+
sky_area = 1.0 * units.deg**2
52+
cosmology = Planck15
53+
sigma8, ns = 0.82, 0.96
54+
55+
# Sample redshifts
56+
z_halo = colossus_mf_redshift(redshift, model, mdef, m_min, m_max, sky_area,
57+
cosmology, sigma8, ns, noise=False)
58+
59+
# Integrate the mass function to get the number density of halos at each redshift
60+
def dndlnM(lnm, z):
61+
return massFunction(np.exp(lnm), z, 'M', 'dndlnM', mdef, model)
62+
lnmmin = np.log(m_min*cosmology.h)
63+
lnmmax = np.log(m_max*cosmology.h)
64+
density = [integrate.quad(dndlnM, lnmmin, lnmmax, args=(z))[0] for z in redshift]
65+
density = np.array(density) * np.power(cosmology.h, 3)
66+
67+
# Integrate total number of halos for the given area of sky
68+
density *= (sky_area * cosmology.differential_comoving_volume(redshift)).to_value('Mpc3')
69+
n_halo = np.trapz(density, redshift, axis=-1)
70+
71+
# make sure noise-free sample has right size
72+
assert np.isclose(len(z_halo), n_halo, atol=1.0)
73+
74+
# Halo redshift CDF
75+
cdf = density # same memory
76+
np.cumsum((density[1:]+density[:-1])/2*np.diff(redshift), out=cdf[1:])
77+
cdf[0] = 0
78+
cdf /= cdf[-1]
79+
80+
# Check distribution of sampled halo redshifts
81+
D, p = kstest(z_halo, lambda z_: np.interp(z_, redshift, cdf))
82+
assert p > 0.01, 'D = {}, p = {}'.format(D, p)
83+
84+
85+
@pytest.mark.skipif(not HAS_COLOSSUS, reason='test requires colossus')
86+
def test_colossus_mf():
87+
88+
from skypy.halos import colossus_mf
89+
from astropy.cosmology import Planck15
90+
from astropy import units
91+
92+
# redshift and mass distributions are tested separately
93+
# only test that output is consistent here
94+
95+
z = np.linspace(0., 1., 100)
96+
model, mdef = 'despali16', '500c'
97+
m_min, m_max = 1e+12, 1e+16
98+
sky_area = 1.0 * units.deg**2
99+
cosmo = Planck15
100+
sigma8, ns = 0.82, 0.96
101+
z_halo, m_halo = colossus_mf(z, model, mdef, m_min, m_max, sky_area, cosmo, sigma8, ns)
102+
103+
assert len(z_halo) == len(m_halo)
104+
assert np.all(z_halo >= np.min(z))
105+
assert np.all(z_halo <= np.max(z))
106+
assert np.all(m_halo >= m_min)
107+
assert np.all(m_halo <= m_max)

0 commit comments

Comments
 (0)