Source code for sigmaepsilon.solid.fourier.loads.loads

from typing import Iterable, Any, Optional, TypeVar, Generic, TypeAlias, Hashable
from types import NoneType
from abc import abstractmethod

from numpy import ndarray
import numpy as np

from sigmaepsilon.deepdict import DeepDict

from ..protocols import NavierProblemProtocol, LoadGroupProtocol, LoadCaseProtocol
from ..postproc import eval_loads_1d, eval_loads_2d

__all__ = ["LoadGroup", "LoadCase"]


Float1d: TypeAlias = Iterable[float]
Float2d: TypeAlias = Iterable[Float1d]

Int1d: TypeAlias = Iterable[int]
Int2d: TypeAlias = Iterable[Int1d]

LoadDomainType = TypeVar("LoadDomainType")
LoadValueType = TypeVar("LoadValueType")


[docs] class LoadCase(Generic[LoadDomainType, LoadValueType]): """ Generic base class for all load cases. Parameters ---------- domain: LoadDomainType The domain of the load. value: LoadValueType The value of the load. num_mc: int, Optional The number of sampling points for Monte Carlo integration. If no value is provided, the global config value is used. """ def __init__( self, domain: LoadDomainType, value: LoadValueType, num_mc: int | NoneType = None, ): super().__init__() self._domain = domain self._value = value self._num_mc = num_mc @property def domain(self) -> LoadDomainType: """Returns the domain of the load.""" return self._domain @domain.setter def domain(self, value: LoadDomainType) -> None: """Sets the domain of the load.""" self._domain = value @property def value(self) -> LoadValueType: """Returns the value of the load.""" return self._value @value.setter def value(self, value: LoadValueType) -> None: """Sets the value of the load.""" self._value = value @abstractmethod def rhs(self, problem: NavierProblemProtocol) -> ndarray: raise NotImplementedError("The method 'rhs' must be implemented.")
[docs] def eval_approx(self, problem: NavierProblemProtocol, points: Iterable) -> ndarray: """ Evaluates the Fourier series approximation of the load at the given points. The returned array is an 1d array with the same length as the number of points. """ if problem.model_type.is_2d: length_X, length_Y = problem.size number_of_modes_X, number_of_modes_Y = problem.shape rhs = self.rhs(problem) points = np.array(points, dtype=float) return eval_loads_2d( (length_X, length_Y), (number_of_modes_X, number_of_modes_Y), rhs, points, ) elif problem.model_type.is_1d: length_X = float(problem.size) number_of_modes_X = problem.shape rhs = self.rhs(problem) points = np.array(points, dtype=float) return eval_loads_1d( length_X, number_of_modes_X, rhs, points, ) else: # pragma: no cover raise NotImplementedError
[docs] class LoadGroup(DeepDict[Hashable, LoadGroupProtocol | LoadCaseProtocol | Any]): """ A class to handle load groups for Navier's semi-analytic solution of rectangular plates and beams with specific boundary conditions. This class is also the base class of all other load types. See Also -------- :class:`~sigmaepsilon.deepdict.deepdict.DeepDict` Examples -------- >>> from sigmaepsilon.solid.fourier import LoadGroup, PointLoad >>> >>> loads = LoadGroup( >>> group1 = LoadGroup( >>> case1 = PointLoad(x=L/3, v=[1.0, 0.0]), >>> case2 = PointLoad(x=L/3, v=[0.0, 1.0]), >>> ), >>> group2 = LoadGroup( >>> case1 = PointLoad(x=2*L/3, v=[1.0, 0.0]), >>> case2 = PointLoad(x=2*L/3, v=[0.0, 1.0]), >>> ), >>> ) Since the LoadGroup class is a subclass of :class:`~sigmaepsilon.deepdict.deepdict.DeepDict`, a case is accessible as >>> loads['group1', 'case1'] If you want to protect the object from the accidental creation of nested subdirectories, you can lock the layout by typing >>> loads.lock() """ def __init__(self, *args, cooperative: bool = False, **kwargs): super().__init__(*args, **kwargs) self._cooperative = cooperative @property def cooperative(self) -> bool: """ Returns `True` if the load cases of this group can interact. """ return self._cooperative @cooperative.setter def cooperative(self, value: bool): """ Sets the cooperativity of the cases in the group. """ self._cooperative = value
[docs] def groups( self, *, inclusive: Optional[bool] = False, blocktype: Optional[Any] = None, deep: Optional[bool] = True, ) -> Iterable[LoadGroupProtocol]: """ Returns a generator object that yields all the subgroups. Parameters ---------- inclusive: bool, Optional If ``True``, returns the object the call is made upon. Default is False. blocktype: Any, Optional The type of the load groups to return. Default is ``None``, that returns all types. deep: bool, Optional If ``True``, all deep groups are returned separately. Default is ``True``. Yields ------ :class:`~sigmaepsilon.solid.fourier.loads.LoadGroup` """ dtype = LoadGroup if blocktype is None else blocktype return self.containers(inclusive=inclusive, dtype=dtype, deep=deep)
[docs] def cases( self, case_type: Any = None, ) -> Iterable[LoadCaseProtocol]: """ Returns a generator that yields the load cases in the group. Parameters ---------- case_type: Any, Optional The type of the load cases to return. Default is None, that returns all types. Yields ------ :class:`~sigmaepsilon.solid.fourier.loads.LoadCase` """ case_type = LoadCase if case_type is None else case_type return filter( lambda i: isinstance(i, case_type), self.values(deep=True), )