spherical_harmonics.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. from sympy.core.expr import Expr
  2. from sympy.core.function import Function, ArgumentIndexError
  3. from sympy.core.numbers import I, pi
  4. from sympy.core.singleton import S
  5. from sympy.core.symbol import Dummy
  6. from sympy.functions import assoc_legendre
  7. from sympy.functions.combinatorial.factorials import factorial
  8. from sympy.functions.elementary.complexes import Abs, conjugate
  9. from sympy.functions.elementary.exponential import exp
  10. from sympy.functions.elementary.miscellaneous import sqrt
  11. from sympy.functions.elementary.trigonometric import sin, cos, cot
  12. _x = Dummy("x")
  13. class Ynm(Function):
  14. r"""
  15. Spherical harmonics defined as
  16. .. math::
  17. Y_n^m(\theta, \varphi) := \sqrt{\frac{(2n+1)(n-m)!}{4\pi(n+m)!}}
  18. \exp(i m \varphi)
  19. \mathrm{P}_n^m\left(\cos(\theta)\right)
  20. Explanation
  21. ===========
  22. ``Ynm()`` gives the spherical harmonic function of order $n$ and $m$
  23. in $\theta$ and $\varphi$, $Y_n^m(\theta, \varphi)$. The four
  24. parameters are as follows: $n \geq 0$ an integer and $m$ an integer
  25. such that $-n \leq m \leq n$ holds. The two angles are real-valued
  26. with $\theta \in [0, \pi]$ and $\varphi \in [0, 2\pi]$.
  27. Examples
  28. ========
  29. >>> from sympy import Ynm, Symbol, simplify
  30. >>> from sympy.abc import n,m
  31. >>> theta = Symbol("theta")
  32. >>> phi = Symbol("phi")
  33. >>> Ynm(n, m, theta, phi)
  34. Ynm(n, m, theta, phi)
  35. Several symmetries are known, for the order:
  36. >>> Ynm(n, -m, theta, phi)
  37. (-1)**m*exp(-2*I*m*phi)*Ynm(n, m, theta, phi)
  38. As well as for the angles:
  39. >>> Ynm(n, m, -theta, phi)
  40. Ynm(n, m, theta, phi)
  41. >>> Ynm(n, m, theta, -phi)
  42. exp(-2*I*m*phi)*Ynm(n, m, theta, phi)
  43. For specific integers $n$ and $m$ we can evaluate the harmonics
  44. to more useful expressions:
  45. >>> simplify(Ynm(0, 0, theta, phi).expand(func=True))
  46. 1/(2*sqrt(pi))
  47. >>> simplify(Ynm(1, -1, theta, phi).expand(func=True))
  48. sqrt(6)*exp(-I*phi)*sin(theta)/(4*sqrt(pi))
  49. >>> simplify(Ynm(1, 0, theta, phi).expand(func=True))
  50. sqrt(3)*cos(theta)/(2*sqrt(pi))
  51. >>> simplify(Ynm(1, 1, theta, phi).expand(func=True))
  52. -sqrt(6)*exp(I*phi)*sin(theta)/(4*sqrt(pi))
  53. >>> simplify(Ynm(2, -2, theta, phi).expand(func=True))
  54. sqrt(30)*exp(-2*I*phi)*sin(theta)**2/(8*sqrt(pi))
  55. >>> simplify(Ynm(2, -1, theta, phi).expand(func=True))
  56. sqrt(30)*exp(-I*phi)*sin(2*theta)/(8*sqrt(pi))
  57. >>> simplify(Ynm(2, 0, theta, phi).expand(func=True))
  58. sqrt(5)*(3*cos(theta)**2 - 1)/(4*sqrt(pi))
  59. >>> simplify(Ynm(2, 1, theta, phi).expand(func=True))
  60. -sqrt(30)*exp(I*phi)*sin(2*theta)/(8*sqrt(pi))
  61. >>> simplify(Ynm(2, 2, theta, phi).expand(func=True))
  62. sqrt(30)*exp(2*I*phi)*sin(theta)**2/(8*sqrt(pi))
  63. We can differentiate the functions with respect
  64. to both angles:
  65. >>> from sympy import Ynm, Symbol, diff
  66. >>> from sympy.abc import n,m
  67. >>> theta = Symbol("theta")
  68. >>> phi = Symbol("phi")
  69. >>> diff(Ynm(n, m, theta, phi), theta)
  70. m*cot(theta)*Ynm(n, m, theta, phi) + sqrt((-m + n)*(m + n + 1))*exp(-I*phi)*Ynm(n, m + 1, theta, phi)
  71. >>> diff(Ynm(n, m, theta, phi), phi)
  72. I*m*Ynm(n, m, theta, phi)
  73. Further we can compute the complex conjugation:
  74. >>> from sympy import Ynm, Symbol, conjugate
  75. >>> from sympy.abc import n,m
  76. >>> theta = Symbol("theta")
  77. >>> phi = Symbol("phi")
  78. >>> conjugate(Ynm(n, m, theta, phi))
  79. (-1)**(2*m)*exp(-2*I*m*phi)*Ynm(n, m, theta, phi)
  80. To get back the well known expressions in spherical
  81. coordinates, we use full expansion:
  82. >>> from sympy import Ynm, Symbol, expand_func
  83. >>> from sympy.abc import n,m
  84. >>> theta = Symbol("theta")
  85. >>> phi = Symbol("phi")
  86. >>> expand_func(Ynm(n, m, theta, phi))
  87. sqrt((2*n + 1)*factorial(-m + n)/factorial(m + n))*exp(I*m*phi)*assoc_legendre(n, m, cos(theta))/(2*sqrt(pi))
  88. See Also
  89. ========
  90. Ynm_c, Znm
  91. References
  92. ==========
  93. .. [1] https://en.wikipedia.org/wiki/Spherical_harmonics
  94. .. [2] https://mathworld.wolfram.com/SphericalHarmonic.html
  95. .. [3] https://functions.wolfram.com/Polynomials/SphericalHarmonicY/
  96. .. [4] https://dlmf.nist.gov/14.30
  97. """
  98. @classmethod
  99. def eval(cls, n, m, theta, phi):
  100. # Handle negative index m and arguments theta, phi
  101. if m.could_extract_minus_sign():
  102. m = -m
  103. return S.NegativeOne**m * exp(-2*I*m*phi) * Ynm(n, m, theta, phi)
  104. if theta.could_extract_minus_sign():
  105. theta = -theta
  106. return Ynm(n, m, theta, phi)
  107. if phi.could_extract_minus_sign():
  108. phi = -phi
  109. return exp(-2*I*m*phi) * Ynm(n, m, theta, phi)
  110. # TODO Add more simplififcation here
  111. def _eval_expand_func(self, **hints):
  112. n, m, theta, phi = self.args
  113. rv = (sqrt((2*n + 1)/(4*pi) * factorial(n - m)/factorial(n + m)) *
  114. exp(I*m*phi) * assoc_legendre(n, m, cos(theta)))
  115. # We can do this because of the range of theta
  116. return rv.subs(sqrt(-cos(theta)**2 + 1), sin(theta))
  117. def fdiff(self, argindex=4):
  118. if argindex == 1:
  119. # Diff wrt n
  120. raise ArgumentIndexError(self, argindex)
  121. elif argindex == 2:
  122. # Diff wrt m
  123. raise ArgumentIndexError(self, argindex)
  124. elif argindex == 3:
  125. # Diff wrt theta
  126. n, m, theta, phi = self.args
  127. return (m * cot(theta) * Ynm(n, m, theta, phi) +
  128. sqrt((n - m)*(n + m + 1)) * exp(-I*phi) * Ynm(n, m + 1, theta, phi))
  129. elif argindex == 4:
  130. # Diff wrt phi
  131. n, m, theta, phi = self.args
  132. return I * m * Ynm(n, m, theta, phi)
  133. else:
  134. raise ArgumentIndexError(self, argindex)
  135. def _eval_rewrite_as_polynomial(self, n, m, theta, phi, **kwargs):
  136. # TODO: Make sure n \in N
  137. # TODO: Assert |m| <= n ortherwise we should return 0
  138. return self.expand(func=True)
  139. def _eval_rewrite_as_sin(self, n, m, theta, phi, **kwargs):
  140. return self.rewrite(cos)
  141. def _eval_rewrite_as_cos(self, n, m, theta, phi, **kwargs):
  142. # This method can be expensive due to extensive use of simplification!
  143. from sympy.simplify import simplify, trigsimp
  144. # TODO: Make sure n \in N
  145. # TODO: Assert |m| <= n ortherwise we should return 0
  146. term = simplify(self.expand(func=True))
  147. # We can do this because of the range of theta
  148. term = term.xreplace({Abs(sin(theta)):sin(theta)})
  149. return simplify(trigsimp(term))
  150. def _eval_conjugate(self):
  151. # TODO: Make sure theta \in R and phi \in R
  152. n, m, theta, phi = self.args
  153. return S.NegativeOne**m * self.func(n, -m, theta, phi)
  154. def as_real_imag(self, deep=True, **hints):
  155. # TODO: Handle deep and hints
  156. n, m, theta, phi = self.args
  157. re = (sqrt((2*n + 1)/(4*pi) * factorial(n - m)/factorial(n + m)) *
  158. cos(m*phi) * assoc_legendre(n, m, cos(theta)))
  159. im = (sqrt((2*n + 1)/(4*pi) * factorial(n - m)/factorial(n + m)) *
  160. sin(m*phi) * assoc_legendre(n, m, cos(theta)))
  161. return (re, im)
  162. def _eval_evalf(self, prec):
  163. # Note: works without this function by just calling
  164. # mpmath for Legendre polynomials. But using
  165. # the dedicated function directly is cleaner.
  166. from mpmath import mp, workprec
  167. n = self.args[0]._to_mpmath(prec)
  168. m = self.args[1]._to_mpmath(prec)
  169. theta = self.args[2]._to_mpmath(prec)
  170. phi = self.args[3]._to_mpmath(prec)
  171. with workprec(prec):
  172. res = mp.spherharm(n, m, theta, phi)
  173. return Expr._from_mpmath(res, prec)
  174. def Ynm_c(n, m, theta, phi):
  175. r"""
  176. Conjugate spherical harmonics defined as
  177. .. math::
  178. \overline{Y_n^m(\theta, \varphi)} := (-1)^m Y_n^{-m}(\theta, \varphi).
  179. Examples
  180. ========
  181. >>> from sympy import Ynm_c, Symbol, simplify
  182. >>> from sympy.abc import n,m
  183. >>> theta = Symbol("theta")
  184. >>> phi = Symbol("phi")
  185. >>> Ynm_c(n, m, theta, phi)
  186. (-1)**(2*m)*exp(-2*I*m*phi)*Ynm(n, m, theta, phi)
  187. >>> Ynm_c(n, m, -theta, phi)
  188. (-1)**(2*m)*exp(-2*I*m*phi)*Ynm(n, m, theta, phi)
  189. For specific integers $n$ and $m$ we can evaluate the harmonics
  190. to more useful expressions:
  191. >>> simplify(Ynm_c(0, 0, theta, phi).expand(func=True))
  192. 1/(2*sqrt(pi))
  193. >>> simplify(Ynm_c(1, -1, theta, phi).expand(func=True))
  194. sqrt(6)*exp(I*(-phi + 2*conjugate(phi)))*sin(theta)/(4*sqrt(pi))
  195. See Also
  196. ========
  197. Ynm, Znm
  198. References
  199. ==========
  200. .. [1] https://en.wikipedia.org/wiki/Spherical_harmonics
  201. .. [2] https://mathworld.wolfram.com/SphericalHarmonic.html
  202. .. [3] https://functions.wolfram.com/Polynomials/SphericalHarmonicY/
  203. """
  204. return conjugate(Ynm(n, m, theta, phi))
  205. class Znm(Function):
  206. r"""
  207. Real spherical harmonics defined as
  208. .. math::
  209. Z_n^m(\theta, \varphi) :=
  210. \begin{cases}
  211. \frac{Y_n^m(\theta, \varphi) + \overline{Y_n^m(\theta, \varphi)}}{\sqrt{2}} &\quad m > 0 \\
  212. Y_n^m(\theta, \varphi) &\quad m = 0 \\
  213. \frac{Y_n^m(\theta, \varphi) - \overline{Y_n^m(\theta, \varphi)}}{i \sqrt{2}} &\quad m < 0 \\
  214. \end{cases}
  215. which gives in simplified form
  216. .. math::
  217. Z_n^m(\theta, \varphi) =
  218. \begin{cases}
  219. \frac{Y_n^m(\theta, \varphi) + (-1)^m Y_n^{-m}(\theta, \varphi)}{\sqrt{2}} &\quad m > 0 \\
  220. Y_n^m(\theta, \varphi) &\quad m = 0 \\
  221. \frac{Y_n^m(\theta, \varphi) - (-1)^m Y_n^{-m}(\theta, \varphi)}{i \sqrt{2}} &\quad m < 0 \\
  222. \end{cases}
  223. Examples
  224. ========
  225. >>> from sympy import Znm, Symbol, simplify
  226. >>> from sympy.abc import n, m
  227. >>> theta = Symbol("theta")
  228. >>> phi = Symbol("phi")
  229. >>> Znm(n, m, theta, phi)
  230. Znm(n, m, theta, phi)
  231. For specific integers n and m we can evaluate the harmonics
  232. to more useful expressions:
  233. >>> simplify(Znm(0, 0, theta, phi).expand(func=True))
  234. 1/(2*sqrt(pi))
  235. >>> simplify(Znm(1, 1, theta, phi).expand(func=True))
  236. -sqrt(3)*sin(theta)*cos(phi)/(2*sqrt(pi))
  237. >>> simplify(Znm(2, 1, theta, phi).expand(func=True))
  238. -sqrt(15)*sin(2*theta)*cos(phi)/(4*sqrt(pi))
  239. See Also
  240. ========
  241. Ynm, Ynm_c
  242. References
  243. ==========
  244. .. [1] https://en.wikipedia.org/wiki/Spherical_harmonics
  245. .. [2] https://mathworld.wolfram.com/SphericalHarmonic.html
  246. .. [3] https://functions.wolfram.com/Polynomials/SphericalHarmonicY/
  247. """
  248. @classmethod
  249. def eval(cls, n, m, theta, phi):
  250. if m.is_positive:
  251. zz = (Ynm(n, m, theta, phi) + Ynm_c(n, m, theta, phi)) / sqrt(2)
  252. return zz
  253. elif m.is_zero:
  254. return Ynm(n, m, theta, phi)
  255. elif m.is_negative:
  256. zz = (Ynm(n, m, theta, phi) - Ynm_c(n, m, theta, phi)) / (sqrt(2)*I)
  257. return zz