XLB

Activity, over time

Daily observations from the public event archive.

Attention

Daily stars & forks

Loading attention chart

Star accumulation

Cumulative stars

Loading star accumulation chart

Pull requests

Daily opens & merges

Loading pull requests chart

Conversations

Daily issues & comments

Loading conversations chart

Community

Daily accounts & reviews

Loading community chart

Releases

Daily releases

Loading releases chart

Mentions

1post · 30d
Nov 40 posts
  1. LBM流体解析ツールXLBの活用【10:XLB独自例題の作成と分析】 この文書では、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リポジトリは、以下にあります。 Autodesk/XLB ここでは、最も基本となるCPUで動作する解析環境「JAX-CPU」を、以下の手順で準備した状態を用いて、独自例題の作成と分析を行います。 qiita.com 対象とする独自例題の概要 ここでは、「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次元に簡略化した内容です。ただし、流体内部の四角形の「位置・規模・角度」が任意で設定できるので、様々な条件を確認することができます。これを参考にして、「左から流入 → 中央の正方形物体 → 右へ流出」 という、外部流れの最小例にできます。 qiita.com 今回作るモデル まずは非常に単純化します。設定する流体空間中に、正方形を任意の位置・規模・角度に、境界条件: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」を作っておきます。 qiita.com 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」を以下に示します。こちらを用いて一括コピーしても、同じ解析結果が得られます。なお、対応するブロック①~㉜について、該当する行末に記してあります。 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 likes

Latest stargazers

The last 10 distinct accounts we observed this year.

Most active participants

PR, issue, comment & review events in the past year.

Packages

Scan pending.

Live mentions badge

Show how many posts mention your project in the last 30 days.

Live mentions · last 30 days
Autodesk/XLB mentions in the last 30 days