packages feed

smcdel-1.3.0: src/SMCDEL/Symbolic/S5_CUDD.hs

{-# LANGUAGE DerivingStrategies, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, PolyKinds, ScopedTypeVariables #-}

module SMCDEL.Symbolic.S5_CUDD where

import Cudd.Cudd (DdManager)
import Data.Char (isSpace)
import Data.GraphViz
import Data.GraphViz.Printing (renderDot)
import qualified Data.GraphViz.Types.Generalised as DotGen
import Data.List ((\\), dropWhileEnd, intercalate, nub)
import qualified Data.Text.Lazy as B
import Data.Typeable()
import System.IO
import System.IO.Temp
import System.IO.Unsafe (unsafePerformIO)
import System.Process ( runInteractiveCommand )

import SMCDEL.Internal.Help ((!),apply,powerset)
import SMCDEL.Internal.MyHaskCUDD
import SMCDEL.Internal.TexDisplay
import SMCDEL.Language

boolDdOf :: (DdCtx a b c) => Cudd.Cudd.DdManager -> Form -> Dd a b c
boolDdOf mgr Top           = top mgr
boolDdOf mgr Bot           = bot mgr
boolDdOf mgr (PrpF (P n))  = var mgr n
boolDdOf mgr (Neg form)    = neg mgr $ boolDdOf mgr form
boolDdOf mgr (Conj forms)  = conSet mgr $ map (boolDdOf mgr) forms
boolDdOf mgr (Disj forms)  = disSet mgr $ map (boolDdOf mgr) forms
boolDdOf mgr (Xor forms)   = xorSet mgr $ map (boolDdOf mgr) forms
boolDdOf mgr (Impl f g)    = imp mgr (boolDdOf mgr f) (boolDdOf mgr g)
boolDdOf mgr (Equi f g)    = equ mgr (boolDdOf mgr f) (boolDdOf mgr g)
boolDdOf mgr (Forall ps f) = boolDdOf mgr (foldl singleForall f ps) where
  singleForall g p = Conj [ substit p Top g, substit p Bot g ]
boolDdOf mgr (Exists ps f) = boolDdOf mgr (foldl singleExists f ps) where
  singleExists g p = Disj [ substit p Top g, substit p Bot g ]
boolDdOf _   f             = error $ "boolDdOf failed: Not a boolean formula:" ++ show f

-- | Knowledge structures using a BDD or ZDD variant.
data KnowStruct a b c =
  KnS Cudd.Cudd.DdManager [Prp] (Dd a b c) [(Agent,[Prp])]
  deriving stock (Eq,Show)
type KnState = [Prp]
type KnowScene a b c = (KnowStruct a b c, KnState)

instance HasAgents (KnowStruct a b c) where
  agentsOf (KnS _ _ _ obs)= map fst obs

instance HasVocab (KnowStruct a b c) where
  vocabOf (KnS _ props _ _) = props

instance Pointed (KnowStruct a b c) KnState


ddOf :: (DdCtx a b c) => KnowStruct a b c -> Form -> Dd a b c
ddOf     (KnS mgr _ _ _) Top           = top mgr
ddOf     (KnS mgr _ _ _) Bot           = bot mgr
ddOf     (KnS mgr _ _ _) (PrpF (P n))  = var mgr n
ddOf kns@(KnS mgr _ _ _) (Neg form)    = neg mgr $ ddOf kns form
ddOf kns@(KnS mgr _ _ _) (Conj forms)  = conSet mgr $ map (ddOf kns) forms
ddOf kns@(KnS mgr _ _ _) (Disj forms)  = disSet mgr $ map (ddOf kns) forms
ddOf kns@(KnS mgr _ _ _) (Xor  forms)  = xorSet mgr $ map (ddOf kns) forms
ddOf kns@(KnS mgr _ _ _) (Impl f g)    = imp mgr (ddOf kns f) (ddOf kns g)
ddOf kns@(KnS mgr _ _ _) (Equi f g)    = equ mgr (ddOf kns f) (ddOf kns g)
ddOf kns@(KnS mgr _ _ _) (Forall ps f) = forallSet mgr (map fromEnum ps) (ddOf kns f)
ddOf kns@(KnS mgr _ _ _) (Exists ps f) = existsSet mgr (map fromEnum ps) (ddOf kns f)
ddOf kns@(KnS mgr allprops lawbdd obs) (K i form) =
  forallSet mgr otherps (imp mgr lawbdd (ddOf kns form)) where
    otherps = map (\(P n) -> n) $ allprops \\ apply obs i --what does this do?
ddOf kns@(KnS mgr allprops lawbdd obs) (Kw i form) =
  disSet mgr [ forallSet mgr otherps (imp mgr lawbdd (ddOf kns f)) | f <- [form, Neg form] ] where
    otherps = map (\(P n) -> n) $ allprops \\ apply obs i
ddOf kns@(KnS mgr allprops lawbdd obs) (Ck ags form) = gfp mgr lambda where
  lambda z = conSet mgr $ ddOf kns form : [ forallSet mgr (otherps i) (imp mgr lawbdd z) | i <- ags ]
  otherps i = map (\(P n) -> n) $ allprops \\ apply obs i
ddOf kns@(KnS mgr _ _ _) (Ckw ags form) = dis mgr (ddOf kns (Ck ags form)) (ddOf kns (Ck ags (Neg form)))
ddOf kns@(KnS mgr allprops lawbdd obs) (Dk ags form) =
  forallSet mgr otherps (imp mgr lawbdd (ddOf kns form)) where
    otherps = map (\(P n) -> n) $ allprops \\ uoi
    uoi = nub (concat [obs ! i | i <- ags])
ddOf kns@(KnS mgr allprops lawbdd obs) (Dkw ags form) =
  disSet mgr [ forallSet mgr otherps (imp mgr lawbdd (ddOf kns f)) | f <- [form, Neg form] ] where
    otherps = map (\(P n) -> n) $ allprops \\ uoi
    uoi = nub (concat [obs ! i | i <- ags])
ddOf kns@(KnS mgr props _ _) (Announce ags form1 form2) =
  imp mgr (ddOf kns form1) (restrict mgr bdd2 (k,True)) where
    bdd2  = ddOf (announce kns ags form1) form2
    (P k) = freshp props
ddOf kns@(KnS mgr props _ _) (AnnounceW ags form1 form2) =
  ifthenelse mgr (ddOf kns form1) bdd2a bdd2b where
    bdd2a = restrict mgr (ddOf (announce kns ags form1) form2) (k,True)
    bdd2b = restrict mgr (ddOf (announce kns ags form1) form2) (k,False)
    (P k) = freshp props
ddOf kns@(KnS mgr _ _ _) (PubAnnounce form1 form2) = imp mgr (ddOf kns form1) newform2 where
    newform2 = ddOf (pubAnnounce kns form1) form2
ddOf kns@(KnS mgr _ _ _) (PubAnnounceW form1 form2) =
  ifthenelse mgr (ddOf kns form1) newform2a newform2b where
    newform2a = ddOf (pubAnnounce kns form1) form2
    newform2b = ddOf (pubAnnounce kns (Neg form1)) form2
ddOf _ (Dia _ _) = error "Dynamic operators are not implemented in S5_CUDD."

pubAnnounce :: (DdCtx a b c) => KnowStruct a b c -> Form -> KnowStruct a b c
pubAnnounce kns@(KnS mgr props lawbdd obs) psi = KnS mgr props newlawbdd obs where
  newlawbdd = con mgr lawbdd (ddOf kns psi)

announce :: (DdCtx a b c) => KnowStruct a b c -> [Agent] -> Form -> KnowStruct a b c
announce kns@(KnS mgr props lawbdd obs) ags psi = KnS mgr newprops newlawbdd newobs where
  proppsi@(P k) = freshp props
  newprops  = proppsi:props
  newlawbdd = con mgr lawbdd (equ mgr (var mgr k) (ddOf kns psi))
  newobs    = [(i, apply obs i ++ [proppsi | i `elem` ags]) | i <- map fst obs]

evalAssDD :: (DdCtx a b c) => Cudd.Cudd.DdManager -> Dd a b c -> (Int -> Bool) -> Bool
evalAssDD mgr (dd :: Dd a b c) f = bool where
  bool | b==(top mgr :: Dd a b c) = True
       | b==(bot mgr :: Dd a b c) = False
       | otherwise = error "evalAssBDD failed: DD leftover:\n"
  b    = restrictSet mgr dd list
  list = [ (n, f n) | n <- getSupport mgr dd ]

ddEval :: (DdCtx a b c) => Cudd.Cudd.DdManager -> [Prp] -> Dd a b c -> Bool
ddEval mgr truths querybdd = evalAssDD mgr querybdd (\n -> P n `elem` truths)

--Somewhat fast statesOf, faster woud be to use primitive construction of all Satifying Assignments (e.i. explicitly looping through the dd instead of using restrict).
statesOf :: DdCtx a b c => KnowStruct a b c -> [KnState]
statesOf (KnS mgr allprops lawdd _) = loop allprops lawdd where
  loop [] _ = []
  loop v d = r v d True ++ r v d False
  r ((P n):ns) d b
    | restrict mgr d (n,b) == bot mgr = []
    | restrict mgr d (n,b) == top mgr = if b then map ([P n] ++) (powerset ns) else powerset ns
    | otherwise =
      if b then map ([P n] ++) $ loop ns (restrict mgr d (n,b)) else loop ns (restrict mgr d (n,b))
  r [] _ _ = error "impossible?"

boolDDoutof :: (DdCtx a b c) => Cudd.Cudd.DdManager -> [Prp] -> [Prp] -> Dd a b c
boolDDoutof mgr ps qs = conSet mgr $
  [ var mgr n | (P n) <- ps ] ++
  [ neg mgr $ var mgr n | (P n) <- qs \\ ps ]

ddToForm :: (DdCtx a b c) => Cudd.Cudd.DdManager -> [Prp] -> Dd a b c -> Form
ddToForm mgr v dd = unravel mgr dd (map P $ getDependentVars mgr (map fromEnum v) dd)

unravel :: (DdCtx a b c) => Cudd.Cudd.DdManager -> Dd a b c -> [Prp] -> Form
unravel _ _ [] = Top
unravel mgr dd [P n] = Disj [ result True, result False] where
  result True
    | restrict mgr dd (n, True) == bot mgr = Bot
    | otherwise = PrpF (P n)
  result False
    | restrict mgr dd (n, False) == bot mgr = Bot
    | otherwise  = Neg $ PrpF (P n)
unravel mgr dd (P n:ns) = Disj [ result True, result False] where
  result True
    | restrict mgr dd (n, True) == top mgr = PrpF (P n)
    | restrict mgr dd (n, True) == bot mgr = Bot
    | otherwise = Conj [PrpF (P n), unravel mgr (restrict mgr dd (n, True)) ns]
  result False
    | restrict mgr dd (n, False) == top mgr = Neg $ PrpF (P n)
    | restrict mgr dd (n, False) == bot mgr = Bot
    | otherwise = Conj [Neg $ PrpF (P n), unravel mgr (restrict mgr dd (n, False)) ns]

-- * Truth and Validity

whereViaDd :: DdCtx a b c => KnowStruct a b c -> Form -> [KnState]
whereViaDd kns f = statesOf (kns `update` f)

evalViaDd :: (DdCtx a b c) => KnowScene a b c -> Form -> Bool
evalViaDd ((kns@(KnS mgr allprops _ _),s) :: KnowScene a b c) f = bool where
  bool | b== (top mgr :: Dd a b c) = True
       | b== (bot mgr :: Dd a b c)  = False
       | otherwise = error ("evalViaDd failed: DD leftover:\n" ++ show b)
  b    = restrictSet mgr (ddOf kns f) list
  list = [ (n, P n `elem` s) | (P n) <- allprops ]

validViaDd :: (DdCtx a b c) => KnowStruct a b c -> Form -> Bool
validViaDd kns@(KnS mgr _ lawdd _) f = top mgr == imp mgr lawdd (ddOf kns f)

instance (DdCtx a b c) => Semantics (KnowScene a b c) where
  isTrue = evalViaDd

instance  (DdCtx a b c) => Semantics (KnowStruct a b c) where
  isTrue = validViaDd

instance (DdCtx a b c) => Update (KnowStruct a b c) Form where
  checks = [ ] -- unpointed structures can be updated with anything
  unsafeUpdate kns@(KnS mgr props lawdd obs) psi =
    KnS mgr props (con mgr lawdd (ddOf kns psi)) obs

-- * Visualisation functions

texDdWith :: DdCtx a b c => Cudd.Cudd.DdManager -> Dd a b c -> [Prp] -> String
texDdWith mgr d vocab = unsafePerformIO $ do
  (i,o,_,_) <- runInteractiveCommand "dot2tex --figpreamble=\"\\huge\" --figonly -traw"
  xDotText <- B.pack <$> returnDot mgr d
  -- currently uses P1 .. Pn for names of variables 1 .. n, can be changed when the parser accepts non number propositions
  let myShow = formatDotCUDD vocab
  let xDotGraph = parseDotGraphLiberally xDotText :: DotGen.DotGraph String
  let renamedXDotGraph = renameMyGraph xDotGraph myShow
  hPutStr i (B.unpack (renderDot $ toDot renamedXDotGraph) ++ "\n")
  handle <- openFile "xDotGraph.txt" ReadWriteMode
  hPrint handle (show xDotGraph ++ "\n\n" ++ show renamedXDotGraph)
  hClose i
  result <- hGetContents o
  return $ dropWhileEnd isSpace $ dropWhile isSpace result

texDdFun :: DdCtx a b c => Cudd.Cudd.DdManager -> Dd a b c -> (Int-> String) -> String
texDdFun mgr d myShowF = unsafePerformIO $ do
  (i,o,_,_) <- runInteractiveCommand "dot2tex --figpreamble=\"\\huge\" --figonly -traw"
  xDotText <- B.pack <$> returnDot mgr d
  let xDotGraph = (parseDotGraphLiberally xDotText :: DotGen.DotGraph String)
  let myShow = zip (map (\x -> " " ++ show x ++ " ") $ getSupport mgr d) (map myShowF $ getSupport mgr d)
  let renamedXDotGraph = renameMyGraph xDotGraph myShow
  hPutStr i (B.unpack (renderDot $ toDot renamedXDotGraph) ++ "\n")
  hClose i
  result <- hGetContents o
  return $ dropWhileEnd isSpace $ dropWhile isSpace result

texDd :: DdCtx a b c => Cudd.Cudd.DdManager -> Dd a b c -> String
texDd mgr d = unsafePerformIO $ withSystemTempDirectory "smcdel" $ \tmpdir -> do
  writeToDot mgr d (tmpdir ++ "/texDd.dot")
  dot2tex $ dot2texDefaultArgs ++ " --figonly " ++ tmpdir ++ "/texDd.dot > " ++ tmpdir ++ "/texDd.tex;"
  result <- readFile (tmpdir ++ "/texDd.tex")
  return $ dropWhileEnd isSpace $ dropWhile isSpace result

data OwnedDd a b c = Owned Manager (Dd a b c)

instance DdCtx a b c => TexAble (OwnedDd a b c) where
  tex (Owned mgr dd) = texDd mgr dd

renameMyGraph :: DotGen.DotGraph String -> [(String, String)] -> DotGen.DotGraph String
renameMyGraph dg myShow =
  dg { DotGen.graphStatements = fmap changeGraphStatement (DotGen.graphStatements dg) } where
    changeGraphStatement gs = case gs of
      DotGen.SG sg -> DotGen.SG (sg {DotGen.subGraphStmts = fmap renameNodeNames (DotGen.subGraphStmts sg)}) where
        renameNodeNames sgStmt = case sgStmt of
          DotGen.DN dn -> DotGen.DN (renameNode dn myShow)
          DotGen.DE de -> DotGen.DE (renameEdge de myShow)
          x -> x
      DotGen.DE de -> DotGen.DE (renameEdge de myShow)
      x -> x

renameNode :: DotGen.DotNode String -> [(String, String)] -> DotGen.DotNode String
renameNode dn myShow = case lookup (DotGen.nodeID dn) myShow of
  (Just v) -> dn { nodeID = v } --nodeID is in myShow, thus replace the Int with the proposition
  Nothing -> dn --otherwise do nothing

renameEdge :: DotGen.DotEdge String -> [(String, String)] -> DotGen.DotEdge String
-- replace also the node name occurences in the edge statements
renameEdge de myShow = changeFromNode (changeToNode de) where
  changeToNode edge = case lookup (DotGen.toNode edge) myShow of
    (Just v) -> edge {toNode = v }
    Nothing -> edge
  changeFromNode edge = case lookup (DotGen.fromNode edge) myShow of
    (Just v) -> edge {fromNode = v }
    Nothing -> edge

formatDotCUDD :: [Prp] -> [(String, String)]
formatDotCUDD = map propToString where
  propToString p = (" " ++ show (fromEnum p - 1) ++ " ", "p" ++ show (fromEnum p))

instance DdCtx a b c => TexAble (KnowStruct a b c) where
  tex (KnS mgr props lawbdd obs) = concat
    [ " \\left( \n"
    , tex props ++ ", "
    , " \\begin{array}{l} \\scalebox{0.4}{"
    , texDd mgr lawbdd
    , "} \\end{array}\n "
    , ", \\begin{array}{l}\n"
    , intercalate " \\\\\n " (map (\(_,os) -> tex os) obs)
    , "\\end{array}\n"
    , " \\right)" ]

instance DdCtx a b c => TexAble (KnowScene a b c) where
  tex (kns, state) = tex kns ++ " , " ++ tex state

giveDdTex ::  DdCtx a b c => Cudd.Cudd.DdManager -> Dd a b c -> String
giveDdTex mgr d = concat
  [
    " \\begin{array}{l} \\scalebox{0.4}{"
    , texDd mgr d
    , "} \\end{array}\n "]