Source code for nekmeshpy.tags.welding
"""Combining tag tables that meet on one shared entity."""
from __future__ import annotations
from collections.abc import Sequence
import numpy as np
from .._typing import BoolArray, IntArray, StrArray
from ._arrays import _str_array
from .tags import Tags
__all__ = ["weld"]
[docs]
def weld(tables: Sequence[Tags]) -> Tags:
"""Combine tables that may name the **same** element -- what a ``merge`` produces
once its weld has carried two blocks' tags onto one shared entity.
Plain :meth:`Tags.concatenate <nekmeshpy.tags.Tags.concatenate>` cannot do this: it would hand the constructor two
rows for one id, which is rejected. Here the duplicate is expected and meaningful,
so it is resolved rather than refused -- but only when the two agree. Two different
non-empty names on one entity is the contradiction the shared-entity storage exists
to rule out, and there is no honest way to pick between them, so it raises.
An entity named by one side and left untagged by the other simply takes the name:
that is the ordinary case of a tagged block welding onto an untagged neighbour.
Every table must be over the same element count, which the result keeps."""
if not tables:
return Tags.empty(0)
size = tables[0].size
if any(t.size != size for t in tables):
raise ValueError("weld: tables are over different element counts %s"
% sorted({t.size for t in tables}))
live = [t for t in tables if len(t)]
if not live:
return Tags.empty(size)
# concatenated as raw columns rather than through ``concatenate``: the duplicate ids
# this resolves are exactly what the constructor is there to reject
raw_ids: IntArray = np.concatenate([t.ids for t in live])
raw_names: StrArray = _str_array(np.concatenate([t.tags for t in live]))
order = np.argsort(raw_ids, kind="stable")
ids, names = raw_ids[order], raw_names[order]
dup: BoolArray = np.zeros(ids.shape[0], dtype=bool)
dup[1:] = ids[1:] == ids[:-1]
if not dup.any():
return Tags(ids, names, size)
differs: BoolArray = np.zeros(ids.shape[0], dtype=bool)
differs[1:] = names[1:] != names[:-1]
clash = np.flatnonzero(dup & differs)
if clash.size:
i = int(clash[0])
raise ValueError(
"the weld puts two different names on one entity -- element %d is "
"tagged both %r and %r. A shared entity carries one tag, so leave one of "
"the two sides untagged, or give them the same name."
% (int(ids[i]), str(names[i - 1]), str(names[i])))
return Tags(ids[~dup], names[~dup], size)