Source code for planestress.analysis.plane_stress

"""Class for a planestress analysis."""

from __future__ import annotations

import copy
from typing import TYPE_CHECKING

import numpy as np

import planestress.analysis.solver as solver
import planestress.pre.boundary_condition as bc
from planestress.analysis.utils import dof_map
from planestress.post.results import Results


if TYPE_CHECKING:
    from planestress.pre.geometry import Geometry
    from planestress.pre.load_case import LoadCase
    from planestress.pre.mesh import Mesh


[docs] class PlaneStress: """Class for a plane-stress analysis. Attributes: geometry: ``Geometry`` object containing a meshed geometry. load_cases: List of load cases to analyse. mesh: ``Mesh`` object. """
[docs] def __init__( self, geometry: Geometry, load_cases: list[LoadCase], ) -> None: """Inits the PlaneStress class. Args: geometry: ``Geometry`` object containing a meshed geometry. load_cases: List of load cases to analyse. Raises: RuntimeError: If there is no mesh in the ``Geometry`` object. ValueError: If there is an invalid boundary condition in a load case. """ self.geometry = geometry self.load_cases = load_cases # check mesh has been created if len(self.geometry.mesh.nodes) < 1: raise RuntimeError( "No mesh detected, run Geometry.create_mesh() before creating a " "PlaneStress object." ) self.mesh: Mesh = self.geometry.mesh # assign tagged items to boundary conditions for load_case in self.load_cases: for b in load_case.boundary_conditions: # if a mesh tag hasn't been assigned yet if not hasattr(b, "mesh_tag"): # if the boundary condition relates to a node if isinstance(b, bc.NodeBoundaryCondition): b.mesh_tag = self.mesh.get_tagged_node_by_coordinates( x=b.point[0], y=b.point[1], ) # if the boundary condition relates to a line elif isinstance(b, bc.LineBoundaryCondition): b.mesh_tag = self.mesh.get_tagged_line_by_coordinates( point1=b.point1, point2=b.point2, ) else: raise ValueError(f"{b} is not a valid boundary condition.")
[docs] def solve(self) -> list[Results]: """Solves each load case. Returns: A list of ``Results`` objects for post-processing corresponding to each load case. """ # get number of degrees of freedom num_dofs = self.mesh.num_nodes() * 2 # allocate stiffness matrix and load vector k = np.zeros((num_dofs, num_dofs)) f = np.zeros(num_dofs) # allocate results results: list[Results] = [] # assemble stiffness matrix for el in self.mesh.elements: # get element stiffness matrix k_el = el.element_stiffness_matrix() # get element degrees of freedom el_dofs = dof_map(node_idxs=el.node_idxs) el_dofs_mat = np.ix_(el_dofs, el_dofs) # add element stiffness matrix to global stiffness matrix k[el_dofs_mat] += k_el # for each load case for lc in self.load_cases: # initialise modified stiffness matrix k_mod = copy.deepcopy(k) # assemble load vector for el in self.mesh.elements: # get element load vector f_el = el.element_load_vector() # get element degrees of freedom el_dofs = dof_map(node_idxs=el.node_idxs) # add element load vector to global load vector f[el_dofs] += f_el # apply boundary conditions for boundary_condition in lc.boundary_conditions: # apply boundary condition k_mod, f = boundary_condition.apply_bc(k=k_mod, f=f) # solve system u = solver.solve_direct(k=k_mod, f=f) # post-processing res = Results(plane_stress=self, u=u) res.calculate_node_forces(k=k) res.calculate_element_results(elements=self.mesh.elements) # add to results list results.append(res) return results