# -*- coding: utf-8 -*-
"""
Implements pregroup grammars and distributional compositional models.
>>> from discopy.tensor import Functor
>>> from discopy.rigid import Ty
>>> s, n = Ty('s'), Ty('n')
>>> Alice, Bob = Word('Alice', n), Word('Bob', n)
>>> loves = Word('loves', n.r @ s @ n.l)
>>> grammar = Cup(n, n.r) @ Id(s) @ Cup(n.l, n)
>>> sentence = grammar << Alice @ loves @ Bob
>>> ob = {s: 1, n: 2}
>>> ar = {Alice: [1, 0], loves: [0, 1, 1, 0], Bob: [0, 1]}
>>> F = Functor(ob, ar)
>>> assert F(sentence) == True
>>> draw(sentence, figsize=(4, 2),\\
... path='docs/_static/imgs/grammar/pregroup-example.png')
.. image:: ../_static/imgs/grammar/pregroup-example.png
:align: center
"""
from discopy import messages, drawing, rewriting, monoidal
from discopy.grammar import cfg
from discopy.rigid import Ty, Box, Diagram, Id, Cup, Cap, Swap
[docs]class Word(cfg.Word, Box):
""" Word with a :class:`discopy.rigid.Ty` as codomain. """
def __repr__(self):
extra = ", dom={}".format(repr(self.dom)) if self.dom else ""
extra += ", _dagger=True" if self._dagger else ""
extra += ", _z={}".format(self._z) if self._z != 0 else ""
return "Word({}, {}{})".format(
repr(self.name), repr(self.cod), extra)
[docs]def eager_parse(*words, target=Ty('s')):
"""
Tries to parse a given list of words in an eager fashion.
"""
result = Id(Ty()).tensor(*words)
scan = result.cod
while True:
fail = True
for i in range(len(scan) - 1):
if scan[i: i + 1].r != scan[i + 1: i + 2]:
continue
cup = Cup(scan[i: i + 1], scan[i + 1: i + 2])
result = result >> Id(scan[: i]) @ cup @ Id(scan[i + 2:])
scan, fail = result.cod, False
break
if result.cod == target:
return result
if fail:
raise NotImplementedError
def normal_form(diagram, normalizer=None, **params):
"""
Applies normal form to a pregroup diagram of the form
`word @ ... @ word >> cups` by normalising the words and the sentences
seperately before combining them, so it can be drawn using `grammar.draw`.
"""
words, is_pregroup = Id(Ty()), True
for _, box, right in diagram.layers:
if isinstance(box, Word):
if right: # word boxes should be tensored left to right.
is_pregroup = False
break
words = words @ box
else:
break
wires = diagram[len(words):]
is_pregroup = is_pregroup and all(
isinstance(box, Cup) or isinstance(box, Swap) or isinstance(box, Cap)
for box in wires.boxes)
if not is_pregroup:
raise ValueError(messages.expected_pregroup())
norm = lambda d: monoidal.Diagram.normal_form(
d, normalizer=normalizer or Diagram.normalize, **params)
return norm(words) >> norm(wires)
normalize = rewriting.snake_removal
[docs]def brute_force(*vocab, target=Ty('s')):
"""
Given a vocabulary, search for grammatical sentences.
"""
test = [()]
for words in test:
for word in vocab:
try:
yield eager_parse(*(words + (word, )), target=target)
except NotImplementedError:
pass
test.append(words + (word, ))
[docs]def draw(diagram, **params):
"""
Draws a pregroup diagram, i.e. of shape :code:`word @ ... @ word >> cups`.
Parameters
----------
width : float, optional
Width of the word triangles, default is :code:`2.0`.
space : float, optional
Space between word triangles, default is :code:`0.5`.
textpad : pair of floats, optional
Padding between text and wires, default is :code:`(0.1, 0.2)`.
draw_type_labels : bool, optional
Whether to draw type labels, default is :code:`True`.
aspect : string, optional
Aspect ratio, one of :code:`['equal', 'auto']`.
margins : tuple, optional
Margins, default is :code:`(0.05, 0.05)`.
fontsize : int, optional
Font size for the words, default is :code:`12`.
fontsize_types : int, optional
Font size for the types, default is :code:`12`.
figsize : tuple, optional
Figure size.
path : str, optional
Where to save the image, if :code:`None` we call :code:`plt.show()`.
pretty_types : bool, optional
Whether to draw type labels with superscript, default is :code:`False`.
triangles : bool, optional
Whether to draw words as triangular states, default is :code:`False`.
Raises
------
ValueError
Whenever the input is not a pregroup diagram.
"""
from discopy.rigid import Swap
if not isinstance(diagram, Diagram):
raise TypeError(messages.type_err(Diagram, diagram))
words, is_pregroup = Id(Ty()), True
for _, box, right in diagram.layers:
if isinstance(box, Word):
if right: # word boxes should be tensored left to right.
is_pregroup = False
break
words = words @ box
else:
break
cups = diagram[len(words):].foliation().boxes\
if len(words) < len(diagram) else []
is_pregroup = is_pregroup and words and all(
isinstance(box, Cup) or isinstance(box, Swap)
for s in cups for box in s.boxes)
if not is_pregroup:
raise ValueError(messages.expected_pregroup())
drawing.pregroup_draw(words, cups, **params)