We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
compose() erases some MultiGraph edges
If a user wants to compose two multigraphs, it is very likely that they want to use all of the edges present in both. In MultiGraphs, edges which share the same (source, target) pair are not the same. Currently, edges which share the same (source, target, key) tuple are treated the same: as keys are assigned in insertion order by default, rather than having anything to do with the data, the end user just sees that an arbitrary few of their edges have gone missing.

``` python
import networkx as nx

a, b = nx.MultiGraph(), nx.MultiGraph()
a.add_path([1,2])
b.add_path([1,2])

nx.compose(a,b).number_of_edges() == a.number_of_edges() + b.number_of_edges()
>>> False
```

The documentation states that the edge sets are unioned. If this edge set is hashed by (source, target) pair, then the function cannot be advertised as applicable to MultiGraphs, because it collapses all multiedges. If the edge set is hashed by (source, target, key), as it is currently, then there is unexpected and possibly arbitrary behaviour which is not well documented. The edge set should be hashed by a UUID for MultiGraphs (i.e. all edges are distinct), in order to reflect how these classes are actually going to be used.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `networkx/algorithms/operators/binary.py`
Content:
```
1 """
2 Operations on graphs including union, intersection, difference.
3 """
4 #    Copyright (C) 2004-2016 by
5 #    Aric Hagberg <hagberg@lanl.gov>
6 #    Dan Schult <dschult@colgate.edu>
7 #    Pieter Swart <swart@lanl.gov>
8 #    All rights reserved.
9 #    BSD license.
10 import networkx as nx
11 from networkx.utils import is_string_like
12 __author__ = """\n""".join(['Aric Hagberg <aric.hagberg@gmail.com>',
13                            'Pieter Swart (swart@lanl.gov)',
14                            'Dan Schult(dschult@colgate.edu)'])
15 __all__ = ['union', 'compose', 'disjoint_union', 'intersection',
16            'difference', 'symmetric_difference']
17 
18 
19 def union(G, H, rename=(None, None), name=None):
20     """ Return the union of graphs G and H.
21 
22     Graphs G and H must be disjoint, otherwise an exception is raised.
23 
24     Parameters
25     ----------
26     G,H : graph
27        A NetworkX graph
28 
29     create_using : NetworkX graph
30        Use specified graph for result.  Otherwise
31 
32     rename : bool , default=(None, None)
33        Node names of G and H can be changed by specifying the tuple
34        rename=('G-','H-') (for example).  Node "u" in G is then renamed
35        "G-u" and "v" in H is renamed "H-v".
36 
37     name : string
38        Specify the name for the union graph
39 
40     Returns
41     -------
42     U : A union graph with the same type as G.
43 
44     Notes
45     -----
46     To force a disjoint union with node relabeling, use
47     disjoint_union(G,H) or convert_node_labels_to integers().
48 
49     Graph, edge, and node attributes are propagated from G and H
50     to the union graph.  If a graph attribute is present in both
51     G and H the value from H is used.
52 
53     See Also
54     --------
55     disjoint_union
56     """
57     if not G.is_multigraph() == H.is_multigraph():
58         raise nx.NetworkXError('G and H must both be graphs or multigraphs.')
59     # Union is the same type as G
60     R = G.__class__()
61     if name is None:
62         name = "union( %s, %s )" % (G.name, H.name)
63     R.name = name
64 
65     # rename graph to obtain disjoint node labels
66     def add_prefix(graph, prefix):
67         if prefix is None:
68             return graph
69 
70         def label(x):
71             if is_string_like(x):
72                 name = prefix + x
73             else:
74                 name = prefix + repr(x)
75             return name
76         return nx.relabel_nodes(graph, label)
77     G = add_prefix(G, rename[0])
78     H = add_prefix(H, rename[1])
79     if set(G) & set(H):
80         raise nx.NetworkXError('The node sets of G and H are not disjoint.',
81                                'Use appropriate rename=(Gprefix,Hprefix)'
82                                'or use disjoint_union(G,H).')
83     if G.is_multigraph():
84         G_edges = G.edges(keys=True, data=True)
85     else:
86         G_edges = G.edges(data=True)
87     if H.is_multigraph():
88         H_edges = H.edges(keys=True, data=True)
89     else:
90         H_edges = H.edges(data=True)
91 
92     # add nodes
93     R.add_nodes_from(G)
94     R.add_edges_from(G_edges)
95     # add edges
96     R.add_nodes_from(H)
97     R.add_edges_from(H_edges)
98     # add node attributes
99     R.node.update(G.node)
100     R.node.update(H.node)
101     # add graph attributes, H attributes take precedent over G attributes
102     R.graph.update(G.graph)
103     R.graph.update(H.graph)
104 
105     return R
106 
107 
108 def disjoint_union(G, H):
109     """ Return the disjoint union of graphs G and H.
110 
111     This algorithm forces distinct integer node labels.
112 
113     Parameters
114     ----------
115     G,H : graph
116        A NetworkX graph
117 
118     Returns
119     -------
120     U : A union graph with the same type as G.
121 
122     Notes
123     -----
124     A new graph is created, of the same class as G.  It is recommended
125     that G and H be either both directed or both undirected.
126 
127     The nodes of G are relabeled 0 to len(G)-1, and the nodes of H are
128     relabeled len(G) to len(G)+len(H)-1.
129 
130     Graph, edge, and node attributes are propagated from G and H
131     to the union graph.  If a graph attribute is present in both
132     G and H the value from H is used.
133     """
134     R1 = nx.convert_node_labels_to_integers(G)
135     R2 = nx.convert_node_labels_to_integers(H, first_label=len(R1))
136     R = union(R1, R2)
137     R.name = "disjoint_union( %s, %s )" % (G.name, H.name)
138     R.graph.update(G.graph)
139     R.graph.update(H.graph)
140     return R
141 
142 
143 def intersection(G, H):
144     """Return a new graph that contains only the edges that exist in
145     both G and H.
146 
147     The node sets of H and G must be the same.
148 
149     Parameters
150     ----------
151     G,H : graph
152        A NetworkX graph.  G and H must have the same node sets.
153 
154     Returns
155     -------
156     GH : A new graph with the same type as G.
157 
158     Notes
159     -----
160     Attributes from the graph, nodes, and edges are not copied to the new
161     graph.  If you want a new graph of the intersection of G and H
162     with the attributes (including edge data) from G use remove_nodes_from()
163     as follows
164 
165     >>> G=nx.path_graph(3)
166     >>> H=nx.path_graph(5)
167     >>> R=G.copy()
168     >>> R.remove_nodes_from(n for n in G if n not in H)
169     """
170     # create new graph
171     R = nx.create_empty_copy(G)
172 
173     R.name = "Intersection of (%s and %s)" % (G.name, H.name)
174     if not G.is_multigraph() == H.is_multigraph():
175         raise nx.NetworkXError('G and H must both be graphs or multigraphs.')
176     if set(G) != set(H):
177         raise nx.NetworkXError("Node sets of graphs are not equal")
178 
179     if G.number_of_edges() <= H.number_of_edges():
180         if G.is_multigraph():
181             edges = G.edges(keys=True)
182         else:
183             edges = G.edges()
184         for e in edges:
185             if H.has_edge(*e):
186                 R.add_edge(*e)
187     else:
188         if H.is_multigraph():
189             edges = H.edges(keys=True)
190         else:
191             edges = H.edges()
192         for e in edges:
193             if G.has_edge(*e):
194                 R.add_edge(*e)
195 
196     return R
197 
198 
199 def difference(G, H):
200     """Return a new graph that contains the edges that exist in G but not in H.
201 
202     The node sets of H and G must be the same.
203 
204     Parameters
205     ----------
206     G,H : graph
207        A NetworkX graph.  G and H must have the same node sets.
208 
209     Returns
210     -------
211     D : A new graph with the same type as G.
212 
213     Notes
214     -----
215     Attributes from the graph, nodes, and edges are not copied to the new
216     graph.  If you want a new graph of the difference of G and H with
217     with the attributes (including edge data) from G use remove_nodes_from()
218     as follows:
219 
220     >>> G = nx.path_graph(3)
221     >>> H = nx.path_graph(5)
222     >>> R = G.copy()
223     >>> R.remove_nodes_from(n for n in G if n in H)
224     """
225     # create new graph
226     if not G.is_multigraph() == H.is_multigraph():
227         raise nx.NetworkXError('G and H must both be graphs or multigraphs.')
228     R = nx.create_empty_copy(G)
229     R.name = "Difference of (%s and %s)" % (G.name, H.name)
230 
231     if set(G) != set(H):
232         raise nx.NetworkXError("Node sets of graphs not equal")
233 
234     if G.is_multigraph():
235         edges = G.edges(keys=True)
236     else:
237         edges = G.edges()
238     for e in edges:
239         if not H.has_edge(*e):
240             R.add_edge(*e)
241     return R
242 
243 
244 def symmetric_difference(G, H):
245     """Return new graph with edges that exist in either G or H but not both.
246 
247     The node sets of H and G must be the same.
248 
249     Parameters
250     ----------
251     G,H : graph
252        A NetworkX graph.  G and H must have the same node sets.
253 
254     Returns
255     -------
256     D : A new graph with the same type as G.
257 
258     Notes
259     -----
260     Attributes from the graph, nodes, and edges are not copied to the new
261     graph.
262     """
263     # create new graph
264     if not G.is_multigraph() == H.is_multigraph():
265         raise nx.NetworkXError('G and H must both be graphs or multigraphs.')
266     R = nx.create_empty_copy(G)
267     R.name = "Symmetric difference of (%s and %s)" % (G.name, H.name)
268 
269     if set(G) != set(H):
270         raise nx.NetworkXError("Node sets of graphs not equal")
271 
272     gnodes = set(G)  # set of nodes in G
273     hnodes = set(H)  # set of nodes in H
274     nodes = gnodes.symmetric_difference(hnodes)
275     R.add_nodes_from(nodes)
276 
277     if G.is_multigraph():
278         edges = G.edges(keys=True)
279     else:
280         edges = G.edges()
281     # we could copy the data here but then this function doesn't
282     # match intersection and difference
283     for e in edges:
284         if not H.has_edge(*e):
285             R.add_edge(*e)
286 
287     if H.is_multigraph():
288         edges = H.edges(keys=True)
289     else:
290         edges = H.edges()
291     for e in edges:
292         if not G.has_edge(*e):
293             R.add_edge(*e)
294     return R
295 
296 
297 def compose(G, H, name=None):
298     """Return a new graph of G composed with H.
299 
300     Composition is the simple union of the node sets and edge sets.
301     The node sets of G and H do not need to be disjoint.
302 
303     Parameters
304     ----------
305     G,H : graph
306        A NetworkX graph
307 
308     name : string
309        Specify name for new graph
310 
311     Returns
312     -------
313     C: A new graph  with the same type as G
314 
315     Notes
316     -----
317     It is recommended that G and H be either both directed or both undirected.
318     Attributes from H take precedent over attributes from G.
319     """
320     if not G.is_multigraph() == H.is_multigraph():
321         raise nx.NetworkXError('G and H must both be graphs or multigraphs.')
322 
323     if name is None:
324         name = "compose( %s, %s )" % (G.name, H.name)
325     R = G.__class__()
326     R.name = name
327 
328     R.add_nodes_from(G.nodes(data=True))
329     R.add_nodes_from(H.nodes(data=True))
330 
331     if G.is_multigraph():
332         R.add_edges_from(G.edges(keys=True, data=True))
333     else:
334         R.add_edges_from(G.edges(data=True))
335     if H.is_multigraph():
336         R.add_edges_from(H.edges(keys=True, data=True))
337     else:
338         R.add_edges_from(H.edges(data=True))
339 
340     # add graph attributes, H attributes take precedent over G attributes
341     R.graph.update(G.graph)
342     R.graph.update(H.graph)
343     return R
344
```
--- 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/networkx/algorithms/operators/binary.py b/networkx/algorithms/operators/binary.py
--- a/networkx/algorithms/operators/binary.py
+++ b/networkx/algorithms/operators/binary.py
@@ -316,6 +316,10 @@
     -----
     It is recommended that G and H be either both directed or both undirected.
     Attributes from H take precedent over attributes from G.
+
+    For MultiGraphs, the edges are identified by incident nodes AND edge-key.
+    This can cause surprises (i.e., edge `(1, 2)` may or may not be the same
+    in two graphs) if you use MultiGraph without keeping track of edge keys.
     """
     if not G.is_multigraph() == H.is_multigraph():
         raise nx.NetworkXError('G and H must both be graphs or multigraphs.')

{"golden_diff": "diff --git a/networkx/algorithms/operators/binary.py b/networkx/algorithms/operators/binary.py\n--- a/networkx/algorithms/operators/binary.py\n+++ b/networkx/algorithms/operators/binary.py\n@@ -316,6 +316,10 @@\n     -----\n     It is recommended that G and H be either both directed or both undirected.\n     Attributes from H take precedent over attributes from G.\n+\n+    For MultiGraphs, the edges are identified by incident nodes AND edge-key.\n+    This can cause surprises (i.e., edge `(1, 2)` may or may not be the same\n+    in two graphs) if you use MultiGraph without keeping track of edge keys.\n     \"\"\"\n     if not G.is_multigraph() == H.is_multigraph():\n         raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n", "issue": "compose() erases some MultiGraph edges\nIf a user wants to compose two multigraphs, it is very likely that they want to use all of the edges present in both. In MultiGraphs, edges which share the same (source, target) pair are not the same. Currently, edges which share the same (source, target, key) tuple are treated the same: as keys are assigned in insertion order by default, rather than having anything to do with the data, the end user just sees that an arbitrary few of their edges have gone missing.\n\n``` python\nimport networkx as nx\n\na, b = nx.MultiGraph(), nx.MultiGraph()\na.add_path([1,2])\nb.add_path([1,2])\n\nnx.compose(a,b).number_of_edges() == a.number_of_edges() + b.number_of_edges()\n>>> False\n```\n\nThe documentation states that the edge sets are unioned. If this edge set is hashed by (source, target) pair, then the function cannot be advertised as applicable to MultiGraphs, because it collapses all multiedges. If the edge set is hashed by (source, target, key), as it is currently, then there is unexpected and possibly arbitrary behaviour which is not well documented. The edge set should be hashed by a UUID for MultiGraphs (i.e. all edges are distinct), in order to reflect how these classes are actually going to be used.\n\n", "before_files": [{"content": "\"\"\"\nOperations on graphs including union, intersection, difference.\n\"\"\"\n#    Copyright (C) 2004-2016 by\n#    Aric Hagberg <hagberg@lanl.gov>\n#    Dan Schult <dschult@colgate.edu>\n#    Pieter Swart <swart@lanl.gov>\n#    All rights reserved.\n#    BSD license.\nimport networkx as nx\nfrom networkx.utils import is_string_like\n__author__ = \"\"\"\\n\"\"\".join(['Aric Hagberg <aric.hagberg@gmail.com>',\n                           'Pieter Swart (swart@lanl.gov)',\n                           'Dan Schult(dschult@colgate.edu)'])\n__all__ = ['union', 'compose', 'disjoint_union', 'intersection',\n           'difference', 'symmetric_difference']\n\n\ndef union(G, H, rename=(None, None), name=None):\n    \"\"\" Return the union of graphs G and H.\n\n    Graphs G and H must be disjoint, otherwise an exception is raised.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph\n\n    create_using : NetworkX graph\n       Use specified graph for result.  Otherwise\n\n    rename : bool , default=(None, None)\n       Node names of G and H can be changed by specifying the tuple\n       rename=('G-','H-') (for example).  Node \"u\" in G is then renamed\n       \"G-u\" and \"v\" in H is renamed \"H-v\".\n\n    name : string\n       Specify the name for the union graph\n\n    Returns\n    -------\n    U : A union graph with the same type as G.\n\n    Notes\n    -----\n    To force a disjoint union with node relabeling, use\n    disjoint_union(G,H) or convert_node_labels_to integers().\n\n    Graph, edge, and node attributes are propagated from G and H\n    to the union graph.  If a graph attribute is present in both\n    G and H the value from H is used.\n\n    See Also\n    --------\n    disjoint_union\n    \"\"\"\n    if not G.is_multigraph() == H.is_multigraph():\n        raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n    # Union is the same type as G\n    R = G.__class__()\n    if name is None:\n        name = \"union( %s, %s )\" % (G.name, H.name)\n    R.name = name\n\n    # rename graph to obtain disjoint node labels\n    def add_prefix(graph, prefix):\n        if prefix is None:\n            return graph\n\n        def label(x):\n            if is_string_like(x):\n                name = prefix + x\n            else:\n                name = prefix + repr(x)\n            return name\n        return nx.relabel_nodes(graph, label)\n    G = add_prefix(G, rename[0])\n    H = add_prefix(H, rename[1])\n    if set(G) & set(H):\n        raise nx.NetworkXError('The node sets of G and H are not disjoint.',\n                               'Use appropriate rename=(Gprefix,Hprefix)'\n                               'or use disjoint_union(G,H).')\n    if G.is_multigraph():\n        G_edges = G.edges(keys=True, data=True)\n    else:\n        G_edges = G.edges(data=True)\n    if H.is_multigraph():\n        H_edges = H.edges(keys=True, data=True)\n    else:\n        H_edges = H.edges(data=True)\n\n    # add nodes\n    R.add_nodes_from(G)\n    R.add_edges_from(G_edges)\n    # add edges\n    R.add_nodes_from(H)\n    R.add_edges_from(H_edges)\n    # add node attributes\n    R.node.update(G.node)\n    R.node.update(H.node)\n    # add graph attributes, H attributes take precedent over G attributes\n    R.graph.update(G.graph)\n    R.graph.update(H.graph)\n\n    return R\n\n\ndef disjoint_union(G, H):\n    \"\"\" Return the disjoint union of graphs G and H.\n\n    This algorithm forces distinct integer node labels.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph\n\n    Returns\n    -------\n    U : A union graph with the same type as G.\n\n    Notes\n    -----\n    A new graph is created, of the same class as G.  It is recommended\n    that G and H be either both directed or both undirected.\n\n    The nodes of G are relabeled 0 to len(G)-1, and the nodes of H are\n    relabeled len(G) to len(G)+len(H)-1.\n\n    Graph, edge, and node attributes are propagated from G and H\n    to the union graph.  If a graph attribute is present in both\n    G and H the value from H is used.\n    \"\"\"\n    R1 = nx.convert_node_labels_to_integers(G)\n    R2 = nx.convert_node_labels_to_integers(H, first_label=len(R1))\n    R = union(R1, R2)\n    R.name = \"disjoint_union( %s, %s )\" % (G.name, H.name)\n    R.graph.update(G.graph)\n    R.graph.update(H.graph)\n    return R\n\n\ndef intersection(G, H):\n    \"\"\"Return a new graph that contains only the edges that exist in\n    both G and H.\n\n    The node sets of H and G must be the same.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph.  G and H must have the same node sets.\n\n    Returns\n    -------\n    GH : A new graph with the same type as G.\n\n    Notes\n    -----\n    Attributes from the graph, nodes, and edges are not copied to the new\n    graph.  If you want a new graph of the intersection of G and H\n    with the attributes (including edge data) from G use remove_nodes_from()\n    as follows\n\n    >>> G=nx.path_graph(3)\n    >>> H=nx.path_graph(5)\n    >>> R=G.copy()\n    >>> R.remove_nodes_from(n for n in G if n not in H)\n    \"\"\"\n    # create new graph\n    R = nx.create_empty_copy(G)\n\n    R.name = \"Intersection of (%s and %s)\" % (G.name, H.name)\n    if not G.is_multigraph() == H.is_multigraph():\n        raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n    if set(G) != set(H):\n        raise nx.NetworkXError(\"Node sets of graphs are not equal\")\n\n    if G.number_of_edges() <= H.number_of_edges():\n        if G.is_multigraph():\n            edges = G.edges(keys=True)\n        else:\n            edges = G.edges()\n        for e in edges:\n            if H.has_edge(*e):\n                R.add_edge(*e)\n    else:\n        if H.is_multigraph():\n            edges = H.edges(keys=True)\n        else:\n            edges = H.edges()\n        for e in edges:\n            if G.has_edge(*e):\n                R.add_edge(*e)\n\n    return R\n\n\ndef difference(G, H):\n    \"\"\"Return a new graph that contains the edges that exist in G but not in H.\n\n    The node sets of H and G must be the same.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph.  G and H must have the same node sets.\n\n    Returns\n    -------\n    D : A new graph with the same type as G.\n\n    Notes\n    -----\n    Attributes from the graph, nodes, and edges are not copied to the new\n    graph.  If you want a new graph of the difference of G and H with\n    with the attributes (including edge data) from G use remove_nodes_from()\n    as follows:\n\n    >>> G = nx.path_graph(3)\n    >>> H = nx.path_graph(5)\n    >>> R = G.copy()\n    >>> R.remove_nodes_from(n for n in G if n in H)\n    \"\"\"\n    # create new graph\n    if not G.is_multigraph() == H.is_multigraph():\n        raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n    R = nx.create_empty_copy(G)\n    R.name = \"Difference of (%s and %s)\" % (G.name, H.name)\n\n    if set(G) != set(H):\n        raise nx.NetworkXError(\"Node sets of graphs not equal\")\n\n    if G.is_multigraph():\n        edges = G.edges(keys=True)\n    else:\n        edges = G.edges()\n    for e in edges:\n        if not H.has_edge(*e):\n            R.add_edge(*e)\n    return R\n\n\ndef symmetric_difference(G, H):\n    \"\"\"Return new graph with edges that exist in either G or H but not both.\n\n    The node sets of H and G must be the same.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph.  G and H must have the same node sets.\n\n    Returns\n    -------\n    D : A new graph with the same type as G.\n\n    Notes\n    -----\n    Attributes from the graph, nodes, and edges are not copied to the new\n    graph.\n    \"\"\"\n    # create new graph\n    if not G.is_multigraph() == H.is_multigraph():\n        raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n    R = nx.create_empty_copy(G)\n    R.name = \"Symmetric difference of (%s and %s)\" % (G.name, H.name)\n\n    if set(G) != set(H):\n        raise nx.NetworkXError(\"Node sets of graphs not equal\")\n\n    gnodes = set(G)  # set of nodes in G\n    hnodes = set(H)  # set of nodes in H\n    nodes = gnodes.symmetric_difference(hnodes)\n    R.add_nodes_from(nodes)\n\n    if G.is_multigraph():\n        edges = G.edges(keys=True)\n    else:\n        edges = G.edges()\n    # we could copy the data here but then this function doesn't\n    # match intersection and difference\n    for e in edges:\n        if not H.has_edge(*e):\n            R.add_edge(*e)\n\n    if H.is_multigraph():\n        edges = H.edges(keys=True)\n    else:\n        edges = H.edges()\n    for e in edges:\n        if not G.has_edge(*e):\n            R.add_edge(*e)\n    return R\n\n\ndef compose(G, H, name=None):\n    \"\"\"Return a new graph of G composed with H.\n\n    Composition is the simple union of the node sets and edge sets.\n    The node sets of G and H do not need to be disjoint.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph\n\n    name : string\n       Specify name for new graph\n\n    Returns\n    -------\n    C: A new graph  with the same type as G\n\n    Notes\n    -----\n    It is recommended that G and H be either both directed or both undirected.\n    Attributes from H take precedent over attributes from G.\n    \"\"\"\n    if not G.is_multigraph() == H.is_multigraph():\n        raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n\n    if name is None:\n        name = \"compose( %s, %s )\" % (G.name, H.name)\n    R = G.__class__()\n    R.name = name\n\n    R.add_nodes_from(G.nodes(data=True))\n    R.add_nodes_from(H.nodes(data=True))\n\n    if G.is_multigraph():\n        R.add_edges_from(G.edges(keys=True, data=True))\n    else:\n        R.add_edges_from(G.edges(data=True))\n    if H.is_multigraph():\n        R.add_edges_from(H.edges(keys=True, data=True))\n    else:\n        R.add_edges_from(H.edges(data=True))\n\n    # add graph attributes, H attributes take precedent over G attributes\n    R.graph.update(G.graph)\n    R.graph.update(H.graph)\n    return R\n", "path": "networkx/algorithms/operators/binary.py"}], "after_files": [{"content": "\"\"\"\nOperations on graphs including union, intersection, difference.\n\"\"\"\n#    Copyright (C) 2004-2016 by\n#    Aric Hagberg <hagberg@lanl.gov>\n#    Dan Schult <dschult@colgate.edu>\n#    Pieter Swart <swart@lanl.gov>\n#    All rights reserved.\n#    BSD license.\nimport networkx as nx\nfrom networkx.utils import is_string_like\n__author__ = \"\"\"\\n\"\"\".join(['Aric Hagberg <aric.hagberg@gmail.com>',\n                           'Pieter Swart (swart@lanl.gov)',\n                           'Dan Schult(dschult@colgate.edu)'])\n__all__ = ['union', 'compose', 'disjoint_union', 'intersection',\n           'difference', 'symmetric_difference']\n\n\ndef union(G, H, rename=(None, None), name=None):\n    \"\"\" Return the union of graphs G and H.\n\n    Graphs G and H must be disjoint, otherwise an exception is raised.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph\n\n    create_using : NetworkX graph\n       Use specified graph for result.  Otherwise\n\n    rename : bool , default=(None, None)\n       Node names of G and H can be changed by specifying the tuple\n       rename=('G-','H-') (for example).  Node \"u\" in G is then renamed\n       \"G-u\" and \"v\" in H is renamed \"H-v\".\n\n    name : string\n       Specify the name for the union graph\n\n    Returns\n    -------\n    U : A union graph with the same type as G.\n\n    Notes\n    -----\n    To force a disjoint union with node relabeling, use\n    disjoint_union(G,H) or convert_node_labels_to integers().\n\n    Graph, edge, and node attributes are propagated from G and H\n    to the union graph.  If a graph attribute is present in both\n    G and H the value from H is used.\n\n    See Also\n    --------\n    disjoint_union\n    \"\"\"\n    if not G.is_multigraph() == H.is_multigraph():\n        raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n    # Union is the same type as G\n    R = G.__class__()\n    if name is None:\n        name = \"union( %s, %s )\" % (G.name, H.name)\n    R.name = name\n\n    # rename graph to obtain disjoint node labels\n    def add_prefix(graph, prefix):\n        if prefix is None:\n            return graph\n\n        def label(x):\n            if is_string_like(x):\n                name = prefix + x\n            else:\n                name = prefix + repr(x)\n            return name\n        return nx.relabel_nodes(graph, label)\n    G = add_prefix(G, rename[0])\n    H = add_prefix(H, rename[1])\n    if set(G) & set(H):\n        raise nx.NetworkXError('The node sets of G and H are not disjoint.',\n                               'Use appropriate rename=(Gprefix,Hprefix)'\n                               'or use disjoint_union(G,H).')\n    if G.is_multigraph():\n        G_edges = G.edges(keys=True, data=True)\n    else:\n        G_edges = G.edges(data=True)\n    if H.is_multigraph():\n        H_edges = H.edges(keys=True, data=True)\n    else:\n        H_edges = H.edges(data=True)\n\n    # add nodes\n    R.add_nodes_from(G)\n    R.add_edges_from(G_edges)\n    # add edges\n    R.add_nodes_from(H)\n    R.add_edges_from(H_edges)\n    # add node attributes\n    R.node.update(G.node)\n    R.node.update(H.node)\n    # add graph attributes, H attributes take precedent over G attributes\n    R.graph.update(G.graph)\n    R.graph.update(H.graph)\n\n    return R\n\n\ndef disjoint_union(G, H):\n    \"\"\" Return the disjoint union of graphs G and H.\n\n    This algorithm forces distinct integer node labels.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph\n\n    Returns\n    -------\n    U : A union graph with the same type as G.\n\n    Notes\n    -----\n    A new graph is created, of the same class as G.  It is recommended\n    that G and H be either both directed or both undirected.\n\n    The nodes of G are relabeled 0 to len(G)-1, and the nodes of H are\n    relabeled len(G) to len(G)+len(H)-1.\n\n    Graph, edge, and node attributes are propagated from G and H\n    to the union graph.  If a graph attribute is present in both\n    G and H the value from H is used.\n    \"\"\"\n    R1 = nx.convert_node_labels_to_integers(G)\n    R2 = nx.convert_node_labels_to_integers(H, first_label=len(R1))\n    R = union(R1, R2)\n    R.name = \"disjoint_union( %s, %s )\" % (G.name, H.name)\n    R.graph.update(G.graph)\n    R.graph.update(H.graph)\n    return R\n\n\ndef intersection(G, H):\n    \"\"\"Return a new graph that contains only the edges that exist in\n    both G and H.\n\n    The node sets of H and G must be the same.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph.  G and H must have the same node sets.\n\n    Returns\n    -------\n    GH : A new graph with the same type as G.\n\n    Notes\n    -----\n    Attributes from the graph, nodes, and edges are not copied to the new\n    graph.  If you want a new graph of the intersection of G and H\n    with the attributes (including edge data) from G use remove_nodes_from()\n    as follows\n\n    >>> G=nx.path_graph(3)\n    >>> H=nx.path_graph(5)\n    >>> R=G.copy()\n    >>> R.remove_nodes_from(n for n in G if n not in H)\n    \"\"\"\n    # create new graph\n    R = nx.create_empty_copy(G)\n\n    R.name = \"Intersection of (%s and %s)\" % (G.name, H.name)\n    if not G.is_multigraph() == H.is_multigraph():\n        raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n    if set(G) != set(H):\n        raise nx.NetworkXError(\"Node sets of graphs are not equal\")\n\n    if G.number_of_edges() <= H.number_of_edges():\n        if G.is_multigraph():\n            edges = G.edges(keys=True)\n        else:\n            edges = G.edges()\n        for e in edges:\n            if H.has_edge(*e):\n                R.add_edge(*e)\n    else:\n        if H.is_multigraph():\n            edges = H.edges(keys=True)\n        else:\n            edges = H.edges()\n        for e in edges:\n            if G.has_edge(*e):\n                R.add_edge(*e)\n\n    return R\n\n\ndef difference(G, H):\n    \"\"\"Return a new graph that contains the edges that exist in G but not in H.\n\n    The node sets of H and G must be the same.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph.  G and H must have the same node sets.\n\n    Returns\n    -------\n    D : A new graph with the same type as G.\n\n    Notes\n    -----\n    Attributes from the graph, nodes, and edges are not copied to the new\n    graph.  If you want a new graph of the difference of G and H with\n    with the attributes (including edge data) from G use remove_nodes_from()\n    as follows:\n\n    >>> G = nx.path_graph(3)\n    >>> H = nx.path_graph(5)\n    >>> R = G.copy()\n    >>> R.remove_nodes_from(n for n in G if n in H)\n    \"\"\"\n    # create new graph\n    if not G.is_multigraph() == H.is_multigraph():\n        raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n    R = nx.create_empty_copy(G)\n    R.name = \"Difference of (%s and %s)\" % (G.name, H.name)\n\n    if set(G) != set(H):\n        raise nx.NetworkXError(\"Node sets of graphs not equal\")\n\n    if G.is_multigraph():\n        edges = G.edges(keys=True)\n    else:\n        edges = G.edges()\n    for e in edges:\n        if not H.has_edge(*e):\n            R.add_edge(*e)\n    return R\n\n\ndef symmetric_difference(G, H):\n    \"\"\"Return new graph with edges that exist in either G or H but not both.\n\n    The node sets of H and G must be the same.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph.  G and H must have the same node sets.\n\n    Returns\n    -------\n    D : A new graph with the same type as G.\n\n    Notes\n    -----\n    Attributes from the graph, nodes, and edges are not copied to the new\n    graph.\n    \"\"\"\n    # create new graph\n    if not G.is_multigraph() == H.is_multigraph():\n        raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n    R = nx.create_empty_copy(G)\n    R.name = \"Symmetric difference of (%s and %s)\" % (G.name, H.name)\n\n    if set(G) != set(H):\n        raise nx.NetworkXError(\"Node sets of graphs not equal\")\n\n    gnodes = set(G)  # set of nodes in G\n    hnodes = set(H)  # set of nodes in H\n    nodes = gnodes.symmetric_difference(hnodes)\n    R.add_nodes_from(nodes)\n\n    if G.is_multigraph():\n        edges = G.edges(keys=True)\n    else:\n        edges = G.edges()\n    # we could copy the data here but then this function doesn't\n    # match intersection and difference\n    for e in edges:\n        if not H.has_edge(*e):\n            R.add_edge(*e)\n\n    if H.is_multigraph():\n        edges = H.edges(keys=True)\n    else:\n        edges = H.edges()\n    for e in edges:\n        if not G.has_edge(*e):\n            R.add_edge(*e)\n    return R\n\n\ndef compose(G, H, name=None):\n    \"\"\"Return a new graph of G composed with H.\n\n    Composition is the simple union of the node sets and edge sets.\n    The node sets of G and H do not need to be disjoint.\n\n    Parameters\n    ----------\n    G,H : graph\n       A NetworkX graph\n\n    name : string\n       Specify name for new graph\n\n    Returns\n    -------\n    C: A new graph  with the same type as G\n\n    Notes\n    -----\n    It is recommended that G and H be either both directed or both undirected.\n    Attributes from H take precedent over attributes from G.\n\n    For MultiGraphs, the edges are identified by incident nodes AND edge-key.\n    This can cause surprises (i.e., edge `(1, 2)` may or may not be the same\n    in two graphs) if you use MultiGraph without keeping track of edge keys.\n    \"\"\"\n    if not G.is_multigraph() == H.is_multigraph():\n        raise nx.NetworkXError('G and H must both be graphs or multigraphs.')\n\n    if name is None:\n        name = \"compose( %s, %s )\" % (G.name, H.name)\n    R = G.__class__()\n    R.name = name\n\n    R.add_nodes_from(G.nodes(data=True))\n    R.add_nodes_from(H.nodes(data=True))\n\n    if G.is_multigraph():\n        R.add_edges_from(G.edges(keys=True, data=True))\n    else:\n        R.add_edges_from(G.edges(data=True))\n    if H.is_multigraph():\n        R.add_edges_from(H.edges(keys=True, data=True))\n    else:\n        R.add_edges_from(H.edges(data=True))\n\n    # add graph attributes, H attributes take precedent over G attributes\n    R.graph.update(G.graph)\n    R.graph.update(H.graph)\n    return R\n", "path": "networkx/algorithms/operators/binary.py"}]}