Compte tenu des coefficients de séries de Fourier a[n]
et b[n]
(par cosinus et sinus respectivement) d'une fonction par période T
et t
un intervalle également espacés le code suivant permettra d'évaluer la somme partielle pour tous les points dans l'intervalle t
(a
, b
, t
sont tous des tableaux numpy
). Il est précisé que len (t) <> len (a). Ma question est: Est-ce que cette boucle for peut être vectorisée?Comment vectoriser série de Fourier somme partielle en numpy
Répondre
est ici une approche vectorisé utilisant broadcasting
pour créer la version tableau 2D
de cosinus/entrée sinus: 2*pi*n*t/T
puis en utilisant matrix-multiplication
avec np.dot
pour le sum-reduction
-
r = np.arange(1,len(a))
S = 2*np.pi*r[:,None]*t/T
cS = np.cos(S)
sS = np.sin(S)
out = a[1:].dot(cS) - b[1:].dot(sS) + a[0]
plus poussée de la performance
Pour plus de boost, nous pouvons utiliser numexpr
module pour calculer ces étapes trignométriques -
import numexpr as ne
cS = ne.evaluate('cos(S)')
sS = ne.evaluate('sin(S)')
Test d'exécution -
approches -
def original_app(t,a,b,T):
yn=np.ones(len(t))*a[0]
for n in range(1,len(a)):
yn=yn+(a[n]*np.cos(2*np.pi*n*t/T)-b[n]*np.sin(2*np.pi*n*t/T))
return yn
def vectorized_app(t,a,b,T):
r = np.arange(1,len(a))
S = (2*np.pi/T)*r[:,None]*t
cS = np.cos(S)
sS = np.sin(S)
return a[1:].dot(cS) - b[1:].dot(sS) + a[0]
def vectorized_app_v2(t,a,b,T):
r = np.arange(1,len(a))
S = (2*np.pi/T)*r[:,None]*t
cS = ne.evaluate('cos(S)')
sS = ne.evaluate('sin(S)')
return a[1:].dot(cS) - b[1:].dot(sS) + a[0]
également, y compris la fonction PP
de poste de Panzer @ Paul.
synchronisations -
In [22]: # Setup inputs
...: n = 10000
...: t = np.random.randint(0,9,(n))
...: a = np.random.randint(0,9,(n))
...: b = np.random.randint(0,9,(n))
...: T = 3.45
...:
In [23]: print np.allclose(original_app(t,a,b,T), vectorized_app(t,a,b,T))
...: print np.allclose(original_app(t,a,b,T), vectorized_app_v2(t,a,b,T))
...: print np.allclose(original_app(t,a,b,T), PP(t,a,b,T))
...:
True
True
True
In [25]: %timeit original_app(t,a,b,T)
...: %timeit vectorized_app(t,a,b,T)
...: %timeit vectorized_app_v2(t,a,b,T)
...: %timeit PP(t,a,b,T)
...:
1 loops, best of 3: 6.49 s per loop
1 loops, best of 3: 6.24 s per loop
1 loops, best of 3: 1.54 s per loop
1 loops, best of 3: 1.96 s per loop
Merci. Vous avez supposé que a et t sont de même longueur, ce qui n'est pas ce que j'avais en tête; mais il est facile de s'en occuper. Je vais poster le code modifié. Je n'ai pas encore étudié les optimisations. J'ai également remarqué que vous avez changé l'étiquette de Fourier à FFT. Je ne suis pas d'accord. – NameOfTheRose
@NameOfTheRose Eh bien, le code original avait 'pour n dans la plage (1, len (t)):', qui a ensuite été modifié pour utiliser 'len (a)'. Donc, je devais éditer la partie 'r' pour utiliser' len (a) 'à la place. Edité en conséquence. – Divakar
Mon erreur alors, je m'en excuse. Comme vous avez déjà édité votre message, il n'y a aucune raison de poster le code modifié moi-même, c'est une modification très simple de toute façon. – NameOfTheRose
ne peut pas battre numexpr, mais si ce n'est pas disponible, nous pouvons économiser sur les transcendantaux (tests et le code étalonnage fortement basé sur @ code de Divakar au cas où vous ne l'avez pas remarqué; -)):
import numpy as np
from timeit import timeit
def PP(t,a,b,T):
CS = np.empty((len(t), len(a)-1), np.complex)
CS[...] = np.exp(2j*np.pi*(t[:, None])/T)
np.cumprod(CS, axis=-1, out=CS)
return a[1:].dot(CS.T.real) - b[1:].dot(CS.T.imag) + a[0]
def original_app(t,a,b,T):
yn=np.ones(len(t))*a[0]
for n in range(1,len(a)):
yn=yn+(a[n]*np.cos(2*np.pi*n*t/T)-b[n]*np.sin(2*np.pi*n*t/T))
return yn
def vectorized_app(t,a,b,T):
r = np.arange(1,len(a))
S = 2*np.pi*r[:,None]*t/T
cS = np.cos(S)
sS = np.sin(S)
return a[1:].dot(cS) - b[1:].dot(sS) + a[0]
n = 1000
t = 2000
t = np.random.randint(0,9,(t))
a = np.random.randint(0,9,(n))
b = np.random.randint(0,9,(n))
T = 3.45
print(np.allclose(original_app(t,a,b,T), vectorized_app(t,a,b,T)))
print(np.allclose(original_app(t,a,b,T), PP(t,a,b,T)))
print('{:18s} {:9.6f}'.format('orig', timeit(lambda: original_app(t,a,b,T), number=10)/10))
print('{:18s} {:9.6f}'.format('Divakar no numexpr', timeit(lambda: vectorized_app(t,a,b,T), number=10)/10))
print('{:18s} {:9.6f}'.format('PP', timeit(lambda: PP(t,a,b,T), number=10)/10))
Prints:
True
True
orig 0.166903
Divakar no numexpr 0.179617
PP 0.060817
btw. si delta t
divise T
on peut potentiellement économiser plus, ou même exécuter le plein fft et jeter ce qui est trop.
Votre commentaire sur transcendentals me fait espérer que Numpy obtient ['sincos'] (https://github.com/numpy/numpy/issues/2626) bientôt! –
@AhmedFasih intéressant. Merci d'avoir partagé le lien. Je ne savais même pas que «sin» et «cos» étaient des instructions machine ... –
Une pensée intelligente avec cette création de tableau complexe! – Divakar
Ce n'est pas vraiment une autre réponse mais un commentaire sur celui de @Paul Panzer, écrit en réponse car j'avais besoin de poster du code. S'il y a un moyen d'afficher du code correctement formaté dans un commentaire, veuillez le conseiller.
Inspiré par Panzer idée cumprod
@ Paul, je suis venu avec les éléments suivants:
an = ones((len(a)-1,len(te)))*2j*pi*te/T
CS = exp(cumsum(an,axis=0))
out = (a[1:].dot(CS.real) - b[1:].dot(CS.imag)) + a[0]
Bien qu'il semble bien vectorisé et produit des résultats corrects, sa performance est misérable. Il est non seulement beaucoup plus lent que le cumprod
, ce qui est attendu comme len(a)-1
exponentiations plus sont faites, mais 50% plus lent que la version originale nonvectorisée. Quelle est la cause de cette mauvaise performance?
Ma conjecture serait 1) comme vous le suggérez, les différents nombres d'appels 'exp'. Veuillez noter que la différence est de l'ordre 'len (a) x len (t)' car le nombre d'éléments de 'an' est le produit, pas la somme de ses dimensions. 2) disposition de la mémoire: dans votre code original semi-vectorisé, toutes les variables sont probablement contiguës en mémoire et donc susceptibles de bénéficier d'un code optimisé que les tableaux entrelacés '.real' et' .imag' ne peuvent pas utiliser. –
@Paul Panzer, merci, mon intuition (naïve) était une performance similaire à la 1ère version de Divakar. Êtes-vous toujours surpris à plusieurs reprises par la performance de numpy? – NameOfTheRose
Plus impressionné que surpris. Ils le rendent vraiment meilleur à chaque sortie. Mais si tu veux dire, je reçois parfois le "ça devrait être plus rapide que ça", je me sens mal? Oui, en fait assez souvent. –
Typo dans votre exemple, vous voulez certainement dire 'len (a)'? –
@Ahmed Fasih, c'est correct bien sûr, corrigé, merci. – NameOfTheRose
Pourquoi n'utilisez-vous pas simplement la transformée de Fourier inverse? Si vous voulez interpoler, vous pouvez remplir les hautes fréquences avec des zéros. – Dirklinux