SpECTRE Documentation Coverage Report
Current view: top level - Domain/CoordinateMaps - Wedge.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 57.1 % 49 28
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              : #include "Domain/CoordinateMaps/Distribution.hpp"
      13              : #include "Domain/Structure/OrientationMap.hpp"
      14              : 
      15              : /// \cond
      16              : namespace PUP {
      17              : class er;
      18              : }  // namespace PUP
      19              : /// \endcond
      20              : 
      21              : namespace domain::CoordinateMaps {
      22              : 
      23              : namespace detail {
      24              : // This mapping can be deleted once the 2D and 3D wedges are oriented the same
      25              : // (see issue https://github.com/sxs-collaboration/spectre/issues/2988)
      26              : template <size_t Dim>
      27              : struct WedgeCoordOrientation;
      28              : template <>
      29              : struct WedgeCoordOrientation<2> {
      30              :   static constexpr size_t radial_coord = 0;
      31              :   static constexpr size_t polar_coord = 1;
      32              :   static constexpr size_t azimuth_coord = 2;  // unused
      33              : };
      34              : template <>
      35              : struct WedgeCoordOrientation<3> {
      36              :   static constexpr size_t radial_coord = 2;
      37              :   static constexpr size_t polar_coord = 0;
      38              :   static constexpr size_t azimuth_coord = 1;
      39              : };
      40              : }  // namespace detail
      41              : 
      42              : /*!
      43              :  * \ingroup CoordinateMapsGroup
      44              :  *
      45              :  * \brief Map from a square or cube to a wedge.
      46              :  * \image html Shell.png "A shell can be constructed out of six wedges."
      47              :  *
      48              :  * \details The mapping that goes from a reference cube (in 3D) or square (in
      49              :  * 2D) to a wedge centered on a coordinate axis covering a volume between an
      50              :  * inner surface and outer surface. Each surface can be given a curvature
      51              :  * between flat (a sphericity of 0) or spherical (a sphericity of 1).
      52              :  *
      53              :  * In 2D, the first logical coordinate corresponds to the radial coordinate,
      54              :  * and the second logical coordinate corresponds to the angular coordinate. In
      55              :  * 3D, the first two logical coordinates correspond to the two angular
      56              :  * coordinates, and the third to the radial coordinate. This difference
      57              :  * originates from separate implementations for the 2D and 3D map that were
      58              :  * merged. The 3D implementation can be changed to use the first logical
      59              :  * coordinate as the radial direction, but this requires propagating the change
      60              :  * through the rest of the domain code (see issue
      61              :  * https://github.com/sxs-collaboration/spectre/issues/2988).
      62              :  *
      63              :  * The following documentation is for the **centered** 3D map, as we will defer
      64              :  * the dicussion of `Wedge`s with a `focal_offset_` to a later section. The 2D
      65              :  * map is obtained by setting either of the two angular coordinates to zero
      66              :  * (and using \f$\xi\f$ as the radial coordinate). Note that there is also a
      67              :  * normalization factor of $\sqrt{3}$ that appears in multiple expressions in
      68              :  * the 3D case that becomes $\sqrt{2}$ in the 2D case.
      69              :  *
      70              :  * The Wedge map is constructed by linearly interpolating between a bulged
      71              :  * face of radius `radius_inner_` to a bulged face of radius `radius_outer_`,
      72              :  * where the radius of each bulged face is defined to be the radius of the
      73              :  * sphere circumscribing the bulge.
      74              :  *
      75              :  * We make a choice here as to whether we wish to use the logical coordinates
      76              :  * parameterizing these surface as they are, in which case we have the
      77              :  * equidistant choice of coordinates, or whether to apply a tangent map to them
      78              :  * which leads us to the equiangular choice of coordinates. `Wedge`s have
      79              :  * variable `opening_angles_` which, for centered `Wedge`s, are the angular
      80              :  * sizes of the wedge in the $\xi$ and $\eta$ directions (for the 3D case) in
      81              :  * the target frame. By default, `Wedge`s have opening angles of $\pi/2$, so we
      82              :  * will discuss that case here and defer both the discussion of generalized
      83              :  * opening angles and the interaction between opening angles and non-zero focal
      84              :  * offsets for later sections.
      85              :  *
      86              :  * For a Wedge with $\xi$ and $\eta$ opening angles of $\pi/2$, the
      87              :  * equiangular coordinates in terms of the logical coordinates are:
      88              :  *
      89              :  * \begin{align}
      90              :  *   \textrm{equiangular xi} : \Xi(\xi) = \textrm{tan}(\xi\pi/4)
      91              :  *   \label{eq:equiangular_xi_pi_over_2}
      92              :  * \end{align}
      93              :  *
      94              :  * \begin{align}
      95              :  *   \textrm{equiangular eta} :
      96              :  *       \mathrm{H}(\eta) =  \textrm{tan}(\eta\pi/4)
      97              :  *       \label{eq:equiangular_eta_pi_over_2}
      98              :  * \end{align}
      99              :  *
     100              :  * With derivatives:
     101              :  *
     102              :  * \begin{align}
     103              :  *   \Xi'(\xi) &= \frac{\pi}{4}(1+\Xi^2) \\
     104              :  *   \mathrm{H}'(\eta) &= \frac{\pi}{4}(1+\mathrm{H}^2)
     105              :  * \end{align}
     106              :  *
     107              :  * The equidistant coordinates are:
     108              :  *
     109              :  * \begin{align}
     110              :  *   \textrm{equidistant xi}  : \Xi = \xi \\
     111              :  *   \textrm{equidistant eta}  : \mathrm{H} = \eta
     112              :  * \end{align}
     113              :  *
     114              :  * with derivatives:
     115              :  *
     116              :  * \begin{align}
     117              :  *   \Xi'(\xi) &= 1 \\
     118              :  *   \mathrm{H}'(\eta) &= 1
     119              :  * \end{align}
     120              :  *
     121              :  * We also define the variable \f$\rho\f$, given by:
     122              :  *
     123              :  * \begin{align}
     124              :  *   \textrm{rho} : \rho = \sqrt{1+\Xi^2+\mathrm{H}^2}
     125              :  * \end{align}
     126              :  *
     127              :  * ### The Spherical Face Map
     128              :  * The surface map for the spherical face of radius \f$R\f$ lying in the
     129              :  * \f$+z\f$ direction in either choice of coordinates is then given by:
     130              :  *
     131              :  * \begin{align}
     132              :  *   \vec{\sigma}_{spherical}: \vec{\xi} \rightarrow \vec{x}(\vec{\xi})
     133              :  * \end{align}
     134              :  *
     135              :  * Where
     136              :  *
     137              :  * \begin{align}
     138              :  *   \vec{x}(\xi,\eta) =
     139              :  *       \begin{bmatrix}
     140              :  *         x(\xi,\eta) \\
     141              :  *         y(\xi,\eta) \\
     142              :  *         z(\xi,\eta) \\
     143              :  *       \end{bmatrix}  =
     144              :  *           \frac{R}{\rho}
     145              :  *               \begin{bmatrix}
     146              :  *                 \Xi \\
     147              :  *                 \mathrm{H} \\
     148              :  *                 1 \\
     149              :  *               \end{bmatrix}
     150              :  * \end{align}
     151              :  *
     152              :  * ### The Bulged Face Map
     153              :  * The bulged surface is itself constructed by linearly interpolating between
     154              :  * a cubical face and a spherical face. The surface map for the cubical face
     155              :  * of side length \f$2L\f$ lying in the \f$+z\f$ direction is given by:
     156              :  *
     157              :  * \begin{align}
     158              :  *   \vec{\sigma}_{cubical}: \vec{\xi} \rightarrow \vec{x}(\vec{\xi})
     159              :  * \end{align}
     160              :  *
     161              :  * Where
     162              :  *
     163              :  * \begin{align}
     164              :  *   \vec{x}(\xi,\eta) =
     165              :  *       \begin{bmatrix}
     166              :  *         x(\xi,\eta) \\
     167              :  *         y(\xi,\eta) \\
     168              :  *         L \\
     169              :  *       \end{bmatrix} =
     170              :  *           L\begin{bmatrix}
     171              :  *              \Xi \\
     172              :  *              \mathrm{H} \\
     173              :  *              1 \\
     174              :  *            \end{bmatrix}
     175              :  * \end{align}
     176              :  *
     177              :  * To construct the bulged map we interpolate between this cubical face map
     178              :  * and a spherical face map of radius \f$R\f$, with the interpolation
     179              :  * parameter being \f$s\f$, called the *sphericity* and which ranges from
     180              :  * 0 to 1, with 0 corresponding to a flat surface and 1 corresponding to a
     181              :  * spherical surface. The surface map for the bulged face lying in the \f$+z\f$
     182              :  * direction is then given by:
     183              :  *
     184              :  * \begin{align}
     185              :  *   \vec{\sigma}_{bulged}(\xi,\eta) =
     186              :  *       \left\{(1-s)L +
     187              :  *       \frac{sR}{\rho}\right\}
     188              :  *           \begin{bmatrix}
     189              :  *             \Xi \\
     190              :  *             \mathrm{H} \\
     191              :  *             1 \\
     192              :  *           \end{bmatrix}
     193              :  * \end{align}
     194              :  *
     195              :  * We constrain $L$ by demanding that the spherical face circumscribe the cube.
     196              :  * With this condition, we have \f$L = R/\sqrt3\f$.
     197              :  * \note This differs from the choice in SpEC where it is demanded that the
     198              :  * surfaces touch at the cube face centers, which leads to \f$L = R\f$.
     199              :  *
     200              :  * ### The Full Volume Map
     201              :  * The final map for the wedge which lies along the \f$+z\f$ axis is obtained
     202              :  * by interpolating between the two surfaces with the interpolation parameter
     203              :  * being the logical coordinate \f$\zeta\f$. For a wedge whose gridpoints are
     204              :  * **linearly** distributed in the radial direction (`radial_distribution_` is
     205              :  * \ref domain::CoordinateMaps::Distribution
     206              :  * "domain::CoordinateMaps::Distribution::Linear"), this interpolation results
     207              :  * in the following map:
     208              :  *
     209              :  * \begin{align}
     210              :  *   \vec{x}(\xi,\eta,\zeta) =
     211              :  *       \frac{1}{2}\left\{
     212              :  *         (1-\zeta)\Big[
     213              :  *           (1-s_{inner})\frac{R_{inner}}{\sqrt 3} +
     214              :  *           s_{inner}\frac{R_{inner}}{\rho}
     215              :  *         \Big] +
     216              :  *         (1+\zeta)\Big[
     217              :  *           (1-s_{outer})\frac{R_{outer}}{\sqrt 3} +
     218              :  *           s_{outer}\frac{R_{outer}}{\rho}
     219              :  *         \Big]
     220              :  *       \right\}
     221              :  *           \begin{bmatrix}
     222              :  *             \Xi \\
     223              :  *             \mathrm{H} \\
     224              :  *             1 \\
     225              :  *           \end{bmatrix}
     226              :  * \end{align}
     227              :  *
     228              :  * We will define the variables \f$F(\zeta)\f$ and \f$S(\zeta)\f$, the frustum
     229              :  * and sphere factors (in the linear case):
     230              :  *
     231              :  * \begin{align}
     232              :  *   F(\zeta) &= F_0 + F_1\zeta \label{eq:frustum_factor} \\
     233              :  *   S(\zeta) &= S_0 + S_1\zeta \label{eq:sphere_factor}
     234              :  * \end{align}
     235              :  *
     236              :  * Where
     237              :  *
     238              :  * \begin{align}
     239              :  *   F_0 &=
     240              :  *       \frac{1}{2} \big\{
     241              :  *         (1-s_{outer})R_{outer} + (1-s_{inner})R_{inner}
     242              :  *       \big\} \label{eq:frustum_zero_linear} \\
     243              :  *   F_1 &= \partial_{\zeta}F
     244              :  *        = \frac{1}{2} \big\{
     245              :  *            (1-s_{outer})R_{outer} - (1-s_{inner})R_{inner}
     246              :  *          \big\} \label{eq:frustum_rate_linear} \\
     247              :  *   S_0 &=
     248              :  *       \frac{1}{2} \big\{
     249              :  *         s_{outer}R_{outer} + s_{inner}R_{inner}
     250              :  *       \big\} \label{eq:sphere_zero_linear} \\
     251              :  *   S_1 &= \partial_{\zeta}S
     252              :  *        = \frac{1}{2} \big\{ s_{outer}R_{outer} - s_{inner}R_{inner}\big\}
     253              :  *        \label{eq:sphere_rate_linear}
     254              :  * \end{align}
     255              :  *
     256              :  * The map can then be rewritten as:
     257              :  *
     258              :  * \begin{align}
     259              :  *   \vec{x}(\xi,\eta,\zeta) =
     260              :  *       \left\{
     261              :  *         \frac{F(\zeta)}{\sqrt 3} + \frac{S(\zeta)}{\rho}
     262              :  *       \right\}
     263              :  *           \begin{bmatrix}
     264              :  *             \Xi \\
     265              :  *             \mathrm{H} \\
     266              :  *             1 \\
     267              :  *           \end{bmatrix}
     268              :  * \end{align}
     269              :  *
     270              :  * The inverse map is given by:
     271              :  *
     272              :  * \begin{align}
     273              :  *   \xi &= \frac{x}{z} \\
     274              :  *   \eta &= \frac{y}{z} \\
     275              :  *   \zeta &= \frac{z - \left(\frac{F_0}{\sqrt{3}} + \frac{S_0}{\rho}\right)}
     276              :  *                 {\left(\frac{F_1}{\sqrt{3}} + \frac{S_1}{\rho}\right)}
     277              :  * \end{align}
     278              :  *
     279              :  * We provide some common derivatives:
     280              :  *
     281              :  * \f{align}
     282              :  *   \partial_{\xi}z &= \frac{-S(\zeta)\Xi\Xi'}{\rho^3} \\
     283              :  *   \partial_{\eta}z &= \frac{-S(\zeta)\mathrm{H}\mathrm{H}'}{\rho^3} \\
     284              :  *   \partial_{\zeta}z &= \frac{F'}{\sqrt 3} + \frac{S'(\zeta)}{\rho}
     285              :  * \f}
     286              :  *
     287              :  * The Jacobian then is:
     288              :  *
     289              :  * \begin{align}
     290              :  *   J =
     291              :  *       \begin{bmatrix}
     292              :  *         \Xi'z + \Xi\partial_{\xi}z &
     293              :  *             \Xi\partial_{\eta}z &
     294              :  *             \Xi\partial_{\zeta}z \\
     295              :  *         \mathrm{H}\partial_{\xi}z &
     296              :  *             \mathrm{H}'z + \mathrm{H}\partial_{\eta}z &
     297              :  *             \mathrm{H}\partial_{\zeta}z \\
     298              :  *         \partial_{\xi}z &
     299              :  *             \partial_{\eta}z &
     300              :  *             \partial_{\zeta}z \\
     301              :  *       \end{bmatrix}
     302              :  *       \label{eq:jacobian_centered_wedge}
     303              :  * \end{align}
     304              :  *
     305              :  * A common factor that shows up in the inverse Jacobian is:
     306              :  *
     307              :  * \begin{align}
     308              :  *   T:= \frac{S(\zeta)}{(\partial_{\zeta}z)\rho^3}
     309              :  * \end{align}
     310              :  *
     311              :  * The inverse Jacobian then is:
     312              :  * \f{align}
     313              :  *   J^{-1} =
     314              :  *       \frac{1}{z}\begin{bmatrix}
     315              :  *         \Xi'^{-1} & 0 & -\Xi\Xi'^{-1} \\
     316              :  *         0 & \mathrm{H}'^{-1} & -\mathrm{H}\mathrm{H}'^{-1} \\
     317              :  *         T\Xi & T\mathrm{H} & T + F(\partial_{\zeta}z)^{-1}/\sqrt 3 \\
     318              :  *       \end{bmatrix}
     319              :  * \f}
     320              :  *
     321              :  * ### Changing the radial distribution of the gridpoints
     322              :  * By default, Wedge linearly distributes its gridpoints in the radial
     323              :  * direction. An exponential distribution of gridpoints can be obtained by
     324              :  * linearly interpolating in the logarithm of the radius in order to obtain
     325              :  * a relatively higher resolution at smaller radii. Since this is a radial
     326              :  * rescaling of Wedge, this option is only supported for fully spherical
     327              :  * wedges with `sphericity_inner_` = `sphericity_outer_` = 1.
     328              :  *
     329              :  * The linear interpolation done for a logarithmic radial distribution
     330              :  * (`radial_distribution_` is \ref domain::CoordinateMaps::Distribution
     331              :  * "domain::CoordinateMaps::Distribution::Logarithmic") is:
     332              :  *
     333              :  * \begin{align}
     334              :  *   \ln r = \frac{1-\zeta}{2}\ln R_{inner} + \frac{1+\zeta}{2}\ln R_{outer}
     335              :  * \end{align}
     336              :  *
     337              :  * The map then is:
     338              :  *
     339              :  * \begin{align}
     340              :  *   \vec{x}(\xi,\eta,\zeta) =
     341              :  *       \frac{\sqrt{R_{inner}^{1-\zeta}R_{outer}^{1+\zeta}}}{\rho}
     342              :  *           \begin{bmatrix}
     343              :  *             \Xi \\
     344              :  *             \mathrm{H} \\
     345              :  *             1 \\
     346              :  *           \end{bmatrix}
     347              :  * \end{align}
     348              :  *
     349              :  * We can rewrite this map to take on the same form as the map for the linear
     350              :  * radial distribution, where we set
     351              :  *
     352              :  * \begin{align}
     353              :  *   F(\zeta) &= 0 \\
     354              :  *   S(\zeta) &= \sqrt{R_{inner}^{1-\zeta}R_{outer}^{1+\zeta}} \\
     355              :  * \end{align}
     356              :  *
     357              :  * Which gives us
     358              :  *
     359              :  * \begin{align}
     360              :  *   \vec{x}(\xi,\eta,\zeta) =
     361              :  *       \frac{S(\zeta)}{\rho}
     362              :  *           \begin{bmatrix}
     363              :  *             \Xi \\
     364              :  *             \mathrm{H} \\
     365              :  *             1 \\
     366              :  *           \end{bmatrix}
     367              :  * \end{align}
     368              :  *
     369              :  * The Jacobian then is still Eq. ($\ref{eq:jacobian_centered_wedge}$) but
     370              :  * where $F(\zeta)$ and $S(\zeta)$ are the quantities defined here for the
     371              :  * logarithmic distribution.
     372              :  *
     373              :  * Alternatively, an inverse radial distribution (`radial_distribution_` is
     374              :  * \ref domain::CoordinateMaps::Distribution
     375              :  * "domain::CoordinateMaps::Distribution::Inverse") can be chosen where the
     376              :  * linear interpolation is:
     377              :  *
     378              :  * \begin{align}
     379              :  *   \frac{1}{r} =
     380              :  *       \frac{R_\mathrm{inner} + R_\mathrm{outer}}
     381              :  *            {2 R_\mathrm{inner}R_\mathrm{outer}} +
     382              :  *       \frac{R_\mathrm{inner} - R_\mathrm{outer}}
     383              :  *            {2R_\mathrm{inner} R_\mathrm{outer}} \zeta
     384              :  * \end{align}
     385              :  *
     386              :  * Which can be rewritten as:
     387              :  *
     388              :  * \begin{align}
     389              :  *   \frac{1}{r} = \frac{1-\zeta}{2R_{inner}} + \frac{1+\zeta}{2R_{outer}}
     390              :  * \end{align}
     391              :  *
     392              :  * The map likewise takes the form:
     393              :  *
     394              :  * \begin{align}
     395              :  *   \vec{x}(\xi,\eta,\zeta) =
     396              :  *       \frac{S(\zeta)}{\rho}
     397              :  *           \begin{bmatrix}
     398              :  *             \Xi \\
     399              :  *             \mathrm{H} \\
     400              :  *             1 \\
     401              :  *           \end{bmatrix}
     402              :  * \end{align}
     403              :  *
     404              :  * Where
     405              :  *
     406              :  * \begin{align}
     407              :  *   F(\zeta) &= 0 \\
     408              :  *   S(\zeta) &=
     409              :  *       \frac{2R_{inner}R_{outer}}
     410              :  *            {(1 + \zeta)R_{inner} + (1 - \zeta)R_{outer}}
     411              :  * \end{align}
     412              :  *
     413              :  * Again, the Jacobian is still Eq. ($\ref{eq:jacobian_centered_wedge}$) but
     414              :  * where $F(\zeta)$ and $S(\zeta)$ are the quantities defined here for the
     415              :  * inverse distribution.
     416              :  *
     417              :  * ### Changing the opening angles
     418              :  * Consider the following map on \f$\xi \in [-1,1]\f$, which maps this interval
     419              :  * onto a parameterized curve that extends one fourth of a circle.
     420              :  *
     421              :  * \begin{align}
     422              :  *   \vec{\Gamma}(\xi) =
     423              :  *       \frac{R}{\sqrt{1+\xi^2}}
     424              :  *           \begin{bmatrix}
     425              :  *             1 \\
     426              :  *             \xi \\
     427              :  *           \end{bmatrix}.
     428              :  *   \label{eq:quarter_circle}
     429              :  * \end{align}
     430              :  *
     431              :  * It is convenient to compute the polar coordinate $\theta$ of the mapped
     432              :  * point as a function of $\xi$:
     433              :  *
     434              :  * \begin{align}
     435              :  *   \theta(\xi) = \tan^{-1}\left(\frac{\Gamma_y(\xi)}{\Gamma_x(\xi)}\right).
     436              :  *   \label{eq:polar_coord}
     437              :  * \end{align}
     438              :  *
     439              :  * The *opening angle* of the map is defined to be:
     440              :  *
     441              :  * \begin{align}
     442              :  *   \Delta \theta = \theta(1) - \theta(-1),
     443              :  *   \label{eq:define_opening_angle}
     444              :  * \end{align}
     445              :  *
     446              :  * We can see that with $\xi=\pm 1$, we have $\Gamma_x = R/\sqrt{2}$ and
     447              :  * $\Gamma_y=\pm R/\sqrt{2}$, giving us
     448              :  * $\theta(1) = \pi/4$ and $\theta(-1) = -\pi/4$. This wedge has an opening
     449              :  * angle $\pi/2$ radians, as expected.
     450              :  *
     451              :  * On the other hand, the following map has an opening angle of $\theta_O$:
     452              :  *
     453              :  * \begin{align}
     454              :  *   \vec{\Gamma}(\xi) =
     455              :  *       \frac{R}{\sqrt{1+\tan^2{(\theta_O/2)}\xi^2}}
     456              :  *           \begin{bmatrix}
     457              :  *           1 \\
     458              :  *           \tan{(\theta_O/2)}\xi \\
     459              :  *           \end{bmatrix}.
     460              :  * \end{align}
     461              :  *
     462              :  * Let us also consider the generalized map
     463              :  *
     464              :  * \begin{align}
     465              :  *   \vec{\Gamma}(\xi) =
     466              :  *       \frac{R}{\sqrt{1+\Xi^2}}
     467              :  *           \begin{bmatrix}
     468              :  *             1 \\
     469              :  *             \Xi \\
     470              :  *           \end{bmatrix},
     471              :  * \end{align}
     472              :  *
     473              :  * where $\Xi(\xi)$ is a function of $\xi$. $\theta(\xi)$ can then be written as
     474              :  *
     475              :  * \begin{align}
     476              :  *   \theta(\xi) = \tan^{-1}(\Xi).
     477              :  *   \label{eq:theta}
     478              :  * \end{align}
     479              :  *
     480              :  * For the map $\Xi(\xi) = \tan(\pi\xi/4)$, Eq. ($\ref{eq:theta}$) yields
     481              :  * $\theta(\xi) = \pi\xi/4$ and $\Delta\theta = \pi/2$. Note that this choice of
     482              :  * $\Xi(\xi)$ is equivalent to a reparameterization of the previous map given in
     483              :  * Eq. ($\ref{eq:quarter_circle}$). The reparameterization of the curve
     484              :  * $\vec{\Gamma}(\xi)$ via the tangent map yields an empirically superior
     485              :  * gridpoint distribution in practice. That this reparameterization should have
     486              :  * this property can be motivated by an observation of the following:
     487              :  *
     488              :  * \begin{align}
     489              :  *   \frac{\mathrm{d}\tan^{-1}\Xi}{\mathrm{d}\xi}
     490              :  *       = \frac{1}{1+\Xi^2}\frac{\mathrm{d}\Xi}{\mathrm{d}\xi}
     491              :  *       = \frac{\pi}{4}.
     492              :  * \end{align}
     493              :  *
     494              :  * In other words, this parameterization has the property that the logical
     495              :  * coordinate $\xi$ subtends the angle $\theta$ at a constant rate. In general,
     496              :  * we say that a curve $\vec{\Gamma}(\xi)$ is parameterized *equiangularly* if
     497              :  *
     498              :  * \begin{align}
     499              :  *   \frac{\mathrm{d}\theta}{\mathrm{d}\xi} = \text{const}.
     500              :  * \end{align}
     501              :  *
     502              :  * As for the map
     503              :  *
     504              :  * \begin{align}
     505              :  *   \Xi(\xi) =
     506              :  *       \tan{(\theta_O/2)}\frac{\tan{(\theta_D \xi/2)}}{\tan{(\theta_D/2)}},
     507              :  * \end{align}
     508              :  *
     509              :  * this choice of $\Xi(\xi)$ results in a $\vec{\Gamma}(\xi)$ with opening
     510              :  * angle $\theta_O$, which is equiangularly distributed if
     511              :  * $\theta_O = \theta_D$. In the Wedge map, the argument
     512              :  * `with_adapted_equiangular_map` controls whether to set
     513              :  * $\theta_O = \theta_D$ (the `true` case) or to set $\theta_D = \pi/2$
     514              :  * (the `false` case). When working with a 3D Wedge, the opening angles for the
     515              :  * Wedge can be separately controlled for both the $\xi$ and $\eta$ directions,
     516              :  * but `with_adapted_equiangular_map` will apply to both directions.
     517              :  * Additionally in the 3D case, it is not possible to set
     518              :  * `with_equiangular_map_` to `true` for all of the six wedges of a sphere
     519              :  * unless every opening angle is $\pi/2$. In the
     520              :  * \ref ::domain::creators::BinaryCompactObject "BinaryCompactObject" domain,
     521              :  * the outer $+y$, $-y$, $+z$, and $-z$ `Wedge`s are allowed to have a
     522              :  * user-specified opening angle in the $\xi$-direction, with a corresponding
     523              :  * $\theta_D$ equal to this opening angle, while in the $\eta$-direction the
     524              :  * opening angle is set to $\pi/2$. The two end cap `Wedge`s in the $+x$ and
     525              :  * $-x$ directions have angular dimensions and gridpoint distributions
     526              :  * determined by the other four `Wedge`s, as the six `Wedge`s must conforming
     527              :  * have gridpoint distributions at the $\xi = \pm1$, $\eta = \pm 1$ boundaries.
     528              :  *
     529              :  * ### Wedge with a Focal Offset
     530              :  * \image html FocalOffset.jpg "Wedges without and with a focal offset"
     531              :  *
     532              :  * In the case of the rectangular
     533              :  * \ref ::domain::creators::BinaryCompactObject "BinaryCompactObject" domain,
     534              :  * it becomes desirable to offset the center of the spherical excision surface
     535              :  * relative to the center of the cubical surface surrounding it. To enable the
     536              :  * offsetting of the central excision, the Wedge map must be generalized
     537              :  * according to the *focal lifting* method, which we will now discuss.
     538              :  *
     539              :  * We consider the problem of creating parameterized volumes from parameterized
     540              :  * surfaces. Consider a parameterized surface $\vec{\sigma}_{parent}(\xi,\eta)$,
     541              :  * also referred to as the *parent surface*. We define *focal lifting* as the
     542              :  * projection of this parent surface into a three-dimensional parameterized
     543              :  * volume $\vec{x}(\xi,\eta, \zeta)$ with respect to some *focus* $\vec{x}_0$
     544              :  * and *lifting scale factor* $\Lambda(\xi,\eta,\zeta)$. The resulting volume
     545              :  * is then said to be a *focally lifted* volume. These volume maps can be cast
     546              :  * into the following form:
     547              :  *
     548              :  * \begin{align}
     549              :  *   \vec{x} - \vec{x}_0 = \Lambda(\vec{\sigma}_{parent}-\vec{x}_0),
     550              :  *   \label{eq:focal_lifting}
     551              :  * \end{align}
     552              :  *
     553              :  * which makes apparent how the mapped point $\vec{x}(\xi,\eta,\zeta)$ is
     554              :  * obtained. The parametric equations for the generalized 3D Wedge maps can all
     555              :  * be written in the above form, which we will refer to as
     556              :  * *focally lifted form*. In the case of the 3D Wedge map with no focal offset,
     557              :  * we have:
     558              :  *
     559              :  * \begin{align}
     560              :  *   \vec{x}_0 &= 0 \\
     561              :  *   \Lambda &= \left\{\frac{F(\zeta)}{\sqrt{3}} +
     562              :  *                     \frac{S(\zeta)}{\rho} \right\} \\
     563              :  *   \vec{\sigma}_{parent} &= \begin{bmatrix} \Xi, \mathrm{H}, 1 \end{bmatrix}^T
     564              :  * \end{align}
     565              :  *
     566              :  * The above map can be thought of as constructing a wedge from a biunit cube
     567              :  * centered at the origin. Points on the parent surface are scaled by a factor
     568              :  * of $\Lambda(\xi,\eta,\zeta)$ to obtain the corresponding point in the
     569              :  * volume. When generalizing the map to have a focus shifted from the origin
     570              :  * (obtained by setting `focal_offset_` to be non-zero), we scale the original
     571              :  * parent surface $\vec{\sigma}_{parent} = [\Xi, \mathrm{H},1]^T$ by a factor
     572              :  * $L$, and let the focus $\vec{x_0}$ shift away from the origin. The
     573              :  * generalized wedge map is then given by:
     574              :  *
     575              :  * \begin{align}
     576              :  *   \vec{x} - \vec{x}_0 =
     577              :  *       \left\{\frac{F(\zeta)}{L\sqrt 3} +
     578              :  *       \frac{S(\zeta)}{L\rho}\right\}
     579              :  *           \begin{bmatrix}
     580              :  *             L\Xi - x_0 \\
     581              :  *             L\mathrm{H} - y_0 \\
     582              :  *             L-z_0 \\
     583              :  *           \end{bmatrix}
     584              :  * \end{align}
     585              :  *
     586              :  * where we are now defining $\rho$ to be
     587              :  *
     588              :  * \begin{align}
     589              :  *   \rho = \sqrt{(\Xi - x_0/L)^2 + (\mathrm{H} - y_0/L)^2 + (1 - z_0/L)^2}.
     590              :  *   \label{eq:generalized_rho}
     591              :  * \end{align}
     592              :  *
     593              :  * This map is often written as:
     594              :  *
     595              :  * \begin{align}
     596              :  *   \vec{x} - \vec{x}_0 =
     597              :  *       \left\{\frac{F(\zeta)}{\sqrt{3}} +
     598              :  *       \frac{S(\zeta)}{\rho}\right\}(\vec{\sigma}_0 - \vec{x}_0/L),
     599              :  *    \label{eq:focally_lifted_map_with_s_and_f_factors}
     600              :  * \end{align}
     601              :  *
     602              :  * where $\vec{\sigma}_0 = [\Xi, \mathrm{H},1]^T$, as the parent surface
     603              :  * $\vec{\sigma}_{parent}$ is now $L\vec{\sigma}_0$. We give the quantity in
     604              :  * braces the name $z_{\Lambda} = L\Lambda$, *generalized z*. With this
     605              :  * definition, we can rewrite
     606              :  * Eq. ($\ref{eq:focally_lifted_map_with_s_and_f_factors}$) in the simpler form,
     607              :  *
     608              :  * \begin{align}
     609              :  *   \vec{x} - \vec{x}_0 = z_{\Lambda}(\vec{\sigma}_0 - \vec{x}_0/L).
     610              :  *   \label{eq:focally_lifted_map_with_generalized_z_coef}
     611              :  * \end{align}
     612              :  *
     613              :  * \note In the offset case, the frustum factor $F(\zeta)$ and sphere factor
     614              :  * $S(\zeta)$ (Eqs. ($\ref{eq:frustum_factor}$) and ($\ref{eq:sphere_factor}$))
     615              :  * for a linear radial distribution are no longer defined by the general $F_0$,
     616              :  * $F_1$, $S_0$, and $S_1$ given by Eqs.
     617              :  * ($\ref{eq:frustum_zero_linear}$), ($\ref{eq:frustum_rate_linear}$),
     618              :  * ($\ref{eq:sphere_zero_linear}$), and ($\ref{eq:sphere_rate_linear}$). In the
     619              :  * offset case, the inner surface must be spherical $(s_{inner} = 1)$ and the
     620              :  * outer surface can only be spherical or flat
     621              :  * $(s_{outer} = 0 \textrm{ or } s_{outer} = 1)$. In the case where
     622              :  * $s_{outer} = 0$, $L/\sqrt{3}$ is taken to be $R_{outer}$.
     623              :  *
     624              :  * The map can be inverted by first solving for \f$z_{\Lambda}\f$ in terms of
     625              :  * the target coordinates. We make use of the fact that the parent surface
     626              :  * $\vec{\sigma}_{parent}$ has a constant normal vector $\hat{n} = \hat{z}$.
     627              :  *
     628              :  * \begin{align}
     629              :  *   z_{\Lambda} = \frac{(\vec{x} - \vec{x}_0)\cdot\hat{n}}
     630              :  *                      {(\vec{\sigma}_0-\vec{x}_0/L)\cdot\hat{n}}.
     631              :  * \end{align}
     632              :  *
     633              :  * In other words, when $\hat{n} = \hat{z}$,
     634              :  *
     635              :  * \begin{align}
     636              :  *   z_{\Lambda} = \left\{\frac{F(\zeta)}{\sqrt{3}} +
     637              :  *                 \frac{S(\zeta)}{\rho}\right\}
     638              :  *               = \frac{z - z_0}{1 - z_0/L}
     639              :  * \end{align}
     640              :  *
     641              :  * Moving all the known quantities in
     642              :  * Eq. ($\ref{eq:focally_lifted_map_with_generalized_z_coef}$) to the left hand
     643              :  * side results in the following expression that solves for the source
     644              :  * coordinates $\xi$ and $\eta$ in terms of the target coordinates:
     645              :  *
     646              :  * \begin{align}
     647              :  *   \frac{\vec{x} - \vec{x}_0}{z_{\Lambda}} + \frac{\vec{x}_0}{L}
     648              :  *        = \vec{\sigma}_0(\xi,\eta)
     649              :  *        = \begin{bmatrix}
     650              :  *            \Xi \\
     651              :  *            \mathrm{H} \\
     652              :  *            1 \\
     653              :  *          \end{bmatrix},
     654              :  * \end{align}
     655              :  *
     656              :  * Note that $|\vec{\sigma}_0 - \vec{x}_0/L| = \sqrt{(\Xi - x_0/L)^2 +
     657              :  * (\mathrm{H} - y_0/L)^2 + (1 - z_0/L)^2} = \rho$, indicating that an
     658              :  * expression for $\rho$ in terms of the target coordinates can be computed via
     659              :  * taking the magnitude of both sides of
     660              :  * Eq. ($\ref{eq:focally_lifted_map_with_generalized_z_coef}$):
     661              :  *
     662              :  * \begin{align}
     663              :  *   |\vec{x} - \vec{x}_0| = z_{\Lambda}|\vec{\sigma}_0 - \vec{x}_0/L|
     664              :  *                         = z_{\Lambda}\rho.
     665              :  * \end{align}
     666              :  *
     667              :  * The quantity $\rho$ is then given by:
     668              :  *
     669              :  * \begin{align}
     670              :  *   \rho = \frac{|\vec{x} - \vec{x}_0|}{z_{\Lambda}}.
     671              :  * \end{align}
     672              :  *
     673              :  * With $\rho$ computed, the radial source coordinate $\zeta$ can be computed
     674              :  * from
     675              :  *
     676              :  * \begin{align}
     677              :  *   z_{\Lambda} = \left\{\frac{F(\zeta)}{\sqrt{3}} +
     678              :  *                 \frac{S(\zeta)}{\rho} \right\}
     679              :  *               = \left\{\frac{F_0}{\sqrt{3}} + \frac{S_0}{\rho} +
     680              :  *                 \frac{F_1\zeta}{\sqrt{3}} + \frac{S_1\zeta}{\rho}\right\},
     681              :  * \end{align}
     682              :  *
     683              :  * which gives
     684              :  *
     685              :  * \begin{align}
     686              :  *   \zeta = \frac{z_{\Lambda} -
     687              :  *                 \left(\frac{F_0}{\sqrt{3}} + \frac{S_0}{\rho}\right)}
     688              :  *                {\left(\frac{F_1}{\sqrt{3}} + \frac{S_1}{\rho}\right)}.
     689              :  * \end{align}
     690              :  *
     691              :  * To compute the Jacobian, it is useful to first note that $\rho$
     692              :  * (Eq. ($\ref{eq:generalized_rho}$)) is the magnitude of the vector
     693              :  *
     694              :  * \begin{align}
     695              :  *   \vec{\rho} = \vec{\sigma}_0 - \vec{x}_0/L
     696              :  *              = \begin{bmatrix}
     697              :  *                  \Xi - x_0/L \\
     698              :  *                  \mathrm{H} - y_0/L \\
     699              :  *                  1 - z_0/L
     700              :  *                \end{bmatrix}
     701              :  * \end{align}
     702              :  *
     703              :  * and that we can express the target coordinates in
     704              :  * Eq. ($\ref{eq:focally_lifted_map_with_generalized_z_coef}$) in terms of the
     705              :  * components of $\vec{\rho}$:
     706              :  *
     707              :  * \begin{align}
     708              :  *   x &= z_{\Lambda}\rho_x + x_0 \\
     709              :  *   y &= z_{\Lambda}\rho_y + y_0 \\
     710              :  *   z &= z_{\Lambda}\rho_z + z_0
     711              :  * \end{align}
     712              :  *
     713              :  * Some common terms used in the Jacobian are the derivatives of $z_{\Lambda}$
     714              :  * with respect to the source coordinates:
     715              :  *
     716              :  * \begin{align}
     717              :  *   \partial_{\xi}z_{\Lambda} &=
     718              :  *       \frac{-S(\zeta)\Xi'\rho_x}{\rho^3} \\
     719              :  *   \partial_{\eta}z_{\Lambda} &=
     720              :  *       \frac{-S(\zeta)\mathrm{H}'\rho_y}{\rho^3} \\
     721              :  *   \partial_{\zeta}z_{\Lambda} &=
     722              :  *       \frac{F'(\zeta)}{\sqrt{3}} + \frac{S'(\zeta)}{\rho}
     723              :  * \end{align}
     724              :  *
     725              :  * The Jacobian then is:
     726              :  *
     727              :  * \begin{align}
     728              :  *   J =
     729              :  *       \begin{bmatrix}
     730              :  *         \Xi'z_{\Lambda} + \rho_x\partial_{\xi}z_{\Lambda} &
     731              :  *             \rho_x\partial_{\eta}z_{\Lambda} &
     732              :  *             \rho_x\partial_{\zeta}z_{\Lambda} \\
     733              :  *         \rho_y\partial_{\xi}z_{\Lambda} &
     734              :  *             \mathrm{H}'z_{\Lambda} + \rho_y\partial_{\eta}z_{\Lambda} &
     735              :  *             \rho_y\partial_{\zeta}z_{\Lambda} \\
     736              :  *         \rho_z\partial_{\xi}z_{\Lambda} &
     737              :  *             \rho_z\partial_{\eta}z_{\Lambda} &
     738              :  *             \rho_z\partial_{\zeta}z_{\Lambda} \\
     739              :  *       \end{bmatrix}
     740              :  * \end{align}
     741              :  *
     742              :  * A common factor that shows up in this inverse Jacobian is:
     743              :  *
     744              :  * \begin{align}
     745              :  *   T:= \frac{S(\zeta)}{(\partial_{\zeta}z_{\Lambda})\rho^3}
     746              :  * \end{align}
     747              :  *
     748              :  * And the inverse Jacobian is then:
     749              :  *
     750              :  * \begin{align}
     751              :  *   J^{-1} =
     752              :  *       \frac{1}{z_{\Lambda}}\begin{bmatrix}
     753              :  *         \Xi'^{-1} & 0 & -\rho_x(\Xi'\rho_z)^{-1} \\
     754              :  *         0 & \mathrm{H}'^{-1} & -\rho_y(\mathrm{H}'\rho_z)^{-1} \\
     755              :  *         T\rho_x & T\rho_y &
     756              :  *             T\rho_z + F(\partial_{\zeta}z_{\Lambda}\rho_z)^{-1}/\sqrt{3}
     757              :  *       \end{bmatrix}
     758              :  * \end{align}
     759              :  *
     760              :  * ### Offsetting a Rotated Wedge
     761              :  * The default Wedge map is oriented in the $+z$ direction, so the
     762              :  * construction of a Wedge oriented along a different direction requires an
     763              :  * additional OrientationMap $R$ to be passed to `orientation_of_wedge`. When
     764              :  * offsetting a rotated Wedge, the coordinates passed as parameters to
     765              :  * `focal_offset` are in the coordinate frame in which the Wedge is rotated
     766              :  * (the target frame). However, the focal lifting procedure (shown in
     767              :  * Eq. ($\ref{eq:focal_lifting}$)) is done in the default frame in which the
     768              :  * Wedge is facing the $+z$ direction, so the focal offset $\vec{x}_0$ is first
     769              :  * hit by the inverse rotation $R^{-1}$ and then the rotated focus
     770              :  * $R^{-1}\vec{x}_0$ is used internally as the focus for the $+z$ Wedge. When
     771              :  * the focal lifting calculation has completed, the rotation of the $+z$ Wedge
     772              :  * into the desired orientation by $R$ also rotates the focus into the desired
     773              :  * location. When performing the inverse operation, the focus is similarly
     774              :  * rotated into the default frame, where the inversion is performed.
     775              :  *
     776              :  * ### Interaction between opening angles and focal offsets
     777              :  * When a Wedge is created with a non-zero focal offset, the resulting shape
     778              :  * can take on a variety of possible angular sizes, depending on where the
     779              :  * focus is placed relative to the default centered location. The reader might
     780              :  * note that the angular size of a Wedge can also be controlled by passing an
     781              :  * argument to the `opening_angles` parameter in the Wedge constructor. While
     782              :  * both of these methods allow the angular size of a Wedge to be changed, the
     783              :  * user is prevented from employing both of them at the same time. In
     784              :  * particular, when the the offset is set to some non-zero value, the
     785              :  * `opening_angles_` member variable is set to $\pi/2$. Note that the
     786              :  * `opening_angles_` member being set to $\pi/2$ does not imply the
     787              :  * resulting Wedge will have an angular size of $\pi/2$. On the contrary, the
     788              :  * Wedge will have the angular size that is determined by the application of
     789              :  * the focal lifting method on the parent surface, which is the upper $+z$ face
     790              :  * of a cube that is centered at the origin.
     791              :  *
     792              :  * Because `opening_angles_` is set to $\pi/2$ when there is a non-zero focal
     793              :  * offset, when there is a non-zero focal offset and `with_equiangular_map_` is
     794              :  * `true`, $\Xi$ is given by Eq. ($\ref{eq:equiangular_xi_pi_over_2}$) and
     795              :  * $\mathrm{H}$ by Eq. ($\ref{eq:equiangular_eta_pi_over_2}$), just as it is
     796              :  * for the case of a centered Wedge with `opening_angles_` of $\pi/2$.
     797              :  */
     798              : template <size_t Dim>
     799            1 : class Wedge {
     800              :  public:
     801            0 :   static constexpr size_t dim = Dim;
     802            0 :   enum class WedgeHalves {
     803              :     /// Use the entire wedge
     804              :     Both,
     805              :     /// Use only the upper logical half
     806              :     UpperOnly,
     807              :     /// Use only the lower logical half
     808              :     LowerOnly
     809              :   };
     810              : 
     811              :   /*!
     812              :    * \brief Constructs a centered wedge (one with no focal offset)
     813              :    *
     814              :    * \param radius_inner Distance from the origin to one of the corners which
     815              :    * lie on the inner surface.
     816              :    * \param radius_outer Distance from the origin to one of the corners which
     817              :    * lie on the outer surface.
     818              :    * \param orientation_of_wedge The orientation of the desired wedge relative
     819              :    * to the orientation of the default wedge which is a wedge that has its
     820              :    * curved surfaces pierced by the upper-z axis. The logical $\xi$ and $\eta$
     821              :    * coordinates point in the cartesian x and y directions, respectively.
     822              :    * \param sphericity_inner Value between 0 and 1 which determines
     823              :    * whether the inner surface is flat (value of 0), spherical (value of 1) or
     824              :    * somewhere in between.
     825              :    * \param sphericity_outer Value between 0 and 1 which determines
     826              :    * whether the outer surface is flat (value of 0), spherical (value of 1) or
     827              :    * somewhere in between.
     828              :    * \param with_equiangular_map Determines whether to apply a tangent function
     829              :    * mapping to the logical coordinates (for `true`) or not (for `false`).
     830              :    * \param halves_to_use Determines whether to construct a full wedge or only
     831              :    * half a wedge. If constructing only half a wedge, the resulting shape has a
     832              :    * face normal to the x direction (assuming default OrientationMap). If
     833              :    * constructing half a wedge, an intermediate affine map is applied to the
     834              :    * logical xi coordinate such that the interval [-1,1] is mapped to the
     835              :    * corresponding logical half of the wedge. For example, if `UpperOnly` is
     836              :    * specified, [-1,1] is mapped to [0,1], and if `LowerOnly` is specified,
     837              :    * [-1,1] is mapped to [-1,0]. The case of `Both` means a full wedge, with no
     838              :    * intermediate map applied. In all cases, the logical points returned by the
     839              :    * inverse map will lie in the range [-1,1] in each dimension. Half wedges are
     840              :    * currently only useful in constructing domains for binary systems.
     841              :    * \param radial_distribution Determines how to distribute gridpoints along
     842              :    * the radial direction. For wedges that are not exactly spherical, only
     843              :    * `Distribution::Linear` is currently supported.
     844              :    * \param opening_angles Determines the angular size of the wedge. The default
     845              :    * value is $\pi/2$, which corresponds to a wedge size of $\pi/2$. For this
     846              :    * setting, four Wedges can be put together to cover $2\pi$ in angle along a
     847              :    * great circle. This option is meant to be used with the equiangular map
     848              :    * option turned on.
     849              :    * \param with_adapted_equiangular_map Determines whether to adapt the
     850              :    * point distribution in the wedge to match its physical angular size. When
     851              :    * `true`, angular distances are proportional to logical distances. Note
     852              :    * that it is not possible to use adapted maps in every Wedge of a Sphere
     853              :    * unless each Wedge has the same size along both angular directions.
     854              :    */
     855            1 :   Wedge(double radius_inner, double radius_outer, double sphericity_inner,
     856              :         double sphericity_outer, OrientationMap<Dim> orientation_of_wedge,
     857              :         bool with_equiangular_map,
     858              :         WedgeHalves halves_to_use = WedgeHalves::Both,
     859              :         Distribution radial_distribution = Distribution::Linear,
     860              :         const std::array<double, Dim - 1>& opening_angles =
     861              :             make_array<Dim - 1>(M_PI_2),
     862              :         bool with_adapted_equiangular_map = true);
     863              : 
     864              :   /*!
     865              :    * \brief Constructs a wedge with a focal offset
     866              :    *
     867              :    * \details Can construct an offset Wedge with a spherical inner surface and
     868              :    * either a spherical or a flat outer surface. If `radius_outer` has a value,
     869              :    * a spherical Wedge will be constructed, and if not, a flat one will be
     870              :    * constructed.
     871              :    *
     872              :    * Note that because the focal offset is what determines the angular size of
     873              :    * the Wedge, opening angles cannot be used with offset Wedges.
     874              :    *
     875              :    * In the event that `focal_offset` happens to be zero, the Wedge's member
     876              :    * variables and behavior will be set up to be equivalent to that of a
     877              :    * centered Wedge:
     878              :    * - `cube_half_length` will be ignored
     879              :    * - if `radius_outer` is `std::nullopt`, the outer radius of the Wedge will
     880              :    * be set to $\sqrt{\mathrm{Dim}}L$, where $L$ is the `cube_half_length`
     881              :    * - the opening angles ($\theta_O$) and opening angles distribution
     882              :    * ($\theta_D$) used will be $\pi/2$
     883              :    *
     884              :    * \param radius_inner Distance from the origin to one of the corners which
     885              :    * lie on the inner surface.
     886              :    * \param radius_outer If this has a value, it creates a spherical Wedge
     887              :    * (inner and outer sphericity are 1) where this is the distance from the
     888              :    * origin to one of the corners that lie on the inner surface. If this is
     889              :    * `std::nullopt`, it creates a Wedge with a flat outer surface
     890              :    * (inner sphericity is 1 and outer sphericity is 0). In the event that
     891              :    * `radius_outer == std::nullopt` **and** `focal_offset` is zero,
     892              :    * the outer radius will instead be set to $\sqrt{\mathrm{Dim}}L$, where $L$
     893              :    * is the `cube_half_length`. The outer radius is given a value in this
     894              :    * circumstance so that it can be handled as a centered Wedge (one with no
     895              :    * offset).
     896              :    * \param orientation_of_wedge The orientation of the desired wedge relative
     897              :    * to the orientation of the default wedge which is a wedge that has its
     898              :    * curved surfaces pierced by the upper-z axis. The logical $\xi$ and $\eta$
     899              :    * coordinates point in the cartesian x and y directions, respectively.
     900              :    * \param cube_half_length Half the length of the parent surface (see Wedge
     901              :    * documentation for more details). If `focal_offset` is zero, this
     902              :    * parameter has no effect and is ignored so that the Wedge can be handled
     903              :    * as a centered Wedge (one with no offset).
     904              :    * \param focal_offset The target frame coordinates of the focus from which
     905              :    * the Wedge is focally lifted.
     906              :    * \param with_equiangular_map Determines whether to apply a tangent function
     907              :    * mapping to the logical coordinates (for `true`) or not (for `false`).
     908              :    * \param halves_to_use Determines whether to construct a full wedge or only
     909              :    * half a wedge. If constructing only half a wedge, the resulting shape has a
     910              :    * face normal to the x direction (assuming default OrientationMap). If
     911              :    * constructing half a wedge, an intermediate affine map is applied to the
     912              :    * logical xi coordinate such that the interval [-1,1] is mapped to the
     913              :    * corresponding logical half of the wedge. For example, if `UpperOnly` is
     914              :    * specified, [-1,1] is mapped to [0,1], and if `LowerOnly` is specified,
     915              :    * [-1,1] is mapped to [-1,0]. The case of `Both` means a full wedge, with no
     916              :    * intermediate map applied. In all cases, the logical points returned by the
     917              :    * inverse map will lie in the range [-1,1] in each dimension. Half wedges are
     918              :    * currently only useful in constructing domains for binary systems.
     919              :    * \param radial_distribution Determines how to distribute gridpoints along
     920              :    * the radial direction. For wedges that are not exactly spherical, only
     921              :    * `Distribution::Linear` is currently supported.
     922              :    */
     923            1 :   Wedge(double radius_inner, std::optional<double> radius_outer,
     924              :         double cube_half_length, std::array<double, Dim> focal_offset,
     925              :         OrientationMap<Dim> orientation_of_wedge, bool with_equiangular_map,
     926              :         WedgeHalves halves_to_use = WedgeHalves::Both,
     927              :         Distribution radial_distribution = Distribution::Linear);
     928              : 
     929            0 :   Wedge() = default;
     930            0 :   ~Wedge() = default;
     931            0 :   Wedge(Wedge&&) = default;
     932            0 :   Wedge(const Wedge&) = default;
     933            0 :   Wedge& operator=(const Wedge&) = default;
     934            0 :   Wedge& operator=(Wedge&&) = default;
     935              : 
     936              :   template <typename T>
     937            0 :   std::array<T, Dim> operator()(const std::array<T, Dim>& source_coords) const;
     938              : 
     939              :   /// For a \f$+z\f$-oriented `Wedge`, returns invalid if \f$z<=0\f$
     940              :   /// or if \f$(x,y,z)\f$ is on or outside the cone defined
     941              :   /// by \f$(x^2/z^2 + y^2/z^2+1)^{1/2} = -S/F\f$, where
     942              :   /// \f$S = \frac{1}{2}(s_1 r_1 - s_0 r_0)\f$ and
     943              :   /// \f$F = \frac{1}{2\sqrt{3}}((1-s_1) r_1 - (1-s_0) r_0)\f$.
     944              :   /// Here \f$s_0,s_1\f$ and \f$r_0,r_1\f$ are the specified sphericities
     945              :   /// and radii of the inner and outer \f$z\f$ surfaces.  The map is singular on
     946              :   /// the cone and on the xy plane.
     947              :   /// The inverse function is only callable with doubles because the inverse
     948              :   /// might fail if called for a point out of range, and it is unclear
     949              :   /// what should happen if the inverse were to succeed for some points in a
     950              :   /// DataVector but fail for other points.
     951            1 :   std::optional<std::array<double, Dim>> inverse(
     952              :       const std::array<double, Dim>& target_coords) const;
     953              : 
     954              :   template <typename T>
     955            0 :   tnsr::Ij<T, Dim, Frame::NoFrame> jacobian(
     956              :       const std::array<T, Dim>& source_coords) const;
     957              : 
     958              :   template <typename T>
     959            0 :   tnsr::Ij<T, Dim, Frame::NoFrame> inv_jacobian(
     960              :       const std::array<T, Dim>& source_coords) const;
     961              : 
     962              :   // NOLINTNEXTLINE(google-runtime-references)
     963            0 :   void pup(PUP::er& p);
     964              : 
     965            0 :   static constexpr bool is_identity() { return false; }
     966              : 
     967            0 :   static constexpr bool supports_hessian{true};
     968              : 
     969              :  private:
     970              :   // maps between 2D and 3D choices for coordinate axis orientations
     971            0 :   static constexpr size_t radial_coord =
     972              :       detail::WedgeCoordOrientation<Dim>::radial_coord;
     973            0 :   static constexpr size_t polar_coord =
     974              :       detail::WedgeCoordOrientation<Dim>::polar_coord;
     975            0 :   static constexpr size_t azimuth_coord =
     976              :       detail::WedgeCoordOrientation<Dim>::azimuth_coord;
     977              : 
     978              :   /*!
     979              :    * \brief Factors out the calculation of \f$\Xi(\xi)\f$ and $\mathrm{H}$
     980              :    *
     981              :    * \details The **equidistant** parametrization
     982              :    * (when `with_equiangular_map_ == false`) of the logical coordinates is
     983              :    *
     984              :    * \f{align*}{
     985              :    *   \Xi(\xi) = \xi.
     986              :    * \f}
     987              :    *
     988              :    * The **equiangular** reparametrization
     989              :    * (when `with_equiangular_map_ == true`) of the logical coordinates is
     990              :    *
     991              :    * \f{align*}{
     992              :    *   \Xi(\xi) =
     993              :    *       \tan{(\theta_O/2)}\frac{\tan{(\theta_D \xi/2)}}{\tan{(\theta_D/2)}},
     994              :    * \f}
     995              :    *
     996              :    * where $\theta_O$ (element of `opening_angles_`) and $\theta_D$
     997              :    * (element of `opening_angles_distribution_`) are described in the Wedge
     998              :    * class documentation.
     999              :    *
    1000              :    * When `focal_offset_` is nonzero, the **equiangular** reparametrization
    1001              :    * is instead
    1002              :    *
    1003              :    * \f{align*}{
    1004              :    *   \Xi(\xi) = \tan{(\pi/4)}\xi
    1005              :    * \f}
    1006              :    *
    1007              :    * \tparam FuncIsXi whether the logical cooridnate `lowercase_xi_or_eta` is
    1008              :    * $\xi$ (polar coordinate) or $\eta$ (azimuthal coordinate)
    1009              :    * \param lowercase_xi_or_eta the logical coordinate $\xi$ or $\eta$ to map
    1010              :    */
    1011              :   template <bool FuncIsXi, typename T>
    1012            1 :   T get_cap_angular_function(const T& lowercase_xi_or_eta) const;
    1013              : 
    1014              :   /*!
    1015              :    * \brief Factors out the calculation of \f$\Xi'(\xi)\f$ and $\mathrm{H}'$
    1016              :    *
    1017              :    * \details Computes the derivatives of the quantities defined in
    1018              :    * `get_cap_angular_function()`.
    1019              :    *
    1020              :    * \tparam FuncIsXi whether the logical cooridnate `lowercase_xi_or_eta` is
    1021              :    * $\xi$ (polar coordinate) or $\eta$ (azimuthal coordinate)
    1022              :    * \param lowercase_xi_or_eta the logical coordinate $\xi$ or $\eta$ to map
    1023              :    */
    1024              :   template <bool FuncIsXi, typename T>
    1025            1 :   T get_deriv_cap_angular_function(const T& lowercase_xi_or_eta) const;
    1026              : 
    1027              :   /*!
    1028              :    * \brief Factors out the calculation of $\vec{\rho}$
    1029              :    *
    1030              :    * \details Computes
    1031              :    * \f{align*}{
    1032              :    *   \vec{\rho} = [\Xi-x_0/L, \mathrm{H}-y_0/L, 1-z_0/L]^T
    1033              :    * \f}
    1034              :    *
    1035              :    * where \f$\Xi\f$ and $\mathrm{H}$ are the logical coordinate maps defined in
    1036              :    * `get_cap_angular_function()` and the Wedge class documentation,
    1037              :    * \f$\vec{x_0} = [x_0, y_0, z_0]^T\f$ is the result of applying the inverse
    1038              :    * map of the `orientation_of_wedge_` on the `focal_offset_`, and $L$ is the
    1039              :    * `cube_half_length_`.
    1040              :    *
    1041              :    * \param rotated_focus the result of applying the inverse map of the
    1042              :    * `orientation_of_wedge_` on the `focal_offset_`
    1043              :    * \param cap the function(s) \f$\Xi\f$ (and $\mathrm{H}$ in 3D)
    1044              :    */
    1045              :   template <typename T>
    1046            1 :   std::array<T, Dim> get_rho_vec(const std::array<double, Dim>& rotated_focus,
    1047              :                                  const std::array<T, Dim - 1>& cap) const;
    1048              : 
    1049              :   /*!
    1050              :    * \brief Factors out the calculation of $1/\rho$
    1051              :    *
    1052              :    * \details Computes $1/\rho$ where
    1053              :    *
    1054              :    * \f{align*}{
    1055              :    *   \rho = \sqrt{(\Xi - x_0/L)^2 + (\mathrm{H} - y_0/L)^2 + (1 - z_0/L)^2}.
    1056              :    * \f}
    1057              :    *
    1058              :    * Here, \f$\Xi\f$ and $\mathrm{H}$ are the logical coordinate maps defined in
    1059              :    * `get_cap_angular_function()` and the Wedge class documentation,
    1060              :    * \f$\vec{x_0} = [x_0, y_0, z_0]^T\f$ is the result of applying the inverse
    1061              :    * map of the `orientation_of_wedge_` on the `focal_offset_`, and $L$ is the
    1062              :    * `cube_half_length_`.
    1063              :    *
    1064              :    * \param rotated_focus the result of applying the inverse map of the
    1065              :    * `orientation_of_wedge_` on the `focal_offset_`
    1066              :    * \param cap the function(s) \f$\Xi\f$ (and $\mathrm{H}$ in 3D)
    1067              :    */
    1068              :   template <typename T>
    1069            1 :   T get_one_over_rho(const std::array<double, Dim>& rotated_focus,
    1070              :                      const std::array<T, Dim - 1>& cap) const;
    1071              : 
    1072              :   /*!
    1073              :    * \brief Factors out the calculation of $S(\zeta)$ needed for the map and the
    1074              :    * Jacobian
    1075              :    *
    1076              :    * \details The value of $S(\zeta)$ is computed differently for different
    1077              :    * radial distributions.
    1078              :    *
    1079              :    * For a **linear** radial distribution:
    1080              :    *
    1081              :    * \f{align*}{
    1082              :    *   S(\zeta) = S_0 + S_1\zeta
    1083              :    * \f}
    1084              :    *
    1085              :    * where $S_0$ and $S_1$ are defined as
    1086              :    *
    1087              :    * \f{align*}{
    1088              :    *   S_0 &=
    1089              :    *       \frac{1}{2} \big\{
    1090              :    *         s_{outer}R_{outer} + s_{inner}R_{inner}
    1091              :    *       \big\} \\
    1092              :    *   S_1 &= \partial_{\zeta}S
    1093              :    *        = \frac{1}{2} \big\{ s_{outer}R_{outer} - s_{inner}R_{inner}\big\}
    1094              :    * \f}
    1095              :    *
    1096              :    * and are stored in `sphere_zero_` and `sphere_rate_`, respectively.
    1097              :    *
    1098              :    * For a **logarithmic** radial distribution:
    1099              :    *
    1100              :    * \f{align*}{
    1101              :    *   S(\zeta) = \exp{(S_0 + S_1\zeta)}
    1102              :    * \f}
    1103              :    *
    1104              :    * where $S_0$ and $S_1$ are defined as
    1105              :    *
    1106              :    * \f{align*}{
    1107              :    *   S_0 &= \frac{1}{2} \ln(R_{outer}R_{inner}) \\
    1108              :    *   S_1 &= \frac{1}{2} \ln(R_{outer}/R_{inner})
    1109              :    * \f}
    1110              :    *
    1111              :    * With these definitions of $S_0$ and $S_1$, we can rewrite the expression
    1112              :    * for $S(\zeta)$ as:
    1113              :    *
    1114              :    * \f{align*}{
    1115              :    *   S(\zeta) &= \sqrt{R_{inner}^{1-\zeta}R_{outer}^{1+\zeta}}
    1116              :    * \f}
    1117              :    *
    1118              :    * As with the linear distribution, $S_0$ and $S_1$ are stored in
    1119              :    * `sphere_zero_` and `sphere_rate_`, respectively.
    1120              :    *
    1121              :    * For an **inverse** radial distribution:
    1122              :    *
    1123              :    * \f{align*}{
    1124              :    *   S(\zeta) =
    1125              :    *       \frac{2R_{inner}R_{outer}}
    1126              :    *            {(1 + \zeta)R_{inner} + (1 - \zeta)R_{outer}}
    1127              :    * \f}
    1128              :    *
    1129              :    * In this case, `sphere_zero_` and `sphere_rate_` will simply be `NaN`.
    1130              :    *
    1131              :    * See Wedge for more details on these quantities.
    1132              :    *
    1133              :    * \param zeta the radial source coordinate
    1134              :    */
    1135              :   template <typename T>
    1136            1 :   T get_s_factor(const T& zeta) const;
    1137              :   /*!
    1138              :    * \brief Factors out the calculation of $S'(\zeta)$ needed for the Jacobian
    1139              :    *
    1140              :    * \details The value of $S'(\zeta)$ is computed differently for different
    1141              :    * radial distributions.
    1142              :    *
    1143              :    * For a **linear** radial distribution:
    1144              :    *
    1145              :    * \f{align*}{
    1146              :    *   S'(\zeta) =
    1147              :    *       \frac{1}{2} \big\{ s_{outer}R_{outer} - s_{inner}R_{inner}\big\}
    1148              :    * \f}
    1149              :    *
    1150              :    * For a **logarithmic** radial distribution:
    1151              :    *
    1152              :    * \f{align*}{
    1153              :    *   S'(\zeta) = \frac{1}{2} S(\zeta)\ln(R_{outer}/R_{inner})
    1154              :    * \f}
    1155              :    *
    1156              :    * where $S(\zeta)$ is defined in `get_s_factor()`.
    1157              :    *
    1158              :    * For an **inverse** radial distribution:
    1159              :    *
    1160              :    * \f{align*}{
    1161              :    *   S'(\zeta) =
    1162              :    *       \frac{2(R_{inner} R_{outer}^2 - R_{inner}^2 R_{outer})}
    1163              :    *            {(R_{inner} + R_{outer} + \zeta(R_{inner} - R_{outer}))^2}
    1164              :    * \f}
    1165              :    *
    1166              :    * See Wedge and `get_s_factor()` for more details on these quantities.
    1167              :    *
    1168              :    * \param zeta the radial source coordinate
    1169              :    * \param s_factor $S(\zeta)$ (see `get_s_factor()`)
    1170              :    */
    1171              :   template <typename T>
    1172            1 :   T get_s_factor_deriv(const T& zeta, const T& s_factor) const;
    1173              : 
    1174              :   /*!
    1175              :    * \brief Factors out the calculation of $z_{\Lambda}$ needed for the map and
    1176              :    * the Jacobian
    1177              :    *
    1178              :    * \details The value of $z_{\Lambda}$  is computed differently for different
    1179              :    * radial distributions.
    1180              :    *
    1181              :    * For a **linear** radial distribution:
    1182              :    *
    1183              :    * \f{align*}{
    1184              :    *   z_{\Lambda} = \frac{F(\zeta)}{\sqrt 3} + \frac{S(\zeta)}{\rho}
    1185              :    * \f}
    1186              :    *
    1187              :    * For a **logarithmic** or **inverse** radial distribution:
    1188              :    *
    1189              :    * \f{align*}{
    1190              :    *   z_{\Lambda} = \frac{S(\zeta)}{\rho}
    1191              :    * \f}
    1192              :    *
    1193              :    * See Wedge and `get_s_factor()` for more details on these quantities.
    1194              :    *
    1195              :    * \param zeta the radial source coordinate
    1196              :    * \param one_over_rho one over $\rho$ where
    1197              :    * $\rho = |\vec{\sigma}_0 - \vec{x}_0/L| = \sqrt{(\Xi - x_0/L)^2 +
    1198              :    * (\mathrm{H} - y_0/L)^2 + (1 - z_0/L)^2}$ (see Wedge)
    1199              :    * \param s_factor $S(\zeta)$ (see `get_s_factor()`)
    1200              :    */
    1201              :   template <typename T>
    1202            1 :   T get_generalized_z(const T& zeta, const T& one_over_rho,
    1203              :                       const T& s_factor) const;
    1204              :   template <typename T>
    1205            0 :   T get_generalized_z(const T& zeta, const T& one_over_rho) const;
    1206              :   /*!
    1207              :    * \brief Factors out the calculation of $\partial_i z_{\Lambda}$ needed for
    1208              :    * the Jacobian
    1209              :    *
    1210              :    * \details For **all** radial distributions:
    1211              :    *
    1212              :    * \f{align*}{
    1213              :    *   \partial_{\xi} z_{\Lambda} &=
    1214              :    *       \frac{-S(\zeta)\Xi'\rho_x}{\rho^3} \\
    1215              :    *   \partial_{\eta} z_{\Lambda} &=
    1216              :    *       \frac{-S(\zeta)\mathrm{H}'\rho_y}{\rho^3} \\
    1217              :    *   \partial_{\zeta} z_{\Lambda} &=
    1218              :    *       \frac{F'(\zeta)}{\sqrt 3} + \frac{S'(\zeta)}{\rho}
    1219              :    * \f}
    1220              :    *
    1221              :    * However, $\partial_{\zeta} z_{\Lambda}$ reduces to
    1222              :    *
    1223              :    * \f{align*}{
    1224              :    *   \partial_{\zeta} z_{\Lambda} &= \frac{S'(\zeta)}{\rho}
    1225              :    * \f}
    1226              :    *
    1227              :    * for **logarithmic** and **inverse** radial distributions because
    1228              :    * $F(\zeta) = 0$.
    1229              :    *
    1230              :    * See Wedge and `get_s_factor()` for more details on these quantities.
    1231              :    *
    1232              :    * \param zeta the radial source coordinate
    1233              :    * \param one_over_rho one over $\rho$ where
    1234              :    * $\rho = |\vec{\sigma}_0 - \vec{x}_0/L| = \sqrt{(\Xi - x_0/L)^2 +
    1235              :    * (\mathrm{H} - y_0/L)^2 + (1 - z_0/L)^2}$ (see Wedge)
    1236              :    * \param s_factor $S(\zeta)$ (see `get_s_factor()`)
    1237              :    * \param cap_deriv $\Xi'$ and $\mathrm{H}'$ (see Wedge)
    1238              :    * \param rho_vec $\vec{\rho} = [\Xi-x_0/L, \mathrm{H}-y_0/L, 1-z_0/L]^T$
    1239              :    * (see Wedge)
    1240              :    */
    1241              :   template <typename T>
    1242            1 :   std::array<T, Dim> get_d_generalized_z(
    1243              :       const T& zeta, const T& one_over_rho, const T& s_factor,
    1244              :       const std::array<T, Dim - 1>& cap_deriv,
    1245              :       const std::array<T, Dim>& rho_vec) const;
    1246              : 
    1247              :   template <size_t LocalDim>
    1248              :   // NOLINTNEXTLINE(readability-redundant-declaration)
    1249            0 :   friend bool operator==(const Wedge<LocalDim>& lhs,
    1250              :                          const Wedge<LocalDim>& rhs);
    1251              : 
    1252              :   /// Distance from the origin to one of the corners which lie on the inner
    1253              :   /// surface.
    1254            1 :   double radius_inner_{std::numeric_limits<double>::signaling_NaN()};
    1255              :   /// If this contains a value, it is the distance from the `focal_offset` to
    1256              :   /// one of the corners that lie on the outer surface. Set to `std::nullopt`
    1257              :   /// when `focal_offset` is nonzero and the outer surface is flat, because
    1258              :   /// there is no single outer radius like there is for a centered Wedge or a
    1259              :   /// spherical offset Wedge.
    1260            1 :   std::optional<double> radius_outer_ = std::nullopt;
    1261              :   /// Value between 0 and 1 which determines whether the inner surface is flat
    1262              :   /// (value of 0), spherical (value of 1) or somewhere in between. If
    1263              :   /// `focal_offset` is nonzero, `sphericity_inner` must be `1.0`.
    1264            1 :   double sphericity_inner_{std::numeric_limits<double>::signaling_NaN()};
    1265              :   /// Value between 0 and 1 which determines whether the outer surface is flat
    1266              :   /// (value of 0), spherical (value of 1) or somewhere in between. If
    1267              :   /// `focal_offset` is nonzero, `sphericity_outer` must be `0.0` or `1.0`.
    1268            1 :   double sphericity_outer_{std::numeric_limits<double>::signaling_NaN()};
    1269              :   /// Half the length of the parent surface (see Wedge documentation for more
    1270              :   /// details). This parameter has no effect and is set to `std::nullopt` when
    1271              :   /// `focal_offset` is zero.
    1272            1 :   std::optional<double> cube_half_length_ = std::nullopt;
    1273              :   /// The target frame coordinates of the focus from which the Wedge is focally
    1274              :   /// lifted.
    1275            1 :   std::array<double, Dim> focal_offset_{
    1276              :       make_array<Dim>(std::numeric_limits<double>::signaling_NaN())};
    1277              :   /// The orientation of the desired wedge relative to the orientation of the
    1278              :   /// default wedge which is a wedge that has its curved surfaces pierced by the
    1279              :   /// upper-z axis. The logical $\xi$ and $\eta$ coordinates point in the
    1280              :   /// cartesian x and y directions, respectively.
    1281            1 :   OrientationMap<Dim> orientation_of_wedge_ =
    1282              :       OrientationMap<Dim>::create_aligned();
    1283              :   /// Determines whether to apply a tangent function mapping to the logical
    1284              :   /// coordinates (for `true`) or not (for `false`).
    1285            1 :   bool with_equiangular_map_ = false;
    1286              :   /// Determines whether to construct a full wedge or only half a wedge (see
    1287              :   /// Wedge documentation for more details)
    1288            1 :   WedgeHalves halves_to_use_ = WedgeHalves::Both;
    1289              :   /// Determines how to distribute gridpoints along the radial direction. For
    1290              :   /// wedges that are not exactly spherical, only `Distribution::Linear` is
    1291              :   /// currently supported.
    1292            1 :   Distribution radial_distribution_ = Distribution::Linear;
    1293              :   /// $F_0 / \sqrt{3}$ (see Wedge documentation)
    1294            1 :   double scaled_frustum_zero_{std::numeric_limits<double>::signaling_NaN()};
    1295              :   /// $S_0$ (see Wedge documentation)
    1296            1 :   double sphere_zero_{std::numeric_limits<double>::signaling_NaN()};
    1297              :   /// $F_1 / \sqrt{3}$ (see Wedge documentation)
    1298            1 :   double scaled_frustum_rate_{std::numeric_limits<double>::signaling_NaN()};
    1299              :   /// $S_1$ (see Wedge documentation)
    1300            1 :   double sphere_rate_{std::numeric_limits<double>::signaling_NaN()};
    1301              :   /// $\theta_O$ (see Wedge documentation). Set to `std::nullopt` when
    1302              :   /// `focal_offset_` is nonzero.
    1303            1 :   std::optional<std::array<double, Dim - 1>> opening_angles_ = std::nullopt;
    1304              :   /// $\theta_D$ (see Wedge documentation). Set to `std::nullopt` when
    1305              :   /// `focal_offset_` is nonzero.
    1306            1 :   std::optional<std::array<double, Dim - 1>> opening_angles_distribution_ =
    1307              :       std::nullopt;
    1308              : };
    1309              : 
    1310              : template <size_t Dim>
    1311            0 : bool operator!=(const Wedge<Dim>& lhs, const Wedge<Dim>& rhs);
    1312              : }  // namespace domain::CoordinateMaps
        

Generated by: LCOV version 2.0-1