packages feed

sde-solver-0.1.0.0: src/Numeric/DSDE/Distribute.hs

{-# LANGUAGE MultiParamTypeClasses, ConstraintKinds, BangPatterns,
             ExistentialQuantification, FlexibleInstances, DeriveGeneric,
             DefaultSignatures, ScopedTypeVariables, TupleSections,
             FlexibleContexts #-}

module Numeric.DSDE.Distribute where

import Control.Applicative ((<$>))
import Control.Concurrent
import Control.Concurrent.MVar
import Control.Monad.Identity hiding (mapM)
import Control.Monad.State
import Control.Parallel
import Control.Parallel.MPI.Simple 
import Data.Foldable (fold, foldl')
import Data.Monoid
import Data.Serialize (Serialize(..))
import qualified Data.Vector.Unboxed as V
import Data.Vector.Serialize
import GHC.Generics (Generic)
import Numeric.DSDE.RNG
import Numeric.DSDE.SDE
import Numeric.DSDE.SDESolver
import Prelude hiding (sum, init, map)

-- | Wrapper used by all distributors supplied to the 'evaluate' function.
data DistributeInstance m = forall a. Distribute a m => Distr a

-- | Container describing the result produced by an SDE solution.
data SDEResult = Scalar !Double !Int             -- ^ Average of all end-point values, with the number of samples recorded.
               | Distribution !(V.Vector Double) -- ^ All end-point samples stored as an unboxed vector.
  deriving (Generic, Show)

-- | Monoid instance used when folding results from multiple sources.
instance Monoid SDEResult where
  mempty = Scalar 0 0
  Scalar a n         `mappend` Scalar b n'        =
    Scalar (elemSum / fromIntegral s) s
    where
    s = n + n'
    elemSum = a * fromIntegral n + b * fromIntegral n'

  Distribution v     `mappend` Distribution v'    = Distribution $ v V.++ v'
  Scalar _ _         `mappend` d@(Distribution _) = d
  d@(Distribution _) `mappend` Scalar _ _         = d

-- | Serialize instance used by MPI.
instance Serialize SDEResult

-- | Internal abstraction over the choice of specifying either the interval length or the number of steps.
data Accuracy = End Double
              | Steps Int
  deriving (Generic, Show)

-- | Serialize instance used by MPI.
instance Serialize Accuracy

-- | Set of parameters supplied to solve an SDE problem.
data InstanceParams = IP {
  accuracy :: !Accuracy,
  start :: !Double,
  deltat :: !Double,
  simulations :: !Int }
  deriving (Generic, Show)

-- | Serialize instance used by MPI.
instance Serialize InstanceParams

-- | MPI cluster distributor.
data MPI = MPI

-- | Local evaluation using GHC threads
data Local = Local Int

type SDEConstraint b c g m p = (SDE b, SDESolver c, Parameter p, RNGGen g m p)
type SDEInstance b c g m p = (b p, c, Maybe Int -> m g, InstanceParams)

-- | Type class indicating the ability to distribute data in some way.
--   Several distributors may be chained.
class Monad m => Distribute a m where
  -- | Inject an SDE instance into the context.
  inject :: SDEConstraint b c g m p =>
    a -> SDEInstance b c g m p -> m (SDEInstance b c g m p)
  -- | Remove an SDE result from the context.
  remove :: a -> SDEResult -> m SDEResult

-- | Type class indicating ability to solve an SDE problem.
class Execute a m p where
  execute :: SDEConstraint b c g m p =>  a -> SDEInstance b c g m p -> m SDEResult

-- | Type class indicating ability to perform a set of actions in an efficient way.
class Mappable m p where
  map' :: RNGGen g m p => (Int, Maybe Int -> m g) -> (g -> m b) -> [a] -> m [b] 

-- | Mappable instance for the IO monad. Work is divided using forkIO.
instance Mappable IO Double where
  map' (seed, rng) f l = do
    rand <- rng (Just seed)
    seeds <- mapM (\_ -> (Just . round <$> getRand rand) >>= rng) l
    mapM splitWork seeds >>= mapM takeMVar
    where
    splitWork rand = do
      var <- newEmptyMVar
      forkIO $ f rand >>= putMVar var
      return var

-- | Mappable instance for the pure State monad, uses 'par' annotations.
--   This does not perform well in general and needs to be optimized to
--   compete with the monadic IO instance.
instance RealFrac a => Mappable (State s) a where
  map' (seed, rng) f l = (go f seed l >>= sequence)
    where
    go f _ [] = return []
    go f s (_:t) = do
      worker <- rng (Just s)
      s' <- round <$> getRand worker
      rest <- go f s' t
      return $ f worker `par` f worker : rest

-- | Distribute instance over MPI which defines data transportation.
instance Distribute MPI IO where
  inject _ (sde, solver, rng, params) = do
    init
    size <- commSize commWorld
    rank <- commRank commWorld
    (sde, solver, rng,) <$> case rank of
      0 -> do
        let slaveSize = ceiling $ (fromIntegral $ simulations params :: Double) /
                                   fromIntegral size
        let slave = params { simulations = slaveSize}
        bcastSend commWorld 0 slave
        return slave

      _ -> bcastRecv commWorld 0

  remove _ localResult = do
    result <- commRank commWorld >>= retrieve
    finalize
    return result
    where
    retrieve 0 = do
      clusterResult <- gatherRecv commWorld 0 localResult
      return $ fold clusterResult
    retrieve _ =
      gatherSend commWorld 0 localResult >> return localResult

-- | Generic execute instances over any mappable monad 'm'.
instance Mappable m Double => Execute Local m Double where
  execute (Local cores) (!sde, !solver, !rng, params) = do
    seedRNG <- round <$> (rng Nothing >>= getRand)
    fold <$> map' (seedRNG, rng) runThread [1..cores]
    where
    perThread = ceiling $ (fromIntegral $ simulations params :: Double) /
                           fromIntegral cores :: Int
    steps = case accuracy params of
      End endTime -> floor $ endTime / deltat params
      Steps n -> n

    runThread rng = Distribution <$> thread rng
    thread rand = V.mapM (const $ threadEvaluation rand) $ V.replicate perThread (0.0 :: Double)
    threadEvaluation rand = foldM' (eval rand) (start params) [1..steps]
    eval rand w_i step = w_iplus1 solver sde rand (fromIntegral step * deltat params) w_i (deltat params)

-- | Evaluate the SDE using the supplied distributors and execution method.
evaluate :: (Monad m, SDEConstraint b c g m p, Execute e m p) =>
  ([DistributeInstance m], e) -> SDEInstance b c g m p -> m SDEResult
evaluate ([], method) input = execute method input
evaluate (Distr method : other, final) input =
  inject method input >>= evaluate (other, final) >>= remove method

-- | Monadic strict fold.
foldM' :: Monad m => (a -> b -> m a) -> a -> [b] -> m a
foldM' _ z [] = return z
foldM' func z (x:xs) = do
  z' <- func z x
  z' `seq` foldM' func z' xs