目录
一、研究目标
二、理论推导
三、算例实现
四、结论
一、研究目标
上个专栏我们介绍了双曲型偏微分方程的主要算法及实现。从今天开始,我们在新的专栏介绍另一种形式偏微分方程-椭圆型的解法。
研究目标选取经典的二维椭圆型方程(也称泊松Poisson方程):
当f=0时,就是著名的拉普拉斯(Laplace)方程。椭圆型方程在流体力学、弹性力学、电磁学、几何学和变分法中都广泛应用。现假设所要讨论的为矩形区域,考虑以下Poisson方程的边值问题:
固定边界的无厚薄膜,受外力作用后达到平衡状态时的位移函数u满足上述方程。一般情况下,公式(2)是很难直接用解析的方式计算精确解的,所以需要利用数值方法求解。
二、理论推导
首先介绍五点菱形差分格式,推导过程如下:
第一步:网格剖分。对矩形区域进行剖分,即在x方向对[a,b]进行步长为的等距剖分,分成m份,得到m+1个节点,其中。在y方向对[c,d]进行步长为的等距剖分,分成n份,得到n+1个节点,其中。然后用两族平行线将区域分成mn个小矩形,得到节点,如图所示。X表示边界节点,其余为内部节点。
第二步:弱化原方程,使得在离散点处成立,即
其中,或且或且。也就是为内节点,为边界节点。
第三步:用差商近似微商,建立数值格式,即
将上面两式代入离散节点处的方程,可得
用数值解代替精确解并忽略高阶项,可得数值格式为
整理上式可得
公式(3)每一步计算要涉及5个点,除中心点外其余4个点正好位于一个菱形的4个顶点,所以这个格式称为“五点菱形差分格式”,简称“五点格式”。
第四步:差分格式求解。公式(3)无法写成线性方程组的简单形式,只能写成
记
且设
则数值格式可写为
上式可简写为。其中,,且为m-1阶单位矩阵:
且
为解出此方程组,将未知量按下标拉长为一个列向量,并写成块矩阵形式,有
公式(4)的特点是:系数矩阵对称、正定,且绝大多数都是零元素,每一行最多只有5个非零元素,为稀疏矩阵。对于阶数不高的线性方程组的求解,直接法非常有效,而对于阶数高、系数矩阵稀疏的线性方程组,若采用直接法求解,就需要存储大量零元素。为减少运算律、节省内存,通常采用迭代法进行求解。在二维抛物型、双曲型方程的初边值问题中都曾遇到过这一类方程组,因为存在求解上的困难,后来就直接借助新的思路用交替方向隐式方法去处理数值逼近,从而避免了上述问题的求解。但事实上,公式(4)还是可以通过迭代法处理的,相比二维抛物型、双曲型方程初边值问题,由于不存在时间变量,处理起来会简单许多。具体的迭代法以及相应理论推导在下节中介绍(包括Jacobi迭代、Gauss-Seidel迭代、SOR迭代)。
三、算例实现
用五点菱形格式求解椭圆型方程边值问题:
已知该问题精确解为。分别取步长和,输出6个节点和处的数值解和误差。要求在各节点处最大误差的迭代误差限为。
代码如下:(采用Gauss-Seidel迭代)
#include <cmath>
#include <stdlib.h>
#include <stdio.h>
double pi=3.14159265359;
int main(int argc, char* argv[])
{
int m, n, i, j, k;
double xa, xb, ya, yb, dx, dy, alpha, beta, gamma, err, maxerr;
double *x, *y, **u, **temp;
double leftboundary(double y);
double rightboundary(double y);
double topboundary(double x);
double bottomboundary(double x);
double f(double x, double y);
double exact(double x, double y);
xa=0.0;
xb=2.0;
ya=0.0;
yb=1.0;
m=128;
n=64;
printf("m=%d,n=%d.\n",m,n);
dx=(xb-xa)/m;
dy=(yb-ya)/n;
beta=1.0/(dx*dx);
gamma=1.0/(dy*dy);
alpha=2*(beta+gamma);
x=(double*)malloc(sizeof(double)*(m+1));
for(i=0;i<=m;i++)
x[i]=xa+i*dx;
y=(double*)malloc(sizeof(double)*(n+1));
for(j=0;j<=n;j++)
y[j]=ya+j*dy;
u=(double**)malloc(sizeof(double *)*(m+1));
temp=(double**)malloc(sizeof(double *)*(m+1));
for(i=0;i<=m;i++)
{
u[i]=(double*)malloc(sizeof(double)*(n+1));
temp[i]=(double*)malloc(sizeof(double)*(n+1));
}
for(j=0;j<=n;j++)
{
u[0][j]=leftboundary(y[j]);
u[m][j]=rightboundary(y[j]);
}
for(i=1;i<m;i++)
{
u[i][0]=bottomboundary(x[i]);
u[i][n]=topboundary(x[i]);
}
for(i=1;i<m;i++)
for(j=1;j<n;j++)
u[i][j]=0.0;
for(i=0;i<=m;i++)
for(j=0;j<=n;j++)
temp[i][j]=u[i][j];
//Gauss-Seidel迭代
k=0;
do
{
maxerr=0.0;
for(i=1;i<m;i++)
{
for(j=1;j<n;j++)
{
temp[i][j]=(f(x[i],y[j])+beta*(u[i-1][j]+temp[i+1][j])+gamma*(u[i][j-1]+temp[i][j+1]))/alpha;
err=temp[i][j]-u[i][j];
if(err>maxerr)
maxerr=err;
u[i][j]=temp[i][j];
}
}
k=k+1;
}while(maxerr>0.5*1e-10);
printf("k=%d\n",k);
k=m/4;
for(i=k;i<m;i=i+k)
{
printf("(%.2f,0.25), y=%f, err=%.4e.\n",x[i],u[i][n/4],fabs(exact(x[i],y[n/4])-u[i][n/4]));
}
k=m/4;
for(i=k;i<m;i=i+k)
{
printf("(%.2f,0.50), y=%f, err=%.4e.\n",x[i],u[i][n/2],fabs(exact(x[i],y[n/2])-u[i][n/2]));
}
return 0;
}
double leftboundary(double y)
{
return sin(pi*y);
}
double rightboundary(double y)
{
return exp(1.0)*exp(1.0)*sin(pi*y);
}
double topboundary(double x)
{
return 0.0;
}
double bottomboundary(double x)
{
return 0.0;
}
double f(double x, double y)
{
return (pi*pi - 1)*exp(x)*sin(pi*y);
}
double exact(double x, double y)
{
return exp(x)*sin(pi*y);
}
步长为时,计算结果如下:
m=64,n=32.
k=3315
(0.50,0.25), y=1.166702, err=8.7958e-04.
(1.00,0.25), y=1.923620, err=1.5048e-03.
(1.50,0.25), y=3.170908, err=1.8751e-03.
(0.50,0.50), y=1.649965, err=1.2439e-03.
(1.00,0.50), y=2.720410, err=2.1281e-03.
(1.50,0.50), y=4.484341, err=2.6518e-03.
步长为时,计算结果如下:
m=128,n=64.
k=12332
(0.50,0.25), y=1.166042, err=2.1984e-04.
(1.00,0.25), y=1.922492, err=3.7612e-04.
(1.50,0.25), y=3.169502, err=4.6879e-04.
(0.50,0.50), y=1.649032, err=3.1090e-04.
(1.00,0.50), y=2.718814, err=5.3191e-04.
(1.50,0.50), y=4.482352, err=6.6297e-04.
四、结论
从计算结果可知,当步长减小为1/2时,误差减小为1/4,可见五点菱形差分格式是二阶收敛的。