Skip to content

Commit 450a332

Browse files
authored
Tutorial for interpolation of 2D real-valued maps (#722)
* Add general Python interpolation code for tutorial * Fix typo in docstring * Docstring should say nonnegative n * Resolving @ahbarnett 's review comments * fix typo * Simplify code
1 parent c9ba3ec commit 450a332

File tree

2 files changed

+140
-7
lines changed

2 files changed

+140
-7
lines changed

docs/tutorial/realinterp1d.rst

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ When run this gives:
6363
6464
which shows that both schemes work.
6565
See the full code `tutorial/realinterp1d.py <https://github.com/flatironinstitute/finufft/blob/master/tutorial/realinterp1d.py>`_.
66+
See the full code `tutorial/realinterp2d.py <https://github.com/flatironinstitute/finufft/blob/master/tutorial/realinterp2d.py>`_.
6667
This arose from Discussion https://github.com/flatironinstitute/finufft/discussions/720
6768

6869

@@ -72,10 +73,3 @@ This arose from Discussion https://github.com/flatironinstitute/finufft/discussi
7273
efficiency gain on the nonuniform point side,
7374
possibly motivating real-valued NUFFT variants. Since the decisions about
7475
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/realinterp2d.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Written by Kaya Unalmis following discussion with Barnett on 08/15/25.
2+
3+
import numpy as np
4+
import finufft as fi
5+
import pytest
6+
7+
8+
def nufft1d2r(x, f, **kwargs):
9+
"""Non-uniform real fast Fourier transform of second type.
10+
11+
Parameters
12+
----------
13+
x : np.ndarray
14+
Real query points of coordinate where interpolation is desired.
15+
f : np.ndarray
16+
Fourier coefficients fₙ of the map x ↦ c(x)
17+
such that c(x) = ∑ₙ fₙ exp(i n x) where n >= 0.
18+
19+
Returns
20+
-------
21+
cq : np.ndarray
22+
Real function value at query points.
23+
24+
"""
25+
s = f.shape[-1] // 2
26+
s = np.exp(1j * s * x)
27+
return np.real(fi.nufft1d2(x, f, isign=1, modeord=0, **kwargs) * s)
28+
29+
30+
def nufft2d2r(x, y, f, rfft_axis=-1, **kwargs):
31+
"""Non-uniform real fast Fourier transform of second type.
32+
33+
Parameters
34+
----------
35+
x : np.ndarray
36+
Real query points of coordinate where interpolation is desired.
37+
The coordinates stored here must be the same coordinate enumerated across axis
38+
``-2`` of ``f``.
39+
y : np.ndarray
40+
Real query points of coordinate where interpolation is desired.
41+
The coordinates stored here must be the same coordinate enumerated across axis
42+
``-1`` of ``f``.
43+
f : np.ndarray
44+
Fourier coefficients fₘₙ of the map x,y ↦ c(x,y)
45+
such that c(x,y) = ∑ₘₙ fₘₙ exp(i m x) exp(i n y).
46+
rfft_axis : int
47+
Axis along which real FFT was performed.
48+
If -1 (-2), assumes c(x,y) = ∑ₘₙ fₘₙ exp(i m x) exp(i n y) where
49+
n ( m) >= 0, respectively.
50+
51+
Returns
52+
-------
53+
cq : np.ndarray
54+
Real function value at query points.
55+
56+
"""
57+
if rfft_axis != -1 and rfft_axis != -2:
58+
raise NotImplementedError(f"rfft_axis must be -1 or -2, but got {rfft_axis}.")
59+
60+
s = f.shape[rfft_axis] // 2
61+
s = np.exp(1j * s * (y if rfft_axis == -1 else x))
62+
f = np.fft.ifftshift(f, axes=rfft_axis)
63+
return np.real(fi.nufft2d2(x, y, f, isign=1, modeord=1, **kwargs) * s)
64+
65+
66+
def _f_1d(x):
67+
"""Test function for 1D FFT."""
68+
return np.cos(7 * x) + np.sin(x) - 33.2
69+
70+
71+
def _f_1d_nyquist_freq():
72+
return 7
73+
74+
75+
def _f_2d(x, y):
76+
"""Test function for 2D FFT."""
77+
x_freq, y_freq = 3, 5
78+
return (
79+
# something that's not separable
80+
np.cos(x_freq * x) * np.sin(2 * x + y)
81+
+ np.sin(y_freq * y) * np.cos(x + 3 * y)
82+
- 33.2
83+
+ np.cos(x)
84+
+ np.cos(y)
85+
)
86+
87+
88+
def _f_2d_nyquist_freq():
89+
x_freq, y_freq = 3, 5
90+
x_freq_nyquist = x_freq + 2
91+
y_freq_nyquist = y_freq + 3
92+
return x_freq_nyquist, y_freq_nyquist
93+
94+
95+
@pytest.mark.parametrize(
96+
"func, n",
97+
[
98+
(_f_1d, 2 * _f_1d_nyquist_freq() + 1),
99+
(_f_1d, 2 * _f_1d_nyquist_freq()),
100+
],
101+
)
102+
def test_non_uniform_real_FFT(func, n):
103+
"""Test non-uniform real FFT interpolation."""
104+
x = np.linspace(0, 2 * np.pi, n, endpoint=False)
105+
c = func(x)
106+
xq = np.array([7.34, 1.10134, 2.28])
107+
108+
f = 2 * np.fft.rfft(c, norm="forward")
109+
f[..., (0, -1) if (n % 2 == 0) else 0] /= 2
110+
np.testing.assert_allclose(nufft1d2r(xq, f), func(xq))
111+
112+
113+
@pytest.mark.parametrize(
114+
"func, m, n, rfft_axis",
115+
[
116+
(_f_2d, 2 * _f_2d_nyquist_freq()[0] + 1, 2 * _f_2d_nyquist_freq()[1] + 1, -1),
117+
(_f_2d, 2 * _f_2d_nyquist_freq()[0] + 1, 2 * _f_2d_nyquist_freq()[1] + 1, -2),
118+
],
119+
)
120+
def test_non_uniform_real_FFT_2D(func, m, n, rfft_axis):
121+
"""Test non-uniform real FFT 2D interpolation."""
122+
x = np.linspace(0, 2 * np.pi, m, endpoint=False)
123+
y = np.linspace(0, 2 * np.pi, n, endpoint=False)
124+
x, y = map(np.ravel, tuple(np.meshgrid(x, y, indexing="ij")))
125+
c = func(x, y).reshape(m, n)
126+
127+
xq = np.array([7.34, 1.10134, 2.28, 1e3 * np.e])
128+
yq = np.array([1.1, 3.78432, 8.542, 0])
129+
130+
index = [slice(None)] * c.ndim
131+
index[rfft_axis] = (0, -1) if (c.shape[rfft_axis] % 2 == 0) else 0
132+
index = tuple(index)
133+
axes = (-2, -1)
134+
if rfft_axis == -2:
135+
axes = axes[::-1]
136+
137+
f = 2 * np.fft.rfft2(c, axes=axes, norm="forward")
138+
f[index] /= 2
139+
np.testing.assert_allclose(nufft2d2r(xq, yq, f, rfft_axis), func(xq, yq))

0 commit comments

Comments
 (0)