22#define DOCTEST_CONFIG_IMPLEMENT
52 {
"UniformSquare_10",
"UniformSquare_10.cgns", 2,
false,
53 {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, 100, 40},
54 {
"IV10_10",
"IV10_10.cgns", 2,
true,
55 {10, 0, 0}, {0, 10, 0}, {0, 0, 10}, 100, -1},
56 {
"NACA0012_H2",
"NACA0012_H2.cgns", 2,
false,
57 {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, 20816, 484},
58 {
"IV10U_10",
"IV10U_10.cgns", 2,
true,
59 {10, 0, 0}, {0, 10, 0}, {0, 0, 10}, 322, -1},
60 {
"Ball2",
"Ball2.cgns", 3,
false,
61 {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, 958994, 16402},
63static constexpr int N_CONFIGS =
sizeof(g_configs) /
sizeof(g_configs[0]);
77static std::string meshPath(
const std::string &name)
79 std::string f(__FILE__);
80 for (
int i = 0; i < 4; i++)
82 auto pos = f.rfind(
'/');
83 if (pos == std::string::npos)
85 if (pos != std::string::npos)
88 return f +
"/data/mesh/" + name;
92 int meshId,
const MeshConfig &cfg,
int nReorderParts = 1)
95 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] START" << std::endl;
97 auto mesh = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
103 mesh->SetPeriodicGeometry(
109 reader.ReadFromCGNSSerial(meshPath(cfg.
file));
110 reader.Deduplicate1to1Periodic(1e-8);
111 reader.BuildCell2Cell();
118 reader.MeshPartitionCell2Cell(pOpt);
119 reader.PartitionReorderToMeshCell2Cell();
121 mesh->RecoverNode2CellAndNode2Bnd();
122 mesh->RecoverCell2CellAndBnd2Cell();
123 mesh->BuildGhostPrimary();
125 mesh->AdjGlobal2LocalPrimary();
126 mesh->AdjGlobal2LocalN2CB();
128 if (nReorderParts > 1)
129 mesh->ReorderLocalCells(nReorderParts);
131 mesh->InterpolateFace();
132 mesh->AssertOnFaces();
134 mesh->AdjLocal2GlobalN2CB();
135 mesh->BuildGhostN2CB();
136 mesh->AdjGlobal2LocalN2CB();
137 mesh->AssertOnN2CB();
139 mesh->BuildCell2CellFace();
140 mesh->AdjGlobal2LocalC2CFace();
143 mesh->RecreatePeriodicNodes();
144 mesh->BuildVTKConnectivity();
147 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] DONE" << std::endl;
152static std::vector<std::vector<DNDS::index>> snapshotAdj(
155 std::vector<std::vector<DNDS::index>> out(nRows);
158 out[i].resize(
adj.RowSize(i));
160 out[i][j] =
adj(i, j);
165static std::vector<std::pair<DNDS::index, DNDS::index>> snapshotAdj2(
168 std::vector<std::pair<DNDS::index, DNDS::index>> out(nRows);
170 out[i] = {
adj(i, 0),
adj(i, 1)};
174static std::vector<DNDS::index> snapshotAdj1(
177 std::vector<DNDS::index> out(nRows);
183static void checkAdjMatchesSnapshot(
185 const std::vector<std::vector<DNDS::index>> &snap)
198 MPI_Init(&argc, &argv);
202 for (
int ic = 0; ic < N_CONFIGS; ic++)
203 g_full[ic] = buildFullMesh(ic, g_configs[ic]);
205 doctest::Context ctx;
206 ctx.applyCommandLine(argc, argv);
209 for (
auto &m : g_full)
219#define FOR_EACH_MESH_CONFIG(body) \
220 for (int _ci = 0; _ci < N_CONFIGS; _ci++) \
223 if (_ci == 4 && g_mpi.size != 8) continue; \
225 const auto &cfg = g_configs[_ci]; \
226 auto m = g_full[_ci]; \
227 SUBCASE(cfg.name) { body } \
245TEST_CASE(
"Pipeline: global cell count matches expected")
251 CHECK(globalCells > 0);
266 CHECK(globalBnds > 0);
275 CHECK(m->NumFace() > 0);
279TEST_CASE(
"InterpolateFace: face2cell has exactly 2 entries per face")
283 CHECK(m->face2cell.RowSize(iF) == 2);
287TEST_CASE(
"InterpolateFace: face2cell owner is a valid local cell")
290 DNDS::index totalCells = m->NumCell() + m->NumCellGhost();
295 CHECK(owner < totalCells);
300TEST_CASE(
"InterpolateFace: face2node indices in valid range")
303 DNDS::index totalNodes = m->NumNode() + m->NumNodeGhost();
309 CHECK(iN < totalNodes);
314TEST_CASE(
"InterpolateFace: cell2face row sizes match expected face count")
319 auto elem = m->GetCellElement(iC);
320 int nFaceExpected = elem.GetNumFaces();
321 CHECK(m->cell2face.RowSize(iC) == nFaceExpected);
326TEST_CASE(
"InterpolateFace: cell2face entries are valid face indices")
329 DNDS::index totalFaces = m->NumFace() + m->NumFaceGhost();
335 CHECK(iF < totalFaces);
340TEST_CASE(
"InterpolateFace: bnd2face maps to valid faces")
348 CHECK(iFace < m->NumFace() + m->NumFaceGhost());
353TEST_CASE(
"InterpolateFace: face element types are valid")
357 CHECK(m->GetFaceElement(iF).type != Elem::ElemType::UnknownElem);
363TEST_CASE(
"N2CB: every local node has at least one adjacent cell")
366 for (
DNDS::index iNode = 0; iNode < m->NumNode(); iNode++)
368 bool hasCell =
false;
369 for (
DNDS::rowsize j = 0; j < m->node2cell.RowSize(iNode); j++)
370 if (m->node2cell(iNode, j) >= 0)
377TEST_CASE(
"N2CB: node2cell entries are valid local indices")
380 DNDS::index totalCells = m->NumCell() + m->NumCellGhost();
381 for (
DNDS::index iNode = 0; iNode < m->NumNodeProc(); iNode++)
382 for (
DNDS::rowsize j = 0; j < m->node2cell.RowSize(iNode); j++)
386 CHECK(iC < totalCells);
393TEST_CASE(
"BuildCell2CellFace: row sizes match cell2face")
397 CHECK(m->cell2cellFace.RowSize(iC) == m->cell2face.RowSize(iC));
401TEST_CASE(
"BuildCell2CellFace: entries are valid cells or negative")
404 DNDS::index totalCells = m->NumCell() + m->NumCellGhost();
406 for (
DNDS::rowsize j = 0; j < m->cell2cellFace.RowSize(iC); j++)
410 CHECK(nb < totalCells);
417TEST_CASE(
"ConstructBndMesh: correct dimensions and state")
423 m->ConstructBndMesh(bMesh);
436 m->ConstructBndMesh(bMesh);
443 CHECK(pn < m->NumNode() + m->NumNodeGhost());
450TEST_CASE(
"BuildVTKConnectivity: arrays populated correctly")
453 CHECK(m->vtkCell2node.size() > 0);
454 CHECK(m->vtkCell2nodeOffsets.size() ==
static_cast<size_t>(m->NumCell() + 1));
455 CHECK(m->vtkCellType.size() ==
static_cast<size_t>(m->NumCell()));
456 for (
size_t i = 1; i < m->vtkCell2nodeOffsets.size(); i++)
457 CHECK(m->vtkCell2nodeOffsets[i] >= m->vtkCell2nodeOffsets[i - 1]);
458 for (
auto ct : m->vtkCellType)
470 m->ReorderLocalCells(2);
486 m->ReorderLocalCells(2);
496 m->ReorderLocalCells(2);
497 int nParts = m->NLocalParts();
499 CHECK(m->LocalPartStart(0) == 0);
500 CHECK(m->LocalPartEnd(nParts - 1) == m->NumCell());
501 for (
int ip = 0; ip < nParts; ip++)
502 CHECK(m->LocalPartStart(ip) <= m->LocalPartEnd(ip));
509 m->ReorderLocalCells(2);
510 DNDS::index totalNodes = m->NumNode() + m->NumNodeGhost();
516 CHECK(iN < totalNodes);
528 m->ReorderLocalCells(2);
539TEST_CASE(
"AdjFacial round-trip: face2node and face2cell identity")
544 auto f2nSnap = snapshotAdj(m->face2node, m->NumFace());
545 auto f2cSnap = snapshotAdj2(m->face2cell, m->NumFace());
547 m->AdjLocal2GlobalFacial();
552 CHECK(m->face2node(iF, j) >= 0);
554 m->AdjGlobal2LocalFacial();
557 checkAdjMatchesSnapshot(m->face2node, m->NumFace(), f2nSnap);
560 CHECK(m->face2cell(iF, 0) == f2cSnap[iF].first);
561 CHECK(m->face2cell(iF, 1) == f2cSnap[iF].second);
567TEST_CASE(
"AdjC2F round-trip: cell2face and bnd2face identity")
572 auto c2fSnap = snapshotAdj(m->cell2face, m->NumCell());
573 auto b2fSnap = snapshotAdj1(m->bnd2face, m->NumBnd());
575 m->AdjLocal2GlobalC2F();
578 m->AdjGlobal2LocalC2F();
581 checkAdjMatchesSnapshot(m->cell2face, m->NumCell(), c2fSnap);
583 CHECK(m->bnd2face(ib, 0) == b2fSnap[ib]);
587TEST_CASE(
"AdjN2CB round-trip: node2cell and node2bnd identity")
592 auto n2cSnap = snapshotAdj(m->node2cell, m->NumNodeProc());
593 auto n2bSnap = snapshotAdj(m->node2bnd, m->NumNodeProc());
595 m->AdjLocal2GlobalN2CB();
598 m->AdjGlobal2LocalN2CB();
601 checkAdjMatchesSnapshot(m->node2cell, m->NumNodeProc(), n2cSnap);
602 checkAdjMatchesSnapshot(m->node2bnd, m->NumNodeProc(), n2bSnap);
606TEST_CASE(
"AdjC2CFace round-trip: cell2cellFace identity")
611 auto c2cfSnap = snapshotAdj(m->cell2cellFace, m->NumCell());
613 m->AdjLocal2GlobalC2CFace();
616 m->AdjGlobal2LocalC2CFace();
619 checkAdjMatchesSnapshot(m->cell2cellFace, m->NumCell(), c2cfSnap);
628 auto c2nSnap = snapshotAdj(m->cell2node, m->NumCell());
629 auto c2cSnap = snapshotAdj(m->cell2cell, m->NumCell());
630 auto b2nSnap = snapshotAdj(m->bnd2node, m->NumBnd());
631 auto b2cSnap = snapshotAdj2(m->bnd2cell, m->NumBnd());
633 m->AdjLocal2GlobalPrimary();
636 DNDS::index globalNodeSize = m->coords.father->globalSize();
642 CHECK(gN < globalNodeSize);
645 m->AdjGlobal2LocalPrimary();
648 checkAdjMatchesSnapshot(m->cell2node, m->NumCell(), c2nSnap);
649 checkAdjMatchesSnapshot(m->cell2cell, m->NumCell(), c2cSnap);
650 checkAdjMatchesSnapshot(m->bnd2node, m->NumBnd(), b2nSnap);
653 CHECK(m->bnd2cell(ib, 0) == b2cSnap[ib].first);
654 CHECK(m->bnd2cell(ib, 1) == b2cSnap[ib].second);
659TEST_CASE(
"AdjForBnd round-trip: ConstructBndMesh + ForBnd")
663 m->ConstructBndMesh(bMesh);
666 auto c2nSnap = snapshotAdj(bMesh.cell2node, bMesh.NumCell());
668 bMesh.AdjLocal2GlobalPrimaryForBnd();
671 DNDS::index globalNodeSize = bMesh.coords.father->globalSize();
672 for (
DNDS::index iC = 0; iC < bMesh.NumCell(); iC++)
673 for (
DNDS::rowsize j = 0; j < bMesh.cell2node.RowSize(iC); j++)
677 CHECK(gN < globalNodeSize);
680 bMesh.AdjGlobal2LocalPrimaryForBnd();
683 checkAdjMatchesSnapshot(bMesh.cell2node, bMesh.NumCell(), c2nSnap);
701 std::map<Elem::ElemType, int> typeCounts;
704 auto elem = m->GetCellElement(iC);
705 typeCounts[elem.type]++;
709 std::vector<int> localTypes;
710 for (
const auto& [type, count] : typeCounts)
711 localTypes.push_back(
static_cast<int>(type));
713 int nLocalTypes = localTypes.size();
714 std::vector<int> allRankSizes(g_mpi.
size);
715 MPI_Gather(&nLocalTypes, 1, MPI_INT, allRankSizes.data(), 1, MPI_INT, 0, g_mpi.
comm);
717 std::vector<int> allTypes;
718 std::vector<int> displs;
723 displs.resize(g_mpi.
size);
724 for (
int i = 0; i < g_mpi.
size; i++)
726 displs[i] = totalTypes;
727 totalTypes += allRankSizes[i];
729 allTypes.resize(totalTypes);
732 MPI_Gatherv(localTypes.data(), nLocalTypes, MPI_INT,
733 allTypes.data(), allRankSizes.data(), displs.data(), MPI_INT, 0, g_mpi.
comm);
736 std::set<Elem::ElemType> uniqueTypes;
739 for (
int typeInt : allTypes)
744 int nUnique = uniqueTypes.size();
745 MPI_Bcast(&nUnique, 1, MPI_INT, 0, g_mpi.
comm);
746 std::vector<int> uniqueTypesVec;
749 for (
auto type : uniqueTypes)
750 uniqueTypesVec.push_back(
static_cast<int>(type));
752 uniqueTypesVec.resize(nUnique);
753 MPI_Bcast(uniqueTypesVec.data(), nUnique, MPI_INT, 0, g_mpi.
comm);
756 std::map<Elem::ElemType, int> globalCounts;
757 for (
int typeInt : uniqueTypesVec)
760 int localCount = typeCounts.count(type) ? typeCounts[type] : 0;
762 MPI_Reduce(&localCount, &globalCount, 1, MPI_INT, MPI_SUM, 0, g_mpi.
comm);
764 globalCounts[type] = globalCount;
769 std::cout <<
"\nBall2 element type counts:" << std::endl;
770 for (
const auto& [type, count] : globalCounts)
772 std::cout <<
" Type " <<
static_cast<int>(type) <<
": " << count << std::endl;
777 for (
const auto& [type, count] : globalCounts)
779 CHECK(total == 958994);
787TEST_CASE(
"Elevation: O2 mesh cell count equals O1 cell count")
790 const auto& cfg = g_configs[0];
793 auto mO1 = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
814 mO1->RecoverNode2CellAndNode2Bnd();
815 mO1->RecoverCell2CellAndBnd2Cell();
816 mO1->BuildGhostPrimary();
817 mO1->AdjGlobal2LocalPrimary();
820 auto mO2 = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
821 mO2->BuildO2FromO1Elevation(*mO1);
824 mO2->RecoverNode2CellAndNode2Bnd();
825 mO2->RecoverCell2CellAndBnd2Cell();
826 mO2->BuildGhostPrimary();
827 mO2->AdjGlobal2LocalPrimary();
833 CHECK(nCellO2 == nCellO1);
836 for (
DNDS::index iC = 0; iC < mO2->NumCell(); iC++)
838 auto elem = mO2->GetCellElement(iC);
839 CHECK(elem.GetOrder() == 2);
845 const auto& cfg = g_configs[0];
848 auto mO1 = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
869 mO1->RecoverNode2CellAndNode2Bnd();
870 mO1->RecoverCell2CellAndBnd2Cell();
871 mO1->BuildGhostPrimary();
872 mO1->AdjGlobal2LocalPrimary();
875 auto mO2 = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
876 mO2->BuildO2FromO1Elevation(*mO1);
879 mO2->RecoverNode2CellAndNode2Bnd();
880 mO2->RecoverCell2CellAndBnd2Cell();
881 mO2->BuildGhostPrimary();
882 mO2->AdjGlobal2LocalPrimary();
884 DNDS::index nNodeO1 = mO1->coords.father->globalSize();
885 DNDS::index nNodeO2 = mO2->coords.father->globalSize();
888 CHECK(nNodeO2 > nNodeO1);
893 const auto& cfg = g_configs[0];
896 auto mO1 = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
917 mO1->RecoverNode2CellAndNode2Bnd();
918 mO1->RecoverCell2CellAndBnd2Cell();
919 mO1->BuildGhostPrimary();
920 mO1->AdjGlobal2LocalPrimary();
923 auto mO2 = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
924 mO2->BuildO2FromO1Elevation(*mO1);
925 mO2->RecoverNode2CellAndNode2Bnd();
926 mO2->RecoverCell2CellAndBnd2Cell();
927 mO2->BuildGhostPrimary();
928 mO2->AdjGlobal2LocalPrimary();
931 mO2->AdjLocal2GlobalPrimary();
932 auto mO1B = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
933 mO1B->BuildBisectO1FormO2(*mO2);
939 CHECK(nCellO1B > nCellO2);
942 for (
DNDS::index iC = 0; iC < mO1B->NumCell(); iC++)
944 auto elem = mO1B->GetCellElement(iC);
945 CHECK(elem.GetOrder() == 1);
949TEST_CASE(
"Bisection: global node count is preserved from O2 mesh")
951 const auto& cfg = g_configs[0];
954 auto mO1 = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
975 mO1->RecoverNode2CellAndNode2Bnd();
976 mO1->RecoverCell2CellAndBnd2Cell();
977 mO1->BuildGhostPrimary();
978 mO1->AdjGlobal2LocalPrimary();
981 auto mO2 = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
982 mO2->BuildO2FromO1Elevation(*mO1);
983 mO2->RecoverNode2CellAndNode2Bnd();
984 mO2->RecoverCell2CellAndBnd2Cell();
985 mO2->BuildGhostPrimary();
986 mO2->AdjGlobal2LocalPrimary();
989 mO2->AdjLocal2GlobalPrimary();
990 auto mO1B = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
991 mO1B->BuildBisectO1FormO2(*mO2);
994 DNDS::index nNodeO2 = mO2->coords.father->globalSize();
995 DNDS::index nNodeO1B = mO1B->coords.father->globalSize();
996 CHECK(nNodeO1B == nNodeO2);
999TEST_CASE(
"Elevation+Bisection: exact cell count progression")
1004 const auto& cfg = g_configs[0];
1007 auto mO1 = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
1028 mO1->RecoverNode2CellAndNode2Bnd();
1029 mO1->RecoverCell2CellAndBnd2Cell();
1030 mO1->BuildGhostPrimary();
1031 mO1->AdjGlobal2LocalPrimary();
1034 auto mO2 = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
1035 mO2->BuildO2FromO1Elevation(*mO1);
1036 mO2->RecoverNode2CellAndNode2Bnd();
1037 mO2->RecoverCell2CellAndBnd2Cell();
1038 mO2->BuildGhostPrimary();
1039 mO2->AdjGlobal2LocalPrimary();
1042 mO2->AdjLocal2GlobalPrimary();
1043 auto mO1B = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
1044 mO1B->BuildBisectO1FormO2(*mO2);
1048 DNDS::index nCellBisected = mO1B->NumCellGlobal();
1050 CHECK(nCellOrig == 100);
1051 CHECK(nCellElevated == 100);
1052 CHECK(nCellBisected == 400);
#define DNDS_assert(expr)
Debug-only assertion (compiled out when DNDS_NDEBUG is defined). Prints the expression + file/line + ...
the host side operators are provided as implemented
const MPI_Datatype DNDS_MPI_INDEX
MPI datatype matching index (= MPI_INT64_T).
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 MeshPartitionCell2Cell(const PartitionOptions &options)
void ReadFromCGNSSerial(const std::string &fName, const t_FBCName_2_ID &FBCName_2_ID)
reads a cgns file as serial input
void PartitionReorderToMeshCell2Cell()
void BuildCell2Cell()
build cell2cell topology, with node-neighbors included
void Deduplicate1to1Periodic(real searchEps=1e-8)
index NumNodeGhost() const
std::vector< index > node2parentNode
parent built
MeshAdjState adjPrimaryState
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).
MPI_Comm comm
The underlying MPI communicator handle.
void setWorld()
Initialise the object to MPI_COMM_WORLD. Requires MPI_Init to have run.
DNDS::index expectedCells
Expected global cell count (-1 = skip check)
tPoint translation1
Periodic translation axis 1.
bool periodic
Has periodic boundaries.
int dim
Spatial dimension.
tPoint translation3
Periodic translation axis 3.
const char * file
Filename relative to data/mesh/.
tPoint translation2
Periodic translation axis 2.
const char * name
Human-readable label.
DNDS::index expectedBnds
Expected global bnd count (-1 = skip check)
#define FOR_EACH_MESH_CONFIG(body)
TEST_CASE("3D: VFV P2 HQM error < P1 on sinCos3D")