packages feed

ac-library-hs-1.2.2.0: test/Tests/Extra/LazyKdTree.hs

{-# LANGUAGE RecordWildCards #-}

module Tests.Extra.LazyKdTree where

import AtCoder.Extra.LazyKdTree qualified as Lkt
import AtCoder.Extra.Monoid.Affine1 (Affine1 (..))
import AtCoder.Extra.Monoid.Affine1 qualified as Affine1
import AtCoder.LazySegTree (segAct)
import AtCoder.ModInt qualified as M
import Control.Monad (when)
import Control.Monad.ST (RealWorld)
import Data.Foldable (for_)
import Data.Semigroup (Sum (..))
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
import Tests.Util
import Test.Tasty.HUnit

type Mint = M.ModInt998244353

modInt :: Int -> Mint
modInt = M.new

data Init = Init
  { n :: {-# UNPACK #-} !Int,
    q :: {-# UNPACK #-} !Int,
    refM :: !(IO (VUM.MVector RealWorld (Int, Int, Sum Mint))),
    ktM :: !(IO (Lkt.LazyKdTree RealWorld (Affine1 Mint) (Sum Mint)))
  }

rng :: (Int, Int)
rng = (-rngI, rngI)

rngI :: Int
rngI = 10

instance Show Init where
  show Init {..} = show n

instance QC.Arbitrary Init where
  arbitrary = do
    n <- QC.chooseInt (1, 256)
    q <- QC.chooseInt (1, 256)
    xyws <- (VU.fromList <$>) $ QC.vectorOf n $ do
      x <- QC.chooseInt rng
      y <- QC.chooseInt rng
      w <- Sum . M.new <$> QC.arbitrary @Int
      pure (x, y, w)
    let refM = VU.thaw xyws
    let ktM = Lkt.build3 xyws
    pure Init {..}

data Query
  = Write !Int !(Sum Mint)
  | Modify !Int !(Sum Mint)
  | ModifyM !Int !(Sum Mint)
  | Affine !((Int, Int), (Int, Int)) !(Affine1 Mint)
  | Prod !((Int, Int), (Int, Int))
  | AllProd
  deriving (Show)

-- | Arbitrary return type for the `Query` result.
data Result
  = None
  | S !(Sum Mint)
  deriving (Show, Eq)

queryGen :: Int -> QC.Gen Query
queryGen n = do
  QC.oneof
    [ Write <$> i <*> v,
      Modify <$> i <*> v,
      ModifyM <$> i <*> v,
      Affine <$> r <*> f,
      Prod <$> r,
      pure AllProd
    ]
  where
    i = QC.chooseInt (0, n - 1)
    r = (,) <$> intervalGen' (-rngI) rngI <*> intervalGen' (-rngI) rngI
    v = Sum . modInt <$> QC.arbitrary
    f = Affine1.new <$> (modInt <$> QC.arbitrary) <*> (modInt <$> QC.arbitrary)

-- | containers. (referencial implementation)
handleRef :: VUM.MVector RealWorld (Int, Int, Sum Mint) -> Query -> IO Result
handleRef vec q = case q of
  Write i v -> do
    VGM.write ws i v
    pure None
  Modify k v -> do
    VGM.modify ws (+ v) k
    pure None
  ModifyM k v -> do
    VGM.modify ws (+ v) k
    pure None
  Affine ((!xl, !xr), (!yl, !yr)) f -> do
    xys <- VU.unsafeFreeze $ VUM.zip xs ys
    VU.iforM_ xys $ \i (!x, !y) -> do
      when (xl <= x && x < xr && yl <= y && y < yr) $ do
        VGM.modify ws (segAct f) i
    pure None
  Prod ((!xl, !xr), (!yl, !yr)) -> do
    S
      . VU.foldl'
        ( \acc (!x, !y, !v) ->
            if xl <= x && x < xr && yl <= y && y < yr then acc <> v else acc
        )
        mempty
      <$> VU.unsafeFreeze vec
  AllProd -> do
    S <$> VGM.foldl' (<>) mempty ws
  where
    (!xs, !ys, !ws) = VUM.unzip3 vec

-- | ac-library-hs.
handleAcl :: (HasCallStack) => Lkt.LazyKdTree RealWorld (Affine1 Mint) (Sum Mint) -> Query -> IO Result
handleAcl kt q = case q of
  Write i v -> do
    Lkt.write kt i v
    pure None
  Modify i v -> do
    Lkt.modify kt (+ v) i
    pure None
  ModifyM i v -> do
    Lkt.modifyM kt (pure . (+ v)) i
    pure None
  Affine ((!xl, !xr), (!yl, !yr)) f -> do
    Lkt.applyIn kt xl xr yl yr f
    pure None
  Prod ((!xl, !xr), (!yl, !yr)) -> do
    S <$> Lkt.prod kt xl xr yl yr
  AllProd -> do
    S <$> Lkt.allProd kt

prop_randomTest :: Init -> QC.Property
prop_randomTest Init {..} = QCM.monadicIO $ do
  qs <- QCM.pick $ QC.vectorOf q (queryGen n)
  vec <- QCM.run refM
  kt <- QCM.run ktM
  for_ qs $ \query -> do
    expected <- QCM.run $ handleRef vec query
    res <- QCM.run $ handleAcl kt query
    QCM.assertWith (expected == res) $ show (query, expected, res)

unit_zero :: TestTree
unit_zero = testCase "zero" $ do
  kt <- Lkt.build @_ @(Affine1 Mint) @(Sum Mint) VU.empty VU.empty VU.empty
  Lkt.applyIn kt 0 10 0 10 $ Affine1.new 1 1
  (@?= mempty) =<< Lkt.prod kt 0 10 0 10
  (@?= mempty) =<< Lkt.allProd kt
  pure ()

tests :: [TestTree]
tests =
  [ QC.testProperty "randomTest" prop_randomTest,
    unit_zero
  ]