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

Generated by: LCOV version 1.14