Артефакты из суммы Римана в scipy.signal.convolve

Краткое резюме. Как быстро вычислить конечную свертка двух массивов?

Описание проблемы

Я пытаюсь получить конечную свертку двух функций f (x), g (x), определенных как

конечная свертка

Для этого я взял дискретные образцы функций и превратил их в массивы длины steps:

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

Затем я попытался вычислить свертку, используя функцию scipy.signal.convolve. Эта функция дает те же результаты, что и алгоритм, conv предложенный здесь. Однако результаты существенно отличаются от аналитических решений. Изменение алгоритма conv для использования правила трапеций дает желаемые результаты.

Чтобы проиллюстрировать это, я позволю

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

результаты:

введите здесь описание изображения

Здесь Riemann представляет собой простую сумму Римана, trapezoidal — это модифицированная версия алгоритма Римана для использования правила трапеций, scipy.signal.convolve — это scipy-функция, а analytical — аналитическая свертка.

Теперь пусть g(x) = x^2 * exp(-x) и результаты станут такими:

введите здесь описание изображения

Здесь «отношение» — это отношение значений, полученных из scipy, к аналитическим значениям. Вышеизложенное показывает, что задача не может быть решена путем перенормировки интеграла.

Вопрос

Можно ли использовать скорость scipy, но сохранить лучшие результаты трапециевидного правила или мне нужно написать расширение C для достижения желаемых результатов?

Пример

Просто скопируйте и вставьте приведенный ниже код, чтобы увидеть проблему, с которой я столкнулся. Два результата можно привести к более близкому согласию, увеличив переменную steps. Я считаю, что проблема связана с артефактами от правых сумм Римана, потому что интеграл завышается, когда он увеличивается, и снова приближается к аналитическому решению, когда он уменьшается.

EDIT: теперь я включил исходный алгоритм 2 в качестве сравнения, которое дает те же результаты, что и функция 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()

Спасибо за уделенное время!


person Till Hoffmann    schedule 15.01.2012    source источник
comment
было бы полезно, если бы вы предоставили полный минимальный пример, который воспроизводит проблему, чтобы исключить тривиальные ошибки, такие как целочисленное деление. используется там, где необходимо использовать истинное деление.   -  person jfs    schedule 16.01.2012
comment
если сдвинуть массив sci вправо (на один шаг) и нормализовать его, то решения похоже: sci = np.r_[0,sci[:steps-1]]*0.86. Кажется, существуют разные определения того, что означает convolve() (fft, circal), см. обсуждение в Вычисления свертки в Numpy/Scipy.   -  person jfs    schedule 16.01.2012
comment
Спасибо за ссылки на поток свертки. Сдвиг вправо выглядит интересным способом решения проблемы. Можете ли вы сказать мне, как вы получили коэффициент нормализации 0,86? Я включил исходный алгоритм суммирования Римана, чтобы проиллюстрировать, почему я считаю, что это числовой артефакт, а не другое определение того, что означает свертка.   -  person Till Hoffmann    schedule 17.01.2012
comment
значение 0.86: trapezoidal[1:]/sci[:-1]. Он показывает только, что один и тот же постоянный коэффициент работает для всех точек.   -  person jfs    schedule 17.01.2012
comment
Относительно нормализации: рассмотрим две разные функции f(x)=exp(-x) и g(x)=x^2*exp(-x). Теперь результаты нельзя преобразовать друг в друга с помощью скалярного умножения. См. пример кода здесь: gist.github.com/1626219 Я добавил изображение к сообщению выше.   -  person Till Hoffmann    schedule 17.01.2012


Ответы (2)


или для тех, кто предпочитает numpy C. Это будет медленнее, чем реализация C, но это всего несколько строк.

>>> 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))

в данном случае это выглядит как трапеция (но я не проверял математику)

>>> 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])

самая большая абсолютная ошибка:

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

Короткий ответ: напишите на C!

Длинный ответ

Используя кулинарную книгу о массивах numpy, я переписал метод трапециевидной свертки на C. Чтобы используйте код C, для которого требуется три файла (https://gist.github.com/1626919)

  • Код C (performancemodule.c).
  • Установочный файл для создания кода и обеспечения возможности его вызова из Python (performancemodulesetup.py).
  • Файл Python, использующий расширение C (performancetest.py)

Код должен запускаться после загрузки, выполнив следующие действия.

  • Отрегулируйте путь включения в performancemodule.c.
  • Запустите следующее

    Python performancemodulesetup.py собрать python performancetest.py

Возможно, вам придется скопировать файл библиотеки performancemodule.so или performancemodule.dll в тот же каталог, что и performancetest.py.

Результаты и производительность

Результаты четко согласуются друг с другом, как показано ниже:

Сравнение методов

Производительность метода C даже лучше, чем метод свертки scipy. Для запуска 10k сверток с длиной массива 50 требуется

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

Таким образом, реализация C примерно в 1000 раз быстрее, чем реализация Python, и чуть более чем в 20 раз быстрее, чем реализация scipy (правда, реализация scipy более универсальна).

EDIT: это не решает исходный вопрос точно, но достаточно для моих целей.

person Till Hoffmann    schedule 17.01.2012
comment
Cython позволяет легко создавать расширения C, например, rotT(). - person jfs; 17.01.2012