SpECTRE Documentation Coverage Report
Current view: top level - Evolution/Systems/Cce/Events - ObserveFields.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 11.5 % 26 3
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 <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 2.0-1