vector-fftw-0.1.4.0: tests/FFTProperties.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
-- This module uses the test-framework-quickcheck2 package.
module Main where
import Control.Monad
import qualified Data.Vector.Unboxed as V
import qualified Data.Vector.Storable as VS
import Data.Complex
import Test.Framework (defaultMain, testGroup)
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Test.QuickCheck
import qualified Numeric.FFT.Vector.Invertible as I
import qualified Numeric.FFT.Vector.Invertible.Multi as IM
import qualified Numeric.FFT.Vector.Unitary as U
import qualified Numeric.FFT.Vector.Unitary.Multi as UM
import Numeric.FFT.Vector.Plan
main = defaultMain
-- NB: There's no explicit tests for the Unnormalized package.
-- However, its Planners are implicitly used by the other modules,
-- so it's covered in the below tests.
[ testGroup "invertibility"
[ testProperty "I.dft" $ prop_invert I.dft I.idft
, testProperty "I.dftR2C" $ prop_invert I.dftR2C I.dftC2R
, testProperty "I.dct1" $ prop_invert I.dct1 I.idct1
, testProperty "I.dct2" $ prop_invert I.dct2 I.idct2
, testProperty "I.dct3" $ prop_invert I.dct3 I.idct3
, testProperty "I.dct4" $ prop_invert I.dct4 I.idct4
, testProperty "I.dst1" $ prop_invert I.dst1 I.idst1
, testProperty "I.dst2" $ prop_invert I.dst2 I.idst2
, testProperty "I.dst3" $ prop_invert I.dst3 I.idst3
, testProperty "I.dst4" $ prop_invert I.dst4 I.idst4
, testProperty "U.dft" $ prop_invert U.dft U.idft
, testProperty "U.dftR2C" $ prop_invert U.dftR2C U.dftC2R
, testProperty "U.dct2" $ prop_invert U.dct2 U.idct2
]
, testGroup "orthogonality"
[ testProperty "U.dft" $ prop_orthog U.dft
, testProperty "U.idft" $ prop_orthog U.idft
, testProperty "U.dftR2C" $ prop_orthog U.dftR2C
, testProperty "U.dftC2R" $ prop_orthog U.dftR2C
, testProperty "U.dct2" $ prop_orthog U.dct2
, testProperty "U.idct2" $ prop_orthog U.idct2
, testProperty "U.dct4" $ prop_orthog U.dct4
]
, testGroup "invertibility ND"
[ testProperty "IM.dft" $ prop_invertND IM.dft IM.idft
, testProperty "IM.dftR2C" $ prop_invertND IM.dftR2C IM.dftC2R
, testProperty "UM.dft" $ prop_invertND UM.dft UM.idft
, testProperty "UM.dftR2C" $ prop_invertND UM.dftR2C UM.dftC2R
]
, testGroup "orthogonality"
[ testProperty "UM.dft" $ prop_orthogND UM.dft
, testProperty "UM.idft" $ prop_orthogND UM.idft
, testProperty "UM.dftR2C" $ prop_orthogND UM.dftR2C
, testProperty "UM.dftC2R" $ prop_orthogND UM.dftR2C
]
]
-------------------
-- An instance of Arbitrary that probably belongs in another package.
instance (V.Unbox a, Arbitrary a) => Arbitrary (V.Vector a) where
arbitrary = V.fromList `fmap` arbitrary
-------------------------
-- Support functions to compare Doubles for (near) equality.
class Num a => Mag a where
mag :: a -> Double
instance Mag Double where
mag = abs
instance Mag (Complex Double) where
mag = magnitude
-- Robustly test whether two Doubles are nearly identical.
close :: Mag a => a -> a -> Bool
close x y = tol > mag (x-y) / max 1 (mag x + mag y)
where
tol = 1e-10
withinTol :: (Mag a, V.Unbox a) => V.Vector a -> V.Vector a -> Bool
withinTol a b
| V.length a /= V.length b = False
| otherwise = V.and $ V.zipWith close a b
---------------------
-- The actual properties
-- Test whether the inverse actually inverts the forward transform.
prop_invert f g a = let
p1 = plan f (V.length a)
p2 = plan g (V.length a)
in (V.length a > 1) ==> withinTol a $ execute p2 $ execute p1 a
-- Test whether the transform preserves the L2 (sum-of-squares) norm.
prop_orthog f a = let
p1 = plan f (V.length a)
in (V.length a > 1) ==> close (norm2 a) (norm2 $ execute p1 a)
data DimsAndValues a = DimsAndValues (VS.Vector Int) (V.Vector a)
deriving (Show)
instance (Arbitrary a, V.Unbox a) => Arbitrary (DimsAndValues a) where
arbitrary = do
dims <- liftM (VS.fromList . map getPositive) arbitrary `suchThatMap` maybeReduceSize
values <- V.replicateM (VS.product dims) arbitrary
return (DimsAndValues dims values)
where
-- We use this to prevent test cases from growing too big
maybeReduceSize ds =
if VS.product ds < 1000 then Just ds else maybeReduceSize (VS.init ds)
prop_invertND f g (DimsAndValues ds a) = let
p1 = planND f ds
p2 = planND g ds
in (V.length a > 1) ==> withinTol a $ execute p2 $ execute p1 a
prop_orthogND f (DimsAndValues ds a) = let
p1 = planND f ds
in (V.length a > 1) ==> close (norm2 a) (norm2 $ execute p1 a)
norm2 a = sqrt $ V.sum $ V.map (\x -> x*x) $ V.map mag a