SpECTRE Documentation Coverage Report
Current view: top level - IO/Importers/Actions - ReadVolumeData.hpp Hit Total Coverage
Commit: c3e43f8d41800b0ecefb9d1393f1de1d5a280c8f Lines: 5 12 41.7 %
Date: 2026-07-24 22:09:25
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 <algorithm>
       7             : #include <cstddef>
       8             : #include <optional>
       9             : #include <string>
      10             : #include <tuple>
      11             : #include <variant>
      12             : #include <vector>
      13             : 
      14             : #include "DataStructures/DataBox/DataBox.hpp"
      15             : #include "DataStructures/DataBox/PrefixHelpers.hpp"
      16             : #include "DataStructures/DataBox/Tag.hpp"
      17             : #include "DataStructures/DataVector.hpp"
      18             : #include "DataStructures/TaggedTuple.hpp"
      19             : #include "DataStructures/Tensor/EagerMath/Magnitude.hpp"
      20             : #include "DataStructures/Tensor/Tensor.hpp"
      21             : #include "Domain/Domain.hpp"
      22             : #include "Domain/ElementMap.hpp"
      23             : #include "Domain/Structure/ElementId.hpp"
      24             : #include "IO/Exporter/Exporter.hpp"
      25             : #include "IO/Exporter/PointwiseInterpolator.hpp"
      26             : #include "IO/Exporter/SelectObservation.hpp"
      27             : #include "IO/H5/AccessType.hpp"
      28             : #include "IO/H5/File.hpp"
      29             : #include "IO/H5/TensorData.hpp"
      30             : #include "IO/H5/VolumeData.hpp"
      31             : #include "IO/Importers/ObservationSelector.hpp"
      32             : #include "IO/Importers/Tags.hpp"
      33             : #include "NumericalAlgorithms/Interpolation/RegularGridInterpolant.hpp"
      34             : #include "NumericalAlgorithms/Spectral/LogicalCoordinates.hpp"
      35             : #include "Parallel/AlgorithmExecution.hpp"
      36             : #include "Parallel/ArrayCollection/IsDgElementCollection.hpp"
      37             : #include "Parallel/ArrayComponentId.hpp"
      38             : #include "Parallel/ArrayIndex.hpp"
      39             : #include "Parallel/GlobalCache.hpp"
      40             : #include "Parallel/Invoke.hpp"
      41             : #include "Utilities/EqualWithinRoundoff.hpp"
      42             : #include "Utilities/ErrorHandling/Assert.hpp"
      43             : #include "Utilities/ErrorHandling/CaptureForError.hpp"
      44             : #include "Utilities/ErrorHandling/Error.hpp"
      45             : #include "Utilities/FileSystem.hpp"
      46             : #include "Utilities/Gsl.hpp"
      47             : #include "Utilities/Literals.hpp"
      48             : #include "Utilities/Overloader.hpp"
      49             : #include "Utilities/Requires.hpp"
      50             : #include "Utilities/Serialization/Serialize.hpp"
      51             : #include "Utilities/TMPL.hpp"
      52             : 
      53           1 : namespace importers {
      54             : 
      55             : /// \cond
      56             : template <typename Metavariables>
      57             : struct ElementDataReader;
      58             : namespace Actions {
      59             : template <size_t Dim, typename FieldTagsList, typename ReceiveComponent>
      60             : struct ReadAllVolumeDataAndDistribute;
      61             : }  // namespace Actions
      62             : /// \endcond
      63             : 
      64           1 : namespace Tags {
      65             : /*!
      66             :  * \brief Indicates an available tensor field is selected for importing, along
      67             :  * with the name of the dataset in the volume data file.
      68             :  *
      69             :  * Set the value to a dataset name to import the `FieldTag` from that dataset,
      70             :  * or to `std::nullopt` to skip importing the `FieldTag`. The dataset name
      71             :  * excludes tensor component suffixes like "_x" or "_xy". These suffixes will be
      72             :  * added automatically. A sensible value for the dataset name is often
      73             :  * `db::tag_name<FieldTag>()`, but the user should generally be given the
      74             :  * opportunity to set the dataset name in the input file.
      75             :  */
      76             : template <typename FieldTag>
      77           1 : struct Selected : db::SimpleTag {
      78           0 :   using type = std::optional<std::string>;
      79             : };
      80             : }  // namespace Tags
      81             : 
      82             : namespace detail {
      83             : 
      84             : // Translate the importer's observation selection into the Exporter's
      85             : // `ObservationVariant`
      86             : inline spectre::Exporter::ObservationVariant observation_variant(
      87             :     const std::variant<double, ObservationSelector>& observation_value,
      88             :     const std::optional<double>& observation_value_epsilon) {
      89             :   return std::visit(
      90             :       Overloader{[&observation_value_epsilon](const double local_obs_value)
      91             :                      -> spectre::Exporter::ObservationVariant {
      92             :                    if (observation_value_epsilon.has_value()) {
      93             :                      return spectre::Exporter::ObservationValue{
      94             :                          local_obs_value, observation_value_epsilon.value()};
      95             :                    }
      96             :                    return local_obs_value;
      97             :                  },
      98             :                  [](const ObservationSelector local_obs_selector)
      99             :                      -> spectre::Exporter::ObservationVariant {
     100             :                    switch (local_obs_selector) {
     101             :                      case ObservationSelector::First:
     102             :                        return spectre::Exporter::ObservationStep{0};
     103             :                      case ObservationSelector::Last:
     104             :                        return spectre::Exporter::ObservationStep{-1};
     105             :                      default:
     106             :                        ERROR("Unknown importers::ObservationSelector: "
     107             :                              << local_obs_selector);
     108             :                    }
     109             :                  }},
     110             :       observation_value);
     111             : }
     112             : 
     113             : // Read the single `tensor_name` from the `volume_file`, taking care of suffixes
     114             : // like "_x" etc for its components.
     115             : template <typename TensorType>
     116             : void read_tensor_data(const gsl::not_null<TensorType*> tensor_data,
     117             :                       const std::string& tensor_name,
     118             :                       const h5::VolumeData& volume_file,
     119             :                       const size_t observation_id) {
     120             :   for (size_t i = 0; i < tensor_data->size(); ++i) {
     121             :     const auto& tensor_component = volume_file.get_tensor_component(
     122             :         observation_id, tensor_name + tensor_data->component_suffix(
     123             :                                           tensor_data->get_tensor_index(i)));
     124             :     if (not std::holds_alternative<DataVector>(tensor_component.data)) {
     125             :       ERROR("The tensor component '"
     126             :             << tensor_component.name
     127             :             << "' is not a double-precision DataVector. Reading in "
     128             :                "single-precision volume data is not supported.");
     129             :     }
     130             :     (*tensor_data)[i] = std::get<DataVector>(tensor_component.data);
     131             :   }
     132             : }
     133             : 
     134             : // Read the `selected_fields` from the `volume_file`. Reads the data
     135             : // for all elements in the `volume_file` at once. Invoked lazily when data
     136             : // for an element in the volume file is needed.
     137             : template <typename FieldTagsList>
     138             : tuples::tagged_tuple_from_typelist<FieldTagsList> read_tensor_data(
     139             :     const h5::VolumeData& volume_file, const size_t observation_id,
     140             :     const tuples::tagged_tuple_from_typelist<
     141             :         db::wrap_tags_in<Tags::Selected, FieldTagsList>>& selected_fields) {
     142             :   tuples::tagged_tuple_from_typelist<FieldTagsList> all_tensor_data{};
     143             :   tmpl::for_each<FieldTagsList>([&all_tensor_data, &volume_file,
     144             :                                  &observation_id,
     145             :                                  &selected_fields](auto field_tag_v) {
     146             :     using field_tag = tmpl::type_from<decltype(field_tag_v)>;
     147             :     const auto& selection = get<Tags::Selected<field_tag>>(selected_fields);
     148             :     if (not selection.has_value()) {
     149             :       return;
     150             :     }
     151             :     read_tensor_data(make_not_null(&get<field_tag>(all_tensor_data)),
     152             :                      selection.value(), volume_file, observation_id);
     153             :   });
     154             :   return all_tensor_data;
     155             : }
     156             : 
     157             : // Extract this element's data from the read-in dataset
     158             : template <typename FieldTagsList>
     159             : tuples::tagged_tuple_from_typelist<FieldTagsList> extract_element_data(
     160             :     const std::pair<size_t, size_t>& element_data_offset_and_length,
     161             :     const tuples::tagged_tuple_from_typelist<FieldTagsList>& all_tensor_data,
     162             :     const tuples::tagged_tuple_from_typelist<
     163             :         db::wrap_tags_in<Tags::Selected, FieldTagsList>>& selected_fields) {
     164             :   tuples::tagged_tuple_from_typelist<FieldTagsList> element_data{};
     165             :   tmpl::for_each<FieldTagsList>(
     166             :       [&element_data, &offset = element_data_offset_and_length.first,
     167             :        &num_points = element_data_offset_and_length.second, &all_tensor_data,
     168             :        &selected_fields](auto field_tag_v) {
     169             :         using field_tag = tmpl::type_from<decltype(field_tag_v)>;
     170             :         const auto& selection = get<Tags::Selected<field_tag>>(selected_fields);
     171             :         if (not selection.has_value()) {
     172             :           return;
     173             :         }
     174             :         auto& element_tensor_data = get<field_tag>(element_data);
     175             :         // Iterate independent components of the tensor
     176             :         for (size_t i = 0; i < element_tensor_data.size(); ++i) {
     177             :           const DataVector& data_tensor_component =
     178             :               get<field_tag>(all_tensor_data)[i];
     179             :           DataVector element_tensor_component{num_points};
     180             :           // Retrieve data from slice of the contigious dataset
     181             :           for (size_t j = 0; j < element_tensor_component.size(); ++j) {
     182             :             element_tensor_component[j] = data_tensor_component[offset + j];
     183             :           }
     184             :           element_tensor_data[i] = element_tensor_component;
     185             :         }
     186             :       });
     187             :   return element_data;
     188             : }
     189             : 
     190             : // Check that the inertial coordinates computed with the given domain are the
     191             : // same as the ones passed to this function.
     192             : // This is important to avoid hard-to-find bugs where data is loaded
     193             : // to the wrong coordinates. For example, if the evolution domain deforms the
     194             : // excision surfaces a bit but the initial data doesn't, then it would be wrong
     195             : // to load the initial data to the evolution grid without an interpolation.
     196             : template <size_t Dim>
     197             : void verify_inertial_coordinates(
     198             :     const Domain<Dim>& domain, const double time,
     199             :     const domain::FunctionsOfTimeMap& functions_of_time,
     200             :     const ElementId<Dim>& element_id, const Mesh<Dim>& mesh,
     201             :     const tnsr::I<DataVector, Dim, Frame::Inertial>& inertial_coords) {
     202             :   const auto logical_coords = logical_coordinates(mesh);
     203             :   ElementMap<Dim, Frame::Inertial> element_map{
     204             :       element_id, domain.blocks()[element_id.block_id()]};
     205             :   const auto mapped_inertial_coords =
     206             :       element_map(logical_coords, time, functions_of_time);
     207             :   const double scale = blaze::max(get(magnitude(mapped_inertial_coords)));
     208             :   if (not equal_within_roundoff(mapped_inertial_coords, inertial_coords,
     209             :                                 std::numeric_limits<double>::epsilon() * 100.0,
     210             :                                 scale)) {
     211             :     DataVector diff =
     212             :         square(get<0>(inertial_coords) - get<0>(mapped_inertial_coords));
     213             :     for (size_t d = 1; d < Dim; ++d) {
     214             :       diff += square(inertial_coords.get(d) - mapped_inertial_coords.get(d));
     215             :     }
     216             :     diff = sqrt(diff);
     217             :     const double max_coord_distance = blaze::max(diff);
     218             :     CAPTURE_FOR_ERROR(element_id);
     219             :     CAPTURE_FOR_ERROR(max_coord_distance);
     220             :     CAPTURE_FOR_ERROR(scale);
     221             :     ERROR_NO_TRACE(
     222             :         "The source and target domain don't match. Set 'ElementsAreIdentical: "
     223             :         "False' to enable interpolation between the grids.");
     224             :   }
     225             : }
     226             : 
     227             : // Interpolate only the `selected_fields` in `source_element_data` to the
     228             : // `target_mesh` (used when elements differ only by p-refinement)
     229             : template <typename FieldTagsList, size_t Dim>
     230             : void interpolate_selected_fields(
     231             :     const gsl::not_null<tuples::tagged_tuple_from_typelist<FieldTagsList>*>
     232             :         target_element_data,
     233             :     const tuples::tagged_tuple_from_typelist<FieldTagsList>&
     234             :         source_element_data,
     235             :     const Mesh<Dim>& source_mesh, const Mesh<Dim>& target_mesh,
     236             :     const tuples::tagged_tuple_from_typelist<
     237             :         db::wrap_tags_in<Tags::Selected, FieldTagsList>>& selected_fields) {
     238             :   const intrp::RegularGrid<Dim> interpolator{source_mesh, target_mesh};
     239             :   tmpl::for_each<FieldTagsList>([&source_element_data, &target_element_data,
     240             :                                  &interpolator,
     241             :                                  &selected_fields](auto field_tag_v) {
     242             :     using field_tag = tmpl::type_from<decltype(field_tag_v)>;
     243             :     const auto& selection = get<Tags::Selected<field_tag>>(selected_fields);
     244             :     if (not selection.has_value()) {
     245             :       return;
     246             :     }
     247             :     const auto& source_tensor_data = get<field_tag>(source_element_data);
     248             :     auto& target_tensor_data = get<field_tag>(*target_element_data);
     249             :     // Iterate independent components of the tensor
     250             :     for (size_t i = 0; i < source_tensor_data.size(); ++i) {
     251             :       const DataVector& source_tensor_component = source_tensor_data[i];
     252             :       DataVector& target_tensor_component = target_tensor_data[i];
     253             :       // Interpolate
     254             :       interpolator.interpolate(make_not_null(&target_tensor_component),
     255             :                                source_tensor_component);
     256             :     }
     257             :   });
     258             : }
     259             : 
     260             : // Scatter the slice `[start, start + num_points)` of the flat,
     261             : // component-indexed `interpolated_data` (as produced by
     262             : // `spectre::Exporter::interpolate_to_points`) into a tagged tuple of tensors
     263             : // for a single target element. The components are laid out in `FieldTagsList`
     264             : // order, skipping unselected fields, matching the order of the
     265             : // `tensor_components` passed to the interpolation.
     266             : template <typename FieldTagsList>
     267             : tuples::tagged_tuple_from_typelist<FieldTagsList> scatter_element_data(
     268             :     const std::vector<DataVector>& interpolated_data, const size_t start,
     269             :     const size_t num_points,
     270             :     const tuples::tagged_tuple_from_typelist<
     271             :         db::wrap_tags_in<Tags::Selected, FieldTagsList>>& selected_fields) {
     272             :   tuples::tagged_tuple_from_typelist<FieldTagsList> element_data{};
     273             :   size_t component_index = 0;
     274             :   tmpl::for_each<FieldTagsList>([&element_data, &interpolated_data, &start,
     275             :                                  &num_points, &selected_fields,
     276             :                                  &component_index](auto field_tag_v) {
     277             :     using field_tag = tmpl::type_from<decltype(field_tag_v)>;
     278             :     if (not get<Tags::Selected<field_tag>>(selected_fields).has_value()) {
     279             :       return;
     280             :     }
     281             :     auto& element_tensor_data = get<field_tag>(element_data);
     282             :     for (size_t i = 0; i < element_tensor_data.size(); ++i) {
     283             :       DataVector component{num_points};
     284             :       const DataVector& interpolated_component =
     285             :           interpolated_data[component_index];
     286             :       for (size_t j = 0; j < num_points; ++j) {
     287             :         component[j] = interpolated_component[start + j];
     288             :       }
     289             :       element_tensor_data[i] = std::move(component);
     290             :       ++component_index;
     291             :     }
     292             :   });
     293             :   return element_data;
     294             : }
     295             : 
     296             : }  // namespace detail
     297             : 
     298           0 : namespace Actions {
     299             : 
     300             : /*!
     301             :  * \brief Read a volume data file and distribute the data to all registered
     302             :  * elements, interpolating to the target points if needed.
     303             :  *
     304             :  * \note Use this action if you want to quickly load and distribute volume data.
     305             :  * If you need to beyond that (such as more control over input-file options),
     306             :  * write a new action and dispatch to
     307             :  * `importers::Actions::ReadAllVolumeDataAndDistribute`.
     308             :  *
     309             :  * \details Invoke this action on the elements of an array parallel component to
     310             :  * dispatch reading the volume data file specified by options placed in the
     311             :  * `ImporterOptionsGroup`. The tensors in `FieldTagsList` will be loaded from
     312             :  * the file and distributed to all elements that have previously registered. Use
     313             :  * `importers::Actions::RegisterWithElementDataReader` to register the elements
     314             :  * of the array parallel component in a previous phase.
     315             :  *
     316             :  * Note that the volume data file will only be read once per node, triggered by
     317             :  * the first element that invokes this action. All subsequent invocations of
     318             :  * this action on the node will do nothing. See
     319             :  * `importers::Actions::ReadAllVolumeDataAndDistribute` for details.
     320             :  *
     321             :  * The data is distributed to the elements using `Parallel::receive_data`. The
     322             :  * elements can monitor `importers::Tags::VolumeData` in their inbox to wait for
     323             :  * the data and process it once it's available. We provide the action
     324             :  * `importers::Actions::ReceiveVolumeData` that waits for the data and moves it
     325             :  * directly into the DataBox. You can also implement a specialized action that
     326             :  * might verify and post-process the data before populating the DataBox.
     327             :  *
     328             :  * \see Dev guide on \ref dev_guide_importing
     329             :  */
     330             : template <typename ImporterOptionsGroup, typename FieldTagsList>
     331           1 : struct ReadVolumeData {
     332           0 :   using const_global_cache_tags =
     333             :       tmpl::list<Tags::ImporterOptions<ImporterOptionsGroup>>;
     334             : 
     335             :   template <typename DbTagsList, typename... InboxTags, typename Metavariables,
     336             :             size_t Dim, typename ActionList, typename ParallelComponent>
     337           0 :   static Parallel::iterable_action_return_t apply(
     338             :       db::DataBox<DbTagsList>& /*box*/,
     339             :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     340             :       Parallel::GlobalCache<Metavariables>& cache,
     341             :       const ElementId<Dim>& /*array_index*/, const ActionList /*meta*/,
     342             :       const ParallelComponent* const /*meta*/) {
     343             :     // Not using `ckLocalBranch` here to make sure the simple action invocation
     344             :     // is asynchronous.
     345             :     auto& reader_component = Parallel::get_parallel_component<
     346             :         importers::ElementDataReader<Metavariables>>(cache);
     347             :     Parallel::simple_action<importers::Actions::ReadAllVolumeDataAndDistribute<
     348             :         Dim, FieldTagsList, ParallelComponent>>(
     349             :         reader_component,
     350             :         get<Tags::ImporterOptions<ImporterOptionsGroup>>(cache), 0_st);
     351             :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     352             :   }
     353             : };
     354             : 
     355             : /*!
     356             :  * \brief Read a volume data file and distribute the data to all registered
     357             :  * elements, interpolating to the target points if needed.
     358             :  *
     359             :  * This action can be invoked on the `importers::ElementDataReader` component
     360             :  * once all elements have been registered with it. It opens the data file, reads
     361             :  * the data for each registered element and uses `Parallel::receive_data` to
     362             :  * distribute the data to the elements. The elements can monitor
     363             :  * `importers::Tags::VolumeData` in their inbox to wait for the data and process
     364             :  * it once it's available. You can use `importers::Actions::ReceiveVolumeData`
     365             :  * to wait for the data and move it directly into the DataBox, or implement a
     366             :  * specialized action that might verify and post-process the data.
     367             :  *
     368             :  * Note that instead of invoking this action directly on the
     369             :  * `importers::ElementDataReader` component you can invoke the iterable action
     370             :  * `importers::Actions::ReadVolumeData` on the elements of an array parallel
     371             :  * component for simple use cases.
     372             :  *
     373             :  * - Pass along the following arguments to the simple action invocation:
     374             :  *   - `options`: `importers::ImporterOptions` that specify the H5 files
     375             :  *     with volume data to load.
     376             :  *   - `volume_data_id`: A number (or hash) that identifies this import
     377             :  *     operation. Will also be used to identify the loaded volume data in the
     378             :  *     inbox of the receiving elements.
     379             :  *   - `selected_fields` (optional): See below.
     380             :  * - The `FieldTagsList` parameter specifies a typelist of tensor tags that
     381             :  * can be read from the file and provided to each element. The subset of tensors
     382             :  * that will actually be read and distributed can be selected at runtime with
     383             :  * the `selected_fields` argument that is passed to this simple action. See
     384             :  * importers::Tags::Selected for details. By default, all tensors in the
     385             :  * `FieldTagsList` are selected, and read from datasets named
     386             :  * `db::tag_name<Tag>() + suffix`, where the `suffix` is empty for scalars, or
     387             :  * `"_"` followed by the `Tensor::component_name` for each independent tensor
     388             :  * component.
     389             :  * - `Parallel::receive_data` is invoked on each registered element of the
     390             :  * `ReceiveComponent` to populate `importers::Tags::VolumeData` in the element's
     391             :  * inbox with a `tuples::tagged_tuple_from_typelist<FieldTagsList>` containing
     392             :  * the tensor data for that element. The `ReceiveComponent` must the the same
     393             :  * that was encoded into the `Parallel::ArrayComponentId` used to register the
     394             :  * elements. The `volume_data_id` passed to this action is used as key.
     395             :  *
     396             :  * \par Memory consumption
     397             :  * This action runs once on every node. Volume data files are loaded one at a
     398             :  * time, so memory consumption does _not_ grow with the the number of source
     399             :  * files. All coordinates of elements on this node and their interpolated data
     400             :  * is held in memory at once, so memory consumption scales with the number of
     401             :  * elements on this node.
     402             :  *
     403             :  * \see Dev guide on \ref dev_guide_importing
     404             :  */
     405             : template <size_t Dim, typename FieldTagsList, typename ReceiveComponent>
     406           1 : struct ReadAllVolumeDataAndDistribute {
     407             :   template <typename ParallelComponent, typename DataBox,
     408             :             typename Metavariables, typename ArrayIndex>
     409           0 :   static void apply(DataBox& box, Parallel::GlobalCache<Metavariables>& cache,
     410             :                     const ArrayIndex& /*array_index*/,
     411             :                     const ImporterOptions& options, const size_t volume_data_id,
     412             :                     tuples::tagged_tuple_from_typelist<
     413             :                         db::wrap_tags_in<Tags::Selected, FieldTagsList>>
     414             :                         selected_fields = select_all_fields(FieldTagsList{})) {
     415             :     const bool elements_are_identical =
     416             :         get<OptionTags::ElementsAreIdentical>(options);
     417             : 
     418             :     // Only read and distribute the volume data once
     419             :     // This action will be invoked by `importers::Actions::ReadVolumeData` from
     420             :     // every element on the node, but only the first invocation reads the file
     421             :     // and distributes the data to all elements. Subsequent invocations do
     422             :     // nothing. The `volume_data_id` identifies whether or not we have already
     423             :     // read the requested data. Doing this at runtime avoids having to collect
     424             :     // all data files that will be read in at compile-time to initialize a flag
     425             :     // in the DataBox for each of them.
     426             :     const auto& has_read_volume_data =
     427             :         db::get<Tags::ElementDataAlreadyRead>(box);
     428             :     if (has_read_volume_data.find(volume_data_id) !=
     429             :         has_read_volume_data.end()) {
     430             :       return;
     431             :     }
     432             :     db::mutate<Tags::ElementDataAlreadyRead>(
     433             :         [&volume_data_id](const auto local_has_read_volume_data) {
     434             :           local_has_read_volume_data->insert(volume_data_id);
     435             :         },
     436             :         make_not_null(&box));
     437             : 
     438             :     // This is the subset of elements that reside on this node. They have
     439             :     // registered themselves before. Our job is to fill them with volume data.
     440             :     std::unordered_set<ElementId<Dim>> target_element_ids{};
     441             :     for (const auto& target_element : get<Tags::RegisteredElements<Dim>>(box)) {
     442             :       const auto& element_array_component_id = target_element.first;
     443             :       const CkArrayIndex& raw_element_index =
     444             :           element_array_component_id.array_index();
     445             :       // Check if the parallel component of the registered element matches the
     446             :       // callback, because it's possible that elements from other components
     447             :       // with the same index are also registered.
     448             :       // Since the way the component is encoded in `ArrayComponentId` is
     449             :       // private to that class, we construct one and compare.
     450             :       // Can't use Parallel::make_array_component_id here because we need the
     451             :       // original array_index type, not a CkArrayIndex.
     452             :       if (element_array_component_id !=
     453             :           Parallel::ArrayComponentId(
     454             :               std::add_pointer_t<ReceiveComponent>{nullptr},
     455             :               raw_element_index)) {
     456             :         continue;
     457             :       }
     458             :       const auto target_element_id =
     459             :           Parallel::ArrayIndex<ElementId<Dim>>(raw_element_index).get_index();
     460             :       target_element_ids.insert(target_element_id);
     461             :     }
     462             :     if (UNLIKELY(target_element_ids.empty())) {
     463             :       return;
     464             :     }
     465             : 
     466             :     // Resolve the file glob
     467             :     const std::string& file_glob = get<OptionTags::FileGlob>(options);
     468             :     const std::vector<std::string> file_paths = file_system::glob(file_glob);
     469             :     if (file_paths.empty()) {
     470             :       ERROR_NO_TRACE("The file glob '" << file_glob << "' matches no files.");
     471             :     }
     472             : 
     473             :     // Select observation to read from each file
     474             :     const spectre::Exporter::ObservationVariant observation =
     475             :         detail::observation_variant(
     476             :             get<OptionTags::ObservationValue>(options),
     477             :             get<OptionTags::ObservationValueEpsilon>(options));
     478             : 
     479             :     // When interpolation between the source and target grids is needed, reuse
     480             :     // spectre::Exporter::interpolate_to_points to interpolate to all target
     481             :     // points on this node at once, then scatter the results to the elements.
     482             :     if (not elements_are_identical) {
     483             :       // Gather all target points on this node into a single contiguous tensor,
     484             :       // recording the range [start, start + num_points) of each target element.
     485             :       std::vector<ElementId<Dim>> target_ids{};
     486             :       std::vector<size_t> target_starts{};
     487             :       std::vector<size_t> target_num_points{};
     488             :       std::vector<const tnsr::I<DataVector, Dim, Frame::Inertial>*>
     489             :           target_coords{};
     490             :       target_ids.reserve(target_element_ids.size());
     491             :       target_starts.reserve(target_element_ids.size());
     492             :       target_num_points.reserve(target_element_ids.size());
     493             :       target_coords.reserve(target_element_ids.size());
     494             :       size_t total_num_points = 0;
     495             :       for (const auto& target_element_id : target_element_ids) {
     496             :         const auto& target_points =
     497             :             get<Tags::RegisteredElements<Dim>>(box)
     498             :                 .at(Parallel::make_array_component_id<ReceiveComponent>(
     499             :                     target_element_id))
     500             :                 .first;
     501             :         target_ids.push_back(target_element_id);
     502             :         target_coords.push_back(&target_points);
     503             :         target_starts.push_back(total_num_points);
     504             :         target_num_points.push_back(target_points.begin()->size());
     505             :         total_num_points += target_num_points.back();
     506             :       }
     507             :       tnsr::I<DataVector, Dim, Frame::Inertial> all_target_points{
     508             :           total_num_points};
     509             :       for (size_t e = 0; e < target_ids.size(); ++e) {
     510             :         for (size_t d = 0; d < Dim; ++d) {
     511             :           for (size_t i = 0; i < target_num_points[e]; ++i) {
     512             :             all_target_points.get(d)[target_starts[e] + i] =
     513             :                 target_coords[e]->get(d)[i];
     514             :           }
     515             :         }
     516             :       }
     517             : 
     518             :       // Flat list of dataset component names for the selected fields, in
     519             :       // `FieldTagsList` order. The layout matches
     520             :       // `detail::scatter_element_data`.
     521             :       std::vector<std::string> tensor_components{};
     522             :       tmpl::for_each<FieldTagsList>([&tensor_components,
     523             :                                      &selected_fields](auto field_tag_v) {
     524             :         using field_tag = tmpl::type_from<decltype(field_tag_v)>;
     525             :         const auto& selection = get<Tags::Selected<field_tag>>(selected_fields);
     526             :         if (not selection.has_value()) {
     527             :           return;
     528             :         }
     529             :         using TensorType = typename field_tag::type;
     530             :         for (size_t i = 0; i < TensorType::size(); ++i) {
     531             :           tensor_components.push_back(selection.value() +
     532             :                                       TensorType::component_suffix(i));
     533             :         }
     534             :       });
     535             : 
     536             :       // Interpolate all target points at once. Error if any target point lies
     537             :       // outside the source domain. This implementation opens each file in turn
     538             :       // (so it doesn't hold all files in memory at once), and it is efficient
     539             :       // about mapping points through the blocks of the source domain.
     540             :       std::vector<DataVector> interpolated_data{};
     541             :       spectre::Exporter::interpolate_to_points(
     542             :           make_not_null(&interpolated_data), file_paths,
     543             :           "/" + get<OptionTags::Subgroup>(options), observation,
     544             :           tensor_components, all_target_points,
     545             :           get<OptionTags::ExtrapolateIntoExcisions>(options),
     546             :           /*error_on_missing_points=*/true,
     547             :           get<OptionTags::NumThreads>(options));
     548             :       // The target points are no longer needed; free them before distributing
     549             :       // the (potentially large) interpolated data to the target elements.
     550             :       all_target_points = tnsr::I<DataVector, Dim, Frame::Inertial>{};
     551             : 
     552             :       // Distribute the interpolated data to the target elements.
     553             :       for (size_t e = 0; e < target_ids.size(); ++e) {
     554             :         auto target_element_data = detail::scatter_element_data<FieldTagsList>(
     555             :             interpolated_data, target_starts[e], target_num_points[e],
     556             :             selected_fields);
     557             :         if constexpr (Parallel::is_dg_element_collection_v<ReceiveComponent>) {
     558             :           ERROR("Can't yet do numerical initial data with nodegroups");
     559             :         } else {
     560             :           Parallel::receive_data<Tags::VolumeData<FieldTagsList>>(
     561             :               Parallel::get_parallel_component<ReceiveComponent>(
     562             :                   cache)[target_ids[e]],
     563             :               volume_data_id, std::move(target_element_data));
     564             :         }
     565             :       }
     566             :       return;
     567             :     }  // not elements_are_identical
     568             : 
     569             :     // Now handle identical elements:
     570             :     // The source and target elements are the same (matching domains and
     571             :     // h-refinement), so data is transferred one-to-one, interpolating only
     572             :     // between different meshes (p-refinement).
     573             :     std::optional<size_t> prev_observation_id{};
     574             :     double observation_value = std::numeric_limits<double>::signaling_NaN();
     575             :     std::optional<Domain<Dim>> source_domain{};
     576             :     domain::FunctionsOfTimeMap source_domain_functions_of_time{};
     577             :     for (const std::string& file_name : file_paths) {
     578             :       // Open the volume data file
     579             :       h5::H5File<h5::AccessType::ReadOnly> h5file(file_name);
     580             :       constexpr size_t version_number = 0;
     581             :       const auto& volume_file = h5file.get<h5::VolumeData>(
     582             :           "/" + get<OptionTags::Subgroup>(options), version_number);
     583             : 
     584             :       // Select observation ID
     585             :       const size_t observation_id = std::visit(
     586             :           spectre::Exporter::SelectObservation{volume_file}, observation);
     587             :       if (prev_observation_id.has_value() and
     588             :           prev_observation_id.value() != observation_id) {
     589             :         ERROR("Inconsistent selection of observation ID in file "
     590             :               << file_name
     591             :               << ". Make sure all files select the same observation ID.");
     592             :       }
     593             :       prev_observation_id = observation_id;
     594             :       observation_value = volume_file.get_observation_value(observation_id);
     595             : 
     596             :       // Memory buffer for the tensor data stored in this file. The data is
     597             :       // loaded lazily when it is needed, so we can skip loading files that
     598             :       // contain none of the elements on this node.
     599             :       std::optional<tuples::tagged_tuple_from_typelist<FieldTagsList>>
     600             :           all_tensor_data{};
     601             : 
     602             :       // Retrieve the information needed to reconstruct which element the data
     603             :       // belongs to
     604             :       const auto source_grid_names = volume_file.get_grid_names(observation_id);
     605             :       const auto source_extents = volume_file.get_extents(observation_id);
     606             :       const auto source_bases = volume_file.get_bases(observation_id);
     607             :       const auto source_quadratures =
     608             :           volume_file.get_quadratures(observation_id);
     609             :       // Reconstruct domain from volume data file
     610             :       const std::optional<std::vector<char>> serialized_domain =
     611             :           volume_file.get_domain();
     612             :       if (serialized_domain.has_value()) {
     613             :         if (source_domain.has_value()) {
     614             : #ifdef SPECTRE_DEBUG
     615             :           // Check that the domain is the same in all files (only in debug
     616             :           // mode)
     617             :           const auto deserialized_domain =
     618             :               deserialize<Domain<Dim>>(serialized_domain->data());
     619             :           if (*source_domain != deserialized_domain) {
     620             :             ERROR_NO_TRACE(
     621             :                 "The domain in all volume files must be the same. Domain in "
     622             :                 "file '"
     623             :                 << file_name << volume_file.subfile_path()
     624             :                 << "' differs from a previously read file.");
     625             :           }
     626             : #endif
     627             :         } else {
     628             :           source_domain = deserialize<Domain<Dim>>(serialized_domain->data());
     629             :         }
     630             :       } else {
     631             :         Parallel::printf(
     632             :             "WARNING: No serialized domain found in file. "
     633             :             "Verification that elements in the source and target domain "
     634             :             "match will be skipped.\n");
     635             :       }
     636             :       // Reconstruct functions of time from volume data file
     637             :       if (source_domain_functions_of_time.empty() and
     638             :           source_domain.has_value() and
     639             :           alg::any_of(source_domain->blocks(), [](const auto& block) {
     640             :             return block.is_time_dependent();
     641             :           })) {
     642             :         const std::optional<std::vector<char>> serialized_functions_of_time =
     643             :             volume_file.get_functions_of_time(observation_id);
     644             :         if (not serialized_functions_of_time.has_value()) {
     645             :           ERROR_NO_TRACE("No domain functions of time found in file '"
     646             :                          << file_name << volume_file.subfile_path()
     647             :                          << "'. The functions of time are needed to verify the "
     648             :                             "inertial coordinates with time-dependent maps.");
     649             :         }
     650             :         source_domain_functions_of_time =
     651             :             deserialize<domain::FunctionsOfTimeMap>(
     652             :                 serialized_functions_of_time->data());
     653             :       }
     654             : 
     655             :       // Transfer the data to the target elements contained in this file. We
     656             :       // erase target elements when they are complete, so subsequent files only
     657             :       // search for the remaining elements and we can stop early.
     658             :       std::unordered_set<ElementId<Dim>> completed_target_elements{};
     659             :       for (const auto& target_element_id : target_element_ids) {
     660             :         const auto& [target_points, target_mesh] =
     661             :             get<Tags::RegisteredElements<Dim>>(box).at(
     662             :                 Parallel::make_array_component_id<ReceiveComponent>(
     663             :                     target_element_id));
     664             :         const auto target_grid_name = get_output(target_element_id);
     665             :         // Process this element only if it's in the file
     666             :         if (std::find(source_grid_names.begin(), source_grid_names.end(),
     667             :                       target_grid_name) == source_grid_names.end()) {
     668             :           continue;
     669             :         }
     670             : 
     671             :         // Lazily load the tensor data from the file
     672             :         if (not all_tensor_data.has_value()) {
     673             :           all_tensor_data = detail::read_tensor_data<FieldTagsList>(
     674             :               volume_file, observation_id, selected_fields);
     675             :         }
     676             : 
     677             :         const auto source_mesh = h5::mesh_for_grid<Dim>(
     678             :             target_grid_name, source_grid_names, source_extents, source_bases,
     679             :             source_quadratures);
     680             :         const auto element_data_offset_and_length =
     681             :             h5::offset_and_length_for_grid(target_grid_name, source_grid_names,
     682             :                                            source_extents);
     683             :         auto source_element_data = detail::extract_element_data<FieldTagsList>(
     684             :             element_data_offset_and_length, *all_tensor_data, selected_fields);
     685             : 
     686             :         // Verify that the source and target elements really are the same
     687             :         if (source_domain.has_value()) {
     688             :           detail::verify_inertial_coordinates(*source_domain, observation_value,
     689             :                                               source_domain_functions_of_time,
     690             :                                               target_element_id, target_mesh,
     691             :                                               target_points);
     692             :         }
     693             : 
     694             :         // Transfer the data one-to-one, interpolating only if the meshes differ
     695             :         // by p-refinement
     696             :         tuples::tagged_tuple_from_typelist<FieldTagsList> target_element_data{};
     697             :         if (source_mesh == target_mesh) {
     698             :           target_element_data = std::move(source_element_data);
     699             :         } else {
     700             :           detail::interpolate_selected_fields<FieldTagsList>(
     701             :               make_not_null(&target_element_data), source_element_data,
     702             :               source_mesh, target_mesh, selected_fields);
     703             :         }
     704             :         if constexpr (Parallel::is_dg_element_collection_v<ReceiveComponent>) {
     705             :           ERROR("Can't yet do numerical initial data with nodegroups");
     706             :         } else {
     707             :           Parallel::receive_data<Tags::VolumeData<FieldTagsList>>(
     708             :               Parallel::get_parallel_component<ReceiveComponent>(
     709             :                   cache)[target_element_id],
     710             :               volume_data_id, std::move(target_element_data));
     711             :         }
     712             :         completed_target_elements.insert(target_element_id);
     713             :       }  // loop over registered elements
     714             :       for (const auto& completed_element_id : completed_target_elements) {
     715             :         target_element_ids.erase(completed_element_id);
     716             :       }
     717             :       // Stop early when all target elements are complete
     718             :       if (target_element_ids.empty()) {
     719             :         break;
     720             :       }
     721             :     }  // loop over volume files
     722             : 
     723             :     // Have we completed all target elements? If we haven't, the source and
     724             :     // target domains probably don't match.
     725             :     if (not target_element_ids.empty()) {
     726             :       ERROR_NO_TRACE("The following "
     727             :                      << target_element_ids.size()
     728             :                      << " element(s) were not found in the source volume "
     729             :                         "data files:\n"
     730             :                      << target_element_ids
     731             :                      << "\nMake sure the source and target domains match "
     732             :                         "when 'ElementsAreIdentical' is enabled, or set "
     733             :                         "it to 'False' to interpolate between the grids.");
     734             :     }
     735             :   }
     736             : 
     737             :  private:
     738             :   template <typename... LocalFieldTags>
     739             :   static tuples::TaggedTuple<Tags::Selected<LocalFieldTags>...>
     740           0 :   select_all_fields(tmpl::list<LocalFieldTags...> /*meta*/) {
     741             :     return {db::tag_name<LocalFieldTags>()...};
     742             :   }
     743             : };
     744             : 
     745             : }  // namespace Actions
     746             : }  // namespace importers

Generated by: LCOV version 1.14