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 <cstdint>
8 : #include <optional>
9 : #include <string>
10 : #include <tuple>
11 : #include <utility>
12 : #include <vector>
13 :
14 : #include "IO/H5/Object.hpp"
15 : #include "IO/H5/OpenGroup.hpp"
16 :
17 : /// \cond
18 : class DataVector;
19 : struct ElementVolumeData;
20 : template <size_t>
21 : class Mesh;
22 : namespace Spectral {
23 : enum class Basis : uint8_t;
24 : enum class Quadrature : uint8_t;
25 : } // namespace Spectral
26 : struct TensorComponent;
27 : /// \endcond
28 :
29 : namespace h5 {
30 : /*!
31 : * \ingroup HDF5Group
32 : * \brief A volume data subfile written inside an H5 file.
33 : *
34 : * The volume data inside the subfile can be of any dimensionality greater than
35 : * zero. This means that in a 3D simulation, data on 2-dimensional surfaces are
36 : * written as a VolumeData subfile. Data can be written using the
37 : * `write_volume_data()` method. An integral observation id is used to keep
38 : * track of the observation instance at which the data is written, and
39 : * associated with it is a floating point observation value, such as the
40 : * simulation time at which the data was written. The observation id will
41 : * generally be the result of hashing the temporal identifier used for the
42 : * simulation.
43 : *
44 : * \par Grid names
45 : * The data stored in the subfile are the tensor components passed to the
46 : * `write_volume_data()` method as a `std::vector<ElementVolumeData>`. Typically
47 : * the `GRID_NAME` should be the output of the stream operator of the spatial ID
48 : * of the parallel component element sending the data to be observed. For
49 : * example, in the case of a dG evolution where the spatial IDs are
50 : * `ElementId`s, the grid names would be of the form `[B0,(L2I3,L2I3,L2I3)]`.
51 : *
52 : * \par Data layout in the H5 file
53 : * The data are written contiguously inside of the H5 subfile, in that each
54 : * tensor component has a single dataset which holds all of the data from all
55 : * elements, e.g. a tensor component `T_xx` which is found on all grids appears
56 : * in the path `H5_FILE_NAME/SUBFILE_NAME.vol/OBSERVATION_ID/GRID_NAME/T_xx` and
57 : * that is where all of the `T_xx` data from all of the grids resides. Note that
58 : * coordinates must be written as tensors in order to visualize the data in
59 : * ParaView, Visit, etc. In order to reconstruct which data came from which
60 : * grid, the `get_grid_names()`, and `get_extents()` methods list the grids
61 : * and their extents in the order which they and the data were written.
62 : * For example, if the first grid has name `GRID_NAME` with extents
63 : * `{2, 2, 2}`, it was responsible for contributing the first 2*2*2 = 8 grid
64 : * points worth of data in each tensor dataset. Use the
65 : * `h5::offset_and_length_for_grid` function to compute the offset into the
66 : * contiguous dataset that corresponds to a particular grid.
67 : *
68 : * \par Domain and FunctionsOfTime
69 : * A serialized representation of the domain and the functions of time can be
70 : * written into the subfile alongside the tensor data. Reconstructing the domain
71 : * (specifically the block maps) is needed for accurate interpolation of the
72 : * tensor data to another set of points. The domain is currently stored as a
73 : * `std::vector<char>`, and it is up to the calling code to serialize and
74 : * deserialize the data, taking into account that files may be written and read
75 : * with different versions of the code.
76 : *
77 : * \warning Currently the topology of the grids is assumed to be tensor products
78 : * of lines, i.e. lines, quadrilaterals, and hexahedrons. However, this can be
79 : * extended in the future. If support for more topologies is required, please
80 : * file an issue.
81 : */
82 1 : class VolumeData : public h5::Object {
83 : public:
84 0 : static std::string extension() { return ".vol"; }
85 :
86 0 : VolumeData(bool subfile_exists, detail::OpenGroup&& group, hid_t location,
87 : const std::string& name, uint32_t version = 1);
88 :
89 0 : VolumeData(const VolumeData& /*rhs*/) = delete;
90 0 : VolumeData& operator=(const VolumeData& /*rhs*/) = delete;
91 0 : VolumeData(VolumeData&& /*rhs*/) = delete; // NOLINT
92 0 : VolumeData& operator=(VolumeData&& /*rhs*/) = delete; // NOLINT
93 :
94 0 : ~VolumeData() override = default;
95 :
96 : /*!
97 : * \returns the header of the VolumeData file
98 : */
99 1 : const std::string& get_header() const { return header_; }
100 :
101 : /*!
102 : * \returns the user-specified version number of the VolumeData file
103 : *
104 : * \note h5::Version returns a uint32_t, so we return one here too for the
105 : * version
106 : */
107 1 : uint32_t get_version() const { return version_; }
108 :
109 : /*!
110 : * \brief Write volume data at an observation id and observation value.
111 : *
112 : * \param observation_id The integral observation id at which the data is
113 : * written.
114 : * \param observation_value The floating point observation value (e.g. time)
115 : * at which the data is written.
116 : * \param elements The volume data to write, passed as a vector of
117 : * ElementVolumeData structs.
118 : * \param serialized_domain An optional serialized domain. It will only be
119 : * written if there is not already a domain stored in the subfile.
120 : * \param serialized_observation_functions_of_time An optional serialized
121 : * observation-specific functions of time. It should be valid at the given
122 : * observation value, but not contain the entire history to save space.
123 : * \param serialized_global_functions_of_time An optional serialized global
124 : * functions of time oject. It should be valid for the entire simulation. It
125 : * will be used to overwrite the existing global functions of time iff the old
126 : * object was written at a smaller observation_value (i.e. an earlier time).
127 : */
128 1 : void write_volume_data(
129 : size_t observation_id, double observation_value,
130 : const std::vector<ElementVolumeData>& elements,
131 : const std::optional<std::vector<char>>& serialized_domain = std::nullopt,
132 : const std::optional<std::vector<char>>&
133 : serialized_observation_functions_of_time = std::nullopt,
134 : const std::optional<std::vector<char>>&
135 : serialized_global_functions_of_time = std::nullopt);
136 :
137 : /// \returns true if a serialized domain has been written to the subfile.
138 1 : bool has_domain() const;
139 :
140 : /// \returns true if serialized functions of time have been written to the
141 : /// subfile.
142 1 : bool has_global_functions_of_time() const;
143 :
144 : /// Overwrites the current connectivity dataset with a new one. This new
145 : /// connectivity dataset builds connectivity within each block in the domain
146 : /// for each observation id in a list of observation id's
147 : template <size_t SpatialDim>
148 1 : void extend_connectivity_data(const std::vector<size_t>& observation_ids);
149 :
150 0 : void write_tensor_component(const size_t observation_id,
151 : const std::string& component_name,
152 : const DataVector& contiguous_tensor_data,
153 : bool overwrite_existing = false);
154 :
155 0 : void write_tensor_component(const size_t observation_id,
156 : const std::string& component_name,
157 : const std::vector<float>& contiguous_tensor_data,
158 : bool overwrite_existing = false);
159 :
160 : /// List all the integral observation ids in the subfile
161 : ///
162 : /// The list of observation IDs is sorted by their observation value, as
163 : /// returned by get_observation_value(size_t).
164 1 : std::vector<size_t> list_observation_ids() const;
165 :
166 : /// Get the observation value at the the integral observation id in the
167 : /// subfile
168 1 : double get_observation_value(size_t observation_id) const;
169 :
170 : /// Find the observation ID that matches the `observation_value`
171 : ///
172 : /// \details An epsilon can be specified and the observation id that matches
173 : /// within the epsilon of `observation_value` will be returned. If there is
174 : /// more than one id that is within the epsilon, an error will occur. If no
175 : /// epsilon is specified, this function will do exact comparison.
176 1 : size_t find_observation_id(
177 : double observation_value,
178 : const std::optional<double>& observation_value_epsilon =
179 : std::nullopt) const;
180 :
181 : /// List all the tensor components at observation id `observation_id`
182 1 : std::vector<std::string> list_tensor_components(size_t observation_id) const;
183 :
184 : /// List the names of all the grids at observation id `observation_id`
185 1 : std::vector<std::string> get_grid_names(size_t observation_id) const;
186 :
187 : /// Read a tensor component with name `tensor_component` at observation id
188 : /// `observation_id` from all grids in the file
189 1 : TensorComponent get_tensor_component(
190 : size_t observation_id, const std::string& tensor_component) const;
191 :
192 : /// Read the extents of all the grids stored in the file at the observation id
193 : /// `observation_id`
194 1 : std::vector<std::vector<size_t>> get_extents(size_t observation_id) const;
195 :
196 : /// Retrieve volume data for IDs in
197 : /// `[start_observation_value, end_observation_value]`.
198 : ///
199 : /// Returns a `std::vector` over times, sorted in ascending order of the
200 : /// observation value (the time in evolutions). The vector holds a
201 : /// `std::tuple` that holds
202 : /// 1. the integral observation ID
203 : /// 2. the observation value (time in evolutions)
204 : /// 3. a vector of `ElementVolumeData` sorted by the elements' name string.
205 : ///
206 : /// - If `start_observation_value` is `std::nullopt` then return
207 : /// everything from the beginning of the data to `end_observation_value`. If
208 : /// `end_observation_value` is `std::nullopt` then return everything from
209 : /// `start_observation_value` to the end of the data.
210 : /// - If `components_to_retrieve` is `std::nullopt` then return all
211 : /// components.
212 1 : auto get_data_by_element(std::optional<double> start_observation_value,
213 : std::optional<double> end_observation_value,
214 : const std::optional<std::vector<std::string>>&
215 : components_to_retrieve = std::nullopt) const
216 : -> std::vector<
217 : std::tuple<size_t, double, std::vector<ElementVolumeData>>>;
218 :
219 : /// Read the dimensionality of the grids. Note : This is the dimension of
220 : /// the grids as manifolds, not the dimension of the embedding space. For
221 : /// example, the volume data of a sphere is 2-dimensional, even though
222 : /// each point has an x, y, and z coordinate.
223 1 : size_t get_dimension() const;
224 :
225 : /// Return the character used as a separator between grids in the subfile.
226 1 : static char separator() { return ':'; }
227 :
228 : /// Return the basis being used for each element along each axis
229 1 : std::vector<std::vector<Spectral::Basis>> get_bases(
230 : size_t observation_id) const;
231 :
232 : /// Return the quadrature being used for each element along each axis
233 1 : std::vector<std::vector<Spectral::Quadrature>> get_quadratures(
234 : size_t observation_id) const;
235 :
236 : /*!
237 : * \brief Get the serialized domain if it was written.
238 : */
239 1 : std::optional<std::vector<char>> get_domain() const;
240 :
241 : /*!
242 : * \brief Get the observation-specific serialized functions of time at an \p
243 : * observation_id if they were written.
244 : */
245 1 : std::optional<std::vector<char>> get_functions_of_time(
246 : size_t observation_id) const;
247 :
248 : /*!
249 : * \brief Get the serialized global functions of time in the subfile if they
250 : * were written.
251 : */
252 1 : std::optional<std::vector<char>> get_global_functions_of_time() const;
253 :
254 1 : const std::string& subfile_path() const override { return path_; }
255 :
256 : private:
257 0 : detail::OpenGroup group_{};
258 0 : std::string name_{};
259 0 : std::string path_{};
260 0 : uint32_t version_{};
261 0 : detail::OpenGroup volume_data_group_{};
262 0 : std::string header_{};
263 : };
264 :
265 : /*!
266 : * \brief Find the interval within the contiguous dataset stored in
267 : * `h5::VolumeData` that holds data for a particular `grid_name`.
268 : *
269 : * `h5::VolumeData` stores data for all grids that compose the volume
270 : * contiguously. This function helps with reconstructing which part of that
271 : * contiguous dataset belongs to a particular grid. See the `h5::VolumeData`
272 : * documentation for more information on how it stores data.
273 : *
274 : * To use this function, call `h5::VolumeData::get_grid_names` and
275 : * `h5::VolumeData::get_extents` and pass the results for the `all_grid_names`
276 : * and `all_extents` arguments, respectively. This means you can retrieve this
277 : * information from an `h5::VolumeData` once and use it to call
278 : * `offset_and_length_for_grid` multiple times with different `grid_name`s.
279 : *
280 : * Here is an example for using this function:
281 : *
282 : * \snippet Test_VolumeData.cpp find_offset
283 : *
284 : * \see `h5::VolumeData`
285 : */
286 1 : std::pair<size_t, size_t> offset_and_length_for_grid(
287 : const std::string& grid_name,
288 : const std::vector<std::string>& all_grid_names,
289 : const std::vector<std::vector<size_t>>& all_extents);
290 :
291 : template <size_t Dim>
292 0 : Mesh<Dim> mesh_for_grid(
293 : const std::string& grid_name,
294 : const std::vector<std::string>& all_grid_names,
295 : const std::vector<std::vector<size_t>>& all_extents,
296 : const std::vector<std::vector<Spectral::Basis>>& all_bases,
297 : const std::vector<std::vector<Spectral::Quadrature>>& all_quadratures);
298 :
299 : } // namespace h5
|