Python evaluación perezosa numpy ndarray

Tengo una gran matriz 2D que me gustaría declarar una vez y, ocasionalmente, cambio algunos valores dependiendo de un parámetro, sin atravesar toda la matriz.

Para construir esta matriz, he subclasificado la clase nudy ndarray con dtype = object y le asigno a los elementos que quiero cambiar una función, p. :

def f(parameter):
     return parameter**2

for i in range(np.shape(A)[0]):
    A[i,i]=f
    for j in range(np.shape(A)[0]):
        A[i,j]=1.

Luego, he reemplazado el método __ getitem __ para que devuelva la evaluación de la función con un parámetro dado si es invocable; de ​​lo contrario, devuelva el valor en sí.

    def __getitem__(self, key):
        value = super(numpy.ndarray, self).__getitem__(key)
        if callable(value):
            return value(*self.args)
        else:
            return value

donde self.args fueron previamente dados a la instancia de myclass.

Sin embargo, necesito trabajar con matrices flotantes al final, y no puedo simplemente convertir esta matriz en una matriz dtype = float con esta técnica. También traté de usar vistas numpy, que tampoco funciona para dtype = object .

¿Tienes alguna mejor alternativa? ¿Debo anular el método de vista en lugar de getitem?

Edit I will maybe have to use Cython in the future, so if you have a solution involving e.g. C pointers, I am interested.

0
@BasSwinckels: puedo tener muchas f con muchos argumentos. La razón por la que elegí este enfoque es precisamente para facilitar la escritura de tales matrices, donde potencialmente puedo tener más variables.
agregado el autor Damlatien, fuente
@hpaulj: dok es muy interesante. Sin embargo, no puedo usarlo con dtype = object, como en el ejemplo que mostré arriba: github.com/scipy/scipy/issues/2528
agregado el autor Damlatien, fuente
@rth: La razón por la que necesito una evaluación diferida en lugar de acceder a la matriz con clave (incluso de manera eficiente) es que cada afectación puede estar relacionada con diferentes tipos de índices. Para el ejemplo anterior, solo configuro la diagonal para que sea variable. Por ejemplo, también pude haber afectado una fila (o algo más complicado) a otra función g.
agregado el autor Damlatien, fuente
Es un enfoque interesante, pero no estoy seguro de que las matrices numpy sean adecuadas para ello. En general, cuando trabajas con numpy, debes usar operaciones vectorizadas usando matrices o sectores completos, no elemento por acceso a elementos. Subclasificando ndarrays de la manera en que lo haces, esencialmente pierdes todas las ventajas de las operaciones numpy rápidas. Tal vez sea mejor simplemente crear su propia clase desde cero y guardar todo, en estructuras de pitón puras (listas, etc.). En cuanto al rendimiento, será comparable. ¿Por qué realmente necesitas una evaluación perezosa?
agregado el autor rth, fuente
¿Está familiarizado con scipy.sparse ? El formato dok es un diccionario, con la tupla (i, j) como claves. Eso y lil (lista de listas) son las 2 formas más rápidas de acceder/cambiar elementos seleccionados.
agregado el autor hpaulj, fuente
No estaba pensando en usar dok directamente, sino en usarlo como modelo para tu propia subclase de diccionario. Mejor aún, conviértalo en una subclase de diccionario predeterminada, con f (clave) como valor predeterminado.
agregado el autor hpaulj, fuente
¿Solo tiene una función única f ? Con argumentos constantes?
agregado el autor Bas Swinckels, fuente

1 Respuestas

En este caso, no tiene sentido vincular una función de transformación a cada índice de su matriz.

En cambio, un enfoque más eficiente sería definir una transformación, como una función, junto con un subconjunto de la matriz a la que se aplica. Aquí hay una implementación básica,

import numpy as np

class LazyEvaluation(object):
    def __init__(self):
        self.transforms = []

    def add_transform(self, function, selection=slice(None), args={}):
        self.transforms.append( (function, selection, args))

    def __call__(self, x):
        y = x.copy() 
        for function, selection, args in self.transforms:
            y[selection] = function(y[selection], **args)
        return y

que se puede usar de la siguiente manera:

x = np.ones((6, 6))*2

le = LazyEvaluation()
le.add_transform(lambda x: 0, [[3], [0]]) # equivalent to x[3,0]
le.add_transform(lambda x: x**2, (slice(4), slice(4,6)))  # equivalent to x[4,4:6]
le.add_transform(lambda x: -1,  np.diag_indices(x.shape[0], x.ndim), ) # setting the diagonal 
result =  le(x)
print(result)

que imprime,

array([[-1.,  2.,  2.,  2.,  4.,  4.],
       [ 2., -1.,  2.,  2.,  4.,  4.],
       [ 2.,  2., -1.,  2.,  4.,  4.],
       [ 0.,  2.,  2., -1.,  4.,  4.],
       [ 2.,  2.,  2.,  2., -1.,  2.],
       [ 2.,  2.,  2.,  2.,  2., -1.]])

De esta forma, puede admitir fácilmente todos los índices Numpy avanzados (elemento por acceso a los elementos, segmentación, indexación sofisticada, etc.), mientras que al mismo tiempo mantiene sus datos en una matriz con un tipo de datos nativo ( float , int , etc.) que es mucho más eficiente que usar dtype = 'object' .

0
agregado
Gracias, lo he implementado básicamente como una subclase de dict, y funciona de la forma que quiero. Sin embargo, solo como curiosidad, me gustaría saber si es posible realizar algo similar en C/C ++ con punteros de una manera mucho más elegante. Por ejemplo, se podría declarar una tabla de punteros (float) y, en el momento de la declaración, cada puntero dirigiría a cero o al resultado de una función, de modo que pueda actualizar la matriz simplemente llamando a una (o varias) funciones.
agregado el autor Damlatien, fuente
Pero para hacer eso, llamar a una función debería estar vinculado a un puntero en particular, y no estoy seguro de que sea factible. Espero haber sido lo suficientemente claro.
agregado el autor Damlatien, fuente