Source code for nanomesh.region_markers

from __future__ import annotations

from collections import abc
from copy import copy
from dataclasses import dataclass
from functools import singledispatchmethod
from typing import List, Optional, Tuple, Union

import numpy.typing as npt

# tetgen:
# https://wias-berlin.de/software/tetgen/1.5/doc/manual/manual006.html#sec75

# triangle:
# https://www.cs.cmu.edu/~quake/triangle.poly.html


[docs]@dataclass class RegionMarker: """Data class to store region info. A region is typically an area or volume bounded by segments or cells. Attributes ---------- label : int Label used to identify the region. point : tuple[float] Point inside the region. name : str, optional Name of the region. constraint : float, default=0 This value can be used to set the maximum size constraint for cells in the region during meshing. """ label: int point: Union[Tuple[float, ...], npt.NDArray] name: Optional[str] = None constraint: float = 0 # max area or volume constraint def __post_init__(self): self.point = tuple(self.point)
[docs] def update(self, **kwargs) -> RegionMarker: new_marker = copy(self) new_marker.__dict__.update(kwargs) return new_marker
RegionMarkerLike = Union[RegionMarker, Tuple[int, Tuple[float, ...], Optional[str]]]
[docs]class RegionMarkerList(List[RegionMarker]): """Collection of region markers. Sub-classes :func:`list`. """ def __repr__(self): s = ',\n'.join(f' {marker}' for marker in self) return f'{self.__class__.__name__}(\n{s}\n)'
[docs] @singledispatchmethod def relabel(self, old: abc.Callable, new: int, name: str = None) -> RegionMarkerList: """Relabel a sub-group of region markers. Parameters ---------- old : callable or list or int The old label(s) to replace. The value can be of type: - int, matches this exact label - list, matches all labels in this list - callable, returns True if label is a match new : int New label to assign name : str, optional Optional name to assign to the new region markers. Returns ------- RegionMarkerList New list of region markers with updated labels. """ if not name: names = [m.name for m in self if old(m.label)] if len(set(names)) == 1: name = tuple(names)[0] markers = (m.update(label=new, name=name) if old(m.label) else m for m in self) return RegionMarkerList(markers)
@relabel.register def _(self, old: abc.Sequence, new: int, *args, **kwargs): def f(x): return x in old return self.relabel(f, new, *args, **kwargs) @relabel.register def _(self, old: int, new: int, *args, **kwargs): def f(x): return x == old return self.relabel(f, new, *args, **kwargs)
[docs] @singledispatchmethod def label_sequentially(self, old: abc.Callable, fmt_name: str = None): """Re-label a set of regions sequentially. `old` matches a sub-group of region markers and assigns new labels. The new labels are incremented sequentially. E.g.: [1,1,1,2] -> [3,4,5,6] Parameters ---------- old : callable or list or int The old label(s) to replace. The value can be of type: - int, matches this exact label - list, matches all labels in this list - callable, returns True if label is a match fmt_name : str, optional Optional format string for the label name, e.g. 'feature{}'. The placeholder (`{}`) will be substituted by the new label. Returns ------- RegionMarkerList New list of region markers with updated labels. """ markers = [copy(marker) for marker in self] i = max(self.labels) + 1 for marker in markers: if not old(marker.label): continue marker.label = i if fmt_name: marker.name = fmt_name.format(i) i += 1 return RegionMarkerList(markers)
@label_sequentially.register def _(self, old: abc.Sequence, *args, **kwargs): def f(x): return x in old return self.label_sequentially(f, *args, **kwargs) @label_sequentially.register def _(self, old: int, *args, **kwargs): def f(x): return x == old return self.label_sequentially(f, *args, **kwargs) @property def labels(self) -> set: """Return all unique region labels.""" return set(m.label for m in self) @property def names(self) -> set: """Return all unique region names.""" return set(m.name for m in self)