We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Memory leak due to cyclic references in Trace
@karalets noticed memory growth when using Pyro.

We tracked the number of non-garbage-collected objects during SVI inference. We found that objects grow over iterations but are occasionally collected. This evidence points to a cyclic reference somewhere. Among the amassing objects were `Variable`s and  Pyro `Trace` objects, suggesting that the cyclic reference is either in Pyro internals or in the networkx `DiGraph` object from which `Trace` inherits.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pyro/poutine/trace.py`
Content:
```
1 from __future__ import absolute_import, division, print_function
2 
3 import collections
4 import warnings
5 
6 import networkx
7 from torch.autograd import Variable
8 
9 from pyro.distributions.util import scale_tensor
10 from pyro.util import is_nan, is_inf
11 
12 
13 def _warn_if_nan(name, value):
14     if isinstance(value, Variable):
15         value = value.data[0]
16     if is_nan(value):
17         warnings.warn("Encountered NAN log_pdf at site '{}'".format(name))
18     if is_inf(value) and value > 0:
19         warnings.warn("Encountered +inf log_pdf at site '{}'".format(name))
20     # Note that -inf log_pdf is fine: it is merely a zero-probability event.
21 
22 
23 class Trace(networkx.DiGraph):
24     """
25     Execution trace data structure
26     """
27 
28     node_dict_factory = collections.OrderedDict
29 
30     def __init__(self, *args, **kwargs):
31         """
32         :param string graph_type: string specifying the kind of trace graph to construct
33 
34         Constructor. Currently identical to networkx.``DiGraph(\*args, \**kwargs)``,
35         except for storing the graph_type attribute
36         """
37         graph_type = kwargs.pop("graph_type", "flat")
38         assert graph_type in ("flat", "dense"), \
39             "{} not a valid graph type".format(graph_type)
40         self.graph_type = graph_type
41         super(Trace, self).__init__(*args, **kwargs)
42 
43     def add_node(self, site_name, *args, **kwargs):
44         """
45         :param string site_name: the name of the site to be added
46 
47         Adds a site to the trace.
48 
49         Identical to super(Trace, self).add_node,
50         but raises an error when attempting to add a duplicate node
51         instead of silently overwriting.
52         """
53         # XXX should do more validation than this
54         if kwargs["type"] != "param":
55             assert site_name not in self, \
56                 "site {} already in trace".format(site_name)
57 
58         # XXX should copy in case site gets mutated, or dont bother?
59         super(Trace, self).add_node(site_name, *args, **kwargs.copy())
60 
61     def copy(self):
62         """
63         Makes a shallow copy of self with nodes and edges preserved.
64         Identical to super(Trace, self).copy(), but preserves the type
65         and the self.graph_type attribute
66         """
67         trace = super(Trace, self).copy()
68         trace.graph_type = self.graph_type
69         trace.__class__ = Trace
70         return trace
71 
72     def log_pdf(self, site_filter=lambda name, site: True):
73         """
74         Compute the local and overall log-probabilities of the trace.
75 
76         The local computation is memoized.
77 
78         :returns: total log probability.
79         :rtype: torch.autograd.Variable
80         """
81         log_p = 0.0
82         for name, site in self.nodes.items():
83             if site["type"] == "sample" and site_filter(name, site):
84                 try:
85                     site_log_p = site["log_pdf"]
86                 except KeyError:
87                     args, kwargs = site["args"], site["kwargs"]
88                     site_log_p = site["fn"].log_prob(site["value"], *args, **kwargs)
89                     site_log_p = scale_tensor(site_log_p, site["scale"]).sum()
90                     site["log_pdf"] = site_log_p
91                     _warn_if_nan(name, site_log_p)
92                 log_p += site_log_p
93         return log_p
94 
95     # XXX This only makes sense when all tensors have compatible shape.
96     def batch_log_pdf(self, site_filter=lambda name, site: True):
97         """
98         Compute the batched local and overall log-probabilities of the trace.
99 
100         The local computation is memoized, and also stores the local `.log_pdf()`.
101         """
102         log_p = 0.0
103         for name, site in self.nodes.items():
104             if site["type"] == "sample" and site_filter(name, site):
105                 try:
106                     site_log_p = site["batch_log_pdf"]
107                 except KeyError:
108                     args, kwargs = site["args"], site["kwargs"]
109                     site_log_p = site["fn"].log_prob(site["value"], *args, **kwargs)
110                     site_log_p = scale_tensor(site_log_p, site["scale"])
111                     site["batch_log_pdf"] = site_log_p
112                     site["log_pdf"] = site_log_p.sum()
113                     _warn_if_nan(name, site["log_pdf"])
114                 # Here log_p may be broadcast to a larger tensor:
115                 log_p = log_p + site_log_p
116         return log_p
117 
118     def compute_batch_log_pdf(self, site_filter=lambda name, site: True):
119         """
120         Compute the batched local log-probabilities at each site of the trace.
121 
122         The local computation is memoized, and also stores the local `.log_pdf()`.
123         """
124         for name, site in self.nodes.items():
125             if site["type"] == "sample" and site_filter(name, site):
126                 try:
127                     site["batch_log_pdf"]
128                 except KeyError:
129                     args, kwargs = site["args"], site["kwargs"]
130                     site_log_p = site["fn"].log_prob(site["value"], *args, **kwargs)
131                     site_log_p = scale_tensor(site_log_p, site["scale"])
132                     site["batch_log_pdf"] = site_log_p
133                     site["log_pdf"] = site_log_p.sum()
134                     _warn_if_nan(name, site["log_pdf"])
135 
136     def compute_score_parts(self):
137         """
138         Compute the batched local score parts at each site of the trace.
139         """
140         for name, site in self.nodes.items():
141             if site["type"] == "sample" and "score_parts" not in site:
142                 # Note that ScoreParts overloads the multiplication operator
143                 # to correctly scale each of its three parts.
144                 value = site["fn"].score_parts(site["value"], *site["args"], **site["kwargs"]) * site["scale"]
145                 site["score_parts"] = value
146                 site["batch_log_pdf"] = value[0]
147                 site["log_pdf"] = value[0].sum()
148                 _warn_if_nan(name, site["log_pdf"])
149 
150     @property
151     def observation_nodes(self):
152         """
153         Gets a list of names of observe sites
154         """
155         return [name for name, node in self.nodes.items()
156                 if node["type"] == "sample" and
157                 node["is_observed"]]
158 
159     @property
160     def stochastic_nodes(self):
161         """
162         Gets a list of names of sample sites
163         """
164         return [name for name, node in self.nodes.items()
165                 if node["type"] == "sample" and
166                 not node["is_observed"]]
167 
168     @property
169     def reparameterized_nodes(self):
170         """
171         Gets a list of names of sample sites whose stochastic functions
172         are reparameterizable primitive distributions
173         """
174         return [name for name, node in self.nodes.items()
175                 if node["type"] == "sample" and
176                 not node["is_observed"] and
177                 getattr(node["fn"], "reparameterized", False)]
178 
179     @property
180     def nonreparam_stochastic_nodes(self):
181         """
182         Gets a list of names of sample sites whose stochastic functions
183         are not reparameterizable primitive distributions
184         """
185         return list(set(self.stochastic_nodes) - set(self.reparameterized_nodes))
186 
187     def iter_stochastic_nodes(self):
188         """
189         Returns an iterator over stochastic nodes in the trace.
190         """
191         for name, node in self.nodes.items():
192             if node["type"] == "sample" and not node["is_observed"]:
193                 yield name, node
194
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pyro/poutine/trace.py b/pyro/poutine/trace.py
--- a/pyro/poutine/trace.py
+++ b/pyro/poutine/trace.py
@@ -20,13 +20,15 @@
     # Note that -inf log_pdf is fine: it is merely a zero-probability event.
 
 
-class Trace(networkx.DiGraph):
+class DiGraph(networkx.DiGraph):
+    node_dict_factory = collections.OrderedDict
+
+
+class Trace(object):
     """
     Execution trace data structure
     """
 
-    node_dict_factory = collections.OrderedDict
-
     def __init__(self, *args, **kwargs):
         """
         :param string graph_type: string specifying the kind of trace graph to construct
@@ -34,12 +36,59 @@
         Constructor. Currently identical to networkx.``DiGraph(\*args, \**kwargs)``,
         except for storing the graph_type attribute
         """
+        self._graph = DiGraph(*args, **kwargs)
         graph_type = kwargs.pop("graph_type", "flat")
         assert graph_type in ("flat", "dense"), \
             "{} not a valid graph type".format(graph_type)
         self.graph_type = graph_type
         super(Trace, self).__init__(*args, **kwargs)
 
+    def __del__(self):
+        # Work around cyclic reference bugs in networkx.DiGraph
+        # See https://github.com/uber/pyro/issues/798
+        self._graph.__dict__.clear()
+
+    @property
+    def nodes(self):
+        return self._graph.nodes
+
+    @property
+    def edges(self):
+        return self._graph.edges
+
+    @property
+    def graph(self):
+        return self._graph.graph
+
+    @property
+    def remove_node(self):
+        return self._graph.remove_node
+
+    @property
+    def add_edge(self):
+        return self._graph.add_edge
+
+    @property
+    def is_directed(self):
+        return self._graph.is_directed
+
+    @property
+    def in_degree(self):
+        return self._graph.in_degree
+
+    @property
+    def successors(self):
+        return self._graph.successors
+
+    def __contains__(self, site_name):
+        return site_name in self._graph
+
+    def __iter__(self):
+        return iter(self._graph)
+
+    def __len__(self):
+        return len(self._graph)
+
     def add_node(self, site_name, *args, **kwargs):
         """
         :param string site_name: the name of the site to be added
@@ -56,7 +105,7 @@
                 "site {} already in trace".format(site_name)
 
         # XXX should copy in case site gets mutated, or dont bother?
-        super(Trace, self).add_node(site_name, *args, **kwargs.copy())
+        self._graph.add_node(site_name, *args, **kwargs)
 
     def copy(self):
         """
@@ -64,9 +113,9 @@
         Identical to super(Trace, self).copy(), but preserves the type
         and the self.graph_type attribute
         """
-        trace = super(Trace, self).copy()
+        trace = Trace()
+        trace._graph = self._graph.copy()
         trace.graph_type = self.graph_type
-        trace.__class__ = Trace
         return trace
 
     def log_pdf(self, site_filter=lambda name, site: True):

{"golden_diff": "diff --git a/pyro/poutine/trace.py b/pyro/poutine/trace.py\n--- a/pyro/poutine/trace.py\n+++ b/pyro/poutine/trace.py\n@@ -20,13 +20,15 @@\n     # Note that -inf log_pdf is fine: it is merely a zero-probability event.\n \n \n-class Trace(networkx.DiGraph):\n+class DiGraph(networkx.DiGraph):\n+    node_dict_factory = collections.OrderedDict\n+\n+\n+class Trace(object):\n     \"\"\"\n     Execution trace data structure\n     \"\"\"\n \n-    node_dict_factory = collections.OrderedDict\n-\n     def __init__(self, *args, **kwargs):\n         \"\"\"\n         :param string graph_type: string specifying the kind of trace graph to construct\n@@ -34,12 +36,59 @@\n         Constructor. Currently identical to networkx.``DiGraph(\\*args, \\**kwargs)``,\n         except for storing the graph_type attribute\n         \"\"\"\n+        self._graph = DiGraph(*args, **kwargs)\n         graph_type = kwargs.pop(\"graph_type\", \"flat\")\n         assert graph_type in (\"flat\", \"dense\"), \\\n             \"{} not a valid graph type\".format(graph_type)\n         self.graph_type = graph_type\n         super(Trace, self).__init__(*args, **kwargs)\n \n+    def __del__(self):\n+        # Work around cyclic reference bugs in networkx.DiGraph\n+        # See https://github.com/uber/pyro/issues/798\n+        self._graph.__dict__.clear()\n+\n+    @property\n+    def nodes(self):\n+        return self._graph.nodes\n+\n+    @property\n+    def edges(self):\n+        return self._graph.edges\n+\n+    @property\n+    def graph(self):\n+        return self._graph.graph\n+\n+    @property\n+    def remove_node(self):\n+        return self._graph.remove_node\n+\n+    @property\n+    def add_edge(self):\n+        return self._graph.add_edge\n+\n+    @property\n+    def is_directed(self):\n+        return self._graph.is_directed\n+\n+    @property\n+    def in_degree(self):\n+        return self._graph.in_degree\n+\n+    @property\n+    def successors(self):\n+        return self._graph.successors\n+\n+    def __contains__(self, site_name):\n+        return site_name in self._graph\n+\n+    def __iter__(self):\n+        return iter(self._graph)\n+\n+    def __len__(self):\n+        return len(self._graph)\n+\n     def add_node(self, site_name, *args, **kwargs):\n         \"\"\"\n         :param string site_name: the name of the site to be added\n@@ -56,7 +105,7 @@\n                 \"site {} already in trace\".format(site_name)\n \n         # XXX should copy in case site gets mutated, or dont bother?\n-        super(Trace, self).add_node(site_name, *args, **kwargs.copy())\n+        self._graph.add_node(site_name, *args, **kwargs)\n \n     def copy(self):\n         \"\"\"\n@@ -64,9 +113,9 @@\n         Identical to super(Trace, self).copy(), but preserves the type\n         and the self.graph_type attribute\n         \"\"\"\n-        trace = super(Trace, self).copy()\n+        trace = Trace()\n+        trace._graph = self._graph.copy()\n         trace.graph_type = self.graph_type\n-        trace.__class__ = Trace\n         return trace\n \n     def log_pdf(self, site_filter=lambda name, site: True):\n", "issue": "Memory leak due to cyclic references in Trace\n@karalets noticed memory growth when using Pyro.\r\n\r\nWe tracked the number of non-garbage-collected objects during SVI inference. We found that objects grow over iterations but are occasionally collected. This evidence points to a cyclic reference somewhere. Among the amassing objects were `Variable`s and  Pyro `Trace` objects, suggesting that the cyclic reference is either in Pyro internals or in the networkx `DiGraph` object from which `Trace` inherits.\n", "before_files": [{"content": "from __future__ import absolute_import, division, print_function\n\nimport collections\nimport warnings\n\nimport networkx\nfrom torch.autograd import Variable\n\nfrom pyro.distributions.util import scale_tensor\nfrom pyro.util import is_nan, is_inf\n\n\ndef _warn_if_nan(name, value):\n    if isinstance(value, Variable):\n        value = value.data[0]\n    if is_nan(value):\n        warnings.warn(\"Encountered NAN log_pdf at site '{}'\".format(name))\n    if is_inf(value) and value > 0:\n        warnings.warn(\"Encountered +inf log_pdf at site '{}'\".format(name))\n    # Note that -inf log_pdf is fine: it is merely a zero-probability event.\n\n\nclass Trace(networkx.DiGraph):\n    \"\"\"\n    Execution trace data structure\n    \"\"\"\n\n    node_dict_factory = collections.OrderedDict\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        :param string graph_type: string specifying the kind of trace graph to construct\n\n        Constructor. Currently identical to networkx.``DiGraph(\\*args, \\**kwargs)``,\n        except for storing the graph_type attribute\n        \"\"\"\n        graph_type = kwargs.pop(\"graph_type\", \"flat\")\n        assert graph_type in (\"flat\", \"dense\"), \\\n            \"{} not a valid graph type\".format(graph_type)\n        self.graph_type = graph_type\n        super(Trace, self).__init__(*args, **kwargs)\n\n    def add_node(self, site_name, *args, **kwargs):\n        \"\"\"\n        :param string site_name: the name of the site to be added\n\n        Adds a site to the trace.\n\n        Identical to super(Trace, self).add_node,\n        but raises an error when attempting to add a duplicate node\n        instead of silently overwriting.\n        \"\"\"\n        # XXX should do more validation than this\n        if kwargs[\"type\"] != \"param\":\n            assert site_name not in self, \\\n                \"site {} already in trace\".format(site_name)\n\n        # XXX should copy in case site gets mutated, or dont bother?\n        super(Trace, self).add_node(site_name, *args, **kwargs.copy())\n\n    def copy(self):\n        \"\"\"\n        Makes a shallow copy of self with nodes and edges preserved.\n        Identical to super(Trace, self).copy(), but preserves the type\n        and the self.graph_type attribute\n        \"\"\"\n        trace = super(Trace, self).copy()\n        trace.graph_type = self.graph_type\n        trace.__class__ = Trace\n        return trace\n\n    def log_pdf(self, site_filter=lambda name, site: True):\n        \"\"\"\n        Compute the local and overall log-probabilities of the trace.\n\n        The local computation is memoized.\n\n        :returns: total log probability.\n        :rtype: torch.autograd.Variable\n        \"\"\"\n        log_p = 0.0\n        for name, site in self.nodes.items():\n            if site[\"type\"] == \"sample\" and site_filter(name, site):\n                try:\n                    site_log_p = site[\"log_pdf\"]\n                except KeyError:\n                    args, kwargs = site[\"args\"], site[\"kwargs\"]\n                    site_log_p = site[\"fn\"].log_prob(site[\"value\"], *args, **kwargs)\n                    site_log_p = scale_tensor(site_log_p, site[\"scale\"]).sum()\n                    site[\"log_pdf\"] = site_log_p\n                    _warn_if_nan(name, site_log_p)\n                log_p += site_log_p\n        return log_p\n\n    # XXX This only makes sense when all tensors have compatible shape.\n    def batch_log_pdf(self, site_filter=lambda name, site: True):\n        \"\"\"\n        Compute the batched local and overall log-probabilities of the trace.\n\n        The local computation is memoized, and also stores the local `.log_pdf()`.\n        \"\"\"\n        log_p = 0.0\n        for name, site in self.nodes.items():\n            if site[\"type\"] == \"sample\" and site_filter(name, site):\n                try:\n                    site_log_p = site[\"batch_log_pdf\"]\n                except KeyError:\n                    args, kwargs = site[\"args\"], site[\"kwargs\"]\n                    site_log_p = site[\"fn\"].log_prob(site[\"value\"], *args, **kwargs)\n                    site_log_p = scale_tensor(site_log_p, site[\"scale\"])\n                    site[\"batch_log_pdf\"] = site_log_p\n                    site[\"log_pdf\"] = site_log_p.sum()\n                    _warn_if_nan(name, site[\"log_pdf\"])\n                # Here log_p may be broadcast to a larger tensor:\n                log_p = log_p + site_log_p\n        return log_p\n\n    def compute_batch_log_pdf(self, site_filter=lambda name, site: True):\n        \"\"\"\n        Compute the batched local log-probabilities at each site of the trace.\n\n        The local computation is memoized, and also stores the local `.log_pdf()`.\n        \"\"\"\n        for name, site in self.nodes.items():\n            if site[\"type\"] == \"sample\" and site_filter(name, site):\n                try:\n                    site[\"batch_log_pdf\"]\n                except KeyError:\n                    args, kwargs = site[\"args\"], site[\"kwargs\"]\n                    site_log_p = site[\"fn\"].log_prob(site[\"value\"], *args, **kwargs)\n                    site_log_p = scale_tensor(site_log_p, site[\"scale\"])\n                    site[\"batch_log_pdf\"] = site_log_p\n                    site[\"log_pdf\"] = site_log_p.sum()\n                    _warn_if_nan(name, site[\"log_pdf\"])\n\n    def compute_score_parts(self):\n        \"\"\"\n        Compute the batched local score parts at each site of the trace.\n        \"\"\"\n        for name, site in self.nodes.items():\n            if site[\"type\"] == \"sample\" and \"score_parts\" not in site:\n                # Note that ScoreParts overloads the multiplication operator\n                # to correctly scale each of its three parts.\n                value = site[\"fn\"].score_parts(site[\"value\"], *site[\"args\"], **site[\"kwargs\"]) * site[\"scale\"]\n                site[\"score_parts\"] = value\n                site[\"batch_log_pdf\"] = value[0]\n                site[\"log_pdf\"] = value[0].sum()\n                _warn_if_nan(name, site[\"log_pdf\"])\n\n    @property\n    def observation_nodes(self):\n        \"\"\"\n        Gets a list of names of observe sites\n        \"\"\"\n        return [name for name, node in self.nodes.items()\n                if node[\"type\"] == \"sample\" and\n                node[\"is_observed\"]]\n\n    @property\n    def stochastic_nodes(self):\n        \"\"\"\n        Gets a list of names of sample sites\n        \"\"\"\n        return [name for name, node in self.nodes.items()\n                if node[\"type\"] == \"sample\" and\n                not node[\"is_observed\"]]\n\n    @property\n    def reparameterized_nodes(self):\n        \"\"\"\n        Gets a list of names of sample sites whose stochastic functions\n        are reparameterizable primitive distributions\n        \"\"\"\n        return [name for name, node in self.nodes.items()\n                if node[\"type\"] == \"sample\" and\n                not node[\"is_observed\"] and\n                getattr(node[\"fn\"], \"reparameterized\", False)]\n\n    @property\n    def nonreparam_stochastic_nodes(self):\n        \"\"\"\n        Gets a list of names of sample sites whose stochastic functions\n        are not reparameterizable primitive distributions\n        \"\"\"\n        return list(set(self.stochastic_nodes) - set(self.reparameterized_nodes))\n\n    def iter_stochastic_nodes(self):\n        \"\"\"\n        Returns an iterator over stochastic nodes in the trace.\n        \"\"\"\n        for name, node in self.nodes.items():\n            if node[\"type\"] == \"sample\" and not node[\"is_observed\"]:\n                yield name, node\n", "path": "pyro/poutine/trace.py"}], "after_files": [{"content": "from __future__ import absolute_import, division, print_function\n\nimport collections\nimport warnings\n\nimport networkx\nfrom torch.autograd import Variable\n\nfrom pyro.distributions.util import scale_tensor\nfrom pyro.util import is_nan, is_inf\n\n\ndef _warn_if_nan(name, value):\n    if isinstance(value, Variable):\n        value = value.data[0]\n    if is_nan(value):\n        warnings.warn(\"Encountered NAN log_pdf at site '{}'\".format(name))\n    if is_inf(value) and value > 0:\n        warnings.warn(\"Encountered +inf log_pdf at site '{}'\".format(name))\n    # Note that -inf log_pdf is fine: it is merely a zero-probability event.\n\n\nclass DiGraph(networkx.DiGraph):\n    node_dict_factory = collections.OrderedDict\n\n\nclass Trace(object):\n    \"\"\"\n    Execution trace data structure\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        :param string graph_type: string specifying the kind of trace graph to construct\n\n        Constructor. Currently identical to networkx.``DiGraph(\\*args, \\**kwargs)``,\n        except for storing the graph_type attribute\n        \"\"\"\n        self._graph = DiGraph(*args, **kwargs)\n        graph_type = kwargs.pop(\"graph_type\", \"flat\")\n        assert graph_type in (\"flat\", \"dense\"), \\\n            \"{} not a valid graph type\".format(graph_type)\n        self.graph_type = graph_type\n        super(Trace, self).__init__(*args, **kwargs)\n\n    def __del__(self):\n        # Work around cyclic reference bugs in networkx.DiGraph\n        # See https://github.com/uber/pyro/issues/798\n        self._graph.__dict__.clear()\n\n    @property\n    def nodes(self):\n        return self._graph.nodes\n\n    @property\n    def edges(self):\n        return self._graph.edges\n\n    @property\n    def graph(self):\n        return self._graph.graph\n\n    @property\n    def remove_node(self):\n        return self._graph.remove_node\n\n    @property\n    def add_edge(self):\n        return self._graph.add_edge\n\n    @property\n    def is_directed(self):\n        return self._graph.is_directed\n\n    @property\n    def in_degree(self):\n        return self._graph.in_degree\n\n    @property\n    def successors(self):\n        return self._graph.successors\n\n    def __contains__(self, site_name):\n        return site_name in self._graph\n\n    def __iter__(self):\n        return iter(self._graph)\n\n    def __len__(self):\n        return len(self._graph)\n\n    def add_node(self, site_name, *args, **kwargs):\n        \"\"\"\n        :param string site_name: the name of the site to be added\n\n        Adds a site to the trace.\n\n        Identical to super(Trace, self).add_node,\n        but raises an error when attempting to add a duplicate node\n        instead of silently overwriting.\n        \"\"\"\n        # XXX should do more validation than this\n        if kwargs[\"type\"] != \"param\":\n            assert site_name not in self, \\\n                \"site {} already in trace\".format(site_name)\n\n        # XXX should copy in case site gets mutated, or dont bother?\n        self._graph.add_node(site_name, *args, **kwargs)\n\n    def copy(self):\n        \"\"\"\n        Makes a shallow copy of self with nodes and edges preserved.\n        Identical to super(Trace, self).copy(), but preserves the type\n        and the self.graph_type attribute\n        \"\"\"\n        trace = Trace()\n        trace._graph = self._graph.copy()\n        trace.graph_type = self.graph_type\n        return trace\n\n    def log_pdf(self, site_filter=lambda name, site: True):\n        \"\"\"\n        Compute the local and overall log-probabilities of the trace.\n\n        The local computation is memoized.\n\n        :returns: total log probability.\n        :rtype: torch.autograd.Variable\n        \"\"\"\n        log_p = 0.0\n        for name, site in self.nodes.items():\n            if site[\"type\"] == \"sample\" and site_filter(name, site):\n                try:\n                    site_log_p = site[\"log_pdf\"]\n                except KeyError:\n                    args, kwargs = site[\"args\"], site[\"kwargs\"]\n                    site_log_p = site[\"fn\"].log_prob(site[\"value\"], *args, **kwargs)\n                    site_log_p = scale_tensor(site_log_p, site[\"scale\"]).sum()\n                    site[\"log_pdf\"] = site_log_p\n                    _warn_if_nan(name, site_log_p)\n                log_p += site_log_p\n        return log_p\n\n    # XXX This only makes sense when all tensors have compatible shape.\n    def batch_log_pdf(self, site_filter=lambda name, site: True):\n        \"\"\"\n        Compute the batched local and overall log-probabilities of the trace.\n\n        The local computation is memoized, and also stores the local `.log_pdf()`.\n        \"\"\"\n        log_p = 0.0\n        for name, site in self.nodes.items():\n            if site[\"type\"] == \"sample\" and site_filter(name, site):\n                try:\n                    site_log_p = site[\"batch_log_pdf\"]\n                except KeyError:\n                    args, kwargs = site[\"args\"], site[\"kwargs\"]\n                    site_log_p = site[\"fn\"].log_prob(site[\"value\"], *args, **kwargs)\n                    site_log_p = scale_tensor(site_log_p, site[\"scale\"])\n                    site[\"batch_log_pdf\"] = site_log_p\n                    site[\"log_pdf\"] = site_log_p.sum()\n                    _warn_if_nan(name, site[\"log_pdf\"])\n                # Here log_p may be broadcast to a larger tensor:\n                log_p = log_p + site_log_p\n        return log_p\n\n    def compute_batch_log_pdf(self, site_filter=lambda name, site: True):\n        \"\"\"\n        Compute the batched local log-probabilities at each site of the trace.\n\n        The local computation is memoized, and also stores the local `.log_pdf()`.\n        \"\"\"\n        for name, site in self.nodes.items():\n            if site[\"type\"] == \"sample\" and site_filter(name, site):\n                try:\n                    site[\"batch_log_pdf\"]\n                except KeyError:\n                    args, kwargs = site[\"args\"], site[\"kwargs\"]\n                    site_log_p = site[\"fn\"].log_prob(site[\"value\"], *args, **kwargs)\n                    site_log_p = scale_tensor(site_log_p, site[\"scale\"])\n                    site[\"batch_log_pdf\"] = site_log_p\n                    site[\"log_pdf\"] = site_log_p.sum()\n                    _warn_if_nan(name, site[\"log_pdf\"])\n\n    def compute_score_parts(self):\n        \"\"\"\n        Compute the batched local score parts at each site of the trace.\n        \"\"\"\n        for name, site in self.nodes.items():\n            if site[\"type\"] == \"sample\" and \"score_parts\" not in site:\n                # Note that ScoreParts overloads the multiplication operator\n                # to correctly scale each of its three parts.\n                value = site[\"fn\"].score_parts(site[\"value\"], *site[\"args\"], **site[\"kwargs\"]) * site[\"scale\"]\n                site[\"score_parts\"] = value\n                site[\"batch_log_pdf\"] = value[0]\n                site[\"log_pdf\"] = value[0].sum()\n                _warn_if_nan(name, site[\"log_pdf\"])\n\n    @property\n    def observation_nodes(self):\n        \"\"\"\n        Gets a list of names of observe sites\n        \"\"\"\n        return [name for name, node in self.nodes.items()\n                if node[\"type\"] == \"sample\" and\n                node[\"is_observed\"]]\n\n    @property\n    def stochastic_nodes(self):\n        \"\"\"\n        Gets a list of names of sample sites\n        \"\"\"\n        return [name for name, node in self.nodes.items()\n                if node[\"type\"] == \"sample\" and\n                not node[\"is_observed\"]]\n\n    @property\n    def reparameterized_nodes(self):\n        \"\"\"\n        Gets a list of names of sample sites whose stochastic functions\n        are reparameterizable primitive distributions\n        \"\"\"\n        return [name for name, node in self.nodes.items()\n                if node[\"type\"] == \"sample\" and\n                not node[\"is_observed\"] and\n                getattr(node[\"fn\"], \"reparameterized\", False)]\n\n    @property\n    def nonreparam_stochastic_nodes(self):\n        \"\"\"\n        Gets a list of names of sample sites whose stochastic functions\n        are not reparameterizable primitive distributions\n        \"\"\"\n        return list(set(self.stochastic_nodes) - set(self.reparameterized_nodes))\n\n    def iter_stochastic_nodes(self):\n        \"\"\"\n        Returns an iterator over stochastic nodes in the trace.\n        \"\"\"\n        for name, node in self.nodes.items():\n            if node[\"type\"] == \"sample\" and not node[\"is_observed\"]:\n                yield name, node\n", "path": "pyro/poutine/trace.py"}]}