packages feed

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
  ]