packages feed

emd 0.1.9.0 → 0.1.10.0

raw patch · 12 files changed

+898/−254 lines, 12 filesdep +freedep +hedgehogdep +tastydep ~vector-sizedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: free, hedgehog, tasty, tasty-hedgehog, tasty-hunit

Dependency ranges changed: vector-sized

API changes (from Hackage documentation)

- Numeric.EMD: instance Data.Binary.Class.Binary Numeric.EMD.BoundaryHandler
- Numeric.EMD: instance Data.Binary.Class.Binary a => Data.Binary.Class.Binary (Numeric.EMD.EMDOpts a)
- Numeric.EMD: instance Data.Binary.Class.Binary a => Data.Binary.Class.Binary (Numeric.EMD.SiftCondition a)
- Numeric.EMD: instance GHC.Classes.Eq Numeric.EMD.BoundaryHandler
- Numeric.EMD: instance GHC.Classes.Eq a => GHC.Classes.Eq (Numeric.EMD.EMDOpts a)
- Numeric.EMD: instance GHC.Classes.Eq a => GHC.Classes.Eq (Numeric.EMD.SiftCondition a)
- Numeric.EMD: instance GHC.Classes.Ord Numeric.EMD.BoundaryHandler
- Numeric.EMD: instance GHC.Classes.Ord a => GHC.Classes.Ord (Numeric.EMD.EMDOpts a)
- Numeric.EMD: instance GHC.Classes.Ord a => GHC.Classes.Ord (Numeric.EMD.SiftCondition a)
- Numeric.EMD: instance GHC.Generics.Generic (Numeric.EMD.EMDOpts a)
- Numeric.EMD: instance GHC.Generics.Generic (Numeric.EMD.SiftCondition a)
- Numeric.EMD: instance GHC.Generics.Generic Numeric.EMD.BoundaryHandler
- Numeric.EMD: instance GHC.Real.Fractional a => Data.Default.Class.Default (Numeric.EMD.EMDOpts a)
- Numeric.EMD: instance GHC.Real.Fractional a => Data.Default.Class.Default (Numeric.EMD.SiftCondition a)
- Numeric.EMD: instance GHC.Show.Show Numeric.EMD.BoundaryHandler
- Numeric.EMD: instance GHC.Show.Show a => GHC.Show.Show (Numeric.EMD.EMDOpts a)
- Numeric.EMD: instance GHC.Show.Show a => GHC.Show.Show (Numeric.EMD.SiftCondition a)
+ Numeric.EMD: SCCauchy :: SiftProjection -> !a -> SiftCondition a
+ Numeric.EMD: SCProj :: SiftProjection -> !a -> SiftCondition a
+ Numeric.EMD: SCSCond :: !Int -> SiftCondition a
+ Numeric.EMD: SPEnergyDiff :: SiftProjection
+ Numeric.EMD: SPEnvMeanSum :: SiftProjection
+ Numeric.EMD: data SiftProjection
+ Numeric.EMD: scEnergyDiff :: a -> a -> SiftCondition a
+ Numeric.HHT: SCCauchy :: SiftProjection -> !a -> SiftCondition a
+ Numeric.HHT: SCProj :: SiftProjection -> !a -> SiftCondition a
+ Numeric.HHT: SCSCond :: !Int -> SiftCondition a
- Numeric.EMD: emd :: (Vector v a, KnownNat n, Fractional a, Ord a) => EMDOpts a -> Vector v (n + 1) a -> EMD v (n + 1) a
+ Numeric.EMD: emd :: (Vector v a, KnownNat n, Floating a, Ord a) => EMDOpts a -> Vector v (n + 1) a -> EMD v (n + 1) a
- Numeric.EMD: emd' :: (Vector v a, KnownNat n, Fractional a, Ord a, Applicative m) => (SiftResult v (n + 1) a -> m r) -> EMDOpts a -> Vector v (n + 1) a -> m (EMD v (n + 1) a)
+ Numeric.EMD: emd' :: (Vector v a, KnownNat n, Floating a, Ord a, Applicative m) => (SiftResult v (n + 1) a -> m r) -> EMDOpts a -> Vector v (n + 1) a -> m (EMD v (n + 1) a)
- Numeric.EMD: emdTrace :: (Vector v a, KnownNat n, Fractional a, Ord a, MonadIO m) => EMDOpts a -> Vector v (n + 1) a -> m (EMD v (n + 1) a)
+ Numeric.EMD: emdTrace :: (Vector v a, KnownNat n, Floating a, Ord a, MonadIO m) => EMDOpts a -> Vector v (n + 1) a -> m (EMD v (n + 1) a)
- Numeric.EMD: sift :: (Vector v a, KnownNat n, Fractional a, Ord a) => EMDOpts a -> Vector v (n + 1) a -> SiftResult v (n + 1) a
+ Numeric.EMD: sift :: forall v n a. (Vector v a, KnownNat n, Floating a, Ord a) => EMDOpts a -> Vector v (n + 1) a -> SiftResult v (n + 1) a

Files

CHANGELOG.md view
@@ -1,6 +1,25 @@ Changelog ========= +Version 0.1.10.0+---------------++*October 19, 2019*++<https://github.com/mstksg/emd/releases/tag/v0.1.10.0>++*   Sifting condition checking system has been revamped to be more flexible.+*   New sift conditions added:+    *   Projections based on functions:+        *   Energy Difference function (for Energy Difference Tracking)+        *   Mean envelope RMS+    *   For the above, we can now stop sifting based on:+        *   Those projections reaching a certain value+        *   Successive values of those projections reaching a certain squared+            difference (Cauchy-inspired convergence)+    *   S-Number Criterion+*   Add test suites+ Version 0.1.9.0 --------------- 
emd.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 84c1c690a80a0ace06a7280dfac722ed522cdd34fb042042999e67d7a1217442+-- hash: 5230a7607e7d3f26121daa88b982f8f58087a236adc506e45eadf1a038581d7a  name:           emd-version:        0.1.9.0+version:        0.1.10.0 synopsis:       Empirical Mode Decomposition and Hilbert-Huang Transform description:    Empirical Mode decomposition and Hilbert-Huang Transform in pure                 Haskell.@@ -36,12 +36,14 @@       Numeric.EMD.Internal.Spline       Numeric.HHT   other-modules:-      Numeric.EMD.Internal.Tridiagonal       Numeric.EMD.Internal.Extrema+      Numeric.EMD.Internal.Pipe+      Numeric.EMD.Internal.Sift+      Numeric.EMD.Internal.Tridiagonal       Numeric.HHT.Internal.FFT   hs-source-dirs:       src-  ghc-options: -Wall -Wredundant-constraints -Wcompat+  ghc-options: -Wall -Wredundant-constraints -Wcompat -Werror=incomplete-patterns   build-depends:       array     , base >=4.11 && <5@@ -52,6 +54,7 @@     , deepseq     , fft     , finite-typelits+    , free     , ghc-typelits-knownnat     , ghc-typelits-natnormalise     , transformers@@ -64,15 +67,28 @@   type: exitcode-stdio-1.0   main-is: Spec.hs   other-modules:+      Tests.EMD+      Tests.HHT+      Tests.Spline+      Tests.Util       Paths_emd   hs-source-dirs:       test-  ghc-options: -Wall -Wredundant-constraints -Wcompat -threaded -rtsopts -with-rtsopts=-N+  ghc-options: -Wall -Wredundant-constraints -Wcompat -Werror=incomplete-patterns -threaded -rtsopts -with-rtsopts=-N   build-depends:       HUnit     , base >=4.11 && <5     , containers     , emd+    , ghc-typelits-knownnat+    , ghc-typelits-natnormalise+    , hedgehog+    , statistics+    , tasty+    , tasty-hedgehog+    , tasty-hunit+    , typelits-witnesses+    , vector-sized   default-language: Haskell2010  benchmark emd-bench@@ -82,7 +98,7 @@       Paths_emd   hs-source-dirs:       bench-  ghc-options: -Wall -Wredundant-constraints -Wcompat -threaded -rtsopts -with-rtsopts=-N -O2+  ghc-options: -Wall -Wredundant-constraints -Wcompat -Werror=incomplete-patterns -threaded -rtsopts -with-rtsopts=-N -O2   build-depends:       base >=4.11 && <5     , criterion
src/Numeric/EMD.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE DeriveGeneric                            #-} {-# LANGUAGE GADTs                                    #-} {-# LANGUAGE LambdaCase                               #-}+{-# LANGUAGE RankNTypes                               #-} {-# LANGUAGE RecordWildCards                          #-} {-# LANGUAGE ScopedTypeVariables                      #-} {-# LANGUAGE TypeApplications                         #-}@@ -33,9 +34,9 @@ -- 2.  We provide a vector of size of at least one. -- -- There are many functions to convert unsized vectors to sized vectors in--- "Data.Vector.Sized" and associated modules, including 'toSized' (for--- when you know the size at compile-time) and 'withSized' (for when you--- don't).+-- "Data.Vector.Sized" and associated modules, including+-- 'Data.Vector.Sized.toSized' (for when you know the size at compile-time)+-- and 'Data.Vector.Sized.withSized' (for when you don't). --  module Numeric.EMD (@@ -45,116 +46,28 @@   , emd'   , iemd   , EMD(..)-  , EMDOpts(..), defaultEO, BoundaryHandler(..), SiftCondition(..), defaultSC, SplineEnd(..)+  -- ** Configuration+  , EMDOpts(..), defaultEO+  , BoundaryHandler(..)+  , SiftCondition(..), SiftProjection(..), defaultSC+  , scEnergyDiff+  , SplineEnd(..)   -- * Internal   , sift, SiftResult(..)   , envelopes   ) where  import           Control.DeepSeq-import           Control.Monad import           Control.Monad.IO.Class-import           Data.Default.Class-import           Data.Finite import           Data.Functor.Identity import           Data.List-import           GHC.Generics                 (Generic)+import           GHC.Generics              (Generic) import           GHC.TypeNats-import           Numeric.EMD.Internal.Extrema-import           Numeric.EMD.Internal.Spline+import           Numeric.EMD.Internal.Sift import           Text.Printf-import qualified Data.Binary                  as Bi-import qualified Data.Map                     as M-import qualified Data.Vector.Generic          as VG-import qualified Data.Vector.Generic.Sized    as SVG---- | Options for EMD composition.-data EMDOpts a = EO { eoSiftCondition   :: SiftCondition a  -- ^ stop condition for sifting-                    , eoSplineEnd       :: SplineEnd a      -- ^ end conditions for envelope splines-                    , eoBoundaryHandler :: Maybe BoundaryHandler  -- ^ process for handling boundary-                    }-  deriving (Show, Eq, Ord, Generic)--data BoundaryHandler-    -- | Clamp envelope at end points (Matlab implementation)-    = BHClamp-    -- | Extend boundaries symmetrically-    | BHSymmetric-  deriving (Show, Eq, Ord, Generic)--    -- -- | Extend boundaries assuming global periodicity-    -- -- | BHPeriodic---- | @since 0.1.3.0-instance Bi.Binary BoundaryHandler---- | @since 0.1.3.0-instance Bi.Binary a => Bi.Binary (EMDOpts a)---- | Default 'EMDOpts'-defaultEO :: Fractional a => EMDOpts a-defaultEO = EO { eoSiftCondition   = defaultSC-               , eoSplineEnd       = SENatural-               , eoBoundaryHandler = Just BHSymmetric-               }---- | @since 0.1.3.0-instance Fractional a => Default (EMDOpts a) where-    def = defaultEO---- | Stop conditions for sifting process------ Data type is lazy in its fields, so this infinite data type:------ @--- nTimes n = SCTimes n `SCOr` nTimes (n + 1)--- @------ will be treated identically as:------ @--- nTimes = SCTimes--- @-data SiftCondition a-    -- | Stop using standard SD method-    = SCStdDev !a-    -- | Stop after a fixed number of sifting iterations-    | SCTimes !Int-    -- | One or the other-    | SCOr (SiftCondition a) (SiftCondition a)-    -- | Stop when both conditions are met-    | SCAnd (SiftCondition a) (SiftCondition a)-  deriving (Show, Eq, Ord, Generic)---- | @since 0.1.3.0-instance Bi.Binary a => Bi.Binary (SiftCondition a)---- | @since 0.1.3.0-instance Fractional a => Default (SiftCondition a) where-    def = defaultSC---- | Default 'SiftCondition'-defaultSC :: Fractional a => SiftCondition a-defaultSC = SCStdDev 0.3 `SCOr` SCTimes 50     -- R package uses SCTimes 20, Matlab uses no limit--- defaultSC = SCStdDev 0.3---- | 'True' if stop-testCondition-    :: (VG.Vector v a, Fractional a, Ord a)-    => SiftCondition a-    -> Int-    -> SVG.Vector v n a-    -> SVG.Vector v n a-    -> Bool-testCondition tc i v v' = go tc-  where-    sd = SVG.sum $ SVG.zipWith (\x x' -> (x-x')^(2::Int) / (x^(2::Int) + eps)) v v'-    go = \case-      SCStdDev t -> sd <= t-      SCTimes l  -> i >= l-      SCOr  f g  -> go f || go g-      SCAnd f g  -> go f && go g-    eps = 0.0000001+import qualified Data.Binary               as Bi+import qualified Data.Vector.Generic       as VG+import qualified Data.Vector.Generic.Sized as SVG  -- | An @'EMD' v n a@ is an Empirical Mode Decomposition of a time series -- with @n@ items of type @a@ stored in a vector @v@.@@ -186,7 +99,7 @@ -- 1.  The resulting 'EMD' contains IMFs that are all the same length as --     the input vector -- 2.  We provide a vector of size of at least one.-emd :: (VG.Vector v a, KnownNat n, Fractional a, Ord a)+emd :: (VG.Vector v a, KnownNat n, Floating a, Ord a)     => EMDOpts a     -> SVG.Vector v (n + 1) a     -> EMD v (n + 1) a@@ -195,7 +108,7 @@ -- | 'emd', but tracing results to stdout as IMFs are found.  Useful for -- debugging to see how long each step is taking. emdTrace-    :: (VG.Vector v a, KnownNat n, Fractional a, Ord a, MonadIO m)+    :: (VG.Vector v a, KnownNat n, Floating a, Ord a, MonadIO m)     => EMDOpts a     -> SVG.Vector v (n + 1) a     -> m (EMD v (n + 1) a)@@ -205,7 +118,7 @@  -- | 'emd' with a callback for each found IMF. emd'-    :: (VG.Vector v a, KnownNat n, Fractional a, Ord a, Applicative m)+    :: (VG.Vector v a, KnownNat n, Floating a, Ord a, Applicative m)     => (SiftResult v (n + 1) a -> m r)     -> EMDOpts a     -> SVG.Vector v (n + 1) a@@ -228,117 +141,3 @@     => EMD v n a     -> SVG.Vector v n a iemd EMD{..} = foldl' (SVG.zipWith (+)) emdResidual emdIMFs---- | The result of a sifting operation.  Each sift either yields--- a residual, or a new IMF.-data SiftResult v n a = SRResidual !(SVG.Vector v n a)-                      | SRIMF      !(SVG.Vector v n a) !Int   -- ^ number of sifting iterations---- | Iterated sifting process, used to produce either an IMF or a residual.-sift-    :: (VG.Vector v a, KnownNat n, Fractional a, Ord a)-    => EMDOpts a-    -> SVG.Vector v (n + 1) a-    -> SiftResult v (n + 1) a-sift EO{..} = go 1-  where-    go !i !v = case sift' eoSplineEnd eoBoundaryHandler v of-      Nothing -> SRResidual v-      Just !v'-        | testCondition eoSiftCondition i v v' -> SRIMF v' i-        | otherwise                            -> go (i + 1) v'---- | Single sift-sift'-    :: (VG.Vector v a, KnownNat n, Fractional a, Ord a)-    => SplineEnd a-    -> Maybe BoundaryHandler-    -> SVG.Vector v (n + 1) a-    -> Maybe (SVG.Vector v (n + 1) a)-sift' se bh v = go <$> envelopes se bh v-  where-    go (mins, maxs) = SVG.zipWith3 (\x mi ma -> x - (mi + ma)/2) v mins maxs---- | Returns cubic splines of local minimums and maximums.  Returns--- 'Nothing' if there are not enough local minimum or maximums to create--- the splines.-envelopes-    :: (VG.Vector v a, KnownNat n, Fractional a, Ord a)-    => SplineEnd a-    -> Maybe BoundaryHandler-    -> SVG.Vector v (n + 1) a-    -> Maybe (SVG.Vector v (n + 1) a, SVG.Vector v (n + 1) a)-envelopes se bh xs = do-    when (bh == Just BHClamp) $ do-      guard (M.size mins > 1)-      guard (M.size maxs > 1)-    (,) <$> splineAgainst se emin mins-        <*> splineAgainst se emax maxs-  where-    -- minMax = M.fromList [(minBound, SVG.head xs), (maxBound, SVG.last xs)]-    (mins,maxs) = extrema xs-    (emin,emax) = case bh of-      Nothing  -> mempty-      Just bh' -> extendExtrema xs bh' (mins,maxs)-    --   | isJust bh = (mins `M.union` minMax, maxs `M.union` minMax)-    --   | otherwise = (mins, maxs)--extendExtrema-    :: forall v n a. (VG.Vector v a, KnownNat n)-    => SVG.Vector v (n + 1) a-    -> BoundaryHandler-    -> (M.Map (Finite (n + 1)) a, M.Map (Finite (n + 1)) a)-    -> (M.Map Int a, M.Map Int a)-    -- (M.Map (Finite (n + 1)) a, M.Map (Finite (n + 1)) a)-extendExtrema xs = \case-    BHClamp     -> const (firstLast, firstLast)-    BHSymmetric -> \(mins, maxs) ->-      let addFirst = case (flippedMin, flippedMax) of-              (Nothing      , Nothing      ) -> mempty-              -- first point is local maximum-              (Just (_,mn)  , Nothing      ) -> (mn        , firstPoint)-              -- first point is local minimum-              (Nothing      , Just (_,mx)  ) -> (firstPoint, mx        )-              (Just (mni,mn), Just (mxi,mx))-                | mni < mxi                  -> (mn        , firstPoint)-                | otherwise                  -> (firstPoint, mx        )-            where-              flippedMin = flip fmap (M.lookupMin mins) $ \(minIx, minVal) ->-                (minIx, M.singleton (negate (fromIntegral minIx)) minVal)-              flippedMax = flip fmap (M.lookupMin maxs) $ \(maxIx, maxVal) ->-                (maxIx, M.singleton (negate (fromIntegral maxIx)) maxVal)-          addLast = case (flippedMin, flippedMax) of-              (Nothing      , Nothing      ) -> mempty-              -- last point is local maximum-              (Just (_,mn)  , Nothing      ) -> (mn        , lastPoint )-              -- last point is local minimum-              (Nothing      , Just (_,mx)  ) -> (lastPoint , mx        )-              (Just (mni,mn), Just (mxi,mx))-                | mni > mxi                  -> (mn        , lastPoint )-                | otherwise                  -> (lastPoint , mx        )-            where-              flippedMin = flip fmap (M.lookupMax mins) $ \(minIx, minVal) ->-                (minIx, M.singleton (extendSym (fromIntegral minIx)) minVal)-              flippedMax = flip fmap (M.lookupMax maxs) $ \(maxIx, maxVal) ->-                (maxIx, M.singleton (extendSym (fromIntegral maxIx)) maxVal)-      in  addFirst `mappend` addLast-  where-    lastIx = fromIntegral $ maxBound @(Finite n)-    firstPoint = M.singleton 0 (SVG.head xs)-    lastPoint  = M.singleton lastIx (SVG.last xs)-    firstLast  = firstPoint `mappend` lastPoint-    extendSym i = 2 * lastIx - i---- | Build a splined vector against a map of control points.-splineAgainst-    :: (VG.Vector v a, KnownNat n, Fractional a, Ord a)-    => SplineEnd a-    -> M.Map Int a              -- ^ extensions-    -> M.Map (Finite n) a-    -> Maybe (SVG.Vector v n a)-splineAgainst se ext = fmap go-                     . makeSpline se-                     . mappend (M.mapKeysMonotonic fromIntegral ext)-                     . M.mapKeysMonotonic fromIntegral-  where-    go spline = SVG.generate (sampleSpline spline . fromIntegral)
+ src/Numeric/EMD/Internal/Pipe.hs view
@@ -0,0 +1,224 @@+{-# LANGUAGE DeriveFunctor              #-}+{-# LANGUAGE FlexibleContexts           #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase                 #-}+{-# LANGUAGE ScopedTypeVariables        #-}+{-# LANGUAGE TemplateHaskell            #-}++module Numeric.EMD.Internal.Pipe (+    Pipe+  , (.|)+  , runPipe+  , awaitEither, await, awaitSurely+  , repeatM, unfoldP, unfoldPForever, iterateP, sourceList+  , awaitForever, mapP, mapMP+  , dropP+  , foldrP, sinkList+  , ZipSink(..)+  ) where++import           Control.Applicative+import           Control.Monad+import           Control.Monad.Free.Class+import           Control.Monad.Free.TH+import           Control.Monad.Trans.Class+import           Control.Monad.Trans.Free        (FreeT(..), FreeF(..))+import           Control.Monad.Trans.Free.Church+import           Data.Foldable+import           Data.Void++data PipeF i o u a =+      PAwaitF (i -> a) (u -> a)+    | PYieldF o a+  deriving Functor++makeFree ''PipeF++-- | Similar to Conduit+--+-- *  @i@: Type of input stream+-- *  @o@: Type of output stream+-- *  @u@: Type of the /result/ of the upstream pipe (Outputted when+--    upstream pipe finishes)+-- *  @m@: Underlying monad+-- *  @a@: Result type (Outputted when finished)+--+-- Some specializations:+--+-- *  A pipe is a /producer/ if @i@ is @()@: it doesn't need anything to go+--    pump out items.+--+--    If a pipe is producer and @a@ is 'Void', it means that it will+--    produce infinitely.+--+-- *  A pipe is a /consumer/ if @o@ is 'Void': it will never yield anything+--    else downstream.+--+-- *  Normally you can ask for input upstream with 'await', which returns+--    'Nothing' if the pipe upstream stops producing.  However, if @u@ is+--    'Void', it means that the pipe upstream will never stop, so you can+--    use 'awaitSurely' to get a guaranteed answer.+newtype Pipe i o u m a = Pipe { pipeFree :: FT (PipeF i o u) m a }+  deriving (Functor, Applicative, Monad, MonadTrans, MonadFree (PipeF i o u))++awaitEither :: Pipe i o u m (Either i u)+awaitEither = pAwaitF++yield :: o -> Pipe i o u m ()+yield = pYieldF++await :: Pipe i o u m (Maybe i)+await = either Just (const Nothing) <$> awaitEither++awaitSurely :: Pipe i o Void m i+awaitSurely = either id absurd <$> awaitEither++runPipe :: forall u m a. Monad m => Pipe () Void u m a -> m a+runPipe = iterT go . pipeFree+  where+    go :: PipeF () Void u (m a) -> m a+    go = \case+      PAwaitF f _ -> f ()+      PYieldF o _ -> absurd o++-- can this be done without going through FreeT?+(.|) :: forall a b c u m v r. Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m r+Pipe p .| Pipe q = Pipe $ toFT $ compPipe_ (fromFT p) (fromFT q)++compPipe_+    :: forall a b c u v m r. (Monad m)+    => FreeT (PipeF a b u) m v+    -> FreeT (PipeF b c v) m r+    -> FreeT (PipeF a c u) m r+compPipe_ p q = FreeT $ runFreeT q >>= \case+    Pure x             -> pure . Pure $ x+    Free (PAwaitF f g) -> runFreeT p >>= \case+      Pure x'              -> runFreeT $ compPipe_ p  (g x')+      Free (PAwaitF f' g') -> pure . Free $ PAwaitF ((`compPipe_` q) . f')+                                                    ((`compPipe_` q) . g')+      Free (PYieldF x' y') -> runFreeT $ compPipe_ y' (f x')+    Free (PYieldF x y) -> pure . Free $ PYieldF x (compPipe_ p y)+infixr 2 .|++unfoldP :: (b -> Maybe (a, b)) -> b -> Pipe i a u m ()+unfoldP f = go+  where+    go z = case f z of+      Nothing      -> pure ()+      Just (x, z') -> yield x *> go z'++unfoldPForever :: (b -> (a, b)) -> b -> Pipe i a u m r+unfoldPForever f = go+  where+    go z = yield x *> go z'+      where+        (x, z') = f z++iterateP :: (a -> a) -> a -> Pipe i a u m r+iterateP f = unfoldPForever (join (,) . f)++sourceList :: Foldable t => t a -> Pipe i a u m ()+sourceList = traverse_ yield++repeatM :: Monad m => m o -> Pipe i o u m u+repeatM x = go+  where+    go = (yield =<< lift x) *> go++awaitForever :: (i -> Pipe i o u m a) -> Pipe i o u m u+awaitForever f = go+  where+    go = awaitEither >>= \case+      Left x  -> f x *> go+      Right x -> pure x++-- finishPipe+--     :: u+--     -> Pipe i o u    m a+--     -> Pipe i o Void m a++mapP :: (a -> b) -> Pipe a b u m u+mapP f = awaitForever (yield . f)++mapMP :: Monad m => (a -> m b) -> Pipe a b u m u+mapMP f = awaitForever ((yield =<<) . lift . f)++dropP :: Int -> Pipe i o u m ()+dropP n = replicateM_ n await++foldrP :: (a -> b -> b) -> b -> Pipe a Void u m b+foldrP f z = go+  where+    go = await >>= \case+      Nothing -> pure z+      Just x  -> f x <$> go++sinkList :: Pipe i Void u m [i]+sinkList = foldrP (:) []++newtype ZipSink i u m a = ZipSink { getZipSink :: Pipe i Void u m a }+  deriving Functor++zipSink_+    :: Monad m+    => FreeT (PipeF i Void u) m (a -> b)+    -> FreeT (PipeF i Void u) m a+    -> FreeT (PipeF i Void u) m b+zipSink_ p q = FreeT $ go <$> runFreeT p <*> runFreeT q+  where+    go = \case+      Pure x             -> \case+        Pure x'              -> Pure $ x x'+        Free (PAwaitF f' g') -> Free $ PAwaitF (zipSink_ p . f') (zipSink_ p . g')+        Free (PYieldF x' _ ) -> absurd x'+      Free (PAwaitF f g) -> \case+        Pure _               -> Free $ PAwaitF ((`zipSink_` q) . f) ((`zipSink_` q) . g)+        Free (PAwaitF f' g') -> Free $ PAwaitF (zipSink_ <$> f <*> f') (zipSink_ <$> g <*> g')+        Free (PYieldF x' _ ) -> absurd x'+      Free (PYieldF x _) -> absurd x++altSink_+    :: Monad m+    => FreeT (PipeF i Void u) m a+    -> FreeT (PipeF i Void u) m a+    -> FreeT (PipeF i Void u) m a+altSink_ p q = FreeT $ go <$> runFreeT p <*> runFreeT q+  where+    go = \case+      Pure x             -> \_ -> Pure x+      Free (PAwaitF f g) -> \case+        Pure x'              -> Pure x'+        Free (PAwaitF f' g') -> Free $ PAwaitF (altSink_ <$> f <*> f') (altSink_ <$> g <*> g')+        Free (PYieldF x' _ ) -> absurd x'+      Free (PYieldF x _) -> absurd x++zipSink+    :: Monad m+    => Pipe i Void u m (a -> b)+    -> Pipe i Void u m a+    -> Pipe i Void u m b+zipSink (Pipe p) (Pipe q) = Pipe $ toFT $ zipSink_ (fromFT p) (fromFT q)++altSink+    :: Monad m+    => Pipe i Void u m a+    -> Pipe i Void u m a+    -> Pipe i Void u m a+altSink (Pipe p) (Pipe q) = Pipe $ toFT $ altSink_ (fromFT p) (fromFT q)++-- | '<*>' = distribute input to all, and return result when they finish+--+-- 'pure' = immediately finish+instance Monad m => Applicative (ZipSink i u m) where+    pure = ZipSink . pure+    ZipSink p <*> ZipSink q = ZipSink $ zipSink p q++-- | '<|>' = distribute input to all, and return the first result that+-- finishes+--+-- 'empty' = never finish+instance Monad m => Alternative (ZipSink i u m) where+    empty = ZipSink go+      where+        go = forever await+    ZipSink p <|> ZipSink q = ZipSink $ altSink p q
+ src/Numeric/EMD/Internal/Sift.hs view
@@ -0,0 +1,384 @@+{-# LANGUAGE BangPatterns                             #-}+{-# LANGUAGE DeriveGeneric                            #-}+{-# LANGUAGE GADTs                                    #-}+{-# LANGUAGE LambdaCase                               #-}+{-# LANGUAGE RankNTypes                               #-}+{-# LANGUAGE RecordWildCards                          #-}+{-# LANGUAGE ScopedTypeVariables                      #-}+{-# LANGUAGE TypeApplications                         #-}+{-# LANGUAGE TypeInType                               #-}+{-# LANGUAGE TypeOperators                            #-}+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}+{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise       #-}+++module Numeric.EMD.Internal.Sift (+    EMDOpts(..), defaultEO+  , BoundaryHandler(..)+  , SiftCondition(..), SiftProjection(..), defaultSC+  , scEnergyDiff+  , SplineEnd(..)+  -- * Internal+  , sift, SiftResult(..)+  , envelopes+  ) where++import           Control.Applicative+import           Control.Monad+import           Control.Monad.Trans.State+import           Data.Default.Class+import           Data.Finite+import           Data.Void+import           GHC.Generics                 (Generic)+import           GHC.TypeNats+import           Numeric.EMD.Internal.Extrema+import           Numeric.EMD.Internal.Pipe+import           Numeric.EMD.Internal.Spline+import qualified Data.Binary                  as Bi+import qualified Data.Map                     as M+import qualified Data.Vector.Generic          as VG+import qualified Data.Vector.Generic.Sized    as SVG++-- | Options for EMD composition.+data EMDOpts a = EO { eoSiftCondition   :: SiftCondition a  -- ^ stop condition for sifting+                    , eoSplineEnd       :: SplineEnd a      -- ^ end conditions for envelope splines+                    , eoBoundaryHandler :: Maybe BoundaryHandler  -- ^ process for handling boundary+                    }+  deriving (Show, Eq, Ord, Generic)++-- | @since 0.1.3.0+instance Bi.Binary a => Bi.Binary (EMDOpts a)++-- | Default 'EMDOpts'+defaultEO :: Fractional a => EMDOpts a+defaultEO = EO { eoSiftCondition   = defaultSC+               , eoSplineEnd       = SENatural+               , eoBoundaryHandler = Just BHSymmetric+               }++-- | @since 0.1.3.0+instance Fractional a => Default (EMDOpts a) where+    def = defaultEO+++-- | Boundary conditions for splines.+data BoundaryHandler+    -- | Clamp envelope at end points (Matlab implementation)+    = BHClamp+    -- | Extend boundaries symmetrically+    | BHSymmetric+  deriving (Show, Eq, Ord, Generic)++-- | @since 0.1.3.0+instance Bi.Binary BoundaryHandler++-- | Stop conditions for sifting process+--+-- Data type is lazy in its fields, so this infinite data type:+--+-- @+-- nTimes n = SCTimes n `SCOr` nTimes (n + 1)+-- @+--+-- will be treated identically as:+--+-- @+-- nTimes = SCTimes+-- @+data SiftCondition a+    -- | Stop using standard SD method+    = SCStdDev !a+    -- | When the difference between successive items reaches a given threshold+    -- \(\tau\)+    --+    -- \[+    -- \frac{\left(f(t-1) - f(t)\right)^2}{f^2(t-1)} < \tau+    -- \]+    --+    -- @since 0.1.10.0+    | SCCauchy SiftProjection !a+    -- | When the value reaches a given threshold \(\tau\)+    --+    -- \[+    -- f(t) < \tau+    -- \]+    --+    -- @since 0.1.10.0+    | SCProj   SiftProjection !a+    -- | S-condition criteria.+    --+    -- The S-number is the length of current streak where number of extrema+    -- or zero crossings all differ at most by one.+    --+    -- Stop sifting when the S-number reaches a given amount.+    --+    -- @since 0.1.10.0+    | SCSCond !Int+    -- | Stop after a fixed number of sifting iterations+    | SCTimes !Int+    -- | One or the other+    | SCOr (SiftCondition a) (SiftCondition a)+    -- | Stop when both conditions are met+    | SCAnd (SiftCondition a) (SiftCondition a)+  deriving (Show, Eq, Ord, Generic)++-- | A projection of sifting data.  Used as a part of 'SiftCondition' to+-- describe 'SCCauchy' and 'SCProj'.+--+-- @since 0.1.10.0+data SiftProjection+    -- | The root mean square of the envelope means+    = SPEnvMeanSum+    -- | The "energy difference" quotient (Cheng, Yu, Yang 2005)+    | SPEnergyDiff+  deriving (Show, Eq, Ord, Generic)++instance Bi.Binary SiftProjection++-- | @since 0.1.3.0+instance Bi.Binary a => Bi.Binary (SiftCondition a)++-- | @since 0.1.3.0+instance Fractional a => Default (SiftCondition a) where+    def = defaultSC++-- | Default 'SiftCondition'+defaultSC :: Fractional a => SiftCondition a+defaultSC = SCStdDev 0.3 `SCOr` SCTimes 50     -- R package uses SCTimes 20, Matlab uses no limit+++-- | Cheng, Yu, Yang suggest pairing together an energy difference+-- threshold with a threshold for mean envelope RMS.  This is a convenience+-- function to construct that pairing.+scEnergyDiff+    :: a                -- ^ Threshold for Energy Difference+    -> a                -- ^ Threshold for mean envelope RMS+    -> SiftCondition a+scEnergyDiff s t = SCProj SPEnergyDiff s `SCAnd` SCProj SPEnvMeanSum t+++-- | The result of a sifting operation.  Each sift either yields+-- a residual, or a new IMF.+data SiftResult v n a = SRResidual !(SVG.Vector v n a)+                      | SRIMF      !(SVG.Vector v n a) !Int   -- ^ number of sifting iterations++-- | Result of a single sift+data SingleSift v n a = SingleSift+    { ssRes    :: !(SVG.Vector v n a)+    , ssMinEnv :: !(SVG.Vector v n a)+    , ssMaxEnv :: !(SVG.Vector v n a)+    }++type Sifter v n m a = Pipe (SingleSift v n a) Void Void m ()++siftTimes :: Int -> Sifter v n m a+siftTimes n = dropP (n - 1) >> void awaitSurely++siftProj :: (SingleSift v n a -> Bool) -> Sifter v n m a+siftProj p = go+  where+    go = do+      v <- awaitSurely+      unless (p v) go++siftPairs :: (SingleSift v n a -> SingleSift v n a -> Bool) -> Sifter v n m a+siftPairs p = go =<< awaitSurely+  where+    go s = do+      s' <- awaitSurely+      unless (p s s') (go s')++siftStdDev :: forall v n m a. (VG.Vector v a, Fractional a, Ord a) => a -> Sifter v n m a+siftStdDev t = siftPairs $ \(SingleSift v _ _) (SingleSift v' _ _) ->+    SVG.sum (SVG.zipWith (\x x' -> (x-x')^(2::Int) / (x^(2::Int) + eps)) v v')+      <= t+  where+    eps = 0.0000001++siftCauchy+    :: (Fractional b, Ord b)+    => (SingleSift v n a -> b)+    -> b+    -> Sifter v n m a+siftCauchy p t = siftPairs $ \s s' ->+  let ps  = p s+      ps' = p s'+      δ   = ps' - ps+  in  ((δ * δ) / (ps * ps)) <= t++siftSCond :: (VG.Vector v a, KnownNat n, Fractional a, Ord a) => Int -> Sifter v (n + 1) m a+siftSCond n = go []+  where+    go cxs = do+      v <- awaitSurely+      let cx   = crossCount $ ssRes v+          done = all ((<= 1) . abs . subtract cx) cxs+      unless done $+        go (take (n - 1) (cx : cxs))+    crossCount xs = M.size mins + M.size maxs + crosses+      where+        (mins, maxs) = extrema xs+        crosses = fst . flip execState (0, Nothing) . flip SVG.mapM_ xs $ \x -> modify $ \(!i, !y) ->+          let xPos = x > 0+              i'   = case y of+                       Nothing -> i+                       Just y'+                         | xPos == y' -> i+                         | otherwise  -> i + 1+          in  (i', Just xPos)++siftOr :: Monad m => Sifter v n m a -> Sifter v n m a -> Sifter v n m a+siftOr p q = getZipSink $ ZipSink p <|> ZipSink q++siftAnd :: Monad m => Sifter v n m a -> Sifter v n m a -> Sifter v n m a+siftAnd p q = getZipSink $ ZipSink p *> ZipSink q++toSifter+    :: (VG.Vector v a, KnownNat n, Monad m, Floating a, Ord a)+    => SVG.Vector v (n + 1) a+    -> SiftCondition a+    -> Sifter v (n + 1) m a+toSifter v0 = go+  where+    go = \case+      SCStdDev x -> siftStdDev x+      SCCauchy p x -> siftCauchy (toProj p v0) x+      SCProj   p x -> siftProj ((<= x) . toProj p v0)+      SCSCond  n   -> siftSCond n+      SCTimes  i -> siftTimes i+      SCOr p q   -> siftOr (go p) (go q)+      SCAnd p q  -> siftAnd (go p) (go q)++toProj+    :: (VG.Vector v a, Floating a)+    => SiftProjection+    -> SVG.Vector v n a+    -> SingleSift v n a+    -> a+toProj = \case+    SPEnvMeanSum -> \_ SingleSift{..} ->+      sqrt . squareMag $ SVG.zipWith (\x y -> (x + y) / 2) ssMinEnv ssMaxEnv+    SPEnergyDiff -> \v0 ->+      let eX = squareMag v0+      in  \SingleSift{..} ->+            let eTot = squareMag ssRes - squareMag (SVG.zipWith (-) v0 ssRes)+            in  abs $ eX - eTot+  where+    squareMag = SVG.foldl' (\s x -> s + x*x) 0+++-- | Iterated sifting process, used to produce either an IMF or a residual.+sift+    :: forall v n a. (VG.Vector v a, KnownNat n, Floating a, Ord a)+    => EMDOpts a+    -> SVG.Vector v (n + 1) a+    -> SiftResult v (n + 1) a+sift EO{..} v0 = case execStateT (runPipe sifterPipe) (0, v0) of+    Left  v        -> SRResidual v+    Right (!i, !v) -> SRIMF v i+  where+    sifterPipe = repeatM go+              .| toSifter v0 eoSiftCondition+    go = StateT $ \(!i, !v) ->+      case sift' eoSplineEnd eoBoundaryHandler v of+        Nothing                -> Left v+        Just ss@SingleSift{..} -> Right (ss, (i + 1, ssRes))++-- | Single sift+sift'+    :: (VG.Vector v a, KnownNat n, Fractional a, Ord a)+    => SplineEnd a+    -> Maybe BoundaryHandler+    -> SVG.Vector v (n + 1) a+    -> Maybe (SingleSift v (n + 1) a)+sift' se bh v = do+    (mins, maxs) <- envelopes se bh v+    pure SingleSift+      { ssRes    = SVG.zipWith3 (\x mi ma -> x - (mi + ma)/2) v mins maxs+      , ssMinEnv = mins+      , ssMaxEnv = maxs+      }++-- | Returns cubic splines of local minimums and maximums.  Returns+-- 'Nothing' if there are not enough local minimum or maximums to create+-- the splines.+envelopes+    :: (VG.Vector v a, KnownNat n, Fractional a, Ord a)+    => SplineEnd a+    -> Maybe BoundaryHandler+    -> SVG.Vector v (n + 1) a+    -> Maybe (SVG.Vector v (n + 1) a, SVG.Vector v (n + 1) a)+envelopes se bh xs = do+    when (bh == Just BHClamp) $ do+      guard (M.size mins > 1)+      guard (M.size maxs > 1)+    (,) <$> splineAgainst se emin mins+        <*> splineAgainst se emax maxs+  where+    -- minMax = M.fromList [(minBound, SVG.head xs), (maxBound, SVG.last xs)]+    (mins,maxs) = extrema xs+    (emin,emax) = case bh of+      Nothing  -> mempty+      Just bh' -> extendExtrema xs bh' (mins,maxs)+    --   | isJust bh = (mins `M.union` minMax, maxs `M.union` minMax)+    --   | otherwise = (mins, maxs)++extendExtrema+    :: forall v n a. (VG.Vector v a, KnownNat n)+    => SVG.Vector v (n + 1) a+    -> BoundaryHandler+    -> (M.Map (Finite (n + 1)) a, M.Map (Finite (n + 1)) a)+    -> (M.Map Int a, M.Map Int a)+    -- (M.Map (Finite (n + 1)) a, M.Map (Finite (n + 1)) a)+extendExtrema xs = \case+    BHClamp     -> const (firstLast, firstLast)+    BHSymmetric -> \(mins, maxs) ->+      let addFirst = case (flippedMin, flippedMax) of+              (Nothing      , Nothing      ) -> mempty+              -- first point is local maximum+              (Just (_,mn)  , Nothing      ) -> (mn        , firstPoint)+              -- first point is local minimum+              (Nothing      , Just (_,mx)  ) -> (firstPoint, mx        )+              (Just (mni,mn), Just (mxi,mx))+                | mni < mxi                  -> (mn        , firstPoint)+                | otherwise                  -> (firstPoint, mx        )+            where+              flippedMin = flip fmap (M.lookupMin mins) $ \(minIx, minVal) ->+                (minIx, M.singleton (negate (fromIntegral minIx)) minVal)+              flippedMax = flip fmap (M.lookupMin maxs) $ \(maxIx, maxVal) ->+                (maxIx, M.singleton (negate (fromIntegral maxIx)) maxVal)+          addLast = case (flippedMin, flippedMax) of+              (Nothing      , Nothing      ) -> mempty+              -- last point is local maximum+              (Just (_,mn)  , Nothing      ) -> (mn        , lastPoint )+              -- last point is local minimum+              (Nothing      , Just (_,mx)  ) -> (lastPoint , mx        )+              (Just (mni,mn), Just (mxi,mx))+                | mni > mxi                  -> (mn        , lastPoint )+                | otherwise                  -> (lastPoint , mx        )+            where+              flippedMin = flip fmap (M.lookupMax mins) $ \(minIx, minVal) ->+                (minIx, M.singleton (extendSym (fromIntegral minIx)) minVal)+              flippedMax = flip fmap (M.lookupMax maxs) $ \(maxIx, maxVal) ->+                (maxIx, M.singleton (extendSym (fromIntegral maxIx)) maxVal)+      in  addFirst `mappend` addLast+  where+    lastIx = fromIntegral $ maxBound @(Finite n)+    firstPoint = M.singleton 0 (SVG.head xs)+    lastPoint  = M.singleton lastIx (SVG.last xs)+    firstLast  = firstPoint `mappend` lastPoint+    extendSym i = 2 * lastIx - i++-- | Build a splined vector against a map of control points.+splineAgainst+    :: (VG.Vector v a, KnownNat n, Fractional a, Ord a)+    => SplineEnd a+    -> M.Map Int a              -- ^ extensions+    -> M.Map (Finite n) a+    -> Maybe (SVG.Vector v n a)+splineAgainst se ext = fmap go+                     . makeSpline se+                     . mappend (M.mapKeysMonotonic fromIntegral ext)+                     . M.mapKeysMonotonic fromIntegral+  where+    go spline = SVG.generate (sampleSpline spline . fromIntegral)
src/Numeric/EMD/Internal/Spline.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE ApplicativeDo                            #-} {-# LANGUAGE DataKinds                                #-}-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveGeneric                            #-} {-# LANGUAGE GADTs                                    #-} {-# LANGUAGE RecordWildCards                          #-} {-# LANGUAGE ScopedTypeVariables                      #-}
src/Numeric/HHT.hs view
@@ -204,8 +204,8 @@ -- Takes a "binning" function to allow you to specify how specific you want -- your frequencies to be. ----- See 'hhtSparseSpetrum' for a sparser version, and 'hhtDenseSpectrum' for--- a denser version.+-- See 'hhtSparseSpectrum' for a sparser version, and 'hhtDenseSpectrum'+-- for a denser version. hhtSpectrum     :: forall v n a k. (VG.Vector v a, KnownNat n, Ord k, Num a)     => (a -> k)     -- ^ binning function.  takes rev/tick freq between 0 and 1.
test/Spec.hs view
@@ -1,30 +1,12 @@-{-# LANGUAGE TypeApplications #-} -import           Control.Monad-import           Data.Maybe-import           Numeric.EMD.Internal.Spline-import           Test.HUnit-import qualified Data.Map                as M-import qualified Data.Set                as S+import           Test.Tasty+import           Tests.EMD+import           Tests.HHT+import           Tests.Spline  main :: IO ()-main = void . runTestTT $ TestList-    [ "Sine spline" ~: splineTest+main = defaultMain $ testGroup "Tests"+    [ splineTest+    , emdTests+    , hhtTests     ]--splineTest :: Assertion-splineTest = do-    expected <- map (read @Double) . lines <$> readFile "test-data/sintest.csv"-    roundOut expected @=? roundOut samples-  where-    roundOut :: [Double] -> [Double]-    roundOut = map $ (/ 10e12) . fromInteger . round . (* 10e12)-    spline :: Spline Double-    spline = fromJust-           . makeSpline SENotAKnot-           . M.fromSet sin-           . S.fromList-           $ [0, 1, 2.5, 3.6, 5, 7, 8.1, 10]-    samples :: [Double]-    samples = sampleSpline spline . (/ 4) . fromInteger <$> [0..40]-
+ test/Tests/EMD.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE ScopedTypeVariables                      #-}+{-# LANGUAGE TemplateHaskell                          #-}+{-# LANGUAGE TypeApplications                         #-}+{-# LANGUAGE TypeInType                               #-}+{-# LANGUAGE TypeOperators                            #-}+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}+{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise       #-}++module Tests.EMD (+    emdTests+  ) where++import           Control.Monad+import           Data.Functor.Identity+import           Data.Proxy+import           GHC.TypeNats+import           Hedgehog+import           Numeric.EMD+import           Test.Tasty+import           Tests.Util+import qualified Hedgehog.Range        as Range++emdTests :: TestTree+emdTests = groupTree $$(discover)++prop_iemd_default :: Property+prop_iemd_default = iemdProp defaultEO++prop_orthog_default :: Property+prop_orthog_default = orthogProp defaultEO++edtEO :: EMDOpts Double+edtEO = defaultEO+    { eoSiftCondition = scEnergyDiff 0.01 0.01+                 `SCOr` SCTimes 100+    }++prop_iemd_edt :: Property+prop_iemd_edt = iemdProp edtEO++prop_orthog_edt :: Property+prop_orthog_edt = orthogProp edtEO++sCondEO :: EMDOpts Double+sCondEO = defaultEO+    { eoSiftCondition = SCSCond 10+                 `SCOr` SCTimes 100+    }++prop_iemd_sCond :: Property+prop_iemd_sCond = iemdProp sCondEO++prop_orthog_sCond :: Property+prop_orthog_sCond = orthogProp sCondEO+++iemdProp :: EMDOpts Double -> Property+iemdProp eo = property $ withSize (Range.linear 1 8) $ \(_ :: Proxy n) -> do+    xs <- forAll $ generateData @n+    tripping (CE xs) (emd @_ @_ @(2^n-1) eo . getCE) (Identity . CE . iemd)++orthogProp :: EMDOpts Double -> Property+orthogProp eo = property $ withSize (Range.linear 8 10) $ \(_ :: Proxy n) -> do+    xs   <- forAll $ generateData @n+    let imfs = emdIMFs (emd @_ @_ @(2^n-1) eo xs)+        orthoMatrix =+          [ ((i, j), (x, y), dot x y / sqrt (dot x x * dot y y))+          | (i, x) <- zip indices imfs+          , (j, y) <- zip indices imfs+          , i < j+          ]+        badOrthos = filter (\(_,_,d) -> abs d > 0.5) orthoMatrix+        fracBad :: Double+        fracBad = fromIntegral (length badOrthos)+                / fromIntegral (length orthoMatrix)+    annotateShow orthoMatrix+    annotateShow fracBad+    when (length orthoMatrix < 6) discard+    assert $ fracBad <= 0.5+  where+    indices :: [Int]+    indices = [1..]
+ test/Tests/HHT.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE ScopedTypeVariables                      #-}+{-# LANGUAGE TemplateHaskell                          #-}+{-# LANGUAGE TypeApplications                         #-}+{-# LANGUAGE TypeInType                               #-}+{-# LANGUAGE TypeOperators                            #-}+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}+{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise       #-}++module Tests.HHT (+    hhtTests+  ) where++import           Data.Functor.Identity+import           Data.Proxy+import           GHC.TypeNats+import           Hedgehog+import           Numeric.EMD+import           Numeric.HHT+import           Test.Tasty+import           Tests.Util+import qualified Hedgehog.Range        as Range++hhtTests :: TestTree+hhtTests = groupTree $$(discover)++prop_ihht :: Property+prop_ihht = property $ withSize (Range.linear 1 5) $ \(_ :: Proxy n) -> do+    xs <- forAll $ generateData @n+    tripping (CE xs) (hht @_ @(2^n-1) defaultEO . getCE) (Identity . CE . ihht)++
+ test/Tests/Spline.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE TypeApplications #-}++module Tests.Spline (+    splineTest+  ) where++import           Data.Maybe+import           Numeric.EMD.Internal.Spline+import           Test.Tasty+import           Test.Tasty.HUnit+import qualified Data.Map                    as M+import qualified Data.Set                    as S++splineTest :: TestTree+splineTest = testCase "Spline Test" $ do+    expected <- map (read @Double) . lines <$> readFile "test-data/sintest.csv"+    roundOut expected @=? roundOut samples+  where+    roundOut :: [Double] -> [Double]+    roundOut = map $ (/ 10e12) . fromInteger . round . (* 10e12)+    spline :: Spline Double+    spline = fromJust+           . makeSpline SENotAKnot+           . M.fromSet sin+           . S.fromList+           $ [0, 1, 2.5, 3.6, 5, 7, 8.1, 10]+    samples :: [Double]+    samples = sampleSpline spline . (/ 4) . fromInteger <$> [0..40]
+ test/Tests/Util.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE RankNTypes                               #-}+{-# LANGUAGE RecordWildCards                          #-}+{-# LANGUAGE ScopedTypeVariables                      #-}+{-# LANGUAGE TypeApplications                         #-}+{-# LANGUAGE TypeFamilies                             #-}+{-# LANGUAGE TypeInType                               #-}+{-# LANGUAGE TypeOperators                            #-}+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}+++module Tests.Util (+    groupTree+  , CloseEnough(..)+  , generateData+  , withSize+  , dot+  ) where++import           Data.Complex+import           Data.Proxy+import           Data.Type.Equality+import           GHC.TypeLits.Compare+import           GHC.TypeNats+import           Hedgehog+import           Hedgehog.Internal.Property+import           Numeric.Natural+import           Statistics.Transform+import           Test.Tasty+import           Test.Tasty.Hedgehog+import qualified Data.Vector.Sized          as V+import qualified Hedgehog.Gen               as Gen+import qualified Hedgehog.Range             as Range++groupTree :: Group -> TestTree+groupTree Group{..} = testGroup (unGroupName groupName)+                                (map (uncurry go) groupProperties)+  where+    go :: PropertyName -> Property -> TestTree+    go n = testProperty (mkName (unPropertyName n))+    mkName = map deUnderscore . drop (length @[] @Char "prop_")+    deUnderscore '_' = ' '+    deUnderscore c   = c++newtype CloseEnough n = CE { getCE :: V.Vector n Double }+  deriving Show++instance KnownNat n => Eq (CloseEnough n) where+    CE x == CE y = ((d `dot` d) / sqrt ((x `dot` x) * (y `dot` y))) < 0.0001+      where+        d = V.zipWith (-) x y++dot :: Num a => V.Vector n a -> V.Vector n a -> a+dot a b = sum $ V.zipWith (*) a b++withSize+    :: Monad m+    => Range Natural+    -> (forall n. (KnownNat n, 1 <= 2^n) => Proxy n -> PropertyT m a)+    -> PropertyT m a+withSize r f = do+    n <- forAll $ Gen.integral r+    case someNatVal n of+      SomeNat (p :: Proxy n) -> do+        LE Refl <- pure $ Proxy @1 %<=? Proxy @(2^n)+        f p++generateData+    :: KnownNat n+    => Gen (V.Vector (2^n) Double)+generateData = fmap (fmap realPart . ifftSized) . V.generateM $ \i ->+    let i' = recip . (+ 1) . fromIntegral $ i+    in  mkPolar <$> Gen.double (Range.exponentialFloat (i' / 10) i')+                <*> Gen.double (Range.constant (-pi) pi)++ifftSized+    :: V.Vector (2^n) (Complex Double)+    -> V.Vector (2^n) (Complex Double)+ifftSized = V.withVectorUnsafe ifft+