packages feed

toysolver-0.6.0: test/Test/QBF.hs

{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
{-# LANGUAGE TemplateHaskell, ScopedTypeVariables #-}
module Test.QBF (qbfTestGroup) where

import Control.Monad
import qualified Data.IntSet as IntSet
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Data.Maybe (catMaybes)

import Test.Tasty
import Test.Tasty.QuickCheck hiding ((.&&.), (.||.))
import Test.Tasty.TH
import qualified Test.QuickCheck.Monadic as QM

import ToySolver.Data.Boolean
import qualified ToySolver.Data.BoolExpr as BoolExpr
import qualified ToySolver.QBF as QBF

-- -------------------------------------------------------------------

prop_solveCEGAR :: Property
prop_solveCEGAR = QM.monadicIO $ do
  (nv, prefix@(_ : prefix'), matrix) <- QM.pick arbitrarySmallQBF
  (sat, cert) <- QM.run $ QBF.solveCEGAR nv prefix matrix
  QM.assert $ sat == evalQBFNaive prefix matrix
  case cert of
    Nothing -> return ()
    Just ls ->
      QM.assert $ sat == evalQBFNaive' (IntMap.fromList [(abs lit, lit > 0) | lit <- IntSet.toList ls]) prefix' matrix

prop_solveCEGARIncremental :: Property
prop_solveCEGARIncremental = QM.monadicIO $ do
  (nv, prefix@(_ : prefix'), matrix) <- QM.pick arbitrarySmallQBF
  (sat, cert) <- QM.run $ QBF.solveCEGARIncremental nv prefix matrix
  QM.assert $ sat == evalQBFNaive prefix matrix
  case cert of
    Nothing -> return ()
    Just ls ->
      QM.assert $ sat == evalQBFNaive' (IntMap.fromList [(abs lit, lit > 0) | lit <- IntSet.toList ls]) prefix' matrix

prop_solveQE :: Property
prop_solveQE = QM.monadicIO $ do
  (nv, prefix@(_ : prefix'), matrix) <- QM.pick arbitrarySmallQBF
  (sat, cert) <- QM.run $ QBF.solveQE nv prefix matrix
  QM.assert $ sat == evalQBFNaive prefix matrix
  case cert of
    Nothing -> return ()
    Just ls ->
      QM.assert $ sat == evalQBFNaive' (IntMap.fromList [(abs lit, lit > 0) | lit <- IntSet.toList ls]) prefix' matrix

{-
If the innermost quantifier is a universal quantifier,
we can remove the bound variable v from the clauses that
do not contain v and ¬v simultaneously.
-}
prop_eliminate_last_universal_quantifier :: Property
prop_eliminate_last_universal_quantifier = QM.monadicIO $ do
  (nv, prefix, matrix1) <- QM.pick gen
  let (QBF.A, xs) = last prefix
      matrix2 = [[lit | lit <- clause, abs lit `IntSet.notMember` xs] | clause <- matrix1]
      matrix1' = andB [orB [f lit | lit <- clause] | clause <- matrix1]
      matrix2' = andB [orB [f lit | lit <- clause] | clause <- matrix2]
      f lit = if lit > 0
              then BoolExpr.Atom lit
              else notB (BoolExpr.Atom (abs lit))
  (sat1, cert1) <- QM.run $ QBF.solveCEGAR nv prefix matrix1'
  (sat2, cert2) <- QM.run $ QBF.solveCEGAR nv (init prefix) matrix2'
  QM.assert $ sat1 == sat2
  where
    gen :: Gen (Int, QBF.Prefix, [[Int]])
    gen = do
      nv <- choose (0,10)
      nc <- choose (0,50)
      let vs = [1..nv]
      q1 <- arbitrary
      q2 <- arbitrary
      vs1 <- IntSet.fromList <$> subsetof vs
      vs2 <- IntSet.fromList <$> subsetof vs    
      matrix <- replicateM nc $ do
        if nv == 0 then
          return []
        else do
          mapM (\v -> elements [v, -v]) =<< subsetof vs
      return
        ( nv
        , [(q1,vs1), (q2, vs2 IntSet.\\ vs1), (QBF.A, IntSet.fromList vs IntSet.\\ (vs1 `IntSet.union` vs2))]
        , matrix
        )

-- -------------------------------------------------------------------

evalQBFNaive :: QBF.Prefix -> QBF.Matrix -> Bool
evalQBFNaive prefix = evalQBFNaive' IntMap.empty prefix

evalQBFNaive' :: IntMap Bool -> QBF.Prefix -> QBF.Matrix -> Bool
evalQBFNaive' env prefix = f env [(q,v) | (q,vs) <- prefix, v <- IntSet.toList vs]
  where
    f env [] matrix = BoolExpr.fold (env IntMap.!) matrix
    f env ((QBF.A,x) : prefix) matrix =
      f (IntMap.insert x True  env) prefix matrix &&
      f (IntMap.insert x False env) prefix matrix
    f env ((QBF.E,x) : prefix) matrix =
      f (IntMap.insert x True  env) prefix matrix ||
      f (IntMap.insert x False env) prefix matrix

-- -------------------------------------------------------------------

subsetof :: [a] -> Gen [a]
subsetof xs = catMaybes <$> sequence [elements [Just x, Nothing] | x <- xs]

arbitrarySmallQBF :: Gen (Int, QBF.Prefix, QBF.Matrix)
arbitrarySmallQBF = do
  (nv, prefix, matrix') <- arbitrarySmallQBF'
  let f lit = if lit > 0
              then BoolExpr.Atom lit
              else notB (BoolExpr.Atom (abs lit))
      matrix = andB [orB [f lit | lit <- clause] | clause <- matrix']
  return (nv, prefix, matrix)

arbitrarySmallQBF' :: Gen (Int, QBF.Prefix, [[Int]])
arbitrarySmallQBF' = do
  nv <- choose (0,10)
  nc <- choose (0,50)

  let vs = [1..nv]
  q1 <- arbitrary
  q2 <- arbitrary
  q3 <- arbitrary
  vs1 <- IntSet.fromList <$> subsetof vs
  vs2 <- IntSet.fromList <$> subsetof vs

  matrix <- replicateM nc $ do
    len <- choose (0,10)
    if nv == 0 then
      return []
    else
      replicateM len $ choose (-nv, nv) `suchThat` (/= 0)
  
  return
    ( nv
    , [(q1,vs1), (q2, vs2 IntSet.\\ vs1), (q3, IntSet.fromList vs IntSet.\\ (vs1 `IntSet.union` vs2))]
    , matrix
    )

instance Arbitrary QBF.Quantifier where
  arbitrary = arbitraryBoundedEnum

-- -------------------------------------------------------------------
-- Test harness

qbfTestGroup :: TestTree
qbfTestGroup = $(testGroupGenerator)