\documentclass{article}
%include polycode.fmt
\usepackage{fontspec}
\usepackage{amsmath}
\usepackage{unicode-math}
\usepackage{lualatex-math}
\setmainfont{latinmodern-math.otf}
\setmathfont{latinmodern-math.otf}
\usepackage{verbatim}
\author{Sophie Taylor}
\title{haskell-clifford: A Haskell Clifford algebra dynamics library}
\begin{document}
So yeah. This is a Clifford number representation. I will fill out the documentation more fully and stuff once the design has stabilised.
I am basing the design of this on Issac Trotts' geometric algebra library.\cite{hga}
Let us begin. We are going to use the Numeric Prelude because it is (shockingly) nicer for numeric stuff.
\begin{code}
\end{code}
%if False
\begin{code}
\end{code}
%endif
Clifford algebras are a module over a ring. They also support all the usual transcendental functions.
\begin{code}
module Numeric.Clifford.Multivector where
import Numeric.Clifford.Blade
import NumericPrelude hiding (iterate, head, map, tail, reverse, scanl, zipWith, drop, (++), filter, null, length, foldr, foldl1, zip, foldl, concat, (!!), concatMap,any, repeat, replicate, elem, replicate, all)
import Algebra.Absolute
import Algebra.Algebraic
import Algebra.Additive
import Algebra.Ring
import Algebra.OccasionallyScalar
import Algebra.ToInteger
import Algebra.Transcendental
import Algebra.ZeroTestable
import Algebra.Module
import Algebra.Field
import Data.Serialize
import MathObj.Polynomial.Core (progression)
import System.IO
import Data.List.Stream
import Data.Permute (sort, isEven)
import Data.List.Ordered
import Data.Ord
import Data.Maybe
import Numeric.Natural
import qualified Data.Vector as V
import NumericPrelude.Numeric (sum)
import qualified NumericPrelude.Numeric as NPN
import Test.QuickCheck
import Math.Sequence.Converge (convergeBy)
import Control.DeepSeq
import Number.Ratio hiding (scale, recip)
import Algebra.ToRational
import qualified GHC.Num as PNum
import Control.Lens hiding (indices)
import Control.Exception (assert)
import Data.Maybe
import Data.Monoid
import Data.Data
import Data.DeriveTH
import GHC.TypeLits
import Control.Lens.Lens
import Data.Word
import Control.Applicative ((<$>))
import Numeric.Clifford.Internal
\end{code}
A multivector is nothing but a linear combination of basis blades.
\begin{code}
data Multivector (p::Nat) (q::Nat) f where
BladeSum :: forall p q f . (Ord f, Algebra.Field.C f, SingI p, SingI q) => { _terms :: [Blade p q f]} -> Multivector p q f
type STVector = Multivector 3 1 Double
type E3Vector = Multivector 3 0 Double
instance (SingI p, SingI q, Algebra.Field.C f, Arbitrary f, Ord f) => Arbitrary (Multivector p q f) where
arbitrary = mvNormalForm <$> BladeSum <$> (vector d) where
p' = (fromSing (sing :: Sing p)) :: Integer
q' = (fromSing (sing :: Sing q))
d = fromIntegral (p' + q')
deriving instance Eq (Multivector p q f)
deriving instance Ord (Multivector p q f)
deriving instance (Show f) => Show (Multivector p q f)
signature :: forall (p::Nat) (q::Nat) f. (SingI p, SingI q) => Multivector p q f -> (Natural,Natural)
signature _ = (toNatural ((fromIntegral $ fromSing (sing :: Sing p))::Word),toNatural ((fromIntegral $ fromSing (sing :: Sing q))::Word))
terms :: Lens' (Multivector p q f) [Blade p q f]
terms = lens _terms (\bladeSum v -> bladeSum {_terms = v})
mvNormalForm (BladeSum terms) = BladeSum $ if null resultant then [scalarBlade Algebra.Additive.zero] else resultant where
resultant = filter bladeNonZero $ addLikeTerms' $ Data.List.Ordered.sortBy compare $ map bladeNormalForm $ terms
mvTerms m = m^.terms
addLikeTerms' = sumLikeTerms . groupLikeTerms
groupLikeTerms ::Eq f => [Blade p q f] -> [[Blade p q f]]
groupLikeTerms = groupBy (\a b -> a^.indices == b^.indices)
compareTol :: (Algebra.Algebraic.C f, Algebra.Absolute.C f, Ord f, SingI p, SingI q) => Multivector p q f -> Multivector p q f -> f -> Bool
compareTol x y tol = ((NPN.abs $ magnitude (xy) ) <= tol)
compensatedSum' :: (Algebra.Additive.C f) => [f] -> f
compensatedSum' xs = kahan zero zero xs where
kahan s _ [] = s
kahan s c (x:xs) =
let y = x c
t = s + y
in kahan t ((ts)y) xs
compensatedRunningSum :: (Algebra.Algebraic.C f, Ord f, SingI p, SingI q, Show f) => [Multivector p q f] -> [Multivector p q f]
compensatedRunningSum xs=shanksTransformation . map fst $ scanl kahanSum (zero,zero) xs where
kahanSum (s,c) b = (t,newc) where
y = b c
t = s + y
newc = (t s) y
multiplyOutBlades :: (SingI p, SingI q, Algebra.Ring.C a) => [Blade p q a] -> [Blade p q a] -> [Blade p q a]
multiplyOutBlades x y = [bladeMul l r | l <-x, r <- y]
multiplyList [] = error "Empty list"
multiplyList l = mvNormalForm $ BladeSum listOfBlades where
expandedBlades a = foldl1 multiplyOutBlades a
listOfBlades = expandedBlades $ map mvTerms l
multiplyList1 l = mvNormalForm $ BladeSum listOfBlades where
expandedBlades a = foldl1 multiplyOutBlades a
listOfBlades = expandedBlades $ map mvTerms l
sumList xs = mvNormalForm $ BladeSum $ concat $ map mvTerms xs
sumLikeTerms :: (Algebra.Field.C f, SingI p, SingI q) => [[Blade p q f]] -> [Blade p q f]
sumLikeTerms blades = map (\sameIxs -> map bScale sameIxs & compensatedSum' & (\result -> Blade result ((head sameIxs) & bIndices))) blades
instance (Algebra.Field.C f, SingI p, SingI q, Ord f) => Data.Monoid.Monoid (Sum (Multivector p q f)) where
mempty = Data.Monoid.Sum Algebra.Additive.zero
mappend (Data.Monoid.Sum x) (Data.Monoid.Sum y)= Data.Monoid.Sum (x + y)
mconcat = Data.Monoid.Sum . sumList . map getSum
instance (Algebra.Field.C f, SingI p, SingI q, Ord f) => Data.Monoid.Monoid (Product (Multivector p q f)) where
mempty = Product one
mappend (Product x) (Product y) = Product (x * y)
mconcat = Product . foldl (*) one . map getProduct
e :: (Algebra.Field.C f, Ord f, SingI p, SingI q) => f -> [Natural] -> Multivector p q f
s `e` indices = mvNormalForm $ BladeSum [Blade s indices]
scalar s = s `e` []
instance (Control.DeepSeq.NFData f) => Control.DeepSeq.NFData (Multivector p q f)
instance (Algebra.Field.C f, Ord f, SingI p, SingI q) => Algebra.Additive.C (Multivector p q f) where
a + b = mvNormalForm $ BladeSum (mvTerms a ++ mvTerms b)
a b = mvNormalForm $ BladeSum (mvTerms a ++ map bladeNegate (mvTerms b))
zero = BladeSum [scalarBlade Algebra.Additive.zero]
\end{code}
Now it is time for the Clifford product. :3
\begin{code}
instance (Algebra.Field.C f, Ord f, SingI p, SingI q) => Algebra.Ring.C (Multivector p q f) where
BladeSum [Blade s []] * b = BladeSum $ map (bladeScaleLeft s) $ mvTerms b
a * BladeSum [Blade s []] = BladeSum $ map (bladeScaleRight s) $ mvTerms a
a * b = mvNormalForm $ BladeSum [bladeMul x y | x <- mvTerms a, y <- mvTerms b]
one = scalar Algebra.Ring.one
fromInteger i = scalar $ Algebra.Ring.fromInteger i
a ^ 2 = a * a
a ^ 0 = one
a ^ 1 = a
a ^ n = multiplyList (replicate (NPN.fromInteger n) a)
two = fromInteger 2
mul = (Algebra.Ring.*)
psuedoScalar :: forall (p::Nat) (q::Nat) f. (Ord f, Algebra.Field.C f, SingI p, SingI q) => Multivector p q f
psuedoScalar = one `e` [0..(toNatural d)] where
d = fromIntegral (p' + q' 1 )::Word
p'= fromSing (sing :: Sing p)
q' = fromSing (sing :: Sing q)
\end{code}
Clifford numbers have a magnitude and absolute value:
\begin{code}
magnitude :: (Algebra.Algebraic.C f) => Multivector p q f -> f
magnitude = sqrt . compensatedSum' . map (\b -> (bScale b)^ 2) . mvTerms
instance (Algebra.Absolute.C f, Algebra.Algebraic.C f, Ord f, SingI p, SingI q) => Algebra.Absolute.C (Multivector p q f) where
abs v = magnitude v `e` []
signum (BladeSum [Blade scale []]) = scalar $ signum scale
signum (BladeSum []) = scalar Algebra.Additive.zero
instance (Algebra.Field.C f, Ord f, SingI p, SingI q) => Algebra.Module.C f (Multivector p q f) where
(*>) s v = v & mvTerms & map (bladeScaleLeft s) & BladeSum
(</) n d = Numeric.Clifford.Multivector.inverse d * n
(/>) n d = n * Numeric.Clifford.Multivector.inverse d
(</>) n d = n /> d
scaleLeft s v = BladeSum $ map (bladeScaleLeft s) $ mvTerms v
scaleRight v s = BladeSum $ map (bladeScaleRight s) $ mvTerms v
divideRight v s = scaleRight v (recip s)
converge [] = error "converge: empty list"
converge xs = fromMaybe empty (convergeBy checkPeriodic Just xs)
where
empty = error "converge: error in implmentation"
checkPeriodic (a:b:c:_)
| (myTrace ("Converging at " ++ show a) a) == b = Just a
| a == c = Just a
checkPeriodic _ = Nothing
aitkensAcceleration [] = []
aitkensAcceleration a@(xn:[]) = a
aitkensAcceleration a@(xn:xnp1:[]) = a
aitkensAcceleration a@(xn:xnp1:xnp2:[]) = a
aitkensAcceleration (xn:xnp1:xnp2:xs) | xn == xnp1 = [xnp1]
| xn == xnp2 = [xnp2]
| otherwise = xn ((dxn ^ 2) /> ddxn) : aitkensAcceleration (xnp1:xnp2:xs) where
dxn = sumList [xnp1,negate xn]
ddxn = sumList [xn, (2) * xnp1, xnp2]
shanksTransformation :: (Algebra.Algebraic.C f, Ord f, Show f, SingI p, SingI q) => [Multivector p q f] -> [Multivector p q f]
shanksTransformation [] = []
shanksTransformation a@(xnm1:[]) = a
shanksTransformation a@(xnm1:xn:[]) = a
shanksTransformation (xnm1:xn:xnp1:xs) | xnm1 == xn = [xn]
| xnm1 == xnp1 = [xnm1]
| denominator == zero = [xnp1]
| otherwise = myTrace ("Shanks transformation input = " ++ show xn ++ "\nShanks transformation output = " ++ show out) out:shanksTransformation (xn:xnp1:xs) where
out = numerator /> denominator
numerator = sumList [xnp1*xnm1, negate (xn^2)]
denominator = sumList [xnp1, (2)*xn, xnm1]
takeEvery nth xs = case drop (nth1) xs of
(y:ys) -> y : takeEvery nth ys
[] -> []
seriesPlusMinus (x:y:rest) = x:Algebra.Additive.negate y: seriesPlusMinus rest
seriesMinusPlus (x:y:rest) = Algebra.Additive.negate x : y : seriesMinusPlus rest
expTerms :: (Algebra.Algebraic.C f, SingI p, SingI q, Ord f) => Multivector p q f -> [Multivector p q f]
expTerms x = map snd $ iterate (\(n,b) -> (n + 1, (recip $ fromInteger n ) `scaleLeft` (x*b) )) (1::NPN.Integer,one)
instance (Algebra.Transcendental.C f, Ord f, SingI p, SingI q, Show f) => Algebra.Transcendental.C (Multivector p q f) where
pi = scalar pi
exp (BladeSum [ Blade s []]) = myTrace ("scalar exponential of " ++ show s) scalar $ exp s
exp x = myTrace ("Computing exponential of " ++ show x) convergeTerms x where
expMag = exp mag
expScaled = converge $ shanksTransformation.shanksTransformation . compensatedRunningSum $ expTerms scaled
convergeTerms terms = converge $ shanksTransformation.shanksTransformation.compensatedRunningSum $ expTerms terms
mag = myTrace ("In exponential, magnitude is " ++ show ( magnitude x)) magnitude x
scaled = let val = (recip mag) *> x in myTrace ("In exponential, scaled is" ++ show val) val
log (BladeSum [Blade s []]) = scalar $ NPN.log s
log a = scalar (log mag) + log' scaled where
(scaled,mag) = normalised a
log' a = converge $ halleysMethod f f' f'' (one `e` [1,2]) where
f x = a exp x
f' x = NPN.negate $ exp x
f'' = f'
sin (BladeSum [Blade s []]) = scalar $ sin s
sin x = converge $ shanksTransformation $ compensatedRunningSum $ sinTerms x where
sinTerms x = seriesPlusMinus $ takeEvery 2 $ expTerms x
cos (BladeSum [Blade s []]) = scalar $ cos s
cos x = converge $ shanksTransformation $ compensatedRunningSum (one : cosTerms x) where
cosTerms x = seriesMinusPlus $ takeEvery 2 $ tail $ expTerms x
atan (BladeSum [Blade s []]) = scalar $ atan s
atan z = (z/onePlusZSquared) * (one + (converge $ shanksTransformation $ compensatedRunningSum $ map lambda [1..])) where
lambda :: Integer -> Multivector p q f
lambda n = multiplyList1 $ map innerFraction [1..n]
innerFraction :: Integer -> Multivector p q f
innerFraction k = (tk*zSquared)/>((tk+one)*(onePlusZSquared)) where
tk = fromInteger (2*k)
zSquared = z^2 :: Multivector p q f
onePlusZSquared = one+z^2 :: Multivector p q f
cosh x = converge $ shanksTransformation . compensatedRunningSum $ takeEvery 2 $ expTerms x
sinh x = converge $ shanksTransformation . compensatedRunningSum $ takeEvery 2 $ tail $ expTerms x
dot :: Multivector p q f -> Multivector p q f -> Multivector p q f
dot a@(BladeSum _) b@(BladeSum _) = mvNormalForm $ BladeSum [x `bDot` y | x <- mvTerms a, y <- mvTerms b]
wedge::Multivector p q f -> Multivector p q f->Multivector p q f
wedge a@(BladeSum _) b@(BladeSum _) = mvNormalForm $ BladeSum [x `bWedge` y | x <- mvTerms a, y <- mvTerms b]
(∧) :: Multivector p q f -> Multivector p q f -> Multivector p q f
(∧) = wedge
(⋅) :: Multivector p q f -> Multivector p q f -> Multivector p q f
(⋅) = dot
reverseBlade b = bladeNormalForm $ b & indices %~ reverse
reverseMultivector v = mvNormalForm $ v & terms.traverse%~ reverseBlade
inverse a@(BladeSum _) = assert (a /= zero) $ (recip scalarComponent) *> (reverseMultivector a) where
scalarComponent = bScale (head $ mvTerms (a * reverseMultivector a))
instance (Algebra.Field.C f, Ord f, SingI p, SingI q) => Algebra.Field.C (Multivector p q f) where
recip = inverse
instance (Algebra.Field.C f, Ord f, SingI p, SingI q) => Algebra.OccasionallyScalar.C f (Multivector p q f) where
toScalar = bScale . bladeGetGrade 0 . head . mvTerms
toMaybeScalar (BladeSum [Blade s []]) = Just s
toMaybeScalar (BladeSum []) = Just Algebra.Additive.zero
toMaybeScalar _ = Nothing
fromScalar = scalar
\end{code}
Also, we may as well implement the standard prelude Num interface.
\begin{code}
instance (Algebra.Algebraic.C f, SingI p, SingI q, Ord f) => PNum.Num (Multivector p q f) where
(+) = (Algebra.Additive.+)
() = (Algebra.Additive.-)
(*) = (Algebra.Ring.*)
negate = NPN.negate
abs = scalar . magnitude
fromInteger = Algebra.Ring.fromInteger
signum m = Numeric.Clifford.Multivector.inverse (scalar $ magnitude m) * m
\end{code}
Let's use Newton or Halley iteration to find the principal n-th root :3
\begin{code}
instance (Algebra.Algebraic.C f, Show f, Ord f, SingI p, SingI q) => Algebra.Algebraic.C (Multivector p q f) where
root 0 _ = error "Cannot take 0th root"
root _ (BladeSum []) = error "Empty bladesum"
root _ (BladeSum [Blade zero []]) = error "Cannot compute a root of zero"
root n (BladeSum [Blade s []]) = scalar $ root n s
root n a@(BladeSum _) = converge $ rootIterationsStart n a g where
g = if q' <= 1 then one`e`[q',succ q'] else one + one `e` [0,1]
(p',q') = signature a
rootIterationsStart ::(Ord f, Show f, Algebra.Algebraic.C f)=> NPN.Integer -> Multivector p q f -> Multivector p q f -> [Multivector p q f]
rootIterationsStart n a@(BladeSum (Blade s [] :_)) one = rootHalleysIterations n a g where
g = if s >= NPN.zero || q' == 1 then one else (Algebra.Ring.one `e` [0,1])
(p',q') = signature a
rootIterationsStart n a@(BladeSum _) g = rootHalleysIterations n a g
rootNewtonIterations :: (Algebra.Field.C f, Ord f, SingI p, SingI q) => NPN.Integer -> Multivector p q f -> Multivector p q f -> [Multivector p q f]
rootNewtonIterations n a = iterate xkplus1 where
xkplus1 xk = xk + deltaxk xk
deltaxk xk = oneOverN * ((inverse (xk ^ (n one))* a) xk)
oneOverN = scalar $ NPN.recip $ fromInteger n
rootHalleysIterations :: (Show a, Ord a, Algebra.Algebraic.C a, SingI p, SingI q) => NPN.Integer -> Multivector p q a -> Multivector p q a -> [Multivector p q a]
rootHalleysIterations n a = halleysMethod f f' f'' where
f x = a (x^n)
f' x = fromInteger (n) * (x^(n1))
f'' x = fromInteger ((n*(n1))) * (x^(n2))
halleysMethod :: (Show a, Ord a, Algebra.Algebraic.C a, SingI p, SingI q) => (Multivector p q a -> Multivector p q a) -> (Multivector p q a -> Multivector p q a) -> (Multivector p q a -> Multivector p q a) -> Multivector p q a -> [Multivector p q a]
halleysMethod f f' f'' = iterate update where
update x = x (numerator x * inverse (denominator x) ) where
numerator x= multiplyList [2, fx, dfx]
denominator x= multiplyList [2, dfx, dfx] (fx * ddfx)
fx = f x
dfx = f' x
ddfx = f'' x
secantMethod f x0 x1 = update x1 x0 where
update xm1 xm2 | xm1 == xm2 = [xm1]
| otherwise = if x == xm1 then [x] else x : update x xm1 where
x = xm1 f xm1 * (xm1xm2) * Numeric.Clifford.Multivector.inverse (f xm1 f xm2)
\end{code}
Now let's try logarithms by fixed point iteration. It's gonna be slow, but whatever!
\begin{code}
normalised :: (Ord f, Algebra.Algebraic.C f, SingI p, SingI q) => Multivector p q f -> (Multivector p q f,f)
normalised a = (a `scaleRight` ( recip $ mag),mag) where
mag = magnitude a
\end{code}
Now let's do (slow as fuck probably) numerical integration! :D~! Since this is gonna be used for physical applications, it's we're gonna start off with a Hamiltonian structure and then a symplectic integrator.
\begin{code}
\end{code}
\bibliographystyle{IEEEtran}
\bibliography{biblio.bib}
\end{document}