SpECTRE Documentation Coverage Report
Current view: top level - Evolution/Systems/Cce/Initialize - InitializeJ.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 16.7 % 30 5
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 <cmath>
       7              : #include <cstddef>
       8              : #include <cstdint>
       9              : #include <limits>
      10              : #include <memory>
      11              : #include <vector>
      12              : 
      13              : #include "DataStructures/ComplexDataVector.hpp"
      14              : #include "DataStructures/DataBox/DataBox.hpp"
      15              : #include "DataStructures/DataVector.hpp"
      16              : #include "DataStructures/SpinWeighted.hpp"
      17              : #include "DataStructures/Tags.hpp"
      18              : #include "DataStructures/Tensor/TypeAliases.hpp"
      19              : #include "DataStructures/Variables.hpp"
      20              : #include "Evolution/Systems/Cce/GaugeTransformBoundaryData.hpp"
      21              : #include "Evolution/Systems/Cce/Tags.hpp"
      22              : #include "NumericalAlgorithms/Interpolation/SpanInterpolator.hpp"
      23              : #include "NumericalAlgorithms/SpinWeightedSphericalHarmonics/SwshCollocation.hpp"
      24              : #include "NumericalAlgorithms/SpinWeightedSphericalHarmonics/SwshDerivatives.hpp"
      25              : #include "NumericalAlgorithms/SpinWeightedSphericalHarmonics/SwshInterpolation.hpp"
      26              : #include "NumericalAlgorithms/SpinWeightedSphericalHarmonics/SwshTags.hpp"
      27              : #include "Options/String.hpp"
      28              : #include "Parallel/NodeLock.hpp"
      29              : #include "Utilities/CallWithDynamicType.hpp"
      30              : #include "Utilities/Gsl.hpp"
      31              : #include "Utilities/Serialization/CharmPupable.hpp"
      32              : #include "Utilities/TMPL.hpp"
      33              : 
      34              : /// \cond
      35              : namespace Cce::Solutions::LinearizedBondiSachs_detail::InitializeJ {
      36              : struct LinearizedBondiSachs;
      37              : }  // namespace Cce::Solutions::LinearizedBondiSachs_detail::InitializeJ
      38              : /// \endcond
      39              : 
      40              : namespace Cce {
      41              : /// Contains utilities and \ref DataBoxGroup mutators for generating data for
      42              : /// \f$J\f$ on the initial CCE hypersurface.
      43              : namespace InitializeJ {
      44              : 
      45              : namespace detail {
      46              : // used to provide a default for the finalize functor in
      47              : // `iteratively_adapt_angular_coordinates`
      48              : struct NoOpFinalize {
      49              :   void operator()(
      50              :       const Scalar<SpinWeighted<ComplexDataVector, 2>>& /*gauge_c*/,
      51              :       const Scalar<SpinWeighted<ComplexDataVector, 0>>& /*gauge_d*/,
      52              :       const tnsr::i<DataVector, 2, ::Frame::Spherical<::Frame::Inertial>>&
      53              :       /*angular_cauchy_coordinates*/,
      54              :       const Spectral::Swsh::SwshInterpolator& /*interpolator*/) const {}
      55              : };
      56              : 
      57              : // eth of the Cartesian coordinates x^i on the identity grid. The angular solves
      58              : // interpolate these onto the current map at each iteration, rather than
      59              : // differentiating the displaced coordinates, which aliases and raises the
      60              : // residual floor.
      61              : void unit_sphere_eth_cartesian_coordinates(
      62              :     gsl::not_null<SpinWeighted<ComplexDataVector, 1>*> eth_x,
      63              :     gsl::not_null<SpinWeighted<ComplexDataVector, 1>*> eth_y,
      64              :     gsl::not_null<SpinWeighted<ComplexDataVector, 1>*> eth_z, size_t l_max);
      65              : 
      66              : // Normalize the Cartesian coordinates of the current map, and update its
      67              : // angular coordinates, interpolator and Jacobians to match.
      68              : void update_angular_coordinates_and_jacobians(
      69              :     gsl::not_null<tnsr::i<DataVector, 3>*> cartesian_cauchy_coordinates,
      70              :     gsl::not_null<
      71              :         tnsr::i<DataVector, 2, ::Frame::Spherical<::Frame::Inertial>>*>
      72              :         angular_cauchy_coordinates,
      73              :     gsl::not_null<Spectral::Swsh::SwshInterpolator*> interpolator,
      74              :     gsl::not_null<Scalar<SpinWeighted<ComplexDataVector, 2>>*> gauge_c,
      75              :     gsl::not_null<Scalar<SpinWeighted<ComplexDataVector, 0>>*> gauge_d,
      76              :     size_t l_max);
      77              : 
      78              : // What an angular solve does when it stops above its tolerance.
      79              : // `Uninitialized` catches a default-constructed value and is an error to pass.
      80              : enum class UnconvergedAngularSolve : uint8_t {
      81              :   Uninitialized,
      82              :   Error,
      83              :   Warn,
      84              :   Silent
      85              : };
      86              : 
      87              : // The residual of the map an angular solve hands back, and the number of
      88              : // coordinate updates it performed.
      89              : struct AngularSolveResult {
      90              :   double max_error = std::numeric_limits<double>::signaling_NaN();
      91              :   size_t number_of_coordinate_updates = 0;
      92              : };
      93              : 
      94              : // Abort if `max_error` exceeds `error_threshold` or is not a number.
      95              : void check_angular_solve_error_threshold(double max_error,
      96              :                                          double error_threshold);
      97              : 
      98              : // Report an angular solve that stopped above its tolerance as an error, a
      99              : // warning or not at all. The messages describe `tolerance` as a target for
     100              : // `max_error`, the role it has in the default convergence test.
     101              : void report_unconverged_angular_solve(UnconvergedAngularSolve if_unconverged,
     102              :                                       double tolerance,
     103              :                                       size_t number_of_iterations,
     104              :                                       double max_error);
     105              : 
     106              : // used to provide a default for the convergence test in
     107              : // `iteratively_adapt_angular_coordinates`
     108              : struct ResidualBelowTolerance {
     109              :   bool operator()(const double max_error, const double tolerance) const {
     110              :     return max_error < tolerance;
     111              :   }
     112              : };
     113              : 
     114              : // Records the running minimum of the residuals of an angular solve, and tells
     115              : // when it has improved by less than a relative `tolerance` over the last
     116              : // `window` iterations. Unlike the change between iterations, this is not held
     117              : // open by round-off noise on a slow descent.
     118              : class ResidualPlateau {
     119              :  public:
     120              :   explicit ResidualPlateau(size_t window);
     121              : 
     122              :   void record(double residual);
     123              : 
     124              :   // False until `window + 1` residuals are recorded.
     125              :   bool stopped_improving(double tolerance) const;
     126              : 
     127              :  private:
     128              :   size_t window_;
     129              :   std::vector<double> smallest_residual_;
     130              : };
     131              : 
     132              : // perform an iterative solve for the set of angular coordinates. The iteration
     133              : // callable `iteration_function` must have function signature:
     134              : //
     135              : // double iteration_function(
     136              : //     const gsl::not_null<Scalar<SpinWeighted<ComplexDataVector, 2>>*>
     137              : //         gauge_c_step,
     138              : //     const gsl::not_null<Scalar<SpinWeighted<ComplexDataVector, 0>>*>
     139              : //         gauge_d_step,
     140              : //     const Scalar<SpinWeighted<ComplexDataVector, 2>>& gauge_c,
     141              : //     const Scalar<SpinWeighted<ComplexDataVector, 0>>& gauge_d,
     142              : //     const Spectral::Swsh::SwshInterpolator& iteration_interpolator);
     143              : //
     144              : // but need not be a function pointer -- a callable class or lambda will also
     145              : // suffice.
     146              : // For each step specified by the iteration function, the coordinates are
     147              : // updated via \hat \eth \delta x^i = \delta c \eth x^i|_{x^i=\hat x^i}
     148              : //                        + \delta \bar d (\eth x^i)|_{x^i=\hat x^i}
     149              : // This coordinate update is exact, and comes from expanding the chain rule to
     150              : // determine Jacobian factors. However, the result is not guaranteed to
     151              : // produce the desired Jacobian c and d, because \delta c and \delta d are
     152              : // not necessarily consistent with the underlying coordinates.
     153              : // We then update the x^i by inverting \hat \eth, which is also exact, but
     154              : // assumes a no l=0 contribution to the transformation.
     155              : // Depending on the choice of approximations used to specify
     156              : // `iteration_function`, though, the method can be slow to converge.
     157              : 
     158              : // However, the iterations are typically fast, and the computation is for
     159              : // initial data that needs to be computed only once during a simulation, so it
     160              : // is not currently an optimization priority. If this function becomes a
     161              : // bottleneck, the numerical procedure of the iterative method or the choice of
     162              : // approximation used for `iteration_function` should be revisited.
     163              : //
     164              : // The solve stops once `has_converged(max_error, tolerance)` holds (by default
     165              : // `max_error < tolerance`) or after `max_steps` steps, and hands back the best
     166              : // map it evaluated. `has_converged` is called once after each evaluation of
     167              : // `iteration_function` in the loop, but not after the final re-evaluation of
     168              : // the best map. A caller with another convergence test passes
     169              : // `UnconvergedAngularSolve::Silent` and reports a failure itself, since the
     170              : // other modes describe `tolerance` as a target for `max_error`. With
     171              : // `initialize_coordinates = false` it continues from the map the caller
     172              : // supplies.
     173              : template <typename IterationFunctor, typename FinalizeFunctor = NoOpFinalize,
     174              :           typename ConvergenceTest = ResidualBelowTolerance>
     175              : AngularSolveResult iteratively_adapt_angular_coordinates(
     176              :     const gsl::not_null<tnsr::i<DataVector, 3>*> cartesian_cauchy_coordinates,
     177              :     const gsl::not_null<
     178              :         tnsr::i<DataVector, 2, ::Frame::Spherical<::Frame::Inertial>>*>
     179              :         angular_cauchy_coordinates,
     180              :     const size_t l_max, const double tolerance, const size_t max_steps,
     181              :     const double error_threshold, const IterationFunctor& iteration_function,
     182              :     const UnconvergedAngularSolve if_unconverged,
     183              :     const FinalizeFunctor finalize_function = NoOpFinalize{},
     184              :     const bool initialize_coordinates = true,
     185              :     const ConvergenceTest& has_converged = ResidualBelowTolerance{}) {
     186              :   const size_t number_of_angular_points =
     187              :       Spectral::Swsh::number_of_swsh_collocation_points(l_max);
     188              : 
     189              :   if (initialize_coordinates) {
     190              :     Spectral::Swsh::create_angular_and_cartesian_coordinates(
     191              :         cartesian_cauchy_coordinates, angular_cauchy_coordinates, l_max);
     192              :   }
     193              : 
     194              :   Variables<tmpl::list<
     195              :       // eth of cartesian coordinates
     196              :       ::Tags::TempSpinWeightedScalar<3, 1>,
     197              :       ::Tags::TempSpinWeightedScalar<4, 1>,
     198              :       ::Tags::TempSpinWeightedScalar<5, 1>,
     199              :       // eth of gauge-transformed cartesian coordinates
     200              :       ::Tags::TempSpinWeightedScalar<6, 1>,
     201              :       ::Tags::TempSpinWeightedScalar<7, 1>,
     202              :       ::Tags::TempSpinWeightedScalar<8, 1>,
     203              :       // gauge Jacobians
     204              :       ::Tags::TempSpinWeightedScalar<9, 2>,
     205              :       ::Tags::TempSpinWeightedScalar<10, 0>,
     206              :       // gauge Jacobians on next iteration
     207              :       ::Tags::TempSpinWeightedScalar<11, 2>,
     208              :       ::Tags::TempSpinWeightedScalar<12, 0>,
     209              :       // cartesian coordinates steps
     210              :       ::Tags::TempSpinWeightedScalar<13, 0>,
     211              :       ::Tags::TempSpinWeightedScalar<14, 0>,
     212              :       ::Tags::TempSpinWeightedScalar<15, 0>>>
     213              :       computation_buffers{number_of_angular_points};
     214              : 
     215              :   auto& eth_x =
     216              :       get(get<::Tags::TempSpinWeightedScalar<3, 1>>(computation_buffers));
     217              :   auto& eth_y =
     218              :       get(get<::Tags::TempSpinWeightedScalar<4, 1>>(computation_buffers));
     219              :   auto& eth_z =
     220              :       get(get<::Tags::TempSpinWeightedScalar<5, 1>>(computation_buffers));
     221              :   unit_sphere_eth_cartesian_coordinates(make_not_null(&eth_x),
     222              :                                         make_not_null(&eth_y),
     223              :                                         make_not_null(&eth_z), l_max);
     224              : 
     225              :   auto& evolution_gauge_eth_x_step =
     226              :       get(get<::Tags::TempSpinWeightedScalar<6, 1>>(computation_buffers));
     227              :   auto& evolution_gauge_eth_y_step =
     228              :       get(get<::Tags::TempSpinWeightedScalar<7, 1>>(computation_buffers));
     229              :   auto& evolution_gauge_eth_z_step =
     230              :       get(get<::Tags::TempSpinWeightedScalar<8, 1>>(computation_buffers));
     231              : 
     232              :   auto& gauge_c =
     233              :       get<::Tags::TempSpinWeightedScalar<9, 2>>(computation_buffers);
     234              :   auto& gauge_d =
     235              :       get<::Tags::TempSpinWeightedScalar<10, 0>>(computation_buffers);
     236              : 
     237              :   auto& gauge_c_step =
     238              :       get<::Tags::TempSpinWeightedScalar<11, 2>>(computation_buffers);
     239              :   auto& gauge_d_step =
     240              :       get<::Tags::TempSpinWeightedScalar<12, 0>>(computation_buffers);
     241              : 
     242              :   auto& x_step =
     243              :       get(get<::Tags::TempSpinWeightedScalar<13, 0>>(computation_buffers));
     244              :   auto& y_step =
     245              :       get(get<::Tags::TempSpinWeightedScalar<14, 0>>(computation_buffers));
     246              :   auto& z_step =
     247              :       get(get<::Tags::TempSpinWeightedScalar<15, 0>>(computation_buffers));
     248              : 
     249              :   double max_error = 1.0;
     250              :   size_t number_of_steps = 0;
     251              :   bool converged = false;
     252              :   Spectral::Swsh::SwshInterpolator iteration_interpolator;
     253              : 
     254              :   const auto evaluate_current_map = [&]() {
     255              :     update_angular_coordinates_and_jacobians(
     256              :         cartesian_cauchy_coordinates, angular_cauchy_coordinates,
     257              :         make_not_null(&iteration_interpolator), make_not_null(&gauge_c),
     258              :         make_not_null(&gauge_d), l_max);
     259              :     return iteration_function(make_not_null(&gauge_c_step),
     260              :                               make_not_null(&gauge_d_step), gauge_c, gauge_d,
     261              :                               iteration_interpolator);
     262              :   };
     263              : 
     264              :   // The best map is copied before `evaluate_current_map` normalizes it, so that
     265              :   // evaluating it again on the rewind reproduces its residual bit for bit.
     266              :   auto unevaluated_cartesian_cauchy_coordinates = *cartesian_cauchy_coordinates;
     267              :   auto best_cartesian_cauchy_coordinates = *cartesian_cauchy_coordinates;
     268              :   double best_max_error = std::numeric_limits<double>::max();
     269              : 
     270              :   while (true) {
     271              :     unevaluated_cartesian_cauchy_coordinates = *cartesian_cauchy_coordinates;
     272              :     max_error = evaluate_current_map();
     273              :     if (max_error < best_max_error) {
     274              :       best_max_error = max_error;
     275              :       best_cartesian_cauchy_coordinates =
     276              :           unevaluated_cartesian_cauchy_coordinates;
     277              :     }
     278              : 
     279              :     check_angular_solve_error_threshold(max_error, error_threshold);
     280              :     ++number_of_steps;
     281              :     converged = has_converged(max_error, tolerance);
     282              :     if (converged or number_of_steps > max_steps) {
     283              :       break;
     284              :     }
     285              :     // using the evolution_gauge_.._step as temporary buffers for the
     286              :     // interpolation results
     287              :     iteration_interpolator.interpolate(
     288              :         make_not_null(&evolution_gauge_eth_x_step), eth_x);
     289              :     iteration_interpolator.interpolate(
     290              :         make_not_null(&evolution_gauge_eth_y_step), eth_y);
     291              :     iteration_interpolator.interpolate(
     292              :         make_not_null(&evolution_gauge_eth_z_step), eth_z);
     293              : 
     294              :     evolution_gauge_eth_x_step =
     295              :         0.5 * ((get(gauge_c_step)) * conj(evolution_gauge_eth_x_step) +
     296              :                conj((get(gauge_d_step))) * evolution_gauge_eth_x_step);
     297              :     evolution_gauge_eth_y_step =
     298              :         0.5 * ((get(gauge_c_step)) * conj(evolution_gauge_eth_y_step) +
     299              :                conj((get(gauge_d_step))) * evolution_gauge_eth_y_step);
     300              :     evolution_gauge_eth_z_step =
     301              :         0.5 * ((get(gauge_c_step)) * conj(evolution_gauge_eth_z_step) +
     302              :                conj((get(gauge_d_step))) * evolution_gauge_eth_z_step);
     303              : 
     304              :     Spectral::Swsh::angular_derivatives<tmpl::list<
     305              :         Spectral::Swsh::Tags::InverseEth, Spectral::Swsh::Tags::InverseEth,
     306              :         Spectral::Swsh::Tags::InverseEth>>(
     307              :         l_max, 1, make_not_null(&x_step), make_not_null(&y_step),
     308              :         make_not_null(&z_step), evolution_gauge_eth_x_step,
     309              :         evolution_gauge_eth_y_step, evolution_gauge_eth_z_step);
     310              : 
     311              :     get<0>(*cartesian_cauchy_coordinates) += real(x_step.data());
     312              :     get<1>(*cartesian_cauchy_coordinates) += real(y_step.data());
     313              :     get<2>(*cartesian_cauchy_coordinates) += real(z_step.data());
     314              :   }
     315              : 
     316              :   if (best_max_error < max_error) {
     317              :     *cartesian_cauchy_coordinates = best_cartesian_cauchy_coordinates;
     318              :     max_error = evaluate_current_map();
     319              :   }
     320              : 
     321              :   finalize_function(gauge_c, gauge_d, *angular_cauchy_coordinates,
     322              :                     iteration_interpolator);
     323              : 
     324              :   // The loop evaluates once more than it updates.
     325              :   const size_t number_of_coordinate_updates = number_of_steps - 1;
     326              :   if (not converged) {
     327              :     report_unconverged_angular_solve(if_unconverged, tolerance,
     328              :                                      number_of_coordinate_updates, max_error);
     329              :   }
     330              :   return {.max_error = max_error,
     331              :           .number_of_coordinate_updates = number_of_coordinate_updates};
     332              : }
     333              : 
     334              : // Solve for the partially flat angular coordinates with a displacement
     335              : // generated by a single spin-weight-1 potential \eta,
     336              : //
     337              : //     \delta \hat x^i = Re(\eta conj(\eth x^i)|_{\hat x}),
     338              : //
     339              : // where \eth x^i, the eth of the Cartesian coordinates on the identity grid, is
     340              : // evaluated at the current map \hat x.
     341              : // `iteratively_adapt_angular_coordinates` prescribes both Jacobian variations,
     342              : // four real functions for a map that has two, so only part of each requested
     343              : // step is realized. Here the variation of \hat c at the identity is \eth \eta,
     344              : // so the step to the target Jacobian \hat c_* is one application of \eth^{-1},
     345              : //
     346              : //     \eta = \eth^{-1}(\hat c_* - \hat c),
     347              : //
     348              : // and \hat d follows from the map. \eth^{-1} leaves the l = 1 part of \eta at
     349              : // zero: \eth annihilates it, and it is the residual conformal freedom of the
     350              : // problem. Since \hat c_* is the exact root of the gauge condition, a few
     351              : // iterations do the work of many linearized sweeps.
     352              : //
     353              : // `target_function` must have the signature
     354              : //
     355              : // double target_function(
     356              : //     const gsl::not_null<Scalar<SpinWeighted<ComplexDataVector, 2>>*>
     357              : //         gauge_c_target,
     358              : //     const Scalar<SpinWeighted<ComplexDataVector, 2>>& gauge_c,
     359              : //     const Scalar<SpinWeighted<ComplexDataVector, 0>>& gauge_d,
     360              : //     const Spectral::Swsh::SwshInterpolator& iteration_interpolator);
     361              : //
     362              : // setting `gauge_c_target` to \hat c_* and returning the current residual.
     363              : //
     364              : // The residual first falls by orders of magnitude and then creeps back up, so
     365              : // the solve stops at the first iteration from the third on with
     366              : // e_n > 0.9 e_{n-1}, and hands back the best map it evaluated. The returned
     367              : // `number_of_coordinate_updates` counts every update the solve performed,
     368              : // including those discarded by returning to the best map, so that it measures
     369              : // the work done. `CauchySecondOrder` subtracts it from `J0MaxIterations` to
     370              : // get the number of linearized sweeps still allowed.
     371              : template <typename TargetFunctor, typename FinalizeFunctor = NoOpFinalize>
     372              : AngularSolveResult adapt_angular_coordinates_via_potential(
     373              :     const gsl::not_null<tnsr::i<DataVector, 3>*> cartesian_cauchy_coordinates,
     374              :     const gsl::not_null<
     375              :         tnsr::i<DataVector, 2, ::Frame::Spherical<::Frame::Inertial>>*>
     376              :         angular_cauchy_coordinates,
     377              :     const size_t l_max, const double tolerance, const size_t max_steps,
     378              :     const double error_threshold, const TargetFunctor& target_function,
     379              :     const UnconvergedAngularSolve if_unconverged,
     380              :     const FinalizeFunctor finalize_function = NoOpFinalize{},
     381              :     const bool initialize_coordinates = true) {
     382              :   constexpr double plateau_factor = 0.9;
     383              :   const size_t number_of_angular_points =
     384              :       Spectral::Swsh::number_of_swsh_collocation_points(l_max);
     385              : 
     386              :   if (initialize_coordinates) {
     387              :     Spectral::Swsh::create_angular_and_cartesian_coordinates(
     388              :         cartesian_cauchy_coordinates, angular_cauchy_coordinates, l_max);
     389              :   }
     390              : 
     391              :   Variables<tmpl::list<
     392              :       // eth of the cartesian coordinates
     393              :       ::Tags::TempSpinWeightedScalar<0, 1>,
     394              :       ::Tags::TempSpinWeightedScalar<1, 1>,
     395              :       ::Tags::TempSpinWeightedScalar<2, 1>,
     396              :       // ... interpolated onto the current map
     397              :       ::Tags::TempSpinWeightedScalar<3, 1>,
     398              :       ::Tags::TempSpinWeightedScalar<4, 1>,
     399              :       ::Tags::TempSpinWeightedScalar<5, 1>,
     400              :       // gauge Jacobians
     401              :       ::Tags::TempSpinWeightedScalar<6, 2>,
     402              :       ::Tags::TempSpinWeightedScalar<7, 0>,
     403              :       // the target Jacobian, then the step to it
     404              :       ::Tags::TempSpinWeightedScalar<8, 2>,
     405              :       // the potential eta
     406              :       ::Tags::TempSpinWeightedScalar<9, 1>>>
     407              :       computation_buffers{number_of_angular_points};
     408              :   auto& [eth_x, eth_y, eth_z, interpolated_eth_x, interpolated_eth_y,
     409              :          interpolated_eth_z, gauge_c, gauge_d, gauge_c_target, eta] =
     410              :       computation_buffers;
     411              : 
     412              :   unit_sphere_eth_cartesian_coordinates(make_not_null(&get(eth_x)),
     413              :                                         make_not_null(&get(eth_y)),
     414              :                                         make_not_null(&get(eth_z)), l_max);
     415              : 
     416              :   double max_error = 1.0;
     417              :   double previous_max_error = std::numeric_limits<double>::max();
     418              :   size_t number_of_steps = 0;
     419              :   Spectral::Swsh::SwshInterpolator iteration_interpolator;
     420              : 
     421              :   const auto evaluate_current_map = [&]() {
     422              :     update_angular_coordinates_and_jacobians(
     423              :         cartesian_cauchy_coordinates, angular_cauchy_coordinates,
     424              :         make_not_null(&iteration_interpolator), make_not_null(&gauge_c),
     425              :         make_not_null(&gauge_d), l_max);
     426              :     return target_function(make_not_null(&gauge_c_target), gauge_c, gauge_d,
     427              :                            iteration_interpolator);
     428              :   };
     429              : 
     430              :   // As in `iteratively_adapt_angular_coordinates`, the best map is copied
     431              :   // before it is normalized, so that the rewind reproduces it bit for bit.
     432              :   auto unevaluated_cartesian_cauchy_coordinates = *cartesian_cauchy_coordinates;
     433              :   auto best_cartesian_cauchy_coordinates = *cartesian_cauchy_coordinates;
     434              :   double best_max_error = std::numeric_limits<double>::max();
     435              : 
     436              :   while (true) {
     437              :     unevaluated_cartesian_cauchy_coordinates = *cartesian_cauchy_coordinates;
     438              :     max_error = evaluate_current_map();
     439              :     if (max_error < best_max_error) {
     440              :       best_max_error = max_error;
     441              :       best_cartesian_cauchy_coordinates =
     442              :           unevaluated_cartesian_cauchy_coordinates;
     443              :     }
     444              : 
     445              :     check_angular_solve_error_threshold(max_error, error_threshold);
     446              :     ++number_of_steps;
     447              :     if (max_error < tolerance or number_of_steps > max_steps or
     448              :         (number_of_steps > 2 and
     449              :          max_error > plateau_factor * previous_max_error)) {
     450              :       break;
     451              :     }
     452              :     previous_max_error = max_error;
     453              : 
     454              :     // the step to the target Jacobian, then the potential that generates it
     455              :     get(gauge_c_target).data() -= get(gauge_c).data();
     456              :     Spectral::Swsh::angular_derivatives<
     457              :         tmpl::list<Spectral::Swsh::Tags::InverseEth>>(
     458              :         l_max, 1, make_not_null(&get(eta)), get(gauge_c_target));
     459              : 
     460              :     iteration_interpolator.interpolate(make_not_null(&get(interpolated_eth_x)),
     461              :                                        get(eth_x));
     462              :     iteration_interpolator.interpolate(make_not_null(&get(interpolated_eth_y)),
     463              :                                        get(eth_y));
     464              :     iteration_interpolator.interpolate(make_not_null(&get(interpolated_eth_z)),
     465              :                                        get(eth_z));
     466              : 
     467              :     // dx^i = Re(eta conj(eth x^i)) = v^A \partial_A x^i is tangent to the
     468              :     // sphere, since x^i eth x^i = 0; the radial part of its eth is removed when
     469              :     // the next iteration normalizes the coordinates.
     470              :     get<0>(*cartesian_cauchy_coordinates) +=
     471              :         real(get(eta).data() * conj(get(interpolated_eth_x).data()));
     472              :     get<1>(*cartesian_cauchy_coordinates) +=
     473              :         real(get(eta).data() * conj(get(interpolated_eth_y).data()));
     474              :     get<2>(*cartesian_cauchy_coordinates) +=
     475              :         real(get(eta).data() * conj(get(interpolated_eth_z).data()));
     476              :   }
     477              : 
     478              :   if (best_max_error < max_error) {
     479              :     *cartesian_cauchy_coordinates = best_cartesian_cauchy_coordinates;
     480              :     max_error = evaluate_current_map();
     481              :   }
     482              : 
     483              :   finalize_function(gauge_c, gauge_d, *angular_cauchy_coordinates,
     484              :                     iteration_interpolator);
     485              : 
     486              :   // The loop evaluates once more than it updates.
     487              :   const size_t number_of_coordinate_updates = number_of_steps - 1;
     488              :   if (std::isnan(max_error) or max_error >= tolerance) {
     489              :     report_unconverged_angular_solve(if_unconverged, tolerance,
     490              :                                      number_of_coordinate_updates, max_error);
     491              :   }
     492              :   return {.max_error = max_error,
     493              :           .number_of_coordinate_updates = number_of_coordinate_updates};
     494              : }
     495              : 
     496              : double adjust_angular_coordinates_for_j(
     497              :     gsl::not_null<Scalar<SpinWeighted<ComplexDataVector, 2>>*> volume_j,
     498              :     gsl::not_null<tnsr::i<DataVector, 3>*> cartesian_cauchy_coordinates,
     499              :     gsl::not_null<
     500              :         tnsr::i<DataVector, 2, ::Frame::Spherical<::Frame::Inertial>>*>
     501              :         angular_cauchy_coordinates,
     502              :     const SpinWeighted<ComplexDataVector, 2>& surface_j, size_t l_max,
     503              :     double tolerance, size_t max_steps, bool adjust_volume_gauge);
     504              : }  // namespace detail
     505              : 
     506              : /*!
     507              :  * \brief Apply a radius-independent angular gauge transformation to a volume
     508              :  * \f$J\f$, for use with initial data generation.
     509              :  *
     510              :  * \details Performs the gauge transformation to \f$\hat J\f$,
     511              :  *
     512              :  * \f{align*}{
     513              :  * \hat J = \frac{1}{4 \hat{\omega}^2} \left( \bar{\hat d}^2  J(\hat x^{\hat A})
     514              :  *  + \hat c^2 \bar J(\hat x^{\hat A})
     515              :  *  + 2 \hat c \bar{\hat d} K(\hat x^{\hat A}) \right).
     516              :  * \f}
     517              :  *
     518              :  * Where \f$\hat c\f$ and \f$\hat d\f$ are the spin-weighted angular Jacobian
     519              :  * factors computed by `GaugeUpdateJacobianFromCoords`, and \f$\hat \omega\f$ is
     520              :  * the conformal factor associated with the angular coordinate transformation.
     521              :  * Note that the right-hand sides with explicit \f$\hat x^{\hat A}\f$ dependence
     522              :  * must be interpolated and that \f$K = \sqrt{1 + J \bar J}\f$.
     523              :  */
     524            1 : struct GaugeAdjustInitialJ {
     525            0 :   using boundary_tags =
     526              :       tmpl::list<Tags::PartiallyFlatGaugeC, Tags::PartiallyFlatGaugeD,
     527              :                  Tags::PartiallyFlatGaugeOmega, Tags::CauchyAngularCoords,
     528              :                  Spectral::Swsh::Tags::LMax>;
     529            0 :   using return_tags = tmpl::list<Tags::BondiJ>;
     530            0 :   using argument_tags = tmpl::append<boundary_tags>;
     531              : 
     532            0 :   static void apply(
     533              :       gsl::not_null<Scalar<SpinWeighted<ComplexDataVector, 2>>*> volume_j,
     534              :       const Scalar<SpinWeighted<ComplexDataVector, 2>>& gauge_c,
     535              :       const Scalar<SpinWeighted<ComplexDataVector, 0>>& gauge_d,
     536              :       const Scalar<SpinWeighted<ComplexDataVector, 0>>& gauge_omega,
     537              :       const tnsr::i<DataVector, 2, ::Frame::Spherical<::Frame::Inertial>>&
     538              :           cauchy_angular_coordinates,
     539              :       const Spectral::Swsh::SwshInterpolator& interpolator, size_t l_max);
     540              : };
     541              : 
     542              : /// \cond
     543              : struct NoIncomingRadiation;
     544              : struct ZeroNonSmooth;
     545              : template <bool evolve_ccm>
     546              : struct InverseCubic;
     547              : template <bool evolve_ccm>
     548              : struct InitializeJ;
     549              : struct ConformalFactor;
     550              : struct CauchySecondOrder;
     551              : /// \endcond
     552              : 
     553              : /*!
     554              :  * \brief Abstract base class for an initial hypersurface data generator for
     555              :  * Cce, when the partially flat Bondi-like coordinates are evolved.
     556              :  *
     557              :  * \details The algorithm is same as `InitializeJ<false>`, but with an
     558              :  * additional initialization for the partially flat Bondi-like coordinates. The
     559              :  * functions that are required to be overriden in the derived classes are:
     560              :  * - `InitializeJ::get_clone()`: should return a
     561              :  * `std::unique_ptr<InitializeJ<true>>` with cloned state.
     562              :  * - `InitializeJ::operator() const`: should take as arguments, first a
     563              :  * set of `gsl::not_null` pointers represented by `mutate_tags`, followed by a
     564              :  * set of `const` references to quantities represented by `argument_tags`. \note
     565              :  * The `InitializeJ::operator()` should be const, and therefore not alter
     566              :  * the internal state of the generator. This is compatible with all known
     567              :  * use-cases and permits the `InitializeJ` generator to be placed in the
     568              :  * `GlobalCache`.
     569              :  */
     570              : template <>
     571            1 : struct InitializeJ<true> : public PUP::able {
     572            0 :   using boundary_tags = tmpl::list<Tags::BoundaryValue<Tags::BondiJ>,
     573              :                                    Tags::BoundaryValue<Tags::Dr<Tags::BondiJ>>,
     574              :                                    Tags::BoundaryValue<Tags::BondiR>,
     575              :                                    Tags::BoundaryValue<Tags::BondiBeta>>;
     576              : 
     577            0 :   using mutate_tags =
     578              :       tmpl::list<Tags::BondiJ, Tags::CauchyCartesianCoords,
     579              :                  Tags::CauchyAngularCoords, Tags::PartiallyFlatCartesianCoords,
     580              :                  Tags::PartiallyFlatAngularCoords>;
     581            0 :   using return_tags = mutate_tags;
     582            0 :   using argument_tags =
     583              :       tmpl::push_back<boundary_tags, Tags::LMax, Tags::NumberOfRadialPoints>;
     584              : 
     585              :   // The evolution of inertial coordinates are allowed only when InverseCubic is
     586              :   // used
     587            0 :   using creatable_classes = tmpl::list<InverseCubic<true>>;
     588              : 
     589            0 :   InitializeJ() = default;
     590            0 :   explicit InitializeJ(CkMigrateMessage* /*msg*/) {}
     591              : 
     592            0 :   WRAPPED_PUPable_abstract(InitializeJ);  // NOLINT
     593              : 
     594            0 :   virtual std::unique_ptr<InitializeJ<true>> get_clone() const = 0;
     595              : 
     596              :   /// \brief The interpolator this generator wants used to build the worldtube
     597              :   /// boundary value of \f$\partial_u \partial_r J\f$, or `nullptr` to use the
     598              :   /// evolution's `H5Interpolator`.
     599              :   ///
     600              :   /// \details That boundary value is consumed by the initial data alone, so a
     601              :   /// generator is free to choose its own time-interpolation order for it
     602              :   /// without affecting the evolution.
     603            1 :   virtual std::unique_ptr<intrp::SpanInterpolator> du_dr_j_interpolator()
     604              :       const {
     605              :     return nullptr;
     606              :   }
     607              : 
     608              :   // Each derived class declares its own `return_tags` and `argument_tags` and
     609              :   // implements a non-virtual `operator()`; the dispatch below picks the
     610              :   // correct dynamic type and forwards through `db::mutate_apply`.
     611              :   template <typename DbTags>
     612            0 :   void operator()(const gsl::not_null<db::DataBox<DbTags>*> box,
     613              :                   const gsl::not_null<Parallel::NodeLock*> hdf5_lock) const {
     614              :     call_with_dynamic_type<void, creatable_classes>(
     615              :         this, [&](auto* const derived) {
     616              :           db::mutate_apply(*derived, box, hdf5_lock);
     617              :         });
     618              :   }
     619              : };
     620              : 
     621              : /*!
     622              :  * \brief Abstract base class for an initial hypersurface data generator for
     623              :  * Cce, when the partially flat Bondi-like coordinates are not evolved.
     624              :  *
     625              :  * \details The functions that are required to be overriden in the derived
     626              :  * classes are:
     627              :  * - `InitializeJ::get_clone()`: should return a
     628              :  * `std::unique_ptr<InitializeJ<false>>` with cloned state.
     629              :  * - `InitializeJ::operator() const`: should take as arguments, first a
     630              :  * set of `gsl::not_null` pointers represented by `mutate_tags`, followed by a
     631              :  * set of `const` references to quantities represented by `argument_tags`. \note
     632              :  * The `InitializeJ::operator()` should be const, and therefore not alter
     633              :  * the internal state of the generator. This is compatible with all known
     634              :  * use-cases and permits the `InitializeJ` generator to be placed in the
     635              :  * `GlobalCache`.
     636              :  */
     637              : template <>
     638            1 : struct InitializeJ<false> : public PUP::able {
     639              :   // Default boundary/mutate/argument tags used by the simple derived classes
     640              :   // (ConformalFactor, InverseCubic<false>, NoIncomingRadiation, ZeroNonSmooth).
     641              :   // A derived class can shadow these with its own list (see CauchySecondOrder),
     642              :   // in which case the per-class list is used during dispatch.
     643            0 :   using boundary_tags = tmpl::list<Tags::BoundaryValue<Tags::BondiJ>,
     644              :                                    Tags::BoundaryValue<Tags::Dr<Tags::BondiJ>>,
     645              :                                    Tags::BoundaryValue<Tags::BondiR>,
     646              :                                    Tags::BoundaryValue<Tags::BondiBeta>>;
     647              : 
     648            0 :   using mutate_tags = tmpl::list<Tags::BondiJ, Tags::CauchyCartesianCoords,
     649              :                                  Tags::CauchyAngularCoords>;
     650            0 :   using return_tags = mutate_tags;
     651            0 :   using argument_tags =
     652              :       tmpl::push_back<boundary_tags, Tags::LMax, Tags::NumberOfRadialPoints>;
     653              : 
     654            0 :   using creatable_classes =
     655              :       tmpl::list<ConformalFactor, InverseCubic<false>, NoIncomingRadiation,
     656              :                  ZeroNonSmooth, CauchySecondOrder,
     657              :                  ::Cce::Solutions::LinearizedBondiSachs_detail::InitializeJ::
     658              :                      LinearizedBondiSachs>;
     659              : 
     660            0 :   InitializeJ() = default;
     661            0 :   explicit InitializeJ(CkMigrateMessage* /*msg*/) {}
     662              : 
     663            0 :   WRAPPED_PUPable_abstract(InitializeJ);  // NOLINT
     664              : 
     665            0 :   virtual std::unique_ptr<InitializeJ<false>> get_clone() const = 0;
     666              : 
     667              :   /// \brief The interpolator this generator wants used to build the worldtube
     668              :   /// boundary value of \f$\partial_u \partial_r J\f$, or `nullptr` to use the
     669              :   /// evolution's `H5Interpolator`.
     670              :   ///
     671              :   /// \details That boundary value is consumed by the initial data alone, so a
     672              :   /// generator is free to choose its own time-interpolation order for it
     673              :   /// without affecting the evolution.
     674            1 :   virtual std::unique_ptr<intrp::SpanInterpolator> du_dr_j_interpolator()
     675              :       const {
     676              :     return nullptr;
     677              :   }
     678              : 
     679              :   // Each derived class declares its own `return_tags` and `argument_tags` and
     680              :   // implements a non-virtual `operator()` whose signature matches those tags.
     681              :   // The dispatch below picks the correct dynamic type and forwards through
     682              :   // `db::mutate_apply` so the per-class tag lists are honored.
     683              :   template <typename DbTags>
     684            0 :   void operator()(const gsl::not_null<db::DataBox<DbTags>*> box,
     685              :                   const gsl::not_null<Parallel::NodeLock*> hdf5_lock) const {
     686              :     call_with_dynamic_type<void, creatable_classes>(
     687              :         this, [&](auto* const derived) {
     688              :           db::mutate_apply(*derived, box, hdf5_lock);
     689              :         });
     690              :   }
     691              : };
     692              : }  // namespace InitializeJ
     693              : }  // namespace Cce
     694              : 
     695              : // NOLINTBEGIN(misc-header-include-cycle)
     696              : #include "Evolution/Systems/Cce/AnalyticSolutions/LinearizedBondiSachsInitializeJ.hpp"
     697              : #include "Evolution/Systems/Cce/Initialize/CauchySecondOrder.hpp"
     698              : #include "Evolution/Systems/Cce/Initialize/ConformalFactor.hpp"
     699              : #include "Evolution/Systems/Cce/Initialize/InverseCubic.hpp"
     700              : #include "Evolution/Systems/Cce/Initialize/NoIncomingRadiation.hpp"
     701              : #include "Evolution/Systems/Cce/Initialize/ZeroNonSmooth.hpp"
     702              : // NOLINTEND(misc-header-include-cycle)
        

Generated by: LCOV version 2.0-1