SphericalHarmonic.hpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /**
  2. * \file SphericalHarmonic.hpp
  3. * \brief Header for GeographicLib::SphericalHarmonic class
  4. *
  5. * Copyright (c) Charles Karney (2011-2019) <charles@karney.com> and licensed
  6. * under the MIT/X11 License. For more information, see
  7. * https://geographiclib.sourceforge.io/
  8. **********************************************************************/
  9. #if !defined(GEOGRAPHICLIB_SPHERICALHARMONIC_HPP)
  10. #define GEOGRAPHICLIB_SPHERICALHARMONIC_HPP 1
  11. #include <vector>
  12. #include <GeographicLib/Constants.hpp>
  13. #include <GeographicLib/SphericalEngine.hpp>
  14. #include <GeographicLib/CircularEngine.hpp>
  15. namespace GeographicLib {
  16. /**
  17. * \brief Spherical harmonic series
  18. *
  19. * This class evaluates the spherical harmonic sum \verbatim
  20. V(x, y, z) = sum(n = 0..N)[ q^(n+1) * sum(m = 0..n)[
  21. (C[n,m] * cos(m*lambda) + S[n,m] * sin(m*lambda)) *
  22. P[n,m](cos(theta)) ] ]
  23. \endverbatim
  24. * where
  25. * - <i>p</i><sup>2</sup> = <i>x</i><sup>2</sup> + <i>y</i><sup>2</sup>,
  26. * - <i>r</i><sup>2</sup> = <i>p</i><sup>2</sup> + <i>z</i><sup>2</sup>,
  27. * - \e q = <i>a</i>/<i>r</i>,
  28. * - &theta; = atan2(\e p, \e z) = the spherical \e colatitude,
  29. * - &lambda; = atan2(\e y, \e x) = the longitude.
  30. * - P<sub><i>nm</i></sub>(\e t) is the associated Legendre polynomial of
  31. * degree \e n and order \e m.
  32. *
  33. * Two normalizations are supported for P<sub><i>nm</i></sub>
  34. * - fully normalized denoted by SphericalHarmonic::FULL.
  35. * - Schmidt semi-normalized denoted by SphericalHarmonic::SCHMIDT.
  36. *
  37. * Clenshaw summation is used for the sums over both \e n and \e m. This
  38. * allows the computation to be carried out without the need for any
  39. * temporary arrays. See SphericalEngine.cpp for more information on the
  40. * implementation.
  41. *
  42. * References:
  43. * - C. W. Clenshaw,
  44. * <a href="https://doi.org/10.1090/S0025-5718-1955-0071856-0">
  45. * A note on the summation of Chebyshev series</a>,
  46. * %Math. Tables Aids Comput. 9(51), 118--120 (1955).
  47. * - R. E. Deakin, Derivatives of the earth's potentials, Geomatics
  48. * Research Australasia 68, 31--60, (June 1998).
  49. * - W. A. Heiskanen and H. Moritz, Physical Geodesy, (Freeman, San
  50. * Francisco, 1967). (See Sec. 1-14, for a definition of Pbar.)
  51. * - S. A. Holmes and W. E. Featherstone,
  52. * <a href="https://doi.org/10.1007/s00190-002-0216-2">
  53. * A unified approach to the Clenshaw summation and the recursive
  54. * computation of very high degree and order normalised associated Legendre
  55. * functions</a>, J. Geodesy 76(5), 279--299 (2002).
  56. * - C. C. Tscherning and K. Poder,
  57. * <a href="http://cct.gfy.ku.dk/publ_cct/cct80.pdf">
  58. * Some geodetic applications of Clenshaw summation</a>,
  59. * Boll. Geod. Sci. Aff. 41(4), 349--375 (1982).
  60. *
  61. * Example of use:
  62. * \include example-SphericalHarmonic.cpp
  63. **********************************************************************/
  64. class GEOGRAPHICLIB_EXPORT SphericalHarmonic {
  65. public:
  66. /**
  67. * Supported normalizations for the associated Legendre polynomials.
  68. **********************************************************************/
  69. enum normalization {
  70. /**
  71. * Fully normalized associated Legendre polynomials.
  72. *
  73. * These are defined by
  74. * <i>P</i><sub><i>nm</i></sub><sup>full</sup>(\e z)
  75. * = (&minus;1)<sup><i>m</i></sup>
  76. * sqrt(\e k (2\e n + 1) (\e n &minus; \e m)! / (\e n + \e m)!)
  77. * <b>P</b><sub><i>n</i></sub><sup><i>m</i></sup>(\e z), where
  78. * <b>P</b><sub><i>n</i></sub><sup><i>m</i></sup>(\e z) is Ferrers
  79. * function (also known as the Legendre function on the cut or the
  80. * associated Legendre polynomial) https://dlmf.nist.gov/14.7.E10 and
  81. * \e k = 1 for \e m = 0 and \e k = 2 otherwise.
  82. *
  83. * The mean squared value of
  84. * <i>P</i><sub><i>nm</i></sub><sup>full</sup>(cos&theta;)
  85. * cos(<i>m</i>&lambda;) and
  86. * <i>P</i><sub><i>nm</i></sub><sup>full</sup>(cos&theta;)
  87. * sin(<i>m</i>&lambda;) over the sphere is 1.
  88. *
  89. * @hideinitializer
  90. **********************************************************************/
  91. FULL = SphericalEngine::FULL,
  92. /**
  93. * Schmidt semi-normalized associated Legendre polynomials.
  94. *
  95. * These are defined by
  96. * <i>P</i><sub><i>nm</i></sub><sup>schmidt</sup>(\e z)
  97. * = (&minus;1)<sup><i>m</i></sup>
  98. * sqrt(\e k (\e n &minus; \e m)! / (\e n + \e m)!)
  99. * <b>P</b><sub><i>n</i></sub><sup><i>m</i></sup>(\e z), where
  100. * <b>P</b><sub><i>n</i></sub><sup><i>m</i></sup>(\e z) is Ferrers
  101. * function (also known as the Legendre function on the cut or the
  102. * associated Legendre polynomial) https://dlmf.nist.gov/14.7.E10 and
  103. * \e k = 1 for \e m = 0 and \e k = 2 otherwise.
  104. *
  105. * The mean squared value of
  106. * <i>P</i><sub><i>nm</i></sub><sup>schmidt</sup>(cos&theta;)
  107. * cos(<i>m</i>&lambda;) and
  108. * <i>P</i><sub><i>nm</i></sub><sup>schmidt</sup>(cos&theta;)
  109. * sin(<i>m</i>&lambda;) over the sphere is 1/(2\e n + 1).
  110. *
  111. * @hideinitializer
  112. **********************************************************************/
  113. SCHMIDT = SphericalEngine::SCHMIDT,
  114. };
  115. private:
  116. typedef Math::real real;
  117. SphericalEngine::coeff _c[1];
  118. real _a;
  119. unsigned _norm;
  120. public:
  121. /**
  122. * Constructor with a full set of coefficients specified.
  123. *
  124. * @param[in] C the coefficients <i>C</i><sub><i>nm</i></sub>.
  125. * @param[in] S the coefficients <i>S</i><sub><i>nm</i></sub>.
  126. * @param[in] N the maximum degree and order of the sum
  127. * @param[in] a the reference radius appearing in the definition of the
  128. * sum.
  129. * @param[in] norm the normalization for the associated Legendre
  130. * polynomials, either SphericalHarmonic::FULL (the default) or
  131. * SphericalHarmonic::SCHMIDT.
  132. * @exception GeographicErr if \e N does not satisfy \e N &ge; &minus;1.
  133. * @exception GeographicErr if \e C or \e S is not big enough to hold the
  134. * coefficients.
  135. *
  136. * The coefficients <i>C</i><sub><i>nm</i></sub> and
  137. * <i>S</i><sub><i>nm</i></sub> are stored in the one-dimensional vectors
  138. * \e C and \e S which must contain (\e N + 1)(\e N + 2)/2 and \e N (\e N +
  139. * 1)/2 elements, respectively, stored in "column-major" order. Thus for
  140. * \e N = 3, the order would be:
  141. * <i>C</i><sub>00</sub>,
  142. * <i>C</i><sub>10</sub>,
  143. * <i>C</i><sub>20</sub>,
  144. * <i>C</i><sub>30</sub>,
  145. * <i>C</i><sub>11</sub>,
  146. * <i>C</i><sub>21</sub>,
  147. * <i>C</i><sub>31</sub>,
  148. * <i>C</i><sub>22</sub>,
  149. * <i>C</i><sub>32</sub>,
  150. * <i>C</i><sub>33</sub>.
  151. * In general the (\e n,\e m) element is at index \e m \e N &minus; \e m
  152. * (\e m &minus; 1)/2 + \e n. The layout of \e S is the same except that
  153. * the first column is omitted (since the \e m = 0 terms never contribute
  154. * to the sum) and the 0th element is <i>S</i><sub>11</sub>
  155. *
  156. * The class stores <i>pointers</i> to the first elements of \e C and \e S.
  157. * These arrays should not be altered or destroyed during the lifetime of a
  158. * SphericalHarmonic object.
  159. **********************************************************************/
  160. SphericalHarmonic(const std::vector<real>& C,
  161. const std::vector<real>& S,
  162. int N, real a, unsigned norm = FULL)
  163. : _a(a)
  164. , _norm(norm)
  165. { _c[0] = SphericalEngine::coeff(C, S, N); }
  166. /**
  167. * Constructor with a subset of coefficients specified.
  168. *
  169. * @param[in] C the coefficients <i>C</i><sub><i>nm</i></sub>.
  170. * @param[in] S the coefficients <i>S</i><sub><i>nm</i></sub>.
  171. * @param[in] N the degree used to determine the layout of \e C and \e S.
  172. * @param[in] nmx the maximum degree used in the sum. The sum over \e n is
  173. * from 0 thru \e nmx.
  174. * @param[in] mmx the maximum order used in the sum. The sum over \e m is
  175. * from 0 thru min(\e n, \e mmx).
  176. * @param[in] a the reference radius appearing in the definition of the
  177. * sum.
  178. * @param[in] norm the normalization for the associated Legendre
  179. * polynomials, either SphericalHarmonic::FULL (the default) or
  180. * SphericalHarmonic::SCHMIDT.
  181. * @exception GeographicErr if \e N, \e nmx, and \e mmx do not satisfy
  182. * \e N &ge; \e nmx &ge; \e mmx &ge; &minus;1.
  183. * @exception GeographicErr if \e C or \e S is not big enough to hold the
  184. * coefficients.
  185. *
  186. * The class stores <i>pointers</i> to the first elements of \e C and \e S.
  187. * These arrays should not be altered or destroyed during the lifetime of a
  188. * SphericalHarmonic object.
  189. **********************************************************************/
  190. SphericalHarmonic(const std::vector<real>& C,
  191. const std::vector<real>& S,
  192. int N, int nmx, int mmx,
  193. real a, unsigned norm = FULL)
  194. : _a(a)
  195. , _norm(norm)
  196. { _c[0] = SphericalEngine::coeff(C, S, N, nmx, mmx); }
  197. /**
  198. * A default constructor so that the object can be created when the
  199. * constructor for another object is initialized. This default object can
  200. * then be reset with the default copy assignment operator.
  201. **********************************************************************/
  202. SphericalHarmonic() {}
  203. /**
  204. * Compute the spherical harmonic sum.
  205. *
  206. * @param[in] x cartesian coordinate.
  207. * @param[in] y cartesian coordinate.
  208. * @param[in] z cartesian coordinate.
  209. * @return \e V the spherical harmonic sum.
  210. *
  211. * This routine requires constant memory and thus never throws an
  212. * exception.
  213. **********************************************************************/
  214. Math::real operator()(real x, real y, real z) const {
  215. real f[] = {1};
  216. real v = 0;
  217. real dummy;
  218. switch (_norm) {
  219. case FULL:
  220. v = SphericalEngine::Value<false, SphericalEngine::FULL, 1>
  221. (_c, f, x, y, z, _a, dummy, dummy, dummy);
  222. break;
  223. case SCHMIDT:
  224. default: // To avoid compiler warnings
  225. v = SphericalEngine::Value<false, SphericalEngine::SCHMIDT, 1>
  226. (_c, f, x, y, z, _a, dummy, dummy, dummy);
  227. break;
  228. }
  229. return v;
  230. }
  231. /**
  232. * Compute a spherical harmonic sum and its gradient.
  233. *
  234. * @param[in] x cartesian coordinate.
  235. * @param[in] y cartesian coordinate.
  236. * @param[in] z cartesian coordinate.
  237. * @param[out] gradx \e x component of the gradient
  238. * @param[out] grady \e y component of the gradient
  239. * @param[out] gradz \e z component of the gradient
  240. * @return \e V the spherical harmonic sum.
  241. *
  242. * This is the same as the previous function, except that the components of
  243. * the gradients of the sum in the \e x, \e y, and \e z directions are
  244. * computed. This routine requires constant memory and thus never throws
  245. * an exception.
  246. **********************************************************************/
  247. Math::real operator()(real x, real y, real z,
  248. real& gradx, real& grady, real& gradz) const {
  249. real f[] = {1};
  250. real v = 0;
  251. switch (_norm) {
  252. case FULL:
  253. v = SphericalEngine::Value<true, SphericalEngine::FULL, 1>
  254. (_c, f, x, y, z, _a, gradx, grady, gradz);
  255. break;
  256. case SCHMIDT:
  257. default: // To avoid compiler warnings
  258. v = SphericalEngine::Value<true, SphericalEngine::SCHMIDT, 1>
  259. (_c, f, x, y, z, _a, gradx, grady, gradz);
  260. break;
  261. }
  262. return v;
  263. }
  264. /**
  265. * Create a CircularEngine to allow the efficient evaluation of several
  266. * points on a circle of latitude.
  267. *
  268. * @param[in] p the radius of the circle.
  269. * @param[in] z the height of the circle above the equatorial plane.
  270. * @param[in] gradp if true the returned object will be able to compute the
  271. * gradient of the sum.
  272. * @exception std::bad_alloc if the memory for the CircularEngine can't be
  273. * allocated.
  274. * @return the CircularEngine object.
  275. *
  276. * SphericalHarmonic::operator()() exchanges the order of the sums in the
  277. * definition, i.e., &sum;<sub><i>n</i> = 0..<i>N</i></sub>
  278. * &sum;<sub><i>m</i> = 0..<i>n</i></sub> becomes &sum;<sub><i>m</i> =
  279. * 0..<i>N</i></sub> &sum;<sub><i>n</i> = <i>m</i>..<i>N</i></sub>.
  280. * SphericalHarmonic::Circle performs the inner sum over degree \e n (which
  281. * entails about <i>N</i><sup>2</sup> operations). Calling
  282. * CircularEngine::operator()() on the returned object performs the outer
  283. * sum over the order \e m (about \e N operations).
  284. *
  285. * Here's an example of computing the spherical sum at a sequence of
  286. * longitudes without using a CircularEngine object \code
  287. SphericalHarmonic h(...); // Create the SphericalHarmonic object
  288. double r = 2, lat = 33, lon0 = 44, dlon = 0.01;
  289. double
  290. phi = lat * Math::degree<double>(),
  291. z = r * sin(phi), p = r * cos(phi);
  292. for (int i = 0; i <= 100; ++i) {
  293. real
  294. lon = lon0 + i * dlon,
  295. lam = lon * Math::degree<double>();
  296. std::cout << lon << " " << h(p * cos(lam), p * sin(lam), z) << "\n";
  297. }
  298. \endcode
  299. * Here is the same calculation done using a CircularEngine object. This
  300. * will be about <i>N</i>/2 times faster. \code
  301. SphericalHarmonic h(...); // Create the SphericalHarmonic object
  302. double r = 2, lat = 33, lon0 = 44, dlon = 0.01;
  303. double
  304. phi = lat * Math::degree<double>(),
  305. z = r * sin(phi), p = r * cos(phi);
  306. CircularEngine c(h(p, z, false)); // Create the CircularEngine object
  307. for (int i = 0; i <= 100; ++i) {
  308. real
  309. lon = lon0 + i * dlon;
  310. std::cout << lon << " " << c(lon) << "\n";
  311. }
  312. \endcode
  313. **********************************************************************/
  314. CircularEngine Circle(real p, real z, bool gradp) const {
  315. real f[] = {1};
  316. switch (_norm) {
  317. case FULL:
  318. return gradp ?
  319. SphericalEngine::Circle<true, SphericalEngine::FULL, 1>
  320. (_c, f, p, z, _a) :
  321. SphericalEngine::Circle<false, SphericalEngine::FULL, 1>
  322. (_c, f, p, z, _a);
  323. break;
  324. case SCHMIDT:
  325. default: // To avoid compiler warnings
  326. return gradp ?
  327. SphericalEngine::Circle<true, SphericalEngine::SCHMIDT, 1>
  328. (_c, f, p, z, _a) :
  329. SphericalEngine::Circle<false, SphericalEngine::SCHMIDT, 1>
  330. (_c, f, p, z, _a);
  331. break;
  332. }
  333. }
  334. /**
  335. * @return the zeroth SphericalEngine::coeff object.
  336. **********************************************************************/
  337. const SphericalEngine::coeff& Coefficients() const
  338. { return _c[0]; }
  339. };
  340. } // namespace GeographicLib
  341. #endif // GEOGRAPHICLIB_SPHERICALHARMONIC_HPP