{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ExistentialQuantification #-}
-- |
-- Module : Numeric.Series
-- Copyright : (c) 2016 Alexey Khudyakov
-- License : BSD3
--
-- Maintainer : alexey.skladnoy@gmail.com, bos@serpentine.com
-- Stability : experimental
-- Portability : portable
--
-- Functions for working with infinite sequences. In particular
-- summation of series and evaluation of continued fractions.
module Numeric.Series (
-- * Data type for infinite sequences.
Sequence(..)
-- * Constructors
, enumSequenceFrom
, enumSequenceFromStep
, scanSequence
-- * Summation of series
, sumSeries
, sumPowerSeries
, sequenceToList
-- * Evaluation of continued fractions
, evalContFractionB
) where
import Control.Applicative
import Data.List (unfoldr)
import Numeric.MathFunctions.Constants (m_epsilon)
----------------------------------------------------------------
-- | Infinite series. It's represented as opaque state and step
-- function.
data Sequence a = forall s. Sequence s (s -> (a,s))
instance Functor Sequence where
fmap f (Sequence s0 step) = Sequence s0 (\s -> let (a,s') = step s in (f a, s'))
{-# INLINE fmap #-}
instance Applicative Sequence where
pure a = Sequence () (\() -> (a,()))
Sequence sA fA <*> Sequence sB fB = Sequence (sA,sB) $ \(!sa,!sb) ->
let (a,sa') = fA sa
(b,sb') = fB sb
in (a b, (sa',sb'))
{-# INLINE pure #-}
{-# INLINE (<*>) #-}
-- | Elementwise operations with sequences
instance Num a => Num (Sequence a) where
(+) = liftA2 (+)
(*) = liftA2 (*)
(-) = liftA2 (-)
{-# INLINE (+) #-}
{-# INLINE (*) #-}
{-# INLINE (-) #-}
abs = fmap abs
signum = fmap signum
fromInteger = pure . fromInteger
{-# INLINE abs #-}
{-# INLINE signum #-}
{-# INLINE fromInteger #-}
-- | Elementwise operations with sequences
instance Fractional a => Fractional (Sequence a) where
(/) = liftA2 (/)
recip = fmap recip
fromRational = pure . fromRational
{-# INLINE (/) #-}
{-# INLINE recip #-}
{-# INLINE fromRational #-}
----------------------------------------------------------------
-- Constructors
----------------------------------------------------------------
-- | @enumSequenceFrom x@ generate sequence:
--
-- \[ a_n = x + n \]
enumSequenceFrom :: Num a => a -> Sequence a
enumSequenceFrom i = Sequence i (\n -> (n,n+1))
{-# INLINE enumSequenceFrom #-}
-- | @enumSequenceFromStep x d@ generate sequence:
--
-- \[ a_n = x + nd \]
enumSequenceFromStep :: Num a => a -> a -> Sequence a
enumSequenceFromStep n d = Sequence n (\i -> (i,i+d))
{-# INLINE enumSequenceFromStep #-}
-- | Analog of 'scanl' for sequence.
scanSequence :: (b -> a -> b) -> b -> Sequence a -> Sequence b
{-# INLINE scanSequence #-}
scanSequence f b0 (Sequence s0 step) = Sequence (b0,s0) $ \(b,s) ->
let (a,s') = step s
b' = f b a
in (b,(b',s'))
----------------------------------------------------------------
-- Evaluation of series
----------------------------------------------------------------
-- | Calculate sum of series
--
-- \[ \sum_{i=0}^\infty a_i \]
--
-- Summation is stopped when
--
-- \[ a_{n+1} < \varepsilon\sum_{i=0}^n a_i \]
--
-- where ε is machine precision ('m_epsilon')
sumSeries :: Sequence Double -> Double
{-# INLINE sumSeries #-}
sumSeries (Sequence sInit step)
= go x0 s0
where
(x0,s0) = step sInit
go x s | abs (d/x) >= m_epsilon = go x' s'
| otherwise = x'
where (d,s') = step s
x' = x + d
-- | Calculate sum of series
--
-- \[ \sum_{i=0}^\infty x^ia_i \]
--
-- Calculation is stopped when next value in series is less than
-- ε·sum.
sumPowerSeries :: Double -> Sequence Double -> Double
sumPowerSeries x ser = sumSeries $ liftA2 (*) (scanSequence (*) 1 (pure x)) ser
{-# INLINE sumPowerSeries #-}
-- | Convert series to infinite list
sequenceToList :: Sequence a -> [a]
sequenceToList (Sequence s f) = unfoldr (Just . f) s
----------------------------------------------------------------
-- Evaluation of continued fractions
----------------------------------------------------------------
-- |
-- Evaluate continued fraction using modified Lentz algorithm.
-- Sequence contain pairs (a[i],b[i]) which form following expression:
--
-- \[
-- b_0 + \frac{a_1}{b_1+\frac{a_2}{b_2+\frac{a_3}{b_3 + \cdots}}}
-- \]
--
-- Modified Lentz algorithm is described in Numerical recipes 5.2
-- "Evaluation of Continued Fractions"
evalContFractionB :: Sequence (Double,Double) -> Double
{-# INLINE evalContFractionB #-}
evalContFractionB (Sequence sInit step)
= let ((_,b0),s0) = step sInit
f0 = maskZero b0
in go f0 f0 0 s0
where
tiny = 1e-60
maskZero 0 = tiny
maskZero x = x
go f c d s
| abs (delta - 1) >= m_epsilon = go f' c' d' s'
| otherwise = f'
where
((a,b),s') = step s
d' = recip $ maskZero $ b + a*d
c' = maskZero $ b + a/c
delta = c'*d'
f' = f*delta