34class VTKHDFUnstructuredGridWriterImpl :
public GridDetail::WriterBase<is_transient, G>::type {
35 static constexpr int root_rank = 0;
36 static constexpr std::size_t vtk_space_dim = 3;
38 using CT = CoordinateType<G>;
39 template<
typename T>
using Vector = std::array<T, vtk_space_dim>;
40 template<
typename T>
using Tensor = std::array<T, vtk_space_dim*vtk_space_dim>;
43 using HDF5File = HDF5::File<Communicator>;
46 .use_structured_grid_ordering =
false,
47 .append_null_terminator_to_strings =
true
50 struct TimeSeriesOffsets {
51 std::size_t cell_offset;
52 std::size_t connectivity_offset;
53 std::size_t point_offset;
59 explicit VTKHDFUnstructuredGridWriterImpl(LValueReferenceOf<const Grid> grid)
60 requires(!is_transient && std::is_same_v<Communicator, NullCommunicator>)
64 explicit VTKHDFUnstructuredGridWriterImpl(LValueReferenceOf<const Grid> grid,
const Communicator& comm)
65 requires(!is_transient && std::is_copy_constructible_v<Communicator>)
70 explicit VTKHDFUnstructuredGridWriterImpl(LValueReferenceOf<const Grid> grid,
71 std::string filename_without_extension,
73 requires(is_transient && std::is_same_v<Communicator, NullCommunicator>)
76 , _timeseries_filename{std::move(filename_without_extension) +
".hdf"}
77 , _transient_opts{std::move(opts)}
80 explicit VTKHDFUnstructuredGridWriterImpl(LValueReferenceOf<const Grid> grid,
81 const Communicator& comm,
82 std::string filename_without_extension,
84 requires(is_transient && std::is_copy_constructible_v<Communicator>)
87 , _timeseries_filename{std::move(filename_without_extension) +
".hdf"}
88 , _transient_opts{std::move(opts)}
91 const Communicator& communicator()
const {
96 void _write(std::ostream&)
const {
97 throw InvalidState(
"VTKHDFUnstructuredGridWriter does not support export into stream");
100 void _write(
const std::string& filename_with_ext)
const {
101 if constexpr (is_transient)
102 throw InvalidState(
"This overload only works for non-transient output");
104 HDF5File file{filename_with_ext, _comm, HDF5File::overwrite};
107 _write_meta_data(filename_with_ext);
110 std::string _write(
double t) {
111 if constexpr (!is_transient)
112 throw InvalidState(
"This overload only works for transient output");
114 if (this->_step_count == 0)
115 HDF5File::clear(_timeseries_filename, _comm);
118 HDF5File file{_timeseries_filename, _comm, HDF5File::append};
119 const auto offsets = _write_to(file);
121 file.write_attribute(this->_step_count+1,
"/VTKHDF/Steps/NSteps");
122 file.write(std::array{t},
"VTKHDF/Steps/Values");
123 file.write(std::vector{offsets.point_offset},
"VTKHDF/Steps/PointOffsets");
124 file.write(std::vector{std::array{offsets.cell_offset}},
"/VTKHDF/Steps/CellOffsets");
125 file.write(std::vector{std::array{offsets.connectivity_offset}},
"VTKHDF/Steps/ConnectivityIdOffsets");
127 file.write(std::vector{Parallel::size(_comm)},
"/VTKHDF/Steps/NumberOfParts");
128 if (this->_step_count > 0 && _transient_opts.static_grid) {
130 std::vector{_get_last_step_data(file,
"PartOffsets")},
131 "/VTKHDF/Steps/PartOffsets"
134 const std::size_t offset = this->_step_count == 0
136 : _get_last_step_data(file,
"PartOffsets") + Parallel::size(_comm);
137 file.write(std::vector{offset},
"/VTKHDF/Steps/PartOffsets");
140 _write_meta_data(_timeseries_filename);
141 return _timeseries_filename;
144 TimeSeriesOffsets _write_to(HDF5File& file)
const {
145 file.write_attribute(std::array<std::size_t, 2>{(is_transient ? 2 : 1), 0},
"/VTKHDF/Version");
146 file.write_attribute(
"UnstructuredGrid",
"/VTKHDF/Type");
148 TimeSeriesOffsets offsets;
149 const auto context = IOContext::from(this->grid(), _comm, root_rank);
150 _write_num_cells_and_points(file, context);
151 offsets.point_offset = _write_coordinates(file, context);
152 offsets.connectivity_offset = _write_connectivity(file, context);
153 offsets.cell_offset = _write_types(file, context);
154 _write_offsets(file, context);
155 _write_point_fields(file, context);
156 _write_cell_fields(file, context);
161 void _write_num_cells_and_points(HDF5File& file,
const IOContext& context)
const {
162 _write_values(file,
"/VTKHDF/NumberOfPoints", std::vector{number_of_points(this->grid())}, context);
163 _write_values(file,
"/VTKHDF/NumberOfCells", std::vector{number_of_cells(this->grid())}, context);
166 std::size_t _write_coordinates(HDF5File& file,
const IOContext& context)
const {
167 if constexpr (is_transient) {
168 if (this->_step_count > 0 && _transient_opts.static_grid)
169 return _get_last_step_data(file,
"PointOffsets");
171 const auto coords_field = VTK::make_coordinates_field<CT>(this->grid(),
false);
172 const auto offset = _get_current_offset(file,
"/VTKHDF/Points");
173 _write_point_field(file,
"/VTKHDF/Points", *coords_field, context);
177 std::size_t _write_connectivity(HDF5File& file,
const IOContext& context)
const {
178 if constexpr (is_transient) {
179 if (this->_step_count > 0 && _transient_opts.static_grid)
180 return _get_last_step_data(file,
"ConnectivityIdOffsets");
182 const auto point_id_map = make_point_id_map(this->grid());
183 const auto connectivity_field = VTK::make_connectivity_field(this->grid(), point_id_map);
184 const auto num_entries = connectivity_field->layout().number_of_entries();
185 const auto my_num_ids = std::vector{
static_cast<long>(num_entries)};
186 std::vector<long> connectivity(num_entries);
187 connectivity_field->export_to(connectivity);
188 const auto offset = _get_current_offset(file,
"/VTKHDF/Connectivity");
189 _write_values(file,
"/VTKHDF/Connectivity", connectivity, context);
190 _write_values(file,
"/VTKHDF/NumberOfConnectivityIds", my_num_ids, context);
194 std::size_t _write_types(HDF5File& file,
const IOContext& context)
const {
195 if constexpr (is_transient) {
196 if (this->_step_count > 0 && _transient_opts.static_grid)
197 return _get_last_step_data(file,
"CellOffsets");
199 const auto types_field = VTK::make_cell_types_field(this->grid());
200 std::vector<std::uint8_t> types(types_field->layout().number_of_entries());
201 types_field->export_to(types);
202 const auto offset = _get_current_offset(file,
"VTKHDF/Types");
203 _write_values(file,
"VTKHDF/Types", types, context);
207 std::size_t _write_offsets(HDF5File& file,
const IOContext& context)
const {
208 if constexpr (is_transient) {
209 if (this->_step_count > 0 && _transient_opts.static_grid)
210 return _get_last_step_data(file,
"CellOffsets");
212 const auto offsets_field = VTK::make_offsets_field(this->grid());
213 const auto num_offset_entries = offsets_field->layout().number_of_entries() + 1;
214 std::vector<long> offsets(num_offset_entries);
215 offsets_field->export_to(std::ranges::subrange(std::next(offsets.begin()), offsets.end()));
216 offsets[0] =
long{0};
217 const auto offset = _get_current_offset(file,
"/VTKHDF/Offsets");
218 _write_values(file,
"/VTKHDF/Offsets", offsets, context);
225 void _write_meta_data(
const std::string& filename)
const {
227 _write_meta_data(file);
231 void _write_meta_data(HDF5::File<>& file)
const {
232 std::ranges::for_each(this->_meta_data_field_names(), [&] (
const std::string& name) {
234 if constexpr (is_transient) {
235 if (this->_step_count > 0 && _transient_opts.static_meta_data)
245 void _write_point_fields(HDF5File& file,
const IOContext& context)
const {
246 std::ranges::for_each(this->_point_field_names(), [&] (
const std::string& name) {
248 if constexpr (is_transient)
251 _get_current_offset(file,
"/VTKHDF/PointData/" + name),
252 "/VTKHDF/Steps/PointDataOffsets/" + name
254 auto reshaped = _reshape(VTK::make_vtk_field(this->_get_point_field_ptr(name)));
255 _write_point_field(file,
"/VTKHDF/PointData/" + name, *reshaped, context);
259 void _write_cell_fields(HDF5File& file,
const IOContext& context)
const {
260 std::ranges::for_each(this->_cell_field_names(), [&] (
const std::string& name) {
262 if constexpr (is_transient)
265 _get_current_offset(file,
"VTKHDF/CellData/" + name),
266 "/VTKHDF/Steps/CellDataOffsets/" + name
268 auto reshaped = _reshape(VTK::make_vtk_field(this->_get_cell_field_ptr(name)));
269 _write_cell_field(file,
"/VTKHDF/CellData/" + name, *reshaped, context);
275 const auto layout = field_ptr->layout();
276 if (layout.dimension() > 2)
279 MDLayout{{layout.extent(0), layout.number_of_entries(1)}}
284 template<Concepts::Scalar T>
285 void _write_values(HDF5File& file,
286 const std::string& path,
287 const std::vector<T>& values,
288 const IOContext& context)
const {
289 if (context.is_parallel) {
290 const auto num_values = values.size();
291 const auto total_num_values = Parallel::sum(_comm, num_values, root_rank);
292 const auto my_total_num_values = Parallel::broadcast(_comm, total_num_values, root_rank);
293 const auto my_offset = _accumulate_rank_offset(num_values);
295 .offset = std::vector{my_offset},
296 .count = std::vector{num_values},
297 .total_size = std::vector{my_total_num_values}
299 file.write(values, path, slice);
301 file.write(values, path);
305 void _write_point_field(HDF5File& file,
306 const std::string& path,
308 const IOContext& context)
const {
309 _write_field(file, path, field, context.is_parallel, context.my_point_offset, context.num_points_total);
312 void _write_cell_field(HDF5File& file,
313 const std::string& path,
315 const IOContext& context)
const {
316 _write_field(file, path, field, context.is_parallel, context.my_cell_offset, context.num_cells_total);
319 void _write_field(HDF5File& file,
320 const std::string& path,
323 std::size_t main_offset,
324 std::size_t main_size)
const {
326 const auto layout = field.
layout();
327 std::vector<std::size_t> count(layout.dimension());
328 layout.export_to(count);
330 std::vector<std::size_t> size = count;
331 std::vector<std::size_t> offset(layout.dimension(), 0);
332 offset.at(0) = main_offset;
333 size.at(0) = main_size;
335 file.write(field, path, HDF5::Slice{
336 .offset = std::move(offset),
337 .count = std::move(count),
338 .total_size = std::move(size)
341 file.write(field, path);
345 std::size_t _accumulate_rank_offset(std::size_t my_size)
const {
346 auto all_offsets = Parallel::gather(_comm, my_size, root_rank);
347 if (Parallel::rank(_comm) == root_rank) {
348 std::partial_sum(all_offsets.begin(), std::prev(all_offsets.end()), all_offsets.begin());
349 std::copy(std::next(all_offsets.rbegin()), all_offsets.rend(), all_offsets.rbegin());
352 const auto my_offset = Parallel::scatter(_comm, all_offsets, root_rank);
353 if (my_offset.size() != 1)
354 throw ValueError(
"Unexpected scatter result");
358 void _write_step_offset(HDF5File& file,
359 const std::size_t offset,
360 const std::string& path)
const {
361 file.write(std::array{offset}, path);
364 std::size_t _get_current_offset(HDF5File& file,
const std::string& path)
const {
365 const auto cur_dimensions = file.get_dimensions(path);
366 return cur_dimensions ? (*cur_dimensions)[0] : 0;
369 std::size_t _get_last_step_data(
const HDF5File& file,
const std::string& sub_path)
const {
370 if (this->_step_count == 0)
371 throw ValueError(
"Last step data can only be read after at least one write");
372 const std::string path =
"VTKHDF/Steps/" + sub_path;
374 auto step_dimensions = file.get_dimensions(path).value();
375 std::ranges::fill(step_dimensions, std::size_t{1});
377 auto access_offset = step_dimensions;
378 std::ranges::fill(access_offset, std::size_t{0});
379 access_offset.at(0) = this->_step_count - 1;
381 return file.template read_dataset_to<std::size_t>(path, HDF5::Slice{
382 .offset = std::move(access_offset),
383 .count = std::move(step_dimensions)
388 std::string _timeseries_filename =
"";