DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
test_Reconstruction3D.cpp
Go to the documentation of this file.
1/**
2 * @file test_Reconstruction3D.cpp
3 * @brief Regression tests for CFV VariationalReconstruction<3> (3D).
4 *
5 * Exercises the full 3D VR pipeline on a periodic hex mesh:
6 * - Mesh reading, partitioning, ghost building
7 * - VR construction: metrics, base+weight, rec coeff
8 * - Polynomial exactness: constant (P0) for all methods
9 * - Iterative VFV convergence and error measurement
10 *
11 * Uses the Uniform32_3D_Periodic.cgns mesh (32^3 = 32768 hex cells,
12 * periodic on [0,1]^3).
13 *
14 * Golden values are acquired on first run and must be captured manually.
15 * The test verifies the 3D template instantiation works end-to-end.
16 */
17
18#define DOCTEST_CONFIG_IMPLEMENT
19#include "doctest.h"
20
22
23#include <array>
24#include <cmath>
25#include <iostream>
26#include <iomanip>
27#include <functional>
28#include <nlohmann/json.hpp>
29
30using namespace DNDS;
31using namespace DNDS::Geom;
32
33static constexpr int g_dim = 3;
34static constexpr int g_nv = 1;
36
37static MPIInfo g_mpi;
38
39// ===================================================================
40// Mesh builder
41// ===================================================================
42static std::string meshPath(const std::string &name)
43{
44 std::string f(__FILE__);
45 for (int i = 0; i < 4; i++)
46 {
47 auto pos = f.rfind('/');
48 if (pos == std::string::npos)
49 pos = f.rfind('\\');
50 if (pos != std::string::npos)
51 f = f.substr(0, pos);
52 }
53 return f + "/data/mesh/" + name;
54}
55
56static ssp<UnstructuredMesh> buildMesh3D(
57 const std::string &file,
59{
60 auto mesh = std::make_shared<UnstructuredMesh>(g_mpi, g_dim);
61 UnstructuredMeshSerialRW reader(mesh, 0);
62
63 tPoint zero{0, 0, 0};
64 mesh->SetPeriodicGeometry(
65 tPoint{Lx, 0, 0}, zero, zero,
66 tPoint{0, Ly, 0}, zero, zero,
67 tPoint{0, 0, Lz}, zero, zero);
68
69 reader.ReadFromCGNSSerial(meshPath(file));
70 reader.Deduplicate1to1Periodic();
71 reader.BuildCell2Cell();
72
74 pOpt.metisSeed = 42;
75 reader.MeshPartitionCell2Cell(pOpt);
76 reader.PartitionReorderToMeshCell2Cell();
77
78 mesh->RecoverNode2CellAndNode2Bnd();
79 mesh->RecoverCell2CellAndBnd2Cell();
80 mesh->BuildGhostPrimary();
81 mesh->AdjGlobal2LocalPrimary();
82
83 mesh->InterpolateFace();
84 mesh->AssertOnFaces();
85 return mesh;
86}
87
88// ===================================================================
89// VR builder
90// ===================================================================
91enum class RecMethod3D
92{
96};
97
98static int recMethodOrder(RecMethod3D m)
99{
100 switch (m)
101 {
103 return 1;
105 return 1;
107 return 2;
108 }
109 return 1;
110}
111
112static ssp<tVR> buildVR3D(ssp<UnstructuredMesh> mesh, RecMethod3D method)
113{
114 auto vr = std::make_shared<tVR>(g_mpi, mesh);
115
116 CFV::VRSettings defaultSettings(g_dim);
117 nlohmann::ordered_json j;
118 defaultSettings.WriteIntoJson(j);
119
120 int order = recMethodOrder(method);
121 j["maxOrder"] = order;
122 j["intOrder"] = std::max(order + 2, 5);
123 j["cacheDiffBase"] = true;
124 j["SORInstead"] = false;
125 j["jacobiRelax"] = 1.0;
126
127 if (method == RecMethod3D::GaussGreen)
128 {
129 j["subs2ndOrder"] = 1;
130 }
131 else
132 {
133 j["subs2ndOrder"] = 0;
134 j["functionalSettings"]["dirWeightScheme"] = "HQM_OPT";
135 j["functionalSettings"]["geomWeightScheme"] = "HQM_SD";
136 j["functionalSettings"]["geomWeightPower"] = 0.5;
137 j["functionalSettings"]["geomWeightBias"] = 1.0;
138 }
139
140 vr->parseSettings(j);
141 if (mesh->isPeriodic)
142 vr->SetPeriodicTransformations();
143 vr->ConstructMetrics();
144 vr->ConstructBaseAndWeight();
145 vr->ConstructRecCoeff();
146 return vr;
147}
148
149// ===================================================================
150// Boundary callback (periodic -- not needed, but required by interface)
151// ===================================================================
152using ScalarFunc3D = std::function<DNDS::real(const tPoint &)>;
153
154static tVR::TFBoundary<g_nv> g_zeroBC =
155 [](const auto &, const auto &, DNDS::index, DNDS::index, int,
156 const tPoint &, const tPoint &, t_index)
157{ return Eigen::Vector<DNDS::real, g_nv>::Zero(); };
158
159// ===================================================================
160// Core: run reconstruction and measure L1 error at quadrature points,
161// normalized by domain volume.
162// ===================================================================
163static DNDS::real runTest3D(
164 ssp<tVR> vr,
165 RecMethod3D method,
166 const ScalarFunc3D &exactFunc,
167 const tVR::TFBoundary<g_nv> &bc,
168 int maxIters,
169 DNDS::real convTol,
170 bool printProgress)
171{
172 auto mesh = vr->mesh;
173
175 vr->BuildUDof(u, 1);
176
177 for (DNDS::index iCell = 0; iCell < mesh->NumCell(); iCell++)
178 {
179 auto qCell = vr->GetCellQuad(iCell);
180 Eigen::Vector<DNDS::real, g_nv> uc;
181 uc.setZero();
182 qCell.IntegrationSimple(
183 uc,
184 [&](auto &vInc, int iG)
185 {
186 vInc(0) = exactFunc(vr->GetCellQuadraturePPhys(iCell, iG)) *
187 vr->GetCellJacobiDet(iCell, iG);
188 });
189 u[iCell] = uc / vr->GetCellVol(iCell);
190 }
191 u.trans.startPersistentPull();
192 u.trans.waitPersistentPull();
193
194 if (method == RecMethod3D::GaussGreen)
195 {
197 vr->BuildUGrad(uGrad, 1);
198 vr->DoReconstruction2ndGrad<g_nv>(uGrad, u, bc, 1);
199 uGrad.trans.startPersistentPull();
200 uGrad.trans.waitPersistentPull();
201
202 static const auto Seq012 = Eigen::seq(Eigen::fix<0>, Eigen::fix<g_dim - 1>);
203 DNDS::real errLocal = 0.0;
204 for (DNDS::index iCell = 0; iCell < mesh->NumCell(); iCell++)
205 {
206 auto qCell = vr->GetCellQuad(iCell);
207 DNDS::real errCell = 0.0;
208 qCell.IntegrationSimple(
209 errCell,
210 [&](DNDS::real &vInc, int iG)
211 {
212 tPoint pPhy = vr->GetCellQuadraturePPhys(iCell, iG);
213 DNDS::real uRecVal = u[iCell](0) +
214 (uGrad[iCell].transpose() *
215 (pPhy - vr->GetCellBary(iCell))(Seq012))(0);
216 DNDS::real uExact = exactFunc(pPhy);
217 vInc = std::abs(uRecVal - uExact) * vr->GetCellJacobiDet(iCell, iG);
218 });
219 errLocal += errCell;
220 }
221 DNDS::real errGlobal = 0.0;
222 MPI::Allreduce(&errLocal, &errGlobal, 1, DNDS_MPI_REAL, MPI_SUM, g_mpi.comm);
223 return errGlobal / vr->GetGlobalVol();
224 }
225 else
226 {
227 CFV::tURec<g_nv> uRec, uRecNew;
228 vr->BuildURec(uRec, 1);
229 vr->BuildURec(uRecNew, 1);
230
231 DNDS::real lastInc = veryLargeReal;
232 for (int iter = 0; iter < maxIters; iter++)
233 {
234 vr->DoReconstructionIter<g_nv>(
235 uRec, uRecNew, u, bc, true);
236
237 DNDS::real incLocal = 0.0;
238 for (DNDS::index iCell = 0; iCell < mesh->NumCell(); iCell++)
239 incLocal += (uRecNew[iCell] - uRec[iCell]).array().square().sum();
240 DNDS::real incGlobal = 0.0;
241 MPI::Allreduce(&incLocal, &incGlobal, 1, DNDS_MPI_REAL, MPI_SUM, g_mpi.comm);
242 incGlobal = std::sqrt(incGlobal / mesh->NumCellGlobal());
243
244 std::swap(uRec, uRecNew);
245 uRec.trans.startPersistentPull();
246 uRec.trans.waitPersistentPull();
247
248 lastInc = incGlobal;
249 if (printProgress && g_mpi.rank == 0 && (iter < 5 || (iter + 1) % 20 == 0))
250 std::cout << " iter " << iter + 1 << " inc = "
251 << std::scientific << incGlobal << std::endl;
252
253 if (convTol > 0 && incGlobal < convTol)
254 {
255 if (printProgress && g_mpi.rank == 0)
256 std::cout << " converged at iter " << iter + 1 << std::endl;
257 break;
258 }
259 }
260
261 DNDS::real errLocal = 0.0;
262 for (DNDS::index iCell = 0; iCell < mesh->NumCell(); iCell++)
263 {
264 auto qCell = vr->GetCellQuad(iCell);
265 DNDS::real errCell = 0.0;
266 qCell.IntegrationSimple(
267 errCell,
268 [&](DNDS::real &vInc, int iG)
269 {
270 Eigen::VectorXd baseVal =
271 vr->GetIntPointDiffBaseValue(
272 iCell, -1, -1, iG, std::array<int, 1>{0}, 1) *
273 uRec[iCell];
274 DNDS::real uRecVal = baseVal(0) + u[iCell](0);
275 DNDS::real uExact = exactFunc(vr->GetCellQuadraturePPhys(iCell, iG));
276 vInc = std::abs(uRecVal - uExact) * vr->GetCellJacobiDet(iCell, iG);
277 });
278 errLocal += errCell;
279 }
280 DNDS::real errGlobal = 0.0;
281 MPI::Allreduce(&errLocal, &errGlobal, 1, DNDS_MPI_REAL, MPI_SUM, g_mpi.comm);
282 return errGlobal / vr->GetGlobalVol();
283 }
284}
285
286// ===================================================================
287// Prebuilt mesh
288// ===================================================================
289static ssp<UnstructuredMesh> g_mesh3d;
290
291int main(int argc, char **argv)
292{
293 MPI_Init(&argc, &argv);
294 g_mpi.setWorld();
295
296 if (g_mpi.rank == 0)
297 std::cout << "=== Building 3D mesh ===" << std::endl;
298
299 // Uniform32_3D_Periodic: [0,1]^3 periodic hex mesh, 32768 cells
300 g_mesh3d = buildMesh3D("Uniform32_3D_Periodic.cgns", 1.0, 1.0, 1.0);
301
302 if (g_mpi.rank == 0)
303 std::cout << "=== 3D mesh built, nCellGlobal="
304 << g_mesh3d->NumCellGlobal() << " ===" << std::endl;
305
306 doctest::Context ctx;
307 ctx.applyCommandLine(argc, argv);
308 int res = ctx.run();
309
310 g_mesh3d.reset();
311 MPI_Finalize();
312 return res;
313}
314
315// ===================================================================
316// Test functions (periodic on [0,1]^3)
317// ===================================================================
318static const DNDS::real g_L = 1.0;
319static const DNDS::real g_k = 2.0 * pi / g_L;
320
321static ScalarFunc3D sinCos3D = [](const tPoint &p)
322{ return std::sin(g_k * p[0]) * std::cos(g_k * p[1]) * std::cos(g_k * p[2]); };
323
324static ScalarFunc3D cosPlusCos3D = [](const tPoint &p)
325{ return std::cos(g_k * p[0]) + std::cos(g_k * p[1]) + std::cos(g_k * p[2]); };
326
327// ===================================================================
328// Sentinel for golden values not yet acquired: use 1e300.
329// When golden == 1e300, only print the error (no regression check).
330// ===================================================================
331static constexpr DNDS::real GOLDEN_NOT_ACQUIRED = 1e300;
332
333// ===================================================================
334// CONSTANT EXACTNESS: all methods should reproduce a constant exactly
335// ===================================================================
336
337TEST_CASE("3D: constant exactness with GaussGreen")
338{
339 auto vr = buildVR3D(g_mesh3d, RecMethod3D::GaussGreen);
340 ScalarFunc3D f = [](const tPoint &) { return 1.0; };
341 DNDS::real err = runTest3D(vr, RecMethod3D::GaussGreen, f, g_zeroBC, 1, 0, false);
342 if (g_mpi.rank == 0)
343 std::cout << "[3D/const/GG] err = " << std::scientific << err << std::endl;
344 CHECK(err < 1e-12);
345}
346
347TEST_CASE("3D: constant exactness with VFV_P1_HQM")
348{
349 auto vr = buildVR3D(g_mesh3d, RecMethod3D::VFV_P1_HQM);
350 ScalarFunc3D f = [](const tPoint &) { return 1.0; };
351 DNDS::real err = runTest3D(vr, RecMethod3D::VFV_P1_HQM, f, g_zeroBC, 100, 1e-15, false);
352 if (g_mpi.rank == 0)
353 std::cout << "[3D/const/P1-HQM] err = " << std::scientific << err << std::endl;
354 CHECK(err < 1e-12);
355}
356
357TEST_CASE("3D: constant exactness with VFV_P2_HQM")
358{
359 auto vr = buildVR3D(g_mesh3d, RecMethod3D::VFV_P2_HQM);
360 ScalarFunc3D f = [](const tPoint &) { return 1.0; };
361 DNDS::real err = runTest3D(vr, RecMethod3D::VFV_P2_HQM, f, g_zeroBC, 200, 1e-15, false);
362 if (g_mpi.rank == 0)
363 std::cout << "[3D/const/P2-HQM] err = " << std::scientific << err << std::endl;
364 CHECK(err < 1e-12);
365}
366
367// ===================================================================
368// Golden-value regression table for 3D smooth functions.
369// Sentinel GOLDEN_NOT_ACQUIRED (1e300) means "not yet captured".
370// ===================================================================
371
373{
376 const char *funcName;
379 DNDS::real golden; // golden L1/vol (1e300 = not acquired)
380};
381
382static const char *recMethodName3D(RecMethod3D m)
383{
384 switch (m)
385 {
386 case RecMethod3D::GaussGreen: return "GG";
387 case RecMethod3D::VFV_P1_HQM: return "P1-HQM";
388 case RecMethod3D::VFV_P2_HQM: return "P2-HQM";
389 }
390 return "?";
391}
392
393static Test3DCase g_3dTests[] = {
394 // sinCos3D
395 {RecMethod3D::GaussGreen, sinCos3D, "sinCos3D", 1, 0,
396 1.5098818118e-03},
397 {RecMethod3D::VFV_P1_HQM, sinCos3D, "sinCos3D", 200, 1e-14,
398 3.2475021825e-03},
399 {RecMethod3D::VFV_P2_HQM, sinCos3D, "sinCos3D", 200, 1e-14,
400 7.4823241394e-05},
401
402 // cosPlusCos3D
403 {RecMethod3D::GaussGreen, cosPlusCos3D, "cos+cos3D", 1, 0,
404 1.4931684237e-03},
405 {RecMethod3D::VFV_P1_HQM, cosPlusCos3D, "cos+cos3D", 200, 1e-14,
406 2.4261881197e-03},
407 {RecMethod3D::VFV_P2_HQM, cosPlusCos3D, "cos+cos3D", 200, 1e-14,
408 3.7014576777e-05},
409};
410
411static const int g_n3dTests = sizeof(g_3dTests) / sizeof(g_3dTests[0]);
412
413TEST_CASE("3D: golden-value regression for smooth functions")
414{
415 for (int ti = 0; ti < g_n3dTests; ti++)
416 {
417 auto &tc = g_3dTests[ti];
418 std::string label = std::string("3D/") + tc.funcName + "/" +
419 recMethodName3D(tc.method);
420
421 SUBCASE(label.c_str())
422 {
423 auto vr = buildVR3D(g_mesh3d, tc.method);
424 DNDS::real err = runTest3D(vr, tc.method, tc.func,
425 g_zeroBC, tc.maxIters, tc.convTol, false);
426
427 if (g_mpi.rank == 0)
428 std::cout << "[" << label << "] err = " << std::scientific
429 << std::setprecision(10) << err << std::endl;
430
431 CHECK(err > 0.0);
432 CHECK_FALSE(std::isnan(err));
433
434 if (tc.golden < GOLDEN_NOT_ACQUIRED)
435 CHECK(err == doctest::Approx(tc.golden).epsilon(1e-6));
436 }
437 }
438}
439
440// ===================================================================
441// VFV P1 on 3D: convergence check
442// ===================================================================
443
444TEST_CASE("3D: VFV P1 HQM converges on sinCos3D")
445{
446 auto vr = buildVR3D(g_mesh3d, RecMethod3D::VFV_P1_HQM);
447
449 vr->BuildUDof(u, 1);
450 CFV::tURec<g_nv> uRec, uRecNew;
451 vr->BuildURec(uRec, 1);
452 vr->BuildURec(uRecNew, 1);
453
454 auto mesh = vr->mesh;
455 for (DNDS::index iCell = 0; iCell < mesh->NumCell(); iCell++)
456 {
457 auto qCell = vr->GetCellQuad(iCell);
458 Eigen::Vector<DNDS::real, g_nv> uc;
459 uc.setZero();
460 qCell.IntegrationSimple(
461 uc,
462 [&](auto &vInc, int iG)
463 {
464 vInc(0) = sinCos3D(vr->GetCellQuadraturePPhys(iCell, iG)) *
465 vr->GetCellJacobiDet(iCell, iG);
466 });
467 u[iCell] = uc / vr->GetCellVol(iCell);
468 }
469 u.trans.startPersistentPull();
470 u.trans.waitPersistentPull();
471
472 int convergedAt = 0;
473 DNDS::real lastInc = veryLargeReal;
474 for (int iter = 0; iter < 200; iter++)
475 {
476 vr->DoReconstructionIter<g_nv>(uRec, uRecNew, u, g_zeroBC, true);
477
478 DNDS::real incLocal = 0.0;
479 for (DNDS::index iCell = 0; iCell < mesh->NumCell(); iCell++)
480 incLocal += (uRecNew[iCell] - uRec[iCell]).array().square().sum();
481 DNDS::real incGlobal = 0.0;
482 MPI::Allreduce(&incLocal, &incGlobal, 1, DNDS_MPI_REAL, MPI_SUM, g_mpi.comm);
483 incGlobal = std::sqrt(incGlobal / mesh->NumCellGlobal());
484
485 std::swap(uRec, uRecNew);
486 uRec.trans.startPersistentPull();
487 uRec.trans.waitPersistentPull();
488
489 lastInc = incGlobal;
490 if (incGlobal < 1e-14)
491 {
492 convergedAt = iter + 1;
493 break;
494 }
495 }
496
497 if (g_mpi.rank == 0)
498 std::cout << "[3D/Convergence P1-HQM] " << convergedAt
499 << " iters, final inc = " << std::scientific << lastInc << std::endl;
500
501 CHECK(convergedAt > 0);
502 CHECK(convergedAt < 200);
503}
504
505// ===================================================================
506// Order-accuracy check: P2 < P1
507// ===================================================================
508
509TEST_CASE("3D: VFV P2 HQM error < P1 on sinCos3D")
510{
511 auto vrP1 = buildVR3D(g_mesh3d, RecMethod3D::VFV_P1_HQM);
512 auto vrP2 = buildVR3D(g_mesh3d, RecMethod3D::VFV_P2_HQM);
513
514 DNDS::real errP1 = runTest3D(vrP1, RecMethod3D::VFV_P1_HQM, sinCos3D,
515 g_zeroBC, 200, 1e-14, false);
516 DNDS::real errP2 = runTest3D(vrP2, RecMethod3D::VFV_P2_HQM, sinCos3D,
517 g_zeroBC, 200, 1e-14, false);
518
519 if (g_mpi.rank == 0)
520 std::cout << "[3D] P2/P1 ratio = " << errP2 / errP1 << std::endl;
521
522 CHECK(errP2 < errP1);
523}
524
525// ===================================================================
526// Metric sanity: cell volumes sum to domain volume
527// ===================================================================
528
529TEST_CASE("3D: cell volumes sum to 1.0 (unit cube)")
530{
531 auto vr = buildVR3D(g_mesh3d, RecMethod3D::GaussGreen);
532 DNDS::real globalVol = vr->GetGlobalVol();
533 if (g_mpi.rank == 0)
534 std::cout << "[3D] globalVol = " << std::setprecision(15) << globalVol << std::endl;
535 CHECK(globalVol == doctest::Approx(1.0).epsilon(1e-10));
536}
Primary solver state container: an ArrayEigenMatrix pair with MPI-collective vector-space operations.
Definition ArrayDOF.hpp:172
The VR class that provides any information needed in high-order CFV.
int main()
Definition dummy.cpp:3
int32_t t_index
Definition Geometric.hpp:6
Eigen::Vector3d tPoint
Definition Geometric.hpp:9
the host side operators are provided as implemented
DNDS_CONSTANT const real pi
π in double precision (matches DNDS_E_PI macro).
Definition Defines.hpp:199
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
Definition Defines.hpp:107
std::shared_ptr< T > ssp
Shortened alias for std::shared_ptr used pervasively in DNDSR.
Definition Defines.hpp:138
double real
Canonical floating-point scalar used throughout DNDSR (double precision).
Definition Defines.hpp:105
DNDS_CONSTANT const real veryLargeReal
Catch-all upper bound ("practically infinity") for physical scalars.
Definition Defines.hpp:190
const MPI_Datatype DNDS_MPI_REAL
MPI datatype matching real (= MPI_REAL8).
Definition MPI.hpp:92
TTrans trans
Ghost-communication engine bound to father and son.
A means to translate nlohmann json into c++ primitive data types and back; and stores then during com...
Lightweight bundle of an MPI communicator and the calling rank's coordinates.
Definition MPI.hpp:215
int rank
This rank's 0-based index within comm (-1 until initialised).
Definition MPI.hpp:219
MPI_Comm comm
The underlying MPI communicator handle.
Definition MPI.hpp:217
void setWorld()
Initialise the object to MPI_COMM_WORLD. Requires MPI_Init to have run.
Definition MPI.hpp:242
real err
double order
Definition test_ODE.cpp:257
auto res
Definition test_ODE.cpp:196
DNDS::real globalVol
std::function< DNDS::real(const tPoint &)> ScalarFunc3D
TEST_CASE("3D: VFV P2 HQM error < P1 on sinCos3D")
if(g_mpi.rank==0) std CHECK(globalVol==doctest::Approx(1.0).epsilon(1e-10))
CFV::VariationalReconstruction< g_dim > tVR