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 _preconfigure(g, strict): """ Arrange the triples and epidata for ordered traversal. Also perform some basic validation. """ data = [] epidata = g.epidata pushed = set() for triple in g.triples: var, role, target = triple push, pops = None, [] for epi in epidata.get(triple, []): if isinstance(epi, Push): if push is not None or epi.variable in pushed: if strict: raise LayoutError( f"multiple node contexts for '{epi.variable}'") pass # change to 'continue' to disallow multiple contexts if epi.variable not in (triple[0], triple[2]): if strict: raise LayoutError( f"node context '{epi.variable}' " f"invalid for triple: {triple!r}") continue pushed.add(epi.variable) push = epi elif epi is POP: pops.append(epi) elif epi.mode == 1: # role epidata role = f'{role!s}{epi!s}' elif target and epi.mode == 2: # target epidata target = f'{target!s}{epi!s}' else: logging.warning('epigraphical marker lost: %r', epi) if strict and push and pops: raise LayoutError( f'incompatible node context changes on triple: {triple!r}') data.append(((var, role, target), push)) data.extend(pops) return data
def _configure(g, top, model, strict): """ Create the tree that can be created without any improvising. """ if len(g.triples) == 0: return (g.top, []), [], {} nodemap: _Nodemap = {var: None for var in g.variables()} if top is None: top = g.top if top not in nodemap: raise LayoutError(f'top is not a variable: {top!r}') nodemap[top] = (top, []) data = list(reversed(_preconfigure(g, strict))) node = _configure_node(top, data, nodemap, model) return node, data, nodemap