<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Penman API Demo
This notebook demonstrates the basic usage of the Penman API. For an overview of what Penman does, see the project page. For API documentation, see here.
To start, import the penman module.
Step1:  Basic Decoding and Encoding
A common task is reading a PENMAN string into a graph object. The simplest way to do this is with penman.decode()
Step2:  The penman.encode() function can serialize a graph back to PENMAN notation (note that the metadata is also printed)
Step3:  You may customize things like indentation
Step4:  Graph Introspection and Manipulation
The Graph object returned by decode() has methods for inspecting things like the variables and different types of edges.
Step5:  You may also view and modify the full list of triples and the metadata directly
Step6:  Advanced Decoding and Encoding
Penman's decoding strategy has 3 stages
Step7:  Getting the graph from the tree then requires a separate call to penman.interpret()
Step8:  We can also go the other way; call penman.configure() to get a tree from a graph, and finally penman.format() to get a string again
Step9:  The interface between trees and graphs is defined in the penman.layout module. Both penman.interpret() and penman.configure() are just aliases for penman.layout.interpret() and penman.layout.configure().
Tree Inspection and Manipulation
Tree objects are simple structures that contain a node data attribute as a (var, branches) pair, where var is the node's variable and branches is a list of (branch_label, target) pairs. branch_label is like a graph role, but it is not normalized for inversion and concept branches use the / label instead of the 
Step10:  Tree.nodes() traverses the tree and returns a flat list of the nodes in the tree (but the nodes themselves are not flat)
Step11:  Tree.reset_variables() reassigns the node variables based on their appearance in the tree. It takes a formatting parameter with a few possible replacements (see the documentation for details)
Step12:  Using Models
In Penman, the interpretation of a graph from a tree relies on a Model to determine things like whether a role is inverted. By default, a basic model with no special roles defined is used, and this is often enough
Step13:  AMR, however, has some roles that use -of in their primary, or non-inverted, form, which can lead to invalid graphs
Step14:  Instead, by using the AMR-specific model, these edges are correctly interpreted
Step15:  If you don't want to pass in the model object each time, you can create a PENMANCodec object with a model, then it can decode and encode using the model. The only difference between using the codec object or the module functions is how the model is specified, as shown above.
Step16:  Models are also useful as a source of information for transformations, as shown in the next section.
Transformations
Penman's transformations sometimes modify the content of the graph and other times only restructure how the graph is displayed. They rely on a Model for information on how to apply the transformations.
Step17:  Consider the following graph which is erroneous because an inverted relation specifies a constant (meaning the source of the normalized relation is not a node)
Step18:  In AMR, the inverted 
Step19:  Reification is another kind of transformation. It works on graphs. There are two kinds of reification in Penman, and the first is transform.reify_edges(), which does reification as defined by the AMR guidelines
Step20:  There is also transform.reify_attributes() which replaces attribute values with nodes. This is another way one could deal with the warning above about interpretation being unable to deinvert an attribute. As this procedure is not defined by a model, the function does not take one
Step21:  Finally, there are some transformations defined by other parts of Penman. We've already seen Tree.reset_variables(). Two others are defined in the penman.layout module.
First, layout.rearrange() will reorder branches in the tree without otherwise changing its structure. For example
Step22:  Next, layout.reconfigure() performs more significant structure changes to the graph.
<ASSISTANT_TASK:>
Python Code:
import penman
penman.__version__
Explanation: Penman API Demo
This notebook demonstrates the basic usage of the Penman API. For an overview of what Penman does, see the project page. For API documentation, see here.
To start, import the penman module.
End of explanation
g = penman.decode('''
  # ::snt The dog didn't bark
  (b / bark-01
     :polarity -
     :ARG0 (d / dog))''')
g
Explanation: Basic Decoding and Encoding
A common task is reading a PENMAN string into a graph object. The simplest way to do this is with penman.decode():
End of explanation
print(penman.encode(g))
Explanation: The penman.encode() function can serialize a graph back to PENMAN notation (note that the metadata is also printed):
End of explanation
print(penman.encode(g, indent=None))  # single-line
print(penman.encode(g, indent=6, compact=True))  # attributes following concepts printed on same line
Explanation: You may customize things like indentation:
End of explanation
g.variables()
g.instances()
g.attributes()
g.edges()
g.metadata
Explanation: Graph Introspection and Manipulation
The Graph object returned by decode() has methods for inspecting things like the variables and different types of edges.
End of explanation
g.triples
g.triples.extend([('b', ':location', 'g'), ('g', ':instance', 'garden')])
g.triples
g.metadata['snt'] = "The dog didn't bark in the garden."
print(penman.encode(g))
Explanation: You may also view and modify the full list of triples and the metadata directly:
End of explanation
t = penman.parse('''
  # ::snt The dog didn't bark
  (b / bark-01
     :polarity -
     :ARG0 (d / dog))''')
t
Explanation: Advanced Decoding and Encoding
Penman's decoding strategy has 3 stages: first it starts with a PENMAN string and parses it to a tree structure, then it interprets the tree structure to produce a pure graph. Earlier when we called the penman.decode() function, we performed the tree-parsing and graph-interpretation in one call. It is also possible to perform these steps separately with the penman.parse() and penman.interpret(), or to parse to a tree without ever interpreting the graph. This is useful if you prefer to work with AMR data as trees than as pure graphs, or if you wish to use some of Penman's tree transformations.
End of explanation
g = penman.interpret(t)
g
Explanation: Getting the graph from the tree then requires a separate call to penman.interpret():
End of explanation
t2 = penman.configure(g)
print(penman.format(t2))
Explanation: We can also go the other way; call penman.configure() to get a tree from a graph, and finally penman.format() to get a string again:
End of explanation
t.node
t.metadata
Explanation: The interface between trees and graphs is defined in the penman.layout module. Both penman.interpret() and penman.configure() are just aliases for penman.layout.interpret() and penman.layout.configure().
Tree Inspection and Manipulation
Tree objects are simple structures that contain a node data attribute as a (var, branches) pair, where var is the node's variable and branches is a list of (branch_label, target) pairs. branch_label is like a graph role, but it is not normalized for inversion and concept branches use the / label instead of the :instance role. target is either an atomic type (e.g., a string) or, recursively, another node. Tree objects also contain metadata.
End of explanation
t.nodes()
Explanation: Tree.nodes() traverses the tree and returns a flat list of the nodes in the tree (but the nodes themselves are not flat):
End of explanation
t.reset_variables('a{i}')
t
t.reset_variables('{prefix}{j}')
t
Explanation: Tree.reset_variables() reassigns the node variables based on their appearance in the tree. It takes a formatting parameter with a few possible replacements (see the documentation for details):
End of explanation
g = penman.decode('''
  # ::snt The dog that barked slept.
  (s / sleep-01
     :ARG0 (d / dog
              :ARG0-of (b / bark-01)))''')
g.edges()  # note that edge directions are normalized
Explanation: Using Models
In Penman, the interpretation of a graph from a tree relies on a Model to determine things like whether a role is inverted. By default, a basic model with no special roles defined is used, and this is often enough:
End of explanation
g = penman.decode('''
  # ::snt I bought a ceramic knife
  (b / buy-01
     :ARG0 (i / i)
     :ARG1 (k / knife
              :consist-of (c / ceramic)))''')
g.edges()
Explanation: AMR, however, has some roles that use -of in their primary, or non-inverted, form, which can lead to invalid graphs:
End of explanation
from penman.models import amr
g = penman.decode('''
  # ::snt I bought a ceramic knife
  (b / buy-01
     :ARG0 (i / i)
     :ARG1 (k / knife
              :consist-of (c / ceramic)))''',
          model=amr.model)
g.edges()
Explanation: Instead, by using the AMR-specific model, these edges are correctly interpreted:
End of explanation
amrcodec = penman.PENMANCodec(model=amr.model)
amrcodec.decode('(k / knife :consist-of (c / ceramic))').edges()
Explanation: If you don't want to pass in the model object each time, you can create a PENMANCodec object with a model, then it can decode and encode using the model. The only difference between using the codec object or the module functions is how the model is specified, as shown above.
End of explanation
from penman import transform
Explanation: Models are also useful as a source of information for transformations, as shown in the next section.
Transformations
Penman's transformations sometimes modify the content of the graph and other times only restructure how the graph is displayed. They rely on a Model for information on how to apply the transformations.
End of explanation
g = amrcodec.decode('''
  (c / chapter
     :domain-of 7)''')  # this will log a warning
g.attributes()  # note that it is not normalized
Explanation: Consider the following graph which is erroneous because an inverted relation specifies a constant (meaning the source of the normalized relation is not a node):
End of explanation
t = penman.parse('''
  (c / chapter
     :domain-of 7)''')
t2 = transform.canonicalize_roles(t, model=amr.model)
print(penman.format(t2))
Explanation: In AMR, the inverted :domain-of relation has the canonical form :mod which is not inverted and thus eligible for specifying attributes. Note, however, that even decoding with the AMR model above did not convert the :domain-of into the more canonical :mod automatically as doing so would change the triples. To fix the error, you can use transform.canonicalize_roles() with the AMR model. It works on the tree structure, so we first reparse it as a tree:
End of explanation
g = penman.interpret(t2, model=amr.model)  # get a graph from the tree
g2 = transform.reify_edges(g, model=amr.model)  # :mod -> have-mod-91 is defined by the AMR model
print(amrcodec.encode(g2))
Explanation: Reification is another kind of transformation. It works on graphs. There are two kinds of reification in Penman, and the first is transform.reify_edges(), which does reification as defined by the AMR guidelines:
End of explanation
g3 = transform.reify_attributes(g)
print(amrcodec.encode(g3))
Explanation: There is also transform.reify_attributes() which replaces attribute values with nodes. This is another way one could deal with the warning above about interpretation being unable to deinvert an attribute. As this procedure is not defined by a model, the function does not take one:
End of explanation
from penman import layout
t = penman.parse('''
  (t / try-01
     :ARG1 (c / chase-01
              :ARG1 (c2 / cat)
              :ARG0 (d / dog))
     :ARG0 d)''')
layout.rearrange(t, key=amr.model.canonical_order)
print(penman.format(t))
Explanation: Finally, there are some transformations defined by other parts of Penman. We've already seen Tree.reset_variables(). Two others are defined in the penman.layout module.
First, layout.rearrange() will reorder branches in the tree without otherwise changing its structure. For example:
End of explanation
g = penman.interpret(t)  # or layout.interpret()
t2 = layout.reconfigure(g, key=amr.model.canonical_order)
print(penman.format(t2))
Explanation: Next, layout.reconfigure() performs more significant structure changes to the graph.
End of explanation
<END_TASK>