packages feed

rrb-vector-0.2.2.1: src/Data/RRBVector/Internal/Debug.hs

{-# LANGUAGE PatternSynonyms #-}

{- |
This module contains some debug utilities. It should only be used for debugging/testing purposes.
-}

module Data.RRBVector.Internal.Debug
    ( showTree
    , fromListUnbalanced
    , pattern Empty, pattern Root
    , Tree, Shift
    , pattern Balanced, pattern Unbalanced, pattern Leaf
    , Invariant, valid
    ) where

import Control.Monad.ST (runST)
import Data.Bits (shiftL)
import Data.Foldable (foldl', toList, traverse_)
import Data.List (intercalate)
import Data.Primitive.PrimArray (PrimArray, primArrayToList, indexPrimArray, sizeofPrimArray)

import Data.RRBVector.Internal hiding (Empty, Root, Balanced, Unbalanced, Leaf)
import qualified Data.RRBVector.Internal as RRB
import Data.RRBVector.Internal.Array (Array)
import qualified Data.RRBVector.Internal.Array as A
import qualified Data.RRBVector.Internal.Buffer as Buffer

-- | \(O(n)\). Show the underlying tree of a vector.
showTree :: (Show a) => Vector a -> String
showTree Empty = "Empty"
showTree (Root size sh tree) = "Root {size = " ++ show size ++ ", shift = " ++ show sh ++ ", tree = " ++ debugShowTree tree ++ "}"
  where
    debugShowTree (Balanced arr) = "Balanced " ++ debugShowArray arr
    debugShowTree (Unbalanced arr sizes) = "Unbalanced " ++ debugShowArray arr ++ " (" ++ show (primArrayToList sizes) ++ ")"
    debugShowTree (Leaf arr) = "Leaf " ++ show (toList arr)

    debugShowArray arr = "[" ++ intercalate "," (fmap debugShowTree (toList arr)) ++ "]"

-- | \(O(n)\). Create a new unbalanced vector from a list.
--
-- Note that it is not possbible to create an invalid 'Vector' with this function.
fromListUnbalanced :: [a] -> Vector a
fromListUnbalanced [] = RRB.Empty
fromListUnbalanced [x] = singleton x
fromListUnbalanced ls = case nodes RRB.Leaf ls of
    [tree] -> RRB.Root (treeSize 0 tree) 0 tree -- tree is a single leaf
    ls' -> iterateNodes blockShift ls'
  where
    n = blockSize - 1

    nodes f trees = runST $ do
        buffer <- Buffer.new n
        let loop [] = do
                result <- Buffer.get buffer
                pure [f result]
            loop (t : ts) = do
                size <- Buffer.size buffer
                if size == n then do
                    result <- Buffer.get buffer
                    Buffer.push buffer t
                    rest <- loop ts
                    pure (f result : rest)
                else do
                    Buffer.push buffer t
                    loop ts
        loop trees
    {-# INLINE nodes #-}

    iterateNodes sh trees = case nodes (computeSizes sh) trees of
        [tree] -> RRB.Root (treeSize sh tree) sh tree
        trees' -> iterateNodes (up sh) trees'

pattern Empty :: Vector a
pattern Empty <- RRB.Empty

pattern Root :: Int -> Shift -> Tree a -> Vector a
pattern Root size sh tree <- RRB.Root size sh tree

{-# COMPLETE Empty, Root #-}

pattern Balanced :: Array (Tree a) -> Tree a
pattern Balanced arr <- RRB.Balanced arr

pattern Unbalanced :: Array (Tree a) -> PrimArray Int -> Tree a
pattern Unbalanced arr sizes <- RRB.Unbalanced arr sizes

pattern Leaf :: Array a -> Tree a
pattern Leaf arr <- RRB.Leaf arr

{-# COMPLETE Balanced, Unbalanced, Leaf #-}

-- | Structural invariants a vector is expected to hold.
data Invariant
    = RootSizeGt0      -- Root: Size > 0
    | RootShiftDiv     -- Root: The shift at the root is divisible by blockShift
    | RootSizeCorrect  -- Root: The size at the root is correct
    | RootGt1Child     -- Root: The root has more than 1 child if not a Leaf
    | BalShiftGt0      -- Balanced: Shift > 0
    | BalNumChildren   -- Balanced: The number of children is blockSize unless
                       -- the parent is unbalanced or the node is on the right
                       -- edge in which case it is in [1,blockSize]
    | BalFullChildren  -- Balanced: All children are full, except for the last
                       -- if the node is on the right edge
    | UnbalShiftGt0    -- Unbalanced: Shift > 0
    | UnbalParentUnbal -- Unbalanced: Parent is Unbalanced
    | UnbalNumChildren -- Unbalanced: The number of children is in [1,blockSize]
    | UnbalSizes       -- Unbalanced: The sizes array is correct
    | UnbalNotBal      -- Unbalanced: The tree is not full enough to be a
                       -- Balanced
    | LeafShift0       -- Leaf: Shift == 0
    | LeafNumElems     -- Leaf: The number of elements is in [1,blockSize]
    deriving Show

assert :: Invariant -> Bool -> Either Invariant ()
assert i False = Left i
assert _ True = pure ()

-- | Check tree invariants. Returns @Left@ on finding a violated invariant.
valid :: Vector a -> Either Invariant ()
valid RRB.Empty = pure ()
valid (RRB.Root size sh tree) = do
    assert RootSizeGt0 $ size > 0
    assert RootShiftDiv $ sh `mod` blockShift == 0
    assert RootSizeCorrect $ size == countElems tree
    assert RootGt1Child $ case tree of
        Balanced arr -> length arr > 1
        Unbalanced arr _ -> length arr > 1
        Leaf _ -> True
    validTree Unbal sh tree

data NodeDesc
    = Bal           -- parent is Balanced
    | BalRightEdge  -- parent is Balanced and this node is on the right edge
    | Unbal         -- parent is Unbalanced

validTree :: NodeDesc -> Shift -> Tree a -> Either Invariant ()
validTree desc sh (RRB.Balanced arr) = do
    assert BalShiftGt0 $ sh > 0
    assert BalNumChildren $ case desc of
        Bal -> n == blockSize
        BalRightEdge -> n >= 1 && n <= blockSize
        Unbal -> n >= 1 && n <= blockSize
    assert BalFullChildren $
        all (\t -> countElems t == 1 `shiftL` sh) expectedFullChildren
    traverse_ (validTree Bal (down sh)) arrInit
    validTree descLast (down sh) (A.last arr)
  where
    n = length arr
    arrInit = A.take arr (n-1)
    expectedFullChildren = case desc of
        Bal -> arr
        BalRightEdge -> arrInit
        Unbal -> arrInit
    descLast = case desc of
        Bal -> Bal
        BalRightEdge -> BalRightEdge
        Unbal -> BalRightEdge
validTree desc sh (RRB.Unbalanced arr sizes) = do
    assert UnbalShiftGt0 $ sh > 0
    case desc of
        Bal -> assert UnbalParentUnbal False
        BalRightEdge -> assert UnbalParentUnbal False
        Unbal -> assert UnbalNumChildren $ n >= 1 && n <= blockSize
    assert UnbalSizes $ n == sizeofPrimArray sizes
    assert UnbalSizes $
        all (\i -> countElems (A.index arr i) == getSize sizes i) [0 .. n-1]
    assert UnbalNotBal $ not (couldBeBalanced sh arr sizes)
    traverse_ (validTree Unbal (down sh)) arr
  where
    n = length arr
validTree desc sh (RRB.Leaf arr) = do
    assert LeafShift0 $ sh == 0
    assert LeafNumElems $ case desc of
        Bal -> n == blockSize
        BalRightEdge -> n >= 1 && n <= blockSize
        Unbal -> n >= 1 && n <= blockSize
  where
    n = length arr

-- | Check whether an Unbalanced node could be Balanced.
couldBeBalanced :: Shift -> A.Array (Tree a) -> PrimArray Int -> Bool
couldBeBalanced sh arr sizes =
   all (\i -> getSize sizes i == 1 `shiftL` sh) [0 .. n-2] &&
   (case A.last arr of
       Balanced _ -> True
       Unbalanced arr' sizes' -> couldBeBalanced (down sh) arr' sizes'
       Leaf _ -> True)
  where
    n = length arr

getSize :: PrimArray Int -> Int -> Int
getSize sizes 0 = indexPrimArray sizes 0
getSize sizes i = indexPrimArray sizes i - indexPrimArray sizes (i-1)

countElems :: Tree a -> Int
countElems (RRB.Balanced arr) =
    foldl' (\acc tree -> acc + countElems tree) 0 arr
countElems (RRB.Unbalanced arr _) =
    foldl' (\acc tree -> acc + countElems tree) 0 arr
countElems (RRB.Leaf arr) = length arr