この記事は著者が韓国語で書いた原文の翻訳です。 原文(한국어)を読む
ノード1つで誤差が0.21%から76%に跳ねた — べき法則流体LBMの局所緩和時間
べき法則LBMにおけるτの上限は安定化オプションではなく、定常状態に到達するための条件です。
収束しないログに残った inf の一行#
血液をべき法則(power-law)流体として、格子ボルツマン法(LBM)でチャネル流れを回しました。速度場はもっともらしく見えました。残差だけが下がりません。2万ステップ回しても 付近で止まったままでした。
緩和時間 をノードごとに出力すると、チャネル中心線のノード1つに inf がありました。タイプミスでもゼロ除算でもありません。そのノードは本当に無限大の粘度を要求していたのです。
発散はバグではありません。べき法則そのものの性質です。べき指数のスライダーを1.0から0.4へ下げると、中心ノードの が赤い帯へ押し出されます。
Uncapped, the centre node reads tau = infinity — the shear rate vanishes there, so a shear-thinning fluid asks for infinite viscosity. Green band 0.55 < tau < 2 is the usable range; red on the left is the stability edge, red on the right is where bounce-back walls start leaking.
左の速度分布は中心で平らになります。cap tau を外すと、そのノードは軸の外へ消えます。0.21%と76%を分けた場所がそこです。
LBMがひずみ速度をただでくれる場所#
有限体積法で非ニュートン粘度を扱うには、まず速度勾配が要ります。隣接セルを読み、最小二乗なりGreen–Gaussなりで再構成し、それから が出ます。並列計算ではここに通信が1回増えます。
LBMはその段階を飛ばします。Chapman–Enskog展開(クヌーセン数のべき級数で分布関数を分解する多重スケール手法)を1次追うだけで、非平衡分布の2次モーメントがひずみ速度テンソルと直接結びつきます。
はそのノードの平衡からのずれ、 は格子音速の2乗、 は緩和時間です。右辺はすべて1つのノード内の量です。隣を読みません。
同じ2次モーメントは、別の問題では欠損として顔を出すこともあります。ここでは逆に贈り物です。粘性応力はすでに分布関数の中にあり、取り出すだけで済みます。
べき法則はこの 1つで閉じます。
は稠度係数、 はべき指数です。 ならせん断減粘(shear-thinning) — 速くかき混ぜるほど薄くなります。血液は 付近です。 でニュートン流体に戻ります。
粘度が τ の中にあり τ が粘度の中にある
式を2つ並べると問題が見えます。BGK衝突では粘度を緩和時間が決めます。
ところが上の の式にも が分母にあります。 を知らないと が測れず、 がないと が出ず、 がないと が決まりません。ノードごとに小さな非線形方程式が1つ置かれている形です。 に半ステップがどこで付くかは離散LBEの2次展開に分けてあります。ここではその式をノードごとに解き直す側だけを扱います。
多くの実装はここで前ステップの をそのまま使います。この遅延(lagging)は定常状態では正しいのですが、界面や拍動のようにせん断が速く変わる場所では、1ステップ分ずれた粘度で衝突を回すことになります。
遅延せずに解くこともできます。鍵は に依存しない量を先に分離することです。非平衡2次モーメントの偏差(deviatoric)部分を と書き
と置くと、 はその瞬間の だけで計算できます。 が入る余地がありません。そして です。
不動点1つで閉じる局所方程式#
と変数を替えると、3つの式が1行にまとまります。
未知数は だけ、係数 は定数です。この形が良い理由はもう1つあります。右辺を とすると
であり、解では なので です。したがって のとき常に が成り立ちます。せん断減粘流体なら単純代入反復が無条件に縮小写像になります。Newton法も安全装置も初期推定も要りません。
D2Q9で に寄与するのは対角リンク4本だけです。 が0の軸方向リンクは、このモーメントに入りません。
同じチャネルに上限を5通り入れてみた#
体積力で駆動する完全発達チャネルです。壁は halfway bounce-back、幅は31ノード。べき法則チャネル流れには解析解があるので、誤差をそのまま測れます。
は単位質量あたりの体積力、 は半幅です。 を入れると見慣れた放物線に戻ります。
import numpy as np
CX = np.array([0, 1, 0, -1, 0, 1, -1, -1, 1])
CY = np.array([0, 0, 1, 0, -1, 1, 1, -1, -1])
W = np.array([4/9, 1/9, 1/9, 1/9, 1/9, 1/36, 1/36, 1/36, 1/36])
OPP = np.array([0, 3, 4, 1, 2, 7, 8, 5, 6])
CS2, G, NY = 1/3, 2e-6, 31
def eq(rho, ux):
cu = CX[:, None] * ux
return W[:, None] * rho * (1 + cu/CS2 + cu*cu/(2*CS2**2) - ux*ux/(2*CS2))
def relax_time(Q, rho, n, K, sweeps=40):
"""m = cs2*tau。隣接ノードを一度も読まない不動点反復。"""
with np.errstate(divide='ignore', over='ignore', invalid='ignore'):
B = K * (Q / (2*rho))**(n - 1)
m = np.ones_like(Q)
for _ in range(sweeps):
m = CS2/2 + B * m**(1 - n)
return m / CS2
def exact(n, K):
h = NY/2.0
y = np.arange(NY) - (NY - 1)/2.0
return n/(n+1) * (G/K)**(1/n) * (h**((n+1)/n) - np.abs(y)**((n+1)/n))
def consistency_K(n, umax=8e-3):
h = NY/2.0
return G / (umax*(n+1)/(n*h**((n+1)/n)))**n
def channel(n, K, cap, steps=20000, tol=1e-12):
ue = exact(n, K)
f = eq(np.ones(NY), ue) # 解析解から出発し、定常状態への補正だけを緩和させる
prev, tau = ue.copy(), np.full(NY, 0.8)
for it in range(steps):
rho = f.sum(0)
ux = (CX[:, None]*f).sum(0)/rho + 0.5*G
fe = eq(rho, ux)
Pxy = (CX[:, None]*CY[:, None]*(f - fe)).sum(0)
raw = relax_time(2*np.abs(Pxy), rho, n, K)
tau = np.clip(np.nan_to_num(raw, nan=cap, posinf=cap), 0.5005, cap)
cu = CX[:, None]*ux
src = W[:, None]*(1 - 1/(2*tau))*rho*G*((CX[:, None]-ux)/CS2 + cu*CX[:, None]/CS2**2)
post = f - (f - fe)/tau + src
for i in range(9):
f[i] = np.roll(post[i], CY[i])
for i in range(9): # halfway bounce-back
if CY[i] == 1: f[i, 0] = post[OPP[i], 0]
if CY[i] == -1: f[i, -1] = post[OPP[i], -1]
if it % 200 == 199:
res = np.abs(ux - prev).max()/ux.max()
if res < tol: break
prev = ux.copy()
rho = f.sum(0)
ux = (CX[:, None]*f).sum(0)/rho + 0.5*G
err = np.linalg.norm(ux - ue)/np.linalg.norm(ue)
return err, res, it + 1, tau, raw
K1 = consistency_K(1.0)
e, r, it, tau, _ = channel(1.0, K1, 1e6)
print(f"Newtonian n=1.0 L2={e:.4%} tau={tau.min():.4f}..{tau.max():.4f} steps={it}")
n, K = 0.6, consistency_K(0.6)
_, _, _, _, raw = channel(n, K, 4.0, steps=200)
print(f"n=0.6 tau at wall {raw[0]:.4f} tau at centre {raw[NY//2]:.4e}")
print(f"{'cap':>9} {'L2 err':>9} {'residual':>10} {'steps':>7} {'capped':>8}")
for cap in [0.8, 1.5, 4.0, 40.0, 1e6]:
e, r, it, tau, _ = channel(n, K, cap)
print(f"{cap:9.1f} {e:9.4%} {r:10.2e} {it:7d} {int((tau >= cap - 1e-9).sum()):5d}/{NY}")Newtonian n=1.0 L2=0.1362% tau=0.5901..0.5901 steps=20000
n=0.6 tau at wall 0.5690 tau at centre inf
cap L2 err residual steps capped
0.8 0.2112% 1.08e-06 20000 3/31
1.5 0.2121% 1.14e-06 20000 1/31
4.0 0.2134% 1.07e-06 20000 1/31
40.0 0.4850% 1.02e-08 20000 1/31
1000000.0 75.9752% 7.69e-03 20000 1/31Pi_xy を上げると緑の曲線が下がり、不動点の階段が対角線に届く段数が減ります。
The staircase is the whole solver: 0 steps of m → cs2/2 + B m^(1-n) land on tau = 0.5894 (gamma = 2.04e-3, nu = 2.98e-2). Freezing tau at the previous step instead would answer 0.6011. No neighbour node is read at any point.
を1より上にすると傾きが負になり、階段は螺旋を描きます。 ならそれでも縮小します。ロゼットの軸方向リンクが灰色なのは、 で に乗らないからです。
0.21%と76%を分けたのはノード1つだった#
ニュートン流体の基準線が0.1362%です。ソルバ自体は正しく動いています。同じ格子に を入れ、上限だけを変えたところ、誤差が0.2112%から75.9752%まで動きました。
capped の列を見てください。上限が1.5以上のとき、引っかかるノードは31個中ちょうど1個です。チャネル中心線のノードです。その1ノードの を にするか4.0にするかの間に、誤差75.76ポイントが挟まっています。
理由は衝突の式にあります。 で にすると、括弧の前の係数が0になります。そのノードは平衡に戻りません。初期分布が持っていた誤差がそのまま残り、周囲のノードがストリーミングで持ち去ります。残差が で止まったのはその結果です。
なぜ中心線で がちょうど0なのか。対称性です。チャネル中心で が符号を変えるので、その位置で0を通ります。離散的にも が相殺してちょうど0になります。すると で ですから です。連続体でも同じです。べき法則はせん断速度0で定義されません。
壁の を粘性に戻すと です。ニュートン側の は です。同じ 、幅31ノードでも、壁の粘性はすでに違います。コードの下限クリップは0.5005です。そこでは まで下がり、壁の0.0230より二桁以上小さくなります。 なら なので、不動点の傾きは の中に収まります。縮小はします。ただし中心で になると、指数 が を発散させます。
上限0.8と4.0のL2は0.2112%と0.2134%です。差は0.0022パーセントポイントです。1.5は0.2121%で、0.8より0.0009パーセントポイント大きいだけです。引っかかったノードは3個から1個に減ります。上限を まで開くと誤差75.9752%、残差 です。上限4.0の残差 より三桁以上大きい値です。ステップ数はどちらも20000です。上限を閉じれば まで下がり、開くと で止まります。冒頭の はその開いた側です。引っかかったノードは相変わらず31個中1個です。
上限値をどこに置いてもほぼ同じ答えになる理由
ここに実務上重要な事実があります。0.8、1.5、4.0の誤差が0.2112%、0.2121%、0.2134%です。3桁まで一致しています。
そのノードはせん断応力をほとんど運んでいないからです。完全発達チャネルで運動量収支が決める応力は です。中心では なので です。粘度をいくら間違えても、掛かるせん断速度が0なので応力への寄与がありません。
つまり発散するのは であって ではありません。流れが実際に感じるのは後者です。だから上限は精度を損なわずに入れられます。上限を40まで上げると誤差が0.4850%と倍になりますが、これは が大きくそのノードの緩和が遅いためです。精度ではなく収束速度の問題です。
逆に上限を下げすぎると別の代償があります。0.8では3ノードが引っかかります。中心近くの2ノードが実際より薄くなり、その誤差は応力に乗ります。表ではまだ0.2112%と小さいのですが、 がさらに小さくなると引っかかるノード数が急に増えます。最初のシミュレーションで nodes を81に上げ、 を0.4まで下げると、赤い帯に入るノードが目に見えて増えます。
血液を解くときに話が変わるところ
べき法則がせん断速度0で発散することは、レオロジーでも知られた欠点です。実際の血液はせん断速度が0に向かっても粘度が有限値(ゼロせん断粘度)に留まります。Carreau–Yasudaモデルがその部分を直します。
と が両端を塞ぎ、 はせん断減粘が始まるせん断速度の逆数です。このモデルなら で と有限です。上限を手で入れる必要がありません。 がそのまま上限です。
ではなぜべき法則を使うのか。管中央を除く大半の領域で両モデルの差が小さく、べき法則には検証できる解析解があるからです。さらに上の不動点が1行で閉じるのはべき法則のときだけです。Carreau–Yasudaでも同じ反復になりますが、縮小性の証明はそこまできれいになりません。
LBM側で が大きくなるときにもう1つ注意すべきことがあります。bounce-back壁の実効位置が とともに動く点です。せん断減粘流体は壁近くで が小さいので、この誤差はむしろ小さくなります。代わりに が に近づくと安定性の余裕が減ります。上の計算では壁ノードが0.5690でした。余裕はありますが、十分とまでは言えません。 が、下限までの間隔です。
発散することと使えないことは別である
inf はゼロ除算のバグではありませんでした。せん断速度0で なら が正しい答えです。計算が止まったのは、BGKが を係数に使うからです。粘度が無限大だという文と、そのノードを永久に緩和しないという文が、同じ式になります。
上限は後者を止める離散化です。表では1.5と4.0のL2が0.2121%と0.2134%で、ほとんど同じです。 なら同じチャネルの式が放物線に戻り、その基準はポアズイユの解析解の側にあります。ログに引っかかったノードが1を大きく超えたら、直すのは上限ではなく か格子です。
関連記事
役に立ったらシェアしてください。