この文書では、WSL2のUbuntu24.04上でLBM(格子ボルツマン法)流体解析ツールXLBを活用する方法を紹介します。
「XLB(Accelerated Lattice Boltzmann)」は、Autodeskが公開している、GPUによる高速計算と機械学習との連携を重視したPython製LBM(格子ボルツマン法)ライブラリです。2次元・3次元の流体解析に対応し、標準的なBGKモデルや高レイノルズ数流れに適したKBCモデルなどを利用できます。
最大の特徴は、NVIDIA Warp、Google JAX、Autodesk Neonなどの計算バックエンドを利用できる点です。いずれも、PythonコードからGPUを活用した高速なLBM計算を比較的容易に実装できます。
このXLBのgithubリポジトリは、以下にあります。
https://github.com/Autodesk/XLB
ここでは、最も基本となるCPUで動作する解析環境「JAX-CPU」を、以下の手順で準備した状態を用いて、独自例題の作成と分析を行います。
対象とする独自例題の概要
ここでは、「2次元問題として風洞中にある矩形周りの流れ」 を分析することを、目標とします。例えば、以下の論文では、格子ボルツマン法と有限体積法を用いた解析が報告されており、これを参考にします。
「Accurate computations of the laminar flow past a square cylinder based on two different methods: lattice-Boltzmann and finite-volume」
Fig. 2. Streamlines around the square cylinder for different Reynolds numbers (a) Re = 1; (b) Re = 30; (c) Re = 60; (d) Re = 200
解析モデルと解析条件と境界条件などの設定
これより、レイノルズ数Reの違いによって、正方形周りの流れの変化を、比較検討できます。解析モデルと境界条件は、以下の図のような定義です。
独自例題の、格子ボルツマン法LBMをAutodesk XLBで実施するために関する設定は、以下の通りです。
| 項目 | 設定 | 項目 | 設定 |
|---|---|---|---|
| 計算領域 | 2次元長方形 | 流体モデル | D2Q9 |
| 衝突モデル | BGK | 流入 | 一定速度 |
| 流出 | ExtrapolationOutflowBC | 上下壁 | HalfwayBounceBackBC |
| 正方形物体 | HalfwayBounceBackBC | 計算バックエンド | JAX |
| GPU(Jax,Warp,Neon) | 使用しない | CPU | JAX-CPU |
独自例題の具体的な設計
以下では、独自例題のスクリプトを作るうえで、設定する項目をまとめておきます。
スクリプト内の変数名と上記設定値との対応
独自例題での設定値とスクリプト内の 「変数名との対応」 を、下表に示します。問題を簡単にするため、格子や物体などの寸法は、無次元化した数値を用いています。
| 項目 | 説明図の内容 | スクリプトの変数 | 設定値 | 単位・意味 |
|---|---|---|---|---|
| 計算領域・X方向 | 領域の長さ | Nx |
300 | 格子点 |
| 計算領域・Y方向 | 領域の高さ | Ny |
120 | 格子点 |
| 流入速度 | X方向流入速度 | u_in |
0.05 | 格子単位/step |
| レイノルズ数 | 流れの無次元数 | Re |
100.0 | 無次元 |
| 正方形一辺 | 物体の大きさ | square_size |
40 | 格子点 |
| 正方形中心 X | 物体中心のX座標 | square_cx |
75 | 格子座標 |
| 正方形中心 Y | 物体中心のY座標 | square_cy |
60 | 格子座標 |
| 正方形回転角 | 物体の傾き | square_angle |
30.0 | 度 |
| 動粘性係数 | LBMの粘性 | visc |
0.020 | 格子単位 |
| 緩和パラメータ | BGKの緩和係数 | omega |
約1.7857 | 無次元 |
| 計算ステップ数 | 時間発展回数 | num_steps |
5000 | step |
| 出力間隔 | 結果を保存する間隔 | post_process_interval |
500 | step |
さらに重要なのは、「計算で決まる数値」 です。例えば、動粘性係数:visc と BGK緩和パラメータ:omega は直接入力しているのではなく、上の数値から計算されています。
visc = u_in * square_size / Re
omega = 1.0 / (3.0 * visc + 0.5)
したがって、$\nu=\frac{0.05\times40}{100}=0.020$、そして、$\omega=\frac{1}{3\times0.020+0.5}
\approx1.7857$です。
つまり、*上の方のユーザーが変更する基本パラメータ(設定値)**と、**そこから自動的に計算されるパラメータ(計算値)を分けると理解しやすくなります。この表にすると、「どの数値を変更すると、解析モデルの何が変わるのか」**がかなり分かりやすくなります。
| 種類 | 変数 | 値 | 役割 |
|---|---|---|---|
| 計算値 | visc |
0.020 | 動粘性係数 |
| 計算値 | omega |
1.7857 | BGK緩和パラメータ |
以上の設定により、下図の8の解析結果のイメージを目標とします。
独自例題を理解する上でのポイント
この独自例題は、以下の【04:例題flow_past_sphere_3d.pyの分析】に対して、2次元に簡略化した内容です。ただし、流体内部の四角形の「位置・規模・角度」が任意で設定できるので、様々な条件を確認することができます。これを参考にして、「左から流入 → 中央の正方形物体 → 右へ流出」 という、外部流れの最小例にできます。
今回作るモデル
まずは非常に単純化します。設定する流体空間中に、正方形を任意の位置・規模・角度に、境界条件:HalfwayBounceBackとして置くことができます。2次元XY座標において、流体はX方向の+向きに流れ、左辺が流入(Velocity BC)で右辺が流出(Outflow BC)です。
このプログラムの重要なところ
今回の目的は、XLBの構造をできるだけ単純に理解することです。特に重要なのは次の部分を解説します。
【D2Q9】
2次元LBMの最も基本的な速度モデルです。スクリプトでは以下の定義です。中央の1方向+周囲8方向なので D2Q9 です。
velocity_set = xlb.velocity_set.D2Q9(
precision_policy=precision_policy,
compute_backend=compute_backend,
)
【JAX-CPU】
今回は計算規模も小さく、どの計算環境でも動作させるため、CPU動作 としています。従って、「Python → XLB → JAX → CPU」のような計算の仕組みになります。
compute_backend = ComputeBackend.JAX
os.environ["JAX_PLATFORMS"] = "cpu"
【正方形】
今回の解析対象の扱いの中心部分です。回転させた正方形内部を座標返還して、正方形内部の格子を判定しています。詳しくはスクリプトを見ながら、説明します。
【正方形の境界条件】
ここがLBMでは重要です。スクリプトでは以下の定義です。以下の設定で、正方形に到達した流体粒子を反射させる というモデルです。XLBの公式2次元キャビティ例でも、固体壁には HalfwayBounceBackBC が使われています。
bc_square = HalfwayBounceBackBC(
indices=square
)
【流入口】
ここでは、流入として「X方向にu_in=0.05・Y方向に0.0」としています。つまり一様流を左辺から入れます。
bc_inlet = RegularizedBC(
"velocity",
prescribed_value=(u_in, 0.0),
indices=inlet,
)
【流出口】
ここでは、流入として、以下のような定義とします。右側では流体を外へ出します。
bc_outlet = ExtrapolationOutflowBC(
indices=outlet
)
【斜めの境界条件】
この例題では、正方形の中心位置はそのままにして、指定した角度だけ回転させるように変更できます。ここでは、正方形そのものを回転させるのではなく、格子上の各セルについて「そのセルが回転した正方形の内部か?」を判定しています。
したがってXLBでは、「数学的な正方形 → 回転座標系で判定 → square_mask → 固体セル → HalfwayBounceBackBC」という流れになります。
なお、LBMの格子は整数セルなので、30°や45°にすると境界が階段状(staircase) になります。これは今回のような基本的なXLB学習例としては重要なポイントです。LBMにおける斜めの境界条件は、階段状のギザギザ(ボクセル表現)による誤差を減らし、滑らかな傾斜や曲面を表現するために非常に重要です。
【Reynolds数】
今回は、「Re = 100.0」としています。代表長さを正方形の一辺「square_size = 20」として、$Re=\frac{UL}{\nu}$から、動粘性係数 visc = u_in * square_size / Re としています。
さらにBGKの緩和係数 $\omega=\frac{1}{3\nu+1/2}$ を、「omega = 1.0 / (3.0 * visc + 0.5)」で計算しています。
なお、正方形の寸法Lを変化させた場合は、本来ならば、レイノルズ数Reが変化するところですが、このスクリプトでは、動粘性係数viscを計算しなおすので、レイノルズ数は変化しない設定となっています。
【公式例題との関係】
特に、現在のXLB公式例では3次元の球まわり流れについて、HalfwayBounceBackBC、RegularizedBC、ExtrapolationOutflowBC、IncompressibleNavierStokesStepper を組み合わせる構成が使われています。今回のスクリプトは、それを2次元・正方形に最小化したものと考えると分かりやすいです。GitHub
なお、現在のXLB main版の公式2D例では save_fields_vtk() と save_image() による結果保存も使われていますので、今回も同じ方式にしています。GitHub
独自例題のスクリプトの解説
以下では、スクリプトの記述に従って、ブロック毎に区分して、それぞれの機能をコメントの形で記入したので、これを用いて説明します。
以下の説明では、ブロックごとに分けて解説しますが、全体をつなげると「2次元空間での正方形周囲の流れ」を、XLB用いてLBM計算するスクリプト「flow_square_2d-1.py」としておきます。
(最後の「-1」は、最後に説明するスクリプトの改良を期待してのバージョン1と考えています)
スクリプト「flow_square_2d-1.py」の構成
このスクリプトは、以下の 「32のブロック」 に、機能が分かれています。各ブロックについて、スクリプトを提示して、コメント部分に説明を追加します。
① JAXをCPUに設定 → ② 計算条件を設定
→ ③ 正方形のサイズ・位置・角度を設定 → ④ XLBのバックエンドをJAXに設定
→ ⑤ D2Q9を設定 → ⑥ 粘性係数・BGK係数を計算
→ ⑦ XLBを初期化 → ⑧ 計算格子を作成
→ ⑨ 境界を取得 → ⑩ 左側 → 流入口
→ ⑪ 右側 → 流出口 → ⑫ 上下 → 固体壁
→ ⑬ 格子座標を作成 → ⑭ 正方形の角度を設定
→ ⑯ 正方形セルを判定 → ⑰ 固体セルのインデックスを作成
→ ⑱ 流入口 BC → ⑲ 上下壁 BC
→ ⑳ 正方形 BC → ㉑ 流出口 BC
→ ㉒ 境界条件を統合 → ㉓ Navier-Stokes Stepper
→ ㉔ LBM分布関数を初期化 → ㉕ 密度・速度計算
→ ㉖ 計算条件を表示 → ㉗ 結果保存関数
→ ㉘ 計算ステップ設定 → ㉙ タイマー開始
→ ㉚ LBM時間発展 → ㉛ 計算終了
→ ㉜ 計算時間表示
#ブロック【① JAXをCPUに設定】の解説:CPUによるXLBによるLBM計算設定
# ============================================================
# XLB : 2D Flow Around a Rotated Square : flow_square_2d-1.py
#
# 2次元の長方形領域の中に、正方形の物体を置き、
# 左から右へ流体を流す外部流れ問題を計算する。
# この正方形は、任意の位置と寸法と角度で置ける。
# 計算方法: LBM (Lattice Boltzmann Method)
# 速度モデル: D2Q9
# 衝突モデル: BGK
# バックエンド: JAX
# 計算デバイス: CPU
# 重要: このプログラムでは GPU(Jax,Warp,Neon) を使用しない。
# ============================================================
# ============================================================
# ① JAXをCPUで使用する設定
# ============================================================
# JAXが利用する計算デバイスをCPUに限定する。
# "cpu"を指定すると、
# Python → XLB → JAX → CPU という計算経路になる。
# NVIDIA GPUやCUDAは、この例では使用しない。
# この環境変数はJAXをimportする前に設定する必要がある。
import os
# 念の為、環境変数JAX_PLATFORMSをcpuに設定する。
os.environ["JAX_PLATFORMS"] = "cpu"
# ============================================================
# Pythonで使用する基本ライブラリ
# ============================================================
# 時間計測に使用する。
# 解析全体に何秒かかったかを最後に表示するために使用する。
import time
# NumPyは、
# ・格子座標の作成 ・正方形の領域判定 ・角度計算
# などに使用する。
import numpy as np
# JAX版NumPy。
# 計算結果の速度や密度など、
# XLB内部でJAX配列として扱われるデータを処理するときに使用する。
import jax.numpy as jnp
# XLB本体を読み込む。
import xlb
# XLBが使用する計算バックエンドを指定するためのクラス。
# 今回は ComputeBackend.JAX を使用する。
from xlb.compute_backend import ComputeBackend
# 数値精度を指定するためのクラス。
# 今回はFP32を使用する。
from xlb.precision_policy import PrecisionPolicy
# 計算格子を作成するための関数。
from xlb.grid import grid_factory
# CFDに対応するNavier-Stokes方程式をLBMとして時間発展させる
# XLBの主要なStepper。これでステップ毎の処理を進める。
from xlb.operator.stepper import (
IncompressibleNavierStokesStepper
)
# 境界条件を指定するクラスを読み込む。
# HalfwayBounceBackBC:
# 固体壁で流体を反射させる。
# RegularizedBC:
# 流入口などで速度を指定する。
# ExtrapolationOutflowBC:
# 流出口から流体を外へ流す。
from xlb.operator.boundary_condition import (
HalfwayBounceBackBC,
RegularizedBC,
ExtrapolationOutflowBC,
)
# 密度や速度などの巨視的物理量を
# 分布関数から計算するクラス。
from xlb.operator.macroscopic import Macroscopic
# 計算結果をVTKや画像として保存する関数。
# VTK: ParaViewなどで解析結果を確認できる。
# PNG: 速度分布などを画像として確認できる。
from xlb.utils import (
save_fields_vtk,
save_image,
)
#ブロック【② 計算条件を設定】の解説:解析空間や流入速度やレイノルズ数の設定
# ============================================================
# ② 計算条件
# ============================================================
# x方向の格子数。今回は300セル。
# 流れは基本的にx方向、つまり「左 → 右」に進む。
# 流入から物体、特に物体から流出においては、十分な空間を確保する。
Nx = 300
# y方向の格子数。今回は120セル。
# 対象の物体の上下に、十分な空間を確保する。
Ny = 120
# XLBに渡す計算領域のサイズ。
# (Nx, Ny) つまり x方向 = 300・y方向 = 120 の2次元格子になる。
# 対象とする空間内の物体の大きさに対応して、計算領域を定める
grid_shape = (Nx, Ny)
# ------------------------------------------------------------
# 流入速度
# ------------------------------------------------------------
# 左側から流入する流体の速度。
# 今回は無次元速度として0.05を使用する。
# x方向: 0.05 y方向: 0
# したがって流体は水平に流入する。
u_in = 0.05
# ------------------------------------------------------------
# Reynolds数
# ------------------------------------------------------------
# 流れの状態を表す無次元数。
# Re = U L / ν
# U: 代表速度 L: 代表長さ ν: 動粘性係数
# 今回は正方形の一辺を代表長さとする。例題ではReを前提条件とする。
Re = 100.0
#ブロック【③ 正方形のサイズ・位置・角度を設定】の解説:中心位置と回転角度の設定
# ============================================================
# ③ 正方形物体の条件
# ============================================================
# 正方形の一辺の長さ。
# 40 × 40セルの正方形を作る。流れに直交方向の格子数Nyと比較する
# 流体空間の格子数 Nx,Ny に適切に入る大きさが限界となる。
# 閉塞率=物体の断面積/空間の断面積が、50%以下になるように注意する。
square_size = 40
# ------------------------------------------------------------
# 正方形の中心位置
# ------------------------------------------------------------
# x方向の中心。
# Nx = 300なので、300 / 4 = 75 となる。
# したがって正方形の中心はx=75。流体空間の流入側の 1/4 に位置する。
# なお // は整数除算なので、割切れないときは、小数点切り捨ての整数化
square_cx = Nx // 4
# y方向の中心。
# Ny = 120なので、120 / 2 = 60 となる。
# したがって正方形の中心はy=60。上下方向の中央に位置する。
square_cy = Ny // 2
# ------------------------------------------------------------
# 正方形の回転角度
# ------------------------------------------------------------
# 正方形を反時計回りに回転させる角度。
# 0度: 通常の正方形
# 30度: 30度回転
# 45度: ひし形に近い形
# 90度: 見た目は0度と同じ
# この値を変更することで、
# 同じ計算プログラムで様々な角度を試すことができる。
# この角度は、細かな数値の任意の数値で設定可能です。
square_angle = 30.0
#ブロック【④ XLBのバックエンドをJAXに設定】の解説:併せて数値制度をFP32に設定
# ============================================================
# ④ XLBの計算バックエンド
# ============================================================
# XLBの計算バックエンドとしてJAXを選択する。
# JAXにはCPU/GPUなどの実行環境があるが、
# 先ほど、os.environ["JAX_PLATFORMS"] = "cpu"
# と指定しているため、今回はCPUで計算される。
compute_backend = ComputeBackend.JAX
# ------------------------------------------------------------
# 数値精度
# ------------------------------------------------------------
# FP32を使用する。
# 最初のFP32: 主な計算の精度
# 2番目のFP32: 内部計算などで使用する精度
# CFDやLBMの学習用として扱いやすい設定。
precision_policy = PrecisionPolicy.FP32FP32
#ブロック【⑤ D2Q9を設定】の解説:2次元の標準的なLBMの速度モデル
# ============================================================
# ⑤ LBMの速度モデル D2Q9
# ============================================================
# D2Q9速度モデルを作成する。
# D2: 2次元 Q9: 9個の離散速度
# 中心: 0 周囲: 8方向 の合計9方向となる。
# なお、D2Q5 として流速が非常に遅い問題用もあるかXLBでは未実装
velocity_set = xlb.velocity_set.D2Q9(
precision_policy=precision_policy,
compute_backend=compute_backend,
)
#ブロック【⑥ 粘性係数・BGK係数を計算】の解説:寸法とレイノルズ数からこれらを計算
# ============================================================
# ⑥ 粘性係数とBGK緩和係数
# ============================================================
# Reynolds数の定義
# Re = U L / ν から、
# ν = U L / Re と変形して、動粘性係数viscを求める。
# ここで、代表速度:U = u_in, 代表長さ:L = square_size とする。
visc = u_in * square_size / Re
# ------------------------------------------------------------
# BGKの緩和係数
# ------------------------------------------------------------
# LBMでは衝突過程にBGKモデルを使用する。
# BGKモデルでは緩和係数omegaが必要になる。
# XLBで使用する格子単位系では、
# omega = 1 / (3ν + 0.5) として計算する。
# 緩和係数omegaと動粘性係数viscの関係式から、D2Q9の場合の関係式となる。
omega = 1.0 / (3.0 * visc + 0.5)
#ブロック【⑦ XLBを初期化】の解説:LBM計算をXLBで行う計算手法の基本設定
# ============================================================
# ⑦ XLBの初期化
# ============================================================
# XLBに、
# ・D2Q9: velocity_set
# ・JAX: compute_backend
# ・FP32: precision_policy
# を標準設定として登録する。
# 以降のXLBオブジェクトが、この設定を利用できるようになる。
xlb.init(
velocity_set=velocity_set,
default_backend=compute_backend,
default_precision_policy=precision_policy,
)
#ブロック【⑧ 計算格子を作成】の解説:設定した解析空間の計算格子を作成する
# ============================================================
# ⑧ 計算格子を作成
# ============================================================
# XLBのgrid_factoryを使用して、
# 300 × 120の2次元計算格子を作成する。
# イメージ:
# y
# ↑ 300 × 120
# ┌─────────────┐
# │ |
# │ |
# └─────────────┘ → x
grid = grid_factory(
grid_shape,
compute_backend=compute_backend,
)
#ブロック【⑨ 境界を取得】の解説:2次元計算領域の4辺の外周格子を取得する
# ============================================================
# ⑨ 計算領域の境界を取得
# ============================================================
# 計算領域の外周セルを取得する。
# bounding_box_indices()は、
# 格子の ・left ・right ・top ・bottom
# などの境界位置を取得するために使用する。
box = grid.bounding_box_indices()
# ------------------------------------------------------------
# 外周端を除いた境界
# ------------------------------------------------------------
# remove_edges=Trueにすると、
# 境界の角などの重複セルを除いた境界を取得する。
# 流入口・流出口の境界条件を設定するときに利用する。
box_no_edge = grid.bounding_box_indices(
remove_edges=True
)
#ブロック【⑩ 左側 → 流入口】の解説:解析空間の左側を流入部として定義する
# ============================================================
# ⑩ 流入口
# ============================================================
# 左側境界を流入口として使用する。
# 流体は → → → → →
# のように左から右へ流れる。
inlet = box_no_edge["left"]
#ブロック【⑪ 右側 → 流出口】の解説:解析空間の右側を流出部として定義する
# ============================================================
# ⑪ 流出口
# ============================================================
# 右側境界を流出口として使用する。
# 流体は右側から計算領域の外へ出ていく。
outlet = box_no_edge["right"]
#ブロック【⑫ 上下 → 固体壁】の解説:解析空間の上下において固定壁として処理する
# ============================================================
# ⑫ 上下の壁
# ============================================================
# 上側と下側の境界を固体壁として設定する。
# velocity_set.dは空間の次元数。
# D2Q9なのでd=2。
# x方向・y方向の境界情報を組み合わせる。
walls = [
box["bottom"][i] + box["top"][i]
for i in range(velocity_set.d)
]
# 同じセルが重複している場合に整理する。
walls = np.unique(
np.array(walls),
axis=-1
).tolist()
#ブロック【⑬ 格子座標を作成】の解説:設定した大きさの解析空間に格子座標を設定する
# ============================================================
# ⑬ 格子座標を作る
# ============================================================
# x方向の格子番号を作る。
# 0, 1, 2, ..., 299
x = np.arange(Nx)
# y方向の格子番号を作る。
# 0, 1, 2, ..., 119
y = np.arange(Ny)
# x-yの2次元座標を作る。
# X[i,j]: x方向の位置 Y[i,j]: y方向の位置
# これによって、すべての格子セルについて
# 「正方形の内部かどうか」を判定できる。
X, Y = np.meshgrid(
x,
y,
indexing="ij"
)
#ブロック【⑭ 正方形の角度を設定】の解説:角度で設定された回転をラジアン単位に変換する
# ============================================================
# ⑭ 正方形を回転させる
# ============================================================
# 角度を度からラジアンに変換する。
# NumPyのsin/cosはラジアンを使用するため、
# 入力したデグリーの角度を変換する必要がある。
theta = np.deg2rad(square_angle)
# ------------------------------------------------------------
# 正方形中心を原点とした座標に変換
# ------------------------------------------------------------
# すべての格子点について、
# 正方形の中心からの相対座標を計算する。
# Xc: x方向の中心からの距離 Yc: y方向の中心からの距離
Xc = X - square_cx
Yc = Y - square_cy
#ブロック【⑮ 回転座標に変換】の解説:回転した正方形を座標変換して準備する
# ============================================================
# ⑮ 回転座標系へ変換
# ============================================================
# 正方形をangleだけ回転した状態を、
# 数学的に判定するために座標変換を行う。
# 座領変換のために、回転行列を利用している。
# Xr = [ cosθ sinθ ][ Xc ]
# [ -sinθ cosθ ][ Yc ]
# この変換によって、回転した正方形を
# 「回転していない正方形」として判定できる。
Xr = (
np.cos(theta) * Xc
+ np.sin(theta) * Yc
)
# y方向についても同じ座標変換を行う。
Yr = (
-np.sin(theta) * Xc
+ np.cos(theta) * Yc
)
#ブロック【⑯ 正方形セルを判定】の解説:格子上での正方形に対応する位置を判定する
# ============================================================
# ⑯ 正方形内部のセルを判定
# ============================================================
# 正方形の半分の長さを計算する。
# square_size = 40 なので、
# half = 20 となる。
half = square_size / 2.0
# ------------------------------------------------------------
# 正方形内部の判定
# ------------------------------------------------------------
# 回転後の座標Xr,Yrについて、
# -20 <= Xr <= 20
# -20 <= Yr <= 20
# を満たすセルを正方形内部とする。
# つまり、回転座標系で見ると
# 普通の40×40の正方形になっている。
square_mask = (
(np.abs(Xr) <= half)
& (np.abs(Yr) <= half)
)
#ブロック【⑰ 固体セルのインデックスを作成】の解説:格子のなかで対象物体の部分の指標を設定する
# ============================================================
# ⑰ 正方形セルのインデックスを取得
# ============================================================
# square_maskがTrueになったセルの
# x,yインデックスを取得する。
# これがXLBに、「ここは固体です」
# と教えるための情報になる。
indices = np.where(square_mask)
# XLBが利用できる境界インデックス形式に変換する。
# 正方形のセルについて、
# x方向、y方向の位置をリストとして保存する。
square = [
indices[i].astype(np.int32).tolist()
for i in range(velocity_set.d)
]
#ブロック【⑱ 流入口 BC】の解説:流入部の一様流の速度境界条件を設定する
# ============================================================
# ⑱ 流入口の境界条件
# ============================================================
# 左側から速度を指定して流体を入れる。
# "velocity": 速度境界条件
# prescribed_value=(u_in, 0.0) は、
# u_x = 0.05
# u_y = 0 を意味する。
bc_inlet = RegularizedBC(
"velocity",
prescribed_value=(u_in, 0.0),
indices=inlet,
)
#ブロック【⑲ 上下壁 BC】の解説:2次元解析空間の上下の境界条件を設定する
# ============================================================
# ⑲ 上下壁の境界条件
# ============================================================
# 上下の壁を固体壁として扱う。
# HalfwayBounceBackは、壁に向かって進んできた流体粒子を
# 壁で反射させる境界条件。
# イメージ
# 流体 →
# │壁
# │
# ← 反射 │
bc_walls = HalfwayBounceBackBC(
indices=walls
)
#ブロック【⑳ 正方形 BC】の解説:空間内の物体の対する境界条件を設定する
# ============================================================
# ⑳ 正方形物体の境界条件
# ============================================================
# 正方形も固体として扱う。
# 流体が正方形にぶつかった場合、HalfwayBounceBackによって反射する。
# その結果、以下のような流れが発生する。
# → → → →
# ■■■
# → → → ■■■
# ■■■
# → → →
# LBMで固体壁を表現するための境界条件で、
# 壁の「半分の位置」で粒子を跳ね返す方法です。
bc_square = HalfwayBounceBackBC(
indices=square
)
#ブロック【㉑ 流出部の境界条件】の解説:流出部において出口境界条件を設定する
# ============================================================
# ㉑ 流出口の境界条件
# ============================================================
# 右側から流体が計算領域の外へ出ていく出口境界条件を設定する。
# ExtrapolationOutflowBCは、出口付近の流れを外側へ外挿して
# 流体を計算領域から出すために使用する。
bc_outlet = ExtrapolationOutflowBC(
indices=outlet
)
#ブロック【㉒ 境界条件をまとめる】の解説:解析空間の4種類の境界条件をまとめる
# ============================================================
# ㉒ 境界条件をまとめる
# ============================================================
# ここまでで作った境界条件を、1つのリストにまとめる。
# これをStepperに渡すことで、XLBが各境界で適切な処理を行う。
boundary_conditions = [
bc_walls,
bc_inlet,
bc_outlet,
bc_square,
]
#ブロック【㉓ Navier-Stokes Stepper】の解説:CFDのナビエストークス流れのLBM対応
# ============================================================
# ㉓ Navier-Stokes Stepper
# ============================================================
# XLBの時間発展計算を担当するStepperを作成する。
# IncompressibleNavierStokesStepper:
# 非圧縮Navier-Stokes流れを
# LBMによって時間発展させる。
# collision_type="BGK":
# 衝突モデルとしてBGKを使用する。
stepper = IncompressibleNavierStokesStepper(
grid=grid,
boundary_conditions=boundary_conditions,
collision_type="BGK",
)
#ブロック【㉔ LBM分布関数を初期化】の解説:粒子の挙動を定める分布関数の初期化
# ============================================================
# ㉔ 初期分布関数を作成
# ============================================================
# LBMでは、f_i # という「各方向へ移動する粒子の分布関数」を計算する。
# f_0: 現在の分布関数
# f_1: 次の計算で使用する分布関数
# bc_mask: 境界条件の情報
# missing_mask: 不足している隣接セルなどの情報
# を取得する。
f_0, f_1, bc_mask, missing_mask = (
stepper.prepare_fields()
)
#ブロック【㉕ 密度・速度計算】の解説:LBMで密度や速度などのマクロな物理量の計算
# ============================================================
# ㉕ 巨視的物理量を計算するオブジェクト
# ============================================================
# LBMでは、分布関数 f から、
# 密度 rho 速度 u
# などの通常の流体力学的な物理量を計算する。
# その計算を担当するのがMacroscopic。
macro = Macroscopic(
compute_backend=ComputeBackend.JAX,
precision_policy=precision_policy,
velocity_set=xlb.velocity_set.D2Q9(
precision_policy=precision_policy,
compute_backend=ComputeBackend.JAX,
),
)
#ブロック【㉖ 計算条件を表示】の解説:解析の全体設定条件をまとめて出力する
# ============================================================
# ㉖ 計算条件を画面に表示
# ============================================================
print()
print("======================================")
print(" XLB : Flow Around Rotated Square")
print("======================================")
# 計算格子を表示する。
print(f"Grid : {Nx} x {Ny}")
# 今回の計算バックエンドを表示する。
print(f"Backend : JAX-CPU")
# 流入速度を表示する。
print(f"Velocity : {u_in}")
# Reynolds数を表示する。
print(f"Reynolds : {Re}")
# 正方形のサイズを表示する。
print(f"Square size : {square_size} x {square_size}")
# 正方形の中心位置を表示する。
print(
f"Square center : "
f"({square_cx}, {square_cy})"
)
# 正方形の回転角度を表示する。
print(f"Square angle : {square_angle} deg")
# 動粘性係数を表示する。
print(f"Viscosity : {visc}")
# BGK緩和係数を表示する。
print(f"Omega : {omega}")
print("======================================")
print()
#ブロック【㉗ 結果保存関数】の解説:必要量を計算してVTK,PNG形式で保存する
# ============================================================
# ㉗ 結果を保存する関数
# ============================================================
# 一定時間ごとに呼び出して、
# その時点の流れ場を保存する関数。
# step: 現在の時間ステップ
# f: 現在のLBM分布関数
def post_process(step, f):
# --------------------------------------------------------
# 密度と速度を計算
# --------------------------------------------------------
# LBMの分布関数fから、
# rho: 密度 u: 速度 を計算する。
rho, u = macro(f)
# --------------------------------------------------------
# 外周1セルを除去
# --------------------------------------------------------
# 結果表示用に外周セルを除く。
# rhoは、[密度] のデータ。
rho = rho[:, 1:-1, 1:-1]
# uは2次元速度なので、
# u[0]: x方向速度 u[1]: y方向速度 となる。
u = u[:, 1:-1, 1:-1]
# --------------------------------------------------------
# 速度の大きさ
# --------------------------------------------------------
# 速度ベクトル u = (u_x, u_y) の大きさ
# |u| = sqrt(u_x^2 + u_y^2)
# を計算する。
u_magnitude = jnp.sqrt(
u[0] ** 2
+ u[1] ** 2
)
# ========================================================
# 保存する物理量
# ========================================================
# ParaViewなどで確認できるように、物理量を辞書にまとめる。
fields = {
# 密度
"rho": rho[0],
# x方向速度
"u_x": u[0],
# y方向速度
"u_y": u[1],
# 速度の大きさ
"u_magnitude": u_magnitude,
}
# ========================================================
# VTK形式で保存
# ========================================================
# VTKファイルとして保存する
# ParaViewを使用すれば、速度分布などをカラー表示できる。
save_fields_vtk(
fields,
timestep=step,
prefix="square_flow"
)
# ========================================================
# PNG画像として保存
# ========================================================
# 速度の大きさを画像として保存する。
# 解析結果を簡単に確認するときに便利。
save_image(
fields["u_magnitude"],
timestep=step,
prefix="square_flow"
)
# ========================================================
# 計算状況を表示
# ========================================================
# 現在のステップと最大速度を表示する。
print(
f"step = {step:6d} : "
f"max velocity = "
f"{float(jnp.max(u_magnitude)):.6f}"
)
#ブロック【㉘ 計算ステップ設定】の解説:全体の解析ステップ数と結果保存間隔
# ============================================================
# ㉘ 時間発展計算
# ============================================================
# 総計算ステップ数。
# LBMでは、この時間ステップを1回ずつ進めながら
# 流れを計算する。
num_steps = 5000
# ------------------------------------------------------------
# 結果を保存する間隔
# ------------------------------------------------------------
# 500ステップごとに結果を保存する。
# 5000ステップ計算するので、
# 0 → 500 → 1000 → ... → 5000 の結果が保存される。
post_process_interval = 500
#ブロック【㉙ タイマー開始】の解説:解析時間測定のため開始時間を記録する
# ============================================================
# ㉙ 計算時間測定開始
# ============================================================
# 計算開始時刻を記録する。
start_time = time.time()
#ブロック【㉚ LBM時間発展】の解説:指定ステップまで解析を進めて途中結果を保存する
# ============================================================
# ㉚ LBM時間発展ループ
# ============================================================
# 0から4999まで5000回、
# LBMの時間発展を行う。
for step in range(num_steps):
# --------------------------------------------------------
# LBMを1ステップ進める
# --------------------------------------------------------
# XLBのStepperに
# f_0: 現在の分布関数
# f_1: 作業用分布関数
# bc_mask: 境界条件情報
# missing_mask: 不足セル情報
# omega: BGK緩和係数
# step: 現在の時間ステップ
# を渡して計算する。
f_0, f_1 = stepper(
f_0,
f_1,
bc_mask,
missing_mask,
omega,
step,
)
# --------------------------------------------------------
# 分布関数のバッファを交換
# --------------------------------------------------------
# 今回計算したf_1を、次のステップのf_0として使用する。
# これにより、現在 → 次の時刻 の計算を効率的に進める。
f_0, f_1 = f_1, f_0
# ========================================================
# 結果保存
# ========================================================
# 500ステップごと、または最終ステップで結果を保存する。
if (
step % post_process_interval == 0
or step == num_steps - 1
):
# 現在の分布関数から、密度・速度・速度の大きさを計算して
# ファイルに保存する。
post_process(
step,
f_0
)
#ブロック【㉛ 計算終了】の解説:time関数の現時時点から開始時点を引いて経過時間
# ============================================================
# ㉛ 計算終了
# ============================================================
# 計算終了時刻を取得する。
elapsed = time.time() - start_time
#ブロック【㉜ 計算時間表示】の解説:解析の正常完了表示と解析時間の秒単位表示
# ============================================================
# ㉜ 計算時間を表示
# ============================================================
print()
print("======================================")
print(" Simulation finished")
print("======================================")
# 計算にかかった秒数。
print(f"Elapsed time : {elapsed:.3f} s")
print("======================================")
このスクリプト全体の機能と設定の13項目について、対応するスクリプトと解析条件や解析結果と組み合わせて、下図にまとめます。上記の32ブロックの説明をあわせて、スクリプトの改良に活用してください。
スクリプト「flow_square_2d-1.py」の実行
まず、以下の【01:JAXcpu導入方法と動作確認】で準備した解析環境を、以下の手順で準備します。XLBの作業用フォルダ「~/XLBjaxcpuWork」において、独自例題フォルダ「FLOW2D」を作っておきます。
https://qiita.com/ShibataRyoichi/items/cb369e5a38cc9d3e2e00
PowerShell 7.6.6
PS C:\Windows\System32> wsl -l -v
NAME STATE VERSION
* Ubuntu-24.04 Stopped 2
Ubuntu-24.04-XLBjaxCPU Stopped 2
PS C:\Windows\System32> wsl -d Ubuntu-24.04-XLBjaxCPU
dalab@dalab2312nb01:/mnt/c/Windows/System32$ cd
dalab@dalab2312nb01:~$ ls
XLBjaxCPU XLBjaxcpuWork
dalab@dalab2312nb01:~$ source ~/XLBjaxCPU/bin/activate
(XLBjaxCPU) dalab@dalab2312nb01:~$ cd ~/XLBjaxcpuWork/
(XLBjaxCPU) dalab@dalab2312nb01:~/XLBjaxcpuWork$ ls
LDC2D XLB-git
(XLBjaxCPU) dalab@dalab2312nb01:~/XLBjaxcpuWork$ mkdir FLOW2D
(XLBjaxCPU) dalab@dalab2312nb01:~/XLBjaxcpuWork$ ls
FLOW2D LDC2D XLB-git
このなかで、「2次元問題として風洞中にある矩形周りの流れ」を、XLBで解析するために、以上で説明したスクリプト「flow_square_2d-1.py」を、これらの32ブロックを繋げて1つのファイルとして作ります。
(XLBjaxCPU) dalab@dalab2312nb01:~/XLBjaxcpuWork/TEST2D$ cd FLOW2D/
(XLBjaxCPU) dalab@dalab2312nb01:~/XLBjaxcpuWork/FLOW2D$ vi flow_square_2d-1.py
(XLBjaxCPU) dalab@dalab2312nb01:~/XLBjaxcpuWork/FLOW2D$ ls
flow_square_2d-1.py
これを、pythonで実行します。最初にGPUは未設定なので、CPUで計算を進めると表示され、4か所の境界条件の定義が登録されます。
そのあとに、独自例題の解析条件設定が表示されます。そのあとで、解析全体5000ステップで、500ステップ毎に途中結果の保存のログが表示されます。ここでは、500ステップでの解析時間と最大速度も表示されます。
ここで、流入口速度は$u_{\mathrm{in}}=0.05\quad [\mathrm{lu/ts}]$ と考えます。ここで lu は lattice unit(格子数)、ts は time step(時間ステップ)です。つまり、1時間ステップあたり、進む格子数で定義します。この単位で最大速度を見ると、最終5000ステップで0.134なので約2.7倍になっており、正方形の上下の狭い空間を、速く流れていることがわります。
最後に、解析時間が約10秒と表示されます。格子数300x120=36000で、全5000ステップを、Intel Corei9で実行した結果になります。
なお、Windwsのタスクマネージャーで確認すると、CPUで並列処理していることがわかります。
(XLBjaxCPU) dalab@dalab2312nb01:~/XLBjaxcpuWork/FLOW2D$ python flow_square_2d-1.py
No GPU support is available; CPU fallback will be used.
registered bc RegularizedBC_8332170304619 with id 1
registered bc HalfwayBounceBackBC_8332175202663 with id 2
WARNING! Assuming no-slip condition for BC type = HalfwayBounceBackBC!
registered bc HalfwayBounceBackBC_8332163779297 with id 3
WARNING! Assuming no-slip condition for BC type = HalfwayBounceBackBC!
registered bc ExtrapolationOutflowBC_8332163779354 with id 4
Warp 1.17.0 initialized:
CUDA Toolkit 12.9, Driver 13.2
Devices:
"cpu" : "x86_64"
"cuda:0" : "NVIDIA GeForce RTX 3070 Ti Laptop GPU" (8 GiB, sm_86, mempool enabled)
Kernel cache:
/home/dalab/.cache/warp/1.17.0
======================================
XLB : Flow Around Rotated Square
======================================
Grid : 300 x 120
Backend : JAX-CPU
Velocity : 0.05
Reynolds : 100.0
Square size : 40 x 40
Square center : (75, 60)
Square angle : 30.0 deg
Viscosity : 0.02
Omega : 1.7857142857142856
======================================
Saved ./square_flow_0000000.vtk in 0.020215 seconds.
step = 0 : max velocity = 0.000000
Saved ./square_flow_0000500.vtk in 0.002383 seconds.
step = 500 : max velocity = 0.156543
Saved ./square_flow_0001000.vtk in 0.002592 seconds.
step = 1000 : max velocity = 0.129826
Saved ./square_flow_0001500.vtk in 0.002317 seconds.
step = 1500 : max velocity = 0.130439
Saved ./square_flow_0002000.vtk in 0.002421 seconds.
step = 2000 : max velocity = 0.133740
Saved ./square_flow_0002500.vtk in 0.002411 seconds.
step = 2500 : max velocity = 0.135603
Saved ./square_flow_0003000.vtk in 0.002438 seconds.
step = 3000 : max velocity = 0.132879
Saved ./square_flow_0003500.vtk in 0.002409 seconds.
step = 3500 : max velocity = 0.132424
Saved ./square_flow_0004000.vtk in 0.007832 seconds.
step = 4000 : max velocity = 0.138548
Saved ./square_flow_0004500.vtk in 0.007650 seconds.
step = 4500 : max velocity = 0.137304
Saved ./square_flow_0004999.vtk in 0.002434 seconds.
step = 4999 : max velocity = 0.133921
======================================
Simulation finished
======================================
Elapsed time : 10.336 s
======================================
解析結果として、500ステップ毎の「可視化用vtkファイル」と「速度分布pngファイル」が、保存されています。
(XLBjaxCPU) dalab@dalab2312nb01:~/XLBjaxcpuWork/FLOW2D$ ls
flow_square_2d-1.py square_flow_0002000.vtk square_flow_0004999.vtk square_flow_3000.png
square_flow_0000.png square_flow_0002500.vtk square_flow_0500.png square_flow_3500.png
square_flow_0000000.vtk square_flow_0003000.vtk square_flow_1000.png square_flow_4000.png
square_flow_0000500.vtk square_flow_0003500.vtk square_flow_1500.png square_flow_4500.png
square_flow_0001000.vtk square_flow_0004000.vtk square_flow_2000.png square_flow_4999.png
square_flow_0001500.vtk square_flow_0004500.vtk square_flow_2500.png
速度分布のPNG画像ファイルを、ステップ0500-4999の10枚分を確認します。前半で流れが成長し、後半でカルマン渦が確認できます。これは、正方形が30度回転しており、上下の流れが非対称になっており、カルマン渦が誘導されたと思います。

次に、可視化用のVTKデータファイル「square_flow_...vtk」を、ParaViewを用いて確認します。
この可視化用データでは、密度rhoを表示することもできます。

さらに、MP4形式の動画ファイルを作成することもできます。ParaViewの手順(save animation)を確認して試してください。
以上で説明したスクリプト「flow_square_2d-1.py」において、変更可能なパラメータを、下図にまとめます。まず最初は、スクリプトの設定通りで解析を実行して、動作確認をします。そのあとは、この図に示すいろいろなパラメータを変更して、比較検討してみてください。
例えば、正方形の回転角度0度とすると、傾かない状態の速度分布は、以下のようになります。流れは成長しますが、上下対称なので、カルマン渦は発生していません。
コンパクト版スクリプト「flow_square_2d-0.py」
スクリプト全体の把握し、機能拡張などを目的として、コメントなどを削除して、ログの画面表示も圧縮して、コンパクトにしたスクリプト「flow_square_2d-0.py」を以下に示します。こちらを用いて一括コピーしても、同じ解析結果が得られます。なお、対応するブロック①~㉜について、該当する行末に記してあります。
import os
os.environ["JAX_PLATFORMS"] = "cpu"
import time
import numpy as np
import jax.numpy as jnp
import xlb
from xlb.compute_backend import ComputeBackend
from xlb.precision_policy import PrecisionPolicy
from xlb.grid import grid_factory
from xlb.operator.stepper import IncompressibleNavierStokesStepper
from xlb.operator.boundary_condition import HalfwayBounceBackBC, RegularizedBC, ExtrapolationOutflowBC
from xlb.operator.macroscopic import Macroscopic
from xlb.utils import save_fields_vtk, save_image #①
Nx, Ny = 300, 120
u_in, Re = 0.05, 100.0 #②
square_size, square_cx, square_cy, square_angle = 40, Nx // 4, Ny // 2, 30.0 #③
compute_backend = ComputeBackend.JAX
precision_policy = PrecisionPolicy.FP32FP32 #④
velocity_set = xlb.velocity_set.D2Q9(precision_policy=precision_policy, compute_backend=compute_backend) #⑤
visc = u_in * square_size / Re
omega = 1.0 / (3.0 * visc + 0.5) #⑥
xlb.init(velocity_set=velocity_set, default_backend=compute_backend, default_precision_policy=precision_policy) #⑦
grid = grid_factory((Nx, Ny), compute_backend=compute_backend) #⑧
box = grid.bounding_box_indices()
box_no_edge = grid.bounding_box_indices(remove_edges=True) #⑨
inlet = box_no_edge["left"] #⑩
outlet = box_no_edge["right"] #⑪
walls = [box["bottom"][i] + box["top"][i] for i in range(velocity_set.d)]
walls = np.unique(np.array(walls), axis=-1).tolist() #⑫
X, Y = np.meshgrid(np.arange(Nx), np.arange(Ny), indexing="ij") #⑬
theta = np.deg2rad(square_angle)
Xc, Yc = X - square_cx, Y - square_cy
Xr = np.cos(theta) * Xc + np.sin(theta) * Yc
Yr = -np.sin(theta) * Xc + np.cos(theta) * Yc #⑭
half = square_size / 2.0 #⑮
square_mask = (np.abs(Xr) <= half) & (np.abs(Yr) <= half) #⑯
indices = np.where(square_mask)
square = [indices[i].astype(np.int32).tolist() for i in range(velocity_set.d)] #⑰
bc_inlet = RegularizedBC("velocity", prescribed_value=(u_in, 0.0), indices=inlet) #⑱
bc_walls = HalfwayBounceBackBC(indices=walls) #⑲
bc_square = HalfwayBounceBackBC(indices=square) #⑳
bc_outlet = ExtrapolationOutflowBC(indices=outlet) #㉑
boundary_conditions = [bc_walls, bc_inlet, bc_outlet, bc_square] #㉒
stepper = IncompressibleNavierStokesStepper(grid=grid, boundary_conditions=boundary_conditions, collision_type="BGK") #㉓
f_0, f_1, bc_mask, missing_mask = stepper.prepare_fields() #㉔
macro = Macroscopic(compute_backend=ComputeBackend.JAX, precision_policy=precision_policy,
velocity_set=xlb.velocity_set.D2Q9(precision_policy=precision_policy, compute_backend=ComputeBackend.JAX)) #㉕
print(f"Grid={Nx}x{Ny}, Backend=JAX-CPU, U={u_in}, Re={Re}, Square={square_size}x{square_size}, Center=({square_cx},{square_cy}), Angle={square_angle}°, ν={visc}, ω={omega}") #㉖
def post_process(step, f): #㉗
rho, u = macro(f)
rho, u = rho[:, 1:-1, 1:-1], u[:, 1:-1, 1:-1]
u_magnitude = jnp.sqrt(u[0] ** 2 + u[1] ** 2)
fields = {"rho": rho[0], "u_x": u[0], "u_y": u[1], "u_magnitude": u_magnitude}
save_fields_vtk(fields, timestep=step, prefix="square_flow")
save_image(fields["u_magnitude"], timestep=step, prefix="square_flow")
print(f"step={step:6d}, max velocity={float(jnp.max(u_magnitude)):.6f}")
num_steps, post_process_interval = 5000, 500 #㉘
start_time = time.time() #㉙
for step in range(num_steps): #㉚
f_0, f_1 = stepper(f_0, f_1, bc_mask, missing_mask, omega, step)
f_0, f_1 = f_1, f_0
if step % post_process_interval == 0 or step == num_steps - 1:
post_process(step, f_0)
elapsed = time.time() - start_time #㉛
print(f"Simulation finished: {elapsed:.3f} s") #㉜
LBMの考え方を改めて確認する
以上で、「2次元問題として風洞中にある矩形周りの流れ」 を、XLBのスクリプトを用いて、LBM解析で求めることが出来ました。ここで、LBMを初めて学ぶ場合、まず 「流体そのものを直接動かすのではなく、各格子点にある“粒子の分布”を動かして、結果として流体の速度や密度を得る」 として考えると理解しやすいです。
今回のXLBの例題を理解するために、D2Q9 → 分布関数 → Collision → Streaming → 速度・密度の順に、できるだけ具体的に説明します。
1. LBMの基本的な考え方
通常のCFDでは、Navier-Stokes方程式を直接解いて、$u_x,\quad u_y,\quad p$ などを求めます。
一方、LBMでは少し違います。以下の手順、つまり、「分布関数を計算することで、流体の速度・密度を間接的に求める」 という方法です。
LBM(XLB) → 分布関数 f_i を計算 → Collision(衝突)
→ Streaming(移動) → 分布関数 f_i の更新 → 密度 ρ、速度 u を計算
2. 「分布関数」とは何か?
ここがLBMで最も重要な部分です。ある格子点を考えると、「D2Q9」 では、1つの格子点に9方向の分布関数があります。
一般に、$f_0,f_1,f_2,\cdots,f_8$ と表します。例えば、以下のように、それぞれの方向に対応しています。
f0 → その場にとどまる成分
f1 → 右 f2 → 上 f3 → 左 f4 → 下
f5 → 右上 f6 → 左上 f7 → 左下 f8 → 右下
3. 分布関数は「粒子の量」と考える
例えば、ある格子点で、以下の状態だったとします。
右方向:f1 = 0.20・左方向:f3 = 0.10・上方向:f2 = 0.12・下方向:f4 = 0.12
右向きの分布が左向きより大きいので、「右 →→」という流れが生じます。つまり、各方向にどれだけ“流体の分布”が存在しているか を表しているのが f_i です。ここで重要なのは、f_i は普通の意味での「実際の粒子1個1個」ではないことです。LBMでは、流体の状態を9方向の分布として表現していると考える方が適切です。
4. 分布関数から密度を求める
9個の分布関数を全部足すと、密度になります。$\rho=\sum_i f_i$ つまり、「f0 + f1 +...+ f8:全部足すと、密度ρ」になります。
5. 分布関数から速度を求める
速度は、各方向の分布に「方向」を掛けて足し合わせます。$\rho\mathbf{u}=\sum_i f_i\mathbf{c}_i$ したがって、$\mathbf{u}=\frac{1}{\rho}\sum_i f_i\mathbf{c}_i$ となります。ここで、$\mathbf{c}_i$ は各方向の速度です。例えば、「右方向:+x 左方向:-x 上方向:+y 下方向:-y」です。
6. では「Collision」とは何か?
ここからLBMの核心です。英語のCollisionは、日本語では「衝突」です。ただし、流体解析のLBMでは、 分布関数を局所的に平衡状態へ近づける計算 と考える方が分かりやすいです。
7. Collisionのイメージ
例えば、ある格子点で、方向別の値が、以下のような状態だったとします。流体としては少し「乱れた」分布になっています。
現在の分布
右 ████████ 左 ██ 上 ████ 下 ███ 斜め ███...
そこで、「現在の分布 → Collision → 平衡状態に近づける」という操作をします。
8. BGKモデル
今回のXLBのスクリプトでは、「collision_type="BGK"」 を使用しています。BGKでは、非常に簡単に書くと、$f_i^{*}=f_i+\omega(f_i^{eq}-f_i)$ です。
ここで、$f_i$:現在の分布関数、$f_i^{eq}$:平衡分布関数、$f_i^*$:Collision後の分布関数、$\omega$:緩和係数、です。
9. この式を日本語にすると
式 $f_i^{*}=f_i+\omega(f_i^{eq}-f_i)$ を見ると、$f_i^{eq}-f_i$ は、「現在の分布と平衡状態との差」です。したがって、以下のような流れになります。
現在の分布 → 平衡分布との差 → 現在の分布を修正 → Collision後の分布
[差を計算] [ωを掛ける]
10.緩和係数 ωは何を意味する?
今回のスクリプトでは、$\omega=\frac{1}{3\nu+0.5}$ としていました。例えば、Re=100 の場合、ν=0.02 なので、$\omega=\frac{1}{3(0.02)+0.5}\approx1.4286$ となります。つまり、「Re → 粘性 ν
→ 緩和係数 ω → Collisionの強さ」という関係になっています。
11. Collisionだけでは流体は流れない
ここが初心者には特に重要です。Collisionをすると、「格子点A ● → 分布が変化します。」しかし、それだけでは隣の格子点には移動していません。そこで次に、Streaming を行います。
12. Streamingとは?
Streamingは、分布関数を、その分布が向いている隣の格子点へ移動させる 処理です。例えば、以下のように、左図の3つの格子点があるとします。A点に存在する右向き分布 $f_1$ は、Streamingによって、右図のように移動します。
A B C [Streaming] A f1 B
● → ● → ● ● → ●
つまり、$f_i(\mathbf{x}+\mathbf{c}_i,t+1)=f_i^*(\mathbf{x},t)$ です。
13. これが「流れる」の正体
例えば右向きの分布だけを見ると、それぞれの格子点にある右向き分布が、以下のように隣へ移動します。これを大量の格子点で繰り返します。すると、クロな流れが現れます。
時刻 t 時刻 t+1
● →1 ● →2 ● →3 ● ● →0 ● →1 ● →2 ●
14. CollisionとStreamingの違い
ここを明確に区別するとLBMがかなり分かりやすくなります。
| 処理 | 何をする? | イメージ |
|---|---|---|
| Collision | 分布を平衡状態に近づける | その場で分布を混ぜる |
| Streaming | 分布を隣の格子へ移動する | 分布を運ぶ |
15. LBMの1ステップ
LBMの基本的な1ステップを非常に単純化すると、以下のような流れになります。
16. 今回の正方形まわり流れで考える
今回のモデルは、2次元の長方形の流体空間の内部に、正方形の物体があります。左から流体が入ってきて、LBMでは、各格子点で以下の処理を繰り返します。
分布関数 f_i → Collision → Streaming → 境界条件 → 次の時刻の f_i
17. 正方形にぶつかるとどうなる?
Streamingをすると、分布が正方形の内部へ入ろうとする場合があります。しかし、正方形は固体なので、そのまま通過させません。そこで、Bounce Back などの境界条件を使います。
イメージとしては、流体の格子からの分布を反射させます。今回のスクリプトでは、「HalfwayBounceBackBC」を使っています。
18. そして後流ができる
正方形の後ろでは、分布関数の移動とCollisionを何度も繰り返すことで、複雑な流れが形成されます。これをマクロに見ると、後流・再循環・渦 として現れます。
19. 最後に「速度」を取り出す
LBMが直接計算している中心的な量は分布関数 $f_i$ です。そこから、$\rho=\sum_i f_i$と、$\mathbf{u}=\frac{1}{\rho}\sum_i f_i\mathbf{c}_i$ を計算します。今回のXLBスクリプトの、「rho, u = macro(f)」は、まさにこの処理です。
20. XLBのスクリプトとの対応
今回のスクリプトをLBMの考え方に対応させると、次のような、説明図になります。
21. f_0 と f_1 は何?
今回のプログラムには、「f_0, f_1 = stepper.prepare_fields()」がありました。ここは少し注意が必要です。f_0とf_1はD2Q9の9方向そのものではありません。 D2Q9の方向は、$f_0,\ldots,f_8$ ですが、XLBのスクリプトに出てくる「f_0 f_1」は、時間発展の前後の分布関数を格納するための2つの配列(バッファ) として使われています。
そして、以下の処理によって、計算した結果を次のステップへ渡しています。ここはLBMを初めて学ぶと非常に混乱しやすいところです。
f_0, f_1 = stepper(
f_0, f_1,
bc_mask,
missing_mask,
omega,
step
)
f_0, f_1 = f_1, f_0
22. 一番簡単な覚え方
LBMはまず、次の3語を覚えるとよいです。これらを何千回も繰り返すと、最終的に 「流体の流れ」 が現れます。
【分布関数:「各方向にどれだけ流体が分布しているか」】
↓【Collision:「その場所で分布を平衡状態に近づける」】
↓【Streaming:「その分布を方向に従って隣の格子へ運ぶ」】
以上で説明してきた、以下の流れで理解すると、XLBのプログラムが何をしているのかがかなり見通しやすくなります。
分布関数 → Collision → Streaming → 境界条件 → 次の時刻
→ .... → 正方形まわりの流れ → ux, uy, |u| → 解析結果
これを説明図としてまとめると、以下のようになります。











