penman.layout#

Interpreting trees to graphs and configuring graphs to trees.

In order to serialize graphs into the PENMAN format, a tree-like layout of the graph must be decided. Deciding a layout includes choosing the order of the edges from a node and the paths to get to a node definition (the position in the tree where a node’s concept and edges are specified). For instance, the following graphs for “The dog barked loudly” have different edge orders on the b node:

(b / bark-01           (b / bark-01
   :ARG0 (d / dog)        :mod (l / loud)
   :mod (l / loud))       :ARG0 (d / dog))

With re-entrancies, there are choices about which location of a re-entrant node gets the full definition with its concept (node label), etc. For instance, the following graphs for “The dog tried to bark” have different locations for the definition of the d node:

(t / try-01              (t / try-01
   :ARG0 (d / dog)          :ARG0 d
   :ARG1 (b / bark-01       :ARG1 (b / bark-01
      :ARG0 d))                :ARG0 (d / dog))

With inverted edges, there are even more possibilities, such as:

(t / try-01                (t / try-01
   :ARG0 (d / dog             :ARG1 (b / bark-01
      :ARG0-of b)                :ARG0 (d / dog
   :ARG1 (b / bark-01))             :ARG0-of t)))

This module introduces two epigraphical markers so that a pure graph parsed from PENMAN can retain information about its tree layout without altering its graph properties. The first marker type is Push, which is put on a triple to indicate that the triple introduces a new node context, while the sentinel POP indicates that a triple is at the end of one or more node contexts. These markers only work if the triples in the graph’s data are ordered. For instance, one of the graphs above (repeated here) has the following data:

PENMAN                 Graph                            Epigraph
(t / try-01            [('t', ':instance', 'try-01'),   :
   :ARG0 (d / dog)      ('t', ':ARG0', 'd'),            : Push('d')
   :ARG1 (b / bark-01   ('d', ':instance', 'dog'),      : POP
      :ARG0 d))         ('t', ':ARG1', 'b'),            : Push('b')
                        ('b', ':instance', 'bark-01'),  :
                        ('b', ':ARG0', 'd')]            : POP

Epigraphical Markers#

class penman.layout.LayoutMarker[source]#

Bases: Epidatum

Epigraph marker for layout choices.

class penman.layout.Push(variable)[source]#

Bases: LayoutMarker

Epigraph marker to indicate a new node context.

class penman.layout.Pop[source]#

Bases: LayoutMarker

Epigraph marker to indicate the end of a node context.

penman.layout.POP = POP#

A singleton instance of Pop.

Using the POP singleton can help reduce memory usage and processing time when working with many graphs, but it should not be checked for object identity, such as if x is POP, when working with multiple processes because each process gets its own instance. Instead, use a type check such as isinstance(x, Pop).

Tree Functions#

penman.layout.interpret(t, model=None)[source]#

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.

Parameters:
  • t – the Tree to interpret

  • model – the Model used to interpret t

Returns:

The interpreted 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')
penman.layout.rearrange(t, key=None, attributes_first=False)[source]#

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))

Graph Functions#

penman.layout.configure(g, top=None, model=None)[source]#

Create a tree from a graph by making as few decisions as possible.

A graph interpreted from a valid tree using 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.

Parameters:
  • g – the 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 Model used to configure the tree

Returns:

The configured 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')]))]))
penman.layout.reconfigure(g, top=None, model=None, key=None)[source]#

Create a tree from a graph after any discarding layout markers.

If key is provided, triples are sorted according to the key.

Diagnostic Functions#

penman.layout.get_pushed_variable(g, triple)[source]#

Return the variable pushed by triple, if any, otherwise None.

Example

>>> from penman import decode
>>> from penman.layout import get_pushed_variable
>>> g = decode('(a / alpha :ARG0 (b / beta))')
>>> get_pushed_variable(g, ('a', ':instance', 'alpha'))  # None
>>> get_pushed_variable(g, ('a', ':ARG0', 'b'))
'b'
penman.layout.appears_inverted(g, triple)[source]#

Return True if triple appears inverted in serialization.

More specifically, this function returns True if triple has a Push epigraphical marker in graph g whose associated variable is the source variable of triple. This should be accurate when testing a triple in a graph interpreted using interpret() (including PENMANCodec.decode, etc.), but it does not guarantee that a new serialization of g will express triple as inverted as it can change if the graph or its epigraphical markers are modified, if a new top is chosen, etc.

Parameters:
  • g – a Graph containing triple

  • triple – the triple that does or does not appear inverted

Returns:

True if triple appears inverted in graph g.

penman.layout.node_contexts(g)[source]#

Return the list of node contexts corresponding to triples in g.

If a node context is unknown, the value None is substituted.

Example

>>> from penman import decode, layout
>>> g = decode('''
...   (a / alpha
...      :attr val
...      :ARG0 (b / beta :ARG0 (g / gamma))
...      :ARG0-of g)''')
>>> for ctx, trp in zip(layout.node_contexts(g), g.triples):
...     print(ctx, ':', trp)
...
a : ('a', ':instance', 'alpha')
a : ('a', ':attr', 'val')
a : ('a', ':ARG0', 'b')
b : ('b', ':instance', 'beta')
b : ('b', ':ARG0', 'g')
g : ('g', ':instance', 'gamma')
a : ('g', ':ARG0', 'a')