Numpyに[j][i]
いつ [i][j]
に書き込まれますか?
import numpy
a = numpy.symmetric((3, 3))
a[0][1] = 1
a[1][0] == a[0][1]
# True
print(a)
# [[0 1 0], [1 0 0], [0 0 0]]
assert numpy.all(a == a.T) # for any symmetric matrix
自動エルミートもニースになりますが、執筆時にはそれは必要ありません。
計算を行う直前に行列を対称化する余裕がある場合、次の操作はかなり高速です。
_def symmetrize(a):
return a + a.T - numpy.diag(a.diagonal())
_
これは合理的な仮定の下で機能します(symmetrize
を実行する前に_a[0, 1] = 42
_と矛盾する_a[1, 0] = 123
_の両方を行わないなど)。
透過的な対称化が本当に必要な場合は、numpy.ndarrayをサブクラス化して、単に___setitem__
_を再定義することを検討できます。
_class SymNDArray(numpy.ndarray):
def __setitem__(self, (i, j), value):
super(SymNDArray, self).__setitem__((i, j), value)
super(SymNDArray, self).__setitem__((j, i), value)
def symarray(input_array):
"""
Returns a symmetrized version of the array-like input_array.
Further assignments to the array are automatically symmetrized.
"""
return symmetrize(numpy.asarray(input_array)).view(SymNDArray)
# Example:
a = symarray(numpy.zeros((3, 3)))
a[0, 1] = 42
print a # a[1, 0] == 42 too!
_
(または、ニーズに応じて、配列の代わりに行列を使用した同等のもの)。このアプローチは、_a[:, 1] = -1
_要素を正しく設定する_a[1, :]
_のような、より複雑な割り当ても処理します。
Python 3はdef …(…, (i, j),…)
を記述する可能性を削除したため、Python 3で実行する前にコードを少し調整する必要があることに注意してください:def __setitem__(self, indexes, value): (i, j) = indexes
…
Numpyの対称行列の最適な処理のより一般的な問題も私を悩ませました。
それを調べた後、おそらく答えはおそらく、numpyが対称行列の基礎となるBLASルーチンによってサポートされているメモリレイアウトによっていくらか制約されていると思うと思います。
一部のBLASルーチンは対称行列を使用して対称行列の計算を高速化しますが、完全な行列と同じメモリ構造、つまりn(n+1)/2
ではなくn^2
スペースを使用します。マトリックスが対称であり、上三角または下三角の値のみを使用するように言われます。
scipy.linalg
ルーチンの一部は、BLASルーチンに渡されるフラグ(sym_pos=True
のlinalg.solve
など)を受け入れますが、numpyでこれをさらにサポートすると、特にルーチンのラッパーになります。 DSYRK(対称ランクk更新)のように、ドット(MT、M)よりもかなり高速にグラム行列を計算できます。
(時間および/またはスペースの2倍の定数係数の最適化を心配するのは難しいと思われるかもしれませんが、1台のマシンで管理できる問題の大きさのしきい値に差をつけることができます...)
対称行列を格納するための多くのよく知られた方法があるので、それらはn ^ 2個のストレージ要素を占有する必要がありません。さらに、これらの改訂されたストレージ手段にアクセスするために、一般的な操作を書き換えることも可能です。決定的な仕事は、ゴラブとヴァンローン、Matrix Computations、第3版1996、ジョンズホプキンス大学出版局、セクション1.27-1.2.9です。たとえば、対称行列でフォーム(1.2.2)から引用する場合は、A = [a_{i,j} ]
fori >= j
のみを格納する必要があります。次に、行列を保持するvectorがVで示され、Aがn行n列であると仮定して、a_{i,j}
を
V[(j-1)n - j(j-1)/2 + i]
これは1インデックス付けを前提としています。
GolubとVan Loanは、y = V x + y
を計算するためにこのような保存されたVにアクセスする方法を示すアルゴリズム1.2.3を提供しています。
GolubとVan Loanは、行列を対角優勢形式で格納する方法も提供します。これはストレージを保存しませんが、特定の他の種類の操作のための準備ができたアクセスをサポートします。
これは単純なpythonであり、numpyではありませんが、対称行列を満たすためのルーチン(およびそれが正しいことを確認するためのテストプログラム)を一緒に投げました):
import random
# fill a symmetric matrix with costs (i.e. m[x][y] == m[y][x]
# For demonstration purposes, this routine connect each node to all the others
# Since a matrix stores the costs, numbers are used to represent the nodes
# so the row and column indices can represent nodes
def fillCostMatrix(dim): # square array of arrays
# Create zero matrix
new_square = [[0 for row in range(dim)] for col in range(dim)]
# fill in main diagonal
for v in range(0,dim):
new_square[v][v] = random.randrange(1,10)
# fill upper and lower triangles symmetrically by replicating diagonally
for v in range(1,dim):
iterations = dim - v
x = v
y = 0
while iterations > 0:
new_square[x][y] = new_square[y][x] = random.randrange(1,10)
x += 1
y += 1
iterations -= 1
return new_square
# sanity test
def test_symmetry(square):
dim = len(square[0])
isSymmetric = ''
for x in range(0, dim):
for y in range(0, dim):
if square[x][y] != square[y][x]:
isSymmetric = 'NOT'
print "Matrix is", isSymmetric, "symmetric"
def showSquare(square):
# Print out square matrix
columnHeader = ' '
for i in range(len(square)):
columnHeader += ' ' + str(i)
print columnHeader
i = 0;
for col in square:
print i, col # print row number and data
i += 1
def myMain(argv):
if len(argv) == 1:
nodeCount = 6
else:
try:
nodeCount = int(argv[1])
except:
print "argument must be numeric"
quit()
# keep nodeCount <= 9 to keep the cost matrix pretty
costMatrix = fillCostMatrix(nodeCount)
print "Cost Matrix"
showSquare(costMatrix)
test_symmetry(costMatrix) # sanity test
if __== "__main__":
import sys
myMain(sys.argv)
# vim:tabstop=8:shiftwidth=4:expandtab
[i][j]
が入力されている場合、[j][i]
をPythonで入力するのは簡単です。ストレージの質問はもう少し興味深いです。 numpy配列クラスをpacked
属性で補強することができます。これは、ストレージを保存し、後でデータを読み取るのに役立ちます。
class Sym(np.ndarray):
# wrapper class for numpy array for symmetric matrices. New attribute can pack matrix to optimize storage.
# Usage:
# If you have a symmetric matrix A as a shape (n,n) numpy ndarray, Sym(A).packed is a shape (n(n+1)/2,) numpy array
# that is a packed version of A. To convert it back, just wrap the flat list in Sym(). Note that Sym(Sym(A).packed)
def __new__(cls, input_array):
obj = np.asarray(input_array).view(cls)
if len(obj.shape) == 1:
l = obj.copy()
p = obj.copy()
m = int((np.sqrt(8 * len(obj) + 1) - 1) / 2)
sqrt_m = np.sqrt(m)
if np.isclose(sqrt_m, np.round(sqrt_m)):
A = np.zeros((m, m))
for i in range(m):
A[i, i:] = l[:(m-i)]
A[i:, i] = l[:(m-i)]
l = l[(m-i):]
obj = np.asarray(A).view(cls)
obj.packed = p
else:
raise ValueError('One dimensional input length must be a triangular number.')
Elif len(obj.shape) == 2:
if obj.shape[0] != obj.shape[1]:
raise ValueError('Two dimensional input must be a square matrix.')
packed_out = []
for i in range(obj.shape[0]):
packed_out.append(obj[i, i:])
obj.packed = np.concatenate(packed_out)
else:
raise ValueError('Input array must be 1 or 2 dimensional.')
return obj
def __array_finalize__(self, obj):
if obj is None: return
self.packed = getattr(obj, 'packed', None)
`` `