Skip to content

Commit 06ffe61

Browse files
Added code
1 parent ad52730 commit 06ffe61

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

chirp_z_transform.py

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Implements the Chirp Z-Transform and its inverse, as described in
2+
# V. Sukhoy and A. Stoytchev, Generalizing the inverse FFT off the unit
3+
# circle. Sci Rep 9, 14443 (2019).
4+
# https://doi.org/10.1038/s41598-019-50234-9
5+
# (While a fast algorithm for the Chirp Z-Transform has been known for 50 years,
6+
# a fast algorithm for the inverse was only recently discovered (By Sukhoy and
7+
# Stoychev).
8+
9+
import numpy as np
10+
import scipy.fftpack
11+
12+
def circulant_multiply(c, x):
13+
# Compute the product y = Gx of a circulant matrix G and a vector x, where G is generated by its first column
14+
# c = (c[0], c[1], ..., c[n-1]).
15+
if len(x) != len(c):
16+
raise Exception("should have len(x) equal to len(c), but instead len(x) = %d, len(c) = %d" % (len(x), len(c)))
17+
18+
return scipy.fftpack.ifft( scipy.fftpack.fft(c) * scipy.fftpack.fft(x) )
19+
20+
def toeplitz_multiply_e(r, c, x):
21+
# Compute the product y = Tx of a Toeplitz matrix T and a vector x, where T is specified by its first row
22+
# r = (r[0], r[1], r[2], ..., r[N-1])
23+
# and its first column
24+
# c = (c[0], c[1], c[2], ..., c[M-1]),
25+
# where r[0] = c[0].
26+
N = len(r)
27+
M = len(c)
28+
29+
if r[0] != c[0]:
30+
raise Exception("should have r[0] == c[0], but r[0] = %f and c[0] = %f" % (r[0], c[0]))
31+
if len(x) != len(r):
32+
raise Exception("should have len(x) equal to len(r), but instead len(x) = %d, len(r) = %d" % (len(x), len(r)))
33+
34+
n = (2 ** np.ceil(np.log2(M+N-1))).astype(np.int64)
35+
36+
# Form an array C by concatenating c, n - (M + N - 1) zeros, and the reverse of the last N-1 elements of r, ie.
37+
# C = (c[0], c[1], ..., c[M-1], 0,..., 0, r[N-1], ..., r[2], r[1]).
38+
C = np.concatenate(( np.pad(c, (0, n - (M + N - 1)), 'constant'), np.flip(r[1:]) ))
39+
40+
X = np.pad(x, (0, n-N), 'constant')
41+
Y = circulant_multiply(C, X)
42+
43+
# The result is the first M elements of C * X.
44+
return Y[:M]
45+
46+
def czt(x, M, W, A):
47+
# Computes the Chirp Z-transform of a vector x.
48+
#
49+
# To recover a Fourier transform, take
50+
# M = len(x), W = exp(2pi/M * i), A = 1.
51+
N = len(x)
52+
X = np.empty(N) * 1.0j
53+
r = np.empty(N) * 1.0j
54+
c = np.empty(M) * 1.0j
55+
56+
for k in range(N):
57+
X[k] = np.power(W, k**2/2.0) * np.power(A*1.0, -k) * x[k]
58+
r[k] = np.power(W, -k**2/2.0)
59+
60+
for k in range(M):
61+
c[k] = np.power(W, -k**2/2)
62+
63+
X = toeplitz_multiply_e(r, c, X) # len(X) == M
64+
for k in range(M):
65+
X[k] = np.power(W, k**2/2) * X[k]
66+
return X
67+
68+
def iczt(X, N, W, A):
69+
# Compute the inverse Chirp Z-transform of a vector X.
70+
M = len(X)
71+
if M != N:
72+
raise Exception("should have len(X) equal to N, but instead len(X) = %d, N = %d" % (len(X), N))
73+
74+
n = N
75+
x = np.empty(n) * 1.0j
76+
for k in range(n):
77+
x[k] = np.power(W, -k**2/2) * X[k]
78+
79+
p = np.empty(n) * 1.0j
80+
p[0] = 1
81+
for k in range(1, n):
82+
p[k] = p[k-1] * (np.power(W, k)-1)
83+
84+
u = np.empty(n) * 1.0j
85+
for k in range(n):
86+
u[k] = (-1)**k * ( np.power(W, 0.5 * (2*k**2 - (2*n-1)*k + n*(n-1))) / (p[n - k - 1] * p[k]) )
87+
88+
z = np.zeros(n)
89+
uhat = np.pad(np.flip(u[1:]), (1, 0), 'constant')
90+
util = np.pad(u[:1], (0, n-1), 'constant')
91+
92+
xp = toeplitz_multiply_e(z, uhat, toeplitz_multiply_e(uhat, z, x))
93+
xpp = toeplitz_multiply_e(util, u, toeplitz_multiply_e(u, util, x))
94+
95+
for k in range(n):
96+
x[k] = (xpp[k] - xp[k]) / u[0]
97+
98+
for k in range(n):
99+
x[k] = np.power(A, k) * np.power(W, -k**2/2) * x[k]
100+
101+
return x

0 commit comments

Comments
 (0)