123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- # Natural Language Toolkit: Interface to the Prover9 Theorem Prover
- #
- # Copyright (C) 2001-2019 NLTK Project
- # Author: Dan Garrette <dhgarrette@gmail.com>
- # Ewan Klein <ewan@inf.ed.ac.uk>
- #
- # URL: <http://nltk.org/>
- # For license information, see LICENSE.TXT
- """
- A theorem prover that makes use of the external 'Prover9' package.
- """
- from __future__ import print_function
- import os
- import subprocess
- import nltk
- from nltk.sem.logic import (
- Expression,
- ExistsExpression,
- AllExpression,
- NegatedExpression,
- AndExpression,
- IffExpression,
- OrExpression,
- EqualityExpression,
- ImpExpression,
- )
- from nltk.inference.api import BaseProverCommand, Prover
- #
- # Following is not yet used. Return code for 2 actually realized as 512.
- #
- p9_return_codes = {
- 0: True,
- 1: "(FATAL)", # A fatal error occurred (user's syntax error).
- 2: False, # (SOS_EMPTY) Prover9 ran out of things to do
- # (sos list exhausted).
- 3: "(MAX_MEGS)", # The max_megs (memory limit) parameter was exceeded.
- 4: "(MAX_SECONDS)", # The max_seconds parameter was exceeded.
- 5: "(MAX_GIVEN)", # The max_given parameter was exceeded.
- 6: "(MAX_KEPT)", # The max_kept parameter was exceeded.
- 7: "(ACTION)", # A Prover9 action terminated the search.
- 101: "(SIGSEGV)", # Prover9 crashed, most probably due to a bug.
- }
- class Prover9CommandParent(object):
- """
- A common base class used by both ``Prover9Command`` and ``MaceCommand``,
- which is responsible for maintaining a goal and a set of assumptions,
- and generating prover9-style input files from them.
- """
- def print_assumptions(self, output_format='nltk'):
- """
- Print the list of the current assumptions.
- """
- if output_format.lower() == 'nltk':
- for a in self.assumptions():
- print(a)
- elif output_format.lower() == 'prover9':
- for a in convert_to_prover9(self.assumptions()):
- print(a)
- else:
- raise NameError(
- "Unrecognized value for 'output_format': %s" % output_format
- )
- class Prover9Command(Prover9CommandParent, BaseProverCommand):
- """
- A ``ProverCommand`` specific to the ``Prover9`` prover. It contains
- the a print_assumptions() method that is used to print the list
- of assumptions in multiple formats.
- """
- def __init__(self, goal=None, assumptions=None, timeout=60, prover=None):
- """
- :param goal: Input expression to prove
- :type goal: sem.Expression
- :param assumptions: Input expressions to use as assumptions in
- the proof.
- :type assumptions: list(sem.Expression)
- :param timeout: number of seconds before timeout; set to 0 for
- no timeout.
- :type timeout: int
- :param prover: a prover. If not set, one will be created.
- :type prover: Prover9
- """
- if not assumptions:
- assumptions = []
- if prover is not None:
- assert isinstance(prover, Prover9)
- else:
- prover = Prover9(timeout)
- BaseProverCommand.__init__(self, prover, goal, assumptions)
- def decorate_proof(self, proof_string, simplify=True):
- """
- :see BaseProverCommand.decorate_proof()
- """
- if simplify:
- return self._prover._call_prooftrans(proof_string, ['striplabels'])[
- 0
- ].rstrip()
- else:
- return proof_string.rstrip()
- class Prover9Parent(object):
- """
- A common class extended by both ``Prover9`` and ``Mace <mace.Mace>``.
- It contains the functionality required to convert NLTK-style
- expressions into Prover9-style expressions.
- """
- _binary_location = None
- def config_prover9(self, binary_location, verbose=False):
- if binary_location is None:
- self._binary_location = None
- self._prover9_bin = None
- else:
- name = 'prover9'
- self._prover9_bin = nltk.internals.find_binary(
- name,
- path_to_bin=binary_location,
- env_vars=['PROVER9'],
- url='http://www.cs.unm.edu/~mccune/prover9/',
- binary_names=[name, name + '.exe'],
- verbose=verbose,
- )
- self._binary_location = self._prover9_bin.rsplit(os.path.sep, 1)
- def prover9_input(self, goal, assumptions):
- """
- :return: The input string that should be provided to the
- prover9 binary. This string is formed based on the goal,
- assumptions, and timeout value of this object.
- """
- s = ''
- if assumptions:
- s += 'formulas(assumptions).\n'
- for p9_assumption in convert_to_prover9(assumptions):
- s += ' %s.\n' % p9_assumption
- s += 'end_of_list.\n\n'
- if goal:
- s += 'formulas(goals).\n'
- s += ' %s.\n' % convert_to_prover9(goal)
- s += 'end_of_list.\n\n'
- return s
- def binary_locations(self):
- """
- A list of directories that should be searched for the prover9
- executables. This list is used by ``config_prover9`` when searching
- for the prover9 executables.
- """
- return [
- '/usr/local/bin/prover9',
- '/usr/local/bin/prover9/bin',
- '/usr/local/bin',
- '/usr/bin',
- '/usr/local/prover9',
- '/usr/local/share/prover9',
- ]
- def _find_binary(self, name, verbose=False):
- binary_locations = self.binary_locations()
- if self._binary_location is not None:
- binary_locations += [self._binary_location]
- return nltk.internals.find_binary(
- name,
- searchpath=binary_locations,
- env_vars=['PROVER9'],
- url='http://www.cs.unm.edu/~mccune/prover9/',
- binary_names=[name, name + '.exe'],
- verbose=verbose,
- )
- def _call(self, input_str, binary, args=[], verbose=False):
- """
- Call the binary with the given input.
- :param input_str: A string whose contents are used as stdin.
- :param binary: The location of the binary to call
- :param args: A list of command-line arguments.
- :return: A tuple (stdout, returncode)
- :see: ``config_prover9``
- """
- if verbose:
- print('Calling:', binary)
- print('Args:', args)
- print('Input:\n', input_str, '\n')
- # Call prover9 via a subprocess
- cmd = [binary] + args
- try:
- input_str = input_str.encode("utf8")
- except AttributeError:
- pass
- p = subprocess.Popen(
- cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE
- )
- (stdout, stderr) = p.communicate(input=input_str)
- if verbose:
- print('Return code:', p.returncode)
- if stdout:
- print('stdout:\n', stdout, '\n')
- if stderr:
- print('stderr:\n', stderr, '\n')
- return (stdout.decode("utf-8"), p.returncode)
- def convert_to_prover9(input):
- """
- Convert a ``logic.Expression`` to Prover9 format.
- """
- if isinstance(input, list):
- result = []
- for s in input:
- try:
- result.append(_convert_to_prover9(s.simplify()))
- except:
- print('input %s cannot be converted to Prover9 input syntax' % input)
- raise
- return result
- else:
- try:
- return _convert_to_prover9(input.simplify())
- except:
- print('input %s cannot be converted to Prover9 input syntax' % input)
- raise
- def _convert_to_prover9(expression):
- """
- Convert ``logic.Expression`` to Prover9 formatted string.
- """
- if isinstance(expression, ExistsExpression):
- return (
- 'exists '
- + str(expression.variable)
- + ' '
- + _convert_to_prover9(expression.term)
- )
- elif isinstance(expression, AllExpression):
- return (
- 'all '
- + str(expression.variable)
- + ' '
- + _convert_to_prover9(expression.term)
- )
- elif isinstance(expression, NegatedExpression):
- return '-(' + _convert_to_prover9(expression.term) + ')'
- elif isinstance(expression, AndExpression):
- return (
- '('
- + _convert_to_prover9(expression.first)
- + ' & '
- + _convert_to_prover9(expression.second)
- + ')'
- )
- elif isinstance(expression, OrExpression):
- return (
- '('
- + _convert_to_prover9(expression.first)
- + ' | '
- + _convert_to_prover9(expression.second)
- + ')'
- )
- elif isinstance(expression, ImpExpression):
- return (
- '('
- + _convert_to_prover9(expression.first)
- + ' -> '
- + _convert_to_prover9(expression.second)
- + ')'
- )
- elif isinstance(expression, IffExpression):
- return (
- '('
- + _convert_to_prover9(expression.first)
- + ' <-> '
- + _convert_to_prover9(expression.second)
- + ')'
- )
- elif isinstance(expression, EqualityExpression):
- return (
- '('
- + _convert_to_prover9(expression.first)
- + ' = '
- + _convert_to_prover9(expression.second)
- + ')'
- )
- else:
- return str(expression)
- class Prover9(Prover9Parent, Prover):
- _prover9_bin = None
- _prooftrans_bin = None
- def __init__(self, timeout=60):
- self._timeout = timeout
- """The timeout value for prover9. If a proof can not be found
- in this amount of time, then prover9 will return false.
- (Use 0 for no timeout.)"""
- def _prove(self, goal=None, assumptions=None, verbose=False):
- """
- Use Prover9 to prove a theorem.
- :return: A pair whose first element is a boolean indicating if the
- proof was successful (i.e. returns value of 0) and whose second element
- is the output of the prover.
- """
- if not assumptions:
- assumptions = []
- stdout, returncode = self._call_prover9(
- self.prover9_input(goal, assumptions), verbose=verbose
- )
- return (returncode == 0, stdout)
- def prover9_input(self, goal, assumptions):
- """
- :see: Prover9Parent.prover9_input
- """
- s = 'clear(auto_denials).\n' # only one proof required
- return s + Prover9Parent.prover9_input(self, goal, assumptions)
- def _call_prover9(self, input_str, args=[], verbose=False):
- """
- Call the ``prover9`` binary with the given input.
- :param input_str: A string whose contents are used as stdin.
- :param args: A list of command-line arguments.
- :return: A tuple (stdout, returncode)
- :see: ``config_prover9``
- """
- if self._prover9_bin is None:
- self._prover9_bin = self._find_binary('prover9', verbose)
- updated_input_str = ''
- if self._timeout > 0:
- updated_input_str += 'assign(max_seconds, %d).\n\n' % self._timeout
- updated_input_str += input_str
- stdout, returncode = self._call(
- updated_input_str, self._prover9_bin, args, verbose
- )
- if returncode not in [0, 2]:
- errormsgprefix = '%%ERROR:'
- if errormsgprefix in stdout:
- msgstart = stdout.index(errormsgprefix)
- errormsg = stdout[msgstart:].strip()
- else:
- errormsg = None
- if returncode in [3, 4, 5, 6]:
- raise Prover9LimitExceededException(returncode, errormsg)
- else:
- raise Prover9FatalException(returncode, errormsg)
- return stdout, returncode
- def _call_prooftrans(self, input_str, args=[], verbose=False):
- """
- Call the ``prooftrans`` binary with the given input.
- :param input_str: A string whose contents are used as stdin.
- :param args: A list of command-line arguments.
- :return: A tuple (stdout, returncode)
- :see: ``config_prover9``
- """
- if self._prooftrans_bin is None:
- self._prooftrans_bin = self._find_binary('prooftrans', verbose)
- return self._call(input_str, self._prooftrans_bin, args, verbose)
- class Prover9Exception(Exception):
- def __init__(self, returncode, message):
- msg = p9_return_codes[returncode]
- if message:
- msg += '\n%s' % message
- Exception.__init__(self, msg)
- class Prover9FatalException(Prover9Exception):
- pass
- class Prover9LimitExceededException(Prover9Exception):
- pass
- ######################################################################
- # { Tests and Demos
- ######################################################################
- def test_config():
- a = Expression.fromstring('(walk(j) & sing(j))')
- g = Expression.fromstring('walk(j)')
- p = Prover9Command(g, assumptions=[a])
- p._executable_path = None
- p.prover9_search = []
- p.prove()
- # config_prover9('/usr/local/bin')
- print(p.prove())
- print(p.proof())
- def test_convert_to_prover9(expr):
- """
- Test that parsing works OK.
- """
- for t in expr:
- e = Expression.fromstring(t)
- print(convert_to_prover9(e))
- def test_prove(arguments):
- """
- Try some proofs and exhibit the results.
- """
- for (goal, assumptions) in arguments:
- g = Expression.fromstring(goal)
- alist = [Expression.fromstring(a) for a in assumptions]
- p = Prover9Command(g, assumptions=alist).prove()
- for a in alist:
- print(' %s' % a)
- print('|- %s: %s\n' % (g, p))
- arguments = [
- ('(man(x) <-> (not (not man(x))))', []),
- ('(not (man(x) & (not man(x))))', []),
- ('(man(x) | (not man(x)))', []),
- ('(man(x) & (not man(x)))', []),
- ('(man(x) -> man(x))', []),
- ('(not (man(x) & (not man(x))))', []),
- ('(man(x) | (not man(x)))', []),
- ('(man(x) -> man(x))', []),
- ('(man(x) <-> man(x))', []),
- ('(not (man(x) <-> (not man(x))))', []),
- ('mortal(Socrates)', ['all x.(man(x) -> mortal(x))', 'man(Socrates)']),
- ('((all x.(man(x) -> walks(x)) & man(Socrates)) -> some y.walks(y))', []),
- ('(all x.man(x) -> all x.man(x))', []),
- ('some x.all y.sees(x,y)', []),
- (
- 'some e3.(walk(e3) & subj(e3, mary))',
- [
- 'some e1.(see(e1) & subj(e1, john) & some e2.(pred(e1, e2) & walk(e2) & subj(e2, mary)))'
- ],
- ),
- (
- 'some x e1.(see(e1) & subj(e1, x) & some e2.(pred(e1, e2) & walk(e2) & subj(e2, mary)))',
- [
- 'some e1.(see(e1) & subj(e1, john) & some e2.(pred(e1, e2) & walk(e2) & subj(e2, mary)))'
- ],
- ),
- ]
- expressions = [
- r'some x y.sees(x,y)',
- r'some x.(man(x) & walks(x))',
- r'\x.(man(x) & walks(x))',
- r'\x y.sees(x,y)',
- r'walks(john)',
- r'\x.big(x, \y.mouse(y))',
- r'(walks(x) & (runs(x) & (threes(x) & fours(x))))',
- r'(walks(x) -> runs(x))',
- r'some x.(PRO(x) & sees(John, x))',
- r'some x.(man(x) & (not walks(x)))',
- r'all x.(man(x) -> walks(x))',
- ]
- def spacer(num=45):
- print('-' * num)
- def demo():
- print("Testing configuration")
- spacer()
- test_config()
- print()
- print("Testing conversion to Prover9 format")
- spacer()
- test_convert_to_prover9(expressions)
- print()
- print("Testing proofs")
- spacer()
- test_prove(arguments)
- if __name__ == '__main__':
- demo()
|