dawgdic-1.0.0: src/Data/DAWG/Internal/RankedGuideBuilder.hs
{-|
Module: Data.DAWG.Internal.RankedGuideBuilder
Description: Exports ranked guide builder as well as its internal API.
Copyright: (c) Andrey Prokopenko, 2025
License: BSD-3-Clause
Stability: experimental
-}
module Data.DAWG.Internal.RankedGuideBuilder where
import Control.Monad (when)
import Control.Monad.Primitive (PrimMonad, PrimState)
import Data.Bits
import Data.Primitive.MutVar
import Data.Vector.Algorithms.Insertion (sortByBounds)
import GHC.Stack (HasCallStack)
import Data.Primitive.PrimArray.Combinators
import Data.DAWG.Internal.BaseType
import Data.DAWG.Internal.GuideUnit
import Data.DAWG.Internal.Guide (Guide(..))
import Data.DAWG.Internal.DAWG (DAWG)
import Data.DAWG.Internal.Dictionary (Dictionary (..))
import Data.DAWG.Internal.RankedGuide
import Data.DAWG.Internal.RankedGuideLink
import qualified Data.Vector.Generic.Mutable as V
import qualified Data.Vector.Unboxed as UV
import qualified Data.DAWG.Internal.DAWG as Dawg
import qualified Data.DAWG.Internal.Dictionary as Dict
import qualified Data.DAWG.Internal.DictionaryUnit as DictUnit
import qualified Data.DAWG.Internal.GuideUnit as GuideUnit
import qualified Data.DAWG.Internal.RankedGuideLink as Link
-- ** Ranked Guide Builder
-- | A mutable builder of 'Data.DAWG.Internal.RankedGuide.RankedGuide'.
newtype RankedGuideBuilder m =
RGBRef { getRGBRef :: MutVar (PrimState m) (RankedGuideBuilder_ m) }
-- | Builder of Ranked Guide. Do not acess directly. Use 'RankedGuideBuilder' instead.
data RankedGuideBuilder_ m = RankedGuideBuilder
{ rankedGuideBuilderDawg :: DAWG
, rankedGuideBuilderDictionary :: Dictionary
, rankedGuideBuilderUnits :: ObjectPool (PrimState m) GuideUnit
, rankedGuideBuilderLinks :: ObjectPool (PrimState m) RankedGuideLink
, rankedGuideBuilderIsFixedTable :: ObjectPool (PrimState m) UCharType
}
-- | Build 'Data.DAWG.Internal.RankedGuide.RankedGuide'
-- from 'Data.DAWG.Internal.DAWG.DAWG' and 'Data.DAWG.Internal.Dictionary.Dictionary'.
-- | Returns 'Nothing' if build fails.
build :: HasCallStack => PrimMonad m => DAWG -> Dictionary -> m (Maybe RankedGuide)
build dawg dict = do
rgref@RGBRef{..} <- new dawg dict
resizeUnitsAndFlags rgref
rgb <- readMutVar getRGBRef
if Dawg.size (rankedGuideBuilderDawg rgb) <= 1
then freeze rgb >>= pure . Just
else do
maxValueRef <- newMutVar (-1)
buildFromIndexes Dawg.root Dict.root maxValueRef rgref >>= \case
False -> pure Nothing
True -> readMutVar getRGBRef >>= freeze >>= pure . Just
-- | Same as 'build' but throws an error if build fails.
build' :: HasCallStack => PrimMonad m => DAWG -> Dictionary -> m RankedGuide
build' dawg dict = build dawg dict >>= \case
Just guide -> pure guide
Nothing -> error "failed to build guide"
{-# INLINE build' #-}
-- | Generates 'Data.DAWG.Internal.RankedGuide.RankedGuide' out of 'RankedGuideBuilder'.
-- Once this function is called, 'RankedGuideBuilder' must not be used anymore.
freeze :: PrimMonad m => RankedGuideBuilder_ m -> m RankedGuide
freeze rgb = do
funits <- UV.freeze $ rankedGuideBuilderUnits rgb
let !guideUnits = (UV.fromList . UV.toList) funits
!guideSize = fromIntegral $ UV.length guideUnits
!guideDictionary = rankedGuideBuilderDictionary rgb
!rankedGuide = Guide{..}
pure RankedGuide{..}
-- ** Helpers
-- | Initialises a new 'RankedGuideBuilder' from DAWG and Dictionary.
new :: PrimMonad m => DAWG -> Dictionary -> m (RankedGuideBuilder m)
new rankedGuideBuilderDawg rankedGuideBuilderDictionary = do
rankedGuideBuilderUnits <- V.new 0
rankedGuideBuilderLinks <- V.new 0
rankedGuideBuilderIsFixedTable <- V.new 0
let rg = RankedGuideBuilder{..}
RGBRef <$> newMutVar rg
-- | Resize both units and flags based on a dictionary size. If guide size is equal or greater than dictionary size, it will leave guide builder unchanged.
resizeUnitsAndFlags :: PrimMonad m => RankedGuideBuilder m -> m ()
resizeUnitsAndFlags RGBRef{..} = do
rgb <- readMutVar getRGBRef
let dictSize = fromIntegral $ dictionarySize $ rankedGuideBuilderDictionary rgb
unitsSize = V.length $ rankedGuideBuilderUnits rgb
flagsSize = V.length $ rankedGuideBuilderIsFixedTable rgb
newUnits <- V.grow (rankedGuideBuilderUnits rgb)
$ if unitsSize < dictSize then dictSize - unitsSize else 0
newFlags <- V.grow (rankedGuideBuilderIsFixedTable rgb)
$ if flagsSize < (dictSize `div` 8) then (dictSize `div` 8) - flagsSize else 0
let !nrgb = rgb
{ rankedGuideBuilderUnits = newUnits, rankedGuideBuilderIsFixedTable = newFlags }
writeMutVar getRGBRef nrgb
-- | Build ranked guide recursively from dawg index and dictionary index.
-- Returns 'False' if fails.
buildFromIndexes
:: PrimMonad m
=> BaseType -> BaseType -> MutVar (PrimState m) ValueType -> RankedGuideBuilder m -> m Bool
buildFromIndexes !dawgIx !dictIx !maxValueRef rgref@RGBRef{..} = do
let sortLinks n rgb = do
let links = rankedGuideBuilderLinks rgb
l = V.length links
sortByBounds compare links n l
updateMaxValue n valueRef rgb = do
link <- rankedGuideBuilderLinks rgb !~ fromIntegral n
writeMutVar valueRef (Link.value link)
resizeLinks n = do
rgb <- readMutVar getRGBRef
let links = rankedGuideBuilderLinks rgb
l = V.length links
newLinks <- case compare l n of
LT -> V.grow links (n - l)
EQ -> pure links
GT -> pure (V.unsafeSlice 0 n links)
let !nrgb = rgb { rankedGuideBuilderLinks = newLinks }
writeMutVar getRGBRef nrgb
rgb0 <- readMutVar getRGBRef
isFixed dictIx rgb0 >>= \case
True -> findMaxValue dictIx maxValueRef rgb0
False -> do
setIsFixed dictIx rgb0
let initialNumLinks = V.length $ rankedGuideBuilderLinks rgb0
enumerateLinks dawgIx dictIx rgref >>= \case
False -> pure False
True -> do
-- enumerateLinks could resize vectors, thus, reading again
rgb <- readMutVar getRGBRef
sortLinks initialNumLinks rgb
turnLinksToUnits dictIx initialNumLinks rgref >>= \case
False -> pure False
True -> do
updateMaxValue initialNumLinks maxValueRef rgb
resizeLinks initialNumLinks
pure True
-- | Finds the maximum value by using fixed units.
findMaxValue
:: PrimMonad m
=> BaseType -> MutVar (PrimState m) ValueType -> RankedGuideBuilder_ m -> m Bool
findMaxValue !startDictIx !maxValueRef rgb = do
let follow' !dictIx = do
childLabel <- GuideUnit.child <$> rankedGuideBuilderUnits rgb !~ fromIntegral dictIx
if childLabel == 0
then pure $! Just dictIx
else case Dict.followChar (fromIntegral childLabel) dictIx (rankedGuideBuilderDictionary rgb) of
Nothing -> pure Nothing
Just lastDictIx -> follow' lastDictIx
follow' startDictIx >>= \case
Nothing -> pure False
Just !dictIx -> do
let itHasValue = Dict.hasValue dictIx (rankedGuideBuilderDictionary rgb)
when itHasValue $ writeMutVar maxValueRef
$! Dict.value dictIx (rankedGuideBuilderDictionary rgb)
pure itHasValue
-- | Enumerates links to the next states.
enumerateLinks :: PrimMonad m => BaseType -> BaseType -> RankedGuideBuilder m -> m Bool
enumerateLinks !startDawgIx !dictIx rgref@RGBRef{..} = do
rgb0 <- readMutVar getRGBRef
let !startDawgChildIx = Dawg.child startDawgIx (rankedGuideBuilderDawg rgb0)
go !dawgChildIx
| dawgChildIx == 0 = pure True
| otherwise = do
rgb <- readMutVar getRGBRef
let !startValue = -1
!childLabel = Dawg.label dawgChildIx (rankedGuideBuilderDawg rgb)
maxValueRef <- newMutVar startValue
mValue <- if childLabel == 0
then if (not $ Dict.hasValue dictIx $ rankedGuideBuilderDictionary rgb)
then pure Nothing
else do
let !value' = Dict.value dictIx $ rankedGuideBuilderDictionary rgb
writeMutVar maxValueRef value'
pure $! Just value'
else case Dict.followChar (fromIntegral childLabel) dictIx (rankedGuideBuilderDictionary rgb) of
Nothing -> pure Nothing
Just lastDictIx -> do
-- outer recursion loop
buildFromIndexes dawgChildIx lastDictIx maxValueRef rgref >>= \case
False -> pure Nothing
True -> do
value' <- readMutVar maxValueRef
pure $! Just value'
case mValue of
Nothing -> pure False
Just value' -> do
pushLinkBack (RankedGuideLink (childLabel, value'))
rgb1 <- readMutVar getRGBRef
let nextDawgIx = Dawg.sibling dawgChildIx (rankedGuideBuilderDawg rgb1)
go nextDawgIx
pushLinkBack !link = do
rgb <- readMutVar getRGBRef
let linksSize = V.length $ rankedGuideBuilderLinks rgb
newLinks <- V.grow (rankedGuideBuilderLinks rgb) 1
newLinks !<~~ fromIntegral linksSize $ const link
let !nrgb = rgb { rankedGuideBuilderLinks = newLinks }
writeMutVar getRGBRef nrgb
go startDawgChildIx
-- | Modifies units.
turnLinksToUnits :: PrimMonad m => BaseType -> Int -> RankedGuideBuilder m -> m Bool
turnLinksToUnits !dictIx !linksBegin RGBRef{..} = do
rgb <- readMutVar getRGBRef
let !lsize = V.length $ rankedGuideBuilderLinks rgb
links = rankedGuideBuilderLinks rgb
units = rankedGuideBuilderUnits rgb
-- the first child
!firstLabel <- Link.label <$> links !~ fromIntegral linksBegin
units !<~~ fromIntegral dictIx $ GuideUnit.setChild firstLabel
!dictStartChildIx <- followWithoutCheck dictIx firstLabel rgb
-- other children
let go !dictChildIx !ix
| ix >= lsize = pure True
| otherwise = do
!siblingLabel <- Link.label <$> links !~ fromIntegral ix
!dictSiblingIx <- followWithoutCheck dictIx siblingLabel rgb
units !<~~ fromIntegral dictChildIx $ GuideUnit.setSibling siblingLabel
go dictSiblingIx (succ ix)
go dictStartChildIx (succ linksBegin)
-- | Follows a transition without any check.
followWithoutCheck
:: PrimMonad m => BaseType -> UCharType -> RankedGuideBuilder_ m -> m BaseType
followWithoutCheck !ix !label' rgb = do
let dictUnits = Dict.dictionaryUnits $ rankedGuideBuilderDictionary rgb
offset' = DictUnit.offset $ (dictUnits UV.! fromIntegral ix)
pure ((ix .^. offset') .^. fromIntegral label')
-- | Sets dictionary unit as a fixed by index.
setIsFixed :: PrimMonad m => BaseType -> RankedGuideBuilder_ m -> m ()
setIsFixed !ix rgb = do
let setIsFixed' !v = v .|. (1 .<<. (fromIntegral ix `mod` 8))
rankedGuideBuilderIsFixedTable rgb !<~~ (fromIntegral ix `div` 8) $! setIsFixed'
-- | Checks whether given dictionary index is fixed or not.
isFixed :: PrimMonad m => BaseType -> RankedGuideBuilder_ m -> m Bool
isFixed !ix rgb = do
v <- rankedGuideBuilderIsFixedTable rgb !~ (fromIntegral ix `div` 8)
let x = v .&. (1 .<<. (fromIntegral ix `mod` 8))
pure $! x /= 0