Source code for hmf.alternatives.wdm

"""
Module containing Warm Dark Matter models.

This module contains both WDM Components (basic WDM models and also recalibrators for the
HMF) and Frameworks (Transfer and MassFunction). The latter inject WDM modelling into
the standard CDM Frameworks, and provide an example of how one would go about this for
other alternative cosmologies.
"""

import numpy as np
from ..density_field.transfer import Transfer as _Tr
from ..mass_function.hmf import MassFunction as _MF
from .._internals._cache import parameter, cached_quantity
from .._internals._framework import Component, get_mdl, pluggable
from ..cosmology.cosmo import Planck15

import astropy.units as u


# ===============================================================================
# Model Components
# ===============================================================================
[docs]@pluggable class WDM(Component): r""" Base class for all WDM components. Do not use this class directly. The primary purpose of the WDM Component is to modify the transfer function. Thus, the only requisite method to define in any given subclass is :meth:`transfer`, which calculates this quantity in the proposed WDM model. Parameters ---------- mx : float Mass of the particle in keV cosmo : `hmf.cosmo.Cosmology` instance A cosmology. z : float Redshift. \*\*model_parameters : unpack-dict Parameters specific to a model. To see the default values, check the :attr:`_defaults` class attribute. """
[docs] def __init__(self, mx, cosmo=Planck15, z=0, **model_params): self.mx = mx self.cosmo = cosmo self.rho_mean = (1 + z) ** 3 * ( self.cosmo.Om0 * self.cosmo.critical_density0 / self.cosmo.h ** 2 ).to(u.solMass / u.Mpc ** 3).value self.Oc0 = cosmo.Om0 - cosmo.Ob0 super(WDM, self).__init__(**model_params)
[docs] def transfer(self, lnk): """ Transfer function for WDM models Parameters ---------- lnk : array The wavenumbers *k/h* corresponding to ``power_cdm``. Returns ------- transfer : array_like The WDM transfer function at `lnk`. """ raise NotImplementedError( "You shouldn't call the WDM class, and any subclass should define the transfer method." )
[docs]class Viel05(WDM): r""" Transfer function from Viel 2005 (which is exactly the same as Bode et al. 2001). Formula from Bode et. al. 2001 eq. A9. Parameters ---------- mx : float Mass of the particle in keV cosmo : `hmf.cosmo.Cosmology` instance A cosmology. z : float Redshift. \*\*model_parameters : unpack-dict Parameters specific to a model. Available parameters are as follows. To see the default values, check the :attr:`_defaults` class attribute. :mu: :g_x: """ _defaults = {"mu": 1.12, "g_x": 1.5}
[docs] def transfer(self, k): return (1 + (self.lam_eff_fs * k) ** (2 * self.params["mu"])) ** ( -5.0 / self.params["mu"] )
@property def lam_eff_fs(self): """ Effective free-streaming scale. From Schneider+2013, Eq. 6 """ return ( 0.049 * self.mx ** -1.11 * (self.Oc0 / 0.25) ** 0.11 * (self.cosmo.h / 0.7) ** 1.22 * (1.5 / self.params["g_x"]) ** 0.29 ) @property def m_fs(self): """ Free-streaming mass scale. From Schneider+2012, Eq. 7 """ return (4.0 / 3.0) * np.pi * self.rho_mean * (self.lam_eff_fs / 2) ** 3 @property def lam_hm(self): """ Half-mode scale. From Schneider+2012, Eq. 8. """ return ( 2 * np.pi * self.lam_eff_fs * (2 ** (self.params["mu"] / 5) - 1) ** (-0.5 / self.params["mu"]) ) @property def m_hm(self): """ Half-mode mass scale. From Schneider+2013, Eq. 8 """ return (4.0 / 3.0) * np.pi * self.rho_mean * (self.lam_hm / 2) ** 3
[docs]class Bode01(Viel05): pass
[docs]@pluggable class WDMRecalibrateMF(Component): r""" Base class for Components that emulate the effect of WDM on the HMF empirically. Required method is :meth:`dndm_alter`. Parameters ---------- m : array_like Masses at which the HMF is calculated. dndm0 : array_like The original HMF at `m`. wdm : :class:`WDM` subclass instance An instance of :class:`WDM` providing a Warm Dark Matter model. \*\*model_parameters : unpack-dict Parameters specific to a model. To see the default values, check the :attr:`_defaults` class attribute. """
[docs] def __init__(self, m, dndm0, wdm=Viel05(mx=1.0), **model_parameters): self.m = m self.dndm0 = dndm0 self.wdm = wdm super(WDMRecalibrateMF, self).__init__(**model_parameters)
[docs] def dndm_alter(self): pass
[docs]class Schneider12_vCDM(WDMRecalibrateMF): r""" Schneider+2012 recalibration of the CDM HMF. Parameters ---------- m : array_like Masses at which the HMF is calculated. dndm0 : array_like The CDM HMF at `m`. wdm : :class:`WDM` subclass instance An instance of :class:`WDM` providing a Warm Dark Matter model. \*\*model_parameters : unpack-dict Parameters specific to this model: **beta**. To see the default values, check the :attr:`_defaults` class attribute. """ _defaults = {"beta": 1.16}
[docs] def dndm_alter(self): return self.dndm0 * (1 + self.wdm.m_hm / self.m) ** (-self.params["beta"])
[docs]class Schneider12(WDMRecalibrateMF): r""" Schneider+2012 recalibration of the WDM HMF. Parameters ---------- m : array_like Masses at which the HMF is calculated. dndm0 : array_like The original WDM HMF at `m`. wdm : :class:`WDM` subclass instance An instance of :class:`WDM` providing a Warm Dark Matter model. \*\*model_parameters : unpack-dict Parameters specific to this model: **alpha**. To see the default values, check the :attr:`_defaults` class attribute. """ _defaults = {"alpha": 0.6}
[docs] def dndm_alter(self): return self.dndm0 * (1 + self.wdm.m_hm / self.m) ** (-self.params["alpha"])
[docs]class Lovell14(WDMRecalibrateMF): r""" Lovell+2014 recalibration of the WDM HMF. Parameters ---------- m : array_like Masses at which the HMF is calculated. dndm0 : array_like The original HMF at `m`. wdm : :class:`WDM` subclass instance An instance of :class:`WDM` providing a Warm Dark Matter model. \*\*model_parameters : unpack-dict Parameters specific to this model: **beta**. To see the default values, check the :attr:`_defaults` class attribute. """ _defaults = {"beta": 0.99, "gamma": 2.7}
[docs] def dndm_alter(self): return self.dndm0 * (1 + self.params["gamma"] * self.wdm.m_hm / self.m) ** ( -self.params["beta"] )
# =============================================================================== # Frameworks # ===============================================================================
[docs]class TransferWDM(_Tr): """ A subclass of :class:`hmf.transfer.Transfer` that mixes in WDM capabilities. This replaces the standard CDM quantities with WDM-derived ones, where relevant. In addition to the parameters directly passed to this class, others are available which are passed on to its superclass. To read a standard documented list of (all) parameters, use ``TransferWDM.parameter_info()``. If you want to just see the plain list of available parameters, use ``TransferWDM.get_all_parameters()``.To see the actual defaults for each parameter, use ``TransferWDM.get_all_parameter_defaults()``. """
[docs] def __init__( self, wdm_mass=3.0, wdm_model=Viel05, wdm_params={}, **transfer_kwargs ): # Call standard transfer super(TransferWDM, self).__init__(**transfer_kwargs) # Set given parameters self.wdm_mass = wdm_mass self.wdm_model = wdm_model self.wdm_params = wdm_params
@parameter("model") def wdm_model(self, val): """ A model for the WDM effect on the transfer function. :type: str or :class:`WDM` subclass """ return get_mdl(val, WDM) @parameter("param") def wdm_params(self, val): """ Parameters of the WDM model. :type: dict """ return val @parameter("param") def wdm_mass(self, val): """ Mass of the WDM particle. :type: float """ try: val = float(val) except ValueError: raise ValueError("wdm_mass must be a number (", val, ")") if val <= 0: raise ValueError("wdm_mass must be > 0 (", val, ")") return val @cached_quantity def wdm(self): """ The instantiated WDM model. Contains quantities relevant to WDM. """ return self.wdm_model( mx=self.wdm_mass, cosmo=self.cosmo, z=self.z, **self.wdm_params ) @cached_quantity def _unnormalised_lnT(self): return super(TransferWDM, self)._unnormalised_lnT + np.log( self.wdm.transfer(self.k) )
[docs]class MassFunctionWDM(_MF, TransferWDM): """ A subclass of :class:`hmf.MassFunction` that mixes in WDM capabilities. This replaces the standard CDM quantities with WDM-derived ones, where relevant. In addition to the parameters directly passed to this class, others are available which are passed on to its superclass. To read a standard documented list of (all) parameters, use ``MassFunctionWDM.parameter_info()``. If you want to just see the plain list of available parameters, use ``MassFunctionWDM.get_all_parameters()``. To see the actual defaults for each parameter, use ``MassFunctionWDM.get_all_parameter_defaults()``. """
[docs] def __init__(self, alter_model=None, alter_params=None, **kwargs): super(MassFunctionWDM, self).__init__(**kwargs) self.alter_model = alter_model self.alter_params = alter_params or {}
@parameter("switch") def alter_model(self, val): """ A model for empirical recalibration of the HMF. :type: None, str, or :class`WDMRecalibrateMF` subclass. """ if val is None: return None return get_mdl(val, WDMRecalibrateMF) @parameter("param") def alter_params(self, val): """ Model parameters for `alter_model`. """ return val @cached_quantity def dndm(self): r""" The number density of haloes in WDM, ``len=len(m)``. Units of :math:`h^4 M_\odot^{-1} Mpc^{-3}` """ dndm = super(MassFunctionWDM, self).dndm if self.alter_model is not None: alter = self.alter_model( m=self.m, dndm0=dndm, wdm=self.wdm, **self.alter_params ) dndm = alter.dndm_alter() return dndm