SpECTRE Documentation Coverage Report
Current view: top level - Domain/CoordinateMaps/TimeDependent - Shape.hpp Hit Total Coverage
Commit: ecb8a275e1aebab77dcce48a5e098ed4e486ab4b Lines: 2 42 4.8 %
Date: 2026-08-22 01:05:40
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 1.14