Meshes
This page describes what DRAGonFLy needs from a mesh, how meshes are read, and the two ways of telling the solver which boundaries are the wall, the symmetry plane and the far field.
What the solver needs
Cells: a DOLFINx mesh, quadrilaterals in 2D and hexahedra in 3D in all the examples. The mesh must be ghosted across shared facets, which every reader below does (the interior-facet terms need it).
Orientation: the free stream flows in \(+x\) at zero angle of attack. In 2D, y is vertical. In 3D, y is spanwise and z vertical, and the model is a half-wing.
Three kinds of boundary: every exterior facet of the mesh belongs to exactly one of
the wall, the body (slip wall for Euler, adiabatic no-slip wall for RANS). It is the surface the shape parameterization moves.
a symmetry plane, treated as a slip wall. Only 3D half-wing meshes have one.
the far field, which gets the characteristic (Riemann-invariant) boundary condition (see far fields).
The far field is never specified: it is everything that is neither wall nor symmetry plane. Telling the solver which facets are the wall, and which are symmetry, is the “boundary type definition” below.
Reading a mesh
load_dolfinx_mesh reads a mesh file, chosen by its extension, and returns (mesh, info):
Extension |
Format |
Notes |
|---|---|---|
|
DOLFINx XDMF/HDF5 |
|
|
gmsh |
|
|
structured multi-block CGNS |
|
from dragonfly_sim.utils.mesh_io_utils import load_dolfinx_mesh
mesh, info = load_dolfinx_mesh("naca0012.xdmf")
mesh, info = load_dolfinx_mesh("wing.msh", tdim=3)
mesh, info = load_dolfinx_mesh("OAT15A_Rizzi_1.cgns") # info: chord, cell and boundary-edge counts
mesh, info = load_dolfinx_mesh("wing_vol_L3.cgns", tdim=3) # info: point, cell and block counts
A 2D CGNS grid must be one cell thick (or planar). Rank 0 reads it with PyVista, keeps one span plane of every
block, merges the nodes the blocks share by exact coordinate match, and checks that the boundary edges are exactly
those of the wall and far-field families (wall_family="Wall", farfield_family="Farfield"). By default the points
are shifted and scaled so that the wall’s leading edge is at \(x = 0\) and its chord is 1 (scale_to_chord). A 3D
grid becomes hexahedra, the blocks’ shared nodes are merged by exact coordinate match, and the mesh is checked for
inverted cells and unmerged block interfaces. VTK does not read its boundary conditions. DOLFINx then distributes
the mesh; no converted file is written.
A mesh can also be read with plain DOLFINx, for example
dolfinx.io.XDMFFile(comm, "naca0012.xdmf", "r").read_mesh(). Such a mesh carries no boundary tags unless you attach
them (see below), so it can only be used with the boundary function.
Defining the boundary types
DG_windtunnel_model (and Mesh, which it builds) finds the wall in one of two ways, and optionally the symmetry
planes. Either way, all other boundary facets are far field.
Option 1: a boundary function
mesh_inner_bdry_function takes the midpoints of the exterior facets, as an array of shape (dim, n), and returns
a boolean array of length n: True for the wall.
def wall(x): # x[0] = x, x[1] = y (, x[2] = z)
return (x[0] > -0.01) & (x[0] < 1.01) & (x[1] > -0.1) & (x[1] < 0.1)
model = DG_windtunnel_model(mesh, boundary_dict, mesh_inner_bdry_function=wall, poly_order=0)
dragonfly_sim.utils.mesh_manager_utils provides airfoil_inner_bdry_function and wing_inner_bdry_function for the
meshes used in the examples. The function needs no tags, so it works with every mesh. In 3D, the symmetry plane is
fixed at \(y = 0\): the exterior facets with midpoint \(y \le 10^{-6}\).
Which one is used
mesh_inner_bdry_function, if given.wall_tagsandsymmetry_tagsare then ignored.Otherwise
wall_tags.Otherwise the FFD block of the shape parameterization: if it has block corners, its wall function is used (see shape parameterization).
If none applies, the model raises an error.
Warning when tags are available. If the function is used (explicitly, or through the FFD block) and the mesh also
carries boundary tags, a UserWarning says so and lists the tags with the number of exterior facets each marks,
and the name where there is one. For the NACA0012 mesh of the examples:
The input mesh has boundary facet tags: 2 (240 facets), 3 (408 facets). The boundary function is used to find the
wall, so the boundary facet tags are currently unused. To use the tags instead, leave out the boundary function and
pass wall_tags (the tags of the body) and, if there is a symmetry plane, symmetry_tags; all other boundaries are far
field.
With wall_tags also given, the message adds that wall_tags and symmetry_tags are ignored. The warning appears
once per run (from rank 0).
Errors. A mesh that has tags, but is given neither a function nor wall_tags, raises an error that lists the
tags. wall_tags or symmetry_tags that are not on the mesh’s exterior facets (or a name the mesh does not have),
a tag that is both wall and symmetry, or wall_tags on a mesh without tags, also raise.
Tagging a mesh
For the tag option, the tags must mark the boundary facets, not only the cells:
gmsh: give the boundary curves (2D) or surfaces (3D) physical groups, and name them. Name the cell group too, or gmsh writes only the tagged entities. Write the mesh as
.msh(format 4.1).XDMF: write the facet
MeshTagsto the same file as the mesh, named"Facet tags":with dolfinx.io.XDMFFile(comm, "mesh.xdmf", "w") as xdmf: xdmf.write_mesh(mesh) facet_tags.name = "Facet tags" xdmf.write_meshtags(facet_tags, mesh.geometry)