packages feed

sbv-11.1: Documentation/SBV/Examples/KnuckleDragger/Numeric.hs

-----------------------------------------------------------------------------
-- |
-- Module    : Documentation.SBV.Examples.KnuckleDragger.Numeric
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Example use of inductive KnuckleDragger proofs, over integers.
-----------------------------------------------------------------------------

{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE TypeAbstractions #-}
{-# LANGUAGE TypeApplications #-}

{-# OPTIONS_GHC -Wall -Werror #-}

module Documentation.SBV.Examples.KnuckleDragger.Numeric where

import Prelude hiding (sum, length)

import Data.SBV
import Data.SBV.Tools.KnuckleDragger

-- | Prove that sum of constants @c@ from @0@ to @n@ is @n*c@.
--
-- We have:
--
-- >>> sumConstProof
-- Inductive lemma: sumConst_correct
--   Base: sumConst_correct.Base           Q.E.D.
--   Asms: 1                               Q.E.D.
--   Step: 1                               Q.E.D.
--   Asms: 2                               Q.E.D.
--   Step: 2                               Q.E.D.
--   Step: 3                               Q.E.D.
--   Step: 4                               Q.E.D.
--   Step: 5                               Q.E.D.
--   Step: sumConst_correct.Step           Q.E.D.
-- [Proven] sumConst_correct
sumConstProof :: IO Proof
sumConstProof = runKD $ do
   let c :: SInteger
       c = uninterpret "c"

       sum :: SInteger -> SInteger
       sum = smtFunction "sum" $ \n -> ite (n .== 0) 0 (c + sum (n - 1))

       spec :: SInteger -> SInteger
       spec n = c * n

   induct "sumConst_correct"
          (\(Forall @"n" n) -> n .>= 0 .=> sum n .== spec n) $
          \ih k -> [k .>= 0] |- sum (k+1)  ? k .>= 0
                             =: c + sum k  ? [hprf ih, hyp (k .>= 0)]
                             =: c + spec k
                             =: c + c*k
                             =: c*(k+1)
                             =: spec (k+1)
                             =: qed

-- | Prove that sum of numbers from @0@ to @n@ is @n*(n-1)/2@.
--
-- Note that z3 (as of mid Feb 2025) can't converge on this quickly, but CVC5 does just fine. We have:
--
-- >>> sumProof
-- Inductive lemma: sum_correct
--   Base: sum_correct.Base                Q.E.D.
--   Asms: 1                               Q.E.D.
--   Step: 1                               Q.E.D.
--   Asms: 2                               Q.E.D.
--   Step: 2                               Q.E.D.
--   Step: 3                               Q.E.D.
--   Step: sum_correct.Step                Q.E.D.
-- [Proven] sum_correct
sumProof :: IO Proof
sumProof = runKD $ do
   let sum :: SInteger -> SInteger
       sum = smtFunction "sum" $ \n -> ite (n .== 0) 0 (n + sum (n - 1))

       spec :: SInteger -> SInteger
       spec n = (n * (n+1)) `sDiv` 2

       p :: SInteger -> SBool
       p n = sum n .== spec n

   inductWith cvc5 "sum_correct"
          (\(Forall @"n" n) -> n .>= 0 .=> p n) $
          \ih k -> [k .>= 0] |- sum (k+1)    ? k .>= 0
                             =: k+1 + sum k  ? [hprf ih, hyp (k .>= 0)]
                             =: k+1 + spec k
                             =: spec (k+1)
                             =: qed

-- | Prove that sum of square of numbers from @0@ to @n@ is @n*(n+1)*(2n+1)/6@.
--
-- Note that z3 (as of mid Feb 2025) can't converge on this quickly, but CVC5 does just fine. We have:
--
-- >>> sumSquareProof
-- Inductive lemma: sumSquare_correct
--   Base: sumSquare_correct.Base          Q.E.D.
--   Asms: 1                               Q.E.D.
--   Step: 1                               Q.E.D.
--   Asms: 2                               Q.E.D.
--   Step: 2                               Q.E.D.
--   Step: 3                               Q.E.D.
--   Step: sumSquare_correct.Step          Q.E.D.
-- [Proven] sumSquare_correct
sumSquareProof :: IO Proof
sumSquareProof = runKD $ do
   let sumSquare :: SInteger -> SInteger
       sumSquare = smtFunction "sumSquare" $ \n -> ite (n .== 0) 0 (n * n + sumSquare (n - 1))

       spec :: SInteger -> SInteger
       spec n = (n * (n+1) * (2*n+1)) `sDiv` 6

       p :: SInteger -> SBool
       p n = sumSquare n .== spec n

   inductWith cvc5 "sumSquare_correct"
          (\(Forall @"n" n) -> n .>= 0 .=> p n) $
          \ih k -> [k .>= 0] |- sumSquare (k+1)           ? k .>= 0
                             =: (k+1)*(k+1) + sumSquare k ? [hprf ih, hyp (k .>= 0)]
                             =: (k+1)*(k+1) + spec k
                             =: spec (k+1)
                             =: qed

-- | Prove that @11^n - 4^n@ is always divisible by 7.
--
-- NB. As of Feb 2025, z3 struggles with the inductive step in this proof, but cvc5 performs just fine.
--
-- We have:
--
-- >>> elevenMinusFour
-- Lemma: powN                             Q.E.D.
-- Inductive lemma: elevenMinusFour
--   Base: elevenMinusFour.Base            Q.E.D.
--   Step: 1                               Q.E.D.
--   Asms: 2                               Q.E.D.
--   Step: 2                               Q.E.D.
--   Asms: 3                               Q.E.D.
--   Step: 3                               Q.E.D.
--   Step: 4                               Q.E.D.
--   Step: 5                               Q.E.D.
--   Asms: 6                               Q.E.D.
--   Step: 6                               Q.E.D.
--   Step: 7                               Q.E.D.
--   Step: 8                               Q.E.D.
--   Step: elevenMinusFour.Step            Q.E.D.
-- [Proven] elevenMinusFour
elevenMinusFour :: IO Proof
elevenMinusFour = runKD $ do
   let pow :: SInteger -> SInteger -> SInteger
       pow = smtFunction "pow" $ \x y -> ite (y .== 0) 1 (x * pow x (y - 1))

       emf :: SInteger -> SBool
       emf n = 7 `sDivides` (11 `pow` n - 4 `pow` n)

   -- helper
   powN <- lemma "powN" (\(Forall @"x" x) (Forall @"n" n) -> n .>= 0 .=> x `pow` (n+1) .== x * x `pow` n) []

   inductWith cvc5 "elevenMinusFour"
          (\(Forall @"n" n) -> n .>= 0 .=> emf n) $
          \ih k -> [k .>= 0]
                |- emf (k+1)
                =: 7 `sDivides` (11 `pow` (k+1) - 4 `pow` (k+1))  ? [hyp (k .>= 0), hprf (powN `at` (Inst @"x" (11 :: SInteger), Inst @"n" k))]
                =: 7 `sDivides` (11 * 11 `pow` k - 4 `pow` (k+1)) ? [hyp (k .>= 0), hprf (powN `at` (Inst @"x" ( 4 :: SInteger), Inst @"n" k))]
                =: 7 `sDivides` (11 * 11 `pow` k - 4 * 4 `pow` k)
                =: 7 `sDivides` (7 * 11 `pow` k + 4 * 11 `pow` k - 4 * 4 `pow` k)
                =: 7 `sDivides` (7 * 11 `pow` k + 4 * (11 `pow` k - 4 `pow` k)) ? [hyp (k .>= 0), hprf ih]
                =: let x = some "x" (\v -> 7*v .== 11 `pow` k - 4 `pow` k)   -- Apply the IH and grab the witness for it
                in 7 `sDivides` (7 * 11 `pow` k + 4 * 7 * x)
                =: 7 `sDivides` (7 * (11 `pow` k + 4 * x))
                =: sTrue
                =: qed