packages feed

HasBigDecimal-0.2.0.0: demo/Taylor.hs

{-# LANGUAGE DeriveFunctor, DeriveFoldable, NoMonomorphismRestriction #-}
{-# LANGUAGE FlexibleInstances #-}
module Taylor where

-- playing around with ideas from https://iagoleal.com/posts/calculus-symbolic-ode/
-- my idea is to use the taylor series based definitions to give implementations for the BigDecimal Floating instance.

import           Data.BigDecimal
import           Data.BigFloating (piChudnovsky)
import           Data.Foldable (toList)
--import           Prelude hiding (pi)


defaultRounding :: RoundingAdvice
defaultRounding = (DOWN, Just 400)

data Stream a = a :> Stream a
  deriving (Functor, Foldable)

infixr 2 :>

ex :: Num a => Stream a
ex = 1 :> ex

sine :: Num a => Stream a
sine   = 0 :> cosine

cosine :: Num a => Stream a
cosine = 1 :> fmap negate sine


-- | Turn a Stream f into a functional approximation
--   of its Taylor series around a point a.
-- That is, eval a f ≈ f(a + x)
eval :: Fractional a => a -> Stream a -> a -> a
eval a f x = foldr1 (\ fa f' -> fa + (x - a) * f') (take 300 taylor)
 where
  taylor      = zipWith (/) (toList f) factorials
  factorials  = let fats = 1 : zipWith (*) fats [1..]
                in fmap fromIntegral fats

eval' :: BigDecimal -> Stream BigDecimal -> BigDecimal -> BigDecimal
eval' a f x = foldr1 (\ fa f' -> fa + (x - a) * f') (take 5000 taylor)
 where
  taylor      = zipWith (/) (toList f) factorials
  factorials  = let fats = 1 : zipWith (*) fats [1..]
                in fmap fromIntegral fats


-- | Taylor series representation of the derivative.
diff :: Stream a -> Stream a
diff (_ :> f') = f'

-- | Taylor series for the constant zero.
zero :: Num a => Stream a
zero = 0 :> zero

euler :: BigDecimal
euler = eval 0 ex 1

--pii = piChudnovsky defaultRounding

p :: BigDecimal
p = eval 0 pi 0

-- | Taylor series for the identity function `f x = x`.
x :: Num a => Stream a
x = 0 :> 1 :> zero

instance Num a => Num (Stream a) where
  -- Good ol' linearity
  (+)  (fa :> f')  (ga :> g') = fa + ga :> f' + g'
  (-)  (fa :> f')  (ga :> g') = fa - ga :> f' - g'
  negate = fmap negate
  -- Leibniz rule applied to streams
  (*) f@(fa :> f') g@(ga :> g') = fa * ga :> f' * g + f * g'
  fromInteger n = fromInteger n :> zero
  abs    = error "Absolute value is not a smooth function"
  signum = error "No well-defined sign for a series"

instance Fractional a => Fractional (Stream a) where
  -- The division rule from Calculus. We assume g(0) ≠ 0
  (/) f@(fa :> f') g@(ga :> g') = fa / ga :> (f' * g - f * g') / g^2
  fromRational n = fromRational n :> zero

analytic :: Num a => (a -> a) -> (Stream a -> Stream a) -> Stream a -> Stream a
analytic g g' f@(fa :> f') = g fa :> g' f * f'

instance Floating (Stream BigDecimal) where
  pi    = piChudnovsky defaultRounding :> zero
  exp   = analytic exp   exp
  log   = analytic log   recip
  sin   = analytic sin   cos
  cos   = analytic cos   (negate . sin)
  asin  = analytic asin  (\x -> 1 / sqrt (1 - x^2))
  acos  = analytic acos  (\x -> -1 / sqrt (1 - x^2))
  atan  = analytic atan  (\x -> 1 / (1 + x^2))
  sinh  = analytic sinh  cosh
  cosh  = analytic cosh  sinh
  asinh = analytic asinh (\x -> 1 / sqrt (x^2 + 1))
  acosh = analytic acosh (\x -> 1 / sqrt (x^2 - 1))
  atanh = analytic atanh (\x -> 1 / (1 - x^2))