quadmesh¶
- class nekmeshpy.quadmesh.QuadMesh[source]¶
Bases:
objectA quadrilateral surface / cross-section mesh in B-rep form.
- EDGE_POINTS = array([[0, 1], [1, 2], [2, 3], [3, 0]])¶
- __init__(line_mesh, quads, orient, interior=None, element_tags=None)[source]¶
Construct from the B-rep directly:
line_mesh(aLineMeshholding every shared edge – itspointsare the shared corners, itslinesthe shared edge connectivity, itsinteriorthe shared edge-interior HO nodes),quads(Q,4)edge indices intoline_mesh.lines(CCW local edge order),orient(Q,4)bool (True where the quad traverses that edge anti-canonically), andinterior(Q,(order-1)**2,3)private per-quad nodes (omit /Noneat order 1).
- classmethod from_corners(points, quads, element_tags=None, *, order=1)[source]¶
Build a linear
QuadMeshfrom cornerpoints(P,3)+ CCWquads(Q,4)connectivity – the corner -> B-rep bridge every factory routes through.
- property edge_tags: Tags¶
The tags on the shared edges, over edge ids.
This is
lines’ ownelement_tagsread through, not a table of its own: rung N’s side tags are rung N-1’s element tags. An edge is one stored object every incident quad references, so naming it is naming that object – which is why there is no(quad, side)here to disagree with itself.
- property points: NDArray[float64]¶
The
(P,3)shared corner points – a live view of the edgeLineMesh’spoints(the single source of truth), so an in-place edit (mesh.points[:] = X) moves the shared corners for every quad.
- property corners: NDArray[int64]¶
(Q,4)CCW corner connectivity, derived (memoized) from the stored edge indices + flip. Read-only; the B-repquads/flipare the source of truth.
- property edges: NDArray[int64]¶
min corner id first) – the shared edge topology (the
linesof the edgeLineMesh). Non-empty at every order (edges are first-class B-rep storage).- Type:
(Ne,2)unique undirected quad edges (canonical
- nekmeshpy.quadmesh.NO_TAG = ''¶
str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.
quadmesh.quality¶
Quad-element quality metrics.
- nekmeshpy.quadmesh.quality.corner_scaled_jacobian(points, quads)[source]¶
Per-quad minimum corner scaled Jacobian, shape
(N,).- Parameters:
points (NDArray[float64])
quads (NDArray[int64])
- Return type:
NDArray[float64]
- nekmeshpy.quadmesh.quality.scaled_jacobian(mesh, order)[source]¶
Per-quad minimum scaled Jacobian sampled at the
(order+1)**2GLL nodes of the curved element block, shape(Q,)– the order-N generalization ofscaled_jacobian.ordermay exceedmesh.order: the stored map is read on the finer lattice rather than re-meshed, so this answers “what will the solver see atlx1 = N” without leaving the toolkit.
- nekmeshpy.quadmesh.quality.order_scan(mesh, orders=None, *, budget=None)[source]¶
Read the mesh’s own map on finer GLL lattices and report what each one sees.
ordersdefaults toSCAN_ORDERalone – the order the solver runs, which is the only reading that predicts what the solver will do. A sweep of intermediate orders is not worth its cost and does not certify anything either: the reading is not monotone in the sampling order, and a real mesh has read positive at 2, negative at 3 and 4, positive again at 5, and negative at 8 and 11. Checking the order you actually run is the whole point.budgetdefaults toSCAN_BUDGET, read here rather than captured as a default argument, so assigning the module constant at runtime actually takes effect. It caps the sampled points. AtSCAN_ORDERthat is 512 points a hex, so a large mesh runs to minutes; rather than spend it inside a routine report, the order is declined and returned inOrderScan.skipped. Declined is unchecked, not clean.
- nekmeshpy.quadmesh.quality.format_scan(scan, mesh_order)[source]¶
The
order_scan()block of a report, warning line included.The warning is the point of the whole thing: a mesh can be clean at its own order and folded at the one the solver runs, and the two readings are of the same geometry – only the sampling differs.
- nekmeshpy.quadmesh.quality.corner_summary(points, quads)[source]¶
Aggregate quality statistics for a quad mesh.
- Parameters:
points (NDArray[float64])
quads (NDArray[int64])
- Return type:
- nekmeshpy.quadmesh.quality.summary(mesh, order)[source]¶
Aggregate statistics for the order-N
scaled_jacobian()metric.- Parameters:
- Return type:
Operations¶
quadmesh.assemble¶
Variable-arity QuadMesh operations – the only ones that build a numbering.
- class nekmeshpy.quadmesh.assemble.Seam[source]¶
Bases:
NamedTupleOne stated interface between two sections – the quad rung’s counterpart of
hexmesh.Seam, naming edge groups.aandbname a section by its position inattach’smeshesor by the section itself.tag_a/tag_bare edge-tag names or explicit edge-id arrays (tagged_edges).
- nekmeshpy.quadmesh.assemble.attach(meshes, seams)[source]¶
Join sections along the edge groups each
Seamnames, in one pass – the quad rung’shexmesh.attach, with the same contract one rung down.No tolerance: within a named pair of groups the pairing is nearest-neighbour, proved by bijectivity rather than by a distance. The joined edges are cleared of their names unless the seam’s
attach_tagnames them, andownsays whose coordinates the seam keeps.
- nekmeshpy.quadmesh.assemble.components(mesh)[source]¶
The section split into its connected pieces – one
QuadMeshper group of quads reachable through shared corner points, in the order their first quad appears.
- nekmeshpy.quadmesh.assemble.loft(slices, *, loop=False, sweep_nodes=None, element_tags=None, first_tag=None, last_tag=None)[source]¶
Loft a stack of conformal
LineMeshprofiles into a quad section (the general primitive behindextrude, and the middle rung of the uniform sweep shared withlinemesh.assemble.loftandHexMesh.loft).
- nekmeshpy.quadmesh.assemble.loft_fn(f, fractions, *, loop=False, order=None, element_tags=None, first_tag=None, last_tag=None)[source]¶
Loft a section from a parametrized family of profiles –
loftwith the slices evaluated rather than handed in, so every node (the corners and the sweep-direction high-order nodes) comes from callingfand nothing is blended along the sweep.
- nekmeshpy.quadmesh.assemble.loft_spline(slices, *, loop=False, element_tags=None, first_tag=None, last_tag=None)[source]¶
loftwith the sweep-direction nodes read off a cubic spline through the whole stack of profiles, rather than blended straight between the two bounding a layer.Same arguments, same numbering, same tags, and the profiles given come back verbatim as the levels – the spline interpolates them, so this adds curvature between profiles without moving any. It is the automatic form of
loft(..., sweep_nodes=...): where that asks the caller for the intermediate profiles, this fits them. Every node block a profile stores is fitted, itsinterioras well as its points, so the result is curved along the sweep at every node rather than only at the corners.Reach for it when a sweep has a feature that turns sharply across a handful of profiles:
loftcuts the corner with a chord however high the order, and refining the order alone will not fix that – the nodes it adds land on the same chord.
- nekmeshpy.quadmesh.assemble.merge(meshes, *, tol=1e-07)[source]¶
Merge quad sections into one, welding coincident boundary points.
tolis a fraction ofconform.bbox_scale– the largest of the x/y/z ranges over every point handed in – so the coincidence radius istol * bbox_scale. Not a distance: if you know one, divide byconform.bbox_scale(...).The proximity join: told nothing about what meets what, it infers every seam from coordinates at one tolerance.
attach()is told which edge group meets which and confines the search to those two.
- nekmeshpy.quadmesh.assemble.refine(mesh)[source]¶
Uniform H-refinement: split every quad into 4 – its own true center point plus the edge midpoints
linemesh.refinealready put on its sharedline_mesh.Exact at any order, the same way
linemesh.refineis: the new center, and every child’s own curved interior, are read off the parent’s stored polynomial map via the internalcore.interporder-N kernel, not a bilinear guess through the corners. An edge shared between two quads is refined through the one sharedline_meshexactly once, so both neighbours land on the identical midpoint automatically – no coincidence weld needed for that; the new per-quad center points are then welded/deduped implicitly too, since each is private to its own quad and never shared.Quad
q’s new center is point idlinemesh.refine(mesh.line_mesh).n_points + q. Child4*q + k(k= 0..3, the_CORNER_IJK[2][k]corner) is the quadrant nearer cornerk. One call is one level.
- nekmeshpy.quadmesh.assemble.remove(mesh, which)[source]¶
The complement of
select(): everythingwhichdoes not name.
- nekmeshpy.quadmesh.assemble.select(mesh, which)[source]¶
The named quads as a section of their own, renumbered from zero.
whichis a tag string (every quad carrying it), a(Q,)boolean mask, or an array of quad ids. Kept quads hold their relative order, theirelement_tagsand whicheveredge_tagsrows name them; edges and points nothing kept touches are dropped. The inverse ofmerge().Removing elements can open the section up, so the result is not guaranteed to be simply connected – or connected at all. Ask
components()if that matters.
quadmesh.lift¶
Fixed-arity QuadMesh operations that raise a rung (delta +1).
- nekmeshpy.quadmesh.lift.annulus(inner, outer, radial, *, inner_tag=None, outer_tag=None, element_tags=None)[source]¶
Ring O-grid filling the region between an inner and an outer closed loop – e.g. a circular body inside a square far-field box.
- nekmeshpy.quadmesh.lift.extrude(line, length, layers, *, axis=array([0., 0., 1.]), origin=array([0., 0., 0.]), element_tags=None, first_tag=None, last_tag=None)[source]¶
Sweep a
LineMesha distancelengthalongaxisinto a quad section (the straight special case ofloft).
- nekmeshpy.quadmesh.lift.from_grid(P, *, side_tags=None, element_tag='', order=1)[source]¶
Build quads from a structured point grid
P(ni+1,nj+1,3).
- nekmeshpy.quadmesh.lift.sweep(profile, path, fractions, *, origin, tangent=None, orientation='transport', up=None, twist=0.0, close_twist=True, normal=None, loop=False, element_tags=None, first_tag=None, last_tag=None)[source]¶
A strip swept from one
LineMeshprofilealong the curvepath.- Parameters:
profile (LineMesh)
path (Callable[[NDArray[float64]], NDArray[float64]])
fractions (NDArray[float64])
tangent (Callable[[NDArray[float64]], NDArray[float64]] | None)
orientation (Literal['transport', 'fixed', 'frenet'])
up (NDArray[float64] | Sequence[float] | Callable[[NDArray[float64]], NDArray[float64]] | None)
twist (float)
close_twist (bool)
loop (bool)
- Return type:
- nekmeshpy.quadmesh.lift.sweep_path(profile, path, *, origin, target_length=None, layers=None, fractions=None, orientation=None, up=None, twist=0.0, close_twist=True, normal=None, loop=False, element_tags=None, first_tag=None, last_tag=None)[source]¶
sweepdriven by aPath, which carries its own analytic tangent and junction table – so this asks for an element length along the sweep instead of a station array.A path that also carries its own frame (anything from
paths.walk) needs noorientation: with none asked for, that frame is held per station, so a bend out of plane and a distributedrollarrive here as authored. Naming anorientation(or anup) overrides it.- Parameters:
- Return type:
quadmesh.shape¶
Shape factories for the QuadMesh rung – the ones owning a shape model rather
than being generic over any input.
- nekmeshpy.quadmesh.shape.box(half_sizes, n, *, patch_tags=None, element_tags=None, order=1)[source]¶
Closed box surface centred at the origin: six quad patches welded with
merge.half_sizesis a scalar (cube) or(sx, sy, sz);nis a scalar or(nx, ny, nz)cells per axis.
- nekmeshpy.quadmesh.shape.half_box(half_sizes, n, *, n_vertical=None, patch_tags=None, rim_tag='', element_tags=None, order=1)[source]¶
The upper half of a
box: the five patches of the box surface that bound[-sx, sx] x [-sy, sy] x [0, sz], welded withmergeand left open at the ``z = 0`` rim (thez_minpatch is dropped).
- nekmeshpy.quadmesh.shape.half_ogrid(arc, spine, radial, *, center_scale=0.7, quadrant_scale=0.7, wall_tag='', element_tags=None)[source]¶
Structured half-circle O-grid over a half-disk split along the
spineline (A1..A2); the wallarc((4*Ntheta+1, 3), arc[0]=A1, arc[-1]=A2) is the open boundary. Built as twoquadrant_ogridquarters split at the arc’s own apex (its midpoint, directly opposite the spine) and merged there: the spine’s own [north caps, center fan, south caps] halves become theO -> A1/O -> A2seams verbatim (nothing resampled, same as the rest ofspine), and a freshO -> apexseam (viaquadrant_seam_fractions) is the only new geometry.center_scale/quadrant_scaleare as there.
- nekmeshpy.quadmesh.shape.hemisphere(radius, n, *, n_vertical=None, element_tag='', rim_tag='', order=1)[source]¶
Cubed-hemisphere surface of
radiussitting on the ground planez = 0: a unithalf_boxprojected radially onto the sphere. Untagged unlesselement_tagis given.
- nekmeshpy.quadmesh.shape.ogrid(boundary, n_side, radial, *, center_scale=0.7, quadrant_scale=0.7, wall_tag='', element_tags=None)[source]¶
O-grid filling the closed
boundary: fourquadrant_ogridquarters meeting at the loop’s centroid, split at the loop’s own quarter points.center_scaleplaces each quarter’s own hub corner;quadrant_scaleplaces the seam corner shared between neighbours (viaquadrant_seam_fractions) – see there for what each one does.
- nekmeshpy.quadmesh.shape.quadrant_core(arc, seam1, seam2, *, center_scale=0.5)[source]¶
The core quarter of a
quadrant_ogridas an(n+1, n+1, 3)grid, indexed[i][j]withirunningO -> M1alongseam1andjrunningO -> M2alongseam2.
- nekmeshpy.quadmesh.shape.quadrant_ogrid(arc, seam1, seam2, radial, *, center_scale=0.5, wall_tag='', element_tag='', side_tags=None)[source]¶
Quarter-disk O-grid: the 90-degree sibling of
half_ogrid.Untagged unless
element_tagis given, like every other factory. Naming the quadrant itself is what lets a disc assembled from four of them hand a per-quadrant cap name toloftor a single patch name tohexmesh.tetra.
- nekmeshpy.quadmesh.shape.quadrant_seam_fractions(n_side, radial, quadrant_scale=0.7)[source]¶
The normalized
O -> Apositions of then_side+1 + Nradialseam points thatquadrant_ogridrequires, ascending: then_side+1core fan, then theNradialring stations. The seam’s shared cornerMsits atquadrant_scale * RalongO -> Adirectly – an independent knob fromquadrant_ogrid’s owncenter_scale, which places its hub cornerKatcenter_scale * Ralong the arc’s bisector instead. Unlessquadrant_scaleis chosen to landMexactly on the chord between neighbouring quadrants’Kcorners (quadrant_scale == center_scale * cos(45deg)), the merged core’s boundary bows into an octagon rather than sitting flush as a square.
- nekmeshpy.quadmesh.shape.rectangle(corners, nx, ny, *, x_frac=None, y_frac=None, side_tags=None, element_tags=None, order=1)[source]¶
Structured quad grid over the rectangle with four CCW corners
corners = [c0, c1, c2, c3]:nxcells alongc0->c1(bottom/top),nyalongc1->c2(left/right).x_frac/y_fracare read alongc0->c1/c1->c2; the opposite sides run the other way and take them mirrored, so grading stays aligned.
- nekmeshpy.quadmesh.shape.sphere(radius, n, *, element_tag='', order=1)[source]¶
Closed cubed-sphere surface of
radiusabout the origin: a unitboxprojected radially onto the sphere (same connectivity, so it pairs by index with a same-nbox forHexMesh.annulus).Untagged unless
element_tagis given, like every other factory: a shape does not name itself, because the name belongs to the model the caller is assembling.
- nekmeshpy.quadmesh.shape.spine_fractions(n_theta, radial, quadrant_scale=0.7)[source]¶
The normalized
A1 -> A2positions of the2*n_theta+1 + 2*Nradialspine points thathalf_ogridandspined_ogridrequire, in ascending order:Nradialnorth caps, the2*n_theta+1center fan, thenNradialsouth caps (seehalf_ogridfor what each region is).quadrant_scaleplays the same role here as it does inquadrant_seam_fractions: it is the fraction of each half (O -> A1andO -> A2) given to the center fan before theNradialO-ring caps take over.
- nekmeshpy.quadmesh.shape.spined_ogrid(boundary, radial, *, spine=None, center_scale=0.7, quadrant_scale=0.7, wall_tag='', element_tags=None)[source]¶
Full-disk O-grid over a closed
boundarysplit along a spine diameter into twohalf_ogridhalves (each in turn built from twoquadrant_ogridquarters) welded along the spine – the clean way to O-grid a disk that has a naturalA1..A2seam (a saddle-split vessel or pipe cross-section), so the caller need not hand-roll the arc split and merge.center_scale/quadrant_scaleare as inquadrant_ogrid/quadrant_seam_fractions.
- nekmeshpy.quadmesh.shape.structured(edges, *, side_tags=None, element_tags=None)[source]¶
Transfinite (Coons-patch) quad grid over the surface bounded by four open edge lines
edges = [bottom, right, top, left]in CCW loop order. The lines must share corners (form a closed loop).
- nekmeshpy.quadmesh.shape.tri_patch(surface, ab, bc, ca, *, order=1, tip_bias=0.3333333333333333, mids=None, element_tag='')[source]¶
The curved triangle bounded by three surface curves, meshed as the three quadrilateral patches about an interior tip – the all-quad split of a triangle, and the shape
HexMesh.tetrawants for the curved side of a curvilinear tetrahedron.- Parameters:
surface (Callable[[NDArray[float64]], NDArray[float64]])
ab (SurfaceCurve)
bc (SurfaceCurve)
ca (SurfaceCurve)
order (int)
tip_bias (float)
mids (Sequence[NDArray[float64]] | None)
element_tag (str)
- Return type:
- nekmeshpy.quadmesh.shape.tri_patch_tip(u_ab, u_bc, u_ca, *, tip_bias=0.3333333333333333)[source]¶
Where
tri_patch()puts its tip, in surface parameters, given the three curves’ middle nodes.- Parameters:
u_ab (NDArray[float64])
u_bc (NDArray[float64])
u_ca (NDArray[float64])
tip_bias (float)
- Return type:
NDArray[float64]
quadmesh.ports¶
A cross-section together with the two facts a bare section cannot state about itself: which way it faces and where its axis is.
- nekmeshpy.quadmesh.ports.NORMAL_TOL = 1e-12¶
How far
normalmay stray from unit length beforePortrefuses it.
- class nekmeshpy.quadmesh.ports.Port[source]¶
Bases:
objectAn open end of a meshed component: its cross-section, the outward direction, the axis point, and the nominal radius.
- normal: NDArray[float64]¶
Unit vector pointing out of the component, along which a connector leaves.
- center: NDArray[float64]¶
The axis point the section was built about – deliberately not the centroid, which an O-grid’s grading shifts slightly off it.
- reversed()[source]¶
The same section faced the other way – for the end of a component that is about to be continued into rather than out of.
- Return type:
- faces(other)[source]¶
Whether the two ports point at each other, rather than the same way or apart. The check
hexmesh.bridgecannot make from geometry alone.
quadmesh.lower¶
Fixed-arity QuadMesh operations that lower a rung (delta -1).
quadmesh.morph¶
Fixed-arity, rung-preserving QuadMesh operations (delta 0).
- nekmeshpy.quadmesh.morph.blend(a, b, fractions)[source]¶
Linearly morph between two conformal sections
aandb(identicalquads, equal point count), one section per fractiontwith points(1-t)*a + t*b–t=0reproducesa,t=1reproducesb. Each section returned carriesa’sedge_tagsand emptyelement_tags.
- nekmeshpy.quadmesh.morph.mirror(mesh, normal, point=array([0., 0., 0.]))[source]¶
A new section reflected through the plane with
normalthroughpoint, with every quad re-wound so it keeps the orientation it was authored with.A reflection has determinant
-1, so it inverts every element: the baretransformof a reflection matrix hands back a section whose scaled Jacobian has flipped sign throughout.mirrorpairs the coordinate map with the winding fix, so the result measures exactly as its original did and lifts to a hex block the right way out.The symmetric-domain idiom – mesh the half you can, recover the whole:
quadmesh.merge([half, mirror(half, normal=(1, 0, 0))]), which welds along the symmetry plane the two now share.
- nekmeshpy.quadmesh.morph.offset(mesh, distance, crease=np.float64(0.5235987755982988))[source]¶
A new section displaced by
distancealong its own surface normal – the cross product of the two parametric tangents (computed from the underlying GLL nodes, so high-order interior nodes follow the same rule as corners). Consistent CCW winding across the section gives every quad’s normal the same sign, so no reference direction is needed.At a point shared by more than one quad (a corner, or a shared-edge node), the offset direction is the average of every incident quad’s own normal at that node, renormalized – so a quad’s private interior node (touched by no other quad) is simply displaced by its own element’s normal.
This is the building block for skinning: loft a section and its
offsetcopy to get a thin, perpendicular boundary-layer hex shell.
- nekmeshpy.quadmesh.morph.place_on_path(section, path, fractions, *, origin=None, orientation=None, up=None, twist=0.0, close_twist=True, normal=None, loop=False)[source]¶
Where
sweepwould putsectionat each offractions, without building the block: one rigidly placed copy per station, through the sameframes.sweep_placementsthe sweep itself uses –orientationandupincluded, so a path carrying its own frame places the same way here as it sweeps.
- nekmeshpy.quadmesh.morph.reindex(structure, target, sigma)[source]¶
target’s own geometry, reached throughstructure’s own index labels: pointitakestarget’s pointsigma[i], and every shared-edge and per-quad interior node follows its relabelled corners.
- nekmeshpy.quadmesh.morph.rotate(mesh, angle, axis=array([0., 0., 1.]), center=array([0., 0., 0.]))[source]¶
A new section rotated by
angleradians about the line throughcenteralongaxis(right-handed,axisneed not be normalized).
- nekmeshpy.quadmesh.morph.scale(mesh, factor, center=array([0., 0., 0.]))[source]¶
A new section scaled about
centerbyfactor– a scalar (uniform) or a(3,)per-axis vector. Every factor must be positive.
- nekmeshpy.quadmesh.morph.transform(mesh, matrix, offset=array([0., 0., 0.]))[source]¶
A new section with every node mapped through the affine
p @ matrix.T + offset.
- nekmeshpy.quadmesh.morph.transform_fn(mesh, fn)[source]¶
A new section with every node – shared corners, shared edge interiors and private face interiors alike – mapped through an arbitrary
fn;transform()for a warp that is not affine.fnis called once per node block with an(N, 3)array and must return(N, 3). Connectivity and tags ride through verbatim.Unlike the affine
transform(),fncan fold space; this checks the result and emits alogging.warning(not an error) when any element comes out inverted. The check signs each element against its own corners’ mean normal, so it catches a local fold – a strip winding the opposite way from its neighbours – but not a global reflection, which flips every element together and reads clean; that onemirror()is for, andtransform_fnnever re-winds. A curvedfnkeeps a curved section curved; it moves the stored nodes, it does not add any, so a straight-subdivided section stays faceted between its corners.
quadmesh.query¶
Read-only QuadMesh queries – the operations that leave the ladder.
- nekmeshpy.quadmesh.query.angle_defect(mesh)[source]¶
(P,)discrete Gaussian curvature of every vertex by the angle defect, read off the mesh with each quad split along its shorter diagonal:2 pi - sum(angles)at an interior vertex,pi - sum(angles)at a boundary one (the turning of the boundary). The integral of the Gaussian curvature over the vertex’s dual cell, to the mesh’s own resolution.The quads are triangulated first on purpose: a non-planar quad’s four corner angles sum to less than
2 pi, so defects read off the raw corners do not add up to2 pi chi; those of a triangle mesh always do (seegauss_bonnet_residual()).- Parameters:
mesh (QuadMesh)
- Return type:
NDArray[float64]
- nekmeshpy.quadmesh.query.gauss_bonnet_residual(mesh)[source]¶
sum(angle_defect) - 2 pi chiwithchi = V - E + F: zero (to rounding) for any triangulated surface, with or without boundary. It does not measure quality – every mesh satisfies it – it catches a corrupted topology or an angle bug, and it must hold before and after a smoothing round.What it can and cannot see. On a closed mesh the residual is exactly
2 pi (E - 2Q), so it is non-zero only when the edge count is inconsistent with a closed surface: it jumps by2 pifor every non-manifold fin and bypifor a quad that names one vertex twice. On a mesh with a boundary it also fires on a pinch vertex (pinch_vertices()). It is blind to an unwelded seam (the cut mesh is still a valid surface with a boundary), to reversed winding, to self-intersection, to a collapsed edge, to holes and to isolated vertices –check_connectivity()is the check for the first of these, not this one.
- nekmeshpy.quadmesh.query.pinch_vertices(mesh)[source]¶
Ids of the pinch vertices: points whose incident quads do not form one connected fan.
Around a vertex each quad is a wedge between its two edges at that vertex; two wedges are neighbours when they share an edge. A manifold vertex has one fan (a closed ring inside, an open one on the boundary); a vertex where two patches, or two sides of a folded sheet, touch has two or more. Neither the gap check, the edge counts nor
is_conformingsees it – every edge is still used once or twice – but the boundary angle-defect formula assumes one fan, sogauss_bonnet_residualdoes fire. Cost: one union-find over4Qwedges.- Parameters:
mesh (QuadMesh)
- Return type:
NDArray[int64]
- class nekmeshpy.quadmesh.query.ConnectivityReport[source]¶
Bases:
NamedTupleThe preflight gap check of one mesh (read-only; nothing is welded or moved).
A gap is a group of stored points that gencon’s relative-tolerance rule would fuse while the mesh keeps them apart – an open seam or a duplicated vertex;
gap_pointsare their ids andn_gap_clustersthe number of groups. A group only counts when its members are directly withinconnectivity_tolof the local length of each other and no two of them are corners of one quad. Otherwise (gencon’s rule also chains, so on a tangled mesh vertices that merely sit close are linked, or a vertex lies on top of its quad’s other corner) it is counted inn_chained_clusters: a possible tangle, not a seam, whose fix is untangling and not welding.- gap_points: NDArray[int64]¶
Alias for field number 5
- n_gap_on_boundary: int¶
Of those, how many sit on the boundary (an open seam) vs inside (a duplicated vertex).
- worst_gap_in_lengths: float¶
Largest separation inside a gap cluster, in units of that cluster’s local length.
- static __new__(_cls, n_points, n_quads, n_components, connectivity_tol, n_gap_clusters, gap_points, n_gap_on_boundary, worst_gap_in_lengths, n_chained_clusters, n_nonmanifold_edges, n_degenerate_quads, n_collapsed_edges, n_pinch_vertices, passed, notes)¶
Create new instance of ConnectivityReport(n_points, n_quads, n_components, connectivity_tol, n_gap_clusters, gap_points, n_gap_on_boundary, worst_gap_in_lengths, n_chained_clusters, n_nonmanifold_edges, n_degenerate_quads, n_collapsed_edges, n_pinch_vertices, passed, notes)
- Parameters:
n_points (int)
n_quads (int)
n_components (int)
connectivity_tol (float)
n_gap_clusters (int)
gap_points (IntArray)
n_gap_on_boundary (int)
worst_gap_in_lengths (float)
n_chained_clusters (int)
n_nonmanifold_edges (int)
n_degenerate_quads (int)
n_collapsed_edges (int)
n_pinch_vertices (int)
passed (bool)
- nekmeshpy.quadmesh.query.check_connectivity(mesh, *, connectivity_tol=0.2)[source]¶
Preflight gap check: run gencon’s relative-tolerance vertex rule over the mesh’s own corner coordinates and report every pair of stored points it would fuse but the mesh keeps apart (an open seam, a duplicated vertex), plus non-manifold edges, degenerate quads and the component count. Read-only – fixing a gap is a separate, explicit act.
A mesh the toolkit built is conformal by construction, so on its own output this only reports what a coordinate-based weld would still find (a seam closer than
connectivity_tolof the local size thatmergeleft open). It cannot see a leak between two distinct objects that are welded: that needs the component and non-manifold lines, and the user’s eye on the picture.- Parameters:
- Return type:
- class nekmeshpy.quadmesh.query.TagReport[source]¶
Bases:
NamedTupleHow a mesh’s side-tag table lines up with its topological boundary – the same report at the hex rung (faces) and the quad rung (edges).
The two are independent by design –
boundaryis what connectivity says, a side-tag table is a named subset that may also name interior entities – so they can disagree in exactly two ways, and both are worth seeing. Ann_untagged_boundaryabove zero means the export has open sides no boundary condition covers; ann_tagged_interiorabove zero means it will write boundary conditions onto sides that are not on the boundary at all. Either can be deliberate (a flux-measurement plane is a tagged interior face; so is a conjugate fluid/solid interface that keeps the fluid’s wall condition), so these are counts to recognise, not assertions.
- nekmeshpy.quadmesh.query.area(mesh, *, high_order=False)[source]¶
Total surface area –
element_areas()summed.The wetted area of a block’s wall is this over the wall’s own mesh:
quadmesh.area(hexmesh.boundary_mesh(block, "wall")).
- nekmeshpy.quadmesh.query.bounds(mesh, *, high_order=False)[source]¶
The axis-aligned bounding box of the section’s nodes – corners only unless
high_order=Trueasks for the stored interior nodes too. Seelinemesh.boundsfor why neither reading bounds the polynomial itself.
- nekmeshpy.quadmesh.query.boundary_edge_ids(mesh)[source]¶
(n_edges,)mask of the shared edges carried by exactly one quad.The edge-id form of
boundary_edges(), and the cheaper one: the edges are already deduplicated inline_mesh, so this is a bincount over the incidence. It is how a name is checked against the topology it was meant for –edge_tags.idsoutside this mask are the tagged interior edgestag_report()counts.
- nekmeshpy.quadmesh.query.boundary_edges(mesh)[source]¶
(K,2)array of[quad id, local edge (1-4)]for every edge on the section boundary (borne by a single quad).- Parameters:
mesh (QuadMesh)
- Return type:
NDArray[int64]
- nekmeshpy.quadmesh.query.boundary_elements(mesh)[source]¶
Sorted unique quad ids with at least one edge on the section boundary.
- Parameters:
mesh (QuadMesh)
- Return type:
NDArray[int64]
- nekmeshpy.quadmesh.query.boundary_points(mesh)[source]¶
Sorted unique point ids lying on the section boundary.
- Parameters:
mesh (QuadMesh)
- Return type:
NDArray[int64]
- nekmeshpy.quadmesh.query.centroid(mesh, *, high_order=False)[source]¶
The area-weighted centroid
integral x dA / integral dA– the mass property, not the mean of the points.
- nekmeshpy.quadmesh.query.edge_rows(mesh, edges)[source]¶
((K,2) [quad, local edge 1-4] rows, (len(edges),) occurrence counts)– the inverse ofquads, for the given shared-edge ids; the quad rung’shexmesh.face_rows.A boundary edge is carried by one quad and yields one row, an interior one by two and yields two, so
countsis how a caller expands a per-edge value onto the rows it produced.
- nekmeshpy.quadmesh.query.edge_tag_rows(mesh)[source]¶
((K,2) [quad, local edge 1-4] rows, their tags)for every named edge, lexsorted by(quad, edge)–edge_rows()run overedge_tags.ids; the quad rung’shexmesh.face_tag_rows.
- nekmeshpy.quadmesh.query.element_areas(mesh, *, high_order=False)[source]¶
(Q,)surface area of each quad, integrated over the element as it sits in 3-D (so a warped quad is measured as the ruled surface it is, not as a projection).Bilinear corners by default;
high_order=Trueintegrates the curved element the mesh stores. A curved area is not a polynomial – the integrand carries a square root – so that reading is a quadrature approximation converging with the order, wherehexmesh.element_volumesis exact.
- nekmeshpy.quadmesh.query.element_blocks(mesh)[source]¶
(Q, (order+1)**2, 3)– each quad’s own node block, assembled natively from the B-rep: shared corners, then the shared edge-interior nodes in element traversal order, then the private per-quad interiors, each written at its lattice slot. Nothing is resampled or deduplicated.The quad rung of
linemesh.element_blocks, one dimension up.- Parameters:
mesh (QuadMesh)
- Return type:
NDArray[float64]
- nekmeshpy.quadmesh.query.is_conforming(mesh)[source]¶
Trueif the mesh has no hanging points (no T-junctions).
- nekmeshpy.quadmesh.query.is_overlap_free(mesh)[source]¶
Trueif no two quads geometrically overlap. Independent ofis_watertight()andis_conforming(), which are purely topological (facet-sharing) checks, and computed only when asked for, as at the hex rung.
- nekmeshpy.quadmesh.query.overlapping_quads(mesh)[source]¶
Ids of the quads that overlap another quad – the which behind
is_overlap_free().A branched star (one quad covering its neighbours) can have every signed Jacobian positive, so the inverted-quad count cannot see it; this does. Each pair is tested in the first quad’s own mean plane, so quads on a curved surface are read locally. Cost: a KD-tree over centroids, then a half-plane test of 9 sample points per candidate pair.
- Parameters:
mesh (QuadMesh)
- Return type:
NDArray[int64]
- nekmeshpy.quadmesh.query.is_watertight(mesh)[source]¶
Trueif the mesh boundary is a closed curve and the mesh is a single connected component. Does not imply conformity.
- nekmeshpy.quadmesh.query.plane_normal(mesh, *, hint=None, check=True)[source]¶
The unit normal of the plane a planar section lies in: the smallest right singular vector of its centred points, i.e. the exact least-squares plane.
- nekmeshpy.quadmesh.query.quality_summary(mesh, *, order=None)[source]¶
Aggregate scaled-Jacobian statistics over the curved elements – see
scaled_jacobian.- Parameters:
- Return type:
- nekmeshpy.quadmesh.query.report(mesh)[source]¶
Human-readable summary: element/point counts, scaled-Jacobian quality (curved, and on the solver-order lattice), per-name tagged-edge counts cross-checked against the boundary (
tag_report()), and the topology report – the quad rung’shexmesh.report. There is no corner-only reading and no linear one:.re2is hex-only, so nothing here is exported flat.
- nekmeshpy.quadmesh.query.scaled_jacobian(mesh, *, order=None)[source]¶
Per-quad minimum scaled Jacobian
(n_quads,), read off the curved element the mesh actually stores.There is deliberately no corner-only reading. A corner scaled Jacobian cannot see where the high-order nodes went, so it reports a contented number for a mesh whose interior nodes are anywhere at all – a node moved clean outside the element still scores the same. Anything that has to be trusted must read the curved block.
ordersamples that block on a finer GLL lattice than the mesh’s own – what a solver running atlx1 = orderdoes to it. The default reads the mesh’s own order, where the value is exact at the nodes and silent between them: positive there is not a proof the element is not folded.
- nekmeshpy.quadmesh.query.tag_report(mesh)[source]¶
Cross-check
edge_tagsagainst the topological boundary (seeTagReport).
- nekmeshpy.quadmesh.query.tagged_edges(mesh, tag)[source]¶
The shared-edge ids carrying
tag, ascending – the quad rung’s counterpart ofhexmesh.tagged_faces, and whatquadmesh.attachpairs.A tag that names nothing raises rather than returning an empty group.
quadmesh.tag¶
Vocabulary-only QuadMesh operations: rename the tags, touch nothing else.
Separate from morph, which is the delta-0 rung for
operations on the geometry. These change neither coordinates nor connectivity nor
numbering – only what the two tag tables call things – so a caller reading the
sibling list can tell at a glance that a retag cannot have moved a node.
- nekmeshpy.quadmesh.tag.retag_edge(mesh, mapping)[source]¶
The same mesh with its
edge_tagsrenamed throughmapping, rows kept in stored order (seeretag_element()for the map’s own rules).Renaming a tag to
NO_TAGdrops its rows rather than storing an empty name: a side-tag table is a named subset, so leaving it is leaving the table. That is how a boundary name that has stopped meaning anything is retired.
- nekmeshpy.quadmesh.tag.retag_element(mesh, tags)[source]¶
The same mesh with its
element_tagschanged, by whattagsis: a mapping renames the existing names (a tag the map does not name is left alone, the map applies simultaneously, a key naming nothing raises, a rename toNO_TAGuntags); a string names every element; aTagsor per-element sequence replaces the table. Geometry and the side tags are untouched.
- nekmeshpy.quadmesh.tag.tag_edges(mesh, rows, tags)[source]¶
The same mesh with the shared edge each
(quad, side)row names given its tag.The authoring bridge. A factory knows its geometry element-locally – “side 1 of the outermost ring is the wall” – while storage is one tag per shared edge, so the row is resolved through
mesh.quads[quad, side - 1]and the tag written there. What changes is only where it lands: two rows that name the same edge from either side of it no longer become two entries that could disagree, and the later row wins.tagsis one name for all the rows or one name per row, ashexmesh.tag_facestakes them.Rows tagged
NO_TAGname nothing, so a partly-tagged row block can be handed over whole.