123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- # Natural Language Toolkit: Linear Logic
- #
- # Author: Dan Garrette <dhgarrette@gmail.com>
- #
- # Copyright (C) 2001-2019 NLTK Project
- # URL: <http://nltk.org/>
- # For license information, see LICENSE.TXT
- from __future__ import print_function, unicode_literals
- from six import string_types
- from nltk.internals import Counter
- from nltk.compat import python_2_unicode_compatible
- from nltk.sem.logic import LogicParser, APP
- _counter = Counter()
- class Tokens(object):
- # Punctuation
- OPEN = '('
- CLOSE = ')'
- # Operations
- IMP = '-o'
- PUNCT = [OPEN, CLOSE]
- TOKENS = PUNCT + [IMP]
- class LinearLogicParser(LogicParser):
- """A linear logic expression parser."""
- def __init__(self):
- LogicParser.__init__(self)
- self.operator_precedence = {APP: 1, Tokens.IMP: 2, None: 3}
- self.right_associated_operations += [Tokens.IMP]
- def get_all_symbols(self):
- return Tokens.TOKENS
- def handle(self, tok, context):
- if tok not in Tokens.TOKENS:
- return self.handle_variable(tok, context)
- elif tok == Tokens.OPEN:
- return self.handle_open(tok, context)
- def get_BooleanExpression_factory(self, tok):
- if tok == Tokens.IMP:
- return ImpExpression
- else:
- return None
- def make_BooleanExpression(self, factory, first, second):
- return factory(first, second)
- def attempt_ApplicationExpression(self, expression, context):
- """Attempt to make an application expression. If the next tokens
- are an argument in parens, then the argument expression is a
- function being applied to the arguments. Otherwise, return the
- argument expression."""
- if self.has_priority(APP, context):
- if self.inRange(0) and self.token(0) == Tokens.OPEN:
- self.token() # swallow then open paren
- argument = self.process_next_expression(APP)
- self.assertNextToken(Tokens.CLOSE)
- expression = ApplicationExpression(expression, argument, None)
- return expression
- def make_VariableExpression(self, name):
- if name[0].isupper():
- return VariableExpression(name)
- else:
- return ConstantExpression(name)
- @python_2_unicode_compatible
- class Expression(object):
- _linear_logic_parser = LinearLogicParser()
- @classmethod
- def fromstring(cls, s):
- return cls._linear_logic_parser.parse(s)
- def applyto(self, other, other_indices=None):
- return ApplicationExpression(self, other, other_indices)
- def __call__(self, other):
- return self.applyto(other)
- def __repr__(self):
- return '<%s %s>' % (self.__class__.__name__, self)
- @python_2_unicode_compatible
- class AtomicExpression(Expression):
- def __init__(self, name, dependencies=None):
- """
- :param name: str for the constant name
- :param dependencies: list of int for the indices on which this atom is dependent
- """
- assert isinstance(name, string_types)
- self.name = name
- if not dependencies:
- dependencies = []
- self.dependencies = dependencies
- def simplify(self, bindings=None):
- """
- If 'self' is bound by 'bindings', return the atomic to which it is bound.
- Otherwise, return self.
- :param bindings: ``BindingDict`` A dictionary of bindings used to simplify
- :return: ``AtomicExpression``
- """
- if bindings and self in bindings:
- return bindings[self]
- else:
- return self
- def compile_pos(self, index_counter, glueFormulaFactory):
- """
- From Iddo Lev's PhD Dissertation p108-109
- :param index_counter: ``Counter`` for unique indices
- :param glueFormulaFactory: ``GlueFormula`` for creating new glue formulas
- :return: (``Expression``,set) for the compiled linear logic and any newly created glue formulas
- """
- self.dependencies = []
- return (self, [])
- def compile_neg(self, index_counter, glueFormulaFactory):
- """
- From Iddo Lev's PhD Dissertation p108-109
- :param index_counter: ``Counter`` for unique indices
- :param glueFormulaFactory: ``GlueFormula`` for creating new glue formulas
- :return: (``Expression``,set) for the compiled linear logic and any newly created glue formulas
- """
- self.dependencies = []
- return (self, [])
- def initialize_labels(self, fstruct):
- self.name = fstruct.initialize_label(self.name.lower())
- def __eq__(self, other):
- return self.__class__ == other.__class__ and self.name == other.name
- def __ne__(self, other):
- return not self == other
- def __str__(self):
- accum = self.name
- if self.dependencies:
- accum += "%s" % self.dependencies
- return accum
- def __hash__(self):
- return hash(self.name)
- class ConstantExpression(AtomicExpression):
- def unify(self, other, bindings):
- """
- If 'other' is a constant, then it must be equal to 'self'. If 'other' is a variable,
- then it must not be bound to anything other than 'self'.
- :param other: ``Expression``
- :param bindings: ``BindingDict`` A dictionary of all current bindings
- :return: ``BindingDict`` A new combined dictionary of of 'bindings' and any new binding
- :raise UnificationException: If 'self' and 'other' cannot be unified in the context of 'bindings'
- """
- assert isinstance(other, Expression)
- if isinstance(other, VariableExpression):
- try:
- return bindings + BindingDict([(other, self)])
- except VariableBindingException:
- pass
- elif self == other:
- return bindings
- raise UnificationException(self, other, bindings)
- class VariableExpression(AtomicExpression):
- def unify(self, other, bindings):
- """
- 'self' must not be bound to anything other than 'other'.
- :param other: ``Expression``
- :param bindings: ``BindingDict`` A dictionary of all current bindings
- :return: ``BindingDict`` A new combined dictionary of of 'bindings' and the new binding
- :raise UnificationException: If 'self' and 'other' cannot be unified in the context of 'bindings'
- """
- assert isinstance(other, Expression)
- try:
- if self == other:
- return bindings
- else:
- return bindings + BindingDict([(self, other)])
- except VariableBindingException:
- raise UnificationException(self, other, bindings)
- @python_2_unicode_compatible
- class ImpExpression(Expression):
- def __init__(self, antecedent, consequent):
- """
- :param antecedent: ``Expression`` for the antecedent
- :param consequent: ``Expression`` for the consequent
- """
- assert isinstance(antecedent, Expression)
- assert isinstance(consequent, Expression)
- self.antecedent = antecedent
- self.consequent = consequent
- def simplify(self, bindings=None):
- return self.__class__(
- self.antecedent.simplify(bindings), self.consequent.simplify(bindings)
- )
- def unify(self, other, bindings):
- """
- Both the antecedent and consequent of 'self' and 'other' must unify.
- :param other: ``ImpExpression``
- :param bindings: ``BindingDict`` A dictionary of all current bindings
- :return: ``BindingDict`` A new combined dictionary of of 'bindings' and any new bindings
- :raise UnificationException: If 'self' and 'other' cannot be unified in the context of 'bindings'
- """
- assert isinstance(other, ImpExpression)
- try:
- return (
- bindings
- + self.antecedent.unify(other.antecedent, bindings)
- + self.consequent.unify(other.consequent, bindings)
- )
- except VariableBindingException:
- raise UnificationException(self, other, bindings)
- def compile_pos(self, index_counter, glueFormulaFactory):
- """
- From Iddo Lev's PhD Dissertation p108-109
- :param index_counter: ``Counter`` for unique indices
- :param glueFormulaFactory: ``GlueFormula`` for creating new glue formulas
- :return: (``Expression``,set) for the compiled linear logic and any newly created glue formulas
- """
- (a, a_new) = self.antecedent.compile_neg(index_counter, glueFormulaFactory)
- (c, c_new) = self.consequent.compile_pos(index_counter, glueFormulaFactory)
- return (ImpExpression(a, c), a_new + c_new)
- def compile_neg(self, index_counter, glueFormulaFactory):
- """
- From Iddo Lev's PhD Dissertation p108-109
- :param index_counter: ``Counter`` for unique indices
- :param glueFormulaFactory: ``GlueFormula`` for creating new glue formulas
- :return: (``Expression``,list of ``GlueFormula``) for the compiled linear logic and any newly created glue formulas
- """
- (a, a_new) = self.antecedent.compile_pos(index_counter, glueFormulaFactory)
- (c, c_new) = self.consequent.compile_neg(index_counter, glueFormulaFactory)
- fresh_index = index_counter.get()
- c.dependencies.append(fresh_index)
- new_v = glueFormulaFactory('v%s' % fresh_index, a, set([fresh_index]))
- return (c, a_new + c_new + [new_v])
- def initialize_labels(self, fstruct):
- self.antecedent.initialize_labels(fstruct)
- self.consequent.initialize_labels(fstruct)
- def __eq__(self, other):
- return (
- self.__class__ == other.__class__
- and self.antecedent == other.antecedent
- and self.consequent == other.consequent
- )
- def __ne__(self, other):
- return not self == other
- def __str__(self):
- return "%s%s %s %s%s" % (
- Tokens.OPEN,
- self.antecedent,
- Tokens.IMP,
- self.consequent,
- Tokens.CLOSE,
- )
- def __hash__(self):
- return hash(
- '%s%s%s' % (hash(self.antecedent), Tokens.IMP, hash(self.consequent))
- )
- @python_2_unicode_compatible
- class ApplicationExpression(Expression):
- def __init__(self, function, argument, argument_indices=None):
- """
- :param function: ``Expression`` for the function
- :param argument: ``Expression`` for the argument
- :param argument_indices: set for the indices of the glue formula from which the argument came
- :raise LinearLogicApplicationException: If 'function' cannot be applied to 'argument' given 'argument_indices'.
- """
- function_simp = function.simplify()
- argument_simp = argument.simplify()
- assert isinstance(function_simp, ImpExpression)
- assert isinstance(argument_simp, Expression)
- bindings = BindingDict()
- try:
- if isinstance(function, ApplicationExpression):
- bindings += function.bindings
- if isinstance(argument, ApplicationExpression):
- bindings += argument.bindings
- bindings += function_simp.antecedent.unify(argument_simp, bindings)
- except UnificationException as e:
- raise LinearLogicApplicationException(
- 'Cannot apply %s to %s. %s' % (function_simp, argument_simp, e)
- )
- # If you are running it on complied premises, more conditions apply
- if argument_indices:
- # A.dependencies of (A -o (B -o C)) must be a proper subset of argument_indices
- if not set(function_simp.antecedent.dependencies) < argument_indices:
- raise LinearLogicApplicationException(
- 'Dependencies unfulfilled when attempting to apply Linear Logic formula %s to %s'
- % (function_simp, argument_simp)
- )
- if set(function_simp.antecedent.dependencies) == argument_indices:
- raise LinearLogicApplicationException(
- 'Dependencies not a proper subset of indices when attempting to apply Linear Logic formula %s to %s'
- % (function_simp, argument_simp)
- )
- self.function = function
- self.argument = argument
- self.bindings = bindings
- def simplify(self, bindings=None):
- """
- Since function is an implication, return its consequent. There should be
- no need to check that the application is valid since the checking is done
- by the constructor.
- :param bindings: ``BindingDict`` A dictionary of bindings used to simplify
- :return: ``Expression``
- """
- if not bindings:
- bindings = self.bindings
- return self.function.simplify(bindings).consequent
- def __eq__(self, other):
- return (
- self.__class__ == other.__class__
- and self.function == other.function
- and self.argument == other.argument
- )
- def __ne__(self, other):
- return not self == other
- def __str__(self):
- return "%s" % self.function + Tokens.OPEN + "%s" % self.argument + Tokens.CLOSE
- def __hash__(self):
- return hash(
- '%s%s%s' % (hash(self.antecedent), Tokens.OPEN, hash(self.consequent))
- )
- @python_2_unicode_compatible
- class BindingDict(object):
- def __init__(self, bindings=None):
- """
- :param bindings:
- list [(``VariableExpression``, ``AtomicExpression``)] to initialize the dictionary
- dict {``VariableExpression``: ``AtomicExpression``} to initialize the dictionary
- """
- self.d = {}
- if isinstance(bindings, dict):
- bindings = bindings.items()
- if bindings:
- for (v, b) in bindings:
- self[v] = b
- def __setitem__(self, variable, binding):
- """
- A binding is consistent with the dict if its variable is not already bound, OR if its
- variable is already bound to its argument.
- :param variable: ``VariableExpression`` The variable bind
- :param binding: ``Expression`` The expression to which 'variable' should be bound
- :raise VariableBindingException: If the variable cannot be bound in this dictionary
- """
- assert isinstance(variable, VariableExpression)
- assert isinstance(binding, Expression)
- assert variable != binding
- existing = self.d.get(variable, None)
- if not existing or binding == existing:
- self.d[variable] = binding
- else:
- raise VariableBindingException(
- 'Variable %s already bound to another value' % (variable)
- )
- def __getitem__(self, variable):
- """
- Return the expression to which 'variable' is bound
- """
- assert isinstance(variable, VariableExpression)
- intermediate = self.d[variable]
- while intermediate:
- try:
- intermediate = self.d[intermediate]
- except KeyError:
- return intermediate
- def __contains__(self, item):
- return item in self.d
- def __add__(self, other):
- """
- :param other: ``BindingDict`` The dict with which to combine self
- :return: ``BindingDict`` A new dict containing all the elements of both parameters
- :raise VariableBindingException: If the parameter dictionaries are not consistent with each other
- """
- try:
- combined = BindingDict()
- for v in self.d:
- combined[v] = self.d[v]
- for v in other.d:
- combined[v] = other.d[v]
- return combined
- except VariableBindingException:
- raise VariableBindingException(
- 'Attempting to add two contradicting'
- ' VariableBindingsLists: %s, %s' % (self, other)
- )
- def __ne__(self, other):
- return not self == other
- def __eq__(self, other):
- if not isinstance(other, BindingDict):
- raise TypeError
- return self.d == other.d
- def __str__(self):
- return '{' + ', '.join('%s: %s' % (v, self.d[v]) for v in self.d) + '}'
- def __repr__(self):
- return 'BindingDict: %s' % self
- class VariableBindingException(Exception):
- pass
- class UnificationException(Exception):
- def __init__(self, a, b, bindings):
- Exception.__init__(self, 'Cannot unify %s with %s given %s' % (a, b, bindings))
- class LinearLogicApplicationException(Exception):
- pass
- def demo():
- lexpr = Expression.fromstring
- print(lexpr(r'f'))
- print(lexpr(r'(g -o f)'))
- print(lexpr(r'((g -o G) -o G)'))
- print(lexpr(r'g -o h -o f'))
- print(lexpr(r'(g -o f)(g)').simplify())
- print(lexpr(r'(H -o f)(g)').simplify())
- print(lexpr(r'((g -o G) -o G)((g -o f))').simplify())
- print(lexpr(r'(H -o H)((g -o f))').simplify())
- if __name__ == '__main__':
- demo()
|