web-dev-qa-db-fra.com

Numpy ‘smart’ matrice symétrique

Existe-t-il une matrice symétrique intelligente et économe en espace dans numpy qui remplisse automatiquement (et de manière transparente) la position à [j][i] lorsque [i][j] est écrit?

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

Un Hermitien automatique serait aussi Nice, bien que je n’ai pas besoin de ça au moment de la rédaction.

63
Debilski

Si vous pouvez vous permettre de symétriser la matrice juste avant de faire des calculs, procédez comme suit:

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

Cela fonctionne avec des hypothèses raisonnables (par exemple, ne pas utiliser à la fois a[0, 1] = 42 et le a[1, 0] = 123 contradictoire avant d'exécuter symmetrize).

Si vous avez vraiment besoin d'une symétrisation transparente, vous pouvez envisager de sous-classer numpy.ndarray et de simplement redéfinir __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!

(ou l'équivalent avec des matrices au lieu de tableaux, selon vos besoins). Cette approche gère même des affectations plus complexes, telles que a[:, 1] = -1, qui définit correctement les éléments a[1, :].

Notez que Python 3 a supprimé la possibilité d'écrire def …(…, (i, j),…); le code doit donc être légèrement adapté avant de s'exécuter avec Python 3: def __setitem__(self, indexes, value): (i, j) = indexes

66
Eric O Lebigot

La question plus générale du traitement optimal des matrices symétriques dans Numpy m'a également interpellée.

Après examen, je pense que la réponse est probablement que numpy est quelque peu contraint par la structure de mémoire supportée par les routines BLAS sous-jacentes pour les matrices symétriques.

Bien que certaines routines BLAS exploitent la symétrie pour accélérer les calculs sur les matrices symétriques, elles utilisent toujours la même structure de mémoire qu'une matrice complète, c'est-à-dire n^2 space plutôt que n(n+1)/2. Ils se contentent de se faire dire que la matrice est symétrique et de n'utiliser que les valeurs du triangle supérieur ou inférieur.

Certaines des routines scipy.linalg acceptent des indicateurs (tels que sym_pos=True sur linalg.solve) qui sont transmis aux routines BLAS, bien que Nice soit mieux supporté par numpy, en particulier des wrappers pour des routines comme DSYRK une matrice de Gram pour être calculée un peu plus rapide que le point (MT, M).

(Peut-être semble-t-il nerveux de s'inquiéter de l'optimisation pour un facteur 2x constant sur le temps et/ou l'espace, mais cela peut faire toute la différence en ce qui concerne l'ampleur du problème que vous pouvez gérer sur une seule machine ...)

20
Matt

Il existe un certain nombre de méthodes bien connues pour stocker des matrices symétriques afin qu'elles n'aient pas besoin d'occuper n ^ 2 éléments de stockage. De plus, il est possible de réécrire les opérations communes pour accéder à ces moyens de stockage révisés. Les travaux définitifs sont Golub et Van Loan, Matrix Computations, 3e édition de 1996, Johns Hopkins University Press, sections 1.27-1.2.9. Par exemple, en les citant depuis le formulaire (1.2.2), dans une matrice symétrique, il suffit de stocker A = [a_{i,j} ] pouri >= j. Ensuite, en supposant que le vecteur contenant la matrice est noté V et que A est n-par-n, mettez a_{i,j} dans 

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

Cela suppose une indexation. 

Golub et Van Loan proposent un algorithme 1.2.3 qui montre comment accéder à un tel V stocké pour calculer y = V x + y.

Golub et Van Loan fournissent également un moyen de stocker une matrice sous forme dominante diagonale. Cela ne permet pas d'économiser de l'espace de stockage, mais permet un accès immédiat à certains autres types d'opérations. 

7
Jan Galkowski

Ceci est simple python et pas numpy, mais je viens de lancer une routine pour remplir Une matrice symétrique (et un programme de test pour s’assurer qu’elle est correcte):

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

Il est facile de renseigner pythoniquement [i][j] si [j][i] est rempli. La question du stockage est un peu plus intéressante. On peut augmenter la classe de tableaux numpy avec un attribut packed qui est utile à la fois pour enregistrer le stockage et pour lire ultérieurement les données.

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