Juliaで帯行列を極める:BLAS.gbmv()と代替手法の比較

2025-05-27

gbmv() の基本的な機能

この関数は、帯行列(band matrix)とベクトルの積を計算し、その結果を別のベクトルに加算または上書きします。一般的な形式は以下のようになります。

y:=αAx+βy

または

ここで:

  • AT は A の転置
  • α と β はスカラー値
  • y は更新される出力ベクトル
  • x は入力ベクトル
  • A は帯行列(band matrix)

gbmv! (感嘆符付き) は結果を y に直接書き込み(インプレース操作)ますが、gbmv (感嘆符なし) は新しいベクトルを返します。

帯行列とは?

帯行列とは、主対角線から上下に特定の範囲(バンド幅)にしか非ゼロ要素を持たない行列のことです。それ以外の要素は全てゼロです。

  • ku は上対角線(super-diagonals)の数を示します。
  • kl は下対角線(sub-diagonals)の数を示します。

例えば、kl=1, ku=1 の場合、三対角行列(Tridiagonal matrix)になります。

gbmv() の引数

LinearAlgebra.BLAS.gbmv(trans, m, kl, ku, alpha, A, x, beta, y)

  • y: 入力兼出力ベクトル y。
  • beta: スカラー β。
  • x: 入力ベクトル x。
  • A: 帯行列を表す配列。BLASの仕様に従って、特殊な形で格納されている必要があります。通常は、各列が帯の要素を持つように格納されます。
  • alpha: スカラー α。
  • ku: 上対角線の数。
  • kl: 下対角線の数。
  • m: 行列 A の行数(A は m×n 行列となりますが、この m は A の次元のうち、結果のベクトルの次元に影響を与える方を指します)。
  • trans: 行列 A を転置するかどうかを指定する文字。
    • 'N' または 'n': A をそのまま使用します(y:=αAx+βy)。
    • 'T' または 't': A を転置して使用します(y:=αATx+βy)。
    • 'C' または 'c': A を共役転置して使用します(複素数の場合、y:=αAHx+βy)。

なぜ gbmv() を使うのか?

  • メモリ効率: 帯行列の特殊な構造を利用することで、フル行列として扱うよりも少ないメモリで計算できます。
  • パフォーマンス: BLAS関数は通常、CやFortranで最適化された実装(OpenBLAS, MKLなど)が利用されており、非常に高速です。特に大規模な行列演算において、Juliaの一般的な行列演算よりも高速になる可能性があります。
  • 高レベルな関数との使い分け: Juliaでは、LinearAlgebraモジュールにmul!のような、より汎用的な行列・ベクトル積を計算する関数が用意されています。これらの関数は、内部的に最適なBLASルーチン(例えば、帯行列に対してはgbmv)を自動的に選択してくれるため、通常は直接BLAS.gbmv()を呼び出す必要はありません。特殊な行列型(Tridiagonalなど)を扱う場合、mul!はそれらの構造を考慮して効率的な処理を行います。
  • 密行列としてのAの格納: gbmv()は、帯行列をBLASが要求する特定の密なストレージ形式で提供する必要があります。これは、kl + ku + 1 行を持つ行列として帯要素を格納するものです。JuliaのTridiagonalのような特殊な行列型を直接渡すことはできない場合があります。そのような場合は、Array(Tridiagonal_matrix)のように密行列に変換するか、mul! のような高レベルな関数を利用することが推奨されます。


MethodError (引数の型が一致しない)

最もよくあるエラーの一つです。gbmv()は特定の型の引数を期待しており、異なる型の引数を渡すとMethodErrorが発生します。

エラー例

julia> using LinearAlgebra
julia> A = rand(3, 5) # Float64行列
julia> x = rand(Float32, 5) # Float32ベクトル
julia> y = zeros(3)
julia> BLAS.gbmv!('N', 3, 1, 1, 1.0, A, x, 0.0, y)
MethodError: no method matching gbmv!(::Char, ::Int64, ::Int64, ::Int64, ::Float64, ::Matrix{Float64}, ::Vector{Float32}, ::Float64, ::Vector{Float64})
Closest candidates are:
  gbmv!(trans::Char, m::Integer, kl::Integer, ku::Integer, alpha::Real, A::AbstractMatrix{T}, x::AbstractVector{T}, beta::Real, y::AbstractVector{T}) where T<:LinearAlgebra.BlasFloat

原因

gbmv!の全ての数値引数(alpha, A, x, beta, y)は、同じBlasFloat型(Float32, Float64, ComplexF32, ComplexF64のいずれか)である必要があります。上記の例では、AyFloat64xFloat32であるため、型が一致していません。

トラブルシューティング

全ての関連する行列およびベクトルの要素型を統一します。

julia> using LinearAlgebra
julia> A = rand(Float64, 3, 5) # Float64に統一
julia> x = rand(Float64, 5) # Float64に統一
julia> y = zeros(Float64, 3) # Float64に統一
julia> BLAS.gbmv!('N', 3, 1, 1, 1.0, A, x, 0.0, y)

行列 A の形状とバンドパラメータ kl, ku の不一致

gbmv()は、行列Aが特定の帯行列の形式で格納されていることを期待します。kl(下対角線の数)とku(上対角線の数)は、行列Aの行数と密接に関連しています。

エラー例

帯行列ではない普通の密行列をAとして渡し、不適切なkl, kuを指定した場合。

julia> using LinearAlgebra
julia> A = rand(5, 5) # 5x5の密行列
julia> x = rand(5)
julia> y = zeros(5)
julia> # m=5, kl=1, ku=1 と指定すると、Aは3xNの帯行列として扱われることを期待する
julia> BLAS.gbmv!('N', 5, 1, 1, 1.0, A, x, 0.0, y)
# エラーは発生しないかもしれないが、結果が不正になるか、
# アクセス違反(Segmentation Fault)が発生する可能性がある。

原因

BLAS.gbmv関数に渡す行列Aは、帯行列の非ゼロ要素を密に格納した形式である必要があります。これは、通常のJuliaのMatrix型とは異なる特殊なレイアウトです。具体的には、kl + ku + 1行、n列(nは元の帯行列の列数)の行列として格納されます。

例えば、kl=1, ku=1(三対角行列)の場合、Aは3行の行列として要素を格納する必要があります。

トラブルシューティング

  • 通常のJulia行列型ではなく、mul!を使用する
    • ほとんどの場合、直接BLAS.gbmv()を呼び出すのではなく、JuliaのLinearAlgebra.mul!関数を使用することを強く推奨します。
    • mul!は、Tridiagonal, Bidiagonalなどの特殊な行列型を自動的に認識し、内部で最適なBLASルーチン(例えばgbmv)にディスパッチします。
    • これにより、ユーザーはBLASの特定の格納形式を意識する必要がなくなります。
  • BLASの帯行列格納形式を理解する
    • BLASのGBMVルーチンは、帯行列を次のように格納します。
      • 主対角線は ku + 1 行目
      • 上対角線は 1 行目から ku 行目
      • 下対角線は ku + 2 行目から ku + kl + 1 行目
    • これらをAの列方向に格納します。

例(mul!を使用する場合):

julia> using LinearAlgebra
julia> T = Tridiagonal([1.0,2.0], [3.0,4.0,5.0], [6.0,7.0]) # 三対角行列
5×5 Tridiagonal{Float64, Vector{Float64}}:
 3.0   6.0    ⋅     ⋅     ⋅
 1.0   4.0   7.0    ⋅     ⋅
  ⋅    2.0   5.0   ⋅      ⋅
  ⋅     ⋅    ⋅     ⋅     ⋅
  ⋅     ⋅    ⋅     ⋅     ⋅

julia> x = rand(5)
julia> y = zeros(5)
julia> mul!(y, T, x) # これが最も推奨される方法

もしどうしてもBLAS.gbmv!を直接使用したい場合は、Tridiagonalのような特殊行列をBLASが期待する密な帯行列形式に変換する必要があります。これは複雑でエラーの元になりやすいため、通常は推奨されません。

次元不一致 (Vectors x, y のサイズ)

ベクトルxyのサイズが行列Aの次元と一致しない場合に発生します。

エラー例

julia> using LinearAlgebra
julia> A_banded = rand(3, 5) # kl=1, ku=1 の帯行列として扱う
julia> m = 5 # 元の行列の行数 (gbmvのm引数)
julia> kl = 1
julia> ku = 1
julia> x = rand(4) # サイズが合わない
julia> y = zeros(m)
julia> BLAS.gbmv!('N', m, kl, ku, 1.0, A_banded, x, 0.0, y)
ERROR: DimensionMismatch("...") # 詳細なエラーメッセージは環境に依存する

原因

  • trans = 'T' または 'C' の場合: xの長さは元の行列の行数(m)、yの長さは元の行列の列数(size(A_banded, 2))と一致する必要があります。
  • trans = 'N' の場合: xの長さは元の行列の列数(size(A_banded, 2))、yの長さは元の行列の行数(m)と一致する必要があります。

トラブルシューティング

各ベクトルのサイズを、行列の転置オプションとm引数に基づいて正しく設定します。

julia> using LinearAlgebra
julia> A_banded = rand(3, 5) # 例として、3行5列の密なAをバンド行列として使用
julia> m = 5 # 元の行列の行数
julia> n_original = 5 # 元の行列の列数 (A_bandedの2次元目のサイズ)
julia> kl = 1
julia> ku = 1

julia> # trans = 'N' の場合
julia> x_N = rand(n_original)
julia> y_N = zeros(m)
julia> BLAS.gbmv!('N', m, kl, ku, 1.0, A_banded, x_N, 0.0, y_N)

julia> # trans = 'T' の場合
julia> x_T = rand(m)
julia> y_T = zeros(n_original)
julia> BLAS.gbmv!('T', m, kl, ku, 1.0, A_banded, x_T, 0.0, y_T)

A が読み取り専用メモリの場合 (ReadOnlyMemoryError)

ごく稀に、Aが読み取り専用メモリに割り当てられている場合に発生することがあります。

エラー例

BLAS実装によっては、内部でAのコピーを作成しようとして失敗したり、特定の条件下でReadOnlyMemoryErrorが発生する可能性があります。これは大規模な計算でより顕著になることがあります。

原因

OpenBLASなどのBLASライブラリが、内部的な処理のために一時バッファを確保しようとしたり、データへの書き込み権限を必要とする場合に、メモリの権限の問題で発生することがあります。

トラブルシューティング

  • 可能な限り、やはりmul!などの高レベルな関数を使用し、Juliaにメモリ管理を任せるのが最善です。
  • このエラーは通常、BLASライブラリのバグや特定の設定の問題に起因することが多いため、Juliaのバージョンや使用しているBLASライブラリ(OpenBLAS, MKLなど)のバージョンを更新することを検討します。
  • Aが明示的に変更不可能(Immutable)な型でないことを確認します。

これは深刻なエラーで、通常は不正なメモリアクセスが原因で発生します。デバッグが困難な場合があります。

エラー例

Juliaプロセスが突然クラッシュし、「Segmentation fault」のようなメッセージが表示されることがあります。

原因

  • BLASライブラリの問題: まれに、使用しているBLASライブラリ自体のバグが原因であることもあります。
  • メモリ不足: 非常に大きな行列を扱う際に、システムメモリが不足すると発生することがあります。
  • 引数の不一致: m, kl, ku, size(A)xyのサイズが整合性が取れていない場合、BLASルーチンが配列の境界外にアクセスしようとしてクラッシュすることがあります。特に、AがBLASの期待する帯行列の格納形式でなかった場合に発生しやすいです。
  • BLASライブラリの変更/更新: LinearAlgebra.BLAS.vendor()で現在使用しているBLASライブラリを確認できます。もしOpenBLASを使用している場合、MKL.jlパッケージをインストールしてIntel MKLに切り替えてみることで問題が解決する場合があります。また、JuliaやBLASライブラリのバージョンを最新に保つことも重要です。
  • メモリ使用量の監視: 大規模な計算の場合は、システムのメモリ使用量を監視し、不足していないか確認します。
  • mul!の利用: 繰り返しになりますが、BLAS.gbmv!を直接呼び出すのではなく、高レベルなmul!関数を使用することで、これらの低レベルな問題の多くは回避されます。
  • 引数の厳密な確認: 全ての引数がBLASの期待する通りであることを二重三重に確認します。特にm, kl, kuAの実際の次元、xyの長さをチェックします。


重要事項

  • 帯行列の特殊な格納形式: BLAS.gbmv()に渡す帯行列Aは、BLASが期待する特定の密な形式で格納されている必要があります。これは通常のJuliaのMatrix型とは異なります。
  • 通常の用途では推奨されません: ほとんどのケースで、LinearAlgebra.mul!()関数を使用することを強く推奨します。mul!()は、内部的に適切なBLASルーチン(帯行列の場合はgbmv)を自動的に選択し、引数の設定ミスによるエラーのリスクを大幅に減らします。

BLAS.gbmv() の基本的な使い方

まず、BLAS.gbmv()が期待する帯行列の格納形式を理解する必要があります。BLASは、帯行列の非ゼロ要素を、kl + ku + 1行の密な行列として格納します。

  • ku + 2 から kl + ku + 1: 下対角線が格納される行インデックス
  • 1 から ku: 上対角線が格納される行インデックス
  • ku + 1: 主対角線が格納される行インデックス
  • ku: 上対角線の数
  • kl: 下対角線の数

例:三対角行列の場合 (kl=1, ku=1)

元の 5×5 の三対角行列 T: T=​t11​t21​000​t12​t22​t32​00​0t23​t33​t43​0​00t34​t44​t54​​000t45​t55​​​

BLASのgbmvが期待する格納形式の行列A_banded ($ (1+1+1) \times 5 = 3 \times 5 $ 行列):

A_banded = [
    0      t_12   t_23   t_34   t_45   <- 上対角線 (ku番目の行)
    t_11   t_22   t_33   t_44   t_55   <- 主対角線 (ku+1番目の行)
    t_21   t_32   t_43   t_54   0      <- 下対角線 (ku+kl番目の行)
]

0 はパディングです。

using LinearAlgebra

# === 帯行列のデータ作成 ===
# 例: 5x5の三対角行列 (kl=1, ku=1)
# 主対角線: [10, 20, 30, 40, 50]
# 上対角線: [1, 2, 3, 4]
# 下対角線: [5, 6, 7, 8]

# BLASのgbmvが期待する形式の行列A_bandedを作成
# 行数: kl + ku + 1 = 1 + 1 + 1 = 3
# 列数: 元の行列の列数 = 5
A_banded = zeros(Float64, 3, 5)

# 主対角線 (ku+1 = 2行目)
A_banded[2, 1] = 10.0
A_banded[2, 2] = 20.0
A_banded[2, 3] = 30.0
A_banded[2, 4] = 40.0
A_banded[2, 5] = 50.0

# 上対角線 (ku = 1行目)
A_banded[1, 2] = 1.0 # t_12
A_banded[1, 3] = 2.0 # t_23
A_banded[1, 4] = 3.0 # t_34
A_banded[1, 5] = 4.0 # t_45

# 下対角線 (ku+kl = 3行目)
A_banded[3, 1] = 5.0 # t_21
A_banded[3, 2] = 6.0 # t_32
A_banded[3, 3] = 7.0 # t_43
A_banded[3, 4] = 8.0 # t_54

println("BLAS形式の帯行列 A_banded:")
display(A_banded)
# A_banded = [
#     0.0   1.0   2.0   3.0   4.0
#    10.0  20.0  30.0  40.0  50.0
#     5.0   6.0   7.0   8.0   0.0
# ]

# === ベクトルx と y の準備 ===
x = rand(Float64, 5) # 入力ベクトル
y = zeros(Float64, 5) # 出力ベクトル (初期値0)

println("\n入力ベクトル x:")
display(x)

# === BLAS.gbmv! の呼び出し ===
# y := alpha * A * x + beta * y
# 今回は y := A * x を計算するので、alpha=1.0, beta=0.0
trans = 'N' # 転置しない
m = 5       # 元の行列の行数
kl = 1      # 下対角線の数
ku = 1      # 上対角線の数
alpha = 1.0
beta = 0.0

println("\nBLAS.gbmv! を使用して y = A * x を計算...")
BLAS.gbmv!(trans, m, kl, ku, alpha, A_banded, x, beta, y)

println("\n結果ベクトル y:")
display(y)

# === 結果の検証 (おまけ) ===
# JuliaのTridiagonal型を使って同じ計算を行い、結果を比較
diag_main = [10.0, 20.0, 30.0, 40.0, 50.0]
diag_upper = [1.0, 2.0, 3.0, 4.0]
diag_lower = [5.0, 6.0, 7.0, 8.0]
T_matrix = Tridiagonal(diag_lower, diag_main, diag_upper)

println("\nJuliaのTridiagonal型:")
display(T_matrix)

y_validate = T_matrix * x
println("\nTridiagonal型で計算した検証結果 y_validate:")
display(y_validate)

println("\n両方の結果がほぼ同じか: $(y ≈ y_validate)")

trans 引数の使用例

trans引数で行列の転置や共役転置を指定できます。

  • 'C' または 'c': y:=αAHx+βy (共役転置、複素数の場合)
  • 'T' または 't': y:=αATx+βy
  • 'N' または 'n': y:=αAx+βy
using LinearAlgebra

# === 帯行列のデータ作成 (再掲) ===
# 5x5の三対角行列、BLAS形式
A_banded = zeros(Float64, 3, 5)
A_banded[2, :] = [10.0, 20.0, 30.0, 40.0, 50.0] # 主対角線
A_banded[1, 2:5] = [1.0, 2.0, 3.0, 4.0]        # 上対角線
A_banded[3, 1:4] = [5.0, 6.0, 7.0, 8.0]        # 下対角線

# === ベクトルx と y の準備 ===
x_orig = rand(Float64, 5) # 入力ベクトル
y_orig = zeros(Float64, 5) # 出力ベクトル

println("元の行列 A_banded:")
display(A_banded)
println("元の入力ベクトル x_orig:")
display(x_orig)

# === 'N' (転置なし) の場合 ===
# y := A * x
y_N = zeros(Float64, 5)
BLAS.gbmv!('N', 5, 1, 1, 1.0, A_banded, x_orig, 0.0, y_N)
println("\n'N' (A * x) の結果 y_N:")
display(y_N)

# === 'T' (転置) の場合 ===
# y := A^T * x
# 注意: A^T の場合、x のサイズは元の行列の行数 m (ここでは5) に、
# y のサイズは元の行列の列数 n (ここでは5) になります。
# 今回は元の行列が正方なのでサイズは同じ。
x_T = rand(Float64, 5) # A^T の行数に合わせる
y_T = zeros(Float64, 5) # A^T の列数に合わせる
BLAS.gbmv!('T', 5, 1, 1, 1.0, A_banded, x_T, 0.0, y_T)
println("\n'T' (A^T * x) の結果 y_T:")
display(y_T)

# === 検証 (おまけ) ===
T_matrix = Tridiagonal([5.0, 6.0, 7.0, 8.0], [10.0, 20.0, 30.0, 40.0, 50.0], [1.0, 2.0, 3.0, 4.0])

println("\nTridiagonal型の元の行列:")
display(T_matrix)

println("\n検証: T_matrix * x_orig (Nの場合):")
display(T_matrix * x_orig)
println("一致: $(y_N ≈ T_matrix * x_orig)")

println("\n検証: T_matrix' * x_T (Tの場合):")
display(T_matrix' * x_T)
println("一致: $(y_T ≈ T_matrix' * x_T)")

alpha と beta の使用例

y:=αAx+βy の形で計算できることを示します。

using LinearAlgebra

# === 帯行列のデータ作成 (再掲) ===
# 5x5の三対角行列、BLAS形式
A_banded = zeros(Float64, 3, 5)
A_banded[2, :] = [10.0, 20.0, 30.0, 40.0, 50.0]
A_banded[1, 2:5] = [1.0, 2.0, 3.0, 4.0]
A_banded[3, 1:4] = [5.0, 6.0, 7.0, 8.0]

# === ベクトルx と y の準備 ===
x = rand(Float64, 5)
y_initial = rand(Float64, 5) # 初期値を持つy

println("A_banded:")
display(A_banded)
println("x:")
display(x)
println("y_initial:")
display(y_initial)

# === BLAS.gbmv! で y = 2.0 * A * x + 0.5 * y_initial を計算 ===
# y_result に y_initial のコピーを渡してインプレース計算
y_result = deepcopy(y_initial)

alpha = 2.0
beta = 0.5

println("\ny = 2.0 * A * x + 0.5 * y_initial を計算...")
BLAS.gbmv!('N', 5, 1, 1, alpha, A_banded, x, beta, y_result)

println("\n最終結果 y_result:")
display(y_result)

# === 検証 (おまけ) ===
T_matrix = Tridiagonal([5.0, 6.0, 7.0, 8.0], [10.0, 20.0, 30.0, 40.0, 50.0], [1.0, 2.0, 3.0, 4.0])

y_validate = alpha * (T_matrix * x) + beta * y_initial
println("\n検証結果 y_validate:")
display(y_validate)

println("\n両方の結果がほぼ同じか: $(y_result ≈ y_validate)")

前述の通り、通常はBLAS.gbmv()を直接呼び出すのではなく、より高レベルなmul!()関数を使用するべきです。mul!()は、Juliaの特殊な行列型(Tridiagonal, Bidiagonalなど)を効率的に扱うことができます。

using LinearAlgebra

# === Tridiagonal行列の作成 ===
# gbmvが扱う三対角行列に相当するJuliaの型
diag_main = [10.0, 20.0, 30.0, 40.0, 50.0]
diag_upper = [1.0, 2.0, 3.0, 4.0]
diag_lower = [5.0, 6.0, 7.0, 8.0]
T_matrix = Tridiagonal(diag_lower, diag_main, diag_upper)

println("Tridiagonal行列 T_matrix:")
display(T_matrix)

# === ベクトルx と y の準備 ===
x = rand(Float64, 5)
y = zeros(Float64, 5) # 出力ベクトル

println("\n入力ベクトル x:")
display(x)

# === mul! の呼び出し ===
# y := A * x を計算 (alpha=1.0, beta=0.0 を内部的に使用)
println("\nmul!(y, T_matrix, x) を使用して y = T_matrix * x を計算...")
mul!(y, T_matrix, x)

println("\n結果ベクトル y:")
display(y)

# === より一般的な mul! (alpha, beta を指定する場合) ===
# y := alpha * A * x + beta * y_initial
y_initial = rand(Float64, 5)
y_result_mul = deepcopy(y_initial)
alpha = 2.0
beta = 0.5

println("\nmul!(y_result_mul, T_matrix, x, alpha, beta) を使用して計算...")
mul!(y_result_mul, T_matrix, x, alpha, beta)

println("\n結果ベクトル y_result_mul:")
display(y_result_mul)

# === 検証 ===
y_validate_mul = alpha * (T_matrix * x) + beta * y_initial
println("\n検証結果 y_validate_mul:")
display(y_validate_mul)

println("\n両方の結果がほぼ同じか: $(y_result_mul ≈ y_validate_mul)")

これらの例からわかるように、BLAS.gbmv()を直接使用するには、帯行列の特殊な格納形式を理解し、手動でデータを準備する必要があります。これはエラーの元になりやすく、コードの複雑性を増します。

対照的に、LinearAlgebra.mul!()を使用すると、Juliaの組み込みの行列型(Tridiagonalなど)をそのまま渡すことができ、Juliaが内部で最適なBLASルーチン(gbmvを含む)にディスパッチしてくれます。これにより、より簡潔で安全なコードを書くことができます。



LinearAlgebra.mul!() を使用する(最も推奨される方法)

これが最も推奨される代替方法です。mul!()関数は、行列とベクトルの積を計算するためのJuliaの汎用的な関数で、内部的に最適なBLASルーチン(例えば、帯行列に対してはgbmv)を自動的に選択してくれます。

利点

  • 可読性
    コードがより簡潔で分かりやすくなります。
  • 汎用性
    密行列、疎行列、特殊な構造を持つ行列など、様々な行列型に対応しています。
  • 高速
    内部で最適化されたBLAS/LAPACKライブラリを利用するため、手動でBLAS.gbmv()を呼び出すのと同等の、あるいはそれ以上のパフォーマンスが得られます。
  • 安全
    BLASの特殊な行列格納形式を意識する必要がありません。Juliaの標準的な行列型(Matrix, Tridiagonal, Bidiagonalなど)をそのまま使えます。

コード例

using LinearAlgebra

# 例として三対角行列を作成 (gbmvが扱う帯行列の一種)
# JuliaのTridiagonal型は、その構造を内部で効率的に扱います。
diag_main  = [10.0, 20.0, 30.0, 40.0, 50.0]
diag_upper = [1.0, 2.0, 3.0, 4.0]
diag_lower = [5.0, 6.0, 7.0, 8.0]
A = Tridiagonal(diag_lower, diag_main, diag_upper)

x = rand(5) # 入力ベクトル
y = zeros(5) # 出力ベクトル (結果がここに格納される)

println("入力行列 A (Tridiagonal):")
display(A)
println("\n入力ベクトル x:")
display(x)

# y := A * x を計算 (mul!はインプレース操作)
mul!(y, A, x) # これが最も推奨される方法

println("\n結果ベクトル y (mul!による):")
display(y)

# さらに、alphaとbetaを指定することも可能 (y := alpha * A * x + beta * y_initial)
y_initial = rand(5)
y_result_alpha_beta = deepcopy(y_initial)
alpha = 2.0
beta = 0.5

mul!(y_result_alpha_beta, A, x, alpha, beta)

println("\n結果ベクトル y (mul!とalpha, betaによる):")
display(y_result_alpha_beta)

# 検証: A * x と同じ結果が得られることを確認
println("\nA * x の直接計算:")
display(A * x)

行列とベクトルの通常の乗算 * 演算子

Juliaの行列とベクトルの乗算には、* 演算子を直接使用できます。これも内部的に最適化されたBLASルーチンを呼び出すため、非常に効率的です。

利点

  • 新しい配列を返す
    計算結果を新しいベクトルとして返すため、既存の変数を変更したくない場合に便利です。
  • 効率的
    mul!()と同様に、内部で最適化されたBLAS/LAPACKルーチンを利用します。
  • 最もシンプル
    コードが非常に直感的で読みやすいです。

コード例

using LinearAlgebra

# Tridiagonal行列 A (上記と同じ)
diag_main  = [10.0, 20.0, 30.0, 40.0, 50.0]
diag_upper = [1.0, 2.0, 3.0, 4.0]
diag_lower = [5.0, 6.0, 7.0, 8.0]
A = Tridiagonal(diag_lower, diag_main, diag_upper)

x = rand(5) # 入力ベクトル

println("入力行列 A (Tridiagonal):")
display(A)
println("\n入力ベクトル x:")
display(x)

# y = A * x を計算 (新しいベクトルが返される)
y_new = A * x

println("\n結果ベクトル y_new (* 演算子による):")
display(y_new)

SparseArrays.jl (疎行列の場合)

もし行列が非常に疎(多くの要素がゼロ)で、かつ帯行列のように特殊な構造を持たない、またはその構造が非常に広範な場合、SparseArraysパッケージを使用することを検討できます。SparseArraysは、疎行列の操作に特化しており、メモリ効率と計算効率の両方で優れています。

利点

  • 柔軟性
    帯行列に限定されず、任意の疎行列を扱えます。
  • 計算効率
    ゼロ要素の計算をスキップするため、積の計算が高速になります。
  • メモリ効率
    ゼロ要素を格納しないため、大規模な疎行列でメモリ使用量を大幅に削減できます。
using SparseArrays
using LinearAlgebra # mul! や * 演算子のため

# 疎行列の作成例
# 通常の密行列から疎行列に変換するか、直接SparseMatrixCSCを作成します。
m, n = 100, 100
A_dense = rand(m, n)
# 多くの要素をゼロにする (疎にする)
for i in 1:m, j in 1:n
    if rand() > 0.98 # 98%の要素をゼロにする
        A_dense[i, j] = 0.0
    end
end
A_sparse = sparse(A_dense) # 密行列から疎行列へ変換

x = rand(n) # 入力ベクトル
y_sparse = zeros(m) # 出力ベクトル

println("疎行列 A_sparse:")
display(A_sparse) # 疎行列の表示は非ゼロ要素のみ
println("\n入力ベクトル x:")
display(x)

# mul! を使用した疎行列とベクトルの積
mul!(y_sparse, A_sparse, x)

println("\n結果ベクトル y_sparse (SparseArrays と mul! による):")
display(y_sparse)

# または直接 * 演算子を使用
y_sparse_new = A_sparse * x
println("\n結果ベクトル y_sparse_new (SparseArrays と * 演算子による):")
display(y_sparse_new)

# 密行列の計算結果と比較して検証
y_dense_validate = A_dense * x
println("\n密行列で計算した検証結果:")
display(y_dense_validate)
println("疎行列の結果と密行列の結果がほぼ同じか: $(y_sparse ≈ y_dense_validate)")
  • BLAS.gbmv()を直接使う場合
    ごくまれなケースで、上記の高レベルなメソッドでは達成できない特定のパフォーマンスチューニングが必要な場合のみに限定すべきです。その場合でも、BLASのメモリレイアウトや引数の詳細について深い理解が求められます。
  • 非常に疎な行列で、帯状の構造に限定されない場合
    SparseArraysパッケージの使用を検討しましょう。
  • 最も一般的なケース
    LinearAlgebra.mul!() または * 演算子を使いましょう。JuliaのTridiagonalBidiagonalなどの特殊な行列型を使用すれば、帯行列の特性を活かした高速な計算が自動的に行われます。