packages feed

sbv-13.6: Documentation/SBV/Examples/TP/Coins.hs

-----------------------------------------------------------------------------
-- |
-- Module    : Documentation.SBV.Examples.TP.Coins
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Proving the classic coin change theorem: For any amount @n >= 8@, you can make
-- exact change using only 3-cent and 5-cent coins.
--
-- This example is inspired by: <https://github.com/imandra-ai/imandrax-examples/blob/main/src/coins.iml>
-----------------------------------------------------------------------------

{-# LANGUAGE CPP               #-}
{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeApplications  #-}

{-# OPTIONS_GHC -Wall -Werror #-}

module Documentation.SBV.Examples.TP.Coins where

import Data.SBV
import Data.SBV.Maybe hiding (maybe)
import qualified Data.SBV.Maybe as SM
import Data.SBV.TP

#ifdef DOCTEST
-- $setup
-- >>> import Data.SBV.TP
#endif

-- * Types

-- | A pocket contains a count of 3-cent and 5-cent coins.
data Pocket = Pocket { num3s :: Integer
                     , num5s :: Integer
                     }

-- | Create a symbolic version of Pocket.
mkSymbolic [''Pocket]

-- * Making change

-- | Make change for a given amount. Returns 'Nothing' if the amount is less than 8.
-- Base cases:
--
--   *  8 = 3 + 5
--   *  9 = 3 + 3 + 3
--   * 10 = 5 + 5
--
-- For @n > 10@, we use change for @n-3@ and add one more 3-cent coin.
mkChange :: SInteger -> SMaybe Pocket
mkChange = smtFunction "mkChange" $ \n ->
    ite (n .<   8)  sNothing
  $ ite (n .==  8) (sJust (sPocket 1 1))
  $ ite (n .==  9) (sJust (sPocket 3 0))
  $ ite (n .== 10) (sJust (sPocket 0 2))
    -- n > 10: use change for (n-3) and add a 3-cent coin
    [sCase|Pocket fromJust (mkChange (n - 3)) of
       Pocket n3 n5 -> sJust (sPocket (n3 + 1) n5)
    |]

-- | Evaluate the value of a pocket (total cents).
evalPocket :: SMaybe Pocket -> SInteger
evalPocket = SM.maybe 0 $ \p -> [sCase|Pocket p of
                                  Pocket n3 n5 -> 3 * n3 + 5 * n5
                                |]

-- * Correctness

-- | Prove that for any @n >= 8@, @mkChange@ produces a pocket that evaluates to @n@.
--
-- We have:
--
-- >>> runTP correctness
-- Inductive lemma (strong): mkChangeCorrect
--   Step: Measure is non-negative         Q.E.D.
--   Step: 1 (5 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.5.1                         Q.E.D.
--     Step: 1.5.2                         Q.E.D.
--     Step: 1.5.3                         Q.E.D.
--     Step: 1.Completeness                Q.E.D.
--   Result:                               Q.E.D.
-- [Proven] mkChangeCorrect :: Ɐn ∷ Integer → Bool
correctness :: TP (Proof (Forall "n" Integer -> SBool))
correctness = do
    sInduct "mkChangeCorrect"
            (\(Forall n) -> n .>= 8 .=> evalPocket (mkChange n) .== n)
            (id, []) $
            \ih n -> [n .>= 8]
                  |- evalPocket (mkChange n) .== n
                  =: cases [ n .== 8  ==> trivial
                           , n .== 9  ==> trivial
                           , n .== 10 ==> trivial
                           , n .< 8   ==> trivial   -- Vacuously true: contradicts n >= 8
                           , n .> 10  ==> evalPocket (mkChange n) .== n
                                       =: [sCase|Pocket fromJust (mkChange (n - 3)) of
                                            Pocket n3 n5 -> evalPocket (sJust (sPocket (n3 + 1) n5)) .== n
                                         |]
                                       ?? ih `at` Inst @"n" (n - 3)
                                       =: sTrue
                                       =: qed
                           ]