8#ifndef GRIDFORMAT_VTK_PXML_READER_HPP_
9#define GRIDFORMAT_VTK_PXML_READER_HPP_
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>
31#include <gridformat/parallel/communication.hpp>
36namespace GridFormat::VTK {
49template<std::derived_from<Gr
idReader> PieceReader>
60 : _vtk_grid_type{std::move(vtk_grid_type)}
63 explicit PXMLReaderBase(std::string vtk_grid_type,
const NullCommunicator&)
67 template<Concepts::Communicator C>
70 std::optional<bool> merge_exceeding_pieces = {})
72 _num_ranks = Parallel::size(comm);
73 _rank = Parallel::rank(comm);
74 _merge_exceeding = merge_exceeding_pieces;
78 enum class FieldType { point, cell };
80 const std::vector<PieceReader>& _readers()
const {
81 return _piece_readers;
84 const std::string _grid_type()
const {
85 return _vtk_grid_type;
88 std::size_t _num_process_pieces()
const {
89 return _piece_readers.size();
92 std::optional<bool> _merge_exceeding_pieces_option()
const {
93 return _merge_exceeding;
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));
107 if (std::ranges::any_of(_piece_readers | std::views::drop(1), [&] (
const auto& reader) {
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) {
114 throw IOError(
"All pieces must define the same cell fields");
118 void _close_pvtk_file() {
120 _piece_readers.clear();
121 _num_pieces_in_file = 0;
129 void _close()
override {
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();
137 return std::accumulate(
138 std::ranges::begin(num_cells_view),
139 std::ranges::end(num_cells_view),
144 std::size_t _number_of_pieces()
const override {
145 return _num_pieces_in_file;
148 bool _is_sequence()
const override {
152 FieldPtr _meta_data_field(std::string_view
name)
const override {
153 return _piece_readers.front().meta_data_field(
name);
157 return _merge([] (
const PieceReader& reader) {
return reader.points(); }, FieldType::point);
160 FieldPtr _cell_field(std::string_view
name)
const override {
161 return _merge([&] (
const PieceReader& reader) {
return reader.cell_field(
name); }, FieldType::cell);
164 FieldPtr _point_field(std::string_view
name)
const override {
165 return _merge([&] (
const PieceReader& reader) {
return reader.point_field(
name); }, FieldType::point);
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)
173 if (_num_process_pieces() == 1)
174 return get_field(_piece_readers.front());
176 std::vector<FieldPtr> field_pieces;
179 | std::views::transform([&] (
const PieceReader& reader) {
return get_field(reader); }),
180 std::back_inserter(field_pieces)
182 return _merge_field_pieces(std::move(field_pieces), type);
185 virtual FieldPtr _merge_field_pieces(std::vector<FieldPtr>&&, FieldType)
const = 0;
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")); });
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())
197 return std::filesystem::path{_filename.value()}.parent_path() / piece_filename;
202 _read_parallel_piece(helper);
204 std::ranges::for_each(_pieces_paths(helper), [&] (
const std::filesystem::path& path) {
205 _piece_readers.emplace_back(PieceReader{}).
open(path);
210 const auto num_pieces = Ranges::size(_pieces_paths(helper));
211 if (num_pieces < _num_ranks.value() && _rank.value() == 0)
213 "PVTK file defines less pieces than there are ranks. The grids on some ranks will be empty."
215 if (num_pieces > _num_ranks.value() && !_merge_exceeding.has_value() && _rank.value() == 0)
217 "PVTK file defines more pieces than used ranks. Will only read the first "
218 + std::to_string(_num_ranks.value()) +
" pieces"
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;
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);
235 std::string _vtk_grid_type;
237 std::optional<unsigned int> _num_ranks;
238 std::optional<unsigned int> _rank;
239 std::optional<bool> _merge_exceeding;
241 std::optional<std::string> _filename;
242 std::vector<PieceReader> _piece_readers;
243 std::size_t _num_pieces_in_file = 0;
252template<std::derived_from<Gr
idReader> PieceReader>
256 using ParentType::ParentType;
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();
263 return std::accumulate(
264 std::ranges::begin(num_points_view),
265 std::ranges::end(num_points_view),
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);
277 offset += reader.number_of_points();
281 FieldPtr _merge_field_pieces(std::vector<FieldPtr>&& pieces, ParentType::FieldType)
const override {
293template<std::derived_from<Gr
idReader> PieceReader>
297 using ParentType::ParentType;
301 std::array<std::size_t, 6> extents;
302 std::optional<std::array<double, 3>> spacing;
303 std::optional<std::array<double, 3>> origin;
304 std::array<double, 9> direction{
305 1., 0., 0., 0., 1., 0., 0., 0., 1.
310 if (!_grid_specs.has_value())
311 throw InvalidState(
"No data has been read");
312 return _grid_specs.value();
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");
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.");
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"));
337 void _close()
override {
338 ParentType::_close_pvtk_file();
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(),
350 this->_readers().at(0).location().lower_left,
354 return _specs().origin.value();
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();
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)};
369 typename GridReader::PieceLocation _location()
const override {
370 typename GridReader::PieceLocation result;
372 if (this->_num_process_pieces() == 1) {
373 result = this->_readers().at(0).location();
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]};
383 std::size_t _number_of_points()
const override {
384 if (this->_num_process_pieces() == 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());
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);
395 CommonDetail::visit_structured_cells(visitor, _specs().extents);
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;
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});
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)};
415 _check_fields_compatibility(pieces, whole_field_layout, precision);
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; });
425 std::ranges::for_each(pieces_layouts.back(), [] (
auto& o) { o = std::max(o, std::size_t{1}); });
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)
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);
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);
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());
455 piece_span.data() + piece_offset,
457 result_span.data() + global_offset
459 piece_offset += num_comps;
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())
475 if (whole_field_layout.dimension() > 1)
476 return whole_field_layout.sub_layout(1) == piece_layout.sub_layout(1);
479 if (!std::ranges::all_of(pieces, has_compatible_layout))
480 throw ValueError(
"Fields to be merged have incompatible layouts");
482 const auto has_compatible_precision = [&] (
const FieldPtr& piece_field) {
483 return piece_field->precision() == precision;
485 if (!std::ranges::all_of(pieces, has_compatible_precision))
486 throw ValueError(
"Fields to be merged have incompatible precisions");
489 std::array<std::size_t, 6> _whole_point_extents()
const {
490 auto result = _specs().extents;
497 std::optional<StructuredGridSpecs> _grid_specs;
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
Common functionality for VTK writers.
Helper classes and functions for VTK XML-type file format writers & readers.