mpi-hs-0.3.0.0: test/mpi/Main.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
import Control.Concurrent
import Control.Exception
import Control.Monad
import Control.Monad.Loops
import Data.IORef
import Foreign
import Foreign.C.Types
import System.Exit
import System.IO
-- import Test.Tasty
-- import Test.Tasty.HUnit
import qualified Control.Distributed.MPI as MPI
--------------------------------------------------------------------------------
infix 1 @?
(@?) :: Bool -> String -> IO ()
x @? msg = if not x then die msg else return ()
infix 1 @?=
(@?=) :: Eq a => a -> a -> IO ()
x @?= y = x == y @? "test failed"
type TestTree = IO ()
testCase :: String -> IO () -> TestTree
testCase name test =
do rank <- MPI.commRank MPI.commWorld
if rank == 0
then do putStrLn $ " " ++ name ++ "..."
hFlush stdout
else return ()
MPI.barrier MPI.commWorld
test
MPI.barrier MPI.commWorld
testGroup :: String -> [TestTree] -> TestTree
testGroup name cases =
do rank <- MPI.commRank MPI.commWorld
if rank == 0
then do putStrLn $ name ++ ":"
hFlush stdout
else return ()
sequence_ cases
defaultMain :: TestTree -> IO ()
defaultMain tree =
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
if rank == 0
then do putStrLn $ "MPI Tests: running on " ++ show size ++ " processes"
hFlush stdout
else return ()
tree
--------------------------------------------------------------------------------
main :: IO ()
main = bracket
(do _ <- MPI.initThread MPI.ThreadMultiple
return ())
(\_ -> MPI.finalize)
(\_ -> defaultMain tests)
tests :: TestTree
tests = testGroup "MPI"
[ initialized
, rankSize
, pointToPoint
, pointToPointNonBlocking
, collective
, collectiveNonBlocking
, reductions
, dynamic
]
initialized :: TestTree
initialized = testGroup "initialized"
[ testCase "initialized" $
do isInit <- MPI.initialized
isInit @?= True
, testCase "finalized" $
do isFini <- MPI.finalized
isFini @?= False
]
rankSize :: TestTree
rankSize = testGroup "rank and size"
[ testCase "commSelf" $
do rank <- MPI.commRank MPI.commSelf
size <- MPI.commSize MPI.commSelf
rank == 0 && size == 1 @? ""
, testCase "commWorld" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
rank >= 0 && rank < size @? ""
]
pointToPoint :: TestTree
pointToPoint = testGroup "point-to-point"
[ testCase "send and recv" $
do rank <- MPI.commRank MPI.commWorld
let msg = 42
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
MPI.send (buf, 1::Int) rank MPI.unitTag MPI.commWorld
buf' <- mallocForeignPtr @CInt
st <- MPI.recv (buf', 1::Int) rank MPI.unitTag MPI.commWorld
msg' <- withForeignPtr buf' peek
source <- MPI.getSource st
tag <- MPI.getTag st
count <- MPI.getCount st MPI.datatypeInt
(msg' == msg && source == rank && tag == MPI.unitTag && count == 1) @? ""
, testCase "sendrecv" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let msg = 42 + MPI.fromRank rank
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
st <- MPI.sendrecv
(buf, 1::Int) ((rank + 1) `mod` size) MPI.unitTag
(buf', 1::Int) ((rank - 1) `mod` size) MPI.unitTag
MPI.commWorld
msg' <- withForeignPtr buf' peek
source <- MPI.getSource st
tag <- MPI.getTag st
count <- MPI.getCount st MPI.datatypeInt
(msg' == 42 + MPI.fromRank ((rank - 1) `mod` size) &&
source == (rank - 1) `mod` size &&
tag == MPI.unitTag &&
count == 1) @? ""
]
pointToPointNonBlocking :: TestTree
pointToPointNonBlocking = testGroup "point-to-point non-blocking"
[ testCase "send and recv" $
do rank <- MPI.commRank MPI.commWorld
let msg = 42
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
req <- MPI.isend (buf, 1::Int) rank MPI.unitTag MPI.commWorld
buf' <- mallocForeignPtr @CInt
req' <- MPI.irecv (buf', 1::Int) rank MPI.unitTag MPI.commWorld
MPI.wait_ req
st <- MPI.wait req'
touchForeignPtr buf
msg' <- withForeignPtr buf' peek
source <- MPI.getSource st
tag <- MPI.getTag st
count <- MPI.getCount st MPI.datatypeInt
(msg' == msg && source == rank && tag == MPI.unitTag && count == 1) @? ""
]
collective :: TestTree
collective = testGroup "collective"
[ testCase "allgather" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtrArray @CInt sz
MPI.allgather (buf, 1::Int) (buf', 1::Int) MPI.commWorld
msgs' <- withForeignPtr buf' (peekArray sz)
msgs' == [42 .. 42 + fromIntegral (sz-1)] @? ""
, testCase "allreduce" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
MPI.allreduce (buf, 1::Int) (buf', 1::Int) MPI.opSum MPI.commWorld
msg' <- withForeignPtr buf' peek
msg' == sum [42 .. 42 + (sz-1)] @? ""
, testCase "alltoall" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let msgs = fromIntegral <$> [42 + sz * rk + i | i <- [0 .. sz-1]]
buf <- mallocForeignPtrArray @CInt sz
withForeignPtr buf $ \ptr -> pokeArray ptr msgs
buf' <- mallocForeignPtrArray @CInt sz
MPI.alltoall (buf, 1::Int) (buf', 1::Int) MPI.commWorld
msgs' <- withForeignPtr buf' (peekArray sz)
msgs' == (fromIntegral <$> [42 + sz * i + rk | i <- [0 .. sz-1]]) @? ""
, testCase "barrier" $
MPI.barrier MPI.commWorld
, testCase "bcast" $
do rank <- MPI.commRank MPI.commWorld
let rk = MPI.fromRank rank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
MPI.bcast (buf, 1::Int) MPI.rootRank MPI.commWorld
msg' <- withForeignPtr buf peek
msg' == 42 @? ""
, testCase "exscan" $
do rank <- MPI.commRank MPI.commWorld
let rk = MPI.fromRank rank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
MPI.exscan (buf, 1::Int) (buf', 1::Int) MPI.opSum MPI.commWorld
msg' <- withForeignPtr buf' (if rank == 0 then \_ -> return 0 else peek)
msg' == sum [42 .. 42 + rk-1] @? ""
, testCase "gather" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let isroot = rank == MPI.rootRank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtrArray @CInt (if isroot then sz else 0)
MPI.gather (buf, 1::Int) (buf', 1::Int) MPI.rootRank MPI.commWorld
msgs' <- withForeignPtr buf' $ peekArray (if isroot then sz else 0)
(if isroot then msgs' == [42 .. 42 + fromIntegral sz-1] else True) @? ""
, testCase "reduce" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let isroot = rank == MPI.rootRank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
MPI.reduce (buf, 1::Int) (buf', 1::Int) MPI.opSum MPI.rootRank
MPI.commWorld
msg' <- withForeignPtr buf' $ if isroot then peek else \_ -> return 0
(if isroot then msg' == sum [42 .. 42 + sz-1] else True) @? ""
, testCase "scan" $
do rank <- MPI.commRank MPI.commWorld
let rk = MPI.fromRank rank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
MPI.scan (buf, 1::Int) (buf', 1::Int) MPI.opSum MPI.commWorld
msg' <- withForeignPtr buf' peek
msg' == sum [42 .. 42 + rk] @? ""
, testCase "scatter" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let isroot = rank == MPI.rootRank
let msgs =
if isroot then [42 + fromIntegral i | i <- [0 .. sz-1]] else []
buf <- mallocForeignPtrArray @CInt (if isroot then sz else 0)
withForeignPtr buf $
\ptr -> if isroot then pokeArray ptr msgs else return ()
buf' <- mallocForeignPtr @CInt
MPI.scatter (buf, 1::Int) (buf', 1::Int) MPI.rootRank MPI.commWorld
msg' <- withForeignPtr buf' peek
msg' == 42 + rk @? ""
]
reductions :: TestTree
reductions = testGroup "reduction operations"
[ testCase "max" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let isroot = rank == MPI.rootRank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
MPI.reduce (buf, 1::Int) (buf', 1::Int) MPI.opMax MPI.rootRank
MPI.commWorld
msg' <- withForeignPtr buf' $ if isroot then peek else \_ -> return 0
(if isroot then msg' == maximum [42 .. 42 + sz-1] else True) @? ""
, testCase "min" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let isroot = rank == MPI.rootRank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
MPI.reduce (buf, 1::Int) (buf', 1::Int) MPI.opMin MPI.rootRank
MPI.commWorld
msg' <- withForeignPtr buf' $ if isroot then peek else \_ -> return 0
(if isroot then msg' == minimum [42 .. 42 + sz-1] else True) @? ""
, testCase "sum" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let isroot = rank == MPI.rootRank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
MPI.reduce (buf, 1::Int) (buf', 1::Int) MPI.opSum MPI.rootRank
MPI.commWorld
msg' <- withForeignPtr buf' $ if isroot then peek else \_ -> return 0
(if isroot then msg' == sum [42 .. 42 + sz-1] else True) @? ""
]
collectiveNonBlocking :: TestTree
collectiveNonBlocking = testGroup "collective non-blocking"
[ testCase "iallgather" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtrArray @CInt sz
req <- MPI.iallgather (buf, 1::Int) (buf', 1::Int) MPI.commWorld
MPI.wait_ req
touchForeignPtr buf
msgs' <- withForeignPtr buf' (peekArray sz)
msgs' == [42 .. 42 + fromIntegral (sz-1)] @? ""
, testCase "iallreduce" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
req <- MPI.iallreduce (buf, 1::Int) (buf', 1::Int) MPI.opSum
MPI.commWorld
MPI.wait_ req
touchForeignPtr buf
msg' <- withForeignPtr buf' peek
msg' == sum [42 .. 42 + (sz-1)] @? ""
, testCase "ialltoall" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let msgs = fromIntegral <$> [42 + sz * rk + i | i <- [0 .. sz-1]]
buf <- mallocForeignPtrArray @CInt sz
withForeignPtr buf $ \ptr -> pokeArray ptr msgs
buf' <- mallocForeignPtrArray @CInt sz
req <- MPI.ialltoall (buf, 1::Int) (buf', 1::Int) MPI.commWorld
MPI.wait_ req
touchForeignPtr buf
msgs' <- withForeignPtr buf' (peekArray sz)
msgs' == (fromIntegral <$> [42 + sz * i + rk | i <- [0 .. sz-1]]) @? ""
, testCase "ibarrier" $
do req <- MPI.ibarrier MPI.commWorld
MPI.wait_ req
, testCase "ibcast" $
do rank <- MPI.commRank MPI.commWorld
let rk = MPI.fromRank rank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
req <- MPI.ibcast (buf, 1::Int) MPI.rootRank MPI.commWorld
MPI.wait_ req
touchForeignPtr buf
msg' <- withForeignPtr buf peek
msg' == 42 @? ""
, testCase "iexscan" $
do rank <- MPI.commRank MPI.commWorld
let rk = MPI.fromRank rank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
req <- MPI.iexscan (buf, 1::Int) (buf', 1::Int) MPI.opSum MPI.commWorld
MPI.wait_ req
touchForeignPtr buf
msg' <- withForeignPtr buf' (if rank == 0 then \_ -> return 0 else peek)
msg' == sum [42 .. 42 + rk-1] @? ""
, testCase "igather" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let isroot = rank == MPI.rootRank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtrArray @CInt (if isroot then sz else 0)
req <- MPI.igather (buf, 1::Int) (buf', 1::Int) MPI.rootRank
MPI.commWorld
MPI.wait_ req
touchForeignPtr buf
msgs' <- withForeignPtr buf' $ peekArray (if isroot then sz else 0)
(if isroot then msgs' == [42 .. 42 + fromIntegral sz-1] else True) @? ""
, testCase "ireduce" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let isroot = rank == MPI.rootRank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
req <- MPI.ireduce (buf, 1::Int) (buf', 1::Int) MPI.opSum MPI.rootRank
MPI.commWorld
MPI.wait_ req
touchForeignPtr buf
msg' <- withForeignPtr buf' $ if isroot then peek else \_ -> return 0
(if isroot then msg' == sum [42 .. 42 + sz-1] else True) @? ""
, testCase "iscan" $
do rank <- MPI.commRank MPI.commWorld
let rk = MPI.fromRank rank
let msg = 42 + rk
buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr msg
buf' <- mallocForeignPtr @CInt
req <- MPI.iscan (buf, 1::Int) (buf', 1::Int) MPI.opSum MPI.commWorld
MPI.wait_ req
touchForeignPtr buf
msg' <- withForeignPtr buf' peek
msg' == sum [42 .. 42 + rk] @? ""
, testCase "iscatter" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
let rk = MPI.fromRank rank
let sz = MPI.fromRank size
let isroot = rank == MPI.rootRank
let msgs = [42 + fromIntegral i | i <- [0 .. sz-1]]
buf <- mallocForeignPtrArray @CInt (if isroot then sz else 0)
withForeignPtr buf $ \ptr -> pokeArray ptr msgs
buf' <- mallocForeignPtr @CInt
req <- MPI.iscatter (buf, 1::Int) (buf', 1::Int) MPI.rootRank
MPI.commWorld
MPI.wait_ req
touchForeignPtr buf
msg' <- withForeignPtr buf' peek
msg' == 42 + rk @? ""
]
dynamic :: TestTree
dynamic = testGroup "dynamic"
[ testCase "sequential" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
breq <- newIORef Nothing
let signalDone =
do r <- MPI.ibarrier MPI.commWorld
writeIORef breq (Just r)
let checkDone =
do mreq <- readIORef breq
case mreq of
Nothing -> return False
Just req -> MPI.test_ req
sendreqs <- newIORef []
let sendMsg dst =
when (dst < size) $
do buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr 42
r <- MPI.isend (buf, 1::Int) dst MPI.unitTag MPI.commWorld
modifyIORef' sendreqs ((buf, r) :)
let drainSendQueue =
do srs <- readIORef sendreqs
srs' <- filterM (\(_, r) -> not <$> MPI.test_ r) srs
writeIORef sendreqs srs'
let checkForMsg = MPI.iprobe MPI.anySource MPI.unitTag MPI.commWorld
let recvMsg st =
do src <- MPI.getSource st
buf <- mallocForeignPtr @CInt
MPI.recv_ (buf, 1::Int) src MPI.unitTag MPI.commWorld
-- each rank sends to the next
when (rank == 0) $
do sendMsg (rank + 1)
signalDone
untilM_
(do drainSendQueue
mst <- checkForMsg
case mst of
Nothing -> return ()
Just st -> do recvMsg st
sendMsg (rank + 1)
signalDone
)
checkDone
, testCase "tree" $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
breq <- newIORef Nothing
let signalDone =
do r <- MPI.ibarrier MPI.commWorld
writeIORef breq (Just r)
let checkDone =
do mreq <- readIORef breq
case mreq of
Nothing -> return False
Just req -> MPI.test_ req
sendreqs <- newIORef []
let sendMsg dst =
when (dst < size) $
do buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr 42
r <- MPI.isend (buf, 1::Int) dst MPI.unitTag MPI.commWorld
modifyIORef' sendreqs ((buf, r) :)
let drainSendQueue =
do srs <- readIORef sendreqs
srs' <- filterM (\(_, r) -> not <$> MPI.test_ r) srs
writeIORef sendreqs srs'
let checkForMsg = MPI.iprobe MPI.anySource MPI.unitTag MPI.commWorld
let recvMsg st =
do src <- MPI.getSource st
buf <- mallocForeignPtr @CInt
MPI.recv_ (buf, 1::Int) src MPI.unitTag MPI.commWorld
-- rank r sends to 2*r+1 and 2*r+2
when (rank == 0) $
do sendMsg (2 * rank + 1)
sendMsg (2 * rank + 2)
signalDone
untilM_
(do drainSendQueue
mst <- checkForMsg
case mst of
Nothing -> return ()
Just st -> do recvMsg st
sendMsg (2 * rank + 1)
sendMsg (2 * rank + 2)
signalDone
)
checkDone
, testCase "multi-threaded" $
do mts <- MPI.threadSupport
let Just ts = mts
when (ts >= MPI.ThreadMultiple) $
do rank <- MPI.commRank MPI.commWorld
size <- MPI.commSize MPI.commWorld
breq <- newEmptyMVar
let signalDone =
do _ <- forkIO $
do req <- MPI.ibarrier MPI.commWorld
whileM_ (not <$> MPI.test_ req) yield
putMVar breq ()
return ()
let checkDone = not <$> isEmptyMVar breq
let sendMsg dst =
when (dst < size) $
do _ <- forkIO $
do buf <- mallocForeignPtr @CInt
withForeignPtr buf $ \ptr -> poke ptr 42
req <- MPI.isend (buf, 1::Int) dst MPI.unitTag
MPI.commWorld
whileM_ (not <$> MPI.test_ req) yield
return ()
let checkForMsg = MPI.iprobe MPI.anySource MPI.unitTag MPI.commWorld
let recvMsg st =
do src <- MPI.getSource st
buf <- mallocForeignPtr @CInt
MPI.recv_ (buf, 1::Int) src MPI.unitTag MPI.commWorld
-- rank r sends to 2*r+1 and 2*r+2
when (rank == 0) $
do sendMsg (2 * rank + 1)
sendMsg (2 * rank + 2)
signalDone
untilM_
(do mst <- checkForMsg
case mst of
Nothing -> return ()
Just st -> do recvMsg st
sendMsg (2 * rank + 1)
sendMsg (2 * rank + 2)
signalDone
yield
)
checkDone
]