GridFormat 0.4.0
I/O-Library for grid-like data structures
Loading...
Searching...
No Matches
pvts_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_PVTS_WRITER_HPP_
9#define GRIDFORMAT_VTK_PVTS_WRITER_HPP_
10
11#include <ostream>
12#include <string>
13#include <fstream>
14#include <algorithm>
15#include <filesystem>
16#include <array>
17#include <tuple>
18#include <cmath>
19
21#include <gridformat/common/ranges.hpp>
22#include <gridformat/common/exceptions.hpp>
23#include <gridformat/common/lvalue_reference.hpp>
24
25#include <gridformat/parallel/communication.hpp>
26#include <gridformat/parallel/helpers.hpp>
27
28#include <gridformat/grid/grid.hpp>
29#include <gridformat/xml/element.hpp>
32
33namespace GridFormat {
34
39template<Concepts::StructuredGrid Grid,
40 Concepts::Communicator Communicator>
41class PVTSWriter : public VTK::XMLWriterBase<Grid, PVTSWriter<Grid, Communicator>> {
43 using CT = CoordinateType<Grid>;
44
45 static constexpr std::size_t space_dim = 3;
46 static constexpr std::size_t dim = dimension<Grid>;
47 static constexpr int root_rank = 0;
48
49 public:
50 explicit PVTSWriter(LValueReferenceOf<const Grid> grid,
51 Communicator comm,
52 VTK::XMLOptions xml_opts = {})
53 : ParentType(grid.get(), ".pvts", true, xml_opts)
54 , _comm(comm)
55 {}
56
57 const Communicator& communicator() const {
58 return _comm;
59 }
60
61 private:
62 Communicator _comm;
63
64 PVTSWriter _with(VTK::XMLOptions xml_opts) const override {
65 return PVTSWriter{this->grid(), _comm, std::move(xml_opts)};
66 }
67
68 void _write(std::ostream&) const override {
69 throw InvalidState(
70 "PVTSWriter does not support direct export into stream. "
71 "Use overload with filename instead!"
72 );
73 }
74
75 virtual void _write(const std::string& filename_with_ext) const override {
76 const auto& local_extents = extents(this->grid());
77 const auto [origin, is_negative_axis] = _get_origin_and_orientations(local_extents);
78
80 const auto all_origins = Parallel::gather(_comm, origin, root_rank);
81 const auto all_extents = Parallel::gather(_comm, local_extents, root_rank);
82 const auto [exts_begin, exts_end, whole_extent, _] = helper.compute_extents_and_origin(
83 all_origins,
84 all_extents,
85 is_negative_axis
86 );
87
88 const auto my_whole_extent = Parallel::broadcast(_comm, whole_extent, root_rank);
89 const auto my_extent_offset = Parallel::scatter(_comm, Ranges::flat(exts_begin), root_rank);
90
91 _write_piece(filename_with_ext, Ranges::to_array<dim>(my_extent_offset), {my_whole_extent});
92 Parallel::barrier(_comm); // ensure all pieces finished successfully
93 if (Parallel::rank(_comm) == 0)
94 _write_pvts_file(filename_with_ext, my_whole_extent, exts_begin, exts_end);
95 Parallel::barrier(_comm); // ensure .pvts file is written before returning
96 }
97
98 auto _get_origin_and_orientations(const std::ranges::range auto& extents) const {
99 auto is_negative_axis = Ranges::filled_array<dim>(false);
100 std::ranges::for_each(extents, [&, dir=0] (const auto& e) mutable {
101 if (e > 0)
102 is_negative_axis[dir] = _is_negative_axis(dir);
103 dir++;
104 });
105
106 std::array<CT, dim> origin;
107 static constexpr auto origin_loc = _origin_location();
108 for (const auto& p : points(this->grid())) {
109 const auto& loc = location(this->grid(), p);
110 const auto& pos = coordinates(this->grid(), p);
111 if (std::ranges::equal(loc, origin_loc)) {
112 std::ranges::copy(pos, origin.begin());
113 return std::make_tuple(std::move(origin), std::move(is_negative_axis));
114 }
115 }
116
117 throw InvalidState("Could not determine origin");
118 }
119
120 static constexpr auto _origin_location() {
121 std::array<std::size_t, dim> origin;
122 std::ranges::fill(origin, 0);
123 return origin;
124 }
125
126 bool _is_negative_axis(unsigned axis) const {
127 for (const auto& p0 : points(this->grid())) {
128 const auto i0 = Ranges::at(axis, location(this->grid(), p0));
129 const auto x0 = Ranges::at(axis, coordinates(this->grid(), p0));
130 for (const auto& p1 : points(this->grid())) {
131 const auto i1 = Ranges::at(axis, location(this->grid(), p1));
132 const auto x1 = Ranges::at(axis, coordinates(this->grid(), p1));
133 if (i1 > i0)
134 return x1 < x0;
135 }
136 }
137 throw InvalidState("Could not determine axis orientation");
138 }
139
140 void _write_piece(const std::string& par_filename,
141 const std::array<std::size_t, dim>& offset,
142 typename VTSWriter<Grid>::Domain domain) const {
143 auto writer = VTSWriter{this->grid(), this->_xml_opts}
144 .as_piece_for(std::move(domain))
145 .with_offset(offset);
146 this->copy_fields(writer);
147 writer.write(PVTK::piece_basefilename(par_filename, Parallel::rank(_comm)));
148 }
149
150 void _write_pvts_file(const std::string& filename_with_ext,
151 const std::array<std::size_t, dim>& extents,
152 const std::vector<std::array<std::size_t, dim>>& proc_extents_begin,
153 const std::vector<std::array<std::size_t, dim>>& proc_extents_end) const {
154 std::ofstream file_stream(filename_with_ext, std::ios::out);
155
156 XMLElement pvtk_xml("VTKFile");
157 pvtk_xml.set_attribute("type", "PStructuredGrid");
158
159 XMLElement& grid = pvtk_xml.add_child("PStructuredGrid");
160 grid.set_attribute("WholeExtent", VTK::CommonDetail::extents_string(extents));
161
162 XMLElement& ppoint_data = grid.add_child("PPointData");
163 XMLElement& pcell_data = grid.add_child("PCellData");
164 XMLElement& pcoords = grid.add_child("PPoints");
165 std::visit([&] (const auto& encoder) {
166 std::visit([&] (const auto& data_format) {
167 PVTK::PDataArrayHelper pdata_helper{encoder, data_format, ppoint_data};
168 PVTK::PDataArrayHelper cdata_helper{encoder, data_format, pcell_data};
169 std::ranges::for_each(this->_point_field_names(), [&] (const std::string& name) {
170 pdata_helper.add(name, this->_get_point_field(name));
171 });
172 std::ranges::for_each(this->_cell_field_names(), [&] (const std::string& name) {
173 cdata_helper.add(name, this->_get_cell_field(name));
174 });
175
176 std::visit([&] <typename T> (const Precision<T>& prec) {
177 XMLElement& pdata_array = pcoords.add_child("PDataArray");
178 pdata_array.set_attribute("NumberOfComponents", std::to_string(space_dim));
179 pdata_array.set_attribute("Name", "Coordinates");
180 pdata_array.set_attribute("format", VTK::data_format_name(encoder, data_format));
181 pdata_array.set_attribute("type", VTK::attribute_name(prec));
182 }, this->_xml_settings.coordinate_precision);
183 }, this->_xml_settings.data_format);
184 }, this->_xml_settings.encoder);
185
186 std::ranges::for_each(Parallel::ranks(_comm), [&] (int rank) {
187 std::array<std::size_t, dim> extents_begin;
188 std::array<std::size_t, dim> extents_end;
189 for (unsigned dir = 0; dir < dim; ++dir) {
190 extents_begin[dir] = proc_extents_begin[rank][dir];
191 extents_end[dir] = proc_extents_end[rank][dir];
192 }
193 auto& piece = grid.add_child("Piece");
194 piece.set_attribute("Extent", VTK::CommonDetail::extents_string(extents_begin, extents_end));
195 piece.set_attribute("Source", std::filesystem::path{
196 PVTK::piece_basefilename(filename_with_ext, rank) + ".vts"
197 }.filename());
198 });
199
200 this->_set_default_active_fields(pvtk_xml.get_child("PStructuredGrid"));
201 write_xml_with_version_header(pvtk_xml, file_stream, Indentation{{.width = 2}});
202 }
203};
204
205template<typename G, Concepts::Communicator C>
206PVTSWriter(G&&, const C&, VTK::XMLOptions = {}) -> PVTSWriter<std::remove_cvref_t<G>, C>;
207
208} // namespace GridFormat
209
210#endif // GRIDFORMAT_VTK_PVTS_WRITER_HPP_
Helper to add a PDataArray child to an xml element.
Definition: parallel.hpp:45
Writer for parallel .pvts files.
Definition: pvts_writer.hpp:41
Base class for VTK-XML Writer implementations.
Definition: xml.hpp:190
Writer for .vts file format.
Definition: vts_writer.hpp:35
Helper function for writing parallel VTK files.
Options for VTK-XML files for setting the desired encoding, data format and compression.
Definition: xml.hpp:99
Definition: vts_writer.hpp:42