有限元方法入门:有限元方法简单的二维算例(三角形剖分)

有限元方法简单的二维算例(三角形剖分)

算例描述

我们对下述椭圆边值问题 \label{eq1}(破编辑器不容易给公式编号,只能这样写) { − Δ u = f u ∣ ∂ Ω = 0 \left \{ \begin{aligned} & -\Delta u = f\\ & u|_{\partial \Omega} = 0 & \end{aligned} \right. {Δu=fuΩ=0
其中, Ω = ( 0 , 1 ) 2 \Omega = (0,1)^2 Ω=(0,1)2 f = 2 π 2 sin π x sin π y f=2\pi^2\text{sin}\pi x\text{sin}\pi y f=2π2sinπxsinπy。考虑其变分问题,对其变分问题有限元离散并求解,并验证其为一阶收敛。
注:问题的真解为 u ( x ) = sin π x sin π y u(x) = \text{sin}\pi x\text{sin}\pi y u(x)=sinπxsinπy

变分问题

∀ v ∈ H 0 1 \forall v \in H_0^1 vH01,乘([eq1])式两边,使用格林公式,利用边界条件,易得:\label{eq2}
∫ Ω ∇ u ∇ v d x = ∫ Ω f v d x \int _\Omega \nabla u \nabla v dx = \int _\Omega fv dx Ωuvdx=Ωfvdx
其中 f f f为方程中的右端项。令\label{eq3}

a ( u , v ) = ∫ Ω ∇ u ∇ v d x f ( v ) = ∫ Ω f v d x \begin{aligned} &a(u,v) = \int _\Omega \nabla u \nabla v dx & \\ &f(v) = \int _\Omega fv dx & \end{aligned} a(u,v)=Ωuvdxf(v)=Ωfvdx

容易证明,原问题([eq1])等价于变分问题: \label{eq4}
{ 求 u ∈ H 0 1 ,使得 a ( u , v ) = f ( v ) ∀ v ∈ H 0 1 \left \{ \begin{aligned} & \text{求}u \in H_0^1 \text{,使得}\\ & a(u,v) = f(v)&\forall v \in H_0^1 \end{aligned} \right. {uH01,使得a(u,v)=f(v)vH01
事实上,在一定连续性的要求下,强解为弱解,弱解也是强解,二者等价。故求解问题([eq1])变为了求解问题([eq4])。更一般的变分问题,描述为:\label{eq5}
{ 求 u ∈ V , s . t . a ( u , v ) = f ( v ) ∀ v ∈ V \left \{ \begin{aligned} & \text{求}u \in V ,s.t.&\\ & a(u,v) = f(v)&\forall v \in V& \end{aligned} \right. {uV,s.t.a(u,v)=f(v)vV

有限元离散

问题转化

我们来考虑上述变分问题的有限维逼近,即构造 V V V的有限维子空间 V h ⊂ V V_h \subset V VhV,考虑如下的离散问题:\label{eq6}
{ 求 u h ∈ V h , s . t . a ( u h , v h ) = f ( v h ) ∀ v h ∈ V h \left \{ \begin{aligned} & \text{求}u_h \in V_h ,s.t.&\\ & a(u_h,v_h) = f(v_h)&\forall v_h \in V_h& \end{aligned} \right. {uhVh,s.t.a(uh,vh)=f(vh)vhVh

我们用问题([eq6])近似问题([eq5]),后者的解逼近前者的解。所以我们可以通过求([eq6])的解作为近似解。
V h V_h Vh空间中的一组基为 { ϕ i } i = 1 N \{\phi_i\}_{i=1}^N {ϕi}i=1N,若 u h = Σ u i ϕ i u_h = \Sigma u_i \phi_i uh=Σuiϕi(为了书写方便,不加说明,求和指标都为1到N),我们需要求的是组合系数 u i u_i ui,将 u h = Σ u i ϕ i u_h = \Sigma u_i \phi_i uh=Σuiϕi代入,并依次分别取 v h v_h vh 为每个基函数,我们可以得到: \label{eq7} Σ u i a ( ϕ i , ϕ j ) = f ( ϕ j ) , j = 1 , 2 , . . . N . \Sigma u_i a(\phi_i,\phi_j)=f(\phi_j),j=1,2,...N. Σuia(ϕi,ϕj)=f(ϕj),j=1,2,...N.
这事实上就是一个线性方程组 A U = F AU=F AU=F,其中 A = ( a ( ϕ j , ϕ i ) i , j = 1 N ) , U = ( u 1 , . . . , u N ) T , F = ( f ( ϕ 1 , . . . , ϕ N ) ) T A = (a(\phi_j,\phi_i)_{i,j=1}^N),U=(u_1,...,u_N)^T,F=(f(\phi_1,...,\phi_N))^T A=(a(ϕj,ϕi)i,j=1N),U=(u1,...,uN)T,F=(f(ϕ1,...,ϕN))T。那么,求解问题就转为了在 V h V_h Vh的约束下,求解这个线性方程组。

由我们选取 v h v_h vh分别为 ϕ i \phi_i ϕi可知,这个方程组的解对于原问题是必要而不充分的。但是在合适的条件之下,由Lax-Milgram定理可知,离散变分问题的解是存在唯一的。一般来说,我们在初边值条件的约束下,方程组的解存在唯一,那么这个唯一解必然是原问题的解。故而,对于一般的变分问题,我们通过离散化,最后可以通过求解([eq7])来近似。那么问题本质上就转化为了找一个合适的空间逼近,在这个空间中求基函数,和基函数之间的某种相互作用以及基函数和 f f f之间的作用,构建出方程组,最后求解方程组,得到逼近阶关于基函数的组合系数,最后得到原问题的一个逼近的数值解。

需要注意的是,由于 V h ∈ V V_h\in V VhV,我们对 ϕ i \phi_i ϕi是有一定的要求的,或者说我们不管基函数是否属于 V V V,我们最后对组合系数 u i u_i ui施以一定的要求(为满足边界条件),使得 u h = Σ u i ϕ i ∈ V u_h = \Sigma u_i \phi_i \in V uh=ΣuiϕiV,具有相同的效力。

有限元三要素

我们现在来考虑单元上的插值问题。对于问题([eq1]),我们考虑其三要素 ( T , P T , Σ T ) (T,P_T,\Sigma_T) (T,PT,ΣT),其中, T T T为三角形分割,如图[pic1]所示,为了方便,我这里假设的是两个方向上是等距分割,也就是三角形是等边三角形。

这里写图片描述

我们先来考虑如图所示的参考单元,即采用面积坐标。

这里写图片描述

分割单元上的形函数空间为多项式集合 P T = span { λ 1 , λ 2 , λ 3 } P_T = \text{span}\{\lambda_1,\lambda_2,\lambda_3\} PT=span{λ1,λ2,λ3},其中 λ 1 + λ 2 + λ 3 = 1 \lambda_1+ \lambda_2+ \lambda_3=1 λ1+λ2+λ3=1。单元自由度 Σ T = { u ( a 1 ) , u ( a 2 ) , u ( a 3 ) } \Sigma_T = \{u(a_1),u(a_2),u(a_3)\} ΣT={u(a1),u(a2),u(a3)}
表示三个端点的值。(注意,自由度是个集合,而不是个数,很多人在口头表述的时候,总喜欢表述“自由度是几”,这个本身容易引起误导,也侧面反应了这些人基础概念的混淆。)

  • 适定性
    显然,若 Σ = { 0 } \Sigma = \{0\} Σ={0},那么 u ≡ 0 u\equiv0 u0

  • 求基函数
    显然,三个面积坐标 λ 1 , λ 2 , λ 3 \lambda_1,\lambda_2,\lambda_3 λ1,λ2,λ3即为单元上的形函数。

  • 连续性(连续才能保证 V h ∈ H 1 V_h \in H^1 VhH1,只有这样才能和前面的基础理论相容。)
    考虑到 λ 1 + λ 2 + λ 3 = 1 \lambda_1+ \lambda_2+ \lambda_3=1 λ1+λ2+λ3=1,相邻插值函数在共用边上都为某个变量的一次函数,两端点值固定,一次函数也就确定了,故而,在边界处是连续的。

参考单元与一般单元

一般单元上的形函数

我们知道在参考单元上的一组基函数为 λ 1 , λ 2 , λ 3 \lambda_1,\lambda_2,\lambda_3 λ1,λ2,λ3,那么一般单元上的形函数是什么呢?

首先我们注意到,两者转换满足以下公式: x = ∑ i = 1 3 x i λ i = x 3 + λ 1 ( x 1 − x 3 ) + λ 2 ( x 2 − x 3 ) y = ∑ i = 1 3 y i λ i = y 3 + λ 1 ( y 1 − y 3 ) + λ 2 ( y 2 − y 3 ) \begin{aligned}x = \sum\limits_{i=1}^3 x_i\lambda_i=x_3+\lambda_1(x_1-x_3)+\lambda_2(x_2-x_3) \\ y = \sum\limits_{i=1}^3 y_i\lambda_i=y_3+\lambda_1(y_1-y_3)+\lambda_2(y_2-y_3)\end{aligned} x=i=13xiλi=x3+λ1(x1x3)+λ2(x2x3)y=i=13yiλi=y3+λ1(y1y3)+λ2(y2y3)

反过来,令三角形节点标号为逆时针,记为 i , j , k i,j,k i,j,k,且引入记号 ξ i = x j − x k , η i = y j − y k , ω i = x j y k − x k y j \xi_i = x_j -x_k,\eta_i = y_j-y_k,\omega_i = x_jy_k-x_ky_j ξi=xjxk,ηi=yjyk,ωi=xjykxkyj时,面积坐标可表示为(很容易 check):
λ i = η i x − ξ i y + ω i 2 ∣ T ∣ , i = 1 , 2 , 3 \lambda_i = \frac{\eta_ix-\xi_iy+\omega_i}{2|T|},i=1,2,3 λi=2∣Tηixξiy+ωi,i=1,2,3

这里的 ∣ T ∣ |T| T表示三角形面积,这里的 i , j , k i,j,k i,j,k标号一定要以逆时针顺序编号,且 ξ i \xi_i ξi η i \eta_i ηi的表达式为前面一个坐标减去后面一个坐标,否则 λ i \lambda_i λi的表达式会差一个符号,所以,为了不出错,我们提倡节点编号都以逆时针方向来编。

由此,我们得到了 λ i \lambda_i λi到一般单元上的映射,即知道了一般单元上的形函数。

一般单元上的积分计算

我们知道,搞清楚了参考单元上的情况,并不意味着一般单元上的情况就清楚了。我们利用需要两者之间的转换和变量替换关系,搞清楚一般单元上的积分和参考单元上的积分的关系。
放到参考单元上去做,表达起来会更加简洁有力美观,同时,在计算多个单元上的积分时,一些中间量如雅克比行列式和雅克比矩阵等可被复用,一定程度上减少了计算量。当然,你不做 scaling,直接在原单元上求基函数,及其相互之间以及右端项之间的作用的积分,也是可以的,表达式复杂一点点而已。

由此,我们很容易求得雅克比矩阵( 红色部分修正,对雅克比矩阵的定义做了统一,避免前后不一致)。
J = ∂ ( x , y ) ∂ ( λ 1 , λ 2 ) = [ − ξ 2 , ξ 1 ; − η 2 , η 1 ] T J = \frac{\partial(x,y)}{\partial(\lambda_1,\lambda_2)}=[ -\xi_2,\xi_1;-\eta_2,\eta_1 ]^T J=(λ1,λ2)(x,y)=[ξ2,ξ1;η2,η1]T
J = ∂ ( x , y ) ∂ ( λ 1 , λ 2 ) = [ x λ 1 , x λ 2 ; y λ 1 , y λ 2 ] = [ − ξ 2 , ξ 1 ; − η 2 , η 1 ] (修正后) J = \frac{\partial(x,y)}{\partial(\lambda_1,\lambda_2)}=[x_{\lambda_1},x_{\lambda_2}; y_{\lambda_1},y_{\lambda_2}]=[ -\xi_2,\xi_1;-\eta_2,\eta_1 ](修正后) J=(λ1,λ2)(x,y)=[xλ1,xλ2;yλ1,yλ2]=[ξ2,ξ1;η2,η1](修正后)

雅克比行列式 ∣ J ∣ = η 2 ξ 1 − η 1 ξ 2 |J| = \eta_2\xi_1 - \eta_1\xi_2 J=η2ξ1η1ξ2

另外,我们也需要雅克比矩阵的逆 J − 1 = ∂ ( λ 1 , λ 2 ) ∂ ( x , y ) = [ η 1 / ( 2 T ) , − ξ 1 / ( 2 T ) ; η 2 / ( 2 T ) , − ξ 2 / ( 2 T ) ] J^{-1} = \frac{\partial(\lambda_1,\lambda_2)}{\partial(x,y)}= [\eta_1/(2T), -\xi_1/(2T);\eta_2/(2T), -\xi_2/(2T)] J1=(x,y)(λ1,λ2)=[η1/(2T),ξ1/(2T);η2/(2T),ξ2/(2T)]
(这里少了个转置符号,需要加上,具体看评论)

那么,在一般单元上的积分计算就可以和参考单元上的积分计算联系上,如果不涉及求导,那么之差雅克比行列式,涉及求导,两者的积分就不仅仅是差一个常数,如下所示:
∫ T ∇ u ( x , y ) T ∇ v ( x , y ) d x d y = ∫ T ^ ∇ u ( λ 1 , λ 2 ) T J − T J − 1 ∇ v ( λ 1 , λ 2 ) ∣ J ∣ d λ 1 d λ 2 \int_T {\nabla u(x,y)}^T\nabla v(x,y) dxdy = \int _{\hat T} {\nabla u(\lambda_1,\lambda_2)}^T J^{-T}J^{-1}\nabla v(\lambda_1,\lambda_2)|J|d\lambda_1d \lambda_2 Tu(x,y)Tv(x,y)dxdy=T^u(λ1,λ2)TJTJ1v(λ1,λ2)Jdλ1dλ2
∫ T ∇ u ( x , y ) T ∇ v ( x , y ) d x d y = ∫ T ^ ∇ u ( λ 1 , λ 2 ) T J − 1 J − T ∇ v ( λ 1 , λ 2 ) ∣ J ∣ d λ 1 d λ 2 (修正后) \int_T {\nabla u(x,y)}^T\nabla v(x,y) dxdy = \int _{\hat T} {\nabla u(\lambda_1,\lambda_2)}^T J^{-1}J^{-T}\nabla v(\lambda_1,\lambda_2)|J|d\lambda_1d \lambda_2 (修正后) Tu(x,y)Tv(x,y)dxdy=T^u(λ1,λ2)TJ1JTv(λ1,λ2)Jdλ1dλ2(修正后)

∫ T f ( x , y ) v ( x , y ) d x d y = ∫ T ^ f ( λ 1 , λ 2 ) v ( λ 1 , λ 2 ) ∣ J ∣ d λ 1 d λ 2 \int_T f(x,y)v(x,y) dxdy = \int _{\hat T} f(\lambda_1,\lambda_2)v(\lambda_1,\lambda_2)|J|d\lambda_1 d\lambda_2 Tf(x,y)v(x,y)dxdy=T^f(λ1,λ2)v(λ1,λ2)Jdλ1dλ2

问题求解

局部基函数到全局基函数

求得了在每个单元上的插值的节点基函数,对于每个节点,我们将其相关单元的相关基函数并成一个关于这个节点的全局基函数 ϕ i \phi_i ϕi,有多少个节点,就有多少个基函数。

单元扫描计算刚度矩阵和载荷向量

有了基函数,理论上就可以求解变分问题离散后转化成的方程组。我们需要计算 a ( ϕ i , ϕ j ) a(\phi_i,\phi_j) a(ϕi,ϕj) f ( ϕ j ) f(\phi_j) f(ϕj)
这里对于积分的计算( a ( ϕ i , ϕ j ) a(\phi_i,\phi_j) a(ϕi,ϕj) f ( Φ j ) f(\Phi_j) f(Φj))可以采用扫描单元的方式,即在每个单元上计算基函数之间的相互作用并分配到相对应的全局基函数的相互作用上,计算每个单元上的基函数和右端项的作用,分配到与之相关的全局基函数和右端项的作用上。我们就需要计算单元上基函数作用 a ( f i , f j ) a(f_i,f_j) a(fi,fj)和单元上基函数和右端项的作用 f ( f i ) f(f_i) f(fi)。这里为了区分局部基函数和全局基函数,我用 f i f_i fi表示单元上的基函数, ϕ i \phi_i ϕi表示全局基函数。

由前可知,我们事实上已经能够通过将一般单元变换到参考单元来计算一般三角形单元上两个形函数之间的相互作用
a ( f i , f j ) = ∫ T ∇ f i ( x , y ) T ∇ f j ( x , y ) d x d y = ∫ T ^ ∇ λ 1 T J − T J − 1 ∇ λ 2 ∣ J ∣ d λ 1 d λ 2 a(f_i,f_j) = \int_T {\nabla f_i(x,y)}^T\nabla f_j(x,y) dxdy = \int _{\hat T} {\nabla \lambda_1}^T J^{-T}J^{-1}\nabla \lambda_2|J|d\lambda_1d \lambda_2 a(fi,fj)=Tfi(x,y)Tfj(x,y)dxdy=T^λ1TJTJ1λ2Jdλ1dλ2

a ( f i , f j ) = ∫ T ∇ f i ( x , y ) T ∇ f j ( x , y ) d x d y = ∫ T ^ ∇ λ 1 T J − 1 J − T ∇ λ 2 ∣ J ∣ d λ 1 d λ 2 (修正后) a(f_i,f_j) = \int_T {\nabla f_i(x,y)}^T\nabla f_j(x,y) dxdy = \int _{\hat T} {\nabla \lambda_1}^T J^{-1}J^{-T}\nabla \lambda_2|J|d\lambda_1d \lambda_2(修正后) a(fi,fj)=Tfi(x,y)Tfj(x,y)dxdy=T^λ1TJ1JTλ2Jdλ1dλ2(修正后)

以及单元上 f f f和基函数之间的作用 f ( f j ) = ∫ T f ( x , y ) f j ( x , y ) d x d y = ∫ T ^ f ( λ 1 , λ 2 ) λ j ∣ J ∣ d λ 1 d λ 2 f(f_j)=\int_T f(x,y)f_j(x,y) dxdy = \int _{\hat T} f(\lambda_1,\lambda_2)\lambda_j|J|d\lambda_1 d\lambda_2 f(fj)=Tf(x,y)fj(x,y)dxdy=T^f(λ1,λ2)λjJdλ1dλ2

对于 f ( f j ) = ∫ T ^ f ( λ 1 , λ 2 ) λ j ∣ J ∣ d λ 1 d λ 2 f(f_j) = \int _{\hat T} f(\lambda_1,\lambda_2)\lambda_j|J|d\lambda_1 d\lambda_2 f(fj)=T^f(λ1,λ2)λjJdλ1dλ2的计算,由于在一般问题中, f f f不一定就是这个问题的 f f f,所以,对于具体问题的具体的 f f f我们可以用 MATLAB 内置的求积分函数,也可以自己编写程序实现。

边界条件处理

但是在边值条件问题中,粗算出来的方程组的刚度矩阵 K K K,就不是满秩的,那么解就不唯一。这是因为所求解的 u h ∈ V h ⊂ V u_h \in V_h \subset V uhVhV这个条件没满足,所以,一个思路是根据边界条件选取一个合适的基函数子集,另外,也可以最后根据边值条件来调整刚度矩阵 K K K,我采取第二个思路。

调整的思路就是调整 K U = F KU=F KU=F,将其等价转换,使得最后的解满足 U U U的对应位置为固定的值,所谓对应位置,就是边界所对应的位置,所谓固定的值,就是边界值。简单地说,我们可以这样操作,直接令 U U U的对应位置强制为边界值,然后将 U U U中新的不知道的数视为未知数,构建新的方程组求解,本质上无非就是右端项减去边界条件乘以 K K K中与其位置对应的列……假若,原来的 K K K秩比起满秩少了m,那么补上m个边界条件,方程组的解应该是存在唯一的,认识到这一点,再加上一些线性代数的基本知识,事情就很明了了……就不再赘述……

数值实验和收敛阶

程序代码

基于以上的思想,编写了程序代码,直接粘贴如下,为了方便,直接将函数文件直接粘贴在主文件末尾了。程序的细节可以看注释,就不再详述。我基本上将每一句代码都打上了注释。

%% 这是一个二维的有限元程序
%% 这是一个二维的有限元程序
clc;  % 清空命令行窗口
clear; %清除工作空间
close all; %关闭所有图像
%% 参数设置
error_L2s = [];
for k = [2,4,8,16,32,64]
Lx = 1; %定义单元右边界(左边界为0,如果不是,可以平移为0)
Ly = 1;%定义单元上边界
N = k;%分割的一个方向的单元数目
numelx = N;%定义分割的x方向单元数目(按矩形计算)
numely = N;%定义分割的y方向单元数目(按矩形计算)
hx = Lx/numelx;%x方向上的单元长度(对应着三角形两条直角边之一)
hy = Ly/numely;%y方向上的单元长度(对应着三角形两条直角边之一)
numel = numelx*numely*2;%小单元的数目,每个矩形分成两个三角形
u_b = zeros(2*(numelx+numely),1); %定义第一类边界条件,一圈过来都是0
numnodx = numelx + 1; % x方向节点个数比单元个数多1
numnody = numely + 1; % y方向节点个数比单元个数多1
numnod = numnodx*numnody;%总的节点个数
nel = 3;%每个单元的节点数目,即每个单元上有几个形函数参与作用,单元自由度
coordx = linspace(0,Lx,numnodx)'; %等分节点的坐标(为了方便,我这里采用等分的方式,事实上单元长度可以不一致,非均匀网格)
coordy = linspace(0,Ly,numnody)'; %等分节点的坐标(为了方便,我这里采用等分的方式,事实上单元长度可以不一致)
[X, Y] = meshgrid(coordx,coordy);%张成网格,X和Y分别表示对应位置的横纵坐标
X = X';Y = Y';coord = [X(:) Y(:)];%把网格一行一行扯开,coord的每一行是对应节点的坐标,按顺序排
connect = connect_mat(numnodx,numnody,nel);%连接矩阵,表示每个单元周围的节点编号,也就是涉及的形函数编号
ebcdof = unique([1:numnodx numnodx*numely+1:numnodx*numely+numnodx ...
    numnodx+1:numnodx:numnodx*(numely-1)+1 2*numnodx:numnodx:numely*numnodx]); % 强制性边界点的编号,本例子中是四条边,下上左右边
ebcval = u_b; %假设边界值都为u_b
bigk = sparse(numnod,numnod); % 刚度矩阵[K],初始化为0,使用稀疏矩阵存储
fext = sparse(numnod,1);      % 载荷向量{f},初始化为0

%%  计算系数矩阵K和右端项f,单刚组装总刚
for e = 1:numel %同一维的情况,依然按单元来扫描
  ke = elemstiff2d(e,nel,hx,hy,coord,connect);%计算单元刚度矩阵
  fe = elemforce2d(e,nel,hx,hy,coord,connect);%计算单元载荷向量
  sctr = connect(e,:);
  bigk(sctr,sctr) = bigk(sctr,sctr) + ke;
  fext(sctr) = fext(sctr) + fe;
  %full(bigk)
  %full(fext)
end
for i = 1:length(ebcdof)
  n = ebcdof(i);
  for j = 1:numnod
    if (isempty(find(ebcdof == j, 1))) % 第j个点若不是固定点
      fext(j) = fext(j) - bigk(j,n)*ebcval(i);
    end
  end
  bigk(n,:) = 0.0;
  bigk(:,n) = 0.0;
  bigk(n,n) = 1.0;
  fext(n) = ebcval(i);
end
u_coeff = bigk\fext;%求出系数,事实上也是函数在对应点上的值
u_cal = u_coeff;
u_cal_re = reshape(u_coeff,numnodx,numnody);
u_cal_re = full(u_cal_re);

%% 求精确解
L =Lx;
nsamp = 1001;
xsamp = linspace(0,L,nsamp);%100等分区间中间有100个数
[X,Y] = meshgrid(xsamp,xsamp);
uexact = exactsolution2d(X(:),Y(:));
uexact_re = reshape(uexact,nsamp,nsamp);
mesh(xsamp,xsamp,uexact_re)
%% 绘图,可视化
h = mesh(coordx,coordy,u_cal_re);
title(' FE Solutions');%标题
saveas(h,['报告\pics\k' num2str(k) '.eps'],'epsc2')
%% 求精确解,计算误差,没有计算单元准确积分,依然使用近似误差

u_ex = exactsolution2d(coord(:,1),coord(:,2));
u_ex_re = reshape(u_ex,numnodx,numnody);
error_L1 = sum(abs(u_cal - u_ex))*hx*hy
error_L2 = sqrt(sum((u_cal - u_ex).^2)*hx*hy)
error_Linf = max(abs(u_cal - u_ex))
error_L2s(end+1) = error_L2;

end
es = log2(error_L2s(1:end-1)./error_L2s(2:end))


function [ke] = elemstiff2d(e,nel,hx,hy,coord,connect)
T = hx*hy/2;
ke    = zeros(nel,nel); %单刚初始化
nodes = connect(e,:);%相关形函数(节点)编号
xe    = coord(nodes,:); %相关节点的坐标
xi1 = xe(2,1) - xe(3,1);xi2 = xe(3,1) - xe(1,1);%xi3 = xe(1,1) - xe(2,1);
eta1 = xe(2,2) - xe(3,2);eta2 = xe(3,2) - xe(1,2);%eta3 = xe(1,2) - xe(2,2);
%以下内容可修改,具体看评论
auv11 = -((eta1*xi2 - eta2*xi1)*(eta1^2 + eta2^2))/(8*T^2);
auv12 = ((eta1*xi1 + eta2*xi2)*(eta1*xi2 - eta2*xi1))/(8*T^2);
auv13 = -((eta1*xi2 - eta2*xi1)*(- eta1^2 + xi1*eta1 - eta2^2 + xi2*eta2))/(8*T^2);
auv22 = -((xi1^2 + xi2^2)*(eta1*xi2 - eta2*xi1))/(8*T^2);
auv23 = -((eta1*xi2 - eta2*xi1)*(- xi1^2 + eta1*xi1 - xi2^2 + eta2*xi2))/(8*T^2);
auv33 = -((eta1*xi2 - eta2*xi1)*(eta1^2 - 2*eta1*xi1 + eta2^2 - 2*eta2*xi2 + xi1^2 + xi2^2))/(8*T^2);
ke = ke + [auv11 auv12 auv13;auv12 auv22 auv23;auv13 auv23 auv33];
return
end



function [fe] = elemforce2d(e,nel,hx,hy,coord,connect)
%输入单元编号e,单元自由度nel数目,单元长度hx,单元宽度hy,单元节点坐标coord,和连接矩阵connect
%输出单元载荷fe
%考虑以一般单元为基准,可求f在这个一般单元上的取值,再在这个一般单元上积分
%比较规范的划分,求积分不妨直接在一般单元上求,没必要变换到标准单元上,一来若求梯度更加麻烦了,标准单元上的依然需要求不同的积分
%二来因为f的值未定,依然需要求积分
%fe = zeros(nel,1); %初始化载荷向量
nodes = connect(e,:);%自由度编号
xe = coord(nodes,:); % 单元自由节点坐标
xi1 = xe(2,1) - xe(3,1);xi2 = xe(3,1) - xe(1,1);%xi3 = xe(1,1) - xe(2,1);
eta1 = xe(2,2) - xe(3,2);eta2 = xe(3,2) - xe(1,2);%eta3 = xe(1,2) - xe(2,2);
%syms lam1 lam2;


%x =  xe(3,1) + lam1*(-xi2)+lam2*(xi1);
%y =  xe(3,2) + lam1*(-eta2)+lam2*(eta1);


detJ = eta2*xi1 - eta1*xi2;
g1 = @(lam1,lam2) fun(xe(3,1) + lam1*(xi2)+lam2*(xi1),xe(3,2) + lam1*(-eta2)+lam2*(eta1)).*lam1*detJ;%g1 = matlabFunction(g1);
g2 = @(lam1,lam2) fun(xe(3,1) + lam1*(xi2)+lam2*(xi1),xe(3,2) + lam1*(-eta2)+lam2*(eta1)).*lam2*detJ;%g2 = matlabFunction(g2);
g3 = @(lam1,lam2) fun(xe(3,1) + lam1*(xi2)+lam2*(xi1),xe(3,2) + lam1*(-eta2)+lam2*(eta1)).*(1-lam1-lam2)*detJ;%g3 = matlabFunction(g3);

lammax = @(lam1) 1 - lam1;
gx(1) = integral2(g1,0,1,0,lammax);
gx(2) = integral2(g2,0,1,0,lammax);
gx(3) = integral2(g3,0,1,0,lammax);
fe = [gx(1);gx(2);gx(3)];
end

function bx = fun(x,y)
bx = (2*pi^2)*sin(pi*x).*sin(pi*y);
end




function connect_mat = connect_mat( numnodx,numnody,nel)
%输入横纵坐标的节点数目,和单元自由度
%输出连接矩阵,每个单元涉及的节点的编号
xn = 1:(numnodx*numnody);%拉成一条编号
A = reshape(xn,numnodx,numnody);%同形状编号
for i = 1:(numnodx-1)*(numnody-1)
    xg = rem(i,numnodx-1);%xg表示单元为左边界数起第几个
    if xg == 0
        xg = numnodx-1;
    end
    yg = ceil(i/(numnodx-1));%下边界其数第几个
    a = A(xg:xg+1,yg:yg+1);%这个小矩阵,拉直了就是连接矩阵
    a_vec = a(:);
    connect_mat(2*i-1:2*i,1:nel) = [a_vec([1 4 3])';a_vec([4 1 2])'];
end
end

function uexact = exactsolution2d(xg,yg)
uexact = sin(pi*xg).*sin(pi*yg);

return
end

美中不足的是,代码中求解方程组部分,我直接使用matlab的右除算子 \ \backslash \,对于这种稀疏矩阵,其实可以考虑使用一些快速的迭代方法。我这里单元内部的节点编号,使用了一个连接矩阵来存储,即按照一定的顺序将每个单元上的节点全局编号按行存到连接矩阵中,需要的时候再取出来。

误差和收敛阶分析

对于这个二维问题,精确解和数值解不好画在一张图上做对比。我们可以看到,该问题的精确解如图[pic3]所示。

这里写图片描述

定义单元长度为1,不妨假设两个方向分割的单元数目相同,记为k,结果如下图所示。收敛阶的计算是基于 L 2 L_2 L2误差的,使用其它度量结果差不多。分别设置k的不同,使用程序求解,最后的结果如图下图所示。
这里写图片描述
计算其收敛阶,结果如下所示。

这里写图片描述

由此可见,二维线性三角形元的在 L p L_p Lp空间的意义下收敛阶是2。

好的,我会尽力回答你的问题。首先,编写三角形单元有限元程序需要以下步骤: 1. 首先需要确定问题的几何形状和边界条件,例如平面问题可以是一个矩形区域,或者任意形状的区域。 2. 划分网格,将区域分成若干个三角形单元,可以采用Delaunay三角剖分算法。 3. 确定每个节点的坐标和单元节点编号,建立节点和单元之间的关系。 4. 计算每个单元的刚度矩阵和载荷向量,可以采用数值积分方法进行计算。 5. 组装刚度矩阵和载荷向量,得到整个系统的刚度矩阵和载荷向量。 6. 施加边界条件,例如固定边界条件和力边界条件等。 7. 线性方程组,得到节点的位移和应力分布。 8. 计算应力或变形的最大值和平均值等。 下面,我将用一个平面问题算例来说明如何使用三角形单元有限元程序。 假设有一个长方形区域,左边界为固定边界,右边界施加一个水平向右的力,其他边界为自由边界。区域的尺寸为 $L=20$,$H=10$,力的大小为 $F=100$。材料的弹性模量为 $E=200\times 10^9$,泊松比为 $\nu=0.3$。 首先,将区域划分为若干个三角形单元,如下图所示: ![image.png](attachment:image.png) 节点的坐标和单元节点编号如下表所示: | Node | x | y | |------|---|---| | 1 | 0 | 0 | | 2 | 20| 0 | | 3 | 20| 10| | 4 | 0 | 10| | 5 | 10| 0 | | 6 | 20| 5 | | 7 | 10| 10| | 8 | 0 | 5 | | Element | Node 1 | Node 2 | Node 3 | |---------|--------|--------|--------| | 1 | 1 | 5 | 8 | | 2 | 2 | 6 | 3 | | 3 | 6 | 7 | 3 | | 4 | 5 | 2 | 6 | | 5 | 8 | 5 | 7 | | 6 | 1 | 4 | 8 | | 7 | 7 | 4 | 3 | | 8 | 5 | 6 | 7 | 然后,可以使用数值积分方法计算每个单元的刚度矩阵和载荷向量。以第一个单元为例,其刚度矩阵和载荷向量为: $$ \begin{aligned} & K_1=\frac{E}{1-\nu^2} \begin{pmatrix} 1 & \nu & 0 \\ \nu & 1 & 0 \\ 0 & 0 & \frac{1-\nu}{2} \end{pmatrix}\frac{1}{4A_1}\begin{pmatrix} 2 & 1 & -1 \\ 1 & 2 & -1 \\ -1 & -1 & 2 \end{pmatrix} \\ & f_1=F\frac{H}{2}\begin{pmatrix} 1 \\ 1 \\ 1 \end{pmatrix} \end{aligned} $$ 其中,$A_1=25$是第一个单元的面积。 接下来,将所有单元的刚度矩阵和载荷向量组装成整个系统的刚度矩阵和载荷向量。施加边界条件,得到线性方程组: $$ \left[\begin{pmatrix} K_{11} & K_{12} \\ K_{21} & K_{22} \end{pmatrix} \right] \left[\begin{pmatrix} u_1 \\ u_2 \end{pmatrix} \right] = \left[\begin{pmatrix} f_1 \\ f_2 \end{pmatrix} \right] $$ 其中,$K_{11}$和$K_{22}$是固定边界上的节点的位移已知条件所对应的子矩阵,$K_{12}$和$K_{21}$是未知节点和已知节点之间的子矩阵。 最后,线性方程组,得到节点的位移和应力分布。可以计算出右边界上的节点的位移为: $$ u_2=\frac{FL}{2EH}(3\nu+1) $$ 应力分布如下图所示: ![image-2.png](attachment:image-2.png) 其中,最大应力出现在右下角的单元内,为$5.3\times 10^7$。 以上就是使用三角形单元有限元程序分析平面问题方法。希望能对你有所帮助。
评论 76
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陆嵩

有打赏才有动力,你懂的。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值