summaryrefslogtreecommitdiff
path: root/silx/math/fft/test/test_fft.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/math/fft/test/test_fft.py')
-rw-r--r--silx/math/fft/test/test_fft.py247
1 files changed, 87 insertions, 160 deletions
diff --git a/silx/math/fft/test/test_fft.py b/silx/math/fft/test/test_fft.py
index b0e595b..51f846f 100644
--- a/silx/math/fft/test/test_fft.py
+++ b/silx/math/fft/test/test_fft.py
@@ -2,7 +2,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2018 European Synchrotron Radiation Facility
+# Copyright (c) 2018-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -23,21 +23,27 @@
# THE SOFTWARE.
#
# ###########################################################################*/
-"""Test of the MFFT module"""
+"""Test of the FFT module"""
import numpy as np
import unittest
import logging
-from scipy.misc import ascent
-from silx.utils.testutils import parameterize
+try:
+ from scipy.misc import ascent
+ __have_scipy = True
+except ImportError:
+ __have_scipy = False
+from silx.utils.testutils import ParametricTestCase
from silx.math.fft.fft import FFT
from silx.math.fft.clfft import __have_clfft__
from silx.math.fft.cufft import __have_cufft__
from silx.math.fft.fftw import __have_fftw__
from silx.test.utils import test_options
+
logger = logging.getLogger(__name__)
+
class TransformInfos(object):
def __init__(self):
self.dimensions = [
@@ -73,7 +79,7 @@ class TransformInfos(object):
class TestData(object):
def __init__(self):
self.data = ascent().astype("float32")
- self.data1d = self.data[:, 0] # non-contiguous data
+ self.data1d = self.data[:, 0] # non-contiguous data
self.data3d = np.tile(self.data[:128, :128], (128, 1, 1))
self.data_refs = {
1: self.data1d,
@@ -82,29 +88,9 @@ class TestData(object):
}
-
-class TestFFT(unittest.TestCase):
-
- @classmethod
- def setUpClass(cls):
- super(TestFFT, cls).setUpClass()
- cls.Ctx = None
- if __have_clfft__:
- from silx.opencl.common import ocl
- if ocl is not None:
- cls.Ctx = ocl.create_context()
-
- @classmethod
- def tearDownClass(cls):
- super(TestFFT, cls).tearDownClass()
- if cls.Ctx is not None:
- del cls.Ctx
-
-
- def __init__(self, methodName='runTest', param=None):
- unittest.TestCase.__init__(self, methodName)
- self.param = param
-
+@unittest.skipUnless(__have_scipy, "scipy is missing")
+class TestFFT(ParametricTestCase):
+ """Test cuda/opencl/fftw backends of FFT"""
def setUp(self):
self.tol = {
@@ -113,48 +99,8 @@ class TestFFT(unittest.TestCase):
np.dtype("complex64"): 1e-3,
np.dtype("complex128"): 1e-9,
}
- self.backend = self.param["backend"]
- self.trdim = self.param["trdim"]
- self.mode = self.param["mode"]
- self.size = self.param["size"]
- self.transform_infos = self.param["transform_infos"]
- self.test_data = self.param["test_data"]
- self.configure_backends()
- self.configure_extra_args()
- if self.backend == "opencl" and self.Ctx is None:
- self.skipTest("PyopenCL is missing")
-
-
- def tearDown(self):
- pass
-
-
- def configure_backends(self):
- self.__have_clfft__ = __have_clfft__
- self.__have_cufft__ = __have_cufft__
- self.__have_fftw__ = __have_fftw__
-
- if self.backend in ["cuda", "cufft"] and __have_cufft__:
- import pycuda.autoinit
- # Error is higher when using cuda. fast_math mode ?
- self.tol[np.dtype("float32")] *= 2
-
-
- def configure_extra_args(self):
- self.extra_args = {}
- if __have_clfft__ and self.backend in ["opencl", "clfft"]:
- self.extra_args["ctx"] = self.Ctx
-
-
- def check_current_backend(self):
- if self.backend in ["cuda", "cufft"] and not(self.__have_cufft__):
- return "cuda back-end requires pycuda and scikit-cuda"
- if self.backend in ["opencl", "clfft"] and not(self.__have_clfft__):
- return "opencl back-end requires pyopencl and gpyfft"
- if self.backend == "fftw" and not(self.__have_fftw__):
- return "fftw back-end requires pyfftw"
- return None
-
+ self.transform_infos = TransformInfos()
+ self.test_data = TestData()
@staticmethod
def calc_mae(arr1, arr2):
@@ -163,33 +109,66 @@ class TestFFT(unittest.TestCase):
"""
return np.max(np.abs(arr1 - arr2))
+ @unittest.skipIf(not __have_cufft__,
+ "cuda back-end requires pycuda and scikit-cuda")
+ def test_cuda(self):
+ import pycuda.autoinit
+
+ # Error is higher when using cuda. fast_math mode ?
+ self.tol[np.dtype("float32")] *= 2
+
+ self.__run_tests(backend="cuda")
+
+ @unittest.skipIf(not __have_clfft__,
+ "opencl back-end requires pyopencl and gpyfft")
+ def test_opencl(self):
+ from silx.opencl.common import ocl
+ self.__run_tests(backend="opencl", ctx=ocl.create_context())
- def test_fft(self):
- err = self.check_current_backend()
- if err is not None:
- self.skipTest(err)
- if self.size == "3D" and test_options.TEST_LOW_MEM:
+ @unittest.skipIf(not __have_fftw__,
+ "fftw back-end requires pyfftw")
+ def test_fftw(self):
+ self.__run_tests(backend="fftw")
+
+ def __run_tests(self, backend, **extra_args):
+ """Run all tests with the given backend
+
+ :param str backend:
+ :param dict extra_args: Additional arguments to provide to FFT
+ """
+ for trdim in self.transform_infos.dimensions:
+ for mode in self.transform_infos.modes:
+ for size in self.transform_infos.sizes[trdim]:
+ with self.subTest(trdim=trdim, mode=mode, size=size):
+ self.__test(backend, trdim, mode, size, **extra_args)
+
+ def __test(self, backend, trdim, mode, size, **extra_args):
+ """Compare given backend with numpy for given conditions"""
+ logger.debug("backend: %s, trdim: %s, mode: %s, size: %s",
+ backend, trdim, mode, str(size))
+ if size == "3D" and test_options.TEST_LOW_MEM:
self.skipTest("low mem")
- ndim = len(self.size)
- input_data = self.test_data.data_refs[ndim].astype(self.transform_infos.modes[self.mode])
+ ndim = len(size)
+ input_data = self.test_data.data_refs[ndim].astype(
+ self.transform_infos.modes[mode])
tol = self.tol[np.dtype(input_data.dtype)]
- if self.trdim == "3D":
- tol *= 10 # Error is relatively high in high dimensions
+ if trdim == "3D":
+ tol *= 10 # Error is relatively high in high dimensions
# Python < 3.5 does not want to mix **extra_args with existing kwargs
fft_args = {
"template": input_data,
- "axes": self.transform_infos.axes[self.trdim],
- "backend": self.backend,
+ "axes": self.transform_infos.axes[trdim],
+ "backend": backend,
}
- fft_args.update(self.extra_args)
+ fft_args.update(extra_args)
F = FFT(
**fft_args
)
F_np = FFT(
template=input_data,
- axes=self.transform_infos.axes[self.trdim],
+ axes=self.transform_infos.axes[trdim],
backend="numpy"
)
@@ -199,7 +178,7 @@ class TestFFT(unittest.TestCase):
mae = self.calc_mae(res, res_np)
self.assertTrue(
mae < np.abs(input_data.max()) * tol,
- "FFT %s:%s, MAE(%s, numpy) = %f" % (self.mode, self.trdim, self.backend, mae)
+ "FFT %s:%s, MAE(%s, numpy) = %f" % (mode, trdim, backend, mae)
)
# Inverse FFT
@@ -207,19 +186,16 @@ class TestFFT(unittest.TestCase):
mae = self.calc_mae(res2, input_data)
self.assertTrue(
mae < tol,
- "IFFT %s:%s, MAE(%s, numpy) = %f" % (self.mode, self.trdim, self.backend, mae)
+ "IFFT %s:%s, MAE(%s, numpy) = %f" % (mode, trdim, backend, mae)
)
-class TestNumpyFFT(unittest.TestCase):
+@unittest.skipUnless(__have_scipy, "scipy is missing")
+class TestNumpyFFT(ParametricTestCase):
"""
Test the Numpy backend individually.
"""
- def __init__(self, methodName='runTest', param=None):
- unittest.TestCase.__init__(self, methodName)
- self.param = param
-
def setUp(self):
transforms = {
"1D": {
@@ -238,22 +214,30 @@ class TestNumpyFFT(unittest.TestCase):
transforms["batched_1D"] = transforms["1D"]
transforms["batched_2D"] = transforms["2D"]
self.transforms = transforms
+ self.transform_infos = TransformInfos()
+ self.test_data = TestData()
+ def test(self):
+ """Test the numpy backend against native fft.
- def test_numpy_fft(self):
- """
- Test the numpy backend against native fft.
Results should be exactly the same.
"""
- trinfos = self.param["transform_infos"]
- trdim = self.param["trdim"]
- ndim = len(self.param["size"])
- input_data = self.param["test_data"].data_refs[ndim].astype(trinfos.modes[self.param["mode"]])
+ for trdim in self.transform_infos.dimensions:
+ for mode in self.transform_infos.modes:
+ for size in self.transform_infos.sizes[trdim]:
+ with self.subTest(trdim=trdim, mode=mode, size=size):
+ self.__test(trdim, mode, size)
+
+ def __test(self, trdim, mode, size):
+ logger.debug("trdim: %s, mode: %s, size: %s", trdim, mode, str(size))
+ ndim = len(size)
+ input_data = self.test_data.data_refs[ndim].astype(
+ self.transform_infos.modes[mode])
np_fft, np_ifft = self.transforms[trdim][np.isrealobj(input_data)]
F = FFT(
template=input_data,
- axes=trinfos.axes[trdim],
+ axes=self.transform_infos.axes[trdim],
backend="numpy"
)
# Test FFT
@@ -267,72 +251,15 @@ class TestNumpyFFT(unittest.TestCase):
self.assertTrue(np.allclose(res2, ref2))
-def test_numpy_backend(dimensions=None):
- testSuite = unittest.TestSuite()
- transform_infos = TransformInfos()
- test_data = TestData()
- dimensions = dimensions or transform_infos.dimensions
-
- for trdim in dimensions:
- logger.debug(" testing %s" % trdim)
- for mode in transform_infos.modes:
- logger.debug(" testing %s:%s" % (trdim, mode))
- for size in transform_infos.sizes[trdim]:
- logger.debug(" size: %s" % str(size))
- testcase = parameterize(
- TestNumpyFFT,
- param={
- "transform_infos": transform_infos,
- "test_data": test_data,
- "trdim": trdim,
- "mode": mode,
- "size": size,
- }
- )
- testSuite.addTest(testcase)
- return testSuite
-
-
-def test_fft(backend, dimensions=None):
- testSuite = unittest.TestSuite()
- transform_infos = TransformInfos()
- test_data = TestData()
- dimensions = dimensions or transform_infos.dimensions
-
- logger.info("Testing backend: %s" % backend)
- for trdim in dimensions:
- logger.debug(" testing %s" % trdim)
- for mode in transform_infos.modes:
- logger.debug(" testing %s:%s" % (trdim, mode))
- for size in transform_infos.sizes[trdim]:
- logger.debug(" size: %s" % str(size))
- testcase = parameterize(
- TestFFT,
- param={
- "transform_infos": transform_infos,
- "test_data": test_data,
- "backend": backend,
- "trdim": trdim,
- "mode": mode,
- "size": size,
- }
- )
- testSuite.addTest(testcase)
- return testSuite
-
-
-def test_all():
+def suite():
suite = unittest.TestSuite()
-
- suite.addTest(test_numpy_backend())
-
- suite.addTest(test_fft("fftw"))
- suite.addTest(test_fft("opencl"))
- suite.addTest(test_fft("cuda"))
+ for cls in (TestNumpyFFT, TestFFT):
+ suite.addTest(
+ unittest.defaultTestLoader.loadTestsFromTestCase(cls))
return suite
if __name__ == '__main__':
- unittest.main(defaultTest="test_all")
+ unittest.main(defaultTest="suite")