dtw-1.0.3.0: test/MainTest.hs
module Main where
-- module under test
import Data.DTW
import Data.List
import Data.Functor
import Test.Framework
import Test.Framework.Providers.QuickCheck2
import Test.QuickCheck
import qualified Data.Vector.Unboxed as V
newtype SmallNonEmptySeq a = SmallNonEmptySeq { getSmallNonEmpty :: V.Vector a }
deriving (Show, Eq)
instance (V.Unbox a, Arbitrary a) => Arbitrary (SmallNonEmptySeq a) where
arbitrary = SmallNonEmptySeq . V.fromList <$> listOf arbitrary `suchThat` (\l -> length l > 2 && length l < 10)
newtype MediumNonEmptySeq a = MediumNonEmptySeq { getMediumNonEmpty :: V.Vector a }
deriving (Show, Eq)
instance (V.Unbox a, Arbitrary a) => Arbitrary (MediumNonEmptySeq a) where
arbitrary = MediumNonEmptySeq . V.fromList <$> listOf arbitrary `suchThat` (\l -> length l > 100 && length l < 1000)
dist :: Double -> Double -> Double
dist x y = abs (x-y)
-- | reduce a dataset to half its size by averaging neighbour values
-- together
reduceByHalf :: (V.Unbox a, Fractional a) => V.Vector a -> V.Vector a
reduceByHalf v | V.null v = V.empty
| V.length v == 1 = V.singleton (V.head v)
| even (V.length v) = split v
| otherwise = split v `V.snoc` V.last v
where split w = V.generate (V.length w `div` 2) (\i -> (w V.! (i*2+0)) + (w V.! (i*2+1)))
{-testDTWVSDTWNaive :: (SmallNonEmptySeq Double, SmallNonEmptySeq Double) -> Bool-}
{-testDTWVSDTWNaive (la,lb) = abs (dtwNaive dist sa sb - dtw dist sa sb) < 0.01-}
{- where sa = S.fromList $ getSmallNonEmpty la-}
{- sb = S.fromList $ getSmallNonEmpty lb-}
testDTWMemoVSDTWNaive :: (SmallNonEmptySeq Double, SmallNonEmptySeq Double) -> Bool
testDTWMemoVSDTWNaive (la,lb) = abs (dtwNaive dist sa sb - cost (dtwMemo dist sa sb)) < 0.01
where sa = getSmallNonEmpty la
sb = getSmallNonEmpty lb
testFastDTWvsDTWNaive :: (SmallNonEmptySeq Double, SmallNonEmptySeq Double) -> Bool
testFastDTWvsDTWNaive (la,lb) = abs (1 - (ca/l) / (cb/l)) < 0.1
where sa = getSmallNonEmpty la
sb = getSmallNonEmpty lb
l = fromIntegral $ V.length sa + V.length sb
ca = dtwNaive dist sa sb
cb = cost $ fastDtw dist reduceByHalf 2 sa sb
-- FIXME no real idea how to compare an optimal and an approximative
-- algorithm ... best bet below, but still failing tests
testFastDTWvsDTWMemoErr :: Int -> (MediumNonEmptySeq Double, MediumNonEmptySeq Double) -> Double
testFastDTWvsDTWMemoErr radius (la,lb) = err
where sa = getMediumNonEmpty la
sb = getMediumNonEmpty lb
optimal = cost (dtwMemo dist sa sb)
approx = cost (fastDtw dist reduceByHalf radius sa sb)
err = (approx - optimal) / optimal * 100
testFastDTWvsDTWMemo :: Int -> Double -> Property
testFastDTWvsDTWMemo radius goal = forAll (vector 25) go
where go xs = median < goal
where errs = map (testFastDTWvsDTWMemoErr radius) xs
median = sort errs !! (length errs `div` 2)
main :: IO ()
main = defaultMain
[ testProperty "dtwMemo ≡ dtwNaive" testDTWMemoVSDTWNaive
{-, testProperty "fastDtw ≅ dtwNaive" testFastDTWvsDTWNaive-}
, testProperty "fastDtw == dtwMemo (radius=5, maxError=5%)" (testFastDTWvsDTWMemo 5 5)
]