combinator.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. # Natural Language Toolkit: Combinatory Categorial Grammar
  2. #
  3. # Copyright (C) 2001-2020 NLTK Project
  4. # Author: Graeme Gange <ggange@csse.unimelb.edu.au>
  5. # URL: <http://nltk.org/>
  6. # For license information, see LICENSE.TXT
  7. """
  8. CCG Combinators
  9. """
  10. from abc import ABCMeta, abstractmethod
  11. from nltk.ccg.api import FunctionalCategory
  12. class UndirectedBinaryCombinator(metaclass=ABCMeta):
  13. """
  14. Abstract class for representing a binary combinator.
  15. Merely defines functions for checking if the function and argument
  16. are able to be combined, and what the resulting category is.
  17. Note that as no assumptions are made as to direction, the unrestricted
  18. combinators can perform all backward, forward and crossed variations
  19. of the combinators; these restrictions must be added in the rule
  20. class.
  21. """
  22. @abstractmethod
  23. def can_combine(self, function, argument):
  24. pass
  25. @abstractmethod
  26. def combine(self, function, argument):
  27. pass
  28. class DirectedBinaryCombinator(metaclass=ABCMeta):
  29. """
  30. Wrapper for the undirected binary combinator.
  31. It takes left and right categories, and decides which is to be
  32. the function, and which the argument.
  33. It then decides whether or not they can be combined.
  34. """
  35. @abstractmethod
  36. def can_combine(self, left, right):
  37. pass
  38. @abstractmethod
  39. def combine(self, left, right):
  40. pass
  41. class ForwardCombinator(DirectedBinaryCombinator):
  42. """
  43. Class representing combinators where the primary functor is on the left.
  44. Takes an undirected combinator, and a predicate which adds constraints
  45. restricting the cases in which it may apply.
  46. """
  47. def __init__(self, combinator, predicate, suffix=""):
  48. self._combinator = combinator
  49. self._predicate = predicate
  50. self._suffix = suffix
  51. def can_combine(self, left, right):
  52. return self._combinator.can_combine(left, right) and self._predicate(
  53. left, right
  54. )
  55. def combine(self, left, right):
  56. for cat in self._combinator.combine(left, right):
  57. yield cat
  58. def __str__(self):
  59. return ">%s%s" % (self._combinator, self._suffix)
  60. class BackwardCombinator(DirectedBinaryCombinator):
  61. """
  62. The backward equivalent of the ForwardCombinator class.
  63. """
  64. def __init__(self, combinator, predicate, suffix=""):
  65. self._combinator = combinator
  66. self._predicate = predicate
  67. self._suffix = suffix
  68. def can_combine(self, left, right):
  69. return self._combinator.can_combine(right, left) and self._predicate(
  70. left, right
  71. )
  72. def combine(self, left, right):
  73. for cat in self._combinator.combine(right, left):
  74. yield cat
  75. def __str__(self):
  76. return "<%s%s" % (self._combinator, self._suffix)
  77. class UndirectedFunctionApplication(UndirectedBinaryCombinator):
  78. """
  79. Class representing function application.
  80. Implements rules of the form:
  81. X/Y Y -> X (>)
  82. And the corresponding backwards application rule
  83. """
  84. def can_combine(self, function, argument):
  85. if not function.is_function():
  86. return False
  87. return not function.arg().can_unify(argument) is None
  88. def combine(self, function, argument):
  89. if not function.is_function():
  90. return
  91. subs = function.arg().can_unify(argument)
  92. if subs is None:
  93. return
  94. yield function.res().substitute(subs)
  95. def __str__(self):
  96. return ""
  97. # Predicates for function application.
  98. # Ensures the left functor takes an argument on the right
  99. def forwardOnly(left, right):
  100. return left.dir().is_forward()
  101. # Ensures the right functor takes an argument on the left
  102. def backwardOnly(left, right):
  103. return right.dir().is_backward()
  104. # Application combinator instances
  105. ForwardApplication = ForwardCombinator(UndirectedFunctionApplication(), forwardOnly)
  106. BackwardApplication = BackwardCombinator(UndirectedFunctionApplication(), backwardOnly)
  107. class UndirectedComposition(UndirectedBinaryCombinator):
  108. """
  109. Functional composition (harmonic) combinator.
  110. Implements rules of the form
  111. X/Y Y/Z -> X/Z (B>)
  112. And the corresponding backwards and crossed variations.
  113. """
  114. def can_combine(self, function, argument):
  115. # Can only combine two functions, and both functions must
  116. # allow composition.
  117. if not (function.is_function() and argument.is_function()):
  118. return False
  119. if function.dir().can_compose() and argument.dir().can_compose():
  120. return not function.arg().can_unify(argument.res()) is None
  121. return False
  122. def combine(self, function, argument):
  123. if not (function.is_function() and argument.is_function()):
  124. return
  125. if function.dir().can_compose() and argument.dir().can_compose():
  126. subs = function.arg().can_unify(argument.res())
  127. if subs is not None:
  128. yield FunctionalCategory(
  129. function.res().substitute(subs),
  130. argument.arg().substitute(subs),
  131. argument.dir(),
  132. )
  133. def __str__(self):
  134. return "B"
  135. # Predicates for restricting application of straight composition.
  136. def bothForward(left, right):
  137. return left.dir().is_forward() and right.dir().is_forward()
  138. def bothBackward(left, right):
  139. return left.dir().is_backward() and right.dir().is_backward()
  140. # Predicates for crossed composition
  141. def crossedDirs(left, right):
  142. return left.dir().is_forward() and right.dir().is_backward()
  143. def backwardBxConstraint(left, right):
  144. # The functors must be crossed inwards
  145. if not crossedDirs(left, right):
  146. return False
  147. # Permuting combinators must be allowed
  148. if not left.dir().can_cross() and right.dir().can_cross():
  149. return False
  150. # The resulting argument category is restricted to be primitive
  151. return left.arg().is_primitive()
  152. # Straight composition combinators
  153. ForwardComposition = ForwardCombinator(UndirectedComposition(), forwardOnly)
  154. BackwardComposition = BackwardCombinator(UndirectedComposition(), backwardOnly)
  155. # Backward crossed composition
  156. BackwardBx = BackwardCombinator(
  157. UndirectedComposition(), backwardBxConstraint, suffix="x"
  158. )
  159. class UndirectedSubstitution(UndirectedBinaryCombinator):
  160. """
  161. Substitution (permutation) combinator.
  162. Implements rules of the form
  163. Y/Z (X\Y)/Z -> X/Z (<Sx)
  164. And other variations.
  165. """
  166. def can_combine(self, function, argument):
  167. if function.is_primitive() or argument.is_primitive():
  168. return False
  169. # These could potentially be moved to the predicates, as the
  170. # constraints may not be general to all languages.
  171. if function.res().is_primitive():
  172. return False
  173. if not function.arg().is_primitive():
  174. return False
  175. if not (function.dir().can_compose() and argument.dir().can_compose()):
  176. return False
  177. return (function.res().arg() == argument.res()) and (
  178. function.arg() == argument.arg()
  179. )
  180. def combine(self, function, argument):
  181. if self.can_combine(function, argument):
  182. yield FunctionalCategory(
  183. function.res().res(), argument.arg(), argument.dir()
  184. )
  185. def __str__(self):
  186. return "S"
  187. # Predicate for forward substitution
  188. def forwardSConstraint(left, right):
  189. if not bothForward(left, right):
  190. return False
  191. return left.res().dir().is_forward() and left.arg().is_primitive()
  192. # Predicate for backward crossed substitution
  193. def backwardSxConstraint(left, right):
  194. if not left.dir().can_cross() and right.dir().can_cross():
  195. return False
  196. if not bothForward(left, right):
  197. return False
  198. return right.res().dir().is_backward() and right.arg().is_primitive()
  199. # Instances of substitution combinators
  200. ForwardSubstitution = ForwardCombinator(UndirectedSubstitution(), forwardSConstraint)
  201. BackwardSx = BackwardCombinator(UndirectedSubstitution(), backwardSxConstraint, "x")
  202. # Retrieves the left-most functional category.
  203. # ie, (N\N)/(S/NP) => N\N
  204. def innermostFunction(categ):
  205. while categ.res().is_function():
  206. categ = categ.res()
  207. return categ
  208. class UndirectedTypeRaise(UndirectedBinaryCombinator):
  209. """
  210. Undirected combinator for type raising.
  211. """
  212. def can_combine(self, function, arg):
  213. # The argument must be a function.
  214. # The restriction that arg.res() must be a function
  215. # merely reduces redundant type-raising; if arg.res() is
  216. # primitive, we have:
  217. # X Y\X =>(<T) Y/(Y\X) Y\X =>(>) Y
  218. # which is equivalent to
  219. # X Y\X =>(<) Y
  220. if not (arg.is_function() and arg.res().is_function()):
  221. return False
  222. arg = innermostFunction(arg)
  223. # left, arg_categ are undefined!
  224. subs = left.can_unify(arg_categ.arg())
  225. if subs is not None:
  226. return True
  227. return False
  228. def combine(self, function, arg):
  229. if not (
  230. function.is_primitive() and arg.is_function() and arg.res().is_function()
  231. ):
  232. return
  233. # Type-raising matches only the innermost application.
  234. arg = innermostFunction(arg)
  235. subs = function.can_unify(arg.arg())
  236. if subs is not None:
  237. xcat = arg.res().substitute(subs)
  238. yield FunctionalCategory(
  239. xcat, FunctionalCategory(xcat, function, arg.dir()), -(arg.dir())
  240. )
  241. def __str__(self):
  242. return "T"
  243. # Predicates for type-raising
  244. # The direction of the innermost category must be towards
  245. # the primary functor.
  246. # The restriction that the variable must be primitive is not
  247. # common to all versions of CCGs; some authors have other restrictions.
  248. def forwardTConstraint(left, right):
  249. arg = innermostFunction(right)
  250. return arg.dir().is_backward() and arg.res().is_primitive()
  251. def backwardTConstraint(left, right):
  252. arg = innermostFunction(left)
  253. return arg.dir().is_forward() and arg.res().is_primitive()
  254. # Instances of type-raising combinators
  255. ForwardT = ForwardCombinator(UndirectedTypeRaise(), forwardTConstraint)
  256. BackwardT = BackwardCombinator(UndirectedTypeRaise(), backwardTConstraint)