Repository: solvcon/modmesh
Pull request #534: feat: add Fourier transform python wrapper
Author: login: j8xixo12
Created: 2025-05-02T03:50:00Z | Merged: 2025-05-05T09:30:09Z

In this PR, I have added Fourier transform python wrapper, including `fft`, `ifft` and `dft`.


Diff:
diff --git a/cpp/modmesh/CMakeLists.txt b/cpp/modmesh/CMakeLists.txt
index 53c809850..a1d494bba 100644
--- a/cpp/modmesh/CMakeLists.txt
+++ b/cpp/modmesh/CMakeLists.txt
@@ -126,7 +126,6 @@ if (BUILD_QT)
     target_link_libraries(
         modmesh_primary PUBLIC
         ${PYSIDE6_LIBFILE}
-        ${SHIBOKEN6_LIBFILE}
     )
 else () # BUILD_QT
     add_library(
diff --git a/cpp/modmesh/math/pymod/wrap_Complex.cpp b/cpp/modmesh/math/pymod/wrap_Complex.cpp
index 1911406fd..d20c426e6 100644
--- a/cpp/modmesh/math/pymod/wrap_Complex.cpp
+++ b/cpp/modmesh/math/pymod/wrap_Complex.cpp
@@ -60,7 +60,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapComplex
             .def(
                 py::init(
                     []()
-                    { return std::make_shared<wrapped_type>(); }))
+                    { return std::make_shared<wrapped_type>(wrapped_type{0.0, 0.0}); }))
             .def(
                 py::init(
                     [](const value_type & real, const value_type & imag)
diff --git a/cpp/modmesh/modmesh.hpp b/cpp/modmesh/modmesh.hpp
index 5a9c73e0e..7d64a7bb3 100644
--- a/cpp/modmesh/modmesh.hpp
+++ b/cpp/modmesh/modmesh.hpp
@@ -39,6 +39,7 @@
 #include <modmesh/grid.hpp>
 #include <modmesh/mesh/mesh.hpp>
 #include <modmesh/toggle/toggle.hpp>
+#include <modmesh/transform/transform.hpp>
 
 // TODO Add MSVC case once sanitizer can be default turned on for CI testing
 #if defined(USE_SANITIZER) && (defined(__clang__) || defined(__GNUC__))
diff --git a/cpp/modmesh/python/module.cpp b/cpp/modmesh/python/module.cpp
index 48cc5f140..0aef39782 100644
--- a/cpp/modmesh/python/module.cpp
+++ b/cpp/modmesh/python/module.cpp
@@ -31,6 +31,7 @@
 #include <modmesh/toggle/pymod/toggle_pymod.hpp>
 #include <modmesh/universe/pymod/universe_pymod.hpp>
 #include <modmesh/math/pymod/math_pymod.hpp>
+#include <modmesh/transform/pymod/transform_pymod.hpp>
 
 #ifdef USE_PYTEST_HELPER_BINDING
 #include <modmesh/testhelper/pymod/testbuffer_pymod.hpp>
@@ -59,6 +60,7 @@ void initialize(pybind11::module_ mod)
     initialize_spacetime(spacetime_mod);
     pybind11::module_ onedim_mod = mod.def_submodule("onedim", "onedim");
     initialize_onedim(onedim_mod);
+    initialize_transform(mod);
 
     pybind11::module_ testhelper_mod = mod.def_submodule("testhelper", "testhelper");
 #ifdef USE_PYTEST_HELPER_BINDING
diff --git a/cpp/modmesh/transform/CMakeLists.txt b/cpp/modmesh/transform/CMakeLists.txt
index 70a213417..23249ad24 100644
--- a/cpp/modmesh/transform/CMakeLists.txt
+++ b/cpp/modmesh/transform/CMakeLists.txt
@@ -9,9 +9,20 @@ set(MODMESH_TRANSFORM_SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/fourier.cpp
     CACHE FILEPATH "" FORCE)
 
+set(MODMESH_TRANSFORM_PYMODSOURCES
+    ${CMAKE_CURRENT_SOURCE_DIR}/pymod/transform_pymod.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/pymod/wrap_fourier.cpp
+    CACHE FILEPATH "" FORCE)
+
+set(MODMESH_TRANSFORM_PYMODHEADERS
+    ${CMAKE_CURRENT_SOURCE_DIR}/pymod/transform_pymod.hpp
+    CACHE FILEPATH "" FORCE)
+
 set(MODMESH_TRANSFORM_FILES
     ${MODMESH_TRANSFORM_HEADERS}
     ${MODMESH_TRANSFORM_SOURCES}
+    ${MODMESH_TRANSFORM_PYMODSOURCES}
+    ${MODMESH_TRANSFORM_PYMODHEADERS}
     CACHE FILEPATH "" FORCE)
 
 # vim: set ff=unix fenc=utf8 nobomb et sw=4 ts=4 sts=4:
diff --git a/cpp/modmesh/transform/fourier.cpp b/cpp/modmesh/transform/fourier.cpp
index e8a58f9f7..6327a5981 100644
--- a/cpp/modmesh/transform/fourier.cpp
+++ b/cpp/modmesh/transform/fourier.cpp
@@ -3,9 +3,6 @@
 namespace modmesh
 {
 
-namespace transform
-{
-
 namespace detail
 {
 
@@ -34,6 +31,4 @@ size_t next_power_of_two(size_t n)
 
 } /* end namespace detail */
 
-} /* end namespace transform */
-
 } // namespace modmesh
diff --git a/cpp/modmesh/transform/fourier.hpp b/cpp/modmesh/transform/fourier.hpp
index 2bdcf0aa3..a63926cb8 100644
--- a/cpp/modmesh/transform/fourier.hpp
+++ b/cpp/modmesh/transform/fourier.hpp
@@ -6,35 +6,13 @@
 namespace modmesh
 {
 
-namespace transform
-{
-
 namespace detail
 {
 
 size_t bit_reverse(size_t n, const size_t bits);
 size_t next_power_of_two(size_t n);
-
-} /* end namespace detail */
-
 template <template <typename> class T1, typename T2>
-void fft(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out);
-
-// TODO: The template of template is too complicate, we should find a way to make it easier.
-template <template <typename> typename T1, typename T2>
-void dft(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
-{
-    size_t N = in.size();
-    for (size_t i = 0; i < N; ++i)
-    {
-        for (size_t j = 0; j < N; ++j)
-        {
-            T2 tmp = -2.0 * pi<T2> * i * j / static_cast<T2>(N);
-            T1<T2> twiddle_factor{std::cos(tmp), std::sin(tmp)};
-            out[i] += in[j] * twiddle_factor;
-        }
-    }
-}
+void fft_bluestein(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out);
 
 template <template <typename> class T1, typename T2>
 void fft_radix_2(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
@@ -72,24 +50,71 @@ void fft_radix_2(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
     }
 }
 
-template <template <typename> class T1, typename T2>
-void ifft(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
-{
-    size_t N = in.size();
-    SimpleArray<T1<T2>> in_conj{modmesh::small_vector<size_t>{N}, T1<T2>{0.0, 0.0}};
+} /* end namespace detail */
 
-    for (size_t i = 0; i < N; ++i)
+class FourierTransform
+{
+public:
+    FourierTransform() = default;
+    ~FourierTransform() = default;
+    FourierTransform(const FourierTransform & other) = delete;
+    FourierTransform(FourierTransform && other) = delete;
+    FourierTransform & operator=(const FourierTransform & other) = delete;
+    FourierTransform & operator=(FourierTransform && other) = delete;
+
+    template <template <typename> class T1, typename T2>
+    static void fft(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
     {
-        in_conj[i] = in[i].conj();
+        const size_t N = in.size();
+
+        if ((N & (N - 1)) == 0)
+        {
+            detail::fft_radix_2<T1, T2>(in, out);
+        }
+        else
+        {
+            detail::fft_bluestein<T1, T2>(in, out);
+        }
     }
 
-    fft<T1, T2>(in_conj, out);
+    template <template <typename> class T1, typename T2>
+    static void ifft(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
+    {
+        size_t N = in.size();
+        SimpleArray<T1<T2>> in_conj{modmesh::small_vector<size_t>{N}, T1<T2>{0.0, 0.0}};
 
-    for (size_t i = 0; i < N; ++i)
+        for (size_t i = 0; i < N; ++i)
+        {
+            in_conj[i] = in[i].conj();
+        }
+
+        fft<T1, T2>(in_conj, out);
+
+        for (size_t i = 0; i < N; ++i)
+        {
+            out[i] = out[i].conj() / static_cast<T2>(N);
+        }
+    }
+
+    // TODO: The template of template is too complicate, we should find a way to make it easier.
+    template <template <typename> typename T1, typename T2>
+    static void dft(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
     {
-        out[i] = out[i].conj() / static_cast<T2>(N);
+        size_t N = in.size();
+        for (size_t i = 0; i < N; ++i)
+        {
+            for (size_t j = 0; j < N; ++j)
+            {
+                T2 tmp = -2.0 * pi<T2> * i * j / static_cast<T2>(N);
+                T1<T2> twiddle_factor{std::cos(tmp), std::sin(tmp)};
+                out[i] += in[j] * twiddle_factor;
+            }
+        }
     }
-}
+};
+
+namespace detail
+{
 
 template <template <typename> class T1, typename T2>
 void fft_bluestein(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
@@ -127,7 +152,7 @@ void fft_bluestein(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
         A[i] *= B[i];
     }
 
-    ifft<T1, T2>(A, a);
+    FourierTransform::ifft<T1, T2>(A, a);
 
     for (size_t i = 0; i < N; ++i)
     {
@@ -137,22 +162,7 @@ void fft_bluestein(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
     }
 }
 
-template <template <typename> class T1, typename T2>
-void fft(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
-{
-    const size_t N = in.size();
-
-    if ((N & (N - 1)) == 0)
-    {
-        fft_radix_2<T1, T2>(in, out);
-    }
-    else
-    {
-        fft_bluestein<T1, T2>(in, out);
-    }
-}
-
-} /* end namespace transform */
+} /* end of namespace detail */
 
 } /* end namespace modmesh */
 
diff --git a/cpp/modmesh/transform/pymod/transform_pymod.cpp b/cpp/modmesh/transform/pymod/transform_pymod.cpp
new file mode 100644
index 000000000..b3eb433b5
--- /dev/null
+++ b/cpp/modmesh/transform/pymod/transform_pymod.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2025, Chun-Hsu Lai <as2266317@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the copyright holder nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <modmesh/transform/pymod/transform_pymod.hpp>
+
+namespace modmesh
+{
+
+namespace python
+{
+
+struct transform_pymod_tag;
+
+template <>
+OneTimeInitializer<transform_pymod_tag> & OneTimeInitializer<transform_pymod_tag>::me()
+{
+    static OneTimeInitializer<transform_pymod_tag> instance;
+    return instance;
+}
+
+void initialize_transform(pybind11::module & mod)
+{
+    auto initialize_impl = [](pybind11::module & mod)
+    {
+        wrap_FourierTransform(mod);
+    };
+
+    OneTimeInitializer<transform_pymod_tag>::me()(mod, initialize_impl);
+}
+
+} /* end namespace python */
+
+} /* end namespace modmesh */
+
+// vim: set ff=unix fenc=utf8 nobomb et sw=4 ts=4 sts=4:
diff --git a/cpp/modmesh/transform/pymod/transform_pymod.hpp b/cpp/modmesh/transform/pymod/transform_pymod.hpp
new file mode 100644
index 000000000..adf1af331
--- /dev/null
+++ b/cpp/modmesh/transform/pymod/transform_pymod.hpp
@@ -0,0 +1,50 @@
+#pragma once
+
+/*
+ * Copyright (c) 2025, Chun-Hsu Lai <as2266317@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the copyright holder nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <pybind11/pybind11.h> // Must be the first include.
+#include <pybind11/stl.h>
+
+#include <modmesh/modmesh.hpp>
+#include <modmesh/python/common.hpp>
+
+namespace modmesh
+{
+
+namespace python
+{
+
+void initialize_transform(pybind11::module & mod);
+void wrap_FourierTransform(pybind11::module & mod);
+
+} /* end namespace python */
+
+} /* end namespace modmesh */
+
+// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4:
diff --git a/cpp/modmesh/transform/pymod/wrap_fourier.cpp b/cpp/modmesh/transform/pymod/wrap_fourier.cpp
new file mode 100644
index 000000000..42879a29d
--- /dev/null
+++ b/cpp/modmesh/transform/pymod/wrap_fourier.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2025, Chun-Hsu Lai <as2266317@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the copyright holder nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <modmesh/modmesh.hpp>
+
+#include <modmesh/transform/pymod/transform_pymod.hpp>
+
+#include <pybind11/numpy.h>
+#include <pybind11/operators.h>
+
+namespace modmesh
+{
+
+namespace python
+{
+
+class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapFourierTransform
+    : public WrapBase<WrapFourierTransform, modmesh::FourierTransform, std::shared_ptr<modmesh::FourierTransform>>
+{
+    using base_type = WrapBase<WrapFourierTransform, modmesh::FourierTransform, std::shared_ptr<modmesh::FourierTransform>>;
+    using wrapped_type = typename base_type::wrapped_type;
+
+    friend base_type;
+
+    WrapFourierTransform(pybind11::module & mod, char const * pyname, char const * pydoc)
+        : WrapBase<WrapFourierTransform, modmesh::FourierTransform, std::shared_ptr<modmesh::FourierTransform>>(mod, pyname, pydoc)
+    {
+        namespace py = pybind11; // NOLINT(misc-unused-alias-decls)
+
+        (*this)
+            .def_static("fft", &wrapped_type::fft<modmesh::Complex, double>, py::arg("input"), py::arg("output"))
+            .def_static("fft", &wrapped_type::fft<modmesh::Complex, float>, py::arg("input"), py::arg("output"))
+            .def_static("ifft", &wrapped_type::ifft<modmesh::Complex, double>, py::arg("input"), py::arg("output"))
+            .def_static("ifft", &wrapped_type::ifft<modmesh::Complex, float>, py::arg("input"), py::arg("output"))
+            .def_static("dft", &wrapped_type::dft<modmesh::Complex, double>, py::arg("input"), py::arg("output"))
+            .def_static("dft", &wrapped_type::dft<modmesh::Complex, float>, py::arg("input"), py::arg("output"));
+    }
+
+}; /* end class WrapFourierTransform */
+
+void wrap_FourierTransform(pybind11::module & mod)
+{
+    WrapFourierTransform::commit(mod, "FourierTransform", "Fourier transform library");
+}
+
+} /* end namespace python */
+
+} /* end namespace modmesh */
+
+// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4:
diff --git a/gtests/test_nopython_transform.cpp b/gtests/test_nopython_transform.cpp
index 2a7054d06..55ecd55dc 100644
--- a/gtests/test_nopython_transform.cpp
+++ b/gtests/test_nopython_transform.cpp
@@ -148,44 +148,44 @@ TYPED_TEST_SUITE(InverseTest, TestTypes);
 
 TYPED_TEST(ParsevalTest, fft)
 {
-    modmesh::transform::fft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->out);
+    modmesh::FourierTransform::fft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->out);
 
     this->verify_parseval();
 }
 
 TYPED_TEST(ParsevalTest, dft)
 {
-    modmesh::transform::dft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->out);
+    modmesh::FourierTransform::dft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->out);
 
     this->verify_parseval();
 }
 
 TYPED_TEST(DeltaFunctionTest, fft)
 {
-    modmesh::transform::fft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->out);
+    modmesh::FourierTransform::fft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->out);
 
     this->verify_delta_function();
 }
 
 TYPED_TEST(DeltaFunctionTest, dft)
 {
-    modmesh::transform::dft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->out);
+    modmesh::FourierTransform::dft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->out);
 
     this->verify_delta_function();
 }
 
 TYPED_TEST(InverseTest, fft)
 {
-    modmesh::transform::fft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->freq_domain);
-    modmesh::transform::ifft<modmesh::Complex, typename TypeParam::Type>(this->freq_domain, this->time_domain);
+    modmesh::FourierTransform::fft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->freq_domain);
+    modmesh::FourierTransform::ifft<modmesh::Complex, typename TypeParam::Type>(this->freq_domain, this->time_domain);
 
     this->verify_inverse_fft_function();
 }
 
 TYPED_TEST(InverseTest, dft)
 {
-    modmesh::transform::dft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->freq_domain);
-    modmesh::transform::ifft<modmesh::Complex, typename TypeParam::Type>(this->freq_domain, this->time_domain);
+    modmesh::FourierTransform::dft<modmesh::Complex, typename TypeParam::Type>(this->signal, this->freq_domain);
+    modmesh::FourierTransform::ifft<modmesh::Complex, typename TypeParam::Type>(this->freq_domain, this->time_domain);
 
     this->verify_inverse_fft_function();
 }
diff --git a/modmesh/core.py b/modmesh/core.py
index b82026c6c..6acd7b2fa 100644
--- a/modmesh/core.py
+++ b/modmesh/core.py
@@ -53,6 +53,7 @@
     'Plot3d',
     'complex64',
     'complex128',
+    'FourierTransform',
     'SimpleArray',
     'SimpleArrayBool',
     'SimpleArrayInt8',
diff --git a/tests/test_transform.py b/tests/test_transform.py
new file mode 100644
index 000000000..ca14e5085
--- /dev/null
+++ b/tests/test_transform.py
@@ -0,0 +1,137 @@
+# Copyright (c) 2025, Chun-Hsu Lai <as2266317@gmail.com>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the copyright holder nor the names of its contributors
+#   may be used to endorse or promote products derived from this software
+#   without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+import numpy as np
+
+import modmesh as mm
+
+
+class FourierTransformTB(mm.testing.TestBase):
+
+    def setUp(self):
+        pass
+
+    def real_rng(self):
+        pass
+
+    def imag_rng(self):
+        pass
+
+    def test_numpy_dft_comparison(self):
+        input_size = 100
+
+        mm_input = self.SimpleArray(input_size)
+        for i in range(input_size):
+            mm_input[i] = self.complex(self.real_rng(), self.imag_rng())
+
+        np_input = np.array(mm_input, copy=False)
+
+        mm_output = self.SimpleArray(input_size, self.complex())
+        mm.FourierTransform.dft(mm_input, mm_output)
+
+        np_output = np.fft.fft(np_input)
+
+        for i in range(input_size):
+            self.assert_allclose(mm_output[i].real, np_output[i].real)
+            self.assert_allclose(mm_output[i].imag, np_output[i].imag)
+
+    def test_numpy_fft_comparison(self):
+        input_size = 100
+
+        mm_input = self.SimpleArray(input_size)
+        for i in range(input_size):
+            mm_input[i] = self.complex(self.real_rng(), self.imag_rng())
+
+        np_input = np.array(mm_input, copy=False)
+
+        mm_output = self.SimpleArray(input_size, self.complex())
+        mm.FourierTransform.fft(mm_input, mm_output)
+
+        np_output = np.fft.fft(np_input)
+
+        for i in range(input_size):
+            self.assert_allclose(mm_output[i].real, np_output[i].real)
+            self.assert_allclose(mm_output[i].imag, np_output[i].imag)
+
+    def test_numpy_ifft_comparison(self):
+        input_size = 100
+
+        mm_input = self.SimpleArray(input_size)
+        for i in range(input_size):
+            mm_input[i] = self.complex(self.real_rng(), self.imag_rng())
+
+        np_input = np.array(mm_input, copy=False)
+
+        mm_output = self.SimpleArray(input_size, self.complex())
+        mm.FourierTransform.ifft(mm_input, mm_output)
+
+        np_output = np.fft.ifft(np_input)
+
+        for i in range(input_size):
+            self.assert_allclose(mm_output[i].real, np_output[i].real)
+            self.assert_allclose(mm_output[i].imag, np_output[i].imag)
+
+
+class TransformFp32TC(FourierTransformTB, unittest.TestCase):
+
+    def assert_allclose(self, *args, **kw):
+        if 'atol' not in kw:
+            kw['atol'] = 1.e-2
+        return super().assert_allclose(*args, **kw)
+
+    def real_rng(self):
+        return np.float32(np.random.uniform(-1.0, 1.0))
+
+    def imag_rng(self):
+        return np.float32(np.random.uniform(-1.0, 1.0))
+
+    def setUp(self):
+        np.random.seed()
+        self.complex = mm.complex64
+        self.SimpleArray = mm.SimpleArrayComplex64
+
+
+class TransformFp64TC(FourierTransformTB, unittest.TestCase):
+
+    def assert_allclose(self, *args, **kw):
+        if 'atol' not in kw:
+            kw['atol'] = 1.e-10
+        return super().assert_allclose(*args, **kw)
+
+    def real_rng(self):
+        return np.random.uniform(-1.0, 1.0)
+
+    def imag_rng(self):
+        return np.random.uniform(-1.0, 1.0)
+
+    def setUp(self):
+        np.random.seed()
+        self.complex = mm.complex128
+        self.SimpleArray = mm.SimpleArrayComplex128
+
+# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4:


Reviews:
user: login: j8xixo12
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T04:07:36Z
user: login: yungyuc
body: Thank you, @j8xixo12 , for the very complete patch.  Let's tailor a little bit.

- [x] Use `FourierTransform` as the name, not `Transform`, which should be reserved for geometry.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-02T05:43:06Z
user: login: j8xixo12
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T13:56:12Z
user: login: yungyuc
body: - [x] Match test name with `FourierTransform`.
- [x] Move the test helper functions into types in `setUp`.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-04T01:42:17Z
user: login: j8xixo12
body: > * [x]  Match test name with `FourierTransform`.
> * [x]  Move the test helper functions into types in `setUp`.

@yungyuc This PR is ready for the next review.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-04T16:40:10Z
user: login: yungyuc
body: Looks good.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-05T09:30:00Z

Review comments:
diff_hunk: @@ -126,7 +126,6 @@ if (BUILD_QT)
     target_link_libraries(
         modmesh_primary PUBLIC
         ${PYSIDE6_LIBFILE}
-        ${SHIBOKEN6_LIBFILE}
path: cpp/modmesh/CMakeLists.txt
user: login: j8xixo12
body: Since modmesh does not use `shiboken6` directly, I removed `shiboken6` from modmesh static library temporarily  to fix the symbol conflict issue mentioned in https://github.com/solvcon/modmesh/issues/485 and https://github.com/solvcon/modmesh/issues/530.

Once https://github.com/johnny19436 completes the modmesh `PyStaticMethod_New` implementation we can consider adding it back.
created_at: 2025-05-02T03:56:37Z
author_association: COLLABORATOR
diff_hunk: @@ -60,7 +60,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapComplex
             .def(
                 py::init(
                     []()
-                    { return std::make_shared<wrapped_type>(); }))
+                    { return std::make_shared<wrapped_type>(wrapped_type{0.0, 0.0}); }))
path: cpp/modmesh/math/pymod/wrap_Complex.cpp
user: login: j8xixo12
body: To avoid different behaviors across different compilers, I assigned initial values to the Python constructor without any arguments
created_at: 2025-05-02T04:01:01Z
author_association: COLLABORATOR
diff_hunk: @@ -72,24 +50,71 @@ void fft_radix_2(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
     }
 }
 
-template <template <typename> class T1, typename T2>
-void ifft(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
-{
-    size_t N = in.size();
-    SimpleArray<T1<T2>> in_conj{modmesh::small_vector<size_t>{N}, T1<T2>{0.0, 0.0}};
+} /* end namespace detail */
 
-    for (size_t i = 0; i < N; ++i)
+class Transform
path: cpp/modmesh/transform/fourier.hpp
user: login: j8xixo12
body: To prepare for the Python wrapper, encapsulate the Fourier transform interface in `Transform` class.
created_at: 2025-05-02T04:03:41Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,149 @@
+# Copyright (c) 2025, Chun-Hsu Lai <as2266317@gmail.com>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the copyright holder nor the names of its contributors
+#   may be used to endorse or promote products derived from this software
+#   without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+import numpy as np
+
+import modmesh as mm
+
+
+class TransformTB(mm.testing.TestBase):
+
+    def setUp(self):
+        pass
+
+    def mm_complex(self, real=0.0, imag=0.0):
+        pass
+
+    def real_rng(self):
+        pass
+
+    def imag_rng(self):
+        pass
+
+    def mm_simplearraycomplex(self, size):
+        pass
+
+    def test_numpy_dft_comparison(self):
path: tests/test_transform.py
user: login: j8xixo12
body: Comparing results with numpy to check if there are any issues with Fourier transform implementations and python wrapper.
created_at: 2025-05-02T04:04:37Z
author_association: COLLABORATOR
diff_hunk: @@ -126,7 +126,6 @@ if (BUILD_QT)
     target_link_libraries(
         modmesh_primary PUBLIC
         ${PYSIDE6_LIBFILE}
-        ${SHIBOKEN6_LIBFILE}
path: cpp/modmesh/CMakeLists.txt
user: login: yungyuc
body: I think you meant to say `modmesh_primary` does not use `shiboken6` directly, the latter does not need to be linked with the former.  The linking could be a mistake.  I think I did it, but I can't remember why I did it so, maybe [`wrap_pilot.cpp:39`](https://github.com/solvcon/modmesh/blob/c86b07ab77a281fcb558dea7448c3ba3171707b6/cpp/modmesh/pilot/wrap_pilot.cpp#L39):

```cpp
// Usually MODMESH_PYSIDE6_FULL is not defined unless for debugging.
#ifdef MODMESH_PYSIDE6_FULL
#include <pyside.h>
#else // MODMESH_PYSIDE6_FULL
namespace PySide
{
// The prototypes are taken from pyside.h
PyTypeObject * getTypeForQObject(const QObject * cppSelf);
PyObject * getWrapperForQObject(QObject * cppSelf, PyTypeObject * sbk_type);
QObject * convertToQObject(PyObject * object, bool raiseError);
} // end namespace PySide
#endif // MODMESH_PYSIDE6_FULL
```

This could be a cure to issue #530 .  @johnny19436, could you please also test it?
created_at: 2025-05-02T05:24:28Z
author_association: MEMBER
diff_hunk: @@ -60,7 +60,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapComplex
             .def(
                 py::init(
                     []()
-                    { return std::make_shared<wrapped_type>(); }))
+                    { return std::make_shared<wrapped_type>(wrapped_type{0.0, 0.0}); }))
path: cpp/modmesh/math/pymod/wrap_Complex.cpp
user: login: yungyuc
body: This is a benign discrepancy between Python and C++ code.
created_at: 2025-05-02T05:25:08Z
author_association: MEMBER
diff_hunk: @@ -72,24 +50,71 @@ void fft_radix_2(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
     }
 }
 
-template <template <typename> class T1, typename T2>
-void ifft(SimpleArray<T1<T2>> const & in, SimpleArray<T1<T2>> & out)
-{
-    size_t N = in.size();
-    SimpleArray<T1<T2>> in_conj{modmesh::small_vector<size_t>{N}, T1<T2>{0.0, 0.0}};
+} /* end namespace detail */
 
-    for (size_t i = 0; i < N; ++i)
+class Transform
path: cpp/modmesh/transform/fourier.hpp
user: login: yungyuc
body: Please don't use the term `Transform`, which will be more useful for geometry transform.  Please name it explicitly as `FourierTransform`.
created_at: 2025-05-02T05:40:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,149 @@
+# Copyright (c) 2025, Chun-Hsu Lai <as2266317@gmail.com>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the copyright holder nor the names of its contributors
+#   may be used to endorse or promote products derived from this software
+#   without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+import numpy as np
+
+import modmesh as mm
+
+
+class TransformTB(mm.testing.TestBase):
+
+    def setUp(self):
+        pass
+
+    def mm_complex(self, real=0.0, imag=0.0):
+        pass
+
+    def real_rng(self):
+        pass
+
+    def imag_rng(self):
+        pass
+
+    def mm_simplearraycomplex(self, size):
+        pass
+
+    def test_numpy_dft_comparison(self):
+        input_size = 100
+
+        mm_input = self.mm_simplearraycomplex(input_size)
+        for i in range(input_size):
+            mm_input[i] = self.mm_complex(self.real_rng(), self.imag_rng())
+
+        np_input = np.array(mm_input, copy=False)
+
+        mm_output = self.mm_simplearraycomplex(input_size)
+        mm.transform.dft(mm_input, mm_output)
+
+        np_output = np.fft.fft(np_input)
+
+        for i in range(input_size):
+            self.assert_allclose(mm_output[i].real, np_output[i].real)
+            self.assert_allclose(mm_output[i].imag, np_output[i].imag)
+
+    def test_numpy_fft_comparison(self):
+        input_size = 100
+
+        mm_input = self.mm_simplearraycomplex(input_size)
+        for i in range(input_size):
+            mm_input[i] = self.mm_complex(self.real_rng(), self.imag_rng())
+
+        np_input = np.array(mm_input, copy=False)
+
+        mm_output = self.mm_simplearraycomplex(input_size)
+        mm.transform.fft(mm_input, mm_output)
+
+        np_output = np.fft.fft(np_input)
+
+        for i in range(input_size):
+            self.assert_allclose(mm_output[i].real, np_output[i].real)
+            self.assert_allclose(mm_output[i].imag, np_output[i].imag)
+
+    def test_numpy_ifft_comparison(self):
+        input_size = 100
+
+        mm_input = self.mm_simplearraycomplex(input_size)
+        for i in range(input_size):
+            mm_input[i] = self.mm_complex(self.real_rng(), self.imag_rng())
+
+        np_input = np.array(mm_input, copy=False)
+
+        mm_output = self.mm_simplearraycomplex(input_size)
+        mm.transform.ifft(mm_input, mm_output)
+
+        np_output = np.fft.ifft(np_input)
+
+        for i in range(input_size):
+            self.assert_allclose(mm_output[i].real, np_output[i].real)
+            self.assert_allclose(mm_output[i].imag, np_output[i].imag)
+
+
+class TransformFp32TC(TransformTB, unittest.TestCase):
+
+    def assert_allclose(self, *args, **kw):
+        if 'atol' not in kw:
+            kw['atol'] = 1.e-2
+        return super().assert_allclose(*args, **kw)
+
+    def mm_complex(self, real=0.0, imag=0.0):
+        return mm.complex64(real, imag)
+
+    def mm_simplearraycomplex(self, size):
+        return mm.SimpleArrayComplex64(size, mm.complex64())
+
+    def real_rng(self):
+        return np.float32(np.random.uniform(-1.0, 1.0))
+
+    def imag_rng(self):
+        return np.float32(np.random.uniform(-1.0, 1.0))
+
+    def setUp(self):
+        np.random.seed()
+
+
+class TransformFp64TC(TransformTB, unittest.TestCase):
+
+    def assert_allclose(self, *args, **kw):
+        if 'atol' not in kw:
+            kw['atol'] = 1.e-10
+        return super().assert_allclose(*args, **kw)
+
+    def mm_complex(self, real=0.0, imag=0.0):
+        return mm.complex128(real, imag)
+
+    def mm_simplearraycomplex(self, size):
+        return mm.SimpleArrayComplex128(size, mm.complex128())
+
+    def real_rng(self):
+        return np.random.uniform(-1.0, 1.0)
+
+    def imag_rng(self):
+        return np.random.uniform(-1.0, 1.0)
+
+    def setUp(self):
+        np.random.seed()
path: tests/test_transform.py
user: login: yungyuc
body: Modeline.
created_at: 2025-05-02T05:41:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2025, Chun-Hsu Lai <as2266317@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the copyright holder nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <modmesh/modmesh.hpp>
path: cpp/modmesh/transform/pymod/wrap_fourier.cpp
user: login: yungyuc
body: Blankline above.
created_at: 2025-05-02T05:42:04Z
author_association: MEMBER
diff_hunk: @@ -126,7 +126,6 @@ if (BUILD_QT)
     target_link_libraries(
         modmesh_primary PUBLIC
         ${PYSIDE6_LIBFILE}
-        ${SHIBOKEN6_LIBFILE}
path: cpp/modmesh/CMakeLists.txt
user: login: j8xixo12
body: Yes, I meant `modmesh_primary`. I think https://github.com/johnny19436 has tested it and we also have a discussion on [discord](https://discord.com/channels/730297880140578906/1365618257838608495/1366609273479757864).
created_at: 2025-05-02T13:56:12Z
author_association: COLLABORATOR
diff_hunk: @@ -126,7 +126,6 @@ if (BUILD_QT)
     target_link_libraries(
         modmesh_primary PUBLIC
         ${PYSIDE6_LIBFILE}
-        ${SHIBOKEN6_LIBFILE}
path: cpp/modmesh/CMakeLists.txt
user: login: yungyuc
body: Now that adds up.
created_at: 2025-05-04T01:34:49Z
author_association: MEMBER
diff_hunk: @@ -3,9 +3,6 @@
 namespace modmesh
 {
 
-namespace transform
path: cpp/modmesh/transform/fourier.cpp
user: login: yungyuc
body: It is good to remove the namespace `transform`.  Under `modmesh`, there should not be many namespaces.
created_at: 2025-05-04T01:37:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,151 @@
+# Copyright (c) 2025, Chun-Hsu Lai <as2266317@gmail.com>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the copyright holder nor the names of its contributors
+#   may be used to endorse or promote products derived from this software
+#   without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+import numpy as np
+
+import modmesh as mm
+
+
+class TransformTB(mm.testing.TestBase):
path: tests/test_transform.py
user: login: yungyuc
body: The test class name should match that of `FourierTransform`.
created_at: 2025-05-04T01:38:58Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,151 @@
+# Copyright (c) 2025, Chun-Hsu Lai <as2266317@gmail.com>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the copyright holder nor the names of its contributors
+#   may be used to endorse or promote products derived from this software
+#   without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+import numpy as np
+
+import modmesh as mm
+
+
+class TransformTB(mm.testing.TestBase):
+
+    def setUp(self):
+        pass
+
+    def mm_complex(self, real=0.0, imag=0.0):
+        pass
+
+    def real_rng(self):
+        pass
+
+    def imag_rng(self):
+        pass
+
+    def mm_simplearraycomplex(self, size):
+        pass
+
+    def test_numpy_dft_comparison(self):
+        input_size = 100
+
+        mm_input = self.mm_simplearraycomplex(input_size)
+        for i in range(input_size):
+            mm_input[i] = self.mm_complex(self.real_rng(), self.imag_rng())
+
+        np_input = np.array(mm_input, copy=False)
+
+        mm_output = self.mm_simplearraycomplex(input_size)
+        mm.FourierTransform.dft(mm_input, mm_output)
+
+        np_output = np.fft.fft(np_input)
+
+        for i in range(input_size):
+            self.assert_allclose(mm_output[i].real, np_output[i].real)
+            self.assert_allclose(mm_output[i].imag, np_output[i].imag)
+
+    def test_numpy_fft_comparison(self):
+        input_size = 100
+
+        mm_input = self.mm_simplearraycomplex(input_size)
+        for i in range(input_size):
+            mm_input[i] = self.mm_complex(self.real_rng(), self.imag_rng())
+
+        np_input = np.array(mm_input, copy=False)
+
+        mm_output = self.mm_simplearraycomplex(input_size)
+        mm.FourierTransform.fft(mm_input, mm_output)
+
+        np_output = np.fft.fft(np_input)
+
+        for i in range(input_size):
+            self.assert_allclose(mm_output[i].real, np_output[i].real)
+            self.assert_allclose(mm_output[i].imag, np_output[i].imag)
+
+    def test_numpy_ifft_comparison(self):
+        input_size = 100
+
+        mm_input = self.mm_simplearraycomplex(input_size)
+        for i in range(input_size):
+            mm_input[i] = self.mm_complex(self.real_rng(), self.imag_rng())
+
+        np_input = np.array(mm_input, copy=False)
+
+        mm_output = self.mm_simplearraycomplex(input_size)
+        mm.FourierTransform.ifft(mm_input, mm_output)
+
+        np_output = np.fft.ifft(np_input)
+
+        for i in range(input_size):
+            self.assert_allclose(mm_output[i].real, np_output[i].real)
+            self.assert_allclose(mm_output[i].imag, np_output[i].imag)
+
+
+class TransformFp32TC(TransformTB, unittest.TestCase):
+
+    def assert_allclose(self, *args, **kw):
+        if 'atol' not in kw:
+            kw['atol'] = 1.e-2
+        return super().assert_allclose(*args, **kw)
+
+    def mm_complex(self, real=0.0, imag=0.0):
path: tests/test_transform.py
user: login: yungyuc
body: The multitude of test helpers are bad for maintainability.  Please move the helper functions into types in `setUp`:

```python
def setUp(self):
    self.complex = mm.complex64
    self.SimpleArray = mm.SimpleArrayComplex64
    # so on and so forth.
```
created_at: 2025-05-04T01:41:58Z
author_association: MEMBER
diff_hunk: @@ -124,6 +112,8 @@ def imag_rng(self):
 
     def setUp(self):
         np.random.seed()
+        self.complex = mm.complex64
+        self.SimpleArray = mm.SimpleArrayComplex64
path: tests/test_transform.py
user: login: j8xixo12
body: Move helper function to types into `setUp`.
created_at: 2025-05-04T16:37:42Z
author_association: COLLABORATOR
diff_hunk: @@ -31,7 +31,7 @@
 import modmesh as mm
 
 
-class TransformTB(mm.testing.TestBase):
+class FourierTransformTB(mm.testing.TestBase):
path: tests/test_transform.py
user: login: j8xixo12
body: Rename to `FourierTransform`.
created_at: 2025-05-04T16:38:35Z
author_association: COLLABORATOR

PR comments:
user: login: j8xixo12
created_at: 2025-05-02T04:08:33Z
body: @yungyuc this PR is ready for review.
author_association: COLLABORATOR
user: login: j8xixo12
created_at: 2025-05-02T13:57:30Z
body: @yungyuc This PR is ready for next review.
author_association: COLLABORATOR