tetmesh

tetmesh

Linear tetrahedral volume-mesh container (TetMesh) and its ops.

Deliberately thin, mirroring trimesh: a tet mesh here exists to solve a field or walk a volume, not to be exported – a mesher’s actual output is the hex mesh built around what the solve decided. gmsh (tetmesh.ops.tet_mesh) is an optional extra, imported only inside that one function.

class nekmeshpy.tetmesh.TetMesh[source]

Bases: object

A linear tetrahedral volume mesh: points (P,3) and tets (T,4) connectivity.

Deliberately linear only – unlike the point/line/quad/hex ladder, a TetMesh carries no order, no interior nodes and no tags. It exists to solve a field or walk a volume (tetmesh.ops), not to be exported: a mesher’s actual output is the hex mesh built around what the solve decided, never the tets themselves.

FACE_POINTS = array([[0, 1, 2],        [0, 1, 3],        [0, 2, 3],        [1, 2, 3]])
__init__(points, tets)[source]
Parameters:
  • points (NDArray[float64])

  • tets (NDArray[int64])

Return type:

None

property n_points: int

Number of points.

property n_tets: int

Number of tetrahedra.

boundary_faces()[source]

(F,3) unique triangle faces borne by exactly one tet – the open outer boundary.

Return type:

NDArray[int64]

boundary_mesh()[source]

The outer boundary as a TriMesh, over this mesh’s own points (not renumbered – a face id here is a face id there).

Return type:

TriMesh

tetmesh.tetmesh

Linear tetrahedral volume-mesh container (points + tets, both 0-based).

class nekmeshpy.tetmesh.tetmesh.TetMesh[source]

Bases: object

A linear tetrahedral volume mesh: points (P,3) and tets (T,4) connectivity.

Deliberately linear only – unlike the point/line/quad/hex ladder, a TetMesh carries no order, no interior nodes and no tags. It exists to solve a field or walk a volume (tetmesh.ops), not to be exported: a mesher’s actual output is the hex mesh built around what the solve decided, never the tets themselves.

FACE_POINTS = array([[0, 1, 2],        [0, 1, 3],        [0, 2, 3],        [1, 2, 3]])
__init__(points, tets)[source]
Parameters:
  • points (NDArray[float64])

  • tets (NDArray[int64])

Return type:

None

points: NDArray[float64]
tets: NDArray[int64]
property n_points: int

Number of points.

property n_tets: int

Number of tetrahedra.

boundary_faces()[source]

(F,3) unique triangle faces borne by exactly one tet – the open outer boundary.

Return type:

NDArray[int64]

boundary_mesh()[source]

The outer boundary as a TriMesh, over this mesh’s own points (not renumbered – a face id here is a face id there).

Return type:

TriMesh

tetmesh.ops

Volumetric algorithms on a TetMesh as free functions. All indices 0-based.

The gmsh dependency lives entirely inside tet_mesh(), imported at call time – mirrors the toolkit-wide rule that gmsh is an optional extra (pip install .[mesh]), never a hard import of any module here.

nekmeshpy.tetmesh.ops.cap_surface(surf, loops)[source]

Close an open surface by fan-triangulating each boundary loop loops (point ids into surf) from its centroid. Returns (capped, cap_node_sets); the wall vertices keep their indices.

Parameters:
Return type:

tuple[TriMesh, list[NDArray[int64]]]

nekmeshpy.tetmesh.ops.tet_mesh(capped, near, far, ramp, centre)[source]

Tet-mesh the closed surface capped with gmsh, keeping its nodes in place.

Element size is graded by a background field: near at centre, growing to far over ramp.

gmsh cannot refine a discrete surface, so the triangles handed in set the floor on the size and shape of every tet at the wall – the surface has to be good before it gets here. Reparametrizing it (classifySurfaces + createGeometry) so gmsh could remesh it does not work on a smooth closed tube with no sharp edges to split on: it comes out either as one unparametrizable patch or, at an angle small enough to succeed, as hundreds of them, every boundary of which would then be forced into the mesh as a feature curve.

The three MeshSizeFrom* switches are turned off so the field is the only thing setting size; otherwise the surface mesh’s own spacing is extended into the volume and quietly overrides it.

gmsh does not tetrahedralize the same way twice – not across machines, not run to run on one. A caller freezing a result against this should freeze the tets themselves, not just the knobs that produced them.

Parameters:
Return type:

TetMesh

nekmeshpy.tetmesh.ops.cap_nodes(mesh, loops, tol=1e-06)[source]

Which boundary nodes of mesh lie on each opening loops (point coordinates, one array per opening).

Remeshing throws away the numbering the caps used to be identified by, so find them geometrically instead. Each opening is planar and nothing else in the domain lies in its plane, which makes the test both simple and unambiguous.

Parameters:
Return type:

list[NDArray[int64]]

nekmeshpy.tetmesh.ops.tet_gradients(mesh)[source]

(grad (E,4,3), volume (E,)) of the P1 basis on each tet.

Parameters:

mesh (TetMesh)

Return type:

tuple[NDArray[float64], NDArray[float64]]

nekmeshpy.tetmesh.ops.tet_laplacian(mesh)[source]

P1 stiffness K_ij = vol * grad(lam_i) . grad(lam_j) (recomputed each call) – the tet sibling of trimesh.ops.cotan_laplacian.

Parameters:

mesh (TetMesh)

Return type:

csr_matrix

nekmeshpy.tetmesh.ops.solve_dirichlet(mesh, dpoints, dvals)[source]

Solve the Laplace system with Dirichlet values at dpoints; returns an (n_points,) field. Assembles tet_laplacian() fresh – a caller solving several right-hand sides against the same mesh (like seam_fields()) should assemble once and go through tet_laplacian() directly instead.

Parameters:
  • mesh (TetMesh)

  • dpoints (NDArray[int64])

  • dvals (NDArray[float64])

Return type:

NDArray[float64]

nekmeshpy.tetmesh.ops.seam_fields(mesh, caps)[source]

The three conduction fields, one per leg: no-flux on that leg’s cap, 0 and 1 on the other two, shifted to zero mean on the free cap.

Exactly trimesh.ops.seam_fields, with the solve moved into the volume – and the one stiffness assembly shared across all three solves, since tet_laplacian() is the expensive part at junction resolution.

Parameters:
Return type:

NDArray[float64]

nekmeshpy.tetmesh.ops.leg_label(mesh, U)[source]

Which leg each tet of mesh belongs to (1, 2 or 3), from the sign pattern of the three seam fields U (n_points, 3) at its centroid – the volumetric form of trimesh.ops.leg_label, which reads a node’s sign pattern since the surface cut retriangulates; a tet is not split, so its whole-element label comes from the centroid instead.

Parameters:
  • mesh (TetMesh)

  • U (NDArray[float64])

Return type:

NDArray[int64]