@@ -245,19 +245,25 @@ def waveletdiff(x, dt, wavelet='db4', level=None, threshold=1.0, axis=0, mode='p
245245 # the vectorised operations above have already moved all Python-level
246246 # arithmetic outside this loop.
247247 #
248- # After wavelet denoising we have a smooth, noise-free signal. np.gradient
249- # applies a second-order central finite difference to that clean signal,
250- # which gives an accurate derivative. This is appropriate here because the
251- # heavy lifting (noise removal) has already been done by the wavelet
252- # thresholding step; np.gradient on a smooth signal converges at O(dt^2).
248+ # After wavelet denoising we have a smooth, noise-free signal. We
249+ # differentiate it analytically in the Fourier domain: multiplying the
250+ # FFT by i*omega is equivalent to applying the derivative operator exactly,
251+ # with no finite-difference truncation error. This keeps the two concerns
252+ # cleanly separated — wavelets handle denoising, Fourier handles
253+ # differentiation.
253254 x_hat_flat = np .empty_like (x_flat )
254255 dxdt_hat_flat = np .empty_like (x_flat )
255256
257+ # Angular frequency axis for a length-N signal sampled at dt.
258+ # fftfreq returns cycles/sample; multiplying by 2*pi/dt gives rad/s.
259+ k = np .fft .fftfreq (N , d = dt ) * 2 * np .pi
260+
256261 for col in range (M ):
257262 col_coeffs = [coeffs_denoised [i ][:, col ] for i in range (n_levels )]
258263 x_hat_col = pywt .waverec (col_coeffs , wavelet , mode = mode )[:N ]
259264 x_hat_flat [:, col ] = x_hat_col
260- dxdt_hat_flat [:, col ] = np .gradient (x_hat_col , dt )
265+ X = np .fft .fft (x_hat_col )
266+ dxdt_hat_flat [:, col ] = np .real (np .fft .ifft (1j * k * X ))
261267
262268 # Restore original shape and axis order.
263269 x_hat = np .moveaxis (x_hat_flat .reshape (shape ), 0 , axis )
0 commit comments