DNDSR 0.2.1
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
SyntheticMeshBuilders.hpp
Go to the documentation of this file.
1#pragma once
2/// @file SyntheticMeshBuilders.hpp
3/// @brief Shared synthetic mesh builders for MeshConnectivity unit tests.
4
6#include "Geom/Mesh/Mesh.hpp"
7#include <string>
8#include <vector>
9#include <set>
10#include <algorithm>
11#include <unordered_set>
12#include <unordered_map>
13#include <numeric>
14#include <fmt/core.h>
15
16using namespace DNDS;
17using namespace DNDS::Geom;
18
19// ===========================================================================
20// Path helper
21// ===========================================================================
22
23static inline std::string meshPath(const std::string &name)
24{
25 std::string f(__FILE__);
26 for (int i = 0; i < 4; i++)
27 {
28 auto pos = f.rfind('/');
29 if (pos == std::string::npos)
30 pos = f.rfind('\\');
31 if (pos != std::string::npos)
32 f = f.substr(0, pos);
33 }
34 return f + "/data/mesh/" + name;
35}
36
37// ===========================================================================
38// 4-quad mesh builder + helpers
39// ===========================================================================
40
41/// Build a small hand-crafted 4-quad mesh on a single rank.
42///
43/// Layout (nodes 0-8, cells 0-3):
44///
45/// 6---7---8
46/// | | |
47/// | 2 | 3 |
48/// | | |
49/// 3---4---5
50/// | | |
51/// | 0 | 1 |
52/// | | |
53/// 0---1---2
54///
55/// cell2node:
56/// cell 0: [0, 1, 4, 3]
57/// cell 1: [1, 2, 5, 4]
58/// cell 2: [3, 4, 7, 6]
59/// cell 3: [4, 5, 8, 7]
60///
61static inline tAdjPair make4QuadCell2Node(const MPIInfo &mpi)
62{
63 tAdjPair c2n;
64 c2n.InitPair("test_c2n", mpi);
65
66 if (mpi.size == 1)
67 {
68 c2n.father->Resize(4);
69 int data[4][4] = {
70 {0, 1, 4, 3},
71 {1, 2, 5, 4},
72 {3, 4, 7, 6},
73 {4, 5, 8, 7}};
74 for (DNDS::index i = 0; i < 4; i++)
75 {
76 c2n.father->ResizeRow(i, 4);
77 for (DNDS::rowsize j = 0; j < 4; j++)
78 c2n.father->operator()(i, j) = data[i][j];
79 }
80 }
81 else if (mpi.size == 2)
82 {
83 // Partition: rank 0 owns cells 0,1; rank 1 owns cells 2,3
84 c2n.father->Resize(2);
85 if (mpi.rank == 0)
86 {
87 int data[2][4] = {
88 {0, 1, 4, 3},
89 {1, 2, 5, 4}};
90 for (DNDS::index i = 0; i < 2; i++)
91 {
92 c2n.father->ResizeRow(i, 4);
93 for (DNDS::rowsize j = 0; j < 4; j++)
94 c2n.father->operator()(i, j) = data[i][j];
95 }
96 }
97 else
98 {
99 int data[2][4] = {
100 {3, 4, 7, 6},
101 {4, 5, 8, 7}};
102 for (DNDS::index i = 0; i < 2; i++)
103 {
104 c2n.father->ResizeRow(i, 4);
105 for (DNDS::rowsize j = 0; j < 4; j++)
106 c2n.father->operator()(i, j) = data[i][j];
107 }
108 }
109 }
110 else
111 {
112 // For np>2, only rank 0 and 1 have cells; others have 0
113 if (mpi.rank == 0)
114 {
115 c2n.father->Resize(2);
116 int data[2][4] = {
117 {0, 1, 4, 3},
118 {1, 2, 5, 4}};
119 for (DNDS::index i = 0; i < 2; i++)
120 {
121 c2n.father->ResizeRow(i, 4);
122 for (DNDS::rowsize j = 0; j < 4; j++)
123 c2n.father->operator()(i, j) = data[i][j];
124 }
125 }
126 else if (mpi.rank == 1)
127 {
128 c2n.father->Resize(2);
129 int data[2][4] = {
130 {3, 4, 7, 6},
131 {4, 5, 8, 7}};
132 for (DNDS::index i = 0; i < 2; i++)
133 {
134 c2n.father->ResizeRow(i, 4);
135 for (DNDS::rowsize j = 0; j < 4; j++)
136 c2n.father->operator()(i, j) = data[i][j];
137 }
138 }
139 else
140 {
141 c2n.father->Resize(0);
142 }
143 }
144 return c2n;
145}
146
147/// Create a node distribution for the 9-node grid.
148/// For np=1: rank 0 owns all 9.
149/// For np=2: rank 0 owns nodes 0-4, rank 1 owns nodes 5-8.
150/// For np>2: rank 0 owns 0-4, rank 1 owns 5-8, others own 0.
151static inline DNDS::index nodeLocalCount4Quad(const MPIInfo &mpi)
152{
153 if (mpi.size == 1)
154 return 9;
155 if (mpi.rank == 0)
156 return 5; // nodes 0-4
157 if (mpi.rank == 1)
158 return 4; // nodes 5-8
159 return 0;
160}
161
162static inline DNDS::index nodeLocal2Global4Quad(const MPIInfo &mpi, DNDS::index local)
163{
164 if (mpi.size == 1)
165 return local;
166 if (mpi.rank == 0)
167 return local;
168 if (mpi.rank == 1)
169 return local + 5;
170 return -1; // should not be called
171}
172
173static inline ssp<GlobalOffsetsMapping> makeNodeGlobalMapping4Quad(const MPIInfo &mpi)
174{
175 auto gm = std::make_shared<GlobalOffsetsMapping>();
176 gm->setMPIAlignBcast(mpi, nodeLocalCount4Quad(mpi));
177 return gm;
178}
179
180static inline DNDS::index cellLocal2Global4Quad(const MPIInfo &mpi, DNDS::index local)
181{
182 if (mpi.size == 1)
183 return local;
184 // np>=2: rank 0 owns cells 0-1, rank 1 owns cells 2-3
185 if (mpi.rank == 0)
186 return local;
187 if (mpi.rank == 1)
188 return local + 2;
189 return -1;
190}
191
192// ===========================================================================
193// MPI verification helper
194// ===========================================================================
195
196/// Collect all (to-global -> set-of-from-globals) across all ranks for verification.
197static inline std::vector<std::set<DNDS::index>> gatherInverseGlobal(
198 const tAdjPair &support, DNDS::index nToLocal,
199 const std::function<DNDS::index(DNDS::index)> &toLocal2Global,
200 DNDS::index nToGlobal, const MPIInfo &mpi)
201{
202 // Gather local data
203 // Each rank packs: [toGlobal, count, from0, from1, ...]
204 std::vector<DNDS::index> localPacked;
205 for (DNDS::index i = 0; i < nToLocal; i++)
206 {
207 DNDS::index toG = toLocal2Global(i);
208 auto row = support.father->operator[](i);
209 localPacked.push_back(toG);
210 localPacked.push_back(row.size());
211 for (auto v : row)
212 localPacked.push_back(v);
213 }
214
215 // Gather sizes
216 int localSize = static_cast<int>(localPacked.size());
217 std::vector<int> sizes(mpi.size);
218 MPI_Allgather(&localSize, 1, MPI_INT, sizes.data(), 1, MPI_INT, mpi.comm);
219
220 std::vector<int> disps(mpi.size + 1, 0);
221 for (int r = 0; r < mpi.size; r++)
222 disps[r + 1] = disps[r] + sizes[r];
223
224 std::vector<DNDS::index> allPacked(disps[mpi.size]);
225 MPI_Allgatherv(localPacked.data(), localSize, DNDS_MPI_INDEX,
226 allPacked.data(), sizes.data(), disps.data(), DNDS_MPI_INDEX,
227 mpi.comm);
228
229 // Unpack into global result
230 std::vector<std::set<DNDS::index>> result(nToGlobal);
231 DNDS::index pos = 0;
232 while (pos < static_cast<DNDS::index>(allPacked.size()))
233 {
234 DNDS::index toG = allPacked[pos++];
235 DNDS::index count = allPacked[pos++];
236 for (DNDS::index k = 0; k < count; k++)
237 result[toG].insert(allPacked[pos++]);
238 }
239 return result;
240}
241
242// ===========================================================================
243// SubEntityQuery factories
244// ===========================================================================
245
246// ===========================================================================
247// Interpolate Tests
248// ===========================================================================
249
250// ---------------------------------------------------------------------------
251// Helper: build SubEntityQuery from Element topology API
252// ---------------------------------------------------------------------------
253
254/// Build a SubEntityQuery that extracts faces (dim-1 sub-entities)
255/// from parent elements described by parentElemInfo.
256static inline SubEntityQuery makeFaceQuery(const tElemInfoArrayPair &parentElemInfo)
257{
259 q.numSubEntities = [&parentElemInfo](DNDS::index iParent) -> int
260 {
261 auto e = Elem::Element{parentElemInfo[iParent]->getElemType()};
262 return e.GetNumFaces();
263 };
264 q.describe = [&parentElemInfo](DNDS::index iParent, int iSub) -> SubEntityDesc
265 {
266 auto eParent = Elem::Element{parentElemInfo[iParent]->getElemType()};
267 auto eFace = eParent.ObtainFace(iSub);
268 return SubEntityDesc{eFace.GetNumVertices(), eFace.GetNumNodes(),
269 static_cast<t_index>(eFace.type)};
270 };
271 q.extractNodes = [&parentElemInfo](DNDS::index iParent, int iSub,
272 const std::function<DNDS::index(int)> &parentNodes,
273 DNDS::index *out)
274 {
275 auto eParent = Elem::Element{parentElemInfo[iParent]->getElemType()};
276 auto eFace = eParent.ObtainFace(iSub);
277 // Build a temporary vector of parent nodes to pass to ExtractFaceNodes
278 std::vector<DNDS::index> pNodes(eParent.GetNumNodes());
279 for (int i = 0; i < eParent.GetNumNodes(); i++)
280 pNodes[i] = parentNodes(i);
281 std::vector<DNDS::index> fNodes(eFace.GetNumNodes());
282 eParent.ExtractFaceNodes(iSub, pNodes, fNodes);
283 for (int i = 0; i < eFace.GetNumNodes(); i++)
284 out[i] = fNodes[i];
285 };
286 return q;
287}
288
289/// Build a SubEntityQuery that extracts edges (1D sub-entities)
290/// from 3D parent elements described by parentElemInfo.
291static inline SubEntityQuery makeEdgeQuery(const tElemInfoArrayPair &parentElemInfo)
292{
294 q.numSubEntities = [&parentElemInfo](DNDS::index iParent) -> int
295 {
296 auto e = Elem::Element{parentElemInfo[iParent]->getElemType()};
297 return e.GetNumEdges();
298 };
299 q.describe = [&parentElemInfo](DNDS::index iParent, int iSub) -> SubEntityDesc
300 {
301 auto eParent = Elem::Element{parentElemInfo[iParent]->getElemType()};
302 auto eEdge = eParent.ObtainEdge(iSub);
303 return SubEntityDesc{eEdge.GetNumVertices(), eEdge.GetNumNodes(),
304 static_cast<t_index>(eEdge.type)};
305 };
306 q.extractNodes = [&parentElemInfo](DNDS::index iParent, int iSub,
307 const std::function<DNDS::index(int)> &parentNodes,
308 DNDS::index *out)
309 {
310 auto eParent = Elem::Element{parentElemInfo[iParent]->getElemType()};
311 auto eEdge = eParent.ObtainEdge(iSub);
312 std::vector<DNDS::index> pNodes(eParent.GetNumNodes());
313 for (int i = 0; i < eParent.GetNumNodes(); i++)
314 pNodes[i] = parentNodes(i);
315 std::vector<DNDS::index> eNodes(eEdge.GetNumNodes());
316 eParent.ExtractEdgeNodes(iSub, pNodes, eNodes);
317 for (int i = 0; i < eEdge.GetNumNodes(); i++)
318 out[i] = eNodes[i];
319 };
320 return q;
321}
322
323// ===========================================================================
324// Hand-crafted mesh helper
325// ===========================================================================
326
327/// Helper: build cell2node and cellElemInfo for a hand-crafted mesh on rank 0 only.
328/// cells: vector of (ElemType, vector<node indices>).
329/// Returns (cell2node, cellElemInfo, nNodes).
336
337static inline HandCraftedMesh makeHandCraftedMesh(
338 const MPIInfo &mpi,
339 const std::vector<std::pair<Elem::ElemType, std::vector<DNDS::index>>> &cells,
340 DNDS::index nNodes)
341{
343 m.nNodes = nNodes;
344
345 m.cell2node.InitPair("test_c2n", mpi);
346 m.cellElemInfo.InitPair("test_cei", mpi);
347
348 DNDS::index nCells = static_cast<DNDS::index>(cells.size());
349 m.cell2node.father->Resize(nCells);
350 m.cellElemInfo.father->Resize(nCells);
351
352 for (DNDS::index i = 0; i < nCells; i++)
353 {
354 auto &[et, nodes] = cells[i];
355 m.cell2node.father->ResizeRow(i, nodes.size());
356 for (DNDS::rowsize j = 0; j < static_cast<DNDS::rowsize>(nodes.size()); j++)
357 m.cell2node.father->operator()(i, j) = nodes[j];
358
359 ElemInfo ei;
360 ei.setElemType(et);
361 ei.zone = 0;
362 m.cellElemInfo(i, 0) = ei;
363 }
364
365 return m;
366}
367
368/// Collect sorted vertex set from an entity's node row in entity2node.
369static inline std::set<DNDS::index> getEntityVertexSet(
370 const tAdjPair &entity2node,
371 const std::vector<ElemInfo> &entityElemInfo,
372 DNDS::index iEntity)
373{
374 auto eEnt = Elem::Element{entityElemInfo[iEntity].getElemType()};
375 int nVerts = eEnt.GetNumVertices();
376 std::set<DNDS::index> verts;
377 for (int j = 0; j < nVerts; j++)
378 verts.insert(entity2node.father->operator()(iEntity, j));
379 return verts;
380}
381
382// ---------------------------------------------------------------------------
383
384// ===========================================================================
385// Periodic 2x2 mesh
386// ===========================================================================
387
388/// cell 1: {1,0,2,3} pbi: {0,P1,P1,0}
389/// cell 2: {2,3,1,0} pbi: {0,0,P2,P2}
390/// cell 3: {3,2,0,1} pbi: {0,P1,P1|P2,P2}
391///
392/// All 4 cells share all 4 nodes! Without the collaborating check,
393/// face dedup would incorrectly merge edges across the periodic corner.
394/// Expected: 8 unique faces (4 cells × 4 edges / 2), all internal.
395///
404
405static inline Periodic2x2Mesh makePeriodic2x2Mesh(const MPIInfo &mpi)
406{
408 m.cell2node.InitPair("p2x2_c2n", mpi);
409 m.cellElemInfo.InitPair("p2x2_cei", mpi);
410 m.cell2nodePbi.InitPair("p2x2_pbi", mpi);
411
412 m.cell2node.father->Resize(4);
413 m.cellElemInfo.father->Resize(4);
414 m.cell2nodePbi.father->Resize(4);
415
416 // Quad4 node order: BL, BR, TR, TL (CCW)
417 DNDS::index c2n[4][4] = {
418 {0, 1, 3, 2}, // cell 0
419 {1, 0, 2, 3}, // cell 1 (right col wraps)
420 {2, 3, 1, 0}, // cell 2 (top row wraps)
421 {3, 2, 0, 1}, // cell 3 (corner wraps)
422 };
423 uint8_t pbi[4][4] = {
424 {0, 0, 0, 0}, // cell 0: all main
425 {0, 0x01, 0x01, 0}, // cell 1: nodes 1,2 have P1
426 {0, 0, 0x02, 0x02}, // cell 2: nodes 2,3 have P2
427 {0, 0x01, 0x01 | 0x02, 0x02}, // cell 3: mixed
428 };
429
430 ElemInfo qi;
432 qi.zone = 0;
433
434 for (DNDS::index i = 0; i < 4; i++)
435 {
436 m.cell2node.father->ResizeRow(i, 4);
437 m.cell2nodePbi.father->ResizeRow(i, 4);
438 m.cellElemInfo(i, 0) = qi;
439 for (DNDS::rowsize j = 0; j < 4; j++)
440 {
441 m.cell2node.father->operator()(i, j) = c2n[i][j];
442 m.cell2nodePbi.father->operator()(i, j) = NodePeriodicBits{pbi[i][j]};
443 }
444 }
445 return m;
446}
447
448// ===========================================================================
449// Periodic 2x2x2 mesh
450// ===========================================================================
451
452/// After periodic dedup: 27 nodes → 8 nodes, 8 cells.
453/// All 8 cells reference all 8 nodes (with varying pbi).
454///
455/// Expected topology (3-torus T³):
456/// V=8, E=24, F=24, C=8 → Euler χ = 8-24+24-8 = 0.
457///
466
467static inline Periodic2x2x2Mesh makePeriodic2x2x2Mesh(const MPIInfo &mpi)
468{
470 m.cell2node.InitPair("p2x2x2_c2n", mpi);
471 m.cellElemInfo.InitPair("p2x2x2_cei", mpi);
472 m.cell2nodePbi.InitPair("p2x2x2_pbi", mpi);
473
474 m.cell2node.father->Resize(8);
475 m.cellElemInfo.father->Resize(8);
476 m.cell2nodePbi.father->Resize(8);
477
478 // Deduped node index: d(i,j,k) = i + 2*j + 4*k for i,j,k ∈ {0,1}
479 // Cell index: ci + 2*cj + 4*ck for ci,cj,ck ∈ {0,1}
480 // Hex8 local ordering: BL-front-bot, BR-front-bot, BR-back-bot, BL-back-bot,
481 // BL-front-top, BR-front-top, BR-back-top, BL-back-top
482 DNDS::index c2n[8][8] = {
483 {0, 1, 3, 2, 4, 5, 7, 6}, // cell 0 (0,0,0)
484 {1, 0, 2, 3, 5, 4, 6, 7}, // cell 1 (1,0,0)
485 {2, 3, 1, 0, 6, 7, 5, 4}, // cell 2 (0,1,0)
486 {3, 2, 0, 1, 7, 6, 4, 5}, // cell 3 (1,1,0)
487 {4, 5, 7, 6, 0, 1, 3, 2}, // cell 4 (0,0,1)
488 {5, 4, 6, 7, 1, 0, 2, 3}, // cell 5 (1,0,1)
489 {6, 7, 5, 4, 2, 3, 1, 0}, // cell 6 (0,1,1)
490 {7, 6, 4, 5, 3, 2, 0, 1}, // cell 7 (1,1,1)
491 };
492 uint8_t pbi[8][8] = {
493 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // cell 0: all main
494 {0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00}, // cell 1: X-wrap
495 {0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0x02}, // cell 2: Y-wrap
496 {0x00, 0x01, 0x03, 0x02, 0x00, 0x01, 0x03, 0x02}, // cell 3: XY-wrap
497 {0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04}, // cell 4: Z-wrap
498 {0x00, 0x01, 0x01, 0x00, 0x04, 0x05, 0x05, 0x04}, // cell 5: XZ-wrap
499 {0x00, 0x00, 0x02, 0x02, 0x04, 0x04, 0x06, 0x06}, // cell 6: YZ-wrap
500 {0x00, 0x01, 0x03, 0x02, 0x04, 0x05, 0x07, 0x06}, // cell 7: XYZ-wrap
501 };
502
503 ElemInfo hi;
505 hi.zone = 0;
506
507 for (DNDS::index i = 0; i < 8; i++)
508 {
509 m.cell2node.father->ResizeRow(i, 8);
510 m.cell2nodePbi.father->ResizeRow(i, 8);
511 m.cellElemInfo(i, 0) = hi;
512 for (DNDS::rowsize j = 0; j < 8; j++)
513 {
514 m.cell2node.father->operator()(i, j) = c2n[i][j];
515 m.cell2nodePbi.father->operator()(i, j) = NodePeriodicBits{pbi[i][j]};
516 }
517 }
518 return m;
519}
520
521// ===========================================================================
522// Periodic match-extra callback factories
523// ===========================================================================
524
525static inline std::function<bool(DNDS::index, int, DNDS::index, DNDS::index, int)>
526makePeriodicMatchExtra(const tElemInfoArrayPair &cellElemInfo,
527 const tAdjPair &cell2node,
528 const tPbiPair &cell2nodePbi,
529 bool forEdges)
530{
531 return [&cellElemInfo, &cell2node, &cell2nodePbi, forEdges](
532 DNDS::index iParent, int iSub,
533 DNDS::index, DNDS::index candidateParent, int candidateSub) -> bool
534 {
535 auto eParentA = Elem::Element{cellElemInfo[iParent]->getElemType()};
536 auto eParentB = Elem::Element{cellElemInfo[candidateParent]->getElemType()};
537
538 int nFN;
539 std::vector<DNDS::index> nodesA, nodesB;
540 std::vector<NodePeriodicBits> pbiA, pbiB;
541
542 if (forEdges)
543 {
544 auto eEdge = eParentA.ObtainEdge(iSub);
545 nFN = eEdge.GetNumNodes();
546 nodesA.resize(nFN); nodesB.resize(nFN);
547 pbiA.resize(nFN); pbiB.resize(nFN);
548 eParentA.ExtractEdgeNodes(iSub, cell2node[iParent], nodesA);
549 eParentA.ExtractEdgeNodes(iSub, cell2nodePbi[iParent], pbiA);
550 eParentB.ExtractEdgeNodes(candidateSub, cell2node[candidateParent], nodesB);
551 eParentB.ExtractEdgeNodes(candidateSub, cell2nodePbi[candidateParent], pbiB);
552 }
553 else
554 {
555 auto eFace = eParentA.ObtainFace(iSub);
556 nFN = eFace.GetNumNodes();
557 nodesA.resize(nFN); nodesB.resize(nFN);
558 pbiA.resize(nFN); pbiB.resize(nFN);
559 eParentA.ExtractFaceNodes(iSub, cell2node[iParent], nodesA);
560 eParentA.ExtractFaceNodes(iSub, cell2nodePbi[iParent], pbiA);
561 eParentB.ExtractFaceNodes(candidateSub, cell2node[candidateParent], nodesB);
562 eParentB.ExtractFaceNodes(candidateSub, cell2nodePbi[candidateParent], pbiB);
563 }
564
565 using P = std::pair<DNDS::index, NodePeriodicBits>;
566 auto cmp = [](const P &L, const P &R)
567 { return L.first == R.first ? uint8_t(L.second) < uint8_t(R.second)
568 : L.first < R.first; };
569 std::vector<P> pA(nFN), pB(nFN);
570 for (int i = 0; i < nFN; i++)
571 {
572 pA[i] = {nodesA[i], pbiA[i]};
573 pB[i] = {nodesB[i], pbiB[i]};
574 }
575 std::sort(pA.begin(), pA.end(), cmp);
576 std::sort(pB.begin(), pB.end(), cmp);
577
578 auto v0 = pA[0].second ^ pB[0].second;
579 for (int i = 1; i < nFN; i++)
580 if ((pA[i].second ^ pB[i].second) != v0)
581 return false;
582 return true;
583 };
584}
585
586// ===========================================================================
587// Distributed 3D hex mesh
588// ===========================================================================
589
590
591/// Build a distributed NxNxN hex mesh. Each rank owns one NxNxN block.
592/// Blocks are stacked in the X direction: rank p has X in [p*N, (p+1)*N].
593/// Node ownership: rank p owns nodes with X in [p*N, (p+1)*N), except
594/// the last rank also owns X = np*N. Shared boundary nodes at X = p*N
595/// are owned by rank p.
596///
597/// Global cell index: rank-contiguous. Rank p's cells are [p*N^3, (p+1)*N^3).
598/// Cell (ix, iy, iz) on rank p → global index = p*N^3 + ix*N*N + iy*N + iz.
599///
600/// Global node index: rank-contiguous. Rank p owns nodes with
601/// ix in [0, N) (or [0, N] for last rank), iy in [0, N], iz in [0, N].
602/// Local node (ix, iy, iz) → local index = ix*(N+1)*(N+1) + iy*(N+1) + iz.
603/// Global index = rank-offset + local index.
605{
609 DNDS::index nNodeOwned; // nodes owned by this rank
610 DNDS::index nNodeGhost; // ghost nodes from neighbor ranks
611 DNDS::index nNodeTotal; // owned + ghost
612
613 tAdjPair cell2node; // father = local cells, son = ghost cells (1-layer)
615 tPbiPair cell2nodePbi; // only if periodic
616
619
620 bool periodic{false};
621
622 // Local cell (ix, iy, iz) → local cell index
624 {
625 return ix * N * N + iy * N + iz;
626 }
627
628 // Local node (ix, iy, iz) on this rank → local node index (within owned)
630 {
631 return ix * (N + 1) * (N + 1) + iy * (N + 1) + iz;
632 }
633
634 void build(DNDS::index tileN, bool isPeriodic, const MPIInfo &mpi)
635 {
636 N = tileN;
637 np = mpi.size;
638 periodic = isPeriodic;
639
640 // Node ownership: each rank owns N slices in X (except last: N+1 if not periodic).
641 // For periodic: each rank owns exactly N*(N+1)*(N+1) nodes.
642 // For non-periodic: last rank owns (N+1)*(N+1)*(N+1), others own N*(N+1)*(N+1).
643 DNDS::index nodeXSlices = N;
644 if (!periodic && mpi.rank == mpi.size - 1)
645 nodeXSlices = N + 1;
646 nNodeOwned = nodeXSlices * (N + 1) * (N + 1);
647 nCellLocal = N * N * N;
648
649 cellGM = std::make_shared<GlobalOffsetsMapping>();
650 cellGM->setMPIAlignBcast(mpi, nCellLocal);
651 nodeGM = std::make_shared<GlobalOffsetsMapping>();
652 nodeGM->setMPIAlignBcast(mpi, nNodeOwned);
653
654 DNDS::index cellOffset = (*cellGM)(mpi.rank, 0);
655 DNDS::index nodeOffset = (*nodeGM)(mpi.rank, 0);
656
657 // Helper: get global node index for a node at physical position
658 // (gx, gy, gz) where gx = rank*N + ix.
659 // The owning rank of gx is gx / N (or np-1 for periodic wrapping).
660 auto nodeGlobalPhys = [&](DNDS::index gx, DNDS::index gy, DNDS::index gz) -> DNDS::index
661 {
662 DNDS::index totalNx = np * N;
663 if (periodic)
664 gx = ((gx % totalNx) + totalNx) % totalNx;
665 // Find owning rank
666 DNDS::MPI_int ownerRank;
667 DNDS::index localIx;
668 if (!periodic)
669 {
670 if (gx >= totalNx)
671 {
672 ownerRank = mpi.size - 1;
673 localIx = gx - (mpi.size - 1) * N;
674 }
675 else
676 {
677 ownerRank = static_cast<DNDS::MPI_int>(gx / N);
678 localIx = gx - ownerRank * N;
679 }
680 }
681 else
682 {
683 ownerRank = static_cast<DNDS::MPI_int>(gx / N);
684 if (ownerRank >= mpi.size)
685 ownerRank = mpi.size - 1;
686 localIx = gx - ownerRank * N;
687 }
688 DNDS::index localIdx = localIx * (N + 1) * (N + 1) + gy * (N + 1) + gz;
689 return (*nodeGM)(ownerRank, 0) + localIdx;
690 };
691
692 // Build cell2node for local cells (8 nodes per hex).
693 // Also collect ghost cell and node info.
694 cell2node.InitPair("c2n", mpi);
695 cell2node.father->Resize(nCellLocal, 8);
696 cellElemInfo.InitPair("cInfo", mpi);
698
699 // Ghost cells: 1-layer in X direction from neighbor ranks.
700 // Left neighbor (rank-1): cells at ix = N-1 on rank-1 that share X-face
701 // Right neighbor (rank+1): cells at ix = 0 on rank+1
702 std::vector<DNDS::index> ghostCellGlobals;
703 std::vector<std::vector<DNDS::index>> ghostCellNodes; // node globals per ghost cell
704 DNDS::index leftRank = mpi.rank - 1;
705 DNDS::index rightRank = mpi.rank + 1;
706 if (periodic)
707 {
708 leftRank = (mpi.rank - 1 + mpi.size) % mpi.size;
709 rightRank = (mpi.rank + 1) % mpi.size;
710 }
711
712 auto addGhostCellsFromRank = [&](DNDS::MPI_int srcRank, DNDS::index srcIx,
713 DNDS::index physXBase)
714 {
715 for (DNDS::index iy = 0; iy < N; iy++)
716 for (DNDS::index iz = 0; iz < N; iz++)
717 {
718 DNDS::index gcGlobal = (*cellGM)(srcRank, 0) +
719 srcIx * N * N + iy * N + iz;
720 ghostCellGlobals.push_back(gcGlobal);
721
722 std::vector<DNDS::index> nodes(8);
723 DNDS::index gx = physXBase;
724 // Hex8 node ordering: (0,0,0), (1,0,0), (1,1,0), (0,1,0),
725 // (0,0,1), (1,0,1), (1,1,1), (0,1,1)
726 nodes[0] = nodeGlobalPhys(gx, iy, iz);
727 nodes[1] = nodeGlobalPhys(gx + 1, iy, iz);
728 nodes[2] = nodeGlobalPhys(gx + 1, iy + 1, iz);
729 nodes[3] = nodeGlobalPhys(gx, iy + 1, iz);
730 nodes[4] = nodeGlobalPhys(gx, iy, iz + 1);
731 nodes[5] = nodeGlobalPhys(gx + 1, iy, iz + 1);
732 nodes[6] = nodeGlobalPhys(gx + 1, iy + 1, iz + 1);
733 nodes[7] = nodeGlobalPhys(gx, iy + 1, iz + 1);
734 ghostCellNodes.push_back(std::move(nodes));
735 }
736 };
737
738 // Left ghost (X = rank*N - 1)
739 if (periodic || mpi.rank > 0)
740 {
741 DNDS::index physX = mpi.rank * N - 1;
742 if (periodic && mpi.rank == 0)
743 physX = np * N - 1;
744 addGhostCellsFromRank(static_cast<DNDS::MPI_int>(leftRank), N - 1, physX);
745 }
746 // Right ghost (X = (rank+1)*N)
747 if (periodic || mpi.rank < mpi.size - 1)
748 {
749 DNDS::index physX = (mpi.rank + 1) * N;
750 addGhostCellsFromRank(static_cast<DNDS::MPI_int>(rightRank), 0, physX);
751 }
752
753 // Collect all node globals (from local cells + ghost cells)
754 std::set<DNDS::index> allNodeGlobals;
755 for (DNDS::index ix = 0; ix < N; ix++)
756 for (DNDS::index iy = 0; iy < N; iy++)
757 for (DNDS::index iz = 0; iz < N; iz++)
758 {
759 DNDS::index gx = mpi.rank * N + ix;
760 auto n = std::array<DNDS::index, 8>{
761 nodeGlobalPhys(gx, iy, iz),
762 nodeGlobalPhys(gx + 1, iy, iz),
763 nodeGlobalPhys(gx + 1, iy + 1, iz),
764 nodeGlobalPhys(gx, iy + 1, iz),
765 nodeGlobalPhys(gx, iy, iz + 1),
766 nodeGlobalPhys(gx + 1, iy, iz + 1),
767 nodeGlobalPhys(gx + 1, iy + 1, iz + 1),
768 nodeGlobalPhys(gx, iy + 1, iz + 1),
769 };
770 for (auto ng : n)
771 allNodeGlobals.insert(ng);
772 DNDS::index iCell = cellLocal(ix, iy, iz);
773 for (int k = 0; k < 8; k++)
774 cell2node.father->operator()(iCell, k) = n[k];
775 cellElemInfo.father->operator()(iCell, 0) = ElemInfo{
776 static_cast<t_index>(Elem::Hex8), INTERNAL_ZONE};
777 }
778
779 for (auto &gc : ghostCellNodes)
780 for (auto ng : gc)
781 allNodeGlobals.insert(ng);
782
783 // Sort ghost cells by global index to match createGhostMapping's ordering.
784 {
785 std::vector<size_t> sortOrder(ghostCellGlobals.size());
786 std::iota(sortOrder.begin(), sortOrder.end(), 0);
787 std::sort(sortOrder.begin(), sortOrder.end(),
788 [&](size_t a, size_t b)
789 { return ghostCellGlobals[a] < ghostCellGlobals[b]; });
790 std::vector<DNDS::index> sortedGlobals(ghostCellGlobals.size());
791 std::vector<std::vector<DNDS::index>> sortedNodes(ghostCellNodes.size());
792 for (size_t i = 0; i < sortOrder.size(); i++)
793 {
794 sortedGlobals[i] = ghostCellGlobals[sortOrder[i]];
795 sortedNodes[i] = ghostCellNodes[sortOrder[i]];
796 }
797 ghostCellGlobals = std::move(sortedGlobals);
798 ghostCellNodes = std::move(sortedNodes);
799 }
800
801 // Build ghost cell son + ghost node ghost mapping.
802 cell2node.son = std::make_shared<tAdj::element_type>(ObjName{"c2n.son"}, mpi);
803 cell2node.son->Resize(static_cast<DNDS::index>(ghostCellGlobals.size()), 8);
804 cellElemInfo.son = std::make_shared<tElemInfoArray::element_type>(ObjName{"cInfo.son"}, mpi);
805 cellElemInfo.son->Resize(static_cast<DNDS::index>(ghostCellGlobals.size()));
806
807 // Build cell ghost mapping
809 cell2node.trans.createFatherGlobalMapping();
810 cell2node.trans.createGhostMapping(ghostCellGlobals);
811 cell2node.trans.createMPITypes();
812 // Don't pullOnce — populate son manually AFTER createMPITypes
813 // (createMPITypes resizes the son).
814
816 cellElemInfo.trans.BorrowGGIndexing(cell2node.trans);
817 cellElemInfo.trans.createMPITypes();
818
819 // Now populate the son arrays.
820 for (DNDS::index ig = 0; ig < static_cast<DNDS::index>(ghostCellGlobals.size()); ig++)
821 {
822 for (int k = 0; k < 8; k++)
823 cell2node.son->operator()(ig, k) = ghostCellNodes[ig][k];
824 cellElemInfo.son->operator()(ig, 0) = ElemInfo{
825 static_cast<t_index>(Elem::Hex8), INTERNAL_ZONE};
826 }
827
828 cellGhostMapping = cell2node.trans.pLGhostMapping;
829
830 // Build node ghost mapping
831 std::vector<DNDS::index> ghostNodeGlobals;
832 for (auto ng : allNodeGlobals)
833 {
836 if (nodeGM->search(ng, r, v) && r != mpi.rank)
837 ghostNodeGlobals.push_back(ng);
838 }
839 // Create a dummy node array pair for ghost mapping setup.
840 tAdjPair dummyNode;
841 dummyNode.InitPair("dummyNode", mpi);
842 dummyNode.father->Resize(nNodeOwned);
843 dummyNode.TransAttach();
844 dummyNode.trans.createFatherGlobalMapping();
845 dummyNode.trans.createGhostMapping(ghostNodeGlobals);
846 dummyNode.trans.createMPITypes();
847 nodeGhostMapping = dummyNode.trans.pLGhostMapping;
848 nNodeGhost = static_cast<DNDS::index>(ghostNodeGlobals.size());
850
851 // Convert cell2node from global to local-appended node indices.
852 for (DNDS::index iCell = 0; iCell < nCellLocal; iCell++)
853 for (rowsize k = 0; k < 8; k++)
854 {
855 DNDS::index &ng = cell2node.father->operator()(iCell, k);
856 auto [found, r, la] = nodeGhostMapping->search_indexAppend(ng);
857 DNDS_assert(found);
858 ng = la;
859 }
860 for (DNDS::index ig = 0; ig < static_cast<DNDS::index>(ghostCellGlobals.size()); ig++)
861 for (int k = 0; k < 8; k++)
862 {
863 DNDS::index &ng = cell2node.son->operator()(ig, k);
864 auto [found, r, la] = nodeGhostMapping->search_indexAppend(ng);
865 DNDS_assert_info(found, fmt::format("ghost cell {} (global {}) node {} global {} not found "
866 "(rank {}, nNodeOwned {}, nNodeGhost {}, expected from ghostCellNodes {})",
867 ig, ghostCellGlobals[ig], k, ng, mpi.rank, nNodeOwned, nNodeGhost,
868 ghostCellNodes[ig][k]));
869 ng = la;
870 }
871 }
872};
873
874// ===========================================================================
875// SubEntityQueryPbi factories for Hex8
876// ===========================================================================
877
878// Helper: build SubEntityQueryPbi for face extraction from Hex8
879static inline SubEntityQueryPbi makeHex8FaceQueryPbi(const tElemInfoArrayPair &cellElemInfo)
880{
882 q.numSubEntities = [&](DNDS::index iP) -> int
883 {
884 return Elem::Element{cellElemInfo[iP]->getElemType()}.GetNumFaces();
885 };
886 q.describe = [&](DNDS::index iP, int iSub) -> SubEntityDesc
887 {
888 auto e = Elem::Element{cellElemInfo[iP]->getElemType()};
889 auto f = e.ObtainFace(iSub);
890 return SubEntityDesc{f.GetNumVertices(), f.GetNumNodes(), static_cast<t_index>(f.type)};
891 };
892 q.extractNodes = [&](DNDS::index iP, int iSub,
893 const std::function<DNDS::index(int)> &pN,
894 DNDS::index *out)
895 {
896 auto e = Elem::Element{cellElemInfo[iP]->getElemType()};
897 auto f = e.ObtainFace(iSub);
898 std::vector<DNDS::index> pNodes(e.GetNumNodes());
899 for (int i = 0; i < e.GetNumNodes(); i++)
900 pNodes[i] = pN(i);
901 std::vector<DNDS::index> fNodes(f.GetNumNodes());
902 e.ExtractFaceNodes(iSub, pNodes, fNodes);
903 for (int i = 0; i < f.GetNumNodes(); i++)
904 out[i] = fNodes[i];
905 };
906 return q;
907}
908
909
910// Helper: build SubEntityQueryPbi for edge extraction from Hex8
911static inline SubEntityQueryPbi makeHex8EdgeQueryPbi(const tElemInfoArrayPair &cellElemInfo)
912{
914 q.numSubEntities = [&](DNDS::index iP) -> int
915 {
916 return Elem::Element{cellElemInfo[iP]->getElemType()}.GetNumEdges();
917 };
918 q.describe = [&](DNDS::index iP, int iSub) -> SubEntityDesc
919 {
920 auto e = Elem::Element{cellElemInfo[iP]->getElemType()};
921 auto ed = e.ObtainEdge(iSub);
922 return SubEntityDesc{ed.GetNumVertices(), ed.GetNumNodes(), static_cast<t_index>(ed.type)};
923 };
924 q.extractNodes = [&](DNDS::index iP, int iSub,
925 const std::function<DNDS::index(int)> &pN,
926 DNDS::index *out)
927 {
928 auto e = Elem::Element{cellElemInfo[iP]->getElemType()};
929 auto ed = e.ObtainEdge(iSub);
930 std::vector<DNDS::index> pNodes(e.GetNumNodes());
931 for (int i = 0; i < e.GetNumNodes(); i++)
932 pNodes[i] = pN(i);
933 std::vector<DNDS::index> eNodes(ed.GetNumNodes());
934 e.ExtractEdgeNodes(iSub, pNodes, eNodes);
935 for (int i = 0; i < ed.GetNumNodes(); i++)
936 out[i] = eNodes[i];
937 };
938 return q;
939}
940
#define DNDS_assert_info(expr, info)
Debug-only assertion with an extra std::string info message.
Definition Errors.hpp:117
#define DNDS_assert(expr)
Debug-only assertion (compiled out when DNDS_NDEBUG is defined). Prints the expression + file/line + ...
Definition Errors.hpp:112
Layered DAG of mesh adjacency relations with composable DSL operations.
Eigen::Matrix< real, 3, 3 > m
int32_t t_index
Definition Geometric.hpp:6
the host side operators are provided as implemented
const MPI_Datatype DNDS_MPI_INDEX
MPI datatype matching index (= MPI_INT64_T).
Definition MPI.hpp:106
int32_t rowsize
Row-width / per-row element-count type (signed 32-bit).
Definition Defines.hpp:114
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
Definition Defines.hpp:112
std::shared_ptr< T > ssp
Shortened alias for std::shared_ptr used pervasively in DNDSR.
Definition Defines.hpp:143
int MPI_int
MPI counterpart type for MPI_int (= C int). Used for counts and ranks in MPI calls.
Definition MPI.hpp:54
t_arrayDeviceView father
Convenience bundle of a father, son, and attached ArrayTransformer.
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.
t_index zone
positive for BVnum, 0 for internal Elems, Negative for ?
DNDS_DEVICE_CALLABLE void setElemType(Elem::ElemType t)
DNDS_DEVICE_CALLABLE constexpr t_index GetNumVertices() const
Definition Elements.hpp:229
std::function< void(index iParent, int iSub, const std::function< index(int)> &parentNodes, index *out)> extractNodes
std::function< SubEntityDesc(index iParent, int iSub)> describe
Describe sub-entity iSub of parent iParent.
std::function< int(index iParent)> numSubEntities
Number of sub-entities for parent iParent.
Lightweight bundle of an MPI communicator and the calling rank's coordinates.
Definition MPI.hpp:231
Tag type for naming objects created via make_ssp.
Definition Defines.hpp:254
void build(DNDS::index tileN, bool isPeriodic, const MPIInfo &mpi)
tElemInfoArrayPair cellElemInfo
ssp< OffsetAscendIndexMapping > nodeGhostMapping
DNDS::index cellLocal(DNDS::index ix, DNDS::index iy, DNDS::index iz) const
DNDS::index nodeLocal(DNDS::index ix, DNDS::index iy, DNDS::index iz) const
ssp< GlobalOffsetsMapping > cellGM
ssp< GlobalOffsetsMapping > nodeGM
ssp< OffsetAscendIndexMapping > cellGhostMapping
tElemInfoArrayPair cellElemInfo
tElemInfoArrayPair cellElemInfo
tElemInfoArrayPair cellElemInfo
Eigen::Matrix< real, 5, 1 > v
tVec r(NCells)
tVec b(NCells)
auto result
DNDS::MPI_int rightRank
std::set< DNDS::index > allNodeGlobals
tElemInfoArrayPair cellElemInfo
DNDS::index cellOffset
DNDS::index nodeOffset
input explicitMaps push_back(EntityReorderMap{EntityKind::Cell, cellPartition})
Eigen::Vector3d n(1.0, 0.0, 0.0)