sbv-14.1: Documentation/SBV/Examples/TP/TautologyChecker.hs
-----------------------------------------------------------------------------
-- |
-- Module : Documentation.SBV.Examples.TP.TautologyChecker
-- Copyright : (c) Levent Erkok
-- License : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- A verified tautology checker (unordered BDD-style SAT solver) in SBV.
-- This is a port of the Imandra proof by Grant Passmore, originally
-- inspired by Boyer-Moore '79.
-- See <https://raw.githubusercontent.com/imandra-ai/imandrax-examples/refs/heads/main/src/tautology.iml>
--
-- We define a simple formula type with If-then-else, normalize formulas into a canonical form, and prove
-- both soundness and completeness of the tautology checker. The canonical form is essentially an
-- unordered-BDD, making it easy to evaluate it.
-----------------------------------------------------------------------------
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeAbstractions #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wall -Werror #-}
module Documentation.SBV.Examples.TP.TautologyChecker where
import Prelude hiding (null, tail, head, (++))
import Data.SBV
import Data.SBV.List
import Data.SBV.TP
import Data.SBV.Tuple
#ifdef DOCTEST
-- $setup
-- >>> import Data.SBV
-- >>> import Data.SBV.TP
#endif
-- * Formula representation
-- | A propositional formula with variables and if-then-else.
data Formula = FTrue
| FFalse
| Var { fVar :: Integer }
| If { ifCond :: Formula
, ifThen :: Formula
, ifElse :: Formula
}
-- | Make formulas symbolic.
mkSymbolic [''Formula]
-- * Measuring formulas
-- | Depth of nested If constructors in the condition position.
ifDepth :: SFormula -> SInteger
ifDepth = smtFunction "ifDepth"
$ \f -> [sCase| f of
If c _ _ -> 1 + ifDepth c
_ -> 0
|]
-- | \(\mathit{ifDepth}(f) \geq 0\)
--
-- >>> runTP ifDepthNonNeg
-- Lemma: ifDepthNonNeg Q.E.D.
-- Functions proven terminating: ifDepth
-- [Proven] ifDepthNonNeg :: Ɐf ∷ Formula → Bool
ifDepthNonNeg :: TP (Proof (Forall "f" Formula -> SBool))
ifDepthNonNeg = inductiveLemma "ifDepthNonNeg" (\(Forall f) -> ifDepth f .>= 0) []
-- | Complexity of a formula (for termination measure).
ifComplexity :: SFormula -> SInteger
ifComplexity = smtFunction "ifComplexity"
$ \f -> [sCase| f of
If c l r -> ifComplexity c * (ifComplexity l + ifComplexity r)
_ -> 1
|]
-- | \(\mathit{ifComplexity}(f) > 0\)
--
-- >>> runTP ifComplexityPos
-- Lemma: ifComplexityPos Q.E.D.
-- Functions proven terminating: ifComplexity
-- [Proven] ifComplexityPos :: Ɐf ∷ Formula → Bool
ifComplexityPos :: TP (Proof (Forall "f" Formula -> SBool))
ifComplexityPos = inductiveLemma "ifComplexityPos" (\(Forall f) -> ifComplexity f .> 0) []
-- | The branches of an If have smaller complexity than the whole.
--
-- \(\mathit{ifComplexity}(c) < \mathit{ifComplexity}(\mathit{If}(c, l, r)) \land \mathit{ifComplexity}(l) < \mathit{ifComplexity}(\mathit{If}(c, l, r)) \land \mathit{ifComplexity}(r) < \mathit{ifComplexity}(\mathit{If}(c, l, r))\)
--
-- >>> runTP ifComplexitySmaller
-- Lemma: ifComplexityPos Q.E.D.
-- Lemma: ifComplexitySmaller
-- Step: 1 Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: ifComplexity
-- [Proven] ifComplexitySmaller :: Ɐc ∷ Formula → Ɐl ∷ Formula → Ɐr ∷ Formula → Bool
ifComplexitySmaller :: TP (Proof (Forall "c" Formula -> Forall "l" Formula -> Forall "r" Formula -> SBool))
ifComplexitySmaller = do
icp <- recall ifComplexityPos
calc "ifComplexitySmaller"
(\(Forall c) (Forall l) (Forall r) ->
let ic = ifComplexity (sIf c l r)
in ifComplexity c .< ic .&& ifComplexity l .< ic .&& ifComplexity r .< ic) $
\c l r ->
let ic = ifComplexity (sIf c l r)
cc = ifComplexity c
cl = ifComplexity l
cr = ifComplexity r
in [] |- cc .< ic .&& cl .< ic .&& cr .< ic
?? icp `at` Inst @"f" c
?? icp `at` Inst @"f" l
?? icp `at` Inst @"f" r
=: sTrue
=: qed
-- * Normalization
-- | Check if a formula is in normal form (no nested If in condition position).
isNormal :: SFormula -> SBool
isNormal = smtFunction "isNormal"
$ \f -> [sCase| f of
If c p q -> sNot (isIf c) .&& isNormal p .&& isNormal q
_ -> sTrue
|]
-- | Normalize a formula by eliminating nested Ifs in condition position.
--
-- The key transformation is:
--
-- @
-- If (If (p, q, r), left, right)
-- =
-- If (p, If (q, left, right), If (r, left, right))
-- @
--
-- Note that this transformation increases the size of the formula, but reduces its complexity.
normalize :: SFormula -> SFormula
normalize = smtFunctionWithMeasure "normalize"
( \f -> tuple (ifComplexity f, ifDepth f)
, [ measureLemma ifDepthNonNeg
, measureLemma ifComplexityPos
, measureLemmaWith z3 ifComplexitySmaller
, measureLemmaWith z3 normalizePreservesComplexity
]
)
$ \f -> [sCase| f of
If (If p q r) left right -> normalize (sIf p (sIf q left right) (sIf r left right))
If c left right -> sIf c (normalize left) (normalize right)
_ -> f
|]
-- | The normalization transformation preserves complexity.
--
-- \(\mathit{ifComplexity}(\mathit{If}(p, \mathit{If}(q, l, r), \mathit{If}(s, l, r))) = \mathit{ifComplexity}(\mathit{If}(\mathit{If}(p, q, s), l, r))\)
--
-- >>> runTP normalizePreservesComplexity
-- Lemma: helper Q.E.D.
-- Lemma: normalizePreservesComplexity
-- Step: 1 Q.E.D.
-- Step: 2 Q.E.D.
-- Step: 3 Q.E.D.
-- Step: 4 Q.E.D.
-- Step: 5 Q.E.D.
-- Step: 6 Q.E.D.
-- Step: 7 Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: ifComplexity
-- [Proven] normalizePreservesComplexity :: Ɐp ∷ Formula → Ɐq ∷ Formula → Ɐs ∷ Formula → Ɐl ∷ Formula → Ɐr ∷ Formula → Bool
normalizePreservesComplexity :: TP (Proof (Forall "p" Formula -> Forall "q" Formula -> Forall "s" Formula -> Forall "l" Formula -> Forall "r" Formula -> SBool))
normalizePreservesComplexity = do
-- The following is a trivial lemma, but without it the solver don't seem to be able to make progress since
-- it needs to instantiate it properly. So we help the solver out explicitly.
helper <- lemma "helper"
(\(Forall @"a" a) (Forall @"b" b) (Forall @"c" c) -> a .== b .=> a * c .== b * (c :: SInteger))
[]
calc "normalizePreservesComplexity"
(\(Forall p) (Forall q) (Forall s) (Forall l) (Forall r) ->
ifComplexity (sIf p (sIf q l r) (sIf s l r)) .== ifComplexity (sIf (sIf p q s) l r)) $
\p q s l r ->
let cp = ifComplexity p
cq = ifComplexity q
cs = ifComplexity s
cl = ifComplexity l
cr = ifComplexity r
in [] |- ifComplexity (sIf p (sIf q l r) (sIf s l r))
=: cp * (ifComplexity (sIf q l r) + ifComplexity (sIf s l r))
=: cp * (cq * (cl + cr) + cs * (cl + cr))
=: cp * ((cq + cs) * (cl + cr))
=: (cp * (cq + cs)) * (cl + cr)
?? helper `at` (Inst @"a" (ifComplexity (sIf p q s)), Inst @"b" (cp * (cq + cs)), Inst @"c" (cl + cr))
=: ifComplexity (sIf p q s) * (cl + cr)
=: ifComplexity (sIf p q s) * (ifComplexity l + ifComplexity r)
=: ifComplexity (sIf (sIf p q s) l r)
=: qed
-- * Variable bindings
-- | A binding associates a variable ID with a boolean value.
data Binding = Binding { varId :: Integer
, value :: Bool
}
-- | Make bindings symbolic.
mkSymbolic [''Binding]
-- | Look up a variable in the binding list. If it's not in the list, then it's false.
lookUp :: SInteger -> SList Binding -> SBool
lookUp = smtFunction "lookUp"
$ \vid bs -> [sCase| bs of
[] -> sFalse
Binding bId bVal : rest | vid .== bId -> bVal
| True -> lookUp vid rest
|]
-- | Check if a variable is assigned in the bindings.
isAssigned :: SInteger -> SList Binding -> SBool
isAssigned = smtFunction "isAssigned"
$ \vid bs -> [sCase| bs of
[] -> sFalse
Binding bId _ : rst | bId .== vid -> sTrue
| True -> isAssigned vid rst
|]
-- | Add a binding assuming the variable is true.
assumeTrue :: SInteger -> SList Binding -> SList Binding
assumeTrue vid bs = sBinding vid sTrue .: bs
-- | Add a binding assuming the variable is false.
assumeFalse :: SInteger -> SList Binding -> SList Binding
assumeFalse vid bs = sBinding vid sFalse .: bs
-- | Adding a binding preserves existing assignments.
--
-- >>> runTP isAssignedExtends
-- Lemma: isAssignedExtends Q.E.D.
-- Functions proven terminating: isAssigned
-- [Proven] isAssignedExtends :: Ɐi ∷ Integer → Ɐn ∷ Integer → Ɐv ∷ Bool → Ɐbs ∷ [Binding] → Bool
isAssignedExtends :: TP (Proof (Forall "i" Integer -> Forall "n" Integer -> Forall "v" Bool -> Forall "bs" [Binding] -> SBool))
isAssignedExtends = lemma "isAssignedExtends"
(\(Forall i) (Forall n) (Forall v) (Forall bs) -> isAssigned i bs .=> isAssigned i (sBinding n v .: bs))
[]
-- | Looking up a variable in extended bindings: if already assigned, value is preserved.
--
-- >>> runTP lookUpExtends
-- Lemma: lookUpExtends Q.E.D.
-- Functions proven terminating: isAssigned, lookUp
-- [Proven] lookUpExtends :: Ɐi ∷ Integer → Ɐn ∷ Integer → Ɐv ∷ Bool → Ɐbs ∷ [Binding] → Bool
lookUpExtends :: TP (Proof (Forall "i" Integer -> Forall "n" Integer -> Forall "v" Bool -> Forall "bs" [Binding] -> SBool))
lookUpExtends = lemma "lookUpExtends"
(\(Forall i) (Forall n) (Forall v) (Forall bs) ->
isAssigned i bs .&& i ./= n .=> lookUp i (sBinding n v .: bs) .== lookUp i bs)
[]
-- | Looking up a variable that was just added returns the added value.
--
-- >>> runTP lookUpSame
-- Lemma: lookUpSame Q.E.D.
-- Functions proven terminating: lookUp
-- [Proven] lookUpSame :: Ɐn ∷ Integer → Ɐv ∷ Bool → Ɐbs ∷ [Binding] → Bool
lookUpSame :: TP (Proof (Forall "n" Integer -> Forall "v" Bool -> Forall "bs" [Binding] -> SBool))
lookUpSame = lemma "lookUpSame" (\(Forall n) (Forall v) (Forall bs) -> lookUp n (sBinding n v .: bs) .== v) []
-- | Adding a binding for a variable makes it assigned.
--
-- >>> runTP isAssignedSame
-- Lemma: isAssignedSame Q.E.D.
-- Functions proven terminating: isAssigned
-- [Proven] isAssignedSame :: Ɐn ∷ Integer → Ɐv ∷ Bool → Ɐbs ∷ [Binding] → Bool
isAssignedSame :: TP (Proof (Forall "n" Integer -> Forall "v" Bool -> Forall "bs" [Binding] -> SBool))
isAssignedSame = lemma "isAssignedSame" (\(Forall n) (Forall v) (Forall bs) -> isAssigned n (sBinding n v .: bs)) []
-- * Formula evaluation
-- | Evaluate a formula under a binding environment.
eval :: SFormula -> SList Binding -> SBool
eval = smtFunction "eval"
$ \f bs -> [sCase| f of
Var n -> lookUp n bs
If c l r | eval c bs -> eval l bs
| True -> eval r bs
FTrue -> sTrue
FFalse -> sFalse
|]
-- * Tautology checking
-- | Check if a normalized formula is a tautology.
isTautology' :: SFormula -> SList Binding -> SBool
isTautology' = smtFunction "isTautology'" $ \f bs ->
[sCase| f of
-- Trivial cases
FTrue -> sTrue
FFalse -> sFalse
-- Variable
Var _ -> eval f bs
-- Constant branches
If FTrue l _ -> isTautology' l bs
If FFalse _ r -> isTautology' r bs
-- Branching on a variable
If (Var n) l r
-- We have already this variable, so evaluate based on the current choice
| isAssigned n bs, eval (sVar n) bs -> isTautology' l bs
| isAssigned n bs -> isTautology' r bs
-- We haven't yet assigned this variable. Both branches should work out:
| True -> isTautology' l (assumeTrue n bs)
.&& isTautology' r (assumeFalse n bs)
If _ _ _ -> sFalse -- Contradicts isNormal assumption
|]
-- | Main tautology checker.
isTautology :: SFormula -> SBool
isTautology f = isTautology' (normalize f) []
-- * Soundness
-- | \(\mathit{lookUp}(x, a \mathbin{+\!\!+} b) = \mathit{if } \mathit{isAssigned}(x, a) \mathit{ then } \mathit{lookUp}(x, a) \mathit{ else } \mathit{lookUp}(x, b)\)
--
-- If we look up a variable in a concatenated binding list, we first check
-- the first list, and only if not found there, check the second.
--
-- >>> runTP lookUpStable
-- Inductive lemma: lookUpStable
-- Step: Base Q.E.D.
-- Step: 1 (2 way case split)
-- Step: 1.1.1 Q.E.D.
-- Step: 1.1.2 Q.E.D.
-- Step: 1.2.1 Q.E.D.
-- Step: 1.2.2 Q.E.D.
-- Step: 1.Completeness Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: isAssigned, lookUp
-- [Proven] lookUpStable :: Ɐa ∷ [Binding] → Ɐx ∷ Integer → Ɐb ∷ [Binding] → Bool
lookUpStable :: TP (Proof (Forall "a" [Binding] -> Forall "x" Integer -> Forall "b" [Binding] -> SBool))
lookUpStable =
induct "lookUpStable"
(\(Forall a) (Forall x) (Forall b) -> lookUp x (a ++ b) .== ite (isAssigned x a) (lookUp x a) (lookUp x b)) $
\ih (binding, a) x b ->
let vid = svarId binding
val = svalue binding
in [] |- lookUp x ((binding .: a) ++ b)
=: cases [ vid .== x ==> ite (isAssigned x (binding .: a)) (lookUp x (binding .: a)) (lookUp x b)
=: val
=: qed
, vid ./= x ==> lookUp x (a ++ b)
?? ih
=: ite (isAssigned x a) (lookUp x a) (lookUp x b)
=: qed
]
-- | \(\mathit{lookUp}(x, a) \implies \mathit{isAssigned}(x, a)\)
--
-- >>> runTP trueIsAssigned
-- Inductive lemma: trueIsAssigned
-- Step: Base Q.E.D.
-- Step: 1 (2 way case split)
-- Step: 1.1 Q.E.D.
-- Step: 1.2.1 Q.E.D.
-- Step: 1.2.2 Q.E.D.
-- Step: 1.Completeness Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: isAssigned, lookUp
-- [Proven] trueIsAssigned :: Ɐa ∷ [Binding] → Ɐx ∷ Integer → Bool
trueIsAssigned :: TP (Proof (Forall "a" [Binding] -> Forall "x" Integer -> SBool))
trueIsAssigned =
induct "trueIsAssigned"
(\(Forall a) (Forall x) -> lookUp x a .=> isAssigned x a) $
\ih (binding, a) x ->
let vid = [sCase| binding of Binding v _ -> v|]
in [lookUp x (binding .: a)]
|- isAssigned x (binding .: a)
=: cases [ vid .== x ==> trivial
, vid ./= x ==> isAssigned x a
?? ih
=: sTrue
=: qed
]
-- | \(\mathit{value} = \mathit{lookUp}(x, bs) \implies \mathit{eval}(f, \{x \mapsto \mathit{value}\} :: bs) = \mathit{eval}(f, bs)\)
--
-- If we add a redundant binding (same id and value) to the front, evaluation doesn't change.
--
-- >>> runTPWith cvc5 evalStable
-- Lemma: ifComplexityPos Q.E.D.
-- Lemma: ifComplexitySmaller Q.E.D.
-- Inductive lemma (strong): evalStable
-- Step: Measure is non-negative Q.E.D.
-- Step: 1 (4 way case split)
-- Step: 1.1 Q.E.D.
-- Step: 1.2 Q.E.D.
-- Step: 1.3 Q.E.D.
-- Step: 1.4.1 Q.E.D.
-- Step: 1.4.2 Q.E.D.
-- Step: 1.4.3 Q.E.D.
-- Step: 1.4.4 Q.E.D.
-- Step: 1.4.5 Q.E.D.
-- Step: 1.4.6 Q.E.D.
-- Step: 1.Completeness Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: eval, ifComplexity, lookUp
-- [Proven] evalStable :: Ɐf ∷ Formula → Ɐx ∷ Integer → Ɐv ∷ Bool → Ɐbs ∷ [Binding] → Bool
evalStable :: TP (Proof (Forall "f" Formula -> Forall "x" Integer -> Forall "v" Bool -> Forall "bs" [Binding] -> SBool))
evalStable = do
icp <- recall ifComplexityPos
ibs <- recall ifComplexitySmaller
sInduct "evalStable"
(\(Forall f) (Forall x) (Forall v) (Forall bs) -> v .== lookUp x bs .=> eval f (sBinding x v .: bs) .== eval f bs)
(\f _ _ _ -> ifComplexity f, [proofOf icp]) $
\ih f x v bs ->
let b = sBinding x v
in [v .== lookUp x bs]
|- cases [ isFTrue f ==> trivial
, isFFalse f ==> trivial
, isVar f ==> trivial
, isIf f ==>
let c = sifCond f
l = sifThen f
r = sifElse f
in eval f (b .: bs)
=: eval (sIf c l r) (b .: bs)
=: ite (eval c (b .: bs)) (eval l (b .: bs)) (eval r (b .: bs))
?? ih `at` (Inst @"f" c, Inst @"x" x, Inst @"v" v, Inst @"bs" bs)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
=: ite (eval c bs) (eval l (b .: bs)) (eval r (b .: bs))
?? ih `at` (Inst @"f" l, Inst @"x" x, Inst @"v" v, Inst @"bs" bs)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
=: ite (eval c bs) (eval l bs) (eval r (b .: bs))
?? ih `at` (Inst @"f" r, Inst @"x" x, Inst @"v" v, Inst @"bs" bs)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
=: ite (eval c bs) (eval l bs) (eval r bs)
=: eval (sIf c l r) bs
=: qed
]
-- | Key soundness lemma: If a normalized formula is a tautology under bindings @b@,
-- then it evaluates to true under @b ++ a@ for any @a@.
--
-- >>> runTPWith cvc5 tautologyImpliesEval
-- Lemma: ifComplexityPos Q.E.D.
-- Lemma: ifComplexitySmaller Q.E.D.
-- Lemma: lookUpStable Q.E.D.
-- Lemma: trueIsAssigned Q.E.D.
-- Lemma: evalStable Q.E.D.
-- Inductive lemma (strong): tautologyImpliesEval
-- Step: Measure is non-negative Q.E.D.
-- Step: 1 (4 way case split)
-- Step: 1.1 Q.E.D.
-- Step: 1.2 Q.E.D.
-- Step: 1.3.1 Q.E.D.
-- Step: 1.3.2 Q.E.D.
-- Step: 1.3.3 Q.E.D.
-- Step: 1.3.4 Q.E.D.
-- Step: 1.3.5 Q.E.D.
-- Step: 1.4 (4 way case split)
-- Step: 1.4.1.1 Q.E.D.
-- Step: 1.4.1.2 Q.E.D.
-- Step: 1.4.2.1 Q.E.D.
-- Step: 1.4.2.2 Q.E.D.
-- Step: 1.4.2.3 Q.E.D.
-- Step: 1.4.3 (2 way case split)
-- Step: 1.4.3.1.1 Q.E.D.
-- Step: 1.4.3.1.2 Q.E.D.
-- Step: 1.4.3.1.3 Q.E.D.
-- Step: 1.4.3.1.4 Q.E.D.
-- Step: 1.4.3.2 (2 way case split)
-- Step: 1.4.3.2.1.1 Q.E.D.
-- Step: 1.4.3.2.1.2 Q.E.D.
-- Step: 1.4.3.2.1.3 Q.E.D.
-- Step: 1.4.3.2.1.4 Q.E.D.
-- Step: 1.4.3.2.1.5 Q.E.D.
-- Step: 1.4.3.2.1.6 Q.E.D.
-- Step: 1.4.3.2.1.7 Q.E.D.
-- Step: 1.4.3.2.1.8 Q.E.D.
-- Step: 1.4.3.2.2.1 Q.E.D.
-- Step: 1.4.3.2.2.2 Q.E.D.
-- Step: 1.4.3.2.2.3 Q.E.D.
-- Step: 1.4.3.2.2.4 Q.E.D.
-- Step: 1.4.3.2.2.5 Q.E.D.
-- Step: 1.4.3.2.2.6 Q.E.D.
-- Step: 1.4.3.2.2.7 Q.E.D.
-- Step: 1.4.3.2.2.8 Q.E.D.
-- Step: 1.4.3.2.Completeness Q.E.D.
-- Step: 1.4.3.Completeness Q.E.D.
-- Step: 1.4.4 Q.E.D.
-- Step: 1.4.Completeness Q.E.D.
-- Step: 1.Completeness Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: eval, ifComplexity, isAssigned, isNormal, isTautology', lookUp
-- [Proven] tautologyImpliesEval :: Ɐf ∷ Formula → Ɐa ∷ [Binding] → Ɐb ∷ [Binding] → Bool
tautologyImpliesEval :: TP (Proof (Forall "f" Formula -> Forall "a" [Binding] -> Forall "b" [Binding] -> SBool))
tautologyImpliesEval = do
icp <- recall ifComplexityPos
ibs <- recall ifComplexitySmaller
lus <- recall lookUpStable
tia <- recall trueIsAssigned
evs <- recall evalStable
sInduct "tautologyImpliesEval"
(\(Forall f) (Forall a) (Forall b) -> isNormal f .&& isTautology' f b .=> eval f (b ++ a))
(\f _ _ -> ifComplexity f, [proofOf icp]) $
\ih f a b ->
[isNormal f, isTautology' f b]
|- cases [ isFTrue f ==> trivial
, isFFalse f ==> trivial
, isVar f ==> let n = sfVar f
in eval f (b ++ a)
=: eval (sVar n) (b ++ a)
=: lookUp n (b ++ a)
?? lus `at` (Inst @"a" b, Inst @"x" n, Inst @"b" a)
=: ite (isAssigned n b) (lookUp n b) (lookUp n a)
?? tia `at` (Inst @"a" b, Inst @"x" n)
=: lookUp n b
=: sTrue
=: qed
, isIf f ==>
let c = sifCond f
l = sifThen f
r = sifElse f
in cases [ isFTrue c ==> eval (sIf c l r) (b ++ a)
=: ite (eval c (b ++ a)) (eval l (b ++ a)) (eval r (b ++ a))
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` (Inst @"f" l, Inst @"a" a, Inst @"b" b)
=: sTrue
=: qed
, isFFalse c ==> eval (sIf c l r) (b ++ a)
=: ite (eval c (b ++ a)) (eval l (b ++ a)) (eval r (b ++ a))
=: eval r (b ++ a)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` (Inst @"f" r, Inst @"a" a, Inst @"b" b)
=: sTrue
=: qed
, isVar c ==> let n = sfVar c
in cases [ isAssigned n b ==>
eval (sIf (sVar n) l r) (b ++ a)
=: ite (eval (sVar n) (b ++ a)) (eval l (b ++ a)) (eval r (b ++ a))
=: ite (lookUp n (b ++ a)) (eval l (b ++ a)) (eval r (b ++ a))
?? lus `at` (Inst @"a" b, Inst @"x" n, Inst @"b" a)
=: ite (lookUp n b) (eval l (b ++ a)) (eval r (b ++ a))
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` (Inst @"f" l, Inst @"a" a, Inst @"b" b)
?? ih `at` (Inst @"f" r, Inst @"a" a, Inst @"b" b)
=: sTrue
=: qed
, sNot (isAssigned n b) ==>
cases [ lookUp n a ==>
eval (sIf (sVar n) l r) (b ++ a)
=: ite (eval (sVar n) (b ++ a)) (eval l (b ++ a)) (eval r (b ++ a))
=: ite (lookUp n (b ++ a)) (eval l (b ++ a)) (eval r (b ++ a))
?? lus `at` (Inst @"a" b, Inst @"x" n, Inst @"b" a)
=: ite (lookUp n a) (eval l (b ++ a)) (eval r (b ++ a))
=: eval l (b ++ a)
?? evs `at` (Inst @"f" l, Inst @"x" n, Inst @"v" (lookUp n a), Inst @"bs" (b ++ a))
?? lus `at` (Inst @"a" b, Inst @"x" n, Inst @"b" a)
=: eval l (sBinding n (lookUp n a) .: (b ++ a))
=: eval l (sBinding n sTrue .: (b ++ a))
=: eval l (assumeTrue n b ++ a)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` (Inst @"f" l, Inst @"a" a, Inst @"b" (assumeTrue n b))
=: sTrue
=: qed
, sNot (lookUp n a) ==>
eval (sIf (sVar n) l r) (b ++ a)
=: ite (eval (sVar n) (b ++ a)) (eval l (b ++ a)) (eval r (b ++ a))
=: ite (lookUp n (b ++ a)) (eval l (b ++ a)) (eval r (b ++ a))
?? lus `at` (Inst @"a" b, Inst @"x" n, Inst @"b" a)
=: ite (lookUp n a) (eval l (b ++ a)) (eval r (b ++ a))
=: eval r (b ++ a)
?? evs `at` (Inst @"f" r, Inst @"x" n, Inst @"v" (lookUp n a), Inst @"bs" (b ++ a))
?? lus `at` (Inst @"a" b, Inst @"x" n, Inst @"b" a)
=: eval r (sBinding n (lookUp n a) .: (b ++ a))
=: eval r (sBinding n sFalse .: (b ++ a))
=: eval r (assumeFalse n b ++ a)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` (Inst @"f" r, Inst @"a" a, Inst @"b" (assumeFalse n b))
=: sTrue
=: qed
]
]
, isIf c ==> trivial -- Contradicts isNormal
]
]
-- * Normalization correctness
-- | \(\mathit{isNormal}(\mathit{normalize}(f))\)
--
-- Normalization produces normalized formulas.
--
-- >>> runTP normalizeCorrect
-- Lemma: ifComplexityPos Q.E.D.
-- Lemma: ifComplexitySmaller Q.E.D.
-- Lemma: normalizePreservesComplexity Q.E.D.
-- Lemma: ifDepthNonNeg Q.E.D.
-- Inductive lemma (strong): normalizeCorrect
-- Step: Measure is non-negative Q.E.D.
-- Step: 1 (4 way case split)
-- Step: 1.1 Q.E.D.
-- Step: 1.2 Q.E.D.
-- Step: 1.3 Q.E.D.
-- Step: 1.4 (2 way case split)
-- Step: 1.4.1.1 Q.E.D.
-- Step: 1.4.1.2 Q.E.D.
-- Step: 1.4.2.1 Q.E.D.
-- Step: 1.4.2.2 Q.E.D.
-- Step: 1.4.2.3 Q.E.D.
-- Step: 1.4.2.4 Q.E.D.
-- Step: 1.4.2.5 Q.E.D.
-- Step: 1.4.Completeness Q.E.D.
-- Step: 1.Completeness Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: ifComplexity, ifDepth, isNormal, normalize
-- [Proven] normalizeCorrect :: Ɐf ∷ Formula → Bool
normalizeCorrect :: TP (Proof (Forall "f" Formula -> SBool))
normalizeCorrect = do
icp <- recall ifComplexityPos
ibs <- recall ifComplexitySmaller
npc <- recall normalizePreservesComplexity
idn <- recall ifDepthNonNeg
sInductWith cvc5 "normalizeCorrect"
(\(Forall f) -> isNormal (normalize f))
(\f -> tuple (ifComplexity f, ifDepth f), [proofOf icp, proofOf idn]) $
\ih f -> []
|- isNormal (normalize f)
=: cases [ isFTrue f ==> trivial
, isFFalse f ==> trivial
, isVar f ==> trivial
, isIf f ==> let c = sifCond f
l = sifThen f
r = sifElse f
in cases [ isIf c ==>
let p = sifCond c
q = sifThen c
rc = sifElse c
transformed = sIf p (sIf q l r) (sIf rc l r)
in isNormal (normalize transformed)
?? npc `at` (Inst @"p" p, Inst @"q" q, Inst @"s" rc, Inst @"l" l, Inst @"r" r)
?? ih `at` Inst @"f" transformed
=: sTrue
=: qed
, sNot (isIf c) ==>
isNormal (sIf c (normalize l) (normalize r))
=: sNot (isIf c) .&& isNormal (normalize l) .&& isNormal (normalize r)
=: isNormal (normalize l) .&& isNormal (normalize r)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` Inst @"f" l
=: isNormal (normalize r)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` Inst @"f" r
=: sTrue
=: qed
]
]
-- | \(\mathit{isNormal}(f) \implies \mathit{normalize}(f) = f\)
--
-- Normalizing a normalized formula is the identity.
--
-- >>> runTP normalizeSame
-- Lemma: ifComplexityPos Q.E.D.
-- Lemma: ifComplexitySmaller Q.E.D.
-- Inductive lemma (strong): normalizeSame
-- Step: Measure is non-negative Q.E.D.
-- Step: 1 (4 way case split)
-- Step: 1.1 Q.E.D.
-- Step: 1.2 Q.E.D.
-- Step: 1.3 Q.E.D.
-- Step: 1.4.1 Q.E.D.
-- Step: 1.4.2 Q.E.D.
-- Step: 1.Completeness Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: ifComplexity, isNormal, normalize
-- [Proven] normalizeSame :: Ɐf ∷ Formula → Bool
normalizeSame :: TP (Proof (Forall "f" Formula -> SBool))
normalizeSame = do
icp <- recall ifComplexityPos
ibs <- recall ifComplexitySmaller
sInduct "normalizeSame"
(\(Forall f) -> isNormal f .=> normalize f .== f)
(ifComplexity, [proofOf icp]) $
\ih f -> [isNormal f]
|- cases [ isFTrue f ==> trivial
, isFFalse f ==> trivial
, isVar f ==> trivial
, isIf f ==> let c = sifCond f
l = sifThen f
r = sifElse f
in sIf c (normalize l) (normalize r)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` Inst @"f" l
=: sIf c l (normalize r)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` Inst @"f" r
=: sIf c l r
=: qed
]
-- | \(\mathit{eval}(\mathit{normalize}(f), bs) = \mathit{eval}(f, bs)\)
--
-- Normalization preserves semantics.
--
-- >>> runTP normalizeRespectsTruth
-- Lemma: ifComplexityPos Q.E.D.
-- Lemma: ifComplexitySmaller Q.E.D.
-- Lemma: normalizePreservesComplexity Q.E.D.
-- Lemma: ifDepthNonNeg Q.E.D.
-- Inductive lemma (strong): normalizeRespectsTruth
-- Step: Measure is non-negative Q.E.D.
-- Step: 1 (4 way case split)
-- Step: 1.1 Q.E.D.
-- Step: 1.2 Q.E.D.
-- Step: 1.3 Q.E.D.
-- Step: 1.4 (2 way case split)
-- Step: 1.4.1 Q.E.D.
-- Step: 1.4.2.1 Q.E.D.
-- Step: 1.4.2.2 Q.E.D.
-- Step: 1.4.2.3 Q.E.D.
-- Step: 1.4.Completeness Q.E.D.
-- Step: 1.Completeness Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: eval, ifComplexity, ifDepth, lookUp, normalize
-- [Proven] normalizeRespectsTruth :: Ɐf ∷ Formula → Ɐbs ∷ [Binding] → Bool
normalizeRespectsTruth :: TP (Proof (Forall "f" Formula -> Forall "bs" [Binding] -> SBool))
normalizeRespectsTruth = do
icp <- recall ifComplexityPos
ibs <- recall ifComplexitySmaller
npc <- recall normalizePreservesComplexity
idn <- recall ifDepthNonNeg
sInductWith cvc5 "normalizeRespectsTruth"
(\(Forall f) (Forall bs) -> eval (normalize f) bs .== eval f bs)
(\f _ -> tuple (ifComplexity f, ifDepth f), [proofOf icp, proofOf idn]) $
\ih f bs -> []
|- cases [ isFTrue f ==> trivial
, isFFalse f ==> trivial
, isVar f ==> trivial
, isIf f ==> let c = sifCond f
l = sifThen f
r = sifElse f
in cases [ isIf c ==>
let p = sifCond c
q = sifThen c
rc = sifElse c
transformed = sIf p (sIf q l r) (sIf rc l r)
in eval (normalize (sIf c l r)) bs .== eval (sIf c l r) bs
?? npc `at` (Inst @"p" p, Inst @"q" q, Inst @"s" rc, Inst @"l" l, Inst @"r" r)
?? ih `at` (Inst @"f" transformed, Inst @"bs" bs)
=: sTrue
=: qed
, sNot (isIf c) ==>
eval (normalize (sIf c l r)) bs .== eval (sIf c l r) bs
=: eval (sIf c (normalize l) (normalize r)) bs .== eval (sIf c l r) bs
=: ite (eval c bs) (eval (normalize l) bs) (eval (normalize r) bs) .== ite (eval c bs) (eval l bs) (eval r bs)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` (Inst @"f" l, Inst @"bs" bs)
?? ih `at` (Inst @"f" r, Inst @"bs" bs)
=: sTrue
=: qed
]
]
-- * Main soundness theorem
-- | \(\mathit{isTautology}(f) \implies \mathit{eval}(f, \mathit{bindings})\)
--
-- If the tautology checker says a formula is a tautology, then it evaluates
-- to true under any binding environment. This is the soundness theorem.
--
-- >>> runTP soundness
-- Lemma: tautologyImpliesEval Q.E.D.
-- Lemma: normalizeRespectsTruth Q.E.D.
-- Lemma: normalizeCorrect Q.E.D.
-- Lemma: soundness
-- Step: 1 Q.E.D.
-- Step: 2 Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: eval, ifComplexity, ifDepth, isAssigned, isNormal, isTautology', lookUp, normalize
-- [Proven] soundness :: Ɐf ∷ Formula → Ɐbindings ∷ [Binding] → Bool
soundness :: TP (Proof (Forall "f" Formula -> Forall "bindings" [Binding] -> SBool))
soundness = do
tie <- recallWith cvc5 tautologyImpliesEval
nrt <- recall normalizeRespectsTruth
nc <- recall normalizeCorrect
calc "soundness"
(\(Forall f) (Forall bindings) -> isTautology f .=> eval f bindings) $
\f bindings -> [isTautology f]
|- eval f bindings
?? nrt `at` (Inst @"f" f, Inst @"bs" bindings)
=: eval (normalize f) bindings
?? nc `at` Inst @"f" f
?? tie `at` (Inst @"f" (normalize f), Inst @"a" bindings, Inst @"b" [])
=: sTrue
=: qed
-- * Completeness
-- | Result of attempting to falsify a formula.
data FalsifyResult = FalsifyResult { falsified :: Bool
, cex :: [Binding]
}
-- | Make FalsifyResult symbolic.
mkSymbolic [''FalsifyResult]
-- | Attempt to falsify a normalized formula under given bindings.
-- Returns whether falsification succeeded and the counterexample bindings.
falsify' :: SFormula -> SList Binding -> SFalsifyResult
falsify' = smtFunction "falsify'" $ \f bs ->
[sCase| f of
FTrue -> sFalsifyResult sFalse []
FFalse -> sFalsifyResult sTrue bs
Var i
| isAssigned i bs, eval (sVar i) bs -> sFalsifyResult sFalse []
| isAssigned i bs -> sFalsifyResult sTrue bs
| True -> sFalsifyResult sTrue (sBinding i sFalse .: bs)
If (Var i) l r
| isAssigned i bs, eval (sVar i) bs -> falsify' l bs
| isAssigned i bs -> falsify' r bs
| True -> let resL = falsify' l (assumeTrue i bs)
in ite (sNot (sfalsified resL))
(falsify' r (assumeFalse i bs))
resL
If FTrue l _ -> falsify' l bs
If FFalse _ r -> falsify' r bs
If _ _ _ -> sFalsifyResult sFalse [] -- Shouldn't happen for normal formulas
|]
-- | Falsify a formula by first normalizing it.
falsify :: SFormula -> SFalsifyResult
falsify f = falsify' (normalize f) []
-- * Completeness lemmas
-- | If a normalized formula is not a tautology, then falsify' returns falsified = true.
--
-- >>> runTPWith cvc5 nonTautIsFalsified
-- Lemma: ifComplexityPos Q.E.D.
-- Lemma: ifComplexitySmaller Q.E.D.
-- Inductive lemma (strong): nonTautIsFalsified
-- Step: Measure is non-negative Q.E.D.
-- Step: 1 (4 way case split)
-- Step: 1.1 Q.E.D.
-- Step: 1.2 Q.E.D.
-- Step: 1.3 Q.E.D.
-- Step: 1.4 Q.E.D.
-- Step: 1.Completeness Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: eval, falsify', ifComplexity, isAssigned, isNormal, isTautology', lookUp
-- [Proven] nonTautIsFalsified :: Ɐf ∷ Formula → Ɐbs ∷ [Binding] → Bool
nonTautIsFalsified :: TP (Proof (Forall "f" Formula -> Forall "bs" [Binding] -> SBool))
nonTautIsFalsified = do
icp <- recall ifComplexityPos
ibs <- recall ifComplexitySmaller
sInduct "nonTautIsFalsified"
(\(Forall f) (Forall bs) -> isNormal f .&& sNot (isTautology' f bs) .=> sfalsified (falsify' f bs))
(\f _ -> ifComplexity f, [proofOf icp]) $
\ih f bs -> [isNormal f, sNot (isTautology' f bs)]
|- cases [ isFTrue f ==> trivial
, isFFalse f ==> trivial
, isVar f ==> trivial
, isIf f ==> let c = sifCond f
l = sifThen f
r = sifElse f
in sfalsified (falsify' f bs)
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` (Inst @"f" l, Inst @"bs" bs)
?? ih `at` (Inst @"f" r, Inst @"bs" bs)
?? ih `at` (Inst @"f" l, Inst @"bs" (assumeTrue (sfVar c) bs))
?? ih `at` (Inst @"f" r, Inst @"bs" (assumeFalse (sfVar c) bs))
=: sTrue
=: qed
]
-- | If a variable is assigned in the input bindings and falsify' succeeds,
-- the lookup value is preserved in the output bindings.
--
-- >>> runTPWith cvc5 falsifyExtendsBindings
-- Lemma: ifComplexityPos Q.E.D.
-- Lemma: ifComplexitySmaller Q.E.D.
-- Lemma: isAssignedExtends Q.E.D.
-- Lemma: lookUpExtends Q.E.D.
-- Inductive lemma (strong): falsifyExtendsBindings
-- Step: Measure is non-negative Q.E.D.
-- Step: 1 (4 way case split)
-- Step: 1.1 Q.E.D.
-- Step: 1.2 Q.E.D.
-- Step: 1.3 Q.E.D.
-- Step: 1.4 Q.E.D.
-- Step: 1.Completeness Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: eval, falsify', ifComplexity, isAssigned, lookUp
-- [Proven] falsifyExtendsBindings :: Ɐf ∷ Formula → Ɐbs ∷ [Binding] → Ɐi ∷ Integer → Bool
falsifyExtendsBindings :: TP (Proof (Forall "f" Formula -> Forall "bs" [Binding] -> Forall "i" Integer -> SBool))
falsifyExtendsBindings = do
icp <- recall ifComplexityPos
ibs <- recall ifComplexitySmaller
iae <- recall isAssignedExtends
lue <- recall lookUpExtends
sInduct "falsifyExtendsBindings"
(\(Forall f) (Forall bs) (Forall i) ->
isAssigned i bs .&& sfalsified (falsify' f bs) .=>
lookUp i (scex (falsify' f bs)) .== lookUp i bs)
(\f _ _ -> ifComplexity f, [proofOf icp]) $
\ih f bs i -> [isAssigned i bs, sfalsified (falsify' f bs)]
|- cases [ isFTrue f ==> trivial
, isFFalse f ==> trivial
, isVar f ==> let n = sfVar f
in lookUp i (scex (falsify' f bs)) .== lookUp i bs
?? lue `at` (Inst @"i" i, Inst @"n" n, Inst @"v" sFalse, Inst @"bs" bs)
=: sTrue
=: qed
, isIf f ==> let c = sifCond f
l = sifThen f
r = sifElse f
n = sfVar c
in lookUp i (scex (falsify' f bs)) .== lookUp i bs
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? iae `at` (Inst @"i" i, Inst @"n" n, Inst @"v" sTrue, Inst @"bs" bs)
?? iae `at` (Inst @"i" i, Inst @"n" n, Inst @"v" sFalse, Inst @"bs" bs)
?? lue `at` (Inst @"i" i, Inst @"n" n, Inst @"v" sTrue, Inst @"bs" bs)
?? lue `at` (Inst @"i" i, Inst @"n" n, Inst @"v" sFalse, Inst @"bs" bs)
?? ih `at` (Inst @"f" l, Inst @"bs" bs, Inst @"i" i)
?? ih `at` (Inst @"f" r, Inst @"bs" bs, Inst @"i" i)
?? ih `at` (Inst @"f" l, Inst @"bs" (assumeTrue n bs), Inst @"i" i)
?? ih `at` (Inst @"f" r, Inst @"bs" (assumeFalse n bs), Inst @"i" i)
=: sTrue
=: qed
]
-- | If falsify' returns falsified = true, then evaluating the formula
-- with the returned bindings gives false.
--
-- >>> runTPWith cvc5 falsifyFalsifies
-- Lemma: ifComplexityPos Q.E.D.
-- Lemma: ifComplexitySmaller Q.E.D.
-- Lemma: falsifyExtendsBindings Q.E.D.
-- Lemma: lookUpSame Q.E.D.
-- Lemma: isAssignedSame Q.E.D.
-- Inductive lemma (strong): falsifyFalsifies
-- Step: Measure is non-negative Q.E.D.
-- Step: 1 (4 way case split)
-- Step: 1.1.1 Q.E.D.
-- Step: 1.1.2 Q.E.D.
-- Step: 1.1.3 Q.E.D.
-- Step: 1.2.1 Q.E.D.
-- Step: 1.2.2 Q.E.D.
-- Step: 1.2.3 Q.E.D.
-- Step: 1.3.1 Q.E.D.
-- Step: 1.3.2 Q.E.D.
-- Step: 1.3.3 Q.E.D.
-- Step: 1.4 (4 way case split)
-- Step: 1.4.1 Q.E.D.
-- Step: 1.4.2 Q.E.D.
-- Step: 1.4.3 (2 way case split)
-- Step: 1.4.3.1 (2 way case split)
-- Step: 1.4.3.1.1 Q.E.D.
-- Step: 1.4.3.1.2 Q.E.D.
-- Step: 1.4.3.1.Completeness Q.E.D.
-- Step: 1.4.3.2 (2 way case split)
-- Step: 1.4.3.2.1 Q.E.D.
-- Step: 1.4.3.2.2 Q.E.D.
-- Step: 1.4.3.2.Completeness Q.E.D.
-- Step: 1.4.3.Completeness Q.E.D.
-- Step: 1.4.4 Q.E.D.
-- Step: 1.4.Completeness Q.E.D.
-- Step: 1.Completeness Q.E.D.
-- Result: Q.E.D.
-- Functions proven terminating: eval, falsify', ifComplexity, isAssigned, isNormal, lookUp
-- [Proven] falsifyFalsifies :: Ɐf ∷ Formula → Ɐbs ∷ [Binding] → Bool
falsifyFalsifies :: TP (Proof (Forall "f" Formula -> Forall "bs" [Binding] -> SBool))
falsifyFalsifies = do
icp <- recall ifComplexityPos
ibs <- recall ifComplexitySmaller
feb <- recall falsifyExtendsBindings
lus <- recall lookUpSame
ias <- recall isAssignedSame
sInduct "falsifyFalsifies"
(\(Forall f) (Forall bs) -> isNormal f .&& sfalsified (falsify' f bs) .=> sNot (eval f (scex (falsify' f bs))))
(\f _ -> ifComplexity f, [proofOf icp]) $
\ih f bs -> [isNormal f, sfalsified (falsify' f bs)]
|- cases [ isFTrue f ==> sNot (eval f (scex (falsify' f bs)))
=: sNot (eval sFTrue (scex (falsify' sFTrue bs)))
=: sNot sTrue
=: sFalse
=: qed
, isFFalse f ==> sNot (eval f (scex (falsify' f bs)))
=: sNot (eval sFFalse bs)
=: sNot sFalse
=: sTrue
=: qed
, isVar f ==> let n = sfVar f
in sNot (eval f (scex (falsify' f bs)))
=: sNot (eval (sVar n) (scex (falsify' (sVar n) bs)))
=: sNot (lookUp n (scex (falsify' (sVar n) bs)))
=: sTrue
=: qed
, isIf f ==> let c = sifCond f
l = sifThen f
r = sifElse f
in cases [ isFTrue c ==> sNot (eval f (scex (falsify' f bs)))
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` (Inst @"f" l, Inst @"bs" bs)
=: sTrue
=: qed
, isFFalse c ==> sNot (eval f (scex (falsify' f bs)))
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ih `at` (Inst @"f" r, Inst @"bs" bs)
=: sTrue
=: qed
, isVar c ==> let n = sfVar c
in cases [ isAssigned n bs ==>
cases [ lookUp n bs ==>
sNot (eval f (scex (falsify' f bs)))
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? feb `at` (Inst @"f" l, Inst @"bs" bs, Inst @"i" n)
?? ih `at` (Inst @"f" l, Inst @"bs" bs)
=: sTrue
=: qed
, sNot (lookUp n bs) ==>
sNot (eval f (scex (falsify' f bs)))
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? feb `at` (Inst @"f" r, Inst @"bs" bs, Inst @"i" n)
?? ih `at` (Inst @"f" r, Inst @"bs" bs)
=: sTrue
=: qed
]
, sNot (isAssigned n bs) ==>
let resL = falsify' l (assumeTrue n bs)
in cases [ sfalsified resL ==>
sNot (eval f (scex (falsify' f bs)))
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ias `at` (Inst @"n" n, Inst @"v" sTrue, Inst @"bs" bs)
?? lus `at` (Inst @"n" n, Inst @"v" sTrue, Inst @"bs" bs)
?? feb `at` (Inst @"f" l, Inst @"bs" (assumeTrue n bs), Inst @"i" n)
?? ih `at` (Inst @"f" l, Inst @"bs" (assumeTrue n bs))
=: sTrue
=: qed
, sNot (sfalsified resL) ==>
sNot (eval f (scex (falsify' f bs)))
?? ibs `at` (Inst @"c" c, Inst @"l" l, Inst @"r" r)
?? ias `at` (Inst @"n" n, Inst @"v" sFalse, Inst @"bs" bs)
?? lus `at` (Inst @"n" n, Inst @"v" sFalse, Inst @"bs" bs)
?? feb `at` (Inst @"f" r, Inst @"bs" (assumeFalse n bs), Inst @"i" n)
?? ih `at` (Inst @"f" r, Inst @"bs" (assumeFalse n bs))
=: sTrue
=: qed
]
]
, isIf c ==> sNot (eval f (scex (falsify' f bs)))
=: sTrue -- Contradicts isNormal
=: qed
]
]
-- | Helper lemma for completeness: If a formula is not a tautology,
-- evaluating its normalization with falsify's bindings gives false.
--
-- >>> runTPWith cvc5 completenessHelper
-- Lemma: falsifyFalsifies Q.E.D.
-- Lemma: nonTautIsFalsified Q.E.D.
-- Lemma: normalizeCorrect Q.E.D.
-- Lemma: completenessHelper Q.E.D.
-- Functions proven terminating:
-- eval, falsify', ifComplexity, ifDepth, isAssigned, isNormal, isTautology', lookUp, normalize
-- [Proven] completenessHelper :: Ɐf ∷ Formula → Bool
completenessHelper :: TP (Proof (Forall "f" Formula -> SBool))
completenessHelper = do
ff <- recall falsifyFalsifies
nti <- recall nonTautIsFalsified
nc <- recallWith z3 normalizeCorrect
lemma "completenessHelper"
(\(Forall f) -> sNot (isTautology f) .=> sNot (eval (normalize f) (scex (falsify f))))
[proofOf ff, proofOf nti, proofOf nc]
-- * Main completeness theorem
-- | \(\lnot\mathit{isTautology}(f) \implies \lnot\mathit{eval}(f, \mathit{falsify}(f).\mathit{bindings})\)
--
-- If the tautology checker says a formula is not a tautology, then there exists
-- a binding environment (provided by falsify) under which it evaluates to false.
-- This is the completeness theorem.
--
-- >>> runTPWith cvc5 completeness
-- Lemma: completenessHelper Q.E.D.
-- Lemma: normalizeRespectsTruth Q.E.D.
-- Lemma: completeness Q.E.D.
-- Functions proven terminating:
-- eval, falsify', ifComplexity, ifDepth, isAssigned, isNormal, isTautology', lookUp, normalize
-- [Proven] completeness :: Ɐf ∷ Formula → Bool
completeness :: TP (Proof (Forall "f" Formula -> SBool))
completeness = do
ch <- recall completenessHelper
nrt <- recallWith z3 normalizeRespectsTruth
lemma "completeness"
(\(Forall f) -> sNot (isTautology f) .=> sNot (eval f (scex (falsify f))))
[proofOf ch, proofOf nrt]