"""Vocabulary-only ``QuadMesh`` operations: rename the tags, touch nothing else.
Separate from :mod:`morph <nekmeshpy.quadmesh.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.
"""
from __future__ import annotations
from collections.abc import Mapping, Sequence
import numpy as np
from .._typing import IntArray, StrArray
from ..linemesh import LineMesh
from ..linemesh import tag as linemesh
from ..tags.tags import ElementTagSpec
from .quadmesh import QuadMesh
[docs]
def retag_element(mesh: QuadMesh, tags: ElementTagSpec) -> QuadMesh:
"""The same mesh with its ``element_tags`` changed, by what ``tags`` is: 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 to ``NO_TAG`` untags);
a string names every element; a ``Tags`` or per-element sequence replaces the table.
Geometry and the side tags are untouched."""
return QuadMesh(mesh.line_mesh, mesh.quads, mesh.orient, mesh.interior,
mesh.element_tags.retag(tags))
[docs]
def retag_edge(mesh: QuadMesh, mapping: Mapping[str, str]) -> QuadMesh:
"""The same mesh with its ``edge_tags`` renamed through ``mapping``, rows kept in
stored order (see :func:`retag_element` for the map's own rules).
Renaming a tag to ``NO_TAG`` **drops** 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."""
return QuadMesh(linemesh.retag_element(mesh.line_mesh, mapping),
mesh.quads, mesh.orient, mesh.interior, mesh.element_tags)
[docs]
def tag_edges(mesh: QuadMesh, rows: IntArray,
tags: str | Sequence[str] | StrArray) -> QuadMesh:
"""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.
``tags`` is one name for all the rows or one name per row, as
:func:`hexmesh.tag_faces <nekmeshpy.hexmesh.tag.tag_faces>` takes them.
Rows tagged ``NO_TAG`` name nothing, so a partly-tagged row block can be handed over
whole."""
edge_of: IntArray = np.asarray(mesh.quads, dtype=np.int64)
r: IntArray = np.asarray(rows, dtype=np.int64).reshape(-1, 2)
# One name for all of them, or one per row -- as ``hexmesh.tag_faces`` takes them.
# A bare string is a sequence *of characters*, so without this it zipped against the
# rows and quietly tagged only the first. No dtype on the broadcast either:
# ``np.str_`` is a one-character width, which clips every name to its first letter.
names: StrArray = (np.full(r.shape[0], tags) if isinstance(tags, str)
else np.asarray(tags, dtype=np.str_).reshape(-1))
if names.shape[0] != r.shape[0]:
raise ValueError(
"tag_edges: got %d rows but %d tags -- pass one name for all of them or "
"one per row" % (r.shape[0], names.shape[0]))
named = mesh.edge_tags.put(edge_of[r[:, 0], r[:, 1] - 1], names)
return QuadMesh(
LineMesh(mesh.line_mesh.point_mesh, mesh.line_mesh.lines, mesh.line_mesh.interior,
named),
mesh.quads, mesh.orient, mesh.interior, mesh.element_tags)
__all__ = [
"retag_edge",
"retag_element",
"tag_edges",
]