LinearAlgebra.LAPACK.hseqr!()

2025-05-16

まず、基本的な用語から説明します。

  • ! (bang/感嘆符)
    Juliaでは、関数名の最後に!が付いている場合、その関数が引数を**破壊的に変更する(in-place operation)**ことを示します。つまり、新しいオブジェクトを作成するのではなく、既存のオブジェクトの内容を直接書き換えます。
  • LAPACK (Linear Algebra PACKage)
    高性能な線形代数計算のためのFortranライブラリの集合体です。JuliaのLinearAlgebraモジュールは、内部的にこのLAPACKライブラリを呼び出して多くの線形代数操作を実行しています。
  • LinearAlgebra
    Juliaの標準ライブラリの一つで、線形代数に関する関数を提供します。
  • Julia
    高性能な数値計算に特化したプログラミング言語です。

hseqr!()の機能

hseqr!()は、Hessenberg行列のQR反復法による固有値・シューア分解を計算するためのLAPACKルーチン(DGEHQR/ZGEHQRなど)へのJuliaラッパーです。

具体的には、以下の処理を行います。

  1. Hessenberg行列
    hseqr!()は、入力された行列が既にHessenberg形式(準上三角行列)になっていることを前提としています。任意の行列Aの固有値・固有ベクトルを求める場合、通常はまずhessenberg()関数などを使ってAをHessenberg形式Hに変換します。
  2. QR反復法
    このHessenberg行列Hに対して、QR反復法と呼ばれるアルゴリズムを適用します。QR反復法は、行列を反復的にQR分解することで、最終的にシューア分解(Shur decomposition)された行列(対角成分に固有値が並ぶ準上三角行列)や固有ベクトルを求めます。
  3. シューア分解と固有値
    hseqr!()は、Hessenberg行列からシューア分解を計算します。シューア分解とは、A=QTQHのような分解で、Qはユニタリ行列(または直交行列)、Tは準上三角行列(シューア形式)です。このTの対角成分には、元の行列Aの固有値が並びます。
  4. 固有ベクトルの計算(オプション)
    compzという引数によって、シューアベクトル(Qの列ベクトル)を計算するかどうかを指定できます。これらのシューアベクトルは、必要に応じて元の行列の固有ベクトルに変換することができます。

なぜhseqr!()のような低レベル関数を使うのか?

通常、Juliaで固有値を計算する場合は、eigen()schur()のような高レベル関数を使います。これらの関数は、内部的にhseqr!()のようなLAPACKルーチンを呼び出しています。

しかし、以下のような場合にhseqr!()を直接使うことを検討するかもしれません。

  • パフォーマンスの追求
    極めてパフォーマンスが重要な場合、より細かい制御が可能な低レベル関数を直接呼び出すことで、わずかながら最適化できる可能性があります(ただし、多くの場合、高レベル関数が十分最適化されています)。
  • 特定の計算ステップを制御したい場合
    線形代数のアルゴリズムを詳細に理解し、特定の計算ステップをカスタマイズしたい場合に、低レベル関数へのアクセスが必要になることがあります。
  • Hessenberg行列が既にある場合
    ユーザーが既にHessenberg行列を持っている場合、改めて最初から固有値計算を行うよりも、Hessenberg行列から直接hseqr!()を使って効率的に計算したい場合。

hseqr!()はLAPACKのルーチンを直接呼び出すため、その引数はLAPACKの規約に準拠しています。典型的な引数には以下のようなものがあります(実際のJuliaのラッパーでは少し異なる場合もありますが、概念は同じです)。

  • Z::StridedMatrix: シューアベクトルを格納するための行列。compzの指定によって動作が変わります。
  • H::StridedMatrix: Hessenberg行列。この行列は破壊的に変更され、最終的にシューア形式になります。
  • ilo::Integer, ihi::Integer: 行列の有効な部分行列の範囲を指定します。これは通常、gebal(行列のバランス調整)ルーチンによって決定されます。
  • compz::Char: シューアベクトルZを計算するかどうかを指定します。
    • 'N': 計算しない。
    • 'I': Zを単位行列で初期化し、Hのシューアベクトルを返す。
    • 'V': 入力された行列Qに対して、QZを返す。
  • job::Char: シューア形式のみを計算するか、シューアベクトルも計算するかを指定します。
    • 'N': シューア形式のみ。
    • 'E': 固有値のみ。
    • 'S': シューア形式とシューアベクトル(Q)。


hseqr!()における一般的なエラーとトラブルシューティング

LAPACKException(info)

これは最も一般的なエラーで、LAPACKルーチンがエラーコードinfoを返した場合に発生します。infoの値によって原因が異なります。

  • info > 0 (正の値)

    • 原因
      これは、アルゴリズムが収束しなかった場合や、数値的な問題が発生した場合に起こります。hseqr!()の場合、通常はQR反復法が特定の反復回数内に収束しなかったことを意味します。infoの値が、収束に失敗したオフダイアゴナル要素のインデックスを示すこともあります。
    • トラブルシューティング
      • 入力行列の性質
        入力されたHessenberg行列が非常に悪条件である(ill-conditioned)場合や、数値的に不安定な性質を持つ場合に発生しやすいです。
        • 行列のスケールを調整してみる(例: 全体を定数倍する)。
        • 行列の成分が極端に大きい/小さい、またはNaNInfを含んでいないか確認する。
      • 高レベル関数を使う
        ほとんどの場合、eigen()schur()のような高レベル関数は、このような数値的な頑健性(robustness)を向上させるための追加の処理(例えば、バランス調整など)を内部的に行っています。hseqr!()を直接使うのではなく、これらの関数を使用することを検討してください。
      • アルゴリズムの変更
        hseqr!()はQR反復法を用いますが、場合によっては他の固有値アルゴリズム(例: 分割統治法など)の方が安定して収束することがあります。ただし、hseqr!()自体にはアルゴリズムを選択する引数はありません。
    • 原因
      infoの絶対値が、ルーチンに渡された引数の無効な番号を示します。例えば、info = -3であれば3番目の引数が不正だったことを意味します。これは、関数に期待される型や範囲外の値を渡した場合に起こります。
    • トラブルシューティング
      • hseqr!()の引数の順番、型、そして許容される値(例: jobcompzの文字コード)を再確認してください。
      • 特に、行列のサイズや次元が正しくない場合(例: 正方行列が必要なのに異なるサイズの行列を渡す)によく発生します。
      • iloihiの範囲が、行列の実際の次元と矛盾していないか確認してください。

MethodError: no method matching ...

  • トラブルシューティング
    • hseqr!()の正確なシグネチャ(引数の型と数)を確認してください。通常、JuliaのREPLで?LinearAlgebra.LAPACK.hseqr!と入力するとドキュメンテーションが表示されます。
    • 特に、文字引数(job, compz)はChar型でなければなりません(例: 'N'ではなく"N"のように文字列リテラルを渡すとエラーになることがあります)。
    • 行列の要素型が、LAPACKがサポートする浮動小数点型(Float32, Float64, ComplexF32, ComplexF64)であることを確認してください。整数型や他の型では直接使用できません。
  • 原因
    hseqr!()関数に、定義されていない、または互換性のない引数の型や組み合わせを渡した場合に発生します。これはJuliaの多重ディスパッチの仕組みによるものです。

DimensionMismatch

  • トラブルシューティング
    • Hessenberg行列Hは正方行列である必要があります。
    • シューアベクトルを格納する行列Zcompz'I'または'V'の場合)は、Hと同じ次元の正方行列である必要があります。
    • iloihiの範囲が、1 <= ilo <= ihi <= size(H, 1)のように行列の次元内に収まっていることを確認してください。
  • 原因
    引数として渡された行列やベクトルの次元が、操作に必要とされる次元と一致しない場合に発生します。

事前条件の不履行 (Hessenberg形式ではない)

  • トラブルシューティング
    • 任意の行列Aの固有値を計算したい場合は、まずH = hessenberg(A)のようにしてHessenberg分解を行う必要があります。そして、Hの分解結果(H.H)をhseqr!()に渡します。
  • 原因
    hseqr!()は、入力行列が既にHessenberg形式であることを前提としています。もしそうでない行列を直接渡しても、エラーメッセージは出ないかもしれませんが、誤った結果が返されるか、あるいは前述のLAPACKException(特にinfo > 0)につながる可能性があります。

環境依存の問題 (まれ)

  • トラブルシューティング
    • Juliaのバージョンを最新に保つ。
    • 特定のLAPACKライブラリ(例: MKL.jl)を使用している場合は、それが正しくインストールされ、設定されているか確認する。
    • もし特定の環境でのみ問題が発生する場合は、その環境に特有のセットアップや競合を疑う。
  • 原因
    非常に稀ですが、JuliaがリンクしているLAPACKライブラリの実装(OpenBLAS, MKLなど)や、そのバージョン、または特定のOS環境に起因する問題が発生することがあります。
  • エラーメッセージをよく読む
    Juliaのエラーメッセージは非常に詳細です。特にLAPACKException(info)の場合、infoの値が非常に重要です。スタックトレースも、どの関数呼び出しでエラーが発生したかを特定するのに役立ちます。
  • 高レベル関数を優先する
    繰り返しになりますが、ほとんどの場合、eigen()schur()のような高レベル関数を使用するのが安全で効率的です。これらの関数は、低レベルのLAPACKルーチンを正しく、かつロバストに呼び出すための複雑な処理を隠蔽してくれます。hseqr!()は、特定のパフォーマンスチューニングやデバッグの目的でのみ使用を検討すべきです。
  • ドキュメンテーションの確認
    Juliaの公式ドキュメンテーションやLAPACKのユーザーガイド(特にDGEHQRまたはZGEHQRのページ)を参照し、引数の意味や制約を詳しく確認します。
  • シンプルな例で試す
    複雑な行列やコードで問題が発生した場合、まず小さな、既知のHessenberg行列(例: 2×2のHessenberg行列)でhseqr!()を呼び出してみて、期待通りの結果が得られるか確認します。


しかし、hseqr!() の挙動を理解するため、または特定の高度な線形代数アルゴリズムを実装する際に、直接使用するシナリオが考えられます。

以下に、hseqr!() の使い方と、それに付随する(通常は高レベル関数が自動で行う)前処理・後処理の例をいくつか示します。

例1: Hessenberg行列の固有値のみを計算する

最も基本的な使い方です。Hessenberg行列の固有値を計算します。

using LinearAlgebra

# 1. Hessenberg行列の作成
# hseqr!()は入力がHessenberg行列であることを前提とします。
# 実際には、任意の行列Aからhessenberg()を使ってHessenberg形式に変換します。
# ここでは簡単な3x3のHessenberg行列を直接作成します。
H = [
    1.0  2.0  3.0
    4.0  5.0  6.0
    0.0  7.0  8.0
]

# (参考) 任意の行列AからHessenberg行列を導出する場合:
# A = [1.0 2.0 3.0; 4.0 5.0 6.0; 7.0 8.0 9.0]
# F = hessenberg(A)
# H = F.H # これがhseqr!に渡すHessenberg行列

# 2. hseqr! の呼び出し
# job='E': 固有値のみを計算 (Schur formは不要)
# compz='N': シューアベクトルは不要
# ilo=1, ihi=size(H, 1): 行列全体を対象とする
# Zはシューアベクトルを計算しないので、何でもよいが、型を合わせるために単位行列などを用意する
# (hseqr!はHを破壊的に変更します)
H_copy = copy(H) # オリジナルのHを保持するためコピー
Z_dummy = Matrix{eltype(H)}(I, size(H, 1), size(H, 2)) # ダミーのZ行列

# LAPACK.hseqr!(job, compz, ilo, ihi, H, Z) -> (w, s)
# w: 固有値(複素数ベクトルの場合あり)
# s: (job='S'の場合) シューア形式に変換されたH (ここではない)
# H: 破壊的に変更され、最終的にシューア形式になるか、固有値計算の中間結果になる
w, _ = LAPACK.hseqr!('E', 'N', 1, size(H_copy, 1), H_copy, Z_dummy)

println("元のHessenberg行列 H:\n", H)
println("計算された固有値 w:\n", w)

# (参考) 高レベル関数での固有値計算
# using LinearAlgebra
# A_original = [1.0  2.0  3.0; 4.0  5.0  6.0; 0.0  7.0  8.0]
# eigen_vals = eigvals(A_original)
# println("\n高レベル関数 eigvals() による固有値:\n", eigen_vals)

解説

  • Z_dummy はシューアベクトルを計算しないため、形式的な引数として渡すものです。型が H と同じで、正しい次元の正方行列であれば何でも構いません。
  • H は入力のHessenberg行列であり、この関数内で破壊的に変更されます
  • iloihi は、計算対象とする部分行列の範囲を指定します。通常は行列全体 (1 から size(H,1)) です。
  • compz='N' はシューアベクトル(Q行列)を計算しないことを指定します。
  • job='E' は固有値のみを計算することを指定します。この場合、Hは最終的にシューア形式にはなりませんが、固有値を抽出するために必要な計算が行われます。

例2: Hessenberg行列のシューア分解を計算する

シューア分解 H=QTQH を計算し、T(シューア形式)と Q(シューアベクトル)を得る例です。

using LinearAlgebra

# 1. Hessenberg行列の作成
H_orig = [
    1.0  2.0  3.0
    4.0  5.0  6.0
    0.0  7.0  8.0
]

# 2. hseqr! の呼び出し
# job='S': シューア形式を計算
# compz='I': Zを単位行列で初期化し、Hのシューアベクトルを返す
# (Hは破壊的に変更され、Tになる)
# (Z_schurはQになる)
H_schur_form = copy(H_orig) # Tを格納するためコピー
Z_schur_vectors = Matrix{eltype(H_orig)}(I, size(H_orig, 1), size(H_orig, 2)) # Qを格納するため単位行列で初期化

w_schur, _ = LAPACK.hseqr!('S', 'I', 1, size(H_schur_form, 1), H_schur_form, Z_schur_vectors)

println("\n元のHessenberg行列 H_orig:\n", H_orig)
println("計算されたシューア形式 T (H_schur_form):\n", H_schur_form)
println("計算されたシューアベクトル Q (Z_schur_vectors):\n", Z_schur_vectors)
println("計算された固有値 (Tの対角成分) w_schur:\n", w_schur)

# 検証: Q * T * Q' がH_origに戻るか
# Schur分解 H = Q * T * Q^H の検証 (H^H は共役転置)
# ここでは実数行列なので Q^H は Q' (転置) と同じ
reconstructed_H = Z_schur_vectors * H_schur_form * Z_schur_vectors'
println("\n再構成されたH (Q * T * Q'):\n", reconstructed_H)
println("H_orig と再構成されたHの差のノルム: ", norm(H_orig - reconstructed_H))

解説

  • $Q T Q^H$ が元の H に戻ることを確認することで、シューア分解が正しく行われたかを検証できます。ノルムが非常に小さい値であれば成功です。
  • compz='I' は、Z_schur_vectors を単位行列で初期化し、計算されたシューアベクトルをその中に格納することを指定します。結果として Z_schur_vectors が Q 行列になります。
  • job='S' はシューア形式(T)を計算することを指定します。この場合、H_schur_form は計算後、シューア形式の行列 T になります。

例3: 任意の行列の固有値計算(高レベル関数 eigen() の挙動を模倣)

hseqr!() を直接使う場合、前段階として任意の行列をHessenberg形式に変換する必要があります。これは eigen() 関数が内部的に行う処理の一部です。

using LinearAlgebra

# 1. 任意の行列A
A = [
    1.0  2.0  3.0
    4.0  5.0  6.0
    7.0  8.0  9.0
]

# 2. Hessenberg分解
# F.H はHessenberg行列 H
# F.Q はHessenberg分解で使われた変換行列 Q_hessenberg
F = hessenberg(A)
H_hessenberg = F.H
Q_hessenberg = F.Q # Q_hessenberg * H_hessenberg * Q_hessenberg' = A

println("元の行列 A:\n", A)
println("Hessenberg形式 H:\n", H_hessenberg)
println("Hessenberg分解の変換行列 Q_hessenberg:\n", Q_hessenberg)

# 3. hseqr! を用いてHessenberg行列からシューア分解を計算
# compz='V': ZをQ_hessenbergで初期化し、変換後のシューアベクトルを返す
# (つまり、Aに対するシューアベクトルがZに格納される)
H_schur_form_A = copy(H_hessenberg) # T_A を格納するためコピー
Z_schur_vectors_A = copy(Q_hessenberg) # Aに対するシューアベクトル Q_A を格納するためQ_hessenbergで初期化

w_A, _ = LAPACK.hseqr!('S', 'V', 1, size(H_schur_form_A, 1), H_schur_form_A, Z_schur_vectors_A)

println("\n行列 A のシューア形式 T_A (H_schur_form_A):\n", H_schur_form_A)
println("行列 A のシューアベクトル Q_A (Z_schur_vectors_A):\n", Z_schur_vectors_A)
println("行列 A の固有値 (T_A の対角成分) w_A:\n", w_A)

# 検証: Q_A * T_A * Q_A' がAに戻るか
reconstructed_A = Z_schur_vectors_A * H_schur_form_A * Z_schur_vectors_A'
println("\n再構成された A (Q_A * T_A * Q_A'):\n", reconstructed_A)
println("A と再構成されたAの差のノルム: ", norm(A - reconstructed_A))

# (参考) 高レベル関数 eigvals() / eigen() による固有値計算
# eigen_vals_high = eigvals(A)
# println("\n高レベル関数 eigvals() による固有値:\n", eigen_vals_high)
# F_schur_high = schur(A)
# println("\n高レベル関数 schur() によるシューア形式 T:\n", F_schur_high.T)
# println("高レベル関数 schur() によるシューアベクトル Z:\n", F_schur_high.Z)
  • 最終的に、H_schur_form_AはAのシューア形式T_A、Z_schur_vectors_AはAのシューアベクトルQ_Aとなります。T_Aの対角成分がAの固有値です。
  • 次に、このHに対してhseqr!()を適用します。compz='V' を指定し、Z引数にQ_hessenbergを渡すことで、hseqr!()はHのシューアベクトルを計算するだけでなく、それをQ_hessenbergと結合して、元の行列Aに対するシューアベクトルをZに格納します。
    • 具体的には、H=Q_STQ_SH であれば、A=Q_HHQ_HH=Q_H(Q_STQ_SH)Q_HH=(Q_HQ_S)T(Q_HQ_S)H となり、Aのシューアベクトルは Q_HQ_S となります。compz='V'はこの積を効率的に計算してくれます。
  • 任意の行列Aの固有値・シューア分解を計算するには、まずhessenberg(A)を使ってHessenberg分解 A=Q_HHQ_HH を行います。ここで、HはHessenberg行列、Q_Hはユニタリ行列です。
  • パフォーマンス
    hseqr!() を直接使うことでわずかなパフォーマンス改善が見込める場合もありますが、多くの場合、eigen()schur() などの高レベル関数が既に高度に最適化されており、より安全で使いやすいです。特殊な要件がない限り、これら高レベル関数の使用を推奨します。
  • エラーハンドリング
    LAPACKのルーチンはエラーが発生した場合に info コードを返します。Juliaではこれが LAPACKException(info) としてラップされます。トラブルシューティングの際には、info の値を確認することが重要です。
  • 低レベルインターフェース
    LAPACKの規約に厳密に従います。引数の文字コード('N', 'E', 'S', 'I', 'V')や行列の次元、型などに注意が必要です。
  • 入力はHessenberg行列
    hseqr!() の前提条件は、入力行列がHessenberg形式であることです。任意の行列の固有値を求めるには、前処理として hessenberg() 関数を呼び出す必要があります。
  • 破壊的変更
    hseqr!() は引数 HZ を破壊的に変更します。元の行列を保持したい場合は、事前に copy() してください。


eigen() 関数

Juliaで最も一般的な固有値・固有ベクトル計算の方法です。任意の行列の固有値を計算したい場合に利用します。eigen() 関数は、内部的にHessenberg分解(hessenberg())やQR反復法(hseqr!()など)を自動で行ってくれます。

特徴:

  • 安定性
    内部でバランス調整などの数値的な安定化処理が行われます。
  • 結果
    固有値のベクトルと、対応する固有ベクトルを列にもつ行列を返します。
  • 使いやすさ
    最もシンプルで直感的なインターフェース。
  • 汎用性
    任意の正方行列に対して利用可能。

使用例:

using LinearAlgebra

A = [
    1.0  2.0  3.0
    4.0  5.0  6.0
    7.0  8.0  9.0
]

# 固有値と固有ベクトルを計算
# E.values が固有値のベクトル
# E.vectors が固有ベクトルを列にもつ行列
E = eigen(A)

println("元の行列 A:\n", A)
println("固有値 (eigen.values):\n", E.values)
println("固有ベクトル (eigen.vectors):\n", E.vectors)

# 検証: A * v = lambda * v (vは固有ベクトル、lambdaは固有値)
# 例として最初の固有値・固有ベクトルを検証
# 固有ベクトルはスケーリングに依存するため、厳密な一致は難しいですが、方向と比率は合致します
lambda1 = E.values[1]
v1 = E.vectors[:, 1]
println("\nA * v1:\n", A * v1)
println("lambda1 * v1:\n", lambda1 * v1)

eigvals() 関数

行列の固有値のみが必要で、固有ベクトルは不要な場合に利用します。eigen() よりも効率が良い場合があります。

  • 効率
    固有ベクトルを計算しない分、eigen() よりも高速な場合があります。
  • 固有値のみ
    固有値のベクトルだけを返します。
using LinearAlgebra

A = [
    1.0  2.0  3.0
    4.0  5.0  6.0
    7.0  8.0  9.0
]

# 固有値のみを計算
evals = eigvals(A)

println("元の行列 A:\n", A)
println("計算された固有値 (eigvals):\n", evals)

schur() 関数

行列のシューア分解が必要な場合に利用します。シューア分解は、A=QTQH の形式で、ここで Q はユニタリ行列(または直交行列)、T は準上三角行列(シューア形式)であり、T の対角成分には固有値が並びます。hseqr!()は主にこのTとQを計算するために内部的に利用されます。

  • 数値安定性
    非対称行列の場合でも数値的に安定した方法で固有値を導出できます。複素固有値を持つ行列のシューア形式は常に上三角行列になります。実数行列の場合、実数固有値は 1times1 ブロックとして、共役複素固有値のペアは 2times2 ブロックとして T の対角に現れます。
  • シューア分解
    固有値だけでなく、シューア形式 T とシューアベクトル Q を返します。
using LinearAlgebra

A = [
    1.0  2.0  3.0
    4.0  5.0  6.0
    7.0  8.0  9.0
]

# シューア分解を計算
# S.T がシューア形式の行列
# S.Z がシューアベクトルの行列 (ユニタリ/直交行列)
S = schur(A)

println("元の行列 A:\n", A)
println("シューア形式 T (schur.T):\n", S.T)
println("シューアベクトル Z (schur.Z):\n", S.Z)
println("シューア分解から得られる固有値 (diag(S.T) または eigvals(S.T)):\n", eigvals(S.T))

# 検証: Z * T * Z' が A に戻るか
reconstructed_A = S.Z * S.T * S.Z'
println("\n再構成された A (Z * T * Z'):\n", reconstructed_A)
println("A と再構成されたAの差のノルム: ", norm(A - reconstructed_A))

hseqr!()がHessenberg行列を前提とするように、より低レベルなステップを分解して実行したい場合に利用します。これは任意の行列をHessenberg形式に変換する関数です。hseqr!()の直接的な代替というよりは、hseqr!()を使うための前処理として機能します。

  • ステップバイステップの制御
    固有値計算の過程をより細かく制御したい場合に利用します。
  • 前処理
    任意の行列をHessenberg形式に変換します。
using LinearAlgebra

A = [
    1.0  2.0  3.0
    4.0  5.0  6.0
    7.0  8.0  9.0
]

# Hessenberg分解を計算
# F.H がHessenberg形式の行列
# F.Q がHessenberg分解で使われた変換行列
F = hessenberg(A)

println("元の行列 A:\n", A)
println("Hessenberg形式 H (hessenberg.H):\n", F.H)
println("Hessenberg変換行列 Q (hessenberg.Q):\n", F.Q)

# 検証: Q * H * Q' が A に戻るか
reconstructed_A_hessenberg = F.Q * F.H * F.Q'
println("\n再構成された A (Q * H * Q'):\n", reconstructed_A_hessenberg)
println("A と再構成されたAの差のノルム: ", norm(A - reconstructed_A_hessenberg))

# F.H (Hessenberg行列) を hseqr!() に渡すことで、固有値・シューア分解をさらに進めることができます。
# (例として、Hessenberg形式から固有値を計算)
# H_copy = copy(F.H)
# Z_dummy = Matrix{eltype(H_copy)}(I, size(H_copy, 1), size(H_copy, 2))
# w_from_hessenberg, _ = LAPACK.hseqr!('E', 'N', 1, size(H_copy, 1), H_copy, Z_dummy)
# println("\nHessenberg形式から計算された固有値:\n", w_from_hessenberg)

LinearAlgebra.LAPACK.hseqr!() は非常に強力な低レベルツールですが、ほとんどの線形代数計算のタスクでは、上記で説明した高レベルな代替関数(eigen(), eigvals(), schur())を使用する方が、安全性、利便性、そして多くの場合パフォーマンスの点でも推奨されます。これらの高レベル関数は、内部で複雑なLAPACKルーチン(hseqr!()を含む)を適切に呼び出し、数値的に安定した結果を提供します。