我为轴向加载板编写了一个 2D 有限元程序,但结果出乎意料

计算科学 有限元 Python 固体力学
2021-12-14 11:04:24

TLDR:我使用 Python 编写了一个使用“恒定应变三角形”的 2D 有限元程序,并且我的光束一直略微向上而不是笔直的侧面(就像力一样)。我是 FEA 的新手,几乎没有线性代数经验,所以我没有洞察力知道我是否做错了什么。

因此,就目前而言,该程序旨在模拟由于分布式外力而处于张力状态的薄板(或梁)中节点的应变和位移,即看起来像这样的配置(图像中的力显然不是分布式但你明白了):

![文本](在此处输入图像描述

我使用了恒定应变三角形方法,因为当板不是简单的矩形时,三角形元素将方便项目的下一部分。我的主要资源是这里的讲座和示例(与这里的信息几乎相同)。

我运行了程序,每个节点在 x 方向的位移似乎是合理的,但是每个节点似乎都想“向上”漂移,而不是由于泊松效应而向内弯曲:(请 糟糕的自制图形 原谅我自制的图形)。如您所见,施加力的光束向上倾斜,我觉得这很奇怪。它对不同高度/宽度的梁做同样的事情,如果我添加更多节点。(见编辑)

一般来说,我是 FEA 的新手(甚至没有使用过商业软件包),而且我在线性代数方面的经验非常有限。我做了什么导致了这个?

  • 我知道 CST 显然不如其他方法准确,但这会导致这个问题吗?
  • 我读到(事后)三角形元素应该尽可能等边,所以我有问题,因为我的元素是直角三角形吗?
  • 牵引力与此有关吗?这个词在我阅读的讲座中不断出现,但我不完全理解它的含义。
  • 我还应该研究什么?

提前感谢任何对此进行调查的人。我试图在发布之前阅读该问题,但我空无一物,所以我想我会尝试在这里发布。任何帮助表示赞赏!

编辑:我通过调整我的网格算法成功解决了这个问题,以便重复模式被镜像,如检查答案中所建议的那样。此外,似乎腿长度更近的元素效果更好。我的程序的输出如下所示:板现在关于中性轴对称地向内弯曲。我没有像我最初提到的那样长光束的图形,但我试过了,它也有效。感谢所有有建议的人! 在此处输入图像描述

原始代码(Python):

    import graphics as gr
import numpy as np
import math
import matplotlib.pyplot as plt

#constants
P=10000.0 #Load (Newtons)
W=800  #Width of Beam (mm)
H=50   #Height of Beam (mm)
Z=0.05  #Thickness of Beam (mm)
E_beam=10**5    #Beam Elastic Modulus
pr_beam=0.45    #Poissons Ratio of the beam
nds_x=4  #number of nodes extending in the horizontal direction
nds_y=3   #number of nodes extending in the vertical direction

nnds=nds_x*nds_y    #total number of nodes
ndof=nnds*2     #total number of degrees of freedom in the whole system
nele=2*(nds_x-1)*(nds_y-1)    #total number of elements
eper=2*(nds_x-1) #elements per element row

ndcoor=np.zeros((nnds,2))   #Table which stores the INITIAL coordinates (in terms of mm) for each node
nd_rnc=np.zeros((nnds,2))   #Table which stores the 'row and column' coordinates for each node
nds_in_ele=np.zeros((nele, 3))  #the nodes which comprise each element
glbStiff=np.zeros((ndof,ndof))  #global stiffness matrix (GSM)
lst_wallnds=[]  #List of nodes (indices) which are coincident with the rigid wall on the left
lst_wallnds.clear()
lst_walldofs=[]  #dofs indices of nodes coincident with the rigid wall
lst_walldofs.clear()
lst_endnds=[]   #nodes on the free edge of the beam
lst_endnds.clear()

nnf_msg='Node not found!'
#Function 'node_by_rnc' returns the index of the node which has the same row and column as the ones input (in_row, in_col)
def node_by_rnc(in_row, in_col, start_mrow):    #'start_mrow' == where the func starts searching (for efficiency)
    run=True
    row=start_mrow
    while run==True:
        if row>nnds-1:
            run=False
        elif nd_rnc[row][0]==in_row and nd_rnc[row][1]==in_col:
            run=False
        else:
            row=row+1
    if row>nnds-1:
        return nnf_msg  #returns error message
    else:
        return row
    
#Function 'add_to_glbStiff' takes a local stiffness matrix and adds the value of each 'cell' to the corrosponding cell in the GSM
def add_to_glbStiff(in_mtrx, nd_a, nd_b, nd_c): 
    global glbStiff
    #First column in local stiffness matrix (LSM) is the x-DOF of Node A, second is the y-DOF of Node A, third is the x-DOF of Node B, etc. (same system for rows; the matrix is symmetric)
    dofs=[2*nd_a, 2*nd_a+1, 2*nd_b, 2*nd_b+1, 2*nd_c, 2*nd_c+1]    #x-DOF for a node == 2*[index of the node]; y-DOF for node == 2*[node index]+1
    for r in range(0,6):    #LSMs are always 6x6
        for c in range(0,6):
            gr=dofs[r]  #gr == row in global stiffness matrix
            gc=dofs[c]  #gc == column in global stiffness matrix
            glbStiff[gr][gc]=glbStiff[gr][gc]+in_mtrx[r][c]     #Add the value of the LSM 'cell' to what's already in the corrosponding GSM cell
            
for n in range(0,nnds): #puts node coordinates and rnc indices into matrix
    row=n//nds_x
    col=n%nds_x
    nd_rnc[n][0]=row
    nd_rnc[n][1]=col
    ndcoor[n][0]=col*(W/(nds_x-1))
    ndcoor[n][1]=row*(H/(nds_y-1))
    if col==0:
        lst_wallnds.append(n)
    elif col==nds_x-1:
        lst_endnds.append(n)
        
for e in range(0,nele): #FOR EVERY ELEMENT IN THE SYSTEM...
    #...DETERMINE NODES WHICH COMPRISE THE ELEMENT
    erow=e//eper    #erow == the row which element 'e' is on
    eor=e%eper  #element number on row (i.e. eor==0 means the element is attached to rigid wall)
    if eor%2==0:  #downwards-facing triangle
        nd_a_col=eor/2
        nd_b_col=eor/2
        nd_c_col=(eor/2)+1
        nd_a=node_by_rnc(erow, nd_a_col, nds_x*erow)
        nd_b=node_by_rnc(erow+1, nd_b_col, nds_x*erow)
        nd_c=node_by_rnc(erow, nd_c_col, nds_x*erow)
    else:   #upwards-facing triangle
        nd_a_col=(eor//2)+1
        nd_b_col=(eor//2)+1
        nd_c_col=eor//2
        nd_a=node_by_rnc(erow+1, nd_a_col, nds_x*(erow+1))
        nd_b=node_by_rnc(erow, nd_b_col, nds_x*erow)
        nd_c=node_by_rnc(erow+1, nd_c_col, nds_x*(erow+1))
    if nd_a!=nnf_msg and nd_b!=nnf_msg and nd_c!=nnf_msg:   #assign matrix element values if no error
        nds_in_ele[e][0]=nd_a
        nds_in_ele[e][1]=nd_b
        nds_in_ele[e][2]=nd_c
    else:   #raise error
        print(nnf_msg)
    #...BUILD LOCAL STIFFNESS MATRIX
    y_bc=ndcoor[nd_b][1]-ndcoor[nd_c][1]    #used "a, b, c" instead of "1, 2, 3" like the the example PDF; ex: 'y_bc' == 'y_23' == y_2 - y_3 
    y_ca=ndcoor[nd_c][1]-ndcoor[nd_a][1]
    y_ab=ndcoor[nd_a][1]-ndcoor[nd_b][1]
    x_cb=ndcoor[nd_c][0]-ndcoor[nd_b][0]
    x_ac=ndcoor[nd_a][0]-ndcoor[nd_c][0]
    x_ba=ndcoor[nd_b][0]-ndcoor[nd_a][0]
    x_bc=ndcoor[nd_b][0]-ndcoor[nd_c][0]
    y_ac=ndcoor[nd_a][1]-ndcoor[nd_c][1]
    detJ=x_ac*y_bc-y_ac*x_bc
    Ae=0.5*abs(detJ)
    D=(E_beam/(1.0-(pr_beam**2.0)))*np.array([[1.0, pr_beam, 0.0],[pr_beam, 1.0, 0.0],[0.0, 0.0, (1-pr_beam)/2.0]])
    B=(1.0/detJ)*np.array([[y_bc, 0.0, y_ca, 0.0, y_ab, 0.0],[0.0, x_cb, 0.0, x_ac, 0.0, x_ba],[x_cb, y_bc, x_ac, y_ca, x_ba, y_ab]])
    BT=np.transpose(B)
    locStiff=Z*Ae*np.matmul(np.matmul(BT,D),B)
    #...ADD TO GLOBAL STIFFNESS MATRIX
    add_to_glbStiff(locStiff, nd_a, nd_b, nd_c)

#Deleting contrained DOFs from the GSM
nwnds=len(lst_wallnds)  #number of wall nodes
for w in range(0,nwnds):    #Populates list of all DOFs which have 0 displacement (the corrosponding rows and columns get completely erased from GSM)
    lst_walldofs.append(2*lst_wallnds[w])
    lst_walldofs.append(2*lst_wallnds[w]+1)

glbStiff=np.delete(np.delete(glbStiff, lst_walldofs, 0), lst_walldofs, 1)   #delete the rows and columns corrosponding to the DOFs that are fixed
#Keeping track of what rows (and columns) in the 'new' GSM corrospond to which DOF indices
lst_frdofs=np.zeros(ndof)   #lst_frdofs = List of "Free" DOFS i.e. DOFs NOT coincident with the wall
for d in range(0,ndof): lst_frdofs[d]=d   #Before deleting fixed DOFs: [the global index for each DOF] == [the corrosponding row/column in the GSM]...
lst_frdofs=np.delete(lst_frdofs,lst_walldofs)   #...after deleting the fixed DOF rows/columns: 'lst_frdofs' stores the global index for each DOF in the row corrosponding the the row in the GSM

#Specifying the Load
lpn=P/nds_y     #Load per Node (on free end)
mtrx_P=np.zeros(ndof)   #The vector which stores the input force values for each DOF
for en in range(0, len(lst_endnds)): mtrx_P[2*lst_endnds[en]]=lpn   #Applies a force of 'lpn' to each node on the free end in the X-direction
mtrx_P=np.delete(mtrx_P, lst_walldofs)  #Deletes the rows corrosponding to the DOFs that were deleted from the GSM

#Solve for q for each DOF           
mtrx_q=np.linalg.solve(glbStiff, mtrx_P)

#Determining the final locations of each node
nd_disp=np.zeros((nnds,2))  #Tabulating how much each node moved in the x and y directions
for g in range(0,len(lst_frdofs)):
    gdof=lst_frdofs[g]
    if gdof%2==0:   #even global DOF index -> displacement in the x-direction
        nd=int(gdof/2)  #nd == node which the DOF (gdof) belongs to
        nd_disp[nd][0]=mtrx_q[g]    #add the displacement to the table/matrix
    else:   #odd global DOF index -> displacement in the y-direction
        nd=int((gdof-1)/2)
        nd_disp[nd][1]=mtrx_q[g]
fnl_ndcoor=np.add(ndcoor, nd_disp)  #[Final coordinates (in terms of mm) for each node] = [Original coordinates for that node] + [the displacement of the node]
3个回答

主要问题是 CST 近似具有不同的位移响应,具体取决于网格单元相对于施加的单元载荷的方向(您只能在三角形的节点上施加力,因此必须近似分布载荷)。

您可以通过仅查看连接到墙壁的单个三角形并施加完美的水平力(手工绘制并夸张)来看到此效果:

在此处输入图像描述

添加第二个三角形,没有什么“强迫”三角形向上压缩,所以它会在 y 中保持向下位移。

在此处输入图像描述

只要您继续细分(重复)此模式以建立您的光束,就不会消除这种不对称性(尽管我认为增加网格分辨率会降低效果,因为施加到任何单个元素的实际力都会减少)。

但是,您可以决定镜像元素以利用此效果可以自行抵消的事实:

在此处输入图像描述

在这里,您可以看到上半部分倾向于尝试将顶部边缘向下移动,而底部则希望将底部边缘向上移动,而中线保持不动。细分这个 2x2 三角形元素模式可以让您产生更加各向同性的行为,但是您当然应该开始想知道为什么不一开始就使用矩形元素。

幸运的是,您不一定需要这种精确的模式来减少这种各向异性;元素的相对“随机”方向可以抵消大部分这种效果,但是这样做的缺点是你需要很多元素来确保与元素数量相比,少数不抵消的元素很小元素的邻域。

这是您想要测试的方式,您只需要网格中的两个元素。你想定义你的左 BC,这样它就会重现一个恒定的压力状态,如下所示:假设u是 x 方向的位移和vy方向的位移,设置u=0在这条边上的两个节点和v=0在这条边的底部节点。右边缘的两个节点的 x 方向力为P/2. 数值解应该给出这个简单平面应力问题的精确解,如下所示:如果L,h, 和t是带材的长度、高度和厚度,右边缘的 u 位移由下式给出uright=PL/(Eht)并且顶部边缘的 v 位移由下式给出νuright(Eν是材料常数)。

需要有限元公式准确地表示这种恒定应力状态,以便在细化网格时得到精确的解。恒应力三角形单元满足这一要求。

我认为@helloworld922 的回答具有误导性。答案中显示的第一张图片似乎是泊松效应的影响,即由于施加在另一个方向上的载荷而在一个方向上收缩。

如果要在模拟中获得恒定应力状态,则需要更改所应用的边界条件,即:

  • 左侧的所有节点都应在水平方向上进行约束;
  • 只有中间的节点在垂直方向上应该被约束;
  • 右边的力量不相等,因为相邻元素的贡献会使中间的力量更大。

使用与您相同的材料属性,总负载为 10000(拆分为 2500、5000 和 2500),我得到以下

在此处输入图像描述

我使用了 750 mm 长的网格而不是 800 来获得更好的数字。出于可视化目的,位移乘以 10。