Canoe Example#1:Straka Sinking Bubble
本节将通过一个具体的问题- Straka Problem的模拟来学习Canoe的编译和运行。
本模拟的模拟是通过考察高密度空气团的下沉和由此因此的边界层扰动,来评估模式在模拟刚性下表面情形的表现。空气的粘性流体,需要考虑由此引发的能量耗散。
问题描述
Strake Problem是Straka提出的,描述的是一个具有冷空气泡在二维大气中的下沉情形。其中大气是等熵的,水平范围是25.6 km,垂直6.4 km,两个维度的分辨率均为100 m,地表(1 bar)处的物理温度为 300K。
起始状态:
空气泡的起始位置在(0, 3 km)处,气泡内部的温度分布遵循以下方程:
其中, L 为点 到冷空气气泡中心点 的椭圆距:
其中, km, km, km, km。物理过程:
起初,气泡处于流体静力稳定状态(hydrostatically balanced);由于对流不稳定,气泡从起始位置开始下沉,并带动周围的空气流动,由此产生的密度流在界面上传播,形成Kelvin-Helmholtz不稳定。本example的目的就是对这一过程进行模拟。
模拟
$ cd |
实验进行了900s的模拟,生成了straka-test-main.nc
文件,头信息:straka-test-main.headinfo。
脚本:make_plots.py。
绘制0,300,600,900s四个时刻的位温场的模拟:
python3 make_plots.py |
源码解析
源码位置不在本编译目录下,在canoe安装目录下:~canoe/examples/2019-Li-snap/straka.cpp
我们来逐行分析:
头文件
这里简单提一下,Athena++是原子物理层面,适用于量子力学/流体力学的天文磁流体模式。Canoe是在其基础内核上开发的,基于其网格并行架构(mesh,meshblock)、流体力学模块(hydro)和类的封装风格(airparcel)、重力(gravity),坐标(coordinate), I/O等,加入了热力学模块(thermodynmics),辐射传输(radiation, opacity)等分子层面的宏观物理方法的大气模式。所以,canoe需要include很多athena++的类库,同时,自身要实现从atom到particle的转变。
athena.hpp:定义了Athena++的各种各种变量、类型、常量、下标,指针,结构体等等。 如:Real型,ConsIndex,PrimIndex等枚举类;
parameter_input.hpp:设定了用于读取、存储模式输入文件
<straka.inp>
中变量的ParameterInput
类型和GetInteger()
,GetReal()
等函数;coordinates.hpp:定义,存储和计算坐标位置,网格距和几何特性(面积、体积、source term)的文件;
eos.hpp :定义用于计算状态方程(Equation of State)的数据和函数;
hydro.hpp :这位更是重量级,定义了处理流体力学过程的
Hydro
类,负责处理流体动力过程中压强、密度和速度场的演变。包含了指向特定meshblock的指针*pmb
; Hydro类本身可以被指针*hydro引用;Athena++还可以用来模拟磁流体力学过程magnetohydrodynamics (MHD)
;field.hpp:负责处理磁流体力学中的场的变化,大气模拟一般用不到;
mesh.hpp :负责处理mesh和meshblock的类库,并且协调不同处理器并行情形下的mesh分布式计算问题。
- MeshBlock class:格点类,控制单个格点的各种过程和位置信息;
- Mesh class:所有网格类型的总类,结构上是由大量meshBlock示例构成;类别上可以分成规则网格,三角形网格、变分网格等。
- Mesh网格的设计:参考Athena++团队发表的网格设计和工作原理:【The Athena++ Adaptive Mesh Refinement Framework: Design and Magnetohydrodynamic Solvers】;
Mesh网格结构示意图
详细内容会在mesh网格文章中介绍。(挖坑~)
impl.hpp :这是Canoe作为athena++扩展时为了灵活添加需要的物理方案和模块,留下的伪类,称为Opaque pointer,也可以理解为一个接口或后门,后续需要做任何模块的修改,只要子imp类中做扩展并具体实现就可以了。目前,impl类上插了以下几个canoe的实际物理/化学/辐射模块:
radiation
(辐射传输),inversion
(反演),decomposition
(分解),thermodynamics
(热力学),turbulence_model
(湍流),microphysics
(微物理),chemistry
(化学),tracer
(痕量气体)。MeshBlock类:这里介绍以下MeshBlock类,被定义在Mesh类的里面,负责管理每个格点的坐标、状态和相关物理过程的实现,在逻辑上后于Mesh类的实例化。这些物理是由其内部封装的指针来访问的,包括:
- Coordinates *pcoord // 格点的坐标系统
- Hydro *phydro // 流体力学过程
- EquationOfState *peos // 大气状态方程
- 但是,如
*pthermo
,*prad
等属于Canoe扩展的类的指针,定义在impl
高级包装类中。因为不属于MeshBlock类的成员指针,不能在其实例中直接使用,需要先实例化:auto pthermo = Thermodynamics::GetInstance();
,或者通过impl
类的指针来访问:auto prad = pimpl->prad;
。
thermodynamics.hpp :定义在
impl
类中,负责处理热力学相关的过程和状态的解算。包含了温度、压强、位温、能量等参数的求算,以及:- 几种大气层结假设: cpp
enum class Method {
ReversibleAdiabat = 0, // 可逆绝热
PseudoAdiabat = 1, //假绝热
DryAdiabat = 2, //干绝热
Isothermal = 3, // 等温大气
NeutralStability = 4 // 中性稳定
}; - 大气结构构建函数: 这部分内容会在cpp
//! Construct an 1d atmosphere
//! \param[in,out] qfrac mole fraction representation of air parcel
//! \param[in] dzORdlnp vertical grid spacing
//! \param[in] method choose from [reversible, pseudo, dry, isothermal]
//! \param[in] grav gravitational acceleration
//! \param[in] userp user parameter to adjust the temperature gradient
void Thermodynamics::Extrapolate(AirParcel* qfrac, Real dzORdlnp, Method method,
Real grav, Real userp) const {
qfrac->ToMoleFraction();
// equilibrate vapor with clouds
for (int i = 1; i <= NVAPOR; ++i) {
auto rates = TryEquilibriumTP_VaporCloud(*qfrac, i);
// vapor condensation rate
qfrac->w[i] += rates[0];
// cloud concentration rates
for (int j = 1; j < rates.size(); ++j)
qfrac->c[cloud_index_set_[i][j - 1]] += rates[j];
}
// RK4 integration
rk4IntegrateLnp(qfrac, dzORdlnp, method, userp);
rk4IntegrateZ(qfrac, dzORdlnp, method, grav, userp);
}模式的大气热力学基础
中讲(又挖坑~~~)
- 几种大气层结假设:
输出设置
接下来,程序定义了几个全局变量。这里,K
是运动粘度,p0
是表面压力,cp
是比热容,而Rd
是干燥空气的理想气体常数。这里的普朗特数 是1。
Real K, p0, cp, Rd; |
设置输出量:你在inp文件中看到的UOV就是这里的UserOutputVariable的缩写。这里输出了温度场和位温场作为诊断量。
为输出量分配内存:
void MeshBlock::InitUserMeshBlockData(ParameterInput *pin) { |
由于温度和位温仅用于输出目的,它们不需要在每个动态时间步长中计算。下面的子例程循环遍历所有网格,并在输出时间步长之前计算温度和位温的值。特别地,指向Thermodynamics类的指针pthermo
使用其自己的成员函数Thermodynamics::GetTemp和PotentialTemp来计算温度和位温。
void MeshBlock::UserWorkBeforeOutput(ParameterInput *pin) { |
*pthermo
是一个高级管理类MeshBlock的成员。因此,在MeshBlock类的成员函数中,您可以直接使用它。实际上,所有物理模块都由MeshBlock管理。只要您有一个指向MeshBlock的指针,您就可以访问模拟中的所有物理模块。
Forcing function
本个例中,明确要求考虑,下沉的气泡受到重力以及粘性和热耗散的影响。重力的作用已经由程序的其他部分处理,我们只需要编写几行代码来实现耗散。第一步是定义一个函数,该函数以指向MeshBlock的指针作为参数,以便通过该指针访问所有物理量。这个函数的名称并不重要,可以是任何名字。但是参数的顺序和类型必须是
void AnyName(MeshBlock*,Real const,Real const,AthenaArray
它们被称为函数的签名。
void Diffusion(MeshBlock *pmb, Real const time, Real const dt, |
怎么理解这个Forcing function呢?Athena++的 hydro模块解决了一个流体力学的质量守恒和能量守恒问题。
动量方程:
能量守恒方程:
理想流体的能量守恒方程可以通过欧拉方程来表示,该方程描述了无粘性和绝热的流体运动。欧拉方程包括连续性方程、动量方程和能量方程。
那么在Straka问题中,真实的大气是具有一定粘性的,受外力作用会发生热量耗散,方程就需要加上耗散项:
在添加了粘性耗散的Forcing之后,流体的运动方程就完整了,Hydro模块会结合重力场去求解流体的状态演变。
初始化Mesh类参数
这是初始化程序特定变量的地方。注意这个函数是Mesh类的成员函数,而不是我们一直在使用的MeshBlock类的成员函数。
Mesh类是一个全包括的类,管理多个MeshBlock,而MeshBlock类管理所有物理相关模块;在实例化这两个类的过程中,应该首先实例化Mesh类,然后它在内部实例化所有的MeshBlock。
因此,这个子程序在所有MeshBlock之前运行。
void Mesh::InitUserMeshData(ParameterInput *pin) { |
上述的,
初始化MeshBlock参数
这是程序的最后一部分,我们在其中设置初始条件。在Athena++应用程序中,一般称为problem generator
。
这部分可以理解成初始时刻,对每个MeshBlock进行设置。
void MeshBlock::ProblemGenerator(ParameterInput *pin) { |
循环遍历网格。目的是在每个单元格中心网格上设置温度、压力和密度场。
按照straka problem的情景,初始时刻应该是:
1)有一个绝热大气的温压场,使用了大气状态方程(EOS, Equation of State)先对大气进行构建;
2)在此基础上叠加一个冷的气泡的温度异常导致的温度场,公式(1)。
for (int j = js; j <= je; ++j) { |
我们已经设置了所有的primitive variables
。最后一步是基于primitive variables
计算conserved variables
。
这是通过调用成员函数EquationOfState::PrimitiveToConserved
完成的。
注意,pfield->bcc
是一个占位符,因为这个示例中没有MHD涉及。
peos->PrimitiveToConserved(phydro->w, pfield->bcc, phydro->u, pcoord, is, ie, js, je, ks, ke); |
源码和输入文件
/* ------------------------------------------------------------------------------------- |
#straks.inp |
TODO LIST
- Athena++ Mesh网格介绍;
- 模式的大气热力学基础;
- 大气流体力学基础