packages feed

atp-0.1.0.0: src/ATP/Prove.hs

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}

{-|
Module       : ATP.Prove
Description  : Interface to automated theorem provers.
Copyright    : (c) Evgenii Kotelnikov, 2019-2021
License      : GPL-3
Maintainer   : evgeny.kotelnikov@gmail.com
Stability    : experimental

Interface to automated theorem provers.
-}

module ATP.Prove (
  ProvingOptions(..),
  defaultOptions,
  refute,
  refuteUsing,
  refuteWith,
  prove,
  proveUsing,
  proveWith
) where

import Control.Monad (when)
import Data.Text (Text)
import qualified Data.Text as T (pack)
import Data.TPTP (TPTP)
import Data.TPTP.Parse.Text (parseTSTPOnly)
import Data.TPTP.Pretty (pretty)
import System.Exit (ExitCode(..))
import System.Process (readProcessWithExitCode)
import Text.PrettyPrint.ANSI.Leijen (bold, text)

import ATP.Error
import ATP.FOL (Clauses, Theorem, Solution)
import ATP.Codec.TPTP (encodeClauses, encodeTheorem, decodeSolution)
import ATP.Prover


-- | The options that describe what theorem prover to use for a problem and
-- how to run it.
data ProvingOptions = ProvingOptions {
  prover      :: Prover,
  timeLimit   :: TimeLimit,
  memoryLimit :: MemoryLimit,
  debug       :: Bool -- ^ If @True@, print the input, the cli command,
                      --   the exit code and the output of the prover
} deriving (Eq, Show, Ord)

-- | The default options used by 'refute' and 'prove'.
--
-- >>> defaultOptions
-- ProvingOptions {prover = Prover {vendor = E, executable = "eprover"}, timeLimit = 300, memoryLimit = 2000, debug = False}
defaultOptions :: ProvingOptions
defaultOptions = ProvingOptions {
  prover = defaultProver,
  timeLimit = 300,
  memoryLimit = 2000,
  debug = False
}

-- | Attempt to refute a set of clauses using 'defaultProver'.
--
-- > refute = refuteWith defaultOptions
refute :: Clauses -> IO (Partial Solution)
refute = refuteWith defaultOptions

-- | Attempt to refute a set of clauses using a given prover.
refuteUsing :: Prover -> Clauses -> IO (Partial Solution)
refuteUsing p = refuteWith defaultOptions{prover = p}

-- | Attempt to refute a set of clauses with a given set of options.
refuteWith :: ProvingOptions -> Clauses -> IO (Partial Solution)
refuteWith opts = runWith opts . encodeClauses

-- | Attempt to prove a theorem using 'defaultProver'.
--
-- > prove = proveWith defaultOptions
prove :: Theorem -> IO (Partial Solution)
prove = proveWith defaultOptions

-- | Attempt to prove a theorem using a given prover.
proveUsing :: Prover -> Theorem -> IO (Partial Solution)
proveUsing p = proveWith defaultOptions{prover = p}

-- | Attempt to prove a theorem with a given set of options.
proveWith :: ProvingOptions -> Theorem -> IO (Partial Solution)
proveWith opts = runWith opts . encodeTheorem

runWith :: ProvingOptions -> TPTP -> IO (Partial Solution)
runWith opts tptp = do
  let ProvingOptions{prover} = opts
  let Prover{vendor} = prover
  let input = show (pretty tptp)
  (exitCode, stdout, stderr) <- runProver opts input
  let output = proverOutput vendor exitCode stdout stderr
  let solution = output >>= parseSolution
  return solution

runProver :: ProvingOptions -> String -> IO (ExitCode, Text, Text)
runProver opts input = do
  let ProvingOptions{prover, timeLimit, memoryLimit, debug} = opts
  let Prover{vendor, executable} = prover
  let arguments = proverArguments vendor timeLimit memoryLimit
  let debugPrint header str = when debug $
                              print (bold (text header)) >>
                              putStrLn str >> putStr "\n"
  debugPrint "Input" input
  debugPrint "Command" $ unwords (executable:arguments)
  (exitCode, stdout, stderr) <- readProcessWithExitCode executable arguments input
  debugPrint "Exit code" (show exitCode)
  debugPrint "Standard output" stdout
  debugPrint "Standard error"  stderr
  return (exitCode, T.pack stdout, T.pack stderr)

parseSolution :: Text -> Partial Solution
parseSolution = either parsingError decodeSolution . parseTSTPOnly