SpECTRE Documentation Coverage Report
Current view: top level - Domain/CoordinateMaps/TimeDependent - Skew.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 8.0 % 25 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 <array>
       7              : #include <cstddef>
       8              : #include <memory>
       9              : #include <optional>
      10              : #include <string>
      11              : #include <unordered_map>
      12              : #include <unordered_set>
      13              : 
      14              : #include "DataStructures/Tensor/TypeAliases.hpp"
      15              : #include "Domain/FunctionsOfTime/FunctionOfTime.hpp"
      16              : 
      17              : /// \cond
      18              : namespace PUP {
      19              : class er;
      20              : }  // namespace PUP
      21              : /// \endcond
      22              : 
      23              : namespace domain::CoordinateMaps::TimeDependent {
      24              : /*!
      25              :  * \ingroup CoordMapsTimeDependentGroup
      26              :  * \brief %Time dependent coordinate map that keeps the $y$ and $z$ coordinates
      27              :  * unchanged, but will distort (or skew) the $x$ coordinate based on functions
      28              :  * of time and radial distance to the origin.
      29              :  *
      30              :  * \details This coordinate map is only available in 3 dimensions and is
      31              :  * intended to be used in the BinaryCompactObject domain.
      32              :  *
      33              :  * ### Mapped Coordinates
      34              :  * The Skew coordinate map is given by the mapping
      35              :  *
      36              :  * \begin{align}
      37              :  *   \label{eq:map}
      38              :  *   \bar{x} &= x - W(\vec{x})\left(\tan(F_y(t))(y-y_C) +
      39              :  *       \tan(F_z(t))(z-z_C)\right) \\
      40              :  *   \bar{y} &= y \\
      41              :  *   \bar{z} &= z
      42              :  * \end{align}
      43              :  *
      44              :  * where $\vec{x}_C = (x_C, y_C, z_C)$ is the \p center of the skew map (which
      45              :  * is different than the origin of the coordinate system), and $F_y(t)$ and
      46              :  * $F_z(t)$ are the angles within the $(x,y)$ plane between the undistorted
      47              :  * $x$-axis and the skewed $\bar{y}$-axis at the origin, represented by
      48              :  * `domain::FunctionsOfTime::FunctionOfTime`s. The actual function of time
      49              :  * should have two components; the first corresponds to $y$ and the second
      50              :  * corresponds to $z$.
      51              :  *
      52              :  * $W(\vec{x})$ is a spatial function that should be 1 at $\vec{x}_C$ (i.e.
      53              :  * maximally skewed between the two objects) and fall off to 0 at the \p
      54              :  * outer_radius, $R$. Typically the \p outer_radius should be set to the
      55              :  * envelope radius for the `domain::creators::BinaryCompactObject`. The reason
      56              :  * that $W(\vec{x})$ *should* be 1 at $\vec{x}_C$, and doesn't *need* to be 1 at
      57              :  * $\vec{x}_C$ is because having $W(\vec{x})$ centered at the origin is much
      58              :  * better for the smoothness of higher derivatives of $W(\vec{x})$ than if it
      59              :  * were centered at $\vec{x}_C$. The important part is that the map is left
      60              :  * invariant along the $x=x_C$ line and that $W(\vec{x}_C)\approx 1$ for the
      61              :  * skew control system, both of which are satisfied if $W(\vec{x})$ is centered
      62              :  * at the origin and $|\vec{x}_C|\ll R$.
      63              :  *
      64              :  * With that in mind, $W$ is chosen to be
      65              :  *
      66              :  * \begin{equation}
      67              :  *   \label{eq:W}
      68              :  *   W(\vec{x}) = \frac{1}{2}\left(1 + \cos(\pi\lambda(\vec{x}))\right).
      69              :  * \end{equation}
      70              :  *
      71              :  * When the $\cos$ term is $-1$, then $W = 0$, and when the $\cos$ term is 1,
      72              :  * then $W = 1$. Therefore, the function $\lambda(\vec{x})$ must go from 0 at
      73              :  * the origin, to 1 at $R$. We choose $\lambda(\vec{x})$ to quadratically go
      74              :  * from 0 at the origin to 1 at $R$ with the form
      75              :  *
      76              :  * \begin{equation}
      77              :  *   \label{eq:lambda}
      78              :  *   \lambda(\vec{x}) = \frac{|\vec{x}|^2}{R^2}.
      79              :  * \end{equation}
      80              :  *
      81              :  * A quadratic form was chosen for $\lambda$ rather than higher powers of 2 to
      82              :  * avoid needing too much resolution to resolve a steep falloff in the function.
      83              :  *
      84              :  * \note If the quantity $S = \tan(F_y(t))(y-y_C) + \tan(F_z(t))(z-z_C)$ becomes
      85              :  * too large, then the map becomes singular because multiple $x$ will be mapped
      86              :  * to the same $\bar{x}$. This is due to the fact we are adding a linear term to
      87              :  * a cosine term with different weights. If $S$ is too large, the cosine term
      88              :  * will overpower the linear and the map will become singular.
      89              :  *
      90              :  * ### Inverse
      91              :  * To find the inverse, we need to solve a 1D root find for the $x$ component of
      92              :  * the coordinate. The inverse of the $\bar{y}$ and $\bar{z}$ coordinates are
      93              :  * trivial because there was no mapping. The equation we need to find the root
      94              :  * of is
      95              :  *
      96              :  * \begin{equation}
      97              :  *   0 = x - W(\vec{x})\left(\tan(F_y(t))(y-y_C) + \tan(F_z(t))(z-z_C)\right) -
      98              :  *   \bar{x}
      99              :  * \end{equation}
     100              :  *
     101              :  * We can bound the root by noticing that in $\ref{eq:map}$, if we substitute
     102              :  * the extremal values of $W = 0$ and $W = 1$, we get bounds on $\bar{x}$ which
     103              :  * we can turn into bounds on $x$:
     104              :  *
     105              :  * \begin{align}
     106              :  *   x &<=& \bar{x} &<=& x - (\tan(F_y)(y-y_C) + \tan(F_z)(z-z_C)) \\
     107              :  *   0 &<=& \bar{x} - x &<=& -(\tan(F_y)(y-y_C) + \tan(F_z)(z-z_C)) \\
     108              :  *   -\bar{x} &<=& - x &<=& -\bar{x} - (\tan(F_y)(y-y_C) + \tan(F_z)(z-z_C)) \\
     109              :  *   \bar{x} &>=& x &>=& \bar{x} + (\tan(F_y)(y-y_C) + \tan(F_z)(z-z_C))
     110              :  * \end{align}
     111              :  *
     112              :  * where on each line we just made simple arithmetic operations. We pad each
     113              :  * bound by $10^{-14}$ just to avoid roundoff issues. If either of the bounds is
     114              :  * within roundoff of zero, the map is the identity at that point and we forgo
     115              :  * the root find. The root that is found is the original $x$ coordinate.
     116              :  *
     117              :  * ### Frame Velocity
     118              :  * Taking the time derivative of $\ref{eq:map}$, the frame velocity is
     119              :  *
     120              :  * \begin{align}
     121              :  *   \label{eq:frame_vel}
     122              :  *   \dot{\bar{x}} &= -W(\vec{x})\left(\dot{F}_y(t)(1 + \tan^2(F_y(t)))(y-y_C) +
     123              :  *     \dot{F}_z(t)(1 + \tan^2(F_z(t))(z-z_C))\right) \\
     124              :  *   \dot{\bar{y}} &= 0 \\
     125              :  *   \dot{\bar{z}} &= 0
     126              :  * \end{align}
     127              :  *
     128              :  * ### Jacobian and Inverse Jacobian
     129              :  * Considering the first terms in each equation of $\ref{eq:map}$, part of the
     130              :  * jacobian will be the identity matrix. The rest will come from only the $x$
     131              :  * equation. Therefore we can express the jacobian as
     132              :  *
     133              :  * \begin{equation}
     134              :  *   \frac{\partial\bar{x}^i}{\partial x^j} = \delta^i_j + {W^i}_j
     135              :  * \end{equation}
     136              :  *
     137              :  * where all components of ${W^i}_j$ are zero except the following
     138              :  *
     139              :  * \begin{align}
     140              :  *   {W^0}_0 &= \frac{\partial(\bar{x}-x)}{\partial x} &= -\frac{\partial
     141              :  *     W(\vec{x})}{\partial x}&\left(\tan(F_y(t))(y-y_C) +
     142              :  *     \tan(F_z(t))(z-z_C)\right), \\
     143              :  *   {W^0}_1 &= \frac{\partial(\bar{x}-x)}{\partial y} &= -\frac{\partial
     144              :  *     W(\vec{x})}{\partial y}&\left(\tan(F_y(t))(y-y_C) +
     145              :  *     \tan(F_z(t))(z-z_C)\right) -
     146              :  *     W\tan(F_y(t)), \\
     147              :  *   {W^0}_2 &= \frac{\partial(\bar{x}-x)}{\partial z} &= -\frac{\partial
     148              :  *     W(\vec{x})}{\partial z}&\left(\tan(F_y(t))(y-y_C) +
     149              :  *     \tan(F_z(t))(z-z_C)\right) - W\tan(F_z(t)).
     150              :  * \end{align}
     151              :  *
     152              :  * The gradient of $W(\vec{x})$ (Eq. $\ref{eq:W}$) is given by
     153              :  *
     154              :  * \begin{equation}
     155              :  *   \frac{\partial W(\vec{x})}{\partial x^i} =
     156              :  *   -\frac{\pi}{2}\frac{\partial\lambda(\vec{x})}{\partial
     157              :  *   x^i}\sin(\pi\lambda(\vec{x})).
     158              :  * \end{equation}
     159              :  *
     160              :  * The gradient of $\lambda(\vec{x})$ (Eq. $\ref{eq:lambda}$) is given by
     161              :  *
     162              :  * \begin{equation}
     163              :  *   \frac{\partial \lambda(\vec{x})}{\partial x^i} = \frac{2x^i}{R^2}
     164              :  * \end{equation}
     165              :  *
     166              :  * The inverse jacobian is computed by numerically inverting the jacobian.
     167              :  */
     168            1 : class Skew {
     169              :  public:
     170            0 :   static constexpr size_t dim = 3;
     171              : 
     172            0 :   Skew(std::string function_of_time_name, const std::array<double, 3>& center,
     173              :        double outer_radius);
     174            0 :   Skew() = default;
     175              : 
     176              :   template <typename T>
     177            0 :   std::array<T, 3> operator()(
     178              :       const std::array<T, 3>& source_coords, double time,
     179              :       const domain::FunctionsOfTimeMap& functions_of_time) const;
     180              : 
     181              :   /// The inverse function is only callable with doubles because the inverse
     182              :   /// might fail if called for a point out of range, and it is unclear
     183              :   /// what should happen if the inverse were to succeed for some points in a
     184              :   /// DataVector but fail for other points.
     185            1 :   std::optional<std::array<double, 3>> inverse(
     186              :       const std::array<double, 3>& target_coords, double time,
     187              :       const domain::FunctionsOfTimeMap& functions_of_time) const;
     188              : 
     189              :   template <typename T>
     190            0 :   std::array<T, 3> frame_velocity(
     191              :       const std::array<T, 3>& source_coords, double time,
     192              :       const domain::FunctionsOfTimeMap& functions_of_time) const;
     193              : 
     194              :   template <typename T>
     195            0 :   tnsr::Ij<T, 3, Frame::NoFrame> jacobian(
     196              :       const std::array<T, 3>& source_coords, double time,
     197              :       const domain::FunctionsOfTimeMap& functions_of_time) const;
     198              : 
     199              :   template <typename T>
     200            0 :   tnsr::Ij<T, 3, Frame::NoFrame> inv_jacobian(
     201              :       const std::array<T, 3>& source_coords, double time,
     202              :       const domain::FunctionsOfTimeMap& functions_of_time) const;
     203              : 
     204              :   // NOLINTNEXTLINE(google-runtime-references)
     205            0 :   void pup(PUP::er& p);
     206              : 
     207            0 :   static bool is_identity() { return false; }
     208              : 
     209            0 :   static constexpr bool supports_hessian{false};
     210              : 
     211            0 :   const std::unordered_set<std::string>& function_of_time_names() const {
     212              :     return f_of_t_names_;
     213              :   }
     214              : 
     215              :  private:
     216              :   template <typename T>
     217            0 :   T get_width(const std::array<T, 3>& source_coords,
     218              :               bool ignore_error = false) const;
     219              : 
     220              :   template <typename T>
     221            0 :   std::array<T, 3> get_width_deriv(const std::array<T, 3>& source_coords) const;
     222              : 
     223              :   template <typename T>
     224            0 :   std::array<T, 3> map_and_velocity_helper(
     225              :       const std::array<T, 3>& source_coords, double time,
     226              :       const domain::FunctionsOfTimeMap& functions_of_time,
     227              :       bool return_velocity) const;
     228              : 
     229              :   template <typename T>
     230            0 :   void check_for_singular_map(
     231              :       const std::array<T, 3>& source_coords, double time,
     232              :       const FunctionsOfTimeMap& functions_of_time) const;
     233              : 
     234              :   // NOLINTNEXTLINE(readability-redundant-declaration)
     235            0 :   friend bool operator==(const Skew& lhs, const Skew& rhs);
     236            0 :   std::string f_of_t_name_;
     237            0 :   std::array<double, 3> center_{};
     238            0 :   double outer_radius_{};
     239            0 :   double one_over_outer_radius_squared_{};
     240            0 :   std::unordered_set<std::string> f_of_t_names_;
     241              : };
     242              : 
     243            0 : bool operator!=(const Skew& lhs, const Skew& rhs);
     244              : 
     245              : }  // namespace domain::CoordinateMaps::TimeDependent
        

Generated by: LCOV version 2.0-1