SphericalEngine.hpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /**
  2. * \file SphericalEngine.hpp
  3. * \brief Header for GeographicLib::SphericalEngine 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_SPHERICALENGINE_HPP)
  10. #define GEOGRAPHICLIB_SPHERICALENGINE_HPP 1
  11. #include <vector>
  12. #include <istream>
  13. #include <GeographicLib/Constants.hpp>
  14. #if defined(_MSC_VER)
  15. // Squelch warnings about dll vs vector
  16. # pragma warning (push)
  17. # pragma warning (disable: 4251)
  18. #endif
  19. namespace GeographicLib {
  20. class CircularEngine;
  21. /**
  22. * \brief The evaluation engine for SphericalHarmonic
  23. *
  24. * This serves as the backend to SphericalHarmonic, SphericalHarmonic1, and
  25. * SphericalHarmonic2. Typically end-users will not have to access this
  26. * class directly.
  27. *
  28. * See SphericalEngine.cpp for more information on the implementation.
  29. *
  30. * Example of use:
  31. * \include example-SphericalEngine.cpp
  32. **********************************************************************/
  33. class GEOGRAPHICLIB_EXPORT SphericalEngine {
  34. private:
  35. typedef Math::real real;
  36. // CircularEngine needs access to sqrttable, scale
  37. friend class CircularEngine;
  38. // Return the table of the square roots of integers
  39. static std::vector<real>& sqrttable();
  40. // An internal scaling of the coefficients to avoid overflow in
  41. // intermediate calculations.
  42. static real scale() {
  43. using std::pow;
  44. static const real
  45. // Need extra real because, since C++11, pow(float, int) returns double
  46. s = real(pow(real(std::numeric_limits<real>::radix),
  47. -3 * (std::numeric_limits<real>::max_exponent < (1<<14) ?
  48. std::numeric_limits<real>::max_exponent : (1<<14))
  49. / 5));
  50. return s;
  51. }
  52. // Move latitudes near the pole off the axis by this amount.
  53. static real eps() {
  54. using std::sqrt;
  55. return std::numeric_limits<real>::epsilon() *
  56. sqrt(std::numeric_limits<real>::epsilon());
  57. }
  58. SphericalEngine(); // Disable constructor
  59. public:
  60. /**
  61. * Supported normalizations for associated Legendre polynomials.
  62. **********************************************************************/
  63. enum normalization {
  64. /**
  65. * Fully normalized associated Legendre polynomials. See
  66. * SphericalHarmonic::FULL for documentation.
  67. *
  68. * @hideinitializer
  69. **********************************************************************/
  70. FULL = 0,
  71. /**
  72. * Schmidt semi-normalized associated Legendre polynomials. See
  73. * SphericalHarmonic::SCHMIDT for documentation.
  74. *
  75. * @hideinitializer
  76. **********************************************************************/
  77. SCHMIDT = 1,
  78. };
  79. /**
  80. * \brief Package up coefficients for SphericalEngine
  81. *
  82. * This packages up the \e C, \e S coefficients and information about how
  83. * the coefficients are stored into a single structure. This allows a
  84. * vector of type SphericalEngine::coeff to be passed to
  85. * SphericalEngine::Value. This class also includes functions to aid
  86. * indexing into \e C and \e S.
  87. *
  88. * The storage layout of the coefficients is documented in
  89. * SphericalHarmonic and SphericalHarmonic::SphericalHarmonic.
  90. **********************************************************************/
  91. class GEOGRAPHICLIB_EXPORT coeff {
  92. private:
  93. int _Nx, _nmx, _mmx;
  94. std::vector<real>::const_iterator _Cnm;
  95. std::vector<real>::const_iterator _Snm;
  96. public:
  97. /**
  98. * A default constructor
  99. **********************************************************************/
  100. coeff() : _Nx(-1) , _nmx(-1) , _mmx(-1) {}
  101. /**
  102. * The general constructor.
  103. *
  104. * @param[in] C a vector of coefficients for the cosine terms.
  105. * @param[in] S a vector of coefficients for the sine terms.
  106. * @param[in] N the degree giving storage layout for \e C and \e S.
  107. * @param[in] nmx the maximum degree to be used.
  108. * @param[in] mmx the maximum order to be used.
  109. * @exception GeographicErr if \e N, \e nmx, and \e mmx do not satisfy
  110. * \e N &ge; \e nmx &ge; \e mmx &ge; &minus;1.
  111. * @exception GeographicErr if \e C or \e S is not big enough to hold the
  112. * coefficients.
  113. * @exception std::bad_alloc if the memory for the square root table
  114. * can't be allocated.
  115. **********************************************************************/
  116. coeff(const std::vector<real>& C,
  117. const std::vector<real>& S,
  118. int N, int nmx, int mmx)
  119. : _Nx(N)
  120. , _nmx(nmx)
  121. , _mmx(mmx)
  122. , _Cnm(C.begin())
  123. , _Snm(S.begin())
  124. {
  125. if (!((_Nx >= _nmx && _nmx >= _mmx && _mmx >= 0) ||
  126. // If mmx = -1 then the sums are empty so require nmx = -1 also.
  127. (_nmx == -1 && _mmx == -1)))
  128. throw GeographicErr("Bad indices for coeff");
  129. if (!(index(_nmx, _mmx) < int(C.size()) &&
  130. index(_nmx, _mmx) < int(S.size()) + (_Nx + 1)))
  131. throw GeographicErr("Arrays too small in coeff");
  132. SphericalEngine::RootTable(_nmx);
  133. }
  134. /**
  135. * The constructor for full coefficient vectors.
  136. *
  137. * @param[in] C a vector of coefficients for the cosine terms.
  138. * @param[in] S a vector of coefficients for the sine terms.
  139. * @param[in] N the maximum degree and order.
  140. * @exception GeographicErr if \e N does not satisfy \e N &ge; &minus;1.
  141. * @exception GeographicErr if \e C or \e S is not big enough to hold the
  142. * coefficients.
  143. * @exception std::bad_alloc if the memory for the square root table
  144. * can't be allocated.
  145. **********************************************************************/
  146. coeff(const std::vector<real>& C,
  147. const std::vector<real>& S,
  148. int N)
  149. : _Nx(N)
  150. , _nmx(N)
  151. , _mmx(N)
  152. , _Cnm(C.begin())
  153. , _Snm(S.begin())
  154. {
  155. if (!(_Nx >= -1))
  156. throw GeographicErr("Bad indices for coeff");
  157. if (!(index(_nmx, _mmx) < int(C.size()) &&
  158. index(_nmx, _mmx) < int(S.size()) + (_Nx + 1)))
  159. throw GeographicErr("Arrays too small in coeff");
  160. SphericalEngine::RootTable(_nmx);
  161. }
  162. /**
  163. * @return \e N the degree giving storage layout for \e C and \e S.
  164. **********************************************************************/
  165. int N() const { return _Nx; }
  166. /**
  167. * @return \e nmx the maximum degree to be used.
  168. **********************************************************************/
  169. int nmx() const { return _nmx; }
  170. /**
  171. * @return \e mmx the maximum order to be used.
  172. **********************************************************************/
  173. int mmx() const { return _mmx; }
  174. /**
  175. * The one-dimensional index into \e C and \e S.
  176. *
  177. * @param[in] n the degree.
  178. * @param[in] m the order.
  179. * @return the one-dimensional index.
  180. **********************************************************************/
  181. int index(int n, int m) const
  182. { return m * _Nx - m * (m - 1) / 2 + n; }
  183. /**
  184. * An element of \e C.
  185. *
  186. * @param[in] k the one-dimensional index.
  187. * @return the value of the \e C coefficient.
  188. **********************************************************************/
  189. Math::real Cv(int k) const { return *(_Cnm + k); }
  190. /**
  191. * An element of \e S.
  192. *
  193. * @param[in] k the one-dimensional index.
  194. * @return the value of the \e S coefficient.
  195. **********************************************************************/
  196. Math::real Sv(int k) const { return *(_Snm + (k - (_Nx + 1))); }
  197. /**
  198. * An element of \e C with checking.
  199. *
  200. * @param[in] k the one-dimensional index.
  201. * @param[in] n the requested degree.
  202. * @param[in] m the requested order.
  203. * @param[in] f a multiplier.
  204. * @return the value of the \e C coefficient multiplied by \e f in \e n
  205. * and \e m are in range else 0.
  206. **********************************************************************/
  207. Math::real Cv(int k, int n, int m, real f) const
  208. { return m > _mmx || n > _nmx ? 0 : *(_Cnm + k) * f; }
  209. /**
  210. * An element of \e S with checking.
  211. *
  212. * @param[in] k the one-dimensional index.
  213. * @param[in] n the requested degree.
  214. * @param[in] m the requested order.
  215. * @param[in] f a multiplier.
  216. * @return the value of the \e S coefficient multiplied by \e f in \e n
  217. * and \e m are in range else 0.
  218. **********************************************************************/
  219. Math::real Sv(int k, int n, int m, real f) const
  220. { return m > _mmx || n > _nmx ? 0 : *(_Snm + (k - (_Nx + 1))) * f; }
  221. /**
  222. * The size of the coefficient vector for the cosine terms.
  223. *
  224. * @param[in] N the maximum degree.
  225. * @param[in] M the maximum order.
  226. * @return the size of the vector of cosine terms as stored in column
  227. * major order.
  228. **********************************************************************/
  229. static int Csize(int N, int M)
  230. { return (M + 1) * (2 * N - M + 2) / 2; }
  231. /**
  232. * The size of the coefficient vector for the sine terms.
  233. *
  234. * @param[in] N the maximum degree.
  235. * @param[in] M the maximum order.
  236. * @return the size of the vector of cosine terms as stored in column
  237. * major order.
  238. **********************************************************************/
  239. static int Ssize(int N, int M)
  240. { return Csize(N, M) - (N + 1); }
  241. /**
  242. * Load coefficients from a binary stream.
  243. *
  244. * @param[in] stream the input stream.
  245. * @param[in,out] N The maximum degree of the coefficients.
  246. * @param[in,out] M The maximum order of the coefficients.
  247. * @param[out] C The vector of cosine coefficients.
  248. * @param[out] S The vector of sine coefficients.
  249. * @param[in] truncate if false (the default) then \e N and \e M are
  250. * determined by the values in the binary stream; otherwise, the input
  251. * values of \e N and \e M are used to truncate the coefficients read
  252. * from the stream at the given degree and order.
  253. * @exception GeographicErr if \e N and \e M do not satisfy \e N &ge;
  254. * \e M &ge; &minus;1.
  255. * @exception GeographicErr if there's an error reading the data.
  256. * @exception std::bad_alloc if the memory for \e C or \e S can't be
  257. * allocated.
  258. *
  259. * \e N and \e M are read as 4-byte ints. \e C and \e S are resized to
  260. * accommodate all the coefficients (with the \e m = 0 coefficients for
  261. * \e S excluded) and the data for these coefficients read as 8-byte
  262. * doubles. The coefficients are stored in column major order. The
  263. * bytes in the stream should use little-endian ordering. IEEE floating
  264. * point is assumed for the coefficients.
  265. **********************************************************************/
  266. static void readcoeffs(std::istream& stream, int& N, int& M,
  267. std::vector<real>& C, std::vector<real>& S,
  268. bool truncate = false);
  269. };
  270. /**
  271. * Evaluate a spherical harmonic sum and its gradient.
  272. *
  273. * @tparam gradp should the gradient be calculated.
  274. * @tparam norm the normalization for the associated Legendre polynomials.
  275. * @tparam L the number of terms in the coefficients.
  276. * @param[in] c an array of coeff objects.
  277. * @param[in] f array of coefficient multipliers. f[0] should be 1.
  278. * @param[in] x the \e x component of the cartesian position.
  279. * @param[in] y the \e y component of the cartesian position.
  280. * @param[in] z the \e z component of the cartesian position.
  281. * @param[in] a the normalizing radius.
  282. * @param[out] gradx the \e x component of the gradient.
  283. * @param[out] grady the \e y component of the gradient.
  284. * @param[out] gradz the \e z component of the gradient.
  285. * @result the spherical harmonic sum.
  286. *
  287. * See the SphericalHarmonic class for the definition of the sum. The
  288. * coefficients used by this function are, for example, c[0].Cv + f[1] *
  289. * c[1].Cv + ... + f[L&minus;1] * c[L&minus;1].Cv. (Note that f[0] is \e
  290. * not used.) The upper limits on the sum are determined by c[0].nmx() and
  291. * c[0].mmx(); these limits apply to \e all the components of the
  292. * coefficients. The parameters \e gradp, \e norm, and \e L are template
  293. * parameters, to allow more optimization to be done at compile time.
  294. *
  295. * Clenshaw summation is used which permits the evaluation of the sum
  296. * without the need to allocate temporary arrays. Thus this function never
  297. * throws an exception.
  298. **********************************************************************/
  299. template<bool gradp, normalization norm, int L>
  300. static Math::real Value(const coeff c[], const real f[],
  301. real x, real y, real z, real a,
  302. real& gradx, real& grady, real& gradz);
  303. /**
  304. * Create a CircularEngine object
  305. *
  306. * @tparam gradp should the gradient be calculated.
  307. * @tparam norm the normalization for the associated Legendre polynomials.
  308. * @tparam L the number of terms in the coefficients.
  309. * @param[in] c an array of coeff objects.
  310. * @param[in] f array of coefficient multipliers. f[0] should be 1.
  311. * @param[in] p the radius of the circle = sqrt(<i>x</i><sup>2</sup> +
  312. * <i>y</i><sup>2</sup>).
  313. * @param[in] z the height of the circle.
  314. * @param[in] a the normalizing radius.
  315. * @exception std::bad_alloc if the memory for the CircularEngine can't be
  316. * allocated.
  317. * @result the CircularEngine object.
  318. *
  319. * If you need to evaluate the spherical harmonic sum for several points
  320. * with constant \e f, \e p = sqrt(<i>x</i><sup>2</sup> +
  321. * <i>y</i><sup>2</sup>), \e z, and \e a, it is more efficient to construct
  322. * call SphericalEngine::Circle to give a CircularEngine object and then
  323. * call CircularEngine::operator()() with arguments <i>x</i>/\e p and
  324. * <i>y</i>/\e p.
  325. **********************************************************************/
  326. template<bool gradp, normalization norm, int L>
  327. static CircularEngine Circle(const coeff c[], const real f[],
  328. real p, real z, real a);
  329. /**
  330. * Check that the static table of square roots is big enough and enlarge it
  331. * if necessary.
  332. *
  333. * @param[in] N the maximum degree to be used in SphericalEngine.
  334. * @exception std::bad_alloc if the memory for the square root table can't
  335. * be allocated.
  336. *
  337. * Typically, there's no need for an end-user to call this routine, because
  338. * the constructors for SphericalEngine::coeff do so. However, since this
  339. * updates a static table, there's a possible race condition in a
  340. * multi-threaded environment. Because this routine does nothing if the
  341. * table is already large enough, one way to avoid race conditions is to
  342. * call this routine at program start up (when it's still single threaded),
  343. * supplying the largest degree that your program will use. E.g., \code
  344. GeographicLib::SphericalEngine::RootTable(2190);
  345. \endcode
  346. * suffices to accommodate extant magnetic and gravity models.
  347. **********************************************************************/
  348. static void RootTable(int N);
  349. /**
  350. * Clear the static table of square roots and release the memory. Call
  351. * this only when you are sure you no longer will be using SphericalEngine.
  352. * Your program will crash if you call SphericalEngine after calling this
  353. * routine.
  354. *
  355. * \warning It's safest not to call this routine at all. (The space used
  356. * by the table is modest.)
  357. **********************************************************************/
  358. static void ClearRootTable() {
  359. std::vector<real> temp(0);
  360. sqrttable().swap(temp);
  361. }
  362. };
  363. } // namespace GeographicLib
  364. #if defined(_MSC_VER)
  365. # pragma warning (pop)
  366. #endif
  367. #endif // GEOGRAPHICLIB_SPHERICALENGINE_HPP