Example #1
0
def nfw(m, z, dm=0, ref_in='200c', ref_out='500c',
        c=1., err=1e-6, scaling='duffy08', full_output=False):
    """
    Convert the mass of a cluster from one overdensity radius to another
    assuming an NFW profile.

    Parameters
    ----------
        m         : float
                    mass, in units of solar mass
        z         : float
                    redshift
        dm        : float (optional)
                    mass uncertainty
        ref_in    : {'2500c', '500c', '200c', '180c', '100c',
                    '500a', '200a', '100a'} (default '200c')
                    overdensity at which the input mass is measured.
                    The last letter indicates whether the overdensity
                    is with respect to the critical ('c') or average
                    ('a') density of the Universe at redshift z.
        ref_out   : {'2500c', '500c', '200c', '180c', '100c',
                    '500a', '200a', '100a'} (default '500c')
                    overdensity at which the output mass is measured.
        c         : float (default 1)
                    either a fixed concentration or a correction factor
                    to the Duffy et al. relation (useful, e.g., for
                    estimating uncertainties due to the c-M relation).
                    See parameter duffy.
        err       : float (default 1e-6)
                    allowed difference for convergence
        scaling   : {'duffy08', 'dutton14'} (optional)
                    If given, use the corresponding concentration
                    relation with a correction factor *c*. If False,
                    the concentration is fixed to the value of *c*.
                    Only possible if ref_in is either '200c' or '200a'.
        full_output : bool (default False)
                    If True, also return the concentration used.

    Returns
    -------
        m_out     : float
                    The mass at the output overdensity. If dm>0 then an
                    uncertainty on this mass is also returned (m_out is
                    then a tuple of length 2).
        c         : float (optional)
                    concentration. This is returned if full_output is
                    set to True.

    """

    #if ref_in != '200c':
        #return read_nfw(m, z, dm, ref_in=ref_in, ref_out=ref_out)
    if ref_in not in ('200a', '200c'):
        duffy = False

    if not numpy.iterable(m):
        m = numpy.array([m])
    if not numpy.iterable(dm):
        dm = numpy.array([dm])

    # iteratively calculate output mass
    def _mass(c, mx, scale, err):
        mx_out = 0
        mass = mx
        while abs(mx_out/mass - 1) > err:
            mx_out = mass
            r_out = (3 * mx_out / (4 * numpy.pi * rho_out)) ** (1./3.)
            x = r_out / scale
            mass = mx * (numpy.log(1 + x) - x / (1 + x)) / \
                   (numpy.log(1 + c) - c / (1 + c))
        return mass

    # density contrasts
    rho_in = int(ref_in[:-1]) * cosmology.density(z, ref=ref_in[-1])
    rho_out = int(ref_out[:-1]) * cosmology.density(z, ref=ref_out[-1])

    # radii
    r_in = conversions.rsph(m, z, ref=ref_in, unit='Mpc')
    if scaling in ('duffy08', 'dutton14'):
        c = c * scalings.cM(m, z, ref=ref_in, scaling=scaling)
    scale = r_in / c
    # mass and uncertainty (if defined)
    m_out = numpy.array([_mass(ci, mi, rs, err)
                         for ci, mi, rs in izip(c, m, scale)])
    if numpy.any(dm > 0):
        dm_hi = numpy.array([_mass(ci, mi+dmi, rs, err)
                             for ci, mi, dmi, rs in izip(c, m, dm, scale)])
        dm_lo = numpy.array([_mass(ci, mi-dmi, rs, err)
                             for ci, mi, dmi, rs in izip(c, m, dm, scale)])
        m_out = (m_out, (dm_hi+dm_lo)/2)

    if full_output:
        return m_out, c
    return m_out
Example #2
0
def upp(r, m500, z, runit='Mpc', unit='astro',
        self_similar=True, profile='sph'):
    """
    The Universal Pressure Profile (UPP) from Arnaud et al. (2010)

    Parameters
    ----------
        r         : float
                    Radius at which to estimate the pressure
        m500      : float
                    Cluster mass within r500
        z         : float
                    Cluster redshift
        runit     : {'Mpc', 'kpc', 'r500'} (default 'Mpc')
                    Whether R is in absolute units ('Mpc', 'kpc') or in
                    units relative to r500 ('r500'). In the first case,
                    r500 will be estimated from m500 assuming a
                    spherical cluster.
        unit      : {'astro', 'cgs', 'mks'} (default 'astro')
                    Pressure units. Default value is astro, which
                    returns the pressure in units of Msun·Mpc⁻¹·s⁻². In
                    cgs, the units are erg·cm⁻³ = g·cm⁻¹·s⁻² while in
                    mks they are kg·m⁻¹·s⁻².
        self_similar : bool (default True)
                    Whether to use the "self-similar" scaling or the
                    "universal" scaling.
        profile   : {'sph', 'cyl'} (default 'sph')
                    Whether the used profile is spherical or
                    cylindrical, i.e., integrated along the line of
                    sight.

    """
    h = cosmology.h
    h70 = h / 0.7
    if self_similar:
        alpha_p = 0
        Po = 8.130 / h70**1.5
        c500 = 1.156
        gamma = 0.3292
        alpha = 1.0620
        beta = 5.4807
    else:
        alpha_p = 0.12
        Po = 8.403 / h70**1.5
        c500 = 1.177
        gamma = 0.3081
        alpha = 1.0510
        beta = 5.4905

    def alpha_prime(x):
        """ Eq. 8 """
        if self_similar:
            return 0
        f = (2*x) ** 3
        f /= (1+f)
        return 0.10 - f * (alpha_p+0.10)

    def I(x):
        """ Eq. 24 """
        f = lambda u: p(u, c500) * u**2
        return 3 * integrate.quad(f, 0, x)[0]

    def J(x):
        """ Eq. 27 """
        f = lambda u: p(u) * numpy.sqrt(u**2 - x**2) * u
        return I(5) - 3*integrate.quad(f, x, 5)[0]

    def P(x, m500, z):
        """ UPP itself, Eq. 13 """
        exp = alpha_p + alpha_prime(x, alpha_p, self_similar)
        return p(x) * P500(m500, z) * (m500/3e14) ** exp

    def P500(m500, z, unit='astro'):
        """ Eq. 5 """
        # in units of keV·cm⁻³
        pp = 1.65e-3 * cosmology.E(z)**(8./3.) * \
             (m500 / 3e14)**(2./3.) * h70**2
        # in units of erg·cm⁻³ = g·cm⁻¹·s⁻²
        pp *= units.keV
        if unit == 'cgs':
            return pp
        # in units of kg·m⁻¹·s⁻²
        if unit == 'mks':
            return pp / units.kg * units.m
        # in units of Msun·Mpc⁻¹·s⁻²
        if unit == 'astro':
            return pp / units.Msun * units.Mpc
        return pp

    def p(x, c500):
        """ Eq. 11 """
        cx = c500 * x
        exp = (beta-gamma) / alpha
        return Po / (cx**gamma * (1+cx**alpha)**exp)

    def Ysph(R, r500, m500, z):
        f = lambda r: P(r/r500, m500, z) * r**2
        i = 4 * numpy.pi * integrate.quad(f, 0, R)[0]
        return i * constants.Msun * constants.sigmaT / \
            (constants.me * constants.c**2)

    def Ycyl(R, r500, m500, z):
        """ Eq. 15 """
        rb = 5 * r500
        f = lambda x, r: 4 * numpy.pi * r * P(x/r500, m500, z) * \
                        x / numpy.sqrt(x**2-r**2)
        i = integrate.dblquad(f, 0, R, lambda r: r, lambda r: Rb)[0]
        return i * constants.Msun * constants.sigmaT / \
            (constants.me * constants.c**2)

    # the calculation itself
    if runit == 'r500':
        x = r
    else:
        r500 = conversions.rsph(m500, z, ref='500c', unit=runit)
        x = r / r500
    if profile == 'sph':
        Ax = 2.925e-5 * I(x) / h70
        y = Ax * (m500/3e14)**1.78
    elif profile == 'cyl':
        Bx = 2.925e-5 * J(x) / h70
        y = Bx * (m500/3e14)**1.78
    return y
Example #3
0
def sigma_profile(sigma, aperture, r200, z=0, dsigma=0, orbits="iso", concentration="duffy08", err=1e-3):
    """
    A correction for incomplete coverage based on the theoretical velocity
    dispersion profile of Mamon, Biviano & Murante (2010). The profiles
    were kindly provided by Gary Mamon

    Returns
    -------
      s200      : float
                  the corrected velocity dispersion at r200
      aperture  : float
                  the final estimate of the radial coverage, based on the
                  new r200
      r200      : float
                  the final estimate of r200

    """
    import conversions
    import scalings
    import scipy
    from scipy import integrate, interpolate

    def fraction(r0, c=4, orbits="iso"):
        ## these values kindly provided by Gary Mamon, are in a file in the
        ## folder where this module is located
        ## here, s = sigma_LOS / sqrt[GM(r_s)/r_s]. However, we are only
        ## interested in a ratio.
        # r / r_s
        x = [
            0.000,
            0.100,
            0.126,
            0.158,
            0.200,
            0.251,
            0.316,
            0.398,
            0.501,
            0.631,
            0.794,
            1.000,
            1.259,
            1.585,
            1.995,
            2.512,
            3.162,
            3.981,
            5.012,
            6.310,
            7.943,
            10.000,
        ]
        # s for isotropic orbits
        if orbits == "iso":
            s = [
                0.000,
                0.625,
                0.638,
                0.650,
                0.661,
                0.670,
                0.677,
                0.682,
                0.684,
                0.682,
                0.678,
                0.669,
                0.658,
                0.642,
                0.624,
                0.603,
                0.579,
                0.554,
                0.527,
                0.499,
                0.471,
                0.442,
            ]
        # s for progressively more radial orbits, beta=r/2/(r+r_s)
        elif orbits == "radial":
            s = [
                0.000,
                0.700,
                0.714,
                0.727,
                0.737,
                0.745,
                0.750,
                0.751,
                0.748,
                0.740,
                0.728,
                0.712,
                0.691,
                0.667,
                0.640,
                0.611,
                0.581,
                0.549,
                0.518,
                0.486,
                0.455,
                0.425,
            ]
        # in units of r200
        r = scipy.array(x) / c
        profile = interpolate.interp1d(r, s)
        integrand = lambda R: profile(R) * R
        # these are integrated velocity dispersions
        # sigma_ap = 2 * integrate.romberg(integrand, 0.1, r0) / r0**2
        # sigma_r200 = 2 * integrate.romberg(integrand, 0.1, 1) # / 1**2
        sigma_ap = profile(r0)
        sigma_r200 = profile(1)
        return sigma_r200 / sigma_ap

    if type(concentration) == float:
        c = concentration
    elif concentration in ("dolag04", "duffy08"):
        c = scalings.csigma(sigma, z, dsigma, scaling=concentration)[0]
    else:
        msg = "Value for argument concentration not valid, see help page"
        raise ValueError(msg)
    s1 = sigma  # velocity dispersion at r200 -- changing with changing r200
    r0 = aperture * r200  # initial radial coverage -- fixed!
    r1 = [r200]  # r200 -- should go changing
    r2 = aperture * r200  # radial coverage
    x = []
    while abs(r1[-1] - r2) / r1[-1] > err:
        r2 = r1[-1]
        # x = fraction(r0/r1, c, orbits=orbits)
        x.append(fraction(r0 / r1[-1], c, orbits=orbits))
        s1 = x[-1] * sigma
        m1 = scalings.sigma(s1, z)
        # r1 = conversions.rsph(m1, z)
        r1.append(conversions.rsph(m1, z))
    ap = r0 / r1[-1]
    if scipy.isnan(ap):
        print x
        print r1
        print c, sigma, s1, r0, r2, ap
        print ""
        # exit()
    r200new = r1[-1]
    return s1, ap, r200new