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
Function: interpret
▸ interpret(t
, options?
): 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.
The options
param consists of an object with a single model
property.
Example
import { Tree, interpret } from 'penman-js';
const t = new Tree(['b', [
['/', 'bark-01'],
['ARG0', ['d', [
['/', 'dog']
]]]
]]);
const g = interpret(t);
for (const triple of g.triples) {
console.log(triple);
}
// ['b', ':instance', 'bark-01']
// ['b', ':ARG0', 'd']
// ['d', ':instance', 'dog']
Function: rearrange
▸ rearrange(t
, options?
): void
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.
options
consists of an object with optional key
and attributesFirst
properties.
- If the
attributesFirst
argument istrue
, attribute branches will appear before any edges. key
is a function used for sorting branches.
Instance branches (/
) always appear before any other branches.
Example
import { rearrange, Model, PENMANCodec } from 'penman-js';
const c = new PENMANCodec();
const t = c.parse(`
(s / see-01
:ARG1 (c / cat)
:ARG0 (d / dog))`);
rearrange(t, { key: Model().canonicalOrder, attributesFirst: true });
console.log(c.format(t));
// (s / see-01
// :ARG0 (d / dog)
// :ARG1 (c / cat))
Function: configure
▸ configure(g
, options?
): Tree
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.
options
consists of an object with optional top
and model
properties.
top
is the variable to use as the top of the graph; ifnull
, the top ofg
will be used.model
is theModel
used to configure the tree.
Example
import { Graph, configure } from 'penman-js';
const g = new Graph([
['b', ':instance', 'bark-01'],
['b', ':ARG0', 'd'],
['d', ':instance', 'dog']
]);
const t = configure(g);
console.log(t);
// Tree('b', [['/', 'bark-01'], [':ARG0', ['d', [['/', 'dog']]]]])
Function: reconfigure
▸ reconfigure(graph
, options?
): Tree
Create a tree from a graph after any discarding layout markers.
options
consists of an object with optional top
, model
, and key
properties.
If key
is provided, triples are sorted according to the key.
Function: getPushedVariable
▸ getPushedVariable(g
, triple
): Variable
| null
Return the variable pushed by triple
, if any, otherwise null
.
Example
import { decode, getPushedVariable } from 'penman-js';
const g = decode('(a / alpha :ARG0 (b / beta))');
console.log(getPushedVariable(g, ['a', ':instance', 'alpha'])); // Outputs: null
console.log(getPushedVariable(g, ['a', ':ARG0', 'b'])); // Outputs: 'b'
Function: appearsInverted
▸ appearsInverted(g
, triple
): boolean
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
and similar methods),
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.
Function: nodeContexts
▸ nodeContexts(g
): (Variable
| null
)[]
Return the list of node contexts corresponding to triples in g
.
If a node context is unknown, the value null
is substituted.
Example
import { decode, nodeContexts } from 'penman-js';
const g = decode(`
(a / alpha
:attr val
:ARG0 (b / beta :ARG0 (g / gamma))
:ARG0-of g)`);
for (const [ctx, trp] of zip(nodeContexts(g), g.triples)) {
console.log(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']
Class: LayoutMarker
Epigraph marker for layout choices.
constructor
• new LayoutMarker(): LayoutMarker
mode
• mode: number
= 0
The mode
attribute specifies what the Epidatum annotates:
mode = 0
-- unspecifiedmode = 1
-- role epidatamode = 2
-- target epidata
Class: Pop
Epigraph marker to indicate the end of a node context.
constructor
• new Pop(): Pop
mode
• mode: number
= 0
The mode
attribute specifies what the Epidatum annotates:
mode = 0
-- unspecifiedmode = 1
-- role epidatamode = 2
-- target epidata
toString
▸ toString(): string
Class: Push
Epigraph marker to indicate a new node context.
constructor
• new Push(variable
): Push
mode
• mode: number
= 0
The mode
attribute specifies what the Epidatum annotates:
mode = 0
-- unspecifiedmode = 1
-- role epidatamode = 2
-- target epidata
variable
• variable: string
toString
▸ toString(): string
Variable: POP
• Const
POP: Pop
A singleton instance of Pop
.