def from_dens_and_tden(cls, rmin, rmax, density, total_density, stellar_density=None, num_points=1000): """ Construct a hydrostatic equilibrium model using gas density and total density profiles Parameters ---------- rmin : float Minimum radius of profiles in kpc. rmax : float Maximum radius of profiles in kpc. density : :class:`~cluster_generator.radial_profiles.RadialProfile` A radial profile describing the gas mass density. total_density : :class:`~cluster_generator.radial_profiles.RadialProfile` A radial profile describing the total mass density. stellar_density : :class:`~cluster_generator.radial_profiles.RadialProfile`, optional A radial profile describing the stellar mass density, if desired. num_points : integer, optional The number of points the profiles are evaluated at. """ mylog.info("Computing the profiles from density and total density.") rr = np.logspace(np.log10(rmin), np.log10(rmax), num_points, endpoint=True) fields = OrderedDict() fields["radius"] = unyt_array(rr, "kpc") fields["density"] = unyt_array(density(rr), "Msun/kpc**3") fields["total_density"] = unyt_array(total_density(rr), "Msun/kpc**3") mylog.info("Integrating total mass profile.") fields["total_mass"] = unyt_array(integrate_mass(total_density, rr), "Msun") fields["gas_mass"] = unyt_array(integrate_mass(density, rr), "Msun") fields["gravitational_field"] = -G * fields["total_mass"] / ( fields["radius"]**2) fields["gravitational_field"].convert_to_units("kpc/Myr**2") g = fields["gravitational_field"].in_units("kpc/Myr**2").v g_r = InterpolatedUnivariateSpline(rr, g) dPdr_int = lambda r: density(r) * g_r(r) mylog.info("Integrating pressure profile.") P = -integrate(dPdr_int, rr) dPdr_int2 = lambda r: density(r) * g[-1] * (rr[-1] / r)**2 P -= quad(dPdr_int2, rr[-1], np.inf, limit=100)[0] fields["pressure"] = unyt_array(P, "Msun/kpc/Myr**2") fields[ "temperature"] = fields["pressure"] * mu * mp / fields["density"] fields["temperature"].convert_to_units("keV") return cls._from_scratch(fields, stellar_density=stellar_density)
def _from_scratch(cls, fields, stellar_density=None): rr = fields["radius"].d mylog.info("Integrating gravitational potential profile.") tdens_func = InterpolatedUnivariateSpline(rr, fields["total_density"].d) gpot_profile = lambda r: tdens_func(r) * r gpot1 = fields["total_mass"] / fields["radius"] gpot2 = unyt_array(4. * np.pi * integrate(gpot_profile, rr), "Msun/kpc") fields["gravitational_potential"] = -G * (gpot1 + gpot2) fields["gravitational_potential"].convert_to_units("kpc**2/Myr**2") if "density" in fields and "gas_mass" not in fields: mylog.info("Integrating gas mass profile.") m0 = fields["density"].d[0] * rr[0]**3 / 3. fields["gas_mass"] = unyt_array( 4.0 * np.pi * cumtrapz(fields["density"] * rr * rr, x=rr, initial=0.0) + m0, "Msun") if stellar_density is not None: fields["stellar_density"] = unyt_array(stellar_density(rr), "Msun/kpc**3") mylog.info("Integrating stellar mass profile.") fields["stellar_mass"] = unyt_array( integrate_mass(stellar_density, rr), "Msun") mdm = fields["total_mass"].copy() ddm = fields["total_density"].copy() if "density" in fields: mdm -= fields["gas_mass"] ddm -= fields["density"] if "stellar_mass" in fields: mdm -= fields["stellar_mass"] ddm -= fields["stellar_density"] mdm[ddm.v < 0.0] = mdm.max() ddm[ddm.v < 0.0] = 0.0 if ddm.sum() < 0.0 or mdm.sum() < 0.0: mylog.warning( "The total dark matter mass is either zero or negative!!") fields["dark_matter_density"] = ddm fields["dark_matter_mass"] = mdm if "density" in fields: fields["gas_fraction"] = fields["gas_mass"] / fields["total_mass"] fields["electron_number_density"] = \ fields["density"].to("cm**-3", "number_density", mu=mue) fields["entropy"] = \ fields["temperature"]*fields["electron_number_density"]**mtt return cls(rr.size, fields)
def from_scratch(cls, num_particles, rmin, rmax, profile, num_points=1000): rr = np.logspace(np.log10(rmin), np.log10(rmax), num_points, endpoint=True) pden = profile(rr) mylog.info("Integrating dark matter mass profile.") mdm = integrate_mass(profile, rr) mylog.info("Integrating gravitational potential profile.") gpot_profile = lambda r: profile(r)*r gpot = G.v*(mdm/rr + 4.*np.pi*integrate_toinf(gpot_profile, rr)) return cls(num_particles, rr, gpot, pden, mdm)
def from_dens_and_temp(cls, rmin, rmax, density, temperature, stellar_density=None, num_points=1000): """ Construct a hydrostatic equilibrium model using gas density and temperature profiles. Parameters ---------- rmin : float Minimum radius of profiles in kpc. rmax : float Maximum radius of profiles in kpc. density : :class:`~cluster_generator.radial_profiles.RadialProfile` A radial profile describing the gas mass density. temperature : :class:`~cluster_generator.radial_profiles.RadialProfile` A radial profile describing the gas temperature. stellar_density : :class:`~cluster_generator.radial_profiles.RadialProfile`, optional A radial profile describing the stellar mass density, if desired. num_points : integer, optional The number of points the profiles are evaluated at. """ mylog.info("Computing the profiles from density and temperature.") rr = np.logspace(np.log10(rmin), np.log10(rmax), num_points, endpoint=True) fields = OrderedDict() fields["radius"] = unyt_array(rr, "kpc") fields["density"] = unyt_array(density(rr), "Msun/kpc**3") fields["temperature"] = unyt_array(temperature(rr), "keV") fields["pressure"] = fields["density"] * fields["temperature"] fields["pressure"] /= mu * mp fields["pressure"].convert_to_units("Msun/(Myr**2*kpc)") pressure_spline = InterpolatedUnivariateSpline(rr, fields["pressure"].d) dPdr = unyt_array(pressure_spline(rr, 1), "Msun/(Myr**2*kpc**2)") fields["gravitational_field"] = dPdr / fields["density"] fields["gravitational_field"].convert_to_units("kpc/Myr**2") fields["gas_mass"] = unyt_array(integrate_mass(density, rr), "Msun") fields["total_mass"] = -fields["radius"]**2 * fields[ "gravitational_field"] / G total_mass_spline = InterpolatedUnivariateSpline( rr, fields["total_mass"].v) dMdr = unyt_array(total_mass_spline(rr, nu=1), "Msun/kpc") fields["total_density"] = dMdr / (4. * np.pi * fields["radius"]**2) return cls._from_scratch(fields, stellar_density=stellar_density)
def no_gas(cls, rmin, rmax, total_density, stellar_density=None, num_points=1000): rr = np.logspace(np.log10(rmin), np.log10(rmax), num_points, endpoint=True) fields = OrderedDict() fields["radius"] = unyt_array(rr, "kpc") fields["total_density"] = unyt_array(total_density(rr), "Msun/kpc**3") mylog.info("Integrating total mass profile.") fields["total_mass"] = unyt_array(integrate_mass(total_density, rr), "Msun") fields["gravitational_field"] = -G * fields["total_mass"] / ( fields["radius"]**2) fields["gravitational_field"].convert_to_units("kpc/Myr**2") return cls._from_scratch(fields, stellar_density=stellar_density)
def from_scratch( cls, mode, xmin, xmax, profiles, input_units=None, num_points=1000, geometry="spherical", P_amb=0.0 ): r""" Generate a set of profiles of physical quantities based on the assumption of hydrostatic equilibrium. Currently assumes an ideal gas with a gamma-law equation of state. Parameters ---------- mode : string The method to generate the profiles from an initial set. Can be one of the following: "dens_temp": Generate the profiles given a gas density and gas temperature profile. "dens_tden": Generate the profiles given a gas density and total density profile. "dens_grav": Generate the profiles given a gas density and gravitational acceleration profile. "dm_only": Generate profiles of gravitational potential and acceleration assuming an initial DM density profile. xmin : float The minimum radius or height for the profiles, assumed to be in kpc. xmax : float The maximum radius or height for the profiles, assumed to be in kpc. profiles : dict of functions A dictionary of callable functions of radius or height which return quantities such as density, total density, and so on. The functions are not unit-aware for speed purposes, but they assume that the base units are: "length": "kpc" "time": "Myr" "mass": "Msun" "temperature": "keV" parameters : dict of floats A dictionary of parameters needed for the calculation, which include: "mu": The mean molecular weight of the gas. Default is to assume a primordial H/He gas. "gamma": The ratio of specific heats. Default: 5/3. num_points : integer The number of points at which to evaluate the profile. geometry : string The geometry of the model. Can be "cartesian" or "spherical", which will determine whether or not the profiles are of "radius" or "height". """ if not isinstance(P_amb, YTQuantity): P_amb = YTQuantity(P_amb, "erg/cm**3") P_amb.convert_to_units("Msun/(Myr**2*kpc)") for p in modes[mode]: if p not in profiles: raise RequiredProfilesError(mode) if mode in ["dens_tden", "dm_only"] and geometry != "spherical": raise NotImplemented( "Constructing a HydrostaticEquilibrium from gas density and/or " "total density profiles is only allowed in spherical geometry!" ) extra_fields = [field for field in profiles if field not in cls.default_fields] if geometry == "cartesian": x_field = "height" elif geometry == "spherical": x_field = "radius" fields = OrderedDict() xx = np.logspace(np.log10(xmin), np.log10(xmax), num_points, endpoint=True) fields[x_field] = YTArray(xx, "kpc") if mode == "dm_only": fields["density"] = YTArray(np.zeros(num_points), "Msun/kpc**3") fields["pressure"] = YTArray(np.zeros(num_points), "Msun/kpc/Myr**2") fields["temperature"] = YTArray(np.zeros(num_points), "keV") else: fields["density"] = YTArray(profiles["density"](xx), "Msun/kpc**3") if mode == "dens_temp": mylog.info("Computing the profiles from density and temperature.") fields["temperature"] = YTArray(profiles["temperature"](xx), "keV") fields["pressure"] = fields["density"] * fields["temperature"] fields["pressure"] *= muinv / mp fields["pressure"].convert_to_units("Msun/(Myr**2*kpc)") pressure_spline = InterpolatedUnivariateSpline(xx, fields["pressure"].v) dPdx = YTArray(pressure_spline(xx, 1), "Msun/(Myr**2*kpc**2)") fields["gravitational_field"] = dPdx / fields["density"] fields["gravitational_field"].convert_to_units("kpc/Myr**2") else: if mode == "dens_tden" or mode == "dm_only": mylog.info("Computing the profiles from density and total density.") fields["total_density"] = YTArray(profiles["total_density"](xx), "Msun/kpc**3") mylog.info("Integrating total mass profile.") fields["total_mass"] = YTArray(integrate_mass(profiles["total_density"], xx), "Msun") fields["gravitational_field"] = -G * fields["total_mass"] / (fields["radius"] ** 2) fields["gravitational_field"].convert_to_units("kpc/Myr**2") elif mode == "dens_grav": mylog.info("Computing the profiles from density and gravitational acceleration.") fields["gravitational_field"] = YTArray(profiles["gravitational_field"](xx), "kpc/Myr**2") if mode != "dm_only": g = fields["gravitational_field"].in_units("kpc/Myr**2").v g_r = InterpolatedUnivariateSpline(xx, g) dPdr_int = lambda r: profiles["density"](r) * g_r(r) mylog.info("Integrating pressure profile.") fields["pressure"] = -YTArray(integrate(dPdr_int, xx), "Msun/kpc/Myr**2") fields["temperature"] = fields["pressure"] * mp / fields["density"] / muinv fields["temperature"].convert_to_units("keV") if geometry == "spherical": if "total_mass" not in fields: fields["total_mass"] = -fields["radius"] ** 2 * fields["gravitational_field"] / G if "total_density" not in fields: total_mass_spline = InterpolatedUnivariateSpline(xx, fields["total_mass"].v) dMdr = YTArray(total_mass_spline(xx, 1), "Msun/kpc") fields["total_density"] = dMdr / (4.0 * np.pi * fields["radius"] ** 2) mylog.info("Integrating gravitational potential profile.") if "total_density" in profiles: tdens_func = profiles["total_density"] else: tdens_func = InterpolatedUnivariateSpline(xx, fields["total_density"].d) gpot_profile = lambda r: tdens_func(r) * r gpot = YTArray(4.0 * np.pi * integrate(gpot_profile, xx), "Msun/kpc") fields["gravitational_potential"] = -G * (fields["total_mass"] / fields["radius"] + gpot) fields["gravitational_potential"].convert_to_units("kpc**2/Myr**2") if mode != "dm_only": mylog.info("Integrating gas mass profile.") fields["gas_mass"] = YTArray(integrate_mass(profiles["density"], xx), "Msun") mdm = fields["total_mass"].copy() ddm = fields["total_density"].copy() if mode != "dm_only": mdm -= fields["gas_mass"] ddm -= fields["density"] mdm[ddm.v < 0.0][:] = mdm.max() ddm[ddm.v < 0.0][:] = 0.0 fields["dark_matter_density"] = ddm fields["dark_matter_mass"] = mdm fields["pressure"] += P_amb for field in extra_fields: fields[field] = profiles[field](xx) return cls(num_points, fields, geometry)