Getting Started with halomod

In this tutorial, you'll get a basic familiarity with the layout of halomod and some of its features. This is in no way meant to be exhaustive!

The first thing to note is that halomod is based heavily on hmf, and there are a bunch of docs for that code that may help you with halomod.

Most of the functionality of halomod is wrapped up in a few framework classes. Probably the one you'll use most is the TracerHaloModel, which as the name suggests implements halo models for tracer populations (like galaxies). There's a similar framework for pure Dark Matter (DMHaloModel).

Let's import that (and a few other things we'll need):


In [15]:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

from halomod import TracerHaloModel
import halomod
import hmf

In [2]:
print("halomod version: ", halomod.__version__)
print("hmf version:", hmf.__version__)


halomod version:  1.4.6.dev45
hmf version: 3.0.11.dev22

Using the TracerHaloModel

As with all frameworks in halomod (and hmf) all defaults are provided for you, so you can simply create the object:


In [36]:
hm = TracerHaloModel()

Just like that, you have a wide range of quantities available for computation. Note that all the quantities look and feel like they're attributes (i.e. they look like they're just variables pointing to data) but they are properties that lazily compute when they're needed (and are then cached).

Let's have a look at the halo mass function:


In [4]:
plt.plot(hm.m, hm.dndm)
plt.xscale('log')
plt.yscale('log')

plt.xlabel("Halo Mass [$h^{-1} M_\odot$]")
plt.ylabel(r"dn/dm [$h^2 M_\odot^{-1} {\rm Mpc}^{-3}$]");


Most often, what is desired is the power spectrum (or auto-correlation function) of the galaxies:


In [8]:
plt.plot(hm.k, hm.power_auto_tracer, label='Galaxy-Galaxy Power')
plt.plot(hm.k, hm.power_1h_auto_tracer, ls='--', label='1-halo term')
plt.plot(hm.k, hm.power_2h_auto_tracer, ls='--', label='2-halo term')


plt.xscale('log')
plt.yscale('log')

plt.ylim(1e-5,1e6)
plt.legend()
plt.xlabel("Wavenumber [h/Mpc]")
plt.ylabel(r"Galaxy Power Spectrum [${\rm Mpc^3} h^{-3}$]");


You can check all the quantities that are available with


In [10]:
hm.quantities_available()


Out[10]:
['_corr_mm_base',
 '_dlnsdlnm',
 '_find_m_min',
 '_growth_factor_fn',
 '_gtm',
 '_normalisation',
 '_power0',
 '_power_2h_auto_tracer',
 '_power_halo_centres',
 '_sigma_0',
 '_tm',
 '_unn_sig8',
 '_unn_sigma0',
 '_unnormalised_lnT',
 '_unnormalised_power',
 'bias',
 'bias_effective_matter',
 'bias_effective_tracer',
 'central_fraction',
 'central_occupation',
 'cmz_relation',
 'colossus_cosmo',
 'corr_1h_auto_matter',
 'corr_1h_auto_tracer',
 'corr_1h_cross_tracer_matter',
 'corr_1h_cs_auto_tracer',
 'corr_1h_ss_auto_tracer',
 'corr_2h_auto_matter',
 'corr_2h_auto_tracer',
 'corr_2h_cross_tracer_matter',
 'corr_auto_matter',
 'corr_auto_tracer',
 'corr_cross_tracer_matter',
 'corr_gg',
 'corr_gg_1h',
 'corr_gg_2h',
 'corr_halofit_mm',
 'corr_linear_mm',
 'corr_mm',
 'corr_mm_1h',
 'corr_mm_2h',
 'cosmo',
 'delta_k',
 'dndlnm',
 'dndlog10m',
 'dndm',
 'filter',
 'fsigma',
 'growth',
 'growth_factor',
 'halo_bias',
 'halo_concentration',
 'halo_overdensity_crit',
 'halo_overdensity_mean',
 'halo_profile',
 'halo_profile_lam',
 'halo_profile_rho',
 'halo_profile_ukm',
 'hmf',
 'hod',
 'how_big',
 'k',
 'lnsigma',
 'm',
 'mass_effective',
 'mass_nonlinear',
 'mdef',
 'mean_density',
 'mean_density0',
 'mean_tracer_den',
 'mean_tracer_den_unit',
 'n_eff',
 'ngtm',
 'nonlinear_delta_k',
 'nonlinear_power',
 'normalised_filter',
 'nu',
 'power',
 'power_1h_auto_matter',
 'power_1h_auto_tracer',
 'power_1h_cross_tracer_matter',
 'power_1h_cs_auto_tracer',
 'power_1h_ss_auto_tracer',
 'power_2h_auto_matter',
 'power_2h_auto_tracer',
 'power_2h_cross_tracer_matter',
 'power_auto_matter',
 'power_auto_tracer',
 'power_cross_tracer_matter',
 'power_gg',
 'power_gg_1h',
 'power_gg_2h',
 'power_hh',
 'power_mm',
 'power_mm_1h',
 'power_mm_2h',
 'r',
 'radii',
 'rho_gtm',
 'rho_ltm',
 'satellite_fraction',
 'satellite_occupation',
 'sd_bias',
 'sigma',
 'total_occupation',
 'tracer_cm',
 'tracer_concentration',
 'tracer_density_m',
 'tracer_profile',
 'tracer_profile_lam',
 'tracer_profile_rho',
 'tracer_profile_ukm',
 'transfer',
 'transfer_function']

Thus we could estimate the total fraction of galaxies in the sample that are satellites:


In [12]:
hm.satellite_fraction


Out[12]:
0.3278149250898899

Or get the effective galaxy bias:


In [13]:
hm.bias_effective_tracer


Out[13]:
1.1127444840853928

Furthermore, some of the properties of the framework are themselves what we call Components. These are entire objects with their own methods for calculating various quantities (some of which have been exposed to the framework interface if they are commonly used).

For example, the halo_profile object contains methods for evaluating halo-based properties:


In [28]:
r = np.logspace(-3, 1, 20)
for m in [1e10, 1e12, 1e16]:
    plt.plot(r, hm.halo_profile.rho(r=r, m=m), label=f'm={m:1.2e}')

plt.legend()
plt.yscale('log')
plt.xscale('log')

plt.xlabel("Distance from Centre [Mpc/h]")
plt.ylabel(r"Halo Density [$h^2 M_\odot {\rm Mpc}^{-3}$]");


Input Parameters

There are many options for the TracerHaloModel. One of the motivations for halomod is to make it as feature-complete as possible, especially in terms of the input models (and their flexibility).

The documentation for the TracerHaloModel itself does not contain all the possible parameters (as many of them are passed through to super-classes). You can see a full list of available parameters with:


In [31]:
TracerHaloModel.parameter_info()


cosmo_model : instance of `astropy.cosmology.FLRW` subclass
    The basis for the cosmology -- see astropy documentation. Can be a custom
    subclass. Defaults to Planck15.

cosmo_params : dict
    Parameters for the cosmology that deviate from the base cosmology passed.
    This is useful for repeated updates of a single parameter (leaving others
    the same). Default is the empty dict. The parameters passed must match
    the allowed parameters of `cosmo_model`. For the basic class this is
    :Tcmb0: Temperature of the CMB at z=0
    :Neff: Number of massless neutrino species
    :m_nu: Mass of neutrino species (list)
    :H0: The hubble constant at z=0
    :Om0: The normalised matter density at z=0

n : float
    Spectral index of fluctuations
    Must be greater than -3 and less than 4.

sigma_8 : float
    RMS linear density fluctuations in spheres of radius 8 Mpc/h

growth_params : dict
    Relevant parameters of the :attr:`growth_model`.

lnk_min : float
    Minimum (natural) log wave-number, :attr:`k` [h/Mpc].

lnk_max : float
    Maximum (natural) log wave-number, :attr:`k` [h/Mpc].

dlnk : float
    Step-size of log wave-numbers

z : float
    Redshift.
    Must be greater than 0.

transfer_model : str or :class:`hmf.transfer_models.TransferComponent` subclass, optional
    Defines which transfer function model to use.
    Built-in available models are found in the :mod:`hmf.transfer_models` module.
    Default is CAMB if installed, otherwise EH.

transfer_params : dict
    Relevant parameters of the `transfer_model`.

takahashi : bool
    Whether to use updated HALOFIT coefficients from Takahashi+12

growth_model : `hmf.growth_factor.GrowthFactor` subclass
    The model to use to calculate the growth function/growth rate.

hmf_model : str or `hmf.fitting_functions.FittingFunction` subclass
    A model to use as the fitting function :math:`f(\sigma)`

Mmin : float
    Minimum mass at which to perform analysis [units :math:`\log_{10}M_\odot h^{-1}`].

Mmax : float
    Maximum mass at which to perform analysis [units :math:`\log_{10}M_\odot h^{-1}`].

dlog10m : float
    log10 interval between mass bins

mdef_model : str or :class:`hmf.halos.mass_definitions.MassDefinition` subclass
    A model to use as the mass definition.

mdef_params : dict
    Model parameters for `mdef_model`.

delta_c : float
    The critical overdensity for collapse, :math:`\delta_c`.

hmf_params : dict
    Model parameters for `hmf_model`.

filter_model : :class:`hmf.filters.Filter` subclass
    A model for the window/filter function.

filter_params : dict
    Model parameters for `filter_model`.

disable_mass_conversion : bool
    Disable converting mass function from builtin definition to that provided.

halo_profile_model : The halo density halo_profile model

halo_profile_params : Dictionary of parameters for the Profile model

halo_concentration_model : A halo_concentration-mass relation

halo_concentration_params : 

bias_model : 

bias_params : 

sd_bias_model : 

sd_bias_params : 

exclusion_model : A string identifier for the type of halo exclusion used (or None)

exclusion_params : Dictionary of parameters for the Exclusion model.

rmin : 

rmax : 

rnum : 

rlog : 

hc_spectrum : 

force_1halo_turnover : 

colossus_params : Options for colossus cosmology not set/derived in the astropy cosmology.

hod_params : Dictionary of parameters for the HOD model.

hod_model : :class:`~hod.HOD` class

tracer_profile_model : The halo density halo_profile model

tracer_profile_params : Dictionary of parameters for the Profile model

tracer_concentration_model : A halo_concentration-mass relation

tracer_concentration_params : 

tracer_density : Mean density of the tracer, ONLY if passed directly.

Anything listed here can be set at instantiation time. A few common options might be:


In [35]:
hm_smt3 = TracerHaloModel(
    z = 3.0,  # Redshift
    hmf_model = 'SMT',  # Sheth-Tormen mass function
    cosmo_params = {
        'Om0': 0.3,
        'H0': 70.0
    }
)

So then we can compare the correlation functions for each of our defined models:


In [39]:
plt.plot(hm.r, hm.corr_auto_tracer, label='Tinker at z=0')
plt.plot(hm_smt3.r, hm_smt3.corr_auto_tracer, label='SMT at z=3')

plt.xscale('log')
plt.yscale('log')

plt.xlabel("r [Mpc/h]")
plt.ylabel("Correlation Function");


Notice that the first time we accessed hm.corr_auto_tracer it took a few moments to return, because it was computing. Now, however, it will return instantly:


In [40]:
%timeit hm.corr_auto_tracer


4.98 µs ± 405 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Some of the parameters passed into TracerHaloModel are more complex than simply setting a redshift. Many of the parameters themselves define whole Components. Every one of these has two associated parameters: component_model and component_params. You've already seen one of these -- the hmf_model. There is an associated hmf_params which sets arbitrary model-specific parameters, and should be passed as a dictionary. In fact, you saw one of those too: cosmo_params.

Once you've created the object, the actual model instance is available simply as component (so for example, hm.hmf is a full class instance containing methods for calculating $f(\sigma)$).

You can check out what parameters are available for a specific model (and their current values) by printing the .params variable of the Component. For example:


In [42]:
hm_smt3.hmf.params


Out[42]:
{'a': 0.707, 'p': 0.3, 'A': 0.3222}

thus, passing hmf_params = {'A':0.3} would set up a component with different parameters, making it easy to explore parameter space (or constrain those parameters via a fitting/MCMC routine!).

Updating parameters in-place

Once you have a framework created, you can update parameters in-place fully consistently. So, if we wanted to update our halo profile to be a Hernquist model:


In [43]:
hm.halo_profile_model = 'Hernquist'

To ensure it has been properly updated, let's create a new instance:


In [44]:
hm_orig = TracerHaloModel()

And plot the halo profiles:


In [52]:
plt.plot(hm.r, hm.halo_profile_rho[:,-1], label='Hernquist')
plt.plot(hm.r, hm_orig.halo_profile_rho[:,-1], label='NFW')

plt.xscale('log')
plt.yscale('log')
plt.legend(loc='lower left')
plt.text(3, 1e-2, f"Halo Mass = {hm.m[-1]:1.2e}", fontsize=13)

plt.xlabel("Distance from Centre [Mpc/h]")
plt.ylabel(r"Halo Density [$h^2 M_\odot {\rm Mpc}^{-3}$]");


halomod inherits the caching system of hmf, which means that any updated parameter will automatically invalidate the cache for all dependent quantities, updating them on the next time they are accessed.

Whirlwind Tour of Components and Models

There are many different kinds of Components that offer several different models each. Let's take a look at some that you could choose from:

  • Cosmology: All FLRW cosmologies are supported via astropy.
  • Transfer Functions: several commonly-used forms of the transfer function are provided, including: BBKS, BondEfs, CAMB, EH, FromFile.
  • Growth Factor: default is to solve the standard integral in a flat-LCDM cosmology, though one can also use the output from CAMB which supports arbitrary non-flat FLRW cosmologies. Several other approximations are also implemented (eg. GenMFGrowth and Carroll1992).
  • Filters: filters (or window-functions) are convolved with the density field to define "regions" of space associated with overdensities. The standard filter is the TopHat (in real-space), but you may also choose other filters such as the Gaussian, SharpK or SharpKEllipsoid.
  • Mass Definitions: we provide several standard halo mass definitions (i.e. the definition of what makes a halo a halo). These include FoF, SOMean, SOCritical and SOVirial. Explicitly defining the mass definitions allows conversions to be made between definitions.
  • Fitting Functions: we provide many mass function fits reported in the literature, including favourites such as SMT, PS, Jenkins01 and Tinker08.
  • Halo Bias: Used to bias haloes with respect to the background clustering. Options include standards such as SMT01 and Tinker10. Also provided is a generic interface to use bias functions from the COLOSSUS package.
  • Halo Profiles: halomod implements an extensive system of subclasses for halo density profiles. These will compute the density profile itself, the cumulative mass distribution, the virial radius, the normalized fourier transform of the density profile, and its self-convolution. They all have a consistent API. Models include NFW, Moore, Hernquist and Einasto.
  • Concentration-Mass Relations: To fully specify a halo profile, one must have a model for the halo concentration. We provide several such models, including Bullock01, Duffy08 and Ludlow16. We again provide an interface to use concentration relations from the COLOSSUS package.
  • HOD Models: To link galaxies to the DM haloes, we require a halo occupation distribution. A full-featured system of such models is included, and specific models from certain papers are also included, such as those from Zheng05 and Zehavi05. HOD models are not limited to point-tracers like galaxies -- they are generic enough that smooth occupation distributions can be modelled, for example the occupation of neutral hydrogen.
  • Halo Exclusion: to increase fidelity of the auto-power spectra on transition scales between the 1- and 2-halo terms, various forms of "halo exclusion" have been proposed. We implement simple models such as Sphere exclusion, as well as more complex schemes such as DblSphere, DblEllipsoid and NgMatched (from Tinker+2005).

The API Documentation has an exhaustive listing of your options for these components and their models. The key point is that halomod is built to be a system in which these various components can be mixed and matched consistently.

Along with these components, there are many ways to use halomod. We've seen the TracerHaloModel, but you may also be interested in the ProjectedCF (projected correlation function), which performs integrals over the line-of-sight, or the AngularCF which produces the angular correlation function. Furthermore, a set of extensions to Warm Dark Matter models is also provided.

Defining Your Own Models

We've seen that using a new model for a particular Component is as simple as passing its string name. However, you can also pass a class directly. For example, to switch to the Bullock01 concentration-mass relation:


In [54]:
from halomod.concentration import Bullock01

In [60]:
hm.halo_concentration_model = Bullock01
hm.mdef_model = 'SOCritical'

In [61]:
plt.plot(hm.m, hm.cmz_relation)
plt.xscale('log')
plt.yscale('log')

plt.xlabel("Mass [$M_\odot/h$]")
plt.ylabel("Halo Concentration");


This also lets you easily define your own models. For example, say we had a crazy idea and thought that a constant concentration (with mass) was a good idea. We could create such a model:


In [67]:
from halomod.concentration import CMRelation
from hmf.halos.mass_definitions import SOCritical

In [68]:
class ConstantConcentration(CMRelation):
    native_mdefs = (SOCritical(),)
    _defaults = {"amplitude": 3}
    
    def cm(self, m, z=0):
        return self.params['amplitude'] * np.ones_like(m)

Notice that we inherited from CMRelation, which provides a basic set of methods that we don't need to define ourselves, and also provides an interface that we must adhere to. In particular, any parameters that should be changeable by the user should be specified (with defaults) in the _defaults dictionary. Also, a cm method must be implemented which returns the concentration as a function of mass, for a particular redshift. The user-changeable parameters are available as self.params.

We can now instantly use this new definition:


In [69]:
hm.halo_concentration_model = ConstantConcentration

In [70]:
plt.plot(hm.m, hm.cmz_relation)
plt.xscale('log')
plt.yscale('log')

plt.xlabel("Mass [$M_\odot/h$]")
plt.ylabel("Halo Concentration");


And we can see what effect this would have on the power spectrum:


In [75]:
plt.plot(hm.k, hm.power_auto_matter, label='Constant Concentration')
plt.plot(hm_orig.k, hm_orig.power_auto_matter, label='Duffy08 Concentration')

plt.xscale('log')
plt.yscale('log')
plt.xlim(3e-3, 100)
plt.ylim(1e-1, 1e5)

plt.legend()
plt.xlabel("Wavenumber [h/Mpc]")
plt.ylabel(r"Galaxy Power Spectrum [${\rm Mpc^3} h^{-3}$]");


Every single Component allows you to create models in this fashion. Some, like the Profile, implement a very rich set of functionality for free (for the halo profile, you need only specify one function -- the halo density profile itself -- for a full range of functionality to be available).