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
20#include <gridformat/common/hdf5.hpp>
22#include <gridformat/common/precision.hpp>
23#include <gridformat/common/lazy_field.hpp>
24#include <gridformat/common/range_field.hpp>
25#include <gridformat/common/field_transformations.hpp>
26#include <gridformat/common/multi_string.hpp>
27#include <gridformat/common/string_conversion.hpp>
28#include <gridformat/common/exceptions.hpp>
29#include <gridformat/common/logging.hpp>
31#include <gridformat/grid/grid.hpp>
32#include <gridformat/parallel/communication.hpp>
33#include <gridformat/parallel/helpers.hpp>
34
35namespace GridFormat {
36
37namespace VTK {
38
41 bool static_grid = false;
42 bool static_meta_data = true;
43};
44
45} // namespace VTK
46
47
48namespace VTKHDF {
49
51struct IOContext {
52 const int my_rank;
53 const int num_ranks;
54 const bool is_parallel;
55 const std::vector<std::size_t> rank_cells;
56 const std::vector<std::size_t> rank_points;
57 const std::size_t num_cells_total;
58 const std::size_t num_points_total;
59 const std::size_t my_cell_offset;
60 const std::size_t my_point_offset;
61
62 IOContext(int _my_rank,
63 int _num_ranks,
64 std::vector<std::size_t> _rank_cells,
65 std::vector<std::size_t> _rank_points)
66 : my_rank{_my_rank}
67 , num_ranks{_num_ranks}
68 , is_parallel{_num_ranks > 1}
69 , rank_cells{std::move(_rank_cells)}
70 , rank_points{std::move(_rank_points)}
71 , num_cells_total{_accumulate(rank_cells)}
72 , num_points_total{_accumulate(rank_points)}
73 , my_cell_offset{_accumulate_rank_offset(rank_cells)}
74 , my_point_offset{_accumulate_rank_offset(rank_points)} {
75 if (my_rank >= num_ranks)
76 throw ValueError(as_error("Given rank is not within communicator size"));
77 if (num_ranks != static_cast<int>(rank_cells.size()))
78 throw ValueError(as_error("Cells vector does not match communicator size"));
79 if (num_ranks != static_cast<int>(rank_points.size()))
80 throw ValueError(as_error("Points vector does not match communicator size"));
81 }
82
83 template<Concepts::Grid Grid, Concepts::Communicator Communicator>
84 static IOContext from(const Grid& grid, const Communicator& comm, int root_rank = 0) {
85 const int size = Parallel::size(comm);
86 const int rank = Parallel::rank(comm);
87 const std::size_t num_points = number_of_points(grid);
88 const std::size_t num_cells = number_of_cells(grid);
89 if (size == 1)
90 return IOContext{rank, size, std::vector{num_cells}, std::vector{num_points}};
91
92 const auto all_num_points = Parallel::gather(comm, num_points, root_rank);
93 const auto all_num_cells = Parallel::gather(comm, num_cells, root_rank);
94 const auto my_all_num_points = Parallel::broadcast(comm, all_num_points, root_rank);
95 const auto my_all_num_cells = Parallel::broadcast(comm, all_num_cells, root_rank);
96 return IOContext{rank, size, my_all_num_cells, my_all_num_points};
97 }
98
99 private:
100 std::size_t _accumulate(const std::vector<std::size_t>& in) const {
101 return std::accumulate(in.begin(), in.end(), std::size_t{0});
102 }
103
104 std::size_t _accumulate_rank_offset(const std::vector<std::size_t>& in) const {
105 if (in.size() <= static_cast<std::size_t>(my_rank))
106 throw ValueError("Rank-vector length must be equal to number of ranks");
107 return std::accumulate(in.begin(), std::next(in.begin(), my_rank), std::size_t{0});
108 }
109};
110
112inline void check_array_name(const std::string& name) {
113 if (name.empty())
114 throw ValueError("VTKHDF array names must not be empty");
115 if (name.find_first_of("/.") != std::string::npos)
116 throw ValueError("VTKHDF array names must not contain '/' or '.' (received '" + name + "')");
117}
118
120inline bool is_string_field(const Field& field) {
121 return field.precision().template is<char>() && field.layout().dimension() == 1;
122}
123
125inline std::vector<std::string> extract_strings(const Field& field) {
126 const auto strings = field.template export_to<MultiString>();
127 const auto slices = strings.slices();
128 return {slices.begin(), slices.end()};
129}
130
131#if GRIDFORMAT_HAVE_HIGH_FIVE
132
137template<typename C>
138class DataSetField : public LazyField<const HDF5::File<C>&> {
139 using ParentType = LazyField<const HDF5::File<C>&>;
140
141 public:
142 using ParentType::ParentType;
143
144 explicit DataSetField(const HDF5::File<C>& file, std::string path)
145 : ParentType{
146 file,
147 MDLayout{file.get_dimensions(path).value()},
148 DynamicPrecision{file.get_precision(path).value()},
149 [_p=std::move(path)] (const HDF5::File<C>& file) {
150 return file.visit_dataset(_p, [&] <typename F> (F&& field) {
151 return field.serialized();
152 });
153 }
154 } {}
155};
156
157template<typename C>
158DataSetField(const HDF5::File<C>&, std::string) -> DataSetField<C>;
159
160template<typename C, typename CB>
161DataSetField(const HDF5::File<C>&, MDLayout, DynamicPrecision, CB&&) -> DataSetField<C>;
162
164template<typename C>
165std::string get_file_type(const HDF5::File<C>& file) {
166 if (!file.exists("/VTKHDF"))
167 throw IOError("Given file is not a VTK-HDF file");
168 if (!file.has_attribute_at("/VTKHDF/Type"))
169 throw IOError("VTKHDF-Type attribute missing");
170 return file.template read_attribute_to<std::string>("/VTKHDF/Type");
171}
172
176template<typename C>
177void check_version_compatibility(const HDF5::File<C>& file, const std::array<std::size_t, 2>& supported) {
178 if (file.has_attribute_at("/VTKHDF/Version"))
179 file.visit_attribute("/VTKHDF/Version", [&] (auto&& field) {
180 const auto version = field.template export_to<std::vector<std::size_t>>();
181 if (version.size() > 0 && version.at(0) > supported[0])
182 throw ValueError(
183 "File version is higher than supported by the reader (" + as_string(supported, ".") + ")"
184 );
185 });
186}
187
190 const auto layout = field->layout();
191 if (layout.dimension() == 0)
192 return make_field_ptr(ReshapedField{field, MDLayout{{std::size_t{1}}}});
193 if (layout.dimension() > 2)
194 return make_field_ptr(ReshapedField{field, MDLayout{{layout.extent(0), layout.number_of_entries(1)}}});
195 return field;
196}
197
199inline void write_field_data(HDF5::File<>& file, const std::string& name, FieldPtr field) {
200 const std::string path = "/VTKHDF/FieldData/" + name;
201 if (is_string_field(*field))
202 file.write_strings(extract_strings(*field), path);
203 else
204 file.write(*as_field_data_tuples(field), path);
205}
206
213inline void write_field_data_step(HDF5::File<>& file, const std::string& name, FieldPtr field) {
214 const std::string path = "/VTKHDF/FieldData/" + name;
215 const auto offset = file.get_dimensions(path).value_or(std::vector<std::size_t>{0}).at(0);
216 std::array<std::size_t, 2> sizes;
217 if (is_string_field(*field)) {
218 const auto strings = extract_strings(*field);
219 sizes = {1, strings.size()};
220 file.write_strings(strings, path);
221 } else {
222 const auto tuples = as_field_data_tuples(field);
223 const auto layout = tuples->layout();
224 sizes = {layout.dimension() > 1 ? layout.extent(1) : 1, layout.extent(0)};
225 file.write(*tuples, path);
226 }
227 file.write(std::array{offset}, "/VTKHDF/Steps/FieldDataOffsets/" + name);
228 file.write(std::vector{sizes}, "/VTKHDF/Steps/FieldDataSizes/" + name);
229}
230
232inline void write_field_data_step_referencing(std::size_t step_index, HDF5::File<>& file, const std::string& name) {
233 const std::string offsets_path = "/VTKHDF/Steps/FieldDataOffsets/" + name;
234 const std::string sizes_path = "/VTKHDF/Steps/FieldDataSizes/" + name;
235 const auto offset = file.read_dataset_to<std::size_t>(
236 offsets_path, HDF5::Slice{.offset = {step_index}, .count = {1}}
237 );
238 const auto sizes = file.read_dataset_to<std::vector<std::size_t>>(
239 sizes_path, HDF5::Slice{.offset = {step_index, 0}, .count = {1, 2}}
240 );
241 file.write(std::array{offset}, offsets_path);
242 file.write(std::vector{std::array{sizes.at(0), sizes.at(1)}}, sizes_path);
243}
244
246template<typename C>
247FieldPtr read_field_data(const HDF5::File<C>& file, const std::string& name, std::optional<std::size_t> step) {
248 const std::string path = "VTKHDF/FieldData/" + name;
249 const auto precision = file.get_precision(path).value();
250 const bool is_string = precision.template is<char>();
251 if (!step)
252 return is_string ? make_field_ptr(RangeField{file.template read_dataset_to<MultiString>(path)})
253 : make_field_ptr(DataSetField{file, path});
254
255 // Without sizes (written by GridFormat <= 0.5), each step is a single row of the dataset
256 const auto offset = file.template read_dataset_to<std::size_t>(
257 "VTKHDF/Steps/FieldDataOffsets/" + name, HDF5::Slice{.offset = {*step}, .count = {1}}
258 );
259 const std::string sizes_path = "VTKHDF/Steps/FieldDataSizes/" + name;
260 const auto sizes = file.has_dataset_at(sizes_path) // (components, tuples)
261 ? std::optional{file.template read_dataset_to<std::vector<std::size_t>>(
262 sizes_path, HDF5::Slice{.offset = {*step, 0}, .count = {1, 2}}
263 )}
264 : std::nullopt;
265 if (is_string) {
266 const std::size_t count = sizes ? sizes->at(0)*sizes->at(1) : 1;
267 return make_field_ptr(RangeField{file.template read_dataset_to<MultiString>(
268 path, HDF5::Slice{.offset = {offset}, .count = {count}}
269 )});
270 }
271
272 const auto dims = file.get_dimensions(path).value();
273 std::vector<std::size_t> slice_offset(dims.size(), 0);
274 slice_offset.at(0) = offset;
275 auto count = dims;
276 count.at(0) = sizes ? sizes->at(1) : 1;
277 if (sizes && MDLayout{count}.number_of_entries() != sizes->at(0)*sizes->at(1))
278 throw SizeError("Field data '" + name + "' does not match its stated number of components");
279 MDLayout layout = sizes ? MDLayout{count} : MDLayout{count | std::views::drop(1)};
281 file,
282 std::move(layout),
283 precision,
284 [p=path, o=std::move(slice_offset), c=std::move(count)] (const HDF5::File<C>& f) {
285 return f.visit_dataset(p, [&] <typename F> (F&& field) {
286 return FlattenedField{make_field_ptr(std::move(field))}.serialized();
287 }, HDF5::Slice{.offset = o, .count = c});
288 }
289 });
290}
291
297template<Concepts::Communicator C, std::invocable<HDF5::File<>&> Write>
298void write_on_root(const std::string& filename, const C& comm, int root, Write&& write) {
299 Parallel::barrier(comm);
300 // the collective invocation also keeps the other ranks from reopening the file before the root is done
301 Parallel::invoke_collectively(comm, [&] () {
302 if (Parallel::rank(comm) == root) {
303 HDF5::File file{filename, HDF5::File<>::append};
304 write(file);
305 }
306 }, "Writing to '" + filename + "'");
307}
308
309#endif // GRIDFORMAT_HAVE_HIGH_FIVE
310
311} // namespace VTKHDF
312} // namespace GridFormat
313
314#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:138
Abstract interface for fields of values that is used by writers/readers to store fields.
Grid concepts.
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:213
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:232
void check_version_compatibility(const HDF5::File< C > &file, const std::array< std::size_t, 2 > &supported)
Definition hdf_common.hpp:177
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:199
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:112
std::vector< std::string > extract_strings(const Field &field)
Return the strings stored in the given character field (see MultiString).
Definition hdf_common.hpp:125
std::string get_file_type(const HDF5::File< C > &file)
Read the vtk-hdf file type from an hdf5 file.
Definition hdf_common.hpp:165
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:298
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:247
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:120
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:189
Options for transient vtk-hdf file formats.
Definition hdf_common.hpp:40
bool static_grid
Set to true the grid is the same for all time steps (will only be written once).
Definition hdf_common.hpp:41
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:42