auto-0.3.0.0: Denotative, locally stateful programming DSL & platform

Copyright(c) Justin Le 2015
LicenseMIT
Maintainerjustin@jle.im
Stabilityunstable
Portabilityportable
Safe HaskellNone
LanguageHaskell2010

Control.Auto.Process

Contents

Description

Various Autos 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)

Synopsis

Numerical

sumFrom Source

Arguments

:: (Serialize a, Num a) 
=> a

initial count

-> Auto m a a 

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.

sumFrom x0 = accum (+) x0

sumFrom_ Source

Arguments

:: Num a 
=> a

initial count

-> Auto m a a 

The non-resuming/non-serializing version of sumFrom.

sumFromD Source

Arguments

:: (Serialize a, Num a) 
=> a

initial count

-> Auto m a a 

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.

sumFromD x0 = sumFrom x0 . delay 0
sumFromD x0 = delay x0 . sumFrom x0

sumFromD_ Source

Arguments

:: Num a 
=> a

initial count

-> Auto m a a 

The non-resuming/non-serializing version of sumFromD.

productFrom Source

Arguments

:: (Serialize a, Num a) 
=> a

initial product

-> Auto m a a 

The output is the running/cumulative product of all of the inputs so far, starting from an initial product.

productFrom x0 = accum (*) x0

productFrom_ Source

Arguments

:: Num a 
=> a

initial product

-> Auto m a a 

The non-resuming/non-serializing version of productFrom.

deltas :: (Serialize a, Num a) => Interval m a a Source

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_ :: Num a => Interval m a a Source

The non-resuming/non-serializing version of deltas.

Numerical signal transformations/systems

movingAverage Source

Arguments

:: (Num a, Serialize a) 
=> [a]

weights to apply to previous inputs, from most recent

-> [a]

starting history/initial conditions

-> Auto m a a 

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 ws in the weight window, where the first item is w_0. y_n is the nth output, and x_n is the nth 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_ Source

Arguments

:: Num a 
=> [a]

weights to apply to previous inputs, from most recent

-> [a]

starting history/initial conditions

-> Auto m a a 

The non-serializing/non-resuming version of movingAverage.

impulseResponse Source

Arguments

:: (Num a, Serialize a) 
=> [a]

the impulse response function

-> Auto m a a 

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_ Source

Arguments

:: Num a 
=> [a]

the impulse response function

-> Auto m a a 

The non-serializing/non-resuming version of impulseResponse.

autoRegression Source

Arguments

:: (Num b, Serialize b) 
=> [b]

weights to apply to previous outputs, from most recent

-> [b]

starting history/initial conditions

-> Auto m a b 

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_ Source

Arguments

:: Num b 
=> [b]

weights to apply to previous outputs, from most recent

-> [b]

starting history/initial conditions

-> Auto m a b 

The non-serializing/non-resuming version of autoRegression.

arma Source

Arguments

:: (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 

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_ns are all of the "moving average" weights, where the first weight is wm_0, and wa_ns are all of the "autoregression" weights, where the first weight is wa_1.

arma_ Source

Arguments

:: 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 

The non-serializing/non-resuming version of arma.

Monoidal/Semigroup

mappender :: (Serialize a, Monoid a) => Auto m a a Source

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"]
mappender = accum mappend mempty

mappender_ :: Monoid a => Auto m a a Source

The non-resuming/non-serializing version of mappender.

mappendFrom Source

Arguments

:: (Serialize a, Semigroup a) 
=> a

initial value

-> Auto m a a 

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]
mappendFrom m0 = accum (<>) m0

mappendFrom_ Source

Arguments

:: Semigroup a 
=> a

initial value

-> Auto m a a 

The non-resuming/non-serializing version of mappender.