remote-0.1: examples/kmeans/KMeans.hs
{-# LANGUAGE TemplateHaskell,DeriveDataTypeable,BangPatterns #-}
module Main where
import Remote
import Remote.Process (roundtripResponse,setRemoteNodeLogConfig,getConfig,PayloadDisposition(..),roundtripQuery,roundtripQueryMulti)
import KMeansCommon
import Control.Exception (try,SomeException,evaluate)
import Control.Monad (liftM)
import Control.Monad.Trans (liftIO)
import System.Random (randomR,getStdRandom)
import Data.Typeable (Typeable)
import Data.Data (Data)
import Control.Exception (IOException)
import Data.Binary (Binary,get,put,encode,decode)
import Data.Maybe (fromJust)
import Data.List (minimumBy,sortBy)
import Data.Time
import Data.Either (rights)
import qualified Data.ByteString.Lazy as B
import qualified Data.Map as Map
import System.IO
import Debug.Trace
split :: Int -> [a] -> [[a]]
split numChunks l = splitSize (ceiling $ fromIntegral (length l) / fromIntegral numChunks) l
where
splitSize i v = let (first,second) = splitAt i v
in first : splitSize i second
broadcast :: (Serializable a) => [ProcessId] -> a -> ProcessM ()
broadcast pids dat = mapM_ (\pid -> send pid dat) pids
multiSpawn :: [NodeId] -> Closure (ProcessM ()) -> ProcessM [ProcessId]
multiSpawn nodes f = mapM (\node -> spawnLink node f) nodes
where s n = do mypid <- getSelfNode
setRemoteNodeLogConfig n (LogConfig LoTrivial (LtForward mypid) LfAll)
spawnLink n f
mapperProcess :: ProcessM ()
mapperProcess =
let mapProcess :: (Maybe [Vector],Maybe [ProcessId],Map.Map Int (Int,Vector)) -> ProcessM ()
mapProcess (mvecs,mreducers,mresult) =
receiveWait
[
match (\vec -> do vecs<-liftIO $ readf vec
say $ "Mapper read data file"
return (Just vecs,mreducers,mresult)),
match (\reducers -> return (mvecs,Just reducers,mresult)),
roundtripResponse (\() -> return (mresult,(mvecs,mreducers,mresult))),
roundtripResponse
(\clusters -> let tbl = analyze (fromJust mvecs) clustersandcenters Map.empty
clustersandcenters = map (\x -> (x,clusterCenter x)) clusters
reducers = fromJust mreducers
target clust = reducers !! (clust `mod` length reducers)
sendout (clustid,(count,sum)) = send (target clustid) Cluster {clId = clustid,clCount=count, clSum=sum}
in do say $ "calculating: "++show (length reducers)++" reducers"
mapM_ sendout (Map.toList tbl)
return ((),(mvecs,mreducers,tbl))),
matchUnknownThrow
] >>= mapProcess
getit :: Handle -> IO [Vector]
getit h = do l <- liftM lines $ hGetContents h
return (map read l) -- evaluate or return?
readf fn = do h <- openFile fn ReadMode
getit h
condtrace cond s val = if cond
then trace s val
else val
analyze :: [Vector] -> [(Cluster,Vector)] -> Map.Map Int (Int,Vector) -> Map.Map Int (Int,Vector)
analyze [] _ ht = ht
analyze (v:vectors) clusters ht =
let theclust = assignToCluster clusters v
newh = ht `seq` theclust `seq` Map.insertWith' (\(a,v1) (b,v2) -> let av = addVector v1 v2 in av `seq` (a+b,av) ) theclust (1,v) ht
-- condtrace (blarg `mod` 1000 == 0) (show blarg) $
in newh `seq` analyze vectors clusters newh
assignToCluster :: [(Cluster,Vector)] -> Vector -> Int
assignToCluster clusters vector =
let distances = map (\(x,center) -> (clId x,sqDistance center vector)) clusters
in fst $ minimumBy (\(_,a) (_,b) -> compare a b) distances
doit = mapProcess (Nothing,Nothing,Map.empty)
in doit >> return ()
reducerProcess :: ProcessM ()
reducerProcess = let reduceProcess :: ([Cluster],[Cluster]) -> ProcessM ()
reduceProcess (oldclusters,clusters) =
receiveWait [
roundtripResponse (\() -> return (clusters,(clusters,[]))),
match (\x -> return (oldclusters,combineClusters clusters x)),
matchUnknownThrow] >>= reduceProcess
combineClusters :: [Cluster] -> Cluster -> [Cluster]
combineClusters [] a = [a]
combineClusters (fstclst:rest) clust | clId fstclst == clId clust = (Cluster {clId = clId fstclst,
clCount = clCount fstclst + clCount clust,
clSum = addVector (clSum fstclst) (clSum clust)}):rest
combineClusters (fstclst:res) clust = fstclst:(combineClusters res clust)
in reduceProcess ([],[]) >> return ()
$( remotable ['mapperProcess, 'reducerProcess] )
initialProcess "MASTER" =
do peers <- getPeers
-- say $ "Got peers: " ++ show peers
cfg <- getConfig
let mappers = findPeerByRole peers "MAPPER"
let reducers = findPeerByRole peers "REDUCER"
let numreducers = length reducers
let nummappers = length mappers
say $ "Got " ++ show nummappers ++ " mappers and " ++ show numreducers ++ " reducers"
clusters <- liftIO $ getClusters "kmeans-clusters"
say $ "Got "++show (length clusters)++" clusters"
mypid <- getSelfPid
mapperPids <- multiSpawn mappers mapperProcess__closure
reducerPids <- multiSpawn reducers reducerProcess__closure
broadcast mapperPids reducerPids
mapM_ (\(pid,chunk) -> send pid chunk) (zip (mapperPids) (repeat "kmeans-points"))
say "Starting iteration"
starttime <- liftIO $ getCurrentTime
let loop howmany clusters = do
liftIO $ putStrLn $ show howmany
roundtripQueryMulti PldUser mapperPids clusters :: ProcessM [Either TransmitStatus ()]
res <- roundtripQueryMulti PldUser reducerPids () :: ProcessM [Either TransmitStatus [Cluster]]
let newclusters = rights res
let newclusters2 = (sortBy (\a b -> compare (clId a) (clId b)) (concat newclusters))
if newclusters2 == clusters || howmany >= 4
then do
donetime <- liftIO $ getCurrentTime
say $ "Converged in " ++ show howmany ++ " iterations and " ++ (show $ diffUTCTime donetime starttime)
pointmaps <- mapM (\pid -> do (Right m) <- roundtripQuery PldUser pid ()
return (m::Map.Map Int (Int,Vector))) mapperPids
let pointmap = map (\x -> sum $ map fst (Map.elems x)) pointmaps
say $ "Total points: " ++ (show $ sum pointmap)
-- liftIO $ writeFile "kmeans-converged" $ readableShow (Map.toList pointmap)
--respoints <- roundtripQueryAsync PldUser mapperPids () :: ProcessM [Either TransmitStatus (Map.Map Int [Vector])]
--liftIO $ B.writeFile "kmeans-converged" $ encode $ Map.toList $ Map.unionsWith (++) (rights respoints)
else
loop (howmany+1) newclusters2
loop 0 clusters
initialProcess "MAPPER" = receiveWait []
initialProcess "REDUCER" = receiveWait []
initialProcess _ = error "Role must be MAPPER or REDUCER or MASTER"
main = remoteInit (Just "config") [Main.__remoteCallMetaData] initialProcess