Ejemplo n.º 1
0
    def __init__(self,amp=1.,ro=1.,hr=1./3.,hz=1./16.,
                 maxiter=_MAXITER,tol=0.001,normalize=False,
                 new=True,kmaxFac=2.,glorder=10):
        """
        NAME:

           __init__

        PURPOSE:

           initialize a double-exponential disk potential

        INPUT:

           amp - amplitude to be applied to the potential (default: 1)

           hr - disk scale-length in terms of ro

           hz - scale-height

           tol - relative accuracy of potential-evaluations

           maxiter - scipy.integrate keyword

           normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1.

        OUTPUT:

           DoubleExponentialDiskPotential object

        HISTORY:

           2010-04-16 - Written - Bovy (NYU)

           2013-01-01 - Re-implemented using faster integration techniques - Bovy (IAS)

        """
        Potential.__init__(self,amp=amp)
        self.hasC= True
        self._new= new
        self._kmaxFac= kmaxFac
        self._glorder= glorder
        self._ro= ro
        self._hr= hr
        self._scale= self._hr
        self._hz= hz
        self._alpha= 1./self._hr
        self._beta= 1./self._hz
        self._gamma= self._alpha/self._beta
        self._maxiter= maxiter
        self._tol= tol
        self._zforceNotSetUp= True #We have not calculated a typical Kz yet
        #Setup j0 zeros etc.
        self._glx, self._glw= nu.polynomial.legendre.leggauss(self._glorder)
        self._nzeros=100
        #j0 for potential and z
        self._j0zeros= nu.zeros(self._nzeros+1)
        self._j0zeros[1:self._nzeros+1]= special.jn_zeros(0,self._nzeros)
        self._dj0zeros= self._j0zeros-nu.roll(self._j0zeros,1)
        self._dj0zeros[0]= self._j0zeros[0]
        #j1 for R
        self._j1zeros= nu.zeros(self._nzeros+1)
        self._j1zeros[1:self._nzeros+1]= special.jn_zeros(1,self._nzeros)
        self._dj1zeros= self._j1zeros-nu.roll(self._j1zeros,1)
        self._dj1zeros[0]= self._j1zeros[0]
        #j2 for R2deriv
        self._j2zeros= nu.zeros(self._nzeros+1)
        self._j2zeros[1:self._nzeros+1]= special.jn_zeros(2,self._nzeros)
        self._dj2zeros= self._j2zeros-nu.roll(self._j2zeros,1)
        self._dj2zeros[0]= self._j2zeros[0]
        if normalize or \
                (isinstance(normalize,(int,float)) \
                     and not isinstance(normalize,bool)):
            self.normalize(normalize)
        #Load Kepler potential for large R
        self._kp= KeplerPotential(normalize=4.*nu.pi/self._alpha**2./self._beta)
Ejemplo n.º 2
0
class DoubleExponentialDiskPotential(Potential):
    """Class that implements the double exponential disk potential
    rho(R,z) = rho_0 e^-R/h_R e^-|z|/h_z"""
    def __init__(self,amp=1.,ro=1.,hr=1./3.,hz=1./16.,
                 maxiter=_MAXITER,tol=0.001,normalize=False,
                 new=True,kmaxFac=2.,glorder=10):
        """
        NAME:

           __init__

        PURPOSE:

           initialize a double-exponential disk potential

        INPUT:

           amp - amplitude to be applied to the potential (default: 1)

           hr - disk scale-length in terms of ro

           hz - scale-height

           tol - relative accuracy of potential-evaluations

           maxiter - scipy.integrate keyword

           normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1.

        OUTPUT:

           DoubleExponentialDiskPotential object

        HISTORY:

           2010-04-16 - Written - Bovy (NYU)

           2013-01-01 - Re-implemented using faster integration techniques - Bovy (IAS)

        """
        Potential.__init__(self,amp=amp)
        self.hasC= True
        self._new= new
        self._kmaxFac= kmaxFac
        self._glorder= glorder
        self._ro= ro
        self._hr= hr
        self._scale= self._hr
        self._hz= hz
        self._alpha= 1./self._hr
        self._beta= 1./self._hz
        self._gamma= self._alpha/self._beta
        self._maxiter= maxiter
        self._tol= tol
        self._zforceNotSetUp= True #We have not calculated a typical Kz yet
        #Setup j0 zeros etc.
        self._glx, self._glw= nu.polynomial.legendre.leggauss(self._glorder)
        self._nzeros=100
        #j0 for potential and z
        self._j0zeros= nu.zeros(self._nzeros+1)
        self._j0zeros[1:self._nzeros+1]= special.jn_zeros(0,self._nzeros)
        self._dj0zeros= self._j0zeros-nu.roll(self._j0zeros,1)
        self._dj0zeros[0]= self._j0zeros[0]
        #j1 for R
        self._j1zeros= nu.zeros(self._nzeros+1)
        self._j1zeros[1:self._nzeros+1]= special.jn_zeros(1,self._nzeros)
        self._dj1zeros= self._j1zeros-nu.roll(self._j1zeros,1)
        self._dj1zeros[0]= self._j1zeros[0]
        #j2 for R2deriv
        self._j2zeros= nu.zeros(self._nzeros+1)
        self._j2zeros[1:self._nzeros+1]= special.jn_zeros(2,self._nzeros)
        self._dj2zeros= self._j2zeros-nu.roll(self._j2zeros,1)
        self._dj2zeros[0]= self._j2zeros[0]
        if normalize or \
                (isinstance(normalize,(int,float)) \
                     and not isinstance(normalize,bool)):
            self.normalize(normalize)
        #Load Kepler potential for large R
        self._kp= KeplerPotential(normalize=4.*nu.pi/self._alpha**2./self._beta)

    def _evaluate(self,R,z,phi=0.,t=0.,dR=0,dphi=0):
        """
        NAME:
           _evaluate
        PURPOSE:
           evaluate the potential at (R,z)
        INPUT:
           R - Cylindrical Galactocentric radius
           z - vertical height
           phi - azimuth
           t - time
        OUTPUT:
           potential at (R,z)
        HISTORY:
           2010-04-16 - Written - Bovy (NYU)
           2012-12-26 - New method using Gaussian quadrature between zeros - Bovy (IAS)
        DOCTEST:
           >>> doubleExpPot= DoubleExponentialDiskPotential()
           >>> r= doubleExpPot(1.,0) #doctest: +ELLIPSIS
           ...
           >>> assert( r+1.89595350484)**2.< 10.**-6.
        """
        if dR == 1 and dphi == 0:
            return -self._Rforce(R,z,phi=phi,t=t)
        elif dR == 0 and dphi == 1:
            return -self._phiforce(R,z,phi=phi,t=t)
        elif dR == 2 and dphi == 0:
            return self._R2deriv(R,z,phi=phi,t=t)
        elif dR != 0 and dphi != 0:
            warnings.warn("High-order derivatives for DoubleExponentialDiskPotential not implemented",galpyWarning)
            return None
        if self._new:
            if isinstance(R,float):
                floatIn= True
                R= nu.array([R])
                z= nu.array([z])
            else:
                floatIn= False
            out= nu.empty(len(R))
            indx= (R <= 6.)
            out[True-indx]= self._kp(R[True-indx],z[True-indx])
            R4max= nu.copy(R)
            R4max[(R < 1.)]= 1.
            kmax= self._kmaxFac*self._beta
            for jj in range(len(R)):
                if not indx[jj]: continue
                maxj0zeroIndx= nu.argmin((self._j0zeros-kmax*R4max[jj])**2.) #close enough
                ks= nu.array([0.5*(self._glx+1.)*self._dj0zeros[ii+1] + self._j0zeros[ii] for ii in range(maxj0zeroIndx)]).flatten()
                weights= nu.array([self._glw*self._dj0zeros[ii+1] for ii in range(maxj0zeroIndx)]).flatten()
                evalInt= special.jn(0,ks*R[jj])*(self._alpha**2.+ks**2.)**-1.5*(self._beta*nu.exp(-ks*nu.fabs(z[jj]))-ks*nu.exp(-self._beta*nu.fabs(z[jj])))/(self._beta**2.-ks**2.)
                out[jj]= -2.*nu.pi*self._alpha*nu.sum(weights*evalInt)
            if floatIn: return out[0]
            else: return out
        #Old code, uses scipy's quadrature to do the relevant integrals, split into two
        notConvergedSmall= True
        notConvergedLarge= True
        smallkIntegral= integrate.quadrature(_doubleExponentialDiskPotentialPotentialIntegrandSmallk,
                                             0.,1./self._gamma,
                                             args=(self._alpha*R,
                                                   self._beta*nu.fabs(z),
                                                   self._gamma),tol=_TOL,
                                             maxiter=self._maxiter,
                                             vec_func=False)
        largekIntegral= integrate.quadrature(_doubleExponentialDiskPotentialPotentialIntegrandLargek,
                                             0.,self._gamma,
                                             args=(self._alpha*R,
                                                   self._beta*nu.fabs(z),
                                                   self._gamma),tol=_TOL,
                                             maxiter=self._maxiter,
                                             vec_func=False)
        maxiterFactorSmall= 2
        maxiterFactorLarge= 2
        if nu.fabs(smallkIntegral[1]/(smallkIntegral[0]+largekIntegral[0])) <= self._tol:
            notConvergedSmall= False
        if nu.fabs(largekIntegral[1]/(largekIntegral[0]+smallkIntegral[0])) <= self._tol:
            notConvergedLarge= False
        while notConvergedSmall or notConvergedLarge:
            if notConvergedSmall:
                smallkIntegral= integrate.quadrature(_doubleExponentialDiskPotentialPotentialIntegrandSmallk,
                                                     0.,1./self._gamma,
                                                     args=(self._alpha*R,
                                                           self._beta*nu.fabs(z),
                                                           self._gamma),tol=_TOL,
                                                     maxiter= maxiterFactorSmall*self._maxiter,
                                                     vec_func=False)
                if nu.fabs(smallkIntegral[1]/(smallkIntegral[0]+largekIntegral[0])) > self._tol:
                    maxiterFactorSmall*= 2
                else:
                    notConvergedSmall= False
            if notConvergedLarge:
                largekIntegral= integrate.quadrature(_doubleExponentialDiskPotentialPotentialIntegrandLargek,
                                                     0.,self._gamma,
                                                     args=(self._alpha*R,
                                                           self._beta*nu.fabs(z),
                                                           self._gamma),tol=_TOL,
                                                     maxiter=maxiterFactorLarge*self._maxiter,
                                                     vec_func=False)
                if nu.fabs(largekIntegral[1]/(largekIntegral[0]+smallkIntegral[0])) > self._tol:
                    maxiterFactorLarge*= 2
                else:
                    notConvergedLarge= False
        return -4.*nu.pi/self._alpha/self._beta*(smallkIntegral[0]+largekIntegral[0])
    
    def _Rforce(self,R,z,phi=0.,t=0.):
        """
        NAME:
           Rforce
        PURPOSE:
           evaluate radial force K_R  (R,z)
        INPUT:
           R - Cylindrical Galactocentric radius
           z - vertical height
           phi - azimuth
           t - time
        OUTPUT:
           K_R (R,z)
        HISTORY:
           2010-04-16 - Written - Bovy (NYU)
        DOCTEST:
        """
        if self._new:
            if isinstance(R,nu.ndarray):
                if not isinstance(z,nu.ndarray): z= nu.ones_like(R)*z
                out= nu.array([self._Rforce(rr,zz) for rr,zz in zip(R,z)])
                return out
            if R > 6.: return self._kp.Rforce(R,z)
            if R < 1.: R4max= 1.
            else: R4max= R
            kmax= self._kmaxFac*self._beta
            kmax= 2.*self._kmaxFac*self._beta
            maxj1zeroIndx= nu.argmin((self._j1zeros-kmax*R4max)**2.) #close enough
            ks= nu.array([0.5*(self._glx+1.)*self._dj1zeros[ii+1] + self._j1zeros[ii] for ii in range(maxj1zeroIndx)]).flatten()
            weights= nu.array([self._glw*self._dj1zeros[ii+1] for ii in range(maxj1zeroIndx)]).flatten()
            evalInt= ks*special.jn(1,ks*R)*(self._alpha**2.+ks**2.)**-1.5*(self._beta*nu.exp(-ks*nu.fabs(z))-ks*nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks**2.)
            return -2.*nu.pi*self._alpha*nu.sum(weights*evalInt)
        #Old code, uses scipy's quadrature to do the relevant integrals, split into two
        notConvergedSmall= True
        notConvergedLarge= True
        smallkIntegral= integrate.quadrature(_doubleExponentialDiskPotentialRForceIntegrandSmallk,
                                             0.,1./self._gamma,
                                             args=(self._alpha*R,
                                                   self._beta*nu.fabs(z),
                                                   self._gamma),tol=_TOL,
                                             maxiter= 2*self._maxiter,
                                                 vec_func=False)
        largekIntegral= integrate.quadrature(_doubleExponentialDiskPotentialRForceIntegrandLargek,
                                             0.,self._gamma,
                                             args=(self._alpha*R,
                                                   self._beta*nu.fabs(z),
                                                   self._gamma),tol=_TOL,
                                             maxiter= 2*self._maxiter,
                                             vec_func=False)
        maxiterFactorSmall= 4
        maxiterFactorLarge= 4
        if nu.fabs(smallkIntegral[1]/(smallkIntegral[0]+largekIntegral[0])) <= self._tol:
            notConvergedSmall= False
        if nu.fabs(largekIntegral[1]/(largekIntegral[0]+smallkIntegral[0])) <= self._tol:
            notConvergedLarge= False
        while notConvergedSmall or notConvergedLarge:
            if notConvergedSmall:
                smallkIntegral= integrate.quadrature(_doubleExponentialDiskPotentialRForceIntegrandSmallk,
                                                     0.,1./self._gamma,
                                                     args=(self._alpha*R,
                                                           self._beta*nu.fabs(z),
                                                           self._gamma),
                                                     tol=_TOL,
                                                     maxiter= maxiterFactorSmall*self._maxiter,
                                                     vec_func=False)
                if nu.fabs(smallkIntegral[1]/(smallkIntegral[0]+largekIntegral[0])) > self._tol:
                    maxiterFactorSmall*= 2
                else:
                    notConvergedSmall= False
            if notConvergedLarge:
                largekIntegral= integrate.quadrature(_doubleExponentialDiskPotentialRForceIntegrandLargek,
                                                     0.,self._gamma,
                                                     args=(self._alpha*R,
                                                           self._beta*nu.fabs(z),
                                                           self._gamma),
                                                     tol=_TOL,
                                                     maxiter=maxiterFactorLarge*self._maxiter,
                                                     vec_func=False)
            if nu.fabs(largekIntegral[1]/(largekIntegral[0]+smallkIntegral[0])) > self._tol:
                maxiterFactorLarge*= 2
            else:
                notConvergedLarge= False
        return -4.*nu.pi/self._beta*(smallkIntegral[0]+largekIntegral[0])
    
    def _zforce(self,R,z,phi=0.,t=0.):
        """
        NAME:
           zforce
        PURPOSE:
           evaluate vertical force K_z  (R,z)
        INPUT:
           R - Cylindrical Galactocentric radius
           z - vertical height
           phi - azimuth
           t - time
        OUTPUT:
           K_z (R,z)
        HISTORY:
           2010-04-16 - Written - Bovy (NYU)
        DOCTEST:
        """
        if self._new:
            if isinstance(R,nu.ndarray):
                if not isinstance(z,nu.ndarray): z= nu.ones_like(R)*z
                out= nu.array([self._zforce(rr,zz) for rr,zz in zip(R,z)])
                return out
            if R > 6.: return self._kp.zforce(R,z)
            if R < 1.: R4max= 1.
            else: R4max= R
            kmax= self._kmaxFac*self._beta
            maxj0zeroIndx= nu.argmin((self._j0zeros-kmax*R4max)**2.) #close enough
            ks= nu.array([0.5*(self._glx+1.)*self._dj0zeros[ii+1] + self._j0zeros[ii] for ii in range(maxj0zeroIndx)]).flatten()
            weights= nu.array([self._glw*self._dj0zeros[ii+1] for ii in range(maxj0zeroIndx)]).flatten()
            evalInt= ks*special.jn(0,ks*R)*(self._alpha**2.+ks**2.)**-1.5*(nu.exp(-ks*nu.fabs(z))-nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks**2.)
            if z > 0.:
                return -2.*nu.pi*self._alpha*self._beta*nu.sum(weights*evalInt)
            else:
                return 2.*nu.pi*self._alpha*self._beta*nu.sum(weights*evalInt)
        #Old code, uses scipy's quadrature to do the relevant integrals, split into two
        if self._zforceNotSetUp:
            self._zforceNotSetUp= False
            self._typicalKz= self._zforce(self._ro,self._hz)
        notConvergedSmall= True
        notConvergedLarge= True
        smallkIntegral= integrate.quadrature(_doubleExponentialDiskPotentialzForceIntegrandSmallk,
                                             0.,1./self._gamma,
                                             args=(self._alpha*R,
                                                   self._beta*nu.fabs(z),
                                                   self._gamma),tol=_TOL,
                                             maxiter= 2*self._maxiter,
                                             vec_func=False)
        largekIntegral= integrate.quadrature(_doubleExponentialDiskPotentialzForceIntegrandLargek,
                                             0.,self._gamma,
                                             args=(self._alpha*R,
                                                   self._beta*nu.fabs(z),
                                                   self._gamma),tol=_TOL,
                                             maxiter=2*self._maxiter,
                                             vec_func=False)
        maxiterFactorSmall= 4
        maxiterFactorLarge= 4
        try:
            if smallkIntegral[1]/self._typicalKz <= self._tol:
                notConvergedSmall= False
        except AttributeError:
            if nu.fabs(smallkIntegral[1]/(smallkIntegral[0]+largekIntegral[0])) <= self._tol:
                notConvergedSmall= False
        try:
            if largekIntegral[1]/self._typicalKz <= self._tol:
                notConvergedLarge= False
        except AttributeError:                
            if nu.fabs(largekIntegral[1]/(largekIntegral[0]+smallkIntegral[0])) <= self._tol:
                notConvergedLarge= False
        while notConvergedSmall or notConvergedLarge:
            if notConvergedSmall:
                smallkIntegral= integrate.quadrature(_doubleExponentialDiskPotentialzForceIntegrandSmallk,
                                                     0.,1./self._gamma,
                                                     args=(self._alpha*R,
                                                           self._beta*nu.fabs(z),
                                                           self._gamma),tol=_TOL,
                                                     maxiter= maxiterFactorSmall*self._maxiter,
                                                     vec_func=False)
                try:
                    if smallkIntegral[1]/self._typicalKz > self._tol:
                        maxiterFactorSmall*= 2
                    else:
                        notConvergedSmall= False
                except AttributeError:
                    if nu.fabs(smallkIntegral[1]/(smallkIntegral[0]+largekIntegral[0])) > self._tol:
                        maxiterFactorSmall*= 2
                    else:
                        notConvergedSmall= False
            if notConvergedLarge:
                largekIntegral= integrate.quadrature(_doubleExponentialDiskPotentialzForceIntegrandLargek,
                                                     0.,self._gamma,
                                                     args=(self._alpha*R,
                                                           self._beta*nu.fabs(z),
                                                           self._gamma),tol=_TOL,
                                                     maxiter=maxiterFactorLarge*self._maxiter,
                                                     vec_func=False)
                try:
                    if largekIntegral[1]/self._typicalKz > self._tol:
                        maxiterFactorLarge*= 2
                    else:
                        notConvergedLarge= False
                except AttributeError:
                    if largekIntegral[1]/(largekIntegral[0]+smallkIntegral[0]) > self._tol:
                        maxiterFactorLarge*= 2
                    else:
                        notConvergedLarge= False
        if z < 0.:
            return 4.*nu.pi/self._beta*(smallkIntegral[0]+largekIntegral[0])
        else:
            return -4.*nu.pi/self._beta*(smallkIntegral[0]+largekIntegral[0])

    def _R2deriv(self,R,z,phi=0.,t=0.):
        """
        NAME:
           R2deriv
        PURPOSE:
           evaluate R2 derivative
        INPUT:
           R - Cylindrical Galactocentric radius
           z - vertical height
           phi - azimuth
           t - time
        OUTPUT:
           -d K_R (R,z) d R
        HISTORY:
           2012-12-27 - Written - Bovy (IAS)
        """
        if self._new:
            if isinstance(R,nu.ndarray):
                if not isinstance(z,nu.ndarray): z= nu.ones_like(R)*z
                out= nu.array([self._R2deriv(rr,zz) for rr,zz in zip(R,z)])
                return out
            if R > 16.*self._hr or R > 6.: return self._kp.R2deriv(R,z)
            if R < 1.: R4max= 1.
            else: R4max= R
            kmax= 2.*self._kmaxFac*self._beta
            maxj0zeroIndx= nu.argmin((self._j0zeros-kmax*R4max)**2.) #close enough
            maxj2zeroIndx= nu.argmin((self._j2zeros-kmax*R4max)**2.) #close enough
            ks0= nu.array([0.5*(self._glx+1.)*self._dj0zeros[ii+1] + self._j0zeros[ii] for ii in range(maxj0zeroIndx)]).flatten()
            weights0= nu.array([self._glw*self._dj0zeros[ii+1] for ii in range(maxj0zeroIndx)]).flatten()
            ks2= nu.array([0.5*(self._glx+1.)*self._dj2zeros[ii+1] + self._j2zeros[ii] for ii in range(maxj2zeroIndx)]).flatten()
            weights2= nu.array([self._glw*self._dj2zeros[ii+1] for ii in range(maxj2zeroIndx)]).flatten()
            evalInt0= ks0**2.*special.jn(0,ks0*R)*(self._alpha**2.+ks0**2.)**-1.5*(self._beta*nu.exp(-ks0*nu.fabs(z))-ks0*nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks0**2.)
            evalInt2= ks2**2.*special.jn(2,ks2*R)*(self._alpha**2.+ks2**2.)**-1.5*(self._beta*nu.exp(-ks2*nu.fabs(z))-ks2*nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks2**2.)
            return nu.pi*self._alpha*(nu.sum(weights0*evalInt0)
                                      -nu.sum(weights2*evalInt2))
        #Old code, uses scipy's quadrature to do the relevant integrals, split into two
        notConvergedSmall= True
        notConvergedLarge= True
        smallkIntegral= integrate.quadrature(_doubleExponentialDiskPotentialR2derivIntegrandSmallk,
                                             0.,1./self._gamma,
                                             args=(self._alpha*R,
                                                   self._beta*nu.fabs(z),
                                                   self._gamma),tol=_TOL,
                                             maxiter= 2*self._maxiter,
                                                 vec_func=True)
        largekIntegral= integrate.quadrature(_doubleExponentialDiskPotentialR2derivIntegrandLargek,
                                             0.,self._gamma,
                                             args=(self._alpha*R,
                                                   self._beta*nu.fabs(z),
                                                   self._gamma),tol=_TOL,
                                             maxiter= 2*self._maxiter,
                                             vec_func=True)
        maxiterFactorSmall= 4
        maxiterFactorLarge= 4
        if nu.fabs(smallkIntegral[1]/(smallkIntegral[0]+largekIntegral[0])) <= self._tol:
            notConvergedSmall= False
        if nu.fabs(largekIntegral[1]/(largekIntegral[0]+smallkIntegral[0])) <= self._tol:
            notConvergedLarge= False
        while notConvergedSmall or notConvergedLarge:
            if notConvergedSmall:
                smallkIntegral= integrate.quadrature(_doubleExponentialDiskPotentialR2derivIntegrandSmallk,
                                                     0.,1./self._gamma,
                                                     args=(self._alpha*R,
                                                           self._beta*nu.fabs(z),
                                                           self._gamma),
                                                     tol=_TOL,
                                                     maxiter= maxiterFactorSmall*self._maxiter,
                                                     vec_func=True)
                if nu.fabs(smallkIntegral[1]/(smallkIntegral[0]+largekIntegral[0])) > self._tol:
                    maxiterFactorSmall*= 2
                else:
                    notConvergedSmall= False
            if notConvergedLarge:
                largekIntegral= integrate.quadrature(_doubleExponentialDiskPotentialR2derivIntegrandLargek,
                                                     0.,self._gamma,
                                                     args=(self._alpha*R,
                                                           self._beta*nu.fabs(z),
                                                           self._gamma),
                                                     tol=_TOL,
                                                     maxiter=maxiterFactorLarge*self._maxiter,
                                                     vec_func=True)
            if nu.fabs(largekIntegral[1]/(largekIntegral[0]+smallkIntegral[0])) > self._tol:
                maxiterFactorLarge*= 2
            else:
                notConvergedLarge= False
        return 4.*nu.pi*self._alpha/self._beta*(smallkIntegral[0]+largekIntegral[0])
    
    def _z2deriv(self,R,z,phi=0.,t=0.):
        """
        NAME:
           z2deriv
        PURPOSE:
           evaluate z2 derivative
        INPUT:
           R - Cylindrical Galactocentric radius
           z - vertical height
           phi - azimuth
           t - time
        OUTPUT:
           -d K_Z (R,z) d Z
        HISTORY:
           2012-12-26 - Written - Bovy (IAS)
        """
        if self._new:
            if isinstance(R,nu.ndarray):
                if not isinstance(z,nu.ndarray): z= nu.ones_like(R)*z
                out= nu.array([self._z2deriv(rr,zz) for rr,zz in zip(R,z)])
                return out
            if R > 6.: return self._kp.z2deriv(R,z)
            if R < 1.: R4max= 1.
            else: R4max= R
            kmax= self._kmaxFac*self._beta
            maxj0zeroIndx= nu.argmin((self._j0zeros-kmax*R4max)**2.) #close enough
            ks= nu.array([0.5*(self._glx+1.)*self._dj0zeros[ii+1] + self._j0zeros[ii] for ii in range(maxj0zeroIndx)]).flatten()
            weights= nu.array([self._glw*self._dj0zeros[ii+1] for ii in range(maxj0zeroIndx)]).flatten()
            evalInt= ks*special.jn(0,ks*R)*(self._alpha**2.+ks**2.)**-1.5*(ks*nu.exp(-ks*nu.fabs(z))-self._beta*nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks**2.)
            return -2.*nu.pi*self._alpha*self._beta*nu.sum(weights*evalInt)
        raise NotImplementedError("none 'new' z2deriv not implemented for DoubleExponentialDiskPotential")

    def _Rzderiv(self,R,z,phi=0.,t=0.):
        """
        NAME:
           Rzderiv
        PURPOSE:
           evaluate the mixed R,z derivative
        INPUT:
           R - Cylindrical Galactocentric radius
           z - vertical height
           phi - azimuth
           t - time
        OUTPUT:
           d2phi/dR/dz
        HISTORY:
           2013-08-28 - Written - Bovy (IAS)
        """
        if self._new:
            if isinstance(R,nu.ndarray):
                if not isinstance(z,nu.ndarray): z= nu.ones_like(R)*z
                out= nu.array([self._Rzderiv(rr,zz) for rr,zz in zip(R,z)])
                return out
            if R > 6.: return self._kp.Rzderiv(R,z)
            if R < 1.: R4max= 1.
            else: R4max= R
            kmax= 2.*self._kmaxFac*self._beta
            maxj1zeroIndx= nu.argmin((self._j1zeros-kmax*R4max)**2.) #close enough
            ks= nu.array([0.5*(self._glx+1.)*self._dj1zeros[ii+1] + self._j1zeros[ii] for ii in range(maxj1zeroIndx)]).flatten()
            weights= nu.array([self._glw*self._dj1zeros[ii+1] for ii in range(maxj1zeroIndx)]).flatten()
            evalInt= ks**2.*special.jn(1,ks*R)*(self._alpha**2.+ks**2.)**-1.5*(nu.exp(-ks*nu.fabs(z))-nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks**2.)
            if z >= 0.:
                return -2.*nu.pi*self._alpha*self._beta*nu.sum(weights*evalInt)
            else:
                return 2.*nu.pi*self._alpha*self._beta*nu.sum(weights*evalInt)
        raise NotImplementedError("none 'new' Rzderiv not implemented for DoubleExponentialDiskPotential")

    def _dens(self,R,z,phi=0.,t=0.):
        """
        NAME:
           dens
        PURPOSE:
           evaluate the density
        INPUT:
           R - Cylindrical Galactocentric radius
           z - vertical height
           phi - azimuth
           t - time
        OUTPUT:
           rho (R,z)
        HISTORY:
           2010-08-08 - Written - Bovy (NYU)
        """
        return nu.exp(-self._alpha*R-self._beta*nu.fabs(z))