packages feed

GenZ-0.1.0.0: lib/General.hs

{-# LANGUAGE InstanceSigs, FlexibleInstances, LambdaCase #-}

module General where

import Control.Monad
import Data.GraphViz
import Data.GraphViz.Types.Monadic hiding ((-->))
import Data.List as List
import Data.Set (Set)
import qualified Data.Set as Set
import System.IO (hGetContents)
import System.Process
import Basics
import Data.Maybe (fromJust)

type Sequent f = Set (Either f f)

type RuleName = String

-- | A proof as a tree: the Bool flag should start as false and later turn true if provable
data Proof f = Node (Sequent f) (Maybe (RuleName, [Proof f])) Bool
  deriving (Eq,Ord,Show)

-- | Extract truth from a proof
getTruth :: Proof f -> Bool
getTruth (Node _ _ b) = b

-- | Proof size
proofSize :: Proof f -> Int
proofSize (Node _ Nothing _) = 1
proofSize (Node _ (Just (_, ts)) _) = 1 + sum (map proofSize ts)

-- * Histories, Rules, Logics

type History f = [Sequent f]

class HasHistory a where
  histOf :: a f -> History f

class HasProof a where
  proofOf :: a f -> Proof f

-- | A `Rule` takes the history/branch, current sequent and a principal formula.
-- It returns ways to apply a rule, each resulting in possibly multiple branches.
type Rule f = History f -> Sequent f -> Either f f -> [(RuleName, [Sequent f])]

-- | A replace rule only takes a principal formula.
replaceRule :: (Eq f, Ord f) => (Either f f -> [(RuleName, [Sequent f])]) -> Rule f
replaceRule fun _ fs g =
  [ ( fst . head $ fun g
    , [ Set.delete g fs `Set.union` newfs
      | newfs <- snd . head $ fun g ]
    )
  | not (null (fun g)) ]

isApplicable :: History f -> Sequent f -> Either f f -> Rule f -> Bool
isApplicable hs fs f r = not . null $ r hs fs f

isApplicableRule :: History f -> Sequent f -> Rule f -> Bool
isApplicableRule hs fs r = any (\f -> isApplicable hs fs f r) fs

-- | A Logic for a formula type `f`.
data Logic f = Log { name :: String
                   , safeRules   :: [Rule f]
                   , unsafeRules :: [Rule f] }

-- | A prover takes a logic and a formula and returns a Boolean.
type Prover f = Logic f -> f -> Bool

-- * Tree Proofs

newtype ProofWithH f = HP (History f, Proof f)

instance HasHistory ProofWithH where
  histOf (HP (hs, _)) = hs

instance HasProof ProofWithH where
  proofOf (HP (_, pf)) = pf

-- * Zip Proofs

-- | Zipper version of the @Proof@ type.
data ZipProof f = ZP (Proof f) (ZipPath f)
  deriving (Eq,Ord,Show)

data ZipPath f = Top | Step (Sequent f) RuleName (ZipPath f) [Proof f] [Proof f]
  deriving (Eq,Ord,Show)

instance HasHistory ZipPath where
  histOf :: ZipPath f -> History f
  histOf Top = []
  histOf (Step xs _ p _ _) = xs : histOf p

instance HasHistory ZipProof where
  histOf :: ZipProof f -> History f
  histOf (ZP _ zpath) = histOf zpath

instance HasProof ZipProof where
  proofOf (ZP pf _) = pf

-- * Tree-based prover

startForT :: f -> ProofWithH f
startForT f =  HP ([], Node (Set.singleton (Right f)) Nothing False)

extendT  :: (Eq f, Show f, Ord f) => Logic f -> ProofWithH f -> [ProofWithH f]
extendT l pt@(HP (h, Node fs Nothing _)) =
  case ( List.filter (isApplicableRule h fs) (safeRules l)
       , unsafeRules l ) of
  -- The safe rule r can be applied:
  (r:_ , _       ) ->
    [ HP (h, Node fs (Just (therule, map proofOf ts)) $ all (getTruth . proofOf) ts)
    | (therule, result) <- r h fs f
    , ts <- pickOneOfEach [ extendT l (HP (fs : h, Node newSeqs Nothing False))
                          | newSeqs <- result ] ]
    where f = Set.elemAt 0 $ Set.filter (\g -> isApplicable h fs g r) fs
              -- (Using the first possible principal formula.)
  -- At least one unsafe rule can be applied:
  ([], rs@(_:_)) -> List.concatMap applyRule rs
    where
      applyRule r = case List.filter (getTruth . proofOf) nps of
                    tp : _ -> [tp]
                    [] -> [HP (h, Node fs Nothing False)]
        where
          gs = Set.filter (\g -> isApplicable h fs g r) fs
          nps = concat $ List.concatMap tryExtendT gs
          tryExtendT g = [ List.map (\pwh -> HP (h, Node fs (Just (therule, [proofOf pwh])) $ getTruth (proofOf pwh)))
                           $ extendT l (HP (fs : h, Node (head result) Nothing False))
                           -- (Using head because we never have branching unsafeRules.)
                         | (therule, result) <- r (histOf pt) fs g ]
  -- No rule can be applied, leave proof unfinished:
  ([], []      ) -> [HP (h, Node fs Nothing False)]
extendT _ (HP (_,Node _ (Just _) _)) = error "already extended"

-- | Generate a list of (possibly open) proofs.
proveT :: (Eq f, Show f,Ord f) => Logic f -> f -> [Proof f]
proveT l f = List.map proofOf $ extendT l (startForT f)

-- | Check whether there is a closed proof.
isProvableT :: (Eq f, Show f, Ord f) => Prover f
isProvableT l f = any getTruth (proveT l f)

-- | Generate a list of proofs, only keeping the closed ones if there is any.
proofsT :: (Eq f, Show f,Ord f) => Logic f -> f -> [Proof f]
proofsT l f = filterIfAny' getTruth (proveT l f)

-- | Generate the first closed proof, if there is one
proofT :: (Eq f, Show f,Ord f) => Logic f -> f -> Maybe (Proof f)
proofT l f = case dropWhile (not . getTruth) (proveT l f) of
              []      -> Nothing
              (p : _) -> Just p

provePdfT :: (Ord f,Show f, Eq f) => Logic f -> f -> IO FilePath
provePdfT l f= pdf $ fromJust $ proofT l f

-- * Zipper-based prover

instance TreeLike ZipProof where
  zsingleton x                               = ZP (Node (Set.singleton (Right x)) Nothing False) Top
  move_left (ZP c (Step s r p xs ys))    =
    if null xs
      then error "cannot go left"
      else ZP (last xs) (Step s r p (init xs) (c:ys))
  move_left _                                = error "cannot go left"
  move_right (ZP c (Step s r p xs (y:ys)))   = ZP y (Step s r p (xs ++ [c]) ys)
  move_right _                               = error "cannot go right"
  move_up (ZP c@(Node _ _ t) (Step s r p xs ys)) =
      let cs = reverse xs ++ [c] ++ ys
      in ZP (Node s (Just (r, cs)) $ t && all getTruth cs) p   -- update parent's truth
  move_up _                                  = error "cannot go up"
  move_down (ZP (Node s (Just (r, x:xs)) _) p) = ZP x (Step s r p [] xs)
  move_down _                                = error "cannot go down"
  zdelete (ZP _ (Step s _ Top _ _))          = ZP (Node s Nothing False) Top
  zdelete (ZP _ (Step s _ p _ _))            = ZP (Node s Nothing False) p
  zdelete _                                  = error "cannot delete top"

-- | Convert a zipper proof to a tree proof by going to the root.
fromZip :: ZipProof f -> Proof f
fromZip (ZP x Top) = x
fromZip zp = fromZip (move_up zp)

-- | Does the node have a right sibling?
hasRsibi :: ZipPath f -> Bool
hasRsibi (Step _ _ _ _ (_:_))= True
hasRsibi _ = False

-- | Does the node have a left sibling?
hasLsibi :: ZipPath f -> Bool
hasLsibi (Step _ _ _ (_:_) _ )= True
hasLsibi _ = False

-- | Switch path, left-biased
-- All the truth manipulations are done by TreeLike method
switch :: ZipProof f -> ZipProof f
switch (ZP pf Top) = ZP pf Top
switch (ZP pf p) = if hasRsibi p
                      then move_right (ZP pf p)
                      else switch.move_up $ ZP pf p

reverseSwitch :: ZipProof f -> ZipProof f
reverseSwitch (ZP pf Top) = ZP pf Top
reverseSwitch (ZP pf p) = if hasLsibi p
                      then move_left (ZP pf p)
                      else reverseSwitch.move_up $ ZP pf p

leftAllClosed :: ZipProof f -> Bool
leftAllClosed (ZP _ Top) = True
leftAllClosed zp = getTruth (proofOf zp) && leftAllClosed (reverseSwitch zp)


startForZ :: f -> ZipProof f
startForZ f = ZP (Node (Set.singleton (Right f)) Nothing False) Top

extendZ  :: (Ord f,Eq f) => Logic f -> ZipProof f -> [ZipProof f]
extendZ l zp@(ZP (Node fs Nothing _) p) =
  case ( List.filter (isApplicableRule (histOf zp) fs) (safeRules l)
       , unsafeRules l) of
  -- The safe rule r can be applied:
  (r:_ , _       )    ->  let f = Set.elemAt 0 $ Set.filter (\g -> isApplicable (histOf zp) fs g r) fs
                              (therule,results) = head $ r (histOf zp) fs f
                              newPf             = Node fs (Just (therule, [Node newSeq Nothing False | newSeq <- results]))
                              -- The truth condition of the parent will always be false, until set true during the switch
                              -- If results null, True here of course. We will switch branch
                              nextZP
                                | null results  = switch    $ ZP (newPf True) p-- no children, i.e. proved
                                | otherwise     = move_down $ ZP (newPf False) p -- keep extending the child node
                          in extendZ l nextZP
  -- At least one unsafe rule can be applied:
  ([], rs@(_:_))    -> List.concatMap applyRule rs
    where
      applyRule r = case List.filter leftAllClosed nps of
                    np : _ -> [np]                           -- the closed proof
                    []     -> [ZP (Node fs Nothing False) p]
        where
          gs = Set.filter (\g -> isApplicable (histOf zp) fs g r) fs
          nps = concat $ List.concatMap tryExtendZ gs
          tryExtendZ g = [ extendZ l (ZP (Node (head result) Nothing False) (Step fs therule p [] []) )
                         -- head result as unsaferules won't branch
                         | (therule,result) <- r (histOf zp) fs g ]
  -- No rule can be applied, leave proof unfinished:
  ([], []      )    -> [ZP (Node fs Nothing False) p] -- we won't return to top in this case
  -- if provable, will return to top. Not otherwise
extendZ _ zp@(ZP (Node _ (Just _ ) _) _) = [zp] -- needed after switch

-- The easiest way to check whether a zipproof is closed is by always going back to Top and manipulate the truth on the way
-- Then to check the truth of the whole proof, we only need to check the truth condition at the top

-- | Generate a list of (possibly open) zip proofs.
proveZZ :: (Eq f, Ord f) => Logic f -> f -> [ZipProof f]
proveZZ l f = extendZ l (startForZ f)

-- | Generate a list of (possibly open) proofs.
proveZ :: (Eq f, Ord f) => Logic f -> f -> [Proof f]
proveZ l f = List.map fromZip $ proveZZ l f

-- | Check whether there is a closed proof.
isProvableZ :: (Eq f, Ord f) => Prover f
isProvableZ l f = any (getTruth . proofOf) $ List.filter isTop $ proveZZ l f

-- | Generate a list of proofs, only keeping the closed ones if there is any.
proofsZ :: (Eq f, Show f,Ord f) => Logic f -> f -> [Proof f]
proofsZ l f = filterIfAny' getTruth (proveZ l f)

-- | Generate the first closed proof, if there is one
proofZ :: (Eq f, Show f,Ord f) => Logic f -> f -> Maybe (Proof f)
proofZ l f = case dropWhile (not . getTruth) (proveZ l f) of
              []      -> Nothing
              (p : _) -> Just p


isTop :: ZipProof f -> Bool
isTop (ZP _ Top) = True
isTop _ = False

provePdfZ :: (Ord f,Show f, Eq f) => Logic f -> f -> IO FilePath
provePdfZ l f= pdf $ fromJust $ proofZ l f

-- * Pretty printing, GraphViz and LaTeX output

-- | Pretty print a list of formulas.
ppList :: Show f => [f] -> String
ppList = intercalate " , " . map show

-- | Pretty print a Set of formulas.
ppForm :: Show f => Set f -> String
ppForm ms = ppList (Set.toList ms)

-- | Pretty print a Sequent.
ppSeq :: (Show f, Ord f) => Sequent f -> String
ppSeq xs = ppForm (leftsSet xs) ++ " => " ++ ppForm (rightsSet xs)

-- | Pretty print a proof, with the root last.
ppProof ::  (Show f,Ord f) => Proof f -> String
ppProof = ppProof' 0 where
  ppProof' k (Node xs Nothing _) = indent k ++ ppSeq xs
  ppProof' k (Node xs (Just (rule',ts)) _) =
    unlines (map (ppProof' (k + 1)) ts)  -- first children
    ++ indent k ++ replicate (length (ppSeq xs)) '-' ++ " (" ++ rule' ++ ")\n" -- then rule
    ++ indent k ++ ppSeq xs              -- then current sequent
  indent k = concat (replicate k "  ")

-- | Visualisation of proofs.
-- Note that @toGraph@ does not show the Bool flag.
instance (Show f,Ord f) => (DispAble (Proof f)) where
  toGraph = toGraph' "" where
    toGraph' pref (Node fs Nothing _) = do
      node pref [shape PlainText, toLabel $ ppSeq fs]
      node (pref ++ "open") [shape PlainText, toLabel "?"]
      edge pref (pref ++ "open") []
    toGraph' pref (Node fs (Just (rule',ts)) _) = do
      node pref [shape PlainText, toLabel $ ppSeq fs]
      if null ts then do
        node pref [shape PlainText, toLabel $ ppSeq fs]
        node (pref ++ "closed") [shape PlainText, toLabel "."]
        edge pref (pref ++ "closed") [toLabel rule']
      else mapM_ (\(t,y') -> do
        toGraph' (pref ++ show y' ++ ":") t
        edge pref (pref ++ show y' ++ ":") [toLabel rule']
        ) (zip ts [(0::Integer)..])

class TeX a where
  tex :: a -> String
  texFile :: a -> IO ()
  texFile x = do
    let
      pre = unlines [ "\\documentclass[border=2pt]{standalone}"
                   , "\\usepackage[utf8]{inputenc}"
                   , "\\usepackage{bussproofs,fontenc,graphicx,amssymb,amsmath}"
                   , "\\usepackage[pdftex]{hyperref}"
                   , "\\hypersetup{pdfborder={0 0 0},breaklinks=true}"
                   , "\\begin{document}" ]
      post = "\\DisplayProof\n\\end{document}"
    writeFile "temp.tex" (pre ++ tex x ++ post)
    (_inp, _out, err, pid) <- runInteractiveCommand "pdflatex -interaction=nonstopmode temp.tex"
    _ <- waitForProcess pid
    hGetContents err >>= (\e -> unless (null e) (putStrLn e))

instance (Ord f, TeX f) => TeX (Sequent f) where
  tex xs = texList (Set.toList $ leftsSet xs) ++ " \\Rightarrow " ++ texList (Set.toList $ rightsSet xs)

texList :: TeX f => [f] -> String
texList = intercalate " , " . map (removeOutsideBrackets . tex) where
  removeOutsideBrackets ('(':rest) = init rest
  removeOutsideBrackets s = s

texRuleName :: RuleName -> String
texRuleName r = "$" ++ concatMap f r ++ "$" where
  f = \case
    'v' -> "\\lor"
    '→' -> "\\to"
    '∧' -> "\\land"
    'R' -> "{}_{\\mathsf{R}}"
    'L' -> "{}_{\\mathsf{L}}"
    'i' -> "^{i}"
    'T' -> "\\mathsf{T}"
    'a' -> "\\mathsf{a}"
    'x' -> "\\mathsf{x}"
    'c' -> "\\mathsf{c}"
    'y' -> "\\mathsf{y}"
    'l' -> "\\mathsf{l}"
    'e' -> "\\mathsf{e}"
    '⊥' -> "\\bot"
    '4' -> "{}_{\\mathsf{4}}"
    '5' -> "{}_{\\mathsf{5}}"
    'k' -> "{}_{\\mathsf{k}}"
    '☐' -> "\\Box "
    c -> [c]

-- | Generate LaTeX code to show a proof using the buss package.
-- Does not include @\DisplayProof@ yet.
toBuss :: (Show f, TeX f, Ord f) => Proof f -> String
toBuss (Node fs Nothing _) = "\\AxiomC{ $ " ++ tex fs ++ " $ }"
toBuss (Node fs (Just (rule', ts)) _) =
  concatMap toBuss ts
  ++
  case length ts of
  0 -> "\\AxiomC{\\phantom{I}}\n " ++ r ++ "\\UnaryInfC{ $" ++ tex fs ++ "$ }\n"
  1 -> r ++ "\\UnaryInfC{ $" ++ tex fs ++  "$ }\n"
  2 -> r ++ "\\BinaryInfC{ $" ++ tex fs ++  "$ }\n"
  _ -> error "too many premises"
  where r = "\\LeftLabel{" ++ texRuleName rule' ++ "}\n"

instance (Show f, TeX f, Ord f) => TeX (Proof f) where
  tex = toBuss

-- * The Language
type Atom = String

-- | This formula type contains propositional logic.
class PropLog f where
  neg :: f -> f
  con :: f -> f ->f
  dis :: f -> f ->f
  top :: f
  iff :: f -> f -> f
  isAtom :: f -> Bool
  isAxiom :: Rule f
  leftBot :: Rule f
  size :: f -> Int
  subFormulas :: f -> [f]

swap :: Either a b -> Either b a
swap (Left x) = Right x
swap (Right x) = Left x