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