与えられた行列のヌル空間(Ax = 0の解空間)を見つけようとしています。 2つの例を見つけましたが、どちらも機能しないようです。さらに、私は彼らがそこに到達するために何をしているのか理解できないので、デバッグできません。誰かが私にこれを説明してくれるといいのですが。
ドキュメントページ( numpy.linalg.svd
、および numpy.compress
)は私には不透明です。私はこれを行列C = [A|0]
、縮小された行の階層形式を見つけ、行ごとに変数を解きます。これらの例では、それがどのように行われているかを追跡することができないようです。
助けてくれてありがとう!
これが wikipedia example と同じサンプルマトリックスです。
A = matrix([
[2,3,5],
[-4,2,3]
])
import scipy
from scipy import linalg, matrix
def null(A, eps=1e-15):
u, s, vh = scipy.linalg.svd(A)
null_mask = (s <= eps)
null_space = scipy.compress(null_mask, vh, axis=0)
return scipy.transpose(null_space)
試してみると、空の行列が返されます。
Python 2.6.6 (r266:84292, Sep 15 2010, 16:22:56)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import scipy
>>> from scipy import linalg, matrix
>>> def null(A, eps=1e-15):
... u, s, vh = scipy.linalg.svd(A)
... null_mask = (s <= eps)
... null_space = scipy.compress(null_mask, vh, axis=0)
... return scipy.transpose(null_space)
...
>>> A = matrix([
... [2,3,5],
... [-4,2,3]
... ])
>>>
>>> null(A)
array([], shape=(3, 0), dtype=float64)
>>>
私にとっては問題なく動作しているようです:
A = matrix([[2,3,5],[-4,2,3],[0,0,0]])
A * null(A)
>>> [[ 4.02455846e-16]
>>> [ 1.94289029e-16]
>>> [ 0.00000000e+00]]
Sympyはこれを簡単にします。
>>> from sympy import Matrix
>>> A = [[2, 3, 5], [-4, 2, 3], [0, 0, 0]]
>>> A = Matrix(A)
>>> A * A.nullspace()[0]
Matrix([
[0],
[0],
[0]])
>>> A.nullspace()
[Matrix([
[-1/16],
[-13/8],
[ 1]])]
行列A
のSVD分解を取得します。 s
は固有値のベクトルです。ほぼゼロの固有値に興味があります($ A * x =\lambda * x $ where $\abs(\ lambda)<\ epsilon $を参照)。これは、論理値のベクトルnull_mask
。
次に、リストvh
から、ほぼゼロの固有値に対応する固有ベクトルを抽出します。これは、まさに探しているものです。つまり、ヌル空間をまたぐ方法です。基本的には、行を抽出して結果を転置し、固有ベクトルを列とする行列を取得します。
より高速だが数値的に安定していない方法は、 ランクを明らかにするQR分解 を使用することです。たとえば、 scipy.linalg.qr
with pivoting=True
:
import numpy as np
from scipy.linalg import qr
def qr_null(A, tol=None):
Q, R, P = qr(A.T, mode='full', pivoting=True)
tol = np.finfo(R.dtype).eps if tol is None else tol
rnk = min(A.shape) - np.abs(np.diag(R))[::-1].searchsorted(tol)
return Q[:, rnk:].conj()
例えば:
A = np.array([[ 2, 3, 5],
[-4, 2, 3],
[ 0, 0, 0]])
Z = qr_null(A)
print(A.dot(Z))
#[[ 4.44089210e-16]
# [ 6.66133815e-16]
# [ 0.00000000e+00]]
あなたの方法はほぼ正しいです。問題は、関数scipy.linalg.svdによって返されるsの形状が(K、)であり、K = min(M、N)であるということです。したがって、例では、sには2つのエントリしかありません(最初の2つの特異ベクトルの特異値)。次のnull関数の修正により、任意のサイズの行列で機能するようになります。
import scipy
import numpy as np
from scipy import linalg, matrix
def null(A, eps=1e-12):
... u, s, vh = scipy.linalg.svd(A)
... padding = max(0,np.shape(A)[1]-np.shape(s)[0])
... null_mask = np.concatenate(((s <= eps), np.ones((padding,),dtype=bool)),axis=0)
... null_space = scipy.compress(null_mask, vh, axis=0)
... return scipy.transpose(null_space)
A = matrix([[2,3,5],[-4,2,3]])
print A*null(A)
>>>[[ 4.44089210e-16]
>>> [ 6.66133815e-16]]
A = matrix([[1,0,1,0],[0,1,0,0],[0,0,0,0],[0,0,0,0]])
print null(A)
>>>[[ 0. -0.70710678]
>>> [ 0. 0. ]
>>> [ 0. 0.70710678]
>>> [ 1. 0. ]]
print A*null(A)
>>>[[ 0. 0.]
>>> [ 0. 0.]
>>> [ 0. 0.]
>>> [ 0. 0.]]
昨年(2017)の時点で、scipyはnull_space
モジュールに組み込みのscipy.linalg
メソッドを持っています( docs )。
implementation は標準のSVD分解に従い、古いバージョンのscipyを使用していて、自分で実装する必要がある場合は非常に小さくなります(以下を参照)。ただし、最新の場合は、そこにあります。
def null_space(A, rcond=None):
u, s, vh = svd(A, full_matrices=True)
M, N = u.shape[0], vh.shape[1]
if rcond is None:
rcond = numpy.finfo(s.dtype).eps * max(M, N)
tol = numpy.amax(s) * rcond
num = numpy.sum(s > tol, dtype=int)
Q = vh[num:,:].T.conj()
return Q