packages feed

CarneadesDSL-2.0.0.0: src/Language/Carneades/CarneadesDSL.hs

-- | An implementation and DSL for the Carneades argumentation model.
--
-- See \"Haskell Gets Argumentative\" in the Proceedings of Symposium on
-- Trends in Functional Programming (TFP 2012) by Bas van Gijzel and
-- Henrik Nilsson.
--
-- For the literate Haskell source with full definitions, see
-- @doc\/CarneadesDSL.lhs@.
module Language.Carneades.CarneadesDSL where

import Language.Carneades.Cyclic

import Prelude hiding (negate)

import Data.Graph.Inductive
import Data.Map (Map)
import Data.List (nub)
import qualified Data.Set as Set
import qualified Data.Map as Map
import Data.Maybe (fromJust)

-- $arguments
-- In Carneades all logical formulae are literals in propositional logic;
-- i.e., all propositions are either positive or negative atoms. Taking
-- atoms to be strings suffices, and propositional literals can then be
-- formed by pairing this atom with a 'Bool' to denote whether it is
-- negated or not.

-- | A propositional literal is a pair of a polarity and a name.
type PropLiteral = (Bool, String)

-- | Negate a propositional literal.
negate :: PropLiteral -> PropLiteral
negate (b, x) = (not b, x)

-- | An argument is a tuple of premises, exceptions, and a conclusion.
-- Arguments are considered equal if their premises, exceptions and
-- conclusion are equal (comparing lists as sets).
newtype Argument = Arg ([PropLiteral], [PropLiteral], PropLiteral)
 deriving Ord

-- Manual Eq instance for set equality on premises and exceptions
instance Eq Argument where
 (Arg (prems, excs, c)) == (Arg (prems', excs', c'))
    =  Set.fromList prems == Set.fromList prems'  &&
       Set.fromList excs == Set.fromList excs'    &&
       c == c'

-- | Show a propositional literal as a string, prefixing negative
-- literals with @-@.
showProp :: PropLiteral -> String
showProp (True, c)   = c
showProp (_    , c)  = '-' : c

instance Show Argument where
 show (Arg (prems, excs, (True, c)))   = show (map showProp prems) ++ ' ' : '~' : show (map showProp excs) ++ "=>" ++ show c
 show (Arg (prems, excs, (_    , c)))  = show (map showProp prems) ++ ' ' : '~' : show (map showProp excs) ++ "=>" ++ show ('-' : c)

-- $caes
-- A Carneades Argument Evaluation Structure ('CAES') is a triple of an
-- acyclic set of arguments, an audience, and a mapping from propositions
-- to proof standards.

-- | An acyclic set of arguments represented as an FGL graph.
type ArgSet = Gr (PropLiteral, [Argument]) ()

-- | A Carneades Argument Evaluation Structure.
newtype CAES = CAES (ArgSet, Audience, PropStandard)

-- | An audience is a pair of assumptions and a weight function.
type Audience = (Assumptions, ArgWeight)

-- | A list of assumed propositional literals.
type Assumptions = [PropLiteral]

-- | A function mapping arguments to weights in the range @[0,1]@.
type ArgWeight = Argument -> Weight

-- | A weight is a 'Double'.
type Weight = Double

-- | A mapping from propositions to proof standard names.
type PropStandard = PropLiteral -> PSName

-- | Names of the five predefined proof standards.
data PSName = Scintilla
  | Preponderance | ClearAndConvincing
  | BeyondReasonableDoubt | DialecticalValidity
 deriving (Show, Eq)

-- | A proof standard decides, given a proposition and a CAES, whether the
-- proposition is acceptable.
type ProofStandard = PropLiteral -> CAES -> Bool

-- | A named proof standard, with equality based on the name.
newtype ProofStandardNamed = P (String, PropLiteral -> CAES -> Bool)
instance Eq ProofStandardNamed where
 P (l1, _) == P (l2, _) = l1 == l2

-- $evaluation
-- Two concepts central to evaluation: /applicability/ of arguments and
-- /acceptability/ of propositions.

-- | An argument is applicable in a CAES when all premises are assumed or
-- acceptable, and all exceptions are neither assumed nor acceptable.
applicable :: Argument -> CAES -> Bool
applicable  (Arg (prems, excns, _))
            caes@(CAES (_, (assumptions, _), _))
  = and  $  [  p `elem` assumptions ||
             (  negate p `notElem` assumptions &&
                p `acceptable` caes)  |  p <- prems  ]
            ++
            [  (e `notElem` assumptions) &&
             (  negate e `elem` assumptions ||
                not (e `acceptable` caes))  |  e <- excns  ]

-- | A proposition is acceptable in a CAES when it satisfies its
-- associated proof standard.
acceptable :: PropLiteral -> CAES -> Bool
acceptable c caes@(CAES (_, _, standard))
  = c `s` caes
  where s = psMap $ standard c

-- $proofstandards
-- Carneades predefines five proof standards.

-- | Scintilla of evidence: at least one applicable argument pro.
scintilla :: ProofStandard
scintilla p caes@(CAES (g, _, _))
 = any (`applicable` caes) (getArgs p g)

-- | Maximum weight among applicable arguments.
maxWeightApplicable :: [Argument] -> CAES -> Weight
maxWeightApplicable as caes@(CAES (_, (_, argWeight), _))
 = foldl max 0 [argWeight a | a <- as, a `applicable` caes]

-- | Maximum weight of applicable arguments pro a proposition.
maxWeightPro :: PropLiteral -> CAES -> Weight
maxWeightPro p caes@(CAES (g, _, _))
 = maxWeightApplicable (getArgs p g) caes

-- | Maximum weight of applicable arguments con a proposition.
maxWeightCon :: PropLiteral -> CAES -> Weight
maxWeightCon p caes@(CAES (g, _, _))
 = maxWeightApplicable (getArgs (negate p) g) caes

-- | Preponderance: max weight pro exceeds max weight con.
preponderance :: ProofStandard
preponderance p caes = maxWeightPro p caes > maxWeightCon p caes

-- | Clear and convincing evidence: preponderance plus minimum
-- thresholds on weight and difference.
clear_and_convincing :: ProofStandard
clear_and_convincing p caes
 =  (mwp > alpha) && (mwp - mwc > beta)
  where
    mwp  =  maxWeightPro p caes
    mwc  =  maxWeightCon p caes

-- | Beyond reasonable doubt: clear and convincing plus max weight
-- con must be below gamma.
beyond_reasonable_doubt :: ProofStandard
beyond_reasonable_doubt p caes
 = clear_and_convincing p caes && (maxWeightCon p caes < gamma)

-- | Dialectical validity: at least one applicable argument pro and
-- no applicable arguments con.
dialectical_validity :: ProofStandard
dialectical_validity p caes
  = scintilla p caes && not (scintilla (negate p) caes)

-- | Map proof standard names to their implementation.
psMap :: PSName -> ProofStandard
psMap Scintilla = scintilla
psMap Preponderance = preponderance
psMap ClearAndConvincing = clear_and_convincing
psMap BeyondReasonableDoubt = beyond_reasonable_doubt
psMap DialecticalValidity = dialectical_validity

-- $convenience
-- Functions to facilitate construction of propositions, arguments,
-- argument sets and assumptions.

-- | Retrieve all arguments from an argument set.
getAllArgs :: ArgSet -> [Argument]
getAllArgs g = nub $ concatMap (snd . snd) (labNodes g)

-- | Retrieve all propositions from an argument set.
getProps :: ArgSet -> [PropLiteral]
getProps g = map (fst . snd) (labNodes g)

-- | Retrieve all applicable arguments from a CAES.
applicableArgs :: CAES -> [Argument]
applicableArgs c@(CAES (argSet, _, _)) = filter (`applicable` c) (getAllArgs argSet)

-- | Retrieve all non-applicable arguments from a CAES.
nonApplicableArgs :: CAES -> [Argument]
nonApplicableArgs c@(CAES (argSet, _, _)) = filter (not . (`applicable` c)) (getAllArgs argSet)

-- | Retrieve all acceptable propositions from a CAES.
acceptableProps :: CAES  -> [PropLiteral]
acceptableProps c@(CAES (argSet, _, _)) = filter (`acceptable` c) (getProps argSet)

-- | Retrieve all non-acceptable propositions from a CAES.
nonAcceptableProps :: CAES  -> [PropLiteral]
nonAcceptableProps c@(CAES (argSet, _, _)) = filter (not . (`acceptable` c)) (getProps argSet)

contextP :: PropLiteral -> AGraph -> [Context (PropLiteral, [Argument]) ()]
contextP p = gsel (\ (_, _, a, _) -> fst a == p)

-- | Retrieve the arguments for a specific proposition from an argument set.
getArgs :: PropLiteral -> AGraph -> [Argument]
getArgs p g
  =  case contextP p g of
       []                          -> []
       ((_, _, (_, args), _) : _)  -> args

-- $graph
-- Graph construction internals.

-- | Synonym for 'ArgSet'.
type AGraph = ArgSet

-- | A labelled node in the argument graph.
type PropNode = LNode (PropLiteral, [Argument])

-- | An argument graph paired with a map from propositions to node numbers.
type AssociatedGraph = (AGraph, Map PropLiteral Node)

-- | Construct an acyclic argument set from a list of arguments.
mkArgSet :: [Argument] -> ArgSet
mkArgSet = mkArgGraph

mkArgGraph :: [Argument] -> AGraph
mkArgGraph = fst . foldr addArgument (empty, Map.empty)

addArgument :: Argument -> AssociatedGraph -> AssociatedGraph
addArgument arg@(Arg (prem, exc, c)) gr  =
  let  deps             =  prem ++ exc
       (gr',  nodeNr)   =  addArgument' arg gr
       (gr'', nodeNr')  =  addNode (negate c) gr'
  in addEdges nodeNr' deps $ addEdges nodeNr deps gr''

addToContext :: Argument -> (Context (PropLiteral, [Argument]) (), AGraph) -> AGraph
addToContext arg ((adjb, n, (p, args), adja), g') = (adjb, n, (p, arg:args), adja) & g'

unsafeMatch :: Graph gr => Node -> gr a b -> (Context a b, gr a b)
unsafeMatch n g = mapFst fromJust $ match n g

addArgument' :: Argument -> AssociatedGraph -> (AssociatedGraph, Node)
addArgument' arg@(Arg (_, _, c)) (g, m)
 = case Map.lookup c m of
       Nothing  ->  ((insNode (nodeNr, (c, [arg])) g,
                       Map.insert c nodeNr m),
                         nodeNr)
       Just n   ->  ((addToContext arg (unsafeMatch n g),
                       m),
                         n)
  where nodeNr = Map.size m + 1

addNode :: PropLiteral -> AssociatedGraph -> (AssociatedGraph, Node)
addNode p gr@(g,m)
 =  case Map.lookup p m of
       Nothing -> ((insNode (nodeNr, (p, [])) g, Map.insert p nodeNr m), nodeNr)
       Just n  -> (gr, n)
    where nodeNr = Map.size m + 1

addEdges :: Node -> [PropLiteral] -> AssociatedGraph -> AssociatedGraph
addEdges n ps (g, m) = addEdges' n (map (fromJust . flip Map.lookup m') ps)  (g', m')
 where  nodeNr    = Map.size m + 1
        newProps  = filter ( (== Nothing) . flip Map.lookup m) ps
        g'        = insNodes (propsToNodes newProps nodeNr) g
        m'        = Map.union m . Map.fromList $ zip newProps [nodeNr..]

addEdges' :: Node -> [Node] -> AssociatedGraph -> AssociatedGraph
addEdges' c ps (g, m) = (insEdges edges' g, m)
 where  edges' = map (genEdge c) ps
        genEdge k l = (k, l, ())

propsToNodes :: [PropLiteral] -> Node -> [PropNode]
propsToNodes ps n = zip [n..] (map (\ p -> (p, [])) ps)

-- | Check whether an argument graph is cyclic.
checkCycle :: AGraph -> Bool
checkCycle = cyclic

-- | Construct a 'PropLiteral' from a string. A leading @-@ denotes
-- a negative literal.
mkProp :: String -> PropLiteral
mkProp ('-':s)  = mapFst not (mkProp s)
mkProp s        = (True, s)

-- | Construct a list of assumptions from a list of strings.
mkAssumptions :: [String] -> [PropLiteral]
mkAssumptions = map mkProp

-- | Construct an 'Argument' from lists of premise, exception, and
-- conclusion strings.
mkArg :: [String] -> [String] -> String -> Argument
mkArg ps es c = Arg (map mkProp ps, map mkProp es, mkProp c)

-- | Globally predefined alpha, beta and gamma thresholds for proof
-- standards.
alpha, beta, gamma :: Double
alpha  =  0.4
beta   =  0.3
gamma  =  0.2