36#define DOCTEST_CONFIG_IMPLEMENT
45#include <unordered_set>
64 else if (mpi.rank < 2)
82 c2c.
father->operator()(
i,
j) = allNeighbors[globalCell][
j];
85 c2c.
father->pLGlobalMapping = std::make_shared<GlobalOffsetsMapping>();
86 c2c.
father->pLGlobalMapping->setMPIAlignBcast(mpi,
nLocal);
94 tAdjPair c2n = make4QuadCell2Node(mpi);
99 else if (
mpi.rank < 2)
102 c2n.
father->pLGlobalMapping = std::make_shared<GlobalOffsetsMapping>();
103 c2n.
father->pLGlobalMapping->setMPIAlignBcast(mpi,
nLocal);
117 CHECK(tree.roots.size() == 2);
121 for (
auto &
r : tree.roots)
123 if (
r.kind == EntityKind::Cell)
125 if (
r.kind == EntityKind::Bnd)
135 auto &c2cNode = cellRoot->
children[0];
136 CHECK(c2cNode.kind == EntityKind::Cell);
138 CHECK(c2cNode.collect);
139 CHECK(c2cNode.level == 1);
141 REQUIRE(c2cNode.children.size() == 1);
142 auto &c2nNode = c2cNode.children[0];
143 CHECK(c2nNode.kind == EntityKind::Node);
145 CHECK(c2nNode.collect);
146 CHECK(c2nNode.level == 2);
147 CHECK(c2nNode.children.empty());
153 auto &b2nNode = bndRoot->
children[0];
154 CHECK(b2nNode.kind == EntityKind::Node);
156 CHECK(b2nNode.collect);
157 CHECK(b2nNode.level == 1);
159 REQUIRE(b2nNode.children.size() == 1);
160 auto &n2bNode = b2nNode.children[0];
161 CHECK(n2bNode.kind == EntityKind::Bnd);
163 CHECK(n2bNode.collect);
164 CHECK(n2bNode.level == 2);
166 REQUIRE(n2bNode.children.size() == 1);
167 auto &b2n2Node = n2bNode.children[0];
168 CHECK(b2n2Node.kind == EntityKind::Node);
170 CHECK(b2n2Node.collect);
171 CHECK(b2n2Node.level == 3);
172 CHECK(b2n2Node.children.empty());
174 CHECK(tree.maxLevel == 3);
176 auto kinds = tree.collectedKinds();
177 CHECK(kinds.count(EntityKind::Cell) == 1);
178 CHECK(kinds.count(EntityKind::Node) == 1);
179 CHECK(kinds.count(EntityKind::Bnd) == 1);
180 CHECK(kinds.count(EntityKind::Face) == 0);
182 auto adjs = tree.requiredAdjs();
187 CHECK(adjs.size() == 4);
198 REQUIRE(tree.roots.size() == 1);
199 auto &root = tree.roots[0];
200 CHECK(root.kind == EntityKind::Cell);
201 REQUIRE(root.children.size() == 1);
203 auto &ring1 = root.children[0];
204 CHECK(ring1.kind == EntityKind::Cell);
205 CHECK(ring1.collect);
206 CHECK(ring1.level == 1);
207 REQUIRE(ring1.children.size() == 1);
209 auto &ring2 = ring1.children[0];
210 CHECK(ring2.kind == EntityKind::Cell);
211 CHECK(ring2.collect);
212 CHECK(ring2.level == 2);
213 CHECK(ring2.children.empty());
215 CHECK(tree.maxLevel == 2);
222 {EntityKind::Cell, {}, EntityKind::Cell},
257 auto missing = tree.checkAvailable(dag);
258 CHECK(missing.size() == 2);
260 std::unordered_set<AdjKind, AdjKindHash> missingSet(missing.begin(), missing.end());
269 std::string d = tree.dump();
271 CHECK(d.find(
"Cell") != std::string::npos);
272 CHECK(d.find(
"Bnd") != std::string::npos);
273 CHECK(d.find(
"Node") != std::string::npos);
274 CHECK(d.find(
"COLLECT") != std::string::npos);
284 REQUIRE(tree.roots.size() == 1);
285 auto &root = tree.roots[0];
286 CHECK(root.kind == EntityKind::Cell);
287 REQUIRE(root.children.size() == 1);
289 auto &faceNode = root.children[0];
290 CHECK(faceNode.kind == EntityKind::Face);
292 CHECK(faceNode.collect);
293 CHECK(faceNode.level == 1);
295 auto adjs = tree.requiredAdjs();
297 CHECK(adjs.size() == 1);
302 AdjKind a(EntityKind::Cell, EntityKind::Node);
303 AdjKind b(EntityKind::Cell, EntityKind::Node, EntityKind::Face);
306 AdjKind c(EntityKind::Cell, EntityKind::Cell, EntityKind::Node);
307 AdjKind d(EntityKind::Cell, EntityKind::Cell, EntityKind::Face);
310 AdjKind e(EntityKind::Cell, EntityKind::Cell, EntityKind::Node);
387 return rank *
N *
N + localRow *
N + localCol;
458 : (
N + (lastInRow ? 1 : 0)) * (
N + (lastInCol ? 1 : 0)));
460 cellGM = std::make_shared<GlobalOffsetsMapping>();
463 nodeGM = std::make_shared<GlobalOffsetsMapping>();
488 std::set<DNDS::index> neighbors;
493 if (dr == 0 && dc == 0)
508 if (nbGlobal != myGlobal)
509 neighbors.insert(nbGlobal);
512 std::vector<DNDS::index> nbVec(neighbors.begin(), neighbors.end());
514 for (
DNDS::rowsize j = 0; j < static_cast<DNDS::rowsize>(nbVec.size());
j++)
525 return dx * dy - total;
531 std::set<DNDS::index> ghosts;
546 std::set<DNDS::index> ghosts;
551 if (n < nodeOffset || n >= nodeEnd)
564 return dx * dy -
N *
N;
570 return (rMax - rMin) * (cMax - cMin) -
N *
N;
578 std::vector<DNDS::index> sizes = {32, 100};
580 sizes.push_back(500);
598 auto t0 = MPI_Wtime();
600 auto t1 = MPI_Wtime();
604 auto &
ghostCells = result1.ghostIndices[EntityKind::Cell];
606 CHECK(actualCells.size() == expectedCells.size());
607 CHECK(actualCells == expectedCells);
616 auto t2 = MPI_Wtime();
618 auto t3 = MPI_Wtime();
622 auto &
ghostNodes = result2.ghostIndices[EntityKind::Node];
624 CHECK(actualNodes.size() == expectedNodes.size());
625 CHECK(actualNodes == expectedNodes);
629 fmt::print(
" N={}(per rank): cells/rank={}, ghost_cells={} (expected={}), "
630 "ghost_nodes={} (expected={}), "
631 "1-hop={:.4f}s, 2-hop={:.4f}s\n",
638 CHECK(result1.hasGhosts(EntityKind::Cell));
647 auto c2c = make4QuadCell2Cell(g_mpi);
658 auto missing = tree.checkAvailable(dag);
677 else if (g_mpi.
rank == 1)
692TEST_CASE(
"evaluateGhostTree: two-hop cell2cell2node with manual ghost")
697 auto c2c = make4QuadCell2Cell(g_mpi);
698 auto c2n = make4QuadCell2NodeWithMapping(g_mpi);
704 else if (g_mpi.
rank == 0)
706 else if (g_mpi.
rank == 1)
709 auto nodeGM = std::make_shared<GlobalOffsetsMapping>();
710 nodeGM->setMPIAlignBcast(g_mpi, nNodesLocal);
716 c2n.
trans.createFatherGlobalMapping();
718 std::vector<DNDS::index> ghostIndices;
720 ghostIndices = {2, 3};
721 else if (g_mpi.
rank == 1)
722 ghostIndices = {0, 1};
725 c2n.
trans.createGhostMapping(ghostIndices);
726 c2n.
trans.createMPITypes();
727 c2n.
trans.pullOnce();
763 else if (g_mpi.
rank == 1)
776 CHECK(
result.ghostIndices[EntityKind::Node].empty());
786 auto c2c = make4QuadCell2Cell(g_mpi);
791 c2c.trans.createFatherGlobalMapping();
792 std::vector<DNDS::index> ghostIndices;
794 ghostIndices = {2, 3};
795 else if (g_mpi.
rank == 1)
796 ghostIndices = {0, 1};
797 c2c.trans.createGhostMapping(ghostIndices);
798 c2c.trans.createMPITypes();
799 c2c.trans.pullOnce();
817 auto &ghost2 = result2.ghostIndices[EntityKind::Cell];
818 CHECK(ghost1.size() == 2);
819 CHECK(ghost2.size() == 2);
820 CHECK(ghost1 == ghost2);
829 auto c2c = make4QuadCell2Cell(g_mpi);
830 auto c2n = make4QuadCell2NodeWithMapping(g_mpi);
835 else if (g_mpi.
rank == 0)
837 else if (g_mpi.
rank == 1)
839 auto nodeGM = std::make_shared<GlobalOffsetsMapping>();
840 nodeGM->setMPIAlignBcast(g_mpi, nNodesLocal);
845 c2n.
trans.createFatherGlobalMapping();
846 std::vector<DNDS::index> ghostIndices;
848 ghostIndices = {2, 3};
849 else if (g_mpi.
rank == 1)
850 ghostIndices = {0, 1};
851 c2n.
trans.createGhostMapping(ghostIndices);
852 c2n.
trans.createMPITypes();
853 c2n.
trans.pullOnce();
879 std::set<DNDS::index> set2(ghost2.begin(), ghost2.end());
880 CHECK(set2 == std::set<DNDS::index>{5, 6, 7, 8});
884 auto &ghostU = resultUnion.ghostIndices[EntityKind::Node];
885 std::set<DNDS::index> setU(ghostU.begin(), ghostU.end());
886 CHECK(setU == std::set<DNDS::index>{5, 6, 7, 8});
888 else if (g_mpi.
rank == 1)
891 auto &ghost2 = result2.ghostIndices[EntityKind::Node];
892 std::set<DNDS::index> set2(ghost2.begin(), ghost2.end());
893 CHECK(set2 == std::set<DNDS::index>{0, 1, 2, 3, 4});
898 auto &ghostU = resultUnion.ghostIndices[EntityKind::Node];
899 std::set<DNDS::index> setU(ghostU.begin(), ghostU.end());
900 CHECK(setU == std::set<DNDS::index>{0, 1, 2, 3, 4});
905 CHECK(resultUnion.ghostIndices[EntityKind::Node].empty());
914 auto c2c = make4QuadCell2Cell(g_mpi);
937TEST_CASE(
"evaluateGhostTree: doubly-periodic tiled grid")
955 for (
int nL = 1; nL <= 4; nL++)
962 {EntityKind::Cell, cellHops, EntityKind::Cell},
963 {EntityKind::Cell, nodeHops, EntityKind::Node},
969 auto &gnL =
result.ghostIndices[EntityKind::Node];
977 fmt::print(
" [{}] {} Periodic N={}: nLayers={}: cell_ghost={} (expected={}), "
978 "node_ghost={} (expected={})\n",
979 g_mpi.
rank, pos,
N, nL,
985 for (
int nL = 2; nL <= 4; nL++)
988 GhostSpec specML{{{EntityKind::Cell, hops, EntityKind::Cell}}};
994 fmt::print(
" [{}] Periodic N={}: {}-layer cell ghost: size={}\n",
995 g_mpi.
rank,
N, nL, gcL.size());
999TEST_CASE(
"evaluateGhostTree: small periodic tiled grid")
1001 if (g_mpi.
size != 4 && g_mpi.
size != 8)
1042 fmt::print(
" [{}] np={} Periodic N=2: 1-hop cells={} (expected={}), "
1043 "nodes={} (expected={})\n",
1052 MPI_Init(&argc, &argv);
1055 doctest::Context ctx;
1056 ctx.applyCommandLine(argc, argv);
1057 int res = ctx.run();
Layered DAG of mesh adjacency relations with composable DSL operations.
Shared synthetic mesh builders for MeshConnectivity unit tests.
constexpr AdjKind Bnd2Node
constexpr AdjKind Cell2Node
constexpr AdjKind Node2Cell
constexpr AdjKind Cell2Face
constexpr AdjKind Cell2CellFace
constexpr AdjKind Node2Bnd
constexpr AdjKind Cell2Cell
int entityDepth(EntityKind kind, int dim)
std::string adjKindName(const AdjKind &kind)
Format an AdjKind as a diagnostic string, e.g. "Cell2Node", "Cell2Cell(Node)".
the host side operators are provided as implemented
int32_t rowsize
Row-width / per-row element-count type (signed 32-bit).
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
std::shared_ptr< T > ssp
Shortened alias for std::shared_ptr used pervasively in DNDSR.
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.
TTrans trans
Ghost-communication engine bound to father and son.
Hash for AdjKind (for use in unordered containers).
static CompiledGhostTree compile(const GhostSpec &spec)
std::unordered_map< EntityKind, std::vector< index > > ghostIndices
Per EntityKind: sorted, deduplicated global indices to ghost.
static GhostSpec defaultPrimary()
One node in the compiled ghost tree.
int level
BFS depth (root = 0).
std::vector< GhostTreeNode > children
bool collect
If true, non-owned entities here become ghosts.
GhostResult evaluateGhostTree(const CompiledGhostTree &tree, const MPIInfo &mpi) const
void registerGlobalMapping(EntityKind kind, const ssp< GlobalOffsetsMapping > &gm)
Register a GlobalOffsetsMapping for an EntityKind.
void registerAdj(AdjKind kind, ssp< AdjVariant > adjPtr)
Lightweight bundle of an MPI communicator and the calling rank's coordinates.
int size
Number of ranks in comm (-1 until initialised).
int rank
This rank's 0-based index within comm (-1 until initialised).
void setWorld()
Initialise the object to MPI_COMM_WORLD. Requires MPI_Init to have run.
DNDS::index expectedGhostCellsN(DNDS::index nLayers) const
Analytical n-layer cell ghost count.
ssp< GlobalOffsetsMapping > cellGM
std::set< DNDS::index > expectedGhostCells(const MPIInfo &mpi) const
ssp< GlobalOffsetsMapping > nodeGM
DNDS::index nodeGlobal(DNDS::index r, DNDS::index c) const
std::vector< DNDS::index > ownerNodeOffsets
std::set< DNDS::index > expectedGhostNodesFromOwnedCells(const MPIInfo &mpi) const
void build(DNDS::index tileN, const MPIInfo &mpi)
DNDS::index cellGlobal(DNDS::index rank, DNDS::index localRow, DNDS::index localCol) const
DNDS::index cellOwnerRank(DNDS::index gr, DNDS::index gc) const
DNDS::index expectedGhostNodesN(DNDS::index nLayers) const
std::vector< DNDS::index > ghostNodes(ghostNodeSet.begin(), ghostNodeSet.end())
std::unordered_set< DNDS::index > ghostNodeSet
TEST_CASE("evaluateGhostTree: np=1 produces no ghosts")
std::vector< GhostCell > ghostCells
REQUIRE(bool(result.parent2entityPbi.father))
Eigen::Vector3d n(1.0, 0.0, 0.0)