packages feed

data-sketches-core-0.2.0.0: src/DataSketches/Quantiles/KLL/Internal.hs

{-# LANGUAGE ForeignFunctionInterface #-}
module DataSketches.Quantiles.KLL.Internal
  ( KllSketch
  , mkKllSketch
  , kllInsert
  , kllInsertBatch
  , kllCount
  , kllMinimum
  , kllMaximum
  , kllQuantile
  , kllRank
  , kllMerge
  , kllRetainedItems
  , kllIsEmpty
  ) where

import Control.DeepSeq (NFData(..))
import Control.Monad (unless)
import Control.Monad.Primitive
import Data.IORef
import Data.Word
import qualified Data.Vector.Storable as VS
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Ptr

-- | Opaque C struct, managed by a ForeignPtr with a destructor.
-- The entire sketch lives in C-allocated memory: no GC pressure,
-- no monadic bind overhead, no dictionary passing.
newtype KllSketch s = KllSketch (ForeignPtr ())

instance NFData (KllSketch s) where rnf !_ = ()

foreign import ccall unsafe "kll_new"      c_kll_new      :: Word32 -> Word64 -> IO (Ptr ())
foreign import ccall unsafe "&kll_free"    c_kll_free     :: FunPtr (Ptr () -> IO ())
foreign import ccall unsafe "kll_insert"       c_kll_insert       :: Ptr () -> CDouble -> IO ()
foreign import ccall unsafe "kll_insert_batch" c_kll_insert_batch :: Ptr () -> Ptr CDouble -> CInt -> IO ()
foreign import ccall unsafe "kll_count"    c_kll_count    :: Ptr () -> IO Word64
foreign import ccall unsafe "kll_min"      c_kll_min      :: Ptr () -> IO CDouble
foreign import ccall unsafe "kll_max"      c_kll_max      :: Ptr () -> IO CDouble
foreign import ccall unsafe "kll_is_empty" c_kll_is_empty :: Ptr () -> IO CInt
foreign import ccall unsafe "kll_retained" c_kll_retained :: Ptr () -> IO CInt
foreign import ccall unsafe "kll_rank"     c_kll_rank     :: Ptr () -> CDouble -> IO CDouble
foreign import ccall unsafe "kll_quantile" c_kll_quantile :: Ptr () -> CDouble -> IO CDouble
foreign import ccall unsafe "kll_merge"    c_kll_merge    :: Ptr () -> Ptr () -> IO ()

withSketch :: KllSketch s -> (Ptr () -> IO a) -> IO a
withSketch (KllSketch fp) = withForeignPtr fp
{-# INLINE withSketch #-}

mkKllSketch :: PrimMonad m => Word32 -> m (KllSketch (PrimState m))
mkKllSketch k = unsafePrimToPrim $ do
  unless (k >= 8) $ error "KLL sketch: k must be >= 8"
  ptr <- c_kll_new k 12345
  fp <- newForeignPtr c_kll_free ptr
  pure (KllSketch fp)

kllInsert :: PrimMonad m => KllSketch (PrimState m) -> Double -> m ()
kllInsert sk val = unsafePrimToPrim $ withSketch sk $ \p ->
  c_kll_insert p (realToFrac val)
{-# INLINE kllInsert #-}

kllInsertBatch :: PrimMonad m => KllSketch (PrimState m) -> VS.Vector Double -> m ()
kllInsertBatch sk vals = unsafePrimToPrim $ withSketch sk $ \p ->
  VS.unsafeWith (VS.unsafeCast vals) $ \arr ->
    c_kll_insert_batch p arr (fromIntegral $ VS.length vals)

kllCount :: PrimMonad m => KllSketch (PrimState m) -> m Word64
kllCount sk = unsafePrimToPrim $ withSketch sk c_kll_count
{-# INLINE kllCount #-}

kllMinimum :: PrimMonad m => KllSketch (PrimState m) -> m Double
kllMinimum sk = unsafePrimToPrim $ withSketch sk $ fmap realToFrac . c_kll_min
{-# INLINE kllMinimum #-}

kllMaximum :: PrimMonad m => KllSketch (PrimState m) -> m Double
kllMaximum sk = unsafePrimToPrim $ withSketch sk $ fmap realToFrac . c_kll_max
{-# INLINE kllMaximum #-}

kllIsEmpty :: PrimMonad m => KllSketch (PrimState m) -> m Bool
kllIsEmpty sk = unsafePrimToPrim $ withSketch sk $ fmap (/= 0) . c_kll_is_empty
{-# INLINE kllIsEmpty #-}

kllRetainedItems :: PrimMonad m => KllSketch (PrimState m) -> m Int
kllRetainedItems sk = unsafePrimToPrim $ withSketch sk $ fmap fromIntegral . c_kll_retained
{-# INLINE kllRetainedItems #-}

kllRank :: PrimMonad m => KllSketch (PrimState m) -> Double -> m Double
kllRank sk value = unsafePrimToPrim $ withSketch sk $ \p ->
  realToFrac <$> c_kll_rank p (realToFrac value)
{-# INLINE kllRank #-}

kllQuantile :: PrimMonad m => KllSketch (PrimState m) -> Double -> m Double
kllQuantile sk normRank = unsafePrimToPrim $ withSketch sk $ \p ->
  realToFrac <$> c_kll_quantile p (realToFrac normRank)
{-# INLINE kllQuantile #-}

kllMerge :: PrimMonad m => KllSketch (PrimState m) -> KllSketch (PrimState m) -> m ()
kllMerge dst src = unsafePrimToPrim $
  withSketch dst $ \pd ->
    withSketch src $ \ps ->
      c_kll_merge pd ps