27#define DOCTEST_CONFIG_IMPLEMENT
56 {
"UniformSquare_10",
"UniformSquare_10.cgns", 2,
false,
57 {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
58 {
"IV10_10",
"IV10_10.cgns", 2,
true,
59 {10, 0, 0}, {0, 10, 0}, {0, 0, 10}},
60 {
"NACA0012_H2",
"NACA0012_H2.cgns", 2,
false,
61 {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
62 {
"IV10U_10",
"IV10U_10.cgns", 2,
true,
63 {10, 0, 0}, {0, 10, 0}, {0, 0, 10}},
64 {
"Ball2",
"Ball2.cgns", 3,
false,
65 {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
67static constexpr int N_CONFIGS =
sizeof(g_configs) /
sizeof(g_configs[0]);
81static std::string meshPath(
const std::string &name)
83 std::string f(__FILE__);
84 for (
int i = 0; i < 4; i++)
86 auto pos = f.rfind(
'/');
87 if (pos == std::string::npos)
89 if (pos != std::string::npos)
92 return f +
"/data/mesh/" + name;
98 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] START" << std::endl;
100 auto mesh = std::make_shared<UnstructuredMesh>(g_mpi, cfg.
dim);
106 mesh->SetPeriodicGeometry(
113 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] ReadFromCGNSSerial..." << std::endl;
114 reader.ReadFromCGNSSerial(meshPath(cfg.
file));
117 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] Deduplicate1to1Periodic..." << std::endl;
118 reader.Deduplicate1to1Periodic(1e-8);
121 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] BuildCell2Cell..." << std::endl;
122 reader.BuildCell2Cell();
131 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] MeshPartitionCell2Cell..." << std::endl;
132 reader.MeshPartitionCell2Cell(pOpt);
135 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] PartitionReorderToMeshCell2Cell..." << std::endl;
136 reader.PartitionReorderToMeshCell2Cell();
139 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] RecoverNode2CellAndNode2Bnd..." << std::endl;
140 mesh->RecoverNode2CellAndNode2Bnd();
143 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] RecoverCell2CellAndBnd2Cell..." << std::endl;
144 mesh->RecoverCell2CellAndBnd2Cell();
147 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] BuildGhostPrimary..." << std::endl;
148 mesh->BuildGhostPrimary();
151 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] AdjGlobal2LocalPrimary..." << std::endl;
152 mesh->AdjGlobal2LocalPrimary();
155 std::cout <<
"[buildMesh " << meshId <<
" " << cfg.
name <<
"] DONE" << std::endl;
160static std::vector<std::vector<DNDS::index>> snapshotAdj(
163 std::vector<std::vector<DNDS::index>> out(nRows);
166 out[i].resize(
adj.RowSize(i));
168 out[i][j] =
adj(i, j);
177#define FOR_EACH_MESH_CONFIG(body) \
178 for (int _ci = 0; _ci < N_CONFIGS; _ci++) \
181 if (_ci == 4 && g_mpi.size != 8) continue; \
183 const auto &cfg = g_configs[_ci]; \
184 auto m = g_meshes[_ci]; \
185 SUBCASE(cfg.name) { body } \
191 MPI_Init(&argc, &argv);
195 for (
int i = 0; i < N_CONFIGS; i++)
198 if (i == 4 && g_mpi.
size != 8)
200 g_meshes[i] = buildMesh(i, g_configs[i]);
203 doctest::Context ctx;
204 ctx.applyCommandLine(argc, argv);
208 for (
auto &m : g_meshes)
220 CHECK(m->NumCell() >= 1);
221 CHECK(m->NumNode() >= 1);
231 CHECK(m->NumCellGhost() > 0);
232 CHECK(m->NumNodeGhost() > 0);
236TEST_CASE(
"Mesh setup: periodic state matches configuration")
251 auto m = g_meshes[4];
255 std::map<Elem::ElemType, int> typeCounts;
258 auto elem = m->GetCellElement(iC);
259 typeCounts[elem.type]++;
263 std::vector<int> localTypes;
264 for (
const auto& [type, count] : typeCounts)
265 localTypes.push_back(
static_cast<int>(type));
267 int nLocalTypes = localTypes.size();
268 std::vector<int> allRankSizes(g_mpi.
size);
269 MPI_Gather(&nLocalTypes, 1, MPI_INT, allRankSizes.data(), 1, MPI_INT, 0, g_mpi.
comm);
271 std::vector<int> allTypes;
272 std::vector<int> displs;
277 displs.resize(g_mpi.
size);
278 for (
int i = 0; i < g_mpi.
size; i++)
280 displs[i] = totalTypes;
281 totalTypes += allRankSizes[i];
283 allTypes.resize(totalTypes);
286 MPI_Gatherv(localTypes.data(), nLocalTypes, MPI_INT,
287 allTypes.data(), allRankSizes.data(), displs.data(), MPI_INT, 0, g_mpi.
comm);
290 std::set<Elem::ElemType> uniqueTypes;
293 for (
int typeInt : allTypes)
298 int nUnique = uniqueTypes.size();
299 MPI_Bcast(&nUnique, 1, MPI_INT, 0, g_mpi.
comm);
300 std::vector<int> uniqueTypesVec;
303 for (
auto type : uniqueTypes)
304 uniqueTypesVec.push_back(
static_cast<int>(type));
306 uniqueTypesVec.resize(nUnique);
307 MPI_Bcast(uniqueTypesVec.data(), nUnique, MPI_INT, 0, g_mpi.
comm);
310 std::map<Elem::ElemType, int> globalCounts;
311 for (
int typeInt : uniqueTypesVec)
314 int localCount = typeCounts.count(type) ? typeCounts[type] : 0;
316 MPI_Reduce(&localCount, &globalCount, 1, MPI_INT, MPI_SUM, 0, g_mpi.
comm);
318 globalCounts[type] = globalCount;
323 std::cout <<
"\nBall2 element type counts:" << std::endl;
324 for (
const auto& [type, count] : globalCounts)
326 std::cout <<
" Type " <<
static_cast<int>(type) <<
": " << count << std::endl;
331 for (
const auto& [type, count] : globalCounts)
333 CHECK(total == 958994);
340TEST_CASE(
"NodeIndex: Global2Local round-trip on father nodes")
343 for (
DNDS::index iNode = 0; iNode < m->NumNode(); iNode++)
347 CHECK(m->NodeIndexGlobal2Local(g) == iNode);
352TEST_CASE(
"NodeIndex: Global2Local round-trip on ghost nodes")
357 for (
DNDS::index i = m->NumNode(); i < m->NumNode() + m->NumNodeGhost(); i++)
361 CHECK(m->NodeIndexGlobal2Local(g) == i);
366TEST_CASE(
"NodeIndex: _NoSon round-trip on father nodes")
369 for (
DNDS::index iNode = 0; iNode < m->NumNode(); iNode++)
371 DNDS::index g = m->NodeIndexLocal2Global_NoSon(iNode);
373 CHECK(m->NodeIndexGlobal2Local_NoSon(g) == iNode);
378TEST_CASE(
"NodeIndex: _NoSon returns negative for non-local global")
384 DNDS::index g = (*m->coords.father->pLGlobalMapping)(other, 0);
385 CHECK(m->NodeIndexGlobal2Local_NoSon(g) < 0);
389TEST_CASE(
"NodeIndex: Global2Local returns negative for unknown global")
392 DNDS::index bogus = m->coords.father->globalSize() + 999;
402TEST_CASE(
"CellIndex: Global2Local round-trip on father cells")
405 for (
DNDS::index iCell = 0; iCell < m->NumCell(); iCell++)
409 CHECK(m->CellIndexGlobal2Local(g) == iCell);
414TEST_CASE(
"CellIndex: Global2Local round-trip on ghost cells")
419 CHECK(m->NumCellGhost() > 0);
420 for (
DNDS::index i = m->NumCell(); i < m->NumCell() + m->NumCellGhost(); i++)
424 CHECK(m->CellIndexGlobal2Local(g) == i);
429TEST_CASE(
"CellIndex: _NoSon round-trip on father cells")
432 for (
DNDS::index iCell = 0; iCell < m->NumCell(); iCell++)
434 DNDS::index g = m->CellIndexLocal2Global_NoSon(iCell);
436 CHECK(m->CellIndexGlobal2Local_NoSon(g) == iCell);
441TEST_CASE(
"CellIndex: _NoSon returns negative for non-local global")
447 DNDS::index g = (*m->cell2node.father->pLGlobalMapping)(other, 0);
448 CHECK(m->CellIndexGlobal2Local_NoSon(g) < 0);
455TEST_CASE(
"BndIndex: Global2Local round-trip on father bnds")
458 for (
DNDS::index iBnd = 0; iBnd < m->NumBnd(); iBnd++)
462 CHECK(m->BndIndexGlobal2Local(g) == iBnd);
470 for (
DNDS::index iBnd = 0; iBnd < m->NumBnd(); iBnd++)
472 DNDS::index g = m->BndIndexLocal2Global_NoSon(iBnd);
474 CHECK(m->BndIndexGlobal2Local_NoSon(g) == iBnd);
482TEST_CASE(
"UnInitIndex pass-through for all 12 conversion methods")
503TEST_CASE(
"Local2Global: negative local index decodes via -1-x encoding")
509 CHECK(m->NodeIndexLocal2Global(neg) == fakeGlobal);
510 CHECK(m->CellIndexLocal2Global(neg) == fakeGlobal);
511 CHECK(m->BndIndexLocal2Global(neg) == fakeGlobal);
518TEST_CASE(
"AdjPrimary: cell2node local indices are in valid range")
522 DNDS::index totalNodes = m->NumNode() + m->NumNodeGhost();
528 CHECK(iNode < totalNodes);
533TEST_CASE(
"AdjPrimary: cell2cell local entries are valid or not-found")
537 DNDS::index totalCells = m->NumCell() + m->NumCellGhost();
548TEST_CASE(
"AdjPrimary: bnd2cell owner cell is a local father cell")
556 CHECK(owner < m->NumCell());
561TEST_CASE(
"AdjPrimary: bnd2node local indices are in valid range")
565 DNDS::index totalNodes = m->NumNode() + m->NumNodeGhost();
571 CHECK(iNode < totalNodes);
579TEST_CASE(
"AdjPrimary: Local2Global then Global2Local is identity")
584 auto c2nSnap = snapshotAdj(m->cell2node, m->NumCell());
585 auto c2cSnap = snapshotAdj(m->cell2cell, m->NumCell());
587 m->AdjLocal2GlobalPrimary();
595 CHECK(gNode < m->coords.father->globalSize());
598 m->AdjGlobal2LocalPrimary();
603 REQUIRE(
static_cast<DNDS::rowsize>(c2nSnap[iC].size()) == m->cell2node.RowSize(iC));
605 CHECK(m->cell2node(iC, j) == c2nSnap[iC][j]);
606 REQUIRE(
static_cast<DNDS::rowsize>(c2cSnap[iC].size()) == m->cell2cell.RowSize(iC));
608 CHECK(m->cell2cell(iC, j) == c2cSnap[iC][j]);
613TEST_CASE(
"AdjPrimary: three consecutive round-trips are stable")
616 auto snap = snapshotAdj(m->cell2node, m->NumCell());
618 for (
int trip = 0; trip < 3; trip++)
620 m->AdjLocal2GlobalPrimary();
622 m->AdjGlobal2LocalPrimary();
628 CHECK(m->cell2node(iC, j) == snap[iC][j]);
632TEST_CASE(
"AdjPrimaryForBnd: round-trip on cell2node only")
637 auto snap = snapshotAdj(m->cell2node, m->NumCell());
639 m->AdjLocal2GlobalPrimaryForBnd();
642 m->AdjGlobal2LocalPrimaryForBnd();
647 CHECK(m->cell2node(iC, j) == snap[iC][j]);
#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
DNDS_CONSTANT const index UnInitIndex
Sentinel "not initialised" index value (= INT64_MIN).
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.
int MPI_int
MPI counterpart type for MPI_int (= C int). Used for counts and ranks in MPI calls.
Convenience bundle of a father, son, and attached ArrayTransformer.
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.
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.
#define FOR_EACH_MESH_CONFIG(body)
TEST_CASE("3D: VFV P2 HQM error < P1 on sinCos3D")
Eigen::Vector3d n(1.0, 0.0, 0.0)