2012-01-15 12 views
8

Breve sintesi: Come si calcola rapidamente la convoluzione finita di due array?Manufatti di Riemann sum in scipy.signal.convolve

campioni discreti Descrizione del problema

Sto provando avere la convoluzione finita di due funzioni f (x), g (x) definito da

finite convolution

Per ottenere questo, ho preso delle funzioni e trasformati in campi di lunghezza steps:

xarray = [x * i/steps for i in range(steps)] 
farray = [f(x) for x in xarray] 
garray = [g(x) for x in xarray] 

ho quindi cercato di calcolare il conv oluzione usando la funzione scipy.signal.convolve. Questa funzione fornisce gli stessi risultati dell'algoritmo conv suggerito here. Tuttavia, i risultati differiscono notevolmente dalle soluzioni analitiche. La modifica dell'algoritmo conv per utilizzare la regola trapezoidale fornisce i risultati desiderati.

Per illustrare questo, ho lasciato

f(x) = exp(-x) 
g(x) = 2 * exp(-2 * x) 

i risultati sono:

enter image description here

Qui Riemann rappresenta una semplice somma di Riemann, trapezoidal è una versione modificata dell'algoritmo di Riemann di utilizzare la regola trapezoidale, scipy.signal.convolve è la funzione scipy e analytical è la convoluzione analitica.

Ora diamo g(x) = x^2 * exp(-x) ed i risultati diventano:

enter image description here

Qui 'ratio' è il rapporto tra i valori ottenuti SciPy ai valori analitici. Quanto sopra dimostra che il problema non può essere risolto rinormalizzando l'integrale.

La domanda

E 'possibile utilizzare la velocità di SciPy ma mantenere i migliori risultati di una regola trapezoidale o devo scrivere un estensione C per raggiungere i risultati desiderati?

Un esempio

Basta copiare e incollare il codice sottostante per vedere il problema che sto incontrando. I due risultati possono essere portati ad un accordo più stretto aumentando la variabile steps. Credo che il problema sia dovuto a artefatti tratti dalle somme di Riemann di destra, perché l'integrale è sovrastimato quando aumenta e si avvicina di nuovo alla soluzione analitica mentre diminuisce.

EDIT: Ora ho inserito l'algoritmo originale 2 come un confronto che dà gli stessi risultati come la funzione scipy.signal.convolve.

import numpy as np 
import scipy.signal as signal 
import matplotlib.pyplot as plt 
import math 

def convolveoriginal(x, y): 
    ''' 
    The original algorithm from http://www.physics.rutgers.edu/~masud/computing/WPark_recipes_in_python.html. 
    ''' 
    P, Q, N = len(x), len(y), len(x) + len(y) - 1 
    z = [] 
    for k in range(N): 
     t, lower, upper = 0, max(0, k - (Q - 1)), min(P - 1, k) 
     for i in range(lower, upper + 1): 
      t = t + x[i] * y[k - i] 
     z.append(t) 
    return np.array(z) #Modified to include conversion to numpy array 

def convolve(y1, y2, dx = None): 
    ''' 
    Compute the finite convolution of two signals of equal length. 
    @param y1: First signal. 
    @param y2: Second signal. 
    @param dx: [optional] Integration step width. 
    @note: Based on the algorithm at http://www.physics.rutgers.edu/~masud/computing/WPark_recipes_in_python.html. 
    ''' 
    P = len(y1) #Determine the length of the signal 
    z = [] #Create a list of convolution values 
    for k in range(P): 
     t = 0 
     lower = max(0, k - (P - 1)) 
     upper = min(P - 1, k) 
     for i in range(lower, upper): 
      t += (y1[i] * y2[k - i] + y1[i + 1] * y2[k - (i + 1)])/2 
     z.append(t) 
    z = np.array(z) #Convert to a numpy array 
    if dx != None: #Is a step width specified? 
     z *= dx 
    return z 

steps = 50 #Number of integration steps 
maxtime = 5 #Maximum time 
dt = float(maxtime)/steps #Obtain the width of a time step 
time = [dt * i for i in range (steps)] #Create an array of times 
exp1 = [math.exp(-t) for t in time] #Create an array of function values 
exp2 = [2 * math.exp(-2 * t) for t in time] 
#Calculate the analytical expression 
analytical = [2 * math.exp(-2 * t) * (-1 + math.exp(t)) for t in time] 
#Calculate the trapezoidal convolution 
trapezoidal = convolve(exp1, exp2, dt) 
#Calculate the scipy convolution 
sci = signal.convolve(exp1, exp2, mode = 'full') 
#Slice the first half to obtain the causal convolution and multiply by dt 
#to account for the step width 
sci = sci[0:steps] * dt 
#Calculate the convolution using the original Riemann sum algorithm 
riemann = convolveoriginal(exp1, exp2) 
riemann = riemann[0:steps] * dt 

#Plot 
plt.plot(time, analytical, label = 'analytical') 
plt.plot(time, trapezoidal, 'o', label = 'trapezoidal') 
plt.plot(time, riemann, 'o', label = 'Riemann') 
plt.plot(time, sci, '.', label = 'scipy.signal.convolve') 
plt.legend() 
plt.show() 

Grazie per il vostro tempo!

+3

che potrebbe essere utile se si fornisce un [ completa l'esempio minimo] (http: // sscce.org /) che riproduce il problema, per escludere errori banali come una divisione intera viene utilizzato dove deve essere utilizzata la vera divisione. – jfs

+2

se si sposta l'array 'sci' a destra (di un passo) e si normalizza quindi le soluzioni [hanno un aspetto simile] (http://i403.photobucket.com/albums/pp111/uber_ulrich/convolve.png): [' sci = np.r_ [0, sci [: steps-1]] * 0.86'] (https://gist.github.com/ac45fb5d117d8ecb66a3). Sembra che ci siano diverse definizioni di cosa 'convolve()' significa (fft, circolare), vedere la discussione in [Convolution computations in Numpy/Scipy] (http://stackoverflow.com/q/6855169/4279). – jfs

+0

Grazie per i riferimenti al thread convoluzione. Il turno giusto sembra un modo interessante per risolvere il problema. Puoi dirmi come hai ottenuto il fattore di normalizzazione di 0,86? Ho incluso l'algoritmo di somma di Riemann originale per illustrare il motivo per cui ritengo che questo sia un artefatto numerico piuttosto che una diversa definizione di cosa significhi convoluzione. –

risposta

0

Risposta breve: Scrivilo in C!

Long risposta

Uso ricettario circa numpy arrays ho riscritto il metodo convoluzione trapezoidale C. Per utilizzare il codice C si richiede tre file (https://gist.github.com/1626919)

  • Il codice C (performancemodule. c).
  • Il file di installazione per creare il codice e renderlo richiamabile da python (performancemodulesetup.py).
  • Il file python che fa uso del prolungamento C (performancetest.py)

Il codice dovrebbe essere eseguito su scaricando nel modo seguente

  • Regolare il percorso di inclusione in performancemodule.c.
  • Eseguire il seguente

    pitone performancemodulesetup.py costruire pitone performancetest.py

Potrebbe essere necessario copiare il file di libreria performancemodule.so o performancemodule.dll nella stessa directory performancetest.py.

risultati e performance

I risultati concordano perfettamente con l'un l'altro come illustrato di seguito:

Comparison of methods

Le prestazioni del metodo C è anche meglio di metodo convolve di SciPy. Esecuzione 10k convoluzioni con lunghezza matrice 50 richiede

convolve (seconds, microseconds) 81 349969 
scipy.signal.convolve (seconds, microseconds) 1 962599 
convolve in C (seconds, microseconds) 0 87024 

Pertanto, l'implementazione C è di circa 1000 volte veloce rispetto all'attuazione pitone e un po 'più di 20 volte più veloce l'implementazione SciPy (certamente, l'attuazione SciPy è più versatile).

EDIT: Questo non risolve il problema originale esattamente, ma è sufficiente per i miei scopi.

+0

'Cython' consente di creare facilmente estensioni C ad es., [' RotT() '] (http://stackoverflow.com/a/4973390/4279). – jfs

1

o, per coloro che preferiscono numpy a C. Sarà più lento dell'implementazione C, ma è solo poche righe.

>>> t = np.linspace(0, maxtime-dt, 50) 
>>> fx = np.exp(-np.array(t)) 
>>> gx = 2*np.exp(-2*np.array(t)) 
>>> analytical = 2 * np.exp(-2 * t) * (-1 + np.exp(t)) 

questo assomiglia trapezoidale in questo caso (ma non ho controllato la matematica)

>>> s2a = signal.convolve(fx[1:], gx, 'full')*dt 
>>> s2b = signal.convolve(fx, gx[1:], 'full')*dt 
>>> s = (s2a+s2b)/2 
>>> s[:10] 
array([ 0.17235682, 0.29706872, 0.38433313, 0.44235042, 0.47770012, 
     0.49564748, 0.50039326, 0.49527721, 0.48294359, 0.46547582]) 
>>> analytical[:10] 
array([ 0.  , 0.17221333, 0.29682141, 0.38401317, 0.44198216, 
     0.47730244, 0.49523485, 0.49997668, 0.49486489, 0.48254154]) 

più grande errore assoluto:

>>> np.max(np.abs(s[:len(analytical)-1] - analytical[1:])) 
0.00041657780840698155 
>>> np.argmax(np.abs(s[:len(analytical)-1] - analytical[1:])) 
6