packages feed

atp-0.1.0.0: src/ATP/Codec/TPTP.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}

{-|
Module       : ATP.Codec.TPTP
Description  : Coding and decoding of unsorted first-order logic in TPTP.
Copyright    : (c) Evgenii Kotelnikov, 2019-2021
License      : GPL-3
Maintainer   : evgeny.kotelnikov@gmail.com
Stability    : experimental
-}

module ATP.Codec.TPTP (
  encode,
  decode,
  encodeFormula,
  decodeFormula,
  encodeClause,
  decodeClause,
  encodeTheorem,
  encodeClauses,
  decodeSolution
) where

import Control.Applicative (liftA2)
import Control.Monad (foldM)
import Control.Monad.Trans (lift)
import Data.Functor (($>))
import Data.List (genericIndex, find)
import qualified Data.List.NonEmpty as NE (toList)
import Data.Map (Map, (!))
#if !MIN_VERSION_base(4, 11, 0)
import Data.Semigroup (Semigroup(..))
#endif
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.TPTP as TPTP

import ATP.Internal.Enumeration
import ATP.Error
import ATP.FOL


-- * Coding and decoding

-- | Encode a variable in TPTP.
--
-- >>> encodeVar 0
-- Var "X"
--
-- >>> encodeVar 1
-- Var "Y"
--
-- >>> encodeVar 7
-- Var "X1"
--
-- >>> encodeVar (-1)
-- Var "YY"
--
-- >>> encodeVar (-7)
-- Var "XX1"
--
-- @encodeVar@ is injective.
--
-- prop> (v == v') == (encodeVar v == encodeVar v')
--
encodeVar :: Var -> TPTP.Var
encodeVar v = TPTP.Var $ genericIndex variables (abs v)
  where
    variables :: [Text]
    variables = liftA2 prime [0..] ["X", "Y", "Z", "P", "Q", "R", "T"]

    prime :: Integer -> Text -> Text
    prime n w = letter <> index
      where
        letter = if v >= 0 then w else w <> w
        index  = if n == 0 then T.empty else T.pack (show n)

type Substitutions = EnumerationT TPTP.Var Partial

-- | Encode a function symbol in TPTP.
encodeFunction :: FunctionSymbol -> TPTP.Name TPTP.Function
encodeFunction (FunctionSymbol s) = TPTP.Defined (TPTP.Atom s)

-- | Decode a function symbol from TPTP.
decodeFunction :: TPTP.Name s -> Partial FunctionSymbol
decodeFunction = \case
  TPTP.Defined (TPTP.Atom s) -> return (FunctionSymbol s)
  TPTP.Reserved{} -> parsingError "reserved functions are not supported"

-- | Encode a predicate symbol in TPTP.
encodePredicate :: PredicateSymbol -> TPTP.Name TPTP.Predicate
encodePredicate (PredicateSymbol p) = TPTP.Defined (TPTP.Atom p)

-- | Encode a term in TPTP.
encodeTerm :: Term -> TPTP.Term
encodeTerm = \case
  Variable v    -> TPTP.Variable (encodeVar v)
  Function f ts -> TPTP.Function (encodeFunction f) (fmap encodeTerm ts)

-- | Decode a term from TPTP.
decodeTermS :: TPTP.Term -> Substitutions Term
decodeTermS = \case
  TPTP.Function f ts  -> Function <$> lift (decodeFunction f) <*> traverse decodeTermS ts
  TPTP.Variable v     -> Variable <$> enumerate v
  TPTP.Number{}       -> lift $ parsingError "numbers are not supported"
  TPTP.DistinctTerm{} -> lift $ parsingError "distinct objects are not supported"

-- | Encode a literal in TPTP.
encodeLiteral :: Literal -> TPTP.Literal
encodeLiteral = \case
  Predicate p ts  -> TPTP.Predicate (encodePredicate p) (fmap encodeTerm ts)
  Equality a b    -> TPTP.Equality (encodeTerm a) TPTP.Positive (encodeTerm b)
  Propositional b -> TPTP.Predicate (TPTP.Reserved (TPTP.Standard p)) []
    where p = if b then TPTP.Tautology else TPTP.Falsum

-- | Decode a literal from TPTP.
decodeLiteral :: TPTP.Literal -> Substitutions (Signed Literal)
decodeLiteral = \case
  TPTP.Predicate p ts -> do
    p' <- lift (decodePredicate p)
    ts' <- traverse decodeTermS ts
    return $ Signed Positive (p' ts')
  TPTP.Equality a s b -> decodeEquality s <$> decodeTermS a <*> decodeTermS b

decodePredicate :: TPTP.Name TPTP.Predicate -> Partial ([Term] -> Literal)
decodePredicate = \case
  TPTP.Defined  (TPTP.Atom p)                  -> return $ Predicate (PredicateSymbol p)
  TPTP.Reserved (TPTP.Standard TPTP.Tautology) -> return $ const (Propositional True)
  TPTP.Reserved (TPTP.Standard TPTP.Falsum)    -> return $ const (Propositional False)
  TPTP.Reserved (TPTP.Standard p) ->
    parsingError $ "unsupported standard reserved predicate " <> show p
  TPTP.Reserved TPTP.Extended{} ->
    parsingError "extended reserved predicates are not supported"

decodeEquality :: TPTP.Sign -> Term -> Term -> Signed Literal
decodeEquality s a b = Signed (decodeSign s) (Equality a b)

-- | Encode a logical connective in TPTP.
encodeConnective :: Connective -> TPTP.Connective
encodeConnective = \case
  And        -> TPTP.Conjunction
  Or         -> TPTP.Disjunction
  Implies    -> TPTP.Implication
  Equivalent -> TPTP.Equivalence
  Xor        -> TPTP.ExclusiveOr

decodeConnected :: TPTP.Connective -> Formula -> Formula -> Formula
decodeConnected = \case
  TPTP.Conjunction -> Connected And
  TPTP.Disjunction -> Connected Or
  TPTP.Implication -> Connected Implies
  TPTP.Equivalence -> Connected Equivalent
  TPTP.ExclusiveOr -> Connected Xor
  TPTP.NegatedConjunction  -> Negate .: Connected And
  TPTP.NegatedDisjunction  -> Negate .: Connected Or
  TPTP.ReversedImplication -> flip (Connected Implies)
  where
    (.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
    (.:) = (.) . (.)

-- | Encode a quantifier in TPTP.
encodeQuantifier :: Quantifier -> TPTP.Quantifier
encodeQuantifier = \case
  Forall -> TPTP.Forall
  Exists -> TPTP.Exists

-- | Decode a quantifier from TPTP.
decodeQuantifier :: TPTP.Quantifier -> Quantifier
decodeQuantifier = \case
  TPTP.Forall -> Forall
  TPTP.Exists -> Exists

-- | Encode a formula in unsorted first-order logic in TPTP.
encodeFormula :: Formula -> TPTP.UnsortedFirstOrder
encodeFormula = \case
  Atomic l         -> TPTP.Atomic (encodeLiteral l)
  Negate f         -> TPTP.Negated (encodeFormula f)
  Connected  c f g -> TPTP.Connected (encodeFormula f) (encodeConnective c) (encodeFormula g)
  Quantified q v f -> TPTP.quantified (encodeQuantifier q) [(encodeVar v, TPTP.Unsorted ())] (encodeFormula f)

-- | Decode a formula in unsorted first-order logic from TPTP.
decodeFormula :: TPTP.UnsortedFirstOrder -> Partial Formula
decodeFormula = evalEnumerationT . decodeFormulaS

decodeFormulaS :: TPTP.UnsortedFirstOrder -> Substitutions Formula
decodeFormulaS = \case
  TPTP.Atomic l          -> liftSignedLiteral <$> decodeLiteral l
  TPTP.Negated f         -> Negate <$> decodeFormulaS f
  TPTP.Connected f c g   -> decodeConnected c
                        <$> decodeFormulaS f <*> decodeFormulaS g
  TPTP.Quantified q vs f -> foldr (curry $ quantified (decodeQuantifier q))
                        <$> decodeFormulaS f <*> traverse (enumerate . fst) vs

-- | Encode a formula in unsorted first-order logic in TPTP.
encode :: LogicalExpression -> TPTP.Formula
encode = \case
  Clause  c -> TPTP.CNF (encodeClause  c)
  Formula f -> TPTP.FOF (encodeFormula f)

-- | Decode a formula in unsorted first-order logic from TPTP.
decode :: TPTP.Formula -> Partial LogicalExpression
decode = \case
  TPTP.FOF f  -> Formula <$> decodeFormula f
  TPTP.CNF c  -> Clause  <$> decodeClause  c
  TPTP.TFF0 f | Just g <- TPTP.unsortFirstOrder f -> Formula <$> decodeFormula g
  TPTP.TFF0{} -> parsingError "formulas in TFF0 are not supported"
  TPTP.TFF1{} -> parsingError "formulas in TFF1 are not supported"

-- | Encode a clause in unsorted first-order logic in TPTP.
encodeClause :: Clause -> TPTP.Clause
encodeClause = TPTP.clause . fmap encodeSignedLiteral . getLiterals

-- | Decode a clause in unsorted first-order logic from TPTP.
decodeClause :: TPTP.Clause -> Partial Clause
decodeClause = evalEnumerationT . decodeClauseS

decodeClauseS :: TPTP.Clause -> Substitutions Clause
decodeClauseS (TPTP.Clause ls) = Literals <$> traverse decodeSignedLiteralS (NE.toList ls)

encodeSign :: Sign -> TPTP.Sign
encodeSign = \case
  Positive -> TPTP.Positive
  Negative -> TPTP.Negative

decodeSign :: TPTP.Sign -> Sign
decodeSign = \case
  TPTP.Positive -> Positive
  TPTP.Negative -> Negative

encodeSignedLiteral :: Signed Literal -> (TPTP.Sign, TPTP.Literal)
encodeSignedLiteral (Signed s l) = (encodeSign s, encodeLiteral l)

decodeSignedLiteralS :: (TPTP.Sign, TPTP.Literal) -> Substitutions (Signed Literal)
decodeSignedLiteralS (s, l) = sign (decodeSign s) <$> decodeLiteral l

-- | Encode a set of first-order clauses in TPTP.
encodeClauses :: Clauses -> TPTP.TPTP
encodeClauses (Clauses cs) = TPTP.TPTP units
  where
    units = zipWith unit [1..] cs
    unit n f = TPTP.Unit (Right n) (clauze f) Nothing
    clauze = TPTP.Formula (TPTP.Standard TPTP.Axiom) . encode . Clause

-- | Encode a theorem in unsorted first-order logic in TPTP.
encodeTheorem :: Theorem -> TPTP.TPTP
encodeTheorem (Theorem as c) = TPTP.TPTP units
  where
    units = unit TPTP.Conjecture 0 c : zipWith (unit TPTP.Axiom) [1..] as
    unit r n f = TPTP.Unit (Right n) (formula r f) Nothing
    formula r = TPTP.Formula (TPTP.Standard r) . encode . Formula . close

-- | Decode a solution from a TSTP output.
decodeSolution :: TPTP.TSTP -> Partial Solution
decodeSolution (TPTP.TSTP szs units)
  | TPTP.SZS (Just (Right status)) _dataform <- szs = if
    | isProof status -> Proof <$> decodeRefutation units
    | isSaturation status -> Saturation <$> decodeDerivation units
    | otherwise -> parsingError $ "unsupported SZS " <> show status
  | otherwise = proofError "malformed input: missing SZS ontologies"

isProof :: TPTP.Success -> Bool
isProof = \case
  TPTP.UNS -> True
  TPTP.THM -> True
  _ -> False

isSaturation :: TPTP.Success -> Bool
isSaturation = \case
  TPTP.SAT -> True
  TPTP.CSA -> True
  _ -> False

decodeRefutation :: [TPTP.Unit] -> Partial (Refutation Integer)
decodeRefutation units = do
  derivation <- decodeDerivation units
  case unliftRefutation derivation of
    Just refutation -> return refutation
    Nothing -> proofError "malformed input: refutation not found"

decodeDerivation :: [TPTP.Unit] -> Partial (Derivation Integer)
decodeDerivation units = do
  decodedSequents <- traverse decodeSequent units
  let expressions = labeling decodedSequents
  return . evalEnumeration
         . foldM (decodeSequentS expressions) mempty
         $ decodedSequents

decodeSequentS :: Ord n => Map n LogicalExpression -> Derivation Integer ->
                           Sequent n -> Enumeration n (Derivation Integer)
decodeSequentS es d s@(Sequent l i) =
  case find synonymous (antecedents i) of
    Just a  -> alias l a $> d
    Nothing -> addSequent d <$> traverse enumerate s
  where synonymous a = es ! a ~= consequent i

decodeSequent :: TPTP.Unit -> Partial (Sequent TPTP.UnitName)
decodeSequent = \case
  TPTP.Unit name (TPTP.Formula (TPTP.Standard TPTP.Axiom) formula) Nothing -> do
    expression <- decode formula
    return $ Sequent name (Inference Axiom expression)
  TPTP.Unit name (TPTP.Formula role formula) (Just (source, _)) -> do
    rule <- decodeRule source role (collectParents source)
    expression <- decode formula
    return $ Sequent name (Inference rule expression)
  _ -> proofError "malformed input: unexpected unit"

collectParents :: TPTP.Source -> [TPTP.UnitName]
collectParents = \case
  TPTP.File{}           -> []
  TPTP.Theory{}         -> []
  TPTP.Creator{}        -> []
  TPTP.Introduced{}     -> []
  TPTP.Inference _ _ ps -> concatMap (\(TPTP.Parent p _) -> collectParents p) ps
  TPTP.UnitSource p     -> [p]
  TPTP.UnknownSource    -> []

decodeRule :: TPTP.Source -> TPTP.Reserved TPTP.Role -> [f] -> Partial (Rule f)
decodeRule s role as = case s of
  TPTP.Theory{}           -> parsingError $ "unsupported unit source " ++ show s
  TPTP.Creator{}          -> parsingError $ "unsupported unit source " ++ show s
  TPTP.UnitSource{}       -> return $ Other "triviality" as
  TPTP.Introduced taut _  -> return $ decodeTautologyRule taut
  TPTP.UnknownSource      -> return $ Unknown as
  TPTP.File{}             -> decodeIntroductionRule role as
  TPTP.Inference rule _ _ -> return $ decodeInferenceRule rule as

decodeTautologyRule :: TPTP.Reserved TPTP.Intro -> Rule f
decodeTautologyRule = \case
  TPTP.Standard TPTP.ByAxiomOfChoice -> AxiomOfChoice
  TPTP.Extended "choice_axiom" -> AxiomOfChoice
  _ -> Axiom

decodeIntroductionRule :: TPTP.Reserved TPTP.Role -> [a] -> Partial (Rule f)
decodeIntroductionRule role as = case (role, as) of
  (TPTP.Standard TPTP.Axiom,      []) -> return Axiom
  (TPTP.Standard TPTP.Conjecture, []) -> return Conjecture
  _ -> proofError $ "unexpected unit role " <> show role

decodeInferenceRule :: TPTP.Atom -> [f] -> Rule f
decodeInferenceRule (TPTP.Atom rule) as = case (rule, as) of
  ("negated_conjecture",         [f]) -> NegatedConjecture       f
  ("assume_negation",            [f]) -> NegatedConjecture       f
  ("flattening",                 [f]) -> Flattening              f
  ("skolemisation",              [f]) -> Skolemisation           f
  ("skolemize",                  [f]) -> Skolemisation           f
  ("ennf_transformation",        [f]) -> EnnfTransformation      f
  ("nnf_transformation",         [f]) -> NnfTransformation       f
  ("cnf_transformation",         [f]) -> Clausification          f
  ("trivial_inequality_removal", [f]) -> TrivialInequality       f
  ("superposition",           [f, g]) -> Superposition         f g
  ("resolution",              [f, g]) -> Resolution            f g
  ("pm",                      [f, g]) -> Paramodulation        f g
  ("subsumption_resolution",  [f, g]) -> SubsumptionResolution f g
  ("forward_demodulation",    [f, g]) -> ForwardDemodulation   f g
  ("backward_demodulation",   [f, g]) -> BackwardDemodulation  f g
  _ -> Other (RuleName rule) as