io

io.writer

Export / generic-view free functions for a HexMesh.

nekmeshpy.io.writer.BCArg

a ready BoundaryConditions, a {name: code} table (each value a BCSpec) standing for its velocity field alone, or None.

Type:

What bc= accepts

alias of BoundaryConditions | Mapping[str, str | int | tuple[str | None, int | None] | Mapping[str, str | int | tuple[str | None, int | None] | None]] | None

nekmeshpy.io.writer.PeriodicArg

the specs, or a pairing already resolved by hexmesh.periodic_pairs.

Type:

What to_re2’s periodic= accepts

alias of Sequence[Periodic] | PeriodicPairs | None

nekmeshpy.io.writer.FluidArg

region name(s), a ready boolean mask, an array of element ids, or None – see core.selection.mask_for_selection, which resolves it.

Type:

What to_re2’s fluid= accepts

alias of str | Sequence[str] | NDArray[str_] | NDArray[bool] | NDArray[int64] | Sequence[int] | None

nekmeshpy.io.writer.to_mesh(mesh, bc=None)[source]

Return a shared-point Mesh: welded points, hexahedron cells, and one quad boundary cell per tagged face grouped into named cell_sets.

Parameters:
Return type:

Mesh

nekmeshpy.io.writer.to_meshio(mesh, bc=None)[source]

Return a meshio mesh view (requires meshio).

Parameters:
Return type:

Any

nekmeshpy.io.writer.write(mesh, path, file_format=None, *, bc=None)[source]

Write through meshio to any supported format; for native Nek use to_re2.

Parameters:
Return type:

str

nekmeshpy.io.writer.PERIODIC_CODE = 'P  '

The Nek BC code for a periodic face. Unlike every other code, it is not a statement the bc table can make on its own – a 'P' row also has to say which element and face it is periodic with, which is what periodic= supplies.

nekmeshpy.io.writer.to_re2(mesh, filename, *, bc, periodic=None, fluid=None)[source]

Write the binary Nek .re2 to filename (the full name, extension included – nothing is appended). The mesh is written linear at any order: Nek’s re2 has no high-order format, so only the 8 corners of each hex are emitted.

bc is required: this is the one writer that emits Nek BC codes, and a default would put a code the caller never chose in front of the solver. It is where a mesher states what its named surfaces physically are, so it belongs in the mesher, visible next to the tags it names. A BoundaryConditions carries one table per field; a plain {name: code} mapping is its velocity table alone.

periodic is a sequence of Periodic specs (or a ready PeriodicPairs), and is the only thing that fills the boundary record’s bc(1) / bc(2) fields – the partner element and face a 'P  ' row is meaningless without. It is stated separately from bc because it is not a code but a correspondence, and the two must agree: every name coded 'P  ' is named by a spec and vice versa, or this raises:

bc={"wall": "W  ", "inlet": "P  ", "outlet": "P  "},
periodic=[hexmesh.Periodic("inlet", "outlet",
                           affine.translation([0, 0, LENGTH]))]

fluid names the region(s) that are the velocity mesh – anything else (a different region, or untagged) is conduction-only. Nek’s header calls these counts nelgv (velocity) and nelgt (total); a conjugate run needs nelgv < nelgt and the velocity elements listed first, contiguously, which is a statement about order, not just a count – so fluid= reorders the written bytes rather than taking nelv= on trust. The mesh object this returns is unchanged; only the file’s element numbering (and every row that names an element) is permuted. fluid=None – the default – writes every element as velocity-mesh (nelgv == nelgt), which is today’s behaviour and the right one for a single-domain mesh.

Whenever fluid= carves out an actual solid region (nelgv < nelgt), Nek’s .re2 always carries one boundary block per solved field – read_re2_data reads at least two the moment nelgt > nelgv, regardless of what the header declares, so a file with only one is truncated from the reader’s point of view and ierr``s reading the (missing) second block. ``bc.temperature is that second field’s own name -> code table, same shape as bc.velocity, and is required exactly when fluid= makes the two counts differ. It may also be given on a mesh that is all fluid, where temperature is transported in the flow: the file then carries both blocks, each over every element. It covers every element, not just the fluid ones – a name omitted from it is left conformal ('E  ', ordinary continuity), which is normally right for a genuinely conjugate interface: velocity needs an explicit wall there because the solid side carries no velocity unknowns to continue into, but temperature is solved on both sides, so nothing needs stating:

writer.to_re2(mesh, "wire_coil.re2", periodic=PERIODIC, fluid="fluid",
              bc=BoundaryConditions(velocity=VEL_BC, temperature=TEMP_BC))
Parameters:
Return type:

HexMesh

nekmeshpy.io.writer.quad_to_re2(mesh, filename, *, bc, fluid=None)[source]

Write the binary 2-D Nek .re2 of a QuadMesh – to_re2() one rung down, with the same bc= / fluid= contract: bc is required, a BoundaryConditions carries a velocity and a temperature table, fluid names the velocity-mesh region and is written first, and a mesh with a solid region (nelgv < nelgt) needs bc.temperature for its second boundary block.

The mesh is written linear (4 corners a quad, x[4] y[4]) at any order, and must lie in a z = const plane: a 2-D .re2 carries no z. A side name is an edge name, and Nek’s face 1..4 is the quad’s own local edge order, so a row is (quad, edge) with no remapping.

There is no periodic= here: a periodic pairing is hex-only (to_re2()), so a name coded 'P  ' is refused rather than written with partner element 0.

Parameters:
Return type:

QuadMesh

nekmeshpy.io.writer.to_fld(mesh, filename, *, time=0.0, istep=0, wdsz=8, fluid=None)[source]

Write the binary Nek5000 field file (<prefix>0.f00001) to filename (the full name – nothing is appended).

fluid must name the same region as the matching to_re2() call’s own fluid=, and for the same reason: Nek reads a restart/IC field file element by element against the .re2 it already loaded, so the two files’ element numbering has to agree. to_re2’s fluid= reorders its written bytes fluid-first whenever it carves out a solid region (nelgv < nelgt); a to_fld written in the mesh’s own, unpermuted order would then describe element i of one file and element i of the other as two different pieces of geometry – not merely stale, but silently misassigned, since both files are the same length and neither one knows it disagrees with the other. fluid=None writes every element unpermuted, which agrees with an unpermuted to_re2 (fluid=None there too).

Parameters:
Return type:

HexMesh

nekmeshpy.io.writer.element_tag_ids(tags, n_elements)[source]

(per-element id (n_elements,), name per id) for a region table: 1-based positions in the sorted tag vocabulary, 0 where an element carries no tag.

A .vtu cannot hold the names themselves – VTK’s string arrays are per-file field data, not something a cell scalar can point into – so the array written is integer, exactly as bc_id already is. What keeps it readable is that the mapping is a function of the mesh alone: sorted(mesh.element_tags.unique()) reproduces it anywhere, with no legend to carry alongside the file.

Parameters:
Return type:

tuple[NDArray[int64], list[str]]

nekmeshpy.io.writer.to_vtu(mesh, fname, *, bc=None, binary=True, point_data=None, cell_data=None)[source]

Write an XML VTK unstructured grid (.vtu) of a HexMesh with per-point bc_id tags, and per-cell element_tag region ids where the mesh carries element_tags (see element_tag_ids() for the mapping). binary=False writes the arrays as text instead, which is readable and diffable but costs a Python format per row.

cell_data maps a name to a (E,) or (E, k) array and point_data to a (P,) or (P, k) array over the mesh’s points (diagnostics: quality, displacement), each point value repeated on its corner copies; point_data is order 1 only.

Parameters:
Return type:

HexMesh

nekmeshpy.io.writer.line_to_vtu(mesh, fname, *, binary=True)[source]

Write an XML VTK unstructured grid (.vtu) of a LineMesh, un-welded (one node block per line element), with per-cell element_tag region ids where the mesh carries element_tags.

Parameters:
Return type:

LineMesh

nekmeshpy.io.writer.quad_to_vtu(mesh, fname, *, binary=True, point_data=None, cell_data=None)[source]

Write an XML VTK unstructured grid (.vtu) of a QuadMesh, un-welded (one node block per quad), with per-cell element_tag region ids where the mesh carries element_tags.

cell_data maps a name to a (Q,) or (Q, k) array and point_data to a (P,) or (P, k) array over the mesh’s points (diagnostics: quality, constraint kind, displacement); a point value is repeated on each corner copy of its vertex. point_data is order 1 only – a high-order mesh has more nodes than points.

Parameters:
Return type:

QuadMesh

nekmeshpy.io.writer.boundary_to_vtp(mesh, fname, *, tag=None, binary=True)[source]

Write mesh’s named faces as a lightweight VTK PolyData (.vtp) and a per-poly bc_id.

With no tag, every face mesh.face_tags names is exported (via quadmesh.select on mesh.quad_mesh) – not hexmesh.boundary_mesh’s topological boundary, which drops interior planes on purpose (a T-junction’s flux plane, say) even though they carry a name. A mesh with no named faces at all falls back to the topological boundary, untagged, so an unlabelled block still exports something. Passing an explicit tag still selects one named group by way of boundary_mesh (unchanged).

Always written corners only, regardless of mesh’s order – .vtp’s <Polys> has no curved-cell type (a VTK PolyData polygon is always flat), and a viewer only needs the outline, not curved-node fidelity. Interior volume nodes are dropped either way – a viewer only shows the surface.

When the surface carries names, a sidecar <fname minus .vtp>.groups.json ({bc_id: name}) is written alongside it – .vtp’s own field-data support is a poor fit for strings, and a viewer needs the names to label a per-group visibility toggle (e.g. hiding a far-field box to see the body it encloses).

Parameters:
Return type:

QuadMesh

io.reader

Readers for Nek5000’s mesh formats: the ASCII .rea and the binary .re2, each at the hex rung (3-D) and the quad rung (2-D) – read_rea / quad_read_rea, read_re2 / quad_read_re2, the readers’ side of writer.to_re2 / writer.quad_to_re2.

Only the geometry, the element regions and the boundary conditions are read – the surrounding parameters, thermal, restart, and drive-force sections either don’t bear on a mesh’s shape or (curved sides) aren’t supported yet and are refused rather than silently dropped. A mesh with nelv < nel is a conjugate one: its first nelv elements are the "fluid" region and the rest "solid". A boundary row’s raw Nek code is its tag name, and where a file carries a second (thermal) block a side the velocity block does not name takes the thermal block’s code; a periodic row is read as the name "P" with its partner dropped. The row layout below (element/face packed into one field once NELGT reaches four digits, corners grouped X,Y,Z per half rather than per axis) follows Nek5000’s own core/reader_rea.f (rdmesh/rdbdry) exactly – verified against its upstream source, not reverse-engineered from one file.

exception nekmeshpy.io.reader.ReaFormatError[source]

Bases: ValueError

The .rea file doesn’t match the section this reader expected next.

nekmeshpy.io.reader.quad_read_re2(path)[source]

Read a binary 2-D Nek .re2 into a linear QuadMesh – read_re2() one rung down, and the reader’s side of writer.quad_to_re2.

Parameters:

path (str)

Return type:

QuadMesh

nekmeshpy.io.reader.quad_read_rea(path)[source]

Read a 2-D Nek5000 .rea mesh into a linear (order-1) QuadMesh in the z = 0 plane – read_rea() one rung down, welded through the file’s own "E" rows the same way, with named rows carried over as edge tags (quadmesh.tag_edges). Raises ReaFormatError for a 3-D run or a nonzero curved-side count.

Parameters:

path (str)

Return type:

QuadMesh

nekmeshpy.io.reader.read_re2(path)[source]

Read a binary 3-D Nek .re2 into a linear HexMesh – the reader’s side of writer.to_re2.

A .re2 stores each element’s corners independently and, unlike a .rea, carries no element-to-element rows, so coincident corners are welded by coordinate at 1e-7 of the model size (exact doubles, so this is a rounding radius, not a tolerance to tune). Names, regions and the second block follow the module rules.

Parameters:

path (str)

Return type:

HexMesh

nekmeshpy.io.reader.read_rea(path)[source]

Read a 3-D Nek5000 .rea mesh into a linear (order-1) HexMesh.

The ASCII format stores each element’s 8 corners independently – there is no shared point numbering to read directly, and floating-point round-trip through 7-significant-digit text makes a coincidence-tolerance weld genuinely fragile (on the reference file this reader was built against, no single tolerance across four orders of magnitude was both watertight and conformal). Instead this reader uses the file’s own "E" (element-to-element) boundary rows – which name the exact connected element and face, not a inferred one – and matches each connected face pair’s 4 corners by nearest distance within that pair alone, which needs no tolerance because the search space is 4 points against 4, not the whole mesh.

Named rows ("W", "v", "O", …) are carried over as face tags via hexmesh.tag_faces, keyed by the raw Nek code – this reader does not rename them. nelv < nel makes the first nelv hexes "fluid" and the rest "solid".

Raises ReaFormatError for anything outside that scope: a 2-D mesh (see quad_read_rea()), or a nonzero curved-side count (this reader reconstructs straight-sided hexes only, and silently dropping curvature would hand back the wrong geometry rather than an approximate one).

Parameters:

path (str)

Return type:

HexMesh