Skip to content
cfd-lab:~/ja/posts/2026-04-22-cfd-linear-so…online
NOTE #021DAY WED CFD기법DATE 2026.04.22READ 5 min readWORDS 2,558#アルゴリズム#線形ソルバー#Krylov#数値解析

CFD線形ソルバーの選び方:JacobiからBiCGSTABまでの収束速度比較

Krylov部分空間における4大ソルバーの特性と収束速度をコードとシミュレーションで比較解説

1952年、コーネリアス・ランチョス、マグナス・ヘステネス、エドゥアルト・スティフェルの3人は、ある学会で偶然にも独立して同じ結果を発表しました。対称正定値(SPD)行列をわずか NN 回の反復で解くアルゴリズムです。これが現在、私たちが**共役勾配法(Conjugate Gradient, CG)**と呼んでいる手法です。興味深いのは、70年経った今日も、CFDソルバーの核心にはこのアイデアがほぼそのまま生き続けている点です。それは、Krylov(クリロフ)部分空間で解を探すというアイデアです。このポストでは、CFDで最も頻繁に使用される4つのソルバー(Jacobi、Gauss-Seidel、CG、BiCGSTAB)の収束速度を、Pythonとインタラクティブなシミュレーションで比較します。最後まで読めば、実務において「この問題にはどのソルバーが適しているか」を即座に判断できるようになるでしょう。

なぜCFDでは線形システムを反復法で解くのか#

ナビエ・ストークス方程式を離散化すると、各ステップで Ax=bA\mathbf{x}=\mathbf{b} という形式の巨大な線形システムが現れます。ここで、AA は係数行列、x\mathbf{x} はセル中心の変数ベクトル、b\mathbf{b} は右辺のソース項です。3D格子の100万セルの場合、AA106×10610^6 \times 10^6 サイズの疎行列になります。LU分解(行列を下三角行列と上三角行列の積に分解する直接法)をこの規模で実行すると、メモリとCPUを膨大に消費します。OpenFOAM、SU2、GerrisなどのオープンソースCFDコードが例外なく**反復法(iterative method)**を採用しているのはそのためです。

反復法の基本アイデアは単純です。初期推定値 x0\mathbf{x}_0 から出発し、残差 r=bAx\mathbf{r} = \mathbf{b} - A\mathbf{x} が許容値以下になるまで x\mathbf{x} を少しずつ修正していきます。問題は、その「少しずつ」をどう定義するかです。この定義こそがソルバーの名前を分ける境界線となります。

Krylov部分空間というアイデア#

初期残差 r0\mathbf{r}_0 に行列 AA を繰り返し掛けたベクトルが生成する空間があります。

Km(A,r0)=span{r0,Ar0,A2r0,,Am1r0}\mathcal{K}_m(A, \mathbf{r}_0) = \text{span}\{\mathbf{r}_0,\, A\mathbf{r}_0,\, A^2 \mathbf{r}_0,\, \dots,\, A^{m-1} \mathbf{r}_0\}

Km\mathcal{K}_mKrylov部分空間mm は次元、span\text{span} は線形結合で張られる空間です。Krylov系のソルバーは、解を xmx0+Km\mathbf{x}_m \in \mathbf{x}_0 + \mathcal{K}_m の中で探しつつ、残りの残差 rm\mathbf{r}_m がある最適性条件を満たすように決定します。

解を部分空間に限定することには2つの利点があります。第一に、行列とベクトルの積以外の演算が必要ないため、疎行列構造をそのまま活用できます。第二に、理論的にその部分空間内で最適な解を抽出できます。CGはAノルム誤差を、GMRESは2ノルム残差を、Krylov空間内で正確に最小化します。

4つのソルバーの特性比較#

ソルバー要求される行列メモリ長所短所
Jacobi対角優位を推奨極めて低い並列化が容易、実装が10行程度遅い(スペクトル半径 1O(1/N2)1-O(1/N^2)
Gauss-Seidel任意極めて低いJacobiの約2倍速い本質的に逐次処理、GPUに不向き
CGSPD必須ベクトル4本SPDに対して最適、3項漸化式で軽量非対称行列には不可
BiCGSTAB任意ベクトル7本非対称でもOK、転置行列が不要収束が不安定な場合がある、break-downの危険

圧力ポアソン方程式 2p=f-\nabla^2 p = f はSPDであるため、CGが定石です。一方、対流と拡散が混ざった運動量方程式は非対称になるため、BiCGSTABまたはGMRESが選択されます。OpenFOAMの PCG/PBiCGStab や、SU2の BCGSTAB は、まさにこの役割分担に従っています。

Pythonで見る収束速度#

1Dポアソン問題を例に挙げましょう。 u=1-u'' = 1、境界条件 u(0)=u(1)=0u(0)=u(1)=0 を有限差分で離散化すると、三対角行列が得られます。Jacobi法とCG法を直接比較してみましょう。

import numpy as np
 
def build_poisson_1d(n):
    A = np.zeros((n, n))
    for i in range(n):
        A[i, i] = 2.0
        if i > 0:     A[i, i - 1] = -1.0
        if i < n - 1: A[i, i + 1] = -1.0
    return A, np.ones(n)
 
def jacobi_sweep(A, b, tol=1e-10, max_iter=2000):
    D  = np.diag(A)
    LU = A - np.diag(D)
    x  = np.zeros_like(b)
    b0 = np.linalg.norm(b)
    history = []
    for _ in range(max_iter):
        x = (b - LU @ x) / D
        res = np.linalg.norm(b - A @ x) / b0
        history.append(res)
        if res < tol:
            break
    return x, history
 
def cg_sweep(A, b, tol=1e-10, max_iter=2000):
    x  = np.zeros_like(b)
    r  = b - A @ x
    p  = r.copy()
    rr = r @ r
    b0 = np.linalg.norm(b)
    history = [np.sqrt(rr) / b0]
    for _ in range(max_iter):
        Ap    = A @ p
        alpha = rr / (p @ Ap)
        x    += alpha * p
        r    -= alpha * Ap
        rr_new = r @ r
        history.append(np.sqrt(rr_new) / b0)
        if history[-1] < tol:
            break
        p  = r + (rr_new / rr) * p
        rr = rr_new
    return x, history
 
A, b = build_poisson_1d(n=60)
_, hist_j  = jacobi_sweep(A, b)
_, hist_cg = cg_sweep(A, b)
print(f"Jacobi: {len(hist_j):4d}回, 最終残差 {hist_j[-1]:.2e}")
print(f"CG:     {len(hist_cg):4d}回, 最終残差 {hist_cg[-1]:.2e}")

結果は一目瞭然です。

Jacobi: 2000回, 最終残差 2.11e-02
CG:       60回, 最終残差 1.87e-13

Jacobi法が2000回反復しても 10210^{-2} に届かない一方で、CG法は N=60N=60 回でマシン精度(machine epsilon)に到達します。理論上、CG法は最大 NNで厳密解に到達します(浮動小数点誤差を無視した場合)。SPD行列に対して、CG法は魔法のような力を発揮します。

収束の競争を実際に見てみよう#

以下のシミュレーションで、直接操作してみましょう。スライダーで格子サイズ NN と最大反復回数を変更すると、4つのソルバーの残差曲線が対数スケールで再描画されます。

NN を80まで大きくすると、Jacobi法とGauss-Seidel法の曲線はほぼ水平になります。一方、CG法は階段状に下がり、NN 付近で急激に収束します。BiCGSTABは変動しながら下降しますが、この「ジグザグ」はbreak-down(分子または分母が0に爆発し、数値的に破綻すること)のリスクを示す視覚的な信号です。実戦でBiCGSTABが突然発散した場合は、前処理(preconditioner)を強化するか、GMRESに切り替えるのが定石です。

実務ソルバー選択ガイド#

圧力ポアソン方程式、拡散のみの熱伝導: CG + ILU(0)前処理。対称性が保証されているなら、他の選択肢は計算リソースの無駄です。

非対称な運動量方程式、対流-拡散混合: BiCGSTABが基本です。収束が不安定だったり停滞したりする場合は、GMRES(mm)に切り替えます。 mm はリスタート周期であり、メモリと速度のトレードオフを決定します。業界の慣行では m=30m=30 前後です。

並列CFD: Gauss-Seidel法は逐次的な性質のため、GPUでは力を発揮できません。ここ10年の標準は、Jacobiベースの平滑化(smoother)と代数マルチグリッド(AMG)の組み合わせです。

SU2のLU-SGS: SU2には、Krylov系の他にLU-SGS(Lower-Upper Symmetric Gauss-Seidel)という定常反復法も搭載されています。圧縮性流体において暗解法(implicit)の時間前進と組み合わせる際、メモリ使用量を大幅に抑えられます。ただし、Krylov系ほど頑健ではありません。

Tip: 実務では「ソルバーの選択」よりも「前処理の選択」の方が、収束速度をはるかに大きく左右します。素のCGを使うよりも、PCG(Preconditioned CG) + AMGの組み合わせの方が100倍速いことも珍しくありません。

3行まとめ#

  • Jacobi法とGauss-Seidel法は単純ですが、実際のCFD問題での収束率は 1O(1/N2)1-O(1/N^2) と低調です。問題が大きくなるほど反復回数が2乗で増加します。
  • Krylov系(CG、BiCGSTAB、GMRES)は行列ベクトル積のみで、 NN 回以内にマシン精度に到達します。SPDにはCG、非対称にはBiCGSTABまたはGMRESが実務上のデフォルトです。
  • ソルバーよりも前処理が重要です。ILU(0)、AMG、Jacobiスムーザー、これら3つの組み合わせが10倍から100倍の速度差を生みます。

下のκスライダで各ソルバの限界がひと目で見える。

조건수 κ를 키우면 (그림에서 오른쪽으로) Jacobi는 거의 멈추고 CG/GMRES는 √κ 한계를 따른다. BiCGSTAB는 비대칭에 강하지만 잔차가 출렁인다.

役に立ったらシェアしてください。