packages feed

BPS-0.1.0.0: src/test/haskell/JTMSTests.hs

{-|
Description : Testing Justification-based truth maintenance systems (JTMSes)
Copyright   : (c) John Maraist, 2022
              Kenneth D. Forbus, Johan de Kleer and Xerox Corporation, 1986-1993
License     : AllRightsReserved
Maintainer  : haskell-tms@maraist.org
Stability   : experimental
Portability : POSIX

Translation of Forbus and de Kleer's justification-based truth
maintenance systems (JTMSes) from Common Lisp to Haskell.

See the @LICENSE.txt@ and @README-forbus-dekleer.txt@ files
distributed with this work for a paragraph stating scope of permission
and disclaimer of warranty, and for additional information regarding
copyright ownership.  The above copyright notice and that paragraph
must be included in any separate copy of this file.

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, for NON-COMMERCIAL use.  See the License for the specific
language governing permissions and limitations under the License.

-}

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}

module JTMSTests (testEx1, testEx3) where

import Data.Symbol
import Data.Void
import Data.TMS.JTMS
import Control.Monad
import Control.Monad.Extra
import Control.Monad.IO.Class
import Control.Monad.ST.Trans
import Control.Monad.Trans.Class
import Test.TLT

-- Prints result of the string length calculation.
report :: Either JtmsErr () -> IO ()
report (Right _) = putStrLn ("Tests passed")
report (Left e) = putStrLn ("Caught exception: " ++ (show e))

instance MonadTLT m n => MonadTLT (JTMST s m) n where
  liftTLT = lift . liftTLT

type JTMS1ty s m = JTMS Symbol String Void s m
type Node1ty s m = Node Symbol String Void s m
ex1 :: Monad m => JTMST s m (JTMS1ty s m,
                             Node1ty s m, Node1ty s m, Node1ty s m,
                             Node1ty s m, Node1ty s m, Node1ty s m,
                             Node1ty s m)
ex1 = do
  j <- createJTMS "Ex1"
  nodeStringByDatum j
  datumStringByShow j
  informantStringByShow j
  justStringByIndexInformant j
  setNodeString j (show . nodeDatum)
  na <- createNode j (intern "a") True False
  nb <- createNode j (intern "b") True False
  nc <- createNode j (intern "c") True False
  nd <- createNode j (intern "d") True False
  ne <- createNode j (intern "e") True False
  nf <- createNode j (intern "f") True False
  ng <- createNode j (intern "g") True False
  justifyNode "j1" nf [na, nb]
  justifyNode "j2" ne [nb, nc]
  justifyNode "j3" ng [na, ne]
  justifyNode "j4" ng [nd, ne]
  return (j, na, nb, nc, nd, ne, nf, ng)

testEx1 :: MonadIO m => JTMST s (TLT m) ()
testEx1 = do
  (jtms, na, nb, nc, nd, ne, nf, ng) <- ex1
  datumStringByShow jtms
  inGroup "Fresh JTMS" $ do
    assertBeliefs jtms [] [na, nb, nc, nd, ne, nf, ng]
    assertNoAssumptionsOfNodes jtms [na, nb, nc, nd, ne, nf, ng]
    assertNodesUnsupported jtms [na, nb, nc, nd, ne, nf, ng]

  -- debugJTMS "fresh" jtms
  enableAssumption na
  -- debugJTMS "after (enableAssumption na)" jtms
  inGroup "Enabled a as assumption" $ do
    assertBeliefs jtms [na] [nb, nc, nd, ne, nf, ng]
    assertAssumptionsOfNode jtms na [na]
    assertNoAssumptionsOfNodes jtms [nb, nc, nd, ne, nf, ng]
    assertNodeSupportEnabledAssumption jtms na
    assertNodesUnsupported jtms [nb, nc, nd, ne, nf, ng]

  enableAssumption nb
  -- debugJTMS "after (enableAssumption nb)" jtms
  inGroup "Enabled b as assumption" $ do
    assertBeliefs jtms [na, nb, nf] [nc, nd, ne, ng]
    assertAssumptionsOfNode jtms na [na]
    assertAssumptionsOfNode jtms nb [nb]
    assertAssumptionsOfNode jtms nf [na, nb]
    assertNoAssumptionsOfNodes jtms [nc, nd, ne, ng]
    assertNodesSupportEnabledAssumption jtms [na, nb]
    assertNodeSupportInformant jtms nf "j1"
    assertNodesUnsupported jtms [nc, nd, ne, ng]

  enableAssumption nc
  -- debugJTMS "after (enableAssumption nc)" jtms
  inGroup "Enabled c as assumption" $ do
    assertBeliefs jtms [na, nb, nc, ne, nf, ng] [nd]
    assertAssumptionsOfNode jtms na [na]
    assertAssumptionsOfNode jtms nb [nb]
    assertAssumptionsOfNode jtms nc [nc]
    assertAssumptionsOfNode jtms ne [nb, nc]
    assertAssumptionsOfNode jtms nf [na, nb]
    assertAssumptionsOfNode jtms ng [na, nb, nc]
    assertNoAssumptionsOfNodes jtms [nd]
    assertNodesSupportEnabledAssumption jtms [na, nb, nc]
    assertNodeSupportInformant jtms ne "j2"
    assertNodeSupportInformant jtms nf "j1"
    assertNodeSupportInformant jtms ng "j3"
    assertNodesUnsupported jtms [nd]

  enableAssumption nd
  -- debugJTMS "after (enableAssumption nd)" jtms
  inGroup "Enabled d as assumption" $ do
    assertBeliefs jtms [na, nb, nc, nd, ne, nf, ng] []
    assertAssumptionsOfNode jtms na [na]
    assertAssumptionsOfNode jtms nb [nb]
    assertAssumptionsOfNode jtms nc [nc]
    assertAssumptionsOfNode jtms nd [nd]
    assertAssumptionsOfNode jtms ne [nb, nc]
    assertAssumptionsOfNode jtms nf [na, nb]
    assertAssumptionsOfNode jtms ng [na, nb, nc]
    assertNodesSupportEnabledAssumption jtms [na, nb, nc, nd]
    assertNodeSupportInformant jtms ne "j2"
    assertNodeSupportInformant jtms nf "j1"
    assertNodeSupportInformant jtms ng "j3"

  retractAssumption na
  -- debugJTMS "after (retractAssumption na)" jtms
  inGroup "Retracted a as assumption" $ do
    assertBeliefs jtms [nb, nc, nd, ne, ng] [na, nf]
    assertAssumptionsOfNode jtms nb [nb]
    assertAssumptionsOfNode jtms nc [nc]
    assertAssumptionsOfNode jtms nd [nd]
    assertAssumptionsOfNode jtms ne [nb, nc]
    assertAssumptionsOfNode jtms ng [nb, nc, nd]
    assertNoAssumptionsOfNodes jtms [na, nf]
    assertNodesUnsupported jtms [na, nf]
    assertNodesSupportEnabledAssumption jtms [nb, nc, nd]
    assertNodeSupportInformant jtms ne "j2"
    assertNodeSupportInformant jtms ng "j4"

type JTMS3ty s m = JTMS Symbol String () s m
type Node3ty s m = Node Symbol String () s m
ex3 :: Monad m => JTMST s m (JTMS3ty s m,
                             Node3ty s m, Node3ty s m, Node3ty s m,
                             Node3ty s m, Node3ty s m, Node3ty s m)
ex3 = do
  j <- createJTMS "Ex1"

  na <- createNode j (intern "A") True  False
  nc <- createNode j (intern "C") True  False
  ne <- createNode j (intern "E") True  False
  ng <- createNode j (intern "g") False False
  nh <- createNode j (intern "h") False False
  contra <- createNode j (intern "CONTRADICTION") False True

  justifyNode "R1" nh [nc, ne]
  justifyNode "R2" ng [na, nc]
  justifyNode "R3" contra [ng]

  return (j, na, nc, ne, ng, nh, contra)

testEx3 :: MonadIO m => JTMST s (TLT (STT s0 m)) ()
testEx3 = do
  contraHandlerFlag <- lift $ lift $ newSTRef False

  (jtms, na, nc, ne, ng, nh, contra) <- ex3
  setContradictionHandler jtms $ \_ ->
    lift $ lift $ writeSTRef contraHandlerFlag True
  datumStringByShow jtms
  inGroup "Fresh JTMS" $ do
    assertBeliefs jtms [] [na, nc, ne, ng, nh, contra]
    assertNoAssumptionsOfNodes jtms [na, nc, ne, ng, nh, contra]
    assertNodesUnsupported jtms [na, nc, ne, ng, nh, contra]
    "No call to contradiction handler" ~::
      notM (lift $ lift $ readSTRef contraHandlerFlag)

  lift $ lift $ writeSTRef contraHandlerFlag False
  enableAssumption na
  inGroup "Enabled a" $ do
    assertBeliefs jtms [na] [nc, ne, ng, nh, contra]
    assertAssumptionsOfNode jtms na [na]
    assertNoAssumptionsOfNodes jtms [nc, ne, ng, nh, contra]
    "No call to contradiction handler" ~::
      notM (lift $ lift $ readSTRef contraHandlerFlag)

  lift $ lift $ writeSTRef contraHandlerFlag False
  enableAssumption nc
  inGroup "Enabled c" $ do
    assertBeliefs jtms [na, nc, ng, contra] [ne, nh]
    "Did call contradiction handler" ~::
      (lift $ lift $ readSTRef contraHandlerFlag)

  lift $ lift $ writeSTRef contraHandlerFlag False
  enableAssumption ne
  inGroup "Enabled e" $ do
    assertBeliefs jtms [na, nc, ne, ng, nh, contra] []
    "Did call contradiction handler" ~::
      (lift $ lift $ readSTRef contraHandlerFlag)

{------------------------- Local assertions. -------------------------}

assertBeliefs ::
  Monad m => (JTMS d i r s (TLT m)) -> [Node d i r s (TLT m)] -> [Node d i r s (TLT m)] ->
               JTMST s (TLT m) ()
assertBeliefs jtms ins outs = inGroup "Node belief" $ do
  inChecks
  outChecks
  where inChecks = forM_ ins inCheck

        inCheck node = do
          name <- nodeString node
          ("Node " ++ name ++ " is in") ~:: isInNode node

        outChecks = forM_ outs $ outCheck

        outCheck node = do
          name <- nodeString node
          ("Node " ++ name ++ " is out") ~:: isOutNode node

assertAssumptionsOfNode ::
  Monad m =>
    (JTMS d i r s (TLT m)) -> Node d i r s (TLT m) -> [Node d i r s (TLT m)] ->
      JTMST s (TLT m) ()
assertAssumptionsOfNode jtms node assumptions = do
  actuals <- assumptionsOfNode node
  name <- nodeString node
  inGroup ("Checking assumptionsOfNode " ++ name) $ do
    ("Same number of expected and actual assumptions")
      ~: length assumptions @==- length actuals
    forM_ assumptions $ \ expected -> do
      expName <- nodeString expected
      ("Contains expected node " ++ expName) ~::- (expected `elem` actuals)

assertNoAssumptionsOfNodes ::
  Monad m =>
    (JTMS d i r s (TLT m)) -> [Node d i r s (TLT m)] -> JTMST s (TLT m) ()
assertNoAssumptionsOfNodes jtms nodes =
  inGroup ("No assumptionsOfNode") $
    forM_ nodes $ \ node -> do
      name <- nodeString node
      ("Node " ++ name ++ " has no assumptions") ~:
        (empty $ assumptionsOfNode node)

assertNodesUnsupported ::
  Monad m =>
    (JTMS d i r s (TLT m)) -> [Node d i r s (TLT m)] -> JTMST s (TLT m) ()
assertNodesUnsupported jtms nodes =
  inGroup ("Unsupported nodes") $
    forM_ nodes $ \ node -> do
      name <- nodeString node
      ("Node " ++ name ++ " has no support") ~::
        do support <- getNodeSupport node
           case support of
             Nothing -> return True
             Just _ -> return False

assertNodeSupportRule ::
  Monad m =>
    JTMS d i r s (TLT m) -> Node d i r s (TLT m) -> JustRule d i r s (TLT m) ->
      JTMST s (TLT m) ()
assertNodeSupportRule jtms node just = do
  infString <- getJtmsInformantString jtms
  nodeName <- nodeString node
  ("Node " ++ nodeName ++ " supported by rule "
    ++ (infString $ justInformant just)) ~:: do
    support <- getNodeSupport node
    case support of
      Just (ByRule j) | j == just -> return True
      _ -> return False

assertNodeSupportInformant ::
  (Monad m, Eq i) =>
    JTMS d i r s (TLT m) -> Node d i r s (TLT m) -> i -> JTMST s (TLT m) ()
assertNodeSupportInformant jtms node inf = do
  infString <- getJtmsInformantString jtms
  nodeName <- nodeString node
  ("Node " ++ nodeName ++ " supported by informant " ++ (infString inf)) ~:: do
    support <- getNodeSupport node
    case support of
      Just (ByRule j) | (justInformant j) == inf -> return True
      _ -> return False

assertNodeSupportEnabledAssumption ::
  Monad m => JTMS d i r s (TLT m) -> Node d i r s (TLT m) -> JTMST s (TLT m) ()
assertNodeSupportEnabledAssumption jtms node = do
  infString <- getJtmsInformantString jtms
  nodeName <- nodeString node
  ("Node " ++ nodeName ++ " is enabled assumption ") ~:: do
    support <- getNodeSupport node
    case support of
      Just EnabledAssumption -> return True
      _ -> return False

assertNodesSupportEnabledAssumption ::
  Monad m =>
    JTMS d i r s (TLT m) -> [Node d i r s (TLT m)] -> JTMST s (TLT m) ()
assertNodesSupportEnabledAssumption jtms nodes =
  inGroup ("Nodes are enabled assumptions") $
    forM_ nodes $ \ node -> assertNodeSupportEnabledAssumption jtms node

assertNodeSupportUserStipulation ::
  Monad m => JTMS d i r s (TLT m) -> Node d i r s (TLT m) -> JTMST s (TLT m) ()
assertNodeSupportUserStipulation jtms node = do
  infString <- getJtmsInformantString jtms
  nodeName <- nodeString node
  ("Node " ++ nodeName ++ " is enabled assumption ") ~:: do
    support <- getNodeSupport node
    case support of
      Just UserStipulation -> return True
      _ -> return False