|
| 1 | +.. _realinterp1d: |
| 2 | + |
| 3 | +Fast Fourier interpolation of 1D real-valued function at arbitrary points |
| 4 | +========================================================================= |
| 5 | + |
| 6 | +This is a Python variant of the previous 1D MATLAB demo, but illustrating |
| 7 | +a trick for **real-valued** functions. |
| 8 | +For real-valued functions the Fourier series coefficients |
| 9 | +``-N//2`` to ``(N-1)//2`` arising from the FFT of ``N`` regular samples |
| 10 | +have Hermitian symmetry, |
| 11 | +so that it could be considered wasteful to send all ``N`` coefficients |
| 12 | +into a type-2 NUFFT to evaluate the Fourier series at new targets. |
| 13 | +Here we show that it is possible to half the length of the |
| 14 | +NUFFT input array (and thus internal FFT size), increasing |
| 15 | +efficiency if the FFTs are dominant. A similar trick (again saving a factor of two |
| 16 | +in FFT cost, not two per dimension) could be done in 2D or 3D. |
| 17 | + |
| 18 | +Let our periodic domain be $[0,2\pi)$. |
| 19 | +We sample a bandlimited function that is exactly captured by $N$ |
| 20 | +point samples, use the real-valued FFT to get its coefficients |
| 21 | +with nonnegative indices, then show how to interpolate this Fourier |
| 22 | +series to a set of random target points. The problem sizes are kept |
| 23 | +deliberately small (see other demos which scale things up): |
| 24 | + |
| 25 | +.. code-block:: python |
| 26 | +
|
| 27 | + import numpy as np |
| 28 | + import finufft as fi |
| 29 | +
|
| 30 | + N = 5 # num regular samples |
| 31 | + # a generic real test fun with bandlimit <= (N-1)/2 so interp exact... |
| 32 | + fun = lambda t: 1.0 + np.sin(t+1) + np.sin(2*t-2) # bandlimit is 2 |
| 33 | +
|
| 34 | + Nt = 100 # test targs |
| 35 | + targs = np.random.rand(Nt)*2*np.pi |
| 36 | +
|
| 37 | + Nf = N//2 + 1 # num freq outputs for rfft |
| 38 | + g = np.linspace(0,2*np.pi,N,endpoint=False) # sample grid |
| 39 | + f = fun(g) |
| 40 | + c = (1/N) * np.fft.rfft(f) # gets coeffs 0,1,..Nf-1 (don't forget prefac) |
| 41 | + assert c.size==Nf |
| 42 | +
|
| 43 | + # Do the naive (double-length c array) NUFFT version: |
| 44 | + cref = np.concatenate([np.conj(np.flip(c[1:])), c]) # reflect to 1-Nf...Nf-1 coeffs |
| 45 | + ft = np.real(fi.nufft1d2(targs,cref,eps=1e-12,isign=1)) # f at targs (isign!) |
| 46 | + # (taking Re here was just a formality; it is already real to eps_mach) |
| 47 | + print("naive (reflected) 1d2 max err:", np.linalg.norm(fun(targs) - ft, np.inf)) |
| 48 | +
|
| 49 | + # now demo avoid doubling the NUFFT length via freq shift and mult by phase: |
| 50 | + c[1:] *= 2.0 # since each nonzero coeff appears twice in reflected array |
| 51 | + N0 = Nf//2 # starting freq index shift that FINUFFT interprets for c array |
| 52 | + ftp = fi.nufft1d2(targs,c,eps=1e-12,isign=1) # f at targs but with phase |
| 53 | + # the key step: rephase (to account for shift), only then take Re (needed!)... |
| 54 | + ft = np.real( ftp * (np.cos(N0*targs) + 1j*np.sin(N0*targs))) # guess 1j sign |
| 55 | + print("unpadded 1d2 max err:", np.linalg.norm(fun(targs) - ft, np.inf)) |
| 56 | +
|
| 57 | +When run this gives: |
| 58 | + |
| 59 | +.. code-block:: |
| 60 | +
|
| 61 | + naive (reflected) 1d2 max err: 9.898748487557896e-13 |
| 62 | + unpadded 1d2 max err: 6.673550601021816e-13 |
| 63 | +
|
| 64 | +which shows that both schemes work. |
| 65 | +See the full code `tutorial/realinterp1d.py <https://github.com/flatironinstitute/finufft/blob/master/tutorial/realinterp1d.py>`_. |
| 66 | +This arose from Discussion https://github.com/flatironinstitute/finufft/discussions/720 |
| 67 | + |
| 68 | + |
| 69 | +.. note:: |
| 70 | + |
| 71 | + Complex-valued spreading/interpolation is still used under the hood in FINUFFT, so that there is no |
| 72 | + efficiency gain on the nonuniform point side, |
| 73 | + possibly motivating real-valued NUFFT variants. Since the decisions about |
| 74 | + real-valued interfaces become elaborate, we leave this for future work. |
| 75 | + |
| 76 | +Notes about the 2D case: |
| 77 | +``numpy.rfft2`` seems to have a different output |
| 78 | +size than ``rfft``. |
| 79 | +One can still only save a factor of two in the ``nufft2d2`` input array size (hence FFT size), just as in 1D. It would need a rectangular (``N/2`` by ``N``) array, where ``N ``is the number of regular samples per dimension. |
| 80 | +Special handling the origin and the zero-index cases will be needed |
| 81 | +to recreate the effect of the full reflected Hermitian-symmetric coefficient array. Please contribute a demo. |
0 commit comments