cl3-3.0.0.0: benchmarks/NbodyMassiv.hs
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE InstanceSigs #-}
import Algebra.Geometric.Cl3
import Data.Massiv.Array as A
import Control.Monad (replicateM_,when)
import Data.Time.Clock.POSIX (getPOSIXTime)
import Foreign.Ptr (Ptr, castPtr, plusPtr)
import Foreign.Storable (Storable(..))
import Data.IORef (IORef(),newIORef,modifyIORef',readIORef)
main :: IO ()
main = do
arrayProgrammingAlgorithm
bitTwiddlingAlgorithm
arrayProgrammingAlgorithm :: IO ()
arrayProgrammingAlgorithm =
do tStart <- getPOSIXTime
n <- return (50000000 :: Int)
planetsPos :: Array S Ix1 Cl3_V3 <- fromListsM Seq planetspos -- generate immutible Massiv array of the planets position
planetsVel :: Array S Ix1 Cl3_V3 <- fromListsM Seq planetsvel -- generate immutible Massiv array of the planets velocity
planetsMass :: Array S Ix1 Cl3_R <- fromListsM Seq planetsmass -- generate immutible Massiv array of the planets mass
mutablePos <- thaw planetsPos -- copy to a mutible Massiv array of the planets position
mutableVel <- thaw planetsVel -- copy to a mutible Massiv array of the planets velocity
let (Sz1 c) = size planetsVel -- Size of the velocity array
c' = (c^(2 :: Int) - c) `div` 2 -- size of the nChoose2With array
mutableScratch <- newMArray (Sz1 c') (toCl3_V3 $ V3 0 0 0) -- nChoose2With sized scratch pad
mutableScratch2D <- newMArray (Sz (c :. (c-1))) (toCl3_V3 $ V3 0 0 0) -- 2D scratch pad for holding acceleration components
offsetMomentum mutableVel planetsMass -- modify the Sun's velocity such that the total momentum is zero
printEnergy mutablePos mutableVel planetsMass
replicateM_ n (advance mutablePos mutableVel planetsMass mutableScratch mutableScratch2D)
printEnergy mutablePos mutableVel planetsMass
tEnd <- getPOSIXTime
print $ show (tEnd - tStart) ++ " (sec) for " ++ show n ++ " iterations." -- Relative time measurement
-- | 'bitTwiddlingAlgorithm' with Ϟ symbolizing the bit twiddling algorithm versions
bitTwiddlingAlgorithm :: IO ()
bitTwiddlingAlgorithm =
do tStart <- getPOSIXTime
n <- return (50000000 :: Int)
(solarSystem,sz) <- offsetMomentumϞ [sun,jupiter,saturn,uranus,neptune] -- returns a zero momentum solar system
printEnergyϞ solarSystem sz
replicateM_ n (advanceϞ solarSystem sz)
printEnergyϞ solarSystem sz
tEnd <- getPOSIXTime
print $ show (tEnd - tStart) ++ " (sec) for " ++ show n ++ " iterations." -- Relative time measurement
offsetMomentumϞ :: [Body] -> IO (SolarSystem,Int) -- A SolarSystem and the Size : size: sz :: Int
offsetMomentumϞ bodies = do
initBodies :: Array S Ix1 Body <- A.fromListsM Seq bodies
-- mutate the solar system so that the sun's velocity gives zero total momentum
let velocity :: Array D Ix1 Cl3 = A.map (fromCl3_V3.vel) initBodies
mass :: Array D Ix1 Cl3 = A.map (fromCl3_R.mas) initBodies
ss :: SolarSystem <- thaw initBodies
let sz = totalElem.sizeOfMArray $ ss
bs <- readM ss 0
let sunV = fromCl3_V3.vel $ bs
sunM = fromCl3_R.mas $ bs
writeM ss 0 (bs{vel = toCl3_V3 (sunV - ((velocity `dot_V3` mass) / sunM))})
return (ss,sz)
printEnergyϞ :: SolarSystem -> Int -> IO ()
printEnergyϞ ss sz = do
acc :: IORef Cl3 <- newIORef (R 0)
ssFroz <- freeze Seq ss
iforIO_ ssFroz $ \idx bi ->
do let bi_pos :: Cl3 = fromCl3_V3.pos $ bi
bi_vel :: Cl3 = fromCl3_V3.vel $ bi
bi_mas :: Cl3 = fromCl3_R.mas $ bi
ke :: Cl3 = 0.5 * bi_mas * (toR $ bi_vel^(2::Int))
modifyIORef' acc (+ ke) -- accumulate the kenetic energy for each body
-- now calcualate the potential energy
let szMinus1 = sz-1
when (idx < szMinus1) $ do
brest <- extractM (idx+1) (Sz1 (szMinus1-idx)) ssFroz
forIO_ brest $ \bj ->
do let bj_pos :: Cl3 = fromCl3_V3.pos $ bj
bj_mas :: Cl3 = fromCl3_R.mas $ bj
distance :: Cl3 = sqrt.toR $ (bi_pos - bj_pos)^(2::Int)
pe_ij :: Cl3 = (bi_mas * bj_mas) / distance
modifyIORef' acc (\ac -> ac - pe_ij)
energy <- readIORef acc
print energy
advanceϞ :: SolarSystem -> Int -> IO ()
advanceϞ ss sz = do
updateVelocityϞ ss sz
updatePositionϞ ss
updateVelocityϞ :: SolarSystem -> Int -> IO ()
updateVelocityϞ ss sz = do
ssFroz <- freeze Seq ss
iforIO_ ssFroz $ \idx bi ->
when (idx < sz-1) $ do
let bi_pos :: Cl3 = fromCl3_V3.pos $ bi
bi_mas :: Cl3 = fromCl3_R.mas $ bi
brest <- extractM (idx+1) (Sz1 (sz-idx-1)) ssFroz
iforIO_ brest $ \jdx bj ->
do let bj_pos :: Cl3 = fromCl3_V3.pos $ bj
bj_mas :: Cl3 = fromCl3_R.mas $ bj
(absMag,dposUnit) = abssignum $ bi_pos - bj_pos
mag = dt / ((absMag)^(2::Int))
aij = negate $ dposUnit * bj_mas * mag
aji = dposUnit * bi_mas * mag
modify_ ss (modVel aij) idx
modify_ ss (modVel aji) (idx+jdx+1)
modVel :: PrimMonad m => Cl3 -> Body -> m Body
modVel a bod = do
let vbod = fromCl3_V3 $ vel bod
return bod{vel = toCl3_V3 $ vbod + a}
updatePositionϞ :: SolarSystem -> IO ()
updatePositionϞ ss = do
iforPrimM_ ss $ \idx b ->
do let p :: Cl3 = fromCl3_V3.pos $ b
v :: Cl3 = fromCl3_V3.vel $ b
writeM ss idx b{pos = toCl3_V3 (p + dt * v)}
advance :: MArray RealWorld S Ix1 Cl3_V3 -> MArray RealWorld S Ix1 Cl3_V3 -> Array S Ix1 Cl3_R -> MArray RealWorld S Ix1 Cl3_V3 -> MArray RealWorld S Ix2 Cl3_V3 -> IO ()
advance sysPos sysVel sysMass scratchPad scratchPad2D =
do updateVelocity sysPos sysVel sysMass scratchPad scratchPad2D
updatePosition sysPos sysVel
updateVelocity :: MArray RealWorld S Ix1 Cl3_V3 -> MArray RealWorld S Ix1 Cl3_V3 -> Array S Ix1 Cl3_R -> MArray RealWorld S Ix1 Cl3_V3 -> MArray RealWorld S Ix2 Cl3_V3 -> IO ()
updateVelocity sysPos sysVel sysMass scratchPad scratchPad2D =
do -- Calculate the inverse square distance and unit vector between the bodies
frozPos <- freeze Seq sysPos
let ps :: Array D Ix1 Cl3 = A.map fromCl3_V3 frozPos
scaledPij = nChoose2With (\ i j -> let (a,s) = abssignum $ i - j in (recip (a^(2 :: Int))) * s) ps -- probably should use a computeSource here, because scaledPij is used twice, or verify streams are still working.
computeInto scratchPad (A.map toCl3_V3 scaledPij) -- Compute result to the scratch pad
frozScratch <- freeze Seq scratchPad -- Freeze the result for future use
-- Scale the previous inverse square distance by the mass of the other body
let scratch :: Array D Ix1 Cl3 = A.map fromCl3_V3 frozScratch
ms :: Array D Ix1 Cl3 = A.map fromCl3_R sysMass
upper = nChoose2With (\ _ j -> negate j) ms !*! scratch
lower = nChoose2With (\ i _ -> i) ms !*! scratch
computeInto scratchPad (A.map toCl3_V3 upper)
frozScratchUpper <- freeze Seq scratchPad
computeInto scratchPad (A.map toCl3_V3 lower)
frozScratchLower <- freeze Seq scratchPad
-- Load each body acceleration contribution into a 2D array
frozVel <- freeze Seq sysVel
let vs :: Array D Ix1 Cl3 = A.map fromCl3_V3 frozVel
rows = elemsCount vs
iforPrimM_ scratchPad2D (\ix _ -> write_ scratchPad2D ix (genAcc rows frozScratchUpper frozScratchLower ix))
frozScratch2D <- freeze Seq scratchPad2D
-- Sum each row to calcuate the total acceleration and then update the velocity
let accComponents :: Array D Ix2 Cl3 = A.map fromCl3_V3 frozScratch2D
as = makeArray Seq (size vs) (\ ix -> foldlS (+) (V3 0 0 0) (accComponents !> ix))
vs' = vs !+! (dt *. as)
computeInto sysVel (A.map toCl3_V3 vs')
genAcc :: Int -> Array S Ix1 Cl3_V3 -> Array S Ix1 Cl3_V3 -> Ix2 -> Cl3_V3
genAcc rows upper lower (row :. col) | row <= col = upper ! unSplitIdx rows (row,col+1)
| otherwise = lower ! unSplitIdx rows (col,row)
unSplitIdx :: Int -> (Int,Int) -> Int
unSplitIdx c (row,col) = go 0 (c-1) 0 + (col-1 - row)
where
go n c1 acc | n == row = acc
| otherwise = go (n+1) (c1-1) (acc+c1)
updatePosition :: MArray RealWorld S Ix1 Cl3_V3 -> MArray RealWorld S Ix1 Cl3_V3 -> IO ()
updatePosition sysPos sysVel =
do frozPos <- freeze Seq sysPos
frozVel <- freeze Seq sysVel
let ps :: Array D Ix1 Cl3 = A.map fromCl3_V3 frozPos
vs :: Array D Ix1 Cl3 = A.map fromCl3_V3 frozVel
ps' = ps !+! (dt *. vs)
computeInto sysPos (A.map toCl3_V3 ps')
offsetMomentum :: MArray RealWorld S Ix1 Cl3_V3 -> Array S Ix1 Cl3_R -> IO ()
offsetMomentum sysVel sysMass =
do frozVel <- freeze Seq sysVel
planetsvs :: Array D Ix1 Cl3 <- extractM 1 4 (A.map fromCl3_V3 frozVel) -- select just the planets and not the sun
planetms :: Array D Ix1 Cl3 <- extractM 1 4 (A.map fromCl3_R sysMass) -- select just the planets and not the sun
(fromCl3_R -> sunMassCl3) <- sysMass !? 0
writeM sysVel 0 (toCl3_V3 $ negate (planetsvs `dot_V3` planetms) / sunMassCl3)
-- dot_V3 is to work around the typical sum in the dot product that has an aculumulator of a Real 0 value
dot_V3 :: Array D Ix1 Cl3 -> Array D Ix1 Cl3 -> Cl3
dot_V3 v1 v2 = foldlS (+) (V3 0 0 0) (A.zipWith (*) v1 v2)
printEnergy :: MArray RealWorld S Ix1 Cl3_V3 -> MArray RealWorld S Ix1 Cl3_V3 -> Array S Ix1 Cl3_R -> IO ()
printEnergy sysPos sysVel sysMass =
do frozPos <- freeze Seq sysPos
frozVel <- freeze Seq sysVel
let ps :: Array D Ix1 Cl3 = A.map fromCl3_V3 frozPos
vs :: Array D Ix1 Cl3 = A.map fromCl3_V3 frozVel
ms :: Array D Ix1 Cl3 = A.map fromCl3_R sysMass
ke = 0.5 * (ms !.! A.map toR (vs.^2)) -- Kenetic Energy O(n)
pe = negate.A.sum $ nChoose2With (*) ms !/! nChoose2With (\ x y -> abs $ x - y) ps -- Gravitational Potential Energy O((n^2 - n)/2)
print $ ke + pe -- print the total energy
-- This function is a bit of a combinatorial explosion so it is marked to be evaluated in Seqallel, probably should convert to a sgenerate :: Sz1 -> (Ix1 -> e) -> Vector DS e
nChoose2With :: (Cl3 -> Cl3 -> Cl3) -> Array D Ix1 Cl3 -> Array D Ix1 Cl3
nChoose2With f bs =
let (Sz1 c) = size bs
c' = (c^(2 :: Int) - c) `div` 2
bs' :: Array BN Ix1 Cl3 = computeSource bs
in makeArray Seq (Sz1 c') (genElements f bs' c)
genElements :: (Cl3 -> Cl3 -> Cl3) -> Array BN Ix1 Cl3 -> Int -> Ix1 -> Cl3
genElements f bs c idx =
let (ix1,ix2) = splitIdx c idx
in f (bs ! ix1) (bs ! ix2)
splitIdx :: Int -> Int -> (Int,Int)
splitIdx c = go (0,1) (c-2)
where
go (row,col) x ix | ix - x <= 0 = (row, col + ix)
| otherwise = go (row+1,col+1) (x-1) (ix-x-1)
planetspos :: [Cl3_V3]
planetspos = pos <$> [sun,jupiter,saturn,uranus,neptune]
planetsvel :: [Cl3_V3]
planetsvel = vel <$> [sun,jupiter,saturn,uranus,neptune]
planetsmass :: [Cl3_R]
planetsmass = mas <$> [sun,jupiter,saturn,uranus,neptune]
type SolarSystem = MArray RealWorld S Ix1 Body
data Body = Body
{pos :: Cl3_V3
,vel :: Cl3_V3
,mas :: Cl3_R}
instance Storable Body where
sizeOf _ = sizeOf (undefined :: Cl3_V3) + sizeOf (undefined :: Cl3_V3) + sizeOf (undefined :: Cl3_R)
alignment _ = sizeOf (undefined :: Double) -- alignment must be a 2^x
peekElemOff ptr idx = peek (castPtr ptr `plusPtr` (idx * sizeOf (undefined :: Body)))
pokeElemOff ptr idx e = poke (castPtr ptr `plusPtr` (idx * sizeOf (undefined :: Body))) e
peek :: Ptr Body -> IO Body
peek ptr = do
p :: Cl3_V3 <- peek (castPtr ptr `plusPtr` 0)
v :: Cl3_V3 <- peek (castPtr ptr `plusPtr` (sizeOf p))
m :: Cl3_R <- peek (castPtr ptr `plusPtr` (sizeOf p) `plusPtr` (sizeOf v))
return (Body p v m)
poke :: Ptr Body -> Body -> IO ()
poke ptr (Body p v m) = do
poke (castPtr ptr `plusPtr` 0) p
poke (castPtr ptr `plusPtr` (sizeOf p)) v
poke (castPtr ptr `plusPtr` (sizeOf p) `plusPtr` (sizeOf v)) m
sun :: Body
sun = Body (toCl3_V3 $ V3 0 0 0) -- Sun Position
(toCl3_V3 $ V3 0 0 0) -- Sun Velocity
(toCl3_R $ R solarMass) -- Sun Mass
jupiter :: Body
jupiter = Body (toCl3_V3 $ V3 (4.84143144246472090e+00) (-1.16032004402742839e+00) (-1.03622044471123109e-01))
(toCl3_V3 $ V3 (1.66007664274403694e-03*dp) (7.69901118419740425e-03*dp) (-6.90460016972063023e-05*dp))
(toCl3_R $ R (9.54791938424326609e-04 * solarMass))
saturn :: Body
saturn = Body (toCl3_V3 $ V3 (8.34336671824457987e+00) (4.12479856412430479e+00) (-4.03523417114321381e-01))
(toCl3_V3 $ V3 (-2.76742510726862411e-03*dp) (4.99852801234917238e-03*dp) (2.30417297573763929e-05*dp))
(toCl3_R $ R (2.85885980666130812e-04 * solarMass))
uranus :: Body
uranus = Body (toCl3_V3 $ V3 (1.28943695621391310e+01) (-1.51111514016986312e+01) (-2.23307578892655734e-01))
(toCl3_V3 $ V3 (2.96460137564761618e-03*dp) (2.37847173959480950e-03*dp) (-2.96589568540237556e-05*dp))
(toCl3_R $ R (4.36624404335156298e-05 * solarMass))
neptune :: Body
neptune = Body (toCl3_V3 $ V3 (1.53796971148509165e+01) (-2.59193146099879641e+01) (1.79258772950371181e-01))
(toCl3_V3 $ V3 (2.68067772490389322e-03*dp) (1.62824170038242295e-03*dp) (-9.51592254519715870e-05*dp))
(toCl3_R $ R (5.15138902046611451e-05 * solarMass))
daysPerYear :: Double
daysPerYear = 365.24
solarMass :: Double
solarMass = 4 * pi^(2 :: Int)
dp :: Double
dp = daysPerYear
dt :: Cl3
dt = R 0.01