Example #1
0
def jam_axi_vel(surf_lum, sigma_lum, qobs_lum, surf_pot, sigma_pot, qobs_pot,
                 inc, mbh, distance, xbin, ybin, normpsf=1., pixang=0., pixsize=0.,
                 plot=True, vel=None, evel=None, sigmapsf=0., goodbins=None,
                 quiet=False, beta=None, kappa=None, gamma=None, step=0., nrad=20,
                 nang=10, rbh=0.01, vmax=None, component='z', nsteps=51, **kwargs):

    """
    This procedure calculates a prediction for the projected mean velocity V
    for an anisotropic axisymmetric galaxy model. It implements the solution
    of the anisotropic Jeans equations presented in equation (38) of
    Cappellari (2008, MNRAS, 390, 71).
    PSF convolution is done as described in the Appendix of that paper:
    http://adsabs.harvard.edu/abs/2008MNRAS.390...71C

    CALLING SEQUENCE:

    velModel, kappa, chi2, flux = jam_axi_vel(
        surf_lum, sigma_lum, qobs_lum, surf_pot, sigma_pot, qobs_pot,
        inc, mbh, distance, xbin, ybin, normpsf=1, pixang=0, pixsize=0,
        plot=True, vel=None, evel=None, sigmapsf=0, goodbins=None,
        quiet=False, beta=None, kappa=None, gamma=None, step=0,
        nrad=20, nang=10, rbh=0.01, vmax=None, component='z', nsteps=51)

    See the file jam_axi_vel.py for detailed documentation.

    """
    if beta is None:
        beta = np.zeros_like(surf_lum)  # Anisotropy parameter beta = 1 - (sig_z/sig_R)**2  (beta=0-->circle)
    if kappa is None:
        kappa = np.ones_like(surf_lum)  # Anisotropy parameter: V_obs = kappa * V_iso (kappa=1-->circle)
    if gamma is None:
        gamma = np.zeros_like(surf_lum)  # Anisotropy parameter gamma = 1 - (sig_phi/sig_R)^2 (gamma=0-->circle)
    if not (surf_lum.size == sigma_lum.size == qobs_lum.size
                == beta.size == gamma.size == kappa.size):
        raise ValueError("The luminous MGE components and anisotropies do not match")
    if not (surf_pot.size == sigma_pot.size == qobs_pot.size):
        raise ValueError("The total mass MGE components do not match")
    if xbin.size != ybin.size:
        raise ValueError("xbin and ybin do not match")
    if vel is not None:
        if evel is None:
            evel = np.full_like(vel, 5.)  # Constant 5 km/s errors
        if goodbins is None:
            goodbins = np.ones_like(vel, dtype=bool)
        elif goodbins.dtype != bool:
            raise ValueError("goodbins must be a boolean vector")
        if not (xbin.size == vel.size == evel.size == goodbins.size):
            raise ValueError("(vel, evel, goodbins) and (xbin, ybin) do not match")

    sigmapsf = np.atleast_1d(sigmapsf)
    normpsf = np.atleast_1d(normpsf)
    if sigmapsf.size != normpsf.size:
        raise ValueError("sigmaPSF and normPSF do not match")

    pc = distance*np.pi/0.648 # Constant factor to convert arcsec --> pc

    surf_lum_pc = surf_lum
    surf_pot_pc = surf_pot
    sigma_lum_pc = sigma_lum*pc         # Convert from arcsec to pc
    sigma_pot_pc = sigma_pot*pc         # Convert from arcsec to pc
    xbin_pc = xbin*pc                   # Convert all distances to pc
    ybin_pc = ybin*pc
    pixSize_pc = pixsize*pc
    sigmaPsf_pc = sigmapsf*pc
    step_pc = step*pc

    # Add a Gaussian with small sigma and the same total mass as the BH.
    # The Gaussian provides an excellent representation of the second moments
    # of a point-like mass, to 1% accuracy out to a radius 2*sigmaBH.
    # The error increses to 14% at 1*sigmaBH, independently of the BH mass.
    #
    if mbh > 0:
        sigmaBH_pc = rbh*pc # Adopt for the BH just a very small size
        surfBH_pc = mbh/(2*np.pi*sigmaBH_pc**2)
        surf_pot_pc = np.append(surfBH_pc, surf_pot_pc) # Add Gaussian to potential only!
        sigma_pot_pc = np.append(sigmaBH_pc, sigma_pot_pc)
        qobs_pot = np.append(1., qobs_pot)  # Make sure vectors do not have extra dimensions

    qobs_lum = qobs_lum.clip(0, 0.999)
    qobs_pot = qobs_pot.clip(0, 0.999)

    t = clock()
    velModel = _vel(xbin_pc, ybin_pc, inc,
                    surf_lum_pc, sigma_lum_pc, qobs_lum,
                    surf_pot_pc, sigma_pot_pc, qobs_pot,
                    beta, kappa, gamma, sigmaPsf_pc, normpsf,
                    pixSize_pc, pixang, step_pc, nrad, nang, component, nsteps)

    if not quiet:
        print('jam_axis_vel elapsed time sec: %.2f' % (clock() - t))

    # Analytic convolution of the MGE model with an MGE circular PSF
    # using Equations (4,5) of Cappellari (2002, MNRAS, 333, 400)
    #
    lum = surf_lum_pc*qobs_lum*sigma_lum**2 # Luminosity/(2np.pi) of each Gaussian
    flux = np.zeros_like(xbin)  # Total MGE surface brightness for plotting
    for sigp, norp in zip(sigmapsf, normpsf): # loop over the PSF Gaussians
        sigmaX = np.sqrt(sigma_lum**2 + sigp**2)
        sigmaY = np.sqrt((sigma_lum*qobs_lum)**2 + sigp**2)
        surfConv = lum / (sigmaX*sigmaY) # PSF-convolved in Lsun/pc**2
        for srf, sx, sy in zip(surfConv, sigmaX, sigmaY): # loop over the galaxy MGE Gaussians
            flux += norp*srf*np.exp(-0.5*((xbin/sx)**2 + (ybin/sy)**2))

    ####### Output and optional M/L fit
    # If RMS keyword is not given all this section is skipped

    if vel is None:
        
        chi2 = None
        
    else:

        # Only consider the good bins for the chi**2 estimation
        #
        # Scale by having the same angular momentum
        # in the model and in the galaxy (equation 52)
        #
        kappa2 = np.sum(np.abs(vel[goodbins]*xbin[goodbins])) \
               / np.sum(np.abs(velModel[goodbins]*xbin[goodbins]))
        kappa = kappa*kappa2  # Rescale input kappa by the best fit

        # Measure the scaling one would have from a standard chi^2 fit of the V field.
        # This value is only used to get proper sense of rotation for the model.
        # y1 = rms; dy1 = erms (y1 are the data, y2 the model)
        # scale = total(y1*y2/dy1^2)/total(y2^2/dy1^2)  (equation 51)
        #
        kappa3 = np.sum(vel[goodbins]*velModel[goodbins]/evel[goodbins]**2) \
               / np.sum((velModel[goodbins]/evel[goodbins])**2)

        velModel *= kappa2*np.sign(kappa3)

        chi2 = np.sum(((vel[goodbins]-velModel[goodbins])/evel[goodbins])**2) / goodbins.sum()

        if not quiet:
            print('inc=%.1f kappa=%.2f beta_z=%.2f BH=%.2e chi2/DOF=%.3g' % (inc, kappa[0], beta[0], mbh, chi2))
            mass = 2*np.pi*surf_pot_pc*qobs_pot*sigma_pot_pc**2
            print('Total mass MGE %.4g:' % np.sum(mass))

        if plot:

            vel1 = vel.copy() # Only symmetrize good bins
            vel1[goodbins] = symmetrize_velfield(xbin[goodbins], ybin[goodbins], vel[goodbins], sym=1)

            if vmax is None:
                vmax = stats.scoreatpercentile(np.abs(vel1[goodbins]), 99)

            plt.clf()
            plt.subplot(121)
            plot_velfield(xbin, ybin, vel1, vmin=-vmax, vmax=vmax, flux=flux, **kwargs)
            plt.title(r"Input $V_{\rm los}$")

            plt.subplot(122)
            plot_velfield(xbin, ybin, velModel, vmin=-vmax, vmax=vmax, flux=flux, **kwargs)
            plt.plot(xbin[~goodbins], ybin[~goodbins], 'ok', mec='white')
            plt.title(r"Model $V_{\rm los}$")
            plt.tick_params(labelleft='off')
            plt.subplots_adjust(wspace=0.03)

    return velModel, kappa, chi2, flux
Example #2
0
def jam_axi_vel(surf_lum,
                sigma_lum,
                qobs_lum,
                surf_pot,
                sigma_pot,
                qobs_pot,
                inc,
                mbh,
                distance,
                xbin,
                ybin,
                normpsf=1.,
                pixang=0.,
                pixsize=0.,
                plot=True,
                vel=None,
                evel=None,
                sigmapsf=0.,
                goodbins=None,
                quiet=False,
                beta=None,
                kappa=None,
                gamma=None,
                step=0.,
                nrad=20,
                nang=10,
                rbh=0.01,
                vmax=None,
                component='z',
                nsteps=51,
                **kwargs):
    """
    This procedure calculates a prediction for the projected mean velocity V
    for an anisotropic axisymmetric galaxy model. It implements the solution
    of the anisotropic Jeans equations presented in equation (38) of
    Cappellari (2008, MNRAS, 390, 71).
    PSF convolution is done as described in the Appendix of that paper:
    http://adsabs.harvard.edu/abs/2008MNRAS.390...71C

    CALLING SEQUENCE:

    velModel, kappa, chi2, flux = jam_axi_vel(
        surf_lum, sigma_lum, qobs_lum, surf_pot, sigma_pot, qobs_pot,
        inc, mbh, distance, xbin, ybin, normpsf=1, pixang=0, pixsize=0,
        plot=True, vel=None, evel=None, sigmapsf=0, goodbins=None,
        quiet=False, beta=None, kappa=None, gamma=None, step=0,
        nrad=20, nang=10, rbh=0.01, vmax=None, component='z', nsteps=51)

    See the file jam_axi_vel.py for detailed documentation.

    """
    if beta is None:
        beta = np.zeros_like(
            surf_lum
        )  # Anisotropy parameter beta = 1 - (sig_z/sig_R)**2  (beta=0-->circle)
    if kappa is None:
        kappa = np.ones_like(
            surf_lum
        )  # Anisotropy parameter: V_obs = kappa * V_iso (kappa=1-->circle)
    if gamma is None:
        gamma = np.zeros_like(
            surf_lum
        )  # Anisotropy parameter gamma = 1 - (sig_phi/sig_R)^2 (gamma=0-->circle)
    if not (surf_lum.size == sigma_lum.size == qobs_lum.size == beta.size ==
            gamma.size == kappa.size):
        raise ValueError(
            "The luminous MGE components and anisotropies do not match")
    if not (surf_pot.size == sigma_pot.size == qobs_pot.size):
        raise ValueError("The total mass MGE components do not match")
    if xbin.size != ybin.size:
        raise ValueError("xbin and ybin do not match")
    if vel is not None:
        if evel is None:
            evel = np.full_like(vel, 5.)  # Constant 5 km/s errors
        if goodbins is None:
            goodbins = np.ones_like(vel, dtype=bool)
        elif goodbins.dtype != bool:
            raise ValueError("goodbins must be a boolean vector")
        if not (xbin.size == vel.size == evel.size == goodbins.size):
            raise ValueError(
                "(vel, evel, goodbins) and (xbin, ybin) do not match")

    sigmapsf = np.atleast_1d(sigmapsf)
    normpsf = np.atleast_1d(normpsf)
    if sigmapsf.size != normpsf.size:
        raise ValueError("sigmaPSF and normPSF do not match")

    pc = distance * np.pi / 0.648  # Constant factor to convert arcsec --> pc

    surf_lum_pc = surf_lum
    surf_pot_pc = surf_pot
    sigma_lum_pc = sigma_lum * pc  # Convert from arcsec to pc
    sigma_pot_pc = sigma_pot * pc  # Convert from arcsec to pc
    xbin_pc = xbin * pc  # Convert all distances to pc
    ybin_pc = ybin * pc
    pixSize_pc = pixsize * pc
    sigmaPsf_pc = sigmapsf * pc
    step_pc = step * pc

    # Add a Gaussian with small sigma and the same total mass as the BH.
    # The Gaussian provides an excellent representation of the second moments
    # of a point-like mass, to 1% accuracy out to a radius 2*sigmaBH.
    # The error increses to 14% at 1*sigmaBH, independently of the BH mass.
    #
    if mbh > 0:
        sigmaBH_pc = rbh * pc  # Adopt for the BH just a very small size
        surfBH_pc = mbh / (2 * np.pi * sigmaBH_pc**2)
        surf_pot_pc = np.append(surfBH_pc,
                                surf_pot_pc)  # Add Gaussian to potential only!
        sigma_pot_pc = np.append(sigmaBH_pc, sigma_pot_pc)
        qobs_pot = np.append(
            1., qobs_pot)  # Make sure vectors do not have extra dimensions

    qobs_lum = qobs_lum.clip(0, 0.999)
    qobs_pot = qobs_pot.clip(0, 0.999)

    t = clock()
    velModel = _vel(xbin_pc, ybin_pc, inc, surf_lum_pc, sigma_lum_pc, qobs_lum,
                    surf_pot_pc, sigma_pot_pc, qobs_pot, beta, kappa, gamma,
                    sigmaPsf_pc, normpsf, pixSize_pc, pixang, step_pc, nrad,
                    nang, component, nsteps)

    if not quiet:
        print('jam_axis_vel elapsed time sec: %.2f' % (clock() - t))

    # Analytic convolution of the MGE model with an MGE circular PSF
    # using Equations (4,5) of Cappellari (2002, MNRAS, 333, 400)
    #
    lum = surf_lum_pc * qobs_lum * sigma_lum**2  # Luminosity/(2np.pi) of each Gaussian
    flux = np.zeros_like(xbin)  # Total MGE surface brightness for plotting
    for sigp, norp in zip(sigmapsf, normpsf):  # loop over the PSF Gaussians
        sigmaX = np.sqrt(sigma_lum**2 + sigp**2)
        sigmaY = np.sqrt((sigma_lum * qobs_lum)**2 + sigp**2)
        surfConv = lum / (sigmaX * sigmaY)  # PSF-convolved in Lsun/pc**2
        for srf, sx, sy in zip(surfConv, sigmaX,
                               sigmaY):  # loop over the galaxy MGE Gaussians
            flux += norp * srf * np.exp(-0.5 * ((xbin / sx)**2 +
                                                (ybin / sy)**2))

    ####### Output and optional M/L fit
    # If RMS keyword is not given all this section is skipped

    if vel is None:

        chi2 = None

    else:

        # Only consider the good bins for the chi**2 estimation
        #
        # Scale by having the same angular momentum
        # in the model and in the galaxy (equation 52)
        #
        kappa2 = np.sum(np.abs(vel[goodbins]*xbin[goodbins])) \
               / np.sum(np.abs(velModel[goodbins]*xbin[goodbins]))
        kappa = kappa * kappa2  # Rescale input kappa by the best fit

        # Measure the scaling one would have from a standard chi^2 fit of the V field.
        # This value is only used to get proper sense of rotation for the model.
        # y1 = rms; dy1 = erms (y1 are the data, y2 the model)
        # scale = total(y1*y2/dy1^2)/total(y2^2/dy1^2)  (equation 51)
        #
        kappa3 = np.sum(vel[goodbins]*velModel[goodbins]/evel[goodbins]**2) \
               / np.sum((velModel[goodbins]/evel[goodbins])**2)

        velModel *= kappa2 * np.sign(kappa3)

        chi2 = np.sum(((vel[goodbins] - velModel[goodbins]) / evel[goodbins])**
                      2) / goodbins.sum()

        if not quiet:
            print('inc=%.1f kappa=%.2f beta_z=%.2f BH=%.2e chi2/DOF=%.3g' %
                  (inc, kappa[0], beta[0], mbh, chi2))
            mass = 2 * np.pi * surf_pot_pc * qobs_pot * sigma_pot_pc**2
            print('Total mass MGE %.4g:' % np.sum(mass))

        if plot:

            vel1 = vel.copy()  # Only symmetrize good bins
            vel1[goodbins] = symmetrize_velfield(xbin[goodbins],
                                                 ybin[goodbins],
                                                 vel[goodbins],
                                                 sym=1)

            if vmax is None:
                vmax = stats.scoreatpercentile(np.abs(vel1[goodbins]), 99)

            plt.clf()
            plt.subplot(121)
            plot_velfield(xbin,
                          ybin,
                          vel1,
                          vmin=-vmax,
                          vmax=vmax,
                          flux=flux,
                          **kwargs)
            plt.title(r"Input $V_{\rm los}$")

            plt.subplot(122)
            plot_velfield(xbin,
                          ybin,
                          velModel,
                          vmin=-vmax,
                          vmax=vmax,
                          flux=flux,
                          **kwargs)
            plt.plot(xbin[~goodbins], ybin[~goodbins], 'ok', mec='white')
            plt.title(r"Model $V_{\rm los}$")
            plt.tick_params(labelleft='off')
            plt.subplots_adjust(wspace=0.03)

    return velModel, kappa, chi2, flux
Example #3
0
def jam_axi_rms(surf_lum,
                sigma_lum,
                qobs_lum,
                surf_pot,
                sigma_pot,
                qobs_pot,
                inc,
                mbh,
                distance,
                xbin,
                ybin,
                ml=None,
                normpsf=1.,
                pixang=0.,
                pixsize=0.,
                plot=True,
                rms=None,
                erms=None,
                sigmapsf=0.,
                goodbins=None,
                quiet=False,
                beta=None,
                step=0.,
                nrad=20,
                nang=10,
                rbh=0.01,
                tensor='zz',
                vmin=None,
                vmax=None,
                **kwargs):
    """
    This procedure calculates a prediction for the projected second
    velocity moment V_RMS = sqrt(V^2 + sigma^2), or optionally any
    of the six components of the symmetric proper motion dispersion
    tensor, for an anisotropic axisymmetric galaxy model.
    It implements the solution of the anisotropic Jeans equations presented
    in equation (28) and note 5 of Cappellari (2008, MNRAS, 390, 71).
    PSF convolution in done as described in the Appendix of that paper.
    http://adsabs.harvard.edu/abs/2008MNRAS.390...71C

    CALLING SEQUENCE:

    rmsModel, ml, chi2, flux = \
       jam_axi_rms(surf_lum, sigma_lum, qobs_lum, surf_pot, sigma_pot, qobs_pot,
                    inc, mbh, distance, xbin, ybin, ml=None, normpsf=1, pixang=0,
                    pixsize=0, plot=True, rms=None, erms=None, sigmapsf=0,
                    goodbins=None, quiet=False, beta=None, step=0, nrad=20, nang=10,
                    rbh=0.01, tensor='zz', vmin=None, vmax=None)

    See the file jam_axi_rms.py for detailed documentation.

    """
    if beta is None:
        beta = np.zeros_like(
            surf_lum)  # Anisotropy parameter beta = 1 - (sig_z/sig_R)**2
    if not (surf_lum.size == sigma_lum.size == qobs_lum.size == beta.size):
        raise ValueError("The luminous MGE components do not match")
    if not (surf_pot.size == sigma_pot.size == qobs_pot.size):
        raise ValueError("The total mass MGE components do not match")
    if xbin.size != ybin.size:
        raise ValueError("xbin and ybin do not match")
    if rms is not None:
        if erms is None:
            erms = np.full_like(rms,
                                np.median(rms) * 0.05)  # Constant ~5% errors
        if goodbins is None:
            goodbins = np.ones_like(rms, dtype=bool)
        elif goodbins.dtype != bool:
            raise ValueError("goodbins must be a boolean vector")
        if not (xbin.size == rms.size == erms.size == goodbins.size):
            raise ValueError(
                "(rms, erms, goodbins) and (xbin, ybin) do not match")

    sigmapsf = np.atleast_1d(sigmapsf)
    normpsf = np.atleast_1d(normpsf)
    if sigmapsf.size != normpsf.size:
        raise ValueError("sigmaPSF and normPSF do not match")

    pc = distance * np.pi / 0.648  # Constant factor to convert arcsec --> pc

    surf_lum_pc = surf_lum
    surf_pot_pc = surf_pot
    sigma_lum_pc = sigma_lum * pc  # Convert from arcsec to pc
    sigma_pot_pc = sigma_pot * pc  # Convert from arcsec to pc
    xbin_pc = xbin * pc  # Convert all distances to pc
    ybin_pc = ybin * pc
    pixSize_pc = pixsize * pc
    sigmaPsf_pc = sigmapsf * pc
    step_pc = step * pc

    # Add a Gaussian with small sigma and the same total mass as the BH.
    # The Gaussian provides an excellent representation of the second moments
    # of a point-like mass, to 1% accuracy out to a radius 2*sigmaBH.
    # The error increses to 14% at 1*sigmaBH, independently of the BH mass.
    #
    if mbh > 0:
        sigmaBH_pc = rbh * pc  # Adopt for the BH just a very small size
        surfBH_pc = mbh / (2 * np.pi * sigmaBH_pc**2)
        surf_pot_pc = np.append(surfBH_pc,
                                surf_pot_pc)  # Add Gaussian to potential only!
        sigma_pot_pc = np.append(sigmaBH_pc, sigma_pot_pc)
        qobs_pot = np.append(
            1., qobs_pot)  # Make sure vectors do not have extra dimensions

    qobs_lum = qobs_lum.clip(0, 0.999)
    qobs_pot = qobs_pot.clip(0, 0.999)

    t = clock()
    rmsModel = _vrms2(xbin_pc, ybin_pc, inc, surf_lum_pc, sigma_lum_pc,
                      qobs_lum, surf_pot_pc, sigma_pot_pc, qobs_pot, beta,
                      tensor, sigmaPsf_pc, normpsf, pixSize_pc, pixang,
                      step_pc, nrad, nang)
    if not quiet:
        print('jam_axi_rms elapsed time sec: %.2f' % (clock() - t))

    if tensor in ('xx', 'yy', 'zz'):
        rmsModel = np.sqrt(
            rmsModel.clip(0))  # Return SQRT and fix possible rounding errors
    if tensor in ('xy', 'xz'):
        rmsModel *= np.sign(xbin *
                            ybin)  # Calculation was done in positive quadrant

    # Analytic convolution of the MGE model with an MGE circular PSF
    # using Equations (4,5) of Cappellari (2002, MNRAS, 333, 400)
    #
    lum = surf_lum_pc * qobs_lum * sigma_lum**2  # Luminosity/(2np.pi) of each Gaussian
    flux = np.zeros_like(xbin)  # Total MGE surface brightness for plotting
    for sigp, norp in zip(sigmapsf, normpsf):  # loop over the PSF Gaussians
        sigmaX = np.sqrt(sigma_lum**2 + sigp**2)
        sigmaY = np.sqrt((sigma_lum * qobs_lum)**2 + sigp**2)
        surfConv = lum / (sigmaX * sigmaY)  # PSF-convolved in Lsun/pc**2
        for srf, sx, sy in zip(surfConv, sigmaX,
                               sigmaY):  # loop over the galaxy MGE Gaussians
            flux += norp * srf * np.exp(-0.5 * ((xbin / sx)**2 +
                                                (ybin / sy)**2))

    if rms is None:

        chi2 = None
        if ml is None:
            ml = 1.
        else:
            rmsModel *= np.sqrt(ml)

    else:

        if (ml is None) or (ml <= 0):

            # y1, dy1 = rms, erms   # (y1 are the data, y2 the model)
            # scale = sum(y1*y2/dy1**2)/sum(y2**2/dy1**2)  # (equation 51)
            #
            ml = (np.sum(rms[goodbins] * rmsModel[goodbins] /
                         erms[goodbins]**2) / np.sum(
                             (rmsModel[goodbins] / erms[goodbins])**2))**2

        rmsModel *= np.sqrt(ml)
        chi2 = np.sum(((rms[goodbins] - rmsModel[goodbins]) / erms[goodbins])**
                      2) / goodbins.sum()

        if not quiet:
            print('inc=%.1f beta_z=%.2f M/L=%.3g BH=%.2e chi2/DOF=%.3g' %
                  (inc, beta[0], ml, mbh * ml, chi2))
            mass = 2 * np.pi * surf_pot_pc * qobs_pot * sigma_pot_pc**2
            print('Total mass MGE: %.4g' % np.sum(mass * ml))

        if plot:

            rms1 = rms.copy()  # Only symmetrize good bins
            rms1[goodbins] = symmetrize_velfield(xbin[goodbins],
                                                 ybin[goodbins], rms[goodbins])

            if (vmin is None) or (vmax is None):
                vmin, vmax = stats.scoreatpercentile(
                    rms1[goodbins],
                    [0.5, 99.5])  # Could use np.percentile in Numpy 1.10

            plt.clf()
            plt.subplot(121)
            plot_velfield(xbin,
                          ybin,
                          rms1,
                          vmin=vmin,
                          vmax=vmax,
                          flux=flux,
                          **kwargs)
            plt.title(r"Input $V_{\rm rms}$")

            plt.subplot(122)
            plot_velfield(xbin,
                          ybin,
                          rmsModel,
                          vmin=vmin,
                          vmax=vmax,
                          flux=flux,
                          **kwargs)
            plt.plot(xbin[~goodbins], ybin[~goodbins], 'ok', mec='white')
            plt.title(r"Model $V_{\rm rms}$")
            plt.tick_params(labelleft='off')
            plt.subplots_adjust(wspace=0.03)

    return rmsModel, ml, chi2, flux
Example #4
0
    def __init__(self, name, path='.', burnin=0, best='median'):
        self.data = load(name, path=path)
        # load model data into class
        self.ndim = self.data['ndim']
        self.nwalkers = self.data['nwalkers']
        self.chain = self.data['rst']['chain']
        self.goodchains = self.data['rst']['goodchains']
        # check good chain fraction
        if self.goodchains.sum() / float(self.nwalkers) < 0.6:
            self.goodchains = np.ones_like(self.goodchains, dtype=bool)
            print('Warning - goodchain fraction less than 0.6')
            print('Acceptance fraction:')
            print(self.data['rst']['acceptance_fraction'])
        self.lnprob = self.data['rst']['lnprobability']
        self.flatchain = self.chain[self.goodchains,
                                    burnin:, :].reshape(-1, self.ndim)
        self.flatlnprob = self.lnprob[self.goodchains, burnin:].reshape(-1)
        # estimate the beset model parameters
        self.medianPars = estimatePrameters(self.flatchain,
                                            flatlnprob=self.flatlnprob)
        self.meanPars = estimatePrameters(self.flatchain,
                                          flatlnprob=self.flatlnprob,
                                          method='mean')
        self.peakPars = estimatePrameters(self.flatchain,
                                          flatlnprob=self.flatlnprob,
                                          method='peak')
        self.maxPars = estimatePrameters(self.flatchain,
                                         flatlnprob=self.flatlnprob,
                                         method='max')
        # estimate the errors
        percentile = np.percentile(self.flatchain, [16, 50, 84], axis=0)
        self.errPars = np.zeros([2, percentile.shape[1]])
        self.errPars[0, :] = percentile[0, :] - percentile[1, :]
        self.errPars[1, :] = percentile[2, :] - percentile[1, :]
        # choose the best parameter type
        switch = {
            'median': self.medianPars,
            'mean': self.meanPars,
            'peak': self.peakPars,
            'max': self.maxPars
        }
        bestPars = switch[best]
        self.bestPars_list = bestPars
        self.bestPars = {}
        for i, key in enumerate(self.data['JAMpars']):
            self.bestPars[key] = bestPars[i]
        # model inclination
        cosinc = self.bestPars.get('cosinc', np.pi / 2.0)
        self.inc = np.arccos(cosinc)
        if 'ml' in self.bestPars.keys():
            self.ml = self.bestPars['ml']
        else:
            if 'logml' in self.bestPars.keys():
                self.ml = 10**self.bestPars['logml']
            else:
                print('Waring - do not find ml parameter, set to 1.0')
                self.ml = 1.0

        # load observational data
        self.dist = self.data['distance']
        self.pc = self.dist * np.pi / 0.648
        self.lum2d = self.data['lum2d']
        self.pot2d = self.data['pot2d']

        self.xbin = self.data['xbin']
        self.ybin = self.data['ybin']
        self.rms = self.data['rms'].clip(0.0, 600.0)
        self.goodbins = self.data['goodbins']
        self.symetrizedRms = self.rms.copy()
        self.symetrizedRms[self.goodbins] = \
            symmetrize_velfield(self.xbin[self.goodbins],
                                self.ybin[self.goodbins],
                                self.rms[self.goodbins])

        # run a JAM model with the choosen best model parameters
        JAMmodel = self.data['JAM']  # restore JAM model
        JAMlnprob = self.data['lnprob']
        self.rmsModel = JAMlnprob(bestPars,
                                  model=self.data,
                                  returnType='rmsModel')
        self.flux = JAMlnprob(bestPars, model=self.data, returnType='flux')
        self.shape = self.data['shape']
        # create stellar mass mge objected (used in mass profile)
        self.LmMge = util_mge.mge(self.pot2d,
                                  inc=self.inc,
                                  shape=self.shape,
                                  dist=self.dist)
        # set black hole mge object
        bh3dmge = JAMmodel.mge_bh
        if bh3dmge is None:
            self.BhMge = None
        else:
            bh2dmge = util_mge.projection(bh3dmge, inc=self.inc)
            self.BhMge = util_mge.mge(bh2dmge, inc=self.inc)

        if self.data['type'] == 'massFollowLight':
            self.labels = [
                r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{M/L}$'
            ]
            self.DmMge = None  # no dark halo
        elif self.data['type'] == 'spherical_gNFW':
            self.labels = [
                r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{M^*/L}$',
                r'$\mathbf{log\ \rho_s}$', r'$\mathbf{r_s}$',
                r'$\mathbf{\gamma}$'
            ]
            # create dark halo mass mge object
            dh = JAMlnprob(bestPars, model=self.data, returnType='dh')
            dh_mge3d = dh.mge3d()
            self.DmMge = util_mge.mge(dh.mge2d(), inc=self.inc)
        elif self.data['type'] == 'spherical_gNFW_logml':
            self.labels = [
                r'$\mathbf{cosi}$', r'$\mathbf{\beta}$',
                r'$\mathbf{logM^*/L}$', r'$\mathbf{log\ \rho_s}$',
                r'$\mathbf{r_s}$', r'$\mathbf{\gamma}$'
            ]
            # create dark halo mass mge object
            dh = JAMlnprob(bestPars, model=self.data, returnType='dh')
            dh_mge3d = dh.mge3d()
            self.DmMge = util_mge.mge(dh.mge2d(), inc=self.inc)
        elif self.data['type'] == 'spherical_gNFW_gas':
            inc = self.inc
            Beta = np.zeros(self.lum2d.shape[0]) + bestPars[1]
            ml = bestPars[2]
            logrho_s = bestPars[3]
            rs = bestPars[4]
            gamma = bestPars[5]
            dh = util_dm.gnfw1d(10**logrho_s, rs, gamma)
            dh_mge3d = dh.mge3d()
            gas3d = self.data['gas3d']
            dh_mge3d = np.append(gas3d, dh_mge3d, axis=0)
            self.rmsModel = JAMmodel.run(inc, Beta, ml=ml, mge_dh=dh_mge3d)
            self.flux = JAMmodel.flux
            self.labels = [
                r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{M^*/L}$',
                r'$\mathbf{log\ \rho_s}$', r'$\mathbf{r_s}$',
                r'$\mathbf{\gamma}$'
            ]
            # create dark halo mass mge object
            self.DmMge = util_mge.mge(dh.mge2d(), inc=self.inc)
        elif self.data['type'] == 'spherical_gNFW_gradient':
            self.labels = [
                r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{M^*/L}$',
                r'$\mathbf{\log \Delta_{IMF}}$', r'$\mathbf{log\ \rho_s}$',
                r'$\mathbf{r_s}$', r'$\mathbf{\gamma}$'
            ]
            # create dark halo mass mge object
            dh = JAMlnprob(bestPars, model=self.data, returnType='dh')
            dh_mge3d = dh.mge3d()
            self.DmMge = util_mge.mge(dh.mge2d(), inc=self.inc)
        elif self.data['type'] == 'spherical_total_dpl':
            self.labels = [
                r'$\mathbf{cosi}$', r'$\mathbf{\beta}$',
                r'$\mathbf{log\ \rho_s}$', r'$\mathbf{r_s}$',
                r'$\mathbf{\gamma}$'
            ]
            # create dark halo mass mge object
            dh = JAMlnprob(bestPars, model=self.data, returnType='dh')
            dh_mge3d = dh.mge3d()
            self.DmMge = util_mge.mge(dh.mge2d(), inc=self.inc)
        elif self.data['type'] == 'oblate_total_dpl':
            self.labels = [
                r'$\mathbf{cosi}$', r'$\mathbf{\beta}$',
                r'$\mathbf{log\ \rho_s}$', r'$\mathbf{r_s}$',
                r'$\mathbf{\gamma}$', r'$\mathbf{q}$'
            ]
            # create dark halo mass mge object
            dh = JAMlnprob(bestPars, model=self.data, returnType='dh')
            dh_mge3d = dh.mge3d()
            self.DmMge = util_mge.mge(dh.mge2d(self.inc), inc=self.inc)
        else:
            raise ValueError('model type {} not supported'.format(
                self.data['type']))