SpECTRE Documentation Coverage Report
Current view: top level - Domain/CoordinateMaps - Wedge.hpp Hit Total Coverage
Commit: 1f2210958b4f38fdc0400907ee7c6d5af5111418 Lines: 28 48 58.3 %
Date: 2025-12-05 05:03:31
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14