格子玻尔兹曼
格子玻尔兹曼
Create Your Own Lattice Boltzmann Simulation (With Python)
Computational Astrophysicist @Princeton
https://github.com/pmocz/latticeboltzmann-python
射线跟踪Monte Carlo和BTE的关系并不是非常直观的,刚接触的时候很难建立一些概念。但是格子玻尔兹曼(LBM)基本上就是直接离散BTE了,形式上非常直观,感觉对于复杂边界处理比Monte Carlo要容易不少
相空间
分布函数\(f(\boldsymbol{r}, \boldsymbol{p})\)是相空间中(坐标空间+动量空间)的函数,如果分析的体系是N维的,那么\(f(\boldsymbol{r}, \boldsymbol{p})\)就是一个2N维的函数。在相空间中,如果某个点的坐标确定,它仍有N个动量空间的自由度,因此这个点处某个物理量的宏观表现(只是坐标空间的函数),实际是该点处动量空间贡献之和。在相空间中,这个点向各个方向都有速度,而该点宏观体现出来的实际速度就是这些速度按照权重\(f\)叠加出来的净结果。这也是用\(f\)求分布的一般方法: \[ \langle X(\boldsymbol{r})\rangle=\ \int X(\boldsymbol{r}, \boldsymbol{v}) f(\boldsymbol{r}, \boldsymbol{v}) \mathrm{~d} \boldsymbol{v} \]
格子玻尔兹曼
基本想法
在D2Q9(2维9速度分量)格子玻尔兹曼中,空间中的每一个点都储存着9个速度分量,分别指向上下左右以及四个角点。这些速度分量就离散地代表了\(f(\boldsymbol{r}, \boldsymbol{p})\)在动量空间的分布。因此空间中的每一点原本需要计算在动量空间的积分, \[ \langle X(\boldsymbol{r})\rangle=\ \int X(\boldsymbol{r}, \boldsymbol{v}) f(\boldsymbol{r}, \boldsymbol{v}) \mathrm{~d} \boldsymbol{v} \] 现在就可以用求和代替了, \[ X(\boldsymbol{r}) = \sum_{i=1}^9 f(\boldsymbol{r}, \boldsymbol{v_i})X(\boldsymbol{r}, \boldsymbol{v_i}) = \sum_{i=1}^9 f_iX_i \] 实际上密度就是把分布函数的速度部分积分掉: \[ \rho(\boldsymbol{r}) = \sum_{i=1}^{9} f_i \] 动量可以表示为: \[ \rho(\boldsymbol{r})\boldsymbol{u}(\boldsymbol{r}) = \sum_{i=1}^{9} f_i \boldsymbol{v_i} \] 下图是D2Q9模型中空间中每一点的速度离散及其权重示意图,权重主要决定了后面平衡分布的计算,它肯定是可以推导的,不影响对方法本身的理解。
在D2Q9模型中,最终想要求解的是\(f\)在空间中的分布,在空间中的每个点都存储\(f\)的9个分量,因此如果在x方向离散了100个网格,在y方向离散了100个网格,\(f\)的数据结构就是\(100\times100\times9\)的矩阵。
演化
弛豫时间近似下的玻尔兹曼方程: \[ \frac{\mathrm{d}f(\boldsymbol{r}, \boldsymbol{v}, t)}{\mathrm{d}t} = -\frac{f-f^{\mathrm{eq}}}{\tau} \] 写成离散格式: \[ \frac{f(\boldsymbol{r} + \boldsymbol{v}\delta t, \boldsymbol{v} + \boldsymbol{F}\delta t, t+\delta t) - f(\boldsymbol{r}, \boldsymbol{v}, t)}{\delta t} = -\frac{f(\boldsymbol{r}, \boldsymbol{v}, t)-f^{\mathrm{eq}}(\boldsymbol{r}, \boldsymbol{v}, t)}{\tau} \] 在没有外力的情况下,可以把上面这个\(f\)的离散写成9个分量的离散: \[ \frac{f_i(\boldsymbol{r} + \boldsymbol{v_i}\delta t, t+\delta t) - f_i(\boldsymbol{r}, t)}{\delta t} = -\frac{f_i(\boldsymbol{r}, t)-f_i^{\mathrm{eq}}(\boldsymbol{r}, t)}{\tau} \] 左边的离散是一个类似拉格朗日观点的处理方式,把它叫作漂移项(drift),在下一个时间步,当前位置的九个速度格子都跑到对应速度方向的下一个格子上。将右边称作碰撞项(collision),类似欧拉的流场,只关注当地的平衡态和非平衡态。上面这个等式给出了\(f\)更新的方式,\(f\)在每一步中要经历碰撞和漂移两个过程,到达下一时间步,继续碰撞和漂移过程。
对于等温(声速不变)流体,平衡态分布可以表示为: \[ f_{i}^{e q}=w_{i} \rho\left(1+3\left(\mathbf{v}_{i} \cdot \mathbf{u}\right)+\frac{9}{2}\left(\mathbf{v}_{i} \cdot \mathbf{u}\right)^{2}+\frac{3}{2}(\mathbf{u} \cdot \mathbf{u})^{2}\right) \] 以圆柱扰流为例,流场中存在一个圆柱,在格子玻尔兹曼中,圆柱一圈的网格会收到边界的影响,可以简单认为如果粒子撞到了边界就弹回来,体现在分布函数上就是\(f_i <->f_j\),交换对向格子。
Code
定义模拟的基本参数,x方向离散400个网格,y方向离散100个网格,平均密度设置为100,弛豫时间0.6,更新步数选取为4000个
1 | # Simulation parameters |
每个点设置9个速度格子,cxs和cys是格点的横纵坐标,weights是对应点的权重
1 | # Lattice speeds / weights |
F为存储分布函数的三维数组,每个点存储9个速度格子的分布。给出分布函数一个随机的扰动,同时流场的初始条件是向右的速度分布,体现在分布函数上就是每个点正右侧格子的分布函数的值要大一点,F[:,:,3]
就是坐标为\((1, 1)\)的格子在全场的分布。np.sum(F, 2)
代表对数组F的第三维求和。
1 | # Initial Conditions - flow to the right with some perturbations |
定义边界:
1 | # Cylinder boundary |
下面就是主循环过程,首先进行漂移过程,对F进行更新,np.roll(F[:,:,i], cx, axis=1)
的含义是把F[:,:,i]
沿x方向滚动cx个元素,超出最后位置的元素将会滚动到第一个位置。这两句实际上执行了上边的示意图里的过程。接下来处理边界,以及碰撞过程,每一个时间步里F
分别用碰撞和漂移更新一次。
1 | # Simulation Main Loop |