orbits.py

"""Demonstration script to plot the orbit of one galaxy in HYDRO and DM.

It demonstrates reading data from the pre-compiled galaxy tables, matching
subhaloes between the HYDRO and DM-ONLY versions of a simulation, and 
working with the interpolated high-time-resolution orbit tables.
"""

# Import required packages
import numpy as np
import hydrangea as hy
import matplotlib.pyplot as plt
import matplotlib.colors as colors   # For truncated grey-scale color map
from pdb import set_trace

# Set script parameters
sim_index = 0                  # Which simulation do we want?
galaxy_id = 123                # Galaxy ID (in HYDRO) to plot
plotloc = 'orbit_plot.png'     # Where to save the output plot?

# Set up the simulation object for the run we're working with, to easily
# get the relevant file paths
sim_hydro = hy.Simulation(index=sim_index)
sim_dm_only = hy.Simulation(index=sim_index, sim_type='DM')

# Prepare the plot
fig = plt.figure(figsize=(4*0.8/0.6, 4))
ax1 = fig.add_axes((0.15, 0.15, 0.65, 0.8))

# ------------------------------------------------------------------
# Part I: Find the corresponding galaxy ID in the DM-only simulation
# ------------------------------------------------------------------

# Find the corresponding galaxy ID in the DM simulation. For this, we find
# the snapshot in which the galaxy has its maximum total mass, and then
# match its subhalo
mtot_hydro = hy.hdf5.read_data(sim_hydro.fgt_loc, 'Msub',
                               read_index=galaxy_id)
snap_max = np.argmax(mtot_hydro)
print(f"Galaxy {galaxy_id} reaches its max subhalo mass in snap {snap_max}.")

# Now we find the subhalo index of the galaxy in this snapshot
shi_hydro_max = hy.hdf5.read_data(
    sim_hydro.fgt_loc, 'SHI', read_index=galaxy_id)[snap_max]

# And now we find the matching DM-only subhalo in the same snapshot
shi_dm_max = hy.hdf5.read_data(sim_hydro.sh_extra_loc,
                               f'Snapshot_{snap_max:03d}/MatchInDM',
                               read_index=shi_hydro_max)

# And find the DM-only galaxy ID
galaxy_id_dmo = hy.hdf5.read_data(sim_dm_only.spider_loc,
                                  f'Subhalo/Snapshot_{snap_max:03d}/Galaxy',
                                  read_index=shi_dm_max)
print(f"Galaxy {galaxy_id} in HYDRO matched to {galaxy_id_dmo} in DM-only")


# ---------------------------------------------------------------
# Part II: Extract the orbits of the galaxies in both simulations
# ---------------------------------------------------------------

# Find the central of the galaxy at z = 0 in both simulations
central_id_hydro = hy.hdf5.read_data(sim_hydro.fgt_loc, 'CenGal',
                                     read_index=galaxy_id)[29]
central_id_dmo = hy.hdf5.read_data(sim_dm_only.fgt_loc, 'CenGal',
                                   read_index=galaxy_id_dmo)[29]


# We now extract the high-time-resolution orbits of all four galaxies
def get_galaxy_paths(galaxy_id, sim, load_times=False):
    """Extract the galaxy paths for a specified galaxy in a simulation."""
    # First, we must look up the galaxy in the interpolation list
    interp_id = hy.hdf5.read_data(sim.interpolation_loc, 'GalaxyRevIndex',
                                  read_index=galaxy_id)

    # If the galaxy is not in the interpolation list, quit
    if interp_id < 0:
        print(f"Could not locate galaxy {galaxy_id} in interpolation list.")
        return

    # Extract the interpolated orbit
    orbit_pos = hy.hdf5.read_data(sim.interpolation_loc,
                                  'InterpolatedPositions',
                                  read_index=interp_id)

    # If needed, also load the interpolation times
    if not load_times:
        return orbit_pos

    orbit_times = hy.hdf5.read_data(sim.interpolation_loc,
                                    'InterpolationTimes')
    return orbit_pos, orbit_times


orbit_hydro, orbit_times = get_galaxy_paths(galaxy_id, sim_hydro,
                                            load_times=True)
orbit_hydro -= get_galaxy_paths(central_id_hydro, sim_hydro)
orbit_dmo = (get_galaxy_paths(galaxy_id_dmo, sim_dm_only)
             - get_galaxy_paths(central_id_dmo, sim_dm_only))


# -------------------------
# Part III: Plot the orbits
# -------------------------

# Create a truncated grey color map (Greys_r becomes white towards 1)
# [see https://stackoverflow.com/questions/18926031/]
cmap_greys = colors.LinearSegmentedColormap.from_list(
    'trunc(Greys_r, 0.0, 0.8)', plt.cm.Greys_r(np.linspace(0.0, 0.8, 1000)))

plt.sca(ax1)
sc_dm = plt.scatter(orbit_dmo[0, :], orbit_dmo[1, :], c=orbit_times,
                    edgecolor='none', vmin=0, vmax=14.0, cmap=cmap_greys,
                    s=10)
sc_hy = plt.scatter(orbit_hydro[0, :], orbit_hydro[1, :], c=orbit_times,
                    edgecolor='none', vmin=0, vmax=14.0, cmap=plt.cm.viridis,
                    s=10)

# Some embellishments on the plot
ax1.set_xlim((-0.8, 0.8))
ax1.set_ylim((-0.8, 0.8))
ax1.set_xlabel(r'$\Delta x$ [pMpc]')
ax1.set_ylabel(r'$\Delta y$ [pMpc]')

# Add colour bars on the side
ax2 = fig.add_axes([0.81, 0.15, 0.03, 0.8])
ax2.set_xticks([])
ax2.set_yticks([])
cbar_dm = plt.colorbar(sc_dm, cax=ax2, orientation='vertical')
cbar_dm.set_ticks([])

ax3 = fig.add_axes([0.84, 0.15, 0.03, 0.8])
ax3.set_xticks([])
ax3.set_yticks([])
cbar_hy = plt.colorbar(sc_hy, cax=ax3, orientation='vertical')

fig.text(0.94, 0.55, 'Age of the Universe [Gyr]',
         rotation=90.0, va='center', ha='left')

# Save the figure
plt.show
plt.savefig(plotloc, transparent=False, dpi=150)