GridFormat 0.4.0
I/O-Library for grid-like data structures
Loading...
Searching...
No Matches
pxml_reader.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_PXML_READER_HPP_
9#define GRIDFORMAT_VTK_PXML_READER_HPP_
10
11#include <array>
12#include <string>
13#include <optional>
14#include <iterator>
15#include <filesystem>
16#include <algorithm>
17#include <iterator>
18#include <utility>
19#include <cmath>
20
21#include <gridformat/common/ranges.hpp>
23#include <gridformat/common/md_index.hpp>
24#include <gridformat/common/md_layout.hpp>
25#include <gridformat/common/flat_index_mapper.hpp>
26#include <gridformat/common/empty_field.hpp>
27#include <gridformat/common/field_transformations.hpp>
28#include <gridformat/common/exceptions.hpp>
29
31#include <gridformat/parallel/communication.hpp>
32
35
36namespace GridFormat::VTK {
37
49template<std::derived_from<GridReader> PieceReader>
50class PXMLReaderBase : public GridReader {
51 public:
52 virtual ~PXMLReaderBase() = default;
53
54 PXMLReaderBase(PXMLReaderBase&&) = default;
55 PXMLReaderBase(const PXMLReaderBase&) = delete;
56 PXMLReaderBase& operator=(PXMLReaderBase&&) = default;
57 PXMLReaderBase& operator=(const PXMLReaderBase&) = delete;
58
59 PXMLReaderBase(std::string vtk_grid_type)
60 : _vtk_grid_type{std::move(vtk_grid_type)}
61 {}
62
63 explicit PXMLReaderBase(std::string vtk_grid_type, const NullCommunicator&)
64 : PXMLReaderBase(std::move(vtk_grid_type))
65 {}
66
67 template<Concepts::Communicator C>
68 explicit PXMLReaderBase(std::string vtk_grid_type,
69 const C& comm,
70 std::optional<bool> merge_exceeding_pieces = {})
71 : PXMLReaderBase(std::move(vtk_grid_type)) {
72 _num_ranks = Parallel::size(comm);
73 _rank = Parallel::rank(comm);
74 _merge_exceeding = merge_exceeding_pieces;
75 }
76
77 protected:
78 enum class FieldType { point, cell };
79
80 const std::vector<PieceReader>& _readers() const {
81 return _piece_readers;
82 }
83
84 const std::string _grid_type() const {
85 return _vtk_grid_type;
86 }
87
88 std::size_t _num_process_pieces() const {
89 return _piece_readers.size();
90 }
91
92 std::optional<bool> _merge_exceeding_pieces_option() const {
93 return _merge_exceeding;
94 }
95
96 XMLReaderHelper _read_pvtk_file(const std::string& filename, typename GridReader::FieldNames& fields) {
97 _filename = filename;
98 auto helper = XMLReaderHelper::make_from(filename, _vtk_grid_type);
99 _num_pieces_in_file = Ranges::size(_pieces_paths(helper));
100 _read_pieces(helper);
101 if (_piece_readers.size() > 0) {
102 std::ranges::copy(point_field_names(_piece_readers.front()), std::back_inserter(fields.point_fields));
103 std::ranges::copy(cell_field_names(_piece_readers.front()), std::back_inserter(fields.cell_fields));
104 std::ranges::copy(meta_data_field_names(_piece_readers.front()), std::back_inserter(fields.meta_data_fields));
105 }
106
107 if (std::ranges::any_of(_piece_readers | std::views::drop(1), [&] (const auto& reader) {
108 return !std::ranges::equal(point_field_names(reader), fields.point_fields);
109 }))
110 throw IOError("All pieces must define the same point fields");
111 if (std::ranges::any_of(_piece_readers | std::views::drop(1), [&] (const auto& reader) {
112 return !std::ranges::equal(cell_field_names(reader), fields.cell_fields);
113 }))
114 throw IOError("All pieces must define the same cell fields");
115 return helper;
116 }
117
118 void _close_pvtk_file() {
119 _filename.reset();
120 _piece_readers.clear();
121 _num_pieces_in_file = 0;
122 }
123
124 private:
125 void _open(const std::string& filename, typename GridReader::FieldNames& fields) override {
126 _read_pvtk_file(filename, fields);
127 }
128
129 void _close() override {
130 _close_pvtk_file();
131 }
132
133 std::size_t _number_of_cells() const override {
134 auto num_cells_view = _piece_readers | std::views::transform([] (const auto& reader) {
135 return reader.number_of_cells();
136 });
137 return std::accumulate(
138 std::ranges::begin(num_cells_view),
139 std::ranges::end(num_cells_view),
140 std::size_t{0}
141 );
142 }
143
144 std::size_t _number_of_pieces() const override {
145 return _num_pieces_in_file;
146 }
147
148 bool _is_sequence() const override {
149 return false;
150 }
151
152 FieldPtr _meta_data_field(std::string_view name) const override {
153 return _piece_readers.front().meta_data_field(name);
154 }
155
156 FieldPtr _points() const override {
157 return _merge([] (const PieceReader& reader) { return reader.points(); }, FieldType::point);
158 }
159
160 FieldPtr _cell_field(std::string_view name) const override {
161 return _merge([&] (const PieceReader& reader) { return reader.cell_field(name); }, FieldType::cell);
162 }
163
164 FieldPtr _point_field(std::string_view name) const override {
165 return _merge([&] (const PieceReader& reader) { return reader.point_field(name); }, FieldType::point);
166 }
167
168 template<std::invocable<const PieceReader&> FieldGetter>
169 requires(std::same_as<std::invoke_result_t<FieldGetter, const PieceReader&>, FieldPtr>)
170 FieldPtr _merge(const FieldGetter& get_field, FieldType type) const {
171 if (_num_process_pieces() == 0)
172 return make_field_ptr(EmptyField{float64});
173 if (_num_process_pieces() == 1)
174 return get_field(_piece_readers.front());
175
176 std::vector<FieldPtr> field_pieces;
177 std::ranges::copy(
178 _piece_readers
179 | std::views::transform([&] (const PieceReader& reader) { return get_field(reader); }),
180 std::back_inserter(field_pieces)
181 );
182 return _merge_field_pieces(std::move(field_pieces), type);
183 }
184
185 virtual FieldPtr _merge_field_pieces(std::vector<FieldPtr>&&, FieldType) const = 0;
186
187 std::ranges::range auto _pieces_paths(const XMLReaderHelper& helper) const {
188 return children(helper.get(_vtk_grid_type))
189 | std::views::filter([&] (const XMLElement& e) { return e.name() == "Piece"; })
190 | std::views::transform([&] (const XMLElement& p) { return _get_piece_path(p.get_attribute("Source")); });
191 }
192
193 std::filesystem::path _get_piece_path(const std::string& piece_filename) const {
194 std::filesystem::path piece_path{piece_filename};
195 if (piece_path.is_absolute())
196 return piece_path;
197 return std::filesystem::path{_filename.value()}.parent_path() / piece_filename;
198 }
199
200 void _read_pieces(const XMLReaderHelper& helper) {
201 if (_num_ranks)
202 _read_parallel_piece(helper);
203 else
204 std::ranges::for_each(_pieces_paths(helper), [&] (const std::filesystem::path& path) {
205 _piece_readers.emplace_back(PieceReader{}).open(path);
206 });
207 }
208
209 void _read_parallel_piece(const XMLReaderHelper& helper) {
210 const auto num_pieces = Ranges::size(_pieces_paths(helper));
211 if (num_pieces < _num_ranks.value() && _rank.value() == 0)
212 this->_log_warning(
213 "PVTK file defines less pieces than there are ranks. The grids on some ranks will be empty."
214 );
215 if (num_pieces > _num_ranks.value() && !_merge_exceeding.has_value() && _rank.value() == 0)
216 this->_log_warning(
217 "PVTK file defines more pieces than used ranks. Will only read the first "
218 + std::to_string(_num_ranks.value()) + " pieces"
219 );
220
221 const bool is_last_rank = _rank.value() == _num_ranks.value() - 1;
222 const bool merge_final_pieces = is_last_rank && _merge_exceeding.value_or(false);
223 const std::size_t my_num_pieces = merge_final_pieces ? Ranges::size(_pieces_paths(helper)) - _rank.value() : 1;
224
225 std::ranges::for_each(
226 _pieces_paths(helper)
227 | std::views::drop(_rank.value())
228 | std::views::take(my_num_pieces),
229 [&] (const std::filesystem::path& path) {
230 _piece_readers.emplace_back(PieceReader{}).open(path);
231 }
232 );
233 }
234
235 std::string _vtk_grid_type;
236
237 std::optional<unsigned int> _num_ranks;
238 std::optional<unsigned int> _rank;
239 std::optional<bool> _merge_exceeding;
240
241 std::optional<std::string> _filename;
242 std::vector<PieceReader> _piece_readers;
243 std::size_t _num_pieces_in_file = 0;
244};
245
246
252template<std::derived_from<GridReader> PieceReader>
253class PXMLUnstructuredGridReader : public PXMLReaderBase<PieceReader> {
255 public:
256 using ParentType::ParentType;
257
258 private:
259 std::size_t _number_of_points() const override {
260 auto num_points_view = this->_readers() | std::views::transform([] (const auto& reader) {
261 return reader.number_of_points();
262 });
263 return std::accumulate(
264 std::ranges::begin(num_points_view),
265 std::ranges::end(num_points_view),
266 std::size_t{0}
267 );
268 }
269
270 void _visit_cells(const typename GridReader::CellVisitor& visitor) const override {
271 std::size_t offset = 0;
272 std::ranges::for_each(this->_readers(), [&] (const PieceReader& reader) {
273 reader.visit_cells([&] (CellType ct, std::vector<std::size_t> corners) {
274 std::ranges::for_each(corners, [&] (auto& value) { value += offset; });
275 visitor(ct, corners);
276 });
277 offset += reader.number_of_points();
278 });
279 }
280
281 FieldPtr _merge_field_pieces(std::vector<FieldPtr>&& pieces, ParentType::FieldType) const override {
282 return make_field_ptr(MergedField{std::move(pieces)});
283 }
284};
285
286
293template<std::derived_from<GridReader> PieceReader>
294class PXMLStructuredGridReader : public PXMLReaderBase<PieceReader> {
296 public:
297 using ParentType::ParentType;
298
299 protected:
301 std::array<std::size_t, 6> extents;
302 std::optional<std::array<double, 3>> spacing; // for image grids
303 std::optional<std::array<double, 3>> origin; // for image grids
304 std::array<double, 9> direction{ // for image grids
305 1., 0., 0., 0., 1., 0., 0., 0., 1.
306 };
307 };
308
309 const StructuredGridSpecs& _specs() const {
310 if (!_grid_specs.has_value())
311 throw InvalidState("No data has been read");
312 return _grid_specs.value();
313 }
314
315 private:
316 void _open(const std::string& filename, typename GridReader::FieldNames& names) override {
317 if (this->_merge_exceeding_pieces_option().value_or(false))
318 throw IOError("Parallel I/O of structured vtk files does not support the 'merge_exceeding_pieces' option");
319
320 auto helper = ParentType::_read_pvtk_file(filename, names);
321 const XMLElement& vtk_grid = helper.get(this->_grid_type());
322 if (vtk_grid.get_attribute_or(std::size_t{0}, "GhostLevel") > 0)
323 throw IOError("GhostLevel > 0 not yet supported for parallel I/O of structured vtk files.");
324
325 StructuredGridSpecs& specs = _grid_specs.emplace();
326 specs.extents = Ranges::array_from_string<std::size_t, 6>(vtk_grid.get_attribute("WholeExtent"));
327 if (specs.extents[0] != 0 || specs.extents[2] != 0 || specs.extents[4] != 0)
328 throw ValueError("'WholeExtent' is expected to have no offset (e.g. have the shape 0 X 0 Y 0 Z)");
329 if (vtk_grid.has_attribute("Origin"))
330 specs.origin = Ranges::array_from_string<double, 3>(vtk_grid.get_attribute("Origin"));
331 if (vtk_grid.has_attribute("Spacing"))
332 specs.spacing = Ranges::array_from_string<double, 3>(vtk_grid.get_attribute("Spacing"));
333 if (vtk_grid.has_attribute("Direction"))
334 specs.direction = Ranges::array_from_string<double, 9>(vtk_grid.get_attribute("Direction"));
335 }
336
337 void _close() override {
338 ParentType::_close_pvtk_file();
339 _grid_specs.reset();
340 }
341
342 typename GridReader::Vector _origin() const override {
343 if (!_specs().origin.has_value())
344 throw ValueError("PVTK file does not define the origin for '" + this->_grid_type() + "'");
345 if (this->_num_process_pieces() == 1) {
346 const auto& specs = _specs();
347 return CommonDetail::compute_piece_origin(
348 specs.origin.value(),
349 _spacing(),
350 this->_readers().at(0).location().lower_left,
351 specs.direction
352 );
353 } else { // use global origin
354 return _specs().origin.value();
355 }
356 }
357
358 typename GridReader::Vector _spacing() const override {
359 if (!_specs().spacing.has_value())
360 throw ValueError("PVTK file does not define the spacing for '" + this->_grid_type() + "'");
361 return _specs().spacing.value();
362 }
363
364 typename GridReader::Vector _basis_vector(unsigned int i) const override {
365 const auto& direction = _specs().direction;
366 return {direction.at(i), direction.at(i+3), direction.at(i+6)};
367 }
368
369 typename GridReader::PieceLocation _location() const override {
370 typename GridReader::PieceLocation result;
371
372 if (this->_num_process_pieces() == 1) {
373 result = this->_readers().at(0).location();
374 } else { // use "WholeExtent"
375 const auto& specs = _specs();
376 result.lower_left = {specs.extents[0], specs.extents[2], specs.extents[4]};
377 result.upper_right = {specs.extents[1], specs.extents[3], specs.extents[5]};
378 }
379
380 return result;
381 }
382
383 std::size_t _number_of_points() const override {
384 if (this->_num_process_pieces() == 0)
385 return 0;
386 if (this->_num_process_pieces() == 1)
387 return this->_readers().at(0).number_of_points();
388 return CommonDetail::number_of_entities(_whole_point_extents());
389 }
390
391 void _visit_cells(const typename GridReader::CellVisitor& visitor) const override {
392 if (this->_num_process_pieces() == 1)
393 this->_readers().at(0).visit_cells(visitor);
394 else
395 CommonDetail::visit_structured_cells(visitor, _specs().extents);
396 }
397
398 FieldPtr _merge_field_pieces(std::vector<FieldPtr>&& pieces, ParentType::FieldType type) const override {
399 auto whole_grid_extents = type == ParentType::FieldType::point ? _whole_point_extents() : [&] () {
400 auto result = _specs().extents;
401 // avoid zeroes s.t. the index mappers map properly
402 result[1] = std::max(result[1], std::size_t{1});
403 result[3] = std::max(result[3], std::size_t{1});
404 result[5] = std::max(result[5], std::size_t{1});
405 return result;
406 } ();
407 auto num_entities = CommonDetail::number_of_entities(whole_grid_extents);
408 auto precision = pieces.at(0)->precision();
409 MDLayout whole_field_layout = [&] () {
410 const auto first_layout = pieces.at(0)->layout();
411 std::vector<std::size_t> dims(first_layout.begin(), first_layout.end());
412 dims.at(0) = num_entities;
413 return MDLayout{std::move(dims)};
414 } ();
415 _check_fields_compatibility(pieces, whole_field_layout, precision);
416
417 std::vector<MDLayout> pieces_layouts;
418 std::vector<MDIndex> pieces_offsets;
419 std::ranges::for_each(this->_readers(), [&] (const PieceReader& reader) {
420 pieces_layouts.push_back(MDLayout{reader.extents()});
421 pieces_offsets.push_back(MDIndex{reader.location().lower_left});
422 if (type == ParentType::FieldType::point)
423 std::ranges::for_each(pieces_layouts.back(), [] (auto& o) { o += 1; });
424 else
425 std::ranges::for_each(pieces_layouts.back(), [] (auto& o) { o = std::max(o, std::size_t{1}); });
426 });
427
428 return make_field_ptr(LazyField{
429 int{}, // dummy source
430 whole_field_layout,
431 precision,
432 [
433 _whole_ex=std::move(whole_grid_extents), _prec=precision, _field_layout=whole_field_layout,
434 _pieces=std::move(pieces), _offsets=std::move(pieces_offsets), _piece_layouts=std::move(pieces_layouts)
435 ] (const int&) {
436 const auto num_entities = _field_layout.extent(0);
437 const auto num_comps = _field_layout.dimension() > 1 ? _field_layout.number_of_entries(1) : 1;
438 const FlatIndexMapper global_mapper{std::array{_whole_ex[1], _whole_ex[3], _whole_ex[5]}};
439 return _prec.visit([&] <typename T> (const Precision<T>& prec) {
440 Serialization result(num_entities*num_comps*sizeof(T));
441 auto result_span = result.as_span_of(prec);
442
443 for (unsigned int i = 0; i < _pieces.size(); ++i) {
444 auto piece_serialization = _pieces.at(i)->serialized();
445 auto piece_span = piece_serialization.as_span_of(prec);
446
447 std::size_t piece_offset = 0;
448 const auto local_to_global_offset = _offsets.at(i);
449 for (auto piece_index : MDIndexRange{_piece_layouts.at(i)}) {
450 piece_index += local_to_global_offset;
451 const auto global_offset = global_mapper.map(piece_index)*num_comps;
452 assert(global_offset + num_comps <= result_span.size());
453 assert(piece_offset + num_comps <= piece_span.size());
454 std::copy_n(
455 piece_span.data() + piece_offset,
456 num_comps,
457 result_span.data() + global_offset
458 );
459 piece_offset += num_comps;
460 }
461 }
462 return result;
463 });
464 }
465 });
466 }
467
468 void _check_fields_compatibility(const std::vector<FieldPtr>& pieces,
469 const MDLayout& whole_field_layout,
470 const DynamicPrecision& precision) const {
471 const auto has_compatible_layout = [&] (const FieldPtr& piece_field) {
472 const auto piece_layout = piece_field->layout();
473 if (piece_layout.dimension() != whole_field_layout.dimension())
474 return false;
475 if (whole_field_layout.dimension() > 1)
476 return whole_field_layout.sub_layout(1) == piece_layout.sub_layout(1);
477 return true;
478 };
479 if (!std::ranges::all_of(pieces, has_compatible_layout))
480 throw ValueError("Fields to be merged have incompatible layouts");
481
482 const auto has_compatible_precision = [&] (const FieldPtr& piece_field) {
483 return piece_field->precision() == precision;
484 };
485 if (!std::ranges::all_of(pieces, has_compatible_precision))
486 throw ValueError("Fields to be merged have incompatible precisions");
487 }
488
489 std::array<std::size_t, 6> _whole_point_extents() const {
490 auto result = _specs().extents;
491 result[1] += 1;
492 result[3] += 1;
493 result[5] += 1;
494 return result;
495 }
496
497 std::optional<StructuredGridSpecs> _grid_specs;
498};
499
500} // namespace GridFormat::VTK
501
502#endif // GRIDFORMAT_VTK_PXML_READER_HPP_
Abstract base class for all readers, defines the common interface.
Definition: reader.hpp:51
const std::string & filename() const
Return the name of the opened grid file (empty string until open() is called)
Definition: reader.hpp:92
friend std::ranges::range auto point_field_names(const GridReader &reader)
Return a range over the names of all read point fields.
Definition: reader.hpp:231
std::string name() const
Return the name of this reader.
Definition: reader.hpp:73
friend std::ranges::range auto meta_data_field_names(const GridReader &reader)
Return a range over the names of all read metadata fields.
Definition: reader.hpp:236
void open(const std::string &filename)
Open the given grid file.
Definition: reader.hpp:78
friend std::ranges::range auto cell_field_names(const GridReader &reader)
Return a range over the names of all read cell fields.
Definition: reader.hpp:226
Base class for readers of parallel vtk-xml file formats.
Definition: pxml_reader.hpp:50
Base class for readers of parallel vtk-xml file formats for structured grids.
Definition: pxml_reader.hpp:294
Base class for readers of parallel vtk-xml file formats for unstructured grids.
Definition: pxml_reader.hpp:253
Helper class for VTK-XML readers to use.
Definition: xml.hpp:730
Base class for grid data readers.
std::shared_ptr< const Field > FieldPtr
Pointer type used by writers/readers for fields.
Definition: field.hpp:186
FieldPtr make_field_ptr(F &&f)
Factory function for field pointers.
Definition: field.hpp:192
Definition: reader.hpp:266
Common functionality for VTK writers.
Helper classes and functions for VTK XML-type file format writers & readers.