WhFanatic / channel_IDM

Direct numerical simulation and large-eddy simulation of turbulent channel flow and turbulent boundary layer.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

channel_IDM 说明文档

作者:王海宁 (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) 提出。

环境依赖和编译运行

依赖

  • C++17:intel 的 icpc 或 GNU 的 g++ 皆可,具体在 Makefile 中修改,默认使用 icpc

  • FFT 算法:fftw

  • 多线程并行:OpenMP

  • 部分文件读写:hdf5

编译

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

示例

本小节将指导读者配置程序以实现一个 $Re_\tau=180$ 槽道湍流算例的计算。

  1. codes/src/main.cpp 中选用 #define DEFAULT 的宏定义,表明选用默认计算模式 (而不是主计算-辅助计算模式);

  2. codes/src/Evolve.cpp 中开启 #define _FULLCHANNEL 的宏定义,并注释其他宏定义,表明选择计算全槽道湍流的算例;

  3. codes/ 目录下 make dir 生成上一小节所述的目录结构,然后 make 编译生成可执行文件 idm_whn,并按上一小节要求移动到合适的路径和重命名;

  4. XINDAT 移动至与可执行文件同一路径下,并在其中设置计算参数,对于本算例而言大部分参数使用默认值即可,需要修改的参数为:

    • nthrds:并行计算使用的线程数,默认为 8,根据电脑的实际情况设置;
    • Nx,Ny,Nz:网格量,默认设置的网格量较低,可用于程序调试,但不足以反映真实物理流动。对于 $Re_\tau=180$ 的槽道湍流 DNS 而言推荐采用 Nx=129,Ny=129,Nz=129 或更大的网格量;
    • dy_min:第一层网格的法向间距,推荐改为 0.005 或更小值。
  5. 运行可执行文件,查看输出结果。

运行示例图片

运行目录示例 运行输出示例
Screen Shot 2022-06-21 at 22.20.55 image-20220621222820741

本算例推荐的输入参数

Screen Shot 2022-06-21 at 22.18.56

程序结构和模块

文件结构

代码包含两部分,一部分是数值模拟代码,采用 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 中,有 DEFAULTAUXIMAIN 两种运行模式

  • 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 等。

标量场类 class Scla

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() 返回的指针只能读数组元素。
  • 网格访问接口 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()

矢量场类 Vctr & 流场类 Flow

网格类 Geometry & 网格访问接口类 Mesh

数值算法 IDM & 边界条件 Bcond

Implementing the implicit decoupling method, 2nd-order centeral difference, Crank-Nicolson scheme, and FFT-based Poisson solver

数值解类 Solver & 时间推进函数 Evolve

其他模块介绍

算法

详见channel_IDM_algo_doc.pptx 文件中的《channel_IDM 算法说明书》。该说明书针对旧版本程序编写,仅考虑了槽道模型 (x, z 方向周期均匀,y 方向非均匀),而目前最新版本程序可以处理 x, y, z 三个方向都非均匀、x, y 方向非周期的情况。尽管如此,旧版本程序的算法说明书也能很好地帮助理解算法。

参考文献

  1. Perot JB. 1993. An analysis of the fractional step method. J. Comput. Phys. 108(1):51–58.
  2. 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.

TODO

  1. 本文档需要继续完善。

  2. 程序的 "数据同化" 模块 (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."

  3. 程序开发过程中因时间仓促而未能规范化、合理封装的地方,有待进一步完善。

致谢

感谢马明博士、崔智文博士、王罗浩博士等在算法学习、程序开发和 C++ 使用方面给予的讨论和指导,感谢清华大学湍流实验室为程序的测试运行提供计算平台。

About

Direct numerical simulation and large-eddy simulation of turbulent channel flow and turbulent boundary layer.


Languages

Language:C++ 80.1%Language:Python 19.4%Language:Makefile 0.5%