from unyt import Solar_Mass
import traceback

sys.path.append("..")

from scaling_relations import EntropyFgasSpace, EntropyProfiles
from register import find_files, set_mnras_stylesheet, xlargs, default_output_directory
from literature import Sun2009, Pratt2010, Croston2008, Cosmology

snap, cat = find_files()
kwargs = dict(path_to_snap=snap, path_to_catalogue=cat)
set_mnras_stylesheet()

try:

    cosmology = Cosmology()

    gas_profile_obj = EntropyFgasSpace(max_radius_r500=1.)
    radial_bin_centres, cumulative_gas_mass_profile, cumulative_mass_profile, m500fb = gas_profile_obj.process_single_halo(
        **kwargs)
    entropy_profile_obj = EntropyProfiles(max_radius_r500=1)
    _, entropy_profile, K500 = entropy_profile_obj.process_single_halo(
        **kwargs)

    entropy_profile /= K500
    gas_fraction_enclosed = cumulative_gas_mass_profile / m500fb

    fig = plt.figure(figsize=(5, 5), constrained_layout=True)
    gs = fig.add_gridspec(2, 2, hspace=0.35, wspace=0.7)
    axes = gs.subplots()
Beispiel #2
0
    def process_single_halo(self,
                            zoom_obj: Zoom = None,
                            path_to_snap: str = None,
                            path_to_catalogue: str = None,
                            agn_time: str = 'after',
                            z_agn_start: float = 0.1,
                            z_agn_end: float = 0.,
                            **kwargs):
        aperture_fraction = xlargs.aperture_percent / 100
        sw_data, vr_data = self.get_handles_from_zoom(
            zoom_obj,
            path_to_snap,
            path_to_catalogue,
            mask_radius_r500=aperture_fraction,
            **kwargs)

        m500 = vr_data.spherical_overdensities.mass_500_rhocrit[0].to('Msun')
        r500 = vr_data.spherical_overdensities.r_500_rhocrit[0].to('Mpc')

        aperture_fraction = aperture_fraction * r500

        # Convert datasets to physical quantities
        # r500c is already in physical units
        sw_data.gas.radial_distances.convert_to_physical()
        sw_data.gas.coordinates.convert_to_physical()
        sw_data.gas.masses.convert_to_physical()
        sw_data.gas.densities.convert_to_physical()

        gamma = 5 / 3
        a_heat = sw_data.gas.last_agnfeedback_scale_factors

        index = np.where((sw_data.gas.radial_distances < aperture_fraction)
                         & (sw_data.gas.fofgroup_ids == 1)
                         & (a_heat > (1 / (z_agn_start + 1)))
                         & (a_heat < (1 / (z_agn_end + 1))))[0]

        electron_number_density = (sw_data.gas.densities / mh /
                                   mean_molecular_weight).to('cm**-3')[index]
        temperature = sw_data.gas.temperatures.to('K')[index]
        entropy_snapshot = boltzmann_constant * temperature / electron_number_density**(
            2 / 3)
        entropy_snapshot = entropy_snapshot.to('keV*cm**2').value

        # Entropy of all particles in aperture
        index = np.where((sw_data.gas.radial_distances < aperture_fraction)
                         & (sw_data.gas.fofgroup_ids == 1)
                         & (sw_data.gas.temperatures > 1e5))[0]

        electron_number_density = (sw_data.gas.densities / mh /
                                   mean_molecular_weight).to('cm**-3')[index]
        temperature = sw_data.gas.temperatures.to('K')[index]
        entropy_snapshot_all = boltzmann_constant * temperature / electron_number_density**(
            2 / 3)
        entropy_snapshot_all = entropy_snapshot_all.to('keV*cm**2').value

        if agn_time == 'before':

            index = np.where(
                (sw_data.gas.radial_distances < aperture_fraction)
                & (sw_data.gas.fofgroup_ids == 1)
                & (a_heat > (1 / (z_agn_start + 1)))
                & (a_heat < (1 / (z_agn_end + 1)))
                & (sw_data.gas.densities_before_last_agnevent > 0))[0]

            density = sw_data.gas.densities_before_last_agnevent[index]
            electron_number_density = (density / mh /
                                       mean_molecular_weight).to('cm**-3')
            A = sw_data.gas.entropies_before_last_agnevent[
                index] * sw_data.units.mass
            temperature = mean_molecular_weight * (gamma - 1) * (A * density**(
                5 / 3 - 1)) / (gamma - 1) * mh / boltzmann_constant
            temperature = temperature.to('K')

            entropy_heat = boltzmann_constant * temperature / electron_number_density**(
                2 / 3)
            entropy_heat = entropy_heat.to('keV*cm**2').value

        elif agn_time == 'after':

            index = np.where((sw_data.gas.radial_distances < aperture_fraction)
                             & (sw_data.gas.fofgroup_ids == 1)
                             & (a_heat > (1 / (z_agn_start + 1)))
                             & (a_heat < (1 / (z_agn_end + 1)))
                             & (sw_data.gas.densities_at_last_agnevent > 0))[0]

            density = sw_data.gas.densities_at_last_agnevent[index]
            electron_number_density = (density / mh /
                                       mean_molecular_weight).to('cm**-3')
            A = sw_data.gas.entropies_at_last_agnevent[
                index] * sw_data.units.mass
            temperature = mean_molecular_weight * (gamma - 1) * (A * density**(
                5 / 3 - 1)) / (gamma - 1) * mh / boltzmann_constant
            temperature = temperature.to('K')

            entropy_heat = boltzmann_constant * temperature / electron_number_density**(
                2 / 3)
            entropy_heat = entropy_heat.to('keV*cm**2').value

        agn_flag = sw_data.gas.heated_by_agnfeedback[index]
        snii_flag = sw_data.gas.heated_by_sniifeedback[index]
        agn_flag = agn_flag > 0
        snii_flag = snii_flag > 0

        x = entropy_snapshot
        y = entropy_heat
        z = sw_data.metadata.z

        print("Number of particles being plotted", len(x))

        # Set the limits of the figure.
        assert (x > 0).all(), f"Found negative value(s) in x: {x[x <= 0]}"
        assert (y > 0).all(), f"Found negative value(s) in y: {y[y <= 0]}"

        entropy_bounds = [1e-4, 1e6]  # in keV*cm**2
        bins = 256

        # Make the norm object to define the image stretch
        entropy_bins = np.logspace(np.log10(entropy_bounds[0]),
                                   np.log10(entropy_bounds[1]), bins)

        fig = plt.figure(figsize=(5, 5))
        gs = fig.add_gridspec(2, 2, hspace=0.35, wspace=0.45)
        axes = gs.subplots()

        for ax in axes.flat:
            ax.loglog()

            # Draw cross-hair marker
            T500 = (G * mean_molecular_weight * m500 * mp / r500 / 2 /
                    boltzmann_constant).to('K').value
            K500 = (T500 * K * boltzmann_constant /
                    (3 * m500 * Cosmology().fb /
                     (4 * np.pi * r500**3 * mp))**(2 / 3)).to('keV*cm**2')

        # PLOT ALL PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x, y, bins=[entropy_bins, entropy_bins])

        vmax = np.max(H) + 1
        mappable = axes[0, 0].pcolormesh(density_edges,
                                         temperature_edges,
                                         H.T,
                                         norm=LogNorm(vmin=1, vmax=vmax),
                                         cmap='Greys_r')
        # create an axes on the right side of ax. The width of cax will be 5%
        # of ax and the padding between cax and ax will be fixed at 0.05 inch.
        divider = make_axes_locatable(axes[0, 0])
        cax = divider.append_axes("right", size="3%", pad=0.)
        cbar = plt.colorbar(mappable, ax=axes[0, 0], cax=cax)
        ticklab = cbar.ax.get_yticklabels()
        ticks = cbar.ax.get_yticks()
        for i, (t, l) in enumerate(zip(ticks, ticklab)):
            if t < 100:
                ticklab[i] = f'{int(t):d}'
            else:
                ticklab[i] = f'$10^{{{int(np.log10(t)):d}}}$'
        cbar.ax.set_yticklabels(ticklab)

        txt = AnchoredText("All particles",
                           loc="upper right",
                           pad=0.4,
                           borderpad=0,
                           prop={"fontsize": 8})
        axes[0, 0].add_artist(txt)
        axes[0, 0].axvline(K500, color='k', linestyle=':', lw=1, zorder=0)
        axes[0, 0].axhline(K500, color='k', linestyle=':', lw=1, zorder=0)
        add_identity(axes[0, 0], color='k', linestyle='--', lw=1, zorder=0)

        # PLOT SN HEATED PARTICLES ===============================================
        hist_bins = np.logspace(
            np.log10(np.min(np.r_[x, y, entropy_snapshot_all])),
            np.log10(np.max(np.r_[x, y, entropy_snapshot_all])), 100)
        axes[0, 1].hist(entropy_snapshot_all,
                        bins=hist_bins,
                        histtype='step',
                        label=f'All hot gas at z={z:.2f}')
        axes[0, 1].hist(x,
                        bins=hist_bins,
                        histtype='step',
                        label=f'Heated gas at z={z:.2f}')
        axes[0, 1].hist(y,
                        bins=hist_bins,
                        histtype='step',
                        label='Heated gas at feedback time')
        axes[0, 1].axvline(K500, color='k', linestyle=':', lw=1, zorder=0)
        axes[0, 1].set_xlabel(f"Entropy ({agn_time:s} heating) [keV cm$^2$]")
        axes[0, 1].set_ylabel('Number of particles')
        axes[0, 1].legend()

        # PLOT AGN HEATED PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x[agn_flag], y[agn_flag], bins=[entropy_bins, entropy_bins])
        vmax = np.max(H) + 1
        mappable = axes[1, 1].pcolormesh(density_edges,
                                         temperature_edges,
                                         H.T,
                                         norm=LogNorm(vmin=1, vmax=vmax),
                                         cmap='Reds_r',
                                         alpha=0.6)
        divider = make_axes_locatable(axes[1, 1])
        cax = divider.append_axes("right", size="3%", pad=0.)
        cbar = plt.colorbar(mappable, ax=axes[1, 1], cax=cax)
        ticklab = cbar.ax.get_yticklabels()
        ticks = cbar.ax.get_yticks()

        for i, (t, l) in enumerate(zip(ticks, ticklab)):
            if t < 100:
                ticklab[i] = f'{int(t):d}'
            else:
                ticklab[i] = f'$10^{{{int(np.log10(t)):d}}}$'
        cbar.ax.set_yticklabels(ticklab)

        txt = AnchoredText("AGN heated only",
                           loc="upper right",
                           pad=0.4,
                           borderpad=0,
                           prop={"fontsize": 8})
        axes[1, 1].add_artist(txt)
        axes[1, 1].axvline(K500, color='k', linestyle=':', lw=1, zorder=0)
        axes[1, 1].axhline(K500, color='k', linestyle=':', lw=1, zorder=0)
        add_identity(axes[1, 1], color='k', linestyle='--', lw=1, zorder=0)

        # PLOT AGN+SN HEATED PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x[(agn_flag & snii_flag)],
            y[(agn_flag & snii_flag)],
            bins=[entropy_bins, entropy_bins])
        vmax = np.max(H) + 1
        mappable = axes[1, 0].pcolormesh(density_edges,
                                         temperature_edges,
                                         H.T,
                                         norm=LogNorm(vmin=1, vmax=vmax),
                                         cmap='Purples_r',
                                         alpha=0.6)
        divider = make_axes_locatable(axes[1, 0])
        cax = divider.append_axes("right", size="3%", pad=0.)
        cbar = plt.colorbar(mappable, ax=axes[1, 0], cax=cax)
        ticklab = cbar.ax.get_yticklabels()
        ticks = cbar.ax.get_yticks()
        for i, (t, l) in enumerate(zip(ticks, ticklab)):
            if t < 100:
                ticklab[i] = f'{int(t):d}'
            else:
                ticklab[i] = f'$10^{{{int(np.log10(t)):d}}}$'
        cbar.ax.set_yticklabels(ticklab)

        txt = AnchoredText("AGN and SNe heated",
                           loc="upper right",
                           pad=0.4,
                           borderpad=0,
                           prop={"fontsize": 8})
        axes[1, 0].add_artist(txt)
        axes[1, 0].axvline(K500, color='k', linestyle=':', lw=1, zorder=0)
        axes[1, 0].axhline(K500, color='k', linestyle=':', lw=1, zorder=0)
        add_identity(axes[1, 0], color='k', linestyle='--', lw=1, zorder=0)

        fig.text(
            0.5,
            0.04,
            f"Entropy (z = {calibration_zooms.redshift_from_index(xlargs.redshift_index):.2f}) [keV cm$^2$]",
            ha='center')
        fig.text(0.04,
                 0.5,
                 f"Entropy ({agn_time:s} heating) [keV cm$^2$]",
                 va='center',
                 rotation='vertical')

        z_agn_recent_text = (
            f"Selecting gas heated between {z_agn_end:.1f} < z < {z_agn_start:.1f} (relevant to AGN plot only)\n"
            f"({1 / (z_agn_start + 1):.2f} < a < {1 / (z_agn_end + 1):.2f})\n")
        if agn_time is not None:
            z_agn_recent_text = (
                f"Selecting gas {agn_time:s} heated between {z_agn_end:.1f} < z < {z_agn_start:.1f}\n"
                f"({1 / (z_agn_start + 1):.2f} < a < {1 / (z_agn_end + 1):.2f})\n"
            )

        fig.suptitle((
            f"Aperture = {xlargs.aperture_percent / 100:.2f} $R_{{500}}$\t\t"
            f"$z = {calibration_zooms.redshift_from_index(xlargs.redshift_index):.2f}$\n"
            f"{z_agn_recent_text:s}"
            f"Central FoF group only"),
                     fontsize=7)

        if not xlargs.quiet:
            plt.show()
        # fig.savefig(
        #     f'{calibration_zooms.output_directory}/density_temperature_{xlargs.redshift_index:04d}.png',
        #     dpi=300
        # )

        plt.close()
Beispiel #3
0
from typing import Tuple
import matplotlib.patheffects as path_effects

try:
    plt.style.use("../mnras.mplstyle")
except:
    pass

import sys

# Make the register backend visible to the script
sys.path.append("..")
from literature import Sun2009, Cosmology

Sun2009 = Sun2009()
fb = Cosmology().fb

# Constants
mean_molecular_weight = 0.59
mean_atomic_weight_per_free_electron = 1.14


def update_prop(handle, orig):
    handle.update_from(orig)
    handle.set_marker("o")


def histogram_unyt(
    data: unyt.unyt_array,
    bins: unyt.unyt_array = None,
    weights: unyt.unyt_array = None,
    def process_single_halo(
            self,
            zoom_obj: Zoom = None,
            path_to_snap: str = None,
            path_to_catalogue: str = None
    ):

        sw_data, vr_data = self.get_handles_from_zoom(
            zoom_obj,
            path_to_snap,
            path_to_catalogue,
            mask_radius_r500=self.max_radius_r500
        )
        fb = Cosmology().fb0
        critical_density = unyt_quantity(
            sw_data.metadata.cosmology.critical_density(sw_data.metadata.z).value, 'g/cm**3'
        ).to('Msun/Mpc**3')

        sw_data.gas.radial_distances.convert_to_physical()
        sw_data.gas.masses.convert_to_physical()
        sw_data.gas.densities.convert_to_physical()
        sw_data.gas.entropies.convert_to_physical()

        try:
            m500 = vr_data.spherical_overdensities.mass_500_rhocrit[0].to('Msun')
            r500 = vr_data.spherical_overdensities.r_500_rhocrit[0].to('Mpc')
        except AttributeError as err:
            print(f'[{self.__class__.__name__}] {err}')

            spherical_overdensity = SODelta500(
                path_to_snap=path_to_snap,
                path_to_catalogue=path_to_catalogue,
            )
            m500 = spherical_overdensity.get_m500()
            r500 = spherical_overdensity.get_r500()

        if xlargs.mass_estimator == 'hse':
            true_hse = HydrostaticEstimator(
                path_to_catalogue=path_to_catalogue,
                path_to_snap=path_to_snap,
                profile_type='true',
                diagnostics_on=False
            )
            true_hse.interpolate_hse(density_contrast=500.)
            r500 = true_hse.r500hse
            m500 = true_hse.m500hse

        try:
            _ = sw_data.gas.temperatures
        except AttributeError as err:
            print(f'[{self.__class__.__name__}] {err}')
            if xlargs.debug:
                print(f"[{self.__class__.__name__}] Computing gas temperature from internal energies.")
            sw_data.gas.temperatures = sw_data.gas.internal_energies * (gamma - 1) * mean_molecular_weight * mh / kb

        try:
            _ = sw_data.gas.fofgroup_ids
        except AttributeError as err:
            print(f'[{self.__class__.__name__}] {err}')
            if xlargs.debug:
                print(f"[{self.__class__.__name__}] Select particles only by radial distance.")
            sw_data.gas.fofgroup_ids = np.ones_like(sw_data.gas.densities)

        index = np.where(
            (sw_data.gas.radial_distances < self.max_radius_r500 * r500) &
            (sw_data.gas.fofgroup_ids == 1) &
            (sw_data.gas.temperatures > Tcut_halogas)
        )[0]
        radial_distance = sw_data.gas.radial_distances[index] / r500

        # Define radial bins and shell volumes
        lbins = np.logspace(-2, np.log10(self.max_radius_r500), 51) * radial_distance.units
        radial_bin_centres = 10 ** (0.5 * np.log10(lbins[1:] * lbins[:-1])) * radial_distance.units

        if self.weighting == 'xray':
            # Compute hydrogen number density and the log10
            # of the temperature to provide to the xray interpolator.
            data_nH = np.log10(
                sw_data.gas.element_mass_fractions.hydrogen * sw_data.gas.densities.to('g*cm**-3') / mp)
            data_T = np.log10(sw_data.gas.temperatures.value)

            # Interpolate the Cloudy table to get emissivities
            emissivities = unyt_array(
                10 ** cloudy.interpolate_X_Ray(
                    data_nH,
                    data_T,
                    sw_data.gas.element_mass_fractions,
                    fill_value=-50.
                ), 'erg/s/cm**3'
            )
            xray_luminosities = emissivities * sw_data.gas.masses / sw_data.gas.densities
            weighting = xray_luminosities[index]
            del data_nH, data_T, emissivities, xray_luminosities

        elif self.weighting == 'mass':
            weighting = sw_data.gas.masses[index]

        elif self.weighting == 'volume':
            volume_proxy = sw_data.gas.masses[index] / sw_data.gas.densities[index]
            weighting = volume_proxy
            del volume_proxy

        gas_mass = sw_data.gas.masses[index]

        gas_mass_profile = histogram_unyt(
            radial_distance,
            bins=lbins,
            weights=gas_mass,
        )
        gas_mass_profile = np.nancumsum(gas_mass_profile.value) * gas_mass_profile.units
        gas_mass_profile.convert_to_units(Solar_Mass)

        return radial_bin_centres, gas_mass_profile, m500
Beispiel #5
0
    def process_single_halo(self,
                            zoom_obj: Zoom = None,
                            path_to_snap: str = None,
                            path_to_catalogue: str = None,
                            agn_time: str = None,
                            z_agn_start: float = 0.1,
                            z_agn_end: float = 0.,
                            **kwargs):
        aperture_fraction = xlargs.aperture_percent / 100
        sw_data, vr_data = self.get_handles_from_zoom(
            zoom_obj,
            path_to_snap,
            path_to_catalogue,
            mask_radius_r500=aperture_fraction,
            **kwargs)

        m500 = vr_data.spherical_overdensities.mass_500_rhocrit[0].to('Msun')
        r500 = vr_data.spherical_overdensities.r_500_rhocrit[0].to('Mpc')

        aperture_fraction = aperture_fraction * r500

        # Convert datasets to physical quantities
        # r500c is already in physical units
        sw_data.gas.radial_distances.convert_to_physical()
        sw_data.gas.coordinates.convert_to_physical()
        sw_data.gas.masses.convert_to_physical()
        sw_data.gas.densities.convert_to_physical()

        gamma = 5 / 3
        a_heat = sw_data.gas.last_agnfeedback_scale_factors

        if agn_time is None:

            if z_agn_start < 7.2 or z_agn_end > 0:

                index = np.where(
                    (sw_data.gas.radial_distances < aperture_fraction)
                    & (sw_data.gas.fofgroup_ids == 1)
                    & (a_heat > (1 / (z_agn_start + 1)))
                    & (a_heat < (1 / (z_agn_end + 1))))[0]
            else:
                index = np.where(
                    (sw_data.gas.radial_distances < aperture_fraction)
                    & (sw_data.gas.fofgroup_ids == 1))[0]

            number_density = (sw_data.gas.densities / mh).to(
                'cm**-3').value[index] * primordial_hydrogen_mass_fraction
            temperature = sw_data.gas.temperatures.to('K').value[index]

        elif agn_time == 'before':

            index = np.where(
                (sw_data.gas.radial_distances < aperture_fraction)
                & (sw_data.gas.fofgroup_ids == 1)
                & (a_heat > (1 / (z_agn_start + 1)))
                & (a_heat < (1 / (z_agn_end + 1)))
                & (sw_data.gas.densities_before_last_agnevent > 0))[0]

            density = sw_data.gas.densities_before_last_agnevent[index]
            number_density = (density / mh).to(
                'cm**-3').value * primordial_hydrogen_mass_fraction
            A = sw_data.gas.entropies_before_last_agnevent[
                index] * sw_data.units.mass
            temperature = mean_molecular_weight * (gamma - 1) * (A * density**(
                5 / 3 - 1)) / (gamma - 1) * mh / boltzmann_constant
            temperature = temperature.to('K').value

        elif agn_time == 'after':

            index = np.where((sw_data.gas.radial_distances < aperture_fraction)
                             & (sw_data.gas.fofgroup_ids == 1)
                             & (a_heat > (1 / (z_agn_start + 1)))
                             & (a_heat < (1 / (z_agn_end + 1)))
                             & (sw_data.gas.densities_at_last_agnevent > 0))[0]

            density = sw_data.gas.densities_at_last_agnevent[index]
            number_density = (density / mh).to(
                'cm**-3').value * primordial_hydrogen_mass_fraction
            A = sw_data.gas.entropies_at_last_agnevent[
                index] * sw_data.units.mass
            temperature = mean_molecular_weight * (gamma - 1) * (A * density**(
                5 / 3 - 1)) / (gamma - 1) * mh / boltzmann_constant
            temperature = temperature.to('K').value

        agn_flag = sw_data.gas.heated_by_agnfeedback[index]
        snii_flag = sw_data.gas.heated_by_sniifeedback[index]
        agn_flag = agn_flag > 0
        snii_flag = snii_flag > 0

        # Calculate the critical density for the cross-hair marker
        rho_crit = unyt_quantity(
            sw_data.metadata.cosmology.critical_density(
                sw_data.metadata.z).value, 'g/cm**3').to('Msun/Mpc**3')
        nH_500 = (primordial_hydrogen_mass_fraction * Cosmology().fb *
                  rho_crit * 500 / mh).to('cm**-3')

        x = number_density
        y = temperature

        print("Number of particles being plotted", len(x))

        # Set the limits of the figure.
        assert (x > 0).all(), f"Found negative value(s) in x: {x[x <= 0]}"
        assert (y > 0).all(), f"Found negative value(s) in y: {y[y <= 0]}"

        # density_bounds = [1e-6, 1e4]  # in nh/cm^3
        # temperature_bounds = [1e3, 1e10]  # in K
        density_bounds = [1e-6, 1]  # in nh/cm^3
        temperature_bounds = [1e6, 1e10]  # in K
        bins = 256

        # Make the norm object to define the image stretch
        density_bins = np.logspace(np.log10(density_bounds[0]),
                                   np.log10(density_bounds[1]), bins)
        temperature_bins = np.logspace(np.log10(temperature_bounds[0]),
                                       np.log10(temperature_bounds[1]), bins)

        fig = plt.figure(figsize=(5, 5))
        gs = fig.add_gridspec(2, 2, hspace=0.1, wspace=0.2)
        axes = gs.subplots(sharex='col', sharey='row')

        for ax in axes.flat:
            ax.loglog()

            # Draw cross-hair marker
            T500 = (G * mean_molecular_weight * m500 * mp / r500 / 2 /
                    boltzmann_constant).to('K').value
            ax.hlines(y=T500,
                      xmin=nH_500 / 3,
                      xmax=nH_500 * 3,
                      colors='k',
                      linestyles='-',
                      lw=1)
            ax.vlines(x=nH_500,
                      ymin=T500 / 5,
                      ymax=T500 * 5,
                      colors='k',
                      linestyles='-',
                      lw=1)
            K500 = (T500 * K * boltzmann_constant /
                    (3 * m500 * Cosmology().fb /
                     (4 * np.pi * r500**3 * mp))**(2 / 3)).to('keV*cm**2')

            # Make the norm object to define the image stretch
            contour_density_bins = np.logspace(
                np.log10(density_bounds[0]) - 0.5,
                np.log10(density_bounds[1]) + 0.5, bins * 4)
            contour_temperature_bins = np.logspace(
                np.log10(temperature_bounds[0]) - 0.5,
                np.log10(temperature_bounds[1]) + 0.5, bins * 4)

            draw_k500(ax, contour_density_bins, contour_temperature_bins, K500)
            draw_adiabats(ax, contour_density_bins, contour_temperature_bins)
            draw_cooling_contours(ax, contour_density_bins,
                                  contour_temperature_bins)

            # Star formation threshold
            ax.axvline(0.1, color='k', linestyle=':', lw=1, zorder=0)

        # PLOT ALL PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x, y, bins=[density_bins, temperature_bins])

        vmax = np.max(H) + 1
        mappable = axes[0, 0].pcolormesh(density_edges,
                                         temperature_edges,
                                         H.T,
                                         norm=LogNorm(vmin=1, vmax=vmax),
                                         cmap='Greys_r')
        # create an axes on the right side of ax. The width of cax will be 5%
        # of ax and the padding between cax and ax will be fixed at 0.05 inch.
        divider = make_axes_locatable(axes[0, 0])
        cax = divider.append_axes("right", size="3%", pad=0.)
        cbar = plt.colorbar(mappable, ax=axes[0, 0], cax=cax)
        ticklab = cbar.ax.get_yticklabels()
        ticks = cbar.ax.get_yticks()
        for i, (t, l) in enumerate(zip(ticks, ticklab)):
            if t < 100:
                ticklab[i] = f'{int(t):d}'
            else:
                ticklab[i] = f'$10^{{{int(np.log10(t)):d}}}$'
        cbar.ax.set_yticklabels(ticklab)

        txt = AnchoredText("All particles",
                           loc="upper right",
                           pad=0.4,
                           borderpad=0,
                           prop={"fontsize": 8})
        axes[0, 0].add_artist(txt)

        # PLOT SN HEATED PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x[(snii_flag & ~agn_flag)],
            y[(snii_flag & ~agn_flag)],
            bins=[density_bins, temperature_bins])

        if (H > 0).any():
            vmax = np.max(H) + 1
            mappable = axes[0, 1].pcolormesh(density_edges,
                                             temperature_edges,
                                             H.T,
                                             norm=LogNorm(vmin=1, vmax=vmax),
                                             cmap='Greens_r',
                                             alpha=0.6)
            divider = make_axes_locatable(axes[0, 1])
            cax = divider.append_axes("right", size="3%", pad=0.)
            cbar = plt.colorbar(mappable, ax=axes[0, 1], cax=cax)
            ticklab = cbar.ax.get_yticklabels()
            ticks = cbar.ax.get_yticks()
            for i, (t, l) in enumerate(zip(ticks, ticklab)):
                if t < 100:
                    ticklab[i] = f'{int(t):d}'
                else:
                    ticklab[i] = f'$10^{{{int(np.log10(t)):d}}}$'
            cbar.ax.set_yticklabels(ticklab)

        # Heating temperatures
        axes[0, 1].axhline(10**7.5, color='k', linestyle='--', lw=1, zorder=0)
        txt = AnchoredText("SNe heated only",
                           loc="upper right",
                           pad=0.4,
                           borderpad=0,
                           prop={"fontsize": 8})
        axes[0, 1].add_artist(txt)

        # PLOT AGN HEATED PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x[(agn_flag & ~snii_flag)],
            y[(agn_flag & ~snii_flag)],
            bins=[density_bins, temperature_bins])
        vmax = np.max(H) + 1
        mappable = axes[1, 1].pcolormesh(density_edges,
                                         temperature_edges,
                                         H.T,
                                         norm=LogNorm(vmin=1, vmax=vmax),
                                         cmap='Reds_r',
                                         alpha=0.6)
        divider = make_axes_locatable(axes[1, 1])
        cax = divider.append_axes("right", size="3%", pad=0.)
        cbar = plt.colorbar(mappable, ax=axes[1, 1], cax=cax)
        ticklab = cbar.ax.get_yticklabels()
        ticks = cbar.ax.get_yticks()
        for i, (t, l) in enumerate(zip(ticks, ticklab)):
            if t < 100:
                ticklab[i] = f'{int(t):d}'
            else:
                ticklab[i] = f'$10^{{{int(np.log10(t)):d}}}$'
        cbar.ax.set_yticklabels(ticklab)

        txt = AnchoredText("AGN heated only",
                           loc="upper right",
                           pad=0.4,
                           borderpad=0,
                           prop={"fontsize": 8})
        axes[1, 1].add_artist(txt)
        # Heating temperatures
        axes[1, 1].axhline(10**8.5, color='k', linestyle='--', lw=1, zorder=0)

        # PLOT AGN+SN HEATED PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x[(agn_flag & snii_flag)],
            y[(agn_flag & snii_flag)],
            bins=[density_bins, temperature_bins])
        vmax = np.max(H) + 1
        mappable = axes[1, 0].pcolormesh(density_edges,
                                         temperature_edges,
                                         H.T,
                                         norm=LogNorm(vmin=1, vmax=vmax),
                                         cmap='Purples_r',
                                         alpha=0.6)
        divider = make_axes_locatable(axes[1, 0])
        cax = divider.append_axes("right", size="3%", pad=0.)
        cbar = plt.colorbar(mappable, ax=axes[1, 0], cax=cax)
        ticklab = cbar.ax.get_yticklabels()
        ticks = cbar.ax.get_yticks()
        for i, (t, l) in enumerate(zip(ticks, ticklab)):
            if t < 100:
                ticklab[i] = f'{int(t):d}'
            else:
                ticklab[i] = f'$10^{{{int(np.log10(t)):d}}}$'
        cbar.ax.set_yticklabels(ticklab)

        txt = AnchoredText("AGN and SNe heated",
                           loc="upper right",
                           pad=0.4,
                           borderpad=0,
                           prop={"fontsize": 8})
        axes[1, 0].add_artist(txt)
        # Heating temperatures
        axes[1, 0].axhline(10**8.5, color='k', linestyle='--', lw=1, zorder=0)
        axes[1, 0].axhline(10**7.5, color='k', linestyle='--', lw=1, zorder=0)

        fig.text(0.5, 0.04, r"Density [$n_H$ cm$^{-3}$]", ha='center')
        fig.text(0.04,
                 0.5,
                 r"Temperature [K]",
                 va='center',
                 rotation='vertical')

        z_agn_recent_text = (
            f"Selecting gas heated between {z_agn_start:.1f} < z < {z_agn_end:.1f} (relevant to AGN plot only)\n"
            f"({1 / (z_agn_start + 1):.2f} < a < {1 / (z_agn_end + 1):.2f})\n")
        if agn_time is not None:
            z_agn_recent_text = (
                f"Selecting gas {agn_time:s} heated between {z_agn_start:.1f} < z < {z_agn_end:.1f}\n"
                f"({1 / (z_agn_start + 1):.2f} < a < {1 / (z_agn_end + 1):.2f})\n"
            )

        fig.suptitle((
            f"Aperture = {xlargs.aperture_percent / 100:.2f} $R_{{500}}$\t\t"
            f"$z = {calibration_zooms.redshift_from_index(xlargs.redshift_index):.2f}$\n"
            f"{z_agn_recent_text:s}"
            f"Central FoF group only"),
                     fontsize=7)

        if not xlargs.quiet:
            plt.show()
        # fig.savefig(
        #     f'{calibration_zooms.output_directory}/density_temperature_{xlargs.redshift_index:04d}.png',
        #     dpi=300
        # )

        plt.close()
Beispiel #6
0
    def process_single_halo(
            self,
            zoom_obj: Zoom = None,
            path_to_snap: str = None,
            path_to_catalogue: str = None
    ):

        sw_data, vr_data = self.get_handles_from_zoom(
            zoom_obj,
            path_to_snap,
            path_to_catalogue,
            mask_radius_r500=self.max_radius_r500
        )
        fb = Cosmology().fb0
        setattr(self, 'fb', fb)
        setattr(self, 'z', sw_data.metadata.z)

        try:
            r500 = vr_data.spherical_overdensities.r_500_rhocrit[0].to('Mpc')
            m500 = vr_data.spherical_overdensities.mass_500_rhocrit[0].to('Msun')
        except AttributeError as err:
            print(f'[{self.__class__.__name__}] {err}')

            spherical_overdensity = SODelta500(
                path_to_snap=path_to_snap,
                path_to_catalogue=path_to_catalogue,
            )
            r500 = spherical_overdensity.get_r500()
            m500 = spherical_overdensity.get_m500()

        if xlargs.mass_estimator == 'hse':
            true_hse = HydrostaticEstimator(
                path_to_catalogue=path_to_catalogue,
                path_to_snap=path_to_snap,
                profile_type='true',
                diagnostics_on=False
            )
            true_hse.interpolate_hse(density_contrast=500.)
            r500 = true_hse.r500hse
            m500 = true_hse.m500hse

        # Define radial bins
        lbins = np.logspace(-2, np.log10(self.max_radius_r500), 51) * dimensionless
        radial_bin_centres = 10 ** (0.5 * np.log10(lbins[1:] * lbins[:-1])) * dimensionless

        # Compute gas mass profile
        sw_data.gas.radial_distances.convert_to_physical()
        sw_data.gas.masses.convert_to_physical()
        masses = sw_data.gas.masses

        try:
            temperatures = sw_data.gas.temperatures
        except AttributeError as err:
            print(f'[{self.__class__.__name__}] {err}')
            if xlargs.debug:
                print(f"[{self.__class__.__name__}] Computing gas temperature from internal energies.")
            temperatures = sw_data.gas.internal_energies * (gamma - 1) * mean_molecular_weight * mh / kb

        try:
            fof_ids = sw_data.gas.fofgroup_ids

            # Select all particles within sphere
            mask = np.where(
                (sw_data.gas.radial_distances <= self.max_radius_r500 * r500) &
                (fof_ids == 1) &
                (temperatures > Tcut_halogas)
            )[0]
            del fof_ids
        except AttributeError as err:
            print(err)
            print(f"[{self.__class__.__name__}] Select particles only by radial distance.")
            mask = np.where(
                (sw_data.gas.radial_distances <= self.max_radius_r500 * r500) &
                (temperatures > Tcut_halogas)
            )[0]

        radial_distances = sw_data.gas.radial_distances[mask] / r500
        assert (radial_distances >= 0).all()
        masses = unyt_array(masses, sw_data.units.mass)[mask]
        assert (masses >= 0).all()
        del mask

        mass_weights = histogram_unyt(radial_distances, bins=lbins, weights=masses)
        cumulative_gas_mass_profile = np.nancumsum(mass_weights.value) * masses.units

        # Compute total mass profile
        sw_data.gas.radial_distances.convert_to_physical()
        sw_data.dark_matter.radial_distances.convert_to_physical()

        radial_distances_collect = [
            sw_data.gas.radial_distances,
            sw_data.dark_matter.radial_distances,
        ]
        if sw_data.metadata.n_stars > 0:
            sw_data.stars.radial_distances.convert_to_physical()
            radial_distances_collect.append(sw_data.stars.radial_distances)
        elif xlargs.debug:
            print(f"[{self.__class__.__name__}] stars not detected.")

        if sw_data.metadata.n_black_holes > 0:
            sw_data.black_holes.radial_distances.convert_to_physical()
            radial_distances_collect.append(sw_data.black_holes.radial_distances)
        elif xlargs.debug:
            print(f"[{self.__class__.__name__}] black_holes not detected.")

        radial_distances = np.concatenate(radial_distances_collect) * sw_data.units.length / r500

        sw_data.gas.masses.convert_to_physical()
        sw_data.dark_matter.masses.convert_to_physical()
        masses_collect = [
            sw_data.gas.masses,
            sw_data.dark_matter.masses,
        ]
        if sw_data.metadata.n_stars > 0:
            sw_data.stars.masses.convert_to_physical()
            masses_collect.append(sw_data.stars.masses)
        elif xlargs.debug:
            print(f"[{self.__class__.__name__}] stars not detected.")

        if sw_data.metadata.n_black_holes > 0:
            sw_data.black_holes.subgrid_masses.convert_to_physical()
            masses_collect.append(sw_data.black_holes.subgrid_masses)
        elif xlargs.debug:
            print(f"[{self.__class__.__name__}] black_holes not detected.")

        masses = np.concatenate(masses_collect)

        try:
            fof_ids_collect = [
                sw_data.gas.fofgroup_ids,
                sw_data.dark_matter.fofgroup_ids,
            ]
            if sw_data.metadata.n_stars > 0:
                fof_ids_collect.append(sw_data.stars.fofgroup_ids)
            elif xlargs.debug:
                print(f"[{self.__class__.__name__}] stars not detected.")

            if sw_data.metadata.n_black_holes > 0:
                fof_ids_collect.append(sw_data.black_holes.fofgroup_ids)
            elif xlargs.debug:
                print(f"[{self.__class__.__name__}] black_holes not detected.")

            fof_ids = np.concatenate(fof_ids_collect)

            # Select all particles within sphere
            mask = np.where(
                (radial_distances <= self.max_radius_r500) &
                (fof_ids == 1)
            )[0]

            del fof_ids

        except AttributeError as err:
            print(err)
            print(f"[{self.__class__.__name__}] Select particles only by radial distance.")
            mask = np.where(radial_distances <= self.max_radius_r500)[0]

        mass_weights = histogram_unyt(radial_distances[mask], bins=lbins, weights=masses[mask])
        cumulative_mass_profile = np.nancumsum(mass_weights.value) * sw_data.units.mass

        return radial_bin_centres, cumulative_gas_mass_profile, cumulative_mass_profile, m500 * fb
Beispiel #7
0
    def total_mass_profiles(self):

        # Read in halo properties from catalog
        vr_catalogue_handle = vr.load(self.zoom.catalogue_properties_path)
        a = vr_catalogue_handle.a
        self.r500c = vr_catalogue_handle.spherical_overdensities.r_500_rhocrit[
            0].to('Mpc')
        self.r2500c = vr_catalogue_handle.spherical_overdensities.r_2500_rhocrit[
            0].to('Mpc')
        XPotMin = vr_catalogue_handle.positions.xcminpot[0].to('Mpc')
        YPotMin = vr_catalogue_handle.positions.ycminpot[0].to('Mpc')
        ZPotMin = vr_catalogue_handle.positions.zcminpot[0].to('Mpc')

        # Read in gas particles and parse densities and temperatures
        mask = sw.mask(self.zoom.snapshot_path, spatial_only=False)
        region = [[(XPotMin - 1.5 * self.r500c) / a,
                   (XPotMin + 1.5 * self.r500c) / a],
                  [(YPotMin - 1.5 * self.r500c) / a,
                   (YPotMin + 1.5 * self.r500c) / a],
                  [(ZPotMin - 1.5 * self.r500c) / a,
                   (ZPotMin + 1.5 * self.r500c) / a]]

        mask.constrain_spatial(region)
        mask.constrain_mask("gas", "temperatures",
                            Tcut_halogas * mask.units.temperature,
                            1.e12 * mask.units.temperature)
        data = sw.load(self.zoom.snapshot_path, mask=mask)
        self.fbary = Cosmology().get_baryon_fraction(data.metadata.z)

        # Convert datasets to physical quantities
        # r500c is already in physical units
        data.gas.coordinates.convert_to_physical()
        data.gas.masses.convert_to_physical()
        data.gas.temperatures.convert_to_physical()
        data.gas.densities.convert_to_physical()
        data.dark_matter.coordinates.convert_to_physical()
        data.dark_matter.masses.convert_to_physical()
        data.stars.coordinates.convert_to_physical()
        data.stars.masses.convert_to_physical()

        # Set bounds for the radial profiles
        radius_bounds = [0.15, 1.5]
        lbins = np.logspace(np.log10(radius_bounds[0]),
                            np.log10(radius_bounds[1]),
                            true_data_nbins) * dimensionless

        shell_volume = (4 / 3 * np.pi) * self.r500c**3 * (lbins[1:]**3 -
                                                          lbins[:-1]**3)

        critical_density = unyt_quantity(
            data.metadata.cosmology.critical_density(data.metadata.z).value,
            'g/cm**3').to('Msun/Mpc**3')

        # Select hot gas within sphere and without core
        deltaX = data.gas.coordinates[:, 0] - XPotMin
        deltaY = data.gas.coordinates[:, 1] - YPotMin
        deltaZ = data.gas.coordinates[:, 2] - ZPotMin
        deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2) / self.r500c

        # Keep only particles inside 1.5 R500crit
        index = np.where(deltaR < radius_bounds[1])[0]
        central_mass = sum(
            data.gas.masses[np.where(deltaR < radius_bounds[0])[0]])
        mass_weights, _ = histogram_unyt(deltaR[index],
                                         bins=lbins,
                                         weights=data.gas.masses[index])

        self.density_profile_input = mass_weights / shell_volume / critical_density

        # Select DM within sphere and without core
        deltaX = data.dark_matter.coordinates[:, 0] - XPotMin
        deltaY = data.dark_matter.coordinates[:, 1] - YPotMin
        deltaZ = data.dark_matter.coordinates[:, 2] - ZPotMin
        deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2) / self.r500c

        # Keep only particles inside 1.5 R500crit
        index = np.where(deltaR < radius_bounds[1])[0]
        central_mass += sum(
            data.dark_matter.masses[np.where(deltaR < radius_bounds[0])[0]])
        _mass_weights, _ = histogram_unyt(
            deltaR[index], bins=lbins, weights=data.dark_matter.masses[index])
        mass_weights += _mass_weights

        # Select stars within sphere and without core
        deltaX = data.stars.coordinates[:, 0] - XPotMin
        deltaY = data.stars.coordinates[:, 1] - YPotMin
        deltaZ = data.stars.coordinates[:, 2] - ZPotMin
        deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2) / self.r500c

        # Keep only particles inside 1.5 R500crit
        index = np.where(deltaR < radius_bounds[1])[0]
        central_mass += sum(
            data.stars.masses[np.where(deltaR < radius_bounds[0])[0]])
        _mass_weights, _ = histogram_unyt(deltaR[index],
                                          bins=lbins,
                                          weights=data.stars.masses[index])
        mass_weights += _mass_weights

        # Replace zeros with Nans
        mass_weights[mass_weights == 0] = np.nan
        cumulative_mass = central_mass + cumsum_unyt(mass_weights)

        self.radial_bin_centres_input = 10.0**(
            0.5 * np.log10(lbins[1:] * lbins[:-1])) * dimensionless
        self.cumulative_mass_input = cumulative_mass.to('Msun')
        self.total_density_profile_input = mass_weights / shell_volume / critical_density
Beispiel #8
0
    def load_zoom_profiles(self):
        # Read in halo properties from catalog
        vr_catalogue_handle = vr.load(self.catalog_file)
        a = vr_catalogue_handle.a

        try:
            self.m200c = vr_catalogue_handle.masses.mass_200crit[0].to('Msun')
            self.r200c = vr_catalogue_handle.radii.r_200crit[0].to('Mpc')
        except AttributeError as err:
            print(f'[{self.__class__.__name__}] {err}')

            spherical_overdensity = SODelta200(
                path_to_snap=self.snapshot_file,
                path_to_catalogue=self.catalog_file,
            )
            self.m200c = spherical_overdensity.get_m200()
            self.r200c = spherical_overdensity.get_r200()

        try:
            self.m500c = vr_catalogue_handle.spherical_overdensities.mass_500_rhocrit[
                0].to('Msun')
            self.r500c = vr_catalogue_handle.spherical_overdensities.r_500_rhocrit[
                0].to('Mpc')
        except AttributeError as err:
            print(f'[{self.__class__.__name__}] {err}')

            spherical_overdensity = SODelta500(
                path_to_snap=self.snapshot_file,
                path_to_catalogue=self.catalog_file,
            )
            self.m500c = spherical_overdensity.get_m500()
            self.r500c = spherical_overdensity.get_r500()

        try:
            self.r2500c = vr_catalogue_handle.spherical_overdensities.r_2500_rhocrit[
                0].to('Mpc')
            self.m2500c = vr_catalogue_handle.spherical_overdensities.mass_2500_rhocrit[
                0].to('Msun')
        except AttributeError as err:
            print(f'[{self.__class__.__name__}] {err}')

            spherical_overdensity = SODelta2500(
                path_to_snap=self.snapshot_file,
                path_to_catalogue=self.catalog_file,
            )
            self.m2500c = spherical_overdensity.get_m2500()
            self.r2500c = spherical_overdensity.get_r2500()

        XPotMin = vr_catalogue_handle.positions.xcminpot[0].to('Mpc')
        YPotMin = vr_catalogue_handle.positions.ycminpot[0].to('Mpc')
        ZPotMin = vr_catalogue_handle.positions.zcminpot[0].to('Mpc')

        # Read in gas particles and parse densities and temperatures
        mask = sw.mask(self.snapshot_file, spatial_only=True)
        region = [[(XPotMin - 1.5 * self.r500c) / a,
                   (XPotMin + 1.5 * self.r500c) / a],
                  [(YPotMin - 1.5 * self.r500c) / a,
                   (YPotMin + 1.5 * self.r500c) / a],
                  [(ZPotMin - 1.5 * self.r500c) / a,
                   (ZPotMin + 1.5 * self.r500c) / a]]
        mask.constrain_spatial(region)
        data = sw.load(self.snapshot_file, mask=mask)

        try:
            _ = data.gas.temperatures
        except AttributeError as err:
            print(f'[{self.__class__.__name__}] {err}')
            if xlargs.debug:
                print(
                    f"[{self.__class__.__name__}] Computing gas temperature from internal energies."
                )
            data.gas.temperatures = data.gas.internal_energies * (
                gamma - 1) * mean_molecular_weight * mh / kb

        try:
            _ = data.gas.fofgroup_ids
        except AttributeError as err:
            print(f'[{self.__class__.__name__}] {err}')
            if xlargs.debug:
                print(
                    f"[{self.__class__.__name__}] Select particles only by radial distance."
                )
            data.gas.fofgroup_ids = np.ones_like(data.gas.densities)

        self.fbary = Cosmology().fb0

        # Convert datasets to physical quantities
        # r500c is already in physical units
        data.gas.coordinates.convert_to_physical()
        data.gas.masses.convert_to_physical()
        data.gas.densities.convert_to_physical()
        data.dark_matter.coordinates.convert_to_physical()
        data.dark_matter.masses.convert_to_physical()
        # data.stars.coordinates.convert_to_physical()
        # data.stars.masses.convert_to_physical()

        # Calculate the critical density for the density profile
        self.rho_crit = unyt_quantity(
            data.metadata.cosmology.critical_density(data.metadata.z).value,
            'g/cm**3').to('Msun/Mpc**3')

        # Select hot gas within sphere and without core
        deltaX = data.gas.coordinates[:, 0] - XPotMin
        deltaY = data.gas.coordinates[:, 1] - YPotMin
        deltaZ = data.gas.coordinates[:, 2] - ZPotMin
        deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2)

        # Set bounds for the radial profiles
        radius_bounds = [0.15, 1.5]

        # Keep only particles inside 5 R500crit
        index = np.where(deltaR < radius_bounds[1] * self.r500c)[0]
        radial_distance_scaled = deltaR[index] / self.r500c
        assert radial_distance_scaled.units == dimensionless
        gas_masses = data.gas.masses[index]
        gas_temperatures = data.gas.temperatures[index]
        gas_mass_weighted_temperatures = gas_temperatures * gas_masses

        if not self.excise_core:
            # Compute convergence radius and set as inner limit
            gas_convergence_radius = convergence_radius(
                deltaR[index], gas_masses.to('Msun'),
                self.rho_crit.to('Msun/Mpc**3')) / self.r500c
            radius_bounds[0] = gas_convergence_radius.value

        lbins = np.logspace(np.log10(radius_bounds[0]),
                            np.log10(radius_bounds[1]),
                            true_data_nbins) * radial_distance_scaled.units

        mass_weights, bin_edges = histogram_unyt(radial_distance_scaled,
                                                 bins=lbins,
                                                 weights=gas_masses)

        # Replace zeros with Nans
        mass_weights[mass_weights == 0] = np.nan

        # Set the radial bins as object attribute
        self.radial_bin_centres = 10.0**(
            0.5 * np.log10(lbins[1:] * lbins[:-1])) * dimensionless
        self.radial_bin_edges = lbins

        # Compute the radial gas density profile
        volume_shell = (4. * np.pi / 3.) * (self.r500c**3) * (
            (bin_edges[1:])**3 - (bin_edges[:-1])**3)
        self.density_profile = mass_weights / volume_shell / self.rho_crit
        assert self.density_profile.units == dimensionless

        # Compute the radial mass-weighted temperature profile
        hist, _ = histogram_unyt(radial_distance_scaled,
                                 bins=lbins,
                                 weights=gas_mass_weighted_temperatures)
        hist /= mass_weights
        self.temperature_profile = (hist * kb).to('keV')
Beispiel #9
0
from register import set_mnras_stylesheet, xlargs, default_output_directory, calibration_zooms, delete_last_line
from literature import Sun2009, Pratt2010, Croston2008, Cosmology

set_mnras_stylesheet()

# Set axes limits
fgas_bounds = [10 ** (-2.5), 1]  # dimensionless
k_bounds = [1e-2, 7]  # K500 units
r_bounds = [0.01, 1]  # R500 units
t_bounds = [0.4, 5]  # keV units

fig = plt.figure(figsize=(4.5, 5), constrained_layout=True)
gs = fig.add_gridspec(3, 2, hspace=0.35, wspace=0.7, height_ratios=[0.05, 1, 1], width_ratios=[1, 1])
axes = gs.subplots()

cosmology = Cosmology()
redshift = calibration_zooms.redshift_from_index(xlargs.redshift_index)

temperature_obj = MWTemperatures()
temperatures_dataframe = temperature_obj.process_catalogue()
data_color = np.log10(np.array([(i * kb).to('keV').v for i in temperatures_dataframe['T0p75r500_nocore']]))
temperatures_dataframe['color'] = (data_color - np.log10(t_bounds[0])) / (np.log10(t_bounds[1]) - np.log10(t_bounds[0]))

gas_profile_obj = EntropyFgasSpace(max_radius_r500=1.)
gas_profiles_dataframe = gas_profile_obj.process_catalogue()

entropy_profile_obj = EntropyProfiles(max_radius_r500=1)
entropy_profiles_dataframe = entropy_profile_obj.process_catalogue()

# Merge all catalogues into one
catalogue = temperatures_dataframe
Beispiel #10
0
    def process_single_halo(
        self,
        zoom_obj: Zoom = None,
        path_to_snap: str = None,
        path_to_catalogue: str = None,
        agn_time: str = None,
        z_agn_start: float = 18,
        z_agn_end: float = 0.,
    ):
        aperture_fraction = xlargs.aperture_percent / 100

        sw_data, vr_data = self.get_handles_from_zoom(zoom_obj,
                                                      path_to_snap,
                                                      path_to_catalogue,
                                                      mask_radius_r500=5)

        try:
            m500 = vr_data.spherical_overdensities.mass_500_rhocrit[0].to(
                'Msun')
            r500 = vr_data.spherical_overdensities.r_500_rhocrit[0].to('Mpc')
        except AttributeError as err:
            print(err)
            print(
                f'[{self.__class__.__name__}] Launching spherical overdensity calculation...'
            )
            spherical_overdensity = SODelta500(
                path_to_snap=path_to_snap,
                path_to_catalogue=path_to_catalogue,
            )
            m500 = spherical_overdensity.get_m500()
            r500 = spherical_overdensity.get_r500()

        if xlargs.mass_estimator == 'hse':
            true_hse = HydrostaticEstimator(
                path_to_catalogue=path_to_catalogue,
                path_to_snap=path_to_snap,
                profile_type='true',
                diagnostics_on=False).interpolate_hse()
            r500 = true_hse.r500hse
            m500 = true_hse.M500hse

        aperture_fraction = aperture_fraction * r500

        # Convert datasets to physical quantities
        # r500c is already in physical units
        sw_data.gas.radial_distances.convert_to_physical()
        sw_data.gas.coordinates.convert_to_physical()
        sw_data.gas.masses.convert_to_physical()
        sw_data.gas.densities.convert_to_physical()

        activate_cooling_times = True
        try:
            cooling_times = calculate_mean_cooling_times(sw_data)
        except AttributeError as err:
            print(err)
            if xlargs.debug:
                print(
                    f'[{self.__class__.__name__}] Setting activate_cooling_times = False'
                )
            activate_cooling_times = False

        if xlargs.debug:
            print(f"[{self.__class__.__name__}] m500 = ", m500)
            print(f"[{self.__class__.__name__}] r500 = ", r500)
            print(f"[{self.__class__.__name__}] aperture_fraction = ",
                  aperture_fraction)
            print(
                f"[{self.__class__.__name__}] Number of particles being imported",
                len(sw_data.gas.densities))

        gamma = 5 / 3

        try:
            a_heat = sw_data.gas.last_agnfeedback_scale_factors
        except AttributeError as err:
            print(err)
            if xlargs.debug:
                print('Setting `last_agnfeedback_scale_factors` with 0.1.')
            a_heat = np.ones_like(sw_data.gas.masses) * 0.1

        try:
            fof_ids = sw_data.gas.fofgroup_ids
        except AttributeError as err:
            print(err)
            if xlargs.debug:
                print(
                    f"[{self.__class__.__name__}] Select particles only by radial distance."
                )
            fof_ids = np.ones_like(sw_data.gas.densities)

        try:
            temperature = sw_data.gas.temperatures
        except AttributeError as err:
            print(err)
            if xlargs.debug:
                print(
                    f"[{self.__class__.__name__}] Computing gas temperature from internal energies."
                )
            A = sw_data.gas.entropies * sw_data.units.mass
            temperature = mean_molecular_weight * (gamma - 1) * (
                A * sw_data.gas.densities**(5 / 3 - 1)) / (gamma - 1) * mh / kb

        try:
            hydrogen_fractions = sw_data.gas.element_mass_fractions.hydrogen
        except AttributeError as err:
            print(err)
            if xlargs.debug:
                print(
                    f"[{self.__class__.__name__}] Setting H fractions to primordial values."
                )
            hydrogen_fractions = np.ones_like(
                sw_data.gas.densities) * primordial_hydrogen_mass_fraction

        try:
            agn_flag = sw_data.gas.heated_by_agnfeedback
        except AttributeError as err:
            print(err)
            if xlargs.debug:
                print(
                    f"[{self.__class__.__name__}] Setting all agn_flag to zero."
                )
            agn_flag = np.zeros_like(sw_data.gas.densities)

        try:
            snii_flag = sw_data.gas.heated_by_sniifeedback
        except AttributeError as err:
            print(err)
            if xlargs.debug:
                print(
                    f"[{self.__class__.__name__}] Setting all snii_flag to zero."
                )
            snii_flag = np.zeros_like(sw_data.gas.densities)

        if agn_time is None:
            index = np.where((sw_data.gas.radial_distances < aperture_fraction)
                             & (fof_ids == 1) & (temperature > 1e5))[0]

            number_density = (sw_data.gas.densities / mh).to(
                'cm**-3').value[index] * hydrogen_fractions[index]
            temperature = temperature.to('K').value[index]

        elif agn_time == 'before':

            index = np.where(
                (sw_data.gas.radial_distances < aperture_fraction)
                & (fof_ids == 1) & (a_heat > (1 / (z_agn_start + 1)))
                & (a_heat < (1 / (z_agn_end + 1)))
                & (sw_data.gas.densities_before_last_agnevent > 0))[0]

            density = sw_data.gas.densities_before_last_agnevent[index]
            number_density = (
                density / mh).to('cm**-3').value * hydrogen_fractions[index]
            A = sw_data.gas.entropies_before_last_agnevent[
                index] * sw_data.units.mass
            temperature = mean_molecular_weight * (gamma - 1) * (
                A * density**(5 / 3 - 1)) / (gamma - 1) * mh / kb
            temperature = temperature.to('K').value

        elif agn_time == 'after':

            index = np.where((sw_data.gas.radial_distances < aperture_fraction)
                             & (fof_ids == 1) & (a_heat > (1 /
                                                           (z_agn_start + 1)))
                             & (a_heat < (1 / (z_agn_end + 1)))
                             & (sw_data.gas.densities_at_last_agnevent > 0))[0]

            density = sw_data.gas.densities_at_last_agnevent[index]
            number_density = (
                density / mh).to('cm**-3').value * hydrogen_fractions[index]
            A = sw_data.gas.entropies_at_last_agnevent[
                index] * sw_data.units.mass
            temperature = mean_molecular_weight * (gamma - 1) * (
                A * density**(5 / 3 - 1)) / (gamma - 1) * mh / kb
            temperature = temperature.to('K').value

        agn_flag = agn_flag[index]
        snii_flag = snii_flag[index]
        agn_flag = agn_flag > 0
        snii_flag = snii_flag > 0

        # Calculate the critical density for the cross-hair marker
        rho_crit = unyt_quantity(
            sw_data.metadata.cosmology.critical_density(
                sw_data.metadata.z).value, 'g/cm**3').to('Msun/Mpc**3')
        nH_500 = (primordial_hydrogen_mass_fraction * Cosmology().fb0 *
                  rho_crit * 500 / mh).to('cm**-3')

        # Entropy
        electron_number_density = (sw_data.gas.densities[index] /
                                   mh).to('cm**-3') / mean_molecular_weight
        entropy = kb * temperature * K / electron_number_density**(2 / 3)
        entropy = entropy.to('keV*cm**2')

        x = number_density
        y = temperature

        if activate_cooling_times:
            w = cooling_times[index]

        if xlargs.debug:
            print("Number of particles being plotted", len(x))

        # Set the limits of the figure.
        assert (x > 0).all(), f"Found negative value(s) in x: {x[x <= 0]}"
        assert (y > 0).all(), f"Found negative value(s) in y: {y[y <= 0]}"

        # density_bounds = [1e-6, 1e4]  # in nh/cm^3
        # temperature_bounds = [1e3, 1e10]  # in K
        density_bounds = [1e-5, 1]  # in nh/cm^3
        temperature_bounds = [1e6, 1e9]  # in K
        pdf_ybounds = [1, 10**6]
        bins = 256

        # Make the norm object to define the image stretch
        density_bins = np.logspace(np.log10(density_bounds[0]),
                                   np.log10(density_bounds[1]), bins)
        temperature_bins = np.logspace(np.log10(temperature_bounds[0]),
                                       np.log10(temperature_bounds[1]), bins)

        T500 = (G * mean_molecular_weight * m500 * mp / r500 / 2 /
                kb).to('K').value
        K500 = (T500 * K * kb /
                (3 * m500 * Cosmology().fb0 /
                 (4 * np.pi * r500**3 * mp))**(2 / 3)).to('keV*cm**2')

        # Make the norm object to define the image stretch
        contour_density_bins = np.logspace(
            np.log10(density_bounds[0]) - 0.5,
            np.log10(density_bounds[1]) + 0.5, bins * 4)
        contour_temperature_bins = np.logspace(
            np.log10(temperature_bounds[0]) - 0.5,
            np.log10(temperature_bounds[1]) + 0.5, bins * 4)

        fig = plt.figure(figsize=(8, 6), constrained_layout=True)
        gs = fig.add_gridspec(3, 4, hspace=0.35, wspace=0.7)
        axes = gs.subplots()

        for ax in [axes[0, 0], axes[0, 1], axes[0, 2], axes[1, 0], axes[1, 1]]:
            ax.loglog()
            # Draw cross-hair marker
            ax.hlines(y=T500,
                      xmin=nH_500 / 3,
                      xmax=nH_500 * 3,
                      colors='k',
                      linestyles='-',
                      lw=0.5)
            ax.vlines(x=nH_500,
                      ymin=T500 / 5,
                      ymax=T500 * 5,
                      colors='k',
                      linestyles='-',
                      lw=0.5)

            # Draw contours
            draw_k500(ax, contour_density_bins, contour_temperature_bins, K500)
            draw_adiabats(ax, contour_density_bins, contour_temperature_bins)
            draw_cooling_contours(ax,
                                  contour_density_bins,
                                  contour_temperature_bins,
                                  levels=[1, 1e2, 1e3, 1e4, 1e5],
                                  color='green')
            draw_cooling_contours(
                ax,
                contour_density_bins,
                contour_temperature_bins,
                levels=[Cosmology().age(sw_data.metadata.z).to('Myr').value],
                prefix='$t_H(z)=$',
                color='red',
                use_labels=False)

        # PLOT ALL PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x, y, bins=[density_bins, temperature_bins])
        draw_2d_hist(axes[0, 0], density_edges, temperature_edges, H,
                     'Greys_r', "All particles")

        # PLOT SN HEATED PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x[(snii_flag & ~agn_flag)],
            y[(snii_flag & ~agn_flag)],
            bins=[density_bins, temperature_bins])
        draw_2d_hist(axes[0, 1], density_edges, temperature_edges, H,
                     'Greens_r', "SNe heated only")
        axes[0, 1].axhline(10**7.5, color='k', linestyle='--', lw=1, zorder=0)

        # PLOT NOT HEATED PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x[(~snii_flag & ~agn_flag)],
            y[(~snii_flag & ~agn_flag)],
            bins=[density_bins, temperature_bins])
        draw_2d_hist(axes[0, 2], density_edges, temperature_edges, H,
                     'Greens_r', "Not heated")

        # PLOT AGN HEATED PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x[(agn_flag & ~snii_flag)],
            y[(agn_flag & ~snii_flag)],
            bins=[density_bins, temperature_bins])
        draw_2d_hist(axes[1, 1], density_edges, temperature_edges, H, 'Reds_r',
                     "AGN heated only")
        axes[1, 1].axhline(10**8.5, color='k', linestyle='--', lw=1, zorder=0)

        # PLOT AGN+SN HEATED PARTICLES ===============================================
        H, density_edges, temperature_edges = np.histogram2d(
            x[(agn_flag & snii_flag)],
            y[(agn_flag & snii_flag)],
            bins=[density_bins, temperature_bins])
        draw_2d_hist(axes[1, 0], density_edges, temperature_edges, H,
                     'Purples_r', "AGN and SNe heated")
        axes[1, 0].axhline(10**8.5, color='k', linestyle='--', lw=1, zorder=0)
        axes[1, 0].axhline(10**7.5, color='k', linestyle='--', lw=1, zorder=0)

        bins = np.linspace(0., 5.5, 51)

        axes[2, 0].clear()
        axes[2, 0].set_xscale('linear')
        axes[2, 0].set_yscale('log')
        if activate_cooling_times:
            axes[2, 0].hist(w, bins=bins, histtype='step', label='All')
            axes[2, 0].hist(w[(agn_flag & snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='AGN & SN')
            axes[2, 0].hist(w[(agn_flag & ~snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='AGN')
            axes[2, 0].hist(w[(~agn_flag & snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='SN')
            axes[2, 0].hist(w[(~agn_flag & ~snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='Not heated')
        axes[2, 0].axvline(np.log10(Cosmology().age(
            sw_data.metadata.z).to('Myr').value),
                           color='k',
                           linestyle='--',
                           lw=0.5,
                           zorder=0)
        axes[2, 0].set_xlabel(f"$\log_{{10}}$(Cooling time [Myr])")
        axes[2, 0].set_ylabel('Number of particles')
        axes[2, 0].set_ylim(pdf_ybounds)
        axes[2, 0].legend(loc="upper left")

        if activate_cooling_times:
            hydrogen_fraction = sw_data.gas.element_mass_fractions.hydrogen[
                index]
        bins = np.linspace(0, 1, 51)

        axes[2, 1].clear()
        axes[2, 1].set_xscale('linear')
        axes[2, 1].set_yscale('log')
        if activate_cooling_times:
            axes[2, 1].hist(hydrogen_fraction,
                            bins=bins,
                            histtype='step',
                            label='All')
            axes[2, 1].hist(hydrogen_fraction[(agn_flag & snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='AGN & SN')
            axes[2, 1].hist(hydrogen_fraction[(agn_flag & ~snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='AGN')
            axes[2, 1].hist(hydrogen_fraction[(~agn_flag & snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='SN')
            axes[2, 1].hist(hydrogen_fraction[(~agn_flag & ~snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='Not heated')
        axes[2, 1].set_xlabel("Hydrogen fraction")
        axes[2, 1].set_ylabel('Number of particles')
        axes[2, 1].set_ylim(pdf_ybounds)

        if activate_cooling_times:
            log_gas_Z = np.log10(
                sw_data.gas.metal_mass_fractions.value[index] / 0.0133714)
        bins = np.linspace(-4, 1, 51)

        axes[2, 2].clear()
        axes[2, 2].set_xscale('linear')
        axes[2, 2].set_yscale('log')
        if activate_cooling_times:
            axes[2, 2].hist(log_gas_Z, bins=bins, histtype='step', label='All')
            axes[2, 2].hist(log_gas_Z[(agn_flag & snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='AGN & SN')
            axes[2, 2].hist(log_gas_Z[(agn_flag & ~snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='AGN')
            axes[2, 2].hist(log_gas_Z[(~agn_flag & snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='SN')
            axes[2, 2].hist(log_gas_Z[(~agn_flag & ~snii_flag)],
                            bins=bins,
                            histtype='step',
                            label='Not heated')
        axes[2, 2].axvline(0.5, color='k', linestyle='--', lw=0.5, zorder=0)
        axes[2, 2].set_xlabel(f"$\log_{{10}}$(Metallicity [Z$_\odot$])")
        axes[2, 2].set_ylabel('Number of particles')
        axes[2, 2].set_ylim(pdf_ybounds)

        bins = np.logspace(np.log10(density_edges.min()),
                           np.log10(density_edges.max()), 51)

        axes[0, 3].clear()
        axes[0, 3].set_xscale('log')
        axes[0, 3].set_yscale('log')
        axes[0, 3].hist(x, bins=bins, histtype='step', label='All')
        axes[0, 3].hist(x[(agn_flag & snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='AGN & SN')
        axes[0, 3].hist(x[(agn_flag & ~snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='AGN')
        axes[0, 3].hist(x[(~agn_flag & snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='SN')
        axes[0, 3].hist(x[(~agn_flag & ~snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='Not heated')
        axes[0, 3].set_xlabel(f"Density [$n_H$ cm$^{{-3}}$]")
        axes[0, 3].set_ylabel('Number of particles')
        axes[0, 3].set_ylim(pdf_ybounds)
        axes[0, 3].legend()

        bins = np.logspace(np.log10(temperature_edges.min()),
                           np.log10(temperature_edges.max()), 51)

        axes[1, 3].clear()
        axes[1, 3].set_xscale('log')
        axes[1, 3].set_yscale('log')
        axes[1, 3].hist(y, bins=bins, histtype='step', label='All')
        axes[1, 3].hist(y[(agn_flag & snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='AGN & SN')
        axes[1, 3].hist(y[(agn_flag & ~snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='AGN')
        axes[1, 3].hist(y[(~agn_flag & snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='SN')
        axes[1, 3].hist(y[(~agn_flag & ~snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='Not heated')
        axes[1, 3].set_xlabel("Temperature [K]")
        axes[1, 3].set_ylabel('Number of particles')
        axes[1, 3].set_ylim(pdf_ybounds)

        bins = np.logspace(0, 4, 51)

        axes[2, 3].clear()
        axes[2, 3].set_xscale('log')
        axes[2, 3].set_yscale('log')
        axes[2, 3].hist(entropy, bins=bins, histtype='step', label='All')
        axes[2, 3].hist(entropy[(agn_flag & snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='AGN & SN')
        axes[2, 3].hist(entropy[(agn_flag & ~snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='AGN')
        axes[2, 3].hist(entropy[(~agn_flag & snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='SN')
        axes[2, 3].hist(entropy[(~agn_flag & ~snii_flag)],
                        bins=bins,
                        histtype='step',
                        label='Not heated')
        axes[2, 3].set_xlabel("Entropy [keV cm$^2$]")
        axes[2, 3].set_ylabel('Number of particles')
        axes[2, 3].set_ylim(pdf_ybounds)

        # Entropy profile
        max_radius_r500 = 4

        try:
            temperature = sw_data.gas.temperatures
        except AttributeError as err:
            print(err)
            if xlargs.debug:
                print(
                    f"[{self.__class__.__name__}] Computing gas temperature from internal energies."
                )
            A = sw_data.gas.entropies * sw_data.units.mass
            temperature = mean_molecular_weight * (gamma - 1) * (
                A * sw_data.gas.densities**(5 / 3 - 1)) / (gamma - 1) * mh / kb

        index = np.where((sw_data.gas.radial_distances < max_radius_r500 *
                          r500) & (fof_ids == 1) & (temperature > 1e5))[0]
        radial_distance = sw_data.gas.radial_distances[index] / r500
        sw_data.gas.masses = sw_data.gas.masses[index]
        temperature = temperature[index]

        # Define radial bins and shell volumes
        lbins = np.logspace(-2, np.log10(max_radius_r500),
                            51) * radial_distance.units
        radial_bin_centres = 10.0**(
            0.5 * np.log10(lbins[1:] * lbins[:-1])) * radial_distance.units
        volume_shell = (4. * np.pi / 3.) * (r500**3) * ((lbins[1:])**3 -
                                                        (lbins[:-1])**3)

        mass_weights, _ = histogram_unyt(radial_distance,
                                         bins=lbins,
                                         weights=sw_data.gas.masses)
        mass_weights[mass_weights == 0] = np.nan  # Replace zeros with Nans
        density_profile = mass_weights / volume_shell
        number_density_profile = (density_profile.to('g/cm**3') /
                                  (mp * mean_molecular_weight)).to('cm**-3')

        mass_weighted_temperatures = (temperature *
                                      kb).to('keV') * sw_data.gas.masses
        temperature_weights, _ = histogram_unyt(
            radial_distance, bins=lbins, weights=mass_weighted_temperatures)
        temperature_weights[temperature_weights ==
                            0] = np.nan  # Replace zeros with Nans
        temperature_profile = temperature_weights / mass_weights  # kBT in units of [keV]

        entropy_profile = temperature_profile / number_density_profile**(2 / 3)

        rho_crit = unyt_quantity(
            sw_data.metadata.cosmology.critical_density(
                sw_data.metadata.z).value, 'g/cm**3').to('Msun/Mpc**3')
        density_profile /= rho_crit

        kBT500 = (G * mean_molecular_weight * m500 * mp / r500 / 2).to('keV')
        K500 = (kBT500 / (3 * m500 * Cosmology().fb0 /
                          (4 * np.pi * r500**3 * mp))**(2 / 3)).to('keV*cm**2')

        axes[1, 2].plot(
            radial_bin_centres,
            entropy_profile / K500,
            linestyle='-',
            color='r',
            linewidth=1,
            alpha=1,
        )
        axes[1, 2].set_xscale('log')
        axes[1, 2].set_yscale('log')

        axes[1, 2].axvline(0.15, color='k', linestyle='--', lw=0.5, zorder=0)
        axes[1, 2].set_ylabel(r'Entropy [keV cm$^2$]')
        axes[1, 2].set_xlabel(r'$r/r_{500}$')
        # axes[1, 2].set_ylim([1, 1e4])
        axes[1, 2].set_ylim([1e-2, 5])
        axes[1, 2].set_xlim([0.01, max_radius_r500])

        # axes[1, 2].axhline(y=K500, color='k', linestyle=':', linewidth=0.5)
        # axes[1, 2].text(
        #     axes[1, 2].get_xlim()[0], K500, r'$K_{500}$',
        #     horizontalalignment='left',
        #     verticalalignment='bottom',
        #     color='k',
        #     bbox=dict(
        #         boxstyle='square,pad=10',
        #         fc='none',
        #         ec='none'
        #     )
        # )
        sun_observations = Sun2009()
        sun_observations.filter_by('M_500', 8e13, 3e14)
        sun_observations.overlay_entropy_profiles(axes=axes[1, 2],
                                                  k_units='K500adi',
                                                  markersize=1,
                                                  linewidth=0.5)
        rexcess = Pratt2010()
        bin_median, bin_perc16, bin_perc84 = rexcess.combine_entropy_profiles(
            m500_limits=(1e14 * Solar_Mass, 5e14 * Solar_Mass),
            k500_rescale=True)
        axes[1, 2].fill_between(rexcess.radial_bins,
                                bin_perc16,
                                bin_perc84,
                                color='aqua',
                                alpha=0.85,
                                linewidth=0)
        axes[1, 2].plot(rexcess.radial_bins, bin_median, c='k')

        z_agn_recent_text = (
            f"Selecting gas heated between {z_agn_start:.1f} > z > {z_agn_end:.1f} (relevant to AGN plot only)\n"
            f"({1 / (z_agn_start + 1):.2f} < a < {1 / (z_agn_end + 1):.2f})\n")
        if agn_time is not None:
            z_agn_recent_text = (
                f"Selecting gas {agn_time:s} heated between {z_agn_start:.1f} > z > {z_agn_end:.1f}\n"
                f"({1 / (z_agn_start + 1):.2f} < a < {1 / (z_agn_end + 1):.2f})\n"
            )

        fig.suptitle((
            f"{os.path.basename(path_to_snap)}\n"
            f"Aperture = {xlargs.aperture_percent / 100:.2f} $R_{{500}}$\t\t"
            f"$z = {sw_data.metadata.z:.2f}$\t\t"
            f"Age = {Cosmology().age(sw_data.metadata.z).value:.2f} Gyr\t\t"
            f"\t$M_{{500}}={latex_float(m500.value)}\\ {m500.units.latex_repr}$\n"
            f"{z_agn_recent_text:s}"
            f"Central FoF group only\t\tEstimator: {xlargs.mass_estimator}"),
                     fontsize=7)

        if not xlargs.quiet:
            plt.show()

        fig.savefig(os.path.join(
            default_output_directory,
            f"cooling_times_{os.path.basename(path_to_snap)[:-5].replace('.', 'p')}.png"
        ),
                    dpi=300)

        plt.close()
        title,
        color="k",
        ha="center",
        va="top",
        alpha=0.8,
        transform=axes.transAxes,
    )


snap, cat = find_files()

label = ' '.join(os.path.basename(snap).split('_')[1:3])

set_mnras_stylesheet()

cosmology = Cosmology()

fig = plt.figure(figsize=(6, 6), constrained_layout=True)
gs = fig.add_gridspec(2, 3, hspace=0., wspace=0.2)
axes_all = gs.subplots(sharex=True, sharey=False)

for ax in axes_all.flat:
    ax.loglog()
    ax.axvline(1, color='k', linestyle='--', lw=0.5, zorder=0, alpha=0.3)

# ===================== Entropy
axes = axes_all[0, 0]
print('Entropy')
make_title(axes, 'Entropy')
profile_obj = EntropyProfiles(max_radius_r500=2.5,
                              weighting='mass',