SpECTRE Documentation Coverage Report
Current view: top level - NumericalAlgorithms/SpinWeightedSphericalHarmonics - SwshInterpolation.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 35.1 % 37 13
Test Date: 2026-10-22 17:29:42
Legend: Lines:     hit not hit

            Line data    Source code
       1            0 : // Distributed under the MIT License.
       2              : // See LICENSE.txt for details.
       3              : 
       4              : #pragma once
       5              : 
       6              : #include <complex>
       7              : #include <cstddef>
       8              : #include <vector>
       9              : 
      10              : #include "DataStructures/ComplexModalVector.hpp"
      11              : #include "DataStructures/DataVector.hpp"
      12              : #include "DataStructures/SpinWeighted.hpp"
      13              : #include "Utilities/Gsl.hpp"
      14              : 
      15              : /// \cond
      16              : class ComplexDataVector;
      17              : namespace PUP {
      18              : class er;
      19              : }  // namespace PUP
      20              : /// \endcond
      21              : 
      22              : namespace Spectral {
      23              : namespace Swsh {
      24              : 
      25              : /// \ingroup SwshGroup
      26              : /// A utility for evaluating a particular spin-weighted spherical harmonic
      27              : /// function at arbitrary points.
      28              : ///
      29              : /// \warning This should NOT be used for interpolation; such an evaluation
      30              : /// strategy is hopelessly slow compared to Clenshaw recurrence strategies.
      31              : /// Instead use `SwshInterpolator`.
      32            1 : class SpinWeightedSphericalHarmonic {
      33              :  public:
      34              :   // charm needs an empty constructor
      35            0 :   SpinWeightedSphericalHarmonic() = default;
      36              : 
      37            0 :   SpinWeightedSphericalHarmonic(int spin, size_t l, int m);
      38              : 
      39              :   /*!
      40              :    *  Return by pointer the values of the spin-weighted spherical harmonic
      41              :    *  evaluated at `theta` and `phi`.
      42              :    *
      43              :    *  \details The additional values `sin_theta_over_2` and `cos_theta_over_2`,
      44              :    *  representing \f$\sin(\theta/2)\f$ and \f$\cos(\theta/2)\f$ are taken as
      45              :    *  required input to improve the speed of evaluation when called more than
      46              :    *  once.
      47              :    *
      48              :    *  The formula we evaluate (with various prefactors precomputed, cached, and
      49              :    *  optimized from the factorials) is \cite Goldberg1966uu
      50              :    *
      51              :    *  \f{align*}{
      52              :    *  {}_s Y_{l m} = (-1)^m \sqrt{\frac{(l + m)! (l-m)! (2l + 1)}
      53              :    *  {4 \pi (l + s)! (l - s)!}} \sin^{2 l}(\theta / 2)
      54              :    *   \sum_{r = 0}^{l - s} {l - s \choose r} {l + s \choose r + s - m}
      55              :    *  (-1)^{l - r - s} e^{i m \phi} \cot^{2 r + s - m}(\theta / 2).
      56              :    *  \f}
      57              :    */
      58            1 :   void evaluate(gsl::not_null<ComplexDataVector*> result,
      59              :                 const DataVector& theta, const DataVector& phi,
      60              :                 const DataVector& sin_theta_over_2,
      61              :                 const DataVector& cos_theta_over_2) const;
      62              : 
      63              :   /// Return by value the spin-weighted spherical harmonic evaluated at `theta`
      64              :   /// and `phi`.
      65              :   ///
      66              :   /// \details The additional values `sin_theta_over_2` and `cos_theta_over_2`,
      67              :   /// representing \f$\sin(\theta/2)\f$ and \f$\cos(\theta/2)\f$ are taken as
      68              :   /// required input to improve the speed of evaluation when called more than
      69              :   /// once.
      70            1 :   ComplexDataVector evaluate(const DataVector& theta, const DataVector& phi,
      71              :                              const DataVector& sin_theta_over_2,
      72              :                              const DataVector& cos_theta_over_2) const;
      73              : 
      74              :   /// Return by value the spin-weighted spherical harmonic evaluated at `theta`
      75              :   /// and `phi`.
      76            1 :   std::complex<double> evaluate(double theta, double phi) const;
      77              : 
      78              :   /// Serialization for Charm++.
      79              :   // NOLINTNEXTLINE(google-runtime-references)
      80            1 :   void pup(PUP::er& p);
      81              : 
      82              :  private:
      83            0 :   int spin_ = 0;
      84            0 :   size_t l_ = 0;
      85            0 :   int m_ = 0;
      86            0 :   double overall_prefactor_ = 0.0;
      87            0 :   std::vector<double> r_prefactors_;
      88              : };
      89              : 
      90              : /*!
      91              :  * \ingroup SwshGroup
      92              :  * \brief Performs interpolation for spin-weighted spherical harmonics by
      93              :  * taking advantage of the Clenshaw method of expanding recurrence relations.
      94              :  *
      95              :  * \details During construction, we cache several functions of the target
      96              :  * interpolation points that will be used during the Clenshaw evaluation.
      97              :  * A new `SwshInterpolator` object must be created for each new set of target
      98              :  * points, but the member function `SwshInterpolator::interpolate()` may be
      99              :  * called on several different coefficients or collocation sets, and of
     100              :  * different spin-weights.
     101              :  *
     102              :  * Recurrence constants
     103              :  * --------------------
     104              :  * This utility obtains the Clenshaw interpolation constants from a
     105              :  * `StaticCache`, so that 'universal' quantities can be calculated only once per
     106              :  * execution and re-used on each interpolation.
     107              :  *
     108              :  * We evaluate the recurrence coefficients \f$\alpha_l^{(a,b)}\f$ and
     109              :  * \f$\beta_l^{(a,b)}\f$, where \f$a = |s + m|\f$, \f$b = |s - m|\f$, and
     110              :  *
     111              :  * \f[
     112              :  * {}_sY_{l, m}(\theta, \phi) = \alpha_l^{(a,b)}(\theta) {}_s Y_{l-1, m}
     113              :  * +  \beta_l^{(a, b)} {}_s Y_{l - 2, m}(\theta, \phi)
     114              :  * \f]
     115              :  *
     116              :  * The core Clenshaw recurrence is in the\f$l\f$ modes of the
     117              :  * spin-weighted spherical harmonic. For a set of modes \f$a_l^{(a,b)}\f$ at
     118              :  * fixed \f$m\f$, the function value is evaluated by the recurrence:
     119              :  *
     120              :  * \f{align*}{
     121              :  * y^{(a, b)}_{l_\text{max} + 2} &= y^{(a, b)}_{l_\text{max} + 1} = 0 \\
     122              :  * y^{(a, b)}_{l}(\theta) &= \alpha_{l + 1}^{(a, b)} y^{(a, b)}_{l +
     123              :  * 1}(\theta)
     124              :  * + \beta_{l + 2}^{(a,b)} y^{(a, b)}_{l + 2}(\theta) + a_l^{(a, b)} \\
     125              :  * f_m(\theta, \phi) &= \beta_{l_{\text{min}} + 2}
     126              :  * {}_s Y_{l_{\text{min}}, m}(\theta, \phi) y^{(a, b)}_2(\theta) +
     127              :  * {}_s Y_{l_{\text{min}} + 1, m}(\theta, \phi) y^{(a, b)}_1(\theta) +
     128              :  * a_0^{(a, b)} {}_s Y_{l_{\text{min}}, m}(\theta, \phi),
     129              :  * \f}
     130              :  *
     131              :  * where \f$l_{\text{min}} = \max(|m|, |s|)\f$ is the lowest nonvanishing
     132              :  * \f$l\f$ mode for a given \f$m\f$ and \f$s\f$.
     133              :  *
     134              :  * The coefficients to cache are inferred from a mechanical but lengthy
     135              :  * calculation involving the recurrence relation for the Jacobi polynomials.
     136              :  * The result is:
     137              :  *
     138              :  * \f{align*}{
     139              :  * \alpha_l^{(a, b)} &= \frac{\sqrt{(2l + 1)(2l - 1)}}
     140              :  * {2\sqrt{(l + k)(l + k + a + b)(l + k + a)(l + k + b)}}
     141              :  * \left[2 l \cos(\theta) +
     142              :  * \frac{a^2 - b^2}{2 l - 2}\right] \\
     143              :  * \beta_l^{(a, b)} & = - \sqrt{\frac{(2 l + 1)(l + k + a - 1)(l + k + b - 1)
     144              :  * (l + k - 1)(l + k + a + b - 1)}
     145              :  * {(2l - 3)(l + k)(l + k + a + b)(l + k + a)(l + k + b)}}
     146              :  * \frac{2 l}{2 l - 2},
     147              :  * \f}
     148              :  * where \f$k = - (a + b)/2\f$ (which is always integral due to the properties
     149              :  * of \f$a\f$ and \f$b\f$). Note that because the values of \f$\alpha\f$ and
     150              :  * \f$\beta\f$ in the recurrence relation are not needed for any value below
     151              :  * \f$l_{\text{min}} + 2\f$, so none of the values in the square-roots or
     152              :  * denominators take pathological values for any of the coefficients we require.
     153              :  *
     154              :  * The \f$\beta\f$ constants are filled in member variable `beta_constant`. The
     155              :  * \f$\alpha\f$ values are separately stored as the prefactor for the
     156              :  * \f$\cos(\theta)\f$ term and the constant term in `alpha_prefactor` and
     157              :  * `alpha_constant` member variables.
     158              :  *
     159              :  * In addition, it is efficient to cache recurrence coefficients necessary for
     160              :  * generating the first couple of spin-weighted spherical harmonic functions for
     161              :  * each \f$m\f$ used in the Clenshaw sum.
     162              :  *
     163              :  * The member variable `harmonic_at_l_min_prefactors` holds the prefactors for
     164              :  * directly evaluating the harmonics at \f$s >= m\f$,
     165              :  *
     166              :  * \f{align*}{
     167              :  * {}_s Y_{|s| m}  = {}_s C_{m} e^{i m \phi} \sin^a(\theta/2) \cos^b(\theta/2),
     168              :  * \f}
     169              :  *
     170              :  * where \f${}_s C_m\f$ are the cached prefactors that take the values
     171              :  *
     172              :  * \f{align*}{
     173              :  * {}_s C_m = (-1)^{m + \lambda(m)} \sqrt{\frac{(2 |s| + 1) (|s| + k)!}
     174              :  * {4 \pi (|s| + k + a)! (|s| + k + b)!}}
     175              :  * \f}
     176              :  *
     177              :  * and
     178              :  *
     179              :  * \f{align*}{
     180              :  * \lambda(m) =
     181              :  * \left\{
     182              :  * \begin{array}{ll}
     183              :  * 0 &\text{ for } s \ge -m\\
     184              :  * s + m &\text{ for } s < -m
     185              :  * \end{array} \right..
     186              :  * \f}
     187              :  *
     188              :  * The member variable `harmonic_m_recurrence_prefactors` holds the prefactors
     189              :  * necessary to evaluate the lowest harmonics for each \f$m\f$ from the next
     190              :  * lowest-in-magnitude \f$m\f$, allowing most leading harmonics to recursively
     191              :  * derived.
     192              :  *
     193              :  * \f{align*}{
     194              :  * {}_s Y_{|m|, m} = {}_s D_m  \sin(\theta / 2) \cos(\theta / 2)
     195              :  * \left\{
     196              :  * \begin{array}{ll}
     197              :  * e^{i \phi} {}_s Y_{|m| - 1, m - 1} &\text{ for } m > 0\\
     198              :  * e^{-i \phi} {}_s Y_{|m| - 1, m + 1} &\text{ for } m < 0
     199              :  * \end{array}\right.,
     200              :  * \f}
     201              :  *
     202              :  * where \f${}_s D_m\f$ are the cached prefactors that take the values
     203              :  *
     204              :  * \f{align*}{
     205              :  * {}_s D_m = -(-1)^{\Delta \lambda(m)} \sqrt{
     206              :  * \frac{(2 |m| + 1)(k + |m| + a + b - 1)(k + |m| + a + b)}
     207              :  * {(2 |m| - 1)(k + |m| + a)(k + |m| + b)}},
     208              :  * \f}
     209              :  * and \f$\Delta \lambda(m)\f$ is the difference in \f$\lambda(m)\f$ between
     210              :  * the harmonic on the left-hand side and right-hand side of the above
     211              :  * recurrence relation, that is \f$\lambda(m) - \lambda(m - 1)\f$ for positive
     212              :  * \f$m\f$ and \f$\lambda(m) - \lambda(m + 1)\f$ for negative \f$m\f$.
     213              :  *
     214              :  * Finally, the member variable
     215              :  * `harmonic_at_l_min_plus_one_recurrence_prefactors` holds the prefactors
     216              :  * necessary to evaluate parts of the recurrence relations from the lowest
     217              :  * \f$l\f$ for a given \f$m\f$ to the next-to-lowest \f$l\f$.
     218              :  *
     219              :  * \f{align*}{
     220              :  *   {}_s Y_{l_{\min} + 1, m} = {}_s E_m
     221              :  * \left[(a + 1) + (a + b + 2) \frac{(\cos(\theta) - 1)}{2}\right]
     222              :  * {}_s Y_{l_{\min}, m}.
     223              :  * \f}
     224              :  *
     225              :  * where \f${}_s E_m\f$ are the cached prefactors that take the values
     226              :  *
     227              :  * \f{align*}{
     228              :  * {}_s E_{m} = \sqrt{\frac{2 l_{\min} + 3}{ 2 l_{\min} + 1}}
     229              :  * \sqrt{\frac{(l_{\min} + k + 1)(l_{\min} + k + a + b + 1)}
     230              :  * {(l_{\min} + k + a + 1)(l_{\min} + k + b + 1)}}
     231              :  * \f}
     232              :  */
     233            1 : class SwshInterpolator {
     234              :  public:
     235              :   // charm needs the empty constructor
     236            0 :   SwshInterpolator() = default;
     237              : 
     238            0 :   SwshInterpolator(const SwshInterpolator&) = default;
     239            0 :   SwshInterpolator(SwshInterpolator&&) = default;
     240            0 :   SwshInterpolator& operator=(const SwshInterpolator&) = default;
     241            0 :   SwshInterpolator& operator=(SwshInterpolator&&) = default;
     242            0 :   ~SwshInterpolator() = default;
     243              : 
     244            0 :   SwshInterpolator(const DataVector& theta, const DataVector& phi,
     245              :                    size_t l_max);
     246              : 
     247              :   /*!
     248              :    * \brief Perform the Clenshaw recurrence sum, returning by pointer
     249              :    * `interpolated` of interpolating the `goldberg_modes` at the collocation
     250              :    * points passed to the constructor.
     251              :    *
     252              :    * \details The core Clenshaw recurrence is in the\f$l\f$ modes of the
     253              :    * spin-weighted spherical harmonic. For a set of modes \f$a_l^{(a,b)}\f$ at
     254              :    * fixed \f$m\f$, the function value is evaluated by the recurrence:
     255              :    *
     256              :    * \f{align*}{
     257              :    * y^{(a, b)}_{l_\text{max} + 2} &= y^{(a, b)}_{l_\text{max} + 1} = 0 \\
     258              :    * y^{(a, b)}_{l}(\theta) &= \alpha_{l + 1}^{(a, b)} y^{(a, b)}_{l +
     259              :    * 1}(\theta)
     260              :    * + \beta_{l + 2}^{(a,b)} y^{(a, b)}_{l + 2}(\theta) + a_l^{(a, b)} \\
     261              :    * f_m(\theta, \phi) &= \beta_{l_{\text{min}} + 2}
     262              :    * {}_s Y_{l_{\text{min}}, m}(\theta, \phi) y^{(a, b)}_2(\theta) +
     263              :    * {}_s Y_{l_{\text{min}} + 1, m}(\theta, \phi) y^{(a, b)}_1(\theta) +
     264              :    * a_0^{(a, b)} {}_s Y_{l_{\text{min}}, m}(\theta, \phi)
     265              :    * \f}
     266              :    *
     267              :    * The recurrence in \f$l\f$ accomplishes much of the work, but for full
     268              :    * efficiency, we also recursively evaluate the lowest handful of \f$l\f$s for
     269              :    * each \f$m\f$. The details of those additional recurrence tricks can be
     270              :    * found in the documentation for `ClenshawRecurrenceConstants`.
     271              :    */
     272              :   template <int Spin>
     273            1 :   void interpolate(
     274              :       gsl::not_null<SpinWeighted<ComplexDataVector, Spin>*> interpolated,
     275              :       const SpinWeighted<ComplexModalVector, Spin>& goldberg_modes) const;
     276              : 
     277              :   /*!
     278              :    * \brief Perform the Clenshaw recurrence sum, returning by pointer
     279              :    * `interpolated` of interpolating function represented by
     280              :    * `libsharp_collocation` at the target points passed to the constructor.
     281              :    *
     282              :    * \details The core Clenshaw recurrence is in the\f$l\f$ modes of the
     283              :    * spin-weighted spherical harmonic. For a set of modes \f$a_l^{(a,b)}\f$ at
     284              :    * fixed \f$m\f$, the function value is evaluated by the recurrence:
     285              :    *
     286              :    * \f{align*}{
     287              :    * y^{(a, b)}_{l_\text{max} + 2} &= y^{(a, b)}_{l_\text{max} + 1} = 0 \\
     288              :    * y^{(a, b)}_{l}(\theta) &= \alpha_{l + 1}^{(a, b)} y^{(a, b)}_{l +
     289              :    * 1}(\theta)
     290              :    * + \beta_{l + 2}^{(a,b)} y^{(a, b)}_{l + 2}(\theta) + a_l^{(a, b)} \\
     291              :    * f_m(\theta, \phi) &= \beta_{l_{\text{min}} + 2}
     292              :    * {}_s Y_{l_{\text{min}}, m}(\theta, \phi) y^{(a, b)}_2(\theta) +
     293              :    * {}_s Y_{l_{\text{min}} + 1, m}(\theta, \phi) y^{(a, b)}_1(\theta) +
     294              :    * a_0^{(a, b)} {}_s Y_{l_{\text{min}}, m}(\theta, \phi)
     295              :    * \f}
     296              :    *
     297              :    * The recurrence in \f$l\f$ accomplishes much of the work, but for full
     298              :    * efficiency, we also recursively evaluate the lowest handful of \f$l\f$s for
     299              :    * each \f$m\f$. The details of those additional recurrence tricks can be
     300              :    * found in the documentation for `ClenshawRecurrenceConstants`.
     301              :    */
     302              :   template <int Spin>
     303            1 :   void interpolate(
     304              :       gsl::not_null<SpinWeighted<ComplexDataVector, Spin>*> interpolated,
     305              :       const SpinWeighted<ComplexDataVector, Spin>& libsharp_collocation) const;
     306              : 
     307              :   /// \brief Evaluate the SWSH function at the lowest \f$l\f$ value for a given
     308              :   /// \f$m\f$ at the target interpolation points.
     309              :   ///
     310              :   /// \details Included in the public interface for thorough testing, most use
     311              :   /// cases should just use the `SwshInterpolator::interpolate()` member
     312              :   /// function.
     313              :   template <int Spin>
     314            1 :   void direct_evaluation_swsh_at_l_min(
     315              :       gsl::not_null<SpinWeighted<ComplexDataVector, Spin>*> harmonic,
     316              :       int m) const;
     317              : 
     318              :   /// \brief Evaluate the SWSH function at the next-to-lowest \f$l\f$ value for
     319              :   /// a given \f$m\f$ at the target interpolation points, given input harmonic
     320              :   /// values for the lowest \f$l\f$ value.
     321              :   ///
     322              :   /// \details Included in the public interface for thorough testing, most use
     323              :   /// cases should just use the `SwshInterpolator::interpolate()` member
     324              :   /// function.
     325              :   template <int Spin>
     326            1 :   void evaluate_swsh_at_l_min_plus_one(
     327              :       gsl::not_null<SpinWeighted<ComplexDataVector, Spin>*> harmonic,
     328              :       const SpinWeighted<ComplexDataVector, Spin>& harmonic_at_l_min,
     329              :       int m) const;
     330              : 
     331              :   /// \brief Evaluate the SWSH function at the lowest \f$l\f$ value for a given
     332              :   /// \f$m\f$ at the target interpolation points, given harmonic data at the
     333              :   /// next lower \f$m\f$ (by magnitude), passed in by the same pointer used for
     334              :   /// the return.
     335              :   ///
     336              :   /// \details Included in the public interface for thorough testing, most use
     337              :   /// cases should just use the `SwshInterpolator::interpolate()` member
     338              :   /// function.
     339              :   template <int Spin>
     340            1 :   void evaluate_swsh_m_recurrence_at_l_min(
     341              :       gsl::not_null<SpinWeighted<ComplexDataVector, Spin>*> harmonic,
     342              :       int m) const;
     343              : 
     344              :   /// \brief Perform the core Clenshaw interpolation at fixed \f$m\f$,
     345              :   /// accumulating the result in `interpolation`.
     346              :   ///
     347              :   /// \details Included in the public interface for thorough testing, most use
     348              :   /// cases should just use the `interpolate` member function.
     349              :   template <int Spin>
     350            1 :   void clenshaw_sum(
     351              :       gsl::not_null<SpinWeighted<ComplexDataVector, Spin>*> interpolation,
     352              :       const SpinWeighted<ComplexDataVector, Spin>& l_min_harmonic,
     353              :       const SpinWeighted<ComplexDataVector, Spin>& l_min_plus_one_harmonic,
     354              :       const SpinWeighted<ComplexModalVector, Spin>& goldberg_modes,
     355              :       int m) const;
     356              : 
     357              :   /// Serialization for Charm++.
     358              :   // NOLINTNEXTLINE(google-runtime-references)
     359            1 :   void pup(PUP::er& p);
     360              : 
     361              :  private:
     362            0 :   size_t l_max_ = 0;
     363            0 :   DataVector cos_theta_;
     364            0 :   DataVector sin_theta_;
     365            0 :   DataVector cos_theta_over_two_;
     366            0 :   DataVector sin_theta_over_two_;
     367            0 :   std::vector<DataVector> sin_m_phi_;
     368            0 :   std::vector<DataVector> cos_m_phi_;
     369              :   // NOLINTNEXTLINE(spectre-mutable)
     370            0 :   mutable ComplexModalVector raw_libsharp_coefficient_buffer_;
     371              :   // NOLINTNEXTLINE(spectre-mutable)
     372            0 :   mutable ComplexModalVector raw_goldberg_coefficient_buffer_;
     373              : };
     374              : }  // namespace Swsh
     375              : }  // namespace Spectral
        

Generated by: LCOV version 2.0-1