DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
Mesh_ReadSerializeDistributed.cpp
Go to the documentation of this file.
1#include "Mesh.hpp"
3#include "Metis.hpp"
4
5#include <unordered_set>
6
7namespace DNDS::Geom
8{
9 /**
10 * Helper: read a single ArrayPair's father using EvenSplit offset.
11 * Creates father+son, reads father from H5 with even-split.
12 * The son is allocated but empty (size 0).
13 */
14 template <class TArray>
15 static void EvenSplitReadFather(
16 ssp<TArray> &father, ssp<TArray> &son,
17 const MPIInfo &mpi,
19 const std::string &name)
20 {
21 father = std::make_shared<TArray>(mpi);
22 son = std::make_shared<TArray>(mpi);
23 auto offset = Serializer::ArrayGlobalOffset_EvenSplit;
24 father->ReadSerializer(serializerP, name, offset);
25 }
26
27 /// Overload for arrays that need CommType/CommMult (ElemInfo, NodePeriodicBits).
28 template <class TArray>
29 static void EvenSplitReadFather(
30 ssp<TArray> &father, ssp<TArray> &son,
31 const MPIInfo &mpi,
33 const std::string &name,
34 MPI_Datatype commType, int commMult)
35 {
36 father = std::make_shared<TArray>(commType, commMult, mpi);
37 son = std::make_shared<TArray>(commType, commMult, mpi);
38 auto offset = Serializer::ArrayGlobalOffset_EvenSplit;
39 father->ReadSerializer(serializerP, name, offset);
40 }
41
45 const std::string &name,
47 {
49 "ReadSerializeAndDistribute requires a collective (H5) serializer");
50
51 if (mpi.rank == 0)
52 log() << "UnstructuredMesh === ReadSerializeAndDistribute: begin" << std::endl;
53
54 auto cwd = serializerP->GetCurrentPath();
55 serializerP->GoToPath(name);
56
57 /************************************************************/
58 // Step 0: read scalar metadata
59 /************************************************************/
60 {
61 std::string meshRead;
62 index dimRead{0}, sizeRead{0};
64 serializerP->ReadString("mesh", meshRead);
65 serializerP->ReadIndex("dim", dimRead);
66 serializerP->ReadIndex("MPISize", sizeRead);
67 serializerP->ReadInt("isPeriodic", isPeriodicRead);
69 DNDS_assert(meshRead == "UnstructuredMesh");
71 }
72
73 /************************************************************/
74 // Step 1: even-split read of all primary arrays
75 /************************************************************/
76 if (mpi.rank == 0)
77 log() << "UnstructuredMesh === ReadSerializeAndDistribute: even-split read" << std::endl;
78
79 // Navigate into "coords/father" etc. -- ArrayPair::ReadSerialize
80 // expects to navigate into name/father. We replicate the read
81 // logic but with EvenSplit offset.
82 // NOTE: GoToPath("..") doesn't work in H5 serializer, so we
83 // save/restore absolute paths using GetCurrentPath().
84 auto innerCwd = serializerP->GetCurrentPath();
85
86 auto readFatherAt = [&](auto &father, auto &son, const std::string &group)
87 {
88 serializerP->GoToPath(innerCwd);
89 serializerP->GoToPath(group);
90 EvenSplitReadFather(father, son, mpi, serializerP, "father");
91 };
92
93 auto readFatherAtTyped = [&](auto &father, auto &son, const std::string &group,
95 {
96 serializerP->GoToPath(innerCwd);
97 serializerP->GoToPath(group);
98 EvenSplitReadFather(father, son, mpi, serializerP, "father", commType, commMult);
99 };
100
101 readFatherAt(coords.father, coords.son, "coords");
108
109 if (isPeriodic)
110 {
115 serializerP->GoToPath(innerCwd);
116 periodicInfo.ReadSerializer(serializerP, "periodicInfo");
117 }
118
122
123 serializerP->GoToPath(cwd);
124
125 // At this point we have evenly-split data. Node indices in cell2node/bnd2node
126 // point to the ORIGINAL global numbering (as written). The coords are evenly split
127 // by the ORIGINAL node numbering. So cell2node already points to valid global node
128 // indices, and coords.father[i] corresponds to global node index
129 // (coordsGlobalStart + i). This is exactly the state adjPrimaryState == Adj_PointToGlobal.
131
132 if (mpi.rank == 0)
133 log() << "UnstructuredMesh === ReadSerializeAndDistribute: even-split read done, "
134 << "nCellLocal=" << cell2node.father->Size()
135 << " nNodeLocal=" << coords.father->Size()
136 << " nBndLocal=" << bnd2node.father->Size() << std::endl;
137
138 /************************************************************/
139 // Step 2: build distributed cell2cell (node-neighbor)
140 // Reuse existing methods that operate on distributed data
141 // with adjPrimaryState == Adj_PointToGlobal.
142 /************************************************************/
143 if (mpi.rank == 0)
144 log() << "UnstructuredMesh === ReadSerializeAndDistribute: building distributed cell2cell" << std::endl;
145
148
149 // cell2cell is now the node-neighbor adjacency (global indices).
150 // bnd2cell is also built.
151
152 /************************************************************/
153 // Step 3: filter cell2cell to facial neighbors only
154 // (shared O1 vertices >= dim)
155 // Need ghost cell2node and cellElemInfo to check intersection.
156 /************************************************************/
157 if (mpi.rank == 0)
158 log() << "UnstructuredMesh === ReadSerializeAndDistribute: building facial cell2cell" << std::endl;
159
160 // Temporarily pull ghost cell2node and cellElemInfo for the
161 // cells referenced in cell2cell.
162 {
164 cell2cell.trans.createFatherGlobalMapping();
165
166 std::vector<index> ghostCells;
167 for (index iCell = 0; iCell < cell2cell.father->Size(); iCell++)
168 for (rowsize ic2c = 0; ic2c < cell2cell.father->RowSize(iCell); ic2c++)
169 {
171 MPI_int rank;
172 index val;
173 if (!cell2cell.trans.pLGlobalMapping->search(iCellOther, rank, val))
174 DNDS_assert_info(false, "search failed");
175 if (rank != mpi.rank)
176 ghostCells.push_back(iCellOther);
177 }
178
180 cell2node.trans.createFatherGlobalMapping();
182 cellElemInfo.trans.createFatherGlobalMapping();
183
184 // Build temporary ghost mapping on cell2cell, borrow for cell2node/cellElemInfo
185 cell2cell.trans.createGhostMapping(ghostCells);
186 cell2node.trans.BorrowGGIndexing(cell2cell.trans);
187 cellElemInfo.trans.BorrowGGIndexing(cell2cell.trans);
188
189 cell2cell.trans.createMPITypes();
190 cell2node.trans.createMPITypes();
191 cellElemInfo.trans.createMPITypes();
192
193 cell2cell.trans.pullOnce();
194 cell2node.trans.pullOnce();
195 cellElemInfo.trans.pullOnce();
196 }
197
198 // Now build the facial subset.
199 // cell2cell.father + son are populated. For each local cell's
200 // neighbor, get its vertex set via cell2node (father or son) and
201 // check intersection size.
202 auto cell2cellFacialTmp = make_ssp<tAdj::element_type>(ObjName{"cell2cellFacialTmp"}, mpi);
203 cell2cellFacialTmp->Resize(cell2cell.father->Size(), 6);
204
205 for (index iCell = 0; iCell < cell2cell.father->Size(); iCell++)
206 {
207 auto elemI = Elem::Element{cellElemInfo(iCell, 0).getElemType()};
208 rowsize nVertI = elemI.GetNumVertices();
209 std::vector<index> vertI(cell2node[iCell].begin(),
210 cell2node[iCell].begin() + nVertI);
211 std::sort(vertI.begin(), vertI.end());
212
213 std::vector<index> facialNeighbors;
214 facialNeighbors.reserve(6);
215 for (rowsize ic2c = 0; ic2c < cell2cell.father->RowSize(iCell); ic2c++)
216 {
218
219 // Find iCellOther in the father+son appended index
220 auto [found, rank, localAppend] =
221 cell2cell.trans.pLGhostMapping->search_indexAppend(iCellOther);
222 DNDS_assert_info(found, "cell2cell neighbor not found in ghost mapping");
223
224 auto elemJ = Elem::Element{cellElemInfo(localAppend, 0).getElemType()};
225 rowsize nVertJ = elemJ.GetNumVertices();
226 std::vector<index> vertJ(cell2node[localAppend].begin(),
227 cell2node[localAppend].begin() + nVertJ);
228 std::sort(vertJ.begin(), vertJ.end());
229
230 std::vector<index> intersect;
231 intersect.reserve(9);
232 std::set_intersection(vertI.begin(), vertI.end(),
233 vertJ.begin(), vertJ.end(),
234 std::back_inserter(intersect));
235 if (static_cast<int>(intersect.size()) >= dim)
236 facialNeighbors.push_back(iCellOther);
237 }
238 cell2cellFacialTmp->ResizeRow(iCell, facialNeighbors.size());
241 }
242 cell2cellFacialTmp->Compress();
243
244 /************************************************************/
245 // Step 4: ParMetis repartition
246 /************************************************************/
247 if (mpi.rank == 0)
248 log() << "UnstructuredMesh === ReadSerializeAndDistribute: ParMetis repartition" << std::endl;
249
250 cell2cellFacialTmp->createGlobalMapping();
251 idx_t nPart = mpi.size;
252
253 std::vector<idx_t> vtxdist(mpi.size + 1);
254 for (MPI_int r = 0; r <= mpi.size; r++)
255 vtxdist[r] = _METIS::indexToIdx(cell2cellFacialTmp->pLGlobalMapping->ROffsets().at(r));
256
257 std::vector<idx_t> xadj(cell2cellFacialTmp->Size() + 1);
258 for (index i = 0; i <= cell2cellFacialTmp->Size(); i++)
259 xadj[i] = _METIS::indexToIdx(cell2cellFacialTmp->rowPtr(i) - cell2cellFacialTmp->rowPtr(0));
260
261 std::vector<idx_t> adjncy(xadj.back());
262 for (index i = 0; i < xadj.back(); i++)
263 adjncy[i] = _METIS::indexToIdx(cell2cellFacialTmp->data()[i]);
264
265 if (adjncy.empty())
266 adjncy.resize(1, -1); // cope with zero-sized data
267
268 std::vector<MPI_int> cellPartition(cell2cellFacialTmp->Size());
269
270 if (nPart > 1)
271 {
272 // Check that every rank has > 0 cells for ParMetis
273 for (int i = 0; i < mpi.size; i++)
274 DNDS_assert_info(vtxdist[i + 1] - vtxdist[i] > 0,
275 "ParMetis requires > 0 cells on each proc");
276
277 idx_t nCon{1};
278 idx_t wgtflag{0}, numflag{0};
279 std::vector<real_t> tpWeights(static_cast<size_t>(nPart) * nCon, 1.0 / nPart);
280 real_t ubVec[1]{1.05};
281 idx_t optsC[3]{1, 0, static_cast<idx_t>(partitionOptions.metisSeed)};
283 std::vector<idx_t> partOut(cell2cellFacialTmp->Size());
284 if (partOut.empty())
285 partOut.resize(1, 0);
286
288 vtxdist.data(), xadj.data(), adjncy.data(),
290 &nCon, &nPart, tpWeights.data(), ubVec, optsC,
291 &objval, partOut.data(), &mpi.comm);
293 fmt::format("ParMETIS_V3_PartKway returned {}", ret));
294
295 for (index i = 0; i < cell2cellFacialTmp->Size(); i++)
296 cellPartition[i] = static_cast<MPI_int>(partOut[i]);
297 }
298 else
299 {
300 std::fill(cellPartition.begin(), cellPartition.end(), 0);
301 }
302
303 // Print partition stats
304 {
305 std::vector<index> localPartCnt(nPart, 0);
306 for (auto p : cellPartition)
307 localPartCnt.at(p)++;
308 std::vector<index> globalPartCnt(nPart, 0);
310 if (mpi.rank == 0)
311 {
312 auto [minIt, maxIt] = std::minmax_element(globalPartCnt.begin(), globalPartCnt.end());
313 index nCellGlobal = 0;
314 for (auto c : globalPartCnt)
315 nCellGlobal += c;
316 log() << "UnstructuredMesh === ReadSerializeAndDistribute: partition done, "
317 << fmt::format("nCellGlobal [{}], ave [{}], min [{}], max [{}], ratio [{:.4f}]",
318 nCellGlobal, real(nCellGlobal) / nPart, *minIt, *maxIt, real(*minIt) / *maxIt)
319 << std::endl;
320 }
321 }
322
323 /************************************************************/
324 // Step 5: redistribute cells, nodes, bnds to new partition
325 // using ArrayTransformer push-based transfer.
326 // Reuse the helper templates from Mesh_PartitionHelpers.hpp.
327 /************************************************************/
328 if (mpi.rank == 0)
329 log() << "UnstructuredMesh === ReadSerializeAndDistribute: redistributing" << std::endl;
330
331 // 5a: derive node and bnd partitions
332 // Node partition: each node goes to the first (min) cell partition that claims it.
333 std::vector<MPI_int> nodePartition(coords.father->Size(), static_cast<MPI_int>(INT32_MAX));
334 for (index iCell = 0; iCell < cell2node.father->Size(); iCell++)
335 {
336 for (rowsize ic2n = 0; ic2n < cell2node.father->RowSize(iCell); ic2n++)
337 {
339 MPI_int rank;
340 index val;
341 bool found = coords.father->pLGlobalMapping->search(iNode, rank, val);
343 if (rank == mpi.rank)
344 nodePartition[val] = std::min(nodePartition[val], cellPartition.at(iCell));
345 }
346 }
347 // Nodes not claimed by any local cell: assign to this rank
348 // (rare with even-split; correct partition will be set by caller's rebuild).
349 for (auto &p : nodePartition)
350 if (p == static_cast<MPI_int>(INT32_MAX))
351 p = 0;
352
353 // Bnd partition: bnd goes to the same rank as its owner cell.
354 // Bnd partition:
355 // bnd2cell was built by RecoverCell2CellAndBnd2Cell in Step 2.
356 // bnd2cell(iBnd, 0) is the global index of the owner cell, which
357 // may be on another rank in the current even-split. Build a distributed
358 // lookup: for each bnd's owner cell, find its target partition.
359 //
360 // Strategy: create a tAdj1 array holding cellPartition as global data,
361 // pull ghosts for the owner cells needed by our bnds, then look up.
363 auto cellPartArrGhost = make_ssp<tAdj1::element_type>(ObjName{"cellPartArrGhost"}, mpi);
364 cellPartArr->Resize(cell2node.father->Size());
365 for (index i = 0; i < cellPartArr->Size(); i++)
366 (*cellPartArr)(i, 0) = static_cast<index>(cellPartition.at(i));
367 cellPartArr->createGlobalMapping();
368
369 // Gather ghost cell indices needed by our bnds' owner cells
370 std::vector<index> bndOwnerGhostQuery;
371 for (index iBnd = 0; iBnd < bnd2node.father->Size(); iBnd++)
372 {
376 bool found = cellPartArr->pLGlobalMapping->search(iOwnerCell, ownerRank, ownerVal);
378 if (ownerRank != mpi.rank)
380 }
383 cellPartTrans.createGhostMapping(bndOwnerGhostQuery);
384 cellPartTrans.createMPITypes();
385 cellPartTrans.pullOnce();
386
387 std::vector<MPI_int> bndPartition(bnd2node.father->Size());
388 for (index iBnd = 0; iBnd < bnd2node.father->Size(); iBnd++)
389 {
392 fmt::format("bnd {} has no owner cell after RecoverCell2CellAndBnd2Cell", iBnd));
395 bool foundOwner = cellPartArr->pLGlobalMapping->search(iOwnerCell, ownerRank, ownerVal);
398 if (ownerRank == mpi.rank)
399 targetPart = (*cellPartArr)(ownerVal, 0);
400 else
401 {
402 auto [found, gRank, gVal] = cellPartTrans.pLGhostMapping->search_indexAppend(iOwnerCell);
404 if (gRank == -1)
405 targetPart = (*cellPartArr)(gVal, 0);
406 else
407 targetPart = (*cellPartArrGhost)(gVal - cellPartArr->Size(), 0);
408 }
409 bndPartition[iBnd] = static_cast<MPI_int>(targetPart);
410 }
411
412 // Now free temporary adjacencies no longer needed.
413 cell2cell.father.reset();
414 cell2cell.son.reset();
415 cell2cellFacialTmp.reset();
416 node2cell.father.reset();
417 node2cell.son.reset();
418 node2bnd.father.reset();
419 node2bnd.son.reset();
420 bnd2cell.father.reset();
421 bnd2cell.son.reset();
422
423 // 5b: compute push indices and serial-to-global reordering
424 std::vector<index> cell_push, cell_pushStart;
425 std::vector<index> node_push, node_pushStart;
426 std::vector<index> bnd_push, bnd_pushStart;
430
431 std::vector<index> cell_Serial2Global, node_Serial2Global;
434
435 // 5c: convert adjacency indices to new global numbering
438
439 // 5d: transfer all arrays to new partition
440 // Save old fathers, create new ones, push/pull.
441 auto coordsOld = coords.father;
445 // bnd2cell is not serialized; rebuilt by caller's RecoverCell2CellAndBnd2Cell
450
451 coords.InitPair("coords", mpi);
452 cell2node.InitPair("cell2node", mpi);
454 bnd2node.InitPair("bnd2node", mpi);
455 // bnd2cell is not serialized; rebuilt by caller's RecoverCell2CellAndBnd2Cell
457 cell2cellOrig.InitPair("cell2cellOrig", mpi);
458 node2nodeOrig.InitPair("node2nodeOrig", mpi);
459 bnd2bndOrig.InitPair("bnd2bndOrig", mpi);
460
463
467
471
472 // periodic arrays
473 if (isPeriodic)
474 {
481 }
482
484
485 /************************************************************/
486 // Step 6: log final stats and return
487 /************************************************************/
488 // createGlobalMapping so NumXxxGlobal() works for logging
489 coords.father->createGlobalMapping();
490 cell2node.father->createGlobalMapping();
491 bnd2node.father->createGlobalMapping();
492
493
494 {
495 index nCellG = this->NumCellGlobal();
496 index nNodeG = this->NumNodeGlobal();
497 index nBndG = this->NumBndGlobal();
498 if (mpi.rank == 0)
499 {
500 log() << "UnstructuredMesh === ReadSerializeAndDistribute done, "
501 << fmt::format("nCellGlobal [{}], nNodeGlobal [{}], nBndGlobal [{}]",
503 << std::endl;
504 }
505 MPISerialDo(mpi, [&]()
506 { log() << fmt::format(" Rank {}: nCell {}, nNode {}, nBnd {}",
507 mpi.rank, this->NumCell(), this->NumNode(), this->NumBnd())
508 << std::endl; });
509 }
510 }
511
512} // namespace DNDS::Geom
#define DNDS_assert_info(expr, info)
Debug-only assertion with an extra std::string info message.
Definition Errors.hpp:113
#define DNDS_assert(expr)
Debug-only assertion (compiled out when DNDS_NDEBUG is defined). Prints the expression + file/line + ...
Definition Errors.hpp:108
#define DNDS_check_throw_info(expr, info)
Same as DNDS_check_throw but attaches a user-supplied info message to the thrown std::runtime_error.
Definition Errors.hpp:96
Internal helpers for mesh partition redistribution.
Ghost-communication engine for a father / son ParArray pair.
void Partition2LocalIdx(const std::vector< TPartitionIdx > &partition, std::vector< DNDS::index > &localPush, std::vector< DNDS::index > &localPushStart, const DNDS::MPIInfo &mpi)
void Partition2Serial2Global(const std::vector< TPartitionIdx > &partition, std::vector< DNDS::index > &serial2Global, const DNDS::MPIInfo &mpi, DNDS::MPI_int nPart)
void TransferDataSerial2Global(TArr &arraySerial, TArr &arrayDist, const std::vector< DNDS::index > &pushIndex, const std::vector< DNDS::index > &pushIndexStart, const DNDS::MPIInfo &mpi)
void ConvertAdjSerial2Global(TAdj &arraySerialAdj, const std::vector< DNDS::index > &partitionJSerial2Global, const DNDS::MPIInfo &mpi)
MPI_int Allreduce(const void *sendbuf, void *recvbuf, MPI_int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
Wrapper over MPI_Allreduce.
Definition MPI.cpp:203
ssp< SerializerBase > SerializerBaseSSP
const MPI_Datatype DNDS_MPI_INDEX
MPI datatype matching index (= MPI_INT64_T).
Definition MPI.hpp:90
DNDS_CONSTANT const index UnInitIndex
Sentinel "not initialised" index value (= INT64_MIN).
Definition Defines.hpp:176
void MPISerialDo(const MPIInfo &mpi, F f)
Execute f on each rank serially, in rank order.
Definition MPI.hpp:698
int32_t rowsize
Row-width / per-row element-count type (signed 32-bit).
Definition Defines.hpp:109
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
Definition Defines.hpp:107
double real
Canonical floating-point scalar used throughout DNDSR (double precision).
Definition Defines.hpp:105
std::ostream & log()
Return the current DNDSR log stream (either std::cout or the installed file).
Definition Defines.cpp:49
int MPI_int
MPI counterpart type for MPI_int (= C int). Used for counts and ranks in MPI calls.
Definition MPI.hpp:43
void TransAttach()
Bind the transformer to the current father / son pointers.
ssp< TArray > father
Owned-side array (must be resized before ghost setup).
void InitPair(const std::string &name, Args &&...args)
Allocate both father and son arrays, forwarding all args to TArray constructor.
ssp< TArray > son
Ghost-side array (sized automatically by createMPITypes / BorrowAndPull).
TTrans trans
Ghost-communication engine bound to father and son.
static MPI_Datatype CommType()
static MPI_Datatype CommType()
DNDS_HOST void ReadSerializer(Serializer::SerializerBaseSSP serializerP, const std::string &name)
UnstructuredMeshDeviceView< B > t_deviceView
Definition Mesh.hpp:777
tPbiPair cell2nodePbi
periodic only, after reader
Definition Mesh.hpp:65
tCoordPair coords
reader
Definition Mesh.hpp:54
void RecoverNode2CellAndNode2Bnd()
only requires father part of cell2node, bnd2node and coords generates node2cell and node2bnd (father ...
Definition Mesh.cpp:489
tAdjPair node2cell
inverse relations
Definition Mesh.hpp:83
MeshAdjState adjPrimaryState
Definition Mesh.hpp:41
void RecoverCell2CellAndBnd2Cell()
needs to use RecoverNode2CellAndNode2Bnd before doing this. Requires node2cell.father and builds a ve...
Definition Mesh.cpp:542
tElemInfoArrayPair cellElemInfo
Definition Mesh.hpp:59
void ReadSerializeAndDistribute(Serializer::SerializerBaseSSP serializerP, const std::string &name, const PartitionOptions &partitionOptions)
Reads mesh from an H5 serializer using even-split distribution, then repartitions via ParMetis for lo...
tElemInfoArrayPair bndElemInfo
Definition Mesh.hpp:60
int size
Number of ranks in comm (-1 until initialised).
Definition MPI.hpp:221
int rank
This rank's 0-based index within comm (-1 until initialised).
Definition MPI.hpp:219
MPI_Comm comm
The underlying MPI communicator handle.
Definition MPI.hpp:217
Tag type for naming objects created via make_ssp.
Definition Defines.hpp:249
tVec r(NCells)