SpECTRE Documentation Coverage Report
Current view: top level - ParallelAlgorithms/Events - ObserveNorms.hpp Hit Total Coverage
Commit: 1f2210958b4f38fdc0400907ee7c6d5af5111418 Lines: 4 50 8.0 %
Date: 2025-12-05 05:03:31
Legend: Lines: hit not hit

          Line data    Source code
       1           0 : // Distributed under the MIT License.
       2             : // See LICENSE.txt for details.
       3             : 
       4             : #pragma once
       5             : 
       6             : #include <cstddef>
       7             : #include <optional>
       8             : #include <pup.h>
       9             : #include <string>
      10             : #include <unordered_map>
      11             : #include <utility>
      12             : #include <vector>
      13             : 
      14             : #include "DataStructures/DataBox/DataBox.hpp"
      15             : #include "DataStructures/DataBox/ObservationBox.hpp"
      16             : #include "DataStructures/DataBox/TagName.hpp"
      17             : #include "DataStructures/DataVector.hpp"
      18             : #include "DataStructures/Tensor/Tensor.hpp"
      19             : #include "Domain/Structure/ElementId.hpp"
      20             : #include "Domain/Tags.hpp"
      21             : #include "IO/Observer/GetSectionObservationKey.hpp"
      22             : #include "IO/Observer/Helpers.hpp"
      23             : #include "IO/Observer/ObservationId.hpp"
      24             : #include "IO/Observer/ObserverComponent.hpp"
      25             : #include "IO/Observer/ReductionActions.hpp"
      26             : #include "IO/Observer/TypeOfObservation.hpp"
      27             : #include "NumericalAlgorithms/LinearOperators/DefiniteIntegral.hpp"
      28             : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
      29             : #include "NumericalAlgorithms/Spectral/Quadrature.hpp"
      30             : #include "Options/String.hpp"
      31             : #include "Parallel/ArrayIndex.hpp"
      32             : #include "Parallel/GlobalCache.hpp"
      33             : #include "Parallel/Invoke.hpp"
      34             : #include "Parallel/Local.hpp"
      35             : #include "Parallel/Reduction.hpp"
      36             : #include "Parallel/TypeTraits.hpp"
      37             : #include "ParallelAlgorithms/Events/Tags.hpp"
      38             : #include "ParallelAlgorithms/EventsAndTriggers/Event.hpp"
      39             : #include "Utilities/ErrorHandling/Assert.hpp"
      40             : #include "Utilities/ErrorHandling/Error.hpp"
      41             : #include "Utilities/Functional.hpp"
      42             : #include "Utilities/OptionalHelpers.hpp"
      43             : #include "Utilities/PrettyType.hpp"
      44             : #include "Utilities/Serialization/CharmPupable.hpp"
      45             : #include "Utilities/TMPL.hpp"
      46             : 
      47             : namespace Events {
      48             : /// @{
      49             : /*!
      50             :  * \brief Compute norms of tensors in the DataBox and write them to disk.
      51             :  *
      52             :  * The L2 norm is computed as the RMS, so
      53             :  *
      54             :  * \f{align*}{
      55             :  * L_2(u)=\sqrt{\frac{1}{N}\sum_{i=0}^{N} u_i^2}
      56             :  * \f}
      57             :  *
      58             :  * where \f$N\f$ is the number of grid points.
      59             :  *
      60             :  * The norm can be taken for each individual component, or summed over
      61             :  * components. For the max/min it is then the max/min over all components, while
      62             :  * for the L2 norm we have (for a 3d vector, 2d and 1d are similar)
      63             :  *
      64             :  * \f{align*}{
      65             :  * L_2(v^k)=\sqrt{\frac{1}{N}\sum_{i=0}^{N} \left[(v^x_i)^2 + (v^y_i)^2
      66             :  *          + (v^z_i)^2\right]}
      67             :  * \f}
      68             :  *
      69             :  * The L2 integral norm is:
      70             :  *
      71             :  * \begin{equation}
      72             :  * L_{2,\mathrm{int}}(v^k) = \sqrt{\frac{1}{V}\int_\Omega \left[
      73             :  *   (v^x_i)^2 + (v^y_i)^2 + (v^z_i)^2\right] \mathrm{d}V}
      74             :  * \end{equation}
      75             :  *
      76             :  * where $V=\int_\Omega$ is the volume of the entire domain in inertial
      77             :  * coordinates.
      78             :  *
      79             :  * VolumeIntegral only computes the volume integral without any normalization.
      80             :  *
      81             :  * Here is an example of an input file:
      82             :  *
      83             :  * \snippet Test_ObserveNorms.cpp input_file_examples
      84             :  *
      85             :  * \note The `NonTensorComputeTags` are intended to be used for `Variables`
      86             :  * compute tags like `Tags::DerivCompute`
      87             :  *
      88             :  * \par Array sections
      89             :  * This event supports sections (see `Parallel::Section`). Set the
      90             :  * `ArraySectionIdTag` template parameter to split up observations into subsets
      91             :  * of elements. The `observers::Tags::ObservationKey<ArraySectionIdTag>` must be
      92             :  * available in the DataBox. It identifies the section and is used as a suffix
      93             :  * for the path in the output file.
      94             :  *
      95             :  * \par Option name
      96             :  * The `OptionName` template parameter is used to give the event a name in the
      97             :  * input file. If it is not specified, the name defaults to "ObserveNorms". If
      98             :  * you have multiple `ObserveNorms` events in the input file, you must specify a
      99             :  * unique name for each one. This can happen, for example, if you want to
     100             :  * observe norms the full domain and also over a section of the domain.
     101             :  */
     102             : template <typename ObservableTensorTagsList,
     103             :           typename NonTensorComputeTagsList = tmpl::list<>,
     104             :           typename ArraySectionIdTag = void, typename OptionName = void>
     105           1 : class ObserveNorms;
     106             : 
     107             : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
     108             :           typename ArraySectionIdTag, typename OptionName>
     109           0 : class ObserveNorms<tmpl::list<ObservableTensorTags...>,
     110             :                    tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
     111             :                    OptionName> : public Event {
     112             :  private:
     113           0 :   struct ObserveTensor {
     114           0 :     static constexpr Options::String help = {
     115             :         "The tensor to reduce, and how to reduce it."};
     116             : 
     117           0 :     struct Name {
     118           0 :       using type = std::string;
     119           0 :       static constexpr Options::String help = {
     120             :           "The name of the tensor to observe."};
     121             :     };
     122           0 :     struct NormType {
     123           0 :       using type = std::string;
     124           0 :       static constexpr Options::String help = {
     125             :           "The type of norm to use. Must be one of Max, Min, L2Norm, "
     126             :           "L2IntegralNorm, or VolumeIntegral."};
     127             :     };
     128           0 :     struct Components {
     129           0 :       using type = std::string;
     130           0 :       static constexpr Options::String help = {
     131             :           "How to handle tensor components. Must be Individual or Sum."};
     132             :     };
     133             : 
     134           0 :     using options = tmpl::list<Name, NormType, Components>;
     135             : 
     136           0 :     ObserveTensor() = default;
     137             : 
     138           0 :     ObserveTensor(std::string in_tensor, std::string in_norm_type,
     139             :                   std::string in_components,
     140             :                   const Options::Context& context = {});
     141             : 
     142           0 :     std::string tensor{};
     143           0 :     std::string norm_type{};
     144           0 :     std::string components{};
     145             :   };
     146             : 
     147           0 :   using ReductionData = Parallel::ReductionData<
     148             :       // Observation value
     149             :       Parallel::ReductionDatum<double, funcl::AssertEqual<>>,
     150             :       // Number of grid points
     151             :       Parallel::ReductionDatum<size_t, funcl::Plus<>>,
     152             :       // Total volume
     153             :       Parallel::ReductionDatum<double, funcl::Plus<>>,
     154             :       // Max
     155             :       Parallel::ReductionDatum<std::vector<double>,
     156             :                                funcl::ElementWise<funcl::Max<>>>,
     157             :       // Min
     158             :       Parallel::ReductionDatum<std::vector<double>,
     159             :                                funcl::ElementWise<funcl::Min<>>>,
     160             :       // L2Norm
     161             :       Parallel::ReductionDatum<
     162             :           std::vector<double>, funcl::ElementWise<funcl::Plus<>>,
     163             :           funcl::ElementWise<funcl::Sqrt<funcl::Divides<>>>,
     164             :           std::index_sequence<1>>,
     165             :       // L2IntegralNorm
     166             :       Parallel::ReductionDatum<
     167             :           std::vector<double>, funcl::ElementWise<funcl::Plus<>>,
     168             :           funcl::ElementWise<funcl::Sqrt<funcl::Divides<>>>,
     169             :           std::index_sequence<2>>,
     170             :       // VolumeIntegral
     171             :       Parallel::ReductionDatum<std::vector<double>,
     172             :                              funcl::ElementWise<funcl::Plus<>>>>;
     173             : 
     174             :  public:
     175           0 :   static std::string name() {
     176             :     if constexpr (std::is_same_v<OptionName, void>) {
     177             :       return "ObserveNorms";
     178             :     } else {
     179             :       return pretty_type::name<OptionName>();
     180             :     }
     181             :   }
     182             : 
     183             :   /// The name of the subfile inside the HDF5 file
     184           1 :   struct SubfileName {
     185           0 :     using type = std::string;
     186           0 :     static constexpr Options::String help = {
     187             :         "The name of the subfile inside the HDF5 file without an extension and "
     188             :         "without a preceding '/'."};
     189             :   };
     190             :   /// The tensor to observe and how to do the reduction
     191           1 :   struct TensorsToObserve {
     192           0 :     using type = std::vector<ObserveTensor>;
     193           0 :     static constexpr Options::String help = {
     194             :         "List specifying each tensor to observe and how it is reduced."};
     195             :   };
     196             : 
     197           0 :   explicit ObserveNorms(CkMigrateMessage* msg);
     198             :   using PUP::able::register_constructor;
     199           0 :   WRAPPED_PUPable_decl_template(ObserveNorms);  // NOLINT
     200             : 
     201           0 :   using options = tmpl::list<SubfileName, TensorsToObserve>;
     202             : 
     203           0 :   static constexpr Options::String help =
     204             :       "Observe norms of tensors in the DataBox.\n"
     205             :       "\n"
     206             :       "You can choose the norm type for each observation. Note that the\n"
     207             :       "'L2Norm' (root mean square) emphasizes regions of the domain with many\n"
     208             :       "grid points, whereas the 'L2IntegralNorm' emphasizes regions of the\n"
     209             :       "domain with large volume. Choose wisely! When in doubt, try the\n"
     210             :       "'L2Norm' first.\n"
     211             :       "\n"
     212             :       "Writes reduction quantities:\n"
     213             :       " * Observation value (e.g. Time or IterationId)\n"
     214             :       " * NumberOfPoints = total number of points in the domain\n"
     215             :       " * Volume = total volume of the domain in inertial coordinates\n"
     216             :       " * Max values\n"
     217             :       " * Min values\n"
     218             :       " * L2-norm values\n"
     219             :       " * L2 integral norm values\n"
     220             :       " * Volume integral values\n";
     221             : 
     222           0 :   ObserveNorms() = default;
     223             : 
     224           0 :   ObserveNorms(const std::string& subfile_name,
     225             :                const std::vector<ObserveTensor>& observe_tensors);
     226             : 
     227           0 :   using observed_reduction_data_tags =
     228             :       observers::make_reduction_data_tags<tmpl::list<ReductionData>>;
     229             : 
     230           0 :   using compute_tags_for_observation_box =
     231             :       tmpl::list<ObservableTensorTags..., NonTensorComputeTags...>;
     232             : 
     233           0 :   using return_tags = tmpl::list<>;
     234           0 :   using argument_tags = tmpl::list<::Tags::ObservationBox>;
     235             : 
     236             :   template <typename TensorToObserveTag, typename ComputeTagsList,
     237             :             typename DataBoxType, size_t Dim>
     238           0 :   void observe_norms_impl(
     239             :       gsl::not_null<
     240             :           std::unordered_map<std::string, std::pair<std::vector<double>,
     241             :                                                     std::vector<std::string>>>*>
     242             :           norm_values_and_names,
     243             :       const ObservationBox<ComputeTagsList, DataBoxType>& box,
     244             :       const Mesh<Dim>& mesh, const DataVector& det_jacobian,
     245             :       size_t number_of_points) const;
     246             : 
     247             :   template <typename ComputeTagsList, typename DataBoxType,
     248             :             typename Metavariables, size_t VolumeDim,
     249             :             typename ParallelComponent>
     250           0 :   void operator()(const ObservationBox<ComputeTagsList, DataBoxType>& box,
     251             :                   Parallel::GlobalCache<Metavariables>& cache,
     252             :                   const ElementId<VolumeDim>& array_index,
     253             :                   const ParallelComponent* const /*meta*/,
     254             :                   const ObservationValue& observation_value) const;
     255             : 
     256           0 :   using observation_registration_tags = tmpl::list<::Tags::DataBox>;
     257             : 
     258             :   template <typename DbTagsList>
     259             :   std::optional<
     260             :       std::pair<observers::TypeOfObservation, observers::ObservationKey>>
     261           0 :   get_observation_type_and_key_for_registration(
     262             :       const db::DataBox<DbTagsList>& box) const {
     263             :     const std::optional<std::string> section_observation_key =
     264             :         observers::get_section_observation_key<ArraySectionIdTag>(box);
     265             :     if (not section_observation_key.has_value()) {
     266             :       return std::nullopt;
     267             :     }
     268             :     return {{observers::TypeOfObservation::Reduction,
     269             :              observers::ObservationKey(
     270             :                  subfile_path_ + section_observation_key.value() + ".dat")}};
     271             :   }
     272             : 
     273           0 :   using is_ready_argument_tags = tmpl::list<>;
     274             : 
     275             :   template <typename Metavariables, typename ArrayIndex, typename Component>
     276           0 :   bool is_ready(Parallel::GlobalCache<Metavariables>& /*cache*/,
     277             :                 const ArrayIndex& /*array_index*/,
     278             :                 const Component* const /*meta*/) const {
     279             :     return true;
     280             :   }
     281             : 
     282           1 :   bool needs_evolved_variables() const override { return true; }
     283             : 
     284             :   // NOLINTNEXTLINE(google-runtime-references)
     285           0 :   void pup(PUP::er& p) override;
     286             : 
     287             :  private:
     288           0 :   std::string subfile_path_;
     289           0 :   std::vector<std::string> tensor_names_{};
     290           0 :   std::vector<std::string> tensor_norm_types_{};
     291           0 :   std::vector<std::string> tensor_components_{};
     292             : };
     293             : /// @}
     294             : 
     295             : /// \cond
     296             : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
     297             :           typename ArraySectionIdTag, typename OptionName>
     298             : ObserveNorms<tmpl::list<ObservableTensorTags...>,
     299             :              tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
     300             :              OptionName>::ObserveNorms(CkMigrateMessage* msg)
     301             :     : Event(msg) {}
     302             : 
     303             : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
     304             :           typename ArraySectionIdTag, typename OptionName>
     305             : ObserveNorms<tmpl::list<ObservableTensorTags...>,
     306             :              tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
     307             :              OptionName>::ObserveNorms(const std::string& subfile_name,
     308             :                                        const std::vector<ObserveTensor>&
     309             :                                            observe_tensors)
     310             :     : subfile_path_("/" + subfile_name) {
     311             :   tensor_names_.reserve(observe_tensors.size());
     312             :   tensor_norm_types_.reserve(observe_tensors.size());
     313             :   tensor_components_.reserve(observe_tensors.size());
     314             :   for (const auto& observe_tensor : observe_tensors) {
     315             :     tensor_names_.push_back(observe_tensor.tensor);
     316             :     tensor_norm_types_.push_back(observe_tensor.norm_type);
     317             :     tensor_components_.push_back(observe_tensor.components);
     318             :   }
     319             : }
     320             : 
     321             : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
     322             :           typename ArraySectionIdTag, typename OptionName>
     323             : ObserveNorms<
     324             :     tmpl::list<ObservableTensorTags...>, tmpl::list<NonTensorComputeTags...>,
     325             :     ArraySectionIdTag,
     326             :     OptionName>::ObserveTensor::ObserveTensor(std::string in_tensor,
     327             :                                               std::string in_norm_type,
     328             :                                               std::string in_components,
     329             :                                               const Options::Context& context)
     330             :     : tensor(std::move(in_tensor)),
     331             :       norm_type(std::move(in_norm_type)),
     332             :       components(std::move(in_components)) {
     333             :   if (((tensor != db::tag_name<ObservableTensorTags>()) and ...)) {
     334             :     PARSE_ERROR(
     335             :         context, "Tensor '"
     336             :                      << tensor << "' is not known. Known tensors are: "
     337             :                      << ((db::tag_name<ObservableTensorTags>() + ",") + ...));
     338             :   }
     339             :   if (norm_type != "Max" and norm_type != "Min" and norm_type != "L2Norm" and
     340             :       norm_type != "L2IntegralNorm" and norm_type != "VolumeIntegral") {
     341             :     PARSE_ERROR(
     342             :         context,
     343             :         "NormType must be one of Max, Min, L2Norm, L2IntegralNorm, or "
     344             :             "VolumeIntegral not " << norm_type);
     345             :   }
     346             :   if (components != "Individual" and components != "Sum") {
     347             :     PARSE_ERROR(context,
     348             :                 "Components must be Individual or Sum, not " << components);
     349             :   }
     350             : }
     351             : 
     352             : // implementation of ObserveNorms::operator() factored out to save on compile
     353             : // time and compile memory
     354             : namespace ObserveNorms_impl {
     355             : void check_norm_is_observable(const std::string& tensor_name,
     356             :                               bool tag_has_value);
     357             : 
     358             : template <size_t Dim>
     359             : void fill_norm_values_and_names(
     360             :     gsl::not_null<std::unordered_map<
     361             :         std::string, std::pair<std::vector<double>, std::vector<std::string>>>*>
     362             :         norm_values_and_names,
     363             :     const std::pair<std::vector<std::string>, std::vector<DataVector>>&
     364             :         names_and_components,
     365             :     const Mesh<Dim>& mesh, const DataVector& det_jacobian,
     366             :     const std::string& tensor_name, const std::string& tensor_norm_type,
     367             :     const std::string& tensor_component, size_t number_of_points);
     368             : 
     369             : // Expand complex data into real and imaginary parts, or just forward real data
     370             : std::pair<std::vector<std::string>, std::vector<DataVector>>
     371             : split_complex_vector_of_data(
     372             :     std::pair<std::vector<std::string>, std::vector<DataVector>>&&
     373             :         names_and_components);
     374             : std::pair<std::vector<std::string>, std::vector<DataVector>>
     375             : split_complex_vector_of_data(
     376             :     const std::pair<std::vector<std::string>, std::vector<ComplexDataVector>>&
     377             :         names_and_components);
     378             : }  // namespace ObserveNorms_impl
     379             : 
     380             : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
     381             :           typename ArraySectionIdTag, typename OptionName>
     382             : template <typename TensorToObserveTag, typename ComputeTagsList,
     383             :           typename DataBoxType, size_t Dim>
     384             : void ObserveNorms<tmpl::list<ObservableTensorTags...>,
     385             :                   tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
     386             :                   OptionName>::
     387             :     observe_norms_impl(
     388             :         const gsl::not_null<std::unordered_map<
     389             :             std::string,
     390             :             std::pair<std::vector<double>, std::vector<std::string>>>*>
     391             :             norm_values_and_names,
     392             :         const ObservationBox<ComputeTagsList, DataBoxType>& box,
     393             :         const Mesh<Dim>& mesh, const DataVector& det_jacobian,
     394             :         const size_t number_of_points) const {
     395             :   const std::string tensor_name = db::tag_name<TensorToObserveTag>();
     396             :   for (size_t i = 0; i < tensor_names_.size(); ++i) {
     397             :     if (tensor_name == tensor_names_[i]) {
     398             :       ObserveNorms_impl::check_norm_is_observable(
     399             :           tensor_name, has_value(get<TensorToObserveTag>(box)));
     400             :       ObserveNorms_impl::fill_norm_values_and_names(
     401             :           norm_values_and_names,
     402             :           ObserveNorms_impl::split_complex_vector_of_data(
     403             :               value(get<TensorToObserveTag>(box)).get_vector_of_data()),
     404             :           mesh, det_jacobian, tensor_name, tensor_norm_types_[i],
     405             :           tensor_components_[i], number_of_points);
     406             :     }
     407             :   }
     408             : }
     409             : 
     410             : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
     411             :           typename ArraySectionIdTag, typename OptionName>
     412             : template <typename ComputeTagsList, typename DataBoxType,
     413             :           typename Metavariables, size_t VolumeDim, typename ParallelComponent>
     414             : void ObserveNorms<tmpl::list<ObservableTensorTags...>,
     415             :                   tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
     416             :                   OptionName>::
     417             : operator()(const ObservationBox<ComputeTagsList, DataBoxType>& box,
     418             :            Parallel::GlobalCache<Metavariables>& cache,
     419             :            const ElementId<VolumeDim>& array_index,
     420             :            const ParallelComponent* const /*meta*/,
     421             :            const ObservationValue& observation_value) const {
     422             :   // Skip observation on elements that are not part of a section
     423             :   const std::optional<std::string> section_observation_key =
     424             :       observers::get_section_observation_key<ArraySectionIdTag>(box);
     425             :   if (not section_observation_key.has_value()) {
     426             :     return;
     427             :   }
     428             : 
     429             :   const auto& mesh = get<::Events::Tags::ObserverMesh<VolumeDim>>(box);
     430             :   const auto det_jacobian = [&box, &mesh]() -> DataVector {
     431             :     if constexpr (VolumeDim == 3 and
     432             :                   db::tag_is_retrievable_v<
     433             :                       domain::Tags::Coordinates<VolumeDim, Frame::Inertial>,
     434             :                       std::decay_t<decltype(box)>>) {
     435             :       if (mesh.basis(2) == Spectral::Basis::Cartoon) {
     436             :         if (mesh.quadrature(2) == Spectral::Quadrature::SphericalSymmetry) {
     437             :           // Spherical Symmetry, needs x^2 cartesian to spherical jacobian
     438             :           return square(get<0>(
     439             :                      get<domain::Tags::Coordinates<VolumeDim, Frame::Inertial>>(
     440             :                          box))) /
     441             :                  get(get<::Events::Tags::ObserverDetInvJacobian<
     442             :                          Frame::ElementLogical, Frame::Inertial>>(box));
     443             :         } else {
     444             :           // Axial Symmetry, needs x cartesian to cylindrical jacobian
     445             :           ASSERT(mesh.quadrature(2) == Spectral::Quadrature::AxialSymmetry,
     446             :                  "Unexpected quadrature " << mesh.quadrature(2)
     447             :                                           << " (expected AxialSymmetry)");
     448             :           return get<0>(
     449             :                      get<domain::Tags::Coordinates<VolumeDim, Frame::Inertial>>(
     450             :                          box)) /
     451             :                  get(get<::Events::Tags::ObserverDetInvJacobian<
     452             :                          Frame::ElementLogical, Frame::Inertial>>(box));
     453             :         }
     454             :       } else {
     455             :         return 1. / get(get<::Events::Tags::ObserverDetInvJacobian<
     456             :                             Frame::ElementLogical, Frame::Inertial>>(box));
     457             :       }
     458             :     } else {
     459             :       (void)mesh;
     460             :       return 1. / get(get<::Events::Tags::ObserverDetInvJacobian<
     461             :                           Frame::ElementLogical, Frame::Inertial>>(box));
     462             :     }
     463             :   }();
     464             :   const size_t number_of_points = mesh.number_of_grid_points();
     465             :   const double local_volume = definite_integral(det_jacobian, mesh);
     466             : 
     467             :   std::unordered_map<std::string,
     468             :                      std::pair<std::vector<double>, std::vector<std::string>>>
     469             :       norm_values_and_names{};
     470             :   // Loop over ObservableTensorTags and see if it was requested to be observed.
     471             :   // This approach allows us to delay evaluating any compute tags until they're
     472             :   // actually needed for observing.
     473             :   (observe_norms_impl<ObservableTensorTags>(
     474             :        make_not_null(&norm_values_and_names), box, mesh, det_jacobian,
     475             :        number_of_points),
     476             :    ...);
     477             : 
     478             :   // Concatenate the legend info together.
     479             :   std::vector<std::string> legend{observation_value.name, "NumberOfPoints",
     480             :                                   "Volume"};
     481             :   legend.insert(legend.end(), norm_values_and_names["Max"].second.begin(),
     482             :                 norm_values_and_names["Max"].second.end());
     483             :   legend.insert(legend.end(), norm_values_and_names["Min"].second.begin(),
     484             :                 norm_values_and_names["Min"].second.end());
     485             :   legend.insert(legend.end(), norm_values_and_names["L2Norm"].second.begin(),
     486             :                 norm_values_and_names["L2Norm"].second.end());
     487             :   legend.insert(legend.end(),
     488             :                 norm_values_and_names["L2IntegralNorm"].second.begin(),
     489             :                 norm_values_and_names["L2IntegralNorm"].second.end());
     490             :   legend.insert(legend.end(),
     491             :                 norm_values_and_names["VolumeIntegral"].second.begin(),
     492             :                 norm_values_and_names["VolumeIntegral"].second.end());
     493             : 
     494             :   const std::string subfile_path_with_suffix =
     495             :       subfile_path_ + section_observation_key.value();
     496             :   // Send data to reduction observer
     497             :   auto& local_observer = *Parallel::local_branch(
     498             :       Parallel::get_parallel_component<
     499             :           tmpl::conditional_t<Parallel::is_nodegroup_v<ParallelComponent>,
     500             :                               observers::ObserverWriter<Metavariables>,
     501             :                               observers::Observer<Metavariables>>>(cache));
     502             :   observers::ObservationId observation_id{observation_value.value,
     503             :                                           subfile_path_with_suffix + ".dat"};
     504             :   Parallel::ArrayComponentId array_component_id{
     505             :       std::add_pointer_t<ParallelComponent>{nullptr},
     506             :       Parallel::ArrayIndex<ElementId<VolumeDim>>(array_index)};
     507             :   ReductionData reduction_data{
     508             :       observation_value.value,
     509             :       number_of_points,
     510             :       local_volume,
     511             :       std::move(norm_values_and_names["Max"].first),
     512             :       std::move(norm_values_and_names["Min"].first),
     513             :       std::move(norm_values_and_names["L2Norm"].first),
     514             :       std::move(norm_values_and_names["L2IntegralNorm"].first),
     515             :       std::move(norm_values_and_names["VolumeIntegral"].first)};
     516             : 
     517             :   if constexpr (Parallel::is_nodegroup_v<ParallelComponent>) {
     518             :     Parallel::threaded_action<
     519             :         observers::ThreadedActions::CollectReductionDataOnNode>(
     520             :         local_observer, std::move(observation_id),
     521             :         std::move(array_component_id), subfile_path_with_suffix,
     522             :         std::move(legend), std::move(reduction_data));
     523             :   } else {
     524             :     Parallel::simple_action<observers::Actions::ContributeReductionData>(
     525             :         local_observer, std::move(observation_id),
     526             :         std::move(array_component_id), subfile_path_with_suffix,
     527             :         std::move(legend), std::move(reduction_data));
     528             :   }
     529             : }
     530             : 
     531             : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
     532             :           typename ArraySectionIdTag, typename OptionName>
     533             : void ObserveNorms<tmpl::list<ObservableTensorTags...>,
     534             :                   tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
     535             :                   OptionName>::pup(PUP::er& p) {
     536             :   Event::pup(p);
     537             :   p | subfile_path_;
     538             :   p | tensor_names_;
     539             :   p | tensor_norm_types_;
     540             :   p | tensor_components_;
     541             : }
     542             : 
     543             : // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
     544             : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
     545             :           typename ArraySectionIdTag, typename OptionName>
     546             : PUP::able::PUP_ID ObserveNorms<tmpl::list<ObservableTensorTags...>,
     547             :                                tmpl::list<NonTensorComputeTags...>,
     548             :                                ArraySectionIdTag, OptionName>::my_PUP_ID = 0;
     549             : // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
     550             : /// \endcond
     551             : }  // namespace Events

Generated by: LCOV version 1.14