Numpy 有限元计算 +OpenGL 云图显示

2019-08-14 17:23:19 浏览数 (1)

3年前学习Matlab时写的一段简单的有限元程序,使用三角形平面应力单元,模拟一块左边固定,右下角受拉的薄板的位移和应力分布情况。所有的前处理、刚度矩阵和外力矩阵的组装,以及求解和后处理均在程序内部完成。当时求解结果和ANSYS 做过比对,基本完全一致。

Matlab 版本的Von Mises 应力云图(变形500倍放大):

现已用python改写,初步完成。位移基本一致。由于精度问题,以及由于应力是变形的二次计算结果,所以应力分布略有不同。以后有空的话会继续完善。

python版本的Von Mises 应力云图(变形1000倍放大):

代码如下:

代码语言:javascript复制
from numpy import *
from numpy.linalg import det, solve

#由几年前写的matlab代码翻译而来,author:wang_sp
# 注意matlab矩阵索引都是从1开始,而python、numpy索引都是从0 开始
#还有其它诸多不同
#单位体系 N,mm, ton


#前处理
# 材料 material: ID,TYEP ,parameters(eg. E,nuxy,dens...)
MAT=mat([[1,1], [2,2]])
# 材料属性 materaial parameters for type 1:matID, E,nuxy,dens
#碳钢和铝合金.
MatPrmt= mat([[1,200000,0.3,7.85E-9], [2,69000,0.31,2.75E-9]],dtype =double)
#单元 element properity: ID, TYPE
PRPT=mat([[1,1], [2,1]])
#element parameters for type 1:PRPT ID,t(thickness)
PrptPrmt=mat([[1,1.0], [2,0.5]])
#Nodes info.: ID,x,y,z. #Node Id must start from 1,the step is 1.
#40个节点的简单FEM 模型
NODE= mat([[1,0,-10,0],
           [2,0, -5,0],
           [3,0,0,0],
           [4,0,5,0],
           [5,0, 10,0],
           [6,5,-10,0],
           [7,5, -5,0],
           [8,5,0,0],
           [9,5,5,0],
           [10,5,10,0],
           [11,10,-10,0],
           [12,10, -5,0],
           [13,10,0,0],
           [14,10,5,0],
           [15,10, 10,0],
            [16,15,-10,0],
            [17,15, -5,0],
            [18,15,0,0],
            [19,15,5,0],
            [20,15, 10,0],
            [21,20,-10,0],
            [22,20, -5,0],
            [23,20,0,0],
            [24,20,5,0],
            [25,20, 10,0],
            [26,25,-10,0],
            [27,25, -5,0],
            [28,25,0,0],
            [29,25,5,0],
            [30,25, 10,0],
            [31,30,-10,0],
            [32,30, -5,0],
            [33,30,0,0],
            [34,30,5,0],
            [35,30, 10,0],
            [36,35,-10,0],
            [37,35, -5,0],
            [38,35,0,0],
            [39,35,5,0],
            [40,35, 10,0]],
          dtype=double)
node_num = NODE.shape[0] #节点数量,40
#Element ID,MAT ID,TYPE ID,3 Nodes' ID
#56个平面应力单元
ELEM=mat([[1,1,1,1,6,7],
           [2,1,1,1,7,2],
           [3,1,1,2,7,8],
           [4,1,1,2,8,3],
           [5,1,1,3,8,4],
           [6,1,1,4,8,9],
           [7,1,1,4,9,5],
           [8,1,1,5,9,10],
           [9,1,1,6,11,7],
           [10,1,1,7,11,12],
           [11,1,1,7,12,13],
           [12,1,1,7,13,8],
           [13,1,1,8,13,9],
           [14,1,1,9,13,14],
           [15,1,1,9,14,15],
           [16,1,1,9,15,10],
           [17,1,1,11,16,17],
           [18,1,1,11,17,12],
           [19,1,1,12,17,18],
           [20,1,1,12,18,13],
           [21,1,1,13,18,14],
           [22,1,1,14,18,19],
           [23,1,1,14,19,15],
           [24,1,1,15,19,20],
           [25,1,1,16,21,17],
           [26,1,1,17,21,22],
           [27,1,1,17,22,23],
           [28,1,1,17,23,18],
           [29,1,1,18,23,19],
           [30,1,1,19,23,24],
           [31,1,1,19,24,25],
           [32,1,1,19,25,20],
           [33,1,1,21,26,27],
           [34,1,1,21,27,22],
           [35,1,1,22,27,28],
           [36,1,1,22,28,23],
           [37,1,1,23,28,24],
           [38,1,1,24,28,29],
           [39,1,1,24,29,25],
           [40,1,1,25,29,30],
           [41,1,1,26,31,27],
           [42,1,1,27,31,32],
           [43,1,1,27,32,33],
           [44,1,1,27,33,28],
           [45,1,1,28,33,29],
           [46,1,1,29,33,34],
           [47,1,1,29,34,35],
           [48,1,1,29,35,30],
           [49,1,1,31,36,37],
           [50,1,1,31,37,32],
           [51,1,1,32,37,38],
           [52,1,1,32,38,33],
           [53,1,1,33,38,34],
           [54,1,1,34,38,39],
           [55,1,1,34,39,35],
           [56,1,1,35,39,40]],
         dtype=integer)
elem_num = ELEM.shape[0] #单元数量,56
#Boundary conditions
#外力 Force矩阵
F = mat(zeros((node_num*2,1)), dtype=double)
'''
%F=spalloc(node_num*2,1,node_num*2);
%F(2*(5-1) 2)=10; % Fy on node 5
%F(2*(6-1) 1)=0; % Fx on node 6
%F(2*(6-1) 2)=10; % Fy on node 6
'''
F[2*(36-1) 1]=-10 # Fy on node 36,向下 10N

#节点自由度 dof constrain
#node id, dof id(1 for x,2 for y...),dof value
CONS=mat([[1,1,0],
    [1,2,0],
    [2,1,0],
    [2,2,0],
    [3,1,0],
    [3,2,0],
    [4,1,0],
    [4,2,0],
    [5,1,0],
    [5,2,0]])


#生成有限元(FEM) 数学模型:
#K=spalloc(node_num*2,node_num*2,node_num*2)  #matlab%(total nodes num. x dofs_per_node)'''
K= mat(zeros((node_num*2,node_num*2)), dtype=double)#初始化总刚度矩阵
B = zeros((elem_num,3,6), dtype=double) #  单元应变矩阵 ,全部单元的都存到数组
#各个单元的节点坐标
X=mat(zeros((3,elem_num)), dtype=double)
Y=mat(zeros((3,elem_num)),dtype=double)
#遍历单元
for ii in range(elem_num):
    #材料属性
    if ELEM[ii, 1] == 1: #to be updated
        E = MatPrmt[0,1] #弹性模量
        nuxy = MatPrmt[0,3] #泊松比
        dens=MatPrmt[0,3] #密度
    if ELEM[ii,2]==1:    # to be updated
        t = PrptPrmt[0,1] # thickness for plane stree elem
    #弹性矩阵, 对称
    D = E/(1-nuxy**2)* mat([[1,nuxy,0],[nuxy,1,0],[0,0,(1-nuxy)/2]])
    #% nodes contained in the current element.单元的三个节点:
    i = ELEM[ii,3]#  Node ID
    j = ELEM[ii,4]# Node ID
    m = ELEM[ii,5]# Node ID
   
    #for jj=1:node_num #遍历节点,找出单元对应3个节点
    # coordiates of the nodes contained in the current element
    flagi=0
    flagj=0
    flagm=0
    for jj in range(node_num):
        if NODE[jj,0]==i:
            xi=NODE[jj,1]
            yi=NODE[jj,2]
            #zi=NODE[jj,3]
            flagi=1
            continue
        elif NODE[jj,0]==j:
            xj=NODE[jj,1]
            yj=NODE[jj,2]
            # zj=NODE[jj,3]
            flagj=1
            continue
        elif NODE[jj,0]==m:
            xm=NODE[jj,1]
            ym=NODE[jj,2]
            #zm=NODE[jj,3]
            flagm=1
            continue
        else: pass
       
        if flagi*flagj*flagm==1:
           break
    X[: ,ii ] = mat([[xi], [xj], [xm]])#X坐标
    Y[: ,ii ] = mat([[yi], [yj], [ym]])#Y坐标
    A=0.5*det(mat([[1,xi,yi],[1,xj,yj],[1,xm,ym]]))# 三角形(单元)的面积( Area for triangle)
    #单元应变矩阵Be=[Bi,Bj,Bm]
    Bi=mat([[yj-ym,0], [0,-xj xm], [-xj xm,yj-ym]])/(2*A)
    Bj=mat([[ym-yi,0], [0,-xm xi], [-xm xi,ym-yi]])/(2*A)
    Bm=mat([[yi-yj,0], [0,-xi xj], [-xi xj,yi-yj]])/(2*A)
   
    #分块矩阵组装
    B[ii] = bmat("Bi Bj Bm")#仍是数组
   
    #单元刚度矩阵Ke=[Kii,Kij,Kim;Kji,Kjj,Kjm;Kmi,Kmj,Kmm]
    #更新总刚度矩阵
   
    Kii = Bi.T * D * Bi *A*t
    #K(2*i-1:2*i,2*i-1:2*i)=K(2*i-1:2*i,2*i-1:2*i) Kii;%matlab 包含冒号后面那个,numpy不含
    K[2*i-2 : 2*i , 2*i-2 : 2*i]  = Kii
    Kij=Bi.T * D * Bj * A * t
    #K(2*i-1:2*i,2*j-1:2*j)=K(2*i-1:2*i,2*j-1:2*j) Kij;
    K[2*i-2 : 2*i, 2*j-2 : 2*j]  = Kij
    Kim=Bi.T* D * Bm * A * t
    #    K(2*i-1:2*i,2*m-1:2*m)=K(2*i-1:2*i,2*m-1:2*m) Kim;
    K[2*i -2 : 2*i, 2*m-2: 2*m]  = Kim
    Kji=Bj.T*D*Bi*A*t
    #    K(2*j-1:2*j,2*i-1:2*i)=K(2*j-1:2*j,2*i-1:2*i) Kji;
    K[2*j-2 :2*j, 2*i-2: 2*i]  = Kji
   
    Kjj=Bj.T*D*Bj*A*t
    #    K(2*j-1:2*j,2*j-1:2*j)=K(2*j-1:2*j,2*j-1:2*j) Kjj;
    K[2*j - 2 : 2*j, 2*j -2: 2*j]  = Kjj
   
    Kjm=Bj.T*D*Bm*A*t
    #    K(2*j-1:2*j,2*m-1:2*m)=K(2*j-1:2*j,2*m-1:2*m) Kjm;
    K[2*j-2: 2*j, 2*m -2: 2*m]  = Kjm
    Kmi=Bm.T*D*Bi*A*t
    #    K(2*m-1:2*m,2*i-1:2*i)=K(2*m-1:2*m,2*i-1:2*i) Kmi;
    K[2*m-2 : 2*m, 2*i -2 : 2*i]  = Kmi
    Kmj=Bm.T*D*Bj*A*t
    #    K(2*m-1:2*m,2*j-1:2*j)=K(2*m-1:2*m,2*j-1:2*j) Kmj;
    K[2*m-2:2*m,2*j-2:2*j]  = Kmj
    Kmm=Bm.T*D*Bm*A*t
    #    K(2*m-1:2*m,2*m-1:2*m)=K(2*m-1:2*m,2*m-1:2*m) Kmm;
    K[2*m-2:2*m,2*m-2:2*m]  = Kmm;

#% Kff updated from K as per Boundary conditions
#将边界条件(力,位移约束)更新到刚度矩阵;更新外力矩阵
Kff=K[:,:]
BigNum = 1.0e150#大数法
cr = CONS.shape[0]
#遍历约束节点
for ii in range(cr):
    jj = 2*(CONS[ii,0]-1) CONS[ii,1]
    Kff[jj-1, jj-1] *= BigNum
    F[jj-1] = CONS[ii-1, 2]*Kff[jj-1, jj-1]*BigNum
# SOLVE
# 求解 位移矩阵Deformation Matrix Delta
Delta= solve(Kff, F)#位移矩阵

#Delta value from solve replaced by the iputed constrain value
#even though there is nearly no difference
#遍历节点,尽管几乎没有差别(计算精度问题)
#还是将位移在边界节点上的值用输入的约束值修正
for ii in range(cr):
    jj = 2*(CONS[ii,0]-1) CONS[ii,1]
    Delta[jj-1] = CONS[ii,2]
print("位移矩阵:"); print(Delta)


#后处理
#应变矩阵
Epsilon = mat(zeros((4,node_num)),dtype=double)#% used to store node strain(sum at the current loop)
#应力矩阵
Sigma=mat(zeros((3,node_num)),dtype=double)#% used to store node stress(sum at the current loop)
cnte = mat(zeros((1,node_num)),dtype=integer)# %count of elements surrouding each node
#遍历单元
for ii in range(elem_num):
    #材料属性
    if ELEM[ii, 1] == 1: #to be updated
        E = MatPrmt[0,1] #弹性模量
        nuxy = MatPrmt[0,3] #泊松比
        dens=MatPrmt[0,3] #密度
    if ELEM[ii,2]==1:    # to be updated
        t = PrptPrmt[0,1] # thickness for plane stree elem
    #弹性矩阵, 对称
    D = E/(1-nuxy**2)* mat([[1,nuxy,0],[nuxy,1,0],[0,0,(1-nuxy)/2]])
    #% nodes contained in the current element.单元的三个节点:
    i = ELEM[ii,3]#  Node ID
    j = ELEM[ii,4]# Node ID
    m = ELEM[ii,5]# Node ID
    #单元位移矩阵
    #print(Delta[2*(i-1),0])
    Delta_e=mat([[Delta[2*(i-1),0]],[Delta[2*(i-1) 1, 0]], [Delta[2*(j-1),0]],[Delta[2*(j-1) 1,0]],[Delta[2*(m-1),0]],[Delta[2*(m-1) 1,0]]])
    #print(Delta_e)
    #单元应变矩阵,ex,elem_num,rxy
    Strain_e= mat(B[ii])*Delta_e; # without ez
    #单元应力矩阵
    Stress_e= D * Strain_e
   
    # 添加Z向应变,with ez(strain of z direction)
    Strain_e = mat([[Strain_e[0,0]], [Strain_e[1,0]], [Strain_e[2,0]] , [-nuxy/E*(Stress_e[0,0] Stress_e[1,0])]])
    #先求和,后面平均
    Epsilon[:,i-1]  = Strain_e
    Epsilon[:,j-1]  = Strain_e
    Epsilon[:,m-1]  = Strain_e
    Sigma[:,i-1]  = Stress_e
    Sigma[:,j-1]  = Stress_e
    Sigma[:,m-1]  = Stress_e
    cnte[:,i-1]  = 1
    cnte[:,j-1]  = 1
    cnte[:,m-1]  = 1
#
#Epsilon=Epsilon./repmat(cnte,4,1)
Epsilon  = Epsilon/ tile(cnte,(4,1))
Sigma = Sigma / tile(cnte,(3,1))
#之前位移矩阵内数的排列次序是节点1 x向位移,节点1 y向位移; 节点2....
#提高可读性:现在每个节点Ux,Uy 显示在同一行
Delta = Delta.reshape((-1,2))
print("位移矩阵(unit: mm):"); print(Delta)
Epsilon = Epsilon.T # x,y,xy,z 应变
Sigma = Sigma.T
#F=(reshape(F,2,node_num))';% more readable; Fx,Fy for one node listed on one row
#postprocess
# total Deformation 总位移
Delta_sum = sqrt(array(Delta[:, 0])**2   array(Delta[:, 1])**2)
Sigma1 = (Sigma[:, 0]   Sigma[:,1])/2.0   sqrt( array(Sigma[:, 0] - Sigma[:,1]/2.0)**2  array(Sigma[:,2])**2)
Sigma2 = (Sigma[:, 0]   Sigma[:,1])/2.0 - sqrt( array(Sigma[:, 0] - Sigma[:,1]/2.0)**2  array(Sigma[:,2])**2)
#冯米塞斯应力 Von Mises Stress #by Node
Sigma_eqv = sqrt(array(Sigma1)**2   array(Sigma2)**2   array(Sigma1 -Sigma2)**2/2.0)
print(Sigma_eqv.shape) #(40,1)
print(Sigma_eqv[1,0])

X_delta = zeros((3,elem_num), dtype=double)
Y_delta = zeros((3,elem_num), dtype=double)
for ii in range(elem_num):
    i = ELEM[ii,3]#  Node ID
    j = ELEM[ii,4]# Node ID
    m = ELEM[ii,5]# Node ID
    X_delta[:,ii] = Delta[[i-1 ,j-1 ,m-1],0].reshape(3) #for one  element
    Y_delta[:,ii] = Delta[[i-1 ,j-1 ,m-1],1].reshape(3) #for one  element


#结果可视化
scale = 1000 #变形量放大系数
X_new = X scale*X_delta
Y_new = Y scale*Y_delta
def num2RGB(x,LSL=0, USL=1.0):
    r=(x-LSL)/(USL-LSL)
    if r>1:
        return (1, 1, 1)
    elif r>=0.75:
        return (1, 1*(1-r)*4, 0)
    elif r>=0.5:
        return (1*(r-0.5)*4, 1, 0)
    elif r>=0.25:
        return (0, 1, 1*(0.5-r)*4)
    elif r>=0:
        return (0, 1*r*4, 1)
    else:
        return (0.0,0.0,0.0)
   
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtOpenGL import QGLWidget
from OpenGL import GL
class GLWidget(QGLWidget):
    def __init__(self, parent =None):
        super(GLWidget, self).__init__(parent)
    def initializeGL(self):
        self.qglClearColor(QColor("black")) #背景色
        #GL.glClearDepth(1.0)# Enables Clearing Of The Depth Buffer
        GL.glShadeModel(GL.GL_SMOOTH) #!颜色平滑渲染
        #GL.glDepthFunc(GL.GL_LESS)      # The Type Of Depth Test To Do
        #GL.glEnable(GL.GL_DEPTH_TEST)
        self.object = self.makeObject()
       
    def paintGL(self):
        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
        GL.glLoadIdentity()# Reset The Projection Matrix
        #GL.glTranslatef(0, 0.0, 0)#平移
        #GL.glRotated(0.5, 1.0, 0.0, 0.0)#旋转
       
        GL.glCallList(self.object)
    def resizeGL(self, width, height):
        side = min(width, height)
        GL.glViewport((width - side) // 2, (height - side) // 2, side, side)#保持图形的长宽比
        #GL.glViewport(50,50,500,500)
        GL.glMatrixMode(GL.GL_PROJECTION)
        GL.glLoadIdentity()# Reset The Projection Matrix
        #GL.glOrtho(-0.5,  0.5,  0.5, -0.5, 4.0, 15.0)
        GL.glMatrixMode(GL.GL_MODELVIEW)
    def makeObject(self):
        genList = GL.glGenLists(1)
        GL.glNewList(genList, GL.GL_COMPILE)
        #位移和应力的可视化
        global X_new, Y_new,elem_num, ELEM, Sigma_eqv
        XnewMin = X_new.min()
        YnewMin = Y_new.min()
        XnewMax = X_new.max()
        YnewMax = Y_new.max()
        L = -0.8 ; R = 0.8
        RL = R-L

        #stress
        stressMin = Sigma_eqv.min()
        stressMax = Sigma_eqv.max()
       
        if XnewMax - XnewMin >= YnewMax - YnewMin:# X方向模型总长度更长(else则写法类似,暂略过)
            Span = XnewMax - XnewMin
            for i in range(elem_num):
                # Draw a 2d element
                GL.glBegin(GL.GL_POLYGON)# Start drawing a polygon
               
                for j in range(3):
                    #For colors
                    nodeID = int(ELEM[i,3 j])
                    #print(nodeID)
                    stress = Sigma_eqv[nodeID-1, 0] # Node ID 从1开始,但是索引从0开始
                    #print(Sigma_eqv[1, 0])
                    r,g,b = num2RGB(stress, stressMin,stressMax)
                    GL.glColor3f(r, g, b)
                   
                    x = (X_new[j,i] - XnewMin) /Span *RL   L
                    y = (Y_new[j,i] - YnewMin) /Span *RL   L
                    GL.glVertex2f(x, y)
                   
                GL.glEnd()
       
        GL.glEndList()
        return genList
 
    def minimumSizeHint(self):
        return QSize(100, 100)
    #def sizeHint(self):
        #return QSize(800, 800)
   
class MainWindow(QMainWindow):
    def __init__(self):       
        super().__init__()
        print("oo")
        self.setCentralWidget(GLWidget())
        global scale
        self.setWindowTitle("FEM ,plane stress elements [总位移(%d 倍)和Von Mises 应力云图]" % scale )
        self.resize(800, 800)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWin = MainWindow()
    mainWin.show()
    sys.exit(app.exec_())   

0 人点赞