# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import unittest import numpy as np import scipy.fft import paddle class TestFFTAliasBase(unittest.TestCase): def setUp(self): np.random.seed(2025) self.places = [paddle.CPUPlace()] if paddle.is_compiled_with_cuda(): self.places.append(paddle.CUDAPlace(0)) self.init_params() self.init_data() self.paddle_axis_arg = "axes" if self.is_nd_or_shift else "axis" def init_params(self): self.shape = [2, 4, 6] self.test_dim_val = -1 self.norm = "backward" self.dtype = "float32" self.is_real_input = False self.is_nd_or_shift = False self.paddle_api = None self.scipy_api = None def init_data(self): if self.is_real_input: self.np_x = np.random.rand(*self.shape).astype(self.dtype) else: real = np.random.rand(*self.shape).astype(self.dtype) imag = np.random.rand(*self.shape).astype(self.dtype) self.np_x = real + 1j * imag def get_scipy_ref(self): kwargs = {"norm": self.norm} if 'shift' in self.scipy_api.__name__: return self.scipy_api(self.np_x, axes=self.test_dim_val) if self.is_nd_or_shift: kwargs["axes"] = self.test_dim_val else: kwargs["axis"] = self.test_dim_val return self.scipy_api(self.np_x, **kwargs) def test_dygraph_Compatibility(self): if self.paddle_api is None: return paddle.disable_static() for place in self.places: paddle.set_device(place) x = paddle.to_tensor(self.np_x) paddle_dygraph_out = [] # 1. Paddle Usage kw_std = {"x": x, self.paddle_axis_arg: self.test_dim_val} if 'shift' not in self.paddle_api.__name__: kw_std["norm"] = self.norm out1 = self.paddle_api(**kw_std) paddle_dygraph_out.append(out1) # 2. Alias Usage kw_alias = {"input": x, "dim": self.test_dim_val} if 'shift' not in self.paddle_api.__name__: kw_alias["norm"] = self.norm out2 = self.paddle_api(**kw_alias) paddle_dygraph_out.append(out2) # 3. Alias Usage with 'out' parameter (Skip for shift APIs) if 'shift' not in self.paddle_api.__name__: out_tensor = paddle.empty_like(out1) kw_out = { "input": x, "dim": self.test_dim_val, "out": out_tensor, "norm": self.norm, } self.paddle_api(**kw_out) paddle_dygraph_out.append(out_tensor) ref_out = self.get_scipy_ref() for i, out in enumerate(paddle_dygraph_out): np.testing.assert_allclose( out.numpy(), ref_out, rtol=1e-05, atol=1e-08, err_msg=f"Dygraph mismatch case {i} (0=std, 1=alias, 2=out) for {self.paddle_api.__name__} on {place}", ) paddle.enable_static() def test_static_Compatibility(self): if self.paddle_api is None: return paddle.enable_static() main = paddle.static.Program() startup = paddle.static.Program() with paddle.base.program_guard(main, startup): x = paddle.static.data( name="x", shape=self.shape, dtype=self.np_x.dtype ) # 1. Paddle Usage kw_std = {"x": x, self.paddle_axis_arg: self.test_dim_val} if 'shift' not in self.paddle_api.__name__: kw_std["norm"] = self.norm out_std = self.paddle_api(**kw_std) # 2. Alias Usage kw_alias = {"input": x, "dim": self.test_dim_val} if 'shift' not in self.paddle_api.__name__: kw_alias["norm"] = self.norm out_alias = self.paddle_api(**kw_alias) ref_out = self.get_scipy_ref() for place in self.places: exe = paddle.base.Executor(place) exe.run(startup) fetches = exe.run( main, feed={"x": self.np_x}, fetch_list=[out_std, out_alias], ) np.testing.assert_allclose( fetches[0], ref_out, rtol=1e-05, atol=1e-08, err_msg=f"Static graph mismatch (Standard Args) for {self.paddle_api.__name__} on {place}", ) np.testing.assert_allclose( fetches[1], ref_out, rtol=1e-05, atol=1e-08, err_msg=f"Static graph mismatch (Alias Args) for {self.paddle_api.__name__} on {place}", ) paddle.disable_static() class TestFFT_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.fft self.scipy_api = scipy.fft.fft self.is_real_input = False self.is_nd_or_shift = False class TestIFFT_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.ifft self.scipy_api = scipy.fft.ifft self.is_real_input = False self.is_nd_or_shift = False class TestRFFT_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.rfft self.scipy_api = scipy.fft.rfft self.is_real_input = True self.is_nd_or_shift = False class TestIRFFT_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.irfft self.scipy_api = scipy.fft.irfft self.is_real_input = False self.is_nd_or_shift = False class TestHFFT_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.hfft self.scipy_api = scipy.fft.hfft self.is_real_input = False self.is_nd_or_shift = False class TestIHFFT_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.ihfft self.scipy_api = scipy.fft.ihfft self.is_real_input = True self.is_nd_or_shift = False class TestFFT2_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.fft2 self.scipy_api = scipy.fft.fft2 self.is_real_input = False self.is_nd_or_shift = True self.test_dim_val = (0, 1) class TestIFFT2_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.ifft2 self.scipy_api = scipy.fft.ifft2 self.is_real_input = False self.is_nd_or_shift = True self.test_dim_val = (0, 1) class TestRFFT2_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.rfft2 self.scipy_api = scipy.fft.rfft2 self.is_real_input = True self.is_nd_or_shift = True self.test_dim_val = (0, 1) class TestIRFFT2_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.irfft2 self.scipy_api = scipy.fft.irfft2 self.is_real_input = False self.is_nd_or_shift = True self.test_dim_val = (0, 1) class TestHFFT2_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.hfft2 self.scipy_api = scipy.fft.hfft2 self.is_real_input = False self.is_nd_or_shift = True self.test_dim_val = (0, 1) class TestIHFFT2_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.ihfft2 self.scipy_api = scipy.fft.ihfft2 self.is_real_input = True self.is_nd_or_shift = True self.test_dim_val = (0, 1) class TestFFTN_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.fftn self.scipy_api = scipy.fft.fftn self.is_real_input = False self.is_nd_or_shift = True self.test_dim_val = (0, 1, 2) class TestIFFTN_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.ifftn self.scipy_api = scipy.fft.ifftn self.is_real_input = False self.is_nd_or_shift = True self.test_dim_val = (0, 1, 2) class TestRFFTN_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.rfftn self.scipy_api = scipy.fft.rfftn self.is_real_input = True self.is_nd_or_shift = True self.test_dim_val = (0, 1, 2) class TestIRFFTN_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.irfftn self.scipy_api = scipy.fft.irfftn self.is_real_input = False self.is_nd_or_shift = True self.test_dim_val = (0, 1, 2) class TestHFFTN_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.hfftn self.scipy_api = scipy.fft.hfftn self.is_real_input = False self.is_nd_or_shift = True self.test_dim_val = (0, 1, 2) class TestIHFFTN_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.ihfftn self.scipy_api = scipy.fft.ihfftn self.is_real_input = True self.is_nd_or_shift = True self.test_dim_val = (0, 1, 2) class TestFFTShift_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.fftshift self.scipy_api = scipy.fft.fftshift self.is_real_input = False self.is_nd_or_shift = True self.test_dim_val = (0, 1) class TestIFFTShift_Alias(TestFFTAliasBase): def init_params(self): super().init_params() self.paddle_api = paddle.fft.ifftshift self.scipy_api = scipy.fft.ifftshift self.is_real_input = False self.is_nd_or_shift = True self.test_dim_val = (0, 1) class TestFftfreq(unittest.TestCase): def test_basic_usage(self): result = paddle.fft.fftfreq(n=10, d=1.0) self.assertEqual(result.shape, [10]) expected = paddle.to_tensor( [0.0, 0.1, 0.2, 0.3, 0.4, -0.5, -0.4, -0.3, -0.2, -0.1], dtype='float32', ) self.assertTrue(paddle.allclose(result, expected)) def test_all_parameters(self): out = paddle.zeros([10], dtype='float32') result = paddle.fft.fftfreq( n=10, d=2.0, dtype='float64', out=out, device='cpu', requires_grad=True, ) self.assertTrue(result.place.is_cpu_place()) self.assertFalse(result.stop_gradient) self.assertEqual(result.dtype, paddle.float64) expected = paddle.to_tensor( [0.0, 0.05, 0.1, 0.15, 0.2, -0.25, -0.2, -0.15, -0.1, -0.05], dtype='float64', ) self.assertTrue(paddle.allclose(result, expected)) class TestRfftfreq(unittest.TestCase): def test_basic_usage(self): result = paddle.fft.rfftfreq(n=10, d=1.0) self.assertEqual(result.shape, [6]) # 1 + n//2 = 6 expected = paddle.arange(0, 6) * (1.0 / (10 * 1.0)) self.assertTrue(paddle.allclose(result, expected)) def test_all_parameters(self): out = paddle.zeros([6], dtype='float32') result = paddle.fft.rfftfreq( n=10, d=2.0, dtype='float64', out=out, device='cpu', requires_grad=True, ) self.assertTrue(result.place.is_cpu_place()) self.assertFalse(result.stop_gradient) self.assertEqual(result.dtype, paddle.float64) expected = paddle.arange(0, 6, dtype='float64') * (1.0 / (10 * 2.0)) self.assertTrue(paddle.allclose(result, expected)) if __name__ == "__main__": unittest.main()