J'ai lu beaucoup de choses sur différentes techniques pour itérer sur les tableaux numpy récemment et il semble que le consensus soit de ne pas itérer du tout (par exemple, voir un commentaire ici). Il y a plusieurs questions similaires sur SO, mais mon cas est un peu différent car je dois combiner "l'itération" (ou ne pas itérer) et l'accès aux valeurs précédentes.
Disons qu'il y a N (N est petit, généralement 4, peut être jusqu'à 7) tableaux numpy 1-D de type float128
dans une liste X
, tous les tableaux sont de la même taille. Pour vous donner un petit aperçu, il s'agit de données d'intégration EDP, chaque tableau représente une fonction, et j'aimerais appliquer une section de Poincaré. Malheureusement, l'algorithme devrait être à la fois efficace en mémoire et en temps, car ces tableaux font parfois ~1 Go chacun, et il n'y a que 4 Go de RAM à bord (je viens de découvrir l'utilisation de memmap pour les tableaux numpy et envisage maintenant de les utiliser à la place des tableaux classiques).
Un de ces tableaux est utilisé pour "filtrer" les autres, donc je commence par secaxis = X.pop(idx)
. Maintenant je dois localiser les paires d'indices où (secaxis[i-1] > 0 et secaxis[i] < 0) ou (secaxis[i-1] < 0 et secaxis[i] > 0)
et ensuite appliquer des transformations algébriques simples aux autres tableaux, X
(et sauvegarder les résultats). Il est à noter que les données ne doivent pas être gaspillées lors de cette opération.
Il y a plusieurs façons de le faire, mais aucune ne me semble efficace (et assez élégante). L'une est une approche de type C, où vous itérez simplement dans une boucle for :
import array # mieux que les listes
res = [ array.array('d') for _ in X ]
for i in xrange(1,secaxis.size):
if condition: # voir ci-dessus
co = -secaxis[i-1]/secaxis[i]
for j in xrange(N):
res[j].append( (X[j][i-1] + co*X[j][i])/(1+co) )
C'est clairement très inefficace et en plus ce n'est pas une approche pythonique.
Une autre façon est d'utiliser numpy.nditer, mais je n'ai pas encore compris comment on accède à la valeur précédente, même s'il permet d'itérer sur plusieurs tableaux en même temps :
# sans secaxis = X.pop(idx)
it = numpy.nditer(X)
for vec in it:
# vec[idx] est la valeur actuelle, comment obtenir la précédente (ou la suivante) ?
La troisième possibilité est de d'abord trouver les indices recherchés avec des tranches numpy efficaces, puis de les utiliser pour des multiplications/additions en vrac. Je préfère celle-ci pour l'instant :
res = []
inds, = numpy.where((secaxis[:-1] < 0) * (secaxis[1:] > 0) +
(secaxis[:-1] > 0) * (secaxis[1:] < 0))
coefs = -secaxis[inds] / secaxis[inds+1] # tableau de coefficients
for f in X: # la boucle est faite seulement N-1 fois, c'est-à-dire, de 3 à 6
res.append( (f[inds] + coefs*f[inds+1]) / (1+coefs) )
Mais cela semble être fait en 7 + 2*(N - 1) passes, de plus, je ne suis pas sûr de l'adressage secaxis[inds]
(ce n'est pas du slicing et généralement il doit trouver tous les éléments par indices comme dans la première méthode, n'est-ce pas ?).
Enfin, j'ai également essayé d'utiliser itertools et cela a résulté en des structures monstrueuses et obscures, ce qui pourrait venir du fait que je ne suis pas très familier avec la programmation fonctionnelle :
def filt(x):
return (x[0] < 0 and x[1] > 0) or (x[0] > 0 and x[1] < 0)
import array
from itertools import izip, tee, ifilter
res = [ array.array('d') for _ in X ]
iters = [iter(x) for x in X] # N-1 itérateurs dans une liste
prev, curr = tee(izip(*iters)) # 2 itérateurs similaires, dont chacun
# est composé de N-1 itérateurs
next(curr, None) # l'un d'eux est maintenant pour la valeur actuelle
seciter = tee(iter(secaxis))
next(seciter[1], None)
for x in ifilter(filt, izip(seciter[0], seciter[1], prev, curr)):
co = - x[0]/x[1]
for r, p, c in zip(res, x[2], x[3]):
r.append( (p+co*c) / (1+co) )
Ce n'est pas seulement très laid, cela prend aussi un temps terrible pour s'exécuter.
Donc, j'ai les questions suivantes :
- De toutes ces méthodes, est-ce que la troisième est effectivement la meilleure ? Si c'est le cas, que peut-on faire pour améliorer la dernière ?
- Y a-t-il d'autres méthodes meilleures encore ?
- Par simple curiosité, y a-t-il un moyen de résoudre le problème en utilisant nditer ?
- Finalement, serais-je mieux loti en utilisant des versions de memmap des tableaux numpy, ou cela ralentirait-il probablement beaucoup les choses ? Peut-être devrais-je seulement charger le tableau
secaxis
en RAM, laisser les autres sur le disque et utiliser la troisième méthode ? - (question bonus) Une liste de N tableaux numpy 1-D de même longueur provient du chargement de N fichiers
.npy
dont les tailles ne sont pas connues à l'avance (mais N l'est). Serait-il plus efficace de lire un tableau, puis allouer de la mémoire pour un tableau numpy 2-D (légère surcharge mémoire ici) et lire les autres dans ce tableau 2-D ?