packages feed

ideas-math-1.1: src/Domain/Logic/Consequence.hs

{-# LANGUAGE RankNTypes #-}
-----------------------------------------------------------------------------
-- Copyright 2014, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- Exercise for the logic domain: to prove two propositions equivalent
--
-----------------------------------------------------------------------------
--  $Id: Consequence.hs 6548 2014-05-16 10:34:18Z bastiaan $

module Domain.Logic.Consequence (consequenceExercise) where

import Control.Monad
import Data.List
import Data.Maybe
import Data.Traversable
import Domain.Logic.Formula
import Domain.Logic.Generator (equalLogicA, normalizeLogicA)
import Domain.Logic.Parser
import Domain.Logic.Proofs (sortRuleOr, sortRuleAnd)
import Domain.Logic.Rules
import Domain.Logic.Strategies (cnfStrategyDWA)
import Domain.Logic.Utils
import Domain.Math.Expr ()
import Ideas.Common.Library hiding (singleton)
import Ideas.Common.Rewriting.AC
import Ideas.Common.Traversal.Navigator
import Ideas.Common.Utils

consequenceExercise :: Exercise Proof
consequenceExercise = makeExercise
   { exerciseId     = describe "Prove that formula is a logical consequence of a set of formulas" $
                         propositionalId # "consequence"
   , status         = Experimental
   , parser         = mapSecond makeProof . parseConsequence False
   , prettyPrinter  = showProof
   , equivalence    = equivalentProofs
   , similarity     = similarProofs
   , suitable       = predicate suitableProof
   , ready          = predicate readyProof
   , strategy       = proofStrategy
--   , extraRules     = map use extraLogicRules ++ map use buggyRules
   , navigation     = termNavigator
   , examples       = exampleProofs
   }

-- (assumptions, consequence)
type Proof = Logic ([SLogic], SLogic)

makeProof :: ([SLogic], SLogic) -> Proof
makeProof = Var

proofPair :: Proof -> ([SLogic], SLogic)
proofPair x = (map catLogic $ sequenceA (fmap fst x), catLogic (fmap snd x))

initial :: Ref Term
initial = makeRef "initial"

onAssumption :: (IsStrategy f, Navigator a) => f a -> Strategy a
onAssumption s = ruleDown <*> ruleDown <*> s <*> ruleUp <*> ruleUp

onConsequence :: (IsStrategy f, Navigator a) => f a -> Strategy a
onConsequence s = ruleDownLast <*> s <*> ruleUp

getInitial :: Context Proof -> Maybe [SLogic]
getInitial ctx =
   ((initial ? ctx) >>= fromTerm)
    `mplus`
   fmap (fst . proofPair) (fromContext ctx)

showProof :: Proof -> String
showProof proof =
   let (ps, q) = proofPair proof
   in intercalate ", " (map ppLogicPars ps) ++ " => " ++ ppLogicPars q

equivalentProofs :: Context Proof -> Context Proof -> Bool
equivalentProofs c1 c2 = fromMaybe False $ do
   as1     <- getInitial c1
   as2     <- getInitial c2
   (ps, p) <- fmap proofPair (fromContext c1)
   (qs, q) <- fmap proofPair (fromContext c2)
   return $ ands as1 === ands as2
         && ands as1 ==> ands ps
         && ands as2 ==> ands qs
         && (ands ps :->: p) <=> (ands qs :->: q)
         && p === q

similarProofs :: Context Proof -> Context Proof -> Bool
similarProofs c1 c2 = fromMaybe False $ do
   as1     <- getInitial c1
   as2     <- getInitial c2
   (ps, p) <- fmap proofPair (fromContext c1)
   (qs, q) <- fmap proofPair (fromContext c2)
   let f = sort . map normalizeLogicA
   return $ as1 == as2
         && f ps == f qs
         && equalLogicA p q

suitableProof :: Proof -> Bool
suitableProof proof =
   let (ps, p) = proofPair proof
   in ands ps ==> p && p `notElem` ps

readyProof :: Proof -> Bool
readyProof proof =
   case proofPair proof of
      ([p], q) -> equalLogicA p q
      _ -> False

readyProofC :: Context Proof -> Bool
readyProofC = maybe False readyProof . fromContext

proofStrategy :: LabeledStrategy (Context Proof)
proofStrategy = label "consequence" $
   keepInitialAssumptions
   <*> try (use conjIntro)
       -- use cnf for the (singleton) assumption
   <*> repeatS (somewhere (use topIsAnd)) -- topIsAndCom
   <*> onAssumption (useC cnfStrategyDWA)
       -- use cnf for the consequence
   <*> onConsequence (useC cnfStrategyDWA)
       -- strong normalization of assumption and consequence
   <*> repeatS (somewhere (use absorptionSubset <|> use fakeAbsorption <|> use fakeAbsorptionNot))
   <*> repeatS (somewhere splitTop)
   <*> use checkCNF <*> normStrategy
 where
   splitTop = use topIsAnd |> use topIsAndCom

checkCNF :: Rule Proof
checkCNF = minor $ makeRule "is-cnf" $ \proof -> do
   let (ps, p) = proofPair proof
   guard (length ps == 1 && isCNF (head ps) && isCNF p)
   Just proof

-- strong normalization for CNF
normStrategy :: Strategy (Context Proof)
normStrategy = untilS readyProofC $
      use assumptionIsFalse
   |> use conjElim
   |> use comAndSubset
   |> somewhere (use ruleFalseZeroAnd <|> use ruleTrueZeroOr)
   |> somewhere (use ruleComplOr)
   |> somewhere (
         use ruleIdempOr   <|>
         use ruleIdempAnd  <|>
         use ruleOrOverAnd <|>
         use ruleTrueZeroAnd
      )
   |> oncetd (use sortRuleOr)
   |> oncetd (use sortRuleAnd)
   -- |> oncetd eliminateVar
   |> somewhereConjunct introduceVar

keepInitialAssumptions :: Rule (Context Proof)
keepInitialAssumptions = minorRule "initial" $ \ctx -> do
   (ps, _) <- fmap proofPair (fromContext ctx)
   Just $ insertRef initial (toTerm ps) ctx

assumptionIsFalse :: Rule Proof
assumptionIsFalse = makeRule "assump-false" $ \proof ->
   case proofPair proof of
      ([F], q) -> Just (makeProof ([F :&&: q], q))
      _        -> Nothing

comAndSubset :: Rule Proof
comAndSubset = makeRule "command-subset" $ \proof -> do
   let (ps, q) = proofPair proof
   p <- singleton ps
   let cps = conjunctions p
       cqs = conjunctions q
       (cps1, cps2) = partition (\x -> any (equalLogicA x) cqs) cps
   guard (p /= q && cps /= (cqs++cps2))
   guard (all (\x -> any (equalLogicA x) cps1) cqs)
   Just (makeProof ([ands (cqs++cps2)], q))

conjElim :: Rule Proof
conjElim = makeRule "conj-elim" $ \proof -> do
   let (ps, q) = proofPair proof
   p <- singleton ps
   let cps = conjunctions p
       cqs = conjunctions q
   guard (p /= q && ( isPrefixOfWith equalLogicA cqs cps
                   || isSuffixOfWith equalLogicA cqs cps))
   Just (makeProof ([q], q))

conjIntro :: Rule Proof
conjIntro = makeRule "conj-intro" (f . proofPair)
 where
   f (ps, q) | length ps > 1 =
      Just (makeProof ([ands ps], q))
   f _ = Nothing

-- eliminate a variable in the assumption
{-
eliminateVar :: Strategy (Context Proof)
eliminateVar =  check superfluous
            <*> (liftToContext $ makeRule "jippie" (\_ -> Just $ makeProof ([pp], pp)))
 where
   pp = Var (ShowString "b")

superfluous :: Context Proof -> Bool
superfluous = isJust . superfluousVar

superfluousVar :: Context Proof -> Maybe ShowString
superfluousVar cp =
   case currentTerm cp >>= fromTerm of
      Just (ps, q) -> listToMaybe (nub (concatMap varsLogic ps) \\ varsLogic q)
      _ -> Nothing -}

introduceVar :: Strategy (Context Proof)
introduceVar =  check missing
            <*> use introFalseLeft
            <*> introCompl

missing :: Context Proof -> Bool
missing = isJust . missingVar

-- The variables in the current subproof
globalVars :: Context Proof -> [ShowString]
globalVars cp =
   case currentTerm cp >>= fromTerm of
      Just (ps, q) -> nub (concatMap varsLogic (q:ps))
      _ -> maybe [] globalVars (up cp)

missingVar :: Context Proof -> Maybe ShowString
missingVar cp =
   case currentTerm cp >>= fromTerm of
      Just p  -> listToMaybe (globalVars cp \\ varsLogic p)
      Nothing -> Nothing

introFalseLeft :: Rule SLogic
introFalseLeft = rewriteRule "IntroFalseLeft" $
   \x -> x  :~>  F :||: x

introCompl :: Rule (Context Proof)
introCompl = makeRule "IntroCompl" $ \cp -> do
   a <- missingVar cp -- focus is on conjunct
   let f = fromTerm >=> fmap toTerm . introContradiction a
   changeTerm f cp
 where
   introContradiction :: a -> Logic a -> Maybe (Logic a)
   introContradiction a F = Just (Var a :&&: Not (Var a))
   introContradiction a (p :||: q) =
      fmap (:||: q) (introContradiction a p) `mplus`
      fmap (p :||:) (introContradiction a q)
   introContradiction _ _ = Nothing

-- let op: ook commutatieve variant nog toevoegen
topIsAnd :: Rule Proof
topIsAnd = {- minor $                !!!!!!!!!!!!!!!!!!!!!-}
   ruleTrans "top-is-and" $ acTopRuleFor False (collect andView)

topIsAndCom :: Rule Proof
topIsAndCom = ruleTrans "top-is-and.com" $ acTopRuleFor True (collect andView)

acTopRuleFor :: Bool -> (forall a . Isomorphism (Logic a) [Logic a])
             -> Transformation Proof
acTopRuleFor com ep = makeTrans $ \proof -> do
   (ps, q) <- maybeToList (getSingleton proof)
   guard (length ps == 1)
   let pair = (head ps, q)
       pairings = if com then pairingsAC else pairingsA
       ep2 = ep *** ep
       (xs, ys) = from ep2 pair
   guard (length xs > 1 && length ys > 1)
   zs <- liftM (map (to ep2)) (pairings False xs ys)
   guard (all (uncurry (==>)) zs)
   return (to ep [ Var ([x], y) | (x, y) <- zs])

-- see somewhereDisjunct in Proofs
somewhereConjunct :: IsStrategy f => f (Context Proof) -> Strategy (Context Proof)
somewhereConjunct s = somewhere (check isPair <*> onPair)
 where
   isPair ctx = case currentTerm ctx of
                   Just (TList [_, _]) -> True
                   _ -> False
   onPair =
          onAssumption  (somewhereAndG s) -- conjunct in assumption
      <|> onConsequence (somewhereAndG s) -- conjunct in consequence

somewhereAndG :: IsStrategy g => g (Context a) -> Strategy (Context a)
somewhereAndG s =
   let curIsAnd a = case currentTerm a >>= (fromTerm :: Term -> Maybe SLogic) of
                       Just (_ :&&: _) -> True
                       _               -> False
   in fix $ \this -> check (Prelude.not . curIsAnd) <*> s
                 <|> check curIsAnd <*> layer [] this

exampleProofs :: Examples Proof
exampleProofs =
   [ easy      [p :&&: Not p] q
   , medium    [p :->: q, p :->: Not q] (Not p)
   , medium    [Not p :->: Not q, q] p
   , difficult [p :->: q, r :->: s] ((p :&&: r) :->: (q :&&: s))
   , difficult [Not r :<->: q] (p :->: (q :||: r))
   , medium    [p :->: q, p :->: r] (p :->: (q :&&: r))
   , medium    [p :->: q, q :->: r] (p :->: r)
   , medium    [p :->: (q :->: r), q] (p :->: r)
   -- , medium    [p :->: (q :->: p)] p
   , difficult [p :->: (q :&&: r)] ((p :&&: q) :<->: (p :&&: r))
   , difficult [p :<->: r, q :<->: s] ((p :->: q) :<->: (r :->: s))
   , difficult [p :<->: r, q :<->: s] ((p :&&: q) :<->: (r :&&: s))
   , difficult [p :<->: r, q :<->: s] ((p :||: q) :<->: (r :||: s))
   , difficult [q :<->: s] ((p :&&: q) :<->: (p :&&: s))
   , easy      [p :->: (p :->: q), p] q
   , easy      [p :->: q, p] q
   , easy      [p :||: (q :&&:r)] (Not p :->: r)
   , easy      [p :->: q, Not q] (Not p)
   , difficult [q] ((p :&&: q) :<->: p)
   , difficult [Not q] ((p :||: q) :<->: p)
   , difficult [p :<->: q, p] q
   , difficult [p :<->: q, Not p] (Not q)
   , medium    [q] (p :->: (p :&&:q))
   , medium    [Not q] ((p :||: q) :->: p)
   , medium    [Not q, p :||: q] p
   ]
 where
   easy      = make Easy
   medium    = make Medium
   difficult = make Difficult

   make dif ps consq = (dif, makeProof (ps, consq))

   p = Var (ShowString "p")
   q = Var (ShowString "q")
   s = Var (ShowString "s")
   r = Var (ShowString "r")

isPrefixOfWith, isSuffixOfWith :: (a -> a -> Bool) -> [a] -> [a] -> Bool
isPrefixOfWith _ [] _ = True
isPrefixOfWith _ _ [] = False
isPrefixOfWith eq (x:xs) (y:ys) = eq x y && isPrefixOfWith eq xs ys

isSuffixOfWith eq xs ys = isPrefixOfWith eq (reverse xs) (reverse ys)

singleton :: [a] -> Maybe a
singleton [x] = Just x
singleton _   = Nothing

-- debug
{-
vb :: Proof
Right vb = parseConsequence False "p -> q, r -> s => \
    \(~p || q || ~r || s) /\\ (~p || q || ~r || ~s) /\\ (~p || ~q || ~r || s) /\\ (p || q || ~r || s) =>\
    \(~p || q || ~r || s) /\\ (~p || q || ~r || ~s) /\\ (~p || q || ~r || s)"
-}

-- (p \/ q) /\ ... /\ (p \/ q \/ r)    ~> (p \/ q) /\ ...
--    (subset relatie tussen rijtjes: bijzonder geval is gelijke rijtjes)
absorptionSubset :: Rule SLogic
absorptionSubset = ruleList "absorptionSubset" $ \p -> do
   let xss = map disjunctions (conjunctions p)
       yss = nub $ filter (\xs -> all (ok xs) xss) xss
       ok xs ys = not (ys `isSubsetOf` xs) || xs == ys
   guard (length yss < length xss)
   return $ ands (map ors yss)

-- p /\ ... /\ (~p \/ q \/ r)  ~> p /\ ... /\ (q \/ r)
--    (p is hier een losse variabele)
fakeAbsorption :: Rule SLogic
fakeAbsorption = makeRule "fakeAbsorption" $ \p -> do
   let xs = conjunctions p
   v <- [ a | a@(Var _) <- xs ]
   let ys  = map (ors . filter (/= Not v) . disjunctions) xs
       new = ands ys
   guard (p /= new)
   return new

-- ~p /\ ... /\ (p \/ q \/ r)  ~> ~p /\ ... /\ (q \/ r)
--   (p is hier een losse variabele)
fakeAbsorptionNot :: Rule SLogic
fakeAbsorptionNot = makeRule "fakeAbsorptionNot" $ \p -> do
   let xs = conjunctions p
   v <- [ a | Not a@(Var _) <- xs ]
   let ys  = map (ors . filter (/= v) . disjunctions) xs
       new = ands ys
   guard (p /= new)
   return new