SpECTRE Documentation Coverage Report
Current view: top level - Domain/CoordinateMaps - FocallyLiftedMap.hpp Hit Total Coverage
Commit: eb45036e71ee786d31156fb02c6e736b9a032426 Lines: 2 23 8.7 %
Date: 2024-04-18 22:31:00
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 <limits>
       9             : #include <optional>
      10             : 
      11             : #include "DataStructures/Tensor/TypeAliases.hpp"
      12             : #include "Utilities/TypeTraits/RemoveReferenceWrapper.hpp"
      13             : 
      14             : /// \cond
      15             : namespace PUP {
      16             : class er;
      17             : }  // namespace PUP
      18             : /// \endcond
      19             : 
      20             : namespace domain::CoordinateMaps {
      21             : 
      22             : template <typename InnerMap>
      23             : class FocallyLiftedMap;
      24             : 
      25             : template <typename InnerMap>
      26           0 : bool operator==(const FocallyLiftedMap<InnerMap>& lhs,
      27             :                 const FocallyLiftedMap<InnerMap>& rhs);
      28             : 
      29             : /*!
      30             :  * \ingroup CoordinateMapsGroup
      31             :  *
      32             :  * \brief Map from \f$(\bar{x},\bar{y},\bar{z})\f$ to the volume
      33             :  * contained between a 2D surface and the surface of a 2-sphere.
      34             :  *
      35             :  *
      36             :  * \image html FocallyLifted.svg "2D representation of focally lifted map."
      37             :  *
      38             :  * \details We are given the radius \f$R\f$ and
      39             :  * center \f$C^i\f$ of a sphere, and a projection point \f$P^i\f$.  Also,
      40             :  * through the class defined by the `InnerMap` template parameter,
      41             :  * we are given the functions
      42             :  * \f$f^i(\bar{x},\bar{y},\bar{z})\f$ and
      43             :  * \f$\sigma(\bar{x},\bar{y},\bar{z})\f$ defined below; these
      44             :  * functions define the mapping from \f$(\bar{x},\bar{y},\bar{z})\f$
      45             :  * to the 2D surface.
      46             :  *
      47             :  * The above figure is a 2D representation of the focally lifted map,
      48             :  * where the shaded region in \f$\bar{x}^i\f$ coordinates on the left
      49             :  * is mapped to the shaded region in the \f$x^i\f$ coordinates on the
      50             :  * right.  Shown is an arbitrary point \f$\bar{x}^i\f$ that is mapped
      51             :  * to \f$x^i\f$; for that point, the corresponding \f$x_0^i\f$ and
      52             :  * \f$x_1^i\f$ (defined below) are shown on the right, as is the
      53             :  * projection point \f$P^i\f$.  Also shown are the level surfaces
      54             :  * \f$\sigma=0\f$ and \f$\sigma=1\f$.
      55             :  *
      56             :  * The input coordinates are labeled \f$(\bar{x},\bar{y},\bar{z})\f$.
      57             :  * Let \f$x_0^i = f^i(\bar{x},\bar{y},\bar{z})\f$ be the three coordinates
      58             :  * of the 2D surface in 3D space.
      59             :  *
      60             :  * Now let \f$x_1^i\f$ be a point on the surface of the sphere,
      61             :  * constructed so that \f$P^i\f$, \f$x_0^i\f$, and \f$x_1^i\f$ are
      62             :  * co-linear.  In particular, \f$x_1^i\f$ is determined by the
      63             :  * equation
      64             :  *
      65             :  * \f{align} x_1^i = P^i + (x_0^i - P^i) \lambda,\f}
      66             :  *
      67             :  * where \f$\lambda\f$ is a scalar factor that depends on \f$x_0^i\f$ and
      68             :  * that can be computed by solving a quadratic equation.  This quadratic
      69             :  * equation is derived by demanding that \f$x_1^i\f$ lies on the sphere:
      70             :  *
      71             :  * \f{align}
      72             :  * |P^i + (x_0^i - P^i) \lambda - C^i |^2 - R^2 = 0,
      73             :  * \f}
      74             :  *
      75             :  * where \f$|A^i|^2\f$ means \f$\delta_{ij} A^i A^j\f$.
      76             :  *
      77             :  * The quadratic equation, Eq. (2),
      78             :  * takes the usual form \f$a\lambda^2+b\lambda+c=0\f$,
      79             :  * with
      80             :  *
      81             :  * \f{align*}
      82             :  *  a &= |x_0^i-P^i|^2,\\
      83             :  *  b &= 2(x_0^i-P^i)(P^j-C^j)\delta_{ij},\\
      84             :  *  c &= |P^i-C^i|^2 - R^2.
      85             :  * \f}
      86             :  *
      87             :  * Now assume that \f$x_0^i\f$ lies on a level surface defined
      88             :  * by some scalar function \f$\sigma(\bar{x}^i)=0\f$,
      89             :  * where \f$\sigma\f$ is normalized so that \f$\sigma=1\f$ on the sphere.
      90             :  * Then, once \f$\lambda\f$ has been computed and \f$x_1^i\f$ has
      91             :  * been determined, the map is given by
      92             :  *
      93             :  * \f{align}x^i = x_0^i + (x_1^i - x_0^i) \sigma(\bar{x}^i).\f}
      94             :  *
      95             :  * Note that classes using FocallyLiftedMap will place restrictions on
      96             :  * \f$P^i\f$, \f$C^i\f$, \f$x_0^i\f$, and \f$R\f$.  For example, we
      97             :  * demand that \f$P^i\f$ does not lie on either the \f$\sigma=0\f$ or
      98             :  * \f$\sigma=1\f$ surfaces depicted in the right panel of the figure,
      99             :  * and we demand that the \f$\sigma=0\f$ and \f$\sigma=1\f$ surfaces do
     100             :  * not intersect; otherwise the map is singular.
     101             :  *
     102             :  * Also note that the quadratic Eq. (2) typically has more than one
     103             :  * root, corresponding to two intersections of the sphere.  The
     104             :  * boolean parameter `source_is_between_focus_and_target` that is
     105             :  * passed into the constructor of `FocallyLiftedMap` is used to choose the
     106             :  * appropriate root, or to error if a suitable
     107             :  * root is not found. `source_is_between_focus_and_target` should be
     108             :  * true if the source point lies between the projection point
     109             :  * \f$P^i\f$ and the sphere.  `source_is_between_focus_and_target` is
     110             :  * known only by each particular `CoordinateMap` that uses
     111             :  * `FocallyLiftedMap`.
     112             :  *
     113             :  * ### Jacobian
     114             :  *
     115             :  * Differentiating Eq. (1) above yields
     116             :  *
     117             :  * \f{align}
     118             :  * \frac{\partial x_1^i}{\partial x_0^j} = \lambda \delta^i_j +
     119             :  *  (x_0^i - P^i) \frac{\partial \lambda}{\partial x_0^j}.
     120             :  * \f}
     121             :  *
     122             :  * and differentiating Eq. (2) and then inserting Eq. (1) yields
     123             :  *
     124             :  * \f{align}
     125             :  * \frac{\partial\lambda}{\partial x_0^j} &=
     126             :  * \lambda^2 \frac{C_j - x_1^j}{|x_1^i - P^i|^2
     127             :  * + (x_1^i - P^i)(P_i - C_{i})}.
     128             :  * \f}
     129             :  *
     130             :  * The Jacobian can be found by differentiating Eq. (3) above and using the
     131             :  * chain rule, recognizing that \f$x_1^i\f$ depends on \f$\bar{x}^i\f$ only
     132             :  * through its dependence on \f$x_0^i\f$; this is because there is no explicit
     133             :  * dependence on \f$\bar{x}^i\f$ in Eq. (1) (which determines \f$x_1^i\f$)
     134             :  * or Eq. (2) (which determines \f$\lambda\f$).
     135             :  *
     136             :  * \f{align}
     137             :  * \frac{\partial x^i}{\partial \bar{x}^j} &=
     138             :  * \sigma \frac{\partial x_1^i}{\partial x_0^k}
     139             :  * \frac{\partial x_0^k}{\partial \bar{x}^j} +
     140             :  * (1-\sigma)\frac{\partial x_0^i}{\partial \bar{x}^j}
     141             :  * + \frac{\partial \sigma}{\partial \bar{x}^j} (x_1^i - x_0^i),
     142             :  * \nonumber \\
     143             :  * &= (1-\sigma+\lambda\sigma) \frac{\partial x_0^i}{\partial \bar{x}^j} +
     144             :  * \sigma (x_0^i - P^i) \frac{\partial \lambda}{\partial x_0^k}
     145             :  * \frac{\partial x_0^k}{\partial \bar{x}^j}
     146             :  * + \frac{\partial \sigma}{\partial \bar{x}^j} (x_1^i - x_0^i),
     147             :  * \f}
     148             :  * where in the last line we have substituted Eq. (4).
     149             :  *
     150             :  * The class defined by the `InnerMap` template parameter
     151             :  * provides the function `deriv_sigma`, which returns
     152             :  * \f$\partial \sigma/\partial \bar{x}^j\f$, and the function `jacobian`,
     153             :  * which returns \f$\partial x_0^k/\partial \bar{x}^j\f$.
     154             :  *
     155             :  * ### Inverse map.
     156             :  *
     157             :  * Given \f$x^i\f$, we wish to compute \f$\bar{x}^i\f$.
     158             :  *
     159             :  * We first find the coordinates \f$x_0^i\f$ that lie on the 2-surface
     160             :  * and are defined such that \f$P^i\f$, \f$x_0^i\f$,
     161             :  * and \f$x^i\f$ are co-linear.
     162             :  * See the right panel of the above figure.
     163             :  * \f$x_0^i\f$ is determined by the equation
     164             :  *
     165             :  * \f{align} x_0^i = P^i + (x^i - P^i) \tilde{\lambda},\f}
     166             :  *
     167             :  * where \f$\tilde{\lambda}\f$ is a scalar factor that depends on
     168             :  * \f$x^i\f$ and is determined by the class defined by the `InnerMap` template
     169             :  * parameter.  `InnerMap`
     170             :  * provides a function `lambda_tilde` that takes \f$x^i\f$ and
     171             :  * \f$P^i\f$ as arguments and returns \f$\tilde{\lambda}\f$ (or
     172             :  * a default-constructed `std::optional` if the appropriate
     173             :  * \f$\tilde{\lambda}\f$ cannot be
     174             :  * found; this default-constructed value indicates that the point \f$x^i\f$ is
     175             :  * outside the range of the map).
     176             :  *
     177             :  * Now consider the coordinates \f$x_1^i\f$ that lie on the sphere and
     178             :  * are defined such that \f$P^i\f$, \f$x_1^i\f$, and \f$x^i\f$ are
     179             :  * co-linear.  See the right panel of the figure.
     180             :  * \f$x_1^i\f$ is determined by the equation
     181             :  *
     182             :  * \f{align} x_1^i = P^i + (x^i - P^i) \bar{\lambda},\f}
     183             :  *
     184             :  * where \f$\bar{\lambda}\f$ is a scalar factor that depends on \f$x^i\f$ and
     185             :  * is the solution of a quadratic
     186             :  * that is derived by demanding that \f$x_1^i\f$ lies on the sphere:
     187             :  *
     188             :  * \f{align}
     189             :  * |P^i + (x^i - P^i) \bar{\lambda} - C^i |^2 - R^2 = 0.
     190             :  * \f}
     191             :  *
     192             :  * Eq. (9) is a quadratic equation that
     193             :  * takes the usual form \f$a\bar{\lambda}^2+b\bar{\lambda}+c=0\f$,
     194             :  * with
     195             :  *
     196             :  * \f{align*}
     197             :  *  a &= |x^i-P^i|^2,\\
     198             :  *  b &= 2(x^i-P^i)(P^j-C^j)\delta_{ij},\\
     199             :  *  c &= |P^i-C^i|^2 - R^2.
     200             :  * \f}
     201             :  *
     202             :  * Note that we don't actually need to compute \f$x_1^i\f$. Instead, we
     203             :  * can determine \f$\sigma\f$ by the relation (obtained by solving Eq. (3)
     204             :  * for \f$\sigma\f$ and then inserting Eqs. (7) and (8) to eliminate
     205             :  * \f$x_1^i\f$ and \f$x_0^i\f$)
     206             :  *
     207             :  * \f{align}
     208             :  * \sigma = \frac{\tilde{\lambda}-1}{\tilde{\lambda}-\bar{\lambda}}.
     209             :  * \f}
     210             :  *
     211             :  * The denominator of Eq. (10) is nonzero for nonsingular maps:
     212             :  * From Eqs. (7) and (8), \f$\bar{\lambda}=\tilde{\lambda}\f$ means
     213             :  * that \f$x_1^i=x_0^i\f$, which means that
     214             :  * the \f$\sigma=0\f$ and \f$\sigma=1\f$ surfaces intersect, i.e.
     215             :  * the map is singular.
     216             :  *
     217             :  * Once we have \f$x_0^i\f$ and \f$\sigma\f$, the point
     218             :  * \f$(\bar{x},\bar{y},\bar{z})\f$ is uniquely determined by `InnerMap`.
     219             :  * The `inverse` function of `InnerMap` takes \f$x_0^i\f$ and \f$\sigma\f$
     220             :  * as arguments, and returns
     221             :  * \f$(\bar{x},\bar{y},\bar{z})\f$, or a default-constructed `std::optional`
     222             :  * if \f$x_0^i\f$ or
     223             :  * \f$\sigma\f$ are outside the range of the map.
     224             :  *
     225             :  * #### Root polishing
     226             :  *
     227             :  * The inverse function described above will sometimes have errors that
     228             :  * are noticeably larger than roundoff.  Therefore we apply a single
     229             :  * Newton-Raphson iteration to refine the result of the inverse map:
     230             :  * Suppose we are given \f$x^i\f$, and we have computed \f$\bar{x}^i\f$
     231             :  * by the above procedure.  We then correct \f$\bar{x}^i\f$ by adding
     232             :  *
     233             :  * \f{align}
     234             :  * \delta \bar{x}^i = \left(x^j - x^j(\bar{x})\right)
     235             :  * \frac{\partial \bar{x}^i}{\partial x^j},
     236             :  * \f}
     237             :  *
     238             :  * where \f$x^j(\bar{x})\f$ is the result of applying the forward map
     239             :  * to \f$\bar{x}^i\f$ and \f$\partial \bar{x}^i/\partial x^j\f$ is the
     240             :  * inverse jacobian.
     241             :  *
     242             :  * ### Inverse jacobian
     243             :  *
     244             :  * We write the inverse Jacobian as
     245             :  *
     246             :  * \f{align}
     247             :  * \frac{\partial \bar{x}^i}{\partial x^j} =
     248             :  * \frac{\partial \bar{x}^i}{\partial x_0^k}
     249             :  * \frac{\partial x_0^k}{\partial x^j}
     250             :  * + \frac{\partial \bar{x}^i}{\partial \sigma}
     251             :  *   \frac{\partial \sigma}{\partial x^j},
     252             :  * \f}
     253             :  *
     254             :  * where we have recognized that \f$\bar{x}^i\f$ depends both on
     255             :  * \f$x_0^k\f$ (the corresponding point on the 2-surface) and on
     256             :  * \f$\sigma\f$ (encoding the distance away from the 2-surface).
     257             :  *
     258             :  * We now evaluate Eq. (12). The `InnerMap` class provides a function
     259             :  * `inv_jacobian` that returns \f$\partial \bar{x}^i/\partial x_0^k\f$
     260             :  * (where \f$\sigma\f$ is held fixed), and a function `dxbar_dsigma`
     261             :  * that returns \f$\partial \bar{x}^i/\partial \sigma\f$ (where
     262             :  * \f$x_0^i\f$ is held fixed).  The factor \f$\partial x_0^j/\partial
     263             :  * x^i\f$ can be computed by differentiating Eq. (7), which yields
     264             :  *
     265             :  * \f{align}
     266             :  * \frac{\partial x_0^j}{\partial x^i} &= \tilde{\lambda} \delta_i^j
     267             :  * + \frac{x_0^j-P^j}{\tilde{\lambda}}
     268             :  *   \frac{\partial\tilde{\lambda}}{\partial x^i},
     269             :  * \f}
     270             :  *
     271             :  * where \f$\partial \tilde{\lambda}/\partial x^i\f$ is provided by
     272             :  * the `deriv_lambda_tilde` function of `InnerMap`.  Note that for
     273             :  * nonsingular maps there is no worry that \f$\tilde{\lambda}\f$ is
     274             :  * zero in the denominator of the second term of Eq. (13); if
     275             :  * \f$\tilde{\lambda}=0\f$ then \f$x_0^i=P^i\f$ by Eq. (7), and therefore
     276             :  * the map is singular.
     277             :  *
     278             :  * To evaluate the remaining unknown factor in Eq. (12),
     279             :  * \f$\partial \sigma/\partial x^j\f$,
     280             :  * note that [combining Eqs. (1), (7), and (8)]
     281             :  * \f$\bar{\lambda}=\tilde{\lambda}\lambda\f$.
     282             :  * Therefore Eq. (10) is equivalent to
     283             :  *
     284             :  * \f{align}
     285             :  * \sigma &= \frac{\tilde{\lambda}-1}{\tilde{\lambda}(1-\lambda)}.
     286             :  * \f}
     287             :  *
     288             :  * Differentiating this expression yields
     289             :  *
     290             :  * \f{align}
     291             :  * \frac{\partial \sigma}{\partial x^i} &=
     292             :  * \frac{\partial \sigma}{\partial \lambda}
     293             :  * \frac{\partial \lambda}{\partial x_0^j}
     294             :  * \frac{\partial x_0^j}{\partial x^i}
     295             :  * + \frac{\partial \sigma}{\partial \tilde\lambda}
     296             :  *   \frac{\partial \tilde\lambda}{\partial x^i}\\
     297             :  * &=
     298             :  * \frac{\sigma}{1-\lambda}
     299             :  * \frac{\partial \lambda}{\partial x_0^j}
     300             :  * \frac{\partial x_0^j}{\partial x^i}
     301             :  * +
     302             :  * \frac{1}{\tilde{\lambda}^2(1-\lambda)}
     303             :  * \frac{\partial \tilde\lambda}{\partial x^i},
     304             :  * \f}
     305             :  *
     306             :  * where the second factor in the first term can be evaluated using
     307             :  * Eq. (5), the third factor in the first term can be evaluated using
     308             :  * Eq. (13), and the second factor in the second term is provided by
     309             :  * `InnerMap`s function `deriv_lambda_tilde`.
     310             :  *
     311             :  */
     312             : template <typename InnerMap>
     313           1 : class FocallyLiftedMap {
     314             :  public:
     315           0 :   static constexpr size_t dim = 3;
     316           0 :   FocallyLiftedMap(const std::array<double, 3>& center,
     317             :                    const std::array<double, 3>& proj_center, double radius,
     318             :                    bool source_is_between_focus_and_target, InnerMap inner_map);
     319             : 
     320           0 :   FocallyLiftedMap() = default;
     321           0 :   ~FocallyLiftedMap() = default;
     322           0 :   FocallyLiftedMap(FocallyLiftedMap&&) = default;
     323           0 :   FocallyLiftedMap(const FocallyLiftedMap&) = default;
     324           0 :   FocallyLiftedMap& operator=(const FocallyLiftedMap&) = default;
     325           0 :   FocallyLiftedMap& operator=(FocallyLiftedMap&&) = default;
     326             : 
     327             :   template <typename T>
     328           0 :   std::array<tt::remove_cvref_wrap_t<T>, 3> operator()(
     329             :       const std::array<T, 3>& source_coords) const;
     330             : 
     331             :   /// The inverse function is only callable with doubles because the inverse
     332             :   /// might fail if called for a point out of range, and it is unclear
     333             :   /// what should happen if the inverse were to succeed for some points in a
     334             :   /// DataVector but fail for other points.
     335           1 :   std::optional<std::array<double, 3>> inverse(
     336             :       const std::array<double, 3>& target_coords) const;
     337             : 
     338             :   template <typename T>
     339           0 :   tnsr::Ij<tt::remove_cvref_wrap_t<T>, 3, Frame::NoFrame> jacobian(
     340             :       const std::array<T, 3>& source_coords) const;
     341             : 
     342             :   template <typename T>
     343           0 :   tnsr::Ij<tt::remove_cvref_wrap_t<T>, 3, Frame::NoFrame> inv_jacobian(
     344             :       const std::array<T, 3>& source_coords) const;
     345             : 
     346             :   // NOLINTNEXTLINE(google-runtime-references)
     347           0 :   void pup(PUP::er& p);
     348             : 
     349           0 :   static bool is_identity() { return false; }
     350             : 
     351             :  private:
     352           0 :   friend bool operator==<InnerMap>(const FocallyLiftedMap<InnerMap>& lhs,
     353             :                                    const FocallyLiftedMap<InnerMap>& rhs);
     354           0 :   std::array<double, 3> center_{}, proj_center_{};
     355           0 :   double radius_{std::numeric_limits<double>::signaling_NaN()};
     356           0 :   bool source_is_between_focus_and_target_;
     357           0 :   InnerMap inner_map_;
     358             : };
     359             : template <typename InnerMap>
     360           0 : bool operator!=(const FocallyLiftedMap<InnerMap>& lhs,
     361             :                 const FocallyLiftedMap<InnerMap>& rhs);
     362             : }  // namespace domain::CoordinateMaps

Generated by: LCOV version 1.14