def format(tree: Tree, indent: Union[int, None] = -1, compact: bool = False) -> str: """ Format *tree* into a PENMAN string. Args: tree: a Tree object indent: how to indent formatted strings compact: if ``True``, put initial attributes on the first line Returns: the PENMAN-serialized string of the Tree *t* Example: >>> import penman >>> print(penman.format( ... ('b', [('/', 'bark-01'), ... (':ARG0', ('d', [('/', 'dog')]))]))) (b / bark-01 :ARG0 (d / dog)) """ if not isinstance(tree, Tree): tree = Tree(tree) vars = [var for var, _ in tree.nodes()] if compact else [] parts = ['# ::{}{}'.format(key, ' ' + value if value else value) for key, value in tree.metadata.items()] parts.append(_format_node(tree.node, indent, 0, set(vars))) return '\n'.join(parts)
def test_issue_92(): # https://github.com/goodmami/penman/issues/92 g = codec.decode('(a / alpha :ARG0~e.0 (b / beta))') assert configure(g) == Tree(('a', [('/', 'alpha'), (':ARG0~e.0', ('b', [('/', 'beta')]))])) assert configure(g, top='b') == Tree( ('b', [('/', 'beta'), (':ARG0-of~e.0', ('a', [('/', 'alpha')]))]))
def format(self, tree: Tree, indent: Union[int, None] = -1, compact: bool = False) -> str: """ Format *tree* into a PENMAN string. """ if not isinstance(tree, Tree): tree = Tree(tree) vars = [var for var, _ in tree.nodes()] if compact else [] parts = [ '# ::{}{}'.format(key, ' ' + value if value else value) for key, value in tree.metadata.items() ] parts.append(self._format_node(tree.node, indent, 0, set(vars))) return '\n'.join(parts)
def canonicalize_roles(t: Tree, model: Model) -> Tree: """ Normalize roles in *t* so they are canonical according to *model*. This is a tree transformation instead of a graph transformation because the orientation of the pure graph's triples is not decided until the graph is configured into a tree. Args: t: a :class:`Tree` object model: a model defining role normalizations Returns: A new :class:`Tree` object with canonicalized roles. Example: >>> from penman.codec import PENMANCodec >>> from penman.models.amr import model >>> from penman.transform import canonicalize_roles >>> codec = PENMANCodec() >>> t = codec.parse('(c / chapter :domain-of 7)') >>> t = canonicalize_roles(t, model) >>> print(codec.format(t)) (c / chapter :mod 7) """ if model is None: model = Model() tree = Tree(_canonicalize_node(t.node, model), metadata=t.metadata) logger.info('Canonicalized roles: %s', tree) return tree
def configure(g: Graph, top: Variable = None, model: Model = None) -> Tree: """ Create a tree from a graph by making as few decisions as possible. A graph interpreted from a valid tree using :func:`interpret` will contain epigraphical markers that describe how the triples of a graph are to be expressed in a tree, and thus configuring this tree requires only a single pass through the list of triples. If the markers are missing or out of order, or if the graph has been modified, then the configuration process will have to make decisions about where to insert tree branches. These decisions are deterministic, but may result in a tree different than the one expected. Args: g: the :class:`~penman.graph.Graph` to configure top: the variable to use as the top of the graph; if ``None``, the top of *g* will be used model: the :class:`~penman.model.Model` used to configure the tree Returns: The configured :class:`~penman.tree.Tree`. Example: >>> from penman.graph import Graph >>> from penman import layout >>> g = Graph([('b', ':instance', 'bark-01'), ... ('b', ':ARG0', 'd'), ... ('d', ':instance', 'dog')]) >>> t = layout.configure(g) >>> print(t) Tree( ('b', [ ('/', 'bark-01'), (':ARG0', ('d', [ ('/', 'dog')]))])) """ if model is None: model = _default_model node, data, nodemap = _configure(g, top, model) # remove any superfluous POPs at the end (maybe from dereification) while data and data[-1] is POP: data.pop() # if any data remain, the graph was not properly annotated for a tree while data: skipped, var, data = _find_next(data, nodemap) data_count = len(data) if var is None or data_count == 0: raise LayoutError('possibly disconnected graph') _configure_node(var, data, nodemap, model) if len(data) >= data_count: raise LayoutError('possible cycle in configuration') data = skipped + data # remove any superfluous POPs while data and data[-1] is POP: data.pop() tree = Tree(node, metadata=g.metadata) logger.debug('Configured: %s', tree) return tree
def test_issue_90(): # https://github.com/goodmami/penman/issues/90 g = Graph([('i', ':instance', 'iota'), ('i2', ':instance', 'i'), ('i', ':ARG0', 'i2')], top='i') assert reconfigure(g) == Tree(('i', [('/', 'iota'), (':ARG0', ('i2', [('/', 'i')]))]))
def test_configure(amr_model): g = codec.decode('(a / A)') assert configure(g) == Tree(('a', [('/', 'A')])) with pytest.raises(LayoutError): configure(g, top='A') g = codec.decode('(a / A :consist-of (b / B))') assert configure(g) == Tree(('a', [('/', 'A'), (':consist-of', ('b', [('/', 'B')]))])) assert configure(g, top='b') == Tree( ('b', [('/', 'B'), (':consist', ('a', [('/', 'A')]))])) amr_codec = PENMANCodec(model=amr_model) g = amr_codec.decode('(a / A :consist-of (b / B))') assert configure(g, model=amr_model) == Tree( ('a', [('/', 'A'), (':consist-of', ('b', [('/', 'B')]))])) assert configure(g, top='b', model=amr_model) == Tree( ('b', [('/', 'B'), (':consist-of-of', ('a', [('/', 'A')]))]))
def test_reconfigure(): g = codec.decode(''' (a / alpha :ARG0 b :ARG1 (g / gamma :ARG0-of (b / beta)))''') # original order reconfiguration puts node definitions at first # appearance of a variable assert reconfigure(g) == Tree( ('a', [('/', 'alpha'), (':ARG0', ('b', [('/', 'beta')])), (':ARG1', ('g', [('/', 'gamma'), (':ARG0-of', 'b')]))])) # canonical order reconfiguration can also shift things like # inverted arguments assert reconfigure(g, key=model.canonical_order) == Tree( ('a', [('/', 'alpha'), (':ARG0', ('b', [('/', 'beta'), (':ARG0', ('g', [('/', 'gamma')]))])), (':ARG1', 'g')]))
def interpret(t: Tree, model: Model = None) -> Graph: """ Interpret tree *t* as a graph using *model*. Tree interpretation is the process of transforming the nodes and edges of a tree into a directed graph. A semantic model determines which edges are inverted and how to deinvert them. If *model* is not provided, the default model will be used. Args: t: the :class:`~penman.tree.Tree` to interpret model: the :class:`~penman.model.Model` used to interpret *t* Returns: The interpreted :class:`~penman.graph.Graph`. Example: >>> from penman.tree import Tree >>> from penman import layout >>> t = Tree( ... ('b', [ ... ('/', 'bark-01'), ... ('ARG0', ('d', [ ... ('/', 'dog')]))])) >>> g = layout.interpret(t) >>> for triple in g.triples: ... print(triple) ... ('b', ':instance', 'bark-01') ('b', ':ARG0', 'd') ('d', ':instance', 'dog') """ if model is None: model = _default_model variables = {v for v, _ in t.nodes()} top, triples, epidata = _interpret_node(t.node, variables, model) epimap = {} for triple, epis in epidata: if triple in epimap: logger.warning( f'ignoring epigraph data for duplicate triple: {triple}' ) else: epimap[triple] = epis g = Graph(triples, top=top, epidata=epimap, metadata=t.metadata) logger.info('Interpreted: %s', g) return g
def rearrange(t: Tree, key: Callable[[Role], Any] = None, attributes_first: bool = False) -> None: """ Sort the branches at each node in tree *t* according to *key*. Each node in a tree contains a list of branches. This function sorts those lists in-place using the *key* function, which accepts a role and returns some sortable criterion. If the *attributes_first* argument is ``True``, attribute branches are appear before any edges. Instance branches (``/``) always appear before any other branches. Example: >>> from penman import layout >>> from penman.model import Model >>> from penman.codec import PENMANCodec >>> c = PENMANCodec() >>> t = c.parse( ... '(s / see-01' ... ' :ARG1 (c / cat)' ... ' :ARG0 (d / dog))') >>> layout.rearrange(t, key=Model().canonical_order) >>> print(c.format(t)) (s / see-01 :ARG0 (d / dog) :ARG1 (c / cat)) """ if attributes_first: variables = {node[0] for node in t.nodes()} else: variables = set() def sort_key(branch: Branch): role, target = branch if is_atomic(target): criterion1 = target in variables else: criterion1 = target[0] in variables criterion2 = True if key is None else key(role) return (criterion1, criterion2) _rearrange(t.node, sort_key)
def test_issue_34(): # https://github.com/goodmami/penman/issues/34 g = codec.decode(''' # ::snt I think you failed to not not act. (t / think :ARG0 (i / i) :ARG1 (f / fail :ARG0 (y / you) :ARG1 (a / act :polarity - :polarity -)))''') assert configure(g) == Tree( ('t', [('/', 'think'), (':ARG0', ('i', [('/', 'i')])), (':ARG1', ('f', [('/', 'fail'), (':ARG0', ('y', [('/', 'you')])), (':ARG1', ('a', [('/', 'act'), (':polarity', '-'), (':polarity', '-')]))]))]))
def _parse(self, tokens: TokenIterator) -> Tree: metadata = self._parse_comments(tokens) node = self._parse_node(tokens) tree = Tree(node, metadata=metadata) logger.debug('Parsed: %s', tree) return tree
def test_issue_93(): # https://github.com/goodmami/penman/issues/93 g = codec.decode('(a / alpha :ARG0 b~1)') g.triples.append(('b', ':instance', 'beta')) assert configure(g) == Tree(('a', [('/', 'alpha'), (':ARG0', ('b', [('/', 'beta')]))]))