Gyredocs
Depth Practice
API

Gyre/User Guide

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.

Written at L2 Practice goes to L4 TheoryCFD practitionersMDO integratorsagent builders 4574 words Markdown

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#

yamlcylinder_channel.yaml
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
bash
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.

pythoncylinder_channel.py
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 z=constz = \text{const}. 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.boundaries must 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 in flow.extra are listed under Cases.
console
$ 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#

bash
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
  • info prints the summary shown above; its last key, lines, is described below. --scale applies a factor to the coordinates before the mesh is measured.
  • convert reads IN (which validates it) and writes OUT as 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 .npz file is smaller than the Gmsh text file.
  • export writes 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. --set works as for gyre run. It prints the same as convert, 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:

console
$ 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_max below threshold: no cell qualifies, the mesh has no lines.
  • anisotropy_median above threshold: 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, so elements_in_lines can be smaller than the number of such cells, and anisotropy_max can exceed the threshold on a mesh without lines.
  • null instead 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.

bash
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]}}}'
python
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"])
text
→ 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 in mesh.params.path of a case (also when it arrives through overrides), is answered 422 InvalidMeshName, 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 InvalidMesh with 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, /jobs and /sessions answer 404 UnknownMesh (the hint names the PUT to make) if the mesh is not in the store, and 422 InvalidCase if discretization.boundaries does 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_TOKEN set, every mesh route, GET included, 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 (pg+1)d(p_g + 1)^d 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 pgp_g and shared between neighbours. Gmsh stores each element as a list of nodes in its own order, at equally spaced points.

Node ordering of a cubic quadrilateral in a Gmsh file and in Gyre Left: the sixteen nodes of a cubic Gmsh quadrilateral, equally spaced, numbered vertices first, then edge interiors counter-clockwise, then the interior. Right: the same element as a lattice indexed i, j with Legendre-Gauss-Lobatto spacing; each lattice position shows the Gmsh number it is read from. Gmsh element (file) equally spaced nodes, numbered 0 to 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Gyre lattice [i, j] Gauss–Lobatto nodes; the number is the Gmsh node read 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 permute, interpolate u v i j

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 pg−2p_g - 2, recursively. The reader places each node at its lattice position and moves the nodes from equal spacing to Gauss–Lobatto spacing.

  1. 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 pg−2p_g - 2 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).
  2. 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.
  3. 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.
  4. 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.
  5. Periodicity. The $Periodic node 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.
  6. 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 pg+1p_g + 1 equally spaced nodes on an edge and the pg+1p_g + 1 LGL nodes both determine the same polynomial of degree pgp_g, 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.

    ↑ ↓ moveEnter open[ ] change depthResults show section and depth level