{-# LANGUAGE CPP, ScopedTypeVariables #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Control.Parallel.Eden.Map
-- Copyright   :  (c) Philipps Universitaet Marburg 2009-2014
-- License     :  BSD-style (see the file LICENSE)
-- 
-- Maintainer  :  eden@mathematik.uni-marburg.de
-- Stability   :  beta
-- Portability :  not portable
--
-- This Haskell module defines map-like skeletons for 
-- the parallel functional language Eden.
--
-- Depends on GHC. Using standard GHC, you will get a threaded simulation of Eden. 
-- Use the forked GHC-Eden compiler from http:\/\/www.mathematik.uni-marburg.de/~eden 
-- for a parallel build.
--
-- Eden Group ( http:\/\/www.mathematik.uni-marburg.de/~eden )


module Control.Parallel.Eden.Map (
                 -- * Custom map skeletons 
                 -- | These skeletons expose many parameters to the user and thus have varying types
                 parMap, ranch, 
                 farm, farmS, farmB, 
                 offlineFarm, offlineFarmS, offlineFarmB,
                 -- * Custom map skeletons with explicit placement
                 -- | Map skeleton versions with explicit placement
                 parMapAt, ranchAt, farmAt, offlineFarmAt, 
                 -- * Simple map skeleton variants 
                 -- | The map skeletons 'farm' and 'offlineFarm' can be used to define 
                 --  skeletons with the simpler sequential map interface :: (a -> b) -> [a] -> [b]
                 mapFarmB, mapFarmS, mapOfflineFarmS, mapOfflineFarmB,
                 -- * Deprecated map skeletons
                 -- | These skeletons are included to keep old code alive. Use the skeletons above.
                 farmClassic, ssf, offline_farm, map_par, map_farm, map_offlineFarm, map_ssf
                 ) where
#if defined( __PARALLEL_HASKELL__ )
import Control.Parallel.Eden
#else
import Control.Parallel.Eden.EdenConcHs
#endif
import Control.Parallel.Eden.Auxiliary
import Data.List


-- | Basic parMap Skeleton - one process for each list element. This version takes 
-- places for instantiation on particular PEs. 
parMapAt :: (Trans a, Trans b) => 
            Places      -- ^places for instantiation          
            -> (a -> b) -- ^worker function
            -> [a]      -- ^task list
            -> [b]      -- ^result list
parMapAt pos f tasks = spawnAt pos (repeat (process f)) tasks


-- | Basic parMap Skeleton - one process for each list element
parMap :: (Trans a, Trans b) => 
          (a -> b)   -- ^worker function
          -> [a]     -- ^task list
          -> [b]     -- ^result list
parMap = parMapAt [0]


-- | A process ranch is a generalized (or super-) farm. This version takes 
-- places for instantiation. Arbitrary input is transformed into a list of inputs 
-- for the worker processes (one worker for each transformed value). The worker 
-- inputs are processed by the worker function.
-- The results of the worker processes are then reduced using the reduction function.
ranchAt :: (Trans b, Trans c) => 
           Places         -- ^places for instantiation
           -> (a -> [b])  -- ^input transformation function 
           -> ([c] -> d)  -- ^result reduction function
           -> (b -> c)    -- ^worker function
           -> a           -- ^input 
           -> d           -- ^output
ranchAt pos transform reduce fs xs = 
        reduce $ spawnAt pos (repeat $ process fs) (transform xs)

-- | A process ranch is a generalized (or super-) farm.  Arbitrary input 
-- is transformed into a list of inputs for the worker processes (one worker 
-- for each transformed value). The worker inputs are processed by the worker function.
-- The results of the worker processes are then reduced using the reduction function.
ranch :: (Trans b, Trans c) => 
         (a -> [b])     -- ^input transformation function 
         -> ([c] -> d)  -- ^result reduction function
         -> (b ->  c)   -- ^worker function
         -> a           -- ^input 
         -> d           -- ^output
ranch = ranchAt [0]


-- | A farm distributes its input to a number of worker processes.
-- This version takes places for instantiation.
-- The distribution function divides the input list into 
-- sublists - each sublist is input to one worker process, the 
-- number of worker processes is determined by the number of 
-- sublists. The results of the worker processes are 
-- then combined using the combination function.
-- 
-- Use 'map_farm' if you want a simpler interface.
--
farmAt :: (Trans a, Trans b) => 
          Places            -- ^places for instantiation
          -> ([a] -> [[a]]) -- ^input distribution function 
          -> ([[b]] -> [b]) -- ^result combination function
          -> (a ->  b)      -- ^mapped function
          -> [a]            -- ^input 
          -> [b]            -- ^output
farmAt pos distr combine f tasks = ranchAt pos distr combine (map f) tasks

-- | A farm distributes its input to a number of worker processes.
-- The distribution function divides the input list into 
-- sublists - each sublist is input to one worker process, the 
-- number of worker processes is determined by the number of 
-- sublists. The results of the worker processes are 
-- then combined using the combination function.
-- 
-- Use 'mapFarmS' or 'mapFarmB' if you want a simpler interface.
--
farm :: (Trans a, Trans b) => 
        ([a] -> [[a]])    -- ^input distribution function 
        -> ([[b]] -> [b]) -- ^result combination function
        -> (a ->  b)      -- ^mapped function
        -> [a]            -- ^input 
        -> [b]            -- ^output
farm = farmAt [0]

-- | Like the 'farm', but uses a fixed round-robin distribution of tasks.
farmS :: (Trans a, Trans b) 
         => Int            -- ^number of processes
         -> (a ->  b)      -- ^mapped function
         -> [a]            -- ^input 
         -> [b]            -- ^output
farmS np = farm (unshuffle  np) shuffle

-- | Like the 'farm', but uses a fixed block distribution of tasks.
farmB :: (Trans a, Trans b) 
         => Int            -- ^number of processes
         -> (a ->  b)      -- ^mapped function
         -> [a]            -- ^input 
         -> [b]            -- ^output
farmB np = farm (splitIntoN np) concat




-- | Offline farm with explicit placement (alias self-service farm or
-- direct mapping): Like the farm, but tasks are evaluated inside the
-- workers (less communication overhead). Tasks are mapped inside each
-- generated process abstraction, avoiding evaluating and sending
-- them. This often reduces the communication overhead because
-- unevaluated data is usually much smaller than evaluated data.
-- 
-- Use 'map_offlineFarm' if you want a simpler interface.
--
-- Notice: The task lists structure has to be completely defined
-- before process instantiation takes place.
--
offlineFarmAt :: Trans b => 
         Places            -- ^places for instantiation
         -> Int            -- ^number of processes
         -> ([a] -> [[a]]) -- ^input distribution function 
         -> ([[b]] -> [b]) -- ^result combination function
         -> (a ->  b)      -- ^mapped function
         -> [a]            -- ^input 
         -> [b]            -- ^output

offlineFarmAt pos np distribute combine f xs 
  = combine $ spawnAt pos (map (rfi (map f)) [select i xs | i <- [0 .. np-1]])
                          (repeat ())
  where select i xs = (distribute xs ++ repeat []) !! i



-- | Offline farm (alias direct mapping): Like the farm, but
-- tasks are evaluated inside the workers (less communication
-- overhead). Tasks are mapped inside each generated process
-- abstraction avoiding evaluating and sending them. This often
-- reduces the communication overhead because unevaluated data is
-- usually much smaller than evaluated data.
-- 
-- Use 'map_offlineFarm' if you want a simpler interface.
--
-- Notice: The offline farm receives the number of processes to be created
-- as its first parameter. 
-- The task lists structure has to be completely defined
-- before process instantiation takes place.
--
offlineFarm :: Trans b => 
       Int               -- ^number of processes 
       -> ([a] -> [[a]]) -- ^input distribution function 
       -> ([[b]] -> [b]) -- ^result combination function
       -> (a ->  b)      -- ^mapped function
       -> [a]            -- ^input 
       -> [b]            -- ^output

offlineFarm  = offlineFarmAt [0]

-- | Like the 'offlineFarm', but with fixed round-robin distribution of tasks.
offlineFarmS :: (Trans a, Trans b) 
                => Int            -- ^number of processes
                -> (a ->  b)      -- ^mapped function
                -> [a]            -- ^input 
                -> [b]            -- ^output
offlineFarmS np = offlineFarm np (unshuffle np) shuffle

-- | Like the 'offlineFarm', but with fixed block distribution of tasks.
offlineFarmB :: (Trans a, Trans b) 
                => Int            -- ^number of processes
                -> (a ->  b)      -- ^mapped function
                -> [a]            -- ^input 
                -> [b]            -- ^output
offlineFarmB np = offlineFarm np (splitIntoN np) concat


------------------------------------------------------------------------------------
numProc = max (noPe-1) 1

-- | Parallel map variant with map interface using (max (noPe-1) 1) worker processes. Skeletons ending on @S@ use round-robin distribution, skeletons ending on @B@ use block distribution of tasks.
mapFarmS, mapFarmB, mapOfflineFarmS, mapOfflineFarmB :: (Trans a , Trans b) => 
                             (a -> b)   -- ^worker function
                             -> [a]     -- ^task list
                             -> [b]     -- ^result list 
mapFarmS        = farmS numProc
mapFarmB        = farmB numProc
mapOfflineFarmS = offlineFarmS numProc
mapOfflineFarmB = offlineFarmB numProc

-----------------------------DEPRECATED---------------------------------


-- | Deprecated, use the 'farm';
-- @farmClassic@ distributes its input to a number of worker processes.
-- This is the Classic version as described in the Eden standard reference 
-- "Parallel Functional Programming in Eden".
-- The distribution function is expected to divide the input list into 
-- the given number of sublists. In the new farm the number of sublists is 
-- determined only by the distribution function.
-- 
-- Use 'map_farm' if you want a simpler interface.
{-# DEPRECATED farmClassic "better use farm instead" #-}
farmClassic :: (Trans a, Trans b) => 
               Int                      -- ^number of child processes
               -> (Int -> [a] -> [[a]]) -- ^input distribution function 
               -> ([[b]] -> [b])        -- ^result combination function
               -> (a ->  b)             -- ^mapped function
               -> [a]                   -- ^input 
               -> [b]                   -- ^output

farmClassic np distribute = farm $ distribute np


-- | Deprecated, use 'offlineFarm'; 
-- Self service farm. Like the farm, but
-- tasks are evaluated in the workers (less communication overhead).
-- This is the classic version. The distribution function is expected
-- to divide the input list into the given number of sublists. In the
-- new self service farm the number of sublists is determined only by
-- the distribution function.
-- 
-- Use 'map_ssf' if you want a simpler interface.
--
-- Notice: The task lists structure has to be completely defined
-- before process instantiation takes place.
{-# DEPRECATED ssf "better use offlineFarm instead" #-}
ssf :: forall a b . Trans b => 
       Int                          -- ^number of child processes
       -> (Int -> [a] -> [[a]])     -- ^input distribution function 
       -> ([[b]] -> [b])            -- ^result combination function
       -> (a ->  b)                 -- ^mapped function
       -> [a]                       -- ^input 
       -> [b]                       -- ^output

ssf np distribute = offlineFarm np (distribute np) 

-- | Deprecated: Same as 'offlineFarm'.
{-# DEPRECATED offline_farm "better use offlineFarm instead" #-}
offline_farm :: Trans b => 
       Int               -- ^number of processes 
       -> ([a] -> [[a]]) -- ^input distribution function 
       -> ([[b]] -> [b]) -- ^result combination function
       -> (a ->  b)      -- ^mapped function
       -> [a]            -- ^input 
       -> [b]            -- ^output
offline_farm = offlineFarm

-- | Deprecated: Same as 'parMap'.
{-# DEPRECATED map_par "better use parMap instead" #-}
map_par :: (Trans a , Trans b) => 
                             (a -> b)   -- ^worker function
                             -> [a]     -- ^task list
                             -> [b]     -- ^result list 
map_par         = parMap

-- | Deprecated: Parallel map variants with map interface using noPe worker processes.
{-# DEPRECATED map_farm, map_offlineFarm, map_ssf "better use mapFarmS or mapOfflineFarmS instead" #-}
map_farm, map_offlineFarm, map_ssf :: (Trans a , Trans b) => 
                             (a -> b)   -- ^worker function
                             -> [a]     -- ^task list
                             -> [b]     -- ^result list 
map_farm        = farmS noPe
map_offlineFarm = offlineFarmS noPe
map_ssf         = map_offlineFarm