Python Geom Module - Mesh Reader Guide¶
This document describes how to use the DNDSR Python Geom module for reading and manipulating computational meshes.
Overview¶
The DNDSR.Geom module provides Python bindings to the C++ geometry library, enabling:
Reading CGNS mesh files
Mesh partitioning and distribution
Order elevation (O1 → O2)
Mesh bisection for h-refinement
Boundary mesh extraction
VTK output generation
Wall distance computation
CUDA device offloading
Setup¶
Import the module and initialize MPI:
from DNDSR import Geom, DNDS
MPI is initialized automatically when DNDSR.DNDS is imported (via
MPI.Init_thread). If you need to control MPI initialization yourself
(e.g., to set the thread level), import and initialize mpi4py before
importing DNDSR.DNDS.
Core Classes¶
UnstructuredMesh¶
The main mesh container class.
from DNDSR import Geom, DNDS
mpi = DNDS.MPIInfo()
mpi.setWorld()
mesh = Geom.UnstructuredMesh(mpi, dim=3)
Key Methods:
Method |
Description |
|---|---|
|
Number of local cells |
|
Global cell count (collective) |
|
Number of local nodes |
|
Global node count (collective) |
|
Number of local faces |
|
Global face count (collective) |
|
Number of local boundary faces |
|
Global boundary face count (collective) |
|
Number of ghost cells |
|
Number of ghost nodes |
|
Number of ghost faces |
|
Number of ghost boundary faces |
|
Mesh dimension (2 or 3) |
|
Return the |
|
Build node connectivity |
|
Build cell/boundary connectivity |
|
Build ghost cell communication |
|
Convert global indices to local |
|
Convert local indices to global |
|
Global→local for boundary mesh |
|
Local→global for boundary mesh |
|
Global→local for node-to-cell/bnd |
|
Local→global for node-to-cell/bnd |
|
Build ghost node-to-cell/bnd comm |
|
Build face interpolation data |
|
Validate face data |
|
Prepare for VTK output |
|
Reconstruct periodic node mapping |
|
Extract (dim-1) boundary mesh |
|
Reorder cells for cache locality |
|
Elevate O1→O2 in-place |
|
Bisect O2→O1 sub-mesh in-place |
|
Compute wall distance field |
|
Offload arrays to device ( |
|
Pull arrays back to host |
|
Total memory used by arrays |
Key Read-Only Members:
Member |
Type |
Description |
|---|---|---|
|
coordinate pair |
Node coordinates |
|
adjacency pair |
Cell-to-node connectivity |
|
adjacency pair |
Boundary-to-node connectivity |
|
adjacency pair |
Boundary-to-cell connectivity |
|
adjacency pair |
Cell-to-cell connectivity |
|
ElemInfo pair |
Per-cell element type and zone |
|
ElemInfo pair |
Per-bnd element type and zone |
|
adjacency pair |
Cell-to-face (after |
|
adjacency pair |
Face-to-cell (after |
|
adjacency pair |
Face-to-node (after |
|
ElemInfo pair |
Per-face element type and zone |
Note:
GetCellElement(),GetBndElement(),IsO1(), andIsO2()are C++ methods that are not exposed in the Python bindings. To query element types from Python, usemesh.cellElemInfo[iCell][0].getElemType()which returns aGeom.Elem.ElemTypeenum value.
ElemInfo¶
Per-element metadata, accessible via mesh.cellElemInfo and mesh.bndElemInfo.
info = mesh.cellElemInfo[iCell][0]
elem_type = info.getElemType() # returns Geom.Elem.ElemType enum
zone = info.zone # zone index
UnstructuredMeshSerialRW¶
Serial mesh reader/writer for CGNS files.
reader = Geom.UnstructuredMeshSerialRW(mesh, 0)
name2ID = reader.ReadFromCGNSSerial("mesh.cgns")
Key Methods:
Method |
Description |
|---|---|
|
Read CGNS file; returns |
|
Merge periodic 1-to-1 connections |
|
Build serial cell-to-cell adjacency |
|
Partition with Metis |
|
Reorder to match partition |
|
Prepare serial output data |
Key Members:
Member |
Access |
Description |
|---|---|---|
|
read/write |
The |
|
read-only |
Whether serial output is built |
|
read-only |
Whether serial input is ready |
AutoAppendName2ID¶
Returned by ReadFromCGNSSerial. Maps boundary/zone names to integer IDs.
name2ID = reader.ReadFromCGNSSerial(meshFile)
n2id = name2ID.n2id_map # dict-like: name → ID
ElemType Enum¶
Available as Geom.Elem.ElemType:
Value |
Description |
|---|---|
|
Unknown / uninitialized |
|
1D line elements |
|
Triangle elements |
|
Quadrilateral elements |
|
Tetrahedron elements |
|
Hexahedron elements |
|
Prism elements |
|
Pyramid elements |
Note: The C++
Elem::Elementstruct (with methodsGetDim(),GetOrder(),GetNumNodes(),IsO1(),GetParamSpace(), etc.) is not exposed in the Python bindings. Only theElemTypeenum is available.
WallDistOptions¶
Nested class UnstructuredMesh.WallDistOptions for configuring wall distance
computation:
opts = Geom.UnstructuredMesh.WallDistOptions()
opts.method = 1
opts.subdivide_quad = 5
opts.verbose = 10
opts.wallDistExecution = 4
opts.minWallDist = 1e-10
mesh.BuildNodeWallDist(lambda bnd_id: bnd_id == wall_id, opts)
Mesh Reading Workflow¶
Using the High-Level API (Recommended)¶
The recommended API uses read_mesh + prepare_mesh from
DNDSR.Geom.utils:
from DNDSR.Geom.utils import read_mesh, prepare_mesh
from DNDSR import DNDS
mpi = DNDS.MPIInfo()
mpi.setWorld()
result = read_mesh(
"data/mesh/UniformSquare_10.cgns",
mpi=mpi,
dim=2,
)
prepare_mesh(result.mesh, result.reader)
mesh = result.mesh
read_mesh returns a MeshReadResult dataclass with mesh, reader,
and name_to_id fields. prepare_mesh mutates the mesh in-place (cell
reorder, face interpolation, ghost N2CB, serial output, periodic nodes,
VTK connectivity).
The legacy create_mesh_from_CGNS function is still available as a
backward-compatible wrapper:
from DNDSR.Geom.utils import create_mesh_from_CGNS
from DNDSR import DNDS
mpi = DNDS.MPIInfo()
mpi.setWorld()
mesh, reader, name2ID = create_mesh_from_CGNS(
meshFile="data/mesh/UniformSquare_10.cgns",
mpi=mpi,
dim=2,
)
read_mesh parameter list:
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
required |
Path to CGNS or H5 mesh file |
|
|
required |
MPI context |
|
|
|
Mesh dimension (2 or 3) |
|
|
see below |
Periodic transform parameters |
|
|
|
Tolerance for periodic dedup |
|
|
auto |
|
|
|
Metis defaults |
Metis/ParMetis partitioning options |
|
|
|
|
|
|
|
Number of bisection levels (0-4) |
|
|
H5 factory |
For H5 read modes |
prepare_mesh parameter list:
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
required |
Mesh to prepare (mutated in-place) |
|
|
required |
Reader from |
|
|
|
Cell reordering partitions |
|
|
|
Second-level reordering partitions |
|
|
|
Build serial output data |
|
|
|
|
|
|
|
Wall distance settings |
|
|
|
Scale coordinates |
|
|
|
Rotate coordinates around Z axis |
|
|
|
Rectify near-plane coords ( |
|
|
|
Threshold for rectification |
Periodic geometry default:
periodic_geometry={
"translation1": [1, 0, 0],
"rotationCenter1": [0, 0, 0],
"eulerAngles1": [0, 0, 0],
"translation2": [0, 1, 0],
"rotationCenter2": [0, 0, 0],
"eulerAngles2": [0, 0, 0],
"translation3": [0, 0, 1],
"rotationCenter3": [0, 0, 0],
"eulerAngles3": [0, 0, 0],
}
These are passed directly to mesh.SetPeriodicGeometry(), which accepts up to
three periodic direction triples (translation, rotationCenter, eulerAngle).
Read Mode Options¶
read_mesh auto-detects the mode from the file extension (.cgns -> CGNS
serial read, .h5 -> H5 distributed read). Override with the read_mode
parameter:
Mode |
Extension |
Description |
|---|---|---|
|
|
Read CGNS on rank 0, partition with Metis, distribute. Returns |
|
|
Read H5 with even-split + ParMetis repartition. Works with any MPI rank count. |
Warning: In H5 mode,
name_to_id(boundary name mapping) is not read from the serializer and will beNone. Only CGNS mode returns a validAutoAppendName2ID.
Common Usage Patterns¶
from DNDSR.Geom.utils import read_mesh, prepare_mesh
# Basic read
result = read_mesh("data/mesh/UniformSquare_10.cgns", mpi=mpi, dim=2)
prepare_mesh(result.mesh, result.reader)
# With order elevation (O1 → O2)
result = read_mesh(
"data/mesh/UniformSquare_10.cgns", mpi=mpi, dim=2,
elevation="O2",
)
prepare_mesh(result.mesh, result.reader)
# With bisection (h-refinement)
result = read_mesh(
"data/mesh/UniformSquare_10.cgns", mpi=mpi, dim=2,
bisect=1,
)
prepare_mesh(result.mesh, result.reader)
# Combined elevation + bisection
result = read_mesh(
"data/mesh/UniformSquare_10.cgns", mpi=mpi, dim=2,
elevation="O2", bisect=2,
)
prepare_mesh(result.mesh, result.reader)
# With cell reordering for cache locality
result = read_mesh("data/mesh/UniformSquare_10.cgns", mpi=mpi, dim=2)
prepare_mesh(result.mesh, result.reader, reorder_parts=4, reorder_inner_parts=4)
# H5 distributed read with auto-repartitioning
result = read_mesh("mesh.dnds.h5", mpi=mpi, dim=3)
prepare_mesh(result.mesh, result.reader)
Legacy API (backward-compatible)¶
from DNDSR.Geom.utils import create_mesh_from_CGNS
# create_mesh_from_CGNS wraps read_mesh + prepare_mesh
mesh, reader, name2ID = create_mesh_from_CGNS(
meshFile="data/mesh/UniformSquare_10.cgns",
mpi=mpi, dim=2,
meshElevation="O2",
meshDirectBisect=1,
readMeshMode="Serial",
)
What the Pipeline Does¶
read_mesh + prepare_mesh run the complete mesh pipeline so you do not
need to call these steps manually. For reference, the CGNS-mode pipeline is:
read_mesh (CGNS mode):
reader.ReadFromCGNSSerial(meshFile)— read CGNS on rank 0reader.Deduplicate1to1Periodic(tolerance)— merge periodic connectionsreader.BuildCell2Cell()— build serial cell adjacencyreader.MeshPartitionCell2Cell({...})— partition with Metisreader.PartitionReorderToMeshCell2Cell()— reorder to match partitionGhost + connectivity build (RecoverN2C, RecoverC2C, BuildGhostPrimary, G2L)
(If
elevation == "O2") Build O2 mesh and swap(If
bisect > 0) Elevate→bisect loop
prepare_mesh:
mesh.ReorderLocalCells(nParts, nPartsInner)— cell reorderingmesh.InterpolateFace()— build face datamesh.AssertOnFaces()— validate facesAdjLocal2GlobalN2CB()/BuildGhostN2CB()/AdjGlobal2LocalN2CB()(If
build_serial_out) Build serial outputmesh.RecreatePeriodicNodes()— reconstruct periodic mappingmesh.BuildVTKConnectivity()— prepare VTK output(If
wall_dist_predicate) Compute wall distances(If coord transforms specified) Apply scale/rotation/rectification
Low-Level Serial Read (Manual Pipeline)¶
If you need fine-grained control, you can run the pipeline manually:
from DNDSR import Geom, DNDS
import os
mpi = DNDS.MPIInfo()
mpi.setWorld()
mesh = Geom.UnstructuredMesh(mpi, dim=2)
reader = Geom.UnstructuredMeshSerialRW(mesh, 0)
meshFile = "path/to/mesh.cgns"
assert os.path.isfile(meshFile)
name2ID = reader.ReadFromCGNSSerial(meshFile)
reader.Deduplicate1to1Periodic(1e-9)
reader.BuildCell2Cell()
reader.MeshPartitionCell2Cell({
"metisType": "KWAY",
"metisUfactor": 5,
"metisSeed": 0,
"metisNcuts": 3,
})
reader.PartitionReorderToMeshCell2Cell()
mesh.RecoverNode2CellAndNode2Bnd()
mesh.RecoverCell2CellAndBnd2Cell()
mesh.BuildGhostPrimary()
mesh.AdjGlobal2LocalPrimary()
mesh.AdjGlobal2LocalN2CB()
Order Elevation (O1 → O2)¶
Elevation converts linear elements to quadratic elements:
Quad4 → Quad9
Tri3 → Tri6
Hex8 → Hex27
Tet4 → Tet10
Prism6 → Prism18
Pyramid5 → Pyramid14
meshO2 = Geom.UnstructuredMesh(mpi, dim)
meshO2.BuildO2FromO1Elevation(meshO1)
meshO2.RecoverNode2CellAndNode2Bnd()
meshO2.RecoverCell2CellAndBnd2Cell()
meshO2.BuildGhostPrimary()
meshO2.AdjGlobal2LocalPrimary()
meshO2.AdjGlobal2LocalN2CB()
Node count increase for UniformSquare_10 (10×10 Quad4):
O1: 121 nodes (11×11 grid)
O2: 441 nodes (+320 new nodes)
220 edge midpoints (110 horizontal + 110 vertical)
100 cell centers
Mesh Bisection (h-refinement)¶
Bisection splits O2 elements into O1 sub-elements:
O2 Element |
Sub-elements |
O1 Type |
|---|---|---|
Quad9 |
4 |
Quad4 |
Tri6 |
4 |
Tri3 |
Hex27 |
8 |
Hex8 |
Tet10 |
8 |
Tet4 |
Prism18 |
8 |
Prism6 |
Pyramid14 |
12 |
Pyramid5 |
meshO2 = Geom.UnstructuredMesh(mpi, dim)
meshO2.BuildO2FromO1Elevation(meshO1)
meshO2.RecoverNode2CellAndNode2Bnd()
meshO2.RecoverCell2CellAndBnd2Cell()
meshO2.BuildGhostPrimary()
meshO1B = Geom.UnstructuredMesh(mpi, dim)
meshO1B.BuildBisectO1FormO2(meshO2)
meshO1B.RecoverNode2CellAndNode2Bnd()
meshO1B.RecoverCell2CellAndBnd2Cell()
meshO1B.BuildGhostPrimary()
meshO1B.AdjGlobal2LocalPrimary()
meshO1B.AdjGlobal2LocalN2CB()
Cell count progression for UniformSquare_10:
Original: 100 Quad4 cells
Elevated: 100 Quad9 cells
Bisected: 400 Quad4 cells (100 × 4)
Periodic Meshes¶
For meshes with periodic boundaries:
mesh, reader, name2ID = create_mesh_from_CGNS(
meshFile="IV10_10.cgns",
mpi=mpi,
dim=2,
periodic_geometry={
"translation1": [10, 0, 0],
"rotationCenter1": [0, 0, 0],
"eulerAngles1": [0, 0, 0],
},
periodic_tolerance=1e-9,
)
SetPeriodicGeometry accepts up to three periodic direction triples:
translation1/rotationCenter1/eulerAngles1 through
translation3/rotationCenter3/eulerAngles3.
Boundary Mesh Extraction¶
from DNDSR.Geom.utils import build_bnd_mesh
bnd = build_bnd_mesh(mesh)
meshBnd = bnd.mesh_bnd
readerBnd = bnd.reader_bnd
# Legacy wrapper (backward-compatible):
# from DNDSR.Geom.utils import create_bnd_mesh
# meshBnd, readerBnd = create_bnd_mesh(mesh)
# Access boundary information
n2id = name2ID.n2id_map
id2name = {v: k for k, v in n2id.items()}
for iBnd in range(mesh.NumBnd()):
elem_type = mesh.bndElemInfo[iBnd][0].getElemType()
zone = mesh.bndElemInfo[iBnd][0].zone
print(f"Boundary {iBnd}: type={elem_type}, zone={zone}")
Wall Distance Computation¶
# Identify wall boundaries by name
n2id = name2ID.n2id_map
id2name = {v: k for k, v in n2id.items()}
def id_is_wall(bnd_id):
if bnd_id in id2name:
name = id2name[bnd_id].upper()
return "WALL" in name
return False
opts = Geom.UnstructuredMesh.WallDistOptions()
opts.method = 1
opts.subdivide_quad = 5
opts.verbose = 10
opts.wallDistExecution = 4
mesh.BuildNodeWallDist(id_is_wall, opts)
CUDA Device Offloading¶
mesh.coords.to_device("CUDA")
mesh.to_device("CUDA")
# ... GPU computation ...
mesh.to_host()
VTK Output¶
VTK connectivity is built automatically by create_mesh_from_CGNS. For
manual control:
mesh.BuildVTKConnectivity()
# For serial output
mesh.AdjLocal2GlobalPrimary()
reader.BuildSerialOut()
mesh.AdjGlobal2LocalPrimary()
Querying Mesh State¶
Element Information¶
# Get element type for a cell
elem_type = mesh.cellElemInfo[iCell][0].getElemType() # Geom.Elem.ElemType enum
# Compare with known types
if elem_type == Geom.Elem.Quad4:
print("Linear quad")
elif elem_type == Geom.Elem.Quad9:
print("Quadratic quad")
# Get zone index
zone = mesh.cellElemInfo[iCell][0].zone
Mesh Properties¶
# Global counts
nCellGlobal = mesh.NumCellGlobal()
nNodeGlobal = mesh.NumNodeGlobal()
# Local counts
nCellLocal = mesh.NumCell()
nNodeLocal = mesh.NumNode()
nGhost = mesh.NumCellGhost()
Note: The C++ methods
IsO1()andIsO2(), as well as theMeshElevationStateenum (Elevation_Untouched,Elevation_O1O2), are not exposed in the Python bindings. To check whether a mesh uses O2 elements, inspect the element types viacellElemInfo[i][0].getElemType().
Note: The
MeshAdjStateenum is exposed in Python asGeom.MeshAdjStatewith valuesUnknown,PointToGlobal, andPointToLocal. Each adjacency member (e.g.mesh.cell2node) is anAdjPairTrackedobject whoseidxmember exposes read-only state query methods:state(),isLocal(),isGlobal(),isBuilt(),isWired(). The group state variables (e.g.adjPrimaryState) are still accessible as integers for backward compatibility.
References¶
See
test/Geom/test_basic_geom.pyfor Python usage examplesSee
python/DNDSR/Geom/utils.pyfor the Python API implementationSee
src/Geom/Mesh/Mesh_bind.hppfor the complete list of Python-exposed methodsSee
src/Geom/Elements_bind.hppfor theElemTypeenum bindingsSee
docs/architecture/Paradigm.mdfor overall design philosophy