|
DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
|
This guide takes you from a raw CGNS file to a running PDE solver on top of the DNDSR infrastructure. It covers three layers:
src/Geom/) – distributed unstructured mesh, topology, node coordinates, element/quadrature access.src/CFV/FiniteVolume.hpp) – geometric integration support: cell volumes, face areas, Jacobian determinants, quadrature metrics.src/CFV/VariationalReconstruction.hpp) – polynomial basis, reconstruction matrices, iterative VR solver, limiters.For array concepts (father/son, ghost communication) see Array Infrastructure.
| You are writing... | Minimum layer you need |
|---|---|
| A mesh-manipulation tool (no PDE) | Geom::UnstructuredMesh |
| A first-order finite volume solver | Geom + CFV::FiniteVolume |
| A high-order FV solver with polynomial reconstruction | Geom + CFV::FiniteVolume + CFV::VariationalReconstruction |
| A new PDE on an existing scheme | plug your physics into a ModelEvaluator subclass |
A CFD solver needs topology (cells, nodes, faces), geometry (coords), quadrature, and MPI distribution. Phase A gets you the mesh; phases B–D add the CFV layer.
Each step is explained with why it is needed. (Source: Geom/Mesh.hpp for declarations; Geom/Mesh_Serial_ReadFromCGNS.cpp for the CGNS reader implementation.)
After these 8 steps the mesh is ready. Optional steps (order elevation, h-bisection) can be inserted between steps 7 and 8; see Mesh.hpp:490 for BuildO2FromO1Elevation and Mesh.hpp:497 for BuildBisectO1FormO2.
The helper function create_mesh_from_CGNS wraps the entire 8-step pipeline:
name2ID maps boundary zone names (from the CGNS file) to integer IDs, which you use when setting boundary conditions. (Source: python/DNDSR/Geom/utils.py:23.)
After building, the mesh provides these ArrayPair objects. Father stores owned entities; son stores ghosts from neighboring ranks.
(Source: Geom/Mesh.hpp:54-66.)
| Array | Row entity | Column content | Row width |
|---|---|---|---|
cell2node | cell | node indices | variable (3 for tri, 4 for quad, ...) |
cell2cell | cell | neighbor cell indices | variable |
bnd2cell | boundary face | [innerCell, ghostCell] | fixed 2 |
coords | node | [x, y, z] | fixed 3 |
cellElemInfo | cell | ElemInfo struct | fixed 1 |
(Source: Geom/Mesh.hpp:94-104.)
| Array | Row entity | Column content | Row width |
|---|---|---|---|
face2node | face | node indices | variable |
face2cell | face | [leftCell, rightCell] | fixed 2 |
cell2face | cell | face indices | variable |
bnd2face | boundary | face index | fixed 1 |
(Source: Geom/Mesh.hpp:456-478.)
Looping over all cells including ghosts – useful for reconstruction passes that need to write into ghost cells too:
Looping over faces – the standard finite-volume flux pattern:
Looping over a cell's faces:
Each cell, face, and boundary carries an ElemInfo that records its element type. The Element struct gives shape functions and geometric queries. (Source: Geom/Elements.hpp:186 for Element; Geom/ElemEnum.hpp:18 for ElemType.)
| Enum | Name | Nodes | Dim | Order |
|---|---|---|---|---|
Line2 | Linear edge | 2 | 1 | 1 |
Tri3 | Triangle | 3 | 2 | 1 |
Quad4 | Quad | 4 | 2 | 1 |
Tet4 | Tet | 4 | 3 | 1 |
Hex8 | Hex | 8 | 3 | 1 |
Prism6 | Prism | 6 | 3 | 1 |
Pyramid5 | Pyramid | 5 | 3 | 1 |
Tri6 | Quadratic tri | 6 | 2 | 2 |
Quad9 | Quadratic quad | 9 | 2 | 2 |
Tet10 | Quadratic tet | 10 | 3 | 2 |
Hex27 | Quadratic hex | 27 | 3 | 2 |
Coordinates are always 3D (Eigen::Vector3d), even for 2D meshes (z=0). GetCoordsOnCell at Geom/Mesh.hpp:566 applies periodic coordinate transformations when a cell straddles a periodic boundary; always use it instead of manually reading coords[cell2node[iCell][j]] unless you know the mesh is not periodic.
For simple geometric computations (e.g. computing cell volumes yourself) you can use the Quadrature class directly. For a production solver, however, CFV caches all of this, and you should use CFV's accessors (Part 6 below).
(Source: Geom/Quadrature.hpp:96.)
The CFV::FiniteVolume class (src/CFV/FiniteVolume.hpp:15) holds cached geometric data the solver uses every time step: cell volumes, barycenters, face areas, Jacobian determinants at every quadrature point, face normals, etc. If you are writing a first-order FV solver, this is the only CFV class you need.
For high-order schemes you use VariationalReconstruction instead, which inherits from FiniteVolume and adds reconstruction matrices (Part 6).
The minimum configuration is maxOrder (polynomial order of reconstruction) and intOrder (quadrature order). Additional settings (FiniteVolume/FiniteVolumeSettings.hpp:22-65 for FV, CFV/VRSettings.hpp:32-239 for the VR-extended struct) control caching, anisotropic functionals, limiter behavior, and more.
Minimum baseline:
For a bare-FV scheme (no reconstruction), construct a FiniteVolume directly; for high-order, construct a VariationalReconstruction<dim>:
The periodic-transformations argument tells VR how to rotate or reflect vector variables (velocity components) across periodic boundaries. For a scalar test field, the no-op variant (SetPeriodicTransformationsNoOp) is correct. See VariationalReconstruction.hpp:228-255 for the signature.
ConstructMetrics() runs all 15 geometric construction steps in the correct order. Each step populates a protected ArrayPair. (Source: VariationalReconstruction.cpp:7-44; individual methods in FiniteVolume.cpp.)
After ConstructMetrics you can query:
(Source: FiniteVolume.hpp:198-229, 251-273.)
The Python driver from test/CFV/test_vr_correctness.py:55-82 is the concise equivalent:
Phases D–F build the reconstruction matrices. These phases are only needed if you want high-order (maxOrder >= 2).
The ConstructBaseAndWeight call needs a callback that tells VR which polynomial orders to constrain on each boundary zone. The callback signature is real(Geom::t_index bcID, int iOrder) -> real returning a weight.
The canonical pattern from EulerEvaluator::InitializeFV (EulerEvaluator.hpp:138-152) dispatches on BC type:
iOrder == 0 is the mean-value constraint; iOrder >= 1 are derivative-order constraints. Returning 0 drops that order from the face functional.
In Python, pass a dict {(bc_id, iOrder): weight}:
This assembles per-cell matrices matrixAAInvB (face coefficients) and vectorAInvB (RHS coefficients) used by the iterative VR solver. (Source: VariationalReconstruction.cpp:507-874.) After this call, the VR object is ready for reconstruction.
Three DOF types for a typical second-order PDE:
NDOF (number of reconstruction DOFs) is determined by maxOrder and dim: for 2D, NDOF = 1, 3, 6, 10 for orders 0, 1, 2, 3. The reconstruction array tURec stores NDOF-1 rows because the mean is kept separately in tUDof.
BuildUDof also sets up the ghost mapping (borrowing from mesh->cell2node.trans) and initializes persistent MPI send/recv requests so you can pull ghosts with two calls:
(Source: CFV/DOFFactory.hpp:17-154 for the generic factory; VariationalReconstruction.hpp:807-891 for the typed builders.)
A PDE solver on top of the VR infrastructure consists of:
EvaluateRHS** method that loops over faces, reconstructs the state at each face quadrature point, computes a numerical flux, and accumulates into the cell RHS.Signature (VariationalReconstruction.hpp:893-902):
You return the ghost state (the boundary-imposed value of u) as a function of the interior state plus geometry plus zone ID. VR uses this both during reconstruction (in GetBoundaryRHS, see VariationalReconstruction_Reconstruction.hxx:19-96) and during the flux evaluation for the "right" state at boundary faces.
Typical structure (production example in Euler/EulerEvaluator.hpp:1353, generateBoundaryValue):
This is the heart of your solver. The minimal template, following ModelEvaluator::EvaluateRHS (src/CFV/ModelEvaluator.cpp:6-115):
Key VR accessors used in the loop:
| Call | Returns | Purpose |
|---|---|---|
GetFaceQuad(iFace) | Quadrature | Face quadrature rule (order faceAtr.intOrder) |
GetFaceNorm(iFace, iG) | Vector3d | Outward unit normal at quad pt iG (-1 = mean) |
GetFaceJacobiDet(iFace, iG) | real | Metric at quad pt |
GetFaceQuadraturePPhys(iFace, iG) | Vector3d | Physical coords of quad pt |
GetIntPointDiffBaseValue(iC, iF, if2c, iG, diffs, maxD) | MatrixXR | Basis values/derivatives at quad pt. Multiplied by uRec[iCell] to get the polynomial value or derivative there. |
GetCellVol(iCell) | real | Cell volume |
GetFaceArea(iFace) | real | Face area |
(Sources: FiniteVolume.hpp:198-281, VariationalReconstruction.hpp:378-442.)
For periodic meshes, use the *FromCell variants (GetFaceNormFromCell, GetOtherCellBaryFromCell) which apply the periodic transform automatically. See FiniteVolume.hpp:259-273, 319-337.
Per physical time step, a typical RK-style solver does:
(Source: DoReconstructionIter declared at VariationalReconstruction.hpp:985, impl at VariationalReconstruction_Reconstruction.hxx:485-615.)
For the complete production solver, including all optimizations (persistent reconstruction requests, implicit time integration, preconditioned linear solves, shock-capturing limiters), see:
Euler/EulerSolver.hxx – the main solver loopEuler/EulerEvaluator.hpp:123 (InitializeFV) – Phases B–FEuler/EulerEvaluator.hpp:399 (EvaluateRHS) – production RHSEuler/EulerEvaluator.hpp:980 (fluxFace) – batched face fluxEuler/EulerEvaluator.hpp:1353 (generateBoundaryValue) – BC dispatchA complete Python pipeline from mesh to RHS, adapted from test/CFV/test_vr_correctness.py:
See test/CFV/test_vr_correctness.py:41-120 for the full working test, and test/cpp/Euler/test_EulerEvaluator.cpp for the C++ equivalent exercising a production Euler/NS evaluator.