本项目的源码保存在 github 仓库 https://github.com/cjyyx/CFD_Learning/tree/main/CFD软件学习/FiPy/cylinder。如果下载整个目录,可以直接运行 SIMPLE.py,结果如图

网格生成

采用 Gmsh 的 python 接口生成网格,源码为 Mesh.py,生成网格如图

控制方程组

假设流体是不可压缩流,则流体满足如下控制方程组

连续性方程

\[\nabla \cdot \vec{u}=0
\]

动量方程

\[\rho \left(\vec{u} \cdot \nabla \right) \vec{u} = \nabla \cdot(\mu \nabla \vec{u}) -\nabla p
\]

其中,\(\vec{u} = (u_{x} , u_{y})\) 表示流场,\(p\) 表示压力场,\(\rho\) 表示密度,\(\mu\) 表示粘度。

将上述方程展开,有

连续性方程

\[\frac{\partial u_{x}}{\partial x} + \frac{\partial u_{y}}{\partial y} = 0
\]

动量方程 \(x\) 方向分量

\[\rho \left(u_{x} \frac{\partial u_{x}}{\partial x} + u_{y} \frac{\partial u_{x}}{\partial y} \right) = \frac{\partial}{\partial x}\left(\mu \frac{\partial u_{x}}{\partial x}\right) + \frac{\partial}{\partial y}\left(\mu \frac{\partial u_{x}}{\partial y}\right) -\frac{\partial p}{\partial x}
\]

动量方程 \(y\) 方向分量

\[\rho \left(u_{x} \frac{\partial u_{y}}{\partial x} + u_{y} \frac{\partial u_{y}}{\partial y} \right) = \frac{\partial}{\partial x}\left(\mu \frac{\partial u_{y}}{\partial x}\right) + \frac{\partial}{\partial y}\left(\mu \frac{\partial u_{y}}{\partial y}\right) -\frac{\partial p}{\partial y}
\]

SIMPLE 算法

如上面的控制方程组所示,不可压缩流的控制方程组是非线性且耦合的,即流场 \(\vec{u}\) 非齐次,流场与压力场相耦合。

SIMPLE 算法的核心思想是把流场拆分为网格面上的流场 \(\vec{v}\),网格内部的流场 \(\vec{u}\),压力场 \(p\)。先假设压力场 \(p\) 和网格面上的流场 \(\vec{v}\) 已知,求网格内部的流场 \(\vec{u}\),再通过求得的流场求网格面上的流场,进而求压力场,如此迭代至较高精度。

因此,我们可以在 FiPy 中声明变量

U = 10.

Vx = CellVariable(mesh=mesh, name="x velocity", value=U)
Vy = CellVariable(mesh=mesh, name="y velocity", value=0.) Vf = FaceVariable(mesh=mesh, rank=1)
Vf.setValue((Vx.faceValue, Vy.faceValue)) p = CellVariable(mesh=mesh, name="pressure", value=0.)
pc = CellVariable(mesh=mesh, value=0.) # 压力修正项,后面会提到

首先,我们假设压力场已知,为 \(p^{*}\),且各网格面上的流场已知,为 \(\vec{v}\),则可离散化动量方程,求出网格内部的流场 \(\vec{u}^{*}\)

\[a_{P} \vec{u}_{P}^{*}=\sum_{f} a_{A} \vec{u}_{A}^{*}-V_{P}\left(\nabla p^{*}\right)_{P}
\]

其中 \(a_{P}, a_{A}\) 都可通过网格面上的流场 \(\vec{v}\) 和网格的几何信息计算。\(V_{P}\) 是控制体的体积,\(\left(\nabla p^{*}\right)_{P}\) 是一个源项,通过压力场 \(p^{*}\) 计算。

在 FiPy 中的代码如下

mu = 0.1
rho = 1. Vx_Eq = \
UpwindConvectionTerm(coeff=Vf, var=Vx) * rho == \
DiffusionTerm(coeff=mu, var=Vx) - \
ImplicitSourceTerm(coeff=1.0, var=p.grad[0])
Vy_Eq = \
UpwindConvectionTerm(coeff=Vf, var=Vy) * rho == \
DiffusionTerm(coeff=mu, var=Vy) - \
ImplicitSourceTerm(coeff=1.0, var=p.grad[1])

对这个方程进行求解

Rv = 0.8
apx = CellVariable(mesh=mesh, value=1.)
apy = CellVariable(mesh=mesh, value=1.) Vx_Eq.cacheMatrix()
Vx_Eq.cacheRHSvector()
xres = Vx_Eq.sweep(var=Vx, underRelaxation=Rv)
xmat = Vx_Eq.matrix
xrhs = Vx_Eq.RHSvector
apx[:] = numerix.asarray(xmat.takeDiagonal()) Vy_Eq.cacheMatrix()
Vy_Eq.cacheRHSvector()
yres = Vy_Eq.sweep(var=Vy, underRelaxation=Rv)
ymat = Vy_Eq.matrix
yrhs = Vy_Eq.RHSvector
apy[:] = numerix.asarray(ymat.takeDiagonal())

其中,xmat, ymat 是 \(a_{P}, a_{A}\) 构成的系数矩阵;xrhs, yrhs 是 \(-V_{P}\left(\nabla p^{*}\right)_{P}\) 构成的列向量;apx, apy 是系数矩阵对角线上的值,即 \(a_{P}\) 构成的列向量;xres, yres 是残差,与迭代的收敛相关。

事实上,xmat, ymat 这两个矩阵是完全一致的,即

xmat.matrix.data == ymat.matrix.data # True

且当欠松弛系数 Rv = 1. 时,有

Vc = mesh.cellVolumes

xrhs == (-p.grad[0].value * Vc) # True
yrhs == (-p.grad[1].value * Vc) # True

总之,我们通过 pVf 求出了 Vx, Vy,下面可以通过 Vx, Vy 更新 Vf

Vf.setValue((Vx.faceValue, Vy.faceValue))

值得注意的是,直接通过几何插值获取网格面上的流场,可能会造成网格问题,更好的方案是利用 Rhie-Chow 插值

Vcf = CellVariable(mesh=mesh, value=Vc).faceValue

presgrad = p.grad
facepresgrad = presgrad.faceValue
Vf[0] = Vx.faceValue + Vcf / apx.faceValue * \
(presgrad[0].faceValue-facepresgrad[0])
Vf[1] = Vy.faceValue + Vcf / apx.faceValue * \
(presgrad[1].faceValue-facepresgrad[1])

接下来,可以通过流场来更新压力场。当然我们不能只靠动量方程,还要结合连续性方程。我们假设精确的流场和压力场分别为

\[\vec{u} = \vec{u}^{\ast} + \vec{u}^{\prime}
\]
\[p = p^{\ast} + p^{\prime}
\]

将精确值代入离散动量方程和连续性方程,有

\[a_{P} \left(\vec{u}^{*} + \vec{u}^{\prime}\right)_{P} = \sum_{f} a_{A} \left(\vec{u}^{*} + \vec{u}^{\prime}\right)_{A} - V_{P}\left[\nabla \left(p^{*} + p^{\prime}\right)\right]_{P}
\]
\[\nabla \cdot \vec{u}^{*}+\nabla \cdot \vec{u}^{\prime}=0
\]

由于 \(\vec{u}_{P}^{*}\) 已经满足离散动量方程,则有

\[a_{P} \vec{u}_{P}^{\prime}=\sum_{f} a_{A} \vec{u}_{A}^{\prime}-V_{P}\left(\nabla p^{\prime}\right)_{P}
\]

忽略 \(\sum\limits_{f} a_{A} \vec{u}_{A}^{\prime}\) 项(大概这个忽略不会对最终的结果造成太大的影响吧),有

\[\vec{u}_{P}^{\prime}=-\frac{V_{P}\left(\nabla p^{\prime}\right)_{P}}{a_{P}}
\]

代入连续性方程,可得

\[\nabla \frac{V_{P}}{a_{P}} \cdot \nabla p^{\prime}=\nabla \cdot \vec{u}^{*}
\]

该方程的形式为扩散方程,因此相应的代码为

coeff = (
1. / (
apx.faceValue
* mesh._faceAreas
* mesh._cellDistances
)
)
pc_Eq = \
DiffusionTerm(coeff=coeff, var=pc) \
- Vf.divergence

求解,然后可以更新压力场

pcres = pc_Eq.sweep(var=pc)
p.setValue(p + Rp * pc)

其中 Rp 是欠松弛系数,用来控制收敛速度和稳定性。

接下来可以根据 \(\vec{u} = \vec{u}^{\ast} + \vec{u}^{\prime}\) 更新流场

Vx.setValue(Vx-(Vc*pc.grad[0])/apx)
Vy.setValue(Vy-(Vc*pc.grad[1])/apx) presgrad = p.grad
facepresgrad = presgrad.faceValue
Vf[0] = Vx.faceValue + Vcf / apx.faceValue * \
(presgrad[0].faceValue-facepresgrad[0])
Vf[1] = Vy.faceValue + Vcf / apx.faceValue * \
(presgrad[1].faceValue-facepresgrad[1])

边界条件

对于圆柱绕流,比较合适的边界条件组合是

  • 进口固定速度,压力零梯度
  • 出口速度零梯度,压力固定值
  • 壁面速度为零,压力零梯度

相应的,代码为

inletFace = mesh.physicalFaces["inlet"]
outletFace = mesh.physicalFaces["outlet"]
cylinderFace = mesh.physicalFaces["cylinder"]
top_bottomFace = mesh.physicalFaces["top"] | mesh.physicalFaces["bottom"] Vx.constrain(U, inletFace)
Vy.constrain(0., inletFace)
p.faceGrad.constrain(0., inletFace)
pc.faceGrad.constrain(0., inletFace) Vx.faceGrad.constrain(0., outletFace)
Vy.faceGrad.constrain(0., outletFace)
p.constrain(0., outletFace)
pc.constrain(0., outletFace) Vx.constrain(0., cylinderFace)
Vy.constrain(0., cylinderFace)
p.faceGrad.constrain(0., cylinderFace)
pc.faceGrad.constrain(0., cylinderFace) Vx.faceGrad.constrain(0., top_bottomFace)
Vy.faceGrad.constrain(0., top_bottomFace)
p.constrain(0., top_bottomFace)
pc.constrain(0., top_bottomFace)

稳定性问题

雷诺数

雷诺数(Reynolds number)是描述流体运动状态的一个无量纲数。

其定义为

\[Re = \frac{\rho U L}{\mu}
\]

其中,\(\rho\) 是流体密度,\(U\) 是流体速度,\(L\) 是特征长度,\(\mu\) 是粘度。

当雷诺数大于一定的临界值时,流体在管道中的流动状态将从稳定的层流转变为不稳定的湍流,这时候不存在稳态解。

因此,在求解稳态圆柱绕流时,如果雷诺数过大,则在物理上不应该存在稳态解,这在求解过程中,表现为残差收敛过慢或无法收敛,甚至发生残差爆炸。

当然,可以通过一系列手段,强行求出稳态解。

RuntimeError: Factor is exactly singular

发生这个错误是因为离散动量方程的系数矩阵是奇异的,想要降低该错误发生的概率,可以采取以下措施

  • 使用更精细网格
  • 降低 Rv
  • 提高雷诺数

残差爆炸

主要是因为雷诺数过高。避免残差爆炸可以采取以下措施

  • 使用更精细网格
  • 降低 Rp
  • 设置阈值,避免流场变量溢出,即
V_limit = 1e2
p_limit = 2e3 Vx[Vx.value > V_limit] = V_limit
Vx[Vx.value < -V_limit] = -V_limit Vy[Vy.value > V_limit] = V_limit
Vy[Vy.value < -V_limit] = -V_limit Vf[Vf.value > V_limit] = V_limit
Vf[Vf.value < -V_limit] = -V_limit p[p.value > p_limit] = p_limit
p[p.value < -p_limit] = -p_limit

比较稳定的求解方法

尽可能使用更精细网格。

在迭代的开始阶段,调整雷诺数,使雷诺数较低;使 Rv, Rp 较低。从而确保迭代的稳定性。

在迭代过程中,逐渐提升雷诺数至目标值;提升 Rv, Rp 以加快求解速度,但确保 Rv, Rp 小于 1。

当残差稳定时,判断已经收敛,退出求解。

Gmsh 和 FiPy 求解稳态圆柱绕流的更多相关文章

  1. OpenFOAM&Gmsh&CFD圆柱绕流(两个圆柱)

    问题: 圆柱绕流问题,模拟仿真有两个圆柱.一个源的流体变化情况. 解决步骤: 1.使用Gmsh画出网格,并保存cylindertwo.msh 2.以Cavity为基础创建新的Case:Cylinder ...

  2. LibTorch | 使用神经网络求解一维稳态对流扩散方程

    0. 写在前面 本文将使用基于LibTorch(PyTorch C++接口)的神经网络求解器,对一维稳态对流扩散方程进行求解.研究问题参考自教科书\(^{[1]}\)示例 8.3. 目录 0. 写在前 ...

  3. 【小白的CFD之旅】24 稳态和瞬态

    小白最近在练习案例的时候,对稳态和瞬态的问题,产生了一些疑问.譬如说,为什么有的案例用稳态,而有的案例用瞬态?有时候相同的案例既可以用稳态也可以用瞬态,而有的案例却只能用瞬态计算?小白决定找小牛师兄问 ...

  4. [家里蹲大学数学杂志]第033期稳态可压Navier-Stokes方程弱解的存在性

    1. 方程  考虑 $\bbR^3$ 中有界区域 $\Omega$ 上如下的稳态流动: $$\bee\label{eq} \left\{\ba{ll} \Div(\varrho\bbu)=0,\\ \ ...

  5. ANSYS稳态热分析

    目录 题目 APDL操作 温度云图 题目 管子内径外径为r1=4.125mm,r2=4.635mm,中间物体的产热功率为Q=8.73e8W/m3,管外有温度t=127℃的冷水流过,冷却水与管子外表面的 ...

  6. [Fundamental of Power Electronics]-PART I-2.稳态变换器原理分析-2.2 伏秒平衡/安秒平衡 小纹波近似

    2.2 电感伏秒平衡.电容充放电平衡以及小纹波近似 让我们更加仔细地观察图2.6中的buck变换器的电感和电容的波形.我们是不可能设计一个滤波器能够只允许直流分量通过而完全滤除开关频率次谐波的.所以, ...

  7. [Fundamental of Power Electronics]-PART I-2.稳态变换器原理分析-2.3 Boost 变换器实例

    2.3 Boost 变换器实例 图2.13(a)所示的Boost变换器器是另一个众所周知的开关模式变换器,其能够产生幅值大于直流输入电压的直流输出电压.图2.13(b)给出了使用MOSFET和二极管的 ...

  8. [Fundamental of Power Electronics]-PART I-2.稳态变换器原理分析-2.4 Cuk变换器实例

    2.4 Cuk 变换器 作为第二个示例,考虑图2.20(a)的变换器.该变换器执行类似于降压-升压变换器的直流转换功能:它可以增加或减小直流电压的幅值,并且可以反转极性.使用晶体管和二极管的实际实现如 ...

  9. [Fundamental of Power Electronics]-PART I-2.稳态变换器原理分析-2.5/2.6 多极点滤波器电压纹波估计及要点小结

    2.5 含两极点低通滤波器变换器的输出电压纹波估计 在分析包含两极点低通滤波器的变换器如Cuk变换器及Buck变换器(图2.25)输出时,小纹波近似将会失效.对于这些变换器而言,无论输出滤波电容的值是 ...

  10. [Fundamental of Power Electronics]-PART I-3.稳态等效电路建模,损耗和效率-3.1 直流变压器模型

    3.1 直流变压器模型 如图3.1所示,任何开关变换器都包含三个部分:功率输入,功率输出以及控制输入.输入功率按控制输入进行特定的功率变换输出到负载.理想情况下,这些功能将以100%的效率完成,因此 ...

随机推荐

  1. 程序是怎样跑起来的_第一章-对程序员来说CPU是什么

    通过对第一章的学习,我了解了大体上CPU可以说是电脑的"大脑",即中央处理器.从功能来看可以分为寄存器,控制器,运算器和时钟.在这四个部分中,寄存器是最值得程序员注意的.总的来说, ...

  2. 大模型_2.1:Prompt进阶

    目录: 1.Prompt frameWork 2.Prompt 结构化格式 3.如何写好结构化 Prompt ? 4.Zero-Shot Prompts 5.Few-Shot Prompting 6. ...

  3. SATA与PCI-E速度对比

    SATA SATA接口已经发展到了第三代,理论上的最大速度达到600MB/s.平时大家见到的SATA SSD使用的都是SATA三代,实际测试速度在550MB/s左右,这比普通的机械硬盘的速度100MB ...

  4. 抽丝剥茧:详述一次DevServer Proxy配置无效问题的细致排查过程

    事情的起因是这样的,在一个已上线的项目中,其中一个包含登录和获取菜单的接口因响应时间较长,后端让我尝试未经服务转发的另一域名下的新接口,旧接口允许跨域请求,但新接口不允许本地访问(只允许发布测试/生产 ...

  5. 3 个好玩的前端开源项目「GitHub 热点速览」

    单休的周末总是短暂的,还没缓过神新的一周就又开始了.如果你和我一样状态还没有完全恢复,那就让上周好玩的开源项目唤醒你吧! 每周 GitHub 上总是有一些让人眼前一亮的开源项目,上周好玩的前端项目特别 ...

  6. openstack的用户(user), 租户(tenant), 角色(role)概念区分

    用户身份管理有三个主要的概念: 用户Users租户Tenants角色Roles1. 定义 这三个概念的openstack官网定义(点击打开链接) 1.1 用户(User) openstack官网定义U ...

  7. kubernetes 之 Rolling Update 滚动升级

    滚动升级 1.错误的yml文件 [machangwei@mcwk8s-master ~]$ cat mcwHttpd.yml apiVersion: apps/v1 kind: Deployment ...

  8. SSH-Web 工具之 shellinabox:一款使用 AJAX 的基于 Web 的终端模拟器 安装及使用教程

    本文转载自: shellinabox:一款使用 AJAX 的基于 Web 的终端模拟器 一.shellinabox简介 通常情况下,我们在访问任何远程服务器时,会使用常见的通信工具如OpenSSH和P ...

  9. 实战SQL优化(以MySQL深分页为例)

    1 准备表结构 CREATE TABLE `student` ( `id` int NOT NULL AUTO_INCREMENT, `user_no` varchar(50) CHARACTER S ...

  10. golang errgroup 的超时检测

    errgroup 的超时检测通常是一种事后得到结果的方式. errgroup本身并不直接支持超时控制,而是依赖于与之关联的context.Context来实现超时和取消功能. 当context超时时, ...