ac-library-hs-1.5.2.1: test/Tests/Extra/Tree/Lct.hs
{-# LANGUAGE RecordWildCards #-}
-- | Not complete!
module Tests.Extra.Tree.Lct where
import AtCoder.Extra.Graph qualified as Gr
import AtCoder.Extra.Tree.Lct (Lct (..))
import AtCoder.Extra.Tree.Lct qualified as Lct
import AtCoder.Internal.Buffer qualified as B
import Control.Monad (when)
import Control.Monad.Primitive (PrimMonad, PrimState)
import Data.Foldable (for_)
import Data.Maybe (fromJust, isJust)
import Data.Semigroup (Sum (..))
import Data.Vector qualified as V
import Data.Vector.Generic qualified as VG
import Data.Vector.Generic.Mutable qualified as VGM
import Data.Vector.Unboxed qualified as VU
import Data.Vector.Unboxed.Mutable qualified as VUM
import Test.QuickCheck.Monadic as QCM
import Test.Tasty
import Test.Tasty.QuickCheck as QC
-- | Maximum number of vertices.
maxN :: Int
maxN = 16
-- | Maximum number of queries.
maxQ :: Int
maxQ = 1000
-- | Range of monoid value.
rng :: (Int, Int)
rng = (-rngI, rngI)
-- | Range of monoid value.
rngI :: Int
rngI = 10
data Init s = Init
{ n :: {-# UNPACK #-} !Int,
q :: {-# UNPACK #-} !Int,
initialMonoids :: {-# UNPACK #-} !(VU.Vector (Sum Int)),
refM :: !(IO Ref),
lctM :: !(IO (Lct.Lct s (Sum Int)))
}
-- | Referencial implementation
data Ref = Ref
{ nR :: {-# UNPACK #-} !Int,
-- | Monoid values
vecR :: !(VUM.MVector VUM.RealWorld (Sum Int)),
-- | NxN
edgeR :: !(VUM.MVector VUM.RealWorld Bool)
}
newRef :: Int -> VU.Vector (Sum Int) -> IO Ref
newRef nR xs = do
vecR <- VU.thaw xs
edgeR <- VUM.replicate (nR * nR) False
pure Ref {..}
instance Show (Init s) where
show Init {..} = show (n, q, initialMonoids)
instance QC.Arbitrary (Init VUM.RealWorld) where
arbitrary = do
n <- QC.chooseInt (1, maxN)
q <- QC.chooseInt (1, maxQ)
initialMonoids <- (VU.fromList <$>) $ QC.vectorOf n (Sum <$> QC.chooseInt rng)
let lctM = Lct.buildInv negate initialMonoids VU.empty
let refM = let nR = n in newRef n initialMonoids
pure Init {..}
data Query
= Read Int
| Write Int (Sum Int)
| Modify Int (Sum Int)
| ModifyM Int (Sum Int)
| Link Int Int
| Cut Int Int
| Evert Int
| Expose Int
| Expose_ Int
| -- | Root Int
Same Int Int
| -- TODO: Test parent!
-- | Parent Int
-- | Jump
JumpMaybe Int Int Int
| LengthBetween Int Int
| -- | Lca Int Int
-- | LcaMaybe Int Int
ProdPath Int Int
| ProdSubtree Int Int
-- -- | ProdTree Int Int
deriving (Eq, Show)
-- | Arbitrary return type for the `Query` result.
data Result
= None
| I !Int
| MI !(Maybe Int)
| B Bool
| SI (Sum Int)
deriving (Show, Eq)
queryGen :: Int -> QC.Gen Query
queryGen n =
QC.oneof
[ Read <$> u,
Write <$> u <*> x,
Modify <$> u <*> x,
ModifyM <$> u <*> x,
Link <$> u <*> v,
Cut <$> u <*> v,
Evert <$> u,
Expose <$> u,
Expose_ <$> u,
-- \| Root <$> Int
Same <$> u <*> v,
-- \| Parent <$> Int
-- \| Jump
JumpMaybe <$> u <*> v <*> k,
LengthBetween <$> u <*> v,
-- Lca <$> u <*> v,
-- LcaMaybe <$> u <*> v,
ProdPath <$> u <*> v
-- TODO: test prod subtree
-- ProdSubtree <$> u <*> v
]
where
u = QC.chooseInt (0, n - 1)
v = QC.chooseInt (0, n - 1)
x = Sum <$> QC.chooseInt rng
k = QC.chooseInt (-1, n)
toCsrM :: Ref -> IO (Gr.Csr ())
toCsrM Ref {..} = do
es <- VU.unsafeFreeze edgeR
let uvs = (`VU.mapMaybe` VU.generate (nR * nR) id) $ \i ->
let (!u, !v) = i `divMod` nR
in if u /= v && es VG.! (nR * u + v)
then Just (u, v)
else Nothing
pure $ Gr.build' nR uvs
findPath :: Gr.Csr () -> Int -> Int -> Maybe (VU.Vector Int)
findPath gr v1 v2
| ws VG.! v2 == -1 = Nothing
| otherwise = Just $ Gr.constructPathFromRoot tree v2
where
(!ws, !tree) = Gr.trackingBfs (Gr.nCsr gr) grF (-1 :: Int) src
grF = Gr.adj1 gr
src = VU.singleton (v1, 0)
-- | containers. (referencial implementation)
handleRef :: Ref -> Query -> IO Result
handleRef ref@Ref {..} q = do
case q of
Read i -> do
SI <$> VGM.read vecR i
Write i x -> do
VGM.write vecR i x
pure None
Modify i x -> do
VGM.modify vecR (+ x) i
pure None
ModifyM i x -> do
VGM.modifyM vecR (pure . (+ x)) i
pure None
Link u v -> do
VGM.write edgeR (nR * u + v) True
VGM.write edgeR (nR * v + u) True
pure None
Cut u v -> do
VGM.write edgeR (nR * u + v) False
VGM.write edgeR (nR * v + u) False
pure None
Evert _ -> do
pure None
Expose _ -> do
pure None
Expose_ _ -> do
pure None
-- \| Root Int
Same u v -> do
gr <- toCsrM ref
pure . B . isJust $ findPath gr u v
-- \| Parent Int
-- \| Jump
JumpMaybe u v k -> do
gr <- toCsrM ref
case findPath gr u v of
Nothing -> pure $ MI Nothing
Just path -> pure . MI $ path VG.!? k
LengthBetween u v -> do
gr <- toCsrM ref
let !path = fromJust $ findPath gr u v
pure . I $ VU.length path - 1
-- LcaMaybe u v -> do
-- gr <- toCsrM ref
-- -- FIXME: HLD cannot be used
-- let connected = isJust $ findPath gr u v
-- if connected
-- then pure . MI . Just $ Hld.lca (Hld.new gr) u v
-- else pure $ MI Nothing
ProdPath u v -> do
gr <- toCsrM ref
ws <- VU.unsafeFreeze vecR
case findPath gr u v of
Nothing -> pure $ SI mempty
Just path -> pure . SI $ VU.foldMap (ws VG.!) path
ProdSubtree u v -> do
-- TODO: fix this
pure None
-- | ACL
handleAcl :: (PrimMonad m) => Lct (PrimState m) (Sum Int) -> Query -> m Result
handleAcl lct q = case q of
Read i -> do
SI <$> Lct.read lct i
Write i x -> do
Lct.write lct i x
pure None
Modify i x -> do
Lct.modify lct (+ x) i
pure None
ModifyM i x -> do
Lct.modifyM lct (pure . (+ x)) i
pure None
Link u v -> do
-- TODO: detect if already connect
Lct.link lct u v
pure None
Cut u v -> do
Lct.cut lct u v
pure None
Evert i -> do
Lct.evert lct i
pure None
Expose i -> do
_ <- Lct.expose lct i
pure None
Expose_ i -> do
Lct.expose_ lct i
pure None
-- \| Root Int
Same u v -> do
B <$> Lct.same lct u v
-- \| Parent Int
-- \| Jump
JumpMaybe u v k -> do
MI <$> Lct.jumpMaybe lct u v k
LengthBetween u v -> do
I <$> Lct.lengthBetween lct u v
-- LcaMaybe u v -> do
-- MI <$> Lct.lcaMaybe lct u v
ProdPath u v -> do
SI <$> Lct.prodPath lct u v
ProdSubtree u v -> do
SI <$> Lct.prodSubtree lct u v
prop_randomTest :: Init VUM.RealWorld -> QC.Property
prop_randomTest Init {..} = QCM.monadicIO $ do
lct <- QCM.run lctM
ref <- QCM.run refM
qs <- QCM.pick $ QC.vectorOf q (queryGen n)
record <- QCM.run $ B.new q
for_ qs $ \query -> do
b <- QCM.run $ filterRefM ref query
when b $ do
QCM.run $ B.pushBack record $ VU.DoNotUnboxStrict query
record' <- QCM.run $ B.unsafeFreeze record
expected <- QCM.run $ handleRef ref query
result <- QCM.run $ handleAcl lct query
when (expected /= result) $ do
-- show all the run queries on failure
let s = show . V.map (\(VU.DoNotUnboxStrict x) -> x) $ VU.convert record'
QCM.monitor $ QC.counterexample $ "actual queries in use: " ++ s
QCM.assertWith (expected == result) $ show (query, expected, result)
-- | Filter invalid queries
filterRefM :: Ref -> Query -> IO Bool
filterRefM ref@Ref {..} q = case q of
Read _ -> pure True
Write _ _ -> pure True
Modify _ _ -> pure True
ModifyM _ _ -> pure True
Link u v
| u == v -> pure False
| otherwise -> not <$> same u v
Cut u v -> do
es <- VU.unsafeFreeze edgeR
pure $ es VG.! (nR * u + v)
Evert _ -> pure True
Expose _ -> pure True
Expose_ _ -> pure True
Same _ _ -> pure True
JumpMaybe u v _ -> same u v
LengthBetween u v -> same u v
-- LcaMaybe u v -> same u v
ProdPath u v -> same u v
ProdSubtree u v -> same u v
where
same u v = do
gr <- toCsrM ref
let connected = isJust $ findPath gr u v
pure connected
tests :: [TestTree]
tests =
[ QC.testProperty "randomTest" prop_randomTest
]