/** * \file SphericalEngine.hpp * \brief Header for GeographicLib::SphericalEngine class * * Copyright (c) Charles Karney (2011-2019) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ **********************************************************************/ #if !defined(GEOGRAPHICLIB_SPHERICALENGINE_HPP) #define GEOGRAPHICLIB_SPHERICALENGINE_HPP 1 #include #include #include #if defined(_MSC_VER) // Squelch warnings about dll vs vector # pragma warning (push) # pragma warning (disable: 4251) #endif namespace GeographicLib { class CircularEngine; /** * \brief The evaluation engine for SphericalHarmonic * * This serves as the backend to SphericalHarmonic, SphericalHarmonic1, and * SphericalHarmonic2. Typically end-users will not have to access this * class directly. * * See SphericalEngine.cpp for more information on the implementation. * * Example of use: * \include example-SphericalEngine.cpp **********************************************************************/ class GEOGRAPHICLIB_EXPORT SphericalEngine { private: typedef Math::real real; // CircularEngine needs access to sqrttable, scale friend class CircularEngine; // Return the table of the square roots of integers static std::vector& sqrttable(); // An internal scaling of the coefficients to avoid overflow in // intermediate calculations. static real scale() { using std::pow; static const real // Need extra real because, since C++11, pow(float, int) returns double s = real(pow(real(std::numeric_limits::radix), -3 * (std::numeric_limits::max_exponent < (1<<14) ? std::numeric_limits::max_exponent : (1<<14)) / 5)); return s; } // Move latitudes near the pole off the axis by this amount. static real eps() { using std::sqrt; return std::numeric_limits::epsilon() * sqrt(std::numeric_limits::epsilon()); } SphericalEngine(); // Disable constructor public: /** * Supported normalizations for associated Legendre polynomials. **********************************************************************/ enum normalization { /** * Fully normalized associated Legendre polynomials. See * SphericalHarmonic::FULL for documentation. * * @hideinitializer **********************************************************************/ FULL = 0, /** * Schmidt semi-normalized associated Legendre polynomials. See * SphericalHarmonic::SCHMIDT for documentation. * * @hideinitializer **********************************************************************/ SCHMIDT = 1, }; /** * \brief Package up coefficients for SphericalEngine * * This packages up the \e C, \e S coefficients and information about how * the coefficients are stored into a single structure. This allows a * vector of type SphericalEngine::coeff to be passed to * SphericalEngine::Value. This class also includes functions to aid * indexing into \e C and \e S. * * The storage layout of the coefficients is documented in * SphericalHarmonic and SphericalHarmonic::SphericalHarmonic. **********************************************************************/ class GEOGRAPHICLIB_EXPORT coeff { private: int _Nx, _nmx, _mmx; std::vector::const_iterator _Cnm; std::vector::const_iterator _Snm; public: /** * A default constructor **********************************************************************/ coeff() : _Nx(-1) , _nmx(-1) , _mmx(-1) {} /** * The general constructor. * * @param[in] C a vector of coefficients for the cosine terms. * @param[in] S a vector of coefficients for the sine terms. * @param[in] N the degree giving storage layout for \e C and \e S. * @param[in] nmx the maximum degree to be used. * @param[in] mmx the maximum order to be used. * @exception GeographicErr if \e N, \e nmx, and \e mmx do not satisfy * \e N ≥ \e nmx ≥ \e mmx ≥ −1. * @exception GeographicErr if \e C or \e S is not big enough to hold the * coefficients. * @exception std::bad_alloc if the memory for the square root table * can't be allocated. **********************************************************************/ coeff(const std::vector& C, const std::vector& S, int N, int nmx, int mmx) : _Nx(N) , _nmx(nmx) , _mmx(mmx) , _Cnm(C.begin()) , _Snm(S.begin()) { if (!((_Nx >= _nmx && _nmx >= _mmx && _mmx >= 0) || // If mmx = -1 then the sums are empty so require nmx = -1 also. (_nmx == -1 && _mmx == -1))) throw GeographicErr("Bad indices for coeff"); if (!(index(_nmx, _mmx) < int(C.size()) && index(_nmx, _mmx) < int(S.size()) + (_Nx + 1))) throw GeographicErr("Arrays too small in coeff"); SphericalEngine::RootTable(_nmx); } /** * The constructor for full coefficient vectors. * * @param[in] C a vector of coefficients for the cosine terms. * @param[in] S a vector of coefficients for the sine terms. * @param[in] N the maximum degree and order. * @exception GeographicErr if \e N does not satisfy \e N ≥ −1. * @exception GeographicErr if \e C or \e S is not big enough to hold the * coefficients. * @exception std::bad_alloc if the memory for the square root table * can't be allocated. **********************************************************************/ coeff(const std::vector& C, const std::vector& S, int N) : _Nx(N) , _nmx(N) , _mmx(N) , _Cnm(C.begin()) , _Snm(S.begin()) { if (!(_Nx >= -1)) throw GeographicErr("Bad indices for coeff"); if (!(index(_nmx, _mmx) < int(C.size()) && index(_nmx, _mmx) < int(S.size()) + (_Nx + 1))) throw GeographicErr("Arrays too small in coeff"); SphericalEngine::RootTable(_nmx); } /** * @return \e N the degree giving storage layout for \e C and \e S. **********************************************************************/ int N() const { return _Nx; } /** * @return \e nmx the maximum degree to be used. **********************************************************************/ int nmx() const { return _nmx; } /** * @return \e mmx the maximum order to be used. **********************************************************************/ int mmx() const { return _mmx; } /** * The one-dimensional index into \e C and \e S. * * @param[in] n the degree. * @param[in] m the order. * @return the one-dimensional index. **********************************************************************/ int index(int n, int m) const { return m * _Nx - m * (m - 1) / 2 + n; } /** * An element of \e C. * * @param[in] k the one-dimensional index. * @return the value of the \e C coefficient. **********************************************************************/ Math::real Cv(int k) const { return *(_Cnm + k); } /** * An element of \e S. * * @param[in] k the one-dimensional index. * @return the value of the \e S coefficient. **********************************************************************/ Math::real Sv(int k) const { return *(_Snm + (k - (_Nx + 1))); } /** * An element of \e C with checking. * * @param[in] k the one-dimensional index. * @param[in] n the requested degree. * @param[in] m the requested order. * @param[in] f a multiplier. * @return the value of the \e C coefficient multiplied by \e f in \e n * and \e m are in range else 0. **********************************************************************/ Math::real Cv(int k, int n, int m, real f) const { return m > _mmx || n > _nmx ? 0 : *(_Cnm + k) * f; } /** * An element of \e S with checking. * * @param[in] k the one-dimensional index. * @param[in] n the requested degree. * @param[in] m the requested order. * @param[in] f a multiplier. * @return the value of the \e S coefficient multiplied by \e f in \e n * and \e m are in range else 0. **********************************************************************/ Math::real Sv(int k, int n, int m, real f) const { return m > _mmx || n > _nmx ? 0 : *(_Snm + (k - (_Nx + 1))) * f; } /** * The size of the coefficient vector for the cosine terms. * * @param[in] N the maximum degree. * @param[in] M the maximum order. * @return the size of the vector of cosine terms as stored in column * major order. **********************************************************************/ static int Csize(int N, int M) { return (M + 1) * (2 * N - M + 2) / 2; } /** * The size of the coefficient vector for the sine terms. * * @param[in] N the maximum degree. * @param[in] M the maximum order. * @return the size of the vector of cosine terms as stored in column * major order. **********************************************************************/ static int Ssize(int N, int M) { return Csize(N, M) - (N + 1); } /** * Load coefficients from a binary stream. * * @param[in] stream the input stream. * @param[in,out] N The maximum degree of the coefficients. * @param[in,out] M The maximum order of the coefficients. * @param[out] C The vector of cosine coefficients. * @param[out] S The vector of sine coefficients. * @param[in] truncate if false (the default) then \e N and \e M are * determined by the values in the binary stream; otherwise, the input * values of \e N and \e M are used to truncate the coefficients read * from the stream at the given degree and order. * @exception GeographicErr if \e N and \e M do not satisfy \e N ≥ * \e M ≥ −1. * @exception GeographicErr if there's an error reading the data. * @exception std::bad_alloc if the memory for \e C or \e S can't be * allocated. * * \e N and \e M are read as 4-byte ints. \e C and \e S are resized to * accommodate all the coefficients (with the \e m = 0 coefficients for * \e S excluded) and the data for these coefficients read as 8-byte * doubles. The coefficients are stored in column major order. The * bytes in the stream should use little-endian ordering. IEEE floating * point is assumed for the coefficients. **********************************************************************/ static void readcoeffs(std::istream& stream, int& N, int& M, std::vector& C, std::vector& S, bool truncate = false); }; /** * Evaluate a spherical harmonic sum and its gradient. * * @tparam gradp should the gradient be calculated. * @tparam norm the normalization for the associated Legendre polynomials. * @tparam L the number of terms in the coefficients. * @param[in] c an array of coeff objects. * @param[in] f array of coefficient multipliers. f[0] should be 1. * @param[in] x the \e x component of the cartesian position. * @param[in] y the \e y component of the cartesian position. * @param[in] z the \e z component of the cartesian position. * @param[in] a the normalizing radius. * @param[out] gradx the \e x component of the gradient. * @param[out] grady the \e y component of the gradient. * @param[out] gradz the \e z component of the gradient. * @result the spherical harmonic sum. * * See the SphericalHarmonic class for the definition of the sum. The * coefficients used by this function are, for example, c[0].Cv + f[1] * * c[1].Cv + ... + f[L−1] * c[L−1].Cv. (Note that f[0] is \e * not used.) The upper limits on the sum are determined by c[0].nmx() and * c[0].mmx(); these limits apply to \e all the components of the * coefficients. The parameters \e gradp, \e norm, and \e L are template * parameters, to allow more optimization to be done at compile time. * * Clenshaw summation is used which permits the evaluation of the sum * without the need to allocate temporary arrays. Thus this function never * throws an exception. **********************************************************************/ template static Math::real Value(const coeff c[], const real f[], real x, real y, real z, real a, real& gradx, real& grady, real& gradz); /** * Create a CircularEngine object * * @tparam gradp should the gradient be calculated. * @tparam norm the normalization for the associated Legendre polynomials. * @tparam L the number of terms in the coefficients. * @param[in] c an array of coeff objects. * @param[in] f array of coefficient multipliers. f[0] should be 1. * @param[in] p the radius of the circle = sqrt(x2 + * y2). * @param[in] z the height of the circle. * @param[in] a the normalizing radius. * @exception std::bad_alloc if the memory for the CircularEngine can't be * allocated. * @result the CircularEngine object. * * If you need to evaluate the spherical harmonic sum for several points * with constant \e f, \e p = sqrt(x2 + * y2), \e z, and \e a, it is more efficient to construct * call SphericalEngine::Circle to give a CircularEngine object and then * call CircularEngine::operator()() with arguments x/\e p and * y/\e p. **********************************************************************/ template static CircularEngine Circle(const coeff c[], const real f[], real p, real z, real a); /** * Check that the static table of square roots is big enough and enlarge it * if necessary. * * @param[in] N the maximum degree to be used in SphericalEngine. * @exception std::bad_alloc if the memory for the square root table can't * be allocated. * * Typically, there's no need for an end-user to call this routine, because * the constructors for SphericalEngine::coeff do so. However, since this * updates a static table, there's a possible race condition in a * multi-threaded environment. Because this routine does nothing if the * table is already large enough, one way to avoid race conditions is to * call this routine at program start up (when it's still single threaded), * supplying the largest degree that your program will use. E.g., \code GeographicLib::SphericalEngine::RootTable(2190); \endcode * suffices to accommodate extant magnetic and gravity models. **********************************************************************/ static void RootTable(int N); /** * Clear the static table of square roots and release the memory. Call * this only when you are sure you no longer will be using SphericalEngine. * Your program will crash if you call SphericalEngine after calling this * routine. * * \warning It's safest not to call this routine at all. (The space used * by the table is modest.) **********************************************************************/ static void ClearRootTable() { std::vector temp(0); sqrttable().swap(temp); } }; } // namespace GeographicLib #if defined(_MSC_VER) # pragma warning (pop) #endif #endif // GEOGRAPHICLIB_SPHERICALENGINE_HPP