linearlogic.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. # Natural Language Toolkit: Linear Logic
  2. #
  3. # Author: Dan Garrette <dhgarrette@gmail.com>
  4. #
  5. # Copyright (C) 2001-2020 NLTK Project
  6. # URL: <http://nltk.org/>
  7. # For license information, see LICENSE.TXT
  8. from nltk.internals import Counter
  9. from nltk.sem.logic import LogicParser, APP
  10. _counter = Counter()
  11. class Tokens(object):
  12. # Punctuation
  13. OPEN = "("
  14. CLOSE = ")"
  15. # Operations
  16. IMP = "-o"
  17. PUNCT = [OPEN, CLOSE]
  18. TOKENS = PUNCT + [IMP]
  19. class LinearLogicParser(LogicParser):
  20. """A linear logic expression parser."""
  21. def __init__(self):
  22. LogicParser.__init__(self)
  23. self.operator_precedence = {APP: 1, Tokens.IMP: 2, None: 3}
  24. self.right_associated_operations += [Tokens.IMP]
  25. def get_all_symbols(self):
  26. return Tokens.TOKENS
  27. def handle(self, tok, context):
  28. if tok not in Tokens.TOKENS:
  29. return self.handle_variable(tok, context)
  30. elif tok == Tokens.OPEN:
  31. return self.handle_open(tok, context)
  32. def get_BooleanExpression_factory(self, tok):
  33. if tok == Tokens.IMP:
  34. return ImpExpression
  35. else:
  36. return None
  37. def make_BooleanExpression(self, factory, first, second):
  38. return factory(first, second)
  39. def attempt_ApplicationExpression(self, expression, context):
  40. """Attempt to make an application expression. If the next tokens
  41. are an argument in parens, then the argument expression is a
  42. function being applied to the arguments. Otherwise, return the
  43. argument expression."""
  44. if self.has_priority(APP, context):
  45. if self.inRange(0) and self.token(0) == Tokens.OPEN:
  46. self.token() # swallow then open paren
  47. argument = self.process_next_expression(APP)
  48. self.assertNextToken(Tokens.CLOSE)
  49. expression = ApplicationExpression(expression, argument, None)
  50. return expression
  51. def make_VariableExpression(self, name):
  52. if name[0].isupper():
  53. return VariableExpression(name)
  54. else:
  55. return ConstantExpression(name)
  56. class Expression(object):
  57. _linear_logic_parser = LinearLogicParser()
  58. @classmethod
  59. def fromstring(cls, s):
  60. return cls._linear_logic_parser.parse(s)
  61. def applyto(self, other, other_indices=None):
  62. return ApplicationExpression(self, other, other_indices)
  63. def __call__(self, other):
  64. return self.applyto(other)
  65. def __repr__(self):
  66. return "<%s %s>" % (self.__class__.__name__, self)
  67. class AtomicExpression(Expression):
  68. def __init__(self, name, dependencies=None):
  69. """
  70. :param name: str for the constant name
  71. :param dependencies: list of int for the indices on which this atom is dependent
  72. """
  73. assert isinstance(name, str)
  74. self.name = name
  75. if not dependencies:
  76. dependencies = []
  77. self.dependencies = dependencies
  78. def simplify(self, bindings=None):
  79. """
  80. If 'self' is bound by 'bindings', return the atomic to which it is bound.
  81. Otherwise, return self.
  82. :param bindings: ``BindingDict`` A dictionary of bindings used to simplify
  83. :return: ``AtomicExpression``
  84. """
  85. if bindings and self in bindings:
  86. return bindings[self]
  87. else:
  88. return self
  89. def compile_pos(self, index_counter, glueFormulaFactory):
  90. """
  91. From Iddo Lev's PhD Dissertation p108-109
  92. :param index_counter: ``Counter`` for unique indices
  93. :param glueFormulaFactory: ``GlueFormula`` for creating new glue formulas
  94. :return: (``Expression``,set) for the compiled linear logic and any newly created glue formulas
  95. """
  96. self.dependencies = []
  97. return (self, [])
  98. def compile_neg(self, index_counter, glueFormulaFactory):
  99. """
  100. From Iddo Lev's PhD Dissertation p108-109
  101. :param index_counter: ``Counter`` for unique indices
  102. :param glueFormulaFactory: ``GlueFormula`` for creating new glue formulas
  103. :return: (``Expression``,set) for the compiled linear logic and any newly created glue formulas
  104. """
  105. self.dependencies = []
  106. return (self, [])
  107. def initialize_labels(self, fstruct):
  108. self.name = fstruct.initialize_label(self.name.lower())
  109. def __eq__(self, other):
  110. return self.__class__ == other.__class__ and self.name == other.name
  111. def __ne__(self, other):
  112. return not self == other
  113. def __str__(self):
  114. accum = self.name
  115. if self.dependencies:
  116. accum += "%s" % self.dependencies
  117. return accum
  118. def __hash__(self):
  119. return hash(self.name)
  120. class ConstantExpression(AtomicExpression):
  121. def unify(self, other, bindings):
  122. """
  123. If 'other' is a constant, then it must be equal to 'self'. If 'other' is a variable,
  124. then it must not be bound to anything other than 'self'.
  125. :param other: ``Expression``
  126. :param bindings: ``BindingDict`` A dictionary of all current bindings
  127. :return: ``BindingDict`` A new combined dictionary of of 'bindings' and any new binding
  128. :raise UnificationException: If 'self' and 'other' cannot be unified in the context of 'bindings'
  129. """
  130. assert isinstance(other, Expression)
  131. if isinstance(other, VariableExpression):
  132. try:
  133. return bindings + BindingDict([(other, self)])
  134. except VariableBindingException:
  135. pass
  136. elif self == other:
  137. return bindings
  138. raise UnificationException(self, other, bindings)
  139. class VariableExpression(AtomicExpression):
  140. def unify(self, other, bindings):
  141. """
  142. 'self' must not be bound to anything other than 'other'.
  143. :param other: ``Expression``
  144. :param bindings: ``BindingDict`` A dictionary of all current bindings
  145. :return: ``BindingDict`` A new combined dictionary of of 'bindings' and the new binding
  146. :raise UnificationException: If 'self' and 'other' cannot be unified in the context of 'bindings'
  147. """
  148. assert isinstance(other, Expression)
  149. try:
  150. if self == other:
  151. return bindings
  152. else:
  153. return bindings + BindingDict([(self, other)])
  154. except VariableBindingException:
  155. raise UnificationException(self, other, bindings)
  156. class ImpExpression(Expression):
  157. def __init__(self, antecedent, consequent):
  158. """
  159. :param antecedent: ``Expression`` for the antecedent
  160. :param consequent: ``Expression`` for the consequent
  161. """
  162. assert isinstance(antecedent, Expression)
  163. assert isinstance(consequent, Expression)
  164. self.antecedent = antecedent
  165. self.consequent = consequent
  166. def simplify(self, bindings=None):
  167. return self.__class__(
  168. self.antecedent.simplify(bindings), self.consequent.simplify(bindings)
  169. )
  170. def unify(self, other, bindings):
  171. """
  172. Both the antecedent and consequent of 'self' and 'other' must unify.
  173. :param other: ``ImpExpression``
  174. :param bindings: ``BindingDict`` A dictionary of all current bindings
  175. :return: ``BindingDict`` A new combined dictionary of of 'bindings' and any new bindings
  176. :raise UnificationException: If 'self' and 'other' cannot be unified in the context of 'bindings'
  177. """
  178. assert isinstance(other, ImpExpression)
  179. try:
  180. return (
  181. bindings
  182. + self.antecedent.unify(other.antecedent, bindings)
  183. + self.consequent.unify(other.consequent, bindings)
  184. )
  185. except VariableBindingException:
  186. raise UnificationException(self, other, bindings)
  187. def compile_pos(self, index_counter, glueFormulaFactory):
  188. """
  189. From Iddo Lev's PhD Dissertation p108-109
  190. :param index_counter: ``Counter`` for unique indices
  191. :param glueFormulaFactory: ``GlueFormula`` for creating new glue formulas
  192. :return: (``Expression``,set) for the compiled linear logic and any newly created glue formulas
  193. """
  194. (a, a_new) = self.antecedent.compile_neg(index_counter, glueFormulaFactory)
  195. (c, c_new) = self.consequent.compile_pos(index_counter, glueFormulaFactory)
  196. return (ImpExpression(a, c), a_new + c_new)
  197. def compile_neg(self, index_counter, glueFormulaFactory):
  198. """
  199. From Iddo Lev's PhD Dissertation p108-109
  200. :param index_counter: ``Counter`` for unique indices
  201. :param glueFormulaFactory: ``GlueFormula`` for creating new glue formulas
  202. :return: (``Expression``,list of ``GlueFormula``) for the compiled linear logic and any newly created glue formulas
  203. """
  204. (a, a_new) = self.antecedent.compile_pos(index_counter, glueFormulaFactory)
  205. (c, c_new) = self.consequent.compile_neg(index_counter, glueFormulaFactory)
  206. fresh_index = index_counter.get()
  207. c.dependencies.append(fresh_index)
  208. new_v = glueFormulaFactory("v%s" % fresh_index, a, set([fresh_index]))
  209. return (c, a_new + c_new + [new_v])
  210. def initialize_labels(self, fstruct):
  211. self.antecedent.initialize_labels(fstruct)
  212. self.consequent.initialize_labels(fstruct)
  213. def __eq__(self, other):
  214. return (
  215. self.__class__ == other.__class__
  216. and self.antecedent == other.antecedent
  217. and self.consequent == other.consequent
  218. )
  219. def __ne__(self, other):
  220. return not self == other
  221. def __str__(self):
  222. return "%s%s %s %s%s" % (
  223. Tokens.OPEN,
  224. self.antecedent,
  225. Tokens.IMP,
  226. self.consequent,
  227. Tokens.CLOSE,
  228. )
  229. def __hash__(self):
  230. return hash(
  231. "%s%s%s" % (hash(self.antecedent), Tokens.IMP, hash(self.consequent))
  232. )
  233. class ApplicationExpression(Expression):
  234. def __init__(self, function, argument, argument_indices=None):
  235. """
  236. :param function: ``Expression`` for the function
  237. :param argument: ``Expression`` for the argument
  238. :param argument_indices: set for the indices of the glue formula from which the argument came
  239. :raise LinearLogicApplicationException: If 'function' cannot be applied to 'argument' given 'argument_indices'.
  240. """
  241. function_simp = function.simplify()
  242. argument_simp = argument.simplify()
  243. assert isinstance(function_simp, ImpExpression)
  244. assert isinstance(argument_simp, Expression)
  245. bindings = BindingDict()
  246. try:
  247. if isinstance(function, ApplicationExpression):
  248. bindings += function.bindings
  249. if isinstance(argument, ApplicationExpression):
  250. bindings += argument.bindings
  251. bindings += function_simp.antecedent.unify(argument_simp, bindings)
  252. except UnificationException as e:
  253. raise LinearLogicApplicationException(
  254. "Cannot apply %s to %s. %s" % (function_simp, argument_simp, e)
  255. )
  256. # If you are running it on complied premises, more conditions apply
  257. if argument_indices:
  258. # A.dependencies of (A -o (B -o C)) must be a proper subset of argument_indices
  259. if not set(function_simp.antecedent.dependencies) < argument_indices:
  260. raise LinearLogicApplicationException(
  261. "Dependencies unfulfilled when attempting to apply Linear Logic formula %s to %s"
  262. % (function_simp, argument_simp)
  263. )
  264. if set(function_simp.antecedent.dependencies) == argument_indices:
  265. raise LinearLogicApplicationException(
  266. "Dependencies not a proper subset of indices when attempting to apply Linear Logic formula %s to %s"
  267. % (function_simp, argument_simp)
  268. )
  269. self.function = function
  270. self.argument = argument
  271. self.bindings = bindings
  272. def simplify(self, bindings=None):
  273. """
  274. Since function is an implication, return its consequent. There should be
  275. no need to check that the application is valid since the checking is done
  276. by the constructor.
  277. :param bindings: ``BindingDict`` A dictionary of bindings used to simplify
  278. :return: ``Expression``
  279. """
  280. if not bindings:
  281. bindings = self.bindings
  282. return self.function.simplify(bindings).consequent
  283. def __eq__(self, other):
  284. return (
  285. self.__class__ == other.__class__
  286. and self.function == other.function
  287. and self.argument == other.argument
  288. )
  289. def __ne__(self, other):
  290. return not self == other
  291. def __str__(self):
  292. return "%s" % self.function + Tokens.OPEN + "%s" % self.argument + Tokens.CLOSE
  293. def __hash__(self):
  294. return hash(
  295. "%s%s%s" % (hash(self.antecedent), Tokens.OPEN, hash(self.consequent))
  296. )
  297. class BindingDict(object):
  298. def __init__(self, bindings=None):
  299. """
  300. :param bindings:
  301. list [(``VariableExpression``, ``AtomicExpression``)] to initialize the dictionary
  302. dict {``VariableExpression``: ``AtomicExpression``} to initialize the dictionary
  303. """
  304. self.d = {}
  305. if isinstance(bindings, dict):
  306. bindings = bindings.items()
  307. if bindings:
  308. for (v, b) in bindings:
  309. self[v] = b
  310. def __setitem__(self, variable, binding):
  311. """
  312. A binding is consistent with the dict if its variable is not already bound, OR if its
  313. variable is already bound to its argument.
  314. :param variable: ``VariableExpression`` The variable bind
  315. :param binding: ``Expression`` The expression to which 'variable' should be bound
  316. :raise VariableBindingException: If the variable cannot be bound in this dictionary
  317. """
  318. assert isinstance(variable, VariableExpression)
  319. assert isinstance(binding, Expression)
  320. assert variable != binding
  321. existing = self.d.get(variable, None)
  322. if not existing or binding == existing:
  323. self.d[variable] = binding
  324. else:
  325. raise VariableBindingException(
  326. "Variable %s already bound to another value" % (variable)
  327. )
  328. def __getitem__(self, variable):
  329. """
  330. Return the expression to which 'variable' is bound
  331. """
  332. assert isinstance(variable, VariableExpression)
  333. intermediate = self.d[variable]
  334. while intermediate:
  335. try:
  336. intermediate = self.d[intermediate]
  337. except KeyError:
  338. return intermediate
  339. def __contains__(self, item):
  340. return item in self.d
  341. def __add__(self, other):
  342. """
  343. :param other: ``BindingDict`` The dict with which to combine self
  344. :return: ``BindingDict`` A new dict containing all the elements of both parameters
  345. :raise VariableBindingException: If the parameter dictionaries are not consistent with each other
  346. """
  347. try:
  348. combined = BindingDict()
  349. for v in self.d:
  350. combined[v] = self.d[v]
  351. for v in other.d:
  352. combined[v] = other.d[v]
  353. return combined
  354. except VariableBindingException:
  355. raise VariableBindingException(
  356. "Attempting to add two contradicting"
  357. " VariableBindingsLists: %s, %s" % (self, other)
  358. )
  359. def __ne__(self, other):
  360. return not self == other
  361. def __eq__(self, other):
  362. if not isinstance(other, BindingDict):
  363. raise TypeError
  364. return self.d == other.d
  365. def __str__(self):
  366. return "{" + ", ".join("%s: %s" % (v, self.d[v]) for v in self.d) + "}"
  367. def __repr__(self):
  368. return "BindingDict: %s" % self
  369. class VariableBindingException(Exception):
  370. pass
  371. class UnificationException(Exception):
  372. def __init__(self, a, b, bindings):
  373. Exception.__init__(self, "Cannot unify %s with %s given %s" % (a, b, bindings))
  374. class LinearLogicApplicationException(Exception):
  375. pass
  376. def demo():
  377. lexpr = Expression.fromstring
  378. print(lexpr(r"f"))
  379. print(lexpr(r"(g -o f)"))
  380. print(lexpr(r"((g -o G) -o G)"))
  381. print(lexpr(r"g -o h -o f"))
  382. print(lexpr(r"(g -o f)(g)").simplify())
  383. print(lexpr(r"(H -o f)(g)").simplify())
  384. print(lexpr(r"((g -o G) -o G)((g -o f))").simplify())
  385. print(lexpr(r"(H -o H)((g -o f))").simplify())
  386. if __name__ == "__main__":
  387. demo()