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()
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()
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
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()
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
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
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')
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
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',