DNDSR 0.2.1
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
MeshConnectivity_Interpolate.hxx
Go to the documentation of this file.
1// Template implementations for Interpolate family.
2// Included at the bottom of MeshConnectivity.hpp.
3// Do NOT include this file directly — include MeshConnectivity.hpp instead.
4
5#include <vector>
6#include <algorithm>
7#include <unordered_map>
8#include <unordered_set>
9#include <fmt/core.h>
10
11namespace DNDS::Geom
12{
13 template <rowsize p2n_rs>
15 const ArrayAdjacencyPair<p2n_rs> &parent2node,
16 const SubEntityQuery &query,
17 index nParent,
19 const MPIInfo &mpi)
20 {
22
23 // Allocate parent2entity
24 result.parent2entity.InitPair("Interpolate_p2e", mpi);
25 result.parent2entity.father->Resize(nParent);
26
27 auto &entityElemInfoV = result.entityElemInfo;
28 index &nEntities = result.nEntities;
29 nEntities = 0;
30
31 // Temporary storage (compacted into ArrayPair at end)
32 std::vector<std::vector<index>> ent2nodeVec;
33 std::vector<std::vector<index>> ent2parentVec; // variable-width: accumulates all parents
34
35 // Per-entity creating (parent, sub) pair — needed for matchExtra callback
36 struct EntityOrigin
37 {
38 index parentIdx;
39 int subIdx;
40 };
41 std::vector<EntityOrigin> ent2origin;
42
43 // Reverse index: for each node, which entities contain it.
44 // Used for O(1)-amortized deduplication lookup.
45 std::vector<std::vector<index>> node2entity(nNode);
46
47 // Scratch buffer for extracted sub-entity nodes
48 constexpr int maxSubEntityNodes = 10; // Quad9 = 9, padded
49 std::array<index, maxSubEntityNodes> subNodesBuf{};
50
51 const bool hasMatchExtra = bool(query.matchExtra);
52
53 for (index iParent = 0; iParent < nParent; iParent++)
54 {
55 int nSubs = query.numSubEntities(iParent);
56 result.parent2entity.father->ResizeRow(iParent, nSubs);
57
58 // Adapter: wrap parent2node[iParent] row as a function(int) -> index
59 auto parentNodeAccessor = [&](int j) -> index
60 {
61 return parent2node[iParent][j];
62 };
63
64 for (int iSub = 0; iSub < nSubs; iSub++)
65 {
66 SubEntityDesc desc = query.describe(iParent, iSub);
67 DNDS_assert_info(desc.nNodes <= maxSubEntityNodes,
68 fmt::format("Interpolate: sub-entity has {} nodes, max is {}",
69 desc.nNodes, maxSubEntityNodes));
70
71 // Extract sub-entity nodes from parent's node list
72 query.extractNodes(iParent, iSub, parentNodeAccessor, subNodesBuf.data());
73
74 // Sort vertices for comparison (first nVertices entries)
75 std::vector<index> subVerts(subNodesBuf.begin(),
76 subNodesBuf.begin() + desc.nVertices);
77 std::sort(subVerts.begin(), subVerts.end());
78
79 // Deduplicate: search existing entities via reverse index
80 index iFound = -1;
81 for (auto iV : subVerts)
82 {
83 if (iFound >= 0)
84 break;
85 DNDS_assert_info(iV >= 0 && iV < nNode,
86 fmt::format("Interpolate: node index {} out of range [0, {})",
87 iV, nNode));
88 for (auto iEnt : node2entity[iV])
89 {
90 // Type-tag must match
91 if (entityElemInfoV[iEnt].type != desc.typeTag)
92 continue;
93
94 // Compare sorted vertices
95 auto &entNodes = ent2nodeVec[iEnt];
96 int nEntVerts = desc.nVertices;
97
98 std::vector<index> entVerts(entNodes.begin(),
99 entNodes.begin() + nEntVerts);
100 std::sort(entVerts.begin(), entVerts.end());
101
102 if (!std::equal(subVerts.begin(), subVerts.end(),
103 entVerts.begin(), entVerts.end()))
104 continue;
105
106 // Vertex match found. Apply optional extra predicate.
107 if (hasMatchExtra)
108 {
109 auto &orig = ent2origin[iEnt];
110 if (!query.matchExtra(iParent, iSub,
111 iEnt,
112 orig.parentIdx, orig.subIdx))
113 continue; // Rejected by extra predicate
114 }
115
116 iFound = iEnt;
117 break;
118 }
119 }
120
121 if (iFound < 0)
122 {
123 // New sub-entity
124 std::vector<index> subNodesVec(subNodesBuf.begin(),
125 subNodesBuf.begin() + desc.nNodes);
126 ent2nodeVec.emplace_back(std::move(subNodesVec));
127 ent2parentVec.push_back({iParent});
128 entityElemInfoV.emplace_back(ElemInfo{desc.typeTag, 0});
129 ent2origin.push_back({iParent, iSub});
130
131 // Register in reverse index (vertices only)
132 for (auto iV : subVerts)
133 node2entity[iV].push_back(nEntities);
134
135 result.parent2entity.father->operator()(iParent, iSub) = nEntities;
136 nEntities++;
137 }
138 else
139 {
140 // Existing sub-entity: append parent to the parent list.
141 ent2parentVec[iFound].push_back(iParent);
142
143 result.parent2entity.father->operator()(iParent, iSub) = iFound;
144 }
145 }
146 }
147
148 // ----- Compact temporary vectors into ArrayPair structures -----
149
150 // entity2node
151 result.entity2node.InitPair("Interpolate_e2n", mpi);
152 result.entity2node.father->Resize(nEntities);
153 for (index iEnt = 0; iEnt < nEntities; iEnt++)
154 {
155 auto &nodes = ent2nodeVec[iEnt];
156 result.entity2node.father->ResizeRow(iEnt, nodes.size());
157 for (rowsize j = 0; j < static_cast<rowsize>(nodes.size()); j++)
158 result.entity2node.father->operator()(iEnt, j) = nodes[j];
159 }
160
161 // entity2parent (variable-width)
162 result.entity2parent.InitPair("Interpolate_e2p", mpi);
163 result.entity2parent.father->Resize(nEntities);
164 for (index iEnt = 0; iEnt < nEntities; iEnt++)
165 {
166 auto &parents = ent2parentVec[iEnt];
167 result.entity2parent.father->ResizeRow(iEnt, parents.size());
168 for (rowsize j = 0; j < static_cast<rowsize>(parents.size()); j++)
169 result.entity2parent.father->operator()(iEnt, j) = parents[j];
170 }
171
172 return result;
173 }
174
175 template <rowsize p2n_rs>
177 const ArrayAdjacencyPair<p2n_rs> &parent2node,
178 const OffsetAscendIndexMapping &parentGhostMapping,
179 const SubEntityQuery &query,
180 index nLocalParents,
181 index nTotalParents,
182 index nNode,
185 const MPIInfo &mpi)
186 {
187 // ============================================================
188 // Step 1: Local interpolation on all parents (local + ghost)
189 // ============================================================
190 auto localResult = Interpolate(parent2node, query, nTotalParents, nNode, mpi);
191
192 // ============================================================
193 // Step 2: Ownership decision and compaction mapping
194 // ============================================================
195 index nAllEntities = localResult.nEntities;
196 std::vector<index> oldToOwned(nAllEntities, UnInitIndex);
197 std::vector<std::vector<index>> pushPerRank(mpi.size);
198
199 index nOwned = 0;
200 for (index iEnt = 0; iEnt < nAllEntities; iEnt++)
201 {
202 index parentL = localResult.entity2parent.father->operator()(iEnt, 0);
203 index parentR = (localResult.entity2parent.father->RowSize(iEnt) >= 2)
204 ? localResult.entity2parent.father->operator()(iEnt, 1)
205 : UnInitIndex;
206
207 auto decision = resolver(parentL, parentR, nLocalParents);
208 if (decision.owned)
209 {
210 oldToOwned[iEnt] = nOwned;
211 for (auto pr : decision.peerRanks)
212 if (pr >= 0 && pr != mpi.rank)
213 pushPerRank[pr].push_back(nOwned);
214 nOwned++;
215 }
216 }
217
218 // ============================================================
219 // Step 3: Build owned entity arrays (father) with global
220 // parent indices in entity2parent
221 // ============================================================
223 result.nOwnedEntities = nOwned;
224
225 // entity2node (father = owned, local node indices)
226 result.entity2node.InitPair("InterpDist_e2n", mpi);
227 result.entity2node.father->Resize(nOwned);
228
229 // entity2parent (father = owned, GLOBAL parent indices)
230 result.entity2parent.InitPair("InterpDist_e2p", mpi);
231 result.entity2parent.father->Resize(nOwned);
232
233 // entityElemInfo (father = owned)
234 result.entityElemInfo.InitPair("InterpDist_eInfo", mpi);
235 result.entityElemInfo.father->Resize(nOwned);
236
237 for (index iEnt = 0; iEnt < nAllEntities; iEnt++)
238 {
239 index iNew = oldToOwned[iEnt];
240 if (iNew == UnInitIndex)
241 continue;
242
243 // entity2node: convert local node indices to GLOBAL
244 auto row = localResult.entity2node.father->operator[](iEnt);
245 result.entity2node.father->ResizeRow(iNew, row.size());
246 for (rowsize j = 0; j < static_cast<rowsize>(row.size()); j++)
247 result.entity2node.father->operator()(iNew, j) =
248 nodeGhostMapping(-1, row[j]);
249
250 // entity2parent: convert local-appended → global parent indices.
251 // InterpolateLocal produces variable-width; extract first 2 for the
252 // fixed-2 InterpolateDistributedResult.
253 {
254 auto parentRow = localResult.entity2parent.father->operator[](iEnt);
255 index pL = parentRow[0];
256 index pR = (parentRow.size() >= 2) ? parentRow[1] : UnInitIndex;
257 result.entity2parent.father->operator()(iNew, 0) =
258 parentGhostMapping(-1, pL);
259 result.entity2parent.father->operator()(iNew, 1) =
260 (pR == UnInitIndex) ? UnInitIndex : parentGhostMapping(-1, pR);
261 }
262
263 // entityElemInfo
264 result.entityElemInfo.father->operator()(iNew, 0) =
265 localResult.entityElemInfo[iEnt];
266 }
267 result.entity2node.father->Compress();
268
269 // ============================================================
270 // Step 4: Push-based ghost exchange
271 // ============================================================
272 std::vector<index> pushIdx;
273 std::vector<index> pushStarts(mpi.size + 1, 0);
274 for (MPI_int r = 0; r < mpi.size; r++)
275 pushStarts[r + 1] = pushStarts[r] + static_cast<index>(pushPerRank[r].size());
276 pushIdx.resize(pushStarts.back());
277 for (MPI_int r = 0; r < mpi.size; r++)
278 std::copy(pushPerRank[r].begin(), pushPerRank[r].end(),
279 pushIdx.begin() + pushStarts[r]);
280
281 result.entity2parent.TransAttach();
282 result.entity2parent.trans.createFatherGlobalMapping();
283 result.entity2parent.trans.createGhostMapping(pushIdx, pushStarts);
284 result.entity2parent.trans.createMPITypes();
285 result.entity2parent.trans.pullOnce();
286 // Now entity2parent.son has ghost entities with global parent indices.
287
288 result.entity2node.TransAttach();
289 result.entity2node.trans.BorrowGGIndexing(result.entity2parent.trans);
290 result.entity2node.trans.createMPITypes();
291 result.entity2node.trans.pullOnce();
292
293 result.entityElemInfo.TransAttach();
294 result.entityElemInfo.trans.BorrowGGIndexing(result.entity2parent.trans);
295 result.entityElemInfo.trans.createMPITypes();
296 result.entityElemInfo.trans.pullOnce();
297
298 // ============================================================
299 // Step 5: Convert entity2parent from global to local-appended
300 // parent indices (receiver side)
301 // ============================================================
302 index nGhostEntities = result.entity2parent.son
303 ? result.entity2parent.son->Size()
304 : 0;
305 for (index iGhost = 0; iGhost < nGhostEntities; iGhost++)
306 {
307 for (rowsize side = 0; side < 2; side++)
308 {
309 index &pGlobal = (*result.entity2parent.son)(iGhost, side);
310 if (pGlobal == UnInitIndex)
311 continue;
312 auto [found, rank, localAppend] =
313 parentGhostMapping.search_indexAppend(pGlobal);
314 DNDS_assert_info(found,
315 fmt::format("InterpolateDistributed: ghost entity parent "
316 "global {} not found locally",
317 pGlobal));
318 pGlobal = localAppend;
319 }
320 }
321 // Convert father entity2parent back to local-appended too.
322 for (index iOwned = 0; iOwned < nOwned; iOwned++)
323 {
324 for (rowsize side = 0; side < 2; side++)
325 {
326 index &pGlobal = result.entity2parent.father->operator()(iOwned, side);
327 if (pGlobal == UnInitIndex)
328 continue;
329 auto [found, rank, localAppend] =
330 parentGhostMapping.search_indexAppend(pGlobal);
331 DNDS_assert_info(found,
332 fmt::format("InterpolateDistributed: owned entity parent "
333 "global {} not found locally",
334 pGlobal));
335 pGlobal = localAppend;
336 }
337 }
338
339 // Convert entity2node from global to local-appended node indices
340 // (for both father/owned and son/ghost).
341 for (index iFace = 0; iFace < nOwned + nGhostEntities; iFace++)
342 {
343 for (rowsize j = 0; j < result.entity2node.RowSize(iFace); j++)
344 {
345 index &nGlobal = result.entity2node(iFace, j);
346 auto [found, rank, localAppend] =
347 nodeGhostMapping.search_indexAppend(nGlobal);
348 DNDS_assert_info(found,
349 fmt::format("InterpolateDistributed: entity node "
350 "global {} not found locally",
351 nGlobal));
352 nGlobal = localAppend;
353 }
354 }
355
356 // ============================================================
357 // Step 6: Build parent2entity with local-appended entity indices
358 // ============================================================
359 // Build a reverse map from ghost entities: for each ghost entity,
360 // which parent slots does it fill?
361 //
362 // First, initialize parent2entity from the local Interpolate result,
363 // remapping owned entities via oldToOwned.
364 result.parent2entity.InitPair("InterpDist_p2e", mpi);
365 result.parent2entity.father->Resize(nLocalParents);
366 result.parent2entity.son = std::make_shared<tAdj::element_type>(
367 ObjName{"InterpDist_p2e.son"}, mpi);
368 result.parent2entity.son->Resize(nTotalParents - nLocalParents);
369
370 for (index iParent = 0; iParent < nTotalParents; iParent++)
371 {
372 rowsize nSubs = localResult.parent2entity.father->RowSize(iParent);
373 result.parent2entity.ResizeRow(iParent, nSubs);
374
375 for (rowsize j = 0; j < nSubs; j++)
376 {
377 index oldEntIdx = localResult.parent2entity.father->operator()(iParent, j);
378 index newEntIdx = oldToOwned[oldEntIdx];
379 // Owned → direct mapping. Non-owned → -1 (resolved next).
380 result.parent2entity(iParent, j) = (newEntIdx != UnInitIndex)
381 ? newEntIdx
382 : -1;
383 }
384 }
385
386 // Resolve -1 entries using ghost entities.
387 // For each ghost entity, find which parents reference it and match the
388 // correct parent2entity slot by comparing vertex sets.
389 for (index iGhost = 0; iGhost < nGhostEntities; iGhost++)
390 {
391 index entityLocalAppended = nOwned + iGhost;
392
393 // Build sorted vertex set for this ghost entity.
394 auto ghostRow = result.entity2node[entityLocalAppended];
395 // Note: we need vertices only (first nVertices entries).
396 // Since we don't have the SubEntityDesc here, use all nodes for matching.
397 // Actually, entity2node stores ALL nodes (vertices + higher-order).
398 // For vertex-set matching, we sort the full node set.
399 std::vector<index> ghostNodes(ghostRow.begin(), ghostRow.end());
400 std::sort(ghostNodes.begin(), ghostNodes.end());
401
402 for (rowsize side = 0; side < 2; side++)
403 {
404 index parentLocalAppended = result.entity2parent(entityLocalAppended, side);
405 if (parentLocalAppended == UnInitIndex)
406 continue;
407 rowsize nSubs = result.parent2entity.RowSize(parentLocalAppended);
408 bool found = false;
409 for (rowsize j = 0; j < nSubs; j++)
410 {
411 if (result.parent2entity(parentLocalAppended, j) != -1)
412 continue;
413
414 // Get the old entity that was in this slot and compare node sets.
415 index oldEntIdx = localResult.parent2entity.father->operator()(parentLocalAppended, j);
416 auto oldRow = localResult.entity2node.father->operator[](oldEntIdx);
417 std::vector<index> oldNodes(oldRow.begin(), oldRow.end());
418 std::sort(oldNodes.begin(), oldNodes.end());
419
420 if (ghostNodes == oldNodes)
421 {
422 result.parent2entity(parentLocalAppended, j) = entityLocalAppended;
423 found = true;
424 break;
425 }
426 }
427 DNDS_assert_info(found,
428 fmt::format("InterpolateDistributed: ghost entity {} "
429 "could not be matched to parent {}",
430 iGhost, parentLocalAppended));
431 }
432 }
433
434 return result;
435 }
436
437 // =================================================================
438 // InterpolateGlobal: distributed sub-entity creation
439 // =================================================================
440
441 template <rowsize p2n_rs, rowsize e2p_rs>
443 const ArrayAdjacencyPair<p2n_rs> &parent2node,
444 const tPbiPair &parent2nodePbi,
445 const OffsetAscendIndexMapping &parentGhostMapping,
446 const GlobalOffsetsMapping &parentGlobalMapping,
449 index nLocalParents,
450 index nTotalParents,
451 index nNode,
453 const MPIInfo &mpi)
454 {
455 const bool hasPbi = bool(parent2nodePbi.father);
456
457 // ============================================================
458 // Step 1: Local interpolation on all parents (father + ghost)
459 // ============================================================
460 auto localResult = Interpolate(parent2node, query, nTotalParents, nNode, mpi);
461 index nAllEntities = localResult.nEntities;
462
463 // ============================================================
464 // Step 2: Extract B→C pbi via SubEntityQueryPbi::extractPbi
465 // ============================================================
466 // Pbi is extracted from the FIRST parent's perspective.
467 // Stored per local entity, used for dedup fingerprint and output.
468 constexpr int maxSubEntityNodes = 10;
469 std::vector<std::vector<NodePeriodicBits>> localEntityPbi(nAllEntities);
470 if (hasPbi && query.extractPbi)
471 {
472 // For each entity, extract pbi from its first parent
473 for (index iEnt = 0; iEnt < nAllEntities; iEnt++)
474 {
475 index firstParent = localResult.entity2parent.father->operator()(iEnt, 0);
476 // Find which sub-entity slot of firstParent corresponds to iEnt
477 int iSub = -1;
478 for (rowsize j = 0; j < localResult.parent2entity.father->RowSize(firstParent); j++)
479 {
480 if (localResult.parent2entity.father->operator()(firstParent, j) == iEnt)
481 {
482 iSub = j;
483 break;
484 }
485 }
486 DNDS_assert(iSub >= 0);
487
488 auto desc = query.describe(firstParent, iSub);
489 localEntityPbi[iEnt].resize(desc.nNodes);
490 auto pbiAccessor = [&](int k) -> NodePeriodicBits
491 {
492 return parent2nodePbi[firstParent][k];
493 };
494 query.extractPbi(firstParent, iSub, pbiAccessor,
495 localEntityPbi[iEnt].data());
496 }
497 }
498
499 // ============================================================
500 // Step 2b: Compute parent2entityPbi for all parents
501 // ============================================================
502 // For each (parent, sub) pair, compute the uniform XOR between this
503 // parent's sub-entity pbi and the entity's stored pbi (first-parent view).
504 // This is a single NodePeriodicBits value per (parent, sub) slot.
505 //
506 // The XOR must be computed per matching node (by node index), not per
507 // position, because different parents may enumerate the same face/edge
508 // nodes in a different local order.
509 std::vector<std::vector<NodePeriodicBits>> localParent2EntityPbi;
510 if (hasPbi && query.extractPbi)
511 {
512 localParent2EntityPbi.resize(nTotalParents);
513 std::array<NodePeriodicBits, maxSubEntityNodes> thisPbiBuf{};
514 std::array<index, maxSubEntityNodes> thisNodeBuf{};
515 for (index iParent = 0; iParent < nTotalParents; iParent++)
516 {
517 rowsize nSubs = localResult.parent2entity.father->RowSize(iParent);
518 localParent2EntityPbi[iParent].resize(nSubs);
519 for (rowsize j = 0; j < nSubs; j++)
520 {
521 index iEnt = localResult.parent2entity.father->operator()(iParent, j);
522 auto desc = query.describe(iParent, j);
523
524 // Extract pbi and nodes from THIS parent's view
525 auto pbiAccessor = [&](int k) -> NodePeriodicBits
526 {
527 return parent2nodePbi[iParent][k];
528 };
529 query.extractPbi(iParent, j, pbiAccessor, thisPbiBuf.data());
530
531 auto nodeAccessor = [&](int k) -> index
532 {
533 return parent2node[iParent][k];
534 };
535 query.extractNodes(iParent, j, nodeAccessor, thisNodeBuf.data());
536
537 // Entity's stored pbi and nodes (from first parent's view)
538 auto &entPbi = localEntityPbi[iEnt];
539 auto entNodeRow = localResult.entity2node.father->operator[](iEnt);
540 DNDS_assert(desc.nNodes == static_cast<int>(entPbi.size()));
541
542 if (desc.nNodes > 0)
543 {
544 // Build sorted (node, pbi) pairs for this parent
545 using NP = std::pair<index, uint8_t>;
546 std::vector<NP> thisNP(desc.nNodes), entNP(desc.nNodes);
547 for (int k = 0; k < desc.nNodes; k++)
548 {
549 thisNP[k] = {thisNodeBuf[k], uint8_t(thisPbiBuf[k])};
550 entNP[k] = {entNodeRow[k], uint8_t(entPbi[k])};
551 }
552 auto cmp = [](const NP &a, const NP &b)
553 { return a.first == b.first ? a.second < b.second : a.first < b.first; };
554 std::sort(thisNP.begin(), thisNP.end(), cmp);
555 std::sort(entNP.begin(), entNP.end(), cmp);
556
557 // Verify same node set and compute uniform XOR
558 uint8_t xorVal = thisNP[0].second ^ entNP[0].second;
559 for (int k = 1; k < desc.nNodes; k++)
560 {
561 DNDS_assert_info(thisNP[k].first == entNP[k].first,
562 fmt::format("parent2entityPbi: node mismatch at parent {} sub {} node {}: "
563 "this={}, ent={}",
564 iParent, j, k, thisNP[k].first, entNP[k].first));
565 uint8_t xk = thisNP[k].second ^ entNP[k].second;
566 DNDS_assert_info(xk == xorVal,
567 fmt::format("parent2entityPbi: non-uniform XOR at parent {} sub {} node {}: "
568 "expected 0x{:02x}, got 0x{:02x}",
569 iParent, j, k, xorVal, xk));
570 }
571 localParent2EntityPbi[iParent][j] = NodePeriodicBits{xorVal};
572 }
573 else
574 {
575 localParent2EntityPbi[iParent][j] = NodePeriodicBits{0};
576 }
577 }
578 }
579 }
580
581 // ============================================================
582 // Step 3: Classify entities and resolve ownership
583 // ============================================================
584 // For each entity, determine: fully local, fully ghost, or straddling.
585 // For straddling, call the ownership resolver.
586
587 // Map parent local-appended → rank
588 auto getParentRank = [&](index parentLocal) -> MPI_int
589 {
590 index parentGlobal = parentGhostMapping(-1, parentLocal);
591 MPI_int rank = UnInitMPIInt;
592 index val = UnInitIndex;
593 auto ret = parentGlobalMapping.search(parentGlobal, rank, val);
594 DNDS_assert(ret);
595 return rank;
596 };
597
598 struct EntityClassification
599 {
600 bool owned{false};
601 bool discard{false}; // fully ghost
602 std::vector<MPI_int> peerRanks; // ranks that need this B's global ID
603 };
604 std::vector<EntityClassification> classifications(nAllEntities);
605
606 // Precompute per-entity: parent list + parent ranks
607 for (index iEnt = 0; iEnt < nAllEntities; iEnt++)
608 {
609 auto parentRow = localResult.entity2parent.father->operator[](iEnt);
610 bool anyLocal = false, anyGhost = false;
611 std::vector<index> parents;
612 std::vector<MPI_int> parentRanks;
613 parents.reserve(parentRow.size());
614 parentRanks.reserve(parentRow.size());
615
616 for (auto p : parentRow)
617 {
618 parents.push_back(p);
619 MPI_int r = getParentRank(p);
620 parentRanks.push_back(r);
621 if (p < nLocalParents)
622 anyLocal = true;
623 else
624 anyGhost = true;
625 }
626
627 if (!anyLocal)
628 {
629 // Fully ghost — discard
630 classifications[iEnt].discard = true;
631 continue;
632 }
633
634 if (!anyGhost)
635 {
636 // Fully local — owned, no peers
637 classifications[iEnt].owned = true;
638 continue;
639 }
640
641 // Straddling — call ownership resolver
642 auto decision = resolver(parents, parentRanks, nLocalParents);
643 classifications[iEnt].owned = decision.owned;
644 classifications[iEnt].discard = !decision.owned;
645 if (decision.owned)
646 classifications[iEnt].peerRanks = std::move(decision.peerRanks);
647 }
648
649 // ============================================================
650 // Step 4: Compact owned entities, build owned arrays
651 // ============================================================
652 std::vector<index> oldToOwned(nAllEntities, UnInitIndex);
653 index nOwned = 0;
654 for (index iEnt = 0; iEnt < nAllEntities; iEnt++)
655 {
656 if (classifications[iEnt].owned)
657 {
658 oldToOwned[iEnt] = nOwned;
659 nOwned++;
660 }
661 }
662
664 result.nOwnedEntities = nOwned;
665
666 // entity2node (global C indices)
667 result.entity2node.InitPair("InterpGlobal_e2n", mpi);
668 result.entity2node.father->Resize(nOwned);
669
670 // entity2parent (global A indices, variable-width)
671 result.entity2parent.InitPair("InterpGlobal_e2p", mpi);
672 result.entity2parent.father->Resize(nOwned);
673
674 // entity2nodePbi
675 if (hasPbi)
676 result.entity2nodePbi.InitPair("InterpGlobal_e2nPbi", mpi);
677 if (hasPbi)
678 result.entity2nodePbi.father->Resize(nOwned);
679
680 // entityElemInfo
681 result.entityElemInfo.InitPair("InterpGlobal_eInfo", mpi);
682 result.entityElemInfo.father->Resize(nOwned);
683
684 for (index iEnt = 0; iEnt < nAllEntities; iEnt++)
685 {
686 index iNew = oldToOwned[iEnt];
687 if (iNew == UnInitIndex)
688 continue;
689
690 // entity2node → global C indices
691 auto nodeRow = localResult.entity2node.father->operator[](iEnt);
692 result.entity2node.father->ResizeRow(iNew, nodeRow.size());
693 for (rowsize j = 0; j < static_cast<rowsize>(nodeRow.size()); j++)
694 result.entity2node.father->operator()(iNew, j) =
695 nodeGhostMapping(-1, nodeRow[j]);
696
697 // entity2parent → global A indices
698 auto parentRow = localResult.entity2parent.father->operator[](iEnt);
699 if constexpr (e2p_rs == NonUniformSize)
700 {
701 result.entity2parent.father->ResizeRow(iNew, parentRow.size());
702 for (rowsize j = 0; j < static_cast<rowsize>(parentRow.size()); j++)
703 result.entity2parent.father->operator()(iNew, j) =
704 parentGhostMapping(-1, parentRow[j]);
705 }
706 else
707 {
708 // Fixed-width: fill up to e2p_rs, pad with UnInitIndex.
709 for (rowsize j = 0; j < e2p_rs; j++)
710 result.entity2parent.father->operator()(iNew, j) =
711 (j < static_cast<rowsize>(parentRow.size()))
712 ? parentGhostMapping(-1, parentRow[j])
713 : UnInitIndex;
714 }
715
716 // entity2nodePbi
717 if (hasPbi)
718 {
719 auto &pbi = localEntityPbi[iEnt];
720 result.entity2nodePbi.father->ResizeRow(iNew, pbi.size());
721 for (rowsize j = 0; j < static_cast<rowsize>(pbi.size()); j++)
722 result.entity2nodePbi.father->operator()(iNew, j) = pbi[j];
723 }
724
725 // entityElemInfo
726 result.entityElemInfo.father->operator()(iNew, 0) =
727 localResult.entityElemInfo[iEnt];
728 }
729 result.entity2node.father->Compress();
730 if (hasPbi)
731 result.entity2nodePbi.father->Compress();
732
733 // ============================================================
734 // Step 5: Assign global B indices
735 // ============================================================
736 result.entity2node.TransAttach();
737 result.entity2node.trans.createFatherGlobalMapping();
738 auto faceGlobalMapping = result.entity2node.trans.pLGlobalMapping;
739 index myFaceOffset = (*faceGlobalMapping)(mpi.rank, 0);
740
741 // ============================================================
742 // Step 6: Build parent2entity (A→B) with global B indices
743 // ============================================================
744 // For local A parents: remap owned entities via oldToOwned → global B ID.
745 // For non-owned entities: need to receive global B ID from owner.
746 //
747 // parent2entityPbi does NOT need to be pushed: Step 2b already computed
748 // localParent2EntityPbi[iParent][j] for ALL parents (father+son),
749 // because the XOR is a local property of (cell, sub-entity slot) that
750 // only depends on cell2nodePbi and entity2nodePbi — both available locally.
751
752 // 6a: Push global B IDs to peer ranks.
753 // For each owned B with ghost A-parents, push (globalA, globalB, subIdx)
754 // so the peer can fill its parent2entity at the correct slot.
755 std::vector<std::vector<std::array<index, 3>>> pushMessages(mpi.size);
756 for (index iEnt = 0; iEnt < nAllEntities; iEnt++)
757 {
758 if (!classifications[iEnt].owned)
759 continue;
760 index iNew = oldToOwned[iEnt];
761 index globalB = myFaceOffset + iNew;
762
763 auto parentRow = localResult.entity2parent.father->operator[](iEnt);
764 for (auto pLocal : parentRow)
765 {
766 if (pLocal >= nLocalParents) // ghost parent
767 {
768 MPI_int pRank = getParentRank(pLocal);
769 index pGlobal = parentGhostMapping(-1, pLocal);
770 int subIdx = -1;
771 for (rowsize s = 0; s < localResult.parent2entity.father->RowSize(pLocal); s++)
772 {
773 if (localResult.parent2entity.father->operator()(pLocal, s) == iEnt)
774 {
775 subIdx = s;
776 break;
777 }
778 }
779 DNDS_assert(subIdx >= 0);
780 pushMessages[pRank].push_back({pGlobal, globalB, static_cast<index>(subIdx)});
781 }
782 }
783 }
784
785 // 6b: AllToAllv the (globalA, globalB, subIdx) triplets.
786 std::vector<int> sendCounts(mpi.size), recvCounts(mpi.size);
787 for (MPI_int r = 0; r < mpi.size; r++)
788 sendCounts[r] = static_cast<int>(pushMessages[r].size() * 3);
789 MPI::Alltoall(sendCounts.data(), 1, MPI_INT,
790 recvCounts.data(), 1, MPI_INT, mpi.comm);
791
792 std::vector<int> sendDisp(mpi.size + 1, 0), recvDisp(mpi.size + 1, 0);
793 for (MPI_int r = 0; r < mpi.size; r++)
794 {
795 sendDisp[r + 1] = sendDisp[r] + sendCounts[r];
796 recvDisp[r + 1] = recvDisp[r] + recvCounts[r];
797 }
798
799 std::vector<index> sendBuf(sendDisp.back());
800 for (MPI_int r = 0; r < mpi.size; r++)
801 {
802 index offset = sendDisp[r];
803 for (auto &msg : pushMessages[r])
804 {
805 sendBuf[offset++] = msg[0];
806 sendBuf[offset++] = msg[1];
807 sendBuf[offset++] = msg[2];
808 }
809 }
810 std::vector<index> recvBuf(recvDisp.back());
811 MPI::Alltoallv(sendBuf.data(), sendCounts.data(), sendDisp.data(), DNDS_MPI_INDEX,
812 recvBuf.data(), recvCounts.data(), recvDisp.data(), DNDS_MPI_INDEX,
813 mpi.comm);
814
815 // 6c: Build a map: (globalA, subIdx) → globalB for received triplets.
816 std::unordered_map<index, std::unordered_map<int, index>> receivedA2SubB;
817 for (index i = 0; i < static_cast<index>(recvBuf.size()); i += 3)
818 {
819 index gA = recvBuf[i];
820 index gB = recvBuf[i + 1];
821 int subIdx = static_cast<int>(recvBuf[i + 2]);
822 receivedA2SubB[gA][subIdx] = gB;
823 }
824
825 // 6d: Build parent2entity and parent2entityPbi for all parents (father + son).
826 result.parent2entity.InitPair("InterpGlobal_p2e", mpi);
827 result.parent2entity.father->Resize(nLocalParents);
828 result.parent2entity.son = std::make_shared<tAdj::element_type>(
829 ObjName{"InterpGlobal_p2e.son"}, mpi);
830 result.parent2entity.son->Resize(nTotalParents - nLocalParents);
831
832 if (hasPbi)
833 {
834 result.parent2entityPbi.InitPair("InterpGlobal_p2ePbi", mpi);
835 result.parent2entityPbi.father->Resize(nLocalParents);
836 result.parent2entityPbi.son = std::make_shared<typename decltype(result.parent2entityPbi.son)::element_type>(
837 ObjName{"InterpGlobal_p2ePbi.son"}, NodePeriodicBits::CommType(), NodePeriodicBits::CommMult(), mpi);
838 result.parent2entityPbi.son->Resize(nTotalParents - nLocalParents);
839 }
840
841 for (index iParent = 0; iParent < nTotalParents; iParent++)
842 {
843 rowsize nSubs = localResult.parent2entity.father->RowSize(iParent);
844 result.parent2entity.ResizeRow(iParent, nSubs);
845 if (hasPbi)
846 result.parent2entityPbi.ResizeRow(iParent, nSubs);
847
848 for (rowsize j = 0; j < nSubs; j++)
849 {
850 // parent2entityPbi is always from localParent2EntityPbi (computed
851 // in Step 2b for ALL parents, father+son).
852 if (hasPbi)
853 result.parent2entityPbi(iParent, j) =
854 localParent2EntityPbi[iParent][j];
855
856 index oldEntIdx = localResult.parent2entity.father->operator()(iParent, j);
857 index newOwnedIdx = oldToOwned[oldEntIdx];
858 if (newOwnedIdx != UnInitIndex)
859 {
860 // Owned B → global B ID
861 result.parent2entity(iParent, j) = myFaceOffset + newOwnedIdx;
862 }
863 else
864 {
865 // Non-owned B → look up from received messages by (globalA, subIdx).
866 index parentGlobal = parentGhostMapping(-1, iParent);
867 auto it = receivedA2SubB.find(parentGlobal);
868 if (it != receivedA2SubB.end())
869 {
870 auto jt = it->second.find(static_cast<int>(j));
871 if (jt != it->second.end())
872 {
873 result.parent2entity(iParent, j) = jt->second;
874 continue;
875 }
876 }
877 // No received B for this (parent, slot) — fully ghost B entity.
878 // Will be resolved after ghost B pull via cell2face ghost comm.
879 result.parent2entity(iParent, j) = UnInitIndex;
880 }
881 }
882 }
883
884 return result;
885 }
886
887} // namespace DNDS::Geom
#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
Table mapping rank-local row indices to the global index space.
bool search(index globalQuery, MPI_int &rank, index &val) const
Convert a global index to (rank, local). Returns false if out of range.
Mapping between a rank's main data and its ghost copies.
bool search_indexAppend(index globalQuery, MPI_int &rank, index &val) const
Like search, but the returned val is in the concatenated father+son address space.
std::function< OwnershipDecision(index parentL, index parentR, index nLocalParents)> OwnershipResolver2
Legacy 2-parent ownership resolver (used by InterpolateDistributed).
std::function< OwnershipDecision(const std::vector< index > &parents, const std::vector< MPI_int > &parentRanks, index nLocalParents)> OwnershipResolverMulti
MPI_int Alltoall(void *send, MPI_int sendNum, MPI_Datatype typeSend, void *recv, MPI_int recvNum, MPI_Datatype typeRecv, MPI_Comm comm)
Wrapper over MPI_Alltoall (fixed per-peer count).
Definition MPI.cpp:165
MPI_int Alltoallv(void *send, MPI_int *sendSizes, MPI_int *sendStarts, MPI_Datatype sendType, void *recv, MPI_int *recvSizes, MPI_int *recvStarts, MPI_Datatype recvType, MPI_Comm comm)
Wrapper over MPI_Alltoallv (variable per-peer counts + displacements).
Definition MPI.cpp:181
const MPI_Datatype DNDS_MPI_INDEX
MPI datatype matching index (= MPI_INT64_T).
Definition MPI.hpp:106
DNDS_CONSTANT const index UnInitIndex
Sentinel "not initialised" index value (= INT64_MIN).
Definition Defines.hpp:181
int32_t rowsize
Row-width / per-row element-count type (signed 32-bit).
Definition Defines.hpp:114
constexpr MPI_int UnInitMPIInt
Sentinel "not initialised" MPI_int value (= -1, invalid rank).
Definition MPI.hpp:66
DNDS_CONSTANT const rowsize NonUniformSize
Template parameter flag: "each row has an independent width".
Definition Defines.hpp:284
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
Definition Defines.hpp:112
int MPI_int
MPI counterpart type for MPI_int (= C int). Used for counts and ranks in MPI calls.
Definition MPI.hpp:54
Convenience bundle of a father, son, and attached ArrayTransformer.
ssp< TArray > father
Owned-side array (must be resized before ghost setup).
index nOwnedEntities
Number of owned entities (father size).
index nOwnedEntities
Number of owned B entities (father size).
ArrayAdjacencyPair< p2e_rs > parent2entity
parent → entities. Father-only. Slot j = sub-entity j.
static InterpolateResult Interpolate(const ArrayAdjacencyPair< p2n_rs > &parent2node, const SubEntityQuery &query, index nParent, index nNode, const MPIInfo &mpi)
static InterpolateGlobalResultT< e2p_rs > InterpolateGlobal(const ArrayAdjacencyPair< p2n_rs > &parent2node, const tPbiPair &parent2nodePbi, const OffsetAscendIndexMapping &parentGhostMapping, const GlobalOffsetsMapping &parentGlobalMapping, const OffsetAscendIndexMapping &nodeGhostMapping, const SubEntityQueryPbi &query, index nLocalParents, index nTotalParents, index nNode, const OwnershipResolverMulti &resolver, const MPIInfo &mpi)
static InterpolateDistributedResult InterpolateDistributed(const ArrayAdjacencyPair< p2n_rs > &parent2node, const OffsetAscendIndexMapping &parentGhostMapping, const SubEntityQuery &query, index nLocalParents, index nTotalParents, index nNode, const OwnershipResolver2 &resolver, const OffsetAscendIndexMapping &nodeGhostMapping, const MPIInfo &mpi)
static MPI_Datatype CommType()
int nNodes
Total number of nodes (vertices + mid-edge + ...).
int nVertices
Number of corner vertices (used for deduplication).
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
tVec r(NCells)
tVec b(NCells)
auto result
std::vector< DNDS::index > ghostNodes(ghostNodeSet.begin(), ghostNodeSet.end())
OwnershipResolverMulti resolver
const DNDS::index nNode
input explicitMaps push_back(EntityReorderMap{EntityKind::Cell, cellPartition})
const tPoint const tPoint const tPoint & p