Breve resumen: ¿Cómo calculo rápidamente la convolución finita de dos matrices?Artefactos de la suma de Riemann en scipy.signal.convolve
muestras discretas Descripción del problema
Estoy tratando de obtener la convolución finito de dos funciones f (x), g (x) definida por
Para lograr esto, que he tomado de las funciones y los convirtió en matrices de longitud steps
:
xarray = [x * i/steps for i in range(steps)]
farray = [f(x) for x in xarray]
garray = [g(x) for x in xarray]
luego trató de calcular el conv solución usando la función scipy.signal.convolve
. Esta función proporciona los mismos resultados que el algoritmo conv
sugerido here. Sin embargo, los resultados difieren considerablemente de las soluciones analíticas. La modificación del algoritmo conv
para usar la regla trapezoidal da los resultados deseados.
Para ilustrar esto, vamos
f(x) = exp(-x)
g(x) = 2 * exp(-2 * x)
los resultados son los siguientes:
Aquí Riemann
representa un simple suma de Riemann, trapezoidal
es una versión modificada del algoritmo de Riemann a utilizar el regla trapezoidal, scipy.signal.convolve
es la función scipy y analytical
es la convolución analítica.
Ahora vamos g(x) = x^2 * exp(-x)
y los resultados se convierten en:
Aquí 'relación' es la proporción de los valores obtenidos de scipy a los valores analíticos. Lo anterior demuestra que el problema no se puede resolver renormalizando la integral.
La pregunta
¿Es posible utilizar la velocidad de scipy pero conservan los mejores resultados de una regla trapezoidal o tengo que escribir una extensión C para lograr los resultados deseados?
Un ejemplo
Simplemente copia y pega el código siguiente para ver el problema que estoy encontrando. Los dos resultados pueden ponerse de acuerdo para aumentar la variable steps
. Creo que el problema se debe a los artefactos de la mano derecha de las sumas de Riemann porque la integral se sobreestima cuando aumenta y se acerca a la solución analítica nuevamente a medida que disminuye.
EDITAR: Ahora he incluido el algoritmo original 2 como una comparación que da los mismos resultados que la función 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()
¡Gracias por su tiempo!
podría ser útil si usted proporciona un [ ejemplo mínimo completo] (http: // sscce.org /) que reproduce el problema, para excluir errores triviales como una división entera que se usa donde se debe usar la división verdadera. – jfs
si cambia 'sci' array a la derecha (por un paso) y lo normaliza, las soluciones [se ven similares] (http://i403.photobucket.com/albums/pp111/uber_ulrich/convolve.png): [' sci = np.r_ [0, sci [: steps-1]] * 0.86'] (https://gist.github.com/ac45fb5d117d8ecb66a3). Parece que hay diferentes definiciones de lo que 'convolve()' significa (fft, circular), vea la discusión en [Cálculos de convolución en Numpy/Scipy] (http://stackoverflow.com/q/6855169/4279). – jfs
Gracias por las referencias al hilo de convolución. El cambio a la derecha parece una forma interesante de solucionar el problema. ¿Me puede decir cómo obtuvo el factor de normalización de 0.86? He incluido el algoritmo de suma de Riemann original para ilustrar por qué creo que se trata de un artefacto numérico en lugar de una definición diferente de lo que significa la convolución. –