Source code for planestress.pre.boundary_condition

"""Classes describing a planestress boundary conditions."""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np
import numpy.typing as npt

from planestress.analysis.utils import dof_map


if TYPE_CHECKING:
    from planestress.pre.mesh import TaggedEntity, TaggedLine, TaggedNode


[docs] class BoundaryCondition: """Abstract base class for a boundary condition. Attributes: mesh_tag: Tagged entity object. """
[docs] def __init__(self) -> None: """Inits the BoundaryCondition class.""" self.mesh_tag: TaggedEntity
[docs] def apply_bc( self, k: npt.NDArray[np.float64], f: npt.NDArray[np.float64], ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """Applies the boundary condition. Args: k: Stiffness matrix. f: Load vector. Raises: NotImplementedError: If this method has not been implemented. """ raise NotImplementedError
[docs] class NodeBoundaryCondition(BoundaryCondition): """Abstract base class for a boundary condition at a node. Attributes: mesh_tag: Tagged node object. """
[docs] def __init__( self, point: tuple[float, float], direction: str, value: float, ) -> None: """Inits the NodeBoundaryCondition class. Args: point: Point tuple (``x``, ``y``) describing the node location. direction: Direction of the boundary condition, ``"x"`` or ``"y"``. value: Value of the boundary condition. """ self.point = point self.direction = direction # TODO - verify input self.value = value self.mesh_tag: TaggedNode
def __repr__(self) -> str: """Override __repr__ method. Returns: String representation of the object. """ return ( f"BC Type: {self.__class__.__name__}, dir: {self.direction}, val: " f"{self.value}\n" f"Mesh Tag: {self.mesh_tag}" )
[docs] def get_node_dofs(self) -> list[int]: """Get the degrees of freedom of the node. Returns: List (length 2) of degrees of freedom. """ return dof_map(node_idxs=[self.mesh_tag.node_idx])
[docs] class NodeSupport(NodeBoundaryCondition): """Class for adding a support to a node. Attributes: mesh_tag: Tagged node object. """
[docs] def __init__( self, point: tuple[float, float], direction: str, value: float, ) -> None: """Inits the NodeSupport class. Args: point: Point tuple (``x``, ``y``) describing the node location. direction: Direction of the boundary condition, ``"x"`` or ``"y"``. value: Value of the boundary condition. """ super().__init__(point=point, direction=direction, value=value)
[docs] def apply_bc( self, k: npt.NDArray[np.float64], f: npt.NDArray[np.float64], ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """Applies the boundary condition. Args: k: Stiffness matrix. f: Load vector. Returns: Modified stiffness matrix and load vector (``k``, ``f``). """ # get nodal dofs dofs = self.get_node_dofs() # get relevant dof dof = dofs[0] if self.direction == "x" else dofs[1] # apply bc - TODO - confirm this theory! k[dof, :] = 0 k[dof, dof] = 1 f[dof] = self.value return k, f
[docs] class NodeSpring(NodeBoundaryCondition): """Class for adding a spring to a node. Attributes: mesh_tag: Tagged node object. """
[docs] def __init__( self, point: tuple[float, float], direction: str, value: float, ) -> None: """Inits the NodeSpring class. Args: point: Point tuple (``x``, ``y``) describing the node location. direction: Direction of the boundary condition, ``"x"`` or ``"y"``. value: Value of the boundary condition. """ super().__init__(point=point, direction=direction, value=value)
[docs] def apply_bc( self, k: npt.NDArray[np.float64], f: npt.NDArray[np.float64], ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """Applies the boundary condition. Args: k: Stiffness matrix. f: Load vector. Returns: Modified stiffness matrix and load vector (``k``, ``f``). """ # get nodal dofs dofs = self.get_node_dofs() # get relevant dof dof = dofs[0] if self.direction == "x" else dofs[1] # apply bc - TODO - confirm this theory! k[dof, dof] += self.value return k, f
[docs] class NodeLoad(NodeBoundaryCondition): """Class for adding a load to a node. Attributes: mesh_tag: Tagged node object. """
[docs] def __init__( self, point: tuple[float, float], direction: str, value: float, ) -> None: """Inits the NodeLoad class. Args: point: Point tuple (``x``, ``y``) describing the node location. direction: Direction of the boundary condition, ``"x"`` or ``"y"``. value: Value of the boundary condition. """ super().__init__(point=point, direction=direction, value=value)
[docs] def apply_bc( self, k: npt.NDArray[np.float64], f: npt.NDArray[np.float64], ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """Applies the boundary condition. Args: k: Stiffness matrix. f: Load vector. Returns: Modified stiffness matrix and load vector (``k``, ``f``). """ # get nodal dofs dofs = self.get_node_dofs() # get relevant dof dof = dofs[0] if self.direction == "x" else dofs[1] # apply bc f[dof] += self.value return k, f
[docs] class LineBoundaryCondition(BoundaryCondition): """Abstract base class for a boundary condition along a line. Attributes: mesh_tag: Tagged line object. """
[docs] def __init__( self, point1: tuple[float, float], point2: tuple[float, float], direction: str, value: float, ) -> None: """Inits the LineBoundaryCondition class. Args: point1: Point location (``x``, ``y``) of the start of the line. point2: Point location (``x``, ``y``) of the end of the line. direction: Direction of the boundary condition, ``"x"`` or ``"y"``. value: Value of the boundary condition. """ self.point1 = point1 self.point2 = point2 self.direction = direction # TODO - verify input self.value = value self.mesh_tag: TaggedLine
def __repr__(self) -> str: """Override __repr__ method. Returns: String representation of the object. """ return ( f"BC Type: {self.__class__.__name__}, dir: {self.direction}, val: " f"{self.value}\n" f"Mesh Tag: {self.mesh_tag}" )
[docs] def get_unique_nodes(self) -> list[int]: """Returns a list of unique node indexes along the line BC. Returns: List of unique node indexes along the line. """ # get list of node indexes along line BC node_idxs = [] # loop through all line elements along the line BC for line_el in self.mesh_tag.elements: # loop through all nodes that make up the line element for node_idx in line_el.node_idxs: # if we haven't encountered this node, add it to the list if node_idx not in node_idxs: node_idxs.append(node_idx) return node_idxs
[docs] class LineSupport(LineBoundaryCondition): """Class for adding supports along a line. Attributes: mesh_tag: Tagged line object. """
[docs] def __init__( self, point1: tuple[float, float], point2: tuple[float, float], direction: str, value: float, ) -> None: """Inits the LineSupport class. Args: point1: Point location (``x``, ``y``) of the start of the line. point2: Point location (``x``, ``y``) of the end of the line. direction: Direction of the boundary condition, ``"x"`` or ``"y"``. value: Value of the boundary condition. """ super().__init__(point1=point1, point2=point2, direction=direction, value=value)
[docs] def apply_bc( self, k: npt.NDArray[np.float64], f: npt.NDArray[np.float64], ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """Applies the boundary condition. Args: k: Stiffness matrix. f: Load vector. Returns: Modified stiffness matrix and load vector (``k``, ``f``). """ # get degrees of freedom for node indexes dofs = dof_map(node_idxs=self.get_unique_nodes()) # get relevant dofs dof_list = dofs[0::2] if self.direction == "x" else dofs[1::2] # apply bc - TODO - confirm this theory! for dof in dof_list: k[dof, :] = 0 k[dof, dof] = 1 f[dof] = self.value return k, f
[docs] class LineSpring(LineBoundaryCondition): """Class for adding springs along a line. Attributes: mesh_tag: Tagged line object. """
[docs] def __init__( self, point1: tuple[float, float], point2: tuple[float, float], direction: str, value: float, ) -> None: """Inits the LineSpring class. Args: point1: Point location (``x``, ``y``) of the start of the line. point2: Point location (``x``, ``y``) of the end of the line. direction: Direction of the boundary condition, ``"x"`` or ``"y"``. value: Value of the boundary condition. """ super().__init__(point1=point1, point2=point2, direction=direction, value=value)
[docs] def apply_bc( self, k: npt.NDArray[np.float64], f: npt.NDArray[np.float64], ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """Applies the boundary condition. Args: k: Stiffness matrix. f: Load vector. Returns: Modified stiffness matrix and load vector (``k``, ``f``). """ # get degrees of freedom for node indexes dofs = dof_map(node_idxs=self.get_unique_nodes()) # get relevant dofs dof_list = dofs[0::2] if self.direction == "x" else dofs[1::2] # apply bc - TODO - confirm this theory! for dof in dof_list: k[dof, dof] += self.value return k, f
[docs] class LineLoad(LineBoundaryCondition): """Class for adding a load to a line. Attributes: mesh_tag: Tagged line object. """
[docs] def __init__( self, point1: tuple[float, float], point2: tuple[float, float], direction: str, value: float, ) -> None: """Inits the LineLoad class. Args: point1: Point location (``x``, ``y``) of the start of the line. point2: Point location (``x``, ``y``) of the end of the line. direction: Direction of the boundary condition, ``"x"`` or ``"y"``. value: Value of the boundary condition. """ super().__init__(point1=point1, point2=point2, direction=direction, value=value)
# def apply_bc( # self, # k: npt.NDArray[np.float64], # f: npt.NDArray[np.float64], # dofs: list[int], # ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: # """Applies the boundary condition.""" # get relevant dof # dof = dofs[0] if self.direction == "x" else dofs[1] # # apply bc - TODO - confirm this theory! # k[dof, :] = 0 # k[dof, dof] = 1 # f[dof] = self.value # return k, f # TODO - calculate equivalent loads + Tri6