Source code for galpy.potential.SCFPotential

import hashlib
import numpy
from numpy.polynomial.legendre import leggauss
from scipy.special import lpmn
from scipy.special import gammaln
from .Potential import Potential, _APY_LOADED
if _APY_LOADED:
    from astropy import units
from ..util import bovy_coords

from .NumericalPotentialDerivativesMixin import \
    NumericalPotentialDerivativesMixin

[docs]class SCFPotential(Potential,NumericalPotentialDerivativesMixin): """Class that implements the `Hernquist & Ostriker (1992) <http://adsabs.harvard.edu/abs/1992ApJ...386..375H>`_ Self-Consistent-Field-type potential. Note that we divide the amplitude by 2 such that :math:`Acos = \\delta_{0n}\\delta_{0l}\\delta_{0m}` and :math:`Asin = 0` corresponds to :ref:`Galpy's Hernquist Potential <hernquist_potential>`. .. math:: \\rho(r, \\theta, \\phi) = \\frac{amp}{2}\\sum_{n=0}^{\\infty} \\sum_{l=0}^{\\infty} \\sum_{m=0}^l N_{lm} P_{lm}(\\cos(\\theta)) \\tilde{\\rho}_{nl}(r) \\left(A_{cos, nlm} \\cos(m\\phi) + A_{sin, nlm} \\sin(m\\phi)\\right) where .. math:: \\tilde{\\rho}_{nl}(r) = \\frac{K_{nl}}{\\sqrt{\\pi}} \\frac{(a r)^l}{(r/a) (a + r)^{2l + 3}} C_{n}^{2l + 3/2}(\\xi) .. math:: \\Phi(r, \\theta, \\phi) = \\sum_{n=0}^{\\infty} \\sum_{l=0}^{\\infty} \\sum_{m=0}^l N_{lm} P_{lm}(\\cos(\\theta)) \\tilde{\\Phi}_{nl}(r) \\left(A_{cos, nlm} \\cos(m\\phi) + A_{sin, nlm} \\sin(m\\phi)\\right) where .. math:: \\tilde{\\Phi}_{nl}(r) = -\\sqrt{4 \\pi}K_{nl} \\frac{(ar)^l}{(a + r)^{2l + 1}} C_{n}^{2l + 3/2}(\\xi) where .. math:: \\xi = \\frac{r - a}{r + a} \\qquad N_{lm} = \\sqrt{\\frac{2l + 1}{4\\pi} \\frac{(l - m)!}{(l + m)!}}(2 - \\delta_{m0}) \\qquad K_{nl} = \\frac{1}{2} n (n + 4l + 3) + (l + 1)(2l + 1) and :math:`P_{lm}` is the Associated Legendre Polynomials whereas :math:`C_n^{\\alpha}` is the Gegenbauer polynomial. """
[docs] def __init__(self, amp=1., Acos=numpy.array([[[1]]]),Asin=None, a = 1., normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a SCF Potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass Acos - The real part of the expansion coefficent (NxLxL matrix, or optionally NxLx1 if Asin=None) Asin - The imaginary part of the expansion coefficient (NxLxL matrix or None) a - scale length (can be Quantity) 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. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: SCFPotential object HISTORY: 2016-05-13 - Written - Aladdin """ NumericalPotentialDerivativesMixin.__init__(self,{}) # just use default dR etc. Potential.__init__(self,amp=amp/2.,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro ##Errors shape = Acos.shape errorMessage = None if len(shape) != 3: errorMessage="Acos must be a 3 dimensional numpy array" elif Asin is not None and shape[1] != shape[2]: errorMessage="The second and third dimension of the expansion coefficients must have the same length" elif Asin is None and not (shape[2] == 1 or shape[1] == shape[2]): errorMessage="The third dimension must have length=1 or equal to the length of the second dimension" elif Asin is None and shape[1] > 1 and numpy.any(Acos[:,:,1:] !=0): errorMessage="Acos has non-zero elements at indices m>0, which implies a non-axi symmetric potential.\n" +\ "Asin=None which implies an axi symmetric potential.\n" + \ "Contradiction." elif Asin is not None and Asin.shape != shape: errorMessage = "The shape of Asin does not match the shape of Acos." if errorMessage is not None: raise RuntimeError(errorMessage) ##Warnings warningMessage=None if numpy.any(numpy.triu(Acos,1) != 0) or (Asin is not None and numpy.any(numpy.triu(Asin,1) != 0)): warningMessage="Found non-zero values at expansion coefficients where m > l\n" + \ "The Mth and Lth dimension is expected to make a lower triangular matrix.\n" + \ "All values found above the diagonal will be ignored." if warningMessage is not None: raise RuntimeWarning(warningMessage) ##Is non axi? self.isNonAxi= True if Asin is None or shape[1] == 1 or (numpy.all(Acos[:,:,1:] == 0) and numpy.all(Asin[:,:,:]==0)): self.isNonAxi = False self._a = a NN = self._Nroot(Acos.shape[1], Acos.shape[2]) self._Acos= Acos*NN[numpy.newaxis,:,:] if Asin is not None: self._Asin = Asin*NN[numpy.newaxis,:,:] else: self._Asin = numpy.zeros_like(Acos) self._force_hash= None self.hasC= True self.hasC_dxdv=True self.hasC_dens=True if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) return None
def _Nroot(self, L, M=None): """ NAME: _Nroot PURPOSE: Evaluate the square root of equation (3.15) with the (2 - del_m,0) term outside the square root INPUT: L - evaluate Nroot for 0 <= l <= L M - evaluate Nroot for 0 <= m <= M OUTPUT: The square root of equation (3.15) with the (2 - del_m,0) outside HISTORY: 2016-05-16 - Written - Aladdin """ if M is None: M =L NN = numpy.zeros((L,M),float) l = numpy.arange(0,L)[:,numpy.newaxis] m = numpy.arange(0,M)[numpy.newaxis, :] nLn = gammaln(l-m+1) - gammaln(l+m+1) NN[:,:] = ((2*l+1.)/(4.*numpy.pi) * numpy.e**nLn)**.5 * 2 NN[:,0] /= 2. NN = numpy.tril(NN) return NN def _calculateXi(self, r): """ NAME: _calculateXi PURPOSE: Calculate xi given r INPUT: r - Evaluate at radius r OUTPUT: xi HISTORY: 2016-05-18 - Written - Aladdin """ a = self._a return (r - a)/(r + a) def _rhoTilde(self, r, N,L): """ NAME: _rhoTilde PURPOSE: Evaluate rho_tilde as defined in equation 3.9 and 2.24 for 0 <= n < N and 0 <= l < L INPUT: r - Evaluate at radius r N - size of the N dimension L - size of the L dimension OUTPUT: rho tilde HISTORY: 2016-05-17 - Written - Aladdin """ xi = self._calculateXi(r) CC = _C(xi,N,L) a = self._a rho = numpy.zeros((N,L), float) n = numpy.arange(0,N, dtype=float)[:, numpy.newaxis] l = numpy.arange(0, L, dtype=float)[numpy.newaxis,:] K = 0.5 * n * (n + 4*l + 3) + (l + 1.)*(2*l + 1) rho[:,:] = K * ((a*r)**l) / ((r/a)*(a + r)**(2*l + 3.)) * CC[:,:]* (numpy.pi)**-0.5 return rho def _phiTilde(self, r, N,L): """ NAME: _phiTilde PURPOSE: Evaluate phi_tilde as defined in equation 3.10 and 2.25 for 0 <= n < N and 0 <= l < L INPUT: r - Evaluate at radius r N - size of the N dimension L - size of the L dimension OUTPUT: phi tilde HISTORY: 2016-05-17 - Written - Aladdin """ xi = self._calculateXi(r) CC = _C(xi,N,L) a = self._a phi = numpy.zeros((N,L), float) n = numpy.arange(0,N)[:, numpy.newaxis] l = numpy.arange(0, L)[numpy.newaxis,:] phi[:,:] = - (r*a)**l/ ((a + r)**(2*l + 1.)) * CC[:,:]* (4*numpy.pi)**0.5 return phi def _compute(self, funcTilde, R, z, phi): """ NAME: _compute PURPOSE: evaluate the NxLxM density or potential INPUT: funcTidle - must be _rhoTilde or _phiTilde R - Cylindrical Galactocentric radius z - vertical height phi - azimuth OUTPUT: An NxLxM density or potential at (R,z, phi) HISTORY: 2016-05-18 - Written - Aladdin """ Acos, Asin = self._Acos, self._Asin N, L, M = Acos.shape r, theta, phi = bovy_coords.cyl_to_spher(R,z,phi) PP = lpmn(M-1,L-1,numpy.cos(theta))[0].T ##Get the Legendre polynomials func_tilde = funcTilde(r, N, L) ## Tilde of the function of interest func = numpy.zeros((N,L,M), float) ## The function of interest (density or potential) m = numpy.arange(0, M)[numpy.newaxis, numpy.newaxis, :] mcos = numpy.cos(m*phi) msin = numpy.sin(m*phi) func = func_tilde[:,:,None]*(Acos[:,:,:]*mcos + Asin[:,:,:]*msin)*PP[None,:,:] return func def _computeArray(self, funcTilde, R, z, phi): """ NAME: _computeArray PURPOSE: evaluate the density or potential for a given array of coordinates INPUT: funcTidle - must be _rhoTilde or _phiTilde R - Cylindrical Galactocentric radius z - vertical height phi - azimuth OUTPUT: density or potential evaluated at (R,z, phi) HISTORY: 2016-06-02 - Written - Aladdin """ R = numpy.array(R,dtype=float); z = numpy.array(z,dtype=float); phi = numpy.array(phi,dtype=float); shape = (R*z*phi).shape if shape == (): return numpy.sum(self._compute(funcTilde, R,z,phi)) R = R*numpy.ones(shape); z = z*numpy.ones(shape); phi = phi*numpy.ones(shape); func = numpy.zeros(shape, float) li = _cartesian(shape) for i in range(li.shape[0]): j = numpy.split(li[i], li.shape[1]) func[j] = numpy.sum(self._compute(funcTilde, R[j][0],z[j][0],phi[j][0])) return func def _dens(self, R, z, phi=0., t=0.): """ NAME: _dens PURPOSE: evaluate the density at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: density at (R,z, phi) HISTORY: 2016-05-17 - Written - Aladdin """ if not self.isNonAxi and phi is None: phi= 0. return self._computeArray(self._rhoTilde, R,z,phi) def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: potential at (R,z, phi) HISTORY: 2016-05-17 - Written - Aladdin """ if not self.isNonAxi and phi is None: phi= 0. return self._computeArray(self._phiTilde, R,z,phi) def _dphiTilde(self, r, N, L): """ NAME: _dphiTilde PURPOSE: Evaluate the derivative of phiTilde with respect to r INPUT: r - spherical radius N - size of the N dimension L - size of the L dimension OUTPUT: the derivative of phiTilde with respect to r HISTORY: 2016-06-06 - Written - Aladdin """ a = self._a l = numpy.arange(0, L, dtype=float)[numpy.newaxis, :] n = numpy.arange(0, N, dtype=float)[:, numpy.newaxis] xi = self._calculateXi(r) dC = _dC(xi,N,L) return -(4*numpy.pi)**.5 * (numpy.power(a*r, l)*(l*(a + r)*numpy.power(r,-1) -(2*l + 1))/((a + r)**(2*l + 2))*_C(xi,N,L) + a**-1*(1 - xi)**2 * (a*r)**l / (a + r)**(2*l + 1) *dC/2.) def _computeforce(self,R,z,phi=0,t=0): """ NAME: _computeforce PURPOSE: Evaluate the first derivative of Phi with respect to R, z and phi INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: dPhi/dr, dPhi/dtheta, dPhi/dphi HISTORY: 2016-06-07 - Written - Aladdin """ Acos, Asin = self._Acos, self._Asin N, L, M = Acos.shape r, theta, phi = bovy_coords.cyl_to_spher(R,z,phi) new_hash= hashlib.md5(numpy.array([R, z,phi])).hexdigest() if new_hash == self._force_hash: dPhi_dr = self._cached_dPhi_dr dPhi_dtheta = self._cached_dPhi_dtheta dPhi_dphi = self._cached_dPhi_dphi else: PP, dPP = lpmn(M-1,L-1,numpy.cos(theta)) ##Get the Legendre polynomials PP = PP.T[None,:,:] dPP = dPP.T[None,:,:] phi_tilde = self._phiTilde(r, N, L)[:,:,numpy.newaxis] dphi_tilde = self._dphiTilde(r,N,L)[:,:,numpy.newaxis] m = numpy.arange(0, M)[numpy.newaxis, numpy.newaxis, :] mcos = numpy.cos(m*phi) msin = numpy.sin(m*phi) dPhi_dr = -numpy.sum((Acos*mcos + Asin*msin)*PP*dphi_tilde) dPhi_dtheta = -numpy.sum((Acos*mcos + Asin*msin)*phi_tilde*dPP*(-numpy.sin(theta))) dPhi_dphi =-numpy.sum(m*(Asin*mcos - Acos*msin)*phi_tilde*PP) self._force_hash = new_hash self._cached_dPhi_dr = dPhi_dr self._cached_dPhi_dtheta = dPhi_dtheta self._cached_dPhi_dphi = dPhi_dphi return dPhi_dr,dPhi_dtheta,dPhi_dphi def _computeforceArray(self,dr_dx, dtheta_dx, dphi_dx, R, z, phi): """ NAME: _computeforceArray PURPOSE: evaluate the forces in the x direction for a given array of coordinates INPUT: dr_dx - the derivative of r with respect to the chosen variable x dtheta_dx - the derivative of theta with respect to the chosen variable x dphi_dx - the derivative of phi with respect to the chosen variable x R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: The forces in the x direction HISTORY: 2016-06-02 - Written - Aladdin """ R = numpy.array(R,dtype=float); z = numpy.array(z,dtype=float); phi = numpy.array(phi,dtype=float); shape = (R*z*phi).shape if shape == (): dPhi_dr,dPhi_dtheta,dPhi_dphi = \ self._computeforce(R,z,phi) return dr_dx*dPhi_dr + dtheta_dx*dPhi_dtheta +dPhi_dphi*dphi_dx R = R*numpy.ones(shape); z = z* numpy.ones(shape); phi = phi* numpy.ones(shape); force = numpy.zeros(shape, float) dr_dx = dr_dx*numpy.ones(shape); dtheta_dx = dtheta_dx*numpy.ones(shape);dphi_dx = dphi_dx*numpy.ones(shape); li = _cartesian(shape) for i in range(li.shape[0]): j = tuple(numpy.split(li[i], li.shape[1])) dPhi_dr,dPhi_dtheta,dPhi_dphi = \ self._computeforce(R[j][0],z[j][0],phi[j][0]) force[j] = dr_dx[j][0]*dPhi_dr + dtheta_dx[j][0]*dPhi_dtheta +dPhi_dphi*dphi_dx[j][0] return force def _Rforce(self, R, z, phi=0, t=0): """ NAME: _Rforce PURPOSE: evaluate the radial force at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: radial force at (R,z, phi) HISTORY: 2016-06-06 - Written - Aladdin """ if not self.isNonAxi and phi is None: phi= 0. r, theta, phi = bovy_coords.cyl_to_spher(R,z,phi) #x = R dr_dR = numpy.divide(R,r); dtheta_dR = numpy.divide(z,r**2); dphi_dR = 0 return self._computeforceArray(dr_dR, dtheta_dR, dphi_dR, R,z,phi) def _zforce(self, R, z, phi=0., t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: vertical force at (R,z, phi) HISTORY: 2016-06-06 - Written - Aladdin """ if not self.isNonAxi and phi is None: phi= 0. r, theta, phi = bovy_coords.cyl_to_spher(R,z,phi) #x = z dr_dz = numpy.divide(z,r); dtheta_dz = numpy.divide(-R,r**2); dphi_dz = 0 return self._computeforceArray(dr_dz, dtheta_dz, dphi_dz, R,z,phi) def _phiforce(self, R,z,phi=0,t=0): """ NAME: _phiforce PURPOSE: evaluate the azimuth force at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: azimuth force at (R,z, phi) HISTORY: 2016-06-06 - Written - Aladdin """ if not self.isNonAxi and phi is None: phi= 0. r, theta, phi = bovy_coords.cyl_to_spher(R,z,phi) #x = phi dr_dphi = 0; dtheta_dphi = 0; dphi_dphi = 1 return self._computeforceArray(dr_dphi, dtheta_dphi, dphi_dphi, R,z,phi) def OmegaP(self): return 0
def _xiToR(xi, a =1): return a*numpy.divide((1. + xi),(1. - xi)) def _C(xi, N,L, alpha = lambda x: 2*x + 3./2): """ NAME: _C PURPOSE: Evaluate C_n,l (the Gegenbauer polynomial) for 0 <= l < L and 0<= n < N INPUT: xi - radial transformed variable N - Size of the N dimension L - Size of the L dimension alpha = A lambda function of l. Default alpha = 2l + 3/2 OUTPUT: An LxN Gegenbauer Polynomial HISTORY: 2016-05-16 - Written - Aladdin """ CC = numpy.zeros((N,L), float) for l in range(L): for n in range(N): a = alpha(l) if n==0: CC[n][l] = 1. continue elif n==1: CC[n][l] = 2.*a*xi if n + 1 != N: CC[n+1][l] = (n + 1.)**-1. * (2*(n + a)*xi*CC[n][l] - (n + 2*a - 1)*CC[n-1][l]) return CC def _dC(xi, N, L): l = numpy.arange(0,L)[numpy.newaxis, :] CC = _C(xi,N + 1,L, alpha = lambda x: 2*x + 5./2) CC = numpy.roll(CC, 1, axis=0)[:-1,:] CC[0, :] = 0 CC *= 2*(2*l + 3./2) return CC
[docs]def scf_compute_coeffs_spherical(dens, N, a=1., radial_order=None): """ NAME: scf_compute_coeffs_spherical PURPOSE: Numerically compute the expansion coefficients for a given spherical density INPUT: dens - A density function that takes a parameter R N - size of expansion coefficients a - parameter used to shift the basis functions radial_order - Number of sample points of the radial integral. If None, radial_order=max(20, N + 1) OUTPUT: (Acos,Asin) - Expansion coefficients for density dens that can be given to SCFPotential.__init__ HISTORY: 2016-05-18 - Written - Aladdin """ numOfParam = 0 try: dens(0) numOfParam=1 except: try: dens(0,0) numOfParam=2 except: numOfParam=3 param = [0]*numOfParam; def integrand(xi): r = _xiToR(xi, a) R = r param[0] = R return a**3. * dens(*param)*(1 + xi)**2. * (1 - xi)**-3. * _C(xi, N, 1)[:,0] Acos = numpy.zeros((N,1,1), float) Asin = None Ksample = [max(N + 1, 20)] if radial_order != None: Ksample[0] = radial_order integrated = _gaussianQuadrature(integrand, [[-1., 1.]], Ksample=Ksample) n = numpy.arange(0,N) K = 16*numpy.pi*(n + 3./2)/((n + 2)*(n + 1)*(1 + n*(n + 3.)/2.)) Acos[n,0,0] = 2*K*integrated return Acos, Asin
[docs]def scf_compute_coeffs_axi(dens, N, L, a=1.,radial_order=None, costheta_order=None): """ NAME: scf_compute_coeffs_axi PURPOSE: Numerically compute the expansion coefficients for a given axi-symmetric density INPUT: dens - A density function that takes a parameter R and z N - size of the Nth dimension of the expansion coefficients L - size of the Lth dimension of the expansion coefficients a - parameter used to shift the basis functions radial_order - Number of sample points of the radial integral. If None, radial_order=max(20, N + 3/2L + 1) costheta_order - Number of sample points of the costheta integral. If None, If costheta_order=max(20, L + 1) OUTPUT: (Acos,Asin) - Expansion coefficients for density dens that can be given to SCFPotential.__init__ HISTORY: 2016-05-20 - Written - Aladdin """ numOfParam = 0 try: dens(0,0) numOfParam=2 except: numOfParam=3 param = [0]*numOfParam; def integrand(xi, costheta): l = numpy.arange(0, L)[numpy.newaxis, :] r = _xiToR(xi,a) R = r*numpy.sqrt(1 - costheta**2.) z = r*costheta Legandre = lpmn(0,L-1,costheta)[0].T[numpy.newaxis,:,0] dV = (1. + xi)**2. * numpy.power(1. - xi, -4.) phi_nl = a**3*(1. + xi)**l * (1. - xi)**(l + 1.)*_C(xi, N, L)[:,:] * Legandre param[0] = R param[1] = z return phi_nl*dV * dens(*param) Acos = numpy.zeros((N,L,1), float) Asin = None ##This should save us some computation time since we're only taking the double integral once, rather then L times Ksample = [max(N + 3*L//2 + 1, 20) , max(L + 1,20) ] if radial_order != None: Ksample[0] = radial_order if costheta_order != None: Ksample[1] = costheta_order integrated = _gaussianQuadrature(integrand, [[-1, 1], [-1, 1]], Ksample = Ksample)*(2*numpy.pi) n = numpy.arange(0,N)[:,numpy.newaxis] l = numpy.arange(0,L)[numpy.newaxis,:] K = .5*n*(n + 4*l + 3) + (l + 1)*(2*l + 1) #I = -K*(4*numpy.pi)/(2.**(8*l + 6)) * gamma(n + 4*l + 3)/(gamma(n + 1)*(n + 2*l + 3./2)*gamma(2*l + 3./2)**2) ##Taking the ln of I will allow bigger size coefficients lnI = -(8*l + 6)*numpy.log(2) + gammaln(n + 4*l + 3) - gammaln(n + 1) - numpy.log(n + 2*l + 3./2) - 2*gammaln(2*l + 3./2) I = -K*(4*numpy.pi) * numpy.e**(lnI) constants = -2.**(-2*l)*(2*l + 1.)**.5 Acos[:,:,0] = 2*I**-1 * integrated*constants return Acos, Asin
[docs]def scf_compute_coeffs(dens, N, L, a=1., radial_order=None, costheta_order=None, phi_order=None): """ NAME: scf_compute_coeffs PURPOSE: Numerically compute the expansion coefficients for a given triaxial density INPUT: dens - A density function that takes a parameter R, z and phi N - size of the Nth dimension of the expansion coefficients L - size of the Lth and Mth dimension of the expansion coefficients a - parameter used to shift the basis functions radial_order - Number of sample points of the radial integral. If None, radial_order=max(20, N + 3/2L + 1) costheta_order - Number of sample points of the costheta integral. If None, If costheta_order=max(20, L + 1) phi_order - Number of sample points of the phi integral. If None, If costheta_order=max(20, L + 1) OUTPUT: (Acos,Asin) - Expansion coefficients for density dens that can be given to SCFPotential.__init__ HISTORY: 2016-05-27 - Written - Aladdin """ def integrand(xi, costheta, phi): l = numpy.arange(0, L)[numpy.newaxis, :, numpy.newaxis] m = numpy.arange(0, L)[numpy.newaxis,numpy.newaxis,:] r = _xiToR(xi, a) R = r*numpy.sqrt(1 - costheta**2.) z = r*costheta Legandre = lpmn(L - 1,L-1,costheta)[0].T[numpy.newaxis,:,:] dV = (1. + xi)**2. * numpy.power(1. - xi, -4.) phi_nl = - a**3*(1. + xi)**l * (1. - xi)**(l + 1.)*_C(xi, N, L)[:,:,numpy.newaxis] * Legandre return dens(R,z, phi) * phi_nl[numpy.newaxis, :,:,:]*numpy.array([numpy.cos(m*phi), numpy.sin(m*phi)])*dV Acos = numpy.zeros((N,L,L), float) Asin = numpy.zeros((N,L,L), float) Ksample = [max(N + 3*L//2 + 1,20), max(L + 1,20 ), max(L + 1,20)] if radial_order != None: Ksample[0] = radial_order if costheta_order != None: Ksample[1] = costheta_order if phi_order != None: Ksample[2] = phi_order integrated = _gaussianQuadrature(integrand, [[-1., 1.], [-1., 1.], [0, 2*numpy.pi]], Ksample = Ksample) n = numpy.arange(0,N)[:,numpy.newaxis, numpy.newaxis] l = numpy.arange(0,L)[numpy.newaxis,:, numpy.newaxis] m = numpy.arange(0,L)[numpy.newaxis,numpy.newaxis,:] K = .5*n*(n + 4*l + 3) + (l + 1)*(2*l + 1) Nln = .5*gammaln(l - m + 1) - .5*gammaln(l + m + 1) - (2*l)*numpy.log(2) NN = numpy.e**(Nln) NN[numpy.where(NN == numpy.inf)] = 0 ## To account for the fact that m cant be bigger than l constants = NN*(2*l + 1.)**.5 lnI = -(8*l + 6)*numpy.log(2) + gammaln(n + 4*l + 3) - gammaln(n + 1) - numpy.log(n + 2*l + 3./2) - 2*gammaln(2*l + 3./2) I = -K*(4*numpy.pi) * numpy.e**(lnI) Acos[:,:,:],Asin[:,:,:] = 2*(I**-1.)[numpy.newaxis,:,:,:] * integrated * constants[numpy.newaxis,:,:,:] return Acos, Asin
def _cartesian(arraySizes, out=None): """ NAME: cartesian PURPOSE: Generate a cartesian product of input arrays. INPUT: arraySizes - list of size of arrays out - Array to place the cartesian product in. OUTPUT: 2-D array of shape (product(arraySizes), len(arraySizes)) containing cartesian products formed of input arrays. HISTORY: 2016-06-02 - Obtained from http://stackoverflow.com/questions/1208118/using-numpy-to-build-an-array-of-all-combinations-of-two-arrays """ arrays = [] for i in range(len(arraySizes)): arrays.append(numpy.arange(0, arraySizes[i])) arrays = [numpy.asarray(x) for x in arrays] dtype = arrays[0].dtype n = numpy.prod([x.size for x in arrays]) if out is None: out = numpy.zeros([n, len(arrays)], dtype=dtype) m = n // arrays[0].size out[:,0] = numpy.repeat(arrays[0], m) if arrays[1:]: _cartesian(arraySizes[1:], out=out[0:m,1:]) for j in range(1, arrays[0].size): out[j*m:(j+1)*m,1:] = out[0:m,1:] return out def _gaussianQuadrature(integrand, bounds, Ksample=[20], roundoff=0): """ NAME: _gaussianQuadrature PURPOSE: Numerically take n integrals over a function that returns a float or an array INPUT: integrand - The function you're integrating over. bounds - The bounds of the integral in the form of [[a_0, b_0], [a_1, b_1], ... , [a_n, b_n]] where a_i is the lower bound and b_i is the upper bound Ksample - Number of sample points in the form of [K_0, K_1, ..., K_n] where K_i is the sample point of the ith integral. roundoff - if the integral is less than this value, round it to 0. OUTPUT: The integral of the function integrand HISTORY: 2016-05-24 - Written - Aladdin """ ##Maps the sample point and weights xp = numpy.zeros((len(bounds), numpy.max(Ksample)), float) wp = numpy.zeros((len(bounds), numpy.max(Ksample)), float) for i in range(len(bounds)): x,w = leggauss(Ksample[i]) ##Calculates the sample points and weights a,b = bounds[i] xp[i, :Ksample[i]] = .5*(b-a)*x + .5*(b+a) wp[i, :Ksample[i]] = .5*(b - a)*w ##Determines the shape of the integrand s = 0. shape=None s_temp = integrand(*numpy.zeros(len(bounds))) if type(s_temp).__name__ == numpy.ndarray.__name__ : shape = s_temp.shape s = numpy.zeros(shape, float) #gets all combinations of indices from each integrand li = _cartesian(Ksample) ##Performs the actual integration for i in range(li.shape[0]): index = (numpy.arange(len(bounds)),li[i]) s+= numpy.prod(wp[index])*integrand(*xp[index]) ##Rounds values that are less than roundoff to zero if shape!= None: s[numpy.where(numpy.fabs(s) < roundoff)] = 0 else: s *= numpy.fabs(s) >roundoff return s