content: -- |
  -- Module      : Control.Auto.Process
  -- Description : 'Auto's useful for various commonly occurring processes.
  -- Copyright   : (c) Justin Le 2015
  -- License     : MIT
  -- Maintainer  : justin@jle.im
  -- Stability   : unstable
  -- Portability : portable
  --
  -- Various 'Auto's describing relationships following common processes,
  -- like 'sumFrom', whose output is the cumulative sum of the input.
  --
  -- Also has some 'Auto' constructors inspired from digital signal
  -- processing signal transformation systems and statistical models.
  --
  -- Note that all of these can be turned into an equivalent version acting
  -- on blip streams, with 'perBlip':
  --
  -- @
  -- 'sumFrom' n           :: 'Num' a => 'Auto' m a a
  -- 'perBlip' ('sumFrom' n) :: 'Num' a => 'Auto' m ('Blip' a) ('Blip' a)
  -- @
  --
  module Control.Auto.Process (
    -- * Numerical
      sumFrom
    , sumFrom_
    , sumFromD
    , sumFromD_
    , productFrom
    , productFrom_
    , deltas
    , deltas_
    -- ** Numerical signal transformations/systems
    , movingAverage
    , movingAverage_
    , impulseResponse
    , impulseResponse_
    , autoRegression
    , autoRegression_
    , arma
    , arma_
    -- * Monoidal/Semigroup
    , mappender
    , mappender_
    , mappendFrom
    , mappendFrom_
    ) where
  
  import Control.Auto.Core
  import Control.Auto.Interval
  import Data.Semigroup
  import Data.Serialize
  
  -- | The stream of outputs is the cumulative/running sum of the inputs so
  -- far, starting with an initial count.
  --
  -- The first output takes into account the first input.  See 'sumFromD' for
  -- a version where the first output is the initial count itself.
  --
  -- prop> sumFrom x0 = accum (+) x0
  sumFrom :: (Serialize a, Num a)
          => a             -- ^ initial count
          -> Auto m a a
  sumFrom = accum (+)
  
  -- | The non-resuming/non-serializing version of 'sumFrom'.
  sumFrom_ :: Num a
           => a             -- ^ initial count
           -> Auto m a a
  sumFrom_ = accum_ (+)
  
  -- | Like 'sumFrom', except the first output is the starting count.
  --
  -- >>> let a = sumFromD 5
  -- >>> let (y1, a') = stepAuto' a 10
  -- >>> y1
  -- 5
  -- >>> let (y2, _ ) = stepAuto' a' 3
  -- >>> y2
  -- 10
  --
  -- >>> streamAuto' (sumFrom 0) [1..10]
  -- [1,3,6,10,15,21,28,36,45,55]
  -- >>> streamAuto' (sumFromD 0) [1..10]
  -- [0,1,3,6,10,15,21,28,36,45]
  --
  -- It's 'sumFrom', but "delayed".
  --
  -- Useful for recursive bindings, where you need at least one value to be
  -- able to produce its "first output" without depending on anything else.
  --
  -- prop> sumFromD x0 = sumFrom x0 . delay 0
  -- prop> sumFromD x0 = delay x0 . sumFrom x0
  sumFromD :: (Serialize a, Num a)
           => a             -- ^ initial count
           -> Auto m a a
  sumFromD = accumD (+)
  
  -- | The non-resuming/non-serializing version of 'sumFromD'.
  sumFromD_ :: Num a
            => a             -- ^ initial count
            -> Auto m a a
  sumFromD_ = accumD_ (+)
  
  -- | The output is the running/cumulative product of all of the inputs so
  -- far, starting from an initial product.
  --
  -- prop> productFrom x0 = accum (*) x0
  productFrom :: (Serialize a, Num a)
              => a            -- ^ initial product
              -> Auto m a a
  productFrom = accum (*)
  
  -- | The non-resuming/non-serializing version of 'productFrom'.
  productFrom_ :: Num a
               => a           -- ^ initial product
               -> Auto m a a
  productFrom_ = accum_ (*)
  
  -- | The output is the the difference between the input and the previously
  -- received input.
  --
  -- First result is a 'Nothing', so you can use '<|!>' or 'fromInterval' or
  -- 'fromMaybe' to get a "default first value".
  --
  -- >>> streamAuto' deltas [1,6,3,5,8]
  -- >>> [Nothing, Just 5, Just (-3), Just 2, Just 3]
  --
  -- Usage with '<|!>':
  --
  -- >>> let a = deltas <|!> pure 100
  -- >>> streamAuto' (deltas <|!> pure 100) [1,6,3,5,8]
  -- [100, 5, -3, 2, 3]
  --
  -- Usage with 'fromMaybe':
  --
  -- >>> streamAuto' (fromMaybe 100 <$> deltas) [1,6,3,5,8]
  -- [100, 5, -3, 2, 3]
  --
  deltas :: (Serialize a, Num a) => Interval m a a
  deltas = mkState _deltasF Nothing
  
  -- | The non-resuming/non-serializing version of 'deltas'.
  deltas_ :: Num a => Interval m a a
  deltas_ = mkState_ _deltasF Nothing
  
  _deltasF :: Num a => a -> Maybe a -> (Maybe a, Maybe a)
  _deltasF x s = case s of
                   Nothing   -> (Nothing        , Just x)
                   Just prev -> (Just (x - prev), Just x)
  
  -- | The output is the running/cumulative 'mconcat' of all of the input
  -- seen so far, starting with 'mempty'.
  --
  -- >>> streamauto' mappender . map Last $ [Just 4, Nothing, Just 2, Just 3]
  -- [Last (Just 4), Last (Just 4), Last (Just 2), Last (Just 3)]
  -- >>> streamAuto' mappender ["hello","world","good","bye"]
  -- ["hello","helloworld","helloworldgood","helloworldgoodbye"]
  --
  -- prop> mappender = accum mappend mempty
  mappender :: (Serialize a, Monoid a) => Auto m a a
  mappender = accum mappend mempty
  
  -- | The non-resuming/non-serializing version of 'mappender'.
  mappender_ :: Monoid a => Auto m a a
  mappender_ = accum_ mappend mempty
  
  -- | The output is the running '<>'-sum ('mappend' for 'Semigroup') of all
  -- of the input values so far, starting with a given starting value.
  -- Basically like 'mappender', but with a starting value.
  --
  -- >>> streamAuto' (mappendFrom (Max 0)) [Max 4, Max (-2), Max 3, Max 10]
  -- [Max 4, Max 4, Max 4, Max 10]
  --
  -- prop> mappendFrom m0 = accum (<>) m0
  mappendFrom :: (Serialize a, Semigroup a)
              => a            -- ^ initial value
              -> Auto m a a
  mappendFrom = accum (<>)
  
  -- | The non-resuming/non-serializing version of 'mappender'.
  mappendFrom_ :: Semigroup a
               => a           -- ^ initial value
               -> Auto m a a
  mappendFrom_ = accum_ (<>)
  
  -- | The output is the sum of the past inputs, multiplied by a moving
  -- window of weights.
  --
  -- For example, if the last received inputs are @[1,2,3,4]@ (from most
  -- recent to oldest), and the window of weights is @[2,0.5,4]@, then the
  -- output will be @1*2 + 0.5*2 + 4*3@, or @15@.  (The weights are assumed
  -- to be zero past the end of the weight window)
  --
  -- The immediately received input is counted as a part of the history.
  --
  -- Mathematically,
  -- @y_n = w_0 * x_(n-0) + w_1 + x_(n-1) + w_2 * x_(n-1) + ...@, for all
  -- @w@s in the weight window, where the first item is @w_0@.  @y_n@ is the
  -- @n@th output, and @x_n@ is the @n@th input.
  --
  -- Note that this serializes the history of the input...or at least the
  -- history as far back as the entire window of weights.  (A weight list of
  -- five items will serialize the past five received items)  If your weight
  -- window is very long (or infinite), then serializing is a bad idea!
  --
  -- The second parameter is a list of a "starting history", or initial
  -- conditions, to be used when the actual input history isn't long enough.
  -- If you want all your initial conditions/starting history to be @0@, just
  -- pass in @[]@.
  --
  -- Minus serialization, you can implement 'sumFrom' as:
  --
  -- @
  -- sumFrom n = movingAverage (repeat 1) [n]
  -- @
  --
  -- And you can implement a version of 'deltas' as:
  --
  -- @
  -- deltas = movingAverage [1,-1] []
  -- @
  --
  -- It behaves the same, except the first step outputs the initially
  -- received value.  So it's realy a bit like
  --
  -- @
  -- (movingAverage [1,-1] []) == (deltas <|!> id)
  -- @
  --
  -- Where for the first step, the actual input is used instead of the delta.
  --
  -- Name comes from the statistical model.
  --
  movingAverage :: (Num a, Serialize a)
                => [a]          -- ^ weights to apply to previous inputs,
                                --     from most recent
                -> [a]          -- ^ starting history/initial conditions
                -> Auto m a a
  movingAverage weights = mkState (_movingAverageF weights)
  
  -- | The non-serializing/non-resuming version of 'movingAverage'.
  movingAverage_ :: Num a
                 => [a]         -- ^ weights to apply to previous inputs,
                                --     from most recent
                 -> [a]         -- ^ starting history/initial conditions
                 -> Auto m a a
  movingAverage_ weights = mkState_ (_movingAverageF weights)
  
  _movingAverageF :: Num a => [a] -> a -> [a] -> (a, [a])
  _movingAverageF weights x hist = (sum (zipWith (*) weights hist'), hist')
    where
      hist' = zipWith const (x:hist) weights
  
  -- | Any linear time independent stream transformation can be encoded by
  -- the response of the transformation when given @[1,0,0,0...]@, or @1
  -- : 'repeat' 0@.  So, given an "LTI" 'Auto', if you feed it @1 : 'repeat'
  -- 0@, the output is what is called an "impulse response function".
  --
  -- For any "LTI" 'Auto', we can reconstruct the behavior of the original
  -- 'Auto' given its impulse response.  Give 'impulseResponse' an impulse
  -- response, and it will recreate/reconstruct the original 'Auto'.
  --
  -- >>> let getImpulseResponse a = streamAuto' a (1 : repeat 0)
  -- >>> let sumFromImpulseResponse = getImpulseResponse (sumFrom 0)
  -- >>> streamAuto' (sumFrom 0) [1..10]
  -- [1,3,6,10,15,21,28,36,45,55]
  -- >>> streamAuto' (impulseResponse sumFromImpulseResponse) [1..10]
  -- [1,3,6,10,15,21,28,36,45,55]
  --
  -- Use this function to create an LTI system when you know its impulse
  -- response.
  --
  -- >>> take 10 . streamAuto' (impulseResponse (map (2**) [0,-1..])) $ repeat 1
  -- [1.0,1.5,1.75,1.875,1.9375,1.96875,1.984375,1.9921875,1.99609375,1.998046875]
  --
  -- All impulse response after the end of the given list is assumed to be
  -- zero.
  --
  -- Mathematically,
  -- @y_n = h_0 * x_(n-0) + h_1 + x_(n-1) + h_2 * x_(n-1) + ...@, for all
  -- @h_n@ in the input response, where the first item is @h_0@.
  --
  -- Note that when this is serialized, it must serialize a number of input
  -- elements equal to the length of the impulse response list...so if you give
  -- an infinite impulse response, you might want to use 'impulseResponse_',
  -- or not serialize.
  --
  -- By the way, @'impulseResponse' ir == 'movingAverage' ir []@.
  --
  impulseResponse :: (Num a, Serialize a)
                  => [a]        -- ^ the impulse response function
                  -> Auto m a a
  impulseResponse weights = movingAverage weights []
  
  -- | The non-serializing/non-resuming version of 'impulseResponse'.
  impulseResponse_ :: Num a
                   => [a]       -- ^ the impulse response function
                   -> Auto m a a
  impulseResponse_ weights = movingAverage_ weights []
  
  -- | The output is the sum of the past outputs, multiplied by a moving
  -- window of weights.  Ignores all input.
  --
  -- For example, if the last outputs are @[1,2,3,4]@ (from most recent to
  -- oldest), and the window of weights is @[2,0.5,4]@, then the output will
  -- be @1*2 + 0.5*2 + 4*3@, or @15@.  (The weights are assumed to be zero
  -- past the end of the weight window)
  --
  -- Mathematically, @y_n = w_1 * y_(n-1) + w_2 * y_(n-2) + ...@, for all @w@
  -- in the weight window, where the first item is @w_1@.
  --
  -- Note that this serializes the history of the outputs...or at least the
  -- history as far back as the entire window of weights.  (A weight list of
  -- five items will serialize the past five outputted items)  If your weight
  -- window is very long (or infinite), then serializing is a bad idea!
  --
  -- The second parameter is a list of a "starting history", or initial
  -- conditions, to be used when the actual output history isn't long enough.
  -- If you want all your initial conditions/starting history to be @0@, just
  -- pass in @[]@.
  --
  -- You can use this to implement any linear recurrence relationship, like
  -- he fibonacci sequence:
  --
  -- >>> evalAutoN' 10 (autoRegression [1,1] [1,1]) ()
  -- [2,3,5,8,13,21,34,55,89,144]
  -- >>> evalAutoN' 10 (fromList [1,1] --> autoRegression [1,1] [1,1]) ()
  -- [1,1,2,3,5,8,13,21,34,55]
  --
  -- Which is 1 times the previous value, plus one times the value before
  -- that.
  --
  -- You can create a series that doubles by having it be just twice the
  -- previous value:
  --
  -- >>> evalAutoN' 10 (autoRegression [2] [1]) ()
  -- [2,,4,8,16,32,64,128,256,512,1024]
  --
  -- Name comes from the statistical model.
  --
  autoRegression :: (Num b, Serialize b)
                 => [b]         -- ^ weights to apply to previous outputs,
                                --     from most recent
                 -> [b]         -- ^ starting history/initial conditions
                 -> Auto m a b
  autoRegression weights = mkState (const (_autoRegressionF weights))
  
  -- | The non-serializing/non-resuming version of 'autoRegression'.
  autoRegression_ :: Num b
                  => [b]        -- ^ weights to apply to previous outputs,
                                --     from most recent
                  -> [b]        -- ^ starting history/initial conditions
                  -> Auto m a b
  autoRegression_ weights = mkState_ (const (_autoRegressionF weights))
  
  _autoRegressionF :: Num b => [b] -> [b] -> (b, [b])
  _autoRegressionF weights hist = (result, hist')
    where
      result = sum (zipWith (*) weights hist)
      hist'  = zipWith const (result:hist) weights
  
  -- | A combination of 'autoRegression' and 'movingAverage'.  Inspired by
  -- the statistical model.
  --
  -- Mathematically:
  --
  -- @
  -- y_n = wm_0 * x_(n-0) + wm_1 * x_(n-1) + wm_2 * x_(n-2) + ...
  --                      + wa_1 * y_(n-1) + wa_2 * y_(n-1) + ...
  -- @
  --
  -- Where @wm_n@s are all of the "moving average" weights, where the first
  -- weight is @wm_0@, and @wa_n@s are all of the "autoregression" weights,
  -- where the first weight is @wa_1@.
  arma :: (Num a, Serialize a)
       => [a]   -- ^ weights for the "auto-regression" components
       -> [a]   -- ^ weights for the "moving average" components
       -> [a]   -- ^ an "initial history" of outputs, recents first
       -> [a]   -- ^ an "initial history" of inputs, recents first
       -> Auto m a a
  arma arWeights maWeights arHist maHist =
          mkState (_armaF arWeights maWeights) (arHist, maHist)
  
  -- | The non-serializing/non-resuming version of 'arma'.
  arma_ :: Num a
        => [a]  -- ^ weights for the "auto-regression" components
        -> [a]  -- ^ weights for the "moving average" components
        -> [a]  -- ^ an "initial history" of outputs, recents first
        -> [a]  -- ^ an "initial history" of inputs, recents first
        -> Auto m a a
  arma_ arWeights maWeights arHist maHist =
          mkState_ (_armaF arWeights maWeights) (arHist, maHist)
  
  _armaF :: Num a => [a] -> [a] -> a -> ([a], [a]) -> (a, ([a], [a]))
  _armaF arWeights maWeights x (arHist, maHist) = (y, (arHist', maHist'))
    where
      maHist' = zipWith const (x:maHist) maWeights
      ma      = sum (zipWith (*) maWeights maHist')
  
      ar      = sum (zipWith (*) arWeights arHist)
  
      y       = ar + ma
  
      arHist' = zipWith const (y:arHist) arWeights
  
  
  