pure-borrow-0.1.0.0: test/Control/Concurrent/DivideConquer/LinearSpec.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE QualifiedDo #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
module Control.Concurrent.DivideConquer.LinearSpec (
module Control.Concurrent.DivideConquer.LinearSpec,
) where
import Control.Concurrent.DivideConquer.Linear
import Control.Concurrent.DivideConquer.Linear qualified as DC
import Control.Functor.Linear qualified as Control
import Control.Monad.Borrow.Pure.BO
import Control.Monad.Borrow.Pure.Copyable
import Control.Syntax.DataFlow qualified as DataFlow
import Data.Complex (Complex (..), magnitude)
import Data.List qualified as List
import Data.List qualified as NonLinear
import Data.Vector qualified as V
import Data.Vector.Generic qualified as G
import Data.Vector.Generic.Mutable.Linear.Borrow.Unrestricted qualified as UnrestrictedVector
import Data.Vector.Mutable.Linear.Borrow qualified as VL
import Data.Vector.Unboxed qualified as U
import Prelude.Linear
import System.Random (StdGen, mkStdGen)
import Test.Falsify.Generator qualified as G
import Test.Falsify.Predicate qualified as P
import Test.Falsify.Range qualified as G
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Falsify (testProperty)
import Test.Tasty.Falsify qualified as F
import Test.Tasty.HUnit (assertBool, testCase, (@?=))
import Prelude qualified as NonLinear
test_qsort :: TestTree
test_qsort =
testGroup
"qsort"
[ testProperty "empty" do
seed <- F.gen $ G.int $ G.between (minBound, maxBound)
F.assert $
P.expect (V.empty) P..$ ("output", qsortUnrestrictedDCVec (mkStdGen seed) (V.empty @Int))
, testProperty "coincides with Data.List.sort on Ints" do
seed <- F.gen $ G.int $ G.between (minBound, maxBound)
xs <- F.gen $ G.list (G.between (1, 100)) $ G.int $ G.between (-100, 100)
let v = V.fromList xs
unrestrictedBoxed =
qsortUnrestrictedDCVec (mkStdGen seed) v
unrestrictedUnboxed =
qsortUnrestrictedDCVec
(mkStdGen seed)
(U.fromList xs)
F.collect "length" [ceiling @_ @Int (fromIntegral @_ @Double (V.length v) / 10) * 10]
F.collect "min" [NonLinear.minimum v `quot` 10 * 10]
F.collect "max" [NonLinear.maximum v `quot` 10 * 10]
F.collect "sorted" [V.and $ V.zipWith (NonLinear.<=) v (V.tail v)]
F.info $ "input: " <> show xs
F.assert $
P.expect
( List.sort xs
, List.sort xs
)
P..$ ( "output"
,
( V.toList unrestrictedBoxed
, U.toList unrestrictedUnboxed
)
)
]
test_qsort_exported_modes :: TestTree
test_qsort_exported_modes =
testCase "direct, naive, and work-sharing modes support boxed and unboxed backends" do
let input = [5, 1, 4, 1, 3, 2] :: [Int]
expected = List.sort input
V.toList (qsortUnrestrictedDirectVec (V.fromList input)) @?= expected
U.toList (qsortUnrestrictedDirectVec (U.fromList input)) @?= expected
V.toList (qsortUnrestrictedNaiveVec (V.fromList input)) @?= expected
U.toList (qsortUnrestrictedNaiveVec (U.fromList input)) @?= expected
V.toList
(qsortUnrestrictedDCVec (mkStdGen 42) (V.fromList input))
@?= expected
U.toList
(qsortUnrestrictedDCVec (mkStdGen 42) (U.fromList input))
@?= expected
qsortUnrestrictedDCVec ::
(G.Vector v a, Ord a) =>
StdGen ->
v a ->
v a
qsortUnrestrictedDCVec generator source =
unur $
linearly \linear -> DataFlow.do
(ownerLinear, runLinear) <- dup linear
runBO runLinear Control.do
(vector, lend) <-
borrowM
( UnrestrictedVector.fromVector
source
ownerLinear
)
Control.void $
DC.qsortDC
generator
10
128
vector
pureAfter
( UnrestrictedVector.toVector
(reclaim lend)
)
qsortUnrestrictedDirectVec ::
(G.Vector v a, Ord a) =>
v a ->
v a
qsortUnrestrictedDirectVec source =
unur $
linearly \linear -> DataFlow.do
(ownerLinear, runLinear) <- dup linear
runBO runLinear Control.do
(vector, lend) <-
borrowM (UnrestrictedVector.fromVector source ownerLinear)
DC.qsort 0 vector
pureAfter (UnrestrictedVector.toVector (reclaim lend))
qsortUnrestrictedNaiveVec ::
(G.Vector v a, Ord a) =>
v a ->
v a
qsortUnrestrictedNaiveVec source =
unur $
linearly \linear -> DataFlow.do
(ownerLinear, runLinear) <- dup linear
runBO runLinear Control.do
(vector, lend) <-
borrowM (UnrestrictedVector.fromVector source ownerLinear)
Control.void $
naiveDivideAndConquer
(DC.qsortDC' 4)
vector
pureAfter (UnrestrictedVector.toVector (reclaim lend))
test_fft :: TestTree
test_fft =
testGroup
"fft"
[ testCase "agrees across schedulers and backends" do
let input =
V.generate 16 \index ->
fromIntegral index :+ 0
unboxedInput =
U.generate 16 \index ->
fromIntegral index :+ 0
boxedSequential =
fftUnrestrictedSequential input
boxedNaive =
fftUnrestrictedNaive input
boxedWorkSharing =
fftUnrestrictedWorkSharing input
unboxedSequential =
fftUnrestrictedSequential unboxedInput
unboxedNaive =
fftUnrestrictedNaive unboxedInput
unboxedWorkSharing =
fftUnrestrictedWorkSharing unboxedInput
boxedNaive @?= boxedSequential
boxedWorkSharing @?= boxedSequential
unboxedNaive @?= unboxedSequential
unboxedWorkSharing @?= unboxedSequential
U.toList unboxedSequential @?= V.toList boxedSequential
, testCase "matches a direct DFT for small power-of-two inputs" do
NonLinear.mapM_ checkFftAgainstDft [1, 2, 4, 8, 16]
]
checkFftAgainstDft :: Int -> IO ()
checkFftAgainstDft length_ = do
let values =
[ fromIntegral ((index * 7 + 3) `NonLinear.mod` 11)
:+ fromIntegral ((index * 5 + 1) `NonLinear.mod` 7)
| index <- [0 .. length_ - 1]
]
expected = directDft values
boxed = V.toList (fftUnrestrictedSequential (V.fromList values))
unboxed = U.toList (fftUnrestrictedSequential (U.fromList values))
tolerance = 1e-9 * fromIntegral length_
assertComplexListsClose tolerance expected boxed
assertComplexListsClose tolerance expected unboxed
directDft :: [Complex Double] -> [Complex Double]
directDft values =
[ NonLinear.sum
[ value
NonLinear.* (cos angle :+ sin angle)
| (sampleIndex, value) <- NonLinear.zip [0 :: Int ..] values
, let angle =
2
* pi
* fromIntegral outputIndex
* fromIntegral sampleIndex
/ fromIntegral (NonLinear.length values)
]
| outputIndex <- [0 .. NonLinear.length values - 1]
]
assertComplexListsClose ::
Double ->
[Complex Double] ->
[Complex Double] ->
IO ()
assertComplexListsClose tolerance expected actual =
assertBool
( "expected "
<> show expected
<> ", but got "
<> show actual
)
( NonLinear.length expected
== NonLinear.length actual
&& NonLinear.and
( NonLinear.zipWith
( \expectedValue actualValue ->
magnitude
(expectedValue NonLinear.- actualValue)
<= tolerance
)
expected
actual
)
)
fftUnrestrictedSequential ::
(G.Vector v (Complex Double)) =>
v (Complex Double) ->
v (Complex Double)
fftUnrestrictedSequential source =
unur $
linearly \linear -> DataFlow.do
(ownerLinear, runLinear) <- dup linear
runBO runLinear Control.do
(vector, lend) <-
borrowM
( UnrestrictedVector.fromVector
source
ownerLinear
)
Control.void $
sequentialDivideAndConquer
(DC.fftDC' 4)
vector
pureAfter
( UnrestrictedVector.toVector
(reclaim lend)
)
fftUnrestrictedNaive ::
(G.Vector v (Complex Double)) =>
v (Complex Double) ->
v (Complex Double)
fftUnrestrictedNaive source =
unur $
linearly \linear -> DataFlow.do
(ownerLinear, runLinear) <- dup linear
runBO runLinear Control.do
(vector, lend) <-
borrowM (UnrestrictedVector.fromVector source ownerLinear)
Control.void $
naiveDivideAndConquer
(DC.fftDC' 4)
vector
pureAfter (UnrestrictedVector.toVector (reclaim lend))
fftUnrestrictedWorkSharing ::
(G.Vector v (Complex Double)) =>
v (Complex Double) ->
v (Complex Double)
fftUnrestrictedWorkSharing source =
unur $
linearly \linear -> DataFlow.do
(ownerLinear, runLinear) <- dup linear
runBO runLinear Control.do
(vector, lend) <-
borrowM
( UnrestrictedVector.fromVector
source
ownerLinear
)
Control.void $
DC.fftDC
(mkStdGen 42)
2
4
vector
pureAfter
( UnrestrictedVector.toVector
(reclaim lend)
)