sbv-11.3: Data/SBV/Tools/KD/KnuckleDragger.hs
-----------------------------------------------------------------------------
-- |
-- Module : Data.SBV.Tools.KD.KnuckleDragger
-- Copyright : (c) Levent Erkok
-- License : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- A lightweight theorem proving like interface, built on top of SBV.
-- Inspired by and modeled after Philip Zucker's tool with the same
-- name, see <http://github.com/philzook58/knuckledragger>.
--
-- See the directory Documentation.SBV.Examples.KnuckleDragger for various examples.
-----------------------------------------------------------------------------
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeAbstractions #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wall -Werror #-}
module Data.SBV.Tools.KD.KnuckleDragger (
Proposition, Proof, Instantiatable(..), Inst(..)
, axiom
, lemma, lemmaWith
, theorem, theoremWith
, calc, calcWith, calcThm, calcThmWith
, induct, inductWith, inductThm, inductThmWith
, sInduct, sInductWith, sInductThm, sInductThmWith
, sorry
, KD, runKD, runKDWith, use
, (|-), (⊢), (=:), (≡), (??), (⁇), cases, hyp, hprf, qed
) where
import Data.SBV
import Data.SBV.Core.Model (qSaturateSavingObservables)
import Data.SBV.Control hiding (getProof)
import Data.SBV.Core.Data(SolverContext)
import Data.SBV.Tools.KD.Kernel
import Data.SBV.Tools.KD.Utils
import Control.Monad (forM_)
import Control.Monad.Trans (liftIO, MonadIO)
import qualified Data.SBV.List as SL
import Data.Char (isSpace)
import Data.List (isPrefixOf, isSuffixOf)
import Data.Maybe (catMaybes)
import Data.Proxy
import GHC.TypeLits (KnownSymbol, symbolVal, Symbol)
import Data.SBV.Utils.TDiff
import Data.Dynamic
import Data.Time (NominalDiffTime)
-- | Bring an IO proof into current proof context.
use :: IO Proof -> KD Proof
use = liftIO
-- | Captures the steps for a calculationa proof
data CalcStrategy = CalcStrategy { calcIntros :: SBool
, calcProofSteps :: [([Helper], SBool)]
}
-- | Saturatable things in steps
proofStepSaturatables :: [([Helper], SBool)] -> [SBool]
proofStepSaturatables = concatMap getS
where getS (hs, b) = b : concatMap getH hs
getH (HelperProof p) = [getProof p]
getH (HelperAssum b) = [b]
getH (HelperCase _ ss) = ss
-- | Things that are inside calc-strategy that we have to saturate
getCalcStrategySaturatables :: CalcStrategy -> [SBool]
getCalcStrategySaturatables (CalcStrategy calcIntros calcProofSteps) = calcIntros : proofStepSaturatables calcProofSteps
-- | Based on the helpers given, construct the proofs we have to do in the given case
stepCases :: Int -> [Helper] -> Either (String, SBool) ([(String, SBool)], SBool)
stepCases i helpers
| hasCase
= Right (caseSplits, cover)
| True
= Left (show i, sAnd (map getBools helpers))
where join :: [(String, SBool)] -> Helper -> [(String, SBool)]
join sofar (HelperProof p) = map (\(n, cond) -> (n, cond .&& getProof p)) sofar
join sofar (HelperAssum b) = map (\(n, cond) -> (n, cond .&& b)) sofar
join sofar (HelperCase cn cs) = concatMap (\(n, cond) -> [ (dotN n ++ cn ++ "[" ++ show j ++ "]", cond .&& b)
| (j, b) <- zip [(1::Int)..] cs
]) sofar
-- Add a dot if we have a legit prefix
dotN "" = ""
dotN s = s ++ "."
-- Used only when we have ano case splits:
getBools (HelperProof p) = getProof p
getBools (HelperAssum b) = b
getBools (HelperCase{}) = error "Unexpected case in stepCases: Wasn't expecting to see a HelperCase here."
-- All case-splits. If there isn't any, we'll get just one case
caseSplits = foldl join [("", sTrue)] helpers
-- If there were any cases, then we also need coverage
isCase (HelperProof {}) = False
isCase (HelperAssum {}) = False
isCase (HelperCase {}) = True
hasCase = any isCase helpers
regulars = concatMap getHyp helpers
where getHyp (HelperProof p) = [getProof p]
getHyp (HelperAssum b) = [b]
getHyp (HelperCase {}) = []
cover = sAnd regulars .&& sNot (sOr [b | (_, b) <- caseSplits])
-- | A class for doing equational reasoning style calculational proofs. Use 'calc' to prove a given theorem
-- as a sequence of equalities, each step following from the previous.
class CalcLemma a steps where
-- | Prove a property via a series of equality steps, using the default solver.
-- Let @H@ be a list of already established lemmas. Let @P@ be a property we wanted to prove, named @name@.
-- Consider a call of the form @calc name P (cond, [A, B, C, D]) H@. Note that @H@ is
-- a list of already proven facts, ensured by the type signature. We proceed as follows:
--
-- * Prove: @(H && cond) -> (A == B)@
-- * Prove: @(H && cond && A == B) -> (B == C)@
-- * Prove: @(H && cond && A == B && B == C) -> (C == D)@
-- * Prove: @(H && (cond -> (A == B && B == C && C == D))) -> P@
-- * If all of the above steps succeed, conclude @P@.
--
-- cond acts as the context. Typically, if you are trying to prove @Y -> Z@, then you want cond to be Y.
-- (This is similar to @intros@ commands in theorem provers.)
--
-- So, calc-lemma is essentially modus-ponens, applied in a sequence of stepwise equality reasoning in the case of
-- non-boolean steps.
--
-- If there are no helpers given (i.e., if @H@ is empty), then this call is equivalent to 'lemmaWith'.
-- If @H@ is a singleton, then we bail out. A single step in @H@ indicates a usage mistake, since there's
-- no sequence of steps to reason about.
calc :: Proposition a => String -> a -> steps -> KD Proof
-- | Same as calc, except tagged as Theorem
calcThm :: Proposition a => String -> a -> steps -> KD Proof
-- | Prove a property via a series of equality steps, using the given solver.
calcWith :: Proposition a => SMTConfig -> String -> a -> steps -> KD Proof
-- | Same as calcWith, except tagged as Theorem
calcThmWith :: Proposition a => SMTConfig -> String -> a -> steps -> KD Proof
-- | Internal, shouldn't be needed outside the library
{-# MINIMAL calcSteps #-}
calcSteps :: a -> steps -> Symbolic (SBool, CalcStrategy)
calc nm p steps = getKDConfig >>= \cfg -> calcWith cfg nm p steps
calcThm nm p steps = getKDConfig >>= \cfg -> calcThmWith cfg nm p steps
calcWith = calcGeneric False
calcThmWith = calcGeneric True
calcGeneric :: Proposition a => Bool -> SMTConfig -> String -> a -> steps -> KD Proof
calcGeneric tagTheorem cfg@SMTConfig{kdOptions = KDOptions{measureTime}} nm result steps = do
kdSt <- getKDState
liftIO $ runSMTWith cfg $ do
qSaturateSavingObservables result -- make sure we saturate the result, i.e., get all it's UI's, types etc. pop out
message cfg $ (if tagTheorem then "Theorem" else "Lemma") ++ ": " ++ nm ++ "\n"
mbStartTime <- getTimeStampIf measureTime
(calcGoal, strategy@CalcStrategy {calcIntros, calcProofSteps}) <- calcSteps result steps
let stepHelpers = concatMap fst calcProofSteps
finish et helpers d = finishKD cfg ("Q.E.D." ++ modulo) d et
where (_, modulo) = calculateRootOfTrust nm helpers
-- Collect all subterms and saturate them
mapM_ qSaturateSavingObservables $ calcIntros : getCalcStrategySaturatables strategy
let go :: Int -> SBool -> [([Helper], SBool)] -> Query Proof
go _ accum [] = do
queryDebug [nm ++ ": Proof end: proving the result:"]
checkSatThen cfg kdSt "Result" True
(Just (calcIntros .=> accum))
calcGoal
[]
["", ""]
(Just [nm, "Result"])
Nothing $ \d -> do mbElapsed <- getElapsedTime mbStartTime
let (ros, modulo) = calculateRootOfTrust nm (getHelperProofs stepHelpers)
finishKD cfg ("Q.E.D." ++ modulo) d (catMaybes [mbElapsed])
pure Proof { rootOfTrust = ros
, isUserAxiom = False
, getProof = label nm (quantifiedBool result)
, getProp = toDyn result
, proofName = nm
}
go i accum ((by, s):ss) = do
-- Prove that the assumptions follow, if any
case getHelperAssumes by of
[] -> pure ()
as -> checkSatThen cfg kdSt "Asms "
True
(Just calcIntros)
(sAnd as)
[]
["", show i]
(Just [nm, show i, "Assumptions"])
Nothing
(finish [] [])
queryDebug [nm ++ ": Proof step: " ++ show i ++ " to " ++ show (i+1) ++ ":"]
proveAllCases i cfg kdSt (stepCases i by) "Step " s nm (finish [] (getHelperProofs by))
go (i+1) (s .&& accum) ss
query $ go (1::Int) sTrue calcProofSteps
proveAllCases :: (Monad m, SolverContext m, MonadIO m, MonadQuery m, Proposition a)
=> Int -> SMTConfig -> KDState
-> Either (String, SBool) ([(String, SBool)], SBool)
-> String -> a -> String -> ((Int, Maybe NominalDiffTime) -> IO ()) -> m ()
proveAllCases topStep cfg kdSt caseInfo stepTag s nm finalize
| Left (stepName, asmp) <- caseInfo
= checker stepTag asmp s ["", stepName] (Just [nm, stepName])
| Right (proofCases, coverCond) <- caseInfo
= do let len = length proofCases
ways = case len of
1 -> "one way"
n -> show n ++ " ways"
slen = show len
clen = length slen
sh i = reverse . take clen $ reverse (show i) ++ repeat ' '
_tab <- liftIO $ startKD cfg True ("Step " ++ show topStep) ["", "Case split " ++ ways ++ ":"]
forM_ (zip [(1::Int)..] proofCases) $ \(c, (stepName, asmp)) ->
checker ("Case [" ++ sh c ++ " of " ++ show len ++ "]") asmp s ["", "", stepName] (Just [nm, stepName])
checker "Completeness" coverCond s ["", "", ""] (Just [nm, show topStep, "Completeness"])
where
checker tag caseAsmp cond cnm fnm = checkSatThen cfg kdSt tag True (Just caseAsmp) cond [] cnm fnm Nothing finalize
-- | Turn a sequence of steps into a chain of equalities
mkCalcSteps :: EqSymbolic a => (SBool, [ProofStep a]) -> CalcStrategy
mkCalcSteps (intros, xs) = case reverse xs of
(SingleStep _ (_:_) : _) -> error $ unlines [ ""
, "*** Incorrect calc/induct lemma calculations."
, "***"
, "*** The last step in the proof has a helper, which isn't used."
, "***"
, "*** Perhaps the hint is off-by-one in its placement?"
]
_ -> CalcStrategy { calcIntros = intros
, calcProofSteps = zipWith merge xs (drop 1 xs)
}
where merge (SingleStep a by) (SingleStep b _) = (by, a .== b)
-- | Chaining lemmas that depend on no quantified variables
instance EqSymbolic z => CalcLemma SBool (SBool, [ProofStep z]) where
calcSteps result steps = pure (result, mkCalcSteps steps)
-- | Chaining lemmas that depend on a single quantified variable.
instance (KnownSymbol na, SymVal a, EqSymbolic z) => CalcLemma (Forall na a -> SBool) (SBV a -> (SBool, [ProofStep z])) where
calcSteps result steps = do a <- free (symbolVal (Proxy @na))
pure (result (Forall a), mkCalcSteps (steps a))
-- | Chaining lemmas that depend on two quantified variables.
instance (KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, EqSymbolic z)
=> CalcLemma (Forall na a -> Forall nb b -> SBool)
(SBV a -> SBV b -> (SBool, [ProofStep z])) where
calcSteps result steps = do (a, b) <- (,) <$> free (symbolVal (Proxy @na)) <*> free (symbolVal (Proxy @nb))
pure (result (Forall a) (Forall b), mkCalcSteps (steps a b))
-- | Chaining lemmas that depend on three quantified variables.
instance (KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, EqSymbolic z)
=> CalcLemma (Forall na a -> Forall nb b -> Forall nc c -> SBool)
(SBV a -> SBV b -> SBV c -> (SBool, [ProofStep z])) where
calcSteps result steps = do (a, b, c) <- (,,) <$> free (symbolVal (Proxy @na)) <*> free (symbolVal (Proxy @nb)) <*> free (symbolVal (Proxy @nc))
pure (result (Forall a) (Forall b) (Forall c), mkCalcSteps (steps a b c))
-- | Chaining lemmas that depend on four quantified variables.
instance (KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, KnownSymbol nd, SymVal d, EqSymbolic z)
=> CalcLemma (Forall na a -> Forall nb b -> Forall nc c -> Forall nd d -> SBool)
(SBV a -> SBV b -> SBV c -> SBV d -> (SBool, [ProofStep z])) where
calcSteps result steps = do (a, b, c, d) <- (,,,) <$> free (symbolVal (Proxy @na)) <*> free (symbolVal (Proxy @nb)) <*> free (symbolVal (Proxy @nc)) <*> free (symbolVal (Proxy @nd))
pure (result (Forall a) (Forall b) (Forall c) (Forall d), mkCalcSteps (steps a b c d))
-- | Chaining lemmas that depend on five quantified variables.
instance (KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, KnownSymbol nd, SymVal d, KnownSymbol ne, SymVal e, EqSymbolic z)
=> CalcLemma (Forall na a -> Forall nb b -> Forall nc c -> Forall nd d -> Forall ne e -> SBool)
(SBV a -> SBV b -> SBV c -> SBV d -> SBV e -> (SBool, [ProofStep z])) where
calcSteps result steps = do (a, b, c, d, e) <- (,,,,) <$> free (symbolVal (Proxy @na)) <*> free (symbolVal (Proxy @nb)) <*> free (symbolVal (Proxy @nc)) <*> free (symbolVal (Proxy @nd)) <*> free (symbolVal (Proxy @ne))
pure (result (Forall a) (Forall b) (Forall c) (Forall d) (Forall e), mkCalcSteps (steps a b c d e))
-- | Captures the schema for an inductive proof
data InductionStrategy = InductionStrategy { inductionIntros :: SBool
, inductionBaseCase :: SBool
, inductionProofSteps :: [([Helper], SBool)]
, inductionBaseFailureMsg :: String
, inductiveStep :: SBool
}
-- | Are we doing strong induction or regular induction?
data InductionStyle = RegularInduction | StrongInduction
getInductionStrategySaturatables :: InductionStrategy -> [SBool]
getInductionStrategySaturatables (InductionStrategy inductionIntros
inductionBaseCase
inductionProofSteps
_inductionBaseFailureMsg
inductiveStep)
= inductionIntros : inductionBaseCase : inductiveStep : proofStepSaturatables inductionProofSteps
-- | A class for doing inductive proofs, with the possibility of explicit steps.
class Inductive a steps where
-- | Inductively prove a lemma, using the default config.
-- Inductive proofs over lists only hold for finite lists. We also assume that all functions involved are terminating. SBV does not prove termination, so only
-- partial correctness is guaranteed if non-terminating functions are involved.
induct :: Proposition a => String -> a -> (Proof -> steps) -> KD Proof
-- | Inductively prove a lemma, using strong induction, using the default config.
-- Inductive proofs over lists only hold for finite lists. We also assume that all functions involved are terminating. SBV does not prove termination, so only
-- partial correctness is guaranteed if non-terminating functions are involved.
sInduct :: Proposition a => String -> a -> (Proof -> steps) -> KD Proof
-- | Inductively prove a theorem. Same as 'induct', but tagged as a theorem, using the default config.
-- Inductive proofs over lists only hold for finite lists. We also assume that all functions involved are terminating. SBV does not prove termination, so only
-- partial correctness is guaranteed if non-terminating functions are involved.
inductThm :: Proposition a => String -> a -> (Proof -> steps) -> KD Proof
-- | Inductively prove a theorem, using strong induction. Same as 'sInduct', but tagged as a theorem, using the default config.
-- Inductive proofs over lists only hold for finite lists. We also assume that all functions involved are terminating. SBV does not prove termination, so only
-- partial correctness is guaranteed if non-terminating functions are involved.
sInductThm :: Proposition a => String -> a -> (Proof -> steps) -> KD Proof
-- | Same as 'induct', but with the given solver configuration.
-- Inductive proofs over lists only hold for finite lists. We also assume that all functions involved are terminating. SBV does not prove termination, so only
-- partial correctness is guaranteed if non-terminating functions are involved.
inductWith :: Proposition a => SMTConfig -> String -> a -> (Proof -> steps) -> KD Proof
-- | Same as 'sInduct', but with the given solver configuration.
-- Inductive proofs over lists only hold for finite lists. We also assume that all functions involved are terminating. SBV does not prove termination, so only
-- partial correctness is guaranteed if non-terminating functions are involved.
sInductWith :: Proposition a => SMTConfig -> String -> a -> (Proof -> steps) -> KD Proof
-- | Same as 'inductThm', but with the given solver configuration.
-- Inductive proofs over lists only hold for finite lists. We also assume that all functions involved are terminating. SBV does not prove termination, so only
-- partial correctness is guaranteed if non-terminating functions are involved.
inductThmWith :: Proposition a => SMTConfig -> String -> a -> (Proof -> steps) -> KD Proof
-- | Same as 'sInductThm', but with the given solver configuration.
-- Inductive proofs over lists only hold for finite lists. We also assume that all functions involved are terminating. SBV does not prove termination, so only
-- partial correctness is guaranteed if non-terminating functions are involved.
sInductThmWith :: Proposition a => SMTConfig -> String -> a -> (Proof -> steps) -> KD Proof
induct nm p steps = getKDConfig >>= \cfg -> inductWith cfg nm p steps
inductThm nm p steps = getKDConfig >>= \cfg -> inductThmWith cfg nm p steps
inductWith = inductGeneric RegularInduction False
inductThmWith = inductGeneric RegularInduction True
sInduct nm p steps = getKDConfig >>= \cfg -> sInductWith cfg nm p steps
sInductThm nm p steps = getKDConfig >>= \cfg -> sInductThmWith cfg nm p steps
sInductWith = inductGeneric StrongInduction False
sInductThmWith = inductGeneric StrongInduction True
-- | Internal, shouldn't be needed outside the library
{-# MINIMAL inductionStrategy #-}
inductionStrategy :: Proposition a => InductionStyle -> a -> (Proof -> steps) -> Symbolic InductionStrategy
inductGeneric :: Proposition a => InductionStyle -> Bool -> SMTConfig -> String -> a -> (Proof -> steps) -> KD Proof
inductGeneric style tagTheorem cfg@SMTConfig{kdOptions = KDOptions{measureTime}} nm result steps = do
kdSt <- getKDState
liftIO $ runSMTWith cfg $ do
qSaturateSavingObservables result -- make sure we saturate the result, i.e., get all it's UI's, types etc. pop out
let strong = case style of
RegularInduction -> ""
StrongInduction -> " (strong)"
message cfg $ "Inductive " ++ (if tagTheorem then "theorem" else "lemma") ++ strong ++ ": " ++ nm ++ "\n"
mbStartTime <- getTimeStampIf measureTime
strategy@InductionStrategy { inductionIntros
, inductionBaseCase
, inductionProofSteps
, inductionBaseFailureMsg
, inductiveStep
} <- inductionStrategy style result steps
let stepHelpers = concatMap fst inductionProofSteps
finish et helpers d = finishKD cfg ("Q.E.D." ++ modulo) d et
where (_, modulo) = calculateRootOfTrust nm helpers
-- Collect all subterms and saturate them
mapM_ qSaturateSavingObservables $ getInductionStrategySaturatables strategy
query $ do
queryDebug [nm ++ ": Induction, proving base case:"]
checkSatThen cfg kdSt "Base" True (Just inductionIntros) inductionBaseCase [] [nm, "Base"] Nothing
(Just (liftIO (putStrLn inductionBaseFailureMsg)))
(finish [] [])
let loop i accum ((by, s):ss) = do
-- Prove that the assumptions follow, if any
case getHelperAssumes by of
[] -> pure ()
as -> checkSatThen cfg kdSt "Asms"
True
(Just inductionIntros)
(sAnd as)
[]
["", show i]
(Just [nm, show i, "Assumptions"])
Nothing
(finish [] [])
queryDebug [nm ++ ": Induction, proving step: " ++ show i]
proveAllCases i cfg kdSt (stepCases i by) "Step" s nm (finish [] (getHelperProofs by))
loop (i+1) (accum .&& s) ss
loop _ accum [] = pure accum
-- Get the schema
indSchema <- loop (1::Int) sTrue inductionProofSteps
-- Do the final proof:
queryDebug [nm ++ ": Induction, proving inductive step:"]
checkSatThen cfg kdSt "Step"
True
(Just (inductionIntros .=> indSchema))
inductiveStep
[]
[nm, "Step"]
Nothing
Nothing $ \d -> do
mbElapsed <- getElapsedTime mbStartTime
let (ros, modulo) = calculateRootOfTrust nm (getHelperProofs stepHelpers)
finishKD cfg ("Q.E.D." ++ modulo) d (catMaybes [mbElapsed])
pure $ Proof { rootOfTrust = ros
, isUserAxiom = False
, getProof = label nm $ quantifiedBool result
, getProp = toDyn result
, proofName = nm
}
-- | Induction over 'SInteger'.
instance (KnownSymbol nk, EqSymbolic z) => Inductive (Forall nk Integer -> SBool) (SInteger -> (SBool, [ProofStep z])) where
inductionStrategy style result steps = do
let predicate k = result (Forall k)
nk = symbolVal (Proxy @nk)
k <- free nk
let ih = case style of
RegularInduction -> internalAxiom "IH" $ result (Forall k)
StrongInduction -> internalAxiom "IH" $ \(Forall k' :: Forall nk Integer) -> k' .<= k .=> result (Forall k')
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih k
pure InductionStrategy {
inductionIntros = k .>= 0 .&& calcIntros
, inductionBaseCase = predicate 0
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nk ++ " = 0."
, inductiveStep = observeIf not ("P(" ++ nk ++ "+1)") (predicate (k+1))
}
-- | Induction over 'SInteger' taking an extra argument.
instance forall na a nk z. (KnownSymbol na, SymVal a, KnownSymbol nk, EqSymbolic z)
=> Inductive (Forall nk Integer -> Forall na a -> SBool)
(SInteger -> SBV a -> (SBool, [ProofStep z])) where
inductionStrategy style result steps = do
let predicate k a = result (Forall k) (Forall a)
nk = symbolVal (Proxy @nk)
na = symbolVal (Proxy @na)
k <- free nk
a <- free na
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' -> result (Forall k) (a' :: Forall na a)
StrongInduction -> internalAxiom "IH" $ \(Forall k' :: Forall nk Integer) a' -> 0 .<= k' .&& k' .<= k .=> result (Forall k') (a' :: Forall na a)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih k a
pure InductionStrategy {
inductionIntros = k .>= 0 .&& calcIntros
, inductionBaseCase = predicate 0 a
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nk ++ " = 0."
, inductiveStep = observeIf not ("P(" ++ nk ++ "+1)") (predicate (k+1) a)
}
-- | Induction over 'SInteger' taking two extra arguments.
instance forall na a nb b nk z. (KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nk, EqSymbolic z)
=> Inductive (Forall nk Integer -> Forall na a -> Forall nb b -> SBool)
(SInteger -> SBV a -> SBV b -> (SBool, [ProofStep z])) where
inductionStrategy style result steps = do
let predicate k a b = result (Forall k) (Forall a) (Forall b)
nk = symbolVal (Proxy @nk)
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
k <- free nk
a <- free na
b <- free nb
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' -> result (Forall k) (a' :: Forall na a) (b' :: Forall nb b)
StrongInduction -> internalAxiom "IH" $ \(Forall k' :: Forall nk Integer) a' b' -> 0 .<= k' .&& k' .<= k .=> result (Forall k') (a' :: Forall na a) (b' :: Forall nb b)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih k a b
pure InductionStrategy {
inductionIntros = k .>= 0 .&& calcIntros
, inductionBaseCase = predicate 0 a b
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nk ++ " = 0."
, inductiveStep = observeIf not ("P(" ++ nk ++ "+1)") (predicate (k+1) a b)
}
-- | Induction over 'SInteger' taking three extra arguments.
instance forall na a nb b nc c nk z. (KnownSymbol na, SymVal a , KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, KnownSymbol nk, EqSymbolic z)
=> Inductive (Forall nk Integer -> Forall na a -> Forall nb b -> Forall nc c -> SBool)
(SInteger -> SBV a -> SBV b -> SBV c -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate k a b c = result (Forall k) (Forall a) (Forall b) (Forall c)
nk = symbolVal (Proxy @nk)
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
nc = symbolVal (Proxy @nc)
k <- free nk
a <- free na
b <- free nb
c <- free nc
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' c' -> result (Forall k) (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c)
StrongInduction -> internalAxiom "IH" $ \(Forall k' :: Forall nk Integer) a' b' c' -> 0 .<= k' .&& k' .<= k .=> result (Forall k') (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih k a b c
pure InductionStrategy {
inductionIntros = k .>= 0 .&& calcIntros
, inductionBaseCase = predicate 0 a b c
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nk ++ " = 0."
, inductiveStep = observeIf not ("P(" ++ nk ++ "+1)") (predicate (k+1) a b c)
}
-- | Induction over 'SInteger' taking four extra arguments.
instance forall na a nb b nc c nd d nk z. (KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, KnownSymbol nd, SymVal d, KnownSymbol nk, EqSymbolic z)
=> Inductive (Forall nk Integer -> Forall na a -> Forall nb b -> Forall nc c -> Forall nd d -> SBool)
(SInteger -> SBV a -> SBV b -> SBV c -> SBV d -> (SBool, [ProofStep z])) where
inductionStrategy style result steps = do
let predicate k a b c d = result (Forall k) (Forall a) (Forall b) (Forall c) (Forall d)
nk = symbolVal (Proxy @nk)
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
nc = symbolVal (Proxy @nc)
nd = symbolVal (Proxy @nd)
k <- free nk
a <- free na
b <- free nb
c <- free nc
d <- free nd
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' c' d' -> result (Forall k) (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c) (d' :: Forall nd d)
StrongInduction -> internalAxiom "IH" $ \(Forall k' :: Forall nk Integer) a' b' c' d' -> 0 .<= k' .&& k' .<= k .=> result (Forall k') (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c) (d' :: Forall nd d)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih k a b c d
pure InductionStrategy {
inductionIntros = k .>= 0 .&& calcIntros
, inductionBaseCase = predicate 0 a b c d
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nk ++ " = 0."
, inductiveStep = observeIf not ("P(" ++ nk ++ "+1)") (predicate (k+1) a b c d)
}
-- Given a user name for the list, get a name for the element, in the most suggestive way possible
-- xs -> x
-- xss -> xs
-- foo -> fooElt
singular :: String -> String
singular n = case reverse n of
's':_:_ -> init n
_ -> n ++ "Elt"
-- | Metric for induction. Currently we simply require the list we're assuming correctness for is shorter in length, which
-- is a measure that is guarenteed >= 0. -- Later on, we might want to generalize this to a user given measure.
smaller :: SymVal a => SList a -> SList a -> SBool
smaller xs ys = SL.length xs .<= SL.length ys
-- | Induction over 'SList'.
instance (KnownSymbol nx, SymVal x, EqSymbolic z)
=> Inductive (Forall nx [x] -> SBool)
(SBV x -> SList x -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs = result (Forall xs)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
x <- free nx
xs <- free nxs
let ih = case style of
RegularInduction -> internalAxiom "IH" $ result (Forall xs)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) -> xs' `smaller` xs .=> result (Forall xs')
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = []."
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ")") (predicate (x SL..: xs))
}
-- | Induction over 'SList' taking an extra argument
instance forall na a nx x z. (KnownSymbol na, SymVal a, KnownSymbol nx, SymVal x, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall na a -> SBool)
(SBV x -> SList x -> SBV a -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs a = result (Forall xs) (Forall a)
na = symbolVal (Proxy @na)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
x <- free nx
xs <- free nxs
a <- free na
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' -> result (Forall xs) (a' :: Forall na a)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) a' -> xs' `smaller` xs .=> result (Forall xs') (a' :: Forall na a)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs a
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil a
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = []."
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ")") (predicate (x SL..: xs) a)
}
-- | Induction over 'SList' taking two extra arguments
instance forall na a nb b nx x z. (KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nx, SymVal x, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall na a -> Forall nb b -> SBool)
(SBV x -> SList x -> SBV a -> SBV b -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs a b = result (Forall xs) (Forall a) (Forall b)
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
x <- free nx
xs <- free nxs
a <- free na
b <- free nb
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' -> result (Forall xs) (a' :: Forall na a) (b' :: Forall nb b)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) a' b' -> xs' `smaller` xs .=> result (Forall xs') (a' :: Forall na a) (b' :: Forall nb b)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs a b
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil a b
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = []."
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ")") (predicate (x SL..: xs) a b)
}
-- | Induction over 'SList' taking three extra arguments
instance forall na a nb b nc c nx x z. (KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, KnownSymbol nx, SymVal x, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall na a -> Forall nb b -> Forall nc c -> SBool)
(SBV x -> SList x -> SBV a -> SBV b -> SBV c -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs a b c = result (Forall xs) (Forall a) (Forall b) (Forall c)
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
nc = symbolVal (Proxy @nc)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
x <- free nx
xs <- free nxs
a <- free na
b <- free nb
c <- free nc
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' c' -> result (Forall xs) (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) a' b' c' -> xs' `smaller` xs .=> result (Forall xs') (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs a b c
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil a b c
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = []."
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ")") (predicate (x SL..: xs) a b c)
}
-- | Induction over 'SList' taking four extra arguments
instance forall na a nb b nc c nd d nx x z. (KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, KnownSymbol nd, SymVal d, KnownSymbol nx, SymVal x, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall na a -> Forall nb b -> Forall nc c -> Forall nd d -> SBool)
(SBV x -> SList x -> SBV a -> SBV b -> SBV c -> SBV d -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs a b c d = result (Forall xs) (Forall a) (Forall b) (Forall c) (Forall d)
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
nc = symbolVal (Proxy @nc)
nd = symbolVal (Proxy @nd)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
x <- free nx
xs <- free nxs
a <- free na
b <- free nb
c <- free nc
d <- free nd
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' c' d' -> result (Forall xs) (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c) (d' :: Forall nd d)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) a' b' c' d' -> xs' `smaller` xs .=> result (Forall xs') (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c) (d' :: Forall nd d)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs a b c d
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil a b c d
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = []."
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ")") (predicate (x SL..: xs) a b c d)
}
-- | Induction over 'SList' taking five extra arguments
instance forall na a nb b nc c nd d ne e nx x z. (KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, KnownSymbol nd, SymVal d, KnownSymbol ne, SymVal e, KnownSymbol nx, SymVal x, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall na a -> Forall nb b -> Forall nc c -> Forall nd d -> Forall ne e -> SBool)
(SBV x -> SList x -> SBV a -> SBV b -> SBV c -> SBV d -> SBV e -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs a b c d e = result (Forall xs) (Forall a) (Forall b) (Forall c) (Forall d) (Forall e)
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
nc = symbolVal (Proxy @nc)
nd = symbolVal (Proxy @nd)
ne = symbolVal (Proxy @ne)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
x <- free nx
xs <- free nxs
a <- free na
b <- free nb
c <- free nc
d <- free nd
e <- free ne
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' c' d' e' -> result (Forall xs) (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c) (d' :: Forall nd d) (e' :: Forall ne e)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) a' b' c' d' e' -> xs' `smaller` xs .=> result (Forall xs') (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c) (d' :: Forall nd d) (e' :: Forall ne e)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs a b c d e
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil a b c d e
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = []."
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ")") (predicate (x SL..: xs) a b c d e)
}
-- | Induction over two lists, simultaneously
instance (KnownSymbol nx, SymVal x, KnownSymbol ny, SymVal y, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall ny [y] -> SBool)
(SBV x -> SList x -> SBV y -> SList y -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs ys = result (Forall xs) (Forall ys)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
nys = symbolVal (Proxy @ny)
ny = singular nys
x <- free nx
xs <- free nxs
y <- free ny
ys <- free nys
let ih = case style of
RegularInduction -> internalAxiom "IH" $ result (Forall xs) (Forall ys)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) (Forall ys' :: Forall ny [y]) -> xs' `smaller` xs .&& ys' `smaller` ys .=> result (Forall xs') (Forall ys')
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs y ys
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil SL.nil .&& predicate SL.nil (y SL..: ys) .&& predicate (x SL..: xs) SL.nil
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = [] OR " ++ nys ++ " = []"
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ", " ++ ny ++ ":" ++ nys ++ ")") (predicate (x SL..: xs) (y SL..: ys))
}
-- | Induction over two lists, simultaneously, taking one extra argument
instance (KnownSymbol nx, SymVal x, KnownSymbol ny, SymVal y, KnownSymbol na, SymVal a, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall ny [y] -> Forall na a -> SBool)
(SBV x -> SList x -> SBV y -> SList y -> SBV a -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs ys a = result (Forall xs) (Forall ys) (Forall a)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
nys = symbolVal (Proxy @ny)
ny = singular nys
na = symbolVal (Proxy @na)
x <- free nx
xs <- free nxs
y <- free ny
ys <- free nys
a <- free na
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' -> result (Forall xs) (Forall ys) (a' :: Forall na a)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) (Forall ys' :: Forall ny [y]) a' -> xs' `smaller` xs .&& ys' `smaller` ys .=> result (Forall xs') (Forall ys') (a' :: Forall na a)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs y ys a
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil SL.nil a .&& predicate SL.nil (y SL..: ys) a .&& predicate (x SL..: xs) SL.nil a
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = [] OR " ++ nys ++ " = []"
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ", " ++ ny ++ ":" ++ nys ++ ")") (predicate (x SL..: xs) (y SL..: ys) a)
}
-- | Induction over two lists, simultaneously, taking two extra arguments
instance (KnownSymbol nx, SymVal x, KnownSymbol ny, SymVal y, KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall ny [y] -> Forall na a -> Forall nb b -> SBool)
(SBV x -> SList x -> SBV y -> SList y -> SBV a -> SBV b -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs ys a b = result (Forall xs) (Forall ys) (Forall a) (Forall b)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
nys = symbolVal (Proxy @ny)
ny = singular nys
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
x <- free nx
xs <- free nxs
y <- free ny
ys <- free nys
a <- free na
b <- free nb
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' -> result (Forall xs) (Forall ys) (a' :: Forall na a) (b' :: Forall nb b)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) (Forall ys' :: Forall ny [y]) a' b' -> xs' `smaller` xs .&& ys' `smaller` ys .=> result (Forall xs') (Forall ys') (a' :: Forall na a) (b' :: Forall nb b)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs y ys a b
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil SL.nil a b .&& predicate SL.nil (y SL..: ys) a b .&& predicate (x SL..: xs) SL.nil a b
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = [] OR " ++ nys ++ " = []"
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ", " ++ ny ++ ":" ++ nys ++ ")") (predicate (x SL..: xs) (y SL..: ys) a b)
}
-- | Induction over two lists, simultaneously, taking three extra arguments
instance (KnownSymbol nx, SymVal x, KnownSymbol ny, SymVal y, KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall ny [y] -> Forall na a -> Forall nb b -> Forall nc c -> SBool)
(SBV x -> SList x -> SBV y -> SList y -> SBV a -> SBV b -> SBV c -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs ys a b c = result (Forall xs) (Forall ys) (Forall a) (Forall b) (Forall c)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
nys = symbolVal (Proxy @ny)
ny = singular nys
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
nc = symbolVal (Proxy @nc)
x <- free nx
xs <- free nxs
y <- free ny
ys <- free nys
a <- free na
b <- free nb
c <- free nc
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' c' -> result (Forall xs) (Forall ys) (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) (Forall ys' :: Forall ny [y]) a' b' c' -> xs' `smaller` xs .&& ys' `smaller` ys .=> result (Forall xs') (Forall ys') (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs y ys a b c
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil SL.nil a b c .&& predicate SL.nil (y SL..: ys) a b c .&& predicate (x SL..: xs) SL.nil a b c
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = [] OR " ++ nys ++ " = []"
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ", " ++ ny ++ ":" ++ nys ++ ")") (predicate (x SL..: xs) (y SL..: ys) a b c)
}
-- | Induction over two lists, simultaneously, taking four extra arguments
instance (KnownSymbol nx, SymVal x, KnownSymbol ny, SymVal y, KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, KnownSymbol nd, SymVal d, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall ny [y] -> Forall na a -> Forall nb b -> Forall nc c -> Forall nd d -> SBool)
(SBV x -> SList x -> SBV y -> SList y -> SBV a -> SBV b -> SBV c -> SBV d -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs ys a b c d = result (Forall xs) (Forall ys) (Forall a) (Forall b) (Forall c) (Forall d)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
nys = symbolVal (Proxy @ny)
ny = singular nys
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
nc = symbolVal (Proxy @nc)
nd = symbolVal (Proxy @nd)
x <- free nx
xs <- free nxs
y <- free ny
ys <- free nys
a <- free na
b <- free nb
c <- free nc
d <- free nd
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' c' d' -> result (Forall xs) (Forall ys) (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c) (d' :: Forall nd d)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) (Forall ys' :: Forall ny [y]) a' b' c' d' -> xs' `smaller` xs .&& ys' `smaller` ys .=> result (Forall xs') (Forall ys') (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c) (d' :: Forall nd d)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs y ys a b c d
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil SL.nil a b c d .&& predicate SL.nil (y SL..: ys) a b c d .&& predicate (x SL..: xs) SL.nil a b c d
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = [] OR " ++ nys ++ " = []"
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ", " ++ ny ++ ":" ++ nys ++ ")") (predicate (x SL..: xs) (y SL..: ys) a b c d)
}
-- | Induction over two lists, simultaneously, taking four extra arguments
instance (KnownSymbol nx, SymVal x, KnownSymbol ny, SymVal y, KnownSymbol na, SymVal a, KnownSymbol nb, SymVal b, KnownSymbol nc, SymVal c, KnownSymbol nd, SymVal d, KnownSymbol ne, SymVal e, EqSymbolic z)
=> Inductive (Forall nx [x] -> Forall ny [y] -> Forall na a -> Forall nb b -> Forall nc c -> Forall nd d -> Forall ne e -> SBool)
(SBV x -> SList x -> SBV y -> SList y -> SBV a -> SBV b -> SBV c -> SBV d -> SBV e -> (SBool, [ProofStep z]))
where
inductionStrategy style result steps = do
let predicate xs ys a b c d e = result (Forall xs) (Forall ys) (Forall a) (Forall b) (Forall c) (Forall d) (Forall e)
nxs = symbolVal (Proxy @nx)
nx = singular nxs
nys = symbolVal (Proxy @ny)
ny = singular nys
na = symbolVal (Proxy @na)
nb = symbolVal (Proxy @nb)
nc = symbolVal (Proxy @nc)
nd = symbolVal (Proxy @nd)
ne = symbolVal (Proxy @ne)
x <- free nx
xs <- free nxs
y <- free ny
ys <- free nys
a <- free na
b <- free nb
c <- free nc
d <- free nd
e <- free ne
let ih = case style of
RegularInduction -> internalAxiom "IH" $ \ a' b' c' d' e' -> result (Forall xs) (Forall ys) (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c) (d' :: Forall nd d) (e' :: Forall ne e)
StrongInduction -> internalAxiom "IH" $ \(Forall xs' :: Forall nx [x]) (Forall ys' :: Forall ny [y]) a' b' c' d' e' -> xs' `smaller` xs .&& ys' `smaller` ys .=> result (Forall xs') (Forall ys') (a' :: Forall na a) (b' :: Forall nb b) (c' :: Forall nc c) (d' :: Forall nd d) (e' :: Forall ne e)
CalcStrategy { calcIntros, calcProofSteps } = mkCalcSteps $ steps ih x xs y ys a b c d e
pure InductionStrategy {
inductionIntros = calcIntros
, inductionBaseCase = predicate SL.nil SL.nil a b c d e .&& predicate SL.nil (y SL..: ys) a b c d e .&& predicate (x SL..: xs) SL.nil a b c d e
, inductionProofSteps = calcProofSteps
, inductionBaseFailureMsg = "Property fails for " ++ nxs ++ " = [] OR " ++ nys ++ " = []"
, inductiveStep = observeIf not ("P(" ++ nx ++ ":" ++ nxs ++ ", " ++ ny ++ ":" ++ nys ++ ")") (predicate (x SL..: xs) (y SL..: ys) a b c d e)
}
-- | Instantiation for a universally quantified variable
newtype Inst (nm :: Symbol) a = Inst (SBV a)
instance KnownSymbol nm => Show (Inst nm a) where
show (Inst a) = symbolVal (Proxy @nm) ++ " |-> " ++ show a
-- | Instantiating a proof at different types of arguments. This is necessarily done using
-- dynamics, hand has a cost of not being applicable.
class Instantiatable a where
-- | Apply a universal proof to some arguments, creating an instance of the proof itself.
at :: Proof -> a -> Proof
-- | Single parameter
instance (KnownSymbol na, HasKind a, Typeable a) => Instantiatable (Inst na a) where
at = instantiate $ \f (Inst a) -> f (Forall a :: Forall na a)
-- | Two parameters
instance ( KnownSymbol na, HasKind a, Typeable a
, KnownSymbol nb, HasKind b, Typeable b
) => Instantiatable (Inst na a, Inst nb b) where
at = instantiate $ \f (Inst a, Inst b) -> f (Forall a :: Forall na a) (Forall b :: Forall nb b)
-- | Three parameters
instance ( KnownSymbol na, HasKind a, Typeable a
, KnownSymbol nb, HasKind b, Typeable b
, KnownSymbol nc, HasKind c, Typeable c
) => Instantiatable (Inst na a, Inst nb b, Inst nc c) where
at = instantiate $ \f (Inst a, Inst b, Inst c) -> f (Forall a :: Forall na a) (Forall b :: Forall nb b) (Forall c :: Forall nc c)
-- | Four parameters
instance ( KnownSymbol na, HasKind a, Typeable a
, KnownSymbol nb, HasKind b, Typeable b
, KnownSymbol nc, HasKind c, Typeable c
, KnownSymbol nd, HasKind d, Typeable d
) => Instantiatable (Inst na a, Inst nb b, Inst nc c, Inst nd d) where
at = instantiate $ \f (Inst a, Inst b, Inst c, Inst d) -> f (Forall a :: Forall na a) (Forall b :: Forall nb b) (Forall c :: Forall nc c) (Forall d :: Forall nd d)
-- | Five parameters
instance ( KnownSymbol na, HasKind a, Typeable a
, KnownSymbol nb, HasKind b, Typeable b
, KnownSymbol nc, HasKind c, Typeable c
, KnownSymbol nd, HasKind d, Typeable d
, KnownSymbol ne, HasKind e, Typeable e
) => Instantiatable (Inst na a, Inst nb b, Inst nc c, Inst nd d, Inst ne e) where
at = instantiate $ \f (Inst a, Inst b, Inst c, Inst d, Inst e) -> f (Forall a :: Forall na a) (Forall b :: Forall nb b) (Forall c :: Forall nc c) (Forall d :: Forall nd d) (Forall e :: Forall ne e)
-- | Instantiate a proof over an arg. This uses dynamic typing, kind of hacky, but works sufficiently well.
instantiate :: (Typeable f, Show arg) => (f -> arg -> SBool) -> Proof -> arg -> Proof
instantiate ap p@Proof{getProp, proofName} a = case fromDynamic getProp of
Nothing -> cantInstantiate
Just f -> let result = f `ap` a
nm = proofName ++ " @ " ++ paren sha
in p { getProof = label nm result
, getProp = toDyn result
, proofName = nm
}
where sha = show a
cantInstantiate = error $ unlines [ "at: Cannot instantiate proof:"
, " Name: " ++ proofName
, " Type: " ++ trim (show getProp)
, " At : " ++ sha
]
-- dynamic puts funky <</>> at the beginning and end; trim it:
trim ('<':'<':s) = reverse (trimE (reverse s))
trim s = s
trimE ('>':'>':s) = s
trimE s = s
-- Add parens if necessary
paren s | "(" `isPrefixOf` s && ")" `isSuffixOf` s = s
| not (any isSpace s) = s
| True = '(' : s ++ ")"
-- | Helpers for a step
data Helper = HelperProof Proof -- A previously proven theorem
| HelperAssum SBool -- A hypothesis
| HelperCase String [SBool] -- Case split
-- | Get proofs from helpers
getHelperProofs :: [Helper] -> [Proof]
getHelperProofs = concatMap get
where get (HelperProof p) = [p]
get HelperAssum {} = []
get HelperCase {} = []
-- | Get proofs from helpers
getHelperAssumes :: [Helper] -> [SBool]
getHelperAssumes = concatMap get
where get HelperProof {} = []
get (HelperAssum b) = [b]
get HelperCase {} = []
-- | Smart constructor for creating a helper from a boolean. This is hardly needed, unless you're
-- mixing proofs and booleans in one group of hints.
hyp :: SBool -> Helper
hyp = HelperAssum
-- | Smart constructor for creating a helper from a boolean. This is hardly needed, unless you're
-- mixing proofs and booleans in one group of hints.
hprf :: Proof -> Helper
hprf = HelperProof
-- | A proof-step with associated helpers
data ProofStep a = SingleStep a [Helper]
-- | Class capturing giving a proof-step helper
class ProofHint a b where
-- | Specify a helper for the given proof step
(??) :: a -> b -> ProofStep a
infixl 2 ??
-- | Giving just one proof as a helper.
instance ProofHint a Proof where
a ?? p = SingleStep a [HelperProof p]
-- | Giving just one boolean as a helper.
instance ProofHint a SBool where
a ?? p = SingleStep a [HelperAssum p]
-- | Giving just one helper
instance ProofHint a Helper where
a ?? h = SingleStep a [h]
-- | Giving a bunch of proofs as a helper.
instance ProofHint a [Proof] where
a ?? ps = SingleStep a (map HelperProof ps)
-- | Giving a bunch of booleans as a helper.
instance ProofHint a [SBool] where
a ?? ps = SingleStep a (map HelperAssum ps)
-- | Giving a set of helpers
instance ProofHint a [Helper] where
a ?? hs = SingleStep a hs
-- | Giving user a hint as a string. This doesn't actually do anything for the solver, it just helps with readability
instance ProofHint a String where
a ?? _ = SingleStep a []
-- | Capture what a given step can chain-to. This is a closed-type family, i.e.,
-- we don't allow users to change this and write other chainable things. Probably it is not really necessary,
-- but we'll cross that bridge if someone actually asks for it.
type family ChainsTo a where
ChainsTo (ProofStep a) = [ProofStep a]
ChainsTo a = [ProofStep a]
-- | Chain steps in a calculational proof.
(=:) :: ChainStep a (ChainsTo a) => a -> ChainsTo a -> ChainsTo a
(=:) = chain
infixr 1 =:
-- | Unicode alternative for `=:`.
(≡) :: ChainStep a (ChainsTo a) => a -> ChainsTo a -> ChainsTo a
(≡) = (=:)
infixr 1 ≡
-- | Chaining two steps together
class ChainStep a b where
chain :: a -> b -> b
-- | Chaining from a value without any annotation
instance ChainStep a [ProofStep a] where
chain x y = SingleStep x [] : y
-- | Chaining from another proof step
instance ChainStep (ProofStep a) [ProofStep a] where
chain x y = x : y
-- | Mark the end of a calculational proof.
qed :: [ProofStep a]
qed = []
-- | Start a calculational proof, with the given hypothesis. Use @[]@ as the
-- first argument if the calculation holds unconditionally. The first argument is
-- typically used to introduce hypotheses in proofs of implications such as @A .=> B .=> C@, where
-- we would put @[A, B]@ as the starting assumption. You can name these and later use in the derivation steps.
(|-) :: [SBool] -> [ProofStep a] -> (SBool, [ProofStep a])
bs |- ps = (sAnd bs, ps)
infixl 0 |-
-- | Alternative unicode for `|-`.
(⊢) :: [SBool] -> [ProofStep a] -> (SBool, [ProofStep a])
(⊢) = (|-)
infixl 0 ⊢
-- | Alternative unicode for `??`.
(⁇) :: ProofHint a b => a -> b -> ProofStep a
(⁇) = (??)
infixl 2 ⁇
-- | Specifying a case-split
cases :: String -> [SBool] -> Helper
cases = HelperCase