web-dev-qa-db-de.com

Wackelige "intelligente" symmetrische Matrix

Gibt es eine intelligente und platzsparende symmetrische Matrix in numpy, die die Position an [j][i] automatisch (und transparent) ausfüllt, wenn [i][j] geschrieben wird?

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

Ein automatischer Hermitianer wäre auch Nizza, obwohl ich das zum Zeitpunkt des Schreibens nicht brauche.

63
Debilski

Wenn Sie es sich leisten können, die Matrix unmittelbar vor den Berechnungen zu symmetrisieren, sollte Folgendes einigermaßen schnell sein:

def symmetrize(a):
    return a + a.T - numpy.diag(a.diagonal())

Dies funktioniert unter vernünftigen Annahmen (z. B. dass Sie sowohl a[0, 1] = 42 als auch den widersprüchlichen a[1, 0] = 123 nicht ausführen, bevor Sie symmetrize ausführen).

Wenn Sie wirklich eine transparente Symmetrisierung benötigen, können Sie die Unterklasse numpy.ndarray in Betracht ziehen und einfach __setitem__ neu definieren:

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!

(oder das Äquivalent mit Matrizen anstelle von Arrays, abhängig von Ihren Anforderungen). Dieser Ansatz behandelt sogar kompliziertere Zuweisungen wie a[:, 1] = -1, wodurch a[1, :]-Elemente korrekt festgelegt werden.

Beachten Sie, dass Python 3 die Möglichkeit des Schreibens von def …(…, (i, j),…) entfernt hat. Der Code muss daher vor der Ausführung mit Python 3 geringfügig angepasst werden: def __setitem__(self, indexes, value): (i, j) = indexes

66
Eric O Lebigot

Das allgemeinere Problem der optimalen Behandlung symmetrischer Matrizen in Numpy hat mich auch gestört.

Nach eingehender Betrachtung denke ich, dass die Antwort wahrscheinlich ist, dass numpy durch das Speicherlayout, das von den zugrundeliegenden BLAS-Routinen für symmetrische Matrizen unterstützt wird, etwas eingeschränkt wird.

Während einige BLAS-Routinen die Symmetrie nutzen, um Berechnungen an symmetrischen Matrizen zu beschleunigen, verwenden sie immer noch dieselbe Speicherstruktur wie eine vollständige Matrix, d. H. n^2-Raum statt n(n+1)/2. Sie erfahren nur, dass die Matrix symmetrisch ist und nur die Werte im oberen oder im unteren Dreieck zu verwenden.

Einige der scipy.linalg-Routinen akzeptieren Flags (wie sym_pos=True auf linalg.solve), die an BLAS-Routinen weitergeleitet werden, obwohl in numpy mehr Unterstützung dafür Nice wäre, insbesondere Wrapper für Routinen wie DSYRK (symmetrischer Rang k-Update), die dies zulassen würden Eine zu berechnende Gram-Matrix ist ein bisschen schneller als ein Punkt (MT, M).

(Es mag ein wenig pingelig sein, wenn man sich Sorgen macht, ob man für einen doppelten konstanten Faktor in Bezug auf Zeit und/oder Speicherplatz optimiert, aber es kann einen Unterschied machen, wie groß ein Problem ist, das man auf einer einzelnen Maschine bewältigen kann ...)

20
Matt

Es gibt eine Reihe von bekannten Möglichkeiten, symmetrische Matrizen zu speichern, so dass sie nicht n 2 Speicherelemente belegen müssen. Darüber hinaus ist es möglich, allgemeine Vorgänge umzuschreiben, um auf diese überarbeiteten Speichermittel zuzugreifen. Die endgültige Arbeit ist Golub und Van Loan, Matrix Computations , 3. Auflage 1996, Johns Hopkins University Press, Abschnitte 1.27-1.2.9. Wenn Sie sie beispielsweise aus Formular (1.2.2) zitieren, müssen Sie in einer symmetrischen Matrix nur A = [a_{i,j} ] Für i >= j Speichern. Angenommen, der Vektor, der die Matrix hält, ist mit V bezeichnet, und A ist n-mal-n, geben Sie a_{i,j} Ein

V[(j-1)n - j(j-1)/2 + i]

Dies setzt eine 1-Indizierung voraus.

Golub und Van Loan bieten einen Algorithmus 1.2.3 an, der zeigt, wie auf ein solches gespeichertes V zugegriffen werden kann, um y = V x + y Zu berechnen.

Golub und Van Loan bieten auch die Möglichkeit, eine Matrix in diagonal dominanter Form zu speichern. Dies spart keinen Speicherplatz, unterstützt jedoch den sofortigen Zugriff auf bestimmte andere Arten von Vorgängen.

7
Jan Galkowski

Dies ist normaler Python und nicht numpy, aber ich habe einfach eine Routine geworfen, um eine symmetrische Matrix zu füllen (und ein Testprogramm, um sicherzustellen, dass sie korrekt ist):

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
1
Davidka

Es ist trivial, pythonisch [i][j] auszufüllen, wenn [j][i] eingetragen ist. Die Speicherfrage ist etwas interessanter. Man kann die Array-Klasse numpy mit einem packed-Attribut erweitern, das sowohl zum Speichern von Speicher als auch zum späteren Lesen der Daten nützlich ist.

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)

`` `

0
Charles F