有限元法求解偏微分方程之FEniCS入门讲解

FEniCS入门讲解(求解泊松方程)

FEniCS是一个有限元法求解偏微分方程的开源计算平台。

  • 泊松方程
  • 将泊松方程转化成(弱)变分形式
  • 求解微分方程边值问题(1维泊松方程)
  • 求解2维泊松方程
  • ParaView中查看解

泊松方程

第一个例子自然是求解著名的泊松方程。

\[ -\Delta u=f \qquad \text{ in } \Omega \\ u|_{\Gamma_D}=u_0 \qquad \text{ on } \Gamma_D\\ \left.\frac{\partial u}{\partial n}\right|_{\Gamma_N}=g \qquad \text{ on } \Gamma_N = \partial \Omega - \Gamma_D \]

将泊松方程转化成(弱)变分形式

首先:对泊松方程两边都乘上测试函数\(v\),然后对全域\(\Omega\)积分。

\[ -\int_\Omega \textcolor{red}{\Delta u v} dx=\int_\Omega f v dx \]

这里需要说明一下试探函数测试函数的概念。

测试函数,定义为: \[ \hat{V} = \{v\in H^1(\Omega): \textcolor{red}{v=0} \text{ on } \Gamma_D\} \]

试探函数,定义为: \[ V = \{u\in H^1(\Omega): \textcolor{red}{u=u_0} \text{ on } \Gamma_D\} \]

然后,对上面这个变分方程进行变换

\[ \textcolor{red}{\Delta u v} = \left(\frac{\partial}{\partial x_i}\frac{\partial}{\partial x_i}u\right) v = \frac{\partial}{\partial x_i}\left(\frac{\partial u}{\partial x_i} v\right) - \frac{\partial u}{\partial x_i} \frac{\partial u}{\partial x_i} = \textcolor{red}{\nabla \cdot (\nabla u v) -\nabla u \cdot \nabla v} \]

\[ \int_\Omega \nabla u \cdot \nabla v dx=\int_\Omega f v dx + \textcolor{red}{\int_\Omega \nabla \cdot (\nabla u v) dx} \]

\[ \int_\Omega \nabla \cdot (\nabla u v) dx = \int_{\partial \Omega} n \cdot \nabla u v ds = \int_{\partial \Omega} \frac{\partial u}{\partial n} v ds = \textcolor{red}{\int_{\Gamma_N} g v ds} \]

\[ \boxed{\int_\Omega \nabla u \cdot \nabla v dx = \int_\Omega f v dx + \int_{\Gamma_N} g v ds} \]

左边是双线性形式\(a(u,v)\),右边是线性形式\(L(v)\):

\[ a(u,v) = \int_\Omega \nabla u \cdot \nabla v dx \\ L(v) = \int_\Omega f v dx + \int_{\Gamma_N} g v ds \]

于是得到标准的变分问题:寻求\(u\in V\),满足

\[ a(u,v) = L(v) \qquad \forall v \in \hat{V} \]

这就是和前面的泊松问题等价的变分问题。

求解微分方程边值问题(1维泊松方程)

\[ -u''=f \\ u(0)=0,\ u'(1)=g \]

有了前面的准备,现在可以写代码求解了。

第一步:为求解域创建网格,并定义函数空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from dolfin import *

# 单元个数
nel = 20
# 左右端点
xmin = 0
xmax = 1
# 试探/测试函数的多项式阶数
p = 2

# 创建网格
mesh = IntervalMesh(nel,xmin,xmax)
# 使用连续Galerkin定义函数空间
# 每个单元上的p阶(拉格朗日)函数
V = FunctionSpace(mesh,"CG",p)

第二步:定义已提供的表达式

1
2
3
u0 = Constant(0)
f = Constant(-1)
g = Constant(1)

第三步:创建和应用Dirichlet边界条件

1
2
3
4
5
# 定义Dirichlet边界(x = 0)
def dirichlet_boundary(x, on_boundary):
return on_boundary and abs(x[0]) < DOLFIN_EPS
# 在点x=0施加一个Dirichlet条件
bc = DirichletBC(V,u0,dirichlet_boundary)

第四步:定义变分问题

1
2
3
4
5
u = TrialFunction(V)
v = TestFunction(V)

a = inner(grad(u),grad(v))*dx
L = f*v*dx+g*v*ds

第五步:求解并绘图

1
2
3
4
5
6
# 求解
u = Function(V)
solve(a == L, u, bc)

# 绘图
plot(u)

求解2维泊松方程

  • \(\Omega=[0,1]×[0,1]\) (一个单位矩阵)
  • \(\Gamma_D=\{(0,y)\cup(1,y)\subset\partial \Omega\}\)(Dirichlet边界)
  • \(\Gamma_N=\{(x,0)\cup(x,1)\subset\partial \Omega\}\) (Neumann边界)
  • \(g=\sin(5x)\) (法向导数)
  • \(f=10\exp\left\{−\dfrac{(x−0.5)^2+(y−0.5)^2}{0.02}\right\}\) (源项)

类似地,也分五步求解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from dolfin import *

# 第一步:为求解域创建网格,并定义函数空间
mesh = UnitSquareMesh(32, 32)
V = FunctionSpace(mesh, "Lagrange", 1)

# 第二步:定义已提供的表达式
u0 = Constant(0.0)
f = Expression("10*exp(-(pow(x[0] - 0.5, 2) + pow(x[1] - 0.5, 2)) / 0.02)", degree=2)
g = Expression("sin(5*x[0])", degree=2)

# 第三步:创建和应用`Dirichlet`边界条件
def boundary(x):
return x[0] < DOLFIN_EPS or x[0] > 1.0 - DOLFIN_EPS
bc = DirichletBC(V, u0, boundary)

# 第四步:定义变分问题
u = TrialFunction(V)
v = TestFunction(V)
a = inner(grad(u), grad(v))*dx
L = f*v*dx + g*v*ds

# 第五步:求解并绘图

u = Function(V)
solve(a == L, u, bc)
plot(u)

ParaView中查看解

1
2
3
# 将解保存为VTK格式
file = File("poisson.pvd")
file << u

【结束】