GridFormat 0.6.0
I/O-Library for grid-like data structures
Loading...
Searching...
No Matches
hdf_common.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2022-2023 Dennis Gläser <dennis.glaeser@iws.uni-stuttgart.de>
2// SPDX-License-Identifier: MIT
3
9#ifndef GRIDFORMAT_VTK_HDF_COMMON_HPP_
10#define GRIDFORMAT_VTK_HDF_COMMON_HPP_
11
12#include <vector>
13#include <cstddef>
14#include <numeric>
15#include <string>
16#include <optional>
17#include <ranges>
18#include <array>
19#include <exception>
20
21#include <gridformat/common/hdf5.hpp>
23#include <gridformat/common/precision.hpp>
24#include <gridformat/common/lazy_field.hpp>
25#include <gridformat/common/range_field.hpp>
26#include <gridformat/common/field_transformations.hpp>
27#include <gridformat/common/multi_string.hpp>
28#include <gridformat/common/string_conversion.hpp>
29#include <gridformat/common/exceptions.hpp>
30#include <gridformat/parallel/communication.hpp>
31
32namespace GridFormat {
33
34namespace VTK {
35
38 bool static_grid = false;
39 bool static_meta_data = true;
40};
41
42} // namespace VTK
43
44
45namespace VTKHDF {
46
48struct IOContext {
49 const int my_rank;
50 const int num_ranks;
51 const bool is_parallel;
52 const std::vector<std::size_t> rank_cells;
53 const std::vector<std::size_t> rank_points;
54 const std::size_t num_cells_total;
55 const std::size_t num_points_total;
56 const std::size_t my_cell_offset;
57 const std::size_t my_point_offset;
58
59 IOContext(int _my_rank,
60 int _num_ranks,
61 std::vector<std::size_t> _rank_cells,
62 std::vector<std::size_t> _rank_points)
63 : my_rank{_my_rank}
64 , num_ranks{_num_ranks}
65 , is_parallel{_num_ranks > 1}
66 , rank_cells{std::move(_rank_cells)}
67 , rank_points{std::move(_rank_points)}
68 , num_cells_total{_accumulate(rank_cells)}
69 , num_points_total{_accumulate(rank_points)}
70 , my_cell_offset{_accumulate_rank_offset(rank_cells)}
71 , my_point_offset{_accumulate_rank_offset(rank_points)} {
72 if (my_rank >= num_ranks)
73 throw ValueError(as_error("Given rank is not within communicator size"));
74 if (num_ranks != static_cast<int>(rank_cells.size()))
75 throw ValueError(as_error("Cells vector does not match communicator size"));
76 if (num_ranks != static_cast<int>(rank_points.size()))
77 throw ValueError(as_error("Points vector does not match communicator size"));
78 }
79
80 template<Concepts::Grid Grid, Concepts::Communicator Communicator>
81 static IOContext from(const Grid& grid, const Communicator& comm, int root_rank = 0) {
82 const int size = Parallel::size(comm);
83 const int rank = Parallel::rank(comm);
84 const std::size_t num_points = number_of_points(grid);
85 const std::size_t num_cells = number_of_cells(grid);
86 if (size == 1)
87 return IOContext{rank, size, std::vector{num_cells}, std::vector{num_points}};
88
89 const auto all_num_points = Parallel::gather(comm, num_points, root_rank);
90 const auto all_num_cells = Parallel::gather(comm, num_cells, root_rank);
91 const auto my_all_num_points = Parallel::broadcast(comm, all_num_points, root_rank);
92 const auto my_all_num_cells = Parallel::broadcast(comm, all_num_cells, root_rank);
93 return IOContext{rank, size, my_all_num_cells, my_all_num_points};
94 }
95
96 private:
97 std::size_t _accumulate(const std::vector<std::size_t>& in) const {
98 return std::accumulate(in.begin(), in.end(), std::size_t{0});
99 }
100
101 std::size_t _accumulate_rank_offset(const std::vector<std::size_t>& in) const {
102 if (in.size() <= static_cast<std::size_t>(my_rank))
103 throw ValueError("Rank-vector length must be equal to number of ranks");
104 return std::accumulate(in.begin(), std::next(in.begin(), my_rank), std::size_t{0});
105 }
106};
107
109inline void check_array_name(const std::string& name) {
110 if (name.empty())
111 throw ValueError("VTKHDF array names must not be empty");
112 if (name.find_first_of("/.") != std::string::npos)
113 throw ValueError("VTKHDF array names must not contain '/' or '.' (received '" + name + "')");
114}
115
117inline bool is_string_field(const Field& field) {
118 return field.precision().template is<char>() && field.layout().dimension() == 1;
119}
120
122inline std::vector<std::string> extract_strings(const Field& field) {
123 const auto strings = field.template export_to<MultiString>();
124 const auto slices = strings.slices();
125 return {slices.begin(), slices.end()};
126}
127
128#if GRIDFORMAT_HAVE_HIGH_FIVE
129
134template<typename C>
135class DataSetField : public LazyField<const HDF5::File<C>&> {
136 using ParentType = LazyField<const HDF5::File<C>&>;
137
138 public:
139 using ParentType::ParentType;
140
141 explicit DataSetField(const HDF5::File<C>& file, std::string path)
142 : ParentType{
143 file,
144 MDLayout{file.get_dimensions(path).value()},
145 DynamicPrecision{file.get_precision(path).value()},
146 [_p=std::move(path)] (const HDF5::File<C>& file) {
147 return file.visit_dataset(_p, [&] <typename F> (F&& field) {
148 return field.serialized();
149 });
150 }
151 } {}
152};
153
154template<typename C>
155DataSetField(const HDF5::File<C>&, std::string) -> DataSetField<C>;
156
157template<typename C, typename CB>
158DataSetField(const HDF5::File<C>&, MDLayout, DynamicPrecision, CB&&) -> DataSetField<C>;
159
161template<typename C>
162std::string get_file_type(const HDF5::File<C>& file) {
163 if (!file.exists("/VTKHDF"))
164 throw IOError("Given file is not a VTK-HDF file");
165 if (!file.has_attribute_at("/VTKHDF/Type"))
166 throw IOError("VTKHDF-Type attribute missing");
167 return file.template read_attribute_to<std::string>("/VTKHDF/Type");
168}
169
173template<typename C>
174void check_version_compatibility(const HDF5::File<C>& file, const std::array<std::size_t, 2>& supported) {
175 if (file.has_attribute_at("/VTKHDF/Version"))
176 file.visit_attribute("/VTKHDF/Version", [&] (auto&& field) {
177 const auto version = field.template export_to<std::vector<std::size_t>>();
178 if (version.size() > 0 && version.at(0) > supported[0])
179 throw ValueError(
180 "File version is higher than supported by the reader (" + as_string(supported, ".") + ")"
181 );
182 });
183}
184
187 const auto layout = field->layout();
188 if (layout.dimension() == 0)
189 return make_field_ptr(ReshapedField{field, MDLayout{{std::size_t{1}}}});
190 if (layout.dimension() > 2)
191 return make_field_ptr(ReshapedField{field, MDLayout{{layout.extent(0), layout.number_of_entries(1)}}});
192 return field;
193}
194
196inline void write_field_data(HDF5::File<>& file, const std::string& name, FieldPtr field) {
197 const std::string path = "/VTKHDF/FieldData/" + name;
198 if (is_string_field(*field))
199 file.write_strings(extract_strings(*field), path);
200 else
201 file.write(*as_field_data_tuples(field), path);
202}
203
210inline void write_field_data_step(HDF5::File<>& file, const std::string& name, FieldPtr field) {
211 const std::string path = "/VTKHDF/FieldData/" + name;
212 const auto offset = file.get_dimensions(path).value_or(std::vector<std::size_t>{0}).at(0);
213 std::array<std::size_t, 2> sizes;
214 if (is_string_field(*field)) {
215 const auto strings = extract_strings(*field);
216 sizes = {1, strings.size()};
217 file.write_strings(strings, path);
218 } else {
219 const auto tuples = as_field_data_tuples(field);
220 const auto layout = tuples->layout();
221 sizes = {layout.dimension() > 1 ? layout.extent(1) : 1, layout.extent(0)};
222 file.write(*tuples, path);
223 }
224 file.write(std::array{offset}, "/VTKHDF/Steps/FieldDataOffsets/" + name);
225 file.write(std::vector{sizes}, "/VTKHDF/Steps/FieldDataSizes/" + name);
226}
227
229inline void write_field_data_step_referencing(std::size_t step_index, HDF5::File<>& file, const std::string& name) {
230 const std::string offsets_path = "/VTKHDF/Steps/FieldDataOffsets/" + name;
231 const std::string sizes_path = "/VTKHDF/Steps/FieldDataSizes/" + name;
232 const auto offset = file.read_dataset_to<std::size_t>(
233 offsets_path, HDF5::Slice{.offset = {step_index}, .count = {1}}
234 );
235 const auto sizes = file.read_dataset_to<std::vector<std::size_t>>(
236 sizes_path, HDF5::Slice{.offset = {step_index, 0}, .count = {1, 2}}
237 );
238 file.write(std::array{offset}, offsets_path);
239 file.write(std::vector{std::array{sizes.at(0), sizes.at(1)}}, sizes_path);
240}
241
243template<typename C>
244FieldPtr read_field_data(const HDF5::File<C>& file, const std::string& name, std::optional<std::size_t> step) {
245 const std::string path = "VTKHDF/FieldData/" + name;
246 const auto precision = file.get_precision(path).value();
247 const bool is_string = precision.template is<char>();
248 if (!step)
249 return is_string ? make_field_ptr(RangeField{file.template read_dataset_to<MultiString>(path)})
250 : make_field_ptr(DataSetField{file, path});
251
252 // Without sizes (written by GridFormat <= 0.5), each step is a single row of the dataset
253 const auto offset = file.template read_dataset_to<std::size_t>(
254 "VTKHDF/Steps/FieldDataOffsets/" + name, HDF5::Slice{.offset = {*step}, .count = {1}}
255 );
256 const std::string sizes_path = "VTKHDF/Steps/FieldDataSizes/" + name;
257 const auto sizes = file.has_dataset_at(sizes_path) // (components, tuples)
258 ? std::optional{file.template read_dataset_to<std::vector<std::size_t>>(
259 sizes_path, HDF5::Slice{.offset = {*step, 0}, .count = {1, 2}}
260 )}
261 : std::nullopt;
262 if (is_string) {
263 const std::size_t count = sizes ? sizes->at(0)*sizes->at(1) : 1;
264 return make_field_ptr(RangeField{file.template read_dataset_to<MultiString>(
265 path, HDF5::Slice{.offset = {offset}, .count = {count}}
266 )});
267 }
268
269 const auto dims = file.get_dimensions(path).value();
270 std::vector<std::size_t> slice_offset(dims.size(), 0);
271 slice_offset.at(0) = offset;
272 auto count = dims;
273 count.at(0) = sizes ? sizes->at(1) : 1;
274 if (sizes && MDLayout{count}.number_of_entries() != sizes->at(0)*sizes->at(1))
275 throw SizeError("Field data '" + name + "' does not match its stated number of components");
276 MDLayout layout = sizes ? MDLayout{count} : MDLayout{count | std::views::drop(1)};
278 file,
279 std::move(layout),
280 precision,
281 [p=path, o=std::move(slice_offset), c=std::move(count)] (const HDF5::File<C>& f) {
282 return f.visit_dataset(p, [&] <typename F> (F&& field) {
283 return FlattenedField{make_field_ptr(std::move(field))}.serialized();
284 }, HDF5::Slice{.offset = o, .count = c});
285 }
286 });
287}
288
294template<Concepts::Communicator C, std::invocable<HDF5::File<>&> Write>
295void write_on_root(const std::string& filename, const C& comm, int root, Write&& write) {
296 Parallel::barrier(comm);
297 std::exception_ptr error;
298 if (Parallel::rank(comm) == root) {
299 try {
300 HDF5::File file{filename, HDF5::File<>::append};
301 write(file);
302 } catch (...) {
303 error = std::current_exception();
304 }
305 }
306 // the broadcast also keeps the other ranks from reopening the file before the root is done
307 if (Parallel::broadcast(comm, static_cast<int>(error != nullptr), root)) {
308 if (error)
309 std::rethrow_exception(error);
310 throw IOError("Writing to '" + filename + "' failed on rank " + std::to_string(root));
311 }
312}
313
314#endif // GRIDFORMAT_HAVE_HIGH_FIVE
315
316} // namespace VTKHDF
317} // namespace GridFormat
318
319#endif // GRIDFORMAT_VTK_HDF_COMMON_HPP_
Abstract interface for fields of values that is used by writers/readers to store fields.
Definition field.hpp:38
DynamicPrecision precision() const
Return the precision of the scalar field values.
Definition field.hpp:57
MDLayout layout() const
Return the layout of this field.
Definition field.hpp:52
Field implementation that draws values from an open HDF5 file upon request.
Definition hdf_common.hpp:135
Abstract interface for fields of values that is used by writers/readers to store fields.
FieldPtr make_field_ptr(F &&f)
Factory function for field pointers.
Definition field.hpp:211
std::shared_ptr< const Field > FieldPtr
Pointer type used by writers/readers for fields.
Definition field.hpp:205
void write_field_data_step(HDF5::File<> &file, const std::string &name, FieldPtr field)
Append a time step to a field data array of a transient file.
Definition hdf_common.hpp:210
void write_field_data_step_referencing(std::size_t step_index, HDF5::File<> &file, const std::string &name)
Append a time step to a field data array of a transient file, referring to the data of an earlier ste...
Definition hdf_common.hpp:229
void check_version_compatibility(const HDF5::File< C > &file, const std::array< std::size_t, 2 > &supported)
Definition hdf_common.hpp:174
void write_field_data(HDF5::File<> &file, const std::string &name, FieldPtr field)
Write a field data array into a file that is not transient.
Definition hdf_common.hpp:196
void check_array_name(const std::string &name)
Throw if the given name cannot be used as a vtk-hdf array name.
Definition hdf_common.hpp:109
std::vector< std::string > extract_strings(const Field &field)
Return the strings stored in the given character field (see MultiString).
Definition hdf_common.hpp:122
std::string get_file_type(const HDF5::File< C > &file)
Read the vtk-hdf file type from an hdf5 file.
Definition hdf_common.hpp:162
void write_on_root(const std::string &filename, const C &comm, int root, Write &&write)
Open the given file with the default (non-MPI) driver on the root rank and pass it to write.
Definition hdf_common.hpp:295
FieldPtr read_field_data(const HDF5::File< C > &file, const std::string &name, std::optional< std::size_t > step)
Read the field data array with the given name, at the given step for transient files.
Definition hdf_common.hpp:244
bool is_string_field(const Field &field)
Return true if the given field holds strings (i.e. is a flat array of characters, see MultiString).
Definition hdf_common.hpp:117
FieldPtr as_field_data_tuples(FieldPtr field)
Return the given field data in the shape of tuples, i.e. (tuples) or (tuples, components).
Definition hdf_common.hpp:186
Options for transient vtk-hdf file formats.
Definition hdf_common.hpp:37
bool static_grid
Set to true the grid is the same for all time steps (will only be written once).
Definition hdf_common.hpp:38
bool static_meta_data
Set to true if the metadata is same for all time steps (will only be written once).
Definition hdf_common.hpp:39