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