2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LBM流体解析ツールXLBの活用【10:XLB独自例題の作成と分析】

2
Last updated at Posted at 2026-10-06

この文書では、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」

この論文中の図2では、以下の結果が示されています。
image.png

Fig. 2. Streamlines around the square cylinder for different Reynolds numbers (a) Re = 1; (b) Re = 30; (c) Re = 60; (d) Re = 200

解析モデルと解析条件と境界条件などの設定

これより、レイノルズ数Reの違いによって、正方形周りの流れの変化を、比較検討できます。解析モデルと境界条件は、以下の図のような定義です。

image.png

独自例題の、格子ボルツマン法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の解析結果のイメージを目標とします。

image.png

独自例題を理解する上でのポイント

この独自例題は、以下の【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ブロックの説明をあわせて、スクリプトの改良に活用してください。

image.png

スクリプト「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度回転しており、上下の流れが非対称になっており、カルマン渦が誘導されたと思います。
image.png

次に、可視化用のVTKデータファイル「square_flow_...vtk」を、ParaViewを用いて確認します。

image.png

この可視化用データでは、密度rhoを表示することもできます。
image.png

さらに、MP4形式の動画ファイルを作成することもできます。ParaViewの手順(save animation)を確認して試してください。

以上で説明したスクリプト「flow_square_2d-1.py」において、変更可能なパラメータを、下図にまとめます。まず最初は、スクリプトの設定通りで解析を実行して、動作確認をします。そのあとは、この図に示すいろいろなパラメータを変更して、比較検討してみてください。

image.png

例えば、正方形の回転角度0度とすると、傾かない状態の速度分布は、以下のようになります。流れは成長しますが、上下対称なので、カルマン渦は発生していません。

image.png

コンパクト版スクリプト「flow_square_2d-0.py」

スクリプト全体の把握し、機能拡張などを目的として、コメントなどを削除して、ログの画面表示も圧縮して、コンパクトにしたスクリプト「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方向の分布関数があります。

image.png

一般に、$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ステップを非常に単純化すると、以下のような流れになります。

image.png

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)」は、まさにこの処理です。

image.png

20. XLBのスクリプトとの対応

今回のスクリプトをLBMの考え方に対応させると、次のような、説明図になります。

image.png

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| → 解析結果

これを説明図としてまとめると、以下のようになります。

image.png

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?