我使用 DHT 计算卷积的代码有什么问题?

What is wrong in my code using DHT to compute convolution?

Discrete Hartley Transform可以计算为

import numpy as np

def dht(x:np.array):    
    X = np.fft.fft(x)
    X = np.real(X) - np.imag(X)
    return X

def idht(X:np.array):
    n = len(X)
    X = dht(X)
    x = 1/n * X
    return x

根据 here, here(eq.4.7.22-4.7.25) 和许多其他地方说明的定理,可以使用 DHT 计算循环卷积。我实现了相应的算法并将其与使用 FFT 的循环卷积计算进行了比较,但结果并不接近。为什么?

def conv(x:np.array, y:np.array):
    X = dht(x)
    Y = dht(y)
    Xflip = np.flip(X)
    Yflip = np.flip(Y)
    Yplus = Y + Yflip
    Yminus = Y - Yflip
    Z = 0.5 * (X * Yplus + Xflip * Yminus)
    z = idht(Z)
    return z


def test_conv():
    x = np.ones((5, ))
    y = np.copy(x)
    z = conv(x, y)
    z1 = np.real(np.fft.ifft(np.fft.fft(x)*np.fft.fft(y)))
    np.testing.assert_allclose(z, z1, err_msg="test_convolution() failed")


if (__name__=='__main__'):
    test_conv()
    print("test_conv passed")

输出:

Traceback (most recent call last):
  File "ronf.py", line 35, in <module>
    test_conv()
  File "ronf.py", line 31, in test_conv
    np.testing.assert_allclose(z, z1, err_msg="test_convolution() failed")
  File "/home/andrea/vscode_venv/lib/python3.8/site-packages/numpy/testing/_private/utils.py", line 1528, in assert_allclose
    assert_array_compare(compare, actual, desired, err_msg=str(err_msg),
  File "/home/andrea/vscode_venv/lib/python3.8/site-packages/numpy/testing/_private/utils.py", line 842, in assert_array_compare
    raise AssertionError(msg)
AssertionError: 
Not equal to tolerance rtol=1e-07, atol=0
test_convolution() failed
Mismatched elements: 5 / 5 (100%)
Max absolute difference: 5.65018378
Max relative difference: 1.13003676
 x: array([ 0.      ,  4.105099,  5.992006,  3.053079, -0.650184])
 y: array([5., 5., 5., 5., 5.])

问题出在翻转部分。举例说明:

For a given array X_k, we need X_{-k} i.e., X_{N-k}.

N := 5
X_k := [0, 1, 2, 3, 4]

np.flip does this:
[4, 3, 2, 1, 0]

However, this is not X_{N-k}! We need:
X_{N-k} = [0, 4, 3, 2, 1]

since:
for k = 0 => X[5-0] = X[0] = 0
for k = 1 => X[5-1] = X[4] = 4
...                         ...
for k = 4 => X[5-4] = X[1] = 1

也就是说,我们假设周期为N s.t。 X[0] = X[N] 等等,但是 np.flip 的结果不符合那个。简而言之,第一个元素应该保持原样,其他元素应该翻转。

一种修复方法是 np.roll 在翻转后将其旋转 1 个位置:

def conv(x:np.array, y:np.array):
    X = dht(x)
    Y = dht(y)
    Xflip = np.roll(np.flip(X), shift=1)    # change is here
    Yflip = np.roll(np.flip(Y), shift=1)    # and here only
    Yplus = Y + Yflip
    Yminus = Y - Yflip
    Z = 0.5 * (X * Yplus + Xflip * Yminus)
    z = idht(Z)
    return z

然后我得到:

>>> a = np.ones((5,))
>>> conv(a, a)
array([5., 5., 5., 5., 5.])