SphericalEngine.hpp 16.7 KB
Newer Older
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
/**
 * \file SphericalEngine.hpp
 * \brief Header for GeographicLib::SphericalEngine class
 *
 * Copyright (c) Charles Karney (2011-2019) <charles@karney.com> 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 <vector>
#include <istream>
#include <GeographicLib/Constants.hpp>

#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<real>& 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<real>::radix),
                     -3 * (std::numeric_limits<real>::max_exponent < (1<<14) ?
                           std::numeric_limits<real>::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<real>::epsilon() *
        sqrt(std::numeric_limits<real>::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<real>::const_iterator _Cnm;
      std::vector<real>::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 &ge; \e nmx &ge; \e mmx &ge; &minus;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<real>& C,
            const std::vector<real>& 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 &ge; &minus;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<real>& C,
            const std::vector<real>& 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 &ge;
       *   \e M &ge; &minus;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<real>& C, std::vector<real>& 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&minus;1] * c[L&minus;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<bool gradp, normalization norm, int L>
      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(<i>x</i><sup>2</sup> +
     *   <i>y</i><sup>2</sup>).
     * @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(<i>x</i><sup>2</sup> +
     * <i>y</i><sup>2</sup>), \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 <i>x</i>/\e p and
     * <i>y</i>/\e p.
     **********************************************************************/
    template<bool gradp, normalization norm, int L>
      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<real> temp(0);
      sqrttable().swap(temp);
    }
  };

} // namespace GeographicLib

#if defined(_MSC_VER)
#  pragma warning (pop)
#endif

#endif  // GEOGRAPHICLIB_SPHERICALENGINE_HPP