Example #1
0
    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)
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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)
Example #6
0
    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)