混合形式泊松方程之FEniCS求解

【本文仅是视频中Markdown文件内容,不包括同名视频中讲解】

如果你希望和我一样Win10上安装FEniCS,建议采用Win10 + WSL2 + Ubuntu。

FEniCS讲座系列

第1讲:有限元法求解偏微分方程之FEniCS入门讲解【上一讲】

第2讲要点

  • 将高阶偏微分方程 变换成 1阶偏微分方程组
  • 1阶偏微分方程组 变换成 1个变分方程
  • 自然边界 和 基本边界
  • 如何定义混合空间
  • 如何自定义表达式
  • FEniCS快捷可视化

混合形式的泊松方程

以泊松方程为例,这是一个二阶方程:

\[ -\Delta u = f \]

现在我们要将其改一阶方程组,引入一个叫热通量(或简称 通量)的辅助矢量\(\sigma = - \nabla u\),不难得到:

\[ \nabla \cdot \sigma = f \qquad \text{in } \Omega \\ \sigma + \nabla u = 0 \qquad \text{in } \Omega \]

这个通量是有物理意义的,那就是所谓的傅里叶定律:热通量 \(\sigma\)与温度 \(u\)的负梯度成比例。

\[ \sigma = - \kappa \nabla u \]

这里我们取\(\kappa=1\)而已。

而泊松方程实际就是傅里叶定律+能量守恒的结果。如图:

稳态情况下,根据能量守恒,通过任意闭合曲面\(\partial\omega\)流出的能量(这里指热量) 必须等于 曲面内热源发出的能量:

\[ \textcolor{red}{\int_{\partial\omega} \sigma \cdot n ds} = \int_\omega f dx \]

进而

\[ \textcolor{red}{\int_{\omega} \nabla \cdot \sigma dx} = \int_\omega f dx \]

由于闭合区域\(\omega \subset \Omega\)的任意选择性,所以在整个\(\Omega\),下式成立

\[ \nabla \cdot \sigma = f \]

对应的边界条件则需要改成:

\[ u|_{\Gamma_D}=u_0 \qquad \text{ on } \Gamma_D\\ \left. \textcolor{red}{n\cdot\sigma}\right|_{\Gamma_N}=-g \qquad \text{ on } \Gamma_N \]

转换成变分形式

第一个方程是标量方程,选择标量测试函数\(v\);第二个方程是矢量方程,选择矢量测试函数\(\tau\)。 

方程1× \(v\) + 方程2· \(\tau\),然后对全域\(\Omega\)积分,得到:

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

\[ \nabla u \cdot \tau = \textcolor{red}{\nabla \cdot (u\tau)} - u \nabla\cdot\tau \]

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

\[ \int_\Omega \nabla \cdot (u\tau)dx = \int_{\partial \Omega} u \textcolor{red}{\tau \cdot n} ds = \int_{\textcolor{red}{\Gamma_D}} \textcolor{red}{u_0} \tau \cdot n ds \]

参考这个关系,可以具体地定义测试函数和试探函数。

将所有测试函数元组\((\tau,v)\)的集合记作\(\hat{V}\);类似地,将所有试探函数元组\((\sigma,u)\)的集合记作\(V\)

\[ \hat{V} = \{(\tau,v) : v\in L^2(\Omega), \tau\in H(\mathrm{div},\Omega), \textcolor{red}{\tau\cdot n = 0} \text{ on } \Gamma_N\} \\ V = \{(\sigma,u) : u\in L^2(\Omega), \sigma\in H(\mathrm{div},\Omega), \textcolor{red}{\sigma\cdot n = -g} \text{ on } \Gamma_N\} \]

请注意,这里上一讲的不同之处在于, \(\Gamma_D\)上的第一类边界条件 \(u = u_0\)进入了变分公式(现在是自然边界条件),而第二类边界条件条件\(\sigma \cdot n = -g\)则在\(\Gamma_N\)上进入试探空间\(V\)的定义(现在是基本边界条件)。

将上面的结果整理得到最终的变分方程: \[ \boxed{\int_\Omega [\nabla \cdot \sigma v + \sigma\cdot\tau - u \nabla\cdot\tau] dx = \int_\Omega f v dx - \int_{\Gamma_D} u_0 \tau \cdot n ds} \]

改成标准变分问题表述:寻求\((u,\sigma)\in V\),满足

\[ a(\sigma,u; \tau,v) = L(\tau,v) \qquad \forall (\tau,v)\in \hat{V} \\ \textcolor{red}{a(\sigma,u; \tau,v)}=\int_\Omega [\nabla \cdot \sigma v + \sigma\cdot\tau - u \nabla\cdot\tau] dx \\ \textcolor{red}{L(\tau,v)} = \int_\Omega f v dx - \int_{\Gamma_D} u_0 \tau \cdot n ds \]

求解这个混合形式泊松方程

  • \(\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
from dolfin import *

# 创建单位矩形网格
mesh = UnitSquareMesh(32, 32)

# 定义有限元空间和混合空间
BDM = FiniteElement("BDM", mesh.ufl_cell(), 1)
DG = FiniteElement("DG", mesh.ufl_cell(), 0)
W = FunctionSpace(mesh, BDM * DG)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 第一类界值
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·n = -g的函数G(实际对应公式中的σ)
# g = sin(5x)
class BoundarySource(UserExpression):
def __init__(self, mesh, **kwargs):
self.mesh = mesh
super().__init__(**kwargs)
def eval_cell(self, values, x, ufc_cell):
cell = Cell(self.mesh, ufc_cell.index)
n = cell.normal(ufc_cell.local_facet)
g = sin(5*x[0])
values[0] = -g*n[0]
values[1] = -g*n[1]
def value_shape(self):
return (2,)
G = BoundarySource(mesh, degree=2)

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

1
2
3
4
# 定义基本边界条件
def boundary(x):
return x[1] < DOLFIN_EPS or x[1] > 1.0 - DOLFIN_EPS
bc = DirichletBC(W.sub(0), G, boundary)

第四步:定义变分问题

1
2
3
4
5
6
7
8
# 定义试探函数和测试函数
(sigma, u) = TrialFunctions(W)
(tau, v) = TestFunctions(W)

# 定义变分形式
n = FacetNormal(mesh)
a = (div(sigma)*v + dot(sigma, tau) - div(tau)*u)*dx
L = f*v*dx - u0*dot(n,tau)*ds

第五步:求解并绘图

1
2
3
4
5
6
7
8
# 求解
w = Function(W)
solve(a == L, w, bc)
(sigma, u) = w.split()

# 绘图
plot(sigma)
plot(u)

ParaView可视化

1
2
3
4
5
6
# 将解保存为VTK格式
vtkfile = File("mixed_poisson/sigma.pvd")
vtkfile << sigma

vtkfile = File("mixed_poisson/u.pvd")
vtkfile << u

FEniCS快捷可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 绘制矢量场图(如果是矢量场的话)
# plot(sigma, mode="glyphs")
plot(sigma)

# 绘制位移图(如果是矢量场的话)
plot(sigma, mode="displacement")

# 等高色图(如果是标量场的话)
# plot(u, mode = "contourf",levels = 40)
# plot(u, mode = "color",shading = "gouraud")
plot(u)

# 标量场3d图(曲面)
plot(u, mode="warp", linewidths=0)

# 等高线(如果是标量场的话)
plot(u, mode="contour")

END