SpECTRE Documentation Coverage Report
Current view: top level - Domain/CoordinateMaps/TimeDependent - Shape.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 4.8 % 42 2
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 <cstddef>
       7              : #include <limits>
       8              : #include <memory>
       9              : #include <optional>
      10              : #include <string>
      11              : #include <unordered_set>
      12              : 
      13              : #include "DataStructures/DataVector.hpp"
      14              : #include "DataStructures/Tensor/Tensor.hpp"
      15              : #include "Domain/CoordinateMaps/TimeDependent/ShapeMapTransitionFunctions/ShapeMapTransitionFunction.hpp"
      16              : #include "NumericalAlgorithms/SphericalHarmonics/Spherepack.hpp"
      17              : #include "NumericalAlgorithms/SphericalHarmonics/SpherepackIterator.hpp"
      18              : #include "Parallel/FifoCache.hpp"
      19              : #include "Utilities/Gsl.hpp"
      20              : 
      21              : /// \cond
      22              : namespace domain::FunctionsOfTime {
      23              : class FunctionOfTime;
      24              : }  // namespace domain::FunctionsOfTime
      25              : namespace PUP {
      26              : class er;
      27              : }  // namespace PUP
      28              : /// \endcond
      29              : 
      30              : namespace domain::CoordinateMaps::TimeDependent {
      31              : 
      32            0 : size_t lmax_from_coefs(const DataVector& coefs);
      33              : 
      34            0 : DataVector truncate_coefs(const DataVector& coefs,
      35              :                           ylm::SpherepackIterator iterator,
      36              :                           ylm::SpherepackIterator truncated_iterator);
      37              : 
      38              : /*!
      39              :  * \ingroup CoordMapsTimeDependentGroup
      40              :  * \brief Distorts a distribution of points radially according to a spherical
      41              :  * harmonic expansion while preserving angles.
      42              :  *
      43              :  * \details The shape map distorts the distance \f$r\f$ between a point and
      44              :  * the center while leaving the angles \f$\theta\f$, \f$\phi\f$ between them
      45              :  * preserved by applying a spherical harmonic expansion with time-dependent
      46              :  * coefficients \f$\lambda_{lm}(t)\f$. There are two ways to specify the
      47              :  * time-dependent coefficients \f$\lambda_{lm}(t)\f$:
      48              :  *
      49              :  * 1. A single FunctionOfTime which specifies all coefficients. This
      50              :  *    FunctionOfTime should have `ylm::Spherepack::spectral_size()` number of
      51              :  *    components. These are in Spherepack order and should be the Spherepack
      52              :  *    coefficients, *not* the spherical harmonic coefficients. See the note
      53              :  *    below. To use this, set the `size_function_of_time_name` argument of the
      54              :  *    constructor to `std::nullopt`.
      55              :  * 2. Two different FunctionOfTime%s. The first is similar to 1.) in that it
      56              :  *    should have the same number of components, be in Spherepack order, and be
      57              :  *    the Spherepack coefficients. The only difference is that the \f$l = 0\f$
      58              :  *    coefficient should be identically 0. The second FunctionOfTime should have
      59              :  *    a single component which will be the \f$l = 0\f$ coefficient. This
      60              :  *    component should be stored as the spherical harmonic coefficient and *not*
      61              :  *    a Spherepack coefficient. See the note below. To use this method, set the
      62              :  *    `size_function_of_time_name` argument of the constructor to the name of
      63              :  *    the FunctionOfTime that's in the cache. This method is useful if we have
      64              :  *    control systems because we have a separate control system controlling a
      65              :  *    separate function of time for the \f$l = 0\f$ coefficient than we do for
      66              :  *    the other coefficients.
      67              :  *
      68              :  * \note The quantities stored in the "shape" FunctionOfTime (the
      69              :  * `shape_function_of_time_name` argument in the constructor that must always be
      70              :  * specified) are ***not*** the complex spherical-harmonic coefficients
      71              :  * \f$\lambda_{lm}(t)\f$, but instead are the real-valued SPHEREPACK
      72              :  * coefficients \f$a_{lm}(t)\f$ and \f$b_{lm}(t)\f$ used by Spherepack. This
      73              :  * is the same for both methods of specifying FunctionOfTime%s above. The
      74              :  * relationship between these two sets of coefficients is
      75              :  * \f{align}
      76              :  * a_{l0} & = \sqrt{\frac{2}{\pi}}\lambda_{l0}&\qquad l\geq 0,\\
      77              :  * a_{lm} & = (-1)^m\sqrt{\frac{2}{\pi}} \mathrm{Re}(\lambda_{lm})
      78              :  * &\qquad l\geq 1, m\geq 1, \\
      79              :  * b_{lm} & = (-1)^m\sqrt{\frac{2}{\pi}} \mathrm{Im}(\lambda_{lm})
      80              :  * &\qquad l\geq 1, m\geq 1.
      81              :  * \f}
      82              :  * The "shape" FunctionOfTime stores coefficients only for non-negative \f$m\f$;
      83              :  * this is because the function we are expanding is real, so the
      84              :  * coefficients for \f$m<0\f$ can be obtained from \f$m>0\f$ coefficients by
      85              :  * complex conjugation.
      86              :  * If the `size_function_of_time_name` argument is given to the constructor,
      87              :  * then it is asserted that the \f$l=0\f$ coefficient of the "shape" function of
      88              :  * time is exactly 0. The \f$l=0\f$ coefficient is then controlled by the "size"
      89              :  * FunctionOfTime. Unlike the "shape" FunctionOfTime, the quantity in the
      90              :  * "size" FunctionOfTime ***is*** the "complex" spherical harmonic coefficient
      91              :  * \f$\lambda_{00}(t)\f$, and not the SPHEREPACK coefficient \f$a_{00}(t)\f$
      92              :  * ("complex" is in quotes because all \f$m=0\f$ coefficients are always real.)
      93              :  * Here and below we write the equations in terms of \f$\lambda_{lm}(t)\f$
      94              :  * instead of \f$a_{lm}(t)\f$ and \f$b_{lm}(t)\f$, regardless of which
      95              :  * FunctionOfTime representation we are using, because the resulting expressions
      96              :  * are much shorter.
      97              :  *
      98              :  * \parblock
      99              :  *
     100              :  * \note Also note that the FunctionOfTime coefficients $\lambda_{lm}(t)$ are
     101              :  * stored as *negative* of the coefficients you'd retrieve from a
     102              :  * `Strahlkorper`. This is because you would typically represent the expansion
     103              :  * of a strahlkorper as $S(r) = +\sum S_{lm} Y_{lm}$. However, in equation
     104              :  * $\ref{eq:map_form_2}$ there is a minus sign on the $\sum \lambda_{lm}
     105              :  * Y_{lm}$, not a plus sign. Therefore, $\lambda_{lm}(t)$ picks up an extra
     106              :  * factor of $-1$. This is purely a choice of convention.
     107              :  *
     108              :  * \endparblock
     109              :  *
     110              :  * An additional domain-dependent transition function
     111              :  *
     112              :  * \begin{equation}
     113              :  *     G(r,\theta,\phi) = \frac{f(r,\theta,\phi)}{r}
     114              :  * \end{equation}
     115              :  *
     116              :  * ensures that the distortion falls off correctly to zero at a certain boundary
     117              :  * (must be a block boundary). The dimensionless function \f$f(r, \theta,
     118              :  * \phi)\f$ is restricted such that
     119              :  *
     120              :  * \f{equation}{
     121              :  * 0 \leq f(r, \theta, \phi) \leq 1
     122              :  * \f}
     123              :  *
     124              :  * ### Mapped coordinates
     125              :  *
     126              :  * Given a point with cartesian coordinates \f$\xi^i\f$, let the polar
     127              :  * coordinates \f$(r, \theta, \phi)\f$ with respect to a center \f$x_c^i\f$ be
     128              :  * defined in the usual way:
     129              :  *
     130              :  * \f{align}{
     131              :  * \xi^0 - x_c^0 &= r \sin(\theta) \cos(\phi)\\
     132              :  * \xi^1 - x_c^1 &= r \sin(\theta) \sin(\phi)\\
     133              :  * \xi^2 - x_c^2 &= r \cos(\theta)
     134              :  * \f}
     135              :  *
     136              :  * The shape map maps the unmapped
     137              :  * coordinates \f$\xi^i\f$ to coordinates \f$x^i\f$:
     138              :  *
     139              :  * \f{equation}{\label{eq:map_form_1}
     140              :  * x^i = \xi^i - (\xi^i - x_c^i) G(r,\theta,\phi) \sum_{lm}
     141              :  * \lambda_{lm}(t)Y_{lm}(\theta, \phi).
     142              :  * \f}
     143              :  *
     144              :  * Or written another way
     145              :  *
     146              :  * \f{equation}{\label{eq:map_form_2}
     147              :  * x^i = x_c^i + (\xi^i - x_c^i) \left(1 - G(r,\theta,\phi)
     148              :  * \sum_{lm} \lambda_{lm}(t)Y_{lm}(\theta, \phi)\right).
     149              :  * \f}
     150              :  *
     151              :  * The form in Eq. \f$\ref{eq:map_form_2}\f$ makes two things
     152              :  * clearer
     153              :  *
     154              :  * 1. This shape map is just a radial distortion about \f$x_c^i\f$
     155              :  * 2. The coefficients \f$\lambda_{lm}\f$ have units of distance because
     156              :  *    \f$\sum\lambda_{lm}(t)Y_{lm}(\theta,\phi) / r\f$ must be dimensionless
     157              :  *    (because \f$f\f$ is dimensionless).
     158              :  *
     159              :  * ### Inverse map
     160              :  *
     161              :  * The inverse map is given by:
     162              :  * \f{equation}{
     163              :  * \xi^i = x_c^i + (x^i-x_c^i)*(r/\tilde{r}),
     164              :  * \f}
     165              :  * where \f$\tilde{r}\f$ is the radius of $\vec{x}$, calculated by the
     166              :  * transition map. In order to compute $r/\tilde{r}$, the following equation
     167              :  * must be solved
     168              :  *
     169              :  * \f{equation}{
     170              :  * \frac{r}{\tilde{r}} =
     171              :  * \frac{1}{1-G(r,\theta,\phi)\sum\lambda_{lm}(t)Y_{lm}(\theta,\phi)}
     172              :  * \f}
     173              :  *
     174              :  * For more details, see
     175              :  * \link domain::CoordinateMaps::ShapeMapTransitionFunctions::ShapeMapTransitionFunction::original_radius_over_radius
     176              :  * ShapeMapTransitionFunction::original_radius_over_radius \endlink.
     177              :  *
     178              :  * ### Frame velocity
     179              :  *
     180              :  * The frame velocity \f$v^i\ = dx^i / dt\f$ is calculated trivially:
     181              :  * \f{equation}{
     182              :  * v^i = - (\xi^i - x_c^i) G(r, \theta, \phi) \sum_{lm}
     183              :  * \dot{\lambda}_{lm}(t)Y_{lm}(\theta, \phi).
     184              :  * \f}
     185              :  *
     186              :  * ### Jacobian
     187              :  *
     188              :  * The Jacobian is given by:
     189              :  * \f{align}{
     190              :  * \frac{\partial x^i}{\partial \xi^j} = \delta_j^i &\left( 1 - G(r,\theta,\phi)
     191              :  * \sum_{lm} \lambda_{lm}(t)Y_{lm}(\theta, \phi)\right) \nonumber \\
     192              :  * &- (\xi^i - x_c^i)
     193              :  * \left[\frac{\partial G(r,\theta,\phi)}{\partial\xi^j} \sum_{lm}
     194              :  * \lambda_{lm}(t)Y_{lm}(\theta, \phi) + G(r, \theta, \phi)
     195              :  * \sum_{lm} \lambda_{lm}(t) \frac{\partial}{\partial \xi^j} Y_{lm}(\theta,
     196              :  * \phi) \right].
     197              :  * \f}
     198              :  *
     199              :  * where \f$\xi_j = \xi^j\f$. It should be noted that there is an additional
     200              :  * factor of $1/r$ hidden in the $\partial/\partial\xi^j Y_{lm}(\theta, \phi)$
     201              :  * term, so the transition function $G(r,\theta,\phi)$ must have a functional
     202              :  * form to avoid division by zero if $r=0$.
     203              :  *
     204              :  * ### Inverse Jacobian
     205              :  *
     206              :  * The inverse Jacobian is computed by numerically inverting the Jacobian.
     207              :  *
     208              :  * For future optimization, the `interpolation_info` objects calculated in all
     209              :  * functions of this class could be cached. Since every element should evaluate
     210              :  * the same grid coordinates most time steps, this might greatly decrease
     211              :  * computation. Every element has their own clone of the shape map so the
     212              :  * caching could be done with member variables. Care must be taken that
     213              :  * `jacobian` currently calculates the `interpolation_info` with an order
     214              :  * higher.
     215              :  *
     216              :  * \warning The Shape map uses a mutable spherepack cache that can be read and
     217              :  * mutated concurrently. It is up to the user to guarantee that
     218              :  * the cache is in a valid state while threads are calling the map methods.
     219              :  * Special care needs to be taken when using move and copy assignment.
     220              :  */
     221            1 : class Shape {
     222              :  public:
     223            0 :   using FunctionsOfTimeMap = std::unordered_map<
     224              :       std::string, std::unique_ptr<domain::FunctionsOfTime::FunctionOfTime>>;
     225              : 
     226            0 :   explicit Shape(
     227              :       const std::array<double, 3>& center, double truncation_limit,
     228              :       std::unique_ptr<ShapeMapTransitionFunctions::ShapeMapTransitionFunction>
     229              :           transition_func,
     230              :       std::string shape_function_of_time_name,
     231              :       std::optional<std::string> size_function_of_time_name = std::nullopt);
     232              : 
     233            0 :   Shape() = default;
     234            0 :   ~Shape() = default;
     235            0 :   Shape(const Shape& rhs);
     236            0 :   Shape& operator=(const Shape& rhs);
     237            0 :   Shape(Shape&& rhs);
     238            0 :   Shape& operator=(Shape&& rhs);
     239              : 
     240              :   template <typename T>
     241            0 :   std::array<T, 3> operator()(
     242              :       const std::array<T, 3>& source_coords, double time,
     243              :       const FunctionsOfTimeMap& functions_of_time) const;
     244              : 
     245            0 :   std::optional<std::array<double, 3>> inverse(
     246              :       const std::array<double, 3>& target_coords, double time,
     247              :       const FunctionsOfTimeMap& functions_of_time) const;
     248              : 
     249              :   template <typename T>
     250            0 :   std::array<T, 3> frame_velocity(
     251              :       const std::array<T, 3>& source_coords, double time,
     252              :       const FunctionsOfTimeMap& functions_of_time) const;
     253              : 
     254              :   template <typename T>
     255            0 :   tnsr::Ij<T, 3, Frame::NoFrame> jacobian(
     256              :       const std::array<T, 3>& source_coords, double time,
     257              :       const FunctionsOfTimeMap& functions_of_time) const;
     258              : 
     259              :   template <typename T>
     260            0 :   tnsr::Ij<T, 3, Frame::NoFrame> inv_jacobian(
     261              :       const std::array<T, 3>& source_coords, double time,
     262              :       const FunctionsOfTimeMap& functions_of_time) const;
     263              : 
     264              :   /*!
     265              :    * \brief An optimized call that computes the target coordinates, frame
     266              :    * velocity and jacobian at once to avoid duplicate calculations.
     267              :    *
     268              :    * \details The first argument `source_and_target_coords` should contain
     269              :    * the source coordinates and will be overwritten in place with the target
     270              :    * coordinates.
     271              :    */
     272            1 :   void coords_frame_velocity_jacobian(
     273              :       gsl::not_null<std::array<DataVector, 3>*> source_and_target_coords,
     274              :       gsl::not_null<std::array<DataVector, 3>*> frame_vel,
     275              :       gsl::not_null<tnsr::Ij<DataVector, 3, Frame::NoFrame>*> jac, double time,
     276              :       const FunctionsOfTimeMap& functions_of_time) const;
     277              : 
     278              :   // NOLINTNEXTLINE(google-runtime-references)
     279            0 :   void pup(PUP::er& p);
     280            0 :   static bool is_identity() { return false; }
     281            0 :   static constexpr bool supports_hessian{false};
     282            0 :   static constexpr size_t dim = 3;
     283              : 
     284            0 :   const std::unordered_set<std::string>& function_of_time_names() const {
     285              :     return f_of_t_names_;
     286              :   }
     287              : 
     288              :  private:
     289            0 :   std::string shape_f_of_t_name_;
     290            0 :   std::optional<std::string> size_f_of_t_name_;
     291            0 :   std::unordered_set<std::string> f_of_t_names_;
     292            0 :   std::array<double, 3> center_{};
     293            0 :   double truncation_limit_{0.};
     294              :   std::unique_ptr<ShapeMapTransitionFunctions::ShapeMapTransitionFunction>
     295            0 :       transition_func_;
     296              : 
     297            0 :   using SpherepackEntry = std::pair<ylm::SpherepackIterator, ylm::Spherepack>;
     298            0 :   using SpherepackCache = Parallel::FifoCache<std::unique_ptr<SpherepackEntry>>;
     299            0 :   using CachedItem = SpherepackCache::Cached;
     300            0 :   static constexpr size_t cache_capacity_ = 5;
     301              : 
     302              :   // NOLINTNEXTLINE(spectre-mutable)
     303            0 :   mutable SpherepackCache spherepack_cache_{cache_capacity_};
     304              : 
     305              :   template <typename T>
     306            0 :   std::array<T, 3> center_coordinates(const std::array<T, 3>& coords) const {
     307              :     return {coords[0] - center_[0], coords[1] - center_[1],
     308              :             coords[2] - center_[2]};
     309              :   }
     310              : 
     311              :   template <typename T>
     312            0 :   void center_coordinates(gsl::not_null<std::array<T, 3>*> result,
     313              :                           const std::array<T, 3>& coords) const {
     314              :     for (size_t i = 0; i < 3; ++i) {
     315              :       gsl::at(*result, i) = gsl::at(coords, i) - gsl::at(center_, i);
     316              :     }
     317              :   }
     318              : 
     319              :   template <typename T>
     320            0 :   void jacobian_helper(
     321              :       gsl::not_null<tnsr::Ij<T, 3, Frame::NoFrame>*> result,
     322              :       const ylm::Spherepack::InterpolationInfo<T>& interpolation_info,
     323              :       const DataVector& extended_coefs, const std::array<T, 3>& centered_coords,
     324              :       const T& radial_distortion, const T& transition_func,
     325              :       const ylm::Spherepack& ylm) const;
     326              : 
     327            0 :   void check_size(const gsl::not_null<DataVector*>& coefs,
     328              :                   const FunctionsOfTimeMap& functions_of_time, double time,
     329              :                   bool use_deriv) const;
     330              : 
     331            0 :   size_t find_truncated_l_max(const DataVector& coefs,
     332              :                               const DataVector& coef_derivs,
     333              :                               const DataVector& coef_dderivs,
     334              :                               ylm::SpherepackIterator iterator) const;
     335              : 
     336            0 :   CachedItem get_spherepack_cache_entry(size_t l_max) const;
     337              : 
     338            0 :   friend bool operator==(const Shape& lhs, const Shape& rhs);
     339              : };
     340            0 : bool operator!=(const Shape& lhs, const Shape& rhs);
     341              : 
     342              : }  // namespace domain::CoordinateMaps::TimeDependent
        

Generated by: LCOV version 2.0-1