Line data Source code
1 0 : // Distributed under the MIT License. 2 : // See LICENSE.txt for details. 3 : 4 : #pragma once 5 : 6 : #include <cstddef> 7 : #include <optional> 8 : #include <shared_mutex> 9 : #include <string> 10 : #include <utility> 11 : #include <variant> 12 : #include <vector> 13 : 14 : #include "DataStructures/Tensor/TypeAliases.hpp" 15 : #include "Domain/FunctionsOfTime/FunctionOfTime.hpp" 16 : #include "IO/Exporter/PointwiseInterpolator.hpp" 17 : #include "Utilities/Gsl.hpp" 18 : 19 : namespace spectre::Exporter { 20 : 21 : /*! 22 : * \brief Interpolates data in volume files in both space and time 23 : * 24 : * This class reads the volume data at multiple time steps into memory and can 25 : * then interpolate it to any number of target points at any time within the 26 : * time bounds of the loaded data. 27 : * 28 : * \details The interpolation is first done in space and then in time. For the 29 : * interpolation in space, the PointwiseInterpolator class is used, which does 30 : * spectral interpolation within the elements. For the interpolation in time, a 31 : * polynomial interpolation with fixed order is used (currently third order, 32 : * meaning four time slices are used). 33 : * 34 : * \par Coordinate frames 35 : * The target points are given in the `Frame` specified as template parameter 36 : * (defaults to `Frame::Inertial`). However, the interpolation is done in the 37 : * grid frame, which is time-independent and therefore best suitable for 38 : * interpolation in time. Details of this idea can be found in 39 : * \cite Bohn:2016afc . 40 : * 41 : * \par Thread safety 42 : * Loading volume data from H5 files on multiple threads at once is not 43 : * generally thread safe, so `load_time_bounds` may only be called on a single 44 : * thread. However, once the data is loaded, the `interpolate_to_point` function 45 : * is thread safe. This means the volume data can be loaded once in a single 46 : * thread and then used by multiple threads to interpolate to different points 47 : * in parallel. Also, `load_time_bounds` can be called again to load new data 48 : * while other threads are still accessing the old data, as long as they only 49 : * request data within both the old and the new time bounds. 50 : */ 51 : template <size_t Dim, typename Frame = ::Frame::Inertial> 52 1 : struct SpacetimeInterpolator { 53 0 : SpacetimeInterpolator() = default; 54 0 : SpacetimeInterpolator(const SpacetimeInterpolator&) = delete; 55 0 : SpacetimeInterpolator& operator=(const SpacetimeInterpolator&) = delete; 56 0 : SpacetimeInterpolator(SpacetimeInterpolator&&); 57 0 : SpacetimeInterpolator& operator=(SpacetimeInterpolator&&); 58 0 : ~SpacetimeInterpolator() = default; 59 : 60 : /*! 61 : * \brief Construct the interpolator without loading any volume data 62 : * 63 : * \param volume_files_or_glob The list of H5 files, or a glob pattern 64 : * \param subfile_name The name of the subfile in the H5 files containing the 65 : * volume data 66 : * \param tensor_components The tensor components to interpolate. The order of 67 : * the components in the vector is the order in which they will be returned. 68 : */ 69 1 : SpacetimeInterpolator( 70 : std::variant<std::vector<std::string>, std::string> volume_files_or_glob, 71 : std::string subfile_name, std::vector<std::string> tensor_components); 72 : 73 : /// The maximum time bounds available in the volume data files (taking into 74 : /// account the number of ghost slices needed for interpolation). Only time 75 : /// bounds in this (inclusive) range can be requested with the 76 : /// `load_time_bounds()` function. 77 1 : std::array<double, 2> max_time_bounds() const; 78 : 79 : /*! 80 : * \brief Load volume data into memory such that the given time bounds are 81 : * covered. 82 : * 83 : * The given time bounds must be within the `max_time_bounds()` (inclusive). 84 : * 85 : * Previously loaded volume data slices outside the given time bounds will be 86 : * dropped and new ones loaded. 87 : * 88 : * \par Thread safety 89 : * This function is not generally thread safe, as it loads data from H5 files. 90 : * It should be called from a single thread only. It is safe to keep calling 91 : * interpolation routines from other threads while this function is running, 92 : * as long as they are requesting data within both the old and the new time 93 : * bounds. 94 : */ 95 1 : void load_time_bounds(std::array<double, 2> time_bounds); 96 : 97 : /// The time bounds of the loaded data (inclusive). It is an error to call the 98 : /// interpolation routines outside these bounds. 99 1 : std::array<double, 2> time_bounds() const { return time_bounds_; } 100 : 101 : /// The number of loaded time slices. 102 1 : size_t num_loaded_slices() const { return interpolators_.size(); } 103 : 104 : /*! 105 : * \brief Interpolate to a single point 106 : * 107 : * This function is thread safe, so the interpolator can be constructed to 108 : * load the volume data once and then used by multiple threads to interpolate 109 : * to different points in parallel. Note that updating the `block_order` (if 110 : * provided) is not thread safe, so each thread should manage a separate 111 : * `block_order`. 112 : * 113 : * \param result the interpolated data at the target point. The vector is over 114 : * the number of components. Will be resized automatically. 115 : * \param target_point the point to interpolate to. 116 : * \param time the time to interpolate to. Must be within the `time_bounds()`. 117 : * \param block_order an optional priority order to search for the block 118 : * containing the target point. Will be updated when the point is found. 119 : * See `block_logical_coordinates_single_point` for more details. 120 : */ 121 1 : void interpolate_to_point(gsl::not_null<std::vector<double>*> result, 122 : const tnsr::I<double, Dim, Frame>& target_point, 123 : double time, 124 : std::optional<gsl::not_null<std::vector<size_t>*>> 125 : block_order = std::nullopt) const; 126 : 127 : private: 128 0 : std::variant<std::vector<std::string>, std::string> volume_files_or_glob_; 129 0 : std::string subfile_name_; 130 0 : std::vector<std::string> tensor_components_; 131 0 : constexpr static size_t time_interpolation_order_ = 3; // must be odd 132 0 : constexpr static size_t num_ghost_slices_ = 133 : (time_interpolation_order_ + 1) / 2 - 1; 134 0 : domain::FunctionsOfTimeMap functions_of_time_; 135 : // Metadata collected from data files 136 0 : std::vector<size_t> all_observation_ids_; 137 0 : std::vector<double> all_observation_values_; 138 : // State 139 0 : std::array<double, 2> time_bounds_{ 140 : {std::numeric_limits<double>::signaling_NaN(), 141 : std::numeric_limits<double>::signaling_NaN()}}; 142 : // Loaded from data files 143 0 : std::vector<PointwiseInterpolator<Dim, ::Frame::Grid>> interpolators_{}; 144 : // Mutex to protect other threads from accessing the `interpolators_` vector 145 : // while it is being modified 146 0 : mutable std::shared_mutex interpolators_mutex_; // NOLINT(spectre-mutable) 147 : }; 148 : 149 : } // namespace spectre::Exporter