def test_normal_form(): n = Ty('n') w1, w2 = Word('a', n), Word('b', n) diagram = w1 @ w2 >>\ Id(n) @ Cap(n, n.r) @ Id(n) >> Id(n @ n) @ Cup(n.r, n) expected_result = w1 @ w2 assert expected_result == normal_form(diagram) with raises(ValueError) as err: normal_form(w2 >> w1 @ Id(n))
def generate(self, start, max_sentences, max_depth, max_iter=100, remove_duplicates=False, not_twice=[]): """ Generate sentences from a context-free grammar. Assumes the only terminal symbol is Ty(). Parameters ---------- start : type root of the generated trees. max_sentences : int maximum number of sentences to generate. max_depth : int maximum depth of the trees. max_iter : int maximum number of iterations, set to 100 by default. remove_duplicates : bool if set to True only distinct syntax trees will be generated. not_twice : list list of productions that you don't want appearing twice in a sentence, set to the empty list by default """ prods, cache = list(self.productions), set() n, i = 0, 0 while (not max_sentences or n < max_sentences) and i < max_iter: i += 1 sentence = Id(start) depth = 0 while depth < max_depth: recall = depth if sentence.dom == Ty(): if remove_duplicates and sentence in cache: break yield sentence if remove_duplicates: cache.add(sentence) n += 1 break tag = sentence.dom[0] random.shuffle(prods) for prod in prods: if prod in not_twice and prod in sentence.boxes: continue if Ty(tag) == prod.cod: sentence = sentence << prod @ Id(sentence.dom[1:]) depth += 1 break if recall == depth: # in this case, no production was found break
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)
def generate(self, start, max_sentences, max_depth, max_iter=100, remove_duplicates=False): """ Generate sentences from a context-free grammar. Assumes the only terminal symbol is Ty(). Parameters ---------- start : type root of the generated trees. max_sentences : int maximum number of sentences to generate. max_depth : int maximum depth of the trees. max_iter : int maximum number of iterations, set to 100 by default. remove_duplicates : bool if set to True only distinct syntax trees will be generated. """ prods, cache = list(self.productions), set() n, iter = 0, 0 while n < max_sentences and (iter < max_iter): iter += 1 sentence = Id(start) depth = 0 while depth < max_depth: if sentence.dom == Ty(): if remove_duplicates and sentence in cache: break yield sentence if remove_duplicates: cache.add(sentence) n += 1 break tag = sentence.dom[0] random.shuffle(prods) for prod in prods: if Ty(tag) == prod.cod: sentence = sentence << prod @ Id(sentence.dom[1:]) depth += 1 break
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)
def eager_parse(*words, target=Ty('s')): """ Tries to parse a given list of words in an eager fashion. """ result = fold(lambda x, y: x @ y, 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 test_pregroup_draw_errors(): n = Ty('n') with raises(TypeError): draw(0) with raises(ValueError) as err: draw(Cap(n, n.l)) with raises(ValueError) as err: draw(Cup(n, n.r)) with raises(ValueError) as err: draw(Word('Alice', n) >> Word('Alice', n) @ Id(n)) assert str(err.value) is messages.expected_pregroup()
def test_CFG(): s, n, v, vp = Ty('S'), Ty('N'), Ty('V'), Ty('VP') R0, R1 = Box('R0', vp @ n, s), Box('R1', n @ v, vp) Jane, loves, Bob = Word('Jane', n), Word('loves', v), Word('Bob', n) cfg = CFG(R0, R1, Jane, loves, Bob) assert Jane in cfg.productions assert "CFG(Box('R0', Ty('VP', 'N'), Ty('S'))" in repr(cfg) assert not list(CFG().generate(start=s, max_sentences=1, max_depth=1)) sentence, *_ = cfg.generate( start=s, max_sentences=1, max_depth=10, not_twice=[Jane, Bob], seed=42) assert sentence\ == (Jane @ loves @ Bob).normal_form(left=True) >> R1 @ Id(n) >> R0
def test_brute_force(): s, n = Ty('s'), Ty('n') Alice = Word('Alice', n) loves = Word('loves', n.r @ s @ n.l) Bob = Word('Bob', n) grammar = Cup(n, n.r) @ Id(s) @ Cup(n.l, n) gen = brute_force(Alice, loves, Bob) assert next(gen) == Alice @ loves @ Alice >> grammar assert next(gen) == Alice @ loves @ Bob >> grammar assert next(gen) == Bob @ loves @ Alice >> grammar assert next(gen) == Bob @ loves @ Bob >> grammar gen = brute_force(Alice, loves, Bob, target=n) assert next(gen) == Word('Alice', Ty('n')) assert next(gen) == Word('Bob', Ty('n'))
def test_eager_parse(): s, n = Ty('s'), Ty('n') Alice = Word('Alice', n) loves = Word('loves', n.r @ s @ n.l) Bob = Word('Bob', n) grammar = Cup(n, n.r) @ Id(s) @ Cup(n.l, n) assert eager_parse(Alice, loves, Bob) == grammar << Alice @ loves @ Bob who = Word('who', n.r @ n @ s.l @ n) assert eager_parse(Bob, who, loves, Alice, target=n).offsets ==\ [0, 1, 5, 8, 0, 2, 1, 1] with raises(NotImplementedError): eager_parse(Alice, Bob, loves) with raises(NotImplementedError): eager_parse(Alice, loves, Bob, who, loves, Alice)
def test_from_tree(): s, n = Ty('s'), Ty('n') Alice, Bob = Word('Alice', n), Word('Bob', n) loves = Word('loves', n.r @ s @ n.l) sentence = Alice @ loves @ Bob >> Cup(n, n.r) @ Id(s) @ Cup(n.l, n) sentence == from_tree(sentence.to_tree())