Skip to content

Commit c9ba3ec

Browse files
committed
add realinterp1d.py tutorial demo to docs [no ci]
1 parent 72b57db commit c9ba3ec

File tree

4 files changed

+116
-2
lines changed

4 files changed

+116
-2
lines changed

CHANGELOG

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
List of features / changes made / release notes, in reverse chronological order.
22
If not stated, FINUFFT is assumed (cuFINUFFT <=1.3 is listed separately).
33

4-
Master (working towards v2.5.0), 7/8/25
4+
Master (working towards v2.5.0), 8/13/25
55

6+
* Added real-valued function type-2 interpolation demo (Barnett).
67
* Added functionality for adjoint execution of FINUFFT plans (Reinecke #633,
78
addresses #566 and #571).
89
Work arrays are now only allocated during plan execution, reducing overall

docs/tut.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ The following are instructive demos of using FINUFFT for a variety of
77
spectrally-related tasks arising in
88
scientific computing and signal/image processing. We will slowly grow the
99
list (contact us to add one).
10-
For conciseness of code, and ease of writing, they are
10+
For conciseness of code, and ease of writing, they are mostly
1111
in MATLAB (and should work on versions at least back to R2017a),
1212
unless otherwise stated.
1313

1414
.. toctree::
1515

1616
tutorial/serieseval
17+
tutorial/realinterp1d
1718
tutorial/contft
1819
tutorial/peripois2d
1920
tutorial/peripois2d_python

docs/tutorial/realinterp1d.rst

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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.

tutorial/realinterp1d.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# demo 1D NUFFT interpolation of real periodic func *without* length-doubling.
2+
# A real function is sampled on the 0-offset regular periodic grid, and
3+
# we want to use the type-2 NUFFT to spectrally interpolate it efficiently.
4+
# Barnett 8/13/25 for Kaya Unalmis.
5+
import numpy as np
6+
import finufft as fi
7+
8+
N = 5 # num samples
9+
# a generic real test fun with bandlimit <= (N-1)/2 so interp exact...
10+
fun = lambda t: 1.0 + np.sin(t+1) + np.sin(2*t-2) # bandlimit is 2
11+
Nt = 100 # test targs
12+
targs = np.random.rand(Nt)*2*np.pi
13+
Nf = N//2 + 1 # num freq outputs for rfft; note rfft2 seems different :(
14+
g = np.linspace(0,2*np.pi,N,endpoint=False) # sample grid
15+
f = fun(g)
16+
c = (1/N) * np.fft.rfft(f) # gets coeffs 0,1,..Nf-1 (don't forget prefac)
17+
assert c.size==Nf
18+
19+
# Do the naive (double-length c array) NUFFT version:
20+
cref = np.concatenate([np.conj(np.flip(c[1:])), c]) # reflect to 1-Nf...Nf-1 coeffs
21+
ft = np.real(fi.nufft1d2(targs,cref,eps=1e-12,isign=1)) # f at targs (isign!)
22+
# (taking Re here was just a formality; it is already real to eps_mach)
23+
print("naive (reflected) 1d2 max err:", np.linalg.norm(fun(targs) - ft, np.inf))
24+
25+
# now demo avoid doubling the NUFFT length via freq shift and mult by phase:
26+
c[1:] *= 2.0 # since each nonzero coeff appears twice in reflected array
27+
N0 = Nf//2 # starting freq shift that FINUFFT interprets the c array
28+
ftp = fi.nufft1d2(targs,c,eps=1e-12,isign=1) # f at targs but with phase error
29+
# the key step: rephase (to account for shift), only then take Re (needed!)...
30+
ft = np.real( ftp * (np.cos(N0*targs) + 1j*np.sin(N0*targs))) # guess 1j sign
31+
print("unpadded 1d2 max err:", np.linalg.norm(fun(targs) - ft, np.inf))

0 commit comments

Comments
 (0)