dde-0.1.0: bench/Impl1.lhs
% How to compile a pdf:
% $ cabal exec lhs2TeX -- -o MackeyGlass.tex MackeyGlass.lhs && pdflatex \
% MackeyGlass.tex
\documentclass{article}
%include polycode.fmt
\title{Mackey-Glass DDE}
\author{Bogdan Penkovsky}
\begin{document}
\maketitle
This document describes a numeric model of the Mackey-Glass DDE
optimized for speed. The fourth-order Runge-Kutta integration method is employed.
> {-# LANGUAGE BangPatterns #-}
> module Impl1 ( mgModel ) where
> import qualified Data.Vector.Storable as V
> import qualified Data.Vector.Storable.Mutable as VM
> import System.IO.Unsafe ( unsafePerformIO )
> import Control.Monad
> import Control.Lens hiding ((<.>))
Sample can be a vector of any length (x, y, z, ...).
We import V1 for single-component vector (scalar) Samples.
> import Linear.V1 as V1
> type Sample = V1.V1
> type Delay = V.Vector
> type Input = V.Vector
Autonomous system integrator
iterate1 is the function describing DDE system;
len1 is the number of delay elements in a delay;
krecord is the number of last samples to record;
total is the total number of iterations;
> integrator'
> :: (VM.Storable a, Floating a) =>
> (Sample a -> (a, a) -> Sample a)
> -> Parameters a
> -> Int
> -> Int
> -> Int
> -> (Sample a, Delay a, Input a)
> -> (Sample a, V.Vector a)
> integrator' iterate1 _ len1 krecord total (!xy0, !hist0, _) = a
> where
> a = unsafePerformIO $ do
> ! v <- VM.new (len1 + total) -- Delay history
> -- Copy the initial history values
> copyHist v hist0
>
> -- Calculate the rest of the vector
> xy' <- go v len1 xy0
>
> trace <- V.unsafeFreeze v
> return (xy', V.slice (len1 + total - krecord) krecord trace)
>
> -- Copy initial conditions
> copyHist !v !hist =
> mapM_ (\i -> VM.unsafeWrite v i (hist V.! i)) [0..V.length hist - 1]
>
> go !v !i !xy
> | i == len1 + total =
> return xy
> | otherwise = do
> x_tau1 <- VM.unsafeRead v (i - len1) -- Delayed element
> x_tau1' <- VM.unsafeRead v (i - len1 + 1) -- The next one
> let !xy' = iterate1 xy (x_tau1, x_tau1')
> !x' = xy' ^._x
> VM.unsafeWrite v i x'
> go v (i + 1) xy'
%if False
> {-# SPECIALISE integrator' ::
> (Sample Float -> (Float, Float) -> Sample Float)
> -> Parameters Float
> -> Int
> -> Int
> -> Int
> -> (Sample Float, Delay Float, Input Float)
> -> (Sample Float, V.Vector Float) #-}
> {-# SPECIALISE integrator' ::
> (Sample Double -> (Double, Double) -> Sample Double)
> -> Parameters Double
> -> Int
> -> Int
> -> Int
> -> (Sample Double, Delay Double, Input Double)
> -> (Sample Double, V.Vector Double) #-}
%endif
> data Parameters a = Parameters {
> pBeta :: a
> , pGamma :: a
> } deriving Show
> param1 :: Parameters Double
> param1 = Parameters {
> pBeta = 0.2
> , pGamma = 0.1
> }
The Mackey-Glass model.
> mackeyGlass
> :: Floating a => Parameters a -> ((Sample a, a) -> Sample a)
> mackeyGlass p (V1.V1 !x, !x_tau1) = V1.V1 x'
> where
> ! x' = beta * x_tau1 / (1 + x_tau1^10) - gamma * x
> beta = pBeta p
> gamma = pGamma p
%if False
> {-# SPECIALISE mackeyGlass ::
> Parameters Float -> (Sample Float, Float) -> Sample Float #-}
> {-# SPECIALISE mackeyGlass ::
> Parameters Double -> (Sample Double, Double) -> Sample Double #-}
%endif
Fourth-order Runge-Kutta for a 1D system with a single delay $\tau_1$.
> rk4 :: Double
> -> ((Sample Double, Double) -> Sample Double)
> -> Sample Double -> (Double, Double) -> Sample Double
> rk4 hStep sys !xy (!x_tau1, !x_tau1') = xy_next
> where
> xy_next = xy + over6 * (a + x2 * b + x2 * c + d)
> over6 = V1.V1 (recip 6)
> over2 = V1.V1 (recip 2)
> x2 = V1.V1 2
> h = V1.V1 hStep
> ! a = h * sys (xy, x_tau1)
> ! b = h * sys (xy + over2 * a, x_tau1_b)
> ! c = h * sys (xy + over2 * b, x_tau1_c)
> ! d = h * sys (xy + c, x_tau1')
> ! x_tau1_b = (x_tau1 + x_tau1') / 2
> ! x_tau1_c = x_tau1_b
Returns the last delay
> fastIntegrRk4 :: Double -> Int -> Int -> Int -> (V.Vector Double, Double)
> -> Parameters Double -> (V.Vector Double, Double)
> fastIntegrRk4 hStep len1 len2 totalIters (hist0, x0) p = (data1, x1)
> where sample0 = V1.V1 x0
> -- Iterator implements Runge-Kutta schema
> iterator = rk4 hStep (mackeyGlass p)
> -- Record only the last long delay
> (V1.V1 x1, data1) = integrator' iterator p len1 len1 totalIters (sample0, hist0, V.fromList [])
Records the whole time trace $x(t)$
> fastIntegrRk4' :: Double -> Int -> Int -> (V.Vector Double, Double)
> -> Parameters Double -> (V.Vector Double, Double)
> fastIntegrRk4' hStep len1 totalIters (hist0, x0) p = (data1, x1)
> where sample0 = V1.V1 x0
> -- Iterator implements Runge-Kutta schema
> iterator = rk4 hStep (mackeyGlass p)
> -- Record all the time trace
> (V1.V1 x1, data1) = integrator' iterator p len1 totalIters totalIters (sample0, hist0, V.fromList [])
Constant initial conditions
> initCondConst :: Int -> Double -> [Double]
> initCondConst = replicate
Define a Mackey-Glass simulation, the final state is the result
> mgModel :: Double -> Int -> Double
> mgModel hStep total = s
> where
> len1 = 17 * round (recip hStep) -- tauD = 17, delay time
>
> icond0 = V.fromList $ initCondConst len1 0.2
>
> -- Integrate an autonomous system (no external input)
> (_, s) = fastIntegrRk4' hStep len1 total (icond0, 0.2) param1
\end{document}