Halos
Add dark matter halos to a density field, using Poisson statistics.
HaloDistribution
Source code in fastbox/halos.py
class HaloDistribution(object):
def __init__(self, box, mass_range, mass_bins):
"""
An object to manage the placing of halos on top of a realisation of a
density field in a box.
Parameters:
box (CosmoBox):
Object containing a simulation box.
mass_range (tuple):
Range of halo masses, in Msun.
mass_bins (int):
Number of mass bins.
"""
self.box = box
self.Mmin, self.Mmax = mass_range
self.mass_bins = mass_bins
def construct_bins(self, z):
"""
Construct a binned halo mass function and bias function. This function
sets the ``self.dndlog10M`` and ``self.bias`` arrays.
Parameters:
z (float): Redshift at which to construct the mass function and bias.
"""
a = 1./(1.+z)
# Define mass bins
Mh_edges = np.logspace(np.log10(self.Mmin),
np.log10(self.Mmax),
int(self.mass_bins)+1)
Mh_centres = 0.5 * (Mh_edges[1:] + Mh_edges[:-1])
# Get mass function and bias at mass bin centres
self.dndlog10M = ccl.massfunction.massfunc(self.box.cosmo, Mh_centres,
a, overdensity=200)
self.bias = ccl.halo_bias(cosmo, Mh_centres, a, overdensity=200)
def halo_count_field(self, delta_x, nbar, bias, lognormal=False):
"""
Generate a cube of halo counts, with a Poisson-generated realisation of
the number of halos per voxel.
The expected number of halos per voxel is given by:
N_halo(x) = vol_voxel * nbar(x) * (1 + bias(x) * delta_x(x))
Parameters:
delta_x (array_like):
3D box of density contrast values.
nbar (float or array_like):
Mean number density (per unit comoving volume, Mpc^-3) of a chosen
(sub-)population of halos.
This can be specified as a constant across the whole box, a 1D
array which will be interpreted as a function of the z/LOS
direction, or a 3D array with the value of nbar per voxel.
bias (float or array_like):
Mean bias of the population of halos.
This can be specified as a constant across the whole box, a 1D
array which will be interpreted as a function of the z/LOS
direction, or a 3D array with the value of nbar per voxel.
lognormal (bool, optional):
If True, apply a log-normal transform to the biased density field
before realising the halo counts. This should normally only be
applied to the *linear* density contrast.
Returns:
N_halo (array_like):
3D box of halo counts per voxel.
"""
# Build bias and number density functions with the right shape
nbar = np.atleast_1d(nbar)
bias = np.atleast_1d(bias)
# Check for 1D shape (assumed to be either constant or fn. of redshift)
if len(nbar.shape) == 1:
nbar = nbar[np.newaxis,np.newaxis,:]
if len(bias.shape) == 1:
bias = bias[np.newaxis,np.newaxis,:]
# Comoving volume of each voxel
voxel_vol = self.box.Lx * self.box.Ly * self.box.Lz / self.box.N**3.
# Calculate halo overdensity field
delta_h = bias * delta_x
if lognormal:
delta_h = np.exp(delta_h)
delta_h /= np.mean(delta_h)
delta_h -= 1.
# Expected number of halos in each voxel
Nbar = voxel_vol * nbar * (1. + delta_h)
if not lognormal:
Nbar[np.where(Nbar < 0.)] = 0.
# Realise halo count in each voxel
Nhalo = np.random.poisson(lam=np.nan_to_num(Nbar))
return Nhalo
def realise_halo_catalogue(self, Nhalo, scatter=False, scatter_type='uniform'):
"""
Generate a catalogue of halo positions (comoving) within the 3D box,
using a previously-calculated field of halo counts per voxel.
Parameters:
Nhalo (array_like):
3D array containing the number of halos of this type in each voxel,
typically generated by `self.halo_count_field()`.
scatter (bool, optional):
Whether to randomly shift the positions of the halos within a
voxel. If False, halos in the same voxel have the same position.
scatter_type (str, optional):
If `scatter=True`, how the positions within the voxel should be
assigned. Default: 'uniform' (uniform random positions).
Returns:
cat (array_like):
Catalogue of halo positions, of shape (Nhalos, 3) (comoving
Cartesian positions x,y,z).
"""
cat_x, cat_y, cat_z = [], [], []
# Loop over sets of voxels with the same count
unique_counts = np.unique(Nhalo)
for count in unique_counts:
if count < 1.: continue
# Find voxels containing this number of halos and add to catalogue
# (repeated 'count' times)
idx_x, idx_y, idx_z = np.where(Nhalo == count)
cat_x.append(np.repeat(idx_x, count))
cat_y.append(np.repeat(idx_y, count))
cat_z.append(np.repeat(idx_z, count))
# Concatenate into a single array
cat = np.column_stack( (np.concatenate(cat_x),
np.concatenate(cat_y),
np.concatenate(cat_z)) ).astype(np.float64)
# Add scatter in positions
if scatter:
if scatter_type == 'uniform':
# Uniform scatter within each voxel
cat += np.random.uniform(0., 1.-1e-8, cat.size).reshape(cat.shape)
else:
raise ValueError("scatter_type='%s' not recognised"
% scatter_type)
# Scale coordinates with box size
cat[:,0] *= self.box.Lx / self.box.N
cat[:,1] *= self.box.Ly / self.box.N
cat[:,2] *= self.box.Lz / self.box.N
return cat
__init__(self, box, mass_range, mass_bins)
special
An object to manage the placing of halos on top of a realisation of a density field in a box.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
box |
CosmoBox |
Object containing a simulation box. |
required |
mass_range |
tuple |
Range of halo masses, in Msun. |
required |
mass_bins |
int |
Number of mass bins. |
required |
Source code in fastbox/halos.py
def __init__(self, box, mass_range, mass_bins):
"""
An object to manage the placing of halos on top of a realisation of a
density field in a box.
Parameters:
box (CosmoBox):
Object containing a simulation box.
mass_range (tuple):
Range of halo masses, in Msun.
mass_bins (int):
Number of mass bins.
"""
self.box = box
self.Mmin, self.Mmax = mass_range
self.mass_bins = mass_bins
construct_bins(self, z)
Construct a binned halo mass function and bias function. This function
sets the self.dndlog10M
and self.bias
arrays.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
z |
float |
Redshift at which to construct the mass function and bias. |
required |
Source code in fastbox/halos.py
def construct_bins(self, z):
"""
Construct a binned halo mass function and bias function. This function
sets the ``self.dndlog10M`` and ``self.bias`` arrays.
Parameters:
z (float): Redshift at which to construct the mass function and bias.
"""
a = 1./(1.+z)
# Define mass bins
Mh_edges = np.logspace(np.log10(self.Mmin),
np.log10(self.Mmax),
int(self.mass_bins)+1)
Mh_centres = 0.5 * (Mh_edges[1:] + Mh_edges[:-1])
# Get mass function and bias at mass bin centres
self.dndlog10M = ccl.massfunction.massfunc(self.box.cosmo, Mh_centres,
a, overdensity=200)
self.bias = ccl.halo_bias(cosmo, Mh_centres, a, overdensity=200)
halo_count_field(self, delta_x, nbar, bias, lognormal=False)
Generate a cube of halo counts, with a Poisson-generated realisation of the number of halos per voxel.
The expected number of halos per voxel is given by:
N_halo(x) = vol_voxel * nbar(x) * (1 + bias(x) * delta_x(x))
Parameters:
Name | Type | Description | Default |
---|---|---|---|
delta_x |
array_like |
3D box of density contrast values. |
required |
nbar |
float or array_like |
Mean number density (per unit comoving volume, Mpc^-3) of a chosen (sub-)population of halos. This can be specified as a constant across the whole box, a 1D array which will be interpreted as a function of the z/LOS direction, or a 3D array with the value of nbar per voxel. |
required |
bias |
float or array_like |
Mean bias of the population of halos. This can be specified as a constant across the whole box, a 1D array which will be interpreted as a function of the z/LOS direction, or a 3D array with the value of nbar per voxel. |
required |
lognormal |
bool |
If True, apply a log-normal transform to the biased density field before realising the halo counts. This should normally only be applied to the linear density contrast. |
False |
Returns:
Type | Description |
---|---|
N_halo (array_like) |
3D box of halo counts per voxel. |
Source code in fastbox/halos.py
def halo_count_field(self, delta_x, nbar, bias, lognormal=False):
"""
Generate a cube of halo counts, with a Poisson-generated realisation of
the number of halos per voxel.
The expected number of halos per voxel is given by:
N_halo(x) = vol_voxel * nbar(x) * (1 + bias(x) * delta_x(x))
Parameters:
delta_x (array_like):
3D box of density contrast values.
nbar (float or array_like):
Mean number density (per unit comoving volume, Mpc^-3) of a chosen
(sub-)population of halos.
This can be specified as a constant across the whole box, a 1D
array which will be interpreted as a function of the z/LOS
direction, or a 3D array with the value of nbar per voxel.
bias (float or array_like):
Mean bias of the population of halos.
This can be specified as a constant across the whole box, a 1D
array which will be interpreted as a function of the z/LOS
direction, or a 3D array with the value of nbar per voxel.
lognormal (bool, optional):
If True, apply a log-normal transform to the biased density field
before realising the halo counts. This should normally only be
applied to the *linear* density contrast.
Returns:
N_halo (array_like):
3D box of halo counts per voxel.
"""
# Build bias and number density functions with the right shape
nbar = np.atleast_1d(nbar)
bias = np.atleast_1d(bias)
# Check for 1D shape (assumed to be either constant or fn. of redshift)
if len(nbar.shape) == 1:
nbar = nbar[np.newaxis,np.newaxis,:]
if len(bias.shape) == 1:
bias = bias[np.newaxis,np.newaxis,:]
# Comoving volume of each voxel
voxel_vol = self.box.Lx * self.box.Ly * self.box.Lz / self.box.N**3.
# Calculate halo overdensity field
delta_h = bias * delta_x
if lognormal:
delta_h = np.exp(delta_h)
delta_h /= np.mean(delta_h)
delta_h -= 1.
# Expected number of halos in each voxel
Nbar = voxel_vol * nbar * (1. + delta_h)
if not lognormal:
Nbar[np.where(Nbar < 0.)] = 0.
# Realise halo count in each voxel
Nhalo = np.random.poisson(lam=np.nan_to_num(Nbar))
return Nhalo
realise_halo_catalogue(self, Nhalo, scatter=False, scatter_type='uniform')
Generate a catalogue of halo positions (comoving) within the 3D box, using a previously-calculated field of halo counts per voxel.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
Nhalo |
array_like |
3D array containing the number of halos of this type in each voxel,
typically generated by |
required |
scatter |
bool |
Whether to randomly shift the positions of the halos within a voxel. If False, halos in the same voxel have the same position. |
False |
scatter_type |
str |
If |
'uniform' |
Returns:
Type | Description |
---|---|
cat (array_like) |
Catalogue of halo positions, of shape (Nhalos, 3) (comoving Cartesian positions x,y,z). |
Source code in fastbox/halos.py
def realise_halo_catalogue(self, Nhalo, scatter=False, scatter_type='uniform'):
"""
Generate a catalogue of halo positions (comoving) within the 3D box,
using a previously-calculated field of halo counts per voxel.
Parameters:
Nhalo (array_like):
3D array containing the number of halos of this type in each voxel,
typically generated by `self.halo_count_field()`.
scatter (bool, optional):
Whether to randomly shift the positions of the halos within a
voxel. If False, halos in the same voxel have the same position.
scatter_type (str, optional):
If `scatter=True`, how the positions within the voxel should be
assigned. Default: 'uniform' (uniform random positions).
Returns:
cat (array_like):
Catalogue of halo positions, of shape (Nhalos, 3) (comoving
Cartesian positions x,y,z).
"""
cat_x, cat_y, cat_z = [], [], []
# Loop over sets of voxels with the same count
unique_counts = np.unique(Nhalo)
for count in unique_counts:
if count < 1.: continue
# Find voxels containing this number of halos and add to catalogue
# (repeated 'count' times)
idx_x, idx_y, idx_z = np.where(Nhalo == count)
cat_x.append(np.repeat(idx_x, count))
cat_y.append(np.repeat(idx_y, count))
cat_z.append(np.repeat(idx_z, count))
# Concatenate into a single array
cat = np.column_stack( (np.concatenate(cat_x),
np.concatenate(cat_y),
np.concatenate(cat_z)) ).astype(np.float64)
# Add scatter in positions
if scatter:
if scatter_type == 'uniform':
# Uniform scatter within each voxel
cat += np.random.uniform(0., 1.-1e-8, cat.size).reshape(cat.shape)
else:
raise ValueError("scatter_type='%s' not recognised"
% scatter_type)
# Scale coordinates with box size
cat[:,0] *= self.box.Lx / self.box.N
cat[:,1] *= self.box.Ly / self.box.N
cat[:,2] *= self.box.Lz / self.box.N
return cat