4. NS 方程#

4.1. 方腔流#

Navier-Stocks 方程:

(4.1)#\[\begin{equation} \left\{ \begin{aligned} &\partial_t u - \mu\Delta u + (u\cdot\nabla)u + \nabla p = f, && {\rm in} \quad \Omega\times(0, T]\\ &\nabla\cdot u = 0, && {\rm in} \quad \Omega\times(0, T] \end{aligned} \right. \end{equation}\]

边界条件除上边界外为滑移边界条件, 上边界速度为 \(u=(1, 0)\). 初边值条件 0.

from firedrake import *
from firedrake.output import VTKFile as File

mu = 1
T = 0.25

N_S = 16
N_T = 128

tau = T/N_T
h = 1/N_S

mesh = RectangleMesh(N_S, N_S, 1, 1)

u_0 = as_vector((0, 0))
u_D = as_vector((1, 0))
f = as_vector([0, 0])

4.2. 函数空间#

采用 MINI 元, 即 P1 \(\times\) P1b.

P1b 由 P1 加上 Bubble 组成.

NodalEnrichedElement, EnrichedElement

VectorFunctionSpace 构造向量空间

cell = mesh.ufl_cell()
tdim = cell.topological_dimension()

# Mini element: P1 X P1b
P1 = FiniteElement("CG", cell, 1)
B = FiniteElement("B", cell, tdim+1)
P1b = P1 + B # or P1b = NodalEnrichedElement(P1, B)

V_u = VectorFunctionSpace(mesh, P1b)
V_p = FunctionSpace(mesh, "CG", 1)
V = MixedFunctionSpace([V_u, V_p])

4.3. 弱形式#

线性格式:

(4.2)#\[\begin{equation} \left\{ \begin{aligned} &\frac{1}{\tau}(u^n - u^{n-1}, v) + \mu(\nabla u^n, \nabla v) + \frac{1}{2}(((u^{n-1}\cdot\nabla)u^n, v) - ((u^{n-1}\cdot\nabla)v, u^n)) - (p^n, \nabla\cdot v) = (f^n, v)\\ &(q, \nabla\cdot u^n) = 0 \end{aligned} \right. \end{equation}\]

非线性格式

(4.3)#\[\begin{equation} \left\{ \begin{aligned} &\frac{1}{\tau}(u^n - u^{n-1}, v) + \mu(\nabla u^n, \nabla v) + \frac{1}{2}(((u^n\cdot\nabla)u^n, v) - ((u^n\cdot\nabla)v, u^n)) - (p^n, \nabla\cdot v) = (f^n, v)\\ &(q, \nabla\cdot u^n) = 0 \end{aligned} \right. \end{equation}\]
  • TrialFunctions, TestFunctions:

    tuple 返回函数空间中的试验/测试函数,

    主要用于 MixedFunctionSpace.

  • split

    • split: 以索引的方式获取 MixedFunctionSpace 中函数的分量 (保留 UFL 关联信息, 用于定义变分形式)

由于该问题是非线性问题, 我们打算用 NonlinearVariationalSolver 进行求解, 所以下面定义 w 使用了 Function 而不是 TrialFunction/TrialFunctions.

w = Function(V) # u and p
u, p = split(w)

v, q = TestFunctions(V)

w_nm1 = Function(V)
u_nm1, p_nm1 = w_nm1.subfunctions
u_nm1.rename('u_h') # for visualization in paraview
p_nm1.rename('p_h')

F = (
      Constant(1/tau)*inner(u - u_nm1, v)*dx
    + mu*inner(grad(u), grad(v))*dx
    + 1/2*inner(dot(grad(u), u/2), v)*dx
    - 1/2*inner(dot(grad(v), u/2), u)*dx
    - p*div(v)*dx
    + div(u)*q*dx
    - inner(f, v)*dx(domain=mesh)
)

4.4. 定义 Solver#

类似于纯 Neumann 问题, 我们将使用 nullspace 参数.

注意下面混合空间中, 边界条件和 nullspace 的定义.

bc1 = DirichletBC(V.sub(0).sub(0), 0, (1, 2))
bc2 = DirichletBC(V.sub(0).sub(1), 0, (3, 4))
bc3 = DirichletBC(V.sub(0).sub(0), 1, 4)  # upper boundary

nullspace = MixedVectorSpaceBasis(V, [V.sub(0), VectorSpaceBasis(constant=True, comm=mesh.comm)])

problem = NonlinearVariationalProblem(F, w, bcs=[bc1, bc2, bc3])  # F = 0
solver = NonlinearVariationalSolver(problem,
                                    options_prefix='ns',
                                    solver_parameters=None, # {'snes_converged_reason': None, 'snes_max_it': 100},
                                    nullspace=nullspace
                                   )

4.5. 时间循环和保存结果到 pvd 文件#

u_, p_ = w.subfunctions

output = File('pvd/ns-equation.pvd')

u_nm1.assign(0)
output.write(u_nm1, p_nm1, time=0)

# for i in range(N_T):
for i in ProgressBar('Time').iter(range(N_T)):
    t = tau*(i+1)
    
    solver.solve()
    
    u_nm1.assign(u_)
    p_nm1.assign(p_)

    if (i+1)%32 == 0:
        output.write(u_nm1, p_nm1, time=t)
?25l
Time ▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣ 128/128 [0:00:07]
?25h

4.6. TODO: 随时间变系数问题#

from firedrake import *
c = Constant(0)

for i in range(5):
    c.assign(i*0.1)
    print(f"i = {i}, c = {c}")

4.7. ParaView 可视化计算结果#

Pipeline 和 Filter

4.7.1. 二维结果 (surf 图)#

Filter: Wrap by scalar

4.7.2. 选择部分区域显示#

View -> Find Data

4.7.3. 并行数据显示各进程区域#

Filter -> Connectivity