OGS
NodePartitionedMeshReader.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
5
6#include <mpi.h>
7
8#include <fstream>
9#include <numeric>
10
11#include "BaseLib/FileTools.h"
12#include "BaseLib/Logging.h"
13#include "BaseLib/MPI.h"
14#include "BaseLib/RunTime.h"
16#include "MeshLib/MeshEnums.h"
17#include "MeshLib/Properties.h"
18
19// Check if the value can by converted to given type without overflow.
20template <typename VALUE, typename TYPE>
21bool is_safely_convertable(VALUE const& value)
22{
23 bool const result = value <= std::numeric_limits<TYPE>::max();
24 if (!result)
25 {
26 ERR("The value {:d} is too large for conversion.", value);
27 ERR("Maximum available size is {:d}.",
28 std::numeric_limits<TYPE>::max());
29 }
30 return result;
31}
32
33namespace MeshLib
34{
35namespace IO
36{
41
46
48{
49 int const count = 2;
50 int blocks[count] = {1, 3};
51 MPI_Datatype types[count] = {MPI_UNSIGNED_LONG, MPI_DOUBLE};
52 MPI_Aint displacements[count] = {0, sizeof(NodeData::index)};
53
54 MPI_Type_create_struct(count, blocks, displacements, types,
56 MPI_Type_commit(&_mpi_node_type);
57}
58
60 const std::string& file_name_base)
61{
62 BaseLib::RunTime timer;
63 timer.start();
64
65 // Always try binary file first
66 std::string const fname_new = file_name_base + "_partitioned_msh_cfg" +
67 std::to_string(mpi_.size) + ".bin";
68
69 if (!BaseLib::IsFileExisting(fname_new)) // binary file does not exist.
70 {
71 std::string const fname_ascii = file_name_base +
72 "_partitioned_msh_cfg" +
73 std::to_string(mpi_.size) + ".msh";
74 if (BaseLib::IsFileExisting(fname_ascii))
75 {
76 ERR("Reading of ASCII mesh file {:s} is not supported since OGS "
77 "version 6.3.3.",
78 fname_ascii);
79 }
80 OGS_FATAL("Required binary file {:s} does not exist.\n", fname_new);
81 }
82
83 INFO("Reading corresponding part of mesh data from binary file {:s} ...",
84 file_name_base);
85
86 MeshLib::NodePartitionedMesh* mesh = readMesh(file_name_base);
87
88 INFO("[time] Reading the mesh took {:f} s.", timer.elapsed());
89
90 MPI_Barrier(mpi_.communicator);
91
92 return mesh;
93}
94
95template <typename DATA>
96bool NodePartitionedMeshReader::readDataFromFile(std::string const& filename,
97 MPI_Offset offset,
98 MPI_Datatype type,
99 DATA& data) const
100{
101 // Check container size
103 {
104 ERR("The container size is too large for MPI_File_read() call.");
105 return false;
106 }
107
108 // Open file
109 MPI_File file;
110
111 char* filename_char = const_cast<char*>(filename.data());
112 int const file_status =
113 MPI_File_open(mpi_.communicator, filename_char, MPI_MODE_RDONLY,
114 MPI_INFO_NULL, &file);
115
116 if (file_status != 0)
117 {
118 ERR("Error opening file {:s}. MPI error code {:d}", filename,
119 file_status);
120 return false;
121 }
122
123 // Read data
124 char file_mode[] = "native";
125 MPI_File_set_view(file, offset, type, type, file_mode, MPI_INFO_NULL);
126 // The static cast is checked above.
127 MPI_File_read(file, data.data(), static_cast<int>(data.size()), type,
128 MPI_STATUS_IGNORE);
129 MPI_File_close(&file);
130
131 return true;
132}
133
135 const std::string& file_name_base)
136{
137 //----------------------------------------------------------------------------------
138 // Read headers
139 const std::string fname_header = file_name_base + "_partitioned_msh_";
140 const std::string fname_num_p_ext = std::to_string(mpi_.size) + ".bin";
141
142 // Read the config meta data from *cfg* file into struct PartitionedMeshInfo
143 // _mesh_info
144 if (!readDataFromFile(
145 fname_header + "cfg" + fname_num_p_ext,
146 static_cast<MPI_Offset>(static_cast<unsigned>(mpi_.rank) *
147 sizeof(_mesh_info)),
148 MPI_LONG, _mesh_info))
149 return nullptr;
150
151 //----------------------------------------------------------------------------------
152 // Read Nodes
153 std::vector<NodeData> nodes(_mesh_info.number_of_nodes);
154
155 if (!readDataFromFile(fname_header + "nod" + fname_num_p_ext,
156 static_cast<MPI_Offset>(_mesh_info.offset[2]),
157 _mpi_node_type, nodes))
158 return nullptr;
159
160 std::vector<MeshLib::Node*> mesh_nodes;
161 std::vector<unsigned long> glb_node_ids;
162 setNodes(nodes, mesh_nodes, glb_node_ids);
163
164 //----------------------------------------------------------------------------------
165 // Read non-ghost elements
166 std::vector<unsigned long> elem_data(_mesh_info.number_of_regular_elements +
167 _mesh_info.offset[0]);
168 if (!readDataFromFile(fname_header + "ele" + fname_num_p_ext,
169 static_cast<MPI_Offset>(_mesh_info.offset[3]),
170 MPI_LONG, elem_data))
171 return nullptr;
172
173 std::vector<MeshLib::Element*> mesh_elems(
174 _mesh_info.number_of_regular_elements +
175 _mesh_info.number_of_ghost_elements);
176 setElements(mesh_nodes, elem_data, mesh_elems);
177
178 //----------------------------------------------------------------------------------
179 // Read ghost element
180 std::vector<unsigned long> ghost_elem_data(
181 _mesh_info.number_of_ghost_elements + _mesh_info.offset[1]);
182
183 if (!readDataFromFile(fname_header + "ele_g" + fname_num_p_ext,
184 static_cast<MPI_Offset>(_mesh_info.offset[4]),
185 MPI_LONG, ghost_elem_data))
186 return nullptr;
187
188 const bool process_ghost = true;
189 setElements(mesh_nodes, ghost_elem_data, mesh_elems, process_ghost);
190
191 //----------------------------------------------------------------------------------
192 // read the properties
193 MeshLib::Properties p(readProperties(file_name_base));
194
195 return newMesh(BaseLib::extractBaseName(file_name_base), mesh_nodes,
196 glb_node_ids, mesh_elems, p);
197}
198
200 const std::string& file_name_base) const
201{
206 return p;
207}
208
210 const std::string& file_name_base, MeshLib::MeshItemType t,
211 MeshLib::Properties& p) const
212{
213 const std::string item_type = MeshLib::toString(t);
214
215 const std::string fname_cfg = file_name_base + "_partitioned_" + item_type +
216 "_properties_cfg" +
217 std::to_string(mpi_.size) + ".bin";
218 std::ifstream is(fname_cfg.c_str(), std::ios::binary | std::ios::in);
219 if (!is)
220 {
221 WARN(
222 "Could not open file '{:s}'.\n"
223 "\tYou can ignore this warning if the mesh does not contain {:s}-"
224 "wise property data.",
225 fname_cfg, item_type.data());
226 return;
227 }
228 std::size_t number_of_properties = 0;
229 is.read(reinterpret_cast<char*>(&number_of_properties),
230 sizeof(std::size_t));
231 std::vector<std::optional<MeshLib::IO::PropertyVectorMetaData>> vec_pvmd(
232 number_of_properties);
233 for (std::size_t i(0); i < number_of_properties; ++i)
234 {
236 if (!vec_pvmd[i])
237 {
238 OGS_FATAL(
239 "Error in NodePartitionedMeshReader::readProperties: "
240 "Could not read the meta data for the PropertyVector {:d}",
241 i);
242 }
243 }
244 for (std::size_t i(0); i < number_of_properties; ++i)
245 {
247 }
248 auto pos = is.tellg();
249 auto offset =
250 static_cast<long>(pos) +
251 static_cast<long>(mpi_.rank *
253 is.seekg(offset);
254 std::optional<MeshLib::IO::PropertyVectorPartitionMetaData> pvpmd(
256 bool const all_pvpmd_read_ok =
257 BaseLib::MPI::allreduce(static_cast<bool>(pvpmd), MPI_LOR, mpi_);
258 if (!all_pvpmd_read_ok)
259 {
260 OGS_FATAL(
261 "Error in NodePartitionedMeshReader::readProperties: "
262 "Could not read the partition meta data for the mpi process {:d}",
263 mpi_.rank);
264 }
265 DBUG("offset in the PropertyVector: {:d}", pvpmd->offset);
266 DBUG("{:d} tuples in partition.", pvpmd->number_of_tuples);
267 is.close();
268
269 const std::string fname_val = file_name_base + "_partitioned_" + item_type +
270 "_properties_val" +
271 std::to_string(mpi_.size) + ".bin";
272 is.open(fname_val.c_str(), std::ios::binary | std::ios::in);
273 if (!is)
274 {
275 ERR("Could not open file '{:s}'\n."
276 "\tYou can ignore this warning if the mesh does not contain {:s}-"
277 "wise property data.",
278 fname_val, item_type.data());
279 }
280
281 readDomainSpecificPartOfPropertyVectors(vec_pvmd, *pvpmd, t, is, p);
282}
283
285 std::vector<std::optional<MeshLib::IO::PropertyVectorMetaData>> const&
286 vec_pvmd,
289 std::istream& is,
290 MeshLib::Properties& p) const
291{
292 unsigned long global_offset = 0;
293 std::size_t const number_of_properties = vec_pvmd.size();
294 for (std::size_t i(0); i < number_of_properties; ++i)
295 {
296 DBUG("global offset: {:d}, offset within the PropertyVector: {:d}.",
297 global_offset,
298 global_offset + pvpmd.offset * vec_pvmd[i]->number_of_components *
299 vec_pvmd[i]->data_type_size_in_bytes);
300
301 // Special field data such as OGS_VERSION, IntegrationPointMetaData,
302 // etc., which are not "real" integration points, are copied "as is"
303 // (i.e. fully) for every partition.
304 if (vec_pvmd[i]->property_name.find("_ip") == std::string::npos &&
306 {
307 createSpecificPropertyVectorPart<char>(is, *vec_pvmd[i], t,
308 global_offset, p);
309
310 global_offset += vec_pvmd[i]->data_type_size_in_bytes *
311 vec_pvmd[i]->number_of_tuples;
312 continue;
313 }
314
315 if (vec_pvmd[i]->is_int_type)
316 {
317 if (vec_pvmd[i]->is_data_type_signed)
318 {
319 if (vec_pvmd[i]->data_type_size_in_bytes == sizeof(char))
320 {
321 createPropertyVectorPart<char>(is, *vec_pvmd[i], pvpmd, t,
322 global_offset, p);
323 }
324 else if (vec_pvmd[i]->data_type_size_in_bytes == sizeof(int))
325 {
326 createPropertyVectorPart<int>(is, *vec_pvmd[i], pvpmd, t,
327 global_offset, p);
328 }
329 else if (vec_pvmd[i]->data_type_size_in_bytes == sizeof(long))
330 createPropertyVectorPart<long>(is, *vec_pvmd[i], pvpmd, t,
331 global_offset, p);
332 else
333 {
334 WARN(
335 "Implementation for reading signed integer property "
336 "vector '{:s}' is not available.",
337 vec_pvmd[i]->property_name);
338 }
339 }
340 else
341 {
342 if (vec_pvmd[i]->data_type_size_in_bytes ==
343 sizeof(unsigned char))
344 {
346 is, *vec_pvmd[i], pvpmd, t, global_offset, p);
347 }
348 else if (vec_pvmd[i]->data_type_size_in_bytes ==
349 sizeof(unsigned int))
351 is, *vec_pvmd[i], pvpmd, t, global_offset, p);
352 else if (vec_pvmd[i]->data_type_size_in_bytes ==
353 sizeof(unsigned long))
355 is, *vec_pvmd[i], pvpmd, t, global_offset, p);
356 else
357 {
358 WARN(
359 "Implementation for reading unsigned property vector "
360 "'{:s}' is not available.",
361 vec_pvmd[i]->property_name);
362 }
363 }
364 }
365 else
366 {
367 if (vec_pvmd[i]->data_type_size_in_bytes == sizeof(float))
368 createPropertyVectorPart<float>(is, *vec_pvmd[i], pvpmd, t,
369 global_offset, p);
370 else if (vec_pvmd[i]->data_type_size_in_bytes == sizeof(double))
371 createPropertyVectorPart<double>(is, *vec_pvmd[i], pvpmd, t,
372 global_offset, p);
373 else
374 {
375 WARN(
376 "Implementation for reading floating point property vector "
377 "'{:s}' is not available.",
378 vec_pvmd[i]->property_name);
379 }
380 }
381 global_offset += vec_pvmd[i]->data_type_size_in_bytes *
382 vec_pvmd[i]->number_of_tuples *
383 vec_pvmd[i]->number_of_components;
384 }
385}
386
388 std::string const& mesh_name,
389 std::vector<MeshLib::Node*> const& mesh_nodes,
390 std::vector<unsigned long> const& glb_node_ids,
391 std::vector<MeshLib::Element*> const& mesh_elems,
392 MeshLib::Properties const& properties) const
393{
394 std::vector<std::size_t> const gathered_n_regular_base_nodes =
395 BaseLib::MPI::allgather(_mesh_info.number_of_regular_base_nodes, mpi_);
396
397 std::vector<std::size_t> n_regular_base_nodes_at_rank =
398 BaseLib::sizesToOffsets(gathered_n_regular_base_nodes);
399
400 std::size_t const n_regular_high_order_nodes =
401 _mesh_info.number_of_regular_nodes -
402 _mesh_info.number_of_regular_base_nodes;
403 std::vector<std::size_t> const gathered_n_regular_high_order_nodes =
404 BaseLib::MPI::allgather(n_regular_high_order_nodes, mpi_);
405
406 std::vector<std::size_t> n_regular_high_order_nodes_at_rank =
407 BaseLib::sizesToOffsets(gathered_n_regular_high_order_nodes);
408
410 mesh_name, mesh_nodes, glb_node_ids, mesh_elems, properties,
411 _mesh_info.number_of_global_base_nodes,
412 _mesh_info.number_of_global_nodes, _mesh_info.number_of_regular_nodes,
413 std::move(n_regular_base_nodes_at_rank),
414 std::move(n_regular_high_order_nodes_at_rank));
415}
416
418 const std::vector<NodeData>& node_data,
419 std::vector<MeshLib::Node*>& mesh_node,
420 std::vector<unsigned long>& glb_node_ids) const
421{
422 mesh_node.resize(_mesh_info.number_of_nodes);
423 glb_node_ids.resize(_mesh_info.number_of_nodes);
424
425 for (std::size_t i = 0; i < mesh_node.size(); i++)
426 {
427 NodeData const& nd = node_data[i];
428 glb_node_ids[i] = nd.index;
429 mesh_node[i] = new MeshLib::Node(nd.x, nd.y, nd.z, i);
430 }
431}
432
434 const std::vector<MeshLib::Node*>& mesh_nodes,
435 const std::vector<unsigned long>& elem_data,
436 std::vector<MeshLib::Element*>& mesh_elems, const bool ghost) const
437{
438 // Number of elements, either ghost or regular
439 unsigned long const ne = ghost ? _mesh_info.number_of_ghost_elements
440 : _mesh_info.number_of_regular_elements;
441 unsigned long const id_offset_ghost =
442 ghost ? _mesh_info.number_of_regular_elements : 0;
443
444 for (unsigned long i = 0; i < ne; i++)
445 {
446 unsigned long id_offset_elem = elem_data[i];
447
448 // Unused for now, keep for elem_data documentation purpose here.
449 {
450 const unsigned mat_idx =
451 static_cast<unsigned>(elem_data[id_offset_elem++]);
452 (void)mat_idx;
453 }
454 const unsigned long e_type = elem_data[id_offset_elem++];
455 unsigned long const nnodes = elem_data[id_offset_elem++];
456
457 MeshLib::Node** elem_nodes = new MeshLib::Node*[nnodes];
458 for (unsigned long k = 0; k < nnodes; k++)
459 elem_nodes[k] = mesh_nodes[elem_data[id_offset_elem++]];
460
461 // The element types below are defined by the MeshLib::CellType.
462 switch (static_cast<CellType>(e_type))
463 {
464 case CellType::POINT1:
465 mesh_elems[i + id_offset_ghost] =
466 new MeshLib::Point(elem_nodes);
467 break;
468 case CellType::LINE2:
469 mesh_elems[i + id_offset_ghost] = new MeshLib::Line(elem_nodes);
470 break;
471 case CellType::LINE3:
472 mesh_elems[i + id_offset_ghost] =
473 new MeshLib::Line3(elem_nodes);
474 break;
475 case CellType::QUAD4:
476 mesh_elems[i + id_offset_ghost] = new MeshLib::Quad(elem_nodes);
477 break;
478 case CellType::QUAD8:
479 mesh_elems[i + id_offset_ghost] =
480 new MeshLib::Quad8(elem_nodes);
481 break;
482 case CellType::QUAD9:
483 mesh_elems[i + id_offset_ghost] =
484 new MeshLib::Quad9(elem_nodes);
485 break;
486 case CellType::HEX8:
487 mesh_elems[i + id_offset_ghost] = new MeshLib::Hex(elem_nodes);
488 break;
489 case CellType::HEX20:
490 mesh_elems[i + id_offset_ghost] =
491 new MeshLib::Hex20(elem_nodes);
492 break;
493 case CellType::HEX27:
494 OGS_FATAL(
495 "NodePartitionedMeshReader: construction of HEX27 element "
496 "with id {:d} is not implemented.",
497 i);
498 break;
499 case CellType::TRI3:
500 mesh_elems[i + id_offset_ghost] = new MeshLib::Tri(elem_nodes);
501 break;
502 case CellType::TRI6:
503 mesh_elems[i + id_offset_ghost] = new MeshLib::Tri6(elem_nodes);
504 break;
505 case CellType::TET4:
506 mesh_elems[i + id_offset_ghost] = new MeshLib::Tet(elem_nodes);
507 break;
508 case CellType::TET10:
509 mesh_elems[i + id_offset_ghost] =
510 new MeshLib::Tet10(elem_nodes);
511 break;
512 case CellType::PRISM6:
513 mesh_elems[i + id_offset_ghost] =
514 new MeshLib::Prism(elem_nodes);
515 break;
517 mesh_elems[i + id_offset_ghost] =
518 new MeshLib::Prism15(elem_nodes);
519 break;
521 mesh_elems[i + id_offset_ghost] =
522 new MeshLib::Pyramid(elem_nodes);
523 break;
525 mesh_elems[i + id_offset_ghost] =
526 new MeshLib::Pyramid13(elem_nodes);
527 break;
529 OGS_FATAL(
530 "NodePartitionedMeshReader: construction of INVALID "
531 "element type with id {:d} is not possible.",
532 i);
533 break;
534 default:
535 OGS_FATAL(
536 "NodePartitionedMeshReader: construction of element type "
537 "{:d} is not implemented.",
538 e_type);
539 }
540 }
541}
542} // namespace IO
543} // namespace MeshLib
#define OGS_FATAL(...)
Definition Error.h:19
void INFO(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:28
void DBUG(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:22
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:40
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:34
bool is_safely_convertable(VALUE const &value)
Count the running time.
Definition RunTime.h:18
double elapsed() const
Get the elapsed time in seconds.
Definition RunTime.h:31
void start()
Start the timer.
Definition RunTime.h:21
void createPropertyVectorPart(std::istream &is, MeshLib::IO::PropertyVectorMetaData const &pvmd, MeshLib::IO::PropertyVectorPartitionMetaData const &pvpmd, MeshLib::MeshItemType t, unsigned long global_offset, MeshLib::Properties &p) const
void setElements(const std::vector< MeshLib::Node * > &mesh_nodes, const std::vector< unsigned long > &elem_data, std::vector< MeshLib::Element * > &mesh_elems, const bool ghost=false) const
Set mesh elements from a temporary array containing node data read from file.
bool readDataFromFile(std::string const &filename, MPI_Offset offset, MPI_Datatype type, DATA &data) const
MeshLib::NodePartitionedMesh * readMesh(const std::string &file_name_base)
Create a NodePartitionedMesh object, read binary mesh data in the manner of parallel,...
MeshLib::NodePartitionedMesh * read(const std::string &file_name_base)
Create a NodePartitionedMesh object, read data to it, and return a pointer to it. Data files are in b...
void setNodes(const std::vector< NodeData > &node_data, std::vector< MeshLib::Node * > &mesh_node, std::vector< unsigned long > &glb_node_ids) const
Set mesh nodes from a temporary array containing node data read from file.
void createSpecificPropertyVectorPart(std::istream &is, MeshLib::IO::PropertyVectorMetaData const &pvmd, MeshLib::MeshItemType t, unsigned long global_offset, MeshLib::Properties &p) const
MPI_Datatype _mpi_node_type
MPI data type for struct NodeData.
void registerNodeDataMpiType()
Define MPI data type for NodeData struct.
struct MeshLib::IO::NodePartitionedMeshReader::PartitionedMeshInfo _mesh_info
MeshLib::Properties readProperties(const std::string &file_name_base) const
MeshLib::NodePartitionedMesh * newMesh(std::string const &mesh_name, std::vector< MeshLib::Node * > const &mesh_nodes, std::vector< unsigned long > const &glb_node_ids, std::vector< MeshLib::Element * > const &mesh_elems, MeshLib::Properties const &properties) const
Create a new mesh of NodePartitionedMesh after reading and processing the data.
BaseLib::MPI::Mpi mpi_
Pointer to MPI communicator, the rank and the size.
void readDomainSpecificPartOfPropertyVectors(std::vector< std::optional< MeshLib::IO::PropertyVectorMetaData > > const &vec_pvmd, MeshLib::IO::PropertyVectorPartitionMetaData const &pvpmd, MeshLib::MeshItemType t, std::istream &is, MeshLib::Properties &p) const
Property manager on mesh items. Class Properties manages scalar, vector or matrix properties....
static T allreduce(T const &value, MPI_Op const &mpi_op, Mpi const &mpi)
Definition MPI.h:122
static std::vector< T > allgather(T const &value, Mpi const &mpi)
Definition MPI.h:98
bool IsFileExisting(const std::string &strFilename)
Returns true if given file exists.
Definition FileTools.cpp:23
std::vector< ranges::range_value_t< R > > sizesToOffsets(R const &sizes)
Definition Algorithm.h:276
std::string extractBaseName(std::string const &pathname)
void writePropertyVectorMetaData(std::ostream &os, PropertyVectorMetaData const &pvmd)
std::optional< PropertyVectorMetaData > readPropertyVectorMetaData(std::istream &is)
std::optional< PropertyVectorPartitionMetaData > readPropertyVectorPartitionMetaData(std::istream &is)
TemplateElement< MeshLib::QuadRule9 > Quad9
Definition Quad.h:19
TemplateElement< MeshLib::TetRule10 > Tet10
Definition Tet.h:15
CellType
Types of mesh elements supported by OpenGeoSys.
Definition MeshEnums.h:53
TemplateElement< MeshLib::HexRule20 > Hex20
Definition Hex.h:15
TemplateElement< MeshLib::TetRule4 > Tet
Definition Tet.h:14
TemplateElement< MeshLib::LineRule2 > Line
Definition Line.h:14
TemplateElement< MeshLib::QuadRule8 > Quad8
Definition Quad.h:18
TemplateElement< MeshLib::PyramidRule13 > Pyramid13
Definition Pyramid.h:15
TemplateElement< MeshLib::QuadRule4 > Quad
Definition Quad.h:17
TemplateElement< MeshLib::TriRule3 > Tri
Definition Tri.h:15
TemplateElement< MeshLib::PyramidRule5 > Pyramid
Definition Pyramid.h:14
TemplateElement< MeshLib::PrismRule6 > Prism
Definition Prism.h:14
TemplateElement< PointRule1 > Point
TemplateElement< MeshLib::PrismRule15 > Prism15
Definition Prism.h:15
TemplateElement< MeshLib::TriRule6 > Tri6
Definition Tri.h:16
static constexpr char const * toString(const MeshItemType t)
Returns a char array for a specific MeshItemType.
Definition MeshEnums.h:25
TemplateElement< MeshLib::LineRule3 > Line3
Definition Line.h:15
TemplateElement< MeshLib::HexRule8 > Hex
Definition Hex.h:14
struct NodeData used for parallel reading and also partitioning
Definition NodeData.h:12
std::size_t index
Global node index.
Definition NodeData.h:20