packages feed

rp-tree-0.3: bench/time/Main.hs

{-# LANGUAGE FlexibleContexts #-}
{-# options_ghc -Wno-unused-imports #-}
module Main where

import Control.Exception (bracket)
import Control.Monad (forM, forM_)
import Control.Monad.IO.Class (MonadIO(..))
import GHC.Word (Word8, Word64)
import System.CPUTime (getCPUTime)

-- -- benchpress
-- import Test.BenchPress (Stats(..), benchmark, printDetailedStats)
-- conduit
import Conduit (runResourceT, MonadResource)
import qualified Data.Conduit as C (ConduitT, runConduit, runConduitRes, yield, await, transPipe)
import Data.Conduit ((.|))
import qualified Data.Conduit.Combinators as C (map, mapM, scanl, scanlM, last, print, takeExactly)
-- deepseq
import Control.DeepSeq (NFData(..), force)
-- exceptions
import Control.Monad.Catch (MonadThrow(..))
-- mnist-idx-conduit
import Data.IDX.Conduit (sourceIdxSparse, sBufSize, sNzComponents)
-- splitmix-distributions
import System.Random.SplitMix.Distributions (GenT, sampleT, sample, samples)

-- mtl
import Control.Monad.Trans.Class (MonadTrans(..))

-- vector
import qualified Data.Vector as V (Vector, replicateM, fromList)
import qualified Data.Vector.Unboxed as VU (Unbox, Vector, map)

import Data.RPTree (tree, forest, recallWith, knn, fromVectorSv, fromListSv, RPForest, RPTree, SVector, Inner(..), normalSparse2, liftC, Embed(..))
import Data.RPTree.Internal.Testing (BenchConfig(..), randSeed, datD, datS)

main :: IO ()
main = do -- putStrLn "hello!"
  binMixFQBench
  -- mnistBench

benchConfigs :: String -- ^ description of the experiment
             -> Int -- ^ dimension of the projection vectors
             -> [BenchConfig]
benchConfigs descr pdim = [ BenchConfig descr maxd minl nt chunk nzd pdim n nq
                          | maxd <- [5],
                            minl <- [10],
                            nt <- [3],
                            chunk <- [100],
                            nzd <- [0.2],
                            n <- [1000],
                            nq <- [10]
                          ]

-- -- Binary mixture

binMixFQBench :: IO ()
binMixFQBench = do
  let
    cfgs = benchConfigs "binary mixture of sparse Gaussian RVs" 1000
  forM_ cfgs $ \cfg -> do
    stats <- binMixFQBench1 cfg
    print cfg
    -- printDetailedStats stats
    print stats
    pure stats
  -- print s

-- | Measure recall @ 10 and mean time
binMixFQBench1 :: BenchConfig -> IO (Double, Double)
binMixFQBench1 cfg = forestBenchGen seed src act 2 cfg
  where
    n = bcDataSize cfg
    d = bcVectorDim cfg
    nq = bcNumQueryPoints cfg
    -- pnz = bcNZDensity cfg -- nz density of proj vectors
    nzData = 0.8 -- nz density of data 
    k = 10 -- number of ANN's to return
    seed = 1234
    src = datS n d nzData .| C.map (\r -> Embed r ())
    qs = samples nq seed $ normalSparse2 nzData d
    act tt = do
      -- pure $! recallWith metricL2 tt k `map` qs
      let
        recs = recallWith metricL2 tt k `map` qs
        r = mean recs
      pure $! r



-- -- MNIST dataset

-- mnistBench :: IO ()
-- mnistBench = do
--   let
--     cfgs = benchConfigs "MNIST dataset" 784
--     mnfpath = "assets/mnist/train-images-idx3-ubyte"
--   forM_ cfgs $ \cfg -> do
--     stats <- mnistFQBench1 mnfpath cfg
--     print cfg
--     print stats

-- -- | Measure recall @ 10 and mean time
-- -- mnistFQBench1 :: FilePath -> BenchConfig -> IO (Double, Double)
-- mnistFQBench1 fp cfg = forestBench (mnist fp n) act 1 cfg
--   where
--     n = bcDataSize cfg
--     k = 10 -- number of ANN's to return
--     d = bcVectorDim cfg
--     nzData = 0.8 -- nz density of data 
--     act x = do
--       tt <- runResourceT x
--       let q = sample 1234 $ normalSparse2 nzData d
--       pure $! recallWith metricL2 tt k q

mnist :: MonadResource m =>
         FilePath -- path to uncompressed MNIST IDX data file
      -> Int -- number of data items
      -> C.ConduitT a (Embed SVector Double ()) m ()
mnist fp n = C.takeExactly n src
  where
    src = sourceIdxSparse fp .|
          C.map (\r -> fromVectorSv (sBufSize r) (VU.map f $ sNzComponents r)) .|
          C.map (\r -> Embed r ())
    f (i, x) = (i, toUnitRange x)

toUnitRange :: Word8 -> Double
toUnitRange w8 = fromIntegral w8 / 255




-- -- UTILS

-- -- | runs a benchmark on a newly created RPForest initialized with a random seed
-- forestBench :: (MonadThrow m, Inner SVector v) =>
--                C.ConduitT () (v Double) m ()
--             -> (m (RPForest Double (V.Vector (v Double))) -> IO c) -- ^ allows for both deterministic and random data sources
--             -> Int  -- ^ number of replicates
--             -> BenchConfig
--             -> IO (c, Double) -- ^ result, mean wall-clock time measurement
forestBench src go n cfg = benchmark n setup (const $ pure ()) go
  where
    setup = do
      s <- randSeed
      -- let src' = C.transPipe lift src
      pure $ growForest s cfg src


forestBenchGen :: (MonadIO m, Inner SVector v, NFData x, NFData (v Double)) =>
                  Word64
               -> C.ConduitT () (Embed v Double x) (GenT m) ()
               -> (RPForest Double (V.Vector (Embed v Double x)) -> m a2)
               -> Int
               -> BenchConfig
               -> m (a2, Double)
forestBenchGen seed src go n cfg = benchmarkM n setup go
  where
    setup = do
      s <- randSeed
      x <- sampleT seed $ growForest s cfg src
      pure $ force x



-- treeBench :: (Monad m, Inner SVector v) =>
--              C.ConduitT () (v Double) m ()
--           -> (m (RPTree Double (V.Vector (v Double))) -> IO c)
--           -> Int
--           -> BenchConfig
--           -> IO (c, Double)
treeBench src go n cfg = benchmark n setup (const $ pure ()) go
      where
        setup = do
          s <- randSeed
          -- let src' = C.transPipe lift src
          pure $ growTree s cfg src

-- growTree :: (Monad m, Inner SVector v) =>
--             Word64
--          -> BenchConfig
--          -> C.ConduitT () (v Double) m ()
--          -> m (RPTree Double (V.Vector (v Double)))
growTree seed (BenchConfig _ maxd minl _ chunksize pnz pdim _ _) =
  tree seed maxd minl chunksize pnz pdim

-- growForest :: (Monad m, Inner SVector v) =>
--               Word64
--            -> BenchConfig
--            -> C.ConduitT () (v Double) m ()
--            -> m (RPForest Double (V.Vector (v Double)))
growForest seed (BenchConfig _ maxd minl ntrees chunksize pnz pdim _ _) =
  forest seed maxd minl ntrees chunksize pnz pdim

-- growForest' seed (BenchConfig _ maxd minl ntrees chunksize pnz pdim _) =
--   forest' seed maxd minl ntrees chunksize pnz pdim


-- -- adapted from 'benchpress', until https://github.com/WillSewell/benchpress/issues/9 is merged
benchmark :: Int -> IO a -> (a -> IO b) -> (a -> IO c) -> IO (c, Double)
benchmark iters setup teardown action =
  if iters < 1
    then error "benchmark: iters must be greater than 0"
    else do
      (vals, cpuTimes) <- unzip `fmap` go iters
      let tcm = mean cpuTimes
          v = head vals
      return (v, tcm)
      where
        go 0 = return []
        go n = do
          elapsed <- bracket setup teardown $ \a -> do
            startCpu <- getCPUTime
            x <- action a
            endCpu <- getCPUTime
            return (x
                   ,picosToMillis $! endCpu - startCpu)
          timings <- go $! n - 1
          return $ elapsed : timings

benchmarkM :: (MonadIO m) =>
              Int -> m t -> (t -> m a2) -> m (a2, Double)
benchmarkM iters setup action =
  if iters < 1
    then error "benchmark: iters must be greater than 0"
    else do
      (vals, cpuTimes) <- unzip `fmap` go iters
      let tcm = mean cpuTimes
          v = head vals
      return (v, tcm)
      where
        go 0 = return []
        go n = do
          a <- setup
          elapsed <- do
            startCpu <- liftIO getCPUTime
            x <- action a
            endCpu <- liftIO getCPUTime
            return (x
                   ,picosToMillis $! endCpu - startCpu)
          timings <- go $! n - 1
          return $ elapsed : timings


-- | Converts picoseconds to milliseconds.
picosToMillis :: Integer -> Double
picosToMillis t = realToFrac t / (10^(9 :: Int))

-- | Numerically stable mean.
mean :: Floating a => [a] -> a
mean = go 0 0
    where
      go :: Floating a => a -> Int -> [a] -> a
      go m _ []     = m
      go m n (x:xs) = go (m + (x - m) / fromIntegral (n + 1)) (n + 1) xs




mnistV0 :: SVector Double
mnistV0 = fromListSv 784 (map (\(i, x) -> (i, toUnitRange x)) cs)
  where
    cs = [(208,55),(209,148),(210,210),(211,253),(212,253),(213,113),(214,87),(215,148),(216,55),(235,87),(236,232),(237,252),(238,253),(239,189),(240,210),(241,252),(242,252),(243,253),(244,168),(261,4),(262,57),(263,242),(264,252),(265,190),(266,65),(267,5),(268,12),(269,182),(270,252),(271,253),(272,116),(289,96),(290,252),(291,252),(292,183),(293,14),(296,92),(297,252),(298,252),(299,225),(300,21),(316,132),(317,253),(318,252),(319,146),(320,14),(324,215),(325,252),(326,252),(327,79),(343,126),(344,253),(345,247),(346,176),(347,9),(350,8),(351,78),(352,245),(353,253),(354,129),(370,16),(371,232),(372,252),(373,176),(377,36),(378,201),(379,252),(380,252),(381,169),(382,11),(398,22),(399,252),(400,252),(401,30),(402,22),(403,119),(404,197),(405,241),(406,253),(407,252),(408,251),(409,77),(426,16),(427,231),(428,252),(429,253),(430,252),(431,252),(432,252),(433,226),(434,227),(435,252),(436,231),(455,55),(456,235),(457,253),(458,217),(459,138),(460,42),(461,24),(462,192),(463,252),(464,143),(489,62),(490,255),(491,253),(492,109),(517,71),(518,253),(519,252),(520,21),(546,253),(547,252),(548,21),(573,71),(574,253),(575,252),(576,21),(601,106),(602,253),(603,252),(604,21),(629,45),(630,255),(631,253),(632,21),(658,218),(659,252),(660,56),(686,96),(687,252),(688,189),(689,42),(714,14),(715,184),(716,252),(717,170),(718,11),(743,14),(744,147),(745,252),(746,42)]