GridFormat 0.5.0
I/O-Library for grid-like data structures
Loading...
Searching...
No Matches
hdf_image_grid_writer.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
8#ifndef GRIDFORMAT_VTK_HDF_IMAGE_GRID_WRITER_HPP_
9#define GRIDFORMAT_VTK_HDF_IMAGE_GRID_WRITER_HPP_
10#if GRIDFORMAT_HAVE_HIGH_FIVE
11
12#include <ranges>
13#include <iterator>
14#include <algorithm>
15#include <utility>
16#include <tuple>
17
18#include <gridformat/common/exceptions.hpp>
19#include <gridformat/common/md_layout.hpp>
20#include <gridformat/common/concepts.hpp>
21#include <gridformat/common/matrix.hpp>
22#include <gridformat/common/ranges.hpp>
23#include <gridformat/common/type_traits.hpp>
24#include <gridformat/common/lvalue_reference.hpp>
25#include <gridformat/common/field_transformations.hpp>
26
27#include <gridformat/parallel/communication.hpp>
28#include <gridformat/parallel/concepts.hpp>
29
32
36
37namespace GridFormat {
38
39template<bool is_transient, Concepts::ImageGrid Grid, Concepts::Communicator Communicator = NullCommunicator>
40class VTKHDFImageGridWriterImpl : public GridDetail::WriterBase<is_transient, Grid>::type {
41 static constexpr int root_rank = 0;
42 static constexpr std::size_t dim = dimension<Grid>;
43 static constexpr std::size_t vtk_space_dim = 3;
44 static constexpr std::array<std::size_t, 2> version{1, 0};
45
46 using CT = CoordinateType<Grid>;
48 using HDF5File = HDF5::File<Communicator>;
49
50 struct ImageSpecs {
51 const std::array<CT, dim> origin;
52 const std::array<CT, dim> spacing;
53 const std::array<std::size_t, dim> extents;
54
55 template<typename O, typename S, typename E>
56 ImageSpecs(O&& origin, S&& spacing, E&& extents)
57 : origin{Ranges::to_array<dim, CT>(std::forward<O>(origin))}
58 , spacing{Ranges::to_array<dim, CT>(std::forward<S>(spacing))}
59 , extents{Ranges::to_array<dim, std::size_t>(std::forward<E>(extents))}
60 {}
61 };
62
63 static constexpr WriterOptions writer_opts{
65 .append_null_terminator_to_strings = true
66 };
67
68 public:
69 explicit VTKHDFImageGridWriterImpl(LValueReferenceOf<const Grid> grid)
70 requires(std::is_same_v<Communicator, NullCommunicator>)
71 : GridWriter<Grid>(grid.get(), ".hdf", writer_opts)
72 {}
73
74 explicit VTKHDFImageGridWriterImpl(LValueReferenceOf<const Grid> grid, const Communicator& comm)
75 requires(std::is_copy_constructible_v<Communicator>)
76 : GridWriter<Grid>(grid.get(), ".hdf", writer_opts)
77 , _comm{comm}
78 {}
79
80 explicit VTKHDFImageGridWriterImpl(LValueReferenceOf<const Grid> grid,
81 std::string filename_without_extension,
83 .static_grid = true,
84 .static_meta_data = false
85 })
86 requires(is_transient && std::is_same_v<Communicator, NullCommunicator>)
87 : VTKHDFImageGridWriterImpl(grid.get(), NullCommunicator{}, filename_without_extension, std::move(opts))
88 {}
89
90 explicit VTKHDFImageGridWriterImpl(LValueReferenceOf<const Grid> grid,
91 const Communicator& comm,
92 std::string filename_without_extension,
94 .static_grid = true,
95 .static_meta_data = false
96 })
97 requires(is_transient && std::is_copy_constructible_v<Communicator>)
98 : TimeSeriesGridWriter<Grid>(grid.get(), writer_opts)
99 , _comm{comm}
100 , _timeseries_filename{std::move(filename_without_extension) + ".hdf"}
101 , _transient_opts{std::move(opts)} {
102 if (!_transient_opts.static_grid)
103 throw ValueError("Transient VTK-HDF ImageData files do not support evolving grids");
104 }
105
106 const Communicator& communicator() const {
107 return _comm;
108 }
109
110 private:
111 void _write(std::ostream&) const {
112 throw InvalidState("VTKHDFImageGridWriter does not support export into stream");
113 }
114
115 std::string _write([[maybe_unused]] double t) {
116 if constexpr (!is_transient)
117 throw InvalidState("This overload only works for transient output");
118
119 if (this->_step_count == 0)
120 HDF5File::clear(_timeseries_filename, _comm);
121
122 { // scope to close the file before writing meta data on rank 0 with serial I/O
123 HDF5File file{_timeseries_filename, _comm, HDF5File::Mode::append};
124 _write_to(file);
125 file.write_attribute(this->_step_count+1, "/VTKHDF/Steps/NSteps");
126 file.write(std::array{t}, "/VTKHDF/Steps/Values");
127 }
128 _write_meta_data(_timeseries_filename);
129 return _timeseries_filename;
130 }
131
132 void _write(const std::string& filename_with_ext) const {
133 if constexpr (is_transient)
134 throw InvalidState("This overload only works for non-transient output");
135 { // scope to close the file before writing meta data on rank 0 with serial I/O
136 HDF5File file{filename_with_ext, _comm, HDF5File::Mode::overwrite};
137 _write_to(file);
138 }
139 _write_meta_data(filename_with_ext);
140 }
141
142 // hdf5 cannot write variable-length strings into files opened for parallel I/O, not even from
143 // a single rank. Therefore, all meta data is written by rank 0 once the (parallel) file is closed,
144 // reopened with the default driver. Meta data is assumed to be the same on all ranks.
145 void _write_meta_data(const std::string& filename) const {
146 VTKHDF::write_on_root(filename, _comm, root_rank, [&] (HDF5::File<>& file) {
147 _write_meta_data(file);
148 });
149 }
150
151 void _write_meta_data(HDF5::File<>& file) const {
152 std::ranges::for_each(this->_meta_data_field_names(), [&] (const std::string& name) {
153 VTKHDF::check_array_name(name);
154 if constexpr (is_transient) {
155 if (this->_step_count > 0 && _transient_opts.static_meta_data)
156 VTKHDF::write_field_data_step_referencing(0, file, name);
157 else
158 VTKHDF::write_field_data_step(file, name, this->_get_meta_data_field_ptr(name));
159 } else {
160 VTKHDF::write_field_data(file, name, this->_get_meta_data_field_ptr(name));
161 }
162 });
163 }
164
165 void _write_to(HDF5File& file) const {
166 const ImageSpecs my_specs{origin(this->grid()), spacing(this->grid()), extents(this->grid())};
167 const auto [overall_specs, my_offset] = _get_image_specs(my_specs);
168 const auto cell_slice_base = _make_slice(
169 overall_specs.extents,
170 my_specs.extents,
171 my_offset
172 );
173
174 // in order to avoid overlapping hyperslabs for point data in parallel I/O,
175 // we only write the last entries of the slab (per direction) when our portion
176 // of the image is the last portion of the overall image in that direction.
177 const auto my_point_extents = Ranges::to_array<dim>(
178 std::views::iota(std::size_t{0}, dim)
179 | std::views::transform([&] (std::size_t dir) {
180 const auto overall_end = overall_specs.extents[dir];
181 const auto my_end = my_specs.extents[dir] + my_offset[dir];
182 return my_end < overall_end ? my_specs.extents[dir] : my_specs.extents[dir] + 1;
183 }));
184 const auto point_slice_base = _make_slice<true>(
185 overall_specs.extents,
186 my_point_extents,
187 my_offset
188 );
189
190 file.write_attribute(std::array<std::size_t, 2>{(is_transient ? 2 : 1), 0}, "/VTKHDF/Version");
191 file.write_attribute(Ranges::to_array<vtk_space_dim>(overall_specs.origin), "/VTKHDF/Origin");
192 file.write_attribute(Ranges::to_array<vtk_space_dim>(overall_specs.spacing), "/VTKHDF/Spacing");
193 file.write_attribute(VTK::CommonDetail::get_extents(overall_specs.extents), "/VTKHDF/WholeExtent");
194 file.write_attribute(_get_direction(), "/VTKHDF/Direction");
195 file.write_attribute("ImageData", "/VTKHDF/Type");
196
197 std::vector<std::size_t> non_zero_extents;
198 std::ranges::copy(
199 my_specs.extents | std::views::filter([] (auto e) { return e != 0; }),
200 std::back_inserter(non_zero_extents)
201 );
202
203 std::ranges::for_each(this->_point_field_names(), [&] (const std::string& name) {
204 VTKHDF::check_array_name(name);
205 auto field_ptr = _reshape(
206 VTK::make_vtk_field(this->_get_point_field_ptr(name)),
207 Ranges::incremented(non_zero_extents, 1) | std::views::reverse,
208 point_slice_base.count
209 );
210 _write_field(file, field_ptr, "/VTKHDF/PointData/" + name, point_slice_base);
211 });
212
213 std::ranges::for_each(this->_cell_field_names(), [&] (const std::string& name) {
214 VTKHDF::check_array_name(name);
215 auto field_ptr = _reshape(
216 VTK::make_vtk_field(this->_get_cell_field_ptr(name)),
217 non_zero_extents | std::views::reverse,
218 cell_slice_base.count
219 );
220 _write_field(file, field_ptr, "/VTKHDF/CellData/" + name, cell_slice_base);
221 });
222 }
223
224 template<std::ranges::range E, std::ranges::range S>
225 FieldPtr _reshape(FieldPtr f, const E& row_major_extents, S&& _slice_end) const {
226 auto flat = _flatten(f);
227 auto structured = _make_structured(flat, row_major_extents);
228 const auto structured_layout = structured->layout();
229
230 std::vector<std::size_t> slice_end;
231 std::ranges::copy(_slice_end, std::back_inserter(slice_end));
232 for (std::size_t i = slice_end.size(); i < structured_layout.dimension(); ++i)
233 slice_end.push_back(structured_layout.extent(i));
234 return transform(structured, FieldTransformation::take_slice({
235 .from = std::vector<std::size_t>(slice_end.size(), 0),
236 .to = slice_end
237 }));
238 }
239
240 FieldPtr _flatten(FieldPtr f) const {
241 const auto layout = f->layout();
242 if (layout.dimension() <= 2)
243 return f;
244 // vtk requires tensors to be made flat
245 auto nl = MDLayout{{layout.extent(0), layout.number_of_entries(1)}};
246 return transform(f, FieldTransformation::reshape_to(std::move(nl)));
247 }
248
249 template<std::ranges::range E>
250 FieldPtr _make_structured(FieldPtr f, const E& row_major_extents) const {
251 const auto layout = f->layout();
252 std::vector<std::size_t> target_layout(Ranges::size(row_major_extents));
253 std::ranges::copy(row_major_extents, target_layout.begin());
254 if (layout.dimension() > 1)
255 target_layout.push_back(layout.extent(1));
256 return transform(f, FieldTransformation::reshape_to(MDLayout{std::move(target_layout)}));
257 }
258
259 auto _get_image_specs(const ImageSpecs& piece_specs) const {
260 using OffsetType = std::ranges::range_value_t<decltype(piece_specs.extents)>;
261 if (Parallel::size(_comm) > 1) {
263 const auto all_origins = Parallel::gather(_comm, piece_specs.origin, root_rank);
264 const auto all_extents = Parallel::gather(_comm, piece_specs.extents, root_rank);
265 const auto is_negative_axis = VTK::CommonDetail::structured_grid_axis_orientation(piece_specs.spacing);
266 const auto [exts_begin, exts_end, whole_extent, origin] = helper.compute_extents_and_origin(
267 all_origins,
268 all_extents,
269 is_negative_axis,
270 basis(this->grid())
271 );
272
273 const auto my_whole_extent = Parallel::broadcast(_comm, whole_extent, root_rank);
274 const auto my_whole_origin = Parallel::broadcast(_comm, origin, root_rank);
275 const auto my_extent_offset = Parallel::scatter(_comm, Ranges::flat(exts_begin), root_rank);
276 return std::make_tuple(
277 ImageSpecs{my_whole_origin, piece_specs.spacing, my_whole_extent},
278 my_extent_offset
279 );
280 } else {
281 return std::make_tuple(piece_specs, std::vector<OffsetType>(dim, 0));
282 }
283 }
284
285 template<bool increment = false, typename TotalExtents, typename Extents, typename Offsets>
286 HDF5::Slice _make_slice(const TotalExtents& total_extents,
287 const Extents& extents,
288 const Offsets& offsets) const {
289 HDF5::Slice result;
290 result.total_size.emplace();
291
292 // use only those dimensions that are not zero
293 std::vector<bool> is_nonzero;
294 std::ranges::copy(
295 total_extents | std::views::transform([] (const auto& v) { return v != 0; }),
296 std::back_inserter(is_nonzero)
297 );
298 std::ranges::for_each(total_extents, [&, i=0] (const auto& value) mutable {
299 if (is_nonzero[i++])
300 result.total_size.value().push_back(value + (increment ? 1 : 0));
301 });
302 std::ranges::for_each(extents, [&, i=0] (const auto& value) mutable {
303 if (is_nonzero[i++])
304 result.count.push_back(value);
305 });
306 std::ranges::for_each(offsets, [&, i=0] (const auto& value) mutable {
307 if (is_nonzero[i++])
308 result.offset.push_back(value);
309 });
310
311 // slices in VTK are accessed with the last coordinate first (i.e. values[z][y][x])
312 std::ranges::reverse(result.total_size.value());
313 std::ranges::reverse(result.count);
314 std::ranges::reverse(result.offset);
315
316 return result;
317 }
318
319 auto _get_direction() const {
320 using T = MDRangeScalar<decltype(basis(this->grid()))>;
321 std::array<T, vtk_space_dim*vtk_space_dim> coefficients;
322 std::ranges::fill(coefficients, T{0});
323 std::ranges::for_each(
324 Matrix{basis(this->grid())}.transposed(),
325 [it = coefficients.begin()] (const std::ranges::range auto& row) mutable {
326 std::ranges::copy(row, it);
327 std::advance(it, vtk_space_dim);
328 }
329 );
330 return coefficients;
331 }
332
333 void _write_field(HDF5File& file,
334 FieldPtr field,
335 const std::string& path,
336 const HDF5::Slice& slice) const {
337 const std::size_t dimension_offset = is_transient ? 1 : 0;
338 std::vector<std::size_t> size(slice.total_size.value().size() + dimension_offset);
339 std::vector<std::size_t> count(slice.count.size() + dimension_offset);
340 std::vector<std::size_t> offset(slice.offset.size() + dimension_offset);
341 std::ranges::copy(slice.total_size.value(), std::ranges::begin(size | std::views::drop(dimension_offset)));
342 std::ranges::copy(slice.count, std::ranges::begin(count | std::views::drop(dimension_offset)));
343 std::ranges::copy(slice.offset, std::ranges::begin(offset | std::views::drop(dimension_offset)));
344
345 const auto layout = field->layout();
346 const bool is_vector_field = layout.dimension() > size.size() - dimension_offset;
347 if (is_vector_field)
348 std::ranges::for_each(
349 std::views::iota(size.size() - dimension_offset, layout.dimension()),
350 [&] (const std::size_t codim) {
351 size.push_back(layout.extent(codim));
352 count.push_back(layout.extent(codim));
353 offset.push_back(0);
354 }
355 );
356
357 if constexpr (is_transient) {
358 size.at(0) = 1;
359 count.at(0) = 1;
360 offset.at(0) = 0;
361 FieldPtr sub_field = transform(field, FieldTransformation::as_sub_field);
362 file.write(*sub_field, path, HDF5::Slice{
363 .offset = std::move(offset),
364 .count = std::move(count),
365 .total_size = std::move(size)
366 });
367 } else {
368 file.write(*field, path, HDF5::Slice{
369 .offset = std::move(offset),
370 .count = std::move(count),
371 .total_size = std::move(size)
372 });
373 }
374 }
375
376 Communicator _comm;
377 std::string _timeseries_filename = "";
378 VTK::HDFTransientOptions _transient_opts;
379};
380
385template<Concepts::ImageGrid G, Concepts::Communicator C = NullCommunicator>
388 public:
389 using ParentType::ParentType;
390};
391
396template<Concepts::ImageGrid G, Concepts::Communicator C = NullCommunicator>
399 public:
400 using ParentType::ParentType;
401};
402
403template<Concepts::ImageGrid G>
405template<Concepts::ImageGrid G, Concepts::Communicator C>
407
408template<Concepts::ImageGrid G>
409VTKHDFImageGridTimeSeriesWriter(const G&, std::string, VTK::HDFTransientOptions = {}) -> VTKHDFImageGridTimeSeriesWriter<G, NullCommunicator>;
410template<Concepts::ImageGrid G, Concepts::Communicator C>
411VTKHDFImageGridTimeSeriesWriter(const G&, const C&, std::string, VTK::HDFTransientOptions = {}) -> VTKHDFImageGridTimeSeriesWriter<G, C>;
412
413
414namespace Traits {
415
416template<typename... Args>
417struct WritesConnectivity<VTKHDFImageGridWriter<Args...>> : public std::false_type {};
418
419template<typename... Args>
420struct WritesConnectivity<VTKHDFImageGridTimeSeriesWriter<Args...>> : public std::false_type {};
421
422} // namespace Traits
423} // namespace GridFormat
424
425#endif // GRIDFORMAT_HAVE_HIGH_FIVE
426#endif // GRIDFORMAT_VTK_HDF_IMAGE_GRID_WRITER_HPP_
Abstract base class for grid file writers.
Definition: writer.hpp:306
Abstract base class for time series file writers.
Definition: writer.hpp:347
Writer for the transient VTK HDF file format for image grids.
Definition: hdf_image_grid_writer.hpp:397
Definition: hdf_image_grid_writer.hpp:40
Writer for the VTK HDF file format for image grids.
Definition: hdf_image_grid_writer.hpp:386
Grid concepts.
Base classes for grid data writers.
std::shared_ptr< const Field > FieldPtr
Pointer type used by writers/readers for fields.
Definition: field.hpp:205
Common functionality for writing VTK HDF files.
Helper function for writing parallel VTK files.
Can be specialized by writers in case the file format does not contain connectivity information.
Definition: writer.hpp:36
Helper class to store processor offsets in parallel writes.
Definition: hdf_common.hpp:48
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
Options that writer implementations can pass to the base class.
Definition: writer.hpp:59
bool use_structured_grid_ordering
Use row-major structured grid ordering.
Definition: writer.hpp:60
Common functionality for VTK writers.