8#ifndef GRIDFORMAT_VTK_HDF_IMAGE_GRID_WRITER_HPP_
9#define GRIDFORMAT_VTK_HDF_IMAGE_GRID_WRITER_HPP_
10#if GRIDFORMAT_HAVE_HIGH_FIVE
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>
27#include <gridformat/parallel/communication.hpp>
28#include <gridformat/parallel/concepts.hpp>
39template<
bool is_transient, Concepts::ImageGr
id Gr
id, Concepts::Communicator Communicator = NullCommunicator>
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};
46 using CT = CoordinateType<Grid>;
48 using HDF5File = HDF5::File<Communicator>;
51 const std::array<CT, dim> origin;
52 const std::array<CT, dim> spacing;
53 const std::array<std::size_t, dim> extents;
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))}
65 .append_null_terminator_to_strings =
true
70 requires(std::is_same_v<Communicator, NullCommunicator>)
75 requires(std::is_copy_constructible_v<Communicator>)
81 std::string filename_without_extension,
84 .static_meta_data =
false
86 requires(is_transient && std::is_same_v<Communicator, NullCommunicator>)
91 const Communicator& comm,
92 std::string filename_without_extension,
95 .static_meta_data =
false
97 requires(is_transient && std::is_copy_constructible_v<Communicator>)
100 , _timeseries_filename{std::move(filename_without_extension) +
".hdf"}
101 , _transient_opts{std::move(opts)} {
103 throw ValueError(
"Transient VTK-HDF ImageData files do not support evolving grids");
106 const Communicator& communicator()
const {
111 void _write(std::ostream&)
const {
112 throw InvalidState(
"VTKHDFImageGridWriter does not support export into stream");
115 std::string _write([[maybe_unused]]
double t) {
116 if constexpr (!is_transient)
117 throw InvalidState(
"This overload only works for transient output");
119 if (this->_step_count == 0)
120 HDF5File::clear(_timeseries_filename, _comm);
123 HDF5File file{_timeseries_filename, _comm, HDF5File::Mode::append};
125 file.write_attribute(this->_step_count+1,
"/VTKHDF/Steps/NSteps");
126 file.write(std::array{t},
"/VTKHDF/Steps/Values");
128 _write_meta_data(_timeseries_filename);
129 return _timeseries_filename;
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");
136 HDF5File file{filename_with_ext, _comm, HDF5File::Mode::overwrite};
139 _write_meta_data(filename_with_ext);
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);
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) {
156 VTKHDF::write_field_data_step_referencing(0, file, name);
158 VTKHDF::write_field_data_step(file, name, this->_get_meta_data_field_ptr(name));
160 VTKHDF::write_field_data(file, name, this->_get_meta_data_field_ptr(name));
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,
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;
184 const auto point_slice_base = _make_slice<true>(
185 overall_specs.extents,
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");
197 std::vector<std::size_t> non_zero_extents;
199 my_specs.extents | std::views::filter([] (
auto e) { return e != 0; }),
200 std::back_inserter(non_zero_extents)
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
210 _write_field(file, field_ptr,
"/VTKHDF/PointData/" + name, point_slice_base);
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
220 _write_field(file, field_ptr,
"/VTKHDF/CellData/" + name, cell_slice_base);
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();
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),
241 const auto layout = f->layout();
242 if (layout.dimension() <= 2)
245 auto nl = MDLayout{{layout.extent(0), layout.number_of_entries(1)}};
246 return transform(f, FieldTransformation::reshape_to(std::move(nl)));
249 template<std::ranges::range E>
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)}));
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(
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},
281 return std::make_tuple(piece_specs, std::vector<OffsetType>(dim, 0));
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 {
290 result.total_size.emplace();
293 std::vector<bool> is_nonzero;
295 total_extents | std::views::transform([] (
const auto& v) {
return v != 0; }),
296 std::back_inserter(is_nonzero)
298 std::ranges::for_each(total_extents, [&, i=0] (
const auto& value)
mutable {
300 result.total_size.value().push_back(value + (increment ? 1 : 0));
302 std::ranges::for_each(extents, [&, i=0] (
const auto& value)
mutable {
304 result.count.push_back(value);
306 std::ranges::for_each(offsets, [&, i=0] (
const auto& value)
mutable {
308 result.offset.push_back(value);
312 std::ranges::reverse(result.total_size.value());
313 std::ranges::reverse(result.count);
314 std::ranges::reverse(result.offset);
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);
333 void _write_field(HDF5File& file,
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)));
345 const auto layout = field->layout();
346 const bool is_vector_field = layout.dimension() > size.size() - dimension_offset;
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));
357 if constexpr (is_transient) {
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)
368 file.write(*field, path, HDF5::Slice{
369 .offset = std::move(offset),
370 .count = std::move(count),
371 .total_size = std::move(size)
377 std::string _timeseries_filename =
"";
385template<Concepts::ImageGr
id G, Concepts::Communicator C = NullCommunicator>
389 using ParentType::ParentType;
396template<Concepts::ImageGr
id G, Concepts::Communicator C = NullCommunicator>
400 using ParentType::ParentType;
403template<Concepts::ImageGr
id G>
405template<Concepts::ImageGr
id G, Concepts::Communicator C>
408template<Concepts::ImageGr
id G>
410template<Concepts::ImageGr
id G, Concepts::Communicator C>
411VTKHDFImageGridTimeSeriesWriter(
const G&,
const C&, std::string, VTK::HDFTransientOptions = {}) -> VTKHDFImageGridTimeSeriesWriter<G, C>;
416template<
typename... Args>
419template<
typename... Args>
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.
Helper class to store processor offsets in parallel writes.
Definition: hdf_common.hpp:48
Common functionality for VTK writers.