Plugins and Extensions¶
[14]:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
If you’ve already read the Broad Overview, you’ll know that hmf
is extremely flexible, and is defined by a number of Component
parts, that come together in Framework
objects.
In this notebook, we’ll look at these a bit more closely and show how to define your own.
Built-in Models¶
In hmf
, we call each kind of calculation a Component
. For example, the hmf FittingFunction
is a Component
, and so is the Cosmology
. In fact, most of the functionality is defined in one of these components. Each particular implementation of a Component
is a Model
– so, eg. the SMT
fitting function is a model, and so is the PS
.
Components are based on a simple plugin architecture, so that each base component knows about all its models. Let’s see this in a few examples:
[11]:
from hmf.mass_function import FittingFunction, PS
from hmf.halos import MassDefinition
from hmf import MassFunction
To determine all the implemented models for a particular component, use get_models()
:
[2]:
FittingFunction.get_models()
[2]:
{'PS': hmf.mass_function.fitting_functions.PS,
'SMT': hmf.mass_function.fitting_functions.SMT,
'ST': hmf.mass_function.fitting_functions.ST,
'Jenkins': hmf.mass_function.fitting_functions.Jenkins,
'Warren': hmf.mass_function.fitting_functions.Warren,
'Reed03': hmf.mass_function.fitting_functions.Reed03,
'Reed07': hmf.mass_function.fitting_functions.Reed07,
'Peacock': hmf.mass_function.fitting_functions.Peacock,
'Angulo': hmf.mass_function.fitting_functions.Angulo,
'AnguloBound': hmf.mass_function.fitting_functions.AnguloBound,
'Watson_FoF': hmf.mass_function.fitting_functions.Watson_FoF,
'Watson': hmf.mass_function.fitting_functions.Watson,
'Crocce': hmf.mass_function.fitting_functions.Crocce,
'Courtin': hmf.mass_function.fitting_functions.Courtin,
'Bhattacharya': hmf.mass_function.fitting_functions.Bhattacharya,
'Tinker08': hmf.mass_function.fitting_functions.Tinker08,
'Tinker10': hmf.mass_function.fitting_functions.Tinker10,
'Behroozi': hmf.mass_function.fitting_functions.Behroozi,
'Pillepich': hmf.mass_function.fitting_functions.Pillepich,
'Manera': hmf.mass_function.fitting_functions.Manera,
'Ishiyama': hmf.mass_function.fitting_functions.Ishiyama}
[3]:
MassDefinition.get_models()
[3]:
{'SphericalOverdensity': hmf.halos.mass_definitions.SphericalOverdensity,
'SOGeneric': hmf.halos.mass_definitions.SOGeneric,
'SOMean': hmf.halos.mass_definitions.SOMean,
'SOCritical': hmf.halos.mass_definitions.SOCritical,
'SOVirial': hmf.halos.mass_definitions.SOVirial,
'FOF': hmf.halos.mass_definitions.FOF}
Any of the listed models can be used (as a string, or a class) when constructing a Framework
, and it will be automatically converted to an actual class:
[5]:
mf = MassFunction(hmf_model='Behroozi')
[6]:
mf.hmf_model
[6]:
hmf.mass_function.fitting_functions.Behroozi
Also, you can get at those models from any model:
[12]:
PS.get_models()
[12]:
{'PS': hmf.mass_function.fitting_functions.PS,
'SMT': hmf.mass_function.fitting_functions.SMT,
'ST': hmf.mass_function.fitting_functions.ST,
'Jenkins': hmf.mass_function.fitting_functions.Jenkins,
'Warren': hmf.mass_function.fitting_functions.Warren,
'Reed03': hmf.mass_function.fitting_functions.Reed03,
'Reed07': hmf.mass_function.fitting_functions.Reed07,
'Peacock': hmf.mass_function.fitting_functions.Peacock,
'Angulo': hmf.mass_function.fitting_functions.Angulo,
'AnguloBound': hmf.mass_function.fitting_functions.AnguloBound,
'Watson_FoF': hmf.mass_function.fitting_functions.Watson_FoF,
'Watson': hmf.mass_function.fitting_functions.Watson,
'Crocce': hmf.mass_function.fitting_functions.Crocce,
'Courtin': hmf.mass_function.fitting_functions.Courtin,
'Bhattacharya': hmf.mass_function.fitting_functions.Bhattacharya,
'Tinker08': hmf.mass_function.fitting_functions.Tinker08,
'Tinker10': hmf.mass_function.fitting_functions.Tinker10,
'Behroozi': hmf.mass_function.fitting_functions.Behroozi,
'Pillepich': hmf.mass_function.fitting_functions.Pillepich,
'Manera': hmf.mass_function.fitting_functions.Manera,
'Ishiyama': hmf.mass_function.fitting_functions.Ishiyama,
'MyNewFit': __main__.MyNewFit}
Also note, we haven’t actually “constructed” the SMT fitting function here, we’re just using it as a bare class.
Defining your own Models¶
As long as you follow the prescribed interface, you can define your own model for any Component
. The interface for each component is different, by necessity, and you can find information about what the interface is by looking at the documentation for the base component. In the case of fitting functions, that’s FittingFunction
. In this case, we can just define one method to create our own fit.
Let’s create a mass function very similar to the Press-Schechter form, but with an exponent that is not \(\nu^2/2\), but instead \(0.4 \nu^2\):
[8]:
class MyNewFit(FittingFunction):
@property
def fsigma(self):
return np.sqrt(2.0 / np.pi) * self.nu * np.exp(-0.4 * self.nu2)
We can immediately use this new model in a Framework
:
[18]:
mf = MassFunction(hmf_model=MyNewFit, transfer_model='EH')
plt.plot(mf.m, mf.dndm)
mf.hmf_model = PS
plt.plot(mf.m, mf.dndm)
plt.xscale('log')
plt.yscale('log')
plt.xlabel("Mass")
plt.ylabel("dndm")
[18]:
Text(0, 0.5, 'dndm')
It’s clear that our new function doesn’t drop off at high masses quite so fast.
Parameterizing Custom Models¶
Imagine that we doubt that the cutoff should be proportional to \(0.5 \nu^2\), and want to parameterize it as \(a \nu^2\), and fit that free parameter \(a\) to our simulated data. Any Component
may be parameterized by writing a _defaults
dictionary:
[20]:
class MyNewFitParameterized(FittingFunction):
_defaults = {'a': 0.5}
@property
def fsigma(self):
return np.sqrt(2.0 / np.pi) * self.nu * np.exp(-self.params['a'] * self.nu2)
Notice that to access the actual user-supplied value of \(a\), we use the self.params
dictionary. The _defaults
dictionary only specifies the default values.
Constructing a model, we can use the hmf_params
to set \(a\):
[24]:
mf = MassFunction(hmf_model=MyNewFitParameterized, hmf_params={'a':0.55}, transfer_model='EH')
[25]:
for a in np.arange(0.4,0.6,0.02):
mf.hmf_params = {'a':a}
plt.plot(mf.m, mf.dndm)
plt.xscale('log')
plt.yscale('log')
plt.xlabel("Mass")
plt.ylabel("dndm")
[25]:
Text(0, 0.5, 'dndm')
Your Custom Model is a Plugin¶
Due to the generic way that the plugin architecture is implemented, your custom models also appear as plugins:
[26]:
FittingFunction.get_models()
[26]:
{'PS': hmf.mass_function.fitting_functions.PS,
'SMT': hmf.mass_function.fitting_functions.SMT,
'ST': hmf.mass_function.fitting_functions.ST,
'Jenkins': hmf.mass_function.fitting_functions.Jenkins,
'Warren': hmf.mass_function.fitting_functions.Warren,
'Reed03': hmf.mass_function.fitting_functions.Reed03,
'Reed07': hmf.mass_function.fitting_functions.Reed07,
'Peacock': hmf.mass_function.fitting_functions.Peacock,
'Angulo': hmf.mass_function.fitting_functions.Angulo,
'AnguloBound': hmf.mass_function.fitting_functions.AnguloBound,
'Watson_FoF': hmf.mass_function.fitting_functions.Watson_FoF,
'Watson': hmf.mass_function.fitting_functions.Watson,
'Crocce': hmf.mass_function.fitting_functions.Crocce,
'Courtin': hmf.mass_function.fitting_functions.Courtin,
'Bhattacharya': hmf.mass_function.fitting_functions.Bhattacharya,
'Tinker08': hmf.mass_function.fitting_functions.Tinker08,
'Tinker10': hmf.mass_function.fitting_functions.Tinker10,
'Behroozi': hmf.mass_function.fitting_functions.Behroozi,
'Pillepich': hmf.mass_function.fitting_functions.Pillepich,
'Manera': hmf.mass_function.fitting_functions.Manera,
'Ishiyama': hmf.mass_function.fitting_functions.Ishiyama,
'MyNewFit': __main__.MyNewFit,
'MyNewFitParameterized': __main__.MyNewFitParameterized}
This means we can construct the Framework
using string names of the models:
[27]:
mf = MassFunction(hmf_model = 'MyNewFit')
[28]:
mf.hmf_model
[28]:
__main__.MyNewFit
This is particularly useful for dynamically creating frameworks from a library of custom classes, or specifying models in YAML/TOML configuration files.