packages feed

rp-tree 0.1.0.0 → 0.2.0.0

raw patch · 10 files changed

+654/−142 lines, 10 filesdep +benchpressdep +mnist-idx-conduitdep +splitmixdep ~basedep ~splitmix-distributionsdep ~vectorPVP ok

version bump matches the API change (PVP)

Dependencies added: benchpress, mnist-idx-conduit, splitmix

Dependency ranges changed: base, splitmix-distributions, vector

API changes (from Hackage documentation)

- Data.RPTree: recall :: (Inner u v, Inner SVector v, Unbox a, Ord a, Ord (u a), Floating a) => RPForest a (Vector (u a)) -> Int -> v a -> Double
+ Data.RPTree: BenchConfig :: String -> Int -> Int -> Int -> Int -> Double -> Int -> Int -> Int -> BenchConfig
+ Data.RPTree: [bcChunkSize] :: BenchConfig -> Int
+ Data.RPTree: [bcDataSize] :: BenchConfig -> Int
+ Data.RPTree: [bcDescription] :: BenchConfig -> String
+ Data.RPTree: [bcMaxTreeDepth] :: BenchConfig -> Int
+ Data.RPTree: [bcMinLeafSize] :: BenchConfig -> Int
+ Data.RPTree: [bcNZDensity] :: BenchConfig -> Double
+ Data.RPTree: [bcNumQueryPoints] :: BenchConfig -> Int
+ Data.RPTree: [bcNumTrees] :: BenchConfig -> Int
+ Data.RPTree: [bcVectorDim] :: BenchConfig -> Int
+ Data.RPTree: data BenchConfig
+ Data.RPTree: deserialiseRPForest :: (Serialise d, Serialise a, Unbox d) => [ByteString] -> Either String (RPForest d a)
+ Data.RPTree: fromVectorDv :: Vector a -> DVector a
+ Data.RPTree: fromVectorSv :: Int -> Vector (Int, a) -> SVector a
+ Data.RPTree: innerDD :: (Vector v a, Num a) => v a -> v a -> a
+ Data.RPTree: innerSD :: (Num a, Vector u (Int, a), Vector v a, Unbox a) => u (Int, a) -> v a -> a
+ Data.RPTree: innerSS :: (Vector u (Int, a), Vector v (Int, a), Unbox a, Num a) => u (Int, a) -> v (Int, a) -> a
+ Data.RPTree: liftC :: (Monad m, MonadTrans t) => ConduitT i o m r -> ConduitT i o (t m) r
+ Data.RPTree: metricSDL2 :: (Floating a, Vector v1 a, Unbox a, Vector v1 (Int, a), Vector v2 a) => v1 (Int, a) -> v2 a -> a
+ Data.RPTree: metricSSL2 :: (Floating a, Vector u a, Unbox a, Vector u (Int, a), Vector v (Int, a)) => u (Int, a) -> v (Int, a) -> a
+ Data.RPTree: normal2 :: Monad m => GenT m (DVector Double)
+ Data.RPTree: normalSparse2 :: Monad m => Double -> Int -> GenT m (SVector Double)
+ Data.RPTree: randSeed :: MonadIO m => m Word64
+ Data.RPTree: recallWith :: (Inner SVector v, Unbox a, Fractional a, Ord a, Ord d, Ord p) => (p -> v a -> d) -> RPForest a (Vector p) -> Int -> v a -> a
+ Data.RPTree: scaleD :: (Vector v b, Num b) => b -> v b -> v b
+ Data.RPTree: scaleS :: (Vector v (a, b), Num b) => b -> v (a, b) -> v (a, b)
+ Data.RPTree: serialiseRPForest :: (Serialise d, Serialise a, Unbox d) => RPForest d a -> [ByteString]
+ Data.RPTree: tree :: (Monad m, Inner SVector v) => Word64 -> Int -> Int -> Int -> Double -> Int -> ConduitT () (v Double) m () -> m (RPTree Double (Vector (v Double)))
+ Data.RPTree.Internal.Testing: BenchConfig :: String -> Int -> Int -> Int -> Int -> Double -> Int -> Int -> Int -> BenchConfig
+ Data.RPTree.Internal.Testing: [bcChunkSize] :: BenchConfig -> Int
+ Data.RPTree.Internal.Testing: [bcDataSize] :: BenchConfig -> Int
+ Data.RPTree.Internal.Testing: [bcDescription] :: BenchConfig -> String
+ Data.RPTree.Internal.Testing: [bcMaxTreeDepth] :: BenchConfig -> Int
+ Data.RPTree.Internal.Testing: [bcMinLeafSize] :: BenchConfig -> Int
+ Data.RPTree.Internal.Testing: [bcNZDensity] :: BenchConfig -> Double
+ Data.RPTree.Internal.Testing: [bcNumQueryPoints] :: BenchConfig -> Int
+ Data.RPTree.Internal.Testing: [bcNumTrees] :: BenchConfig -> Int
+ Data.RPTree.Internal.Testing: [bcVectorDim] :: BenchConfig -> Int
+ Data.RPTree.Internal.Testing: datD :: Monad m => Int -> Int -> ConduitT i (DVector Double) (GenT m) ()
+ Data.RPTree.Internal.Testing: datS :: Monad m => Int -> Int -> Double -> ConduitT i (SVector Double) (GenT m) ()
+ Data.RPTree.Internal.Testing: data BenchConfig
+ Data.RPTree.Internal.Testing: instance GHC.Show.Show Data.RPTree.Internal.Testing.BenchConfig
+ Data.RPTree.Internal.Testing: randSeed :: MonadIO m => m Word64
- Data.RPTree: forest :: (MonadThrow m, Inner SVector v) => Word64 -> Int -> Int -> Int -> Int -> Double -> Int -> ConduitT () (v Double) m () -> m (RPForest Double (Vector (v Double)))
+ Data.RPTree: forest :: (Monad m, Inner SVector v) => Word64 -> Int -> Int -> Int -> Int -> Double -> Int -> ConduitT () (v Double) m () -> m (RPForest Double (Vector (v Double)))

Files

+ Changelog.md view
@@ -0,0 +1,6 @@+0.2++- fix 'candidates' such that 'knn' now does the right thing+- now 'knn' accepts a distance function as parameter as well+- add I/O functionality+- some time benchmarks
README.md view
@@ -1,5 +1,5 @@ # rp-tree -![scatterplot](/r/scatter.png "scatterplot")+![rp-tree](https://github.com/ocramz/rp-tree/blob/main/r/scatter.png ) -Random projection trees+Random projection trees for approximate nearest neighbor search in high-dimensional vector spaces
app/Main.hs view
@@ -3,9 +3,12 @@ {-# options_ghc -Wno-unused-imports #-} module Main where +import Control.Monad (replicateM) import Data.Foldable (fold)+import Data.Functor (void)+ -- conduit-import qualified Data.Conduit as C (ConduitT, runConduit, yield, await)+import qualified Data.Conduit as C (ConduitT, runConduit, yield, await, transPipe) import Data.Conduit ((.|)) import qualified Data.Conduit.Combinators as C (map, mapM, scanl, scanlM, last, print) import qualified Data.Conduit.List as C (chunksOf, unfold, unfoldM)@@ -13,25 +16,46 @@ import qualified Data.IntMap as IM (IntMap, fromList, insert, lookup, map, mapWithKey, traverseWithKey, foldlWithKey, foldrWithKey) -- exceptions import Control.Monad.Catch (MonadThrow(..))+-- mnist-idx-conduit+import Data.IDX.Conduit (sourceIdxSparse, sBufSize, sNzComponents) -- splitmix-distributions import System.Random.SplitMix.Distributions (Gen, GenT, sample, sampleT, bernoulli, normal) -- transformers import Control.Monad.Trans.State.Lazy (State, get, put, evalState)+import Control.Monad.Trans.Class (MonadTrans(..)) -- vector import qualified Data.Vector as V (Vector, toList, fromList, replicate, zip)  import Control.Monad (replicateM)-import Data.RPTree (knn, candidates, Inner(..), RPTree, RPForest, leaves, SVector, DVector, fromListDv, dense, writeCsv, forest, dataSource)+import Data.RPTree (knn, candidates, Inner(..), RPTree, RPForest, leaves, SVector, fromListSv, DVector, fromListDv, dense, writeCsv, tree, forest, dataSource, sparse, normal2, normalSparse2)+import Data.RPTree.Internal.Testing (datS, datD)  main :: IO ()-main = do+main = do -- putStrLn "hello!"   let-    n = 10000-  -- renderTree0 n-  -- renderTree1 n-  undefined -- FIXME+    n = 1000+    maxd = 3+    minl = 10+    ntree = 10+    d = 100+    pnz = 0.3+    chunk = 20+    src = datS n d pnz+    seed = 1234+  (q, tts) <- sampleT seed $ do+    tts <- C.runConduit $+           forest seed maxd minl ntree chunk pnz d (liftC src)+    q <- sparse 0.3 d (normal 0.1 0.6)+    pure (q, tts)+  let+    res = knn metricL2 1 tts q+  print res  +liftC = C.transPipe lift+++ -- renderTree0 :: Int -> IO () renderTree0 tt = do   let csvrows = V.toList $ fold $ flip evalState A $ traverse labeledV tt -- (tree0 n)@@ -115,9 +139,4 @@ srcC :: Monad m => Int -> C.ConduitT i (DVector Double) (GenT m) () srcC n = dataSource n normal2 -normal2 :: (Monad m) => GenT m (DVector Double)-normal2 = do-  b <- bernoulli 0.5-  if b-    then dense 2 $ normal 0 0.5-    else dense 2 $ normal 2 0.5+
+ bench/time/Main.hs view
@@ -0,0 +1,277 @@+{-# 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)+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, 100]+                          ]++-- -- 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 (datS n d nzData) 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+    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 (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))+    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+++-- -- forestBenchRes :: (MonadResource m, Inner SVector v) =>+-- --                   C.ConduitT+-- --                   ()+-- --                   (v Double)+-- --                   m+-- --                   ()+-- --                -> (RPForest Double (V.Vector (v Double)) -> IO c)+-- --                -> Int+-- --                -> BenchConfig+-- --                -> IO (c, Double)+forestBenchRes src go n cfg = benchmarkM n setup go+  where+    setup = do+      s <- randSeed+      runResourceT $ growForest s cfg src++forestBenchGen :: (MonadIO m, Inner SVector v, NFData (v Double)) =>+                  Word64+               -> C.ConduitT () (v Double) (GenT m) ()+               -> (RPForest Double (V.Vector (v Double)) -> 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)]
rp-tree.cabal view
@@ -1,5 +1,5 @@ name:                rp-tree-version:             0.1.0.0+version:             0.2.0.0 synopsis:            Random projection trees description:         Random projection trees for approximate nearest neighbor search in high-dimensional vector spaces homepage:            https://github.com/ocramz/rp-tree@@ -11,6 +11,7 @@ category:            Data Mining, Data Structures, Machine Learning, Data build-type:          Simple extra-source-files:  README.md+                     Changelog.md cabal-version:       >=1.10 tested-with:         GHC == 8.10.4 @@ -19,7 +20,7 @@   ghc-options:         -Wall   hs-source-dirs:      src   exposed-modules:     Data.RPTree-+                       Data.RPTree.Internal.Testing   other-modules:       Data.RPTree.Internal                        Data.RPTree.Gen                        Data.RPTree.Draw@@ -41,8 +42,11 @@                      -- , ulid                      , vector >= 0.12.1.2                      , vector-algorithms-                       -- debug+                     -- -- DEBUG+                     , benchpress                      , hspec+                     , mnist-idx-conduit+                     , splitmix >= 0.1.0.3  test-suite spec   default-language:    Haskell2010@@ -56,16 +60,37 @@                      , QuickCheck                      , splitmix-distributions +benchmark bench-time+  default-language:    Haskell2010+  ghc-options:         -threaded -O2+  type:                exitcode-stdio-1.0+  hs-source-dirs:      bench/time+  main-is:             Main.hs+  build-depends:       base+                     , benchpress+                     , conduit+                     , deepseq >= 1.4.4.0+                     , exceptions+                     , mnist-idx-conduit+                     , mtl+                     , rp-tree+                     , splitmix >= 0.1.0.3+                     , splitmix-distributions+                     , transformers+                     , vector+ executable rp-tree   default-language:    Haskell2010-  ghc-options:         -threaded -rtsopts -with-rtsopts=-N+  ghc-options:         -threaded -O2   hs-source-dirs:      app   main-is:             Main.hs   build-depends:       base                      , conduit                      , containers                      , exceptions+                     , mnist-idx-conduit                      , rp-tree+                     , splitmix                      , splitmix-distributions                      , transformers                      , vector
src/Data/RPTree.hs view
@@ -15,40 +15,45 @@ -} module Data.RPTree (   -- * Construction-  forest+  tree, forest   -- * Query   , knn-  -- , nearest+  -- * I/O+  , serialiseRPForest+  , deserialiseRPForest   -- * Validation-  , recall+  , recallWith   -- * Access   , levels, points, leaves, candidates   -- * Types   -- ** RPTree   , RPTree, RPForest-  -- -- *** internal-  -- , RPT-  -- -- ** RT-  -- , RT   -- *-  , SVector, fromListSv-  , DVector, fromListDv-  -- * inner product+  , SVector, fromListSv, fromVectorSv+  , DVector, fromListDv, fromVectorDv+  -- * Vector space types   , Inner(..), Scale(..)-  --   -- ** helpers for implementing Inner instances-  --   -- *** inner product-  -- , innerSS, innerSD-  --   -- *** L2 distance-  -- , metricSSL2, metricSDL2+    -- ** helpers for implementing Inner instances+    -- *** inner product+  , innerSS, innerSD, innerDD+    -- *** L2 distance+  , metricSSL2, metricSDL2+  -- *** Scale+  , scaleS, scaleD   -- * Conduit   , dataSource   -- * Random generation   -- ** vector   , sparse, dense+  , normal2+   -- * Rendering   , draw   -- * CSV   , writeCsv+  -- * Testing+  , randSeed, BenchConfig(..), normalSparse2+  , liftC   ) where  import Control.Monad (replicateM)@@ -83,10 +88,10 @@ -- vector-algorithms import qualified Data.Vector.Algorithms.Merge as V (sortBy) -import Data.RPTree.Conduit (forest, dataSource)-import Data.RPTree.Gen (sparse, dense)-import Data.RPTree.Internal (RPTree(..), RPForest, RPT(..), levels, points, leaves, RT(..), Inner(..), Scale(..), (/.), innerSD, innerSS, metricSSL2, metricSDL2, SVector(..), fromListSv, DVector(..), fromListDv, partitionAtMedian, Margin, getMargin, sortByVG)-+import Data.RPTree.Conduit (tree, forest, dataSource, liftC)+import Data.RPTree.Gen (sparse, dense, normal2, normalSparse2)+import Data.RPTree.Internal (RPTree(..), RPForest, RPT(..), levels, points, leaves, RT(..), Inner(..), Scale(..), scaleS, scaleD, (/.), innerDD, innerSD, innerSS, metricSSL2, metricSDL2, SVector(..), fromListSv, fromVectorSv, DVector(..), fromListDv, fromVectorDv, partitionAtMedian, Margin, getMargin, sortByVG, serialiseRPForest, deserialiseRPForest)+import Data.RPTree.Internal.Testing (BenchConfig(..), randSeed) import Data.RPTree.Draw (draw, writeCsv)  @@ -103,32 +108,37 @@   -- | average recall-at-k, computed over a set of trees-recall :: (Inner u v, Inner SVector v, VU.Unbox a, Ord a,-            Ord (u a), Floating a) =>-          RPForest a (V.Vector (u a))-       -> Int -- ^ k : number of nearest neighbors to consider-       -> v a -- ^ query point-       -> Double-recall tt k q = sum rs / fromIntegral n+recallWith :: (Inner SVector v, VU.Unbox a, Fractional a, Ord a, Ord d, Ord p) =>+              (p -> v a -> d)+           -> RPForest a (V.Vector p)+           -> Int -- ^ k : number of nearest neighbors to consider+           -> v a -- ^ query point+           -> a+recallWith distf tt k q = sum rs / fromIntegral n   where-    rs = fmap (\t -> recall1 t k q) tt+    rs = fmap (\t -> recallWith1 distf t k q) tt     n = length tt -recall1 :: (Inner SVector v, Inner u v, VU.Unbox a, Ord a, Ord (u a), Floating a) =>-          RPTree a (V.Vector (u a))-       -> Int -- ^ k : number of nearest neighbors to consider-       -> v a  -- ^ query point-       -> Double-recall1 = recallWith metricL2+-- -- | Recall computed wrt the Euclidean distance+-- recallEuclid :: (Inner SVector v, Inner u v, VU.Unbox a, Ord a, Ord (u a), Floating a) =>+--                 RPTree a (V.Vector (u a))+--              -> Int -- ^ k : number of nearest neighbors to consider+--              -> v a  -- ^ query point+--              -> Double+-- recallEuclid = recallWith metricL2 -recallWith :: (Fractional a1, Inner SVector v, Ord d, VU.Unbox d,+recallWith1 :: (Fractional a1, Inner SVector v, Ord d, VU.Unbox d,                 Num d, Ord a3, Ord a2) =>-              (a2 -> v d -> a3) -> RPTree d (V.Vector a2) -> Int -> v d -> a1-recallWith distf tt k q = fromIntegral (length aintk) / fromIntegral k+              (a2 -> v d -> a3) -- ^ distance function+           -> RPTree d (V.Vector a2)+           -> Int -- ^ k : number of nearest neighbors to consider+           -> v d -- ^ query point+           -> a1+recallWith1 distf tt k q = fromIntegral (length aintk) / fromIntegral k   where     xs = points tt     dists = sortBy (comparing snd) $ toList $ fmap (\x -> (x, x `distf` q)) xs-    kk = S.fromList $ map fst $ take k dists+    kk = S.fromList $ map fst $ take k dists -- first k nn's     aa = set $ candidates tt q     aintk = aa `S.intersection` kk @@ -137,6 +147,7 @@   +{-# SCC candidates #-} -- | Retrieve points nearest to the query -- -- in case of a narrow margin, collect both branches of the tree@@ -153,11 +164,13 @@         r = rvs VG.! ixLev         proj = r `inner` x         i' = succ ixLev+        dl = abs (mglo - proj) -- left margin+        dr = abs (mghi - proj) -- right margin       if | proj < thr &&-           mglo > mghi -> go i' ltree <> go i' rtree+           dl > dr -> go i' ltree <> go i' rtree          | proj < thr  -> go i' ltree          | proj > thr &&-           mglo < mghi -> go i' ltree <> go i' rtree+           dl < dr -> go i' ltree <> go i' rtree          | otherwise   -> go i' rtree  
src/Data/RPTree/Conduit.hs view
@@ -1,23 +1,28 @@ {-# language DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-}+{-# language BangPatterns #-} {-# options_ghc -Wno-unused-imports #-} {-# options_ghc -Wno-unused-top-binds #-}-module Data.RPTree.Conduit (+module Data.RPTree.Conduit+  (tree,   forest   -- ** helpers   , dataSource-  ) where+  , liftC+  )+where  import Control.Monad (replicateM)+import Data.Functor (void) import GHC.Word (Word64)  -- conduit-import qualified Data.Conduit as C (ConduitT, runConduit, yield, await)+import qualified Data.Conduit as C (ConduitT, runConduit, yield, await, transPipe) import Data.Conduit ((.|))-import qualified Data.Conduit.Combinators as C (map, mapM, scanl, scanlM, last, print)-import qualified Data.Conduit.List as C (chunksOf, unfold, unfoldM)+import qualified Data.Conduit.Combinators as C (map, mapM, last, scanl, print, foldl)+import qualified Data.Conduit.List as C (chunksOf, unfold, unfoldM, mapAccum) -- containers-import qualified Data.IntMap as IM (IntMap, fromList, insert, lookup, map, mapWithKey, traverseWithKey, foldlWithKey, foldrWithKey, intersectionWith)+import qualified Data.IntMap.Strict as IM (IntMap, fromList, insert, lookup, map, mapWithKey, traverseWithKey, foldlWithKey, foldrWithKey, intersectionWith) -- exceptions import Control.Monad.Catch (MonadThrow(..)) -- mtl@@ -37,19 +42,8 @@ import Data.RPTree.Internal (RPTree(..), RPForest, RPT(..), levels, points, Inner(..), innerSD, innerSS, metricSSL2, metricSDL2, SVector(..), fromListSv, DVector(..), fromListDv, partitionAtMedian, RPTError(..))  ----- | Source of random data points-dataSource :: (Monad m) =>-              Int -- ^ number of vectors to generate-           -> GenT m a -- ^ random generator for the vector components-           -> C.ConduitT i a (GenT m) ()-dataSource n gg = flip C.unfoldM 0 $ \i -> do-  if i == n-    then pure Nothing-    else do-      x <- gg-      pure $ Just (x, i + 1)+liftC :: (Monad m, MonadTrans t) => C.ConduitT i o m r -> C.ConduitT i o (t m) r+liftC = C.transPipe lift  -- | Populate a tree from a data stream --@@ -62,33 +56,34 @@ -- * bounded : we wait until the end of the stream to produce a result -- -- Throws 'EmptyResult' if the conduit is empty-tree :: (MonadThrow m, Inner SVector v) =>-            Word64 -- ^ random seed-         -> Int -- ^ max tree depth-         -> Int -- ^ min leaf size-         -> Int -- ^ data chunk size-         -> Double -- ^ nonzero density of projection vectors-         -> Int -- ^ dimension of projection vectors-         -> C.ConduitT () (v Double) m () -- ^ data source-         -> m (RPTree Double (V.Vector (v Double)))+tree :: (Monad m, Inner SVector v) =>+        Word64 -- ^ random seed+     -> Int -- ^ max tree depth+     -> Int -- ^ min leaf size+     -> Int -- ^ data chunk size+     -> Double -- ^ nonzero density of projection vectors+     -> Int -- ^ dimension of projection vectors+     -> C.ConduitT () (v Double) m () -- ^ data source+     -> m (RPTree Double (V.Vector (v Double))) tree seed maxDepth minLeaf n pnz dim src = do   let     rvs = sample seed $ V.replicateM maxDepth (sparse pnz dim stdNormal)-  tm <- C.runConduit $ src .|-                       insertC maxDepth minLeaf n rvs .|-                       C.last-  case tm of-    Just t -> pure $ RPTree rvs t-    _ -> throwM $ EmptyResult "treeSink"+  t <- C.runConduit $ src .|+                      insertC maxDepth minLeaf n rvs+  pure $ RPTree rvs t ++++ -- | Incrementally build a tree insertC :: (Monad m, Inner u v, Ord d, VU.Unbox d, Fractional d) =>            Int -- ^ max tree depth         -> Int -- ^ min leaf size         -> Int -- ^ data chunk size         -> V.Vector (u d) -- ^ random projection vectors-        -> C.ConduitT (v d) (RPT d (V.Vector (v d))) m ()-insertC maxDepth minLeaf n rvs = chunked n z (insert maxDepth minLeaf rvs)+        -> C.ConduitT (v d) o m (RPT d (V.Vector (v d))) +insertC maxDepth minLeaf n rvs = chunkedAccum n z (insert maxDepth minLeaf rvs)   where     z = Tip mempty @@ -105,35 +100,30 @@ -- * bounded : we wait until the end of the stream to produce a result -- -- Throws 'EmptyResult' if the conduit is empty-forest :: (MonadThrow m, Inner SVector v) =>-                 Word64 -- ^ random seed-              -> Int -- ^ max tree depth-              -> Int -- ^ min leaf size-              -> Int -- ^ number of trees-              -> Int -- ^ data chunk size-              -> Double -- ^ nonzero density of projection vectors-              -> Int -- ^ dimension of projection vectors-              -> C.ConduitT () (v Double) m () -- ^ data source-              -> m (RPForest Double (V.Vector (v Double)))+forest :: (Monad m, Inner SVector v) =>+          Word64 -- ^ random seed+       -> Int -- ^ max tree depth+       -> Int -- ^ min leaf size+       -> Int -- ^ number of trees+       -> Int -- ^ data chunk size+       -> Double -- ^ nonzero density of projection vectors+       -> Int -- ^ dimension of projection vectors+       -> C.ConduitT () (v Double) m () -- ^ data source+       -> m (RPForest Double (V.Vector (v Double))) forest seed maxd minl ntrees chunksize pnz dim src = do   let     rvss = sample seed $ do       rvs <- replicateM ntrees $ V.replicateM maxd (sparse pnz dim stdNormal)       pure $ IM.fromList $ zip [0 .. ] rvs-  tm <- C.runConduit $ src .|-                       insertMultiC maxd minl chunksize rvss .|-                       C.last-  case tm of-    Just ts -> do-      let-        res = IM.intersectionWith RPTree rvss ts-      pure res-    _ -> throwM $ EmptyResult "forestSink"+  ts <- C.runConduit $ src .|+                       insertMultiC maxd minl chunksize rvss+  pure $ IM.intersectionWith RPTree rvss ts     + insertMultiC :: (Monad m, Ord d, Inner u v, VU.Unbox d, Fractional d, VG.Vector v1 (u d)) =>                 Int  -- ^ max tree depth              -> Int -- ^ min leaf size@@ -141,15 +131,16 @@              -> IM.IntMap (v1 (u d)) -- one entry per tree              -> C.ConduitT                 (v d)-                (IM.IntMap (RPT d (V.Vector (v d))))+                o                 m-                ()-insertMultiC maxd minl n rvss = chunked n im0 (insertMulti maxd minl rvss)+                (IM.IntMap (RPT d (V.Vector (v d))))+insertMultiC maxd minl n rvss = chunkedAccum n im0 (insertMulti maxd minl rvss)   where     im0 = IM.map (const z) rvss     z = Tip mempty  +{-# SCC insertMulti #-} insertMulti :: (Ord d, Inner u v, VU.Unbox d, Fractional d, VG.Vector v1 (u d)) =>                Int             -> Int@@ -158,10 +149,11 @@             -> V.Vector (v d) -- ^ data chunk             -> IM.IntMap (RPT d (V.Vector (v d))) insertMulti maxd minl rvss tacc xs =-  flip IM.mapWithKey tacc $ \i t -> case IM.lookup i rvss of-                                      Just rvs -> insert maxd minl rvs t xs+  flip IM.mapWithKey tacc $ \ !i !t -> case IM.lookup i rvss of+                                      Just !rvs -> insert maxd minl rvs t xs                                       _        -> t +{-# SCC insert #-} insert :: (VG.Vector v1 (u d), Ord d, Inner u v, VU.Unbox d, Fractional d) =>           Int -- ^ max tree depth        -> Int -- ^ min leaf size@@ -172,7 +164,7 @@ insert maxDepth minLeaf rvs = loop 0   where     z = Tip mempty-    loop ixLev tt xs =+    loop ixLev !tt xs =       let         r = rvs VG.! ixLev       in@@ -203,13 +195,60 @@                 in Bin thr margin tl tr  -chunked :: (Monad m) =>-           Int -- ^ chunk size-        -> t -- ^ initial tree-        -> (t -> V.Vector a -> t)-        -> C.ConduitT a t m ()-chunked n z f = do C.chunksOf n .|+-- | Aggregate the input stream in chunks of a given size (semantics of 'C.chunksOf'), and fold over the resulting stream building up an accumulator structure (e.g. a tree)+chunkedAccum :: (Monad m) =>+                Int -- ^ chunk size+             -> t -- ^ initial accumulator state+             -> (t -> V.Vector a -> t)+             -> C.ConduitT a o m t+chunkedAccum n z f = C.chunksOf n .|                      C.map V.fromList .|-                     C.scanl f z -- .|+                     C.foldl f z +-- | Source of random data points+dataSource :: (Monad m) =>+              Int -- ^ number of vectors to generate+           -> GenT m a -- ^ random generator for the vector components+           -> C.ConduitT i a (GenT m) ()+dataSource n gg = flip C.unfoldM 0 $ \i -> do+  if i == n+    then pure Nothing+    else do+      x <- gg+      pure $ Just (x, i + 1) ++++-- -- sinks++-- tree' :: (Monad m, Inner SVector v) =>+--         Word64 -- ^ random seed+--      -> Int -- ^ max tree depth+--      -> Int -- ^ min leaf size+--      -> Int -- ^ data chunk size+--      -> Double -- ^ nonzero density of projection vectors+--      -> Int -- ^ dimension of projection vectors+--       -> C.ConduitT (v Double) o m (RPTree Double (V.Vector (v Double)))+-- tree' seed maxDepth minLeaf n pnz dim = do+--   let+--     rvs = sample seed $ V.replicateM maxDepth (sparse pnz dim stdNormal)+--   t <- insertC maxDepth minLeaf n rvs+--   pure $ RPTree rvs t++-- forest' :: (Monad m, Inner SVector v) =>+--            Word64 -- ^ random seed+--         -> Int -- ^ max tree depth+--         -> Int -- ^ min leaf size+--         -> Int -- ^ number of trees+--         -> Int -- ^ data chunk size+--         -> Double -- ^ nonzero density of projection vectors+--         -> Int -- ^ dimension of projection vectors+--         -> C.ConduitT (v Double) o m (IM.IntMap (RPTree Double (V.Vector (v Double))))+-- forest' seed maxd minl ntrees chunksize pnz dim = do+--   let+--     rvss = sample seed $ do+--       rvs <- replicateM ntrees $ V.replicateM maxd (sparse pnz dim stdNormal)+--       pure $ IM.fromList $ zip [0 .. ] rvs+--   ts <- insertMultiC maxd minl chunksize rvss+--   pure $ IM.intersectionWith RPTree rvss ts
src/Data/RPTree/Gen.hs view
@@ -13,7 +13,7 @@ import Control.Monad.Trans.Class (MonadTrans(..)) import Control.Monad.State (MonadState(..), modify) -- splitmix-distribitions-import System.Random.SplitMix.Distributions (Gen, GenT, stdUniform, bernoulli)+import System.Random.SplitMix.Distributions (Gen, GenT, stdUniform, bernoulli, exponential, normal, discrete, categorical) -- transformers import Control.Monad.Trans.State (StateT(..), runStateT, evalStateT, State, runState, evalState) -- vector@@ -96,6 +96,47 @@   let ix = floor (fromIntegral k * u)   pure $ IM.insert ix y imm +++++++-- mixtures++mixtureN :: Monad m => [(Double, GenT m b)] -> GenT m b+mixtureN pgs = go+  where+    (ps, gs) = unzip pgs+    go = do+      miix <- categorical ps+      case miix of+        Nothing -> gs !! 0+        Just i  -> do+          let p = gs !! i+          p+++normalSparse2 :: Monad m => Double -> Int -> GenT m (SVector Double)+normalSparse2 pnz d = do+  b <- bernoulli 0.5+  if b+    then sparse pnz d (normal 0 0.5)+    else sparse pnz d (normal 2 0.5)++normalDense2 :: Monad m => Int -> GenT m (DVector Double)+normalDense2 d = do+  b <- bernoulli 0.5+  if b+    then dense d (normal 0 0.5)+    else dense d (normal 2 0.5)++normal2 :: (Monad m) => GenT m (DVector Double)+normal2 = do+  b <- bernoulli 0.5+  if b+    then dense 2 $ normal 0 0.5+    else dense 2 $ normal 2 0.5   -- | Generate a sparse random vector with a given nonzero density and components sampled from the supplied random generator
src/Data/RPTree/Internal.hs view
@@ -10,6 +10,7 @@ {-# language MultiParamTypeClasses #-} {-# language GeneralizedNewtypeDeriving #-} {-# language TemplateHaskell #-}+{-# LANGUAGE BangPatterns        #-} {-# options_ghc -Wno-unused-imports #-} module Data.RPTree.Internal where @@ -17,7 +18,7 @@ import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.ST (runST) import Data.Function ((&))-import Data.Foldable (fold, foldl')+import Data.Foldable (fold, foldl', toList) import Data.Functor.Identity (Identity(..)) import Data.List (nub) import Data.Monoid (Sum(..))@@ -26,8 +27,10 @@ import Data.Typeable (Typeable) import GHC.Generics (Generic) +-- bytestring+import qualified Data.ByteString.Lazy as LBS (ByteString, toStrict, fromStrict) -- containers-import qualified Data.IntMap as IM (IntMap)+import qualified Data.IntMap.Strict as IM (IntMap, fromList) -- deepseq import Control.DeepSeq (NFData(..)) -- microlens@@ -36,7 +39,7 @@ -- mtl import Control.Monad.State (MonadState(..), modify) -- serialise-import Codec.Serialise (Serialise(..))+import Codec.Serialise (Serialise(..), serialise, deserialiseOrFail) -- transformers import Control.Monad.Trans.State (StateT(..), runStateT, evalStateT, State, runState, evalState) -- vector@@ -47,6 +50,13 @@ -- vector-algorithms import qualified Data.Vector.Algorithms.Merge as V (sortBy) +-- | Pair a datum with a vector embedding+data Embed v e a = Embed {+  eEmbed :: !(v e) -- ^ the embedding is a vector+  , eData :: !a+                       } deriving (Eq, Ord, Show, Generic, Functor)+instance (NFData (v e), NFData a) => NFData (Embed v e a)+instance (Serialise (v e), Serialise a) => Serialise (Embed v e a)  -- | Exceptions data RPTError =@@ -59,8 +69,8 @@  -- | Bounds around the cutting plane data Margin a = Margin {-  cMarginLow :: Max a -- ^ lower bound on the cut point-  , cMarginHigh :: Min a -- ^ upper bound+  cMarginLow :: !(Max a) -- ^ lower bound on the cut point+  , cMarginHigh :: !(Min a) -- ^ upper bound                    } deriving (Eq, Show, Generic) instance (Serialise a) => Serialise (Margin a) getMargin :: Margin a -> (a, a)@@ -72,7 +82,8 @@   -- | Sparse vectors with unboxed components-data SVector a = SV { svDim :: !Int, svVec :: VU.Vector (Int, a) } deriving (Eq, Ord, Generic)+data SVector a = SV { svDim :: {-# UNPACK #-} !Int+                    , svVec :: VU.Vector (Int, a) } deriving (Eq, Ord, Generic) instance (VU.Unbox a, Serialise a) => Serialise (SVector a) instance (VU.Unbox a, Show a) => Show (SVector a) where   show (SV n vv) = unwords ["SV", show n, show (VU.toList vv)]@@ -80,15 +91,29 @@  fromListSv :: VU.Unbox a => Int -> [(Int, a)] -> SVector a fromListSv n ll = SV n $ VU.fromList ll+-- | (Unsafe) Pack a 'SVector' from its vector dimension and components+--+-- Note : the relevant invariants are not checked :+--+-- * vector components are _assumed_ to be in increasing order+--+-- * vector dimension is larger than any component index+fromVectorSv :: Int -- ^ vector dimension+             -> VU.Vector (Int, a) -- ^ vector components (in increasing order)+             -> SVector a+fromVectorSv = SV  -- | Dense vectors with unboxed components newtype DVector a = DV { dvVec :: VU.Vector a } deriving (Eq, Ord, Generic) instance (VU.Unbox a, Serialise a) => Serialise (DVector a) instance (VU.Unbox a, Show a) => Show (DVector a) where   show (DV vv) = unwords ["DV", show (VU.toList vv)]+instance NFData (DVector a)  fromListDv :: VU.Unbox a => [a] -> DVector a fromListDv ll = DV $ VU.fromList ll+fromVectorDv :: VU.Vector a -> DVector a+fromVectorDv = DV toListDv :: (VU.Unbox a) => DVector a -> [a] toListDv (DV v) = VU.toList v @@ -108,11 +133,11 @@ -- one projection vector per tree level (as suggested in https://www.cs.helsinki.fi/u/ttonteri/pub/bigdata2016.pdf ) data RPT d a =   Bin {-  _rpThreshold :: !d-  , _rpMargin :: !(Margin d)+  _rpThreshold ::  !d+  , _rpMargin :: {-# UNPACK #-} !(Margin d)   , _rpL :: !(RPT d a)   , _rpR :: !(RPT d a) }-  | Tip { _rpData :: a }+  | Tip { _rpData :: !a }   deriving (Eq, Show, Generic, Functor, Foldable, Traversable) instance (Serialise a, Serialise d) => Serialise (RPT d a) makeLensesFor [("_rpData", "rpData")] ''RPT@@ -127,14 +152,31 @@ -- One projection vector per tree level (as suggested in https://www.cs.helsinki.fi/u/ttonteri/pub/bigdata2016.pdf ) data RPTree d a = RPTree {   _rpVectors :: V.Vector (SVector d) -- ^ one random projection vector per tree level-  , _rpTree :: RPT d a+  , _rpTree :: !(RPT d a)                          } deriving (Eq, Show, Functor, Foldable, Traversable, Generic) instance (Serialise d, Serialise a, VU.Unbox d) => Serialise (RPTree d a) makeLensesFor [("_rpTree", "rpTree")] ''RPTree instance (NFData a, NFData d) => NFData (RPTree d a) +-- | A random projection forest is an ordered set of 'RPTree's+--+-- This supports efficient updates of the ensemble in the streaming/online setting. type RPForest d a = IM.IntMap (RPTree d a) +-- | Serialise each tree in the 'RPForest' as a separate bytestring+serialiseRPForest :: (Serialise d, Serialise a, VU.Unbox d) =>+                     RPForest d a+                  -> [LBS.ByteString] -- ^ the order is undefined+serialiseRPForest tt = serialise `map` toList tt++-- | Deserialise each tree in the 'RPForest' from a separate bytestring and reconstruct+deserialiseRPForest :: (Serialise d, Serialise a, VU.Unbox d) =>+                       [LBS.ByteString]+                    -> Either String (RPForest d a) -- ^ the order is undefined+deserialiseRPForest bss = case deserialiseOrFail `traverse` bss of+  Left e -> Left (show e)+  Right xs -> Right $ IM.fromList $ zip [0 ..] xs+ rpTreeData :: Traversal' (RPTree d a) a rpTreeData = rpTree . rpData @@ -152,6 +194,7 @@ -- -- points in 2d -- data P a = P !a !a deriving (Eq, Show) +-- | Scale a vector class Scale v where   (.*) :: (VU.Unbox a, Num a) => a -> v a -> v a instance Scale SVector where@@ -271,7 +314,7 @@ -- | Vector sum sumSS :: (VG.Vector u (Int, a), VG.Vector v (Int, a), VU.Unbox a, Num a) =>          u (Int, a) -> v (Int, a) -> u (Int, a)-sumSS = binSS (+) 0 +sumSS = binSS (+) 0  -- | Vector difference diffSD :: (VG.Vector u (Int, a), VG.Vector v a, VU.Unbox a, Num a) =>@@ -302,7 +345,7 @@             GT -> Just ((ir, f z  xr), (i1     , succ i2))  -+-- FIXME the return type of a sparse-dense binary operation depends on the operator itself (S * D = S , S + D = D ), so 'binSD' must be changed binSD :: (VG.Vector u (Int, a), VG.Vector v a, VU.Unbox a) =>          (a -> a -> a) -> u (Int, a) -> v a -> u (Int, a) binSD f vv1 vv2 = VG.unfoldr go 0@@ -318,6 +361,7 @@             y = f xl xr  +{-# SCC partitionAtMedian #-} -- | Partition the data wrt the median value of the inner product partitionAtMedian :: (Ord a, Inner u v, VU.Unbox a, Fractional a) =>                      u a -- ^ projection vector@@ -329,9 +373,7 @@     -- (pjl, pjr) = (VG.head inns, VG.last inns) -- (min, max) inner product values     (mgl, mgr) = (inns VG.! (nh - 1), inns VG.! (nh + 1))     margin = Margin (Max mgl) (Min mgr)-    -- marginL = mgl / (pjr - pjl) -- lower bound of margin, normalized to range-    -- marginR = mgr / (pjr - pjl) -- upper bound of margin, normalized to range-    thr = inns VG.! nh -- inner product threshold+    thr = inns VG.! nh -- inner product threshold,  mgl < thr < mgr     n = VG.length xs -- total data size     nh = n `div` 2 -- size of left partition     projs = sortByVG snd $ VG.map (\x -> (x, r `inner` x)) xs
+ src/Data/RPTree/Internal/Testing.hs view
@@ -0,0 +1,50 @@+{-# options_ghc -Wno-unused-imports #-}+module Data.RPTree.Internal.Testing where++import Control.Monad.IO.Class (MonadIO(..))+import GHC.Word (Word8, Word64)++-- conduit+import qualified Data.Conduit as C (ConduitT, runConduit, yield, await, transPipe)+import Data.Conduit ((.|))+import qualified Data.Conduit.Combinators as C (map, mapM, scanl, scanlM, last, print)+import qualified Data.Conduit.List as C (chunksOf, unfold, unfoldM)+-- splitmix+import System.Random.SplitMix (initSMGen, unseedSMGen)+-- splitmix-distributions+import System.Random.SplitMix.Distributions (GenT)++import Data.RPTree.Internal (SVector, fromListSv, DVector, fromListDv)+import Data.RPTree.Gen (dense, sparse, normal2, normalSparse2, normalDense2)+import Data.RPTree.Conduit (dataSource)++data BenchConfig = BenchConfig {+  bcDescription :: String+  , bcMaxTreeDepth :: Int+  , bcMinLeafSize :: Int+  , bcNumTrees :: Int+  , bcChunkSize :: Int+  , bcNZDensity :: Double+  , bcVectorDim :: Int+  , bcDataSize :: Int+  , bcNumQueryPoints :: Int+                               } deriving (Show)++randSeed :: MonadIO m => m Word64+randSeed = liftIO (fst . unseedSMGen <$> initSMGen)+++-- | binary mixture of isotropic Gaussian rvs+datD :: Monad m =>+        Int -- ^ number of data points+     -> Int -- ^ vector dimension+     -> C.ConduitT i (DVector Double) (GenT m) ()+datD n d = dataSource n $ normalDense2 d++-- | binary mixture of isotropic Gaussian rvs with sparse components+datS :: Monad m =>+        Int -- ^ number of data points+     -> Int -- ^ vector dimension+     -> Double -- ^ nonzero density+     -> C.ConduitT i (SVector Double) (GenT m) ()+datS n d pnz = dataSource n $ normalSparse2 pnz d