SpECTRE Documentation Coverage Report
Current view: top level - Evolution/Systems/Cce/Events - ObserveFields.hpp Hit Total Coverage
Commit: c3e43f8d41800b0ecefb9d1393f1de1d5a280c8f Lines: 3 26 11.5 %
Date: 2026-07-24 22:09:25
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 <cstddef>
       7             : #include <pup.h>
       8             : #include <string>
       9             : #include <tuple>
      10             : #include <type_traits>
      11             : #include <unordered_set>
      12             : #include <vector>
      13             : 
      14             : #include "DataStructures/ComplexDataVector.hpp"
      15             : #include "DataStructures/ComplexModalVector.hpp"
      16             : #include "DataStructures/DataBox/DataBox.hpp"
      17             : #include "DataStructures/DataBox/DataBoxTag.hpp"
      18             : #include "DataStructures/DataBox/ObservationBox.hpp"
      19             : #include "DataStructures/DataBox/Prefixes.hpp"
      20             : #include "Domain/Structure/ElementId.hpp"
      21             : #include "Evolution/Systems/Cce/LinearOperators.hpp"
      22             : #include "Evolution/Systems/Cce/NewmanPenrose.hpp"
      23             : #include "Evolution/Systems/Cce/OptionTags.hpp"
      24             : #include "Evolution/Systems/Cce/SwshDerivatives.hpp"
      25             : #include "Evolution/Systems/Cce/Tags.hpp"
      26             : #include "IO/Observer/ObserverComponent.hpp"
      27             : #include "IO/Observer/ReductionActions.hpp"
      28             : #include "IO/Observer/VolumeActions.hpp"
      29             : #include "NumericalAlgorithms/SpinWeightedSphericalHarmonics/SwshCoefficients.hpp"
      30             : #include "NumericalAlgorithms/SpinWeightedSphericalHarmonics/SwshCollocation.hpp"
      31             : #include "NumericalAlgorithms/SpinWeightedSphericalHarmonics/SwshTransform.hpp"
      32             : #include "Options/Context.hpp"
      33             : #include "Options/ParseError.hpp"
      34             : #include "Options/String.hpp"
      35             : #include "Parallel/GlobalCache.hpp"
      36             : #include "Parallel/Info.hpp"
      37             : #include "Parallel/Invoke.hpp"
      38             : #include "ParallelAlgorithms/EventsAndTriggers/Event.hpp"
      39             : #include "Time/Tags/Time.hpp"
      40             : #include "Utilities/Gsl.hpp"
      41             : #include "Utilities/MakeString.hpp"
      42             : #include "Utilities/TMPL.hpp"
      43             : #include "Utilities/TypeTraits/IsA.hpp"
      44             : 
      45             : /// \cond
      46             : template <size_t Dim>
      47             : class Mesh;
      48             : namespace Frame {
      49             : struct Inertial;
      50             : }  // namespace Frame
      51             : /// \endcond
      52             : 
      53             : namespace Cce::Events {
      54             : namespace detail {
      55             : template <typename Tag>
      56             : std::string name() {
      57             :   if constexpr (std::is_same_v<Tag, Tags::ComplexInertialRetardedTime>) {
      58             :     return db::tag_name<Tags::InertialRetardedTime>();
      59             :   } else {
      60             :     return db::tag_name<Tag>();
      61             :   }
      62             : }
      63             : }  // namespace detail
      64             : 
      65             : /*!
      66             :  * \brief Event to observe fields/variables in a characteristic evolution.
      67             :  *
      68             :  * \details Similar to `dg::Events::ObserveFields`, this event will write volume
      69             :  * data from the characteristic domain to disk when triggered. However, there
      70             :  * are several differences which are important to highlight.
      71             :  *
      72             :  * First is the fields themselves. The DG event takes the fields to observe as
      73             :  * template parameters because the event must work with many evolution systems.
      74             :  * However, since this event is specific to the characteristic evolution system,
      75             :  * we can hardcode the list of fields that are available to observe. The fields
      76             :  * available to observe are the following tags along with their first and second
      77             :  * `Cce::Tags::Dy` derivatives (see `Cce::Tags::Dy` for a definition of `y`):
      78             :  *
      79             :  * - `Cce::Tags::BondiBeta`
      80             :  * - `Cce::Tags::BondiU`
      81             :  * - `Cce::Tags::BondiQ`
      82             :  * - `Cce::Tags::BondiW`
      83             :  * - `Cce::Tags::BondiH` (no second derivative)
      84             :  * - `Cce::Tags::BondiJ`
      85             :  * - `Cce::Tags::Du<Cce::Tags::BondiJ>`
      86             :  *
      87             :  * Some more fields to observe are:
      88             :  *
      89             :  * - `Cce::Tags::Psi0`
      90             :  * - `Cce::Tags::Psi1`
      91             :  * - `Cce::Tags::Psi2`
      92             :  * - `Cce::Tags::ComplexInertialRetardedTime`
      93             :  * - `Cce::Tags::OneMinusY`
      94             :  * - `Cce::Tags::BondiR`
      95             :  * - `Cce::Tags::EthRDividedByR`
      96             :  * - `Cce::Tags::DuRDividedByR`
      97             :  *
      98             :  * The main reason that this event is separate from the DG one is because this
      99             :  * event writes modal data over the sphere for every radial grid point, while
     100             :  * the DG event writes nodal data. Every tag above is a
     101             :  * `Scalar<SpinWeighted<ComplexDataVector, Spin>>` for some `Spin`. While this
     102             :  * data itself is in nodal form, it is more convenient to transform to modal
     103             :  * data and decompose in spherical harmonics before writing. This means
     104             :  * our typical way of writing/storing volume data won't work.
     105             :  *
     106             :  * All data will be written into the `observers::OptionTags::VolumeFileName`
     107             :  * file. If CCE is run on a single core, then this will write the volume data
     108             :  * immediately (synchronously) instead of sending it to the ObserverWriter to be
     109             :  * written asynchronously.  The option `SubgroupName` controls the name of the
     110             :  * H5 group where this volume data is written. For example, if `SubgroupName` is
     111             :  * "CceVolumeData", then the volume file will contain
     112             :  * `/CceVolumeData/VolumeData.vol` for most fields; it would contain
     113             :  * `/CceVolumeData/InertialRetardedTime.vol` for the inertial retarded time; and
     114             :  * it would contain `/CceVolumeData/OneMinusY.vol` for the compactified radial
     115             :  * coordinate. The structure of the .vol subfiles is the same as that for DG
     116             :  * volume data. However, the extents of the three different volume files are all
     117             :  * different, and for modal directions, they take the values l_max in both of
     118             :  * the two extent slots corresponding to angular modes. This  also means that
     119             :  * complex modal data (which has real/imag parts interleaved, as described
     120             :  * below) has twice as much data as the products of the extents suggests.
     121             :  *
     122             :  * The formats for `Cce::Tags::ComplexInertialRetardedTime` and
     123             :  * `Cce::Tags::OneMinusY` are special and are described below. Every other field
     124             :  * follows the same format. Each ObservationId contains one time slice. Within
     125             :  * an ObservationId, each field is an unraveled vector of complex modal
     126             :  * coefficients at compactified radial slices (the compactified coordinate is $y
     127             :  * = 1 - 2R/r$ where $r$ is your coordinate radius and $R$ is the coordinate
     128             :  * radius of your worldtube; it is recommended to always dump the quantity
     129             :  * `Cce::Tags::OneMinusY` so the values of the compactified coordinates are
     130             :  * available as well). The ordering of the coefficients for a field $f$ at
     131             :  * constant observation event are, for example:
     132             :  * Re f_{0,0}(1-y_0), Im f_{0,0}(1-y_0), Re f_{1,-1}(1-y_0), Im f_{1,-1}(1-y_0),
     133             :  * Re f_{1,0}(1-y_0), Im f_{1,0}(1-y_0), ...
     134             :  * Re f_{l_{max},l_{max}}(1-y_0), Im f_{l_{max},l_{max}}(1-y_0),
     135             :  * Re f_{0,0}(1-y_1), Im f_{0,0}(1-y_1), ...
     136             :  * Re f_{l_{max},l_{max}}(1-y_{max)), Im f_{l_{max},l_{max}}(1-y_{max}).
     137             :  * That is, the radial slice indexes the slowest, followed by the $\ell$ number
     138             :  * (always starting at 0, even for s≠0), followed by the azimuthal m number
     139             :  * (from $-\ell$ to $+\ell$ inclusive), followed by interleaving real and
     140             :  * imaginary parts of the complex field.
     141             :  *
     142             :  * There are two notable exceptions to this format. One is
     143             :  * `Cce::Tags::ComplexInertialRetardedTime`. The quantity we are actually
     144             :  * interested in is `Cce::Tags::InertialRetardedTime` which is real and only
     145             :  * defined once for every direction $\theta,\phi$ (meaning it does not have
     146             :  * different values at the different radial grid points). However, we use
     147             :  * `Cce::Tags::ComplexInertialRetardedTime` because it has the same data type as
     148             :  * the other tags which makes the internals of the class simpler. This quantity
     149             :  * is stored in `/<SubgroupName>/InertialRetardedTime.vol`.
     150             :  *
     151             :  * The second is `Cce::Tags::OneMinusY`. Even though this quantity is stored as
     152             :  * a `Scalar<SpinWeighted<ComplexDataVector, 0>>` like the others, there is only
     153             :  * one meaningful value per radial grid point. All angular grid points for a
     154             :  * given radius are set to this value, namely $1-y$. Thus we only need to write
     155             :  * this value once for each radial grid point. We do this in a volume subfile
     156             :  * `/<SubgroupName>/OneMinusY.vol` with the elements in the same order as the
     157             :  * radial index order for the spin weighted quantities above.
     158             :  */
     159           1 : class ObserveFields : public Event {
     160             :   template <typename Tag, bool IncludeSecondDeriv = true>
     161             :   // clang-format off
     162           0 :   using zero_one_two_radial_derivs = tmpl::flatten<tmpl::list<
     163             :       Tag,
     164             :       Tags::Dy<Tag>,
     165             :       tmpl::conditional_t<IncludeSecondDeriv,
     166             :                           Tags::Dy<Tags::Dy<Tag>>,
     167             :                           tmpl::list<>>>>;
     168           0 :   using spin_weighted_tags_to_observe = tmpl::flatten<
     169             :       tmpl::list<zero_one_two_radial_derivs<Tags::BondiBeta>,
     170             :                  zero_one_two_radial_derivs<Tags::BondiU>,
     171             :                  zero_one_two_radial_derivs<Tags::BondiQ>,
     172             :                  zero_one_two_radial_derivs<Tags::BondiW>,
     173             :                  zero_one_two_radial_derivs<Tags::BondiH, false>,
     174             :                  zero_one_two_radial_derivs<Tags::BondiJ>,
     175             :                  zero_one_two_radial_derivs<Tags::Du<Tags::BondiJ>>,
     176             :                  Tags::BondiR,
     177             :                  Tags::Psi0,
     178             :                  Tags::Psi1,
     179             :                  Tags::Psi2,
     180             :                  Tags::NewmanPenroseAlpha,
     181             :                  Tags::NewmanPenroseBeta,
     182             :                  Tags::NewmanPenroseGamma,
     183             :                  Tags::NewmanPenroseEpsilon,
     184             :                  // Tags::NewmanPenroseKappa,
     185             :                  // in our choice of tetrad, \kappa=0
     186             :                  Tags::NewmanPenroseTau,
     187             :                  Tags::NewmanPenroseSigma,
     188             :                  Tags::NewmanPenroseRho,
     189             :                  Tags::NewmanPenrosePi,
     190             :                  Tags::NewmanPenroseNu,
     191             :                  Tags::NewmanPenroseMu,
     192             :                  Tags::NewmanPenroseLambda,
     193             :                  Tags::EthRDividedByR,
     194             :                  Tags::DuRDividedByR>>;
     195             :   // clang-format on
     196             : 
     197             :  public:
     198           0 :   using available_tags_to_observe =
     199             :       tmpl::push_back<spin_weighted_tags_to_observe,
     200             :                       Tags::ComplexInertialRetardedTime, Tags::OneMinusY>;
     201             : 
     202             :   /// \cond
     203             :   explicit ObserveFields(CkMigrateMessage* /*unused*/) {}
     204             :   using PUP::able::register_constructor;
     205             :   WRAPPED_PUPable_decl_template(ObserveFields);  // NOLINT
     206             :   /// \endcond
     207             : 
     208             :   /// The name of the subgroup inside the HDF5 file
     209           1 :   struct SubgroupName {
     210           0 :     using type = std::string;
     211           0 :     static constexpr Options::String help = {
     212             :       "The name of the subgroup inside the HDF5 file without an extension and "
     213             :       "without a preceding '/'."};
     214             :   };
     215             : 
     216           0 :   struct VariablesToObserve {
     217           0 :     static constexpr Options::String help = "Subset of variables to observe";
     218           0 :     using type = std::vector<std::string>;
     219           0 :     static size_t lower_bound_on_size() { return 1; }
     220             :   };
     221             : 
     222           0 :   using options = tmpl::list<SubgroupName, VariablesToObserve>;
     223             : 
     224           0 :   static constexpr Options::String help =
     225             :       "Observe volume tensor fields on the characteristic grid. Writes volume "
     226             :       "quantities from the tensors listed in the 'VariablesToObserve' "
     227             :       "option to volume subfiles in the subgroup named by the option "
     228             :       "'SubgroupName', into the volume h5 file named by the option "
     229             :       "'VolumeFileName' of Observers.\n";
     230             : 
     231           0 :   ObserveFields() = default;
     232             : 
     233           0 :   ObserveFields(const std::string& subgroup_name,
     234             :                 const std::vector<std::string>& variables_to_observe,
     235             :                 const Options::Context& context = {});
     236             : 
     237           0 :   using compute_tags_for_observation_box = tmpl::list<
     238             :       Tags::Psi0Compute, Tags::Psi1Compute, Tags::Psi2Compute,
     239             :       Tags::SwshDerivativeCompute<Tags::BondiJ, Spectral::Swsh::Tags::Eth>,
     240             :       Tags::SwshDerivativeCompute<Tags::BondiW, Spectral::Swsh::Tags::Eth>,
     241             :       Tags::NewmanPenroseAlphaCompute, Tags::NewmanPenroseBetaCompute,
     242             :       Tags::NewmanPenroseGammaCompute, Tags::NewmanPenroseEpsilonCompute,
     243             :       // Tags::NewmanPenroseKappaCompute,
     244             :       // in our choice of tetrad, \kappa=0
     245             :       Tags::NewmanPenroseTauCompute, Tags::NewmanPenroseSigmaCompute,
     246             :       Tags::NewmanPenroseRhoCompute, Tags::NewmanPenrosePiCompute,
     247             :       Tags::NewmanPenroseNuCompute, Tags::NewmanPenroseMuCompute,
     248             :       Tags::NewmanPenroseLambdaCompute,
     249             :       Tags::SwshDerivativeCompute<Tags::NewmanPenrosePi,
     250             :                                   Spectral::Swsh::Tags::Eth>,
     251             :       Tags::SwshDerivativeCompute<Tags::NewmanPenrosePi,
     252             :                                   Spectral::Swsh::Tags::Ethbar>,
     253             :       Tags::DyCompute<Tags::NewmanPenrosePi>,
     254             :       Tags::DyCompute<Tags::NewmanPenroseMu>,
     255             :       // H is the final hypersurface quantity, so unlike the other
     256             :       // Bondi variables its radial derivative is not needed by (and
     257             :       // is not computed during) the hypersurface integration steps.
     258             :       // It is only of interest as volume output, so it is computed
     259             :       // on demand here rather than stored in the evolution DataBox.
     260             :       Tags::DyCompute<Tags::BondiH>>;
     261             : 
     262           0 :   using return_tags = tmpl::list<>;
     263           0 :   using argument_tags = tmpl::list<::Tags::ObservationBox>;
     264             : 
     265             :   template <typename DataBoxType, typename ComputeTagsList,
     266             :             typename Metavariables, typename ArrayIndex,
     267             :             typename ParallelComponent>
     268           0 :   void operator()(const ObservationBox<DataBoxType, ComputeTagsList>& box,
     269             :                   Parallel::GlobalCache<Metavariables>& cache,
     270             :                   const ArrayIndex& /*array_index*/,
     271             :                   const ParallelComponent* const /*component*/,
     272             :                   const ObservationValue& /*observation_value*/) const {
     273             :     const bool write_synchronously =
     274             :         Parallel::number_of_procs<size_t>(cache) == 1;
     275             : 
     276             :     // Number of points
     277             :     const size_t l_max = get<Tags::LMax>(box);
     278             :     const size_t l_max_plus_one_squared = square(l_max + 1);
     279             :     const size_t number_of_angular_points =
     280             :         Spectral::Swsh::number_of_swsh_collocation_points(l_max);
     281             :     const size_t number_of_radial_grid_points =
     282             :         get<Tags::NumberOfRadialPoints>(box);
     283             : 
     284             :     // Time
     285             :     const double time = get<::Tags::Time>(box);
     286             : 
     287             :     // Observer writer
     288             :     auto observer_proxy = Parallel::get_parallel_component<
     289             :         ::observers::ObserverWriter<Metavariables>>(cache)[0];
     290             : 
     291             :     ////////////////////////////////////////////////////////////
     292             :     // The inertial retarded time is special because it's stored as a
     293             :     // Scalar<DataVector> because it's only real and only has one set of angular
     294             :     // points worth of data to write. However, all the machinery is for a
     295             :     // SpinWeighted<ComplexDataVector>. Luckily there is a
     296             :     // ComplexInertialRetardedTime where the real part is the
     297             :     // InertialRetardedTime and the imaginary part is 0, so we use that instead,
     298             :     // swapping the names where necessary.
     299             :     // Put into volume subfile named /<SubgroupName>/InertialRetardedTime.vol
     300             :     const std::string inertial_retarded_time_name =
     301             :         detail::name<Tags::ComplexInertialRetardedTime>();
     302             :     if (variables_to_observe_.count(inertial_retarded_time_name) == 1) {
     303             :       const std::string subfile_name = subgroup_path_
     304             :                                        + "/" + inertial_retarded_time_name;
     305             :       const observers::ObservationId observation_id{time,
     306             :         subfile_name + ".vol"};
     307             :       const std::vector<size_t> extents_vector{{l_max, l_max}};
     308             :       const std::vector<Spectral::Basis> bases_vector{
     309             :         {Spectral::Basis::SphericalHarmonic,
     310             :          Spectral::Basis::SphericalHarmonic}};
     311             :       const std::vector<Spectral::Quadrature> quadratures_vector{
     312             :         {Spectral::Quadrature::Gauss,
     313             :          Spectral::Quadrature::Equiangular}};
     314             : 
     315             :       const SpinWeighted<ComplexDataVector, 0>& complex_inertial_retarded_time =
     316             :           get(get<Tags::ComplexInertialRetardedTime>(box));
     317             : 
     318             :       // Allocate a buffer to receive the transformed data, since
     319             :       // WriteVolumeData only understands DataVectors, not
     320             :       // ComplexDataVectors.
     321             :       DataVector goldberg_modes_interleaved_dv(2 * l_max_plus_one_squared);
     322             : 
     323             :       // A non-owning view of goldberg_modes_interleaved_dv,
     324             :       // with the correct spin
     325             :       SpinWeighted<ComplexModalVector, 0> goldberg_mode_view;
     326             :       goldberg_mode_view.set_data_ref(
     327             :         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
     328             :         reinterpret_cast<std::complex<double>*>(
     329             :           goldberg_modes_interleaved_dv.data()),
     330             :         l_max_plus_one_squared);
     331             : 
     332             :       Spectral::Swsh::libsharp_to_goldberg_modes(
     333             :           make_not_null(&goldberg_mode_view),
     334             :           Spectral::Swsh::swsh_transform(
     335             :               l_max, 1, complex_inertial_retarded_time),
     336             :           l_max);
     337             : 
     338             :       const std::vector<TensorComponent> tensor_components{
     339             :         {inertial_retarded_time_name, goldberg_modes_interleaved_dv}};
     340             : 
     341             :       if (write_synchronously) {
     342             :         Parallel::local_synchronous_action<
     343             :           observers::ThreadedActions::WriteVolumeData>(
     344             :               observer_proxy, cache,
     345             :               Parallel::get<observers::Tags::VolumeFileName>(cache),
     346             :               subfile_name, observation_id,
     347             :               std::vector<ElementVolumeData>{
     348             :                 {inertial_retarded_time_name, tensor_components,
     349             :                  extents_vector, bases_vector,
     350             :                  quadratures_vector}});
     351             :       } else {
     352             :         // Send to observer writer
     353             :         Parallel::threaded_action<
     354             :           observers::ThreadedActions::WriteVolumeData>(
     355             :               observer_proxy,
     356             :               Parallel::get<observers::Tags::VolumeFileName>(cache),
     357             :               subfile_name, observation_id,
     358             :               std::vector<ElementVolumeData>{
     359             :                 {inertial_retarded_time_name, tensor_components,
     360             :                  extents_vector, bases_vector,
     361             :                  quadratures_vector}});
     362             :       }
     363             :     }
     364             : 
     365             :     ////////////////////////////////////////////////////////////
     366             :     // One minus y is also special because every angular grid point for a given
     367             :     // radius holds the same value. Thus we only need to write one double per
     368             :     // radial grid point corresponding to 1 - y. Put into volume subfile named
     369             :     // /<SubgroupName>/OneMinusY.vol
     370             :     const std::string one_minus_y_name = detail::name<Tags::OneMinusY>();
     371             :     if (variables_to_observe_.count(one_minus_y_name) == 1) {
     372             :       const std::string subfile_name = subgroup_path_ + "/" + one_minus_y_name;
     373             :       const observers::ObservationId observation_id{time,
     374             :         subfile_name + ".vol"};
     375             :       const std::vector<size_t> extents_vector{number_of_radial_grid_points};
     376             :       const std::vector<Spectral::Basis> bases_vector{
     377             :         Spectral::Basis::Legendre};
     378             :       const std::vector<Spectral::Quadrature> quadratures_vector{
     379             :         Spectral::Quadrature::GaussLobatto};
     380             : 
     381             :       const ComplexDataVector& one_minus_y =
     382             :           get(get<Tags::OneMinusY>(box)).data();
     383             : 
     384             :       DataVector one_minus_y_to_write(number_of_radial_grid_points);
     385             : 
     386             :       for (size_t radial_index = 0; radial_index < number_of_radial_grid_points;
     387             :            radial_index++) {
     388             :         one_minus_y_to_write[radial_index] =
     389             :             real(one_minus_y[radial_index * number_of_angular_points]);
     390             :       }
     391             : 
     392             :       const std::vector<TensorComponent> tensor_components{
     393             :         {one_minus_y_name, one_minus_y_to_write}};
     394             : 
     395             :       if (write_synchronously) {
     396             :         Parallel::local_synchronous_action<
     397             :           observers::ThreadedActions::WriteVolumeData>(
     398             :               observer_proxy, cache,
     399             :               Parallel::get<observers::Tags::VolumeFileName>(cache),
     400             :               subfile_name, observation_id,
     401             :               std::vector<ElementVolumeData>{
     402             :                 {one_minus_y_name, tensor_components,
     403             :                  extents_vector, bases_vector,
     404             :                  quadratures_vector}});
     405             :       } else {
     406             :         // Send to observer writer
     407             :         Parallel::threaded_action<
     408             :           observers::ThreadedActions::WriteVolumeData>(
     409             :               observer_proxy,
     410             :               Parallel::get<observers::Tags::VolumeFileName>(cache),
     411             :               subfile_name, observation_id,
     412             :               std::vector<ElementVolumeData>{
     413             :                 {one_minus_y_name, tensor_components,
     414             :                  extents_vector, bases_vector,
     415             :                  quadratures_vector}});
     416             :       }
     417             :     }
     418             : 
     419             :     ////////////////////////////////////////////////////////////
     420             :     // Everything else gets written together into the volume subfile named
     421             :     // /<SubgroupName>/VolumeData.vol
     422             : 
     423             :     // Field-independent info for writing into volume data file
     424             :     const std::string subfile_name = subgroup_path_ + "/VolumeData";
     425             :     const observers::ObservationId observation_id{time,
     426             :       subfile_name + ".vol"};
     427             :     const std::vector<size_t> extents_vector{
     428             :       {number_of_radial_grid_points, l_max, l_max}};
     429             :     const std::vector<Spectral::Basis> bases_vector{
     430             :       {Spectral::Basis::Legendre,
     431             :        Spectral::Basis::SphericalHarmonic,
     432             :        Spectral::Basis::SphericalHarmonic}};
     433             :     const std::vector<Spectral::Quadrature> quadratures_vector{
     434             :       {Spectral::Quadrature::GaussLobatto,
     435             :        Spectral::Quadrature::Gauss,
     436             :        Spectral::Quadrature::Equiangular}};
     437             : 
     438             :     // Create tensor_components by looping over all available spin
     439             :     // weighted tags and checking if we are observing this tag.
     440             :     std::vector<TensorComponent> tensor_components;
     441             :     tmpl::for_each<spin_weighted_tags_to_observe>([&](auto tag_v) {
     442             :       using tag = tmpl::type_from<decltype(tag_v)>;
     443             :       constexpr int spin = tag::type::type::spin;
     444             :       const std::string name = detail::name<tag>();
     445             : 
     446             :       // If we aren't observing this tag, then skip it
     447             :       if (not variables_to_observe_.contains(name)) {
     448             :         return;
     449             :       }
     450             : 
     451             :       const SpinWeighted<ComplexDataVector, spin>& field =
     452             :         get(get<tag>(box));
     453             : 
     454             :       // Allocate a buffer to receive the transformed data, since
     455             :       // WriteVolumeData only understands DataVectors, not
     456             :       // ComplexDataVectors.
     457             :       DataVector goldberg_modes_interleaved_dv(2 *
     458             :         l_max_plus_one_squared * number_of_radial_grid_points);
     459             : 
     460             :       // A non-owning view of goldberg_modes_interleaved_dv,
     461             :       // with the correct spin
     462             :       SpinWeighted<ComplexModalVector, spin> goldberg_mode_view;
     463             :       goldberg_mode_view.set_data_ref(
     464             :         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
     465             :         reinterpret_cast<std::complex<double>*>(
     466             :           goldberg_modes_interleaved_dv.data()),
     467             :         l_max_plus_one_squared * number_of_radial_grid_points);
     468             : 
     469             :       Spectral::Swsh::libsharp_to_goldberg_modes(
     470             :         make_not_null(&goldberg_mode_view),
     471             :         Spectral::Swsh::swsh_transform(l_max,
     472             :           number_of_radial_grid_points, field), l_max);
     473             : 
     474             :       tensor_components.emplace_back(
     475             :         name, std::move(goldberg_modes_interleaved_dv));
     476             : 
     477             :     });
     478             : 
     479             :     if (write_synchronously) {
     480             :       Parallel::local_synchronous_action<
     481             :         observers::ThreadedActions::WriteVolumeData>(
     482             :         observer_proxy, cache,
     483             :         Parallel::get<observers::Tags::VolumeFileName>(cache),
     484             :         subfile_name, observation_id,
     485             :         std::vector<ElementVolumeData>{
     486             :           {"VolumeData", tensor_components,
     487             :            extents_vector, bases_vector,
     488             :            quadratures_vector}});
     489             :     } else {
     490             :       // Send to observer writer
     491             :       Parallel::threaded_action<
     492             :         observers::ThreadedActions::WriteVolumeData>(
     493             :         observer_proxy,
     494             :         Parallel::get<observers::Tags::VolumeFileName>(cache),
     495             :         subfile_name, observation_id,
     496             :         std::vector<ElementVolumeData>{
     497             :           {"VolumeData", tensor_components,
     498             :            extents_vector, bases_vector,
     499             :            quadratures_vector}});
     500             :     }
     501             :   }
     502             : 
     503           0 :   using is_ready_argument_tags = tmpl::list<>;
     504             : 
     505             :   template <typename Metavariables, typename ArrayIndex, typename Component>
     506           0 :   bool is_ready(Parallel::GlobalCache<Metavariables>& /*cache*/,
     507             :                 const ArrayIndex& /*array_index*/,
     508             :                 const Component* const /*meta*/) const {
     509             :     return true;
     510             :   }
     511             : 
     512           1 :   bool needs_evolved_variables() const override { return true; }
     513             : 
     514             :   // NOLINTNEXTLINE(google-runtime-references)
     515           0 :   void pup(PUP::er& p) override {
     516             :     Event::pup(p);
     517             :     p | subgroup_path_;
     518             :     p | variables_to_observe_;
     519             :   }
     520             : 
     521             :  private:
     522           0 :   std::string subgroup_path_;
     523           0 :   std::unordered_set<std::string> variables_to_observe_;
     524             : };
     525             : 
     526             : ObserveFields::ObserveFields(
     527             :     const std::string& subgroup_name,
     528             :     const std::vector<std::string>& variables_to_observe,
     529             :     const Options::Context& context)
     530             :     : subgroup_path_("/" + subgroup_name),
     531             :       variables_to_observe_([&context, &variables_to_observe]() {
     532             :         std::unordered_set<std::string> result{};
     533             :         for (const auto& tensor : variables_to_observe) {
     534             :           if (result.contains(tensor)) {
     535             :             PARSE_ERROR(
     536             :                 context,
     537             :                 "Listed variable '"
     538             :                     << tensor
     539             :                     << "' more than once in list of variables to observe.");
     540             :           }
     541             :           result.insert(tensor);
     542             :         }
     543             :         return result;
     544             :       }()) {
     545             :   std::unordered_set<std::string> valid_tensors{};
     546             :   tmpl::for_each<available_tags_to_observe>([&valid_tensors](auto tag_v) {
     547             :     using tag = tmpl::type_from<decltype(tag_v)>;
     548             :     valid_tensors.insert(detail::name<tag>());
     549             :   });
     550             : 
     551             :   for (const auto& name : variables_to_observe_) {
     552             :     if (not valid_tensors.contains(name)) {
     553             :       PARSE_ERROR(
     554             :           context,
     555             :           name << " is not an available variable. Available variables:\n"
     556             :                << valid_tensors);
     557             :     }
     558             :   }
     559             : }
     560             : 
     561             : /// \cond
     562             : PUP::able::PUP_ID ObserveFields::my_PUP_ID = 0;  // NOLINT
     563             : /// \endcond
     564             : }  // namespace Cce::Events

Generated by: LCOV version 1.14