mutable の設計

設計目標

mutable はリポジトリの実行指向の半分です。行列を行優先の平坦な 1 本の配列に格納し、呼び出し側がインプレースで更新したり動的なビューを通じて操作したりできるようにし、浮動小数点の数値ルーチンを実装します。行列式、逆行列、階数(ランク)、行簡約、Cholesky 分解、対称行列の固有値、べき乗法です。公開 API は可能な限り値の操作のように読めるようにしてあります。変更は Unit を返すメソッドとビューに限られるので、呼び出し側はシグネチャから、呼び出しがレシーバーを変更するかどうかを判断できます。

数学的背景

以下では、uu は丸め単位(Double では 2−532^{-53}、Float では 2−242^{-24})、fl(x∘y)=(x∘y)(1+δ)\mathrm{fl}(x \circ y) = (x \circ y)(1 + \delta)(∣δ∣≤u|\delta| \le u)、γn=nu/(1−nu)\gamma_n = n u / (1 - n u) とします。行列間の不等式は要素ごとに成り立つものとし、∣A∣|A| は絶対値をとった行列です。

行列積

(AB)ik=∑jaijbjk(AB)_{ik} = \sum_j a_{ij} b_{jk} のコストは rcnrcn 回の積和です。カーネルによって総和の順序は異なります(展開したカーネルでは 1 ステップに 4 つの部分積、4×16×164 \times 16 \times 16 以上の積では BB の列をパックしたコピー)が、どの順序でも次を満たします。

∣fl(AB)−AB∣≤γn∣A∣ ∣B∣,\big|\mathrm{fl}(AB) - AB\big| \le \gamma_n |A|\,|B| ,

したがって、異なるターゲット上の結果はこの精度で一致しますが、ビット単位では一致しません。

部分ピボット選択付き LU 分解

正方行列 AA について、部分ピボット選択付き Gauss 消去は、置換行列 PP、単位下三角行列 LL、上三角行列 UU を計算し、次を満たします。

PA=LU.PA = LU .

ステップ kk では、∣apk(k)∣|a^{(k)}_{pk}| が最大となる行 p≥kp \ge k を選んで位置 kk に交換し、i>ki > k について乗数 lik=aik(k)/akk(k)l_{ik} = a^{(k)}_{ik} / a^{(k)}_{kk} を格納して aij(k+1)=aij(k)−likakj(k)a^{(k+1)}_{ij} = a^{(k)}_{ij} - l_{ik} a^{(k)}_{kj} と更新します。ピボット選択により ∣lik∣≤1|l_{ik}| \le 1 が保証されます。コストは 23n3\tfrac23 n^3 flops です。

行列式。 PA=LUPA = LU の行列式をとり、det⁡L=1\det L = 1 と、交換が ss 回のときの det⁡P=(−1)s\det P = (-1)^{s} を使うと、

det⁡A=(−1)s∏kukk.\det A = (-1)^{s} \prod_{k} u_{kk} .

求解。 Ax=bAx = b は Ly=PbL y = P b(前進代入)と Ux=yU x = y(後退代入)になり、それぞれ右辺 1 つあたり n2n^2 flops です。逆行列は II の nn 本の列に対する解であり、23n3+n⋅2n2=83n3\tfrac23 n^3 + n \cdot 2n^2 = \tfrac83 n^3 flops です。

安定性。 計算された因子は後退誤差の限界(Wilkinson; Higham, Theorem 9.3)を満たします。11 N. J. Higham, Accuracy and Stability of Numerical Algorithms, 2nd ed., SIAM, 2002, 第 9 章(LU)、第 10 章(Cholesky)、第 8 章(三角行列系)。

L^U^=P(A+ΔA),∣ΔA∣≤γn∣L^∣ ∣U^∣.\hat L \hat U = P(A + \Delta A), \qquad |\Delta A| \le \gamma_n |\hat L|\,|\hat U| .

∣lik∣≤1|l_{ik}| \le 1 からこれは ∥ΔA∥∞≤nγnρn∥A∥∞\lVert \Delta A \rVert_\infty \le n \gamma_n \rho_n \lVert A \rVert_\infty を与えます。ここで ρn=max⁡i,j,k∣aij(k)∣/max⁡i,j∣aij∣\rho_n = \max_{i,j,k} |a^{(k)}_{ij}| / \max_{i,j} |a_{ij}| は増大因子です。部分ピボット選択は ρn≤2n−1\rho_n \le 2^{n-1} と抑えます。この限界に達するのは人為的な行列だけで、実際には ρn\rho_n は小さいため、この方法は実用上後退安定です。求解の 前進 誤差は条件数によって支配されます: ∥x^−x∥/∥x∥≲κ(A) nγnρn\lVert \hat x - x \rVert / \lVert x \rVert \lesssim \kappa(A)\, n \gamma_n \rho_n。

小さな行列式の閉じた公式

n≤4n \le 4 では、行列式は LU ではなく公式で評価します。規則 ad−bcad - bc、n=3n = 3 では第 1 行に沿った余因子展開、n=4n = 4 では最初の 2 行に沿った Laplace 展開による、相補的な 2×22 \times 2 小行列式の 6 つの積です。

det⁡A=∑p<q(−1)p+q+1det⁡A{0,1},{p,q}det⁡A{2,3},{p,q}‾.\det A = \sum_{p<q} (-1)^{p+q+1} \det A_{\{0,1\},\{p,q\}} \det A_{\{2,3\},\overline{\{p,q\}}} .

これらは除算もピボット選択の判断も使わないので、小さな行列では許容誤差の判定を避けられます。LU の意味での後退安定性はありません。悪条件の入力では ad−bcad - bc の桁落ちによって相対精度がすべて失われることがありますが、それはまさにそこで行列式自体が悪条件であるのと同じです。

階数(ランク)と被約行階段形

rank はコピーに対して部分ピボット選択付きの消去を行い、絶対値が許容誤差以上のピボットを数えます。これは絶対しきい値 τ\tau による 数値的階数、つまり ≥τ\ge \tau のピボットの個数です。ピボットが τ\tau から十分離れている行列では厳密ですが、そうでなければスケーリングに依存します。(信頼できる数値的階数 #{σi>τ}\#\{\sigma_i > \tau\} は特異値分解で得られますが、実装されていません。)

reduce_row_elimination は Gauss–Jordan 消去です。各ピボット行をスケーリングしてピボットを 1 にし、ピボット列の上下を消去します。コストはおよそ rcmin⁡(r,c)r c \min(r, c) flops で、インプレースで動作します。

Cholesky 分解

対称正定値(SPD)行列は、LL が下三角で ljj>0l_{jj} > 0 である一意の分解 A=LLTA = L L^{\mathsf T} を持ちます。i≥ji \ge j について A=LLTA = L L^{\mathsf T} の要素を比較すると、

aij=∑k=0jlikljk⟹ljj=ajj−∑k<jljk2,lij=1ljj(aij−∑k<jlikljk).a_{ij} = \sum_{k=0}^{j} l_{ik} l_{jk} \quad\Longrightarrow\quad l_{jj} = \sqrt{a_{jj} - \sum_{k<j} l_{jk}^2}, \qquad l_{ij} = \frac{1}{l_{jj}} \Big(a_{ij} - \sum_{k<j} l_{ik} l_{jk}\Big) .

コードはこれを行ごとに(Cholesky–Banachiewicz の順序で)評価し、13n3\tfrac13 n^3 flops かかります。ステップ jj の根号の中身は、ピボット選択なしの Gauss 消去のピボットに等しく、それは首座小行列式の比 det⁡Mj+1/det⁡Mj\det M_{j+1} / \det M_j です。Sylvester の判定法により、AA が SPD であるのはすべての首座小行列式が正であるときに限るので、分解が成功するのはちょうど SPD 行列のときです。is_positive_definite が分解を試みることで実装されているのはこのためです。Cholesky 分解にはピボット選択が不要です。ajj=∑kljk2a_{jj} = \sum_k l_{jk}^2 からすべての ∣ljk∣≤ajj|l_{jk}| \le \sqrt{a_{jj}} となり要素は増大しえず、計算された因子は ∣ΔA∣≤γn+1∣L^∣ ∣L^T∣|\Delta A| \le \gamma_{n+1} |\hat L|\,|\hat L^{\mathsf T}| である L^L^T=A+ΔA\hat L \hat L^{\mathsf T} = A + \Delta A を満たします(Higham, Theorem 10.3)。

対称固有値問題

実対称行列 AA について、スペクトル定理は QQ を直交行列、Λ\Lambda を実対角行列として A=QΛQTA = Q \Lambda Q^{\mathsf T} を与えます。eigen はこれを 2 段階で計算します。

Householder 三重対角化。 Householder 鏡映 H=I−2vvT/vTvH = I - 2 v v^{\mathsf T} / v^{\mathsf T} v は対称かつ直交で、ベクトルを座標ベクトルの定数倍に写すことができます。n−2n - 2 個の鏡映を両側から適用すると、

Q1TAQ1=T,Q1=H1H2⋯Hn−2,Q_1^{\mathsf T} A Q_1 = T, \qquad Q_1 = H_1 H_2 \cdots H_{n-2},

となります。ここで TT は対角 dd、副対角 ee を持つ対称三重対角行列です。コードは Q1Q_1 を明示的に累積し、合わせておよそ 83n3\tfrac83 n^3 flops かかります。鏡映を作る前に各行をその絶対値の和でスケーリングすることで、∥x∥2\lVert x \rVert_2 のオーバーフローとアンダーフローを防ぎます。

Wilkinson シフト付き陰的 QL 法。 三重対角行列 TT は平面回転によって対角化されます。ll から始まる未簡約ブロックへの各掃き出しの前に、シフト σ\sigma として、先頭の 2×22 \times 2 ブロック (dleleldl+1)\begin{pmatrix} d_l & e_l \\ e_l & d_{l+1} \end{pmatrix} の固有値のうち dld_l に近いほうをとります。g=(dl+1−dl)/(2el)g = (d_{l+1} - d_l)/(2 e_l) とすると、このブロックの固有値は

λ±=dl+el(g±g2+1),\lambda_\pm = d_l + e_l \big(g \pm \sqrt{g^2 + 1}\big),

であり、dld_l に近いほうは

σ=dl+el(g−sgn⁡(g)g2+1)=dl−elg+sgn⁡(g)g2+1,\sigma = d_l + e_l\big(g - \operatorname{sgn}(g)\sqrt{g^2+1}\big) = d_l - \frac{e_l}{g + \operatorname{sgn}(g)\sqrt{g^2 + 1}} ,

です。コードが使う 2 番目の形は桁落ちを避けます。掃き出しでは、生じたバルジを追い出す Givens 回転を適用し、同じ回転で QQ を更新します。副対角要素は次の場合にゼロとみなします。

∣em∣≤τ (∣dm∣+∣dm+1∣+1),|e_m| \le \tau\,\big(|d_m| + |d_{m+1}| + 1\big),

この判定は、大きな対角要素に対しては相対的、ゼロ付近では絶対的です。Wilkinson シフトによる収束は、対称三重対角行列については実際上 3 次であり、有限の入力で失敗した例は観測されていません。それでもコードは、1 つの固有値について 60 回の掃き出し後に中断します。手続き全体は直交変換の積であり後退安定です。計算された固有値は ∥ΔA∥2=O(u)∥A∥2\lVert \Delta A \rVert_2 = O(u)\lVert A \rVert_2 である A+ΔAA + \Delta A に対して厳密なので、Weyl の不等式により

∣λ^i−λi∣≤∥ΔA∥2=O(u) ∥A∥2.|\hat\lambda_i - \lambda_i| \le \lVert \Delta A \rVert_2 = O(u)\,\lVert A \rVert_2 .

固有ベクトルの精度は u∥A∥/gapu\lVert A \rVert / \text{gap} に比例します。ここでギャップとは、最も近い他の固有値までの距離です。

2×22 \times 2 の場合。 A=(abcd)A = \begin{pmatrix} a & b \\ c & d \end{pmatrix} について、特性多項式 λ2−(a+d)λ+(ad−bc)\lambda^2 - (a + d)\lambda + (ad - bc) から、m=(a+d)/2m = (a + d)/2 として次が得られます。

λ1,2=m±m2−(ad−bc).\lambda_{1,2} = m \pm \sqrt{m^2 - (ad - bc)} .

b≠0b \ne 0 のとき、ベクトル (b,λ−a)T(b, \lambda - a)^{\mathsf T} は固有ベクトルです。

(a−λbcd−λ)(bλ−a)=(0bc−(λ−a)(λ−d))=0,\begin{pmatrix} a - \lambda & b \\ c & d - \lambda \end{pmatrix} \begin{pmatrix} b \\ \lambda - a \end{pmatrix} = \begin{pmatrix} 0 \\ bc - (\lambda - a)(\lambda - d) \end{pmatrix} = 0 ,

特性方程式により (λ−a)(λ−d)=λ2−(a+d)λ+ad=bc(\lambda - a)(\lambda - d) = \lambda^2 - (a + d)\lambda + ad = bc だからです。コードはこれらのベクトルを正規化せずに返します。∣λ2∣≪∣λ1∣|\lambda_2| \ll |\lambda_1| のとき、減算 m−⋅m - \sqrt{\cdot} で桁落ちが起こり λ2\lambda_2 の相対精度が失われます。安定な代替は λ2=det⁡A/λ1\lambda_2 = \det A / \lambda_1 です。

べき乗法

x0=(1,…,1)x_0 = (1, \dots, 1)(Ax0=0A x_0 = 0 の場合は座標ベクトル)から出発し、この方法は次を反復します。

y=Axk,xk+1=y/∥y∥∞,λk=xkTAxkxkTxk,y = A x_k, \qquad x_{k+1} = y / \lVert y \rVert_\infty, \qquad \lambda_k = \frac{x_k^{\mathsf T} A x_k}{x_k^{\mathsf T} x_k} ,

そして ∥Axk−λkxk∥∞≤τ\lVert A x_k - \lambda_k x_k \rVert_\infty \le \tau となったら停止します。AA が固有値 ∣λ1∣>∣λ2∣≥…|\lambda_1| > |\lambda_2| \ge \dots で対角化可能であり、x0x_0 が固有ベクトル v1v_1 方向に成分 c1≠0c_1 \ne 0 を持つならば、

Akx0=λ1k(c1v1+∑i≥2ci(λiλ1)kvi),A^{k} x_0 = \lambda_1^{k}\Big(c_1 v_1 + \sum_{i \ge 2} c_i \big(\tfrac{\lambda_i}{\lambda_1}\big)^{k} v_i\Big),

となるので、方向は比 ∣λ2/λ1∣|\lambda_2 / \lambda_1| で線形収束し、対称な AA では Rayleigh 商が比 ∣λ2/λ1∣2|\lambda_2 / \lambda_1|^2 で収束します。λ1≠λ2\lambda_1 \ne \lambda_2 で ∣λ1∣=∣λ2∣|\lambda_1| = |\lambda_2| のとき(たとえば ±1\pm 1)、方向は振動して残差判定を満たすことがなく、AA が冪零のときは反復ベクトルがゼロに達します。どちらの場合も None を返します。

統計量

variance は 2 パスで計算する母分散です。まず平均 aˉ\bar a を求め、次に 1N∑(ai−aˉ)2\tfrac1N \sum (a_i - \bar a)^2 を求めます。1 パスの公式 1N∑ai2−aˉ2\tfrac1N \sum a_i^2 - \bar a^2 は、データの平均が大きく散らばりが小さいときにほぼ等しい 2 つの数を引き算することになり、負の値を返すことさえあります。2 パスの形は非負の項を足し合わせ、その丸め誤差は分散自体に対して相対的です(Chan, Golub and LeVeque, 1983)。個数 NN は T において 1 の和として累積されるので、Double では 2532^{53} 個、Float では 2242^{24} 個まで厳密です。

転置ビューと積

Transpose::mul は、ラップした行列に対して行列カーネルを再利用し、ATBTA^{\mathsf T} B^{\mathsf T} を (BA)T(BA)^{\mathsf T} として計算します。要素ごとに見ると、

(ATBT)ik=∑jajibkj,((BA)T)ik=∑jbkjaji,\big(A^{\mathsf T} B^{\mathsf T}\big)_{ik} = \sum_j a_{ji} b_{kj}, \qquad \big((BA)^{\mathsf T}\big)_{ik} = \sum_j b_{kj} a_{ji} ,

であり、両者はスカラーが可換なときに一致します。Tolerance を持つスカラー型(Float、Double)はすべて可換ですが、Transpose::mul が要求するのは AddMonoid + Mul だけです。非可換なスカラー型では、結果は逆順の積になります(algebra の設計 を参照)。

設計上の判断

行優先の平坦なストレージ

選択肢。 行配列の配列、永続的な構造、平坦な 1 本の配列。決定。 要素 (i,j)(i, j) を ic+ji c + j に置く 1 本の Array[T] で、4 つのターゲットすべてで共通。理由。 座標ごとに 1 回の範囲検査で O(1)O(1) のアクセスが得られ、消去法や積の内側のループで行が連続し、行と列のビューをコストなしで作れます。from_array は大きな入力をコピーしなくて済むように呼び出し側の配列を採用します。その代償はエイリアシングで、API でドキュメント化しています。

コピーではなくビュー

row_view、col_view、to_transpose は動的なビューを O(1)O(1) で返します。ビューは(行列, インデックス)の組かラッパーなので、書き込みは共有ストレージに対して行われ、同期は不要です。実体化は常に明示的です(to_vector、materialize、transpose)。

意味論を共有するターゲット固有のカーネル

このパッケージは、行列・LU・ビュー・転置のコードについてターゲットごとに 1 つのソースファイルを持ちます。違いは各バックエンド向けに選んだループ構造(展開、パッキング、インデックス計算での除算の回避)だけで、範囲やエラーの振る舞いを含む公開された意味論は同一であり、テストは 4 つのターゲットすべてで実行されます。総和の順序が異なるため、浮動小数点の結果は最下位ビットで異なることがあります。

許容誤差に基づく判定

消去法では、計算されたピボットが「ゼロである」のはいつかを判定しなければなりません。このパッケージは Double と Float に対して 1 つの絶対しきい値 τ\tau = Tolerance::tolerance() = 10−1110^{-11} を使い、次のように適用します。

ルーチンテスト
LU(n≥5n \ge 5 の determinant、inverse、is_invertible)ピボット ∣ukk∣<τ\lvert u_{kk}\rvert < \tau なら特異
rank残りの最大の ∣aik∣<τ\lvert a_{ik}\rvert < \tau ならピボットなし
reduce_row_elimination≤τ\le \tau の要素はゼロに設定
cholesky_decomposition根号の中身が ≤τ\le \tau なら正定値でない
is_symmetric、高速経路の判別(単位、対角、置換、三角)∣aij−bij∣≤τ\lvert a_{ij} - b_{ij}\rvert \le \tau
eigen のデフレーション∣em∣≤τ(∣dm∣+∣dm+1∣+1)\lvert e_m\rvert \le \tau(\lvert d_m\rvert + \lvert d_{m+1}\rvert + 1)
power_method残差 ∥Ax−λx∥∞≤τ\lVert Ax - \lambda x\rVert_\infty \le \tau

絶対しきい値は単純で予測しやすいものの、スケール不変ではありません。AA を 10−1210^{-12} 倍するとすべてのピボットが τ\tau を下回るため、完全に良条件の行列でも特異と報告されます。101210^{12} 倍すると、数値的に特異な行列が通ってしまいます。これらのルーチンを呼ぶ前に、データを 1 程度の大きさにスケーリングしてください。Float では τ=10−11\tau = 10^{-11} は丸め単位 ≈6×10−8\approx 6 \times 10^{-8} よりはるかに小さいので、判定は実質的に厳密なゼロの検査になり、ほぼ特異な Float 行列は検出されません。この trait は閉じている(pub(open) ではなく pub)ので、インスタンスはこの 2 つだけです。スケールを考慮した許容誤差の方針は今後の課題で、導入すれば破壊的変更になります。

高速経路

inverse は単位行列(コピーを返す)、対角行列(対角要素を反転する)、置換行列(転置を返す。列が互いに異なる座標ベクトルである行列では PTP=IP^{\mathsf T} P = I となるため)を判別します。determinant は n≥5n \ge 5 で三角行列を判別し、対角要素を掛け合わせます。各判定のコストは O(n2)O(n^2) で、O(n3)O(n^3) の分解を節約します。判定には許容誤差を使うので、τ\tau の範囲で対角な行列は厳密に対角として扱われます。

カーネルを変えずに検査付き形式を提供する

すべての検査付きメソッドは検証(正方性、指数の符号、空でないこと、長さ)を行ってから、元の中断する振る舞いまたは Option の振る舞いを保つ検査なしの対を呼び出します。ここには検査付きの行列積はありません。* は検証して中断し、unchecked_matmul はまったく検証しません。@immut.Matrix::matmul とのこの非対称性は認識されています。検査付きの matmul は変更ではなく追加になるでしょう。

対称行列の固有値のみ

一般の実行列は複素固有値を持ちうるため、実数の T に対して Vector[T] を返す関数では表現できません。そのため eigen は、すべての固有値が実数で正規直交な固有基底が存在する対称行列だけを受け付け、それ以外では中断します。

正しさと不変条件

  • ストレージ。 常に data.length() == row * col です。すべての公開アクセサは行と列を別々に検査します。
  • 値を返すメソッドは変更しない。 行列を変更するのは Unit を返すメソッド、ビューへの書き込み、reduce_row_elimination だけです。
  • 検査付き・検査なしの法則。 前提条件が成り立つときは常に x.f() == Ok(x.unchecked_f()) です。inverse が Err(SingularMatrix) を返すのは、unchecked_inverse が None を返すときに限ります。
  • 行列式の一貫性。 n≥5n \ge 5 で、τ\tau の範囲で三角でない行列について、determinant が 0 を返すのは LU 分解が τ\tau を下回るピボットを報告するときに限り、それはまた is_invertible が false を返し inverse が失敗するときでもあります。n≤4n \le 4 では determinant に閉じた公式を使う一方、is_invertible は引き続き LU を使うので、ごく小さな非ゼロの行列式を持つ行列は、determinant が非ゼロでありながら「逆行列を持たない」と判定されることがあります。
  • 残差の保証。 cholesky_decomposition と eigen は再構成誤差が O(u)∥A∥O(u)\lVert A \rVert の因子を返します。power_method は残差が高々 τ\tau の組だけを返します。
  • 計算量。 *: rcnrcn。determinant、inverse: O(n3)O(n^3)。rank、reduce_row_elimination: O(rcmin⁡(r,c))O(rc\min(r, c))。cholesky_decomposition: n3/3n^3/3。eigen: O(n3)O(n^3)。power_method: 反復ごとに O(n2)O(n^2)。統計量: O(rc)O(rc)。

却下した代替案

  • 相対的な、またはノルムでスケーリングした許容誤差。 より頑健ですが、既存の呼び出し側の結果が変わってしまいます。許容誤差の方針を明示的に渡せるようになるまで見送ります。
  • 複素固有値を返す。 このパッケージが複素数型に依存することになり、実対称な入力に対するシグネチャも変わります。
  • コピーオンライトのビュー。 書き込みのコストを隠し、ビューの存在意義であるインプレースの契約を壊します。
  • 単一の移植可能なカーネル。 計測の結果、一部のターゲットで遅くなりました。ターゲットごとのファイルは、1 つの仕様を共有しながらコードサイズと引き換えに速度を得ています。

境界

mutable は、右辺に対する連立一次方程式の求解を公開メソッドとして提供せず(逆行列のみ)、QR、SVD、最小二乗ソルバー、非対称行列の固有値、疎なストレージ、条件数の推定、スケールを考慮した許容誤差も提供しません。algebra の trait も自身では実装しません。そのために backends/default がこれをラップしています。回帰や最適化のような分野固有のワークフローは下流のパッケージに属します。

Footnotes

  1. N. J. Higham, Accuracy and Stability of Numerical Algorithms, 2nd ed., SIAM, 2002, 第 9 章(LU)、第 10 章(Cholesky)、第 8 章(三角行列系)。 ↩