Analysing Across Multiple Snapshots

We show how to analyse the output of SAGE at a single snapshot here. On this page, we show how to analyse SAGE output across multiple snapshots. This is very useful if you wish to analyse the evolution of (e.g.,) the stellar mass function, the stellar mass density, etc.

This full example is shown in the history module using the default parameter file.

Setting Things Up

In a similar manner to analysing a single snapshot, we first specify which properties we wish to analyse and plot.

# Base specifications.
plot_output_format = "png"
plot_output_path = "./plots"  # Will be created if path doesn't exist.

plot_toggles = {"SMF_z" : 1,  # Stellar mass function across redshift.
                "SFRD_z" : 1,  # Star formation rate density across redshift.
                "SMD_z" : 1}  # Stellar mass density across redshift.

Then, we specify the SAGE output we wish to analyse and the redshifts at which we want the properties to be calculated at.

NOTE: For all the specified redshifts, sage-analysis searches for the snapshot closest to the redshift. Also set the entry to "All" to analyse across all snapshots.

millennium = {"SMF_z" : [0.0, 1.0, 2.0, 3.0],  # Redshifts you wish to plot the stellar mass function at.
              "density_z": "All", # Redshifts to plot the stellar mass/star formation density 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.

Initializing the Model

Calculation and Plotting Dictionaries

Again, as outlined previously, we first generate the dictionaries necessary to analyse and plot properties.

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"
                            )

# 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_"
                    )

Setting up the Class

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)

Specifying the Empty Property Containers

We also initialize the Model properties as outlined previously.

stellar_properties = ["SMF", "red_SMF", "blue_SMF"]
model.init_binned_properties(8.0, 12.0, 0.1, "stellar_mass_bins",
                             stellar_properties)

# Properties that are extended as lists.
scatter_properties = []
model.init_scatter_properties(scatter_properties)

# Properties that are stored as a single number.
single_properties = ["SMFD", "SFRD"]
model.init_single_properties(single_properties)

# We will store the values of each snapshot in a dictionary.
model.properties["SMF_dict"] = {}
model.properties["SFRD_dict"] = {}
model.properties["SMD_dict"] = {}

Setting Up The Snapshot Loop

The key difference for this example is that we want to analyse properties over a number of redshifts. We hence must determine which snapshots in the model correspond to the requested redshifts.

# We may be plotting the density at all snapshots...
if model_dict["density_z"] == "All":
    model.density_redshifts = model.redshifts
else:
    model.density_redshifts = model_dict["density_z"]

# Same for SMF
if model_dict["SMF_z"] == "All":
    model.SMF_redshifts = model.redshifts
else:
    model.SMF_redshifts = model_dict["SMF_z"]

# Find the snapshots that most closely match the requested redshifts.
model.SMF_snaps = [(np.abs(model.redshifts - SMF_redshift)).argmin() for
                   SMF_redshift in model.SMF_redshifts]

model.density_snaps = [(np.abs(model.redshifts - density_redshift)).argmin() for
                       density_redshift in model.density_redshifts]

# Check which snapshots we uniquely need to loop through.
snaps_to_loop = np.unique(my_model.SMF_snaps + my_model.density_snaps)

Iterating Through Snapshots

Finally, we are poised to iterate through the snapshots and calculate all the properties required. Importantly, at the end of each snapshot, we must place the calculate properties into the appropriate dictionary and reset the property.

for snap in snap_iter:

    # Each snapshot is unique. So reset the tracking.
    model.properties["SMF"] = np.zeros(len(model.bins["stellar_mass_bins"])-1,
                                       dtype=np.float64)
    model.properties["SFRD"] = 0.0
    model.properties["SMD"] = 0.0

    # Update the snapshot we're reading from. Data Class specific.
    model.data_class.update_snapshot(model, snap)

    # Calculate all the properties. Since we're using a HDF5 file, we want to keep
    # the file open because we read other snapshots from that one file.
    model.calc_properties_all_files(calculation_functions, close_file=False)

    # We need to place the SMF inside the dictionary to carry through.
    if snap in model.SMF_snaps:
        model.properties["SMF_dict"][snap] = model.properties["SMF"]

    # Same with the densities.
    if snap in model.density_snaps:

        model.properties["SFRD_dict"][snap] = model.properties["SFRD"]
        model.properties["SMD_dict"][snap] = model.properties["SMD"]

# Close the HDF5 file cause we're done with it.
model.data_class.close_file(model)

Finally, plot the properties!

# Similar to the calculation functions, all of the plotting functions are in the
# `example_plots.py` module and are labelled `plot_<toggle>`.
plot_functions = generate_func_dict(plot_toggles,
                                    module_name="sage_analysis.example_plots",
                                    function_prefix="plot_")

# Now do the plotting.
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)

This produces the stellar mass function, star formation rate density, and stellar mass density over the various redshifts.

SMF_redshift SFRD SMD