SpECTRE Documentation Coverage Report
Current view: top level - ParallelAlgorithms/Events - ObserveFields.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 12.0 % 50 6
Test Date: 2026-10-22 17:29:42
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.0-1