Mesh files
In one minuteA case can take its mesh from a file instead of a built-in generator: Gmsh MSH 2.2 or 4.1 (ASCII) with quadrilaterals or hexahedra, or Gyre's own NPZ arrays. This page shows how to produce such a file, how its boundary groups become boundary conditions, what the reader checks, and how a mesh reaches a service.
The built-in generators cover aerofoil O-meshes, cylinders, bump channels and boxes (Cases). For any other geometry the mesh comes from a file, written by Gmsh or by your own program. A mesh read from a file becomes the same object a generator builds, so everything else — solver, outputs, gradients, error estimates, sessions — works unchanged.
Mesh the domain with quadrilaterals (2-D) or hexahedra (3-D) in Gmsh, give every
boundary a named physical group, save as MSH 4.1 or 2.2 ASCII, and write a case with
mesh: {generator: file, params: {path: my.msh}} and one boundary condition per group.
gyre mesh info my.msh tells you whether the file is usable and lists its groups.
A case on a mesh file#
name: cylinder_channel
mesh:
generator: file
params: {path: examples/meshes/cylinder_channel_p3.msh} # optional: scale: 0.001
flow: {mach: 0.2, reynolds: 40.0}
discretization:
order: 2
boundaries: # one entry per boundary group of the mesh
cylinder: noslip_adiabatic
inflow: farfield
outflow: farfield
walls: slip_wall
reference:
moment_center: [0.0, 0.0] # moment about the cylinder axis
gyre run cylinder_channel.yaml -o runs/channel
mesh.params |
Meaning |
|---|---|
path |
the mesh file; the extension selects the reader: .msh (Gmsh) or .npz (Gyre arrays) |
scale |
positive factor applied to all coordinates (default 1), for a mesh drawn in other units than the reference length |
Any other key in mesh.params is a validation error. mesh.geometry_order has no effect on a
mesh file: the file carries its own geometry degree.
With the CLI and in Python, path is used as given (a relative path is relative to the
working directory). If a relative path does not exist and the environment variable
GYRE_MESH_DIR names a directory, the file is looked up there. With GYRE_MESH_RESTRICT=1
only bare file names inside GYRE_MESH_DIR are accepted; a service sets both for itself (see
Meshes in a service).
The case digest covers the contents of the file, not its name: editing the file changes the digest, renaming it does not.
Making a mesh with Gmsh#
Gmsh is not a dependency of Gyre; any installation that writes MSH files will do. The script
below uses Gmsh’s Python API (pip install gmsh) and is the construction that produced
examples/meshes/cylinder_channel_p3.msh (the repository regenerates it with
tools/make_mesh_fixtures.py): a 5 × 3 channel with a circular hole of radius 0.5, meshed with
unstructured cubic quadrilaterals.
import gmsh
gmsh.initialize()
gmsh.model.add("plate")
# Geometry: rectangle [-2, 3] x [-1.5, 1.5] minus a disk of radius 0.5 at the origin.
occ = gmsh.model.occ
rect = occ.addRectangle(-2.0, -1.5, 0.0, 5.0, 3.0)
disk = occ.addDisk(0.0, 0.0, 0.0, 0.5, 0.5)
occ.cut([(2, rect)], [(2, disk)])
occ.synchronize()
# A named physical group on every boundary curve, sorted by position.
def side(box):
x0, y0, _, x1, y1, _ = box
if abs(x0 + 2.0) < 1e-6 and abs(x1 + 2.0) < 1e-6:
return "inflow"
if abs(x0 - 3.0) < 1e-6 and abs(x1 - 3.0) < 1e-6:
return "outflow"
if abs(y0 - y1) < 1e-6 and abs(abs(y0) - 1.5) < 1e-6:
return "walls"
return "cylinder"
groups = {}
for _, tag in gmsh.model.getEntities(1):
groups.setdefault(side(gmsh.model.getBoundingBox(1, tag)), []).append(tag)
for name, tags in sorted(groups.items()):
gmsh.model.addPhysicalGroup(1, tags, name=name)
gmsh.model.addPhysicalGroup(2, [t for _, t in gmsh.model.getEntities(2)], name="fluid")
# Quadrilaterals only, complete Lagrange elements.
gmsh.option.setNumber("Mesh.RecombineAll", 1)
gmsh.option.setNumber("Mesh.Algorithm", 8) # frontal-Delaunay for quads
gmsh.option.setNumber("Mesh.RecombinationAlgorithm", 1) # blossom
gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", 1) # split every element into quads
gmsh.option.setNumber("Mesh.SecondOrderIncomplete", 0)
gmsh.option.setNumber("Mesh.MeshSizeMin", 0.6)
gmsh.option.setNumber("Mesh.MeshSizeMax", 0.6)
gmsh.model.mesh.generate(2)
gmsh.model.mesh.setOrder(3) # cubic elements
# MSH 4.1, ASCII; only elements that belong to a physical group are written.
gmsh.option.setNumber("Mesh.SaveAll", 0)
gmsh.option.setNumber("Mesh.MshFileVersion", 4.1)
gmsh.option.setNumber("Mesh.Binary", 0)
gmsh.write("cylinder_channel_p3.msh")
gmsh.finalize()
In a .geo script the same settings are the options of the same names
(Mesh.RecombineAll = 1;, Mesh.ElementOrder = 3;, Mesh.MshFileVersion = 4.1;) and the
groups are Physical Curve("walls") = {…}; and Physical Surface("fluid") = {…};.
| Supported | Gmsh element types | |
|---|---|---|
| File format | MSH 2.2 and 4.1, ASCII | — |
| 2-D elements | quadrilaterals of degree 1 to 5 | 3, 10, 36, 37, 38 |
| 3-D elements | hexahedra of degree 1 to 4 | 5, 12, 92, 93 |
| Boundary elements | lines of degree 1 to 5 (2-D), quadrilaterals (3-D), in physical groups | 1, 8, 26, 27, 28 |
| Points | accepted and ignored | 15 |
All elements are complete Lagrange elements (Mesh.SecondOrderIncomplete = 0) and all volume
elements of a mesh have the same degree. Triangles, tetrahedra, prisms, pyramids and
serendipity elements are refused. A 2-D mesh must lie in a plane . Binary MSH
files and other format versions are refused; save again with -format msh41 or
-format msh22.
The Gyre NPZ format has no upper limit on the geometry degree; see its arrays below.
Boundary groups and conditions#
Each physical group of boundary curves (2-D) or boundary surfaces (3-D) becomes a
boundary group of the mesh, under the group’s name (group<tag> if the group has no name).
The physical group of the elements themselves (fluid above) is not used.
- Every boundary face must belong to a physical group. A file with untagged boundary faces is refused.
- A face in several physical groups gets the one with the smallest tag. Physical groups on interior curves or surfaces are ignored.
- A mesh file has no default boundary conditions.
discretization.boundariesmust give a condition for every group, and must not name a group the mesh does not have. The available conditions and the data some of them need inflow.extraare listed under Cases.
$ gyre mesh info examples/meshes/cylinder_channel_p3.msh
{
"dim": 2,
"elements": 284,
"geometry_order": 3,
"nodes": 2676,
"boundaries": {
"cylinder": 16,
"inflow": 12,
"outflow": 12,
"walls": 40
},
…
}
The output is shortened here: the quality numbers, the bounding box, sha256, bytes and
lines (the lines of the line smoother on this mesh) follow.
Reference quantities#
The generators know their geometry; a mesh file does not say what its reference length is. The
reference section of the case sets the quantities the coefficients are normalised with:
| Field | Default | Meaning |
|---|---|---|
area |
1 | reference area of the force coefficients (a length in 2-D) |
length |
1 | reference length of the moment coefficient |
moment_center |
[0.25, 0.0, 0.0] |
point the moment is taken about (two or three coordinates) |
The flow conditions are non-dimensional with a unit reference length (flow.reynolds is the
Reynolds number per unit length), so either draw the mesh with the reference length equal to
one or bring it there with mesh.params.scale.
Periodic meshes#
Periodic node pairs of the file ($Periodic, written by Gmsh for curves or surfaces declared
periodic, for example with gmsh.model.mesh.setPeriodic) identify the two sides: the faces on
them become interior faces, and a physical group on a periodic side no longer appears among the
boundary groups, so it needs no boundary condition. periodic_nodes in the summary counts the
identified nodes. A periodic direction needs at least three elements across it.
What the reader checks#
A file is read and validated in full before it is used; nothing is repaired silently. Each
refusal is a message that says what is wrong. gyre mesh info FILE shows it on the command
line, the service returns it as the message of a 422 InvalidMesh.
| Message (beginning) | Cause | Remedy |
|---|---|---|
mesh file '…' does not exist |
wrong path, or the file is not in GYRE_MESH_DIR |
correct mesh.params.path |
unknown mesh file type '.xyz' |
extension is neither .msh nor .npz |
export as MSH, or convert |
not a Gmsh MSH file (no $MeshFormat header) |
the file is something else | export from Gmsh as MSH |
binary MSH files are not supported |
written with -bin or Mesh.Binary = 1 |
Mesh.Binary = 0 |
MSH format version … is not supported |
for example 4.0 or 1.0 | -format msh41 or -format msh22 |
the mesh contains triangles (tetrahedra, prisms, pyramids, serendipity …) |
not an all-quad / all-hex mesh of complete elements | recombined or transfinite meshing, Mesh.SecondOrderIncomplete = 0 |
the mesh contains no quadrilaterals or hexahedra |
only points or lines were saved | add a physical group for the surface or volume, or Mesh.SaveAll = 1 |
elements of several degrees in one mesh |
mixed element orders | one Mesh.ElementOrder for the whole mesh |
N boundary faces belong to no physical group (the first one is near (x, y)) |
a boundary without a physical group; for MSH 4.1 also a file without $Entities |
add Physical Curve / Physical Surface for that boundary; export again |
two physical groups share a name |
duplicate names | rename one |
a 2-D mesh must lie in a plane z = const |
a surface mesh in space | mesh in the x-y plane |
N of K elements have a non-positive Jacobian determinant (element k near …) |
inverted element or tangled curved edges | Mesh.HighOrderOptimize = 2, or refine near strongly curved boundaries |
$Nodes holds coordinates that are not finite numbers (MSH), N nodes have coordinates that are not finite numbers (NPZ) |
NaN or infinity in the file | regenerate the mesh |
a face is shared by more than two elements |
non-manifold mesh, or fewer than three elements across a periodic direction | repair the topology; refine the periodic direction |
periodic identification maps two nodes of one element onto each other, an element edge joins a vertex to itself |
fewer than three elements across a periodic direction, or a collapsed element | refine; remove degenerate elements |
unmatched interior faces, a face is both interior and tagged as boundary |
inconsistent connectivity (usually an NPZ file) | correct elem_nodes / face_bc |
the file ends inside section $…, the file declares N … but holds at most M, $Nodes declares …, malformed MSH … file: … |
truncated or damaged file | export again |
the group name '…' cannot be written to an MSH file |
(writing) a name with a double quote or a line break | rename the group |
no boundary condition for groups […] |
the case omits a group | add it to discretization.boundaries |
discretization.boundaries names boundary groups […] that the mesh does not have |
misspelt or stale group name | use the names gyre mesh info lists |
A damaged file is never read as a smaller or a non-periodic mesh: counts declared in the file are checked against its contents, and a section that is cut off or has lost its opening line is an error.
The summary reports quality numbers for a mesh that passed:
| Key | Meaning |
|---|---|
min_scaled_jacobian |
smallest ratio of the minimum to the maximum Jacobian determinant within one element (1 for a parallelogram; small values mean strongly distorted elements) |
interior_faces, boundary_faces |
number of interior face pairs and of boundary faces |
reoriented_elements |
left-handed elements of the file that were turned right-handed |
reoriented_face_pairs |
interior face pairs whose two elements use different local axes on the face (zero for a structured mesh) |
periodic_nodes |
nodes identified with a periodic master |
bounding_box |
[[xmin, ymin(, zmin)], [xmax, ymax(, zmax)]] after scaling |
sha256, bytes |
hash and size of the file; the hash is what the case digest uses |
Command-line tools#
gyre mesh info FILE [--scale S] # read, validate, print the summary as JSON
gyre mesh convert IN OUT # .msh <-> .npz, chosen by the extension of OUT
gyre mesh export CASE OUT [--set PATH=VALUE ...] # write the mesh of a preset or case file
infoprints the summary shown above; its last key,lines, is described below.--scaleapplies a factor to the coordinates before the mesh is measured.convertreadsIN(which validates it) and writesOUTas Gmsh MSH 2.2 ASCII (.msh) or as Gyre NPZ (.npz). It prints{"written": OUT, …}followed by the summary of the written file, read back. An.npzfile is smaller than the Gmsh text file.exportwrites the mesh of a preset or case file, for example to look at a generator mesh in Gmsh:gyre mesh export cylinder_re40 annulus.msh.--setworks as forgyre run. It prints the same asconvert, so it is also the way to the summary of a generator mesh.
Any other extension of OUT is refused before anything is read. A command that cannot use a
file or case prints gyre mesh <action>: <message> to standard error, prints nothing to
standard output and exits with status 1.
The writer produces $Periodic node pairs for periodic meshes, names the element group
fluid, and writes degrees for which Gmsh has an element type (quadrilaterals up to 5,
hexahedra up to 4); a generator mesh of a higher degree can be exported as .npz only.
Will the line smoother act on this mesh?#
solver.smoother: line solves exactly along lines of thin cells and is block Jacobi wherever
a mesh has none
(how the lines are formed).
The lines depend on the mesh alone, so the three commands report them, under lines, before
anything is solved. For the mesh of the preset flat_plate_sa:
$ gyre mesh export flat_plate_sa plate.npz
{
"written": "plate.npz",
"dim": 2,
"elements": 150,
…
"lines": {
"lines": 18,
"elements_in_lines": 131,
"elements": 150,
"longest": 12,
"colors": 3,
"slots": 152,
"threshold": 4.0,
"anisotropy_max": 50679888.014390774,
"anisotropy_median": 463.07670555770085
}
}
Eighteen lines hold 131 of the 150 elements; the other 19 are smoothed by block Jacobi. For
examples/meshes/cylinder_channel_p3.msh, gyre mesh info prints "lines": 0 with an
anisotropy_max of 2.12 and an anisotropy_median of 1.11: no cell reaches the threshold of
4, and smoother: line would change nothing on that mesh. Both outputs were recorded on the
CPU of the development machine with the working tree that followed revision 15e5de9 (the
change that added slots); … marks omitted keys and the two numbers of the channel mesh
are rounded.
Key of lines |
Meaning |
|---|---|
lines |
number of lines; 0 for a mesh without lines |
elements_in_lines, elements |
elements that belong to a line, and all elements of the mesh |
longest |
number of elements of the longest line |
colors |
colours the smoother needs to assemble the blocks that couple the elements of a line (three for aligned lines); 0 without lines |
slots |
line slots the smoother stores: the lines are kept in a few tables by length, each as long as its longest line, so this exceeds elements_in_lines by the padding; 0 without lines |
threshold |
anisotropy from which a cell qualifies for a line. It is 4, the value the solver uses: a box-shaped cell at least twice as wide in every other direction as it is thick |
anisotropy_max, anisotropy_median |
largest and median anisotropy of the cells |
The anisotropy of a cell is the number the threshold is compared with: the coupling of the cell with its neighbours in its strongest direction divided by that in its second strongest, where the coupling across a face is the face area divided by the distance of the two cell centres. For a box-shaped cell it is the square of the ratio of its two shortest edges: the square of the aspect ratio in two dimensions, and in three dimensions only when the two longer edges are equal.
anisotropy_maxbelowthreshold: no cell qualifies, the mesh has no lines.anisotropy_medianabovethreshold: at least half of the cells qualify. A cell that qualifies is in a line only if a neighbour across its strongest direction qualifies through the same face, soelements_in_linescan be smaller than the number of such cells, andanisotropy_maxcan exceed the threshold on a mesh without lines.nullinstead of a number: the value is not finite. A cell with neighbours in one direction only (a mesh one element wide) has no second direction; it qualifies whatever its shape. A mesh of a single cell has no neighbours to measure.
A run with solver.smoother: line records the first five keys as case.lines of its result
(null if the mesh has no lines or another smoother is used). In Python,
meshio.inspect_mesh(path, lines=True) returns the summary with lines, and
meshio.line_summary(mesh) the lines of a mesh object. The summary a service keeps for a
stored mesh, and inspect_mesh without the argument, do not contain lines.
Meshes in a service#
A service never opens a path from a request. It keeps a mesh store (the directory
meshes of its data directory): a client uploads a file under a name and then refers to it by
that name in mesh.params.path. Below, API is http://HOST:8600/api/v1.
curl -s -X PUT --data-binary @cylinder_channel_p3.msh \
-H 'content-type: application/octet-stream' $API/meshes/cylinder_channel_p3.msh
# → 201 {"name": "cylinder_channel_p3.msh", "bytes": …, "sha256": "…", "dim": 2, "elements": 284,
# "boundaries": {"cylinder": 16, "inflow": 12, "outflow": 12, "walls": 40}, …}
curl -s $API/meshes # {"meshes": [summary, …]}
curl -s $API/meshes/cylinder_channel_p3.msh # one summary
curl -s -X DELETE $API/meshes/cylinder_channel_p3.msh
curl -s $API/jobs -H 'content-type: application/json' -d '{
"case": {"name": "cylinder_channel",
"mesh": {"generator": "file", "params": {"path": "cylinder_channel_p3.msh"}},
"flow": {"mach": 0.2, "reynolds": 40.0},
"discretization": {"order": 2, "boundaries": {
"cylinder": "noslip_adiabatic", "inflow": "farfield",
"outflow": "farfield", "walls": "slip_wall"}},
"reference": {"moment_center": [0.0, 0.0]}}}'
from gyre.client import GyreClient
gyre = GyreClient("http://HOST:8600")
info = gyre.upload_mesh("cylinder_channel_p3.msh") # name defaults to the file name
print(info["boundaries"]) # faces per boundary group
gyre.meshes() # list of summaries
gyre.mesh(info["name"]) # one summary
case = {"name": "cylinder_channel",
"mesh": {"generator": "file", "params": {"path": info["name"]}},
"flow": {"mach": 0.2, "reynolds": 40.0},
"discretization": {"order": 2, "boundaries": {
"cylinder": "noslip_adiabatic", "inflow": "farfield",
"outflow": "farfield", "walls": "slip_wall"}},
"reference": {"moment_center": [0.0, 0.0]}}
result = gyre.run(case)
gyre.delete_mesh(info["name"])
→ upload_mesh(name="cylinder_channel_p3.msh", content="$MeshFormat\n4.1 0 8\n…")
← {"name": "cylinder_channel_p3.msh", "elements": 284, "geometry_order": 3,
"boundaries": {"cylinder": 16, "inflow": 12, "outflow": 12, "walls": 40}, …}
→ validate_case(case={"name": "cylinder_channel",
"mesh": {"generator": "file", "params": {"path": "cylinder_channel_p3.msh"}},
"flow": {"mach": 0.2, "reynolds": 40.0},
"discretization": {"order": 2, "boundaries": {…one per group…}}})
→ list_meshes() → inspect_mesh(name) → delete_mesh(name)
An .npz file is sent as base64 with encoding="base64".
- Names, never paths. A name matches
^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}\.(msh|npz)$(lower-case extension, no directories). Any other name, in the URL or inmesh.params.pathof a case (also when it arrives throughoverrides), is answered 422InvalidMeshName, and nothing is opened or written. - Validated on upload. The request body is the file. It is read by the same reader as on
the command line before it enters the store; a file the reader refuses is answered 422
InvalidMeshwith the reader’s message and is not stored. A mesh already stored under that name stays as it was. A valid upload replaces an existing mesh of the same name. - A case names a stored mesh.
POST /cases/validate,/jobsand/sessionsanswer 404UnknownMesh(the hint names thePUTto make) if the mesh is not in the store, and 422InvalidCaseifdiscretization.boundariesdoes not match the boundary groups of the mesh. - Replacing a mesh. The result cache of jobs is keyed on the contents of the mesh file, so a job on a replaced mesh is computed anew. A session reads its mesh when it starts and keeps running on it, and reports the digest of that moment, if the mesh is replaced or deleted afterwards. A job that is still queued when its mesh is deleted fails.
- Access. With
GYRE_API_TOKENset, every mesh route,GETincluded, requires the bearer token, like the rest of the API.
| Limit | Default | Variable | Answer when exceeded |
|---|---|---|---|
| size of one upload | 64 MiB | GYRE_MESH_MAX_BYTES |
413 MeshTooLarge |
uncompressed size of the arrays of an .npz upload |
the smaller of 4 × the upload limit and 1 GiB | GYRE_MESH_MAX_BYTES, GYRE_MESH_MAX_EXPANDED_BYTES |
422 InvalidMesh |
| number of meshes in the store | 256 | GYRE_MESH_STORE_MAX_FILES |
507 MeshStoreFull |
| total size of the store | 4 GiB | GYRE_MESH_STORE_MAX_BYTES |
507 MeshStoreFull |
The variables belong to the service’s environment and are read at each request. A mesh that replaces one of the same name does not count against the number of meshes.
An .npz upload must pass two limits on the uncompressed size of its arrays: four times
GYRE_MESH_MAX_BYTES, which the service checks, and GYRE_MESH_MAX_EXPANDED_BYTES (1 GiB by
default), which the reader checks in every process, the service included. With an upload limit
above 256 MiB the second one is the smaller unless it is raised as well.
All four values are positive integers in bytes (a count for GYRE_MESH_STORE_MAX_FILES). For
GYRE_MESH_MAX_BYTES, GYRE_MESH_STORE_MAX_FILES and GYRE_MESH_STORE_MAX_BYTES any other
value, zero included, is replaced by the default, so 0 does not switch uploads off. An invalid
GYRE_MESH_MAX_EXPANDED_BYTES is an error instead: every read of an .npz file fails with
GYRE_MESH_MAX_EXPANDED_BYTES must be a positive integer, got '…' (a 422 InvalidMesh for an
upload).
The store is <data_dir>/meshes. While the service runs it sets GYRE_MESH_DIR to that
directory and GYRE_MESH_RESTRICT=1 in its own environment; job workers, session processes
and pipeline runs inherit both, and the previous values are restored at shutdown. With the
restriction the reader accepts only a bare file name inside that directory, so the check holds
in every process that could read a mesh, not only in the request handler. The request handler
checks the name against the pattern before it touches the file system, and error messages name
the mesh as the client named it, never a server directory.
An upload is received into a temporary file of the store’s directory, with the size limit
enforced on the Content-Length header and again while the body arrives. It is validated
there, one upload at a time, and moved to its name only if it is accepted. Leftovers of an
upload that was interrupted by a crash are removed at the next start.
An application object that is used without its start-up phase has not set the two variables;
it answers a case that names a stored mesh with 503 MeshStoreInactive instead of resolving
it. The name check and the lookup in the store come first, so a name that is not a store name
is still a 422 InvalidMeshName and a mesh that is not stored a 404 UnknownMesh.
How a Gmsh mesh becomes a Gyre mesh#
A Gyre mesh stores, per element, the numbers of its geometry nodes on a lattice
indexed [i, j(, l)] along the reference directions, with the nodes at the
Legendre–Gauss–Lobatto (LGL) points of degree and shared between neighbours. Gmsh stores
each element as a list of nodes in its own order, at equally spaced points.
A cubic quadrilateral. Gmsh numbers the four vertices counter-clockwise, then the interior nodes of the four edges in the same sense, then the interior as a quadrilateral of degree , recursively. The reader places each node at its lattice position and moves the nodes from equal spacing to Gauss–Lobatto spacing.
- Ordering. A table maps the Gmsh node order of each element type to lattice positions.
For a hexahedron the order is vertices, edge interiors, face interiors (each as a
quadrilateral of degree in the frame of the face’s vertices) and the volume
interior, recursively. An element whose first two lattice directions (and the third, in
3-D) form a left-handed frame has its first two directions exchanged
(
reoriented_elements). - Node distribution. Along every lattice direction the equally spaced nodes are interpolated to the LGL points of the same degree. Up to degree 2 the two sets coincide and nothing is done.
- Topological numbering. Lattice nodes are numbered from the mesh topology, not from their coordinates: a vertex by its node in the file; an edge node by the two vertices of its edge and its position counted from the vertex with the smaller number; a face node (3-D) by the four vertices of its face and its position in a frame fixed by the smallest vertex; interior nodes are private to their element. Two elements that share an edge or a face therefore share its nodes exactly, whatever their local axes, and nodes that merely coincide in space — the two sides of a slit, a periodic pair — stay distinct.
- Boundary faces. A face that belongs to one element only is a boundary face and takes the physical group of the boundary element with the same vertices. An element listed several times (MSH 2.2 repeats it per physical group) is used once.
- Periodicity. The
$Periodicnode pairs are treated as an equivalence of nodes, so that a corner node that is the slave of two periodic curves is identified consistently. Each node gets a periodic master; faces are matched through the masters. - Validation. All coordinates must be finite and the Jacobian determinant positive at every geometry node of every element; then faces are matched and each interior pair gets its relative orientation.
In an unstructured mesh two neighbours generally disagree about the direction, and in 3-D
about the order, of the axes on their common face. The connectivity records for each interior
face which index symmetry (reversal of either face axis, exchange of the two) maps its nodes
onto the neighbour’s; the spatial operator applies it when it reads the neighbour’s trace.
reoriented_face_pairs counts the pairs for which that map is not the identity. The
verification study on imported meshes tests free-stream
preservation, conservation and neighbour coupling on such meshes.
The equally spaced nodes on an edge and the LGL nodes both determine the same polynomial of degree , so evaluating the Lagrange interpolant of the first set at the second changes the representation and not the curve: the geometry of the file is reproduced exactly, up to round-off. The interpolation acts along lattice lines, and on an edge or face it uses only the nodes of that edge or face. Two neighbours thus compute the same coordinates for a shared node; because the node is stored once, under its topological number, the mesh is watertight by construction, with no tolerance for merging nodes by distance.
The NPZ format#
An .npz file holds the arrays of a Gyre mesh. gyre mesh convert and
gyre.meshio.save_mesh write it; a program that builds meshes can write it directly with
numpy.savez.
| Array | Shape | Content |
|---|---|---|
format |
scalar string | gyre-mesh-1 |
dim |
scalar integer | 2 or 3 |
pg |
scalar integer | geometry degree, at least 1 |
nodes |
(Nn, dim) float |
coordinates of the unique geometry nodes |
elem_nodes |
(K, pg+1, pg+1[, pg+1]) integer |
node numbers on each element’s lattice, indexed [k, i, j(, l)] along the reference directions; the nodes of an element are LGL-distributed in its reference element and shared with its neighbours |
face_bc |
(K, 2*dim) integer |
boundary-group index of each local face, −1 for interior faces; local face 2*a + s is normal to reference direction a, at its lower (s = 0) or upper (s = 1) end |
bc_names |
(G,) strings |
names of the boundary groups, indexed by face_bc |
topo |
(Nn,) integer, optional |
periodic master of each node; a master is its own master (topo[topo] == topo); absent for a mesh without periodicity |
The reader checks shapes, integer types, index ranges and unique group names, and then validates the mesh like a Gmsh mesh (finite coordinates, positive Jacobians, face matching). Elements must be right-handed and neighbours must share node numbers: the NPZ reader does not re-orient or renumber.
The archive is measured before any array is read: every member must be an .npy array
without Python objects whose header describes exactly the bytes stored for it, and the
members together must stay within the size limit (see Limits).
See the imported-mesh example for the channel case with results, and the REST and MCP references for the routes and tools.