作者:王海宁 (https://github.com/WhFanatic)
由于时间仓促和作者懒惰,本文档尚未完善,很多不足之处有待补充和修正。本文档只能作为参考资料而非 "手把手教程"。如在程序使用中有所疑问,欢迎联系作者交流讨论。若有意对代码进一步开发和文档编写做贡献,也大力欢迎,不甚感激!
槽道湍流、边界层湍流的直接数值模拟和大涡模拟。
本程序是作者博士研究课题的基础,基于本程序进行湍流直接数值模拟和大涡模拟,获取和分析湍流数据、研究模拟方法和边界条件。
本程序大量借鉴了清华大学湍流研究组黄伟希教授的 Fortran77 程序,利用 C++ 对程序的算法进行了重新实现,对不同模块进行了抽象、封装和优化,对求解对象的网格、边界条件进行了推广,增加了插值、过滤、壁模型、亚格子模式、数据同化等功能。克服了原 Fortran77 程序中的诸多问题,如:
- 原 Fortran77 程序未利用合适的语法特性使程序简明易读;
- 原 Fortran77 程序设计面向过程,缺乏模块封装;
- 原 Fortran77 程序大量使用全局变量和隐式变量类型;
- 原 Fortran77 程序内嵌的 FFT 算法只支持 2 次幂的网格量。
本程序采用 OpenMP 单节点多线程并行,槽道湍流直接数值模拟计算效率达到 64 核并行 1000 万网格 1 万步迭代耗时 60 分钟。
"channel" 代表槽道,本程序的最初目的是实现槽道湍流的直接数值模拟,虽然目前已不只局限于槽道,拓展了湍流边界层和大涡模拟以及其他诸多功能;
"IDM" 代表 Implicit Decoupling Method,为程序求解 N-S 方程采用的数值算法,由 Kim, Baek & Sung (2002) 提出。
Makefile 文件在 channel_IDM/codes/
路径下:
-
编译:
make
,生成可执行文件idm_whn
-
生成程序运行的目录结构:
make dir
,会在当前目录的上级目录生成 4 个文件夹 -
清除编译结果:
make clean
具体参见 Makefile 文件,编译前需在 Makefile 中配置好相应的编译器和库的路径等。
将编译生成的可执行文件 idm_whn
修改为合适的文件名 (根据实验室要求一般须用缩写注明姓名) 并移动到算例目录下,与 XINDAT
以及 make dir
生成的文件夹置于同一路径。典型的算例目录结构如下:
case_folder # Folder for the simulation case
├── codes # C++ codes, containing 'src', 'include', 'obj', 'Makefile'
├── fielddata # Folder for data of instantaneous flow fields stored in bindary files
├── postdata # Folder for post process results
├── probedata # Folder for data of time series, not used currently
├── statdata # Folder for checking the running state and the initiation state
├── idm_whn # The executable
└── XINDAT # Input file for setting computational parameters
本小节将指导读者配置程序以实现一个
-
在
codes/src/main.cpp
中选用#define DEFAULT
的宏定义,表明选用默认计算模式 (而不是主计算-辅助计算模式); -
在
codes/src/Evolve.cpp
中开启#define _FULLCHANNEL
的宏定义,并注释其他宏定义,表明选择计算全槽道湍流的算例; -
在
codes/
目录下make dir
生成上一小节所述的目录结构,然后make
编译生成可执行文件idm_whn
,并按上一小节要求移动到合适的路径和重命名; -
将
XINDAT
移动至与可执行文件同一路径下,并在其中设置计算参数,对于本算例而言大部分参数使用默认值即可,需要修改的参数为:-
nthrds
:并行计算使用的线程数,默认为 8,根据电脑的实际情况设置; -
Nx,Ny,Nz
:网格量,默认设置的网格量较低,可用于程序调试,但不足以反映真实物理流动。对于$Re_\tau=180$ 的槽道湍流 DNS 而言推荐采用Nx=129,Ny=129,Nz=129
或更大的网格量; -
dy_min
:第一层网格的法向间距,推荐改为0.005
或更小值。
-
-
运行可执行文件,查看输出结果。
运行示例图片
运行目录示例 | 运行输出示例 |
---|---|
本算例推荐的输入参数
代码包含两部分,一部分是数值模拟代码,采用 C++ 编写;一部分是后处理代码,采用 Python3 编写。文件结构如下:
channel_IDM
├── README.md # The current document
├── codes # Folder containing all the C++ simulation codes
│ ├── Makefile # For compiling the simulation codes using a C++ compiler
│ ├── XINDAT # Input file for the simulation, dictating the computational parameters
│ ├── include # Folder containing all the C++ header files
│ ├── src # Folder containing all the C++ source files
│ └── obj # Folder for temporary files during compilation
└── post # Folder containing all the Python3 post-process codes
post
文件夹包含后处理代码,具体如下:
channel_IDM/post
├── main.py # Do calculations and write to files (for periodic X-Z)
├── main_x.py # Do calculations and write to files (for periodic Z)
├── basic.py # Basic information (computational parameters) of the data
├── statis.py # Calculate the statistics
├── write_statis.py # write various statistics to files
├── fileIO.py # write and read raw binary and tecplot files
├── tools.py # Some math utilities
├── diff.py # Implementation of numerical differentiation schemes
├── jpdf.py # Calculate joint probability functions
├── operators.py # Calculate tensor operations, e.g. gradient and divergence
├── pressure.py # Calculate pressure from velocity solving Poisson equation
├── budgets.py # Calculate turbulent kinetic energy transportation equations
└── dimconvertor.py # Rescale binary data to convert to another non-dimensionalization
由于后处理代码较为简单且与数值模拟代码完全独立,后文仅介绍 C++ 数值模拟代码。
遵照 C++ 程序编写的惯例,将类 (class
) 或命名空间 (namespace
) 的声明写在头文件 (.h
文件) 中,将类方法和函数的具体实现写在源文件 (.cpp
文件) 中。要了解各模块的定义,推荐先查阅头文件。头文件置于 include
文件夹中,具体如下:
channel_IDM/codes/include
├── Basic.h # Import modules, define constants, and implement simple algorithms that are needed by almost all the other modules
├── Field.h # Data-holding classes for physical fields, including Scla, Vctr and Flow
├── Geometry.h # Coordinates of the grid points, and the interface to access them
├── IDM.h # The core numerical algorithms for solving the N-S equations
├── Matrix.h # Matrix solvers including TDMA and CTDMA, needed in the numerical procedures
├── Interp.h # Linear interpolator for accessing field data by physical coordinate
├── Filter.h # Box filter for field data
├── Bcond.h # Boundary conditions
├── Para.h # Computational parameters
├── Solver.h # Class holding all data and algorithms needed to get solution of a flow
├── Statis.h # Real-time calculation of some simple statistics
├── SGS.h # Sub-grid scale models for large-eddy simulation
├── WM.h # Wall models for near-wall treatment
├── OFW.h # Off-wall boundary conditions
├── PIO.h # Predictive inner-outer models for near-wall turbulence prediction
└── DA.h # Data assimilation ()
计算参数在输入文件 XINDAT
中配置。各参数含义已在注释中说明,详见 XINDAT
文件。部分参数的用法在此处进一步解释:
bftype
控制模拟方式,默认为 DNS,其他方式中,MFU 相比 DNS 而言增加了每一步去除 u,v 的展向均匀脉动,LES_xxx (其中 xxx 代表亚格子模式) 相比 DNS 而言总粘性是运动粘度和亚格子粘度之和,。nprobe
的设计意图是控制程序输出某些位置的时间序列,但目前程序中尚未启用。args
是预留的自定义输入参数,在需要临时测试一些参数但又不想修改读参数代码的时候,可使用args
传入参数。其用法是,第一个数字 (假设为 n) 表示共有 n 个自定义参数,后面 n 个数字为这 n 个参数,一共 n+1 个数字,用逗号隔开。inread
表示续算的时间步,inread==0
则表示不续算。inpath
表示续算文件的路径,为空 (保留两个双引号 "") 表示原地续算,否则表示初始场续算。prd
标注计算域的周期性:prd==010
表示流向、展向周期,法向非周期;prd==110
表示展向周期,流向、法向非周期。目前程序只实现了这两种周期性。
其他注意事项:
- 读入
XINDAT
时,每行//
符号后的部分视为注释。 - 程序并不会检查输入参数是浮点型还是整型,因此在
XINDAT
中设置参数时若为浮点数则需明确指定 (如 5 需表示为 5.0 或 5. 或 5e0)。
程序主入口位于 main.cpp
中,有 DEFAULT
和 AUXIMAIN
两种运行模式
DEFAULT
模式只求解一个流动;AUXIMAIN
模式同时求解辅助计算 (auxiliary simulation) 和主计算 (main simulation) 两个流动,一般辅助计算用于为主计算实时提供边界条件等信息。
下文只介绍 DEFAULT
模式。主函数如下:
int main()
{
Solver slv(""); // Initiate the numerical solution of the flow from input file 'XINDAT' at the current path
Config(slv.para.nthrds); // Configure OpenMP, set the number of threads for parallel computing
/***** The main loop *****/
while (slv.step < slv.para.Nt) {
slv.Evolve(); // Advance the numerical solution by one time step
slv.Output(); // Write the numerical solution to file at frequency specified in XINDAT
if (slv.step % slv.para.nprint == 0) {
slv.para.checkPara(slv.step); // Check modifications to XINDAT to enable parameter update during running
Config(slv.para.nthrds); // Re-configure OpenMP when parameters are updated
}
}
/*************************/
cout << "\nComputation finished!" << endl;
}
首先,对象 slv
实例化了 Solver
类,在类初始化过程中完成了整个数值求解过程的初始化,包括:
- 计算参数的读入 (
Para para
初始化)。 - 网格点坐标数据内存分配 (
Geometry geom
初始化);网格访问接口Mesh ms
初始化。 - 边界条件数据
bc, sbc
内存分配和初始化;流场fld
、中间场fldh
、粘性场vis
、源项场sou
数据内存分配和初始化;平均压力梯度mpg
初始化。 - 物理时间
time
和时间步step
置零。 - 定义网格点坐标,根据流向是否周期,边界附近格点坐标有所不同;目前程序假定展向均是周期的。
- 如果续算的话,读入续算文件。目前实现了两种续算方式:
- 原地续算:将当前算例在某时间步的所有必要输出直接读入进行续算,计算过程从读入的时间步接续,该时间步之后的输出将被覆盖。本方式用于在服务器宕机或被迫杀题之后续算。原地续算应保证续算结果和不中断的计算结果一致。
- 初始场续算:将另一算例某时间步输出的流场读入,并插值到当前网格,以此作为本算例流动初始场进行计算。计算的物理时间和时间步均从 0 开始。由于平均压力梯度等不属于流场数据 (u,v,w,p) 的参数并未读入,以及插值的处理等因素,即使读入算例与当前算例网格完全一致,也不保证后续计算结果完全一致。
- 如果不续算,则以某一解析分布初始化流场数据 u,v,w,并施加随机扰动。
- 打印算例参数以供检查,并输出初始流场。
接着,程序在主循环中进行时间推进求解,求解用到的时间推进函数 slv.Evolve()
将在下节详述。完成一个时间步的计算以后,根据 XINDAT 中指定的输出间隔决定是否输出数据到文件。为了能够实时修改计算参数 (如雷诺数、时间步长、并行线程数等) 而不终端程序运行,程序每隔一定时间步检查一次 XINDAT
文件是否有更新,若更新则读入新的计算参数。
主循环中,基于本程序采用的数值方法不断迭代推进,以获得流场在各时间的数值解。每步迭代都会运行时间推进函数 slv.Evolve()
将流场向前推进一个时间步。用户可根据要计算的具体算例自行配置 slv.Evolve()
函数的实现方式,包括粘性的求解、边界条件选用、源项设计等,一般要包含以下步骤:
- 推进时间步
step
和时间time
。 - 计算粘性场
vis
。 - 设置边界条件
bc, sbc
。 - 计算外力项
fb
(和质量源项mb
,如果有的话),外力和质量源统称为源项sou
。 - 执行数值算法 (
IDM.h, IDM.cpp
),获得新时间步的流场,存储在fldh
中 (由于算法仅针对域内节点,所以此时边界尚未更新)。 - 根据边界条件,更新流场边界节点数值。
- 施加其他操作,如流量修正、MFU 去除展向均匀脉动,等等。
- 将新时间步流场存储于
fld
,将该时间步流场的变化量存储于fldh
。
目前程序中预置了一些典型算例,通过在 Evolve.cpp
文件中开启相应的宏定义进行选择。包括
_FULLCHANNEL
:全槽道计算_HALFCHANNEL
:半槽道计算_FULLCHANNEL_ALGEWM
:使用代数壁模型的全槽道计算_FULLCHANNEL_SLIPWM
:使用滑移壁面模型的全槽道计算_EBL
:等效边界层计算_SYNMFU
:用合成 MFU 提供离面边界条件的全槽道计算_TBL
:用 EBL 辅助计算提供入口的湍流边界层计算_OFWCHANNEL
:用 MFU 辅助计算提供离面边界条件的全槽道计算
其中最后两个算例采用的是辅助计算-主计算模式 (AUXIMAIN
)。
程序封装的原则是:
-
对含有数据的变量采用类封装,如标量场类
Scla
、网格类Geometry
等,用类的成员变量储存数据,用类方法实现针对数据的常用操作; -
对独立于数据的算法采用命名空间封装,如求解 N-S 方程的核心数值算法
IDM
、插值算法Interp
等,命名空间中只包含函数,不能存储状态,所有数据通过函数参数传递; -
有些算法虽然独立于数据,但在算法执行过程中临时变量占用内存较大,为了避免迭代求解中重复申请内存导致额外的时间开销,这些算法也用类封装,将临时变量定义为类成员,并在程序初始化或初次迭代中将其实例化,如亚格子模式类
SGS
、矩阵类Matrix
等。
Field.h
中声明,Scla.cpp
中实现。
针对标量场进行了抽象,包含
-
数据数组
double *_q
和访问接口- 虽然标量场是三维数据,但采用一维数组存储 (以避免 C++ 多维数组指针嵌套的麻烦)。三维数组展开为一维的方式是:y 方向为最外层,z 方向次之,x 方向为最内层,x,y,z 对应的指标变量一般用
i,j,k
。 - 数据访问接口
- 对
()
和[]
符号进行了重载,()
可用i,j,k
三个指标以三维数组的方式访问元素,[]
可用一个指标以一维数组方式访问元素,二者的对应关系由ms.idx()
确定。 - 访问数据层 (一个 x-z 平面为一层) 或数据块的指针,
GetLyr()
和GetBlk
返回的指针可以读写数组元素,而SeeLyr()
和SeeBlk()
返回的指针只能读数组元素。
- 对
- 虽然标量场是三维数据,但采用一维数组存储 (以避免 C++ 多维数组指针嵌套的麻烦)。三维数组展开为一维的方式是:y 方向为最外层,z 方向次之,x 方向为最内层,x,y,z 对应的指标变量一般用
-
网格访问接口
const Mesh ms
-
Fourier 变换相关方法
- 本程序的设计**认为标量场的物理空间分布和 Fourier 谱系数是同一数据的不同表达形式,因此用同一块内存空间存储,并将 Fourier 变换植入类中作为,作为标量场的基本操作之一;
- 目前实现的变换种类有:
fftx(),ifftx()
:x 方向一维离散 Fourier 变换;fftz(),ifftz()
:z 方向一维离散 Fourier 变换;fftxz(),ifftxz()
:x-z 方向二维离散 Fourier 变换;dctx(),idctx()
:x 方向一维余弦变换;dctxz(),idctxz()
:x-z 方向二维余弦变换,先 x 方向余弦变换,再 z 方向离散 Fourier 变换。
-
一些基础的统计、积分、插值操作;
-
一些基本的标量场之间的算术运算;
-
将标量场输出为二进制文件
fileIO()
。
Implementing the implicit decoupling method, 2nd-order centeral difference, Crank-Nicolson scheme, and FFT-based Poisson solver
详见channel_IDM_algo_doc.pptx
文件中的《channel_IDM 算法说明书》。该说明书针对旧版本程序编写,仅考虑了槽道模型 (x, z 方向周期均匀,y 方向非均匀),而目前最新版本程序可以处理 x, y, z 三个方向都非均匀、x, y 方向非周期的情况。尽管如此,旧版本程序的算法说明书也能很好地帮助理解算法。
参考文献
- Perot JB. 1993. An analysis of the fractional step method. J. Comput. Phys. 108(1):51–58.
- Kim K, Baek S-J, Sung HJ. 2002. An implicit velocity decoupling procedure for the incompressible Navier–Stokes equations. Int. J. Numer. Methods Fluids. 38(2):125–138.
-
本文档需要继续完善。
-
程序的 "数据同化" 模块 (
class DA
) 是在旧版本程序的框架下开发的,而随着程序的版本推进,该模块已不能与新程序兼容,目前在 Makefile 中将其屏蔽。如有需要,可以参照以下文献算法更新这部分代码。-
Othmer, C. (2008). "A continuous adjoint formulation for the computation of topological and surface sensitivities of ducted flows." International Journal for Numerical Methods in Fluids 58(8): 861-877.
-
Lemke M, Sesterhenn J. (2016). “Adjoint-based pressure determination from PIV data in compressible flows — Validation and assessment based on synthetic data.” Eur. J. Mech. - B/Fluids. 58:29–38
-
He, C., Y. Liu and L. Gan (2018). "A data assimilation model for turbulent flows using continuous adjoint formulation." Physics of Fluids 30(10).
-
HE, C., Y. LIU and L. GAN (2020). "Instantaneous Pressure Determination from Unsteady Velocity Fields Using Adjoint-based Sequential Data Assimilation."
-
-
程序开发过程中因时间仓促而未能规范化、合理封装的地方,有待进一步完善。
感谢马明博士、崔智文博士、王罗浩博士等在算法学习、程序开发和 C++ 使用方面给予的讨论和指导,感谢清华大学湍流实验室为程序的测试运行提供计算平台。