예제 #1
0
    def rg(self, lz):
        """
        NAME:
           rg
        PURPOSE:
           calculate the radius of a circular orbit of Lz
        INPUT:
           lz - Angular momentum
        OUTPUT:
           radius
        HISTORY:
           2012-07-25 - Written - Bovy (IAS@MPIA)
        NOTE:
           seems to take about ~0.5 ms for a Miyamoto-Nagai potential; 
           ~0.75 ms for a MWPotential
           about the same with or without interpolation of the rotation curve

           Not sure what to do about negative lz...
        """
        if isinstance(lz, numpy.ndarray):
            indx = (lz > self._precomputergLzmax) * (lz <
                                                     self._precomputergLzmin)
            indxc = True - indx
            out = numpy.empty(lz.shape)
            out[indxc] = self._rgInterp(lz[indxc])
            out[indx] = numpy.array([
                potential.rl(self._pot, lz[indx][ii])
                for ii in range(numpy.sum(indx))
            ])
            return out
        else:
            if lz > self._precomputergLzmax or lz < self._precomputergLzmin:
                return potential.rl(self._pot, lz)
            return self._rgInterp(lz)
예제 #2
0
def orbit_at_E_L(logE, logL, x=4. / 5., pot=MWPotential2014):
    '''
    return an orbit instance at a given energy and angular momentum in a given
    potential
    '''
    Einf = evalPot(pot, 10.**5., 0.)
    Rc = rl(pot, 10**logL)
    Ec = evalPot(pot, Rc, 0.) + 0.5 * (10**logL)**2. / Rc**2.
    Es = Ec + (Einf - Ec) * 10.**logE
    Er = 2. * (Es - Ec)
    vR = numpy.sqrt(x * Er)
    vz = numpy.sqrt((1 - x) * Er)
    o = Orbit([Rc, vR, (10**logL) / Rc, 0., vz, 0.])
    return o
예제 #3
0
def test_actionAngleTorus_basic_freqs():
    from galpy.actionAngle import actionAngleTorus
    from galpy.potential import epifreq, omegac, verticalfreq, rl, \
        JaffePotential, PowerSphericalPotential, HernquistPotential
    tol= -3.
    jr= 10.**-6.
    jz= 10.**-6.
    jp= JaffePotential(normalize=1.)
    aAT= actionAngleTorus(pot=jp)
    # at Lz=1
    jphi= 1.
    om= aAT.Freqs(jr,jphi,jz)
    assert numpy.fabs((om[0]-epifreq(jp,rl(jp,jphi)))/om[0]) < 10.**tol, \
        'Close-to-circular orbit does not have Or=kappa for actionAngleTorus'
    assert numpy.fabs((om[1]-omegac(jp,rl(jp,jphi)))/om[1]) < 10.**tol, \
        'Close-to-circular orbit does not have Ophi=omega for actionAngleTorus'
    assert numpy.fabs((om[2]-verticalfreq(jp,rl(jp,jphi)))/om[2]) < 10.**tol, \
        'Close-to-circular orbit does not have Oz=nu for actionAngleTorus'
    # at Lz=1.5, w/ different potential
    pp= PowerSphericalPotential(normalize=1.)
    aAT= actionAngleTorus(pot=pp)
    jphi= 1.5
    om= aAT.Freqs(jr,jphi,jz)
    assert numpy.fabs((om[0]-epifreq(pp,rl(pp,jphi)))/om[0]) < 10.**tol, \
        'Close-to-circular orbit does not have Or=kappa for actionAngleTorus'
    assert numpy.fabs((om[1]-omegac(pp,rl(pp,jphi)))/om[1]) < 10.**tol, \
        'Close-to-circular orbit does not have Ophi=omega for actionAngleTorus'
    assert numpy.fabs((om[2]-verticalfreq(pp,rl(pp,jphi)))/om[2]) < 10.**tol, \
        'Close-to-circular orbit does not have Oz=nu for actionAngleTorus'
    # at Lz=0.5, w/ different potential
    tol= -2.5 # appears more difficult
    hp= HernquistPotential(normalize=1.)
    aAT= actionAngleTorus(pot=hp)
    jphi= 0.5
    om= aAT.Freqs(jr,jphi,jz)
    assert numpy.fabs((om[0]-epifreq(hp,rl(hp,jphi)))/om[0]) < 10.**tol, \
        'Close-to-circular orbit does not have Or=kappa for actionAngleTorus'
    assert numpy.fabs((om[1]-omegac(hp,rl(hp,jphi)))/om[1]) < 10.**tol, \
        'Close-to-circular orbit does not have Ophi=omega for actionAngleTorus'
    assert numpy.fabs((om[2]-verticalfreq(hp,rl(hp,jphi)))/om[2]) < 10.**tol, \
        'Close-to-circular orbit does not have Oz=nu for actionAngleTorus'
    return None
예제 #4
0
def test_actionAngleTorus_basic():
    from galpy.actionAngle import actionAngleTorus
    from galpy.potential import MWPotential, rl, vcirc, \
        FlattenedPowerPotential, PlummerPotential
    tol= -4.
    jr= 10.**-10.
    jz= 10.**-10.
    aAT= actionAngleTorus(pot=MWPotential)
    # at R=1, Lz=1
    jphi= 1.
    angler= numpy.linspace(0.,2.*numpy.pi,101)
    anglephi= numpy.linspace(0.,2.*numpy.pi,101)+1.
    anglez= numpy.linspace(0.,2.*numpy.pi,101)+2.
    RvR= aAT(jr,jphi,jz,angler,anglephi,anglez).T
    assert numpy.all(numpy.fabs(RvR[0]-rl(MWPotential,jphi)) < 10.**tol), \
        'circular orbit does not have constant radius for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[1]) < 10.**tol), \
        'circular orbit does not have zero radial velocity for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[2]-vcirc(MWPotential,rl(MWPotential,jphi))) < 10.**tol), \
        'circular orbit does not have constant vT=vc for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[3]) < 10.**tol), \
        'circular orbit does not have zero vertical height for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[4]) < 10.**tol), \
        'circular orbit does not have zero vertical velocity for actionAngleTorus'
    # at Lz=1.5, using Plummer
    tol= -3.25
    pp= PlummerPotential(normalize=1.)
    aAT= actionAngleTorus(pot=pp)
    jphi= 1.5
    RvR= aAT(jr,jphi,jz,angler,anglephi,anglez).T
    assert numpy.all(numpy.fabs(RvR[0]-rl(pp,jphi)) < 10.**tol), \
        'circular orbit does not have constant radius for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[1]) < 10.**tol), \
        'circular orbit does not have zero radial velocity for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[2]-vcirc(pp,rl(pp,jphi))) < 10.**tol), \
        'circular orbit does not have constant vT=vc for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[3]) < 10.**tol), \
        'circular orbit does not have zero vertical height for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[4]) < 10.**tol), \
        'circular orbit does not have zero vertical velocity for actionAngleTorus'
    # at Lz=0.5, using FlattenedPowerPotential
    tol= -4.
    fp= FlattenedPowerPotential(normalize=1.)
    aAT= actionAngleTorus(pot=fp)
    jphi= 0.5
    RvR= aAT(jr,jphi,jz,angler,anglephi,anglez).T
    assert numpy.all(numpy.fabs(RvR[0]-rl(fp,jphi)) < 10.**tol), \
        'circular orbit does not have constant radius for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[1]) < 10.**tol), \
        'circular orbit does not have zero radial velocity for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[2]-vcirc(fp,rl(fp,jphi))) < 10.**tol), \
        'circular orbit does not have constant vT=vc for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[3]) < 10.**tol), \
        'circular orbit does not have zero vertical height for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[4]) < 10.**tol), \
        'circular orbit does not have zero vertical velocity for actionAngleTorus'
    return None
예제 #5
0
def test_actionAngleTorus_basic_freqs():
    from galpy.actionAngle import actionAngleTorus
    from galpy.potential import epifreq, omegac, verticalfreq, rl, \
        JaffePotential, PowerSphericalPotential, HernquistPotential
    tol= -3.
    jr= 10.**-6.
    jz= 10.**-6.
    jp= JaffePotential(normalize=1.)
    aAT= actionAngleTorus(pot=jp)
    # at Lz=1
    jphi= 1.
    om= aAT.Freqs(jr,jphi,jz)
    assert numpy.fabs((om[0]-epifreq(jp,rl(jp,jphi)))/om[0]) < 10.**tol, \
        'Close-to-circular orbit does not have Or=kappa for actionAngleTorus'
    assert numpy.fabs((om[1]-omegac(jp,rl(jp,jphi)))/om[1]) < 10.**tol, \
        'Close-to-circular orbit does not have Ophi=omega for actionAngleTorus'
    assert numpy.fabs((om[2]-verticalfreq(jp,rl(jp,jphi)))/om[2]) < 10.**tol, \
        'Close-to-circular orbit does not have Oz=nu for actionAngleTorus'
    # at Lz=1.5, w/ different potential
    pp= PowerSphericalPotential(normalize=1.)
    aAT= actionAngleTorus(pot=pp)
    jphi= 1.5
    om= aAT.Freqs(jr,jphi,jz)
    assert numpy.fabs((om[0]-epifreq(pp,rl(pp,jphi)))/om[0]) < 10.**tol, \
        'Close-to-circular orbit does not have Or=kappa for actionAngleTorus'
    assert numpy.fabs((om[1]-omegac(pp,rl(pp,jphi)))/om[1]) < 10.**tol, \
        'Close-to-circular orbit does not have Ophi=omega for actionAngleTorus'
    assert numpy.fabs((om[2]-verticalfreq(pp,rl(pp,jphi)))/om[2]) < 10.**tol, \
        'Close-to-circular orbit does not have Oz=nu for actionAngleTorus'
    # at Lz=0.5, w/ different potential
    tol= -2.5 # appears more difficult
    hp= HernquistPotential(normalize=1.)
    aAT= actionAngleTorus(pot=hp)
    jphi= 0.5
    om= aAT.Freqs(jr,jphi,jz)
    assert numpy.fabs((om[0]-epifreq(hp,rl(hp,jphi)))/om[0]) < 10.**tol, \
        'Close-to-circular orbit does not have Or=kappa for actionAngleTorus'
    assert numpy.fabs((om[1]-omegac(hp,rl(hp,jphi)))/om[1]) < 10.**tol, \
        'Close-to-circular orbit does not have Ophi=omega for actionAngleTorus'
    assert numpy.fabs((om[2]-verticalfreq(hp,rl(hp,jphi)))/om[2]) < 10.**tol, \
        'Close-to-circular orbit does not have Oz=nu for actionAngleTorus'
    return None
예제 #6
0
def test_actionAngleTorus_basic():
    from galpy.actionAngle import actionAngleTorus
    from galpy.potential import MWPotential, rl, vcirc, \
        FlattenedPowerPotential, PlummerPotential
    tol= -4.
    jr= 10.**-10.
    jz= 10.**-10.
    aAT= actionAngleTorus(pot=MWPotential)
    # at R=1, Lz=1
    jphi= 1.
    angler= numpy.linspace(0.,2.*numpy.pi,101)
    anglephi= numpy.linspace(0.,2.*numpy.pi,101)+1.
    anglez= numpy.linspace(0.,2.*numpy.pi,101)+2.
    RvR= aAT(jr,jphi,jz,angler,anglephi,anglez).T
    assert numpy.all(numpy.fabs(RvR[0]-rl(MWPotential,jphi)) < 10.**tol), \
        'circular orbit does not have constant radius for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[1]) < 10.**tol), \
        'circular orbit does not have zero radial velocity for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[2]-vcirc(MWPotential,rl(MWPotential,jphi))) < 10.**tol), \
        'circular orbit does not have constant vT=vc for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[3]) < 10.**tol), \
        'circular orbit does not have zero vertical height for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[4]) < 10.**tol), \
        'circular orbit does not have zero vertical velocity for actionAngleTorus'
    # at Lz=1.5, using Plummer
    tol= -3.25
    pp= PlummerPotential(normalize=1.)
    aAT= actionAngleTorus(pot=pp)
    jphi= 1.5
    RvR= aAT(jr,jphi,jz,angler,anglephi,anglez).T
    assert numpy.all(numpy.fabs(RvR[0]-rl(pp,jphi)) < 10.**tol), \
        'circular orbit does not have constant radius for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[1]) < 10.**tol), \
        'circular orbit does not have zero radial velocity for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[2]-vcirc(pp,rl(pp,jphi))) < 10.**tol), \
        'circular orbit does not have constant vT=vc for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[3]) < 10.**tol), \
        'circular orbit does not have zero vertical height for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[4]) < 10.**tol), \
        'circular orbit does not have zero vertical velocity for actionAngleTorus'
    # at Lz=0.5, using FlattenedPowerPotential
    tol= -4.
    fp= FlattenedPowerPotential(normalize=1.)
    aAT= actionAngleTorus(pot=fp)
    jphi= 0.5
    RvR= aAT(jr,jphi,jz,angler,anglephi,anglez).T
    assert numpy.all(numpy.fabs(RvR[0]-rl(fp,jphi)) < 10.**tol), \
        'circular orbit does not have constant radius for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[1]) < 10.**tol), \
        'circular orbit does not have zero radial velocity for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[2]-vcirc(fp,rl(fp,jphi))) < 10.**tol), \
        'circular orbit does not have constant vT=vc for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[3]) < 10.**tol), \
        'circular orbit does not have zero vertical height for actionAngleTorus'
    assert numpy.all(numpy.fabs(RvR[4]) < 10.**tol), \
        'circular orbit does not have zero vertical velocity for actionAngleTorus'
    return None
예제 #7
0
def calc_delta_MWPotential2014(savefilename,plotfilename):
    Lmin, Lmax= 0.01, 10.
    if not os.path.exists(savefilename):
        #Setup grid
        nL, nE= 101,101
        Ls= 10.**numpy.linspace(numpy.log10(Lmin),numpy.log10(Lmax),nL)
        #Integration times
        ts= numpy.linspace(0.,20.,1001)
        deltas= numpy.empty((nL,nE))
        Einf= evalPot(10.**12.,0.,MWPotential2014)
        print Einf
        for ii in range(nL):
            #Calculate Ec
            Rc= rl(MWPotential2014,Ls[ii])
            print ii, "Rc = ", Rc*8.
            Ec= evalPot(Rc,0.,MWPotential2014)+0.5*Ls[ii]**2./Rc**2.
            Es= Ec+(Einf-Ec)*10.**numpy.linspace(-2.,0.,nE)
            for jj in range(nE):
                #Setup an orbit with this energy and angular momentum
                Er= 2.*(Es[jj]-Ec) #Random energy times 2 = vR^2 + vz^2
                vR= numpy.sqrt(4./5.*Er)
                vz= numpy.sqrt(1./5.*Er)
                o= Orbit([Rc,vR,Ls[ii]/Rc,0.,vz,0.])
                o.integrate(ts,MWPotential2014,method='symplec4_c')
                deltas[ii,jj]= estimateDeltaStaeckel(o.R(ts),o.z(ts),
                                                     pot=MWPotential2014)
        #Save
        save_pickles(savefilename,deltas)
    else:
        savefile= open(savefilename,'rb')
        deltas= pickle.load(savefile)
        savefile.close()
    #Plot
    print numpy.nanmax(deltas)
    bovy_plot.bovy_print()
    bovy_plot.bovy_dens2d(deltas.T,origin='lower',cmap='jet',
                          xrange=[numpy.log10(Lmin),numpy.log10(Lmax)],
                          yrange=[-2,0.],
                          xlabel=r'$\log_{10} L$',
                          ylabel=r'$\log_{10}\left(\frac{E-E_c(L)}{E(\infty)-E_c(L)}\right)$',
                          colorbar=True,shrink=0.78,
                          zlabel=r'$\mathrm{Approximate\ focal\ length}$',
#                          interpolation='nearest',
                          vmin=0.,vmax=0.6)
    bovy_plot.bovy_end_print(plotfilename)
    return None
예제 #8
0
def orbits_on_grid(logE_range=[-2., 0.],
                   logL_range=[-2., 1.],
                   nE=101,
                   nL=101,
                   x=4. / 5.,
                   pot=MWPotential2014):
    '''
    generate initial phase-space points for a grid of orbits in a given potential
    '''
    grid = np.empty([nE, nL, 6])
    logEs = np.linspace(logE_range[0], logE_range[1], nE)
    logLs = np.linspace(logL_range[0], logL_range[1], nL)
    Einf = evalPot(pot, 10.**12., 0.)
    for j in range(nL):
        Rc = rl(pot, 10**logLs[j])
        Ec = evalPot(pot, Rc, 0.) + 0.5 * (10**logLs[j])**2. / Rc**2.
        Es = Ec + (Einf - Ec) * 10.**logEs
        for i in range(nE):
            Er = 2. * (Es[i] - Ec)
            vR = numpy.sqrt(x * Er)
            vz = numpy.sqrt((1 - x) * Er)
            grid[i, j] = [Rc, vR, (10**logLs[j]) / Rc, 0., vz, 0.]
    return grid
예제 #9
0
 def __init__(self,
              hr,
              sr,
              sz,
              hsr,
              hsz,
              pot=None,
              aA=None,
              cutcounter=False,
              _precomputerg=True,
              _precomputergrmax=None,
              _precomputergnLz=51,
              ro=1.,
              lo=10. / 220. * 8.):
     """
     NAME:
        __init__
     PURPOSE:
        Initialize a quasi-isothermal DF
     INPUT:
        hr - radial scale length
        sr - radial velocity dispersion at the solar radius
        sz - vertical velocity dispersion at the solar radius
        hsr - radial-velocity-dispersion scale length
        hsz - vertial-velocity-dispersion scale length
        pot= Potential instance or list thereof
        aA= actionAngle instance used to convert (x,v) to actions
        cutcounter= if True, set counter-rotating stars' DF to zero
        ro= reference radius for surface mass and sigmas
        lo= reference angular momentum below where there are significant numbers of retrograde stars
     OTHER INPUTS:
        _precomputerg= if True (default), pre-compute the rL(L)
        _precomputergrmax= if set, this is the maximum R for which to pre-compute rg (default: 5*hr)
        _precomputergnLz if set, number of Lz to pre-compute rg for (default: 51)
     OUTPUT:
        object
     HISTORY:
        2012-07-25 - Started - Bovy (IAS@MPIA)
     """
     self._hr = hr
     self._sr = sr
     self._sz = sz
     self._hsr = hsr
     self._hsz = hsz
     self._ro = ro
     self._lo = lo
     self._lnsr = math.log(self._sr)
     self._lnsz = math.log(self._sz)
     if pot is None:
         raise IOError("pot= must be set")
     self._pot = pot
     if aA is None:
         raise IOError("aA= must be set")
     self._aA = aA
     self._cutcounter = cutcounter
     if _precomputerg:
         if _precomputergrmax is None:
             _precomputergrmax = 5 * self._hr
         self._precomputergrmax = _precomputergrmax
         self._precomputergnLz = _precomputergnLz
         self._precomputergLzmin = 0.01
         self._precomputergLzmax= self._precomputergrmax\
             *potential.vcirc(self._pot,self._precomputergrmax)
         self._precomputergLzgrid = numpy.linspace(self._precomputergLzmin,
                                                   self._precomputergLzmax,
                                                   self._precomputergnLz)
         self._rls = numpy.array(
             [potential.rl(self._pot, l) for l in self._precomputergLzgrid])
         #Spline interpolate
         self._rgInterp = interpolate.InterpolatedUnivariateSpline(
             self._precomputergLzgrid, self._rls, k=3)
     else:
         self._precomputergrmax = 0.
         self._rgInterp = None
         self._rls = None
         self._precomputergnr = None
         self._precomputergLzgrid = None
     self._precomputerg = _precomputerg
     return None
def process_single(i):
    vxvv = [ra[i], dec[i], distance[i], pmra[i], pmdec[i], rv[i]]
    if not np.all(np.isfinite(vxvv)) or not np.all(np.isfinite(covariance[i])):
        return np.ones(56) * np.nan
    samp = np.random.multivariate_normal(vxvv, covariance[i], size=1000)
    if not (np.all(samp[:, 1] < 90.) & np.all(samp[:, 1] > -90.)):
        return np.ones(56) * np.nan

    os = Orbit(np.array([
        samp[:, 0], samp[:, 1], samp[:, 2], samp[:, 3], samp[:, 4], samp[:, 5]
    ]).T,
               radec=True,
               ro=_R0,
               vo=_v0,
               zo=_z0,
               solarmotion=[-11.1, 25.7, 7.25])

    sXYZ = np.dstack([os.x(), os.y(), os.z()])[0] / _R0
    sRpz = np.dstack([os.R() / _R0, os.phi(), os.z() / _R0])[0]
    svRvTvz = np.dstack([os.vR(), os.vT(), os.vz()])[0] / _v0

    deltas = estimateDeltaStaeckel(MWPotential2014,
                                   np.median(sRpz[:, 0]),
                                   np.median(sRpz[:, 2]),
                                   no_median=True)
    aAS = actionAngleStaeckel(pot=MWPotential2014, delta=np.mean(deltas))
    e, zmax, rperi, rap = aAS.EccZmaxRperiRap(sRpz[:, 0],
                                              svRvTvz[:, 0],
                                              svRvTvz[:, 1],
                                              sRpz[:, 2],
                                              svRvTvz[:, 2],
                                              sRpz[:, 1],
                                              delta=deltas)
    tcov = np.cov(np.dstack([e, zmax, rperi, rap])[0].T)
    errs = np.sqrt(np.diag(tcov))
    e_zmax_corr = tcov[0, 1] / (errs[0] * errs[1])
    e_rperi_corr = tcov[0, 2] / (errs[0] * errs[2])
    e_rap_corr = tcov[0, 3] / (errs[0] * errs[3])
    zmax_rperi_corr = tcov[1, 2] / (errs[1] * errs[2])
    zmax_rap_corr = tcov[1, 3] / (errs[1] * errs[3])
    rperi_rap_corr = tcov[2, 3] / (errs[2] * errs[3])
    e_err, zmax_err, rperi_err, rap_err = errs
    action = np.array(
        aAS.actionsFreqsAngles(sRpz[:, 0], svRvTvz[:, 0], svRvTvz[:, 1],
                               sRpz[:, 2], svRvTvz[:, 2], sRpz[:, 1]))
    tcov_after_action = np.cov(action)
    errs = np.sqrt(np.diag(tcov_after_action))
    jr_lz_corr = tcov_after_action[0, 1] / (errs[0] * errs[1])
    jr_jz_corr = tcov_after_action[0, 2] / (errs[0] * errs[2])
    lz_jz_corr = tcov_after_action[1, 2] / (errs[1] * errs[2])
    jr_err, lz_err, jz_err, or_err, op_err, oz_err, tr_err, tphi_err, tz_err = errs

    Rc = np.array([rl(MWPotential2014, lz) for lz in action[1]]) * _R0
    Ec = (evaluatePotentials(MWPotential2014, Rc, 0.) + 0.5 *
          (action[1])**2. / Rc**2.) * _v0**2
    E = os.E(pot=MWPotential2014)

    # galactocentric coord and vel uncertainty
    galcen_tcov = np.cov(np.dstack([sRpz[:, 0], sRpz[:, 1], sRpz[:, 2]])[0].T)
    galcen_errs = np.sqrt(np.diag(galcen_tcov))
    galr_err, galphi_err, galz_err = galcen_errs

    galcenv_tcov = np.cov(
        np.dstack([svRvTvz[:, 0], svRvTvz[:, 1], svRvTvz[:, 2]])[0].T)
    galcenv_errs = np.sqrt(np.diag(galcenv_tcov))
    galvr_err, galvt_err, galvz_err = galcenv_errs

    galvr_galvt_corr = galcenv_tcov[0, 1] / (galcenv_errs[0] * galcenv_errs[1])
    galvr_galvz_corr = galcenv_tcov[0, 2] / (galcenv_errs[0] * galcenv_errs[2])
    galvt_galvz_corr = galcenv_tcov[1, 2] / (galcenv_errs[1] * galcenv_errs[2])

    # galr mean to avoid issue near GC, error propagation
    x_mean = np.nanmean(sXYZ[:, 0])
    y_mean = np.nanmean(sXYZ[:, 1])
    x_err = np.nanstd(sXYZ[:, 0])
    y_err = np.nanstd(sXYZ[:, 1])
    x_err_percentage = x_err / np.nanmean(sXYZ[:, 0])
    y_err_percentage = y_err / np.nanmean(sXYZ[:, 1])
    x2_err = (x_mean**2) * (2 * x_err_percentage)
    y2_err = (y_mean**2) * (2 * y_err_percentage)
    galr = np.sqrt(x_mean**2 + y_mean**2)
    galr2_err = np.sqrt(x2_err**2 + y2_err**2)
    galr2_err_percentage = galr2_err / (galr**2)
    galr_err = galr * (0.5 * galr2_err_percentage)

    # galphi mean to avoid issue near GC, error propagation
    galphi = np.arctan2(y_mean, x_mean)
    galphi_err_x = (
        y_mean /
        (x_mean**2 + y_mean**2)) * x_err  # error propagation from x_mean
    galphi_err_y = (
        -x_mean /
        (x_mean**2 + y_mean**2)) * y_err  # error propagation from y_mean
    galphi_err = np.sqrt(galphi_err_x**2 + galphi_err_y**2)  # add them up

    return np.nanmean(e), \
           e_err, \
           np.nanmean(zmax) * _R0, \
           zmax_err * _R0, \
           np.nanmean(rperi) * _R0, \
           rperi_err * _R0, \
           np.nanmean(rap) * _R0, \
           rap_err * _R0, \
           e_zmax_corr, \
           e_rperi_corr, \
           e_rap_corr, \
           zmax_rperi_corr, \
           zmax_rap_corr, \
           rperi_rap_corr, \
           np.nanmean(action[0]) * _R0 * _v0, \
           jr_err * _R0 * _v0, \
           np.nanmean(action[1]) * _R0 * _v0, \
           lz_err * _R0 * _v0, \
           np.nanmean(action[2]) * _R0 * _v0, \
           jz_err * _R0 * _v0, \
           jr_lz_corr, \
           jr_jz_corr, \
           lz_jz_corr, \
           np.nanmean(action[3]) * _freq, or_err * _freq, \
           np.nanmean(action[4]) * _freq, op_err * _freq, \
           np.nanmean(action[5]) * _freq, oz_err * _freq, \
           np.nanmean(action[6]), \
           tr_err, \
           np.nanmean(action[7]), \
           tphi_err, \
           np.nanmean(action[8]), \
           tz_err, \
           np.nanmean(Rc), \
           np.nanstd(Rc), \
           np.nanmean(E), \
           np.nanstd(E), \
           np.nanmean(E - Ec), \
           np.nanstd(E - Ec), \
           galr * _R0, \
           galphi, \
           np.nanmean(sRpz[:, 2]) * _R0, \
           np.nanmean(svRvTvz[:, 0]) * _v0, \
           np.nanmean(svRvTvz[0, 1]) * _v0, \
           np.nanmean(svRvTvz[:, 2]) * _v0, \
           galr_err * _R0, \
           galphi_err, \
           galz_err * _R0, \
           galvr_err * _v0, \
           galvt_err * _v0, \
           galvz_err * _v0, \
           galvr_galvt_corr, \
           galvr_galvz_corr, \
           galvt_galvz_corr
def process_single(i):
    vxvv = [ra[i], dec[i], distance[i], pmra[i], pmdec[i], rv[i]]
    if not np.all(np.isfinite(vxvv)) or not np.all(np.isfinite(covariance[i])):
        return np.ones(56) * np.nan
    samp = np.random.multivariate_normal(vxvv, covariance[i], size=1000)
    if not (np.all(samp[:, 1] < 90.) & np.all(samp[:, 1] > -90.)):
        return np.ones(56) * np.nan
    sXYZ, svxyz, sRpz, svRvTvz = obs_to_galcen(samp[:, 0],
                                               samp[:, 1],
                                               samp[:, 2],
                                               samp[:, 3],
                                               samp[:, 4],
                                               samp[:, 5],
                                               ro=_R0,
                                               vo=_v0,
                                               zo=_z0)
    deltas = estimateDeltaStaeckel(MWPotential2014, np.median(sRpz[:, 0]), np.median(sRpz[:, 2]), no_median=True)
    aAS = actionAngleStaeckel(pot=MWPotential2014, delta=np.mean(deltas))
    e, zmax, rperi, rap = aAS.EccZmaxRperiRap(sRpz[:, 0],
                                              svRvTvz[:, 0],
                                              svRvTvz[:, 1],
                                              sRpz[:, 2],
                                              svRvTvz[:, 2],
                                              sRpz[:, 1], delta=deltas)
    tcov = np.cov(np.dstack([e, zmax, rperi, rap])[0].T)
    errs = np.sqrt(np.diag(tcov))
    e_zmax_corr = tcov[0, 1] / (errs[0] * errs[1])
    e_rperi_corr = tcov[0, 2] / (errs[0] * errs[2])
    e_rap_corr = tcov[0, 3] / (errs[0] * errs[3])
    zmax_rperi_corr = tcov[1, 2] / (errs[1] * errs[2])
    zmax_rap_corr = tcov[1, 3] / (errs[1] * errs[3])
    rperi_rap_corr = tcov[2, 3] / (errs[2] * errs[3])
    e_err, zmax_err, rperi_err, rap_err = errs
    action = np.array(
        aAS.actionsFreqsAngles(sRpz[:, 0], svRvTvz[:, 0], svRvTvz[:, 1], sRpz[:, 2], svRvTvz[:, 2], sRpz[:, 1]))
    tcov_after_action = np.cov(action)
    errs = np.sqrt(np.diag(tcov_after_action))
    jr_lz_corr = tcov_after_action[0, 1] / (errs[0] * errs[1])
    jr_jz_corr = tcov_after_action[0, 2] / (errs[0] * errs[2])
    lz_jz_corr = tcov_after_action[1, 2] / (errs[1] * errs[2])
    jr_err, lz_err, jz_err, or_err, op_err, oz_err, tr_err, tphi_err, tz_err = errs

    Rc = np.array([rl(MWPotential2014, lz) for lz in action[1]]) * _R0
    Ec = (evaluatePotentials(MWPotential2014, Rc, 0.) + 0.5 * (action[1]) ** 2. / Rc ** 2.) * _v0 ** 2
    E = (evaluatePotentials(MWPotential2014, sRpz[:, 0], sRpz[:, 2], phi=sRpz[:, 1]) + np.sum(svRvTvz ** 2 / 2.,
                                                                                              axis=1)) * _v0 ** 2

    # galactocentric coord and vel uncertainty
    galcen_tcov = np.cov(np.dstack([sRpz[:, 0], sRpz[:, 1], sRpz[:, 2]])[0].T)
    galcen_errs = np.sqrt(np.diag(galcen_tcov))
    galr_err, galphi_err, galz_err = galcen_errs

    galcenv_tcov = np.cov(np.dstack([svRvTvz[:, 0], svRvTvz[:, 1], svRvTvz[:, 2]])[0].T)
    galcenv_errs = np.sqrt(np.diag(galcenv_tcov))
    galvr_err, galvt_err, galvz_err = galcenv_errs

    galvr_galvt_corr = galcenv_tcov[0, 1] / (galcenv_errs[0] * galcenv_errs[1])
    galvr_galvz_corr = galcenv_tcov[0, 2] / (galcenv_errs[0] * galcenv_errs[2])
    galvt_galvz_corr = galcenv_tcov[1, 2] / (galcenv_errs[1] * galcenv_errs[2])

    return np.nanmean(e), e_err, np.nanmean(zmax) * _R0, zmax_err * _R0, np.nanmean(
        rperi) * _R0, rperi_err * _R0, np.nanmean(rap) * _R0, rap_err * _R0, \
           e_zmax_corr, e_rperi_corr, e_rap_corr, zmax_rperi_corr, zmax_rap_corr, rperi_rap_corr, \
           np.nanmean(action[0]) * _R0 * _v0, jr_err * _R0 * _v0, np.nanmean(
        action[1]) * _R0 * _v0, lz_err * _R0 * _v0, np.nanmean(action[2]) * _R0 * _v0, jz_err * _R0 * _v0, \
           jr_lz_corr, jr_jz_corr, lz_jz_corr, \
           np.nanmean(action[3]) * _freq, or_err * _freq, np.nanmean(action[4]) * _freq, op_err * _freq, np.nanmean(
        action[5]) * _freq, oz_err * _freq, \
           np.nanmean(action[6]), tr_err, np.nanmean(action[7]), tphi_err, np.nanmean(action[8]), tz_err, \
           np.nanmean(Rc), np.nanstd(Rc), np.nanmean(E), np.nanstd(E), np.nanmean(E - Ec), np.nanstd(
        E - Ec), \
           np.nanmean(sRpz[:, 0]) * _R0, \
           np.nanmean(sRpz[:, 1]), \
           np.nanmean(sRpz[:, 2]) * _R0, \
           np.nanmean(svRvTvz[:, 0]) * _v0, \
           np.nanmean(svRvTvz[0, 1]) * _v0, \
           np.nanmean(svRvTvz[:, 2]) * _v0, \
           galr_err * _R0, \
           galphi_err, \
           galz_err * _R0, \
           galvr_err * _v0, \
           galvt_err * _v0, \
           galvz_err * _v0, \
           galvr_galvt_corr, \
           galvr_galvz_corr, \
           galvt_galvz_corr