Skip to content

Commit f9cbc62

Browse files
vtavanaantonwolfy
andauthored
implement dpnp.fft.fftfreq and dpnp.fft.rfftfreq (#1898)
* implement fftfreq * fix pre-commit * Apply suggestions from code review Co-authored-by: Anton <[email protected]> * update cupy tests --------- Co-authored-by: Anton <[email protected]>
1 parent 0590a92 commit f9cbc62

File tree

5 files changed

+269
-21
lines changed

5 files changed

+269
-21
lines changed

dpnp/fft/dpnp_iface_fft.py

Lines changed: 192 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -197,19 +197,114 @@ def fft2(x, s=None, axes=(-2, -1), norm=None):
197197
return call_origin(numpy.fft.fft2, x, s, axes, norm)
198198

199199

200-
def fftfreq(n=None, d=1.0):
200+
def fftfreq(n, d=1.0, device=None, usm_type=None, sycl_queue=None):
201201
"""
202-
Compute the one-dimensional discrete Fourier Transform sample frequencies.
202+
Return the Discrete Fourier Transform sample frequencies.
203+
204+
The returned float array `f` contains the frequency bin centers in cycles
205+
per unit of the sample spacing (with zero at the start). For instance, if
206+
the sample spacing is in seconds, then the frequency unit is cycles/second.
207+
208+
Given a window length `n` and a sample spacing `d`::
209+
210+
f = [0, 1, ..., n/2-1, -n/2, ..., -1] / (d*n) if n is even
211+
f = [0, 1, ..., (n-1)/2, -(n-1)/2, ..., -1] / (d*n) if n is odd
203212
204213
For full documentation refer to :obj:`numpy.fft.fftfreq`.
205214
206-
Limitations
207-
-----------
208-
Parameter `d` is unsupported.
215+
Parameters
216+
----------
217+
n : int
218+
Window length.
219+
d : scalar, optional
220+
Sample spacing (inverse of the sampling rate).
221+
Default: ``1.0``.
222+
device : {None, string, SyclDevice, SyclQueue}, optional
223+
An array API concept of device where the output array is created.
224+
The `device` can be ``None`` (the default), an OneAPI filter selector
225+
string, an instance of :class:`dpctl.SyclDevice` corresponding to
226+
a non-partitioned SYCL device, an instance of :class:`dpctl.SyclQueue`,
227+
or a `Device` object returned by
228+
:obj:`dpnp.dpnp_array.dpnp_array.device` property.
229+
Default: ``None``.
230+
usm_type : {None, "device", "shared", "host"}, optional
231+
The type of SYCL USM allocation for the output array.
232+
Default: ``None``.
233+
sycl_queue : {None, SyclQueue}, optional
234+
A SYCL queue to use for output array allocation and copying.
235+
Default: ``None``.
236+
237+
Returns
238+
-------
239+
f : dpnp.ndarray
240+
Array of length `n` containing the sample frequencies.
241+
242+
See Also
243+
--------
244+
:obj:`dpnp.fft.rfftfreq` : Return the Discrete Fourier Transform sample
245+
frequencies (for usage with :obj:`dpnp.fft.rfft` and
246+
:obj:`dpnp.fft.irfft`).
247+
248+
Examples
249+
--------
250+
>>> import dpnp as np
251+
>>> signal = np.array([-2, 8, 6, 4, 1, 0, 3, 5])
252+
>>> fourier = np.fft.fft(signal)
253+
>>> n = signal.size
254+
>>> timestep = 0.1
255+
>>> freq = np.fft.fftfreq(n, d=timestep)
256+
>>> freq
257+
array([ 0. , 1.25, 2.5 , 3.75, -5. , -3.75, -2.5 , -1.25])
258+
259+
Creating the output array on a different device or with a
260+
specified usm_type:
261+
262+
>>> x = np.fft.fftfreq(n, d=timestep) # default case
263+
>>> x.shape, x.device, x.usm_type
264+
((8,), Device(level_zero:gpu:0), 'device')
265+
266+
>>> y = np.fft.fftfreq(n, d=timestep, device="cpu")
267+
>>> y.shape, y.device, y.usm_type
268+
((8,), Device(opencl:cpu:0), 'device')
269+
270+
>>> z = np.fft.fftfreq(n, d=timestep, usm_type="host")
271+
>>> z.shape, z.device, z.usm_type
272+
((8,), Device(level_zero:gpu:0), 'host')
209273
210274
"""
211275

212-
return call_origin(numpy.fft.fftfreq, n, d)
276+
if not isinstance(n, int):
277+
raise ValueError("`n` should be an integer")
278+
if not dpnp.isscalar(d):
279+
raise ValueError("`d` should be an scalar")
280+
val = 1.0 / (n * d)
281+
results = dpnp.empty(
282+
n,
283+
dtype=dpnp.intp,
284+
device=device,
285+
usm_type=usm_type,
286+
sycl_queue=sycl_queue,
287+
)
288+
N = (n - 1) // 2 + 1
289+
p1 = dpnp.arange(
290+
0,
291+
N,
292+
dtype=dpnp.intp,
293+
device=device,
294+
usm_type=usm_type,
295+
sycl_queue=sycl_queue,
296+
)
297+
results[:N] = p1
298+
p2 = dpnp.arange(
299+
-(n // 2),
300+
0,
301+
dtype=dpnp.intp,
302+
device=device,
303+
usm_type=usm_type,
304+
sycl_queue=sycl_queue,
305+
)
306+
results[N:] = p2
307+
return results * val
213308

214309

215310
def fftn(x, s=None, axes=None, norm=None):
@@ -870,19 +965,104 @@ def rfft2(x, s=None, axes=(-2, -1), norm=None):
870965
return call_origin(numpy.fft.rfft2, x, s, axes, norm)
871966

872967

873-
def rfftfreq(n=None, d=1.0):
968+
def rfftfreq(n, d=1.0, device=None, usm_type=None, sycl_queue=None):
874969
"""
875-
Compute the one-dimensional discrete Fourier Transform sample frequencies.
970+
Return the Discrete Fourier Transform sample frequencies
971+
(for usage with :obj:`dpnp.fft.rfft`, :obj:`dpnp.fft.irfft`).
972+
973+
The returned float array `f` contains the frequency bin centers in cycles
974+
per unit of the sample spacing (with zero at the start). For instance, if
975+
the sample spacing is in seconds, then the frequency unit is cycles/second.
976+
977+
Given a window length `n` and a sample spacing `d`::
978+
979+
f = [0, 1, ..., n/2-1, n/2] / (d*n) if n is even
980+
f = [0, 1, ..., (n-1)/2-1, (n-1)/2] / (d*n) if n is odd
981+
982+
Unlike :obj:`dpnp.fft.fftfreq` the Nyquist frequency component is
983+
considered to be positive.
876984
877985
For full documentation refer to :obj:`numpy.fft.rfftfreq`.
878986
879-
Limitations
880-
-----------
881-
Parameter `d` is unsupported.
987+
Parameters
988+
----------
989+
n : int
990+
Window length.
991+
d : scalar, optional
992+
Sample spacing (inverse of the sampling rate).
993+
Default: ``1.0``.
994+
device : {None, string, SyclDevice, SyclQueue}, optional
995+
An array API concept of device where the output array is created.
996+
The `device` can be ``None`` (the default), an OneAPI filter selector
997+
string, an instance of :class:`dpctl.SyclDevice` corresponding to
998+
a non-partitioned SYCL device, an instance of :class:`dpctl.SyclQueue`,
999+
or a `Device` object returned by
1000+
:obj:`dpnp.dpnp_array.dpnp_array.device` property.
1001+
Default: ``None``.
1002+
usm_type : {None, "device", "shared", "host"}, optional
1003+
The type of SYCL USM allocation for the output array.
1004+
Default: ``None``.
1005+
sycl_queue : {None, SyclQueue}, optional
1006+
A SYCL queue to use for output array allocation and copying.
1007+
Default: ``None``.
1008+
1009+
Returns
1010+
-------
1011+
f : dpnp.ndarray
1012+
Array of length ``n//2 + 1`` containing the sample frequencies.
1013+
1014+
See Also
1015+
--------
1016+
:obj:`dpnp.fft.fftfreq` : Return the Discrete Fourier Transform sample
1017+
frequencies (for usage with :obj:`dpnp.fft.fft` and
1018+
:obj:`dpnp.fft.ifft`).
1019+
1020+
Examples
1021+
--------
1022+
>>> import dpnp as np
1023+
>>> signal = np.array([-2, 8, 6, 4, 1, 0, 3, 5, -3, 4])
1024+
>>> fourier = np.fft.fft(signal)
1025+
>>> n = signal.size
1026+
>>> sample_rate = 100
1027+
>>> freq = np.fft.fftfreq(n, d=1./sample_rate)
1028+
>>> freq
1029+
array([ 0., 10., 20., 30., 40., -50., -40., -30., -20., -10.])
1030+
>>> freq = np.fft.rfftfreq(n, d=1./sample_rate)
1031+
>>> freq
1032+
array([ 0., 10., 20., 30., 40., 50.])
1033+
1034+
Creating the output array on a different device or with a
1035+
specified usm_type:
1036+
1037+
>>> x = np.fft.rfftfreq(n, d=1./sample_rate) # default case
1038+
>>> x.shape, x.device, x.usm_type
1039+
((6,), Device(level_zero:gpu:0), 'device')
1040+
1041+
>>> y = np.fft.rfftfreq(n, d=1./sample_rate, device="cpu")
1042+
>>> y.shape, y.device, y.usm_type
1043+
((6,), Device(opencl:cpu:0), 'device')
1044+
1045+
>>> z = np.fft.rfftfreq(n, d=1./sample_rate, usm_type="host")
1046+
>>> z.shape, z.device, z.usm_type
1047+
((6,), Device(level_zero:gpu:0), 'host')
8821048
8831049
"""
8841050

885-
return call_origin(numpy.fft.rfftfreq, n, d)
1051+
if not isinstance(n, int):
1052+
raise ValueError("`n` should be an integer")
1053+
if not dpnp.isscalar(d):
1054+
raise ValueError("`d` should be an scalar")
1055+
val = 1.0 / (n * d)
1056+
N = n // 2 + 1
1057+
results = dpnp.arange(
1058+
0,
1059+
N,
1060+
dtype=dpnp.intp,
1061+
device=device,
1062+
usm_type=usm_type,
1063+
sycl_queue=sycl_queue,
1064+
)
1065+
return results * val
8861066

8871067

8881068
def rfftn(x, s=None, axes=None, norm=None):

tests/test_fft.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,21 @@ def test_fft_invalid_dtype(self, func_name):
354354
dpnp_func = getattr(dpnp.fft, func_name)
355355
with pytest.raises(TypeError):
356356
dpnp_func(a)
357+
358+
359+
class TestFftfreq:
360+
@pytest.mark.parametrize("func", ["fftfreq", "rfftfreq"])
361+
@pytest.mark.parametrize("n", [10, 20])
362+
@pytest.mark.parametrize("d", [0.5, 2])
363+
def test_fftfreq(self, func, n, d):
364+
expected = getattr(dpnp.fft, func)(n, d)
365+
result = getattr(numpy.fft, func)(n, d)
366+
assert_dtype_allclose(expected, result)
367+
368+
@pytest.mark.parametrize("func", ["fftfreq", "rfftfreq"])
369+
def test_error(self, func):
370+
# n should be an integer
371+
assert_raises(ValueError, getattr(dpnp.fft, func), 10.0)
372+
373+
# d should be an scalar
374+
assert_raises(ValueError, getattr(dpnp.fft, func), 10, (2,))

tests/test_sycl_queue.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,20 @@ def test_fft_rfft(type, shape, device):
12621262
assert_sycl_queue_equal(result_queue, expected_queue)
12631263

12641264

1265+
@pytest.mark.parametrize("func", ["fftfreq", "rfftfreq"])
1266+
@pytest.mark.parametrize(
1267+
"device",
1268+
valid_devices,
1269+
ids=[device.filter_string for device in valid_devices],
1270+
)
1271+
def test_fftfreq(func, device):
1272+
result = getattr(dpnp.fft, func)(10, 0.5, device=device)
1273+
expected = getattr(numpy.fft, func)(10, 0.5)
1274+
1275+
assert_dtype_allclose(result, expected)
1276+
assert result.sycl_device == device
1277+
1278+
12651279
@pytest.mark.parametrize(
12661280
"data, is_empty",
12671281
[

tests/test_usm_type.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,20 @@ def test_eigenvalue(func, shape, usm_type):
933933
assert a.usm_type == dp_val.usm_type
934934

935935

936+
@pytest.mark.parametrize("func", ["fftfreq", "rfftfreq"])
937+
@pytest.mark.parametrize("usm_type", list_of_usm_types + [None])
938+
def test_fftfreq(func, usm_type):
939+
result = getattr(dp.fft, func)(10, 0.5, usm_type=usm_type)
940+
expected = getattr(numpy.fft, func)(10, 0.5)
941+
942+
if usm_type is None:
943+
# assert against default USM type
944+
usm_type = "device"
945+
946+
assert_dtype_allclose(result, expected)
947+
assert result.usm_type == usm_type
948+
949+
936950
@pytest.mark.parametrize("func", ["fft", "ifft"])
937951
@pytest.mark.parametrize("usm_type", list_of_usm_types, ids=list_of_usm_types)
938952
def test_fft(func, usm_type):

0 commit comments

Comments
 (0)