在 SBV 中编码扩展自然数
Encoding extended naturals in SBV
我正在试验以下在 SMT-LIB 中编码扩展自然数的方法(我定义了一个类似于 Maybe Integer
的数据类型):
; extended integers -- if first field is true, then the value is infinity
(declare-datatypes () ((IntX (mk-int-x (is-infty Bool) (not-infty Int)))))
; addition
(define-fun plus ((x IntX) (y IntX)) IntX
(ite (or (is-infty x) (is-infty y))
(mk-int-x true 0)
(mk-int-x false (+ (not-infty x) (not-infty y)))))
(declare-fun x () IntX)
(assert (= x (plus x (mk-int-x false 1))))
; x = x+1 when x |-> infty
(get-model)
(exit)
我将如何在 SBV 中对其进行编码?我尝试了以下方法,但这只是使 SBV 崩溃了。此外,我不知何故怀疑这会做我想做的事,但我对 SBV 的工作原理还不够熟悉。
!/usr/bin/env stack
{- stack script
--resolver nightly-2018-11-23
--package sbv
--package syb
-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Generics
import Data.SBV
data IntX = IntX (Maybe Integer) deriving (Eq, Ord, Data, Read, Show, SymWord, HasKind)
pretty :: IntX -> String
pretty = \case
IntX Nothing -> "∞"
IntX n -> show n
instance Num IntX where
(+) (IntX x) (IntX y) = IntX $ (+) <$> x <*> y
(*) (IntX x) (IntX y) = IntX $ (*) <$> x <*> y
fromInteger = IntX . Just
ex1 = sat $ do
x :: SBV IntX <- free "x"
return $ x .== x + 1
main :: IO ()
main = print =<< ex1
~/temp ✘ ./sbv.hs
sbv.hs: SBV.SMT.SMTLib2.cvtExp.sh: impossible happened; can't translate: s0 + s1
CallStack (from HasCallStack):
error, called at ./Data/SBV/SMT/SMTLib2.hs:681:13 in sbv-7.12-9AiNAYtrUhB8YA6mr6BTn4:Data.SBV.SMT.SMTLib2
这里的根本问题是您的代码混合了 Haskell 的具体 Maybe
类型并试图将其视为符号对象。但是您在 SMT-Lib2 中的实现方式是正确的:您基本上需要在 SBV 中编写相应的代码。
我将从以下内容开始:
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NamedFieldPuns #-}
import Data.SBV
import Data.SBV.Control
import GHC.Generics (Generic)
这只是样板文件;除非您想使用查询模式,否则您不需要 Data.SBV.Control
导入,但正如我们将要看到的那样,它确实派上用场了。
首先要做的是对 IntX
类型进行符号编码;就像您在 SMTLib 中所做的那样:
data SIntX = SIntX { isInf :: SBool
, xVal :: SInteger
}
deriving (Generic, Mergeable)
instance Show SIntX where
show (SIntX inf val) = case (unliteral inf, unliteral val) of
(Just True, _) -> "oo"
(Just False, Just n) -> show n
_ -> "<symbolic>"
除了 Generic
和 Mergeable
的推导之外,上面没有什么值得惊讶的。它只是让 SBV 能够在您的扩展自然数上使用 ite
。另请注意 Show
实例如何通过使用 unliteral
.
谨慎区分具体值和符号值
接下来,我们添加一些方便的功能,同样不足为奇:
inf :: SIntX
inf = SIntX { isInf = true, xVal = 0 }
nat :: SInteger -> SIntX
nat v = SIntX { isInf = false, xVal = v }
liftU :: (SInteger -> SInteger) -> SIntX -> SIntX
liftU op a = ite (isInf a) inf (nat (op (xVal a)))
liftB :: (SInteger -> SInteger -> SInteger) -> SIntX -> SIntX -> SIntX
liftB op a b = ite (isInf a ||| isInf b) inf (nat (xVal a `op` xVal b))
现在我们可以让 IntX
成为一个数字:
instance Num SIntX where
(+) = liftB (+)
(*) = liftB (*)
negate = liftU negate
abs = liftU abs
signum = liftU signum
fromInteger = nat . literal
(请注意,这意味着 oo - oo = oo
的语义充其量是有问题的。但这不是重点。您可能必须明确定义 -
并根据需要进行处理。类似的评论适用于 signum
。)
既然你想测试相等性,我们还必须定义它的符号版本:
instance EqSymbolic SIntX where
a .== b = ite (isInf a &&& isInf b) true
$ ite (isInf a ||| isInf b) false
$ xVal a .== xVal b
同样,如果你想比较,你必须定义一个OrdSymbolic
实例;但思路还是一样。
我们需要一种方法来创建符号扩展自然数。下面的函数做得很好:
freeSIntX :: String -> Symbolic SIntX
freeSIntX nm = do i <- sBool $ nm ++ "_isInf"
v <- sInteger $ nm ++ "_xVal"
return $ SIntX { isInf = i, xVal = v }
严格来说,您不需要为变量命名。 (即,不需要 nm
参数。)但我发现出于显而易见的原因始终命名我的变量很有帮助。
现在,我们可以编写您的示例代码:
ex1 :: IO SatResult
ex1 = sat $ do x <- freeSIntX "x"
return $ x .== x+1
当我 运行 这个时,我得到:
*Main> ex1
Satisfiable. Model:
x_isInf = True :: Bool
x_xVal = 0 :: Integer
我相信这就是您要找的东西。
当您处理较大的程序时,能够更直接地提取 IntX
值并使用它们进一步编程是有益的。这时候查询模式就派上用场了。一、帮手:
data IntX = IntX (Maybe Integer) deriving Show
queryX :: SIntX -> Query IntX
queryX (SIntX {isInf, xVal}) = do
b <- getValue isInf
v <- getValue xVal
return $ IntX $ if b then Nothing
else Just v
现在我们可以编码了:
ex2 :: IO ()
ex2 = runSMT $ do x <- freeSIntX "x"
constrain $ x .== x+1
query $ do cs <- checkSat
case cs of
Unk -> error "Solver said Unknown!"
Unsat -> error "Solver said Unsatisfiable!"
Sat -> do v <- queryX x
io $ print v
我们得到:
*Main> ex2
IntX Nothing
希望对您有所帮助。我已将所有这些代码放在要点中:https://gist.github.com/LeventErkok/facfd067b813028390c89803b3a0e887
我正在试验以下在 SMT-LIB 中编码扩展自然数的方法(我定义了一个类似于 Maybe Integer
的数据类型):
; extended integers -- if first field is true, then the value is infinity
(declare-datatypes () ((IntX (mk-int-x (is-infty Bool) (not-infty Int)))))
; addition
(define-fun plus ((x IntX) (y IntX)) IntX
(ite (or (is-infty x) (is-infty y))
(mk-int-x true 0)
(mk-int-x false (+ (not-infty x) (not-infty y)))))
(declare-fun x () IntX)
(assert (= x (plus x (mk-int-x false 1))))
; x = x+1 when x |-> infty
(get-model)
(exit)
我将如何在 SBV 中对其进行编码?我尝试了以下方法,但这只是使 SBV 崩溃了。此外,我不知何故怀疑这会做我想做的事,但我对 SBV 的工作原理还不够熟悉。
!/usr/bin/env stack
{- stack script
--resolver nightly-2018-11-23
--package sbv
--package syb
-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Generics
import Data.SBV
data IntX = IntX (Maybe Integer) deriving (Eq, Ord, Data, Read, Show, SymWord, HasKind)
pretty :: IntX -> String
pretty = \case
IntX Nothing -> "∞"
IntX n -> show n
instance Num IntX where
(+) (IntX x) (IntX y) = IntX $ (+) <$> x <*> y
(*) (IntX x) (IntX y) = IntX $ (*) <$> x <*> y
fromInteger = IntX . Just
ex1 = sat $ do
x :: SBV IntX <- free "x"
return $ x .== x + 1
main :: IO ()
main = print =<< ex1
~/temp ✘ ./sbv.hs
sbv.hs: SBV.SMT.SMTLib2.cvtExp.sh: impossible happened; can't translate: s0 + s1
CallStack (from HasCallStack):
error, called at ./Data/SBV/SMT/SMTLib2.hs:681:13 in sbv-7.12-9AiNAYtrUhB8YA6mr6BTn4:Data.SBV.SMT.SMTLib2
这里的根本问题是您的代码混合了 Haskell 的具体 Maybe
类型并试图将其视为符号对象。但是您在 SMT-Lib2 中的实现方式是正确的:您基本上需要在 SBV 中编写相应的代码。
我将从以下内容开始:
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NamedFieldPuns #-}
import Data.SBV
import Data.SBV.Control
import GHC.Generics (Generic)
这只是样板文件;除非您想使用查询模式,否则您不需要 Data.SBV.Control
导入,但正如我们将要看到的那样,它确实派上用场了。
首先要做的是对 IntX
类型进行符号编码;就像您在 SMTLib 中所做的那样:
data SIntX = SIntX { isInf :: SBool
, xVal :: SInteger
}
deriving (Generic, Mergeable)
instance Show SIntX where
show (SIntX inf val) = case (unliteral inf, unliteral val) of
(Just True, _) -> "oo"
(Just False, Just n) -> show n
_ -> "<symbolic>"
除了 Generic
和 Mergeable
的推导之外,上面没有什么值得惊讶的。它只是让 SBV 能够在您的扩展自然数上使用 ite
。另请注意 Show
实例如何通过使用 unliteral
.
接下来,我们添加一些方便的功能,同样不足为奇:
inf :: SIntX
inf = SIntX { isInf = true, xVal = 0 }
nat :: SInteger -> SIntX
nat v = SIntX { isInf = false, xVal = v }
liftU :: (SInteger -> SInteger) -> SIntX -> SIntX
liftU op a = ite (isInf a) inf (nat (op (xVal a)))
liftB :: (SInteger -> SInteger -> SInteger) -> SIntX -> SIntX -> SIntX
liftB op a b = ite (isInf a ||| isInf b) inf (nat (xVal a `op` xVal b))
现在我们可以让 IntX
成为一个数字:
instance Num SIntX where
(+) = liftB (+)
(*) = liftB (*)
negate = liftU negate
abs = liftU abs
signum = liftU signum
fromInteger = nat . literal
(请注意,这意味着 oo - oo = oo
的语义充其量是有问题的。但这不是重点。您可能必须明确定义 -
并根据需要进行处理。类似的评论适用于 signum
。)
既然你想测试相等性,我们还必须定义它的符号版本:
instance EqSymbolic SIntX where
a .== b = ite (isInf a &&& isInf b) true
$ ite (isInf a ||| isInf b) false
$ xVal a .== xVal b
同样,如果你想比较,你必须定义一个OrdSymbolic
实例;但思路还是一样。
我们需要一种方法来创建符号扩展自然数。下面的函数做得很好:
freeSIntX :: String -> Symbolic SIntX
freeSIntX nm = do i <- sBool $ nm ++ "_isInf"
v <- sInteger $ nm ++ "_xVal"
return $ SIntX { isInf = i, xVal = v }
严格来说,您不需要为变量命名。 (即,不需要 nm
参数。)但我发现出于显而易见的原因始终命名我的变量很有帮助。
现在,我们可以编写您的示例代码:
ex1 :: IO SatResult
ex1 = sat $ do x <- freeSIntX "x"
return $ x .== x+1
当我 运行 这个时,我得到:
*Main> ex1
Satisfiable. Model:
x_isInf = True :: Bool
x_xVal = 0 :: Integer
我相信这就是您要找的东西。
当您处理较大的程序时,能够更直接地提取 IntX
值并使用它们进一步编程是有益的。这时候查询模式就派上用场了。一、帮手:
data IntX = IntX (Maybe Integer) deriving Show
queryX :: SIntX -> Query IntX
queryX (SIntX {isInf, xVal}) = do
b <- getValue isInf
v <- getValue xVal
return $ IntX $ if b then Nothing
else Just v
现在我们可以编码了:
ex2 :: IO ()
ex2 = runSMT $ do x <- freeSIntX "x"
constrain $ x .== x+1
query $ do cs <- checkSat
case cs of
Unk -> error "Solver said Unknown!"
Unsat -> error "Solver said Unsatisfiable!"
Sat -> do v <- queryX x
io $ print v
我们得到:
*Main> ex2
IntX Nothing
希望对您有所帮助。我已将所有这些代码放在要点中:https://gist.github.com/LeventErkok/facfd067b813028390c89803b3a0e887