DNDSR 0.2.1
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
MeshConnectivity.hpp
Go to the documentation of this file.
1#pragma once
2/// @file MeshConnectivity.hpp
3/// @brief Layered DAG of mesh adjacency relations with composable DSL operations.
4///
5/// MeshConnectivity manages adjacency (connectivity) tables between entity strata
6/// of different topological depths (cells, faces, edges, nodes). It provides three
7/// core operations:
8/// - Inverse: cone (A→B) → support (B→A)
9/// - Compose: A→B + B→C → A→C
10/// - ComposeFiltered: A→B + B→C → A→C with on-the-fly predicate filtering
11///
12/// Cone adjacencies (downward: higher→lower depth) are ordered by element topology.
13/// Support adjacencies (upward: lower→higher depth) are ordered by creation method
14/// (typically the order entities were discovered during inversion).
15///
16/// Periodic bits (pbi) are only stored on cones whose target depth is 0 (nodes),
17/// since pbi tracks how each node's coordinates transform under periodicity.
18
19#include "DNDS/ArrayPair.hpp"
21#include "Mesh_DeviceView.hpp"
22
23#include <variant>
24#include <unordered_map>
25#include <unordered_set>
26
27namespace DNDS::Geom
28{
29 // Forward declaration for registerAdj overload
30 template <class TPair>
31 struct AdjPairTracked;
32
33 // =================================================================
34 // EntityKind: logical entity roles
35 // =================================================================
36
37 /// Logical entity roles in the mesh. Topological depth depends on the
38 /// mesh dimension (2D or 3D).
39 ///
40 /// In 3D: Cell=3, Face=2, Edge=1, Node=0.
41 /// In 2D: Cell=2, Face=1, Edge=1 (==Face), Node=0.
42 /// Bnd shares Face's depth but is stored separately (zone-labeled subset).
43 enum class EntityKind : int8_t
44 {
45 Cell = 0,
46 Face = 1,
47 Edge = 2,
48 Node = 3,
49 Bnd = 4,
50 NUM_KINDS = 5,
51 };
52
53 /// Resolve EntityKind to topological depth for a given mesh dimension.
54 /// In 2D, Edge and Face both resolve to dim-1 = 1.
55 /// Bnd resolves to dim-1 (same depth as Face).
56 inline int entityDepth(EntityKind kind, int dim)
57 {
58 switch (kind)
59 {
61 return dim;
63 return dim - 1;
65 // NOLINTNEXTLINE(bugprone-branch-clone) — Edge depth is always 1 in both 2D and 3D
66 return (dim == 2) ? 1 : 1;
68 return 0;
69 case EntityKind::Bnd:
70 return dim - 1; // same depth as Face
71 default:
72 return -1;
73 }
74 }
75
76 /// String name for an EntityKind (for diagnostics).
77 inline const char *entityKindName(EntityKind kind)
78 {
79 switch (kind)
80 {
82 return "Cell";
84 return "Face";
86 return "Edge";
88 return "Node";
89 case EntityKind::Bnd:
90 return "Bnd";
91 default:
92 return "Unknown";
93 }
94 }
95
96 // =================================================================
97 // AdjKind: named adjacency hop
98 // =================================================================
99
100 /// Identifies a specific adjacency relation in the DAG.
101 ///
102 /// Direct adjacencies (from != to): cone or support between two entity strata.
103 /// e.g., AdjKind(Cell, Node) = cell2node cone.
104 /// e.g., AdjKind(Node, Cell) = node2cell support.
105 ///
106 /// Intra-level adjacencies (from == to): composed adjacency traversing through
107 /// a lower-level intermediary. Default intermediary is Node.
108 /// e.g., AdjKind(Cell, Cell) = cell2cell via Node (node-neighbor).
109 /// e.g., AdjKind(Cell, Cell, Face) = cell2cell via Face (face-neighbor).
110 /// e.g., AdjKind(Bnd, Bnd) = bnd2bnd via Node.
111 struct AdjKind
112 {
115 EntityKind via{EntityKind::Node}; ///< Intermediary for intra-level (from==to).
116 ///< Ignored for direct (from!=to).
117
118 constexpr AdjKind() = default;
119
120 /// Direct adjacency: from != to. `via` is ignored.
121 constexpr AdjKind(EntityKind from_, EntityKind to_)
122 : from(from_), to(to_)
123 {
124 }
125
126 /// Intra-level adjacency: from == to, with explicit intermediary.
127 constexpr AdjKind(EntityKind from_, EntityKind to_, EntityKind via_)
128 : from(from_), to(to_), via(via_)
129 {
130 }
131
132 /// Whether this is an intra-level (composed) adjacency.
133 [[nodiscard]] constexpr bool isIntraLevel() const { return from == to; }
134
135 /// Whether this is a direct (inter-level) adjacency.
136 [[nodiscard]] constexpr bool isDirect() const { return from != to; }
137
138 /// Equality comparison (for use in hash maps).
139 constexpr bool operator==(const AdjKind &o) const
140 {
141 if (from != o.from || to != o.to)
142 return false;
143 if (isIntraLevel())
144 return via == o.via;
145 return true; // direct: via is ignored
146 }
147
148 constexpr bool operator!=(const AdjKind &o) const { return !(*this == o); }
149 };
150
151 /// Hash for AdjKind (for use in unordered containers).
153 {
154 std::size_t operator()(const AdjKind &k) const noexcept
155 {
156 // Combine from, to, and (if intra-level) via into a single hash.
157 auto h = static_cast<std::size_t>(k.from) * 31 +
158 static_cast<std::size_t>(k.to);
159 if (k.isIntraLevel())
160 h = h * 31 + static_cast<std::size_t>(k.via);
161 return h;
162 }
163 };
164
165 /// Convenience constants for common adjacency kinds.
166 namespace Adj
167 {
168 // Direct cones (downward)
176
177 // Direct supports (upward)
188
189 // Intra-level (composed), default via Node
193
194 // Intra-level with explicit intermediary
196 } // namespace Adj
197
198 /// Format an AdjKind as a diagnostic string, e.g. "Cell2Node", "Cell2Cell(Node)".
199 std::string adjKindName(const AdjKind &kind);
200
201 // Forward declaration for CompiledGhostTree::checkAvailable.
202 struct MeshConnectivity;
203
204 // =================================================================
205 // GhostChain, GhostSpec: user-facing ghost specification
206 // =================================================================
207
208 /// One ghost chain: starts from owned entities of `anchor`, traverses
209 /// explicit adjacency hops, collects ghost entities of `target`.
210 ///
211 /// Validation rules (checked by CompiledGhostTree::compile):
212 /// - anchor == hops[0].from
213 /// - target == hops.back().to
214 /// - consecutive hops: hops[i].to == hops[i+1].from
215 /// - at least one hop
217 {
218 EntityKind anchor; ///< Owned entities to start from.
219 std::vector<AdjKind> hops; ///< Sequence of adjacency lookups.
220 EntityKind target; ///< Entity kind to ghost (must == hops.back().to).
221 };
222
223 /// Full ghost specification: multiple chains, possibly targeting the same
224 /// EntityKind. The ghost set per kind is the union of all chains' results.
226 {
227 std::vector<GhostChain> chains;
228
229 /// The default pipeline specification for 1 ghost layer.
230 /// Cell ghost: owned cells -> Cell2Cell -> cells
231 /// Node ghost: owned cells -> Cell2Cell -> Cell2Node -> nodes
232 /// Bnd ghost: owned bnds -> Bnd2Node -> Node2Bnd -> bnds
233 /// Bnd-node ghost: owned bnds -> Bnd2Node -> Node2Bnd -> Bnd2Node -> nodes
235
236 /// Parameterized default: nLayers hops of Cell2Cell for ghost cells.
237 static GhostSpec defaultPrimary(int nLayers);
238 };
239
240 // =================================================================
241 // CompiledGhostTree: chains compiled into BFS-ordered forest
242 // =================================================================
243
244 /// One node in the compiled ghost tree.
246 {
247 EntityKind kind; ///< Entity kind at this node.
248 AdjKind hop; ///< Adjacency used to reach this node from parent.
249 ///< Undefined (default-constructed) for root nodes.
250 bool collect{false}; ///< If true, non-owned entities here become ghosts.
251 int level{0}; ///< BFS depth (root = 0).
252 int id{-1}; ///< Unique ID within the tree (assigned by compile).
253 int parentId{-1}; ///< Parent node ID (-1 for roots).
254 std::vector<GhostTreeNode> children;
255 };
256
257 /// A reference to a tree node at a specific level, with its parent.
258 /// Used by the precomputed per-level lists.
260 {
261 int nodeId{}; ///< ID of the tree node.
262 int parentId{}; ///< ID of the parent node (-1 for roots).
263 EntityKind kind{}; ///< Entity kind of this node.
264 AdjKind hop; ///< Hop used to reach this node.
265 bool collect{}; ///< Whether to collect at this node.
266 bool hasChildren{}; ///< Whether this node has children (needs pull).
267 };
268
269 /// Compiled forest of ghost traversal chains.
270 ///
271 /// Multiple chains sharing common prefixes are merged into a trie to
272 /// avoid redundant traversals. The tree is evaluated BFS level-by-level
273 /// with pull barriers between levels.
274 ///
275 /// After compilation, `levels[L]` contains all tree nodes at BFS depth L,
276 /// with parent references for efficient evaluation (no recursive scans).
278 {
279 std::vector<GhostTreeNode> roots;
280 int maxLevel{0}; ///< Maximum BFS depth across all nodes.
281 int totalNodes{0}; ///< Total number of nodes (for flat array sizing).
282
283 /// Precomputed per-level node lists. `levels[L]` contains all tree
284 /// nodes at BFS depth L. Level 0 = roots.
285 std::vector<std::vector<LevelEntry>> levels;
286
287 /// Compile a GhostSpec into a forest. Validates chain consistency.
288 /// Assigns node IDs, builds per-level lists.
289 /// Throws std::runtime_error on invalid chains.
290 static CompiledGhostTree compile(const GhostSpec &spec);
291
292 /// Collect all distinct AdjKind values used by any hop in the tree.
293 [[nodiscard]] std::unordered_set<AdjKind, AdjKindHash> requiredAdjs() const;
294
295 /// Pre-check that all required adjacencies exist in the DAG.
296 /// Returns the set of missing AdjKind values (empty = all available).
297 [[nodiscard]] std::vector<AdjKind> checkAvailable(const MeshConnectivity &dag) const;
298
299 /// Collect all EntityKind values that appear at COLLECT nodes.
300 [[nodiscard]] std::unordered_set<EntityKind> collectedKinds() const;
301
302 /// Pretty-print the tree (for diagnostics).
303 [[nodiscard]] std::string dump() const;
304 };
305
306 // =================================================================
307 // GhostResult: output of ghost evaluation
308 // =================================================================
309
310 /// Result of evaluating a CompiledGhostTree.
311 /// Contains per-EntityKind sorted, deduplicated global indices to ghost.
313 {
314 /// Per EntityKind: sorted, deduplicated global indices to ghost.
315 std::unordered_map<EntityKind, std::vector<index>> ghostIndices;
316
317 /// Entity kinds that have ghosts on ANY rank (collective).
318 /// Populated by evaluateGhostTree via MPI_Allreduce.
319 std::unordered_set<EntityKind> activeKinds;
320
321 /// Whether any rank has ghosts for a given kind (collective).
322 /// Safe to branch on — consistent across all ranks.
323 [[nodiscard]] bool hasGhosts(EntityKind kind) const
324 {
325 return activeKinds.count(kind) > 0;
326 }
327
328 /// Total number of ghost entities on THIS rank.
329 [[nodiscard]] index totalGhosts() const
330 {
331 index total = 0;
332 for (const auto &[k, v] : ghostIndices)
333 total += static_cast<index>(v.size());
334 return total;
335 }
336 };
337 // =================================================================
338 // Type-erased adjacency storage
339 // =================================================================
340
341 /// Variant of all supported fixed-width adjacency pair types.
342 /// tAdjPair (NonUniformSize) is the general variable-width CSR;
343 /// tAdj1Pair..tAdj8Pair are compile-time fixed-width for performance
344 /// on hot-path adjacencies (face2cell = Adj2, bnd2face = Adj1, etc.).
345 using AdjVariant = std::variant<
346 tAdjPair, // variable-width (cell2node, cell2face, node2cell, cell2cell, ...)
347 tAdj1Pair, // 1 per row (bnd2face, face2bnd, cell2cellOrig, ...)
348 tAdj2Pair, // 2 per row (face2cell, bnd2cell)
349 tAdj3Pair, // 3 per row
350 tAdj4Pair, // 4 per row
351 tAdj8Pair>; // 8 per row
352
353 /// Convenience: create a shared AdjVariant from a specific pair type.
354 /// Usage: `auto adj = makeAdjVariant(myTAdj2Pair);`
355 /// or: `auto adj = makeAdjVariant<tAdj2Pair>();` (empty)
356 template <class TPair>
357 static ssp<AdjVariant> makeAdjVariant(TPair &&pair)
358 {
359 return make_ssp<AdjVariant>(std::forward<TPair>(pair));
360 }
361
362 /// Create a shared AdjVariant holding a default-constructed TPair.
363 template <class TPair>
364 static ssp<AdjVariant> makeAdjVariant()
365 {
366 return make_ssp<AdjVariant>(TPair{});
367 }
368
369 // =================================================================
370 // narrowAdjToFixed: variable-width → fixed-width conversion
371 // =================================================================
372
373 /// Copy a variable-width adjacency (father-only) into a fixed-width
374 /// target. Each row is truncated or padded with `fill` to exactly
375 /// `target_rs` entries.
376 ///
377 /// @tparam target_rs Compile-time row width of the target.
378 /// @tparam source_rs Row width of the source (typically NonUniformSize).
379 /// @param source Source adjacency (father-only or father+son).
380 /// @param target Target fixed-width adjacency. Father must be pre-Resize'd
381 /// to at least `nRows`.
382 /// @param nRows Number of rows to copy.
383 /// @param fill Value for missing entries (default: UnInitIndex).
384 template <rowsize target_rs, rowsize source_rs = NonUniformSize>
385 static void narrowAdjToFixed(
386 const ArrayAdjacencyPair<source_rs> &source,
387 ArrayAdjacencyPair<target_rs> &target,
388 index nRows,
389 index fill = UnInitIndex)
390 {
391 static_assert(target_rs > 0, "narrowAdjToFixed: target must be fixed-width");
392 for (index i = 0; i < nRows; i++)
393 {
394 auto row = source.father->operator[](i);
395 for (rowsize j = 0; j < target_rs; j++)
396 target.father->operator()(i, j) =
397 (j < static_cast<rowsize>(row.size())) ? row[j] : fill;
398 }
399 }
400
401 // =================================================================
402 // ConeAdj: downward adjacency (higher depth → lower depth)
403 // =================================================================
404
405 /// A cone (downward) adjacency from entities at `fromDepth` to entities at
406 /// `toDepth`. Row order is defined by element topology and must not be
407 /// permuted (e.g., cell2node ordering determines element shape functions).
408 ///
409 /// Periodic bits (`pbi`) are only meaningful when `toDepth == 0` (nodes).
410 /// For non-periodic meshes or non-node targets, `pbi` remains uninitialized.
411 ///
412 /// The adjacency data is stored via `ssp<AdjVariant>` for shared ownership.
413 /// Both the DAG and legacy mesh members can share the same allocation.
414 struct ConeAdj
415 {
416 int fromDepth{-1}; ///< Source stratum (e.g., dim for cells, dim-1 for faces)
417 int toDepth{-1}; ///< Target stratum (e.g., 0 for nodes, dim-1 for faces)
418 ssp<AdjVariant> adj; ///< Shared adjacency pair (typed by row width)
419 tPbiPair pbi; ///< Periodic bits per entry (only for toDepth==0, optional)
420
421 /// Access as variable-width tAdjPair. Throws if adj holds a fixed-width type.
422 tAdjPair &asAdj() { return std::get<tAdjPair>(*adj); }
423 [[nodiscard]] const tAdjPair &asAdj() const { return std::get<tAdjPair>(*adj); }
424
425 /// Access as fixed-width tAdj2Pair (e.g., for cell2face with 2 entries).
426 tAdj2Pair &asAdj2() { return std::get<tAdj2Pair>(*adj); }
427 [[nodiscard]] const tAdj2Pair &asAdj2() const { return std::get<tAdj2Pair>(*adj); }
428
429 tAdj1Pair &asAdj1() { return std::get<tAdj1Pair>(*adj); }
430 [[nodiscard]] const tAdj1Pair &asAdj1() const { return std::get<tAdj1Pair>(*adj); }
431
432 /// Typed access: `as<tAdj2Pair>()` etc.
433 template <class TPair>
434 TPair &as() { return std::get<TPair>(*adj); }
435 template <class TPair>
436 [[nodiscard]] const TPair &as() const { return std::get<TPair>(*adj); }
437
438 /// Number of local (father) rows.
439 [[nodiscard]] index fatherSize() const
440 {
441 if (!adj)
442 return 0;
443 return std::visit([](const auto &p) -> index
444 { return p.father ? p.father->Size() : 0; }, *adj);
445 }
446
447 /// Check if the adjacency pair is initialized (father is non-null).
448 [[nodiscard]] bool initialized() const
449 {
450 if (!adj)
451 return false;
452 return std::visit([](const auto &p) -> bool
453 { return bool(p.father); }, *adj);
454 }
455
456 /// Check if pbi is attached (only valid for toDepth==0).
457 [[nodiscard]] bool hasPbi() const { return bool(pbi.father); }
458 };
459
460 // =================================================================
461 // SupportAdj: upward adjacency (lower depth → higher depth)
462 // =================================================================
463
464 /// A support (upward) adjacency from entities at `fromDepth` to entities at
465 /// `toDepth`. Row order is determined by the creation method (typically
466 /// Inverse), and is stable but not semantically ordered.
467 ///
468 /// Supports never carry periodic bits — pbi is a property of cones to nodes.
469 ///
470 /// The adjacency data is stored via `ssp<AdjVariant>` for shared ownership.
472 {
473 int fromDepth{-1}; ///< Source stratum (e.g., 0 for nodes)
474 int toDepth{-1}; ///< Target stratum (e.g., dim for cells)
475 ssp<AdjVariant> adj; ///< Shared adjacency pair (typed by row width)
476
477 /// Access as variable-width tAdjPair.
478 tAdjPair &asAdj() { return std::get<tAdjPair>(*adj); }
479 [[nodiscard]] const tAdjPair &asAdj() const { return std::get<tAdjPair>(*adj); }
480
481 tAdj2Pair &asAdj2() { return std::get<tAdj2Pair>(*adj); }
482 [[nodiscard]] const tAdj2Pair &asAdj2() const { return std::get<tAdj2Pair>(*adj); }
483
484 tAdj1Pair &asAdj1() { return std::get<tAdj1Pair>(*adj); }
485 [[nodiscard]] const tAdj1Pair &asAdj1() const { return std::get<tAdj1Pair>(*adj); }
486
487 /// Typed access: `as<tAdj2Pair>()` etc.
488 template <class TPair>
489 TPair &as() { return std::get<TPair>(*adj); }
490 template <class TPair>
491 [[nodiscard]] const TPair &as() const { return std::get<TPair>(*adj); }
492
493 [[nodiscard]] index fatherSize() const
494 {
495 if (!adj)
496 return 0;
497 return std::visit([](const auto &p) -> index
498 { return p.father ? p.father->Size() : 0; }, *adj);
499 }
500
501 [[nodiscard]] bool initialized() const
502 {
503 if (!adj)
504 return false;
505 return std::visit([](const auto &p) -> bool
506 { return bool(p.father); }, *adj);
507 }
508 };
509
510 // =================================================================
511 // SharedCountPredicate
512 // =================================================================
513
514 /// Predicate for ComposeFiltered: keep (A, C) pairs sharing >= minShared
515 /// intermediate B-entities.
517 {
518 int minShared{1};
519 bool removeSelf{false};
520
521 bool operator()(index a, index c, int nShared) const
522 {
523 if (removeSelf && a == c)
524 return false;
525 return nShared >= minShared;
526 }
527 };
528
529 // =================================================================
530 // SubEntityDef: user-provided sub-entity topology query
531 // =================================================================
532
533 /// Describes one sub-entity extracted from a parent entity.
534 /// Returned by SubEntityQuery::describe().
536 {
537 int nVertices{0}; ///< Number of corner vertices (used for deduplication).
538 int nNodes{0}; ///< Total number of nodes (vertices + mid-edge + ...).
539 t_index typeTag{0}; ///< Element type tag to store in entityElemInfo.
540 ///< Opaque to Interpolate; only used for type-match
541 ///< during deduplication (two sub-entities with different
542 ///< typeTags are never considered duplicates).
543 };
544
545 /// User-provided callbacks that describe how to decompose parent entities
546 /// into sub-entities. This decouples Interpolate from any specific element
547 /// topology module.
548 ///
549 /// Example: to extract faces from cells, the caller would implement:
550 /// - numSubEntities(iParent): returns eCell.GetNumFaces()
551 /// - describe(iParent, iSub): returns {nVerts, nNodes, faceElemType}
552 /// - extractNodes(iParent, iSub, parentNodes, out): calls ExtractFaceNodes
553 /// - matchExtra (optional): periodic collaborating check
555 {
556 /// Number of sub-entities for parent iParent.
557 std::function<int(index iParent)> numSubEntities;
558
559 /// Describe sub-entity iSub of parent iParent.
560 std::function<SubEntityDesc(index iParent, int iSub)> describe;
561
562 /// Extract node indices of sub-entity iSub from parentNodes into out.
563 /// Must write exactly desc.nNodes entries starting at out[0].
564 /// parentNodes is an indexable range (operator[] returning index).
565 std::function<void(index iParent, int iSub,
566 const std::function<index(int)> &parentNodes,
567 index *out)>
569
570 /// Optional extra match predicate for deduplication.
571 ///
572 /// Called after vertex-set match succeeds. If set, must return true
573 /// for the match to be accepted. Used for periodic meshes to implement
574 /// the "collaborating" check (uniform XOR of periodic bits).
575 ///
576 /// @param iParent Current parent entity index.
577 /// @param iSub Current sub-entity index within parent.
578 /// @param iCandEntity Index of the candidate entity in the result arrays.
579 /// @param candidateParent Index of the parent that created the candidate entity.
580 /// @param candidateSub Sub-entity index that created the candidate entity.
581 /// @return true if the match is valid, false to reject.
582 std::function<bool(index iParent, int iSub,
583 index iCandEntity,
584 index candidateParent, int candidateSub)>
586 };
587
588 // =================================================================
589 // InterpolateGlobal: distributed sub-entity creation with global dedup
590 // =================================================================
591
592 /// Extended sub-entity query with periodic bit extraction.
593 ///
594 /// Inherits all callbacks from SubEntityQuery and adds extractPbi for
595 /// periodic meshes. Used by InterpolateGlobal.
596 ///
597 /// extractPbi must produce node-parallel pbi matching extractNodes output:
598 /// extractPbi(iParent, iSub, ...)[k] is the pbi for the node at position k
599 /// in the sub-entity's node list (same ordering as extractNodes).
601 {
602 /// Extract pbi of sub-entity iSub from parentPbi into out.
603 /// Must write exactly desc.nNodes entries starting at out[0].
604 /// Ordering must match extractNodes (out[k] corresponds to node k).
605 /// Only called when the mesh is periodic.
606 std::function<void(index iParent, int iSub,
607 const std::function<NodePeriodicBits(int)> &parentPbi,
608 NodePeriodicBits *out)>
610 };
611
612 /// Ownership decision for a globally-deduplicated B entity.
613 /// Returned by the OwnershipResolverMulti callback.
615 {
616 bool owned; ///< true if this rank owns the entity.
617 /// Peer ranks that need this entity pushed (for owned entities).
618 /// Empty if fully local or single-sided.
619 /// For non-owned entities, this is unused.
620 std::vector<MPI_int> peerRanks;
621 };
622
623 /// Callback for ownership resolution during distributed interpolation.
624 ///
625 /// Called for each locally-enumerated entity with its parent information.
626 /// Faces have exactly 2 parents; edges can have N >= 2.
627 ///
628 /// @param parents All parent indices (local-appended) of this entity.
629 /// [0, nLocalParents) = owned, [nLocal, nTotal) = ghost.
630 /// @param nLocalParents Number of owned (father) parents on this rank.
631 /// @param parentRanks Rank owning each parent (parallel to parents vector).
632 /// @return OwnershipDecision: whether this rank owns the entity and which
633 /// peers need it pushed.
635 const std::vector<index> &parents,
636 const std::vector<MPI_int> &parentRanks,
637 index nLocalParents)>;
638
639 /// Legacy 2-parent ownership resolver (used by InterpolateDistributed).
641 index parentL, index parentR, index nLocalParents)>;
642
643 /// Result of distributed interpolation with globally-unique B entities.
644 ///
645 /// Given A→C cone (global C indices, A is ghosted via A→C→A), creates
646 /// globally-unique B entities (faces or edges) with ownership resolution.
647 ///
648 /// ## Outputs
649 ///
650 /// - **parent2entity**: A→B mapping in global B indices. Father = local A,
651 /// son = ghost A. Slot ordering matches element topology: slot j is
652 /// face/edge j as defined by Element::ObtainFace(j)/ObtainEdge(j).
653 /// Some son entries may be UnInitIndex (fully-ghost B not resolved by
654 /// the push protocol — resolved later by the caller's ghost B pull).
655 ///
656 /// - **parent2entityPbi**: Parallel to parent2entity. One NodePeriodicBits
657 /// per (A, B) slot — the uniform XOR between A's sub-entity node-pbi
658 /// and B's stored entity2nodePbi. To get B's node coords in A's frame:
659 /// apply parent2entityPbi to entity2nodePbi, then GetCoordByBits.
660 /// For faces, this is at most 1-bit (a face crosses one periodic
661 /// boundary). For edges, can be multi-bit (e.g., P1|P2 for a corner
662 /// edge). Zero for the first parent. Empty if not periodic.
663 /// Computed locally — no MPI push needed (depends only on cell2nodePbi
664 /// and entity2nodePbi, both available after Step 2b).
665 ///
666 /// - **entity2node**: B→C mapping in global C indices. Father-only (owned B).
667 /// Node ordering is from the first-discovered parent's ExtractFaceNodes/
668 /// ExtractEdgeNodes call — this is the entity's own canonical ordering.
669 ///
670 /// - **entity2nodePbi**: B→C pbi. Father-only. Parallel to entity2node.
671 /// Pbi from the first-discovered parent's perspective (the entity's own
672 /// frame). Other parents' perspectives differ by parent2entityPbi.
673 /// Empty if not periodic.
674 ///
675 /// - **entity2parent**: B→A mapping in global A indices. Father-only.
676 /// Variable-width: 1 (boundary) or 2 (internal) for faces, 1..N for edges.
677 /// Complete under A→C→A ghosting (all parents sharing any C-vertex with
678 /// a local A are present as ghosts, so all parents of any locally-visible
679 /// B entity are enumerated).
680 ///
681 /// - **entityElemInfo**: Per-entity element info. Father-only.
682 ///
683 /// ## Ghost B entities
684 ///
685 /// NOT included. Use evaluateGhostTree(A → A2B → B) to pull them, then
686 /// BorrowAndPull on entity2node / entity2nodePbi / entityElemInfo.
687 /// parent2entityPbi for ghost A parents is already populated (the push
688 /// only carries the global B index; pbi is local). After ghost B pull,
689 /// entity2parent for ghost B can be obtained via Inverse(parent2entity)
690 /// or by ghost-pulling entity2parent itself.
691 /// @tparam e2p_rs Row-size of entity2parent (NonUniformSize = variable,
692 /// 2 = fixed for faces, etc.).
693 template <rowsize e2p_rs = NonUniformSize>
695 {
696 tAdjPair parent2entity; ///< A → B (global B indices). Father = local A,
697 ///< son = ghost A. Slot j = face/edge j per topology.
698 tPbiPair parent2entityPbi; ///< A → B pbi (parallel to parent2entity).
699 ///< Uniform XOR: A's sub-entity node-pbi vs B's stored
700 ///< entity2nodePbi. Faces: at most 1 bit. Edges: multi-bit.
701 ///< 0 for B's first parent. Empty if not periodic.
702 ///< No push needed — computed locally in Step 2b.
703 tAdjPair entity2node; ///< B → C (global C indices). Father-only (owned B).
704 ///< Node order: first-discovered parent's extraction order.
705 ArrayAdjacencyPair<e2p_rs> entity2parent; ///< B → A (global A indices). Father-only (owned B).
706 ///< Width 1-2 for faces, 1-N for edges.
707 ///< Complete under A→C→A ghosting.
708 tPbiPair entity2nodePbi; ///< B → C pbi. Father-only (owned B).
709 ///< First-discovered parent's perspective (entity's own frame).
710 ///< Parallel to entity2node. Empty if not periodic.
711 tElemInfoArrayPair entityElemInfo; ///< Per-B elem info. Father-only (owned B).
712 index nOwnedEntities{0}; ///< Number of owned B entities (father size).
713 };
714
715 /// Default: all fields variable-width.
717
718 // =================================================================
719 // InterpolateResult: output of LOCAL sub-entity interpolation
720 // =================================================================
721
722 /// Result of interpolating (extracting) sub-entities from parent→node connectivity.
723 ///
724 /// Given parent→node (e.g., cell→node), Interpolate creates intermediate entities
725 /// (e.g., faces or edges) by extracting sub-entities from element topology,
726 /// deduplicating by sorted vertex comparison, and building both parent→entity
727 /// and entity→node adjacencies.
728 ///
729 /// All indices are local (0-based within the input arrays). No MPI communication
730 /// is performed — the caller is responsible for providing a complete view
731 /// (local + ghost cells) and for subsequent ownership resolution / ghost exchange.
732 /// Result of local (rank-only) sub-entity interpolation.
733 ///
734 /// Given parent→node (e.g., cell→node), Interpolate creates intermediate entities
735 /// (e.g., faces or edges) by extracting sub-entities from element topology,
736 /// deduplicating by sorted vertex comparison (+ optional matchExtra for periodic),
737 /// and building both parent→entity and entity→node adjacencies.
738 ///
739 /// All indices are local (0-based within the input arrays). No MPI communication
740 /// is performed. The caller provides a complete view (local + ghost cells) and
741 /// handles subsequent ownership resolution / ghost exchange.
742 ///
743 /// ## Ordering guarantees
744 ///
745 /// - parent2entity[iParent][j] corresponds to sub-entity j of parent iParent
746 /// as defined by query.numSubEntities / query.describe / query.extractNodes.
747 /// - entity2node[iEnt] stores nodes in the order extracted by the **first parent**
748 /// that created entity iEnt. Later parents that find the same entity do not
749 /// alter the node order.
750 /// - entity2parent[iEnt] lists parents in discovery order (first parent first).
751 ///
752 /// @tparam p2e_rs Row-size of parent2entity (NonUniformSize = variable).
753 /// @tparam e2n_rs Row-size of entity2node (NonUniformSize = variable).
754 /// @tparam e2p_rs Row-size of entity2parent (NonUniformSize = variable).
755 template <rowsize p2e_rs = NonUniformSize,
756 rowsize e2n_rs = NonUniformSize,
757 rowsize e2p_rs = NonUniformSize>
759 {
760 ArrayAdjacencyPair<p2e_rs> parent2entity; ///< parent → entities. Father-only. Slot j = sub-entity j.
761 ArrayAdjacencyPair<e2n_rs> entity2node; ///< entity → nodes. Father-only. First-parent extraction order.
762 ArrayAdjacencyPair<e2p_rs> entity2parent; ///< entity → parents. Father-only. Variable-width:
763 ///< 1 for boundary faces, 2 for internal faces, N for edges.
764 ///< Discovery order (first parent first).
765 std::vector<ElemInfo> entityElemInfo; ///< Per-entity element info (zone=0, type from SubEntityDesc::typeTag).
766 std::vector<std::vector<NodePeriodicBits>> parent2entityPbi;
767 ///< Per-parent, per-sub pbi. Parallel to parent2entity.
768 ///< Populated by InterpolateGlobal Step 2b, not by
769 ///< InterpolateLocal itself. Empty if not periodic or
770 ///< if InterpolateLocal was called directly.
771 index nEntities{0}; ///< Total number of unique entities created.
772 };
773
774 /// Default InterpolateResult: all fields are variable-width (NonUniformSize).
776
777 // (InterpolateDistributedResult and legacy types moved above)
778
779 // =================================================================
780 // InterpolateDistributedResult: output of distributed interpolation
781 // =================================================================
782
783 /// Result of legacy distributed interpolation (2-parent only).
784 ///
785 /// Extends InterpolateResult with ghost communication: entity arrays have
786 /// father (owned) + son (ghost) populated. parent2entity entries use
787 /// local-appended entity indices that span both father and son.
788 ///
789 /// **Legacy**: superseded by InterpolateGlobal for production use.
790 /// Retained for backward compatibility with InterpolateDistributed.
791 /// Limitations: entity2parent is fixed-2 (tAdj2Pair), does not support
792 /// N-parent edges, does not produce parent2entityPbi.
793 ///
794 /// @tparam p2e_rs Row-size of parent2entity (NonUniformSize = variable).
795 /// @tparam e2n_rs Row-size of entity2node (NonUniformSize = variable).
796 /// @tparam e2p_rs Row-size of entity2parent (2 = fixed face-to-parent).
797 template <rowsize p2e_rs = NonUniformSize,
798 rowsize e2n_rs = NonUniformSize,
799 rowsize e2p_rs = 2>
801 {
802 ArrayAdjacencyPair<p2e_rs> parent2entity; ///< parent → entities. Father = local parents,
803 ///< son = ghost parents. Local-appended entity indices.
804 ArrayAdjacencyPair<e2n_rs> entity2node; ///< entity → nodes. Father = owned, son = ghost.
805 ArrayAdjacencyPair<e2p_rs> entity2parent; ///< entity → (parentL, parentR).
806 ///< Father = owned, son = ghost. Local-appended parent indices.
807 ///< parentR = UnInitIndex for boundary entities.
808 tElemInfoArrayPair entityElemInfo; ///< Per-entity element info. Father = owned, son = ghost.
809 index nOwnedEntities{0}; ///< Number of owned entities (father size).
810 };
811
812 /// Default InterpolateDistributedResult: entity2parent is fixed-2 (as before).
814
815 // =================================================================
816 // MeshConnectivity: the layered DAG
817 // =================================================================
818
819 /// Manages the layered DAG of mesh adjacency relations.
820 ///
821 /// Cones (downward adjacencies) and supports (upward adjacencies) are stored
822 /// in separate vectors. Each is identified by a `(fromDepth, toDepth)` pair
823 /// using dynamic depth tags (e.g., `(dim, 0)` for cell→node).
824 ///
825 /// Adjacency data is stored via `ssp<AdjVariant>` for shared ownership.
826 /// Both the DAG's ConeAdj/SupportAdj and the legacy mesh members can share
827 /// the same underlying arrays (shallow copy of ssp<Array> pointers).
828 ///
829 /// The adjacency registry (`adjRegistry`) maps AdjKind tags to
830 /// `ssp<AdjVariant>` for use by the ghost traversal system. No raw pointers.
831 /// Only a restricted set of adjacencies may be registered:
832 /// - Direct cones/supports (inter-level, e.g., Cell2Node, Node2Cell)
833 /// - Intra-level adjacencies via Node or Face (e.g., Cell2Cell, Cell2CellFace)
834 /// More complex composed adjacencies are NOT stored in the registry.
836 {
837 int meshDim{0};
838 std::vector<ConeAdj> cones;
839 std::vector<SupportAdj> supports;
840
841 // -----------------------------------------------------------------
842 // Adjacency registry (restricted set for ghost traversal)
843 // -----------------------------------------------------------------
844
845 /// Maps AdjKind to the shared AdjVariant used by ghost chain evaluation.
846 /// Only direct cones/supports and intra-level (via Node/Face)
847 /// adjacencies are allowed. Registered via registerAdj().
848 /// Owns shared references (ssp) — no raw pointers.
849 std::unordered_map<AdjKind, ssp<AdjVariant>, AdjKindHash> adjRegistry;
850
851 /// Per-EntityKind global offsets mapping (for ownership determination).
852 /// Must be registered for every EntityKind that appears as a root anchor
853 /// or COLLECT target in any ghost chain.
854 std::unordered_map<EntityKind, ssp<GlobalOffsetsMapping>> globalMappings;
855
856 /// Register an adjacency for ghost traversal.
857 /// The ssp shares ownership with the ConeAdj/SupportAdj that holds the data.
858 /// Overwrites any existing registration for the same AdjKind.
859 void registerAdj(AdjKind kind, ssp<AdjVariant> adjPtr);
860
861 /// Convenience: register a specific pair type (tAdjPair, tAdj2Pair, etc.).
862 /// Stores a father-only shallow reference. The evaluator creates its
863 /// own scratch transformers internally for any ghost data it needs.
864 template <class TPair>
865 void registerAdj(AdjKind kind, TPair &pair)
866 {
867 auto adjVar = makeAdjVariant<TPair>();
868 auto &stored = std::get<TPair>(*adjVar);
869 stored.father = pair.father;
870 // son and ghost mapping are NOT copied. The evaluator's scratch
871 // pull creates its own temporary son/transformer when needed.
872 registerAdj(kind, std::move(adjVar));
873 }
874
875 /// Const overload: same as above but accepts a const reference.
876 /// Safe because we only copy the father shared_ptr (no mutation).
877 template <class TPair>
878 void registerAdj(AdjKind kind, const TPair &pair)
879 {
880 auto adjVar = makeAdjVariant<TPair>();
881 auto &stored = std::get<TPair>(*adjVar);
882 stored.father = pair.father;
883 registerAdj(kind, std::move(adjVar));
884 }
885
886 /// Overload for AdjPairTracked<TPair>: unwrap to base TPair.
887 template <class TPair>
889 {
890 registerAdj(kind, static_cast<TPair &>(pair));
891 }
892
893 /// Const overload for AdjPairTracked<TPair>.
894 template <class TPair>
896 {
897 registerAdj(kind, static_cast<const TPair &>(pair));
898 }
899
900 /// Register a GlobalOffsetsMapping for an EntityKind.
902
903 /// Resolve an AdjKind to the registered AdjVariant.
904 /// Returns nullptr if not registered.
906
907 /// Resolve a GlobalOffsetsMapping for an EntityKind.
908 /// Returns nullptr if not registered.
910
911 /// Check whether an AdjKind is registered.
912 [[nodiscard]] bool hasAdj(AdjKind kind) const;
913
914 // -----------------------------------------------------------------
915 // Cone management
916 // -----------------------------------------------------------------
917
918 /// Add a new cone for (fromDepth, toDepth). Returns reference.
919 /// Asserts no duplicate exists.
920 ConeAdj &addCone(int fromDepth, int toDepth);
921
922 /// Find a cone by (fromDepth, toDepth). Returns nullptr if not found.
923 ConeAdj *findCone(int fromDepth, int toDepth);
924 const ConeAdj *findCone(int fromDepth, int toDepth) const;
925 bool hasCone(int fromDepth, int toDepth) const;
926
927 // -----------------------------------------------------------------
928 // Support management
929 // -----------------------------------------------------------------
930
931 /// Add a new support for (fromDepth, toDepth). Returns reference.
932 /// Asserts no duplicate exists.
933 SupportAdj &addSupport(int fromDepth, int toDepth);
934
935 /// Find a support by (fromDepth, toDepth). Returns nullptr if not found.
936 SupportAdj *findSupport(int fromDepth, int toDepth);
937 const SupportAdj *findSupport(int fromDepth, int toDepth) const;
938 bool hasSupport(int fromDepth, int toDepth) const;
939
940 // -----------------------------------------------------------------
941 // Core DSL operations (static, templated on adjacency row-size)
942 // -----------------------------------------------------------------
943 // Input adjacencies can be any ArrayAdjacencyPair<rs> (fixed or variable).
944 // Output adjacencies are always tAdjPair (NonUniformSize) because the
945 // result width is generally unpredictable at compile time.
946 // Default template args = NonUniformSize for backward compatibility.
947
948 /// Invert a cone to get its support (distributed, MPI push-back).
949 ///
950 /// Given a cone adjacency A→B (for each A-entity, list of B-entities),
951 /// compute the support adjacency B→A (for each B-entity, list of A-entities
952 /// that reference it). Result is globally complete via MPI push-back.
953 ///
954 /// The input cone can be any row-size (fixed or variable). The output
955 /// support is always variable-width (tAdjPair) because fan-in is not
956 /// known at compile time.
957 ///
958 /// @tparam cone_rs Row-size of the input cone (NonUniformSize or fixed).
959 /// @param cone Adjacency: from → to (global indices).
960 /// @param nToLocal Local number of "to" entities on this rank.
961 /// @param mpi MPI communicator.
962 /// @param fromLocal2Global Maps local from-entity index to global index.
963 /// @param toLocal2Global Maps local to-entity index to global index.
964 /// @param toGlobalMapping Global mapping for to-entities (ownership lookup).
965 /// @return CSR adjacency: to → from (global, complete). Father-only.
966 template <rowsize cone_rs = NonUniformSize>
967 static tAdjPair Inverse(
968 const ArrayAdjacencyPair<cone_rs> &cone,
969 index nToLocal,
970 const MPIInfo &mpi,
971 const std::function<index(index)> &fromLocal2Global,
972 const std::function<index(index)> &toLocal2Global,
973 const ssp<GlobalOffsetsMapping> &toGlobalMapping);
974
975 /// Compose two adjacencies: A→B + B→C → A→C (delegates to ComposeFiltered
976 /// with SharedCountPredicate{1}).
977 ///
978 /// @tparam rs_AB Row-size of AB adjacency.
979 /// @tparam rs_BC Row-size of BC adjacency.
980 /// @tparam out_rs Row-size of output (NonUniformSize or fixed).
981 template <rowsize rs_AB = NonUniformSize, rowsize rs_BC = NonUniformSize,
982 rowsize out_rs = NonUniformSize>
986 index nALocal,
987 const std::unordered_map<index, index> &bGlobal2Local,
988 const std::function<index(index)> &aLocal2Global,
989 bool removeSelf = false);
990
991 /// Compose two adjacencies with on-the-fly predicate filtering.
992 ///
993 /// For each row a in AB, iterates b in AB[a], collects c in BC[b].
994 /// Counts shared B-entities per candidate, applies predicate.
995 ///
996 /// ## Ghost prerequisite
997 ///
998 /// **BC must contain (father+son) all B-entities referenced by AB.**
999 /// The caller must ghost-pull BC for every global B-index that appears
1000 /// in any row of AB. The `bGlobal2Local` map must cover all such
1001 /// B-globals. If a B-global from AB is not in `bGlobal2Local`, the
1002 /// method asserts (fatal error indicating incomplete ghost pull).
1003 ///
1004 /// Typical pattern: before calling ComposeFiltered, the caller builds
1005 /// BC (e.g., node2cell via Inverse), then ghost-pulls it for all
1006 /// off-rank B-entities referenced by local AB rows.
1007 ///
1008 /// @tparam rs_AB Row-size of AB adjacency.
1009 /// @tparam rs_BC Row-size of BC adjacency.
1010 /// @tparam Predicate Predicate type.
1011 /// @param AB A → B (father only, global B indices).
1012 /// @param BC B → C (father+son, global C indices).
1013 /// @param nALocal Number of local A-entities.
1014 /// @param bGlobal2Local Maps global B-index to local-appended index in BC.
1015 /// Must contain every B-global that appears in AB.
1016 /// @param aLocal2Global Maps local A-index to global A-index.
1017 /// @param pred Predicate(a_global, c_global, nShared) → keep?
1018 /// @param matchExtra Optional second predicate called after pred passes.
1019 /// Receives (aLocal, cGlobal, sharedBGlobals) where
1020 /// sharedBGlobals is the list of B-entities through
1021 /// which A and C are connected. If set and returns
1022 /// false, the (A, C) pair is rejected.
1023 /// Used for periodic pbi filtering.
1024 /// @return A → C adjacency, father-only. Variable or fixed-width
1025 /// depending on out_rs.
1026 /// @tparam out_rs Row-size of output (NonUniformSize = variable,
1027 /// or fixed: rows shorter than out_rs are padded with
1028 /// UnInitIndex, rows longer trigger assert).
1029 template <rowsize rs_AB = NonUniformSize, rowsize rs_BC = NonUniformSize,
1030 rowsize out_rs = NonUniformSize, class Predicate = SharedCountPredicate>
1032 const ArrayAdjacencyPair<rs_AB> &AB,
1033 const ArrayAdjacencyPair<rs_BC> &BC,
1034 index nALocal,
1035 const std::unordered_map<index, index> &bGlobal2Local,
1036 const std::function<index(index)> &aLocal2Global,
1037 Predicate &&pred,
1038 const std::function<bool(index aLocal, index cGlobal,
1039 const std::vector<index> &sharedBGlobals)>
1040 &matchExtra = nullptr);
1041
1042 // -----------------------------------------------------------------
1043 // InterpolateLocal: rank-local sub-entity extraction (no MPI)
1044 // -----------------------------------------------------------------
1045
1046 /// Extract sub-entities (faces or edges) from parent→node connectivity.
1047 /// This is a **local-only** operation — no MPI communication.
1048 ///
1049 /// Enumerates and deduplicates B entities within a contiguous block of
1050 /// parent A entities (typically local + ghost cells). Produces local-indexed
1051 /// parent→entity, entity→node, and entity→parent adjacencies.
1052 ///
1053 /// Used internally by InterpolateGlobal (Step 1). Can also be used
1054 /// directly for rank-local analysis or testing.
1055 ///
1056 /// ## Deduplication
1057 ///
1058 /// Two sub-entities are considered the same B entity if:
1059 /// 1. They have the same typeTag (from SubEntityDesc).
1060 /// 2. Their sorted vertex sets (first nVertices nodes) are equal.
1061 /// 3. query.matchExtra (if set) returns true — used for periodic meshes
1062 /// to implement the collaborating pbi check.
1063 ///
1064 /// ## Ordering guarantees
1065 ///
1066 /// - parent2entity[iParent][j]: slot j = sub-entity j per query callbacks.
1067 /// - entity2node[iEnt]: nodes in first-discovered parent's extraction order.
1068 /// - entity2parent[iEnt]: parents in discovery order (first parent first).
1069 ///
1070 /// ## Ghost note
1071 ///
1072 /// The caller typically passes all A parents (father+son) so that
1073 /// shared sub-entities are deduplicated within the local view. If ghost
1074 /// A parents are omitted, shared B entities at rank boundaries will
1075 /// appear as separate single-sided entities.
1076 ///
1077 /// ## Pbi note
1078 ///
1079 /// InterpolateLocal does not handle pbi. parent2entityPbi in the result
1080 /// is left empty. InterpolateGlobal computes it in Step 2b after calling
1081 /// InterpolateLocal, using the parent2nodePbi input and extractPbi callback.
1082 ///
1083 /// @param parent2node Parent → nodes (father-only or father+son).
1084 /// Accessed via operator[] for indices [0, nParent).
1085 /// @tparam p2n_rs Row-size of parent2node (NonUniformSize or fixed).
1086 /// @param query User-provided callbacks describing sub-entity topology.
1087 /// @param nParent Number of parent entities to process.
1088 /// @param nNode Total number of nodes (for reverse-index sizing).
1089 /// @param mpi MPI info (only for array allocation, no communication).
1090 /// @return InterpolateResult with all adjacencies (local indices).
1091 template <rowsize p2n_rs = NonUniformSize>
1093 const ArrayAdjacencyPair<p2n_rs> &parent2node,
1094 const SubEntityQuery &query,
1095 index nParent,
1096 index nNode,
1097 const MPIInfo &mpi);
1098
1099 /// Legacy distributed interpolation (2-parent only, no pbi output).
1100 ///
1101 /// Superseded by InterpolateGlobal for production use. Retained for
1102 /// backward compatibility. Limitations vs InterpolateGlobal:
1103 /// - entity2parent is fixed-2 (tAdj2Pair), cannot represent N-parent edges.
1104 /// - No parent2entityPbi output.
1105 /// - No entity2nodePbi output.
1106 /// - Uses push-based ghost exchange (not pull via evaluateGhostTree).
1107 ///
1108 /// Extends Interpolate by:
1109 /// 1. Calling the local Interpolate on all parents (local + ghost).
1110 /// 2. Using the OwnershipResolver callback to decide which rank owns
1111 /// each entity.
1112 /// 3. Compacting owned entities and assigning global IDs.
1113 /// 4. Push-based ghost exchange so non-owning ranks receive the
1114 /// entities they need.
1115 /// 5. Resolving parent2entity so all entries (for both local and ghost
1116 /// parents) use local-appended entity indices.
1117 ///
1118 /// @param parent2node CSR: parent → nodes (father + son).
1119 /// @param parentGhostMapping Ghost mapping for parent entities (for global
1120 /// index lookups). Must map local-appended index
1121 /// to global parent index.
1122 /// @param query Sub-entity topology callbacks.
1123 /// @param nLocalParents Number of local (owned/father) parents.
1124 /// @param nTotalParents Total parents (father + son).
1125 /// @param nNode Total number of nodes.
1126 /// @param resolver Ownership callback.
1127 /// @param nodeGhostMapping Ghost mapping for nodes (local-appended → global).
1128 /// Used to convert entity2node to global before push
1129 /// and back to local on the receiver.
1130 /// @param mpi MPI communicator.
1131 /// @return InterpolateDistributedResult with owned + ghost entities.
1132 template <rowsize p2n_rs = NonUniformSize>
1134 const ArrayAdjacencyPair<p2n_rs> &parent2node,
1135 const OffsetAscendIndexMapping &parentGhostMapping,
1136 const SubEntityQuery &query,
1137 index nLocalParents,
1138 index nTotalParents,
1139 index nNode,
1142 const MPIInfo &mpi);
1143
1144 /// Distributed interpolation producing globally-unique B entities.
1145 ///
1146 /// Given an A→C cone with pbi (where A is ghosted via A→C→A), creates
1147 /// globally-unique B entities (faces or edges). B entities are created
1148 /// only from **local (non-ghost) A parents**. Ghost A parents are used
1149 /// solely for deduplication — when a local A's sub-entity shares C-vertices
1150 /// with a ghost A's sub-entity, they are recognized as the same B entity.
1151 ///
1152 /// ## Ghost prerequisites (caller's responsibility)
1153 ///
1154 /// **A must be ghosted by at least the A→C→A 1-hop node-neighbor set.**
1155 /// Every A entity sharing at least one C-vertex with a local A must be
1156 /// present as a ghost. This guarantees:
1157 /// - All B entities at rank boundaries are locally deduplicated.
1158 /// - entity2parent is complete (all parents of any locally-visible B
1159 /// are enumerated).
1160 ///
1161 /// If the ghost set is insufficient, shared B entities across ranks will
1162 /// appear as single-sided. This is a silent error the method cannot detect
1163 /// (boundary faces/edges are legitimately single-sided).
1164 ///
1165 /// **C (nodes) must be ghosted** to cover all C-vertices referenced by
1166 /// ghost A parents. Typically guaranteed by the A→C→A ghost chain.
1167 ///
1168 /// ## Algorithm
1169 ///
1170 /// 1. Local enumeration + dedup (InterpolateLocal) on all A (father+son).
1171 /// 2. Extract B→C pbi via extractPbi. Compute parent2entityPbi (uniform
1172 /// XOR per matched node between each parent's view and entity's stored
1173 /// first-parent view).
1174 /// 3. Classify: fully local → owned; fully ghost → discard; straddling →
1175 /// ownership resolver callback.
1176 /// 4. Compact owned entities, convert indices to global.
1177 /// 5. Assign global B indices via createFatherGlobalMapping.
1178 /// 6. Push (globalA, globalB, subIdx) triplets to peer ranks so they can
1179 /// fill parent2entity. parent2entityPbi is NOT pushed — it is a local
1180 /// property of (cell, slot) computed entirely from cell2nodePbi and
1181 /// entity2nodePbi.
1182 ///
1183 /// Ghost B entities are NOT produced. Use evaluateGhostTree(A→A2B→B)
1184 /// after this call to pull them.
1185 ///
1186 /// ## Ordering guarantees
1187 ///
1188 /// parent2entity[iParent][j] corresponds to sub-entity j per element topology
1189 /// (ObtainFace(j) / ObtainEdge(j)). entity2node stores nodes in the first-
1190 /// discovered parent's extraction order. All pbi arrays are parallel to their
1191 /// adjacency counterparts.
1192 ///
1193 /// @param parent2node CSR: A → C (father+son, local C indices).
1194 /// @param parent2nodePbi A → C pbi (father+son). Pass empty pair if not periodic.
1195 /// @param parentGhostMapping Ghost mapping for A (local-appended → global).
1196 /// @param parentGlobalMapping Global offsets mapping for A (for rank lookup).
1197 /// @param nodeGhostMapping Ghost mapping for C (local-appended → global).
1198 /// @param query Sub-entity topology + pbi extraction callbacks.
1199 /// @param nLocalParents Number of owned A.
1200 /// @param nTotalParents Total A (father + son).
1201 /// @param nNode Total C (father + son).
1202 /// @param resolver N-parent ownership callback.
1203 /// @param mpi MPI communicator.
1204 /// @return InterpolateGlobalResultT<e2p_rs> with owned B entities in global indices.
1205 /// @tparam p2n_rs Row-size of parent2node input.
1206 /// @tparam e2p_rs Row-size of entity2parent output (NonUniformSize = variable,
1207 /// 2 = fixed for faces). Fixed-width rows shorter than e2p_rs
1208 /// are padded with UnInitIndex.
1209 template <rowsize p2n_rs = NonUniformSize, rowsize e2p_rs = NonUniformSize>
1211 const ArrayAdjacencyPair<p2n_rs> &parent2node,
1212 const tPbiPair &parent2nodePbi,
1213 const OffsetAscendIndexMapping &parentGhostMapping,
1214 const GlobalOffsetsMapping &parentGlobalMapping,
1216 const SubEntityQueryPbi &query,
1217 index nLocalParents,
1218 index nTotalParents,
1219 index nNode,
1221 const MPIInfo &mpi);
1222
1223 // -----------------------------------------------------------------
1224 // Ghost evaluation
1225 // -----------------------------------------------------------------
1226
1227 /// Evaluate a compiled ghost tree to determine which entities to ghost.
1228 ///
1229 /// BFS level-by-level evaluation with scratch pulls between levels.
1230 /// Produces a GhostResult containing per-EntityKind ghost index sets
1231 /// (union of all COLLECT nodes).
1232 ///
1233 /// @param tree Compiled ghost tree (from CompiledGhostTree::compile).
1234 /// @param mpi MPI communicator.
1235 /// @return Per-EntityKind sorted, deduplicated ghost indices.
1236 ///
1237 /// Preconditions:
1238 /// - All AdjKind values in the tree must be registered via registerAdj().
1239 /// - The registered tAdjPair arrays must be in Adj_PointToGlobal state.
1240 /// - GlobalOffsetsMapping must be available for entity kinds at root
1241 /// level (to determine ownership).
1243 const CompiledGhostTree &tree,
1244 const MPIInfo &mpi) const;
1245 };
1246
1247 // =====================================================================
1248 // Template implementation: ComposeFiltered
1249 // =====================================================================
1250
1251 template <rowsize rs_AB, rowsize rs_BC, rowsize out_rs, class Predicate>
1253 const ArrayAdjacencyPair<rs_AB> &AB,
1254 const ArrayAdjacencyPair<rs_BC> &BC,
1255 index nALocal,
1256 const std::unordered_map<index, index> &bGlobal2Local,
1257 const std::function<index(index)> &aLocal2Global,
1258 Predicate &&pred,
1259 const std::function<bool(index aLocal, index cGlobal,
1260 const std::vector<index> &sharedBGlobals)>
1261 &matchExtra)
1262 {
1263 const auto &mpi = AB.father->getMPI();
1264 const bool hasMatchExtra = bool(matchExtra);
1266 result.InitPair("ComposeFiltered_result", mpi);
1267 result.father->Resize(nALocal);
1268
1269 for (index iA = 0; iA < nALocal; iA++)
1270 {
1271 index aGlobal = aLocal2Global(iA);
1272
1273 // Collect all candidate C-entities with their shared-B count
1274 // and optionally the list of shared B-entities.
1275 std::unordered_map<index, int> candidateSharedCount;
1276 std::unordered_map<index, std::vector<index>> candidateSharedBs;
1277 for (auto iB : AB.father->operator[](iA))
1278 {
1279 // Fixed-width adjacencies may have UnInitIndex padding — skip.
1280 if constexpr (rs_AB > 0)
1281 {
1282 if (iB == UnInitIndex)
1283 continue;
1284 }
1285
1286 auto it = bGlobal2Local.find(iB);
1287 DNDS_assert_info(it != bGlobal2Local.end(),
1288 fmt::format("ComposeFiltered: B-entity global {} (referenced by A-entity "
1289 "local {} / global {}) not found in bGlobal2Local. "
1290 "Ghost pull of B→C is incomplete — the caller must "
1291 "pull BC for all B-globals referenced by AB rows.",
1292 iB, iA, aGlobal));
1293 index bLocal = it->second;
1294 for (auto iC : BC[bLocal])
1295 {
1296 // Fixed-width adjacencies may have UnInitIndex padding — skip.
1297 if constexpr (rs_BC > 0)
1298 {
1299 if (iC == UnInitIndex)
1300 continue;
1301 }
1302
1303 candidateSharedCount[iC]++;
1304 if (hasMatchExtra)
1305 candidateSharedBs[iC].push_back(iB);
1306 }
1307 }
1308
1309 // Apply predicate and collect passing entries
1310 std::vector<index> accepted;
1311 for (auto &[cGlobal, nShared] : candidateSharedCount)
1312 {
1313 if (!pred(aGlobal, cGlobal, nShared))
1314 continue;
1315 if (hasMatchExtra)
1316 {
1317 if (!matchExtra(iA, cGlobal, candidateSharedBs[cGlobal]))
1318 continue;
1319 }
1320 accepted.push_back(cGlobal);
1321 }
1322
1323 // Sort for deterministic output
1324 std::sort(accepted.begin(), accepted.end());
1325
1326 if constexpr (out_rs == NonUniformSize)
1327 {
1328 result.father->ResizeRow(iA, accepted.size());
1329 for (rowsize j = 0; j < static_cast<rowsize>(accepted.size()); j++)
1330 result.father->operator()(iA, j) = accepted[j];
1331 }
1332 else
1333 {
1334 // Fixed-width output: fill accepted entries, pad remainder with UnInitIndex.
1335 DNDS_assert_info(static_cast<rowsize>(accepted.size()) <= out_rs,
1336 fmt::format("ComposeFiltered: row {} has {} entries but out_rs={}",
1337 iA, accepted.size(), out_rs));
1338 for (rowsize j = 0; j < out_rs; j++)
1339 result.father->operator()(iA, j) =
1340 (j < static_cast<rowsize>(accepted.size())) ? accepted[j] : UnInitIndex;
1341 }
1342 }
1343
1344 return result;
1345 }
1346
1347 // =====================================================================
1348 // Template implementation: Compose
1349 // =====================================================================
1350
1351 template <rowsize rs_AB, rowsize rs_BC, rowsize out_rs>
1353 const ArrayAdjacencyPair<rs_AB> &AB,
1354 const ArrayAdjacencyPair<rs_BC> &BC,
1355 index nALocal,
1356 const std::unordered_map<index, index> &bGlobal2Local,
1357 const std::function<index(index)> &aLocal2Global,
1358 bool removeSelf)
1359 {
1360 return ComposeFiltered<rs_AB, rs_BC, out_rs>(
1361 AB, BC, nALocal, bGlobal2Local, aLocal2Global,
1362 SharedCountPredicate{.minShared = 1, .removeSelf = removeSelf});
1363 }
1364
1365 // =====================================================================
1366 // Template implementation: Inverse
1367 // =====================================================================
1368
1369 template <rowsize cone_rs>
1371 const ArrayAdjacencyPair<cone_rs> &cone,
1372 index nToLocal,
1373 const MPIInfo &mpi,
1374 const std::function<index(index)> &fromLocal2Global,
1375 const std::function<index(index)> &toLocal2Global,
1376 const ssp<GlobalOffsetsMapping> &toGlobalMapping)
1377 {
1378 // ----- Step 1: Local inversion -----
1379 std::unordered_map<index, std::unordered_set<index>> to2fromRecord;
1380 std::vector<index> ghostToIndices;
1381 std::unordered_set<index> ghostToSet;
1382
1383 index nFromLocal = cone.father->Size();
1384 for (index iFrom = 0; iFrom < nFromLocal; iFrom++)
1385 {
1386 index fromGlobal = fromLocal2Global(iFrom);
1387 for (auto iTo : cone.father->operator[](iFrom))
1388 {
1389 // Fixed-width adjacencies may have UnInitIndex padding — skip.
1390 if constexpr (cone_rs > 0)
1391 {
1392 if (iTo == UnInitIndex)
1393 continue;
1394 }
1395
1396 to2fromRecord[iTo].insert(fromGlobal);
1397
1398 auto [ret, rank, val] = toGlobalMapping->search(iTo);
1399 DNDS_assert_info(ret, fmt::format("Inverse: to-entity {} not found in global mapping", iTo));
1400 if (rank != mpi.rank && ghostToSet.find(iTo) == ghostToSet.end())
1401 {
1402 ghostToIndices.push_back(iTo);
1403 ghostToSet.insert(iTo);
1404 }
1405 }
1406 }
1407
1408 // ----- Step 2: Build initial support pair -----
1409 tAdjPair support;
1410 support.InitPair("Inverse_support", mpi);
1411 support.father->Resize(nToLocal);
1412
1413 for (index iTo = 0; iTo < nToLocal; iTo++)
1414 {
1415 index toGlobal = toLocal2Global(iTo);
1416 auto it = to2fromRecord.find(toGlobal);
1417 if (it != to2fromRecord.end())
1418 {
1419 support.father->ResizeRow(iTo, it->second.size());
1420 rowsize j = 0;
1421 for (auto fromG : it->second)
1422 support.father->operator()(iTo, j++) = fromG;
1423 }
1424 }
1425
1426 // Set up ghost communication
1427 support.TransAttach();
1428 support.trans.createFatherGlobalMapping();
1429 support.trans.createGhostMapping(ghostToIndices);
1430
1431 support.son->Resize(support.trans.pLGhostMapping->ghostIndex.size());
1432 for (auto &[toGlobal, fromSet] : to2fromRecord)
1433 {
1434 MPI_int rank{-1};
1435 index val{-1};
1436 if (!support.trans.pLGhostMapping->search(toGlobal, rank, val))
1437 DNDS_check_throw_info(false, "Inverse: ghost search failed");
1438 if (rank >= 0)
1439 {
1440 support.son->ResizeRow(val, fromSet.size());
1441 rowsize j = 0;
1442 for (auto fromG : fromSet)
1443 support.son->operator()(val, j++) = fromG;
1444 }
1445 }
1446
1447 // ----- Step 3: Reverse-push -----
1448 using tSupportArr = typename decltype(support.son)::element_type;
1449 ssp<tSupportArr> supportPast = make_ssp<tSupportArr>(ObjName{"Inverse_supportPast"}, mpi);
1450
1451 typename DNDS::ArrayTransformerType<tSupportArr>::Type supportPastTrans;
1452 supportPastTrans.setFatherSon(support.son, supportPast);
1453 supportPastTrans.createFatherGlobalMapping();
1454
1455 std::vector<index> pushSonSeries(support.son->Size());
1456 for (index i = 0; i < support.son->Size(); i++)
1457 pushSonSeries[i] = i;
1458 supportPastTrans.createGhostMapping(pushSonSeries, support.trans.pLGhostMapping->ghostStart);
1459 supportPastTrans.createMPITypes();
1460 supportPastTrans.pullOnce();
1461
1462 // ----- Step 4: Merge remote contributions -----
1463 DNDS_assert(DNDS::size_to_index(support.trans.pLGhostMapping->ghostIndex.size()) == support.son->Size());
1464 DNDS_assert(DNDS::size_to_index(support.trans.pLGhostMapping->pushingIndexGlobal.size()) == supportPast->Size());
1465
1466 for (index i = 0; i < supportPast->Size(); i++)
1467 {
1468 index toGlobal = support.trans.pLGhostMapping->pushingIndexGlobal[i];
1469 for (auto fromG : (*supportPast)[i])
1470 to2fromRecord[toGlobal].insert(fromG);
1471 }
1472
1473 // ----- Step 5: Rebuild father -----
1475 result.InitPair("Inverse_result", mpi);
1476 result.father->Resize(nToLocal);
1477
1478 for (index iTo = 0; iTo < nToLocal; iTo++)
1479 {
1480 index toGlobal = toLocal2Global(iTo);
1481 auto it = to2fromRecord.find(toGlobal);
1482 if (it != to2fromRecord.end())
1483 {
1484 std::vector<index> fromVec(it->second.begin(), it->second.end());
1485 std::sort(fromVec.begin(), fromVec.end());
1486 result.father->ResizeRow(iTo, fromVec.size());
1487 for (rowsize j = 0; j < static_cast<rowsize>(fromVec.size()); j++)
1488 result.father->operator()(iTo, j) = fromVec[j];
1489 }
1490 }
1491
1492 return result;
1493 }
1494
1495} // namespace DNDS::Geom
1496
1497// Template implementations for Interpolate family (too large for inline)
Adjacency array (CSR-like index storage) built on ParArray.
Father-son array pairs with device views and ghost communication.
#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
#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:100
Ghost-communication engine for a father / son ParArray pair.
void setFatherSon(const t_pArray &n_father, const t_pArray &n_son)
Attach father and son arrays. First setup step.
Table mapping rank-local row indices to the global index space.
Mapping between a rank's main data and its ghost copies.
constexpr AdjKind Bnd2Node
constexpr AdjKind Cell2Node
constexpr AdjKind Node2Cell
constexpr AdjKind Cell2Edge
constexpr AdjKind Face2Cell
constexpr AdjKind Edge2Node
constexpr AdjKind Cell2Face
constexpr AdjKind Cell2CellFace
constexpr AdjKind Node2Edge
constexpr AdjKind Node2Bnd
constexpr AdjKind Face2Edge
constexpr AdjKind Edge2Cell
constexpr AdjKind Bnd2Bnd
constexpr AdjKind Bnd2Face
constexpr AdjKind Cell2Cell
constexpr AdjKind Edge2Face
constexpr AdjKind Face2Node
constexpr AdjKind Node2Face
constexpr AdjKind Face2Bnd
constexpr AdjKind Face2Face
constexpr AdjKind Bnd2Cell
int32_t t_index
Definition Geometric.hpp:6
DNDS::ArrayAdjacencyPair< 2 > tAdj2Pair
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
DNDS::ArrayAdjacencyPair< 1 > tAdj1Pair
DNDS::ArrayAdjacencyPair< 4 > tAdj4Pair
const char * entityKindName(EntityKind kind)
String name for an EntityKind (for diagnostics).
int entityDepth(EntityKind kind, int dim)
DNDS::ArrayAdjacencyPair< DNDS::NonUniformSize > tAdjPair
DNDS::ArrayAdjacencyPair< 3 > tAdj3Pair
std::string adjKindName(const AdjKind &kind)
Format an AdjKind as a diagnostic string, e.g. "Cell2Node", "Cell2Cell(Node)".
std::variant< tAdjPair, tAdj1Pair, tAdj2Pair, tAdj3Pair, tAdj4Pair, tAdj8Pair > AdjVariant
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
DNDS_CONSTANT const rowsize NonUniformSize
Template parameter flag: "each row has an independent width".
Definition Defines.hpp:284
index size_to_index(size_t v)
Range-checked conversion from size_t to DNDS::index.
Definition Defines.hpp:431
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
DNDS_DEVICE_CALLABLE index Size() const
Combined father + son row count.
Definition ArrayPair.hpp:33
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.
Hash for AdjKind (for use in unordered containers).
std::size_t operator()(const AdjKind &k) const noexcept
constexpr bool operator==(const AdjKind &o) const
Equality comparison (for use in hash maps).
constexpr AdjKind()=default
constexpr AdjKind(EntityKind from_, EntityKind to_)
Direct adjacency: from != to. via is ignored.
constexpr AdjKind(EntityKind from_, EntityKind to_, EntityKind via_)
Intra-level adjacency: from == to, with explicit intermediary.
constexpr bool isIntraLevel() const
Whether this is an intra-level (composed) adjacency.
constexpr bool operator!=(const AdjKind &o) const
constexpr bool isDirect() const
Whether this is a direct (inter-level) adjacency.
Flattened wrapper: inherits from TPair and adds AdjIndexInfo.
int maxLevel
Maximum BFS depth across all nodes.
int totalNodes
Total number of nodes (for flat array sizing).
std::string dump() const
Pretty-print the tree (for diagnostics).
std::vector< AdjKind > checkAvailable(const MeshConnectivity &dag) const
static CompiledGhostTree compile(const GhostSpec &spec)
std::vector< std::vector< LevelEntry > > levels
std::vector< GhostTreeNode > roots
std::unordered_set< EntityKind > collectedKinds() const
Collect all EntityKind values that appear at COLLECT nodes.
std::unordered_set< AdjKind, AdjKindHash > requiredAdjs() const
Collect all distinct AdjKind values used by any hop in the tree.
TPair & as()
Typed access: as<tAdj2Pair>() etc.
tAdjPair & asAdj()
Access as variable-width tAdjPair. Throws if adj holds a fixed-width type.
const tAdjPair & asAdj() const
bool initialized() const
Check if the adjacency pair is initialized (father is non-null).
tAdj2Pair & asAdj2()
Access as fixed-width tAdj2Pair (e.g., for cell2face with 2 entries).
int toDepth
Target stratum (e.g., 0 for nodes, dim-1 for faces)
int fromDepth
Source stratum (e.g., dim for cells, dim-1 for faces)
index fatherSize() const
Number of local (father) rows.
const TPair & as() const
tPbiPair pbi
Periodic bits per entry (only for toDepth==0, optional)
const tAdj2Pair & asAdj2() const
const tAdj1Pair & asAdj1() const
ssp< AdjVariant > adj
Shared adjacency pair (typed by row width)
bool hasPbi() const
Check if pbi is attached (only valid for toDepth==0).
EntityKind target
Entity kind to ghost (must == hops.back().to).
EntityKind anchor
Owned entities to start from.
std::vector< AdjKind > hops
Sequence of adjacency lookups.
bool hasGhosts(EntityKind kind) const
std::unordered_map< EntityKind, std::vector< index > > ghostIndices
Per EntityKind: sorted, deduplicated global indices to ghost.
std::unordered_set< EntityKind > activeKinds
index totalGhosts() const
Total number of ghost entities on THIS rank.
static GhostSpec defaultPrimary()
std::vector< GhostChain > chains
One node in the compiled ghost tree.
int level
BFS depth (root = 0).
int parentId
Parent node ID (-1 for roots).
EntityKind kind
Entity kind at this node.
std::vector< GhostTreeNode > children
bool collect
If true, non-owned entities here become ghosts.
ArrayAdjacencyPair< e2n_rs > entity2node
entity → nodes. Father = owned, son = ghost.
tElemInfoArrayPair entityElemInfo
Per-entity element info. Father = owned, son = ghost.
index nOwnedEntities
Number of owned entities (father size).
tElemInfoArrayPair entityElemInfo
Per-B elem info. Father-only (owned B).
ArrayAdjacencyPair< e2p_rs > entity2parent
index nOwnedEntities
Number of owned B entities (father size).
ArrayAdjacencyPair< e2p_rs > entity2parent
ArrayAdjacencyPair< p2e_rs > parent2entity
parent → entities. Father-only. Slot j = sub-entity j.
ArrayAdjacencyPair< e2n_rs > entity2node
entity → nodes. Father-only. First-parent extraction order.
index nEntities
Total number of unique entities created.
std::vector< std::vector< NodePeriodicBits > > parent2entityPbi
std::vector< ElemInfo > entityElemInfo
Per-entity element info (zone=0, type from SubEntityDesc::typeTag).
int nodeId
ID of the tree node.
bool collect
Whether to collect at this node.
EntityKind kind
Entity kind of this node.
int parentId
ID of the parent node (-1 for roots).
bool hasChildren
Whether this node has children (needs pull).
AdjKind hop
Hop used to reach this node.
ConeAdj & addCone(int fromDepth, int toDepth)
ssp< AdjVariant > resolveAdj(AdjKind kind) const
void registerAdj(AdjKind kind, const AdjPairTracked< TPair > &pair)
Const overload for AdjPairTracked<TPair>.
std::unordered_map< EntityKind, ssp< GlobalOffsetsMapping > > globalMappings
std::vector< SupportAdj > supports
ConeAdj * findCone(int fromDepth, int toDepth)
Find a cone by (fromDepth, toDepth). Returns nullptr if not found.
static tAdjPair Inverse(const ArrayAdjacencyPair< cone_rs > &cone, index nToLocal, const MPIInfo &mpi, const std::function< index(index)> &fromLocal2Global, const std::function< index(index)> &toLocal2Global, const ssp< GlobalOffsetsMapping > &toGlobalMapping)
static InterpolateResult Interpolate(const ArrayAdjacencyPair< p2n_rs > &parent2node, const SubEntityQuery &query, index nParent, index nNode, const MPIInfo &mpi)
std::unordered_map< AdjKind, ssp< AdjVariant >, AdjKindHash > adjRegistry
bool hasSupport(int fromDepth, int toDepth) const
void registerAdj(AdjKind kind, AdjPairTracked< TPair > &pair)
Overload for AdjPairTracked<TPair>: unwrap to base TPair.
static ArrayAdjacencyPair< out_rs > Compose(const ArrayAdjacencyPair< rs_AB > &AB, const ArrayAdjacencyPair< rs_BC > &BC, index nALocal, const std::unordered_map< index, index > &bGlobal2Local, const std::function< index(index)> &aLocal2Global, bool removeSelf=false)
SupportAdj & addSupport(int fromDepth, int toDepth)
static ArrayAdjacencyPair< out_rs > ComposeFiltered(const ArrayAdjacencyPair< rs_AB > &AB, const ArrayAdjacencyPair< rs_BC > &BC, index nALocal, const std::unordered_map< index, index > &bGlobal2Local, const std::function< index(index)> &aLocal2Global, Predicate &&pred, const std::function< bool(index aLocal, index cGlobal, const std::vector< index > &sharedBGlobals)> &matchExtra=nullptr)
const ssp< GlobalOffsetsMapping > & getGlobalMapping(EntityKind kind) const
GhostResult evaluateGhostTree(const CompiledGhostTree &tree, const MPIInfo &mpi) const
void registerAdj(AdjKind kind, const TPair &pair)
bool hasCone(int fromDepth, int toDepth) const
bool hasAdj(AdjKind kind) const
Check whether an AdjKind is registered.
void registerGlobalMapping(EntityKind kind, const ssp< GlobalOffsetsMapping > &gm)
Register a GlobalOffsetsMapping for an EntityKind.
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)
SupportAdj * findSupport(int fromDepth, int toDepth)
Find a support by (fromDepth, toDepth). Returns nullptr if not found.
void registerAdj(AdjKind kind, TPair &pair)
void registerAdj(AdjKind kind, ssp< AdjVariant > adjPtr)
std::vector< MPI_int > peerRanks
bool operator()(index a, index c, int nShared) const
int nNodes
Total number of nodes (vertices + mid-edge + ...).
int nVertices
Number of corner vertices (used for deduplication).
std::function< void(index iParent, int iSub, const std::function< NodePeriodicBits(int)> &parentPbi, NodePeriodicBits *out)> extractPbi
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.
std::function< bool(index iParent, int iSub, index iCandEntity, index candidateParent, int candidateSub)> matchExtra
TPair & as()
Typed access: as<tAdj2Pair>() etc.
const TPair & as() const
const tAdj2Pair & asAdj2() const
int toDepth
Target stratum (e.g., dim for cells)
const tAdjPair & asAdj() const
int fromDepth
Source stratum (e.g., 0 for nodes)
tAdjPair & asAdj()
Access as variable-width tAdjPair.
const tAdj1Pair & asAdj1() const
ssp< AdjVariant > adj
Shared adjacency pair (typed by row width)
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
Eigen::Matrix< real, 5, 1 > v
auto result
OwnershipResolverMulti resolver
const DNDS::index nNode
const tPoint const tPoint const tPoint & p