Analysing SAGE Output

On this page, we show how to analyse the SAGE output for a single snapshot. This full example is shown in the galaxy_properties module using the default parameter file.

We explain how to analyse SAGE output across multiple snapshots here.

Setting Things Up

First things first, we need to specify exactly what we want to analyse/plot, how the plots will be saved, and where the plots will be saved.

# Going to just plot the stellar mass function.
plot_toggles = {"SMF": 1}

plot_output_format = "png"
plot_output_path = "./plots"

if not os.path.exists(plot_output_path):
    os.makedirs(plot_output_path)

Model Dictionary

Each model that you wish to analyse is specifed through a dictionary. This defines properties such as the snapshot you wish to analyse, the location of the SAGE parameter file, etc.

millennium = { "snapshot": 63,   # Snapshot we're plotting properties at.
               "IMF": "Chabrier",  # Chabrier or Salpeter.
               "label": "Mini-Millennium",  # Legend label.
               "sage_file": "../input/millennium.par",
               "sage_output_format": "sage_hdf5",
               "first_file": 0,  # File range (or core range for HDF5) to plot.
               "last_file": 0,  # Closed interval, [first_file, last_file].
             }

NOTE: If the sage_output_format is sage_binary (i.e., SAGE wrote as binary output), then you must also specify the number of output files, num_output_files.

Setting up the Calculation and Plotting Dictionaries

To ensure that sage-analysis does not perform extraneous computations, the properties for each Model are calculated depending upon the plot_toggles specified. For example, the black hole mass of each galaxy will only be read if the black hole-bulge relationship plot toggle is set. We refer to this page <./custom_calculations for a full list of the default plot toggles.

To achieve this, we search for all functions in a module that are named calc_<plot_toggle>. We build these functions into a dictionary that are passed into calc_properties_all_files().

from sage_analysis.utils import generate_func_dict

# Search for functions named "calc_<plot_toggle>" in the "example_calcs"
# module.
calculation_functions = generate_func_dict(
                            plot_toggles,
                            module_name="sage_analysis.example_calcs",
                            function_prefix="calc"
                            )

NOTE: All functions must have the signature calc_<plot_toggle>(model, galaxies, **optional keyword arguments). We expand on this more in Using Keyword Arguments.

In a similar manner, we search for all the functions in a module that are named plot_<plot_toggle>. From this dictionary, we can then iterate over and make all the plots!

# Search for functions named "plot_<plot_toggles>" in the "example_plots"
# module.
plot_functions = generate_func_dict(
                    plot_toggles,
                    module_name="sage_analysis.example_plots",
                    function_prefix="plot_"
                    )

NOTE: All functions must have the signature calc_<plot_toggle>(list of models, plot_output_path, **optional keyword arguments). We expand on this more in Using Keyword Arguments.

Initializing a Model

With the calculation functions prepped, we are now poised to perform the actual analysis. The analysis of SAGE models is done through a specialized Model class. Importantly, the Model class only handles the calculating properties. To actually read the SAGE output, each Model requires a data class. These are specific to the SAGE output format. For example, we include a data class for SageHdf5Data and SageBinaryData. Through this data class, the package can be easily extended to ingest any arbitrary SAGE output format. We show such an example here.

from sage_analysis.model import Model
from sage_analysis.sage_hdf5 import SageHdf5Data

model = Model()
model.plot_output_format = plot_output_format

model.data_class = SageHdf5Data(model, millennium["sage_file"])

# The data class has read the SAGE ini file.  Update the model with the parameters
# read and those specified by the user.
model.update_attributes(model_dict)

Storing Galaxy Properties

When performing calculations, sage-analysis stores all the calculating properties in the properties attribute of the Model instance. This attribute is a dictionary and can be used to access any of the properties pertaining to the Model; for example, model.properties["SMF"] stores the array representing the stellar mass function.

These properties must first be initialized. sage-analysis offers three ways to compute and store galaxy properties.

Binned Properties

These are properties that are binned on some value. For example: the stellar mass function is binned depending upon the galaxy stellar mass; the fraction of quiescent galaxies is binned upon the galaxy stellar mass; the mass of gas in each SAGE reservoir (cold gas/hot gas/stars/etc) is binned upon the friends-of-friends halo mass. The bins themselves are accessed through the bins attribute of the model instance. This attribute is a dictionary and can be used to access any of the bins for the Model; for example, model.bins["stellar_mass_bins"] would return the stellar mass bins used for the stellar mass function.

# Properties binned on stellar mass.
stellar_properties = ["SMF", "red_SMF", "blue_SMF"]
min_mass = 8.0  # log10(Msun).
max_mass = 12.0  # log10(Msun).
bin_width = 0.1  # log10(Msun).
bin_name = "stellar_mass_bins"
model.init_binned_properties(min_mass, max_mass, bin_width, bin_name,
                             stellar_properties)

# Properties binned on FoF halo mass.
component_properties = [f"halo_{component}_fraction_sum" for component in
                        ["baryon", "stars", "cold", "hot", "ejected", "ICS", "bh"]]
min_mass = 10.0  # log10(Msun)
max_mass = 14.0  # log10(Msun)
bin_width = 0.1  # log10(Msun)
bin_name = "halo_mass_bins"
model.init_binned_properties(min_mass, max_mass, bin_width, bin_name,
                             component_properties)

Scatter Properties

In many instances, we don’t want to fit an exact line to the properties, but rather just get a sense of the typical data point values. For these, we want to compute lists of (x, y) coordinates that we will plot later. For example, the black hole bulge relationship will show a number of black hole masses and the corresponding bulge mass. The (maximum) number of data points shown on each plot can be set through the sample_size attribute.

# For each of these, we need a list for both x and y points. E.g., the
# black hole bulge needs both "bh_mass" and "bulge_mass".
scatter_properties = ["bh_mass", "bulge_mass", "BTF_mass", "BTF_vel"]
model.init_scatter_properties(scatter_properties)

Single Properties

Finally, often we want to use a single number to summarize a property for all galaxies across a single snapshot. This is most useful when analyzing galaxy properties over a range of snapshots through the history module. These properties are initialized with a value of 0.0.

single_properties = ["SMFD", "SFRD"]
model.init_single_properties(single_properties)

Doing the Analysis and Plotting

We have set up the dictionary for the plotting functions in Setting up the Calculation and Plotting Dictionaries. Once all the properties have been calculated, we can finally do the plotting!

# Calculate all the properties.
model.calc_properties_all_files(calculations_functions)

# Now do the plotting.
for func_name in plot_functions.keys():
    func = plot_functions[func_name][0]
    func([model], plot_output_path, plot_output_format)

NOTE: The plotting scripts accept a list of Model classes as the first argument. For this scenario, we only have one model and so we cast it to a list first.

The above code snippets produce the glorious stellar mass function!

|SMF| .. |SMF| image:: ../figs/SMF.png

Using Keyword Arguments

generate_func_dict() accepts an optional argument to allow the calculation or plotting functions to handle keyword arugments. This argument is a dictionary with keys equal to the plot toggles. The value of each entry is another dictionary containing all of the keyword arguments and their corresponding value.

from sage_analysis.utils import generate_func_dict

# By default, the stellar mass function is not computed for the red and blue
# galaxy populations. Let's turn it on.
keyword_args = {"SMF": {"calc_sub_populations": True}}

calculation_functions = generate_func_dict(
                            plot_toggles,
                            module_name="sage_analysis.example_calcs",
                            function_prefix="calc",
                            keyword_args=keyword_args
                            )
model.calc_properties_all_files(calculations_functions)

# Then we can adjust "plot_SMF" to also plot these extra populations.
keyword_args = {"SMF": {"plot_sub_populations": True}}

plot_functions = generate_func_dict(
                    plot_toggles,
                    module_name="sage_analysis.example_plots",
                    function_prefix="plot_",
                    keyword_args=keyword_args
                    )

# Now do the plotting with the extra kwargs.
for func_name in plot_functions.keys():
    func = plot_functions[func_name][0]
    keyword_args = plot_functions[func_name][1]
    func(models, plot_output_path, plot_output_format, **keyword_args)

|SMF_pop| .. |SMF_pop| image:: ../figs/SMF_pop.png