Working with Simulation Directories

Analyzing simulation data is as important as running the simulation itself. In this notebook, we will see how we easily access all the data using the SimDir module.

(This notebook is meant to be converted in Sphinx documentation and not used directly.)

[1]:
from kuibit import simdir as sd

Loading simulation data is as easy as generating SimDir objects. Just provide the path, and kuibit will do the rest.

[2]:
sim = sd.SimDir("../../tests/tov")

We can get an overview of what is available by printing sim:

[3]:
print(sim)
Indexed 450 files and 5 subdirectories
Folder /home/runner/work/kuibit/kuibit/tests/tov
/home/runner/work/kuibit/kuibit/tests/tov
Available scalar timeseries:
['physical_time_per_hour', 'current_physical_time_per_hour', 'time_total', 'time_evolution', 'time_computing', 'time_communicating', 'time_io', 'evolution_steps_count', 'local_grid_points_per_second', 'total_grid_points_per_second', 'local_grid_point_updates_count', 'total_grid_point_updates_count', 'local_interior_points_per_second', 'total_interior_points_per_second', 'local_interior_point_updates_count', 'total_interior_point_updates_count', 'io_per_second', 'io_bytes_per_second', 'io_bytes_ascii_per_second', 'io_bytes_binary_per_second', 'io_count', 'io_bytes_count', 'io_bytes_ascii_count', 'io_bytes_binary_count', 'comm_per_second', 'comm_bytes_per_second', 'comm_count', 'comm_bytes_count', 'time_levels']

Available minimum timeseries:
['vel[0]', 'vel[1]', 'vel[2]', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'eps', 'H', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'press', 'M1', 'M2', 'M3', 'alp', 'rho']

Available maximum timeseries:
['gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'alp', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'M1', 'M2', 'M3', 'H', 'vel[0]', 'vel[1]', 'vel[2]', 'eps', 'press', 'rho']

Available norm1 timeseries:
['alp', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'M1', 'M2', 'M3', 'vel[0]', 'vel[1]', 'vel[2]', 'eps', 'rho', 'H', 'press']

Available norm2 timeseries:
['gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'rho', 'eps', 'press', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'M1', 'M2', 'M3', 'H', 'alp', 'vel[0]', 'vel[1]', 'vel[2]']

Available average timeseries:
['press', 'alp', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'vel[0]', 'vel[1]', 'vel[2]', 'eps', 'H', 'M1', 'M2', 'M3', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'rho']
Variables available: dict_keys(['harmonic', 'psi4', 'phi2'])
For variable harmonic:

Avilable radii: [4.0, 8.0]

At radius 4.0, (l, m) available: [(0, 0), (1, -1), (1, 0), (1, 1), (2, -1), (2, -2), (2, 0), (2, 1), (2, 2)]
At radius 8.0, (l, m) available: [(0, 0), (1, -1), (1, 0), (1, 1), (2, -1), (2, -2), (2, 0), (2, 1), (2, 2)]

For variable psi4:

Avilable radii: [110.69, 67.88, 44.78, 140.16, 53.96, 60.13, 77.93, 191.0, 48.95, 91.46]

At radius 44.78, (l, m) available: [(2, -2), (2, 1), (2, 0), (2, 2), (2, -1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 48.95, (l, m) available: [(2, -1), (2, -2), (2, 0), (2, 2), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 53.96, (l, m) available: [(2, 2), (2, 1), (2, 0), (2, -1), (2, -2)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 60.13, (l, m) available: [(2, -2), (2, 0), (2, -1), (2, 2), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 67.88, (l, m) available: [(2, 1), (2, -2), (2, 0), (2, -1), (2, 2)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 77.93, (l, m) available: [(2, -1), (2, 2), (2, -2), (2, 0), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 91.46, (l, m) available: [(2, 1), (2, -2), (2, 2), (2, -1), (2, 0)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 110.69, (l, m) available: [(2, 2), (2, 0), (2, -2), (2, -1), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 140.16, (l, m) available: [(2, 1), (2, -1), (2, 2), (2, 0), (2, -2)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 191.0, (l, m) available: [(2, -2), (2, 1), (2, 0), (2, 2), (2, -1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])

For variable phi2:

Avilable radii: [44.78, 110.69, 67.88, 53.96, 140.16, 91.46, 191.0, 48.95, 77.93, 60.13]

At radius 44.78, (l, m) available: [(2, 2), (2, -2), (2, 0), (2, -1), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 48.95, (l, m) available: [(2, -1), (2, 2), (2, 0), (2, -2), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 53.96, (l, m) available: [(2, 0), (2, -1), (2, -2), (2, 2), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 60.13, (l, m) available: [(2, -1), (2, 2), (2, 0), (2, -2), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 67.88, (l, m) available: [(2, 1), (2, 0), (2, -1), (2, 2), (2, -2)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 77.93, (l, m) available: [(2, -2), (2, 2), (2, -1), (2, 0), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 91.46, (l, m) available: [(2, 1), (2, 0), (2, -2), (2, 2), (2, -1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 110.69, (l, m) available: [(2, -2), (2, 2), (2, 1), (2, -1), (2, 0)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 140.16, (l, m) available: [(2, 1), (2, 0), (2, -2), (2, 2), (2, -1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 191.0, (l, m) available: [(2, 0), (2, 2), (2, -2), (2, 1), (2, -1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])

Available gravitational wave dataAvailable electromagnetic wave data
Available grid data of dimension 1D (x):
['alp', 'press', 'rho', 'eps', 'H', 'M1', 'M2', 'M3', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'vel[0]', 'vel[1]', 'vel[2]']


Available grid data of dimension 1D (y):
['H', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'eps', 'press', 'alp', 'M1', 'M2', 'M3', 'rho', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'vel[0]', 'vel[1]', 'vel[2]']


Available grid data of dimension 1D (z):
['rho', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'press', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'eps', 'vel[0]', 'vel[1]', 'vel[2]', 'alp', 'H', 'M1', 'M2', 'M3']


Available grid data of dimension 2D (xy):
['vel[2]', 'vel[1]', 'rho', 'vel[0]']


Available grid data of dimension 2D (xz):
['vel[1]', 'vel[2]', 'rho', 'vel[0]']


Available grid data of dimension 2D (yz):
['vel[0]', 'vel[1]', 'vel[2]', 'rho']


Available grid data of dimension 3D (xyz):
['vel[0]', 'rho', 'vel[2]', 'vel[1]', 'mp_harmonic']
No horizon found
Timers available for processes [0, 1]

This is a long output because we have a lot of available data! Everything that is printed is the output is easily accessible through SimDir. Moreover, the loading of data is lazy and the result is cached: computations are done only when needed, and all the subsequent calls will be immediate.

If you have thorns with custom output, you have to extend kuibit to analyze them. SimDir has multiple submodules to handle the different kind of data. Let us explore them.

SimDir and pickles

SimDir performs some operations only when they needed. This work has be done every time the Python interpreter is killed. In some cases, it is useful to save the progress to files, resulting in faster execution times. This can be done with pickles. The simplest way to do so is as follows.

[4]:
with sd.SimDir("../../tests/tov", pickle_file="/tmp/sim.pickle") as sim2:
    print(sim2)
Indexed 450 files and 5 subdirectories
Folder /home/runner/work/kuibit/kuibit/tests/tov
/home/runner/work/kuibit/kuibit/tests/tov
Available scalar timeseries:
['physical_time_per_hour', 'current_physical_time_per_hour', 'time_total', 'time_evolution', 'time_computing', 'time_communicating', 'time_io', 'evolution_steps_count', 'local_grid_points_per_second', 'total_grid_points_per_second', 'local_grid_point_updates_count', 'total_grid_point_updates_count', 'local_interior_points_per_second', 'total_interior_points_per_second', 'local_interior_point_updates_count', 'total_interior_point_updates_count', 'io_per_second', 'io_bytes_per_second', 'io_bytes_ascii_per_second', 'io_bytes_binary_per_second', 'io_count', 'io_bytes_count', 'io_bytes_ascii_count', 'io_bytes_binary_count', 'comm_per_second', 'comm_bytes_per_second', 'comm_count', 'comm_bytes_count', 'time_levels']

Available minimum timeseries:
['vel[0]', 'vel[1]', 'vel[2]', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'eps', 'H', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'press', 'M1', 'M2', 'M3', 'alp', 'rho']

Available maximum timeseries:
['gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'alp', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'M1', 'M2', 'M3', 'H', 'vel[0]', 'vel[1]', 'vel[2]', 'eps', 'press', 'rho']

Available norm1 timeseries:
['alp', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'M1', 'M2', 'M3', 'vel[0]', 'vel[1]', 'vel[2]', 'eps', 'rho', 'H', 'press']

Available norm2 timeseries:
['gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'rho', 'eps', 'press', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'M1', 'M2', 'M3', 'H', 'alp', 'vel[0]', 'vel[1]', 'vel[2]']

Available average timeseries:
['press', 'alp', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'vel[0]', 'vel[1]', 'vel[2]', 'eps', 'H', 'M1', 'M2', 'M3', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'rho']
Variables available: dict_keys(['harmonic', 'psi4', 'phi2'])
For variable harmonic:

Avilable radii: [4.0, 8.0]

At radius 4.0, (l, m) available: [(0, 0), (1, -1), (1, 0), (1, 1), (2, -1), (2, -2), (2, 0), (2, 1), (2, 2)]
At radius 8.0, (l, m) available: [(0, 0), (1, -1), (1, 0), (1, 1), (2, -1), (2, -2), (2, 0), (2, 1), (2, 2)]

For variable psi4:

Avilable radii: [110.69, 67.88, 44.78, 140.16, 53.96, 60.13, 77.93, 191.0, 48.95, 91.46]

At radius 44.78, (l, m) available: [(2, -2), (2, 1), (2, 0), (2, 2), (2, -1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 48.95, (l, m) available: [(2, -1), (2, -2), (2, 0), (2, 2), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 53.96, (l, m) available: [(2, 2), (2, 1), (2, 0), (2, -1), (2, -2)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 60.13, (l, m) available: [(2, -2), (2, 0), (2, -1), (2, 2), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 67.88, (l, m) available: [(2, 1), (2, -2), (2, 0), (2, -1), (2, 2)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 77.93, (l, m) available: [(2, -1), (2, 2), (2, -2), (2, 0), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 91.46, (l, m) available: [(2, 1), (2, -2), (2, 2), (2, -1), (2, 0)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 110.69, (l, m) available: [(2, 2), (2, 0), (2, -2), (2, -1), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 140.16, (l, m) available: [(2, 1), (2, -1), (2, 2), (2, 0), (2, -2)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 191.0, (l, m) available: [(2, -2), (2, 1), (2, 0), (2, 2), (2, -1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])

For variable phi2:

Avilable radii: [44.78, 110.69, 67.88, 53.96, 140.16, 91.46, 191.0, 48.95, 77.93, 60.13]

At radius 44.78, (l, m) available: [(2, 2), (2, -2), (2, 0), (2, -1), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 48.95, (l, m) available: [(2, -1), (2, 2), (2, 0), (2, -2), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 53.96, (l, m) available: [(2, 0), (2, -1), (2, -2), (2, 2), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 60.13, (l, m) available: [(2, -1), (2, 2), (2, 0), (2, -2), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 67.88, (l, m) available: [(2, 1), (2, 0), (2, -1), (2, 2), (2, -2)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 77.93, (l, m) available: [(2, -2), (2, 2), (2, -1), (2, 0), (2, 1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 91.46, (l, m) available: [(2, 1), (2, 0), (2, -2), (2, 2), (2, -1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 110.69, (l, m) available: [(2, -2), (2, 2), (2, 1), (2, -1), (2, 0)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 140.16, (l, m) available: [(2, 1), (2, 0), (2, -2), (2, 2), (2, -1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])
At radius 191.0, (l, m) available: [(2, 0), (2, 2), (2, -2), (2, 1), (2, -1)] (missing: [(1, 0), (1, 1), (1, -1), (0, 0)])

Available gravitational wave dataAvailable electromagnetic wave data
Available grid data of dimension 1D (x):
['alp', 'press', 'rho', 'eps', 'H', 'M1', 'M2', 'M3', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'vel[0]', 'vel[1]', 'vel[2]']


Available grid data of dimension 1D (y):
['H', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'eps', 'press', 'alp', 'M1', 'M2', 'M3', 'rho', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'vel[0]', 'vel[1]', 'vel[2]']


Available grid data of dimension 1D (z):
['rho', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'press', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'eps', 'vel[0]', 'vel[1]', 'vel[2]', 'alp', 'H', 'M1', 'M2', 'M3']


Available grid data of dimension 2D (xy):
['vel[2]', 'vel[1]', 'rho', 'vel[0]']


Available grid data of dimension 2D (xz):
['vel[1]', 'vel[2]', 'rho', 'vel[0]']


Available grid data of dimension 2D (yz):
['vel[0]', 'vel[1]', 'vel[2]', 'rho']


Available grid data of dimension 3D (xyz):
['vel[0]', 'rho', 'vel[2]', 'vel[1]', 'mp_harmonic']
No horizon found
Timers available for processes [0, 1]

When this code is run, kuibit checks if a pickle file exists in /tmp/sim.pickle. If yes, it will load it, effectively “resuming a previous session”. At the end of the with block, the file is updated with the new work done. If the file did not exist initially, it will be created.

It is important to be careful when working with pickles as the risk of stale objects is high. If the data is changing (e.g., new restarts are added), it is likely that it will result in errors.

It is always possible to save the state of a SimDir using the save method and reads back the data with the load_SimDir function

[5]:
sim.save("/tmp/saved.pickle") # The filename can be anything
sim3 = sd.load_SimDir("/tmp/saved.pickle")

cactus_scalars

The cactus_scalars module understands ASCII files as output by CarpetASCII. It supports all the reductions (maximum, minimum, …), 0d files, as well as scalar data. Using regular expression on the header of the file, cactus_scalars can process one variable per file and multiple variables per file. In SimDir, you can access scalars with the ts or timeseries attributes.

[6]:
timeseries = sim.ts
# equivalent: timeseries = sim.timeseries

If we printed timeseries, we would find part of the output from SimDir. This is because the printed output from SimDir is obtained printing all the different submodules. Another way to see the available variable for a secific reduction is with the keys method:

[7]:
timeseries.maximum.keys()
[7]:
dict_keys(['gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'alp', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'M1', 'M2', 'M3', 'H', 'vel[0]', 'vel[1]', 'vel[2]', 'eps', 'press', 'rho'])

There are equivalent three different way to access those variables as timeseries. The different methods are nearly equivalent.

[8]:
rho_max1 = timeseries.maximum.fields.rho
rho_max2 = timeseries.maximum['rho']
rho_max3 = timeseries.maximum.get('rho')

These objects are timeseries, which are extremely convienent representation of data evolving in time. These series span multiple simulation restarts and have files in different folders: kuibit does all the hard work to find the files and combine them. Moreover, they timeseries have a lot of features, as you can see in the timeseries tutorial.

cactus_multipoles and cactus_waves

Gravitational waves are one of the most important quantities in numerical relativity simulations. Typically, these are computed using the Newman-Penrose formalism and extracted in terms of their multipolar components. kuibit fully supports the Multipole thorn with the cactus_multipoles module. The sim.multipoles attribute contains multipolar data. This is a dictionary-like object with keys the various variable for which multipolar data is available.

[9]:
print(sim.multipoles.keys())
dict_keys(['harmonic', 'psi4', 'phi2'])

Once a variable is selected, the returned object is once again a dictionary with keys the various extracion radii.

[10]:
print(sim.multipoles["phi2"].keys())
phi2_0 = sim.multipoles["phi2"][91.46]

print(phi2_0.available_lm)
dict_keys([44.78, 110.69, 67.88, 53.96, 140.16, 91.46, 191.0, 48.95, 77.93, 60.13])
{(2, -2), (2, -1), (2, 1), (2, 0), (2, 2)}

Selected the extraction radius, the variable contains all the various multipole l and m available. (HDF5 data is preferred in this.) Each component is then a timeseries.

[11]:
phi2_0_22 = phi2_0[2,2]
print(type(phi2_0_22))
<class 'kuibit.timeseries.TimeSeries'>

Gravitational waves have a dedicated interface that is based on cactus_multipole and adds new specific features. Gravitational waves can be accessed with the .gravitationalwaves or .gws attributes.

[12]:
psi4 = sim.gws

radius0 = psi4.radii[0]

# Example of specific function
print("Max GW power for l = 2, m = 2:", psi4[radius0].get_power_lm(2, 2, 100).max())
Max GW power for l = 2, m = 2: 1713.080999815251

cactus_grid_functions

The .gridfunctions (or .gf) attribute is used to access grid data, using the cactus_grid_functions module. The structure is similar to time series.

[13]:
print(sim.gf)

Available grid data of dimension 1D (x):
['alp', 'press', 'rho', 'eps', 'H', 'M1', 'M2', 'M3', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'vel[0]', 'vel[1]', 'vel[2]']


Available grid data of dimension 1D (y):
['H', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'eps', 'press', 'alp', 'M1', 'M2', 'M3', 'rho', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'vel[0]', 'vel[1]', 'vel[2]']


Available grid data of dimension 1D (z):
['rho', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz', 'press', 'kxx', 'kxy', 'kxz', 'kyy', 'kyz', 'kzz', 'eps', 'vel[0]', 'vel[1]', 'vel[2]', 'alp', 'H', 'M1', 'M2', 'M3']


Available grid data of dimension 2D (xy):
['vel[2]', 'vel[1]', 'rho', 'vel[0]']


Available grid data of dimension 2D (xz):
['vel[1]', 'vel[2]', 'rho', 'vel[0]']


Available grid data of dimension 2D (yz):
['vel[0]', 'vel[1]', 'vel[2]', 'rho']


Available grid data of dimension 3D (xyz):
['vel[0]', 'rho', 'vel[2]', 'vel[1]', 'mp_harmonic']

[14]:
# rest-mass density on the xy plane at iteration 0
rho = sim.gf.xy["rho"][0]

For more information of grid data, check out the grid_data tutorial.

cactus_horizons

kuibit can read horizon data from QuasiLocalMeasures and AHFinderDirect. The module that is responsible for this is cactus_horizons. Horizon information is in the .horizon attribute.

cactus_timers

If timing information is present, it can be accessed with the .timers attribute. This is provided by the cactus_timers modules.