SpECTRE Documentation Coverage Report
Current view: top level - ParallelAlgorithms/Events - ObserveFields.hpp Hit Total Coverage
Commit: 3ffcbc8ecf43797401b60bcca17d6040ee06f013 Lines: 6 50 12.0 %
Date: 2026-03-03 02:01:44
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 <functional>
       8             : #include <initializer_list>
       9             : #include <optional>
      10             : #include <pup.h>
      11             : #include <string>
      12             : #include <type_traits>
      13             : #include <unordered_map>
      14             : #include <unordered_set>
      15             : #include <utility>
      16             : #include <vector>
      17             : 
      18             : #include "DataStructures/ApplyMatrices.hpp"
      19             : #include "DataStructures/DataBox/ObservationBox.hpp"
      20             : #include "DataStructures/DataBox/Prefixes.hpp"
      21             : #include "DataStructures/DataBox/TagName.hpp"
      22             : #include "DataStructures/DataBox/ValidateSelection.hpp"
      23             : #include "DataStructures/DataVector.hpp"
      24             : #include "DataStructures/FloatingPointType.hpp"
      25             : #include "Domain/Structure/BlockGroups.hpp"
      26             : #include "Domain/Structure/ElementId.hpp"
      27             : #include "Domain/Tags.hpp"
      28             : #include "IO/H5/TensorData.hpp"
      29             : #include "IO/Observer/GetSectionObservationKey.hpp"
      30             : #include "IO/Observer/ObservationId.hpp"
      31             : #include "IO/Observer/Tags.hpp"
      32             : #include "IO/Observer/VolumeActions.hpp"
      33             : #include "NumericalAlgorithms/Interpolation/RegularGridInterpolant.hpp"
      34             : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
      35             : #include "NumericalAlgorithms/Spectral/Projection.hpp"
      36             : #include "Options/Auto.hpp"
      37             : #include "Options/String.hpp"
      38             : #include "Parallel/ArrayComponentId.hpp"
      39             : #include "Parallel/ArrayIndex.hpp"
      40             : #include "Parallel/GlobalCache.hpp"
      41             : #include "Parallel/Printf/Printf.hpp"
      42             : #include "Parallel/TypeTraits.hpp"
      43             : #include "ParallelAlgorithms/Events/Tags.hpp"
      44             : #include "ParallelAlgorithms/EventsAndTriggers/Event.hpp"
      45             : #include "PointwiseFunctions/AnalyticSolutions/Tags.hpp"
      46             : #include "Utilities/Algorithm.hpp"
      47             : #include "Utilities/ErrorHandling/Assert.hpp"
      48             : #include "Utilities/ErrorHandling/Error.hpp"
      49             : #include "Utilities/Literals.hpp"
      50             : #include "Utilities/MakeString.hpp"
      51             : #include "Utilities/Numeric.hpp"
      52             : #include "Utilities/OptionalHelpers.hpp"
      53             : #include "Utilities/Serialization/CharmPupable.hpp"
      54             : #include "Utilities/Serialization/PupStlCpp17.hpp"
      55             : #include "Utilities/StdHelpers.hpp"
      56             : #include "Utilities/TMPL.hpp"
      57             : #include "Utilities/TypeTraits/IsA.hpp"
      58             : 
      59             : /// \cond
      60             : template <size_t Dim>
      61             : class Mesh;
      62             : namespace Frame {
      63             : struct Inertial;
      64             : }  // namespace Frame
      65             : /// \endcond
      66             : 
      67             : namespace dg {
      68             : namespace Events {
      69             : /// \cond
      70             : template <size_t VolumeDim, typename Tensors,
      71             :           typename NonTensorComputeTagsList = tmpl::list<>,
      72             :           typename ArraySectionIdTag = void>
      73             : class ObserveFields;
      74             : /// \endcond
      75             : 
      76             : /*!
      77             :  * \ingroup DiscontinuousGalerkinGroup
      78             :  * \brief %Observe volume tensor fields.
      79             :  *
      80             :  * A class that writes volume quantities to an h5 file during the simulation.
      81             :  * The observed quantitites are specified in the `VariablesToObserve` option.
      82             :  * Any `Tensor` in the `db::DataBox` can be observed but must be listed in the
      83             :  * `Tensors` template parameter. Any additional compute tags that hold a
      84             :  * `Tensor` can also be added to the `Tensors` template parameter. Finally,
      85             :  * `Variables` and other non-tensor compute tags can be listed in the
      86             :  * `NonTensorComputeTags` to facilitate observing. Note that the
      87             :  * `InertialCoordinates` are always observed.
      88             :  *
      89             :  * The user may specify an `interpolation_mesh` to which the
      90             :  * data is interpolated.
      91             :  *
      92             :  * \note The `NonTensorComputeTags` are intended to be used for `Variables`
      93             :  * compute tags like `Tags::DerivCompute`
      94             :  *
      95             :  * \par Array sections
      96             :  * This event supports sections (see `Parallel::Section`). Set the
      97             :  * `ArraySectionIdTag` template parameter to split up observations into subsets
      98             :  * of elements. The `observers::Tags::ObservationKey<ArraySectionIdTag>` must be
      99             :  * available in the DataBox. It identifies the section and is used as a suffix
     100             :  * for the path in the output file.
     101             :  */
     102             : template <size_t VolumeDim, typename... Tensors,
     103             :           typename... NonTensorComputeTags, typename ArraySectionIdTag>
     104           1 : class ObserveFields<VolumeDim, tmpl::list<Tensors...>,
     105             :                     tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag>
     106             :     : public Event {
     107             :  public:
     108             :   /// The name of the subfile inside the HDF5 file
     109           1 :   struct SubfileName {
     110           0 :     using type = std::string;
     111           0 :     static constexpr Options::String help = {
     112             :         "The name of the subfile inside the HDF5 file without an extension and "
     113             :         "without a preceding '/'."};
     114             :   };
     115             : 
     116             :   /// \cond
     117             :   explicit ObserveFields(CkMigrateMessage* /*unused*/) {}
     118             :   using PUP::able::register_constructor;
     119             :   WRAPPED_PUPable_decl_template(ObserveFields);  // NOLINT
     120             :   /// \endcond
     121             : 
     122           0 :   struct VariablesToObserve {
     123           0 :     static constexpr Options::String help = "Subset of variables to observe";
     124           0 :     using type = std::vector<std::string>;
     125           0 :     static size_t lower_bound_on_size() { return 1; }
     126             :   };
     127             : 
     128           0 :   struct InterpolateToMesh {
     129           0 :     using type = Options::Auto<Mesh<VolumeDim>, Options::AutoLabel::None>;
     130           0 :     static constexpr Options::String help =
     131             :         "An optional mesh to which the variables are interpolated. This mesh "
     132             :         "specifies any number of collocation points, basis, and quadrature on "
     133             :         "which the observed quantities are evaluated. If no mesh is given, the "
     134             :         "results will be evaluated on the mesh the simulation runs on. The "
     135             :         "user may add several ObserveField Events e.g. with and without an "
     136             :         "interpolating mesh to output the data both on the original mesh and "
     137             :         "on a new mesh.";
     138             :   };
     139             : 
     140           0 :   struct ProjectToMesh {
     141           0 :     using type = Options::Auto<Mesh<VolumeDim>, Options::AutoLabel::None>;
     142           0 :     static constexpr Options::String help =
     143             :         "An optional mesh to which the variables are projected. This mesh "
     144             :         "must use a Legendre basis (Gauss or Gauss-Lobatto quadrature). This "
     145             :         "option is mutually exclusive with InterpolateToMesh. Projection "
     146             :         "truncates the fields in modal space but works only between two "
     147             :         "Legendre meshes, whereas interpolation works with any mesh but is not "
     148             :         "a clean truncation and therefore picks up an aliasing error.";
     149             :   };
     150             : 
     151             :   /// The floating point type/precision with which to write the data to disk.
     152             :   ///
     153             :   /// Must be specified once for all data or individually for each variable
     154             :   /// being observed.
     155           1 :   struct FloatingPointTypes {
     156           0 :     static constexpr Options::String help =
     157             :         "The floating point type/precision with which to write the data to "
     158             :         "disk.\n\n"
     159             :         "Must be specified once for all data or individually  for each "
     160             :         "variable being observed.";
     161           0 :     using type = std::vector<FloatingPointType>;
     162           0 :     static size_t upper_bound_on_size() { return sizeof...(Tensors); }
     163           0 :     static size_t lower_bound_on_size() { return 1; }
     164             :   };
     165             : 
     166             :   /// The floating point type/precision with which to write the coordinates to
     167             :   /// disk.
     168           1 :   struct CoordinatesFloatingPointType {
     169           0 :     static constexpr Options::String help =
     170             :         "The floating point type/precision with which to write the coordinates "
     171             :         "to disk.";
     172           0 :     using type = FloatingPointType;
     173             :   };
     174             : 
     175             :   /// \brief A list of block or group names on which to observe.
     176             :   ///
     177             :   /// Set to `All` to observe everywhere.
     178           1 :   struct BlocksToObserve {
     179           0 :     using type =
     180             :         Options::Auto<std::vector<std::string>, Options::AutoLabel::All>;
     181           0 :     static constexpr Options::String help = {
     182             :         "A list of block and group names on which to observe."};
     183             :   };
     184             : 
     185           0 :   using options = tmpl::list<SubfileName, CoordinatesFloatingPointType,
     186             :                              FloatingPointTypes, VariablesToObserve,
     187             :                              BlocksToObserve, InterpolateToMesh, ProjectToMesh>;
     188             : 
     189           0 :   static constexpr Options::String help =
     190             :       "Observe volume tensor fields.\n"
     191             :       "\n"
     192             :       "Writes volume quantities:\n"
     193             :       " * InertialCoordinates\n"
     194             :       " * Tensors listed in the 'VariablesToObserve' option\n";
     195             : 
     196           0 :   ObserveFields() = default;
     197             : 
     198             :   // The dependency isn't available through Options
     199           0 :   ObserveFields(
     200             :       const std::string& subfile_name,
     201             :       FloatingPointType coordinates_floating_point_type,
     202             :       const std::vector<FloatingPointType>& floating_point_types,
     203             :       const std::vector<std::string>& variables_to_observe,
     204             :       std::optional<std::vector<std::string>> active_block_or_block_groups = {},
     205             :       std::optional<Mesh<VolumeDim>> interpolation_mesh = {},
     206             :       std::optional<Mesh<VolumeDim>> projection_mesh = {},
     207             :       std::optional<std::string> dependency = {},
     208             :       const Options::Context& context = {});
     209             : 
     210           0 :   ObserveFields(
     211             :       const std::string& subfile_name,
     212             :       FloatingPointType coordinates_floating_point_type,
     213             :       const std::vector<FloatingPointType>& floating_point_types,
     214             :       const std::vector<std::string>& variables_to_observe,
     215             :       std::optional<std::vector<std::string>> active_block_or_block_groups = {},
     216             :       std::optional<Mesh<VolumeDim>> interpolation_mesh = {},
     217             :       std::optional<Mesh<VolumeDim>> projection_mesh = {},
     218             :       const Options::Context& context = {})
     219             :       : ObserveFields(subfile_name, coordinates_floating_point_type,
     220             :                       floating_point_types, variables_to_observe,
     221             :                       std::move(active_block_or_block_groups),
     222             :                       std::move(interpolation_mesh), std::move(projection_mesh),
     223             :                       std::nullopt, context) {}
     224             : 
     225           0 :   using compute_tags_for_observation_box =
     226             :       tmpl::list<Tensors..., NonTensorComputeTags...>;
     227             : 
     228           0 :   using return_tags = tmpl::list<>;
     229           0 :   using argument_tags = tmpl::list<::Tags::ObservationBox,
     230             :                                    ::Events::Tags::ObserverMesh<VolumeDim>>;
     231             : 
     232             :   template <typename DataBoxType, typename ComputeTagsList,
     233             :             typename Metavariables, typename ParallelComponent>
     234           0 :   void operator()(const ObservationBox<DataBoxType, ComputeTagsList>& box,
     235             :                   const Mesh<VolumeDim>& mesh,
     236             :                   Parallel::GlobalCache<Metavariables>& cache,
     237             :                   const ElementId<VolumeDim>& array_index,
     238             :                   const ParallelComponent* const component,
     239             :                   const ObservationValue& observation_value) const {
     240             :     if (not active_block(get<domain::Tags::Domain<VolumeDim>>(box),
     241             :                          array_index)) {
     242             :       return;
     243             :     }
     244             :     // Skip observation on elements that are not part of a section
     245             :     const std::optional<std::string> section_observation_key =
     246             :         observers::get_section_observation_key<ArraySectionIdTag>(box);
     247             :     if (not section_observation_key.has_value()) {
     248             :       return;
     249             :     }
     250             :     call_operator_impl(subfile_path_ + *section_observation_key,
     251             :                        variables_to_observe_, interpolation_mesh_,
     252             :                        projection_mesh_, mesh, box, cache, array_index,
     253             :                        component, observation_value, dependency_);
     254             :   }
     255             : 
     256             :   // We factor out the work into a static member function so it can  be shared
     257             :   // with other field observing events, like the one that deals with DG-subcell
     258             :   // where there are two grids. This is to avoid copy-pasting all of the code.
     259             :   template <typename DataBoxType, typename ComputeTagsList,
     260             :             typename Metavariables, typename ParallelComponent>
     261           0 :   static void call_operator_impl(
     262             :       const std::string& subfile_path,
     263             :       const std::unordered_map<std::string, FloatingPointType>&
     264             :           variables_to_observe,
     265             :       const std::optional<Mesh<VolumeDim>>& interpolation_mesh,
     266             :       const std::optional<Mesh<VolumeDim>>& projection_mesh,
     267             :       const Mesh<VolumeDim>& mesh,
     268             :       const ObservationBox<DataBoxType, ComputeTagsList>& box,
     269             :       Parallel::GlobalCache<Metavariables>& cache,
     270             :       const ElementId<VolumeDim>& element_id,
     271             :       const ParallelComponent* const /*meta*/,
     272             :       const ObservationValue& observation_value,
     273             :       const std::optional<std::string>& dependency) {
     274             :     const bool do_projection = projection_mesh.has_value();
     275             :     const Mesh<VolumeDim>& target_mesh =
     276             :         do_projection ? projection_mesh.value()
     277             :                       : interpolation_mesh.value_or(mesh);
     278             :     if (do_projection) {
     279             :       for (size_t d = 0; d < VolumeDim; ++d) {
     280             :         if (mesh.basis(d) != Spectral::Basis::Legendre or
     281             :             target_mesh.basis(d) != Spectral::Basis::Legendre) {
     282             :           ERROR("ProjectToMesh requires Legendre basis.");
     283             :         }
     284             :         if ((mesh.quadrature(d) != Spectral::Quadrature::Gauss and
     285             :              mesh.quadrature(d) != Spectral::Quadrature::GaussLobatto) or
     286             :             (target_mesh.quadrature(d) != Spectral::Quadrature::Gauss and
     287             :              target_mesh.quadrature(d) != Spectral::Quadrature::GaussLobatto)) {
     288             :           ERROR("ProjectToMesh requires Gauss or Gauss-Lobatto quadrature.");
     289             :         }
     290             :       }
     291             :     }
     292             :     // If no interpolation_mesh is provided, the interpolation is essentially
     293             :     // ignored by the RegularGridInterpolant except for a single copy.
     294             :     const intrp::RegularGrid interpolant(mesh, target_mesh);
     295             :     const std::optional<
     296             :         std::array<std::reference_wrapper<const Matrix>, VolumeDim>>
     297             :         projection_matrices =
     298             :             do_projection ? std::make_optional(Spectral::p_projection_matrices(
     299             :                                 mesh, target_mesh))
     300             :                           : std::nullopt;
     301             : 
     302             :     // Remove tensor types, only storing individual components.
     303             :     std::vector<TensorComponent> components;
     304             :     // This is larger than we need if we are only observing some
     305             :     // tensors, but that's not a big deal and calculating the correct
     306             :     // size is nontrivial.
     307             :     components.reserve(alg::accumulate(
     308             :         std::initializer_list<size_t>{
     309             :             std::decay_t<decltype(value(typename Tensors::type{}))>::size()...},
     310             :         0_st));
     311             : 
     312             :     const auto record_tensor_component_impl =
     313             :         [&components](DataVector&& tensor_component,
     314             :                       const FloatingPointType floating_point_type,
     315             :                       const std::string& component_name) {
     316             :           if (floating_point_type == FloatingPointType::Float) {
     317             :             components.emplace_back(component_name,
     318             :                                     std::vector<float>{tensor_component.begin(),
     319             :                                                        tensor_component.end()});
     320             :           } else {
     321             :             components.emplace_back(component_name,
     322             :                                     std::move(tensor_component));
     323             :           }
     324             :         };
     325             : 
     326             :     const auto record_tensor_components_impl =
     327             :         [&record_tensor_component_impl, &interpolant, &projection_matrices,
     328             :          &mesh, do_projection](const auto& tensor,
     329             :                                const FloatingPointType floating_point_type,
     330             :                                const std::string& tag_name) {
     331             :           using TensorType = std::decay_t<decltype(tensor)>;
     332             :           using VectorType = typename TensorType::type;
     333             :           for (size_t i = 0; i < tensor.size(); ++i) {
     334             :             auto tensor_component =
     335             :                 do_projection ? apply_matrices(*projection_matrices, tensor[i],
     336             :                                                mesh.extents())
     337             :                               : interpolant.interpolate(tensor[i]);
     338             :             const std::string component_name =
     339             :                 tag_name + tensor.component_suffix(i);
     340             :             if constexpr (std::is_same_v<VectorType, ComplexDataVector>) {
     341             :               record_tensor_component_impl(real(tensor_component),
     342             :                                            floating_point_type,
     343             :                                            "Re(" + component_name + ")");
     344             :               record_tensor_component_impl(imag(tensor_component),
     345             :                                            floating_point_type,
     346             :                                            "Im(" + component_name + ")");
     347             :             } else {
     348             :               record_tensor_component_impl(std::move(tensor_component),
     349             :                                            floating_point_type, component_name);
     350             :             }
     351             :           }
     352             :         };
     353             :     const auto record_tensor_components =
     354             :         [&box, &record_tensor_components_impl,
     355             :          &variables_to_observe](const auto tensor_tag_v) {
     356             :           using tensor_tag = tmpl::type_from<decltype(tensor_tag_v)>;
     357             :           const std::string tag_name = db::tag_name<tensor_tag>();
     358             :           if (const auto var_to_observe = variables_to_observe.find(tag_name);
     359             :               var_to_observe != variables_to_observe.end()) {
     360             :             const auto& tensor = get<tensor_tag>(box);
     361             :             if (not has_value(tensor)) {
     362             :               // This will only print a warning the first time it's called on a
     363             :               // node.
     364             :               [[maybe_unused]] static bool t =
     365             :                   ObserveFields::print_warning_about_optional<tensor_tag>();
     366             :               return;
     367             :             }
     368             :             const auto floating_point_type = var_to_observe->second;
     369             :             record_tensor_components_impl(value(tensor), floating_point_type,
     370             :                                           tag_name);
     371             :           }
     372             :         };
     373             :     EXPAND_PACK_LEFT_TO_RIGHT(record_tensor_components(tmpl::type_<Tensors>{}));
     374             : 
     375             :     const Parallel::ArrayComponentId array_component_id{
     376             :         std::add_pointer_t<ParallelComponent>{nullptr},
     377             :         Parallel::ArrayIndex<ElementId<VolumeDim>>{element_id}};
     378             :     ElementVolumeData element_volume_data{element_id, std::move(components),
     379             :                                           target_mesh};
     380             :     observers::ObservationId observation_id{observation_value.value,
     381             :                                             subfile_path + ".vol"};
     382             : 
     383             :     observers::contribute_volume_data<
     384             :         not Parallel::is_nodegroup_v<ParallelComponent>>(
     385             :         cache, std::move(observation_id), subfile_path, array_component_id,
     386             :         std::move(element_volume_data), dependency);
     387             :   }
     388             : 
     389           0 :   using observation_registration_tags = tmpl::list<::Tags::DataBox>;
     390             : 
     391             :   template <typename DbTagsList>
     392             :   std::optional<
     393             :       std::pair<observers::TypeOfObservation, observers::ObservationKey>>
     394           0 :   get_observation_type_and_key_for_registration(
     395             :       const db::DataBox<DbTagsList>& box) const {
     396             :     if (not active_block(db::get<domain::Tags::Domain<VolumeDim>>(box),
     397             :                          db::get<domain::Tags::Element<VolumeDim>>(box).id())) {
     398             :       return std::nullopt;
     399             :     }
     400             :     const std::optional<std::string> section_observation_key =
     401             :         observers::get_section_observation_key<ArraySectionIdTag>(box);
     402             :     if (not section_observation_key.has_value()) {
     403             :       return std::nullopt;
     404             :     }
     405             :     return {{observers::TypeOfObservation::Volume,
     406             :              observers::ObservationKey{
     407             :                  subfile_path_ + section_observation_key.value() + ".vol"}}};
     408             :   }
     409             : 
     410           0 :   using is_ready_argument_tags = tmpl::list<>;
     411             : 
     412             :   template <typename Metavariables, typename ArrayIndex, typename Component>
     413           0 :   bool is_ready(Parallel::GlobalCache<Metavariables>& /*cache*/,
     414             :                 const ArrayIndex& /*array_index*/,
     415             :                 const Component* const /*meta*/) const {
     416             :     return true;
     417             :   }
     418             : 
     419           1 :   bool needs_evolved_variables() const override { return true; }
     420             : 
     421             :   // NOLINTNEXTLINE(google-runtime-references)
     422           0 :   void pup(PUP::er& p) override {
     423             :     Event::pup(p);
     424             :     p | subfile_path_;
     425             :     p | variables_to_observe_;
     426             :     p | active_block_or_block_groups_;
     427             :     p | interpolation_mesh_;
     428             :     p | projection_mesh_;
     429             :     p | dependency_;
     430             :   }
     431             : 
     432             :  private:
     433             :   template <typename Tag>
     434           0 :   static bool print_warning_about_optional() {
     435             :     Parallel::printf(
     436             :         "Warning: ObserveFields is trying to dump the tag %s "
     437             :         "but it is stored as a std::optional and has not been "
     438             :         "evaluated. This most commonly occurs when you are "
     439             :         "trying to either observe an analytic solution or errors when "
     440             :         "no analytic solution is available.\n",
     441             :         db::tag_name<Tag>());
     442             :     return false;
     443             :   }
     444             : 
     445           0 :   bool active_block(const Domain<VolumeDim>& domain,
     446             :                     const ElementId<VolumeDim>& element_id) const {
     447             :     if (not active_block_or_block_groups_.has_value()) {
     448             :       return true;
     449             :     }
     450             :     const std::unordered_set<std::string> block_names =
     451             :         domain::expand_block_groups_to_block_names(
     452             :             active_block_or_block_groups_.value(), domain.block_names(),
     453             :             domain.block_groups());
     454             :     return alg::found(block_names,
     455             :                       domain.blocks().at(element_id.block_id()).name());
     456             :   }
     457             : 
     458           0 :   std::string subfile_path_;
     459           0 :   std::unordered_map<std::string, FloatingPointType> variables_to_observe_{};
     460           0 :   std::optional<std::vector<std::string>> active_block_or_block_groups_{};
     461           0 :   std::optional<Mesh<VolumeDim>> interpolation_mesh_{};
     462           0 :   std::optional<Mesh<VolumeDim>> projection_mesh_{};
     463           0 :   std::optional<std::string> dependency_;
     464             : };
     465             : 
     466             : template <size_t VolumeDim, typename... Tensors,
     467             :           typename... NonTensorComputeTags, typename ArraySectionIdTag>
     468             : ObserveFields<VolumeDim, tmpl::list<Tensors...>,
     469             :               tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag>::
     470             :     ObserveFields(
     471             :         const std::string& subfile_name,
     472             :         const FloatingPointType coordinates_floating_point_type,
     473             :         const std::vector<FloatingPointType>& floating_point_types,
     474             :         const std::vector<std::string>& variables_to_observe,
     475             :         std::optional<std::vector<std::string>> active_block_or_block_groups,
     476             :         std::optional<Mesh<VolumeDim>> interpolation_mesh,
     477             :         std::optional<Mesh<VolumeDim>> projection_mesh,
     478             :         std::optional<std::string> dependency, const Options::Context& context)
     479             :     : subfile_path_("/" + subfile_name),
     480             :       variables_to_observe_([&context, &floating_point_types,
     481             :                              &variables_to_observe]() {
     482             :         if (floating_point_types.size() != 1 and
     483             :             floating_point_types.size() != variables_to_observe.size()) {
     484             :           PARSE_ERROR(context, "The number of floating point types specified ("
     485             :                                    << floating_point_types.size()
     486             :                                    << ") must be 1 or the number of variables "
     487             :                                       "specified for observing ("
     488             :                                    << variables_to_observe.size() << ")");
     489             :         }
     490             :         std::unordered_map<std::string, FloatingPointType> result{};
     491             :         for (size_t i = 0; i < variables_to_observe.size(); ++i) {
     492             :           result[variables_to_observe[i]] = floating_point_types.size() == 1
     493             :                                                 ? floating_point_types[0]
     494             :                                                 : floating_point_types[i];
     495             :           ASSERT(
     496             :               result.at(variables_to_observe[i]) == FloatingPointType::Float or
     497             :                   result.at(variables_to_observe[i]) ==
     498             :                       FloatingPointType::Double,
     499             :               "Floating point type for variable '"
     500             :                   << variables_to_observe[i]
     501             :                   << "' must be either Float or Double.");
     502             :         }
     503             :         return result;
     504             :       }()),
     505             :       active_block_or_block_groups_(std::move(active_block_or_block_groups)),
     506             :       interpolation_mesh_(interpolation_mesh),
     507             :       projection_mesh_(projection_mesh),
     508             :       dependency_(std::move(dependency)) {
     509             :   if (interpolation_mesh_.has_value() and projection_mesh_.has_value()) {
     510             :     PARSE_ERROR(context,
     511             :                 "Specify only one of InterpolateToMesh or ProjectToMesh.");
     512             :   }
     513             :   if (projection_mesh_.has_value()) {
     514             :     const auto& proj_mesh = projection_mesh_.value();
     515             :     for (size_t d = 0; d < VolumeDim; ++d) {
     516             :       if (proj_mesh.basis(d) != Spectral::Basis::Legendre) {
     517             :         PARSE_ERROR(context, "ProjectToMesh requires Legendre basis.");
     518             :       }
     519             :       if (proj_mesh.quadrature(d) != Spectral::Quadrature::Gauss and
     520             :           proj_mesh.quadrature(d) != Spectral::Quadrature::GaussLobatto) {
     521             :         PARSE_ERROR(
     522             :             context,
     523             :             "ProjectToMesh requires Gauss or Gauss-Lobatto quadrature.");
     524             :       }
     525             :     }
     526             :   }
     527             :   ASSERT(
     528             :       (... or (db::tag_name<Tensors>() == "InertialCoordinates")),
     529             :       "There is no tag with name 'InertialCoordinates' specified "
     530             :       "for the observer. Please make sure you specify a tag in the 'Tensors' "
     531             :       "list that has the 'db::tag_name()' 'InertialCoordinates'.");
     532             :   db::validate_selection<tmpl::list<Tensors...>>(variables_to_observe, context);
     533             :   variables_to_observe_["InertialCoordinates"] =
     534             :       coordinates_floating_point_type;
     535             : }
     536             : 
     537             : /// \cond
     538             : template <size_t VolumeDim, typename... Tensors,
     539             :           typename... NonTensorComputeTags, typename ArraySectionIdTag>
     540             : PUP::able::PUP_ID ObserveFields<VolumeDim, tmpl::list<Tensors...>,
     541             :                                 tmpl::list<NonTensorComputeTags...>,
     542             :                                 ArraySectionIdTag>::my_PUP_ID = 0;  // NOLINT
     543             : /// \endcond
     544             : }  // namespace Events
     545             : }  // namespace dg

Generated by: LCOV version 1.14