Source code for sigmaepsilon.mesh.data.celldata

from typing import Union, Iterable, Generic, TypeVar, Optional
from copy import deepcopy

import numpy as np
from numpy import ndarray

from sigmaepsilon.core import classproperty
from sigmaepsilon.math import atleast3d, repeat
from sigmaepsilon.math.linalg import ReferenceFrame

from .akwrapper import AkWrapper
from ..typing import PolyDataProtocol, PointDataProtocol
from ..typing.abcakwrapper import ABC_AkWrapper
from .akwrapper import AwkwardLike

PointDataLike = TypeVar("PointDataLike", bound=PointDataProtocol)
PolyDataLike = TypeVar("PolyDataLike", bound=PolyDataProtocol)


__all__ = ["CellData"]


[docs] class CellData(Generic[PolyDataLike, PointDataLike], AkWrapper, ABC_AkWrapper): """ A class to handle data related to the cells of a polygonal mesh. Technically this is a wrapper around an Awkward data object instance. If you are not a developer, you probably don't have to ever create any instance of this class, but since it operates in the background of every polygonal data structure, it is useful to understand how it works. Parameters ---------- activity: numpy.ndarray, Optional 1d boolean array describing the activity of the elements. t or thickness: numpy.ndarray, Optional 1d float array of thicknesses. Only for 2d cells. Default is None. areas: numpy.ndarray, Optional 1d float array of cross sectional areas. Only for 1d cells. Default is None. fields: dict, Optional Every value of this dictionary is added to the dataset. Default is `None`. frames: numpy.ndarray, Optional Coordinate axes representing cartesian coordinate frames. Default is None. topo: numpy.ndarray, Optional 2d integer array representing node indices. Default is None. i: numpy.ndarray, Optional The (global) indices of the cells. Default is None. **kwargs: dict, Optional For every key and value pair where the value is a numpy array with a matching shape (has entries for all cells), the key is considered as a field and the value is added to the database. """ _attr_map_ = { "nodes": "_nodes", # node indices "frames": "_frames", # coordinate frames "ndf": "_ndf", # nodal distribution factors "id": "_id", # global indices of the cells "areas": "_areas", # areas of 1d cells "t": "_t", # thicknesses for 2d cells "activity": "_activity", # activity of the cells } def __init__( self, *args, wrap: Optional[Union[AwkwardLike, None]] = None, topo: Optional[Union[ndarray, None]] = None, fields: Optional[Union[dict, None]] = None, activity: Optional[Union[ndarray, None]] = None, frames: Optional[Union[ndarray, ReferenceFrame, None]] = None, areas: Optional[Union[ndarray, float, None]] = None, t: Optional[Union[ndarray, float, None]] = None, db: Optional[Union[AwkwardLike, None]] = None, i: Optional[Union[ndarray, None]] = None, **kwargs, ): fields = {} if fields is None else fields assert isinstance(fields, dict) if len(fields) > 0: attr_map = self._attr_map_ fields = {attr_map.get(k, k): v for k, v in fields.items()} # cell indices if isinstance(i, ndarray): kwargs[self._dbkey_id_] = i if db is not None: wrap = db elif wrap is None: nodes = None if len(args) > 0: if isinstance(args[0], ndarray): nodes = args[0] else: nodes = topo if isinstance(activity, ndarray): fields[self._dbkey_activity_] = activity if isinstance(nodes, ndarray): fields[self._dbkey_nodes_] = nodes N = nodes.shape[0] for k, v in kwargs.items(): if isinstance(v, ndarray): if v.shape[0] == N: fields[k] = v if isinstance(areas, np.ndarray): fields[self._dbkey_areas_] = areas super().__init__(*args, wrap=wrap, fields=fields, **kwargs) if self.db is not None: if frames is not None: if isinstance(frames, (ReferenceFrame, ndarray)): self.frames = frames else: msg = ( "'frames' must be a NumPy array, or a ", "sigmaepsilon.math.linalg.ReferenceFrame instance.", ) raise TypeError(msg) if t is not None: self.t = t if areas is not None: self.A = areas def __deepcopy__(self, memo: dict) -> "CellData": return self.__copy__(memo) def __copy__(self, memo: dict = None) -> "CellData": cls = type(self) is_deep = memo is not None if is_deep: copy_function = lambda x: deepcopy(x, memo) else: copy_function = lambda x: x db = copy_function(self.db) result = cls(db=db) if is_deep: memo[id(self)] = result result_dict = result.__dict__ for k, v in self.__dict__.items(): if not k in result_dict: setattr(result, k, copy_function(v)) return result @classproperty def _dbkey_nodes_(cls) -> str: return cls._attr_map_["nodes"] @classproperty def _dbkey_frames_(cls) -> str: return cls._attr_map_["frames"] @classproperty def _dbkey_areas_(cls) -> str: return cls._attr_map_["areas"] @classproperty def _dbkey_thickness_(cls) -> str: return cls._attr_map_["t"] @classproperty def _dbkey_activity_(cls) -> str: return cls._attr_map_["activity"] @classproperty def _dbkey_ndf_(cls) -> str: return cls._attr_map_["ndf"] @classproperty def _dbkey_id_(cls) -> str: return cls._attr_map_["id"] @property def has_nodes(self) -> bool: return self._dbkey_nodes_ in self._wrapped.fields @property def has_id(self) -> bool: return self._dbkey_id_ in self._wrapped.fields @property def has_frames(self) -> bool: return self._dbkey_frames_ in self._wrapped.fields @property def has_thickness(self) -> bool: return self._dbkey_thickness_ in self._wrapped.fields @property def has_areas(self) -> bool: return self._dbkey_areas_ in self._wrapped.fields @property def db(self) -> AwkwardLike: return self._wrapped @property def fields(self) -> Iterable[str]: """Returns the fields in the database object.""" return self._wrapped.fields @property def nodes(self) -> ndarray: """Returns the topology of the cells.""" return self._wrapped[self._dbkey_nodes_].to_numpy() @nodes.setter def nodes(self, value: ndarray) -> None: """ Sets the topology of the cells. Parameters ---------- value: numpy.ndarray A 2d integer array. """ assert isinstance(value, ndarray) self._wrapped[self._dbkey_nodes_] = value @property def frames(self) -> ndarray: """Returns local coordinate frames of the cells.""" return self._wrapped[self._dbkey_frames_].to_numpy() @frames.setter def frames(self, value: Union[ReferenceFrame, ndarray]) -> None: """ Sets local coordinate frames of the cells. Parameters ---------- value: numpy.ndarray A 3d float array. """ if isinstance(value, ReferenceFrame): frames = value.show() elif isinstance(value, ndarray): frames = value else: raise TypeError(f"Expected ndarray or FrameLike, got {type(value)}.") frames = atleast3d(frames) if len(frames) == 1: frames = repeat(frames[0], len(self._wrapped)) else: assert len(frames) == len(self._wrapped) self._wrapped[self._dbkey_frames_] = frames @property def t(self) -> ndarray: """Returns the thicknesses of the cells.""" return self._wrapped[self._dbkey_thickness_].to_numpy() @t.setter def t(self, value: Union[float, int, ndarray]): """Returns the thicknesses of the cells.""" if isinstance(value, (int, float)): value = np.full(len(self), value) self._wrapped[self._dbkey_thickness_] = value @property def A(self) -> ndarray: """Returns the thicknesses of the cells.""" return self._wrapped[self._dbkey_areas_].to_numpy() @A.setter def A(self, value: Union[float, int, ndarray]): """Returns the thicknesses of the cells.""" if isinstance(value, (int, float)): value = np.full(len(self), value) self._wrapped[self._dbkey_areas_] = value @property def id(self) -> ndarray: """Returns global indices of the cells.""" return self._wrapped[self._dbkey_id_].to_numpy() @id.setter def id(self, value: ndarray) -> None: """ Sets global indices of the cells. Parameters ---------- value: numpy.ndarray An 1d integer array. """ if isinstance(value, int): if len(self) == 1: value = np.array( [ value, ], dtype=int, ) else: raise ValueError(f"Expected an array, got {type(value)}") if not isinstance(value, ndarray): raise TypeError(f"Expected ndarray, got {type(value)}") self._wrapped[self._dbkey_id_] = value @property def activity(self) -> ndarray: """Returns a 1d boolean array of cell activity.""" return self._wrapped[self._dbkey_activity_].to_numpy() @activity.setter def activity(self, value: ndarray): """ Sets cell activity with a 1d boolean array. Parameters ---------- value: numpy.ndarray An 1d bool array. """ if isinstance(value, bool): value = np.full(len(self), value, dtype=bool) self._wrapped[self._dbkey_activity_] = value
[docs] def set_nodal_distribution_factors(self, factors: ndarray, key: str = None) -> None: """ Sets nodal distribution factors. Parameters ---------- factors: numpy.ndarray A 3d float array. The length of the array must equal the number pf cells in the block. key: str, Optional A key used to store the values in the database. This makes you able to use more nodal distribution strategies in one model. If not specified, a default key is used. """ if key is None: key = self.__class__._attr_map_[self._dbkey_ndf_] if len(factors) != len(self._wrapped): self._wrapped[key] = factors[self._wrapped.id] else: self._wrapped[key] = factors