NS 方程是偏微分方程领域最重要的一个方程,没有之一。可惜的是,MATLAB 的自带的工具箱无法对它进行很好地求解,主要原因在于非线性项(对流项)无法处理,如果把它和右端载荷项放在一块处理,显然 MATLAB 工具箱是没办法支持这件事情的。
那么,对于不想写太多代码,又想用 MATLAB 这个工具求解 NS 方程,有什么好办法呢?我给大家推荐 FEATool Multiphysics。这个工具够傻瓜够强大,能求解很多 CFD 的问题,能和 FEniCS 和 Openfoam 联动,求足够完整的文档,以及完备的社区。我是推荐。下面举两个简单的例子来说明怎么使用它求解 NS 方程。安装就不用说,一搜就有了。
绕柱平流问题
问题描述
这是一个典型的 Benchmark 的问题了,也可以参考 DFG flow around cylinder benchmark 2D-1, laminar case Re=20。
考虑这样一个区域(图画得很清楚了,不用多解释):
我们要求解的问题是:
{
ρ
(
∂
u
∂
t
+
(
u
⋅
∇
)
u
)
−
∇
⋅
(
μ
(
∇
u
+
∇
u
T
)
)
+
p
=
F
∇
⋅
u
=
0
\left\{\begin{array}{r} \rho\left(\frac{\partial \mathbf{u}}{\partial t}+(\mathbf{u} \cdot \nabla) \mathbf{u}\right)-\nabla \cdot\left(\mu\left(\nabla \mathbf{u}+\nabla \mathbf{u}^T\right)\right)+p=\mathbf{F} \\ \nabla \cdot \mathbf{u}=0 \end{array}\right.
{ρ(∂t∂u+(u⋅∇)u)−∇⋅(μ(∇u+∇uT))+p=F∇⋅u=0
其中:
ρ
=
1
,
μ
=
0.001
\rho=1,\mu=0.001
ρ=1,μ=0.001。
左边界条件:
u
inflow
=
u
(
0
,
y
)
=
4
U
max
H
−
2
(
y
(
H
−
y
)
,
0
)
\mathbf{u}_{\text {inflow }}=\mathbf{u}(0, y)=4 U_{\max } H^{-2}(y(H-y), 0)
uinflow =u(0,y)=4UmaxH−2(y(H−y),0)
这里的
U
max
=
0.3
U_{\max }=0.3
Umax=0.3。
H
=
0.41
H=0.41
H=0.41 表示上图区域的高度。
在这条件下, U mean = 0.2 U_{\text {mean }}=0.2 Umean =0.2, R e = ρ U mean D / μ = 20 R e=\rho U_{\text {mean }} D / \mu=20 Re=ρUmean D/μ=20,这就导致了层流。此时,变成了一个稳态问题。
右边界条件:
ν
∂
η
u
−
p
η
=
0
\nu \partial_\eta u-p \eta=0
ν∂ηu−pη=0
这里的
η
\eta
η 是外法向。
其他的边界是无滑移边界条件(名词看起来高大上,其实就是速度为 0)。
观察量
我们定义阻力系数和升力系数如下:
c
d
=
2
F
d
ρ
U
mean
2
D
,
c
l
=
2
F
l
ρ
U
mean
2
D
c_d=\frac{2 F_d}{\rho U_{\text {mean }}^2 D}, \quad c_l=\frac{2 F_l}{\rho U_{\text {mean }}^2 D}
cd=ρUmean 2D2Fd,cl=ρUmean 2D2Fl
其中的阻力和升力定义为:
F
d
=
∫
S
(
μ
∂
u
τ
(
t
)
∂
n
n
y
−
p
n
x
)
d
S
,
F
l
=
−
∫
S
(
μ
∂
u
τ
(
t
)
∂
n
n
x
+
p
n
y
)
d
S
F_d=\int_S\left(\mu \frac{\partial \mathbf{u}_\tau(t)}{\partial n} n_y-p n_x\right) d S, \quad F_l=-\int_S\left(\mu \frac{\partial \mathbf{u}_\tau(t)}{\partial n} n_x+p n_y\right) d S
Fd=∫S(μ∂n∂uτ(t)ny−pnx)dS,Fl=−∫S(μ∂n∂uτ(t)nx+pny)dS
其中的
u
τ
\mathbf{u}_\tau
uτ 是切方向。
工具箱实现
Benchmark 1。
这个问题是 FEATool Multiphysics 的一个算例。打开工具箱,选择圆柱绕流算例,就是这个例子,点击 Run,它会自动地给你演示如何做。演示完也就做完了,你可以把变量导入到工作空间,也可以把整个过程生成代码。看一下代码:
%% FEATool Multiphysics Version 1.16, Build 22.08.241, Model M-Script file.
%% Created on 18-Oct-2022 11:22:20 with MATLAB 9.9.0.1467703 (R2020b) PCWIN64.
clc
clear
close all
%% Starting new model.
fea.sdim = { 'x', 'y' };
fea.geom = struct;
fea = addphys( fea, @navierstokes, { 'u', 'v', 'p' } );
%% Geometry operations.
gobj = gobj_rectangle( 0, 1, 0, 1, 'R1');
fea = geom_add_gobj( fea, gobj );
gobj = gobj_rectangle( [0], [2.2], [0], [0.41], 'R1' );
fea.geom.objects{1} = gobj;
gobj = gobj_ellipse( [ 0.4;0.2 ], 0.2, 0.1, 'E1');
fea = geom_add_gobj( fea, gobj );
gobj = gobj_ellipse( [0.2 0.2], [0.05], [0.05], 'E1' );
fea.geom.objects{2} = gobj;
fea.geom = geom_apply_formula( fea.geom, 'R1-E1' );
%% Grid operations.
fea.grid = gridgen( fea, 'gridgen', 'default', 'hmax', 0.13, 'grading', 0.3 );
fea.grid = gridgen( fea, 'gridgen', 'default', 'hmax', 0.02, 'grading', 0.3 );
%% Equation settings.
fea.phys.ns.dvar = { 'u', 'v', 'p' };
fea.phys.ns.sfun = { 'sflag1', 'sflag1', 'sflag1' };
fea.phys.ns.eqn.coef = { 'rho_ns', 'rho', 'Density', { 'rho' };
'miu_ns', 'mu', 'Viscosity', { 'miu' };
'Fx_ns', 'F_x', 'Volume force in x-direction', { '0' };
'Fy_ns', 'F_y', 'Volume force in y-direction', { '0' };
'u0_ns', 'u_0', 'Initial condition for u', { '0' };
'v0_ns', 'v_0', 'Initial condition for v', { '0' };
'p0_ns', 'p_0', 'Initial condition for p', { '0' };
'miuT_ns', [], 'Turbulent viscosity', { 0 } };
fea.phys.ns.eqn.sdiff = { ' - (miu_ns+miuT_ns)*(2*ux_x + uy_y + vx_y)', ' - (miu_ns+miuT_ns)*(vx_x + uy_x + 2*vy_y)', [] };
fea.phys.ns.eqn.sconv = { 'rho_ns*(u*ux_t + v*uy_t)', 'rho_ns*(u*vx_t + v*vy_t)', [] };
fea.phys.ns.eqn.seqn = { 'rho_ns*u'' - (miu_ns+miuT_ns)*(2*ux_x + uy_y + vx_y) + rho_ns*(u*ux_t + v*uy_t) + p_x = Fx_ns', 'rho_ns*v'' - (miu_ns+miuT_ns)*(vx_x + uy_x + 2*vy_y) + rho_ns*(u*vx_t + v*vy_t) + p_y = Fy_ns', 'ux_t + vy_t = 0' };
fea.phys.ns.eqn.vars = { 'Velocity field', 'sqrt(u^2+v^2)';
'x-velocity', 'u';
'y-velocity', 'v';
'Pressure', 'p';
'Vorticity', 'vx-uy';
'Velocity field', { 'u', 'v' } };
fea.phys.ns.prop.isaxi = 0;
fea.phys.ns.prop.artstab.id = 0;
fea.phys.ns.prop.artstab.id_coef = 0.5;
fea.phys.ns.prop.artstab.sd = 0;
fea.phys.ns.prop.artstab.sd_coef = 0.25;
fea.phys.ns.prop.artstab.ps = 1;
fea.phys.ns.prop.artstab.ps_coef = 1;
fea.phys.ns.prop.artstab.iupw = 0;
fea.phys.ns.prop.turb.model = 'laminar';
fea.phys.ns.prop.turb.wallf = 1;
fea.phys.ns.prop.turb.inlet = [];
fea.phys.ns.prop.active = [ 1;
1;
1 ];
fea.phys.ns.prop.intb = 0;
%% Constants and expressions.
fea.expr = { 'h', '0.41';
'diam', '0.1';
'rho', '1';
'miu', '0.001';
'umax', '0.3';
'umean', '2/3*umax';
'fx', 'nx*p+miu*(-2*nx*ux-ny*(uy+vx))';
'cd', '2*fx/(rho*umean^2*diam)' };
%% Boundary settings.
fea.phys.ns.bdr.sel = [ 1, 4, 1, 2, 1, 1, 1, 1 ];
fea.phys.ns.bdr.coef = { 'bw_ns', 'u = 0', 'Wall/no-slip', [], { 1, 1, 1, 1, 1, 1, 1, 1;
1, 1, 1, 1, 1, 1, 1, 1;
0, 0, 0, 0, 0, 0, 0, 0 }, [], { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0 };
'bv_ns', 'u = u_0', 'Inlet/velocity', { 'u_0';
'v_0';
[] }, { 1, 1, 1, 1, 1, 1, 1, 1;
1, 1, 1, 1, 1, 1, 1, 1;
0, 0, 0, 0, 0, 0, 0, 0 }, [], { 0, 0, 0, '4*umax*y*(h-y)/h^2', 0, 0, 0, 0;
0, 0, 0, '0', 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0 };
'bn_ns', '-n.(-pI + mu(grad u+grad uT)) = 0', 'Neutral outflow/stress boundary', [], { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0 }, [], { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0 };
'bp_ns', 'p = p_0', 'Outflow/pressure', { [];
[];
'p_0' }, { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
1, 1, 1, 1, 1, 1, 1, 1 }, { { '-p*nx';
'-p*ny';
'0' };
[] }, { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, '0', 0, 0, 0, 0, 0, 0 };
'bs_ns', 'n&.u = 0, -n.(-pI + mu(grad u+grad uT)).t = 0', 'Symmetry/slip', [], { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0 }, [], { 'solve_hook_bdrslip', 'solve_hook_bdrslip', 'solve_hook_bdrslip', 'solve_hook_bdrslip', 'solve_hook_bdrslip', 'solve_hook_bdrslip', 'solve_hook_bdrslip', 'solve_hook_bdrslip';
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0 } };
fea.phys.ns.bdr.coefi = { 'bcic_ns', 'n.(F_1-F_2) = 0, F_i=-p_iI + mu_i(grad u_i+grad u_iT)', 'Continuity', [], { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0 }, [], { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0 };
'bcij_ns', 'u = u_0', 'Prescribed velocity', { 'u_0';
'v_0';
[] }, { 1, 1, 1, 1, 1, 1, 1, 1;
1, 1, 1, 1, 1, 1, 1, 1;
0, 0, 0, 0, 0, 0, 0, 0 }, [], { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0 };
'bcir_ns', 'p = p_0', 'Prescribed pressure', { [];
[];
'p_0' }, { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
1, 1, 1, 1, 1, 1, 1, 1 }, [], { 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0 } };
fea.phys.ns.bdr.vars = { 'Viscous force, x-component', '(miu_ns+miuT_ns)*(2*nx*ux+ny*(uy+vx))';
'Viscous force, y-component', '(miu_ns+miuT_ns)*(nx*(vx+uy)+2*ny*vy)';
'Total force, x-component', '-nx*p+(miu_ns+miuT_ns)*(2*nx*ux+ny*(uy+vx))';
'Total force, y-component', '-ny*p+(miu_ns+miuT_ns)*(nx*(vx+uy)+2*ny*vy)' };
fea.phys.ns.prop.intb = 0;
%% Solver call.
fea = parsephys( fea );
fea = parseprob( fea );
fea.sol.u = solvestat( fea, ...
'iupw', [ 0, 0, 0 ], ...
'linsolv', 'backslash', ...
'icub', 'auto', ...
'nlrlx', 1, ...
'toldef', 1e-06, ...
'tolchg', 1e-06, ...
'reldef', 0, ...
'relchg', 1, ...
'maxnit', 20, ...
'hook', [], ...
'nproc', 2, ...
'init', { 'u0_ns', 'v0_ns', 'p0_ns' }, ...
'solcomp', [ 1; 1; 1 ] );
%% Postprocessing.
postplot( fea, ...
'surfexpr', 'sqrt(u^2+v^2)', ...
'title', 'surface: Velocity field', ...
'solnum', 1 );
postplot( fea, ...
'surfexpr', 'sqrt(u^2+v^2)', ...
'isoexpr', 'p', ...
'isolev', 10, ...
'arrowexpr', { 'u', 'v' }, ...
'arrowspacing', { 10, 10 }, ...
'arrowscale', 1, ...
'title', 'surface: Velocity field, contour: Pressure, arrow: Velocity field', ...
'solnum', 1 );
postplot( fea, ...
'surfexpr', 'sqrt(u^2+v^2)', ...
'isoexpr', 'p', ...
'isolev', 10, ...
'arrowexpr', { 'u', 'v' }, ...
'arrowspacing', { 10, 10 }, ...
'arrowscale', 1, ...
'title', 'surface: Velocity field, contour: Pressure, arrow: Velocity field', ...
'solnum', 1 );
integral_value = intbdr( 'cd', fea, [5 6 7 8], 2, 1 );
需要指明的是,这份代码的运行需要在工具箱打开,变量载入空间的时候才能 work。
周期圆柱绕流问题
这也是个 Benchmark2,可以参考 DFG flow around cylinder benchmark 2D-2, time-periodic case Re=100,此时它不再是工具箱的一个算例,我们需要做简单的修改。几何剖分沿用上面的两个例子。
别的不需要变,我们唯一要改的只是 U max U_{\max } Umax,从 0.3 改为 U max = 1.5 U_{\max }=1.5 Umax=1.5。
在这个情况下, U mean = 2 3 ⋅ 1.5 = 1.0 U_{\text {mean }}=\frac{2}{3} \cdot 1.5=1.0 Umean =32⋅1.5=1.0, L = 2 ⋅ 0.05 = 0.1 L=2 \cdot 0.05=0.1 L=2⋅0.05=0.1, Re = U mean L ν = 1.0 ⋅ 0.1 0.001 = 100 \operatorname{Re}=\frac{U_{\text {mean }} L}{\nu}=\frac{1.0 \cdot 0.1}{0.001}=100 Re=νUmean L=0.0011.0⋅0.1=100。这时候算出来雷诺数是 100。这就形成了时间周期行为。
直接修改上述的代码中的 U max U_{\max } Umax,可以得到。
固定时间间隔圆柱扰流
考虑 Benchmark 3,DFG flow around cylinder benchmark 2D-3, fixed time interval (Re=100)。
事实上,依然还是修改 U max = 1.5 sin ( π ⋅ t / 8 ) U_{\max } = 1.5 \sin (\pi \cdot t / 8) Umax=1.5sin(π⋅t/8)。修改为
u inflow = 6 ⋅ sin ( π ⋅ t / 8 ) ⋅ ( y ⋅ ( 0.41 − y ) ) / 0.4 1 2 u_{\text {inflow }}=6 \cdot \sin (\pi \cdot t / 8) \cdot(y \cdot(0.41-y)) / 0.41^2 uinflow =6⋅sin(π⋅t/8)⋅(y⋅(0.41−y))/0.412
事实上,关于这个,工具箱封装了一个内置函数 function [ fea, out ] = ex_navierstokes6( varargin )
直接可以用。其中,可调参数可以参考:ex_navierstokes6 参数表。
通过阅读代码可以知道,通过修改 instatbc=false
,就可以把 Benchmark 3 改成了 Benchmark 2,即周期圆柱绕流问题。即写成:
clc
clear
close all
tic
[ fea, out ] = ex_navierstokes6( 'instatbc',false );
toc
最后 show 一个结果如下:
这里的总时间 t = 8 t=8 t=8 ,时间步长 d t = 0.02 dt=0.02 dt=0.02,网格尺寸使得空间自由度 9456。所消耗的时间 8 分钟。