行列¶
モジュール sympy.matrices
に関するメモを記す。ここにある機能は、行列を処理するものと、行列に関する数式を処理するものとに分類できる。
Note
紙幅の都合上、出力を一部手で改行した。
本文中のすべての IPython セッション中のサンプルコードで、以下のインポートおよび出力書式設定が済んでいるものとする。
from sympy.matrices import *
init_printing(pretty_print=False)
クラス図¶
行列関連の単純なクラス図を示す。
行列を表現するクラス¶
行列オブジェクトの基本単位を表現するためのクラスの論理的な継承関係は次のようになっている。
モジュール
__init__
で別名が付けられる等されているため、クラス名中のMutable
とDense
の部分は省略できる。例を挙げると、単にMatrix
と書いたとしても、実際はクラスMutableDenseMatrix
である。Mutable
とImmutable
の違いは Python のプログラマーが思い浮かべるものと同じ。Dense
とSparse
の違いは疎行列性を意味する。MatrixBase
のスーパークラスは Python のクラスobject
である。
行列を構成要素とする数式のためのクラス¶
行列オブジェクトを各種演算で構成した数式を表現するためのクラスが多数存在する。そのクラス群はモジュール matrices.expressions
以下で定義されており、それらの継承関係は次のようになっている。全サブクラスを書き出す必要はまったくないが、何かのために記す。
継承ツリーの右側に関連するフリー関数、行列クラスのメソッド、メモを併記する。
MatrixExpr
MatrixSymbol シンボル(記号)としての行列
Identity
ZeroMatrix
MatAdd 加法
MatMul 乗法
MatPow M ** n
Inverse M ** -1, M.inverse(), M.I
Transpose transpose(M), M.T
Adjoint adjoint(M)
Trace trace(M)
DiagonalMatrix
DiagonalOf
Determinant det(M)
DFT 離散 Fourier 変換の行列表現
IDFT 逆離散 Fourier 変換の行列表現
MatrixSlice M[start:stop:step, ...]
HadamardProduct hadamard_product(M, ...)
FunctionMatrix ラムダ式による行列生成
Factorization
LofLU lu(M)
LofCholesky
UofLU lu(M)
UofCholesky
QofQR qr(M)
RofQR qr(M)
EigenVectors eig(M)
EigenValues eig(M)
UofSVD svd(M)
SofSVD svd(M)
VofSVD svd(M)
BlockMatrix blockcut(M, rsize, csize)
BlockDiagMatrix
クラス
MatrixExpr
のスーパークラスは SymPy のクラスBasic
である。
色々な行列の生成方法¶
まず、行列以外の要素から行列オブジェクトを直接生成する方法を記す。
他のライブラリーの類似機能を利用した経験から言えば、クラス Matrix
のコンストラクターを直接する場合は、おそらく行列の各成分を細かく指示する場合に限られると思われる。
Matrix(([[1,0,0], [0,0,0]])
入れ子の
list
は行 (row) を上から下に並べたものとして扱われる。Matrix([[1, 2, 3]])
上の特別な場合だが、入れ子の中身が
list
ひとつの場合は行ベクトルと等価な行列となる。Matrix([1, 2, 3])
入れ子になっていない場合は列ベクトルと等価な行列となる。
Matrix(2, 3, [1, 2, 3, 4, 5, 6])
行列の寸法と成分すべてを行を先に埋める順で指示して生成する。
寸法は行数、列数の順。タテ、ヨコ。
Matrix(3, 4, lambda i,j: 1 - (i+j) % 2)
行列を寸法と添字を引数に取る関数とで生成する。
成分に特徴のある行列を生成する場合は、それに適する関数、メソッドが用意されているので利用する。
関数
eyes
とzeros
は NumPy のそれと同様。関数
diag
は対角行列の生成であるが、より小さい行列を主対角線上に配列するようなこともできる。
基本的な操作¶
要素アクセス
行列の要素ひとつにアクセスするには、適切な添え字を用いて
M[i]
やM[i, j]
とする。添字がひとつの場合は、要素を row-major でアクセスする。NumPy の array と同様にスライスもサポートしている。
記法も同様に
M[start:stop:step]
のようにする。右辺値としてスライスする場合は、元のオブジェクトの一部のコピーとして機能する。一方、左辺値としてスライスする場合は、元のオブジェクトのビューのように機能する。
行列代数
少なくとも同型の行列同士では加法と乗法が成立する。これは標準的な算術演算子
+
,-
,*
で表現する。
ベクトル演算
例えばメソッド
dot
とcross
が利用可能になる。詳細は後述する。
各要素に関数を適用するには、メソッド
applyfunc
を用いる。
線形代数的操作¶
- 関数
GramSchmidt(vlist, orthonormal=False)
ベクトルの集合を基に直交基底を得る。
引数
vlist
は同じサイズのベクトルのコレクション。断りがないが、線形独立である必要があるはずだ。キーワード引数
orthonormal
で得られるベクトルの長さを 1 にするかどうかを指定する。実装を見ると、メソッド
project
を利用していることがわかる。
クラス MatrixBase
¶
このクラスにはメソッドが大量にある。要所に絞って記す。演算子は省略。
プロパティー¶
プロパティーのほぼすべてを記す。下の表のもの以外にも、例えば .rows
のようにしてオブジェクトの属性を得ることができるものもあるようだが、コードから拾えないので割愛した。
プロパティー |
説明 |
---|---|
|
要素ごとに複素共役をとった行列を返す。 |
|
Dirac 共役行列を返す。 |
|
Hermite 共役を返す。 |
|
転置行列を返す。 |
|
Hermite 行列かどうかを返す。 |
|
下三角行列かどうかを返す。 |
|
下 Hessenberg 行列かどうかを返す。 |
|
正方行列かどうかを返す。 |
|
上三角行列かどうかを返す。 |
|
上 Hessenberg 行列かどうかを返す。 |
|
零行列かどうかを返す。 |
|
行列の寸法を返す。必ず (r, c) の型をとる。 |
C
はメソッドconjugate
のエイリアス。D
は結局はモジュールsympy.physics.matrices
の機能を利用している。そのときにまた調べよう。H
はT.C
と同じ。T
はメソッドtranspose
のエイリアス。三角行列判定系のメソッドは
for
ループを二重にまわして、テストに必要な各成分を要素オブジェクトのプロパティーis_zero
でゼロかどうかをテストしている。また、必ずしも正方行列でなくてよい。
要素の物理的な操作¶
行や列のデータを物理的に操作するタイプのものを次の表に記す。
シグニチャー |
説明 |
---|---|
|
複数の行列を横に連結する。 |
|
複数の行列を縦に連結する。 |
|
行列に列を挿入する。 |
|
行列に行を挿入する。 |
|
行列同士を縦に連結する。 |
|
行列同士を横に連結する。 |
|
行を指定の置換で入れ替える。 |
|
行を指定の置換で入れ替える。 |
どのメソッドも inplace な振る舞いをしない。
hstack
,vstack
はクラスメソッド。余談だが同名のものが NumPy にもある。挿入系メソッドの引数
mti
はshape
の縦横の一致を問う。置換系メソッドの核となるのはメソッド
row_swap
である。
部分空間¶
部分空間に関するメソッドを下の表に示す。
シグニチャー |
説明 |
---|---|
|
列空間を返す。 |
|
零空間を返す。 |
どちらもメソッド rref
が重要な働きをする。
乗法¶
行列またはベクトルの乗算に関するメソッドを下の表に示す。行か列のどちらかのサイズを 1 にすれば Matrix
を列ベクトル、行ベクトルとして扱える。
シグニチャー |
説明 |
---|---|
|
ベクトルのクロス積、ベクトル積を計算する。 |
|
ベクトルのドット積、スカラー積を計算する。 |
|
行列の乗算を行う。 |
|
行列の成分ごとの乗算を行う。 |
メソッド
dot
はベクトル同士だけではなく、サイズさえ合えば行列対ベクトルでもよい。ただしcross
のほうは本当にベクトル限定。しかも三次元のみ。メソッド
multiply
は演算子*
で実装されている。メソッド
multiply_elementwise
は関数matrix_multiply_elementwise
で実装されている。
ノルム¶
ノルムに関係するメソッドを次に示す。
シグニチャー |
説明 |
---|---|
|
行列またはベクトルのノルムを返す。 |
|
正規化した行列またはノルムを返す。 |
ノルムはデフォルトでは行列に対しては Frobenius ノルムを、ベクトルに関しては 2- ノルムをそれぞれ返す。
ノルムの種類に関しては help を参照。いつも使うものは揃っている。
メソッド
normalized
で用いられるノルムはデフォルトのノルム。
行列式¶
行列式に関係するメソッドを次に示す。
シグニチャー |
説明 |
---|---|
|
正方行列の行列式を計算する。 |
|
Baries のアルゴリズムにより行列式を計算する。 |
|
LU 分解により行列式を計算する。 |
det
は結局は残りの二つか、後述するメソッドberkowitz_det
のいずれかである。Baries のアルゴリズムというのは、Gauss の消去法の拡張版みたいなもので、丸め誤差に強いという性質が期待できる。
LU 分解による行列式の計算は、分解行列の主対角線の成分だけを見ればよいというものだ。後述するメソッド
LUdecomposition
も参照。
余因子¶
余因子に関係するメソッドを次に示す。
シグニチャー |
説明 |
---|---|
|
余因子行列を生成する。 |
|
指定成分の符号調整を施した余因子を計算する。 |
|
転置を施していない余因子行列を生成する。 |
|
指定成分の符号調整を施していない余因子を計算する。 |
|
元の行列から指定行と指定列を削った、一回り小さい行列を生成する。 |
これらのメソッドの実装は線形の依存関係がある。
まずメソッド adjugate
は cofactorMatrix
の転置行列を返す。その
cofactorMatrix
は単に cofactor
を成分ごとに計算して、その値を成分とする行列を生成する。メソッド cofactor
は対応する成分の minorEntry
の戻り値に、成分の添字から決まる符号を付けるだけだ。最後にメソッド minorEntry
は指定成分の minorMatrix
の行列式を単に返す。
対角行列¶
対角行列に関係するメソッドを次に示す。
シグニチャー |
説明 |
---|---|
|
対角化可能な行列を対角化する。 |
|
行列が対角行列かどうかを返す。 |
|
行列が対角化可能かどうかを返す。 |
行列
M
に対して \(D = P\inv M P\) とすると、メソッドdiagonalize
は(P, D)
を返す。対角化可能性の判定は、各固有値の多重度をチェックすることでなされる。この過程で行列オブジェクトのメンバーデータに中間生成物的なものが残る。これをクリアしたい場合に、キーワード引数
clear_subproducts=True
を指定する。ちなみに対角行列を生成する機能は関数
diag
である。
分解¶
行列の分解 (decomposition, factorization) に関係するメソッドを次に示す。
シグニチャー |
説明 |
---|---|
|
行列の Cholesky 分解を行う。 |
|
行列の LDL 分解を行う。 |
|
行列の LU 分解を行う。 |
|
行列の LU 分解を単純なアルゴリズムで実装したもの。 |
|
行列の LU 分解を fraction-free なアルゴリズムで実装したもの。 |
|
行列の QR 分解を行う。 |
Cholesky 分解は \({A = L L^\top}\) なる \(L\) を計算する。与える行列が正定値実対象行列である必要がある。
LDL 分解は \({A = L D L^\top}\) を満たす行列を計算してペア \({(L, D)}\) を返す。
これもまた与える行列が正定値実対象行列である必要がある。
行列
L
は下三角行列であり、対角成分はすべて 1 である。行列
D
は対角行列である。
LU 分解は \({A = LU}\) というものだ。
LUdecomposition
の戻り値は(L, U, p)
の形である。ここでp
は行の交換情報を表現する list である。なぜ行列ではなくリストの形式を返すのかがよくわからない。LUdecomposition_Simple
の戻り値は(A, p)
の形である。ただしA
はL
とU
の合いの子のような行列だ。LUdecompositionFF
は \({PA = L D\inv U}\) を満たす行列の組{(P, L, D, U)}
を返す。特徴は元の行列がある整域上のものであるとき、L
,D
,U
のいずれも同じ整域に属するというものだ。QR 分解は行列を \({A = QR}\) の形に分解するアルゴリズムだ。行列 \(Q\) は直交行列で、行列 \(R\) は上三角行列となる。
ソルバー¶
線形連立方程式を解くことに関係するメソッドを次に示す。基本的には \({A \mathbf{x} = \mathbf{b}}\) に対して、
\(A\) が行列オブジェクト自身、
\(\mathbf{x}\) が各メソッドの戻り値(の一部)、
\(\mathbf{b}\) が各メソッドの最初の引数
に相当する。
シグニチャー |
説明 |
---|---|
|
Cholesky 分解によるソルバー。 |
|
対角行列を係数とする方程式を解く。 |
|
Gauss-Jordan 消去法によるソルバー。 |
|
LDL 分解によるソルバー。 |
|
LU 分解によるソルバー。 |
|
QR 分解によるソルバー。 |
|
Moore-Penrose の擬逆行列によるソルバー。 |
|
下三角行列を係数とする方程式を解く。 |
|
上三角行列を係数とする方程式を解く。 |
cholesky_solve
,LDLsolve
は係数(自身の行列)が対称行列であるか、行数が列数よりも大きい行列であればなんとか動く。diagonal_solve
は対角成分にゼロがあってはならない(解を得るときの除算の分母だから)。gauss_jordan_solve
は連立方程式の条件が不足して解が不定個求まる場合に適宜パラメーターを導入する。解の行列に加えて、このパラメーターを別途行列にまとめて同時に返す。LUsolve
の利用する分解メソッドはLUdecomposition_Simple
である。その後に前進代入と後退代入を行って解となる行列を返す。対称行列向け。
QRsolve
はQRdecomposition
の結果から後退代入だけで解が得られる。これは教育目的かつシンボル含みの行列用のメソッドとのこと。
逆行列¶
逆行列に関係するメソッドを次に示す。数値計算の観点から言えば、逆行列の計算は相当なコストがかかるため、なるべく避けるのが原則だ。可能な限り先述した分解アルゴリズムやソルバーを利用するのが普通だ。
シグニチャー |
説明 |
---|---|
|
逆行列を計算する。 |
|
法を |
|
Moore-Penrose の擬逆行列を計算する。 |
inv
ではキーワード引数method
で行列式を計算するアルゴリズムを選択できる。'GE'
: Gauss の消去法。デフォルトのアルゴリズム。'LU'
: LU 分解。'ADJ'
: 余因子行列と行列式を用いて逆行列を得る。
面白いのは
inv_mod
だろう。整数論的処理が一部入っていて、オイラー関数、余因子行列、行列式を組み合わせて所望の行列を生成する。
Berkowitz のアルゴリズム¶
Berkowitz のアルゴリズムと、それに関係するメソッドを次に示す。
シグニチャー |
説明 |
---|---|
|
Berkowitz のアルゴリズムを実装する。 |
|
Berkowitz のアルゴリズムにより行列式を計算する。 |
|
Berkowitz のアルゴリズムにより主小行列を計算する。 |
|
Berkowitz のアルゴリズムにより特性多項式を求める。 |
|
Berkowitz のアルゴリズムにより固有値を計算する。 |
まずメソッド berkowitz
の中身について。この記事 のアルゴリズムを実装している。
Berkowitz のアルゴリズムを単純に説明すると、正方行列の対角線に沿って、サイズの小さい順にミニ正方行列を取る。各ミニ正方行列の特性多項式を求めていくのだが、あるステップのミニ正方行列の特性多項式を得るのに、その直前に求めたサイズが一回り小さいミニ正方行列の計算結果を利用できるというものだ。
固有多項式がわければ行列式、行列因子、固有値等が芋づる式にわかるので、クラス
MatrixBase
のメソッドの大部分がこのアルゴリズムに頼っている。
特異値¶
特異値に関するメソッドを記す。固有値に関するメソッドも参照。
シグニチャー |
説明 |
---|---|
|
行列の条件数、つまり最大特異値と最小特異値の比を |
返す。 |
行列の特異値をすべて返す。 |
singular_values
はすべての特異値をlist
に詰めて返す。これらの値が大きい順にソートされている。
微積分¶
微積分に関するメソッドを次の表に記す。実装はすべて想像通りになっている。
シグニチャー |
説明 |
---|---|
|
各成分を微分して得られる行列を返す。 |
|
各成分を積分して得られる行列を返す。 |
|
各成分に対して極限をとった行列を返す。 |
NumPy インターフェイス¶
モジュール sympy.matrices.dense
には SymPy の行列オブジェクトを NumPy の
numpy.array
オブジェクトにコンバートするための関数が少々ある。
- 関数
list2numpy(l, dtype=object)
Python の
list
オブジェクトをnumpy.array
オブジェクトにコンバートする。実は SymPy は関係ない。
キーワード引数
dtype=object
が存在するということは、リストの要素の型を完全に無視できることを示唆している。
- 関数
matrix2numpy(m, dtype=object)
SymPy の行列オブジェクトを
numpy.array
オブジェクトにコンバートする。実装を説明すると、この関数はまず行列のサイズ
m.shape
と同じサイズの空のnumpy.array
を生成する。次にm
の各要素を対応する成分にセットして戻すというものだ。
- 関数
symarray(prefix, shape)
説明するよりも動きを示したほうが早い。
In [1]: symarray('ppppp', (3, 3)) Out[1]: array([[ppppp_0_0, ppppp_0_1, ppppp_0_2], [ppppp_1_0, ppppp_1_1, ppppp_1_2], [ppppp_2_0, ppppp_2_1, ppppp_2_2]], dtype=object)
実際の戻り値は
numpy.array
オブジェクト。行列の各要素は SymPy の
Symbol
オブジェクト。オブジェクトのname
が出力に表れている。
演習¶
Berkowitz のアルゴリズムを検証する¶
シンボルベースの三次正方行列を定義して、メソッド berkowitz
を呼び出して戻り値を得る。この戻り値から行列のさまざまな属性が得られることを見よう。
In [1]: M = Matrix(3, 3, symbols('a:3:3')); M
Out[1]:
Matrix([[a00, a01, a02],
[a10, a11, a12],
[a20, a21, a22]])
In [2]: Mberk = M.berkowitz(); Mberk
Out[2]: ((1,),
(1,
-a00),
(1,
-a00 - a11,
a00*a11 - a01*a10),
(1,
-a00 - a11 - a22,
a00*a11 - a01*a10 - a02*a20 - a12*a21 - a22*(-a00 - a11),
-a20*(a00*a02 + a01*a12) - a21*(a02*a10 + a11*a12) - a22*(a00*a11 - a01*a10) + (-a00 - a11)*(-a02*a20 - a12*a21)))
In [3]: Mcharpoly = M.charpoly().coeffs(); Mcharpoly
Out[3]: [1,
-a00 - a11 - a22,
a00*a11 + a00*a22 - a01*a10 - a02*a20 + a11*a22- a12*a21,
-a00*a11*a22 + a00*a12*a21 + a01*a10*a22 - a01*a12*a20 - a02*a10*a21+ a02*a11*a20]
In [4]: all(simplify(i - j) == 0 for i, j in zip(Mcharpoly, Mberk[-1]))
Out[4]: True
In [5]: simplify(M.cofactor(2, 2) - Mberk[2][-1])
Out[5]: 0
In [6]: simplify(M.det() + Mberk[-1][-1])
Out[6]: 0
[1] 全成分を記号とする三次正方行列
M
を定義する。[2] メソッド
berkowitz
の戻り値を確認する。[3][4]
M
の特性多項式の係数を出力する。これは四次多項式の係数を降べきの順に並べたlist
オブジェクトとして得られる。次にこの係数と
berkowitz
の戻り値のtuple
オブジェクトの末尾の要素とを比較して、両者が一致することを見る。[5]
M
の対角成分末端(行列の右下)要素の余因子は``berkowitz`` の戻り値の 2 ステップ目Mberk[2]
の末尾の要素に等しい。これは同ステップのミニ正方行列の行列式でもある。[6]
M
の行列式はberkowitz
の戻り値の末尾の末尾に符号を考慮したものである。符号の決まり方は、特性多項式の次数の偶奇性による。
最後に固有値が特性多項式の解であることを見せて締めたいところだが、紙幅の都合上、どうしても長い数式をきれいに表示できないため、割愛する。
LU 分解再挑戦¶
線形方程式を解く で LU 分解した行列を SymPy で分解すると結果が異なるのではないか。
In [1]: A = Matrix([[1, 2, 2], [2, 5, 6], [3, 8, 12]])
In [1]: L, U, p = A.LUdecomposition()
In [2]: L
Out[2]:
Matrix([
[1, 0, 0],
[2, 1, 0],
[3, 2, 1]])
In [3]: U
Out[3]:
Matrix([
[1, 2, 2],
[0, 1, 2],
[0, 0, 2]])
In [4]: p
Out[4]: []
In [5]: L * U
Out[5]:
Matrix([
[1, 2, 2],
[2, 5, 6],
[3, 8, 12]])
[2][3] 期待しているようなきれいな三角行列が得られている。
[4] 行の入れ替えはない。
行列の Jordan 分解¶
複素数体上で考えれば、任意の正方行列 \(A\) には次の条件を満たす正方行列 \(S\) と \(N\) が(一意的に)存在する:
\(A = S + N\)
\(NS = SN\)
\(S\) は対角化可能行列である。
\(N\) は冪零行列である。
\(A\) の Jordan 標準形を \({J = P\inv A P}\) とおくと、Jordan 行列が対角行列と冪零行列の和であることから、\({A = PJP\inv}\) は対角化可能行列と冪零行列の和になっていることがわかる。そこで Jordan 分解を次のように求めることができる。
In [1]: A = Matrix([[1, 0, -1],
[2, 1, 0],
[1, 0, 3]])
In [2]: P, J = A.jordan_form()
In [3]: S = P * Matrix.diag(*J.diagonal()) * P.inv(); S
Out[3]:
Matrix([
[2, 0, 0],
[4, 1, 2],
[0, 0, 2]])
In [4]: N = A - S; N
Out[4]:
Matrix([
[-1, 0, -1],
[-2, 0, -2],
[ 1, 0, 1]])
In [5]: S.is_diagonalizable(), N.is_nilpotent()
Out[5]: (True, True)
In [6]: N * S == S * N
Out[6]: True
[1] \({\displaystyle A = \begin{pmatrix}1 & 0 & -1\\2 & 1 & 0\\1 & 0 & 3\end{pmatrix}}\) を Jordan 分解しよう。
[2] メソッド呼び出しだけで変換行列と Jordan 行列が同時に得られる。
[3] \(J\) の対角成分だけを抽出した行列を変換行列 \(P, P\inv\) で挟んで \(S\) を得る。
[4] 冪零行列 \(N\) を得る。
[5][6] \(S\) と \(N\) に関する条件を確認する。
関連ノート¶
- Tentative NumPy Tutorial ノート
NumPy の array と SymPy の
MatrixBase
には共通する機能が多数ある。- 線形方程式を解く
SciPy の機能で LU 分解をする例を記した。