# -*- coding: utf-8 -*-"""Functions for creating mass and stiffness matrices for beam structures.Copyright 2022 National Technology & Engineering Solutions of Sandia,LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.Government retains certain rights in this software.This program is free software: you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation, either version 3 of the License, or(at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program. If not, see <https://www.gnu.org/licenses/>."""importnumpyasnpimportscipy.linalgasla
[docs]defbeamkm(node_coords,element_connectivity,bend_direction_1,ae,jg,ei1,ei2,mass_per_length,tmmi_per_length):'''Compute mass and stiffness matrices for the given beam model This function computes the mass and stiffness matrices for the beam model defined by the given nodal coordinates, element connectivity, orientation parameters, and elastic properties. Parameters ---------- node_coords : ndarray An nx3 array where node_coords[i,:] is the x,y,z coordinate of the i-th node. The number of rows is equal to the number of nodes in the model. element_connectivity : ndarray An mx2 array where element_connectivity[i,:] are the row indices of node_coords corresponding to the nodes in the i-th element. The number of rows is equal to the number of elements in the model bend_direction_1 : ndarray An mx3 array where bend_direction_1[i,:] is the x,y,z coordinates of a vector that defines the first bending axis of the beam. The second bending axis is computed from the cross product of the beam axis and the first bending axis. The number of rows is equal to the number of elements in the model ae : ndarray A length m array where ae[i] is the axial stiffness of element i. This can be computed from the area of the element (A) times the elastic modulus (E). The length of the array should be equal to the number of elements in the model. jg : ndarray A length m array where jg[i] is the torsional stiffness of element i. This can be computed from the torsional constant (J) times the shear modulus (G). The length of the array should be equal to the number of elements in the model. ei1 : ndarray A length m array where ei1[i] is the bending stiffness about axis 1 of element i. This can be computed from the second moment of area of the beam's cross section about bending axis 1 (I1) times the elastic modulus (E). The length of the array should be equal to the number of elements in the model. ei2 : ndarray A length m array where ei2[i] is the bending stiffness about axis 2 of element i. This can be computed from the second moment of area of the beam's cross section about bending axis 2 (I2) times the elastic modulus (E). The length of the array should be equal to the number of elements in the model. mass_per_length : ndarray A length m array where mass_per_length[i] is the linear density of element i. This can be computed from the density of the material times the cross sectional area of the beam. The length of the array should be equal to the number of elements in the model. tmmi_per_length : ndarray A length m array where tmmi_per_length[i] is the torsional mass moment of inertia per unit length of element i. This can be computed from the density of the material times the polar moment of inertia of the beam's cross-section. The length of the array should be equal to the number of elements in the model. Returns ------- K : ndarray The stiffness matrix of the beam model, which will have size 6nx6n where n is the number of nodes in the model. M : ndarray The mass matrix of the beam model, which will have size 6nx6n where n is the number of nodes in the model. Notes ----- The degrees of freedom in the mass and stiffness matrices will be ordered as follows: [disp_x_0, disp_y_0, disp_z_0, rot_x_0, rot_y_0, rot_z_0, disp_x_1, disp_y_1, disp_z_1, rot_x_1, rot_y_1, rot_z_1, ... disp_x_n, disp_y_n, disp_z_n, rot_x_n, rot_y_n, rot_z_n] '''# Validate inputstry:number_of_nodes=node_coords.shape[0]ifnode_coords.ndim!=2:raiseValueError('node_coords should be a 2D array with shape nx3 where n is the number of nodes in the model')ifnode_coords.shape[1]!=3:raiseValueError('node_coords should have shape nx3 where n is the number of nodes in the model')exceptAttributeError:raiseValueError('node_coords should be a numpy.ndarray')try:number_of_elements=element_connectivity.shape[0]ifelement_connectivity.ndim!=2:raiseValueError('element_connectivity should be a 2D array with shape nx2 where n is the number of elements in the model')ifelement_connectivity.shape[1]!=2:raiseValueError('element_connectivity should have shape nx2 where n is the number of elements in the model')exceptAttributeError:raiseValueError('element_connectivity should be a numpy.ndarray')# Check that the element properties that have been passed are the correct# shapetry:ifbend_direction_1.shape!=(number_of_elements,3):raiseValueError('bend_direction_1 should be a 2D array with shape (element_connectivity.shape[0],3)')exceptAttributeError:raiseValueError('bend_direction_1 should be a numpy.ndarray')forvalin[ae,jg,ei1,ei2,mass_per_length,tmmi_per_length]:try:ifval.shape!=(number_of_elements,):raiseValueError('Element Properties (ae,jg,ei1,ei2,mass_per_length,tmmi_per_length) should be 1D arrays with length element_connectivity.shape[0]')exceptAttributeError:raiseValueError('Element Properties (ae,jg,ei1,ei2,mass_per_length,tmmi_per_length) should be numpy.ndarray')# InitializeK=np.zeros((6*number_of_nodes,6*number_of_nodes))M=np.zeros((6*number_of_nodes,6*number_of_nodes))# Initialize Element MatricesPA=np.array([[1,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,1,0,0,0,0,0]])PT=np.array([[0,0,0,1,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,1,0,0]])PB1=np.array([[0,1,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,1,0,0,0,0,0,0],[0,0,0,0,0,0,0,1,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,1]])PB2=np.array([[0,0,1,0,0,0,0,0,0,0,0,0],[0,0,0,0,-1,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,1,0,0,0],[0,0,0,0,0,0,0,0,0,0,-1,0]])# Loop through all elements and assemble mass and stiffness matricesfori,(node_indices,bd1,AE,JG,EI1,EI2,rho,rhoT)inenumerate(zip(element_connectivity,bend_direction_1,ae,jg,ei1,ei2,mass_per_length,tmmi_per_length)):# Get element lengthdx=node_coords[node_indices[1],:]-node_coords[node_indices[0],:]L=np.linalg.norm(dx)# Compute Element MatricesKelemA=AE/L*np.array([[1,-1],[-1,1]])KelemT=JG/L*np.array([[1,-1],[-1,1]])KelemB1=EI1/L**3*np.array([[12,6*L,-12,6*L],[6*L,4*L**2,-6*L,2*L**2],[-12,-6*L,12,-6*L],[6*L,2*L**2,-6*L,4*L**2]])KelemB2=EI2/L**3*np.array([[12,6*L,-12,6*L],[6*L,4*L**2,-6*L,2*L**2],[-12,-6*L,12,-6*L],[6*L,2*L**2,-6*L,4*L**2]])Kelem=PA.T@KelemA@PA+PT.T@KelemT@PT+PB1.T@KelemB1@PB1+PB2.T@KelemB2@PB2MelemA=rho*L*np.array([[1/3,1/6],[1/6,1/3]])MelemT=rhoT*L*np.array([[1/3,1/6],[1/6,1/3]])MelemB1=rho*L*np.array([[13/35,11/210*L,9/70,-13/420*L],[11/210*L,1/105*L**2,13/420*L,-1/140*L**2],[9/70,13/420*L,13/35,-11/210*L],[-13/420*L,-1/140*L**2,-11/210*L,1/105*L**2]])MelemB2=rho*L*np.array([[13/35,11/210*L,9/70,-13/420*L],[11/210*L,1/105*L**2,13/420*L,-1/140*L**2],[9/70,13/420*L,13/35,-11/210*L],[-13/420*L,-1/140*L**2,-11/210*L,1/105*L**2]])Melem=PA.T@MelemA@PA+PT.T@MelemT@PT+PB1.T@MelemB1@PB1+PB2.T@MelemB2@PB2# Compute Directionsd0=dx/np.linalg.norm(dx)d1=bd1/np.linalg.norm(bd1)d2=np.cross(d0,d1)d2=d2/np.linalg.norm(d2)d1=np.cross(d2,d0)d1=d1/np.linalg.norm(d1)C=np.array([d0,d1,d2])A=np.zeros((12,12))A[0:3,0:3]=CA[3:6,3:6]=CA[6:9,6:9]=CA[9:12,9:12]=Ci1=np.arange(6*node_indices[0],6*(node_indices[0]+1))i2=np.arange(6*node_indices[1],6*(node_indices[1]+1))K[np.concatenate((i1,i2))[:,np.newaxis],np.concatenate((i1,i2))]+=A.T@Kelem@AM[np.concatenate((i1,i2))[:,np.newaxis],np.concatenate((i1,i2))]+=A.T@Melem@AreturnK,M
[docs]defrect_beam_props(E,rho,nu,b,h=None,nelem=None):'''Return beam keyword dictionary for a rectangular beam. Returns parameters that can be used in the beamkm function for a beam of uniform rectangular cross-section. Parameters ---------- E : float Young's Modulus rho : float Density nu : float Poisson's Ratio b : float Thickness of the beam in the direction of bend_direction_1 h : float Thickness of the beam in the direction of bend_direction_2. If it is not specified, a square cross section is assumed. nelem : int Number of elements. This will repeat the values in the dict nelem times, as is required by beamkm. If not specified, only a single value will be output for each entry in the dict. Returns ------- kwargs : dict A dictionary that can be used in beamkm(**kwargs). Notes ----- Steel : SI E = 200e9 # [N/m^2], nu = 0.25 # [-], rho = 7850 # [kg/m^3] Aluminum : SI E = 69e9 # [N/m^2], nu = 0.33 # [-], rho = 2830 # [kg/m^3] '''ifhisNone:h=bA=b*hI1=b*h**3/12I2=b**3*h/12G=E/(2*(1-nu))J=I1+I2Ixx_per_L=(1/12)*rho*b*h*(b**2+h**2)return_dict={}return_dict['ae']=A*Ereturn_dict['jg']=J*Greturn_dict['ei1']=E*I1return_dict['ei2']=E*I2return_dict['mass_per_length']=rho*Areturn_dict['tmmi_per_length']=Ixx_per_LifnelemisnotNone:forkeyinreturn_dict:return_dict[key]=return_dict[key]*np.ones((nelem,),float)returnreturn_dict
[docs]defbeamkm_2d(length,width,height,nnodes,E,rho,nu,axial=True):'''A simplified 2D beam with uniform cross section and linear materials Parameters ---------- length : float Length of the beam width : float Width of the beam, perpendicular to the bending plane height : float Height of the beam, in the bending direction. nnodes : int Number of nodes in the beam (number of elements + 1) E : float Elastic modulus rho : float Density nu : float Poisson's Ratio axial : bool Keep axial motions (default = True) Returns ------- K : np.ndarray Stiffness matrix of the beam M : np.ndarray Mass matrix of the beam Notes ----- Dof ordering is axial translation, bending translation, and bending rotation repeated for each node. Axial is X, bending is Z, and rotation is about Y. If axial == False, Dof ordering is bending translation, bending rotation. '''node_coords=np.linspace(0,length,nnodes)coords_3d=np.zeros((nnodes,3))coords_3d[:,0]=node_coordsnode_indices=np.arange(nnodes)elems=np.array((node_indices[:-1],node_indices[1:])).Tbend_direction_1=np.zeros((len(elems),3))bend_direction_1[:,2]=1props=rect_beam_props(E,rho,nu,width,height,nelem=len(elems))K,M=beamkm(coords_3d,elems,bend_direction_1,**props)# Eliminate everything except in bend direction 1. Keep translation in X, Z,# and rotation about yifaxial:keep_dofs=([0,2,4]+np.arange(nnodes)[:,np.newaxis]*6).flatten()else:keep_dofs=([2,4]+np.arange(nnodes)[:,np.newaxis]*6).flatten()K=K[keep_dofs[:,np.newaxis],keep_dofs[np.newaxis,:]]M=M[keep_dofs[:,np.newaxis],keep_dofs[np.newaxis,:]]return(K,M)