packages feed

ac-library-hs-1.5.2.1: test/Tests/Util.hs

module Tests.Util
  ( myForAllShrink,
    laws,
    intervalGen,
    intervalGen',
    Dag (..),
    genDag,
    Tree (..),
    genTree,
  )
where

import AtCoder.Extra.Graph qualified as Gr
import AtCoder.Internal.Assert qualified as ACIA
import Data.Foldable (for_)
import Data.Maybe (fromJust)
import Data.Proxy (Proxy (..))
import Data.Typeable (Typeable, typeRep)
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.Classes qualified as QCC
import Test.QuickCheck.Property qualified as QC
import Test.Tasty
import Test.Tasty.QuickCheck qualified as QC

-- | Taken from `quickcheck-classes-base`.
myForAllShrink ::
  (QC.Arbitrary a, Show b, Eq b) =>
  Bool -> -- Should we show the RHS. It's better not to show it if the RHS is equal to the input.
  (a -> Bool) -> -- is the value a valid input
  (a -> [String]) -> -- show the 'a' values
  String -> -- show the LHS
  (a -> b) -> -- the function that makes the LHS
  String -> -- show the RHS
  (a -> b) -> -- the function that makes the RHS
  QC.Property
myForAllShrink displayRhs isValid showInputs name1 calc1 name2 calc2 =
  QC.MkProperty $
    QC.arbitrary >>= \x ->
      QC.unProperty $
        QC.shrinking QC.shrink x $ \x' ->
          let b1 = calc1 x'
              b2 = calc2 x'
              sb1 = show b1
              sb2 = show b2
              description = "  Description: " ++ name1 ++ " = " ++ name2
              err = description ++ "\n" ++ unlines (map ("  " ++) (showInputs x')) ++ "  " ++ name1 ++ " = " ++ sb1 ++ (if displayRhs then "\n  " ++ name2 ++ " = " ++ sb2 else "")
           in isValid x' QC.==> QC.counterexample err (b1 == b2)

-- | Taken from `quickcheck-classes-base`.
laws :: forall a. (Typeable a) => [Proxy a -> QCC.Laws] -> TestTree
laws =
  testGroup (show (typeRep (Proxy @a)))
    . map
      ( \f ->
          let QCC.Laws name pairs = f (Proxy @a)
           in testGroup name (map (uncurry QC.testProperty) pairs)
      )

-- | Returns an interval [l, r) in [0, n)
intervalGen :: Int -> QC.Gen (Int, Int)
intervalGen = intervalGen' 0

-- | Returns an interval [l, r) in [0, n)
intervalGen' :: Int -> Int -> QC.Gen (Int, Int)
intervalGen' xl xr = do
  l <- QC.chooseInt (xl, xr)
  r <- QC.chooseInt (l, xr)
  pure (l, r)

genDag :: forall w. (QC.Arbitrary w, VU.Unbox w) => Int -> QC.Gen (Gr.Csr w)
genDag n = do
  edges <- VU.fromList <$> QC.sublistOf [(u, v) | u <- [0 .. n - 1], v <- [u + 1 .. n - 1]]
  verts <- VU.fromList <$> QC.shuffle [0 .. n - 1]
  ws <- VU.fromList <$> QC.vectorOf n (QC.arbitrary @w)
  let es = VU.zipWith (\(!u, !v) !w -> (verts VG.! u, verts VG.! v, w)) edges ws
  pure $ Gr.build n es

-- | Newtype for generating a random DAG.
newtype Dag w = Dag (Gr.Csr w)

instance (QC.Arbitrary w, VU.Unbox w) => QC.Arbitrary (Dag w) where
  arbitrary = do
    QC.Positive n <- QC.arbitrary
    Dag <$> genDag n

-- TODO: commit the upper code first

-- | Prüfer sequence: https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence
genTreeEdge :: Int -> QC.Gen (VU.Vector (Int, Int))
genTreeEdge n = case n of
  1 -> pure VU.empty
  2 -> pure $ VU.singleton (0, 1)
  _ -> do
    prufer <- QC.vectorOf (n - 2) (QC.chooseInt (0, n - 1))
    pure $ VU.create $ do
      -- sum(deg) = n + (n - 2) at the beginning
      deg <- VUM.replicate n (1 :: Int)
      for_ prufer $ \v -> do
        VGM.modify deg (+ 1) v

      edges <- VUM.unsafeNew (n - 1)

      -- sum(deg) will be 2(n - 1) - 2(n - 2) = 2
      for_ (zip [0 :: Int ..] prufer) $ \(!i, !u) -> do
        -- NOTE: At the beginning, deg[u] > 1, and deg[i] is never decreased until iterated
        !v <- fromJust . VU.findIndex (== 1) <$> VU.unsafeFreeze deg
        -- So we're sure u /= v
        let !_ = ACIA.runtimeAssert (u /= v) "u /= v"
        VGM.write edges i (u, v)
        VGM.modify deg (subtract 1) u
        VGM.modify deg (subtract 1) v

      -- The last two vertices with degree one make up the last edge
      uv <- VU.findIndices (== 1) <$> VU.unsafeFreeze deg
      let !_ = ACIA.runtimeAssert (VU.length uv == 2) "not found uv?"
      VGM.write edges (n - 1) (uv VG.! 0, uv VG.! 1)

      -- done
      pure edges

genTree :: forall w. (QC.Arbitrary w, VU.Unbox w) => Int -> QC.Gen (Gr.Csr w)
genTree n = do
  es <- genTreeEdge n
  ws <- VU.fromList <$> QC.vectorOf (n - 1) (QC.arbitrary @w)
  pure . Gr.build n . Gr.swapDupe $ VU.zipWith (\(!u, !v) !w -> (u, v, w)) es ws

-- | Newtype for generating a random tree.
newtype Tree w = Tree (Gr.Csr w)

instance (QC.Arbitrary w, VU.Unbox w) => QC.Arbitrary (Tree w) where
  arbitrary = do
    QC.Positive n <- QC.arbitrary
    Tree <$> genTree n