Exemplo n.º 1
0
    def make_fig(self, gal, vmin=None, vmax=None):

        #------------------------------------------
        #Plot the distribution of the burn-in process
        fig = plt.figure(figsize=(5, 10))
        plt.subplot(2, 1, 1)
        plt.title(self.gal)
        plt.plot(self.burn_chains[:, :, 0].T)
        plt.ylabel(r'Chain for $\beta^{JAM}_z$')
        plt.subplot(2, 1, 2)
        plt.plot(self.burn_chains[:, :, 1].T)
        plt.ylabel(r'Chain for $\Upsilon$')
        plt.tight_layout()  # This tightens up the spacing
        plt.savefig('figures/Burn_in/' + self.gal + '_burnchains.png')
        plt.close()

        #------------------------------------------
        #Plot the distribution of final process
        fig = plt.figure(figsize=(5, 10))
        plt.subplot(2, 1, 1)
        plt.title(self.gal)
        plt.plot(self.final_chains[:, :, 0].T)
        plt.ylabel(r'Chain for $\beta^{JAM}_z$')
        plt.subplot(2, 1, 2)
        plt.plot(self.final_chains[:, :, 1].T)
        plt.ylabel(r'Chain for $\Upsilon$')
        plt.tight_layout()  # This tightens up the spacing
        plt.savefig('figures/Chains/' + self.gal + '_finalchains.png')
        plt.close()

        #------------------------------------------
        # Corner Figure of the final Flatchain
        figure=corner.corner(self.final_flatchains, labels=[r"$\beta^{JAM}_z$", "$\Upsilon$"], \
          quantiles=[0.25, 0.50, 0.75],show_titles=True, title_fmt=".3f",title_args={"fontsize": 12} )
        figure.gca().annotate(self.gal+ ' JAM', xy=(0.5, 1.0), xycoords="figure fraction", xytext=(0, -5), \
          textcoords="offset points", ha="center", va="top")
        figure.savefig('figures/Corner/' + self.gal + '_corner.png')
        #------------------------------------------
        # Plot Vrmsbin and Vrmsmodel

        if (vmin is None) or (vmax is None):
            vmin, vmax = ss.scoreatpercentile(self.Vrmsbin, [0.5, 99.5])

        fig = plt.figure(figsize=(10, 5))
        plt.subplot(1, 2, 1)
        plot_velfield(self.xbin, self.ybin, self.Vrmsbin, vmin=vmin, vmax=vmax, flux=self.flux, colorbar=True, \
          orientation='horizontal', xlab='arcsec', ylab='arcsec', nodots=True)
        plt.title(r"$V^{OBS}_{rms}$")

        plt.subplot(1, 2, 2)
        plot_velfield(self.xbin, self.ybin, self.Vrmsmod, vmin=vmin, vmax=vmax, flux=self.flux, colorbar=True, \
          xlab='arcsec', ylab='arcsec', nodots=True)
        plt.title(r"$V^{MOD}_{rms}$, $\chi^2$=" +
                  str("{0:.2f}".format(self.chi2)))
        plt.tight_layout()
        plt.savefig('figures/Vrms/' + self.gal + '_Vrms.png')
        plt.close()
def plot_velfield_setup(vel, v2b_xy_file, flux_scopy_file):
    """
    Use default plotting scheme of pPXF to visualize the velocity field with 1 mag flux contours
    """

    assert os.path.exists(v2b_xy_file), 'File {} does not exist'.format(v2b_xy_file)
    assert os.path.exists(flux_scopy_file), 'File {} does not exist'.format(flux_scopy_file)
    assert len(vel) > 0, 'Input velocity does not make sense'

    xbar, ybar, xnode, ynode = np.loadtxt(v2b_xy_file, unpack=True, skiprows=1)
    flux = np.loadtxt(flux_scopy_file, unpack=True)

    assert len(xbar) == len(ybar), 'Xbar is not the same length as Ybar'
    assert len(xbar) == len(vel), 'Xbar is not the same length as vel'
    assert len(xbar) == len(flux), 'Xbar is not the same length as flux'

    plt.clf()
    plt.title('Velocity')
    plot_velfield(xbar, ybar, vel, flux=flux, colorbar=True, label='km/s')
    plt.show()
Exemplo n.º 3
0
def test_loess_2d():
    """
    Usage example for loess_2d

    """
    n = 200
    x = np.random.uniform(-1,1,n)
    y = np.random.uniform(-1,1,n)
    z = x**2 - y**2
    sigz = 0.2
    zran = np.random.normal(z, sigz)

    zout, wout = loess_2d(x, y, zran)

    plt.clf()
    plt.subplot(131)
    plot_velfield(x, y, z)
    plt.title("Underlying Function")

    plt.subplot(132)
    plot_velfield(x, y, zran)
    plt.title("With Noise Added")

    plt.subplot(133)
    plot_velfield(x, y, zout)
    plt.title("LOESS Recovery")
    plt.pause(1)
Exemplo n.º 4
0
dVrmsbin = np.max((pdVrmsbin, dVrmsbin), axis=0)

# deprojecting the velocity fields
sPA = np.sin(PA_gal * np.pi / 180.)
cPA = np.cos(PA_gal * np.pi / 180.)
xmod = -sPA * xbin + cPA * ybin
ymod = -cPA * xbin - sPA * ybin

#plotting Vbin, Sbin and Vrms,obs for check
fig = plt.figure(figsize=(15, 7))
plt.subplot(1, 3, 1)
plot_velfield(xmod,
              ymod,
              Vbin,
              flux=flux_org,
              colorbar=True,
              label='km/s',
              orientation='horizontal',
              xlab='arcsec',
              ylab='arcsec',
              nodots=True)
plt.title(r"$V_{obs}$")

plt.subplot(1, 3, 2)
plot_velfield(xmod,
              ymod,
              Sbin,
              flux=flux_org,
              colorbar=True,
              label='km/s',
              orientation='horizontal',
              xlab='arcsec',
Exemplo n.º 5
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
Exemplo n.º 6
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
Exemplo n.º 7
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