MultiChor-1.0.0.0: examples/Karatsuba.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TemplateHaskell #-}
{-
# This example was carried over from earlier work, likely HasChor. It doesn't yet have a unit test attached to it.
# Example: Karatsuba Fast Multiplication
## Overview
This example implements the three-way concurrent implementation of the Karatsuba fast multiplication algorithm shown in [Object-Oriented Choreographic Programming](https://arxiv.org/abs/2005.09520).
## Execution
The executable takes two integers as arguments and returns their product. It uses the local backend and distribute the computation over threads.
```bash
cabal run karatsuba 100 200
20000
```
`Reference.h` contains a single-threaded reference implementation of the algorithm.
-}
module Karatsuba where
import Choreography
import Choreography.Network.Local
import Control.Concurrent.Async (mapConcurrently_)
import GHC.TypeLits (KnownSymbol)
import System.Environment
reference :: Integer -> Integer -> Integer
reference n1 n2 =
if n1 < 10 || n2 < 10
then n1 * n2
else result
where
log10 :: Integer -> Double
log10 = logBase 10 . fromIntegral
m = max (log10 n1) (log10 n2) + 1
m2 :: Integer = floor (m / 2)
splitter = 10 ^ m2
h1 = n1 `div` splitter
l1 = n1 `mod` splitter
h2 = n2 `div` splitter
l2 = n2 `mod` splitter
z0 = reference l1 l2
z2 = reference h1 h2
z1 = reference (l1 + h1) (l2 + h2) - z2 - z0
result = z2 * splitter * splitter + z1 * splitter + z0
$(mkLoc "primary")
$(mkLoc "worker1")
$(mkLoc "worker2")
type Participants = ["primary", "worker1", "worker2"]
data KaratsubaNums = KaratsubaNums
{ splitter :: Integer,
h1 :: Integer,
h2 :: Integer,
l1 :: Integer,
l2 :: Integer
}
karatsuba ::
(KnownSymbol a, KnownSymbol b, KnownSymbol c) =>
Member a Participants ->
Member b Participants ->
Member c Participants ->
Located '[a] Integer ->
Located '[a] Integer ->
Choreo Participants IO (Located '[a] Integer)
karatsuba a b c n1 n2 = do
done <- a `locally` \un -> return $ un singleton n1 < 10 || un singleton n2 < 10
broadcast (a, done)
>>= \case
True -> do
a `locally` \un -> return $ un singleton n1 * un singleton n2
False -> do
x <- a `locally` \un -> return $ f (un singleton n1) (un singleton n2)
l1' <- (a, \un -> return $ l1 (un singleton x)) ~~> b @@ nobody
l2' <- (a, \un -> return $ l2 (un singleton x)) ~~> b @@ nobody
h1' <- (a, \un -> return $ h1 (un singleton x)) ~~> c @@ nobody
h2' <- (a, \un -> return $ h2 (un singleton x)) ~~> c @@ nobody
z0' <- karatsuba b c a l1' l2'
z0 <- (b, z0') ~> a @@ nobody
z2' <- karatsuba c a b h1' h2'
z2 <- (c, z2') ~> a @@ nobody
s1 <- a `locally` \un -> return $ l1 (un singleton x) + h1 (un singleton x)
s2 <- a `locally` \un -> return $ l2 (un singleton x) + h2 (un singleton x)
z1' <- karatsuba a b c s1 s2
z1 <- a `locally` \un -> return $ un singleton z1' - un singleton z2 - un singleton z0
a `locally` \un ->
return
let s = splitter (un singleton x)
in (un singleton z2 * s * s) + (un singleton z1 * s) + un singleton z0
where
f n1' n2' = KaratsubaNums {splitter = splitter, h1 = h1, l1 = l1, h2 = h2, l2 = l2}
where
log10 :: Integer -> Double
log10 = logBase 10 . fromIntegral
m = max (log10 n1') (log10 n2') + 1
m2 :: Integer = floor (m / 2)
splitter = 10 ^ m2
h1 = n1' `div` splitter
l1 = n1' `mod` splitter
h2 = n2' `div` splitter
l2 = n2' `mod` splitter
mainChoreo :: Integer -> Integer -> Choreo Participants IO ()
mainChoreo n1' n2' = do
n1 <- primary `_locally` pure n1'
n2 <- primary `_locally` pure n2'
result <- karatsuba primary worker1 worker2 n1 n2
primary `locally_` \un -> do
print (un primary result)
main :: IO ()
main = do
[n1, n2] <- map read <$> getArgs
config <- mkLocalConfig locations
mapConcurrently_ (runChoreography config (mainChoreo n1 n2)) locations
return ()
where
locations = ["primary", "worker1", "worker2"]