Example 5: A subhalo-based model with cross-component dependencies

This section of the Tutorial on building a subhalo-based model, illustrates an example of a component model that depends on the results of some other, independently defined component model.

There is also an IPython Notebook in the following location that can be used as a companion to the material in this section of the tutorial:

halotools/docs/notebooks/hod_modeling/subhalo_modeling_tutorial5.ipynb

By following this tutorial together with this notebook, you can play around with your own variations of the models we’ll build as you learn the basic syntax. The notebook also covers supplementary material that you may find clarifying, so we recommend that you read the notebook side by side with this documentation.

Overview of the Example 5 model

The model we’ll build will be based on the behroozi10 model. Additionally, we’ll add two new component models: one governing galaxy shape, a second governing galaxy size.

In terms of implementation details, the new feature to focus on here is this: the model for galaxy size will have an explicit dependence on galaxy shape, even though these models are controlled by independently defined components. Again, our model will not be physically motivated: the purpose is to teach you how to build a model with inter-dependence between different components.

Briefly, in our model the shape of a galaxy will be randomly selected to be either a disk or an elliptical. The size of disk galaxies will be whatever the spin of the halo is, and the size of elliptical galaxies will be the value of a custom-defined halo property computed in a pre-processing phase. To streamline the presentation, we will omit the features described in the previous example and focus on just the new features introduced in this example.

Source code for the new model

class Shape(object):

    def __init__(self):

        self._mock_generation_calling_sequence = ['assign_shape']
        self._galprop_dtypes_to_allocate = np.dtype([('shape', object)])

    def assign_shape(self, **kwargs):
        table = kwargs['table']
        randomizer = np.random.random(len(table))
        table['shape'][:] = np.where(randomizer > 0.5, 'elliptical', 'disk')

class Size(object):

    def __init__(self):

        self._mock_generation_calling_sequence = ['assign_size']
        self._galprop_dtypes_to_allocate = np.dtype([('galsize', 'f4')])
        self.list_of_haloprops_needed = ['halo_spin']

        self.new_haloprop_func_dict = {'halo_custom_size': self.calculate_halo_size}

    def assign_size(self, **kwargs):
        table = kwargs['table']
        disk_mask = table['shape'] == 'disk'
        table['galsize'][disk_mask] = table['halo_spin'][disk_mask]
        table['galsize'][~disk_mask] = table['halo_custom_size'][~disk_mask]

    def calculate_halo_size(self, **kwargs):
        table = kwargs['table']
        return 2*table['halo_rs']

Now we’ll build our composite model using the model_feature_calling_sequence, a new keyword introduced in this tutorial:

from halotools.empirical_models import Behroozi10SmHm
mstar_model = Behroozi10SmHm(redshift = 0)
shape_model = Shape()
size_model = Size()

from halotools.empirical_models import SubhaloModelFactory
model = SubhaloModelFactory(
    stellar_mass = mstar_model,
    size = size_model,
    shape = shape_model,
    model_feature_calling_sequence = ('stellar_mass','shape', 'size')
    )

The __init__ method of the component models

All features in the constructor of the Shape class have been covered previously in this tutorial. The only thing that may be worth noting here is that one of the physics functions assigns a string: assign_shape writes either elliptical or disk. In such a case, when declaring the _galprop_dtypes_to_allocate, the most robust way to handle this is to use a Python object as the numpy.dtype.

The role of new_haloprop_func_dict

The Size component model illustrates the use of the new_haloprop_func_dict feature. As described in the The new_haloprop_func_dict mechanism section of the Composite Model Bookkeeping Mechanisms documentation page, this feature allows you to add new columns to the halo_table in a pre-processing phase of mock-making. Here we use this mechanism to add a new column to the halo catalog called halo_custom_size, which in this case is twice the NFW scale radius. This mechanism is necessary because the assign_size method expects the halo_custom_size column to be present in the table passed to it. The way the new_haloprop_func_dict mechanism works is this: it stores a dictionary whose key(s) is the name of the new halo column that will be created, and the value bound to that key is a function object that computes this quantity. See The new_haloprop_func_dict mechanism for further discussion.

The “physics functions” of the component models

The physics function in the Size class differs from those covered previously in a subtle but critical detail: the assign_size method requires that the galaxy_table has a column called shape that has already been assigned sensible values, but yet this assignment is not carried out by the Size class itself, it is carried out by the Shape class. This means that we need to make sure that during the process of mock generation, the physics functions in the Shape class are called before the physics functions of the Size class.

The order in which the component models are called is controllable by the model_feature_calling_sequence. Previously, we did not use this keyword. When this keyword is not supplied, the default behavior is for the stellar_mass component to be called first (if it is present), and all other features to be called in a random order. By explicitly listing the features of your model in the model_feature_calling_sequence keyword, you override this default behavior with your own calling sequence.

Concluding comments

This example concludes the tutorial on subhalo-based model building. If you have further questions on how to build models, please contact the Halotools developers and/or raise an Issue on GitHub.