#有限元方法简单的二维算例(矩形剖分)
算例描述
我们对下述椭圆边值问题 \label{eq1}
其中,
且
。考虑其变分问题,对其变分问题有限元离散并求解,并验证其为一阶收敛。
注:问题的真解为
。
变分问题
,乘([eq1])式两边,使用格林公式,利用边界条件,易得\label{eq2}:
其中
为方程中的右端项。令\label{eq3}
容易证明,原问题([eq1])等价于变分问题: \label{eq4}
事实上,在一定连续性的要求下,强解为弱解,弱解也是强解,二者等价。故求解问题([eq1])变为了求解问题([eq4])。更一般的变分问题,描述为:\label{eq5}
有限元离散
问题转化
我们来考虑上述变分问题的有限维逼近,即构造
的有限维子空间
,考虑如下的离散问题:\label{eq6}
我们用问题([eq6])近似问题([eq5]),后者的解逼近前者的解。所以我们可以通过求([eq6])的解作为近似解。
设
空间中的一组基为
,若
(为了书写方便,不加说明,求和指标都为1到N),我们需要求的是组合系数
,将
代入,并依次分别取
为每个基函数,我们可以得到:\label{eq7}
这事实上就是一个线性方程组
,其中
。那么,求解问题就转为了在
的约束下,求解这个线性方程组。
由我们选取 分别为 可知,这个方程组的解对于原问题是必要而不充分的。但是在合适的条件之下,由Lax-Milgram定理可知,离散变分问题的解是存在唯一的。一般来说,我们在初边值条件的约束下,方程组的解存在唯一,那么这个唯一解必然是原问题的解。故而,对于一般的变分问题,我们通过离散化,最后可以通过求解([eq7])来近似。那么问题本质上就转化为了找一个合适的空间逼近,在这个空间中求基函数,和基函数之间的某种相互作用以及基函数和 之间的作用,构建出方程组,最后求解方程组,得到逼近阶关于基函数的组合系数,最后得到原问题的一个逼近的数值解。
需要注意的是,由于 ,我们对 是有一定的要求的,或者说我们不管基函数是否属于 ,我们最后对组合系数 施以一定的要求(为满足边界条件),使得 ,具有相同的效力。
有限元三要素
我们现在来考虑单元上的插值问题。对于问题([eq1]),我们考虑其三要素 ,其中, 为等距或者不等距矩形单元分割,如图[pic1]所示,为了方便,我这里假设的是等距分割。
分割单元上的形函数空间为多项式集合
,单元自由度
表示分割单元四个端点的值。对于一般单元上的问题,我们可以通过一个线性变换,将其变到参考单元
上来考虑,在参考单元上计算基函数和相关积分。但是我觉得,对比较简单的情况,这里的简单指的是分割简单,形函数简单,那么我们可以直接在一般单元上来计算形函数和相关积分,没必要变来变去,徒添计算错误的可能。
考虑一般单元如图[pic2],不妨记
和
。
-
适定性
插值函数在每条边上为单变量的一次函数,若 ,那么插值函数在每条边上的值都为0,进而 。 -
求基函数
考虑 ,它在上面一条边和右边一条边处的值恒为零,故必含 因子和 因子,由于形函数空间次数不超过2,那么 可表达为 。因为 在 点处的值为1,故有: 。同理,我们可以求得, 。 -
连续性
显然,相邻插值函数在共用边上都为单变量一次函数,两端点值固定,一次函数也就确定了,故而,在边界处是连续的。
全局基函数和边值条件的处理
全局基函数和单元上的积分计算
求得了在每个单元上的插值的节点基函数,对于每个节点,我们将其相关单元的相关基函数并成一个关于这个节点的全局基函数 ,有多少个节点,就有多少个基函数。
有了基函数,理论上就可以求解变分问题离散后转化成的方程组。我们需要计算
和
。
这里对于积分的计算(
和
)可以采用扫描单元的方式,即在每个单元上计算基函数之间的相互作用并分配到相对应的全局基函数的相互作用上,计算每个单元上的基函数和右端项的作用,分配到与之相关的全局基函数和右端项的作用上。我们就需要计算单元上基函数作用
和单元上基函数和右端项的作用
。如果我没算错的话,有如下结果:
上面为了书写方便,省略的积分区间
和积分中的微分符合
。当然,对于这个问题,上面这些都用不到,我们需要的是如下结果:
对于$f(f_i) = \int _\Omega ff_i dx f f f$我们可以用matlab内置的求积分函数,也可以自己编写程序实现高斯求积分方法。
边界条件的处理
但是在边值条件问题中,粗算出来的方程组的刚度矩阵 ,就不是满秩的,那么解就不唯一。这是因为所求解的 这个条件没满足,所以,一个思路是根据边界条件选取一个合适的基函数子集,另外,也可以最后根据边值条件来调整刚度矩阵 ,我采取第二个思路。
调整的思路就是调整 ,将其等价转换,使得最后的解满足 的对应位置为固定的值,所谓对应位置,就是边界所对应的位置,所谓固定的值,就是边界值。简单地说,我们可以这样操作,直接令 的对应位置强制为边界值,然后将 中新的不知道的数视为未知数,构建新的方程组求解,本质上无非就是右端项减去边界条件乘以 中与其位置对应的列……假若,原来的 秩比起满秩少了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方向单元数目
%numel = numelx*numely;%单元数目
hx = Lx/numelx;%x方向上的单元长度
hy = Ly/numely;%y方向上的单元长度
numel = numelx*numely;%小单元的数目
u_b = zeros(2*(numelx+numely),1); %定义第一类边界条件,一圈过来都是0
numnodx = numelx + 1; % x方向节点个数比单元个数多1
numnody = numely + 1; % y方向节点个数比单元个数多1
numnod = numnodx*numnody;%总的节点个数
nel = 4;%每个单元的节点数目,即每个单元上有几个形函数参与作用,单元自由度
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;
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 = 101;
xsamp = linspace(0,L,nsamp);%100等分区间中间有100个数
[X,Y] = meshgrid(xsamp,xsamp);
uexact = exactsolution2d(X(:),Y(:));
uexact_re = reshape(uexact,nsamp,nsamp);
%% 绘图,可视化
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 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);%这个小矩阵,拉直了就是连接矩阵
connect_mat(i,1:nel) = a(:);
end
end
function [ke] = elemstiff2d(e,nel,hx,hy,coord,connect)
%二维刚度矩阵,虽然有些输入没用到,但为了格式的统一,还是这么写
%这块程序写得不怎么好,因为考虑得太特殊了,泛化能力差
%ke = zeros(nel,nel); %单刚初始化
%nodes = connect(e,:);%相关形函数(节点)编号
%xe = coord(nodes,:); %相关节点的坐标 hx=0.5;hy = 1;nel=4;
idp11 = 1/3*(hx/hy+hy/hx);
idp12 = 1/6*hx/hy-1/3*hy/hx;
idp13 = 1/6*hy/hx - 1/3*hx/hy;
idp14 = -1/6*(hy/hx+hx/hy);
ke = diag(repmat(idp12,3,1),1)+diag(repmat(idp13,2,1),2);
ke(1,nel) = idp14;ke(2,3)=idp14;
ke = ke + ke'+diag(repmat(idp11,nel,1));
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,:); % 单元自由节点坐标
f_shift = @(x,y) fun(x+xe(1,1),y+xe(1,2));%f_shift作用于单元坐标xy上,返回全局f值,即f在一般单元上的值
p{1} = @(x,y) (x-hx).*(y-hy)/(hx.*hy);
p{2} = @(x,y) x.*(y-hy)/(-hx.*hy);
p{3} = @(x,y) (x-hx).*y/(-hx.*hy);
p{4} = @(x,y) x.*y/(hx.*hy);
for i = 1:nel%这个也可以用高斯点来做,不过比较懒,就不改了,直接用matlab内置的积分函数
pp = p{i};
func = @(x,y) (f_shift(x,y).*pp(x,y));
fe(i,1) = integral2(func,0,hx,0,hy);
end
end
function bx = fun(x,y)
bx = (2*pi^2)*sin(pi*x).*sin(pi*y);
end
function uexact = exactsolution2d(xg,yg)
uexact = sin(pi*xg).*sin(pi*yg);
return
end
美中不足的是,代码中求解方程组部分,我直接使用matlab的右除算子 ,对于这种稀疏矩阵,其实可以考虑使用一些快速的迭代方法。我这里单元内部的节点编号,使用了一个连接矩阵来存储,即按照一定的顺序将每个单元上的节点全局编号按行存到连接矩阵中,需要的时候再取出来。
误差和收敛阶分析
对于这个二维问题,精确解和数值解不好画在一张图上做对比。我们可以看到,该问题的精确解如图[pic3]所示。
定义单元长度为1,不妨假设两个方向分割的单元数目相同,记为k,结果如下图所示。收敛阶的计算是基于 误差的,使用其它度量结果差不多。分别设置k的不同,使用程序求解,最后的结果如图[sh]所示。
计算其收敛阶,结果如下所示。
由此可见,二维线性元的收敛阶是2。