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 = 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()
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 process_single_halo( self, zoom_obj: Zoom = None, path_to_snap: str = None, path_to_catalogue: str = None, **kwargs ): sw_data, vr_data = self.get_handles_from_zoom(zoom_obj, path_to_snap, path_to_catalogue, **kwargs) if xlargs.mass_estimator == 'true': kwarg_parser = dict(zoom_obj=zoom_obj, path_to_snap=path_to_snap, path_to_catalogue=path_to_catalogue) try: r200 = vr_data.radii.r_200crit[0].to('Mpc') except AttributeError as err: print(err) if xlargs.debug: print(f'[{self.__class__.__name__}] Launching spherical overdensity calculation...') spherical_overdensity = SODelta200( path_to_snap=path_to_snap, path_to_catalogue=path_to_catalogue, ) r200 = spherical_overdensity.get_r200() try: r500 = vr_data.spherical_overdensities.r_500_rhocrit[0].to('Mpc') except AttributeError as err: print(err) if xlargs.debug: print(f'[{self.__class__.__name__}] Launching spherical overdensity calculation...') spherical_overdensity = SODelta500( path_to_snap=path_to_snap, path_to_catalogue=path_to_catalogue, ) r500 = spherical_overdensity.get_r500() try: r1000 = vr_data.spherical_overdensities.r_1000_rhocrit[0].to('Mpc') except AttributeError as err: print(err) if xlargs.debug: print(f'[{self.__class__.__name__}] Launching spherical overdensity calculation...') r1000 = SphericalOverdensities(density_contrast=1000).process_single_halo(**kwarg_parser)[0] try: r1500 = vr_data.spherical_overdensities.r_1500_rhocrit[0].to('Mpc') except AttributeError as err: print(err) if xlargs.debug: print(f'[{self.__class__.__name__}] Launching spherical overdensity calculation...') r1500 = SphericalOverdensities(density_contrast=1500).process_single_halo(**kwarg_parser)[0] try: r2500 = vr_data.spherical_overdensities.r_2500_rhocrit[0].to('Mpc') except AttributeError as err: print(err) if xlargs.debug: print(f'[{self.__class__.__name__}] Launching spherical overdensity calculation...') spherical_overdensity = SODelta2500( path_to_snap=path_to_snap, path_to_catalogue=path_to_catalogue, ) r2500 = spherical_overdensity.get_r2500() elif 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 ) for density_contrast in [200, 500, 1000, 1500, 2500]: true_hse.interpolate_hse(density_contrast=density_contrast) r200 = true_hse.r200hse r500 = true_hse.r500hse r1000 = true_hse.r1000hse r1500 = true_hse.r1500hse r2500 = true_hse.r2500hse sw_data.gas.radial_distances.convert_to_physical() # Select hot gas within sphere mask = np.where( (sw_data.gas.radial_distances <= 2 * r500) & (sw_data.gas.temperatures > Tcut_halogas) & (sw_data.gas.fofgroup_ids == 1) )[0] sw_data.gas.radial_distances = sw_data.gas.radial_distances[mask] sw_data.gas.masses = sw_data.gas.masses[mask] sw_data.gas.temperatures = sw_data.gas.temperatures[mask] radial_distances_scaled = sw_data.gas.radial_distances / r500 # Define radial bins and shell volumes lbins = np.linspace( radial_distances_scaled.min().value, radial_distances_scaled.max().value, 100 ) * radial_distances_scaled.units radial_bin_centres = 10.0 ** (0.5 * np.log10(lbins[1:] * lbins[:-1])) * radial_distances_scaled.units volume_shell = (4. * np.pi / 3.) * (r500 ** 3) * ((lbins[1:]) ** 3 - (lbins[:-1]) ** 3) mass_weights, _ = histogram_unyt(radial_distances_scaled, 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 = (sw_data.gas.temperatures * kb).to('keV') * sw_data.gas.masses temperature_weights, _ = histogram_unyt(radial_distances_scaled, 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) entropy_interpolate = interp1d(radial_bin_centres * r500, entropy_profile, kind='linear') k30kpc = entropy_interpolate(0.03 * Mpc) * entropy_profile.units k0p15r500 = entropy_interpolate(0.15 * r500) * entropy_profile.units k2500 = entropy_interpolate(r2500) * entropy_profile.units k1500 = entropy_interpolate(r1500) * entropy_profile.units k1000 = entropy_interpolate(r1000) * entropy_profile.units k500 = entropy_interpolate(r500) * entropy_profile.units k200 = entropy_interpolate(r200) * entropy_profile.units return k30kpc, k0p15r500, k2500, k1500, k1000, k500, k200