Juliaのpttrs!:バンド行列や疎行列への応用

2025-03-21

LinearAlgebra.LAPACK.pttrs!() は、LAPACK (Linear Algebra PACKage) のルーチンである pttrs を Julia から呼び出すための関数です。 この関数は、対称三重対角行列 (symmetric tridiagonal matrix) を係数とする線形方程式系を解くために使用されます。 特に、その中でも正定値 (positive definite) な対称三重対角行列の場合に特によく用いられます。

関数の役割

pttrs!() は、与えられた対称三重対角行列 A とベクトル b に対して、線形方程式系 Ax = b の解 x を計算します。 この関数は、A のLU分解(正確には、LDLᵀ分解)を事前に計算しておく必要があります。 pttrs!() は、その分解結果を用いて解を効率的に計算します。 ! (エクスクラメーションマーク) が関数名に含まれていることからわかるように、この関数は破壊的 (in-place) な操作を行います。つまり、入力のベクトル b は解 x で上書きされます。

引数

pttrs!() の主な引数は以下の通りです。

  • ldb: b の leading dimension (通常は n 以上)。
  • b: 右辺ベクトル b を格納した行列。 この行列は、関数実行後に解 x で上書きされます。n x nrhs のサイズである必要があります。
  • e: 行列 A の上または下の副対角成分を格納したベクトル。 UPLO が 'U' の場合は上副対角成分、'L' の場合は下副対角成分を格納します。
  • d: 行列 A の対角成分を格納したベクトル。
  • nrhs: 右辺ベクトル b の数 (つまり、解くべき線形方程式系の数) を表します。 通常は1ですが、複数の右辺ベクトルに対して同時に解を求めることも可能です。
  • n: 行列 A のサイズ (n x n) を表します。
  • UPLO: 行列 A の上三角部分 (U) または下三角部分 (L) がどのように格納されているかを指定します。 'U' (Upper) または 'L' (Lower) のいずれかの文字を指定します。
using LinearAlgebra

n = 5  # 行列のサイズ
d = [2.0, 3.0, 4.0, 5.0, 6.0]  # 対角成分
e = [1.0, 1.0, 1.0, 1.0]  # 副対角成分
b = [1.0, 2.0, 3.0, 4.0, 5.0]  # 右辺ベクトル

# 正定値対称三重対角行列 A を d と e から構成 (明示的な A の生成は不要)

# pttrs! を呼び出して解 x を計算。b が x で上書きされる。
pttrs!('U', n, 1, d, e, reshape(b, (n,1)), n) # bをn x 1の行列にreshape

x = b # 解 x

println("解 x: ", x)
  • 非正定値の対称三重対角行列の場合には、別のルーチン (例えば、sytrfsytrs) を使う必要があります。
  • pttrs!() は、行列 A の分解を内部で行いません。分解は事前に別の関数 (例えば、ldl!) を使って行う必要があります。正定値対称三重対角行列の場合、ldl! を使うことでLDLᵀ分解を効率的に計算できます。
  • pttrs!() を使う前に、de を適切に用意する必要があります。これらは対称三重対角行列の対角成分と副対角成分を表します。


引数の型やサイズの不一致

  • トラブルシューティング
    • 引数の型 (Float64 など) とサイズを再度確認してください。
    • Julia の型システムを利用して、変数の型とサイズを明示的に指定すると、エラーを早期に発見できます。
    • size() 関数を使って、配列の次元を確認しましょう。
    • 特に bn x nrhs の行列である必要があり、reshape などを用いて適切な形に変換しているか確認しましょう。
  • 原因
    n, nrhs, d, e, b のサイズが正しくない、あるいは型が一致していない場合に発生します。 例えば、d の長さが n でない、b のサイズが n x nrhs でない、e の長さが n-1 でない、UPLO が 'U' または 'L' 以外である、などが考えられます。
  • エラー例
    ArgumentError: matrix dimensions are inconsistent

特異な行列

  • トラブルシューティング
    • 行列 A の固有値を計算し、正定値であるかどうかを確認してください (正定値なら全ての固有値が正)。
    • ldl! による LDLᵀ 分解が失敗する場合、行列が正定値でない可能性があります。
    • もし行列が正定値でない場合は、pttrs!() は使用できません。他の解法 (例えば、sytrfsytrs を組み合わせる) を検討してください。
  • 原因
    行列 A が特異 (singular) または正定値でない場合、pttrs!() は正常に動作しません。 特に、正定値対称三重対角行列を期待している場合に、そうでない行列を与えると問題が発生します。
  • エラー例
    (必ずしもエラーメッセージが出るとは限らないが、計算結果がおかしい、あるいは NaNInf が含まれる)

UPLO の指定ミス

  • トラブルシューティング
    • UPLO の指定が正しいか、de が実際に意図した上三角部分または下三角部分を表しているか確認してください。
    • 図を描くなどして、行列の構造と UPLO の関係を整理すると間違いを防ぎやすくなります。
  • 原因
    UPLO ('U' または 'L') の指定が、実際に de が表している上三角部分または下三角部分と一致していない場合に発生します。
  • エラー例
    (計算結果がおかしい)

LAPACK のエラー

  • 原因
    LAPACK ルーチン自体でエラーが発生した場合。 これは通常、上記の引数の問題が原因ですが、稀に LAPACK のバグや Julia の LAPACK インターフェースの不具合が原因となることもあります。
  • エラー例
    (LAPACK 由来のエラーメッセージ)

破壊的変更

  • トラブルシューティング
    • pttrs!() を呼び出す前に、b のコピーを作成しておくと、元の b を保持できます。 例えば、b_copy = copy(b) のようにします。
  • 原因
    pttrs!() は破壊的な関数であり、入力ベクトル b が解 x で上書きされます。 このことを理解せずにいると、意図しない結果になることがあります。
  • エラー例
    (意図しない結果)
  • Julia のドキュメントやコミュニティを活用する
    Julia のドキュメントやコミュニティフォーラムには、役立つ情報がたくさんあります。
  • @assert を活用する
    コード中で前提条件が満たされているかを @assert でチェックすると、エラーを早期に発見できます。
  • コードを小さくして試す
    問題を切り分けるために、最小限のコードでエラーを再現させると、原因を特定しやすくなります。
  • エラーメッセージをよく読む
    エラーメッセージには、問題の原因に関するヒントが含まれていることが多いです。


基本的な使用例

using LinearAlgebra

n = 4  # 行列サイズ
d = [2.0, 3.0, 4.0, 5.0]  # 対角成分
e = [1.0, 1.0, 1.0]  # 副対角成分 (上)
b = [1.0, 2.0, 3.0, 4.0]  # 右辺ベクトル

# bを n x 1 の行列にreshape (pttrs! は行列を要求するため)
b_reshaped = reshape(b, (n, 1))

# pttrs! を使って解 x を計算 (b_reshaped が x で上書きされる)
pttrs!('U', n, 1, d, e, b_reshaped, n)

x = vec(b_reshaped) # 解ベクトルを取り出す (vec()でベクトル化)

println("解 x: ", x)

この例では、サイズ n の正定値対称三重対角行列 A (対角成分 d、上副対角成分 e で定義) と右辺ベクトル b が与えられ、pttrs!() を使って線形方程式系 Ax = b の解 x を求めています。 bn x 1 の行列に reshape されていることに注意してください。pttrs!b を解で上書きするため、必要であれば事前に copy(b) などでコピーを取っておきましょう。 最後に、vec() を使って結果の行列をベクトルに変換しています。

複数の右辺ベクトル

using LinearAlgebra

n = 3
d = [2.0, 3.0, 4.0]
e = [1.0, 1.0]
B = [1.0 2.0; 3.0 4.0; 5.0 6.0] # 複数の右辺ベクトルを持つ行列

nrhs = size(B, 2) # 右辺ベクトルの数

# B を解で上書きするため、コピーを取っておく (必要であれば)
B_copy = copy(B)

pttrs!('U', n, nrhs, d, e, B, n)

X = B # 解行列

println("解行列 X: ", X)

この例では、複数の右辺ベクトルを持つ行列 B (サイズ n x nrhs) に対して、pttrs!() を使って複数の線形方程式系を同時に解いています。 nrhs は右辺ベクトルの数です。

LDLᵀ 分解と組み合わせる

using LinearAlgebra

n = 4
d = [2.0, 3.0, 4.0, 5.0]
e = [1.0, 1.0, 1.0]
b = [1.0, 2.0, 3.0, 4.0]

# LDLᵀ 分解を計算 (正定値対称三重対角行列の場合)
d_factored = copy(d) # d は分解後上書きされるのでコピー
e_factored = copy(e) # e も同様
ldl!('U', n, d_factored, e_factored)

b_reshaped = reshape(b, (n, 1))
pttrs!('U', n, 1, d_factored, e_factored, b_reshaped, n) # 分解後の d, e を使う

x = vec(b_reshaped)

println("解 x: ", x)

この例では、ldl!() を使って事前に LDLᵀ 分解を計算し、その結果を pttrs!() に渡しています。 正定値対称三重対角行列の場合、ldl!() を使うことで効率的に分解を計算できます。 ldl!de を上書きするので、事前にコピーを取っておく必要があります。

using LinearAlgebra

n = 3
d = [1.0, 2.0, 3.0]
e = [1.0, 1.0]
b = [1.0, 2.0, 3.0]

try
    pttrs!('U', n, 1, d, e, reshape(b, (n,1)), n)
    x = vec(reshape(b, (n,1)))
    println("解 x: ", x)
catch err
    println("エラーが発生しました: ", err)
end


一般的な線形方程式系 (三重対角行列に限らない)

  • \ (バックスラッシュ演算子)
    Julia では、\ 演算子を使って線形方程式系 Ax = b の解 x を直接求めることができます。 これは、A が三重対角行列でなくても使用可能です。
A = [2.0 -1.0 0.0; -1.0 2.0 -1.0; 0.0 -1.0 2.0] # 三重対角行列の例
b = [1.0, 2.0, 3.0]
x = A \ b
println("解 x: ", x)

\ 演算子は、内部で適切な解法 (LU分解、QR分解など) を自動的に選択してくれます。 ただし、三重対角行列の場合、pttrs!() ほど効率的ではありません。

  • lu! (LU分解)
    A が三重対角行列でなくても、LU分解を使って解を求めることができます。
A = [2.0 -1.0 0.0; -1.0 2.0 -1.0; 0.0 -1.0 2.0]
b = [1.0, 2.0, 3.0]
lu_fact = lu!(A)
x = lu_fact \ b
println("解 x: ", x)

lu!()A をLU分解し、その結果を使って \ 演算子で解を計算します。

正定値対称行列 (三重対角行列に限らない)

  • cholesky! (コレスキー分解)
    A が正定値対称行列の場合、コレスキー分解を使うことができます。
A = [4.0 2.0; 2.0 5.0] # 正定値対称行列の例
b = [1.0, 2.0]
chol_fact = cholesky!(A)
x = chol_fact \ b
println("解 x: ", x)

コレスキー分解は、正定値対称行列に対して効率的な解法です。

バンド行列 (三重対角行列を含む)

  • banded! (バンド行列)
    A がバンド行列 (対角成分から一定の幅の中にのみ非ゼロ要素が存在する行列) の場合、banded!() を使って効率的に解くことができます。 三重対角行列はバンド幅が1のバンド行列です。
using BandedMatrices

n = 4
A = BandedMatrix(-1 => [1.0, 1.0, 1.0], 0 => [2.0, 3.0, 4.0, 5.0], 1 => [1.0, 1.0, 1.0]) # 三重対角行列の例
b = [1.0, 2.0, 3.0, 4.0]
x = A \ b
println("解 x: ", x)

BandedMatrices パッケージを使うことで、バンド行列を効率的に扱うことができます。

反復解法

  • gmres (一般化最小残差法)
    大規模な線形方程式系の場合、反復解法 (例えば、GMRES法) が有効です。
using IterativeSolvers

A = [2.0 -1.0 0.0; -1.0 2.0 -1.0; 0.0 -1.0 2.0] # 三重対角行列の例 (大規模な場合を想定)
b = [1.0, 2.0, 3.0]
x, = gmres(A, b) # gmres はタプルを返すので x, = のように書く
println("解 x: ", x)

IterativeSolvers パッケージには、様々な反復解法が用意されています。

  • SparseMatrixCSC (圧縮行形式)
    A が大規模な疎行列 (非ゼロ要素が少ない行列) の場合、SparseMatrixCSC 形式で格納することでメモリ使用量を削減し、計算を高速化できます。
using SparseArrays

n = 1000 # 大規模な例
# 疎行列 A を作成 (ここでは例として対角成分と副対角成分に 1 が入る三重対角行列)
I = [1:n; 2:n; 1:n-1]
J = [1:n; 1:n-1; 2:n]
V = [fill(2.0, n); fill(-1.0, n-1); fill(-1.0, n-1)]
A = sparse(I, J, V)
b = rand(n)
x = A \ b
println("解 x: ", x)