GCC Code Coverage Report


Directory: ../../../builds/dumux-repositories/
File: /builds/dumux-repositories/dumux/dumux/io/vtk/vtkreader.hh
Date: 2024-09-21 20:52:54
Exec Total Coverage
Lines: 0 211 0.0%
Functions: 0 34 0.0%
Branches: 0 854 0.0%

Line Branch Exec Source
1 // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 // vi: set et ts=4 sw=4 sts=4:
3 //
4 // SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5 // SPDX-License-Identifier: GPL-3.0-or-later
6 //
7 /*!
8 * \file
9 * \ingroup InputOutput
10 * \brief A vtk file reader using tinyxml2 as xml backend
11 */
12 #ifndef DUMUX_IO_VTK_VTKREADER_HH
13 #define DUMUX_IO_VTK_VTKREADER_HH
14
15 #include <iostream>
16 #include <iterator>
17 #include <algorithm>
18 #include <memory>
19 #include <type_traits>
20 #include <unordered_map>
21 #include <utility>
22 #include <filesystem>
23
24 #include <dune/common/parallel/mpihelper.hh>
25 #include <dune/common/exceptions.hh>
26 #include <dune/grid/common/capabilities.hh>
27 #include <dune/grid/io/file/vtk/common.hh>
28 #include <dune/grid/common/gridfactory.hh>
29
30 #include <dumux/io/container.hh>
31 #include <dumux/io/xml/tinyxml2.h>
32
33 namespace Dumux {
34
35 /*!
36 * \ingroup InputOutput
37 * \brief A vtk file reader using tinyxml2 as xml backend
38 */
39 class VTKReader
40 {
41 public:
42 /*!
43 * \brief The data array types
44 */
45 enum class DataType { cellData, pointData };
46
47 //! the cell / point data type for point data read from a grid file
48 using Data = std::unordered_map<std::string, std::vector<double>>;
49
50 /*!
51 * \brief The constructor creates a tinyxml2::XMLDocument from file
52 */
53 explicit VTKReader(const std::string& fileName)
54 {
55 using namespace tinyxml2;
56 const auto ext = std::filesystem::path(fileName).extension().string();
57 // If in parallel and the file to read is a parallel piece collection (pvtu/pvtp)
58 // read only the piece belonging to the own process. For this to work, the files
59 // have to have exactly the same amount of pieces than processes.
60 fileName_ = Dune::MPIHelper::instance().size() > 1 && ext[1] == 'p' ?
61 getProcessFileName_(fileName) : fileName;
62
63 const auto eResult = doc_.LoadFile(fileName_.c_str());
64 if (eResult != tinyxml2::XML_SUCCESS)
65 DUNE_THROW(Dune::IOError, "Couldn't open XML file " << fileName_ << ".");
66
67 const XMLElement* pieceNode = getPieceNode_();
68 if (pieceNode == nullptr)
69 DUNE_THROW(Dune::IOError, "Couldn't get 'Piece' node in " << fileName_ << ".");
70 }
71
72 /*!
73 * \brief Reviews data from the vtk file to check if there is a data array with a specified name
74 * \param name the name attribute of the data array to read
75 * \param type the data array type
76 */
77 bool hasData(const std::string& name, const DataType& type) const
78 {
79 using namespace tinyxml2;
80
81 const XMLElement* pieceNode = getPieceNode_();
82 const XMLElement* dataNode = getDataNode_(pieceNode, type);
83 if (dataNode == nullptr)
84 return false;
85
86 const XMLElement* dataArray = findDataArray_(dataNode, name);
87 if (dataArray == nullptr)
88 return false;
89
90 return true;
91 }
92
93 /*!
94 * \brief read data from the vtk file to a container, e.g. std::vector<double>
95 * \tparam Container a container type that has begin(), end(), push_back(), e.g. std::vector<>
96 * \param name the name attribute of the data array to read
97 * \param type the data array type
98 */
99 template<class Container>
100 Container readData(const std::string& name, const DataType& type) const
101 {
102 using namespace tinyxml2;
103
104 const XMLElement* pieceNode = getPieceNode_();
105 const XMLElement* dataNode = getDataNode_(pieceNode, type);
106 if (dataNode == nullptr)
107 DUNE_THROW(Dune::IOError, "Couldn't get 'PointData' or 'CellData' node in " << fileName_ << ".");
108
109 const XMLElement* dataArray = findDataArray_(dataNode, name);
110 if (dataArray == nullptr)
111 DUNE_THROW(Dune::IOError, "Couldn't find the data array " << name << ".");
112
113 return parseDataArray_<Container>(dataArray);
114 }
115
116 /*!
117 * \brief Read a grid from a vtk/vtu/vtp file, ignoring cell and point data
118 * \param verbose if the output should be verbose
119 */
120 template<class Grid>
121 std::unique_ptr<Grid> readGrid(bool verbose = false) const
122 {
123 static_assert(!Dune::Capabilities::isCartesian<Grid>::v, "Grid reader only supports unstructured grid implementations");
124
125 // make a grid factory
126 Dune::GridFactory<Grid> factory;
127
128 // only read on rank 0
129 if (Dune::MPIHelper::instance().rank() == 0)
130 {
131 if (verbose)
132 std::cout << "Reading " << Grid::dimension << "d grid from vtk file " << fileName_ << "." << std::endl;
133 readGrid_(factory, verbose);
134 }
135
136 return std::unique_ptr<Grid>(factory.createGrid());
137 }
138
139 /*!
140 * \brief Read a grid from a vtk/vtu/vtp file, ignoring cell and point data
141 * \note use this signature if the factory might be needed outside to interpret the data via the factory's insertion indices
142 * \param verbose if the output should be verbose
143 * \param factory the (empty) grid factory
144 */
145 template<class Grid>
146 std::unique_ptr<Grid> readGrid(Dune::GridFactory<Grid>& factory, bool verbose = false) const
147 {
148 static_assert(!Dune::Capabilities::isCartesian<Grid>::v, "Grid reader only supports unstructured grid implementations");
149
150 if (Dune::MPIHelper::instance().rank() == 0)
151 {
152 if (verbose)
153 std::cout << "Reading " << Grid::dimension << "d grid from vtk file " << fileName_ << "." << std::endl;
154 readGrid_(factory, verbose);
155 }
156
157 return std::unique_ptr<Grid>(factory.createGrid());
158 }
159
160 /*!
161 * \brief Read a grid from a vtk/vtu/vtp file, reading all cell and point data
162 * \note the factory will be needed outside to interpret the data via the factory's insertion indices
163 * \param factory the (empty) grid factory
164 * \param cellData the cell data arrays to be filled
165 * \param pointData the point data arrays to be filled
166 * \param verbose if the output should be verbose
167 */
168 template<class Grid>
169 std::unique_ptr<Grid> readGrid(Dune::GridFactory<Grid>& factory, Data& cellData, Data& pointData, bool verbose = false) const
170 {
171 static_assert(!Dune::Capabilities::isCartesian<Grid>::v, "Grid reader only supports unstructured grid implementations");
172
173 if (Dune::MPIHelper::instance().rank() == 0)
174 {
175 if (verbose)
176 std::cout << "Reading " << Grid::dimension << "d grid from vtk file " << fileName_ << "." << std::endl;
177 readGrid_(factory, verbose);
178 readGridData_(cellData, pointData, verbose);
179 }
180
181 return std::unique_ptr<Grid>(factory.createGrid());
182 }
183
184 private:
185 /*!
186 * \brief get the vtk filename for the current processor
187 */
188 std::string getProcessFileName_(const std::string& pvtkFileName)
189 {
190 using namespace tinyxml2;
191
192 XMLDocument pDoc;
193 const auto eResult = pDoc.LoadFile(pvtkFileName.c_str());
194 if (eResult != XML_SUCCESS)
195 DUNE_THROW(Dune::IOError, "Couldn't open XML file " << pvtkFileName << ".");
196
197 // get the first piece node
198 const XMLElement* pieceNode = getPieceNode_(pDoc, pvtkFileName);
199 const auto myrank = Dune::MPIHelper::instance().rank();
200 for (int rank = 0; rank < myrank; ++rank)
201 {
202 pieceNode = pieceNode->NextSiblingElement("Piece");
203 if (pieceNode == nullptr)
204 DUNE_THROW(Dune::IOError, "Couldn't find 'Piece' node for rank "
205 << rank << " in " << pvtkFileName << ".");
206 }
207
208 const char *vtkFileName = pieceNode->Attribute("Source");
209 if (vtkFileName == nullptr)
210 DUNE_THROW(Dune::IOError, "Couldn't get 'Source' attribute of 'Piece' node no. " << myrank << " in " << pvtkFileName);
211
212 return vtkFileName;
213 }
214
215 /*!
216 * \brief Read a grid from a vtk/vtu/vtp file
217 * \param factory the (empty) grid factory
218 * \param verbose if the output should be verbose
219 */
220 template<class Grid>
221 void readGrid_(Dune::GridFactory<Grid>& factory, bool verbose = false) const
222 {
223 using namespace tinyxml2;
224
225 const XMLElement* pieceNode = getPieceNode_();
226 const XMLElement* pointsNode = pieceNode->FirstChildElement("Points")->FirstChildElement("DataArray");
227 if (pointsNode == nullptr)
228 DUNE_THROW(Dune::IOError, "Couldn't get data array of points in " << fileName_ << ".");
229
230 using Point3D = Dune::FieldVector<double, 3>;
231 std::vector<Point3D> points3D;
232 std::stringstream dataStream(pointsNode->GetText());
233 std::istream_iterator<Point3D> it(dataStream);
234 std::copy(it, std::istream_iterator<Point3D>(), std::back_inserter(points3D));
235
236 // adapt point dimensions if grid dimension is smaller than 3
237 auto points = adaptPointDimension_<Grid::dimensionworld>(std::move(points3D));
238
239 if (verbose) std::cout << "Found " << points.size() << " vertices." << std::endl;
240
241 // insert vertices to the grid factory
242 for (auto&& point : points)
243 factory.insertVertex(std::move(point));
244
245 const XMLElement* cellsNode = pieceNode->FirstChildElement("Cells");
246 const XMLElement* linesNode = pieceNode->FirstChildElement("Lines");
247 if (cellsNode)
248 {
249 const XMLElement* connectivityNode = findDataArray_(cellsNode, "connectivity");
250 const XMLElement* offsetsNode = findDataArray_(cellsNode, "offsets");
251 const XMLElement* typesNode = findDataArray_(cellsNode, "types");
252
253 const auto connectivity = parseDataArray_<std::vector<unsigned int>>(connectivityNode);
254 const auto offsets = parseDataArray_<std::vector<unsigned int>>(offsetsNode);
255 const auto types = parseDataArray_<std::vector<unsigned int>>(typesNode);
256
257 if (verbose) std::cout << "Found " << offsets.size() << " elements." << std::endl;
258
259 unsigned int lastOffset = 0;
260 for (unsigned int i = 0; i < offsets.size(); ++i)
261 {
262 const auto geomType = vtkToDuneGeomType_(types[i]);
263 unsigned int offset = offsets[i];
264 std::vector<unsigned int> corners; corners.resize(offset-lastOffset);
265 for (unsigned int j = 0; j < offset-lastOffset; ++j)
266 corners[Dune::VTK::renumber(geomType, j)] = connectivity[lastOffset+j];
267 factory.insertElement(geomType, std::move(corners));
268 lastOffset = offset;
269 }
270 }
271 // for poly data
272 else if (linesNode)
273 {
274 // sanity check
275 if (Grid::dimension != 1)
276 DUNE_THROW(Dune::IOError, "Grid expects dimension " << Grid::dimension
277 << " but " << fileName_ << " contains a 1D grid.");
278
279 const XMLElement* connectivityNode = findDataArray_(linesNode, "connectivity");
280 const XMLElement* offsetsNode = findDataArray_(linesNode, "offsets");
281
282 const auto connectivity = parseDataArray_<std::vector<unsigned int>>(connectivityNode);
283 const auto offsets = parseDataArray_<std::vector<unsigned int>>(offsetsNode);
284
285 if (verbose) std::cout << "Found " << offsets.size() << " polylines." << std::endl;
286
287 unsigned int lastOffset = 0;
288 for (unsigned int i = 0; i < offsets.size(); ++i)
289 {
290 // a polyline can have many points in the VTK format
291 // split the line in segments with two points
292 unsigned int offset = offsets[i];
293 for (unsigned int j = 0; j < offset-lastOffset-1; ++j)
294 factory.insertElement(Dune::GeometryTypes::line,
295 std::vector<unsigned int>({ connectivity[lastOffset+j], connectivity[lastOffset+j+1] }));
296 lastOffset = offset;
297 }
298 }
299 else
300 DUNE_THROW(Dune::IOError, "No Cells or Lines element found in " << fileName_);
301 }
302
303 /*!
304 * \brief Read a grid data from a vtk/vtu/vtp file
305 * \param cellData the cell data arrays to be filled
306 * \param pointData the point data arrays to be filled
307 * \param verbose if the output should be verbose
308 */
309 void readGridData_(Data& cellData, Data& pointData, bool verbose = false) const
310 {
311 using namespace tinyxml2;
312
313 const XMLElement* pieceNode = getPieceNode_();
314 const XMLElement* cellDataNode = getDataNode_(pieceNode, DataType::cellData);
315 if (cellDataNode != nullptr)
316 {
317 const XMLElement* cellsNode = pieceNode->FirstChildElement("Cells");
318 const XMLElement* linesNode = pieceNode->FirstChildElement("Lines");
319
320 if (cellsNode)
321 {
322 const XMLElement* dataArray = cellDataNode->FirstChildElement("DataArray");
323 for (; dataArray != nullptr; dataArray = dataArray->NextSiblingElement("DataArray"))
324 {
325 const char *attributeText = dataArray->Attribute("Name");
326
327 if (attributeText == nullptr)
328 DUNE_THROW(Dune::IOError, "Couldn't get Name attribute of a cell data array.");
329
330 cellData[std::string(attributeText)] = parseDataArray_<std::vector<double>>(dataArray);
331
332 if (verbose)
333 std::cout << "Read cell data field " << attributeText << std::endl;
334 }
335 }
336 // for poly data
337 else if (linesNode)
338 {
339 // first parse all the cell data (each cell in this sense can be a polyline)
340 const XMLElement* dataArray = cellDataNode->FirstChildElement("DataArray");
341 if (dataArray)
342 {
343 Data polyLineCellData;
344 for (; dataArray != nullptr; dataArray = dataArray->NextSiblingElement("DataArray"))
345 {
346 const char *attributeText = dataArray->Attribute("Name");
347
348 if (attributeText == nullptr)
349 DUNE_THROW(Dune::IOError, "Couldn't get Name attribute of a cell data array.");
350
351 polyLineCellData[std::string(attributeText)] = parseDataArray_<std::vector<double>>(dataArray);
352
353 if (verbose)
354 std::cout << "Read cell data field " << attributeText << std::endl;
355 }
356
357 // a polyline can have many points in the VTK format
358 // we split the line in segments with two points
359 // so we also need to duplicate the cell data to fit the increased line number
360 const XMLElement* offsetsNode = findDataArray_(linesNode, "offsets");
361 const auto offsets = parseDataArray_<std::vector<unsigned int>>(offsetsNode);
362
363 if (offsets.size() != polyLineCellData.begin()->second.size())
364 DUNE_THROW(Dune::IOError, "Expected the same number of cell data entries (is "
365 << polyLineCellData.begin()->second.size()
366 << ") as polylines (" << offsets.size() << ")!");
367
368 // count the number of Dune cells to be able to resize the data vectors
369 unsigned int lastOffset = 0;
370 std::size_t numCells = 0;
371 for (unsigned int i = 0; i < offsets.size(); ++i)
372 {
373 unsigned int offset = offsets[i];
374 for (unsigned int j = 0; j < offset-lastOffset-1; ++j)
375 ++numCells;
376 lastOffset = offset;
377 }
378
379 // create the data arrays
380 for (const auto& dArray : polyLineCellData)
381 {
382 cellData[dArray.first] = std::vector<double>(numCells);
383 auto& cd = cellData[dArray.first];
384 const auto& pd = dArray.second;
385
386 lastOffset = 0;
387 std::size_t cellIdx = 0;
388 for (unsigned int i = 0; i < offsets.size(); ++i)
389 {
390 unsigned int offset = offsets[i];
391 for (unsigned int j = 0; j < offset-lastOffset-1; ++j)
392 cd[cellIdx++] = pd[i];
393 lastOffset = offset;
394 }
395 }
396 }
397 }
398 else
399 DUNE_THROW(Dune::IOError, "No Cells or Lines element found in " << fileName_);
400 }
401
402 const XMLElement* pointDataNode = getDataNode_(pieceNode, DataType::pointData);
403 if (pointDataNode != nullptr)
404 {
405 const XMLElement* dataArray = pointDataNode->FirstChildElement("DataArray");
406 for (; dataArray != nullptr; dataArray = dataArray->NextSiblingElement("DataArray"))
407 {
408 const char *attributeText = dataArray->Attribute("Name");
409
410 if (attributeText == nullptr)
411 DUNE_THROW(Dune::IOError, "Couldn't get Name attribute of a point data array.");
412
413 pointData[std::string(attributeText)] = parseDataArray_<std::vector<double>>(dataArray);
414
415 if (verbose)
416 std::cout << "Read point data field " << attributeText << std::endl;
417 }
418 }
419 }
420
421 /*!
422 * \brief Get the piece node of the XMLDocument
423 * \note Returns nullptr if the piece node wasn't found
424 */
425 const tinyxml2::XMLElement* getPieceNode_() const
426 { return getPieceNode_(doc_, fileName_); }
427
428 /*!
429 * \brief Get the piece node an xml document
430 * \note Returns nullptr if the piece node wasn't found
431 * \param doc an xml document
432 * \param fileName a file name the doc was created from
433 */
434 const tinyxml2::XMLElement* getPieceNode_(const tinyxml2::XMLDocument& doc, const std::string& fileName) const
435 {
436 using namespace tinyxml2;
437
438 const XMLElement* pieceNode = doc.FirstChildElement("VTKFile");
439 if (pieceNode == nullptr)
440 DUNE_THROW(Dune::IOError, "Couldn't get 'VTKFile' node in " << fileName << ".");
441
442 pieceNode = pieceNode->FirstChildElement("UnstructuredGrid");
443 if (pieceNode == nullptr)
444 pieceNode = doc.FirstChildElement("VTKFile")->FirstChildElement("PolyData");
445 if (pieceNode == nullptr)
446 pieceNode = doc.FirstChildElement("VTKFile")->FirstChildElement("PUnstructuredGrid");
447 if (pieceNode == nullptr)
448 pieceNode = doc.FirstChildElement("VTKFile")->FirstChildElement("PPolyData");
449 if (pieceNode == nullptr)
450 DUNE_THROW(Dune::IOError, "Couldn't get 'UnstructuredGrid', 'PUnstructuredGrid', 'PolyData', or 'PPolyData' node in " << fileName << ".");
451
452 return pieceNode->FirstChildElement("Piece");
453 }
454
455 /*!
456 * \brief Get the piece node of the XMLDocument
457 * \param pieceNode the pieceNode of the vtk file
458 * \param type the vtk data type (cell data or point data)
459 * \note Returns nullptr if the data node wasn't found
460 */
461 const tinyxml2::XMLElement* getDataNode_(const tinyxml2::XMLElement* pieceNode, const DataType& type) const
462 {
463 using namespace tinyxml2;
464
465 const XMLElement* dataNode = nullptr;
466 if (type == DataType::pointData)
467 dataNode = pieceNode->FirstChildElement("PointData");
468 else if (type == DataType::cellData)
469 dataNode = pieceNode->FirstChildElement("CellData");
470 else
471 DUNE_THROW(Dune::IOError, "Only cell and point data are supported.");
472
473 return dataNode;
474 }
475
476 /*!
477 * \brief Find a data array with a specific name
478 * \param dataNode a cell or point data node
479 * \param name the name of the data array to be found
480 * \note Returns nullptr if the data array wasn't found
481 */
482 const tinyxml2::XMLElement* findDataArray_(const tinyxml2::XMLElement* dataNode, const std::string& name) const
483 {
484 using namespace tinyxml2;
485
486 // loop over XML node siblings to find the correct data array
487 const XMLElement* dataArray = dataNode->FirstChildElement("DataArray");
488 for (; dataArray != nullptr; dataArray = dataArray->NextSiblingElement("DataArray"))
489 {
490 const char *attributeText = dataArray->Attribute("Name");
491
492 if (attributeText == nullptr)
493 DUNE_THROW(Dune::IOError, "Couldn't get Name attribute of a data array.");
494
495 if (attributeText == name)
496 break;
497 }
498
499 return dataArray;
500 }
501
502 /*!
503 * \brief Parses the text of a data array into a container
504 * \tparam Container a container type that has begin(), end(), push_back(), e.g. std::vector<double>
505 * \param dataArray the data array node to be parsed
506 */
507 template<class Container>
508 Container parseDataArray_(const tinyxml2::XMLElement* dataArray) const
509 {
510 std::stringstream dataStream(dataArray->GetText());
511 return readStreamToContainer<Container>(dataStream);
512 }
513
514 /*!
515 * \brief Return the Dune::GeometryType for a given VTK geometry type
516 * \param vtkCellType the vtk cell type
517 */
518 Dune::GeometryType vtkToDuneGeomType_(unsigned int vtkCellType) const
519 {
520 switch (vtkCellType)
521 {
522 case Dune::VTK::GeometryType::vertex: return Dune::GeometryTypes::vertex;
523 case Dune::VTK::GeometryType::line: return Dune::GeometryTypes::line;
524 case Dune::VTK::GeometryType::triangle: return Dune::GeometryTypes::triangle;
525 case Dune::VTK::GeometryType::quadrilateral: return Dune::GeometryTypes::quadrilateral;
526 case Dune::VTK::GeometryType::tetrahedron: return Dune::GeometryTypes::tetrahedron;
527 case Dune::VTK::GeometryType::hexahedron: return Dune::GeometryTypes::hexahedron;
528 case Dune::VTK::GeometryType::prism: return Dune::GeometryTypes::prism;
529 case Dune::VTK::GeometryType::pyramid: return Dune::GeometryTypes::pyramid;
530 default: DUNE_THROW(Dune::NotImplemented, "VTK cell type " << vtkCellType);
531 }
532 }
533
534 template<int dim, std::enable_if_t<(dim < 3), int> = 0>
535 std::vector<Dune::FieldVector<double, dim>>
536 adaptPointDimension_(std::vector<Dune::FieldVector<double, 3>>&& points3D) const
537 {
538 std::vector<Dune::FieldVector<double, dim>> points(points3D.size());
539 for (std::size_t i = 0; i < points.size(); ++i)
540 for (int j = 0; j < dim; ++j)
541 points[i][j] = points3D[i][j];
542 return points;
543 }
544
545 template<int dim, std::enable_if_t<(dim == 3), int> = 0>
546 std::vector<Dune::FieldVector<double, dim>>
547 adaptPointDimension_(std::vector<Dune::FieldVector<double, 3>>&& points3D) const
548 { return std::move(points3D); }
549
550 std::string fileName_; //!< the vtk file name
551 tinyxml2::XMLDocument doc_; //!< the xml document created from file with name fileName_
552 };
553
554 } // end namespace Dumux
555
556 #endif
557