I have implemented and validated @AndyWalls' result for discrete use:
Not yet performance optimized, and the optimal version is fairly quick, heaviest steps being 1) three $N$-sized FFTs, 2) one $N$-sized complex mult, and maybe 3) four spaced sinc samplings (but can pre-compute and reuse). The greater the f0
and f1
, the more periodization periods (k - 1
) are needed for sinc's decay.
Doesn't work for non-integer frequencies (need leaking sincs). Code, for now:
import numpy as np
from numpy.fft import fft, ifft
import matplotlib.pyplot as plt
def plot(x, title=None):
plt.plot(x)
if title is not None:
plt.title(title, weight='bold', fontsize=18, loc='left')
plt.gcf().set_size_inches(10, 7)
plt.show()
def dtrain0(t, A): # for reference
def dirac(t):
return (np.abs(t) < 1000*np.finfo(t.dtype).eps).astype('float64')
n_terms = len(t)
ns = np.arange(-n_terms//2, n_terms//2)
return 1/abs(A) * np.sum([dirac(t - n/A) for n in ns], axis=0)
def dtrain1(t, A):
M = len(t)
o = np.zeros(M)
A = abs(int(np.round(1/A)))
scale = t.max() / len(t) * 2
period = int(A / scale)
o[:M//2 + 1][::period] = A
o[M//2:-1][::-1][period - 2::period] = A
return o
dtrain = (dtrain0, dtrain1)[1]
def _ssinc(pm, f):
return np.sinc(2*np.pi / pm * f + .5) + np.sinc(2*np.pi / pm * f - .5)
def formula(w0, w1, f, f0, f1, k=0):
p = w0 + w1
m = w0 - w1
ss0 = np.sinc(2*np.pi / m * f + .5) + np.sinc(2*np.pi / m * f - .5)
ss1 = np.sinc(2*np.pi / p * f + .5) + np.sinc(2*np.pi / p * f - .5)
o0 = dtrain(f, 2*np.pi / m) * ss0
o1 = dtrain(f, 2*np.pi / p) * ss1
# fold fourier <=> subsample time
o0 = o0.reshape(2**k, -1).sum(axis=0)
o1 = o1.reshape(2**k, -1).sum(axis=0)
conv = ifft(fft(o0) * fft(o1)).real
K0 = 2*np.pi / np.abs(m)
K1 = 2*np.pi / np.abs(p)
o = .5 * K0 * K1 * conv
return o
fs, T, f0, f1 = 64, 2, 9, 23
k = 5 # number of periodization periods, minus 1
N = fs * T
w0, w1 = 2*np.pi * f0, 2*np.pi * f1
t = np.linspace(0, T, N, 0)
pos = np.arange(N//2 * 2**k + 1)
neg = -np.arange(1, N//2 * 2**k)[::-1]
f = np.hstack([pos, neg]) / T
x0 = np.cos(w0 * t)
x1 = np.cos(w1 * t)
o_exact = fft(np.abs(x0 + x1)) / N
assert np.allclose(o_exact.imag, 0)
o_exact = o_exact.real
o_formula = formula(w0, w1, f, f0, f1, k=k)
adiff = np.abs(o_exact - o_formula)
plot(o_exact)
plot(o_formula, title="MAE={:.2e} | f0, f1, fs, T, k = {}, {}, {}, {}, {}".format(
adiff.mean(), f0, f1, fs, T, k))