Fixed database typo and removed unnecessary class identifier.

This commit is contained in:
Batuhan Berk Başoğlu 2020-10-14 10:10:37 -04:00
parent 00ad49a143
commit 45fb349a7d
5098 changed files with 952558 additions and 85 deletions

View file

@ -0,0 +1,4 @@
from networkx.algorithms.operators.all import *
from networkx.algorithms.operators.binary import *
from networkx.algorithms.operators.product import *
from networkx.algorithms.operators.unary import *

View file

@ -0,0 +1,163 @@
"""Operations on many graphs.
"""
from itertools import zip_longest
import networkx as nx
__all__ = ["union_all", "compose_all", "disjoint_union_all", "intersection_all"]
def union_all(graphs, rename=(None,)):
"""Returns the union of all graphs.
The graphs must be disjoint, otherwise an exception is raised.
Parameters
----------
graphs : list of graphs
List of NetworkX graphs
rename : bool , default=(None, None)
Node names of G and H can be changed by specifying the tuple
rename=('G-','H-') (for example). Node "u" in G is then renamed
"G-u" and "v" in H is renamed "H-v".
Returns
-------
U : a graph with the same type as the first graph in list
Raises
------
ValueError
If `graphs` is an empty list.
Notes
-----
To force a disjoint union with node relabeling, use
disjoint_union_all(G,H) or convert_node_labels_to integers().
Graph, edge, and node attributes are propagated to the union graph.
If a graph attribute is present in multiple graphs, then the value
from the last graph in the list with that attribute is used.
See Also
--------
union
disjoint_union_all
"""
if not graphs:
raise ValueError("cannot apply union_all to an empty list")
graphs_names = zip_longest(graphs, rename)
U, gname = next(graphs_names)
for H, hname in graphs_names:
U = nx.union(U, H, (gname, hname))
gname = None
return U
def disjoint_union_all(graphs):
"""Returns the disjoint union of all graphs.
This operation forces distinct integer node labels starting with 0
for the first graph in the list and numbering consecutively.
Parameters
----------
graphs : list
List of NetworkX graphs
Returns
-------
U : A graph with the same type as the first graph in list
Raises
------
ValueError
If `graphs` is an empty list.
Notes
-----
It is recommended that the graphs be either all directed or all undirected.
Graph, edge, and node attributes are propagated to the union graph.
If a graph attribute is present in multiple graphs, then the value
from the last graph in the list with that attribute is used.
"""
if not graphs:
raise ValueError("cannot apply disjoint_union_all to an empty list")
graphs = iter(graphs)
U = next(graphs)
for H in graphs:
U = nx.disjoint_union(U, H)
return U
def compose_all(graphs):
"""Returns the composition of all graphs.
Composition is the simple union of the node sets and edge sets.
The node sets of the supplied graphs need not be disjoint.
Parameters
----------
graphs : list
List of NetworkX graphs
Returns
-------
C : A graph with the same type as the first graph in list
Raises
------
ValueError
If `graphs` is an empty list.
Notes
-----
It is recommended that the supplied graphs be either all directed or all
undirected.
Graph, edge, and node attributes are propagated to the union graph.
If a graph attribute is present in multiple graphs, then the value
from the last graph in the list with that attribute is used.
"""
if not graphs:
raise ValueError("cannot apply compose_all to an empty list")
graphs = iter(graphs)
C = next(graphs)
for H in graphs:
C = nx.compose(C, H)
return C
def intersection_all(graphs):
"""Returns a new graph that contains only the edges that exist in
all graphs.
All supplied graphs must have the same node set.
Parameters
----------
graphs : list
List of NetworkX graphs
Returns
-------
R : A new graph with the same type as the first graph in list
Raises
------
ValueError
If `graphs` is an empty list.
Notes
-----
Attributes from the graph, nodes, and edges are not copied to the new
graph.
"""
if not graphs:
raise ValueError("cannot apply intersection_all to an empty list")
graphs = iter(graphs)
R = next(graphs)
for H in graphs:
R = nx.intersection(R, H)
return R

View file

@ -0,0 +1,404 @@
"""
Operations on graphs including union, intersection, difference.
"""
import networkx as nx
__all__ = [
"union",
"compose",
"disjoint_union",
"intersection",
"difference",
"symmetric_difference",
"full_join",
]
def union(G, H, rename=(None, None), name=None):
""" Return the union of graphs G and H.
Graphs G and H must be disjoint, otherwise an exception is raised.
Parameters
----------
G,H : graph
A NetworkX graph
rename : bool , default=(None, None)
Node names of G and H can be changed by specifying the tuple
rename=('G-','H-') (for example). Node "u" in G is then renamed
"G-u" and "v" in H is renamed "H-v".
name : string
Specify the name for the union graph
Returns
-------
U : A union graph with the same type as G.
Notes
-----
To force a disjoint union with node relabeling, use
disjoint_union(G,H) or convert_node_labels_to integers().
Graph, edge, and node attributes are propagated from G and H
to the union graph. If a graph attribute is present in both
G and H the value from H is used.
See Also
--------
disjoint_union
"""
if not G.is_multigraph() == H.is_multigraph():
raise nx.NetworkXError("G and H must both be graphs or multigraphs.")
# Union is the same type as G
R = G.__class__()
# add graph attributes, H attributes take precedent over G attributes
R.graph.update(G.graph)
R.graph.update(H.graph)
# rename graph to obtain disjoint node labels
def add_prefix(graph, prefix):
if prefix is None:
return graph
def label(x):
if isinstance(x, str):
name = prefix + x
else:
name = prefix + repr(x)
return name
return nx.relabel_nodes(graph, label)
G = add_prefix(G, rename[0])
H = add_prefix(H, rename[1])
if set(G) & set(H):
raise nx.NetworkXError(
"The node sets of G and H are not disjoint.",
"Use appropriate rename=(Gprefix,Hprefix)" "or use disjoint_union(G,H).",
)
if G.is_multigraph():
G_edges = G.edges(keys=True, data=True)
else:
G_edges = G.edges(data=True)
if H.is_multigraph():
H_edges = H.edges(keys=True, data=True)
else:
H_edges = H.edges(data=True)
# add nodes
R.add_nodes_from(G)
R.add_nodes_from(H)
# add edges
R.add_edges_from(G_edges)
R.add_edges_from(H_edges)
# add node attributes
for n in G:
R.nodes[n].update(G.nodes[n])
for n in H:
R.nodes[n].update(H.nodes[n])
return R
def disjoint_union(G, H):
""" Return the disjoint union of graphs G and H.
This algorithm forces distinct integer node labels.
Parameters
----------
G,H : graph
A NetworkX graph
Returns
-------
U : A union graph with the same type as G.
Notes
-----
A new graph is created, of the same class as G. It is recommended
that G and H be either both directed or both undirected.
The nodes of G are relabeled 0 to len(G)-1, and the nodes of H are
relabeled len(G) to len(G)+len(H)-1.
Graph, edge, and node attributes are propagated from G and H
to the union graph. If a graph attribute is present in both
G and H the value from H is used.
"""
R1 = nx.convert_node_labels_to_integers(G)
R2 = nx.convert_node_labels_to_integers(H, first_label=len(R1))
R = union(R1, R2)
R.graph.update(G.graph)
R.graph.update(H.graph)
return R
def intersection(G, H):
"""Returns a new graph that contains only the edges that exist in
both G and H.
The node sets of H and G must be the same.
Parameters
----------
G,H : graph
A NetworkX graph. G and H must have the same node sets.
Returns
-------
GH : A new graph with the same type as G.
Notes
-----
Attributes from the graph, nodes, and edges are not copied to the new
graph. If you want a new graph of the intersection of G and H
with the attributes (including edge data) from G use remove_nodes_from()
as follows
>>> G = nx.path_graph(3)
>>> H = nx.path_graph(5)
>>> R = G.copy()
>>> R.remove_nodes_from(n for n in G if n not in H)
"""
# create new graph
R = nx.create_empty_copy(G)
if not G.is_multigraph() == H.is_multigraph():
raise nx.NetworkXError("G and H must both be graphs or multigraphs.")
if set(G) != set(H):
raise nx.NetworkXError("Node sets of graphs are not equal")
if G.number_of_edges() <= H.number_of_edges():
if G.is_multigraph():
edges = G.edges(keys=True)
else:
edges = G.edges()
for e in edges:
if H.has_edge(*e):
R.add_edge(*e)
else:
if H.is_multigraph():
edges = H.edges(keys=True)
else:
edges = H.edges()
for e in edges:
if G.has_edge(*e):
R.add_edge(*e)
return R
def difference(G, H):
"""Returns a new graph that contains the edges that exist in G but not in H.
The node sets of H and G must be the same.
Parameters
----------
G,H : graph
A NetworkX graph. G and H must have the same node sets.
Returns
-------
D : A new graph with the same type as G.
Notes
-----
Attributes from the graph, nodes, and edges are not copied to the new
graph. If you want a new graph of the difference of G and H with
with the attributes (including edge data) from G use remove_nodes_from()
as follows:
>>> G = nx.path_graph(3)
>>> H = nx.path_graph(5)
>>> R = G.copy()
>>> R.remove_nodes_from(n for n in G if n in H)
"""
# create new graph
if not G.is_multigraph() == H.is_multigraph():
raise nx.NetworkXError("G and H must both be graphs or multigraphs.")
R = nx.create_empty_copy(G)
if set(G) != set(H):
raise nx.NetworkXError("Node sets of graphs not equal")
if G.is_multigraph():
edges = G.edges(keys=True)
else:
edges = G.edges()
for e in edges:
if not H.has_edge(*e):
R.add_edge(*e)
return R
def symmetric_difference(G, H):
"""Returns new graph with edges that exist in either G or H but not both.
The node sets of H and G must be the same.
Parameters
----------
G,H : graph
A NetworkX graph. G and H must have the same node sets.
Returns
-------
D : A new graph with the same type as G.
Notes
-----
Attributes from the graph, nodes, and edges are not copied to the new
graph.
"""
# create new graph
if not G.is_multigraph() == H.is_multigraph():
raise nx.NetworkXError("G and H must both be graphs or multigraphs.")
R = nx.create_empty_copy(G)
if set(G) != set(H):
raise nx.NetworkXError("Node sets of graphs not equal")
gnodes = set(G) # set of nodes in G
hnodes = set(H) # set of nodes in H
nodes = gnodes.symmetric_difference(hnodes)
R.add_nodes_from(nodes)
if G.is_multigraph():
edges = G.edges(keys=True)
else:
edges = G.edges()
# we could copy the data here but then this function doesn't
# match intersection and difference
for e in edges:
if not H.has_edge(*e):
R.add_edge(*e)
if H.is_multigraph():
edges = H.edges(keys=True)
else:
edges = H.edges()
for e in edges:
if not G.has_edge(*e):
R.add_edge(*e)
return R
def compose(G, H):
"""Returns a new graph of G composed with H.
Composition is the simple union of the node sets and edge sets.
The node sets of G and H do not need to be disjoint.
Parameters
----------
G, H : graph
A NetworkX graph
Returns
-------
C: A new graph with the same type as G
Notes
-----
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.")
R = G.__class__()
# add graph attributes, H attributes take precedent over G attributes
R.graph.update(G.graph)
R.graph.update(H.graph)
R.add_nodes_from(G.nodes(data=True))
R.add_nodes_from(H.nodes(data=True))
if G.is_multigraph():
R.add_edges_from(G.edges(keys=True, data=True))
else:
R.add_edges_from(G.edges(data=True))
if H.is_multigraph():
R.add_edges_from(H.edges(keys=True, data=True))
else:
R.add_edges_from(H.edges(data=True))
return R
def full_join(G, H, rename=(None, None)):
"""Returns the full join of graphs G and H.
Full join is the union of G and H in which all edges between
G and H are added.
The node sets of G and H must be disjoint,
otherwise an exception is raised.
Parameters
----------
G, H : graph
A NetworkX graph
rename : bool , default=(None, None)
Node names of G and H can be changed by specifying the tuple
rename=('G-','H-') (for example). Node "u" in G is then renamed
"G-u" and "v" in H is renamed "H-v".
Returns
-------
U : The full join graph with the same type as G.
Notes
-----
It is recommended that G and H be either both directed or both undirected.
If G is directed, then edges from G to H are added as well as from H to G.
Note that full_join() does not produce parallel edges for MultiGraphs.
The full join operation of graphs G and H is the same as getting
their complement, performing a disjoint union, and finally getting
the complement of the resulting graph.
Graph, edge, and node attributes are propagated from G and H
to the union graph. If a graph attribute is present in both
G and H the value from H is used.
See Also
--------
union
disjoint_union
"""
R = union(G, H, rename)
def add_prefix(graph, prefix):
if prefix is None:
return graph
def label(x):
if isinstance(x, str):
name = prefix + x
else:
name = prefix + repr(x)
return name
return nx.relabel_nodes(graph, label)
G = add_prefix(G, rename[0])
H = add_prefix(H, rename[1])
for i in G:
for j in H:
R.add_edge(i, j)
if R.is_directed():
for i in H:
for j in G:
R.add_edge(i, j)
return R

View file

@ -0,0 +1,461 @@
"""
Graph products.
"""
from itertools import product
import networkx as nx
from networkx.utils import not_implemented_for
__all__ = [
"tensor_product",
"cartesian_product",
"lexicographic_product",
"strong_product",
"power",
"rooted_product",
]
def _dict_product(d1, d2):
return {k: (d1.get(k), d2.get(k)) for k in set(d1) | set(d2)}
# Generators for producting graph products
def _node_product(G, H):
for u, v in product(G, H):
yield ((u, v), _dict_product(G.nodes[u], H.nodes[v]))
def _directed_edges_cross_edges(G, H):
if not G.is_multigraph() and not H.is_multigraph():
for u, v, c in G.edges(data=True):
for x, y, d in H.edges(data=True):
yield (u, x), (v, y), _dict_product(c, d)
if not G.is_multigraph() and H.is_multigraph():
for u, v, c in G.edges(data=True):
for x, y, k, d in H.edges(data=True, keys=True):
yield (u, x), (v, y), k, _dict_product(c, d)
if G.is_multigraph() and not H.is_multigraph():
for u, v, k, c in G.edges(data=True, keys=True):
for x, y, d in H.edges(data=True):
yield (u, x), (v, y), k, _dict_product(c, d)
if G.is_multigraph() and H.is_multigraph():
for u, v, j, c in G.edges(data=True, keys=True):
for x, y, k, d in H.edges(data=True, keys=True):
yield (u, x), (v, y), (j, k), _dict_product(c, d)
def _undirected_edges_cross_edges(G, H):
if not G.is_multigraph() and not H.is_multigraph():
for u, v, c in G.edges(data=True):
for x, y, d in H.edges(data=True):
yield (v, x), (u, y), _dict_product(c, d)
if not G.is_multigraph() and H.is_multigraph():
for u, v, c in G.edges(data=True):
for x, y, k, d in H.edges(data=True, keys=True):
yield (v, x), (u, y), k, _dict_product(c, d)
if G.is_multigraph() and not H.is_multigraph():
for u, v, k, c in G.edges(data=True, keys=True):
for x, y, d in H.edges(data=True):
yield (v, x), (u, y), k, _dict_product(c, d)
if G.is_multigraph() and H.is_multigraph():
for u, v, j, c in G.edges(data=True, keys=True):
for x, y, k, d in H.edges(data=True, keys=True):
yield (v, x), (u, y), (j, k), _dict_product(c, d)
def _edges_cross_nodes(G, H):
if G.is_multigraph():
for u, v, k, d in G.edges(data=True, keys=True):
for x in H:
yield (u, x), (v, x), k, d
else:
for u, v, d in G.edges(data=True):
for x in H:
if H.is_multigraph():
yield (u, x), (v, x), None, d
else:
yield (u, x), (v, x), d
def _nodes_cross_edges(G, H):
if H.is_multigraph():
for x in G:
for u, v, k, d in H.edges(data=True, keys=True):
yield (x, u), (x, v), k, d
else:
for x in G:
for u, v, d in H.edges(data=True):
if G.is_multigraph():
yield (x, u), (x, v), None, d
else:
yield (x, u), (x, v), d
def _edges_cross_nodes_and_nodes(G, H):
if G.is_multigraph():
for u, v, k, d in G.edges(data=True, keys=True):
for x in H:
for y in H:
yield (u, x), (v, y), k, d
else:
for u, v, d in G.edges(data=True):
for x in H:
for y in H:
if H.is_multigraph():
yield (u, x), (v, y), None, d
else:
yield (u, x), (v, y), d
def _init_product_graph(G, H):
if not G.is_directed() == H.is_directed():
msg = "G and H must be both directed or both undirected"
raise nx.NetworkXError(msg)
if G.is_multigraph() or H.is_multigraph():
GH = nx.MultiGraph()
else:
GH = nx.Graph()
if G.is_directed():
GH = GH.to_directed()
return GH
def tensor_product(G, H):
r"""Returns the tensor product of G and H.
The tensor product $P$ of the graphs $G$ and $H$ has a node set that
is the tensor product of the node sets, $V(P)=V(G) \times V(H)$.
$P$ has an edge $((u,v), (x,y))$ if and only if $(u,x)$ is an edge in $G$
and $(v,y)$ is an edge in $H$.
Tensor product is sometimes also referred to as the categorical product,
direct product, cardinal product or conjunction.
Parameters
----------
G, H: graphs
Networkx graphs.
Returns
-------
P: NetworkX graph
The tensor product of G and H. P will be a multi-graph if either G
or H is a multi-graph, will be a directed if G and H are directed,
and undirected if G and H are undirected.
Raises
------
NetworkXError
If G and H are not both directed or both undirected.
Notes
-----
Node attributes in P are two-tuple of the G and H node attributes.
Missing attributes are assigned None.
Examples
--------
>>> G = nx.Graph()
>>> H = nx.Graph()
>>> G.add_node(0, a1=True)
>>> H.add_node("a", a2="Spam")
>>> P = nx.tensor_product(G, H)
>>> list(P)
[(0, 'a')]
Edge attributes and edge keys (for multigraphs) are also copied to the
new product graph
"""
GH = _init_product_graph(G, H)
GH.add_nodes_from(_node_product(G, H))
GH.add_edges_from(_directed_edges_cross_edges(G, H))
if not GH.is_directed():
GH.add_edges_from(_undirected_edges_cross_edges(G, H))
return GH
def cartesian_product(G, H):
r"""Returns the Cartesian product of G and H.
The Cartesian product $P$ of the graphs $G$ and $H$ has a node set that
is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$.
$P$ has an edge $((u,v),(x,y))$ if and only if either $u$ is equal to $x$
and both $v$ and $y$ are adjacent in $H$ or if $v$ is equal to $y$ and
both $u$ and $x$ are adjacent in $G$.
Parameters
----------
G, H: graphs
Networkx graphs.
Returns
-------
P: NetworkX graph
The Cartesian product of G and H. P will be a multi-graph if either G
or H is a multi-graph. Will be a directed if G and H are directed,
and undirected if G and H are undirected.
Raises
------
NetworkXError
If G and H are not both directed or both undirected.
Notes
-----
Node attributes in P are two-tuple of the G and H node attributes.
Missing attributes are assigned None.
Examples
--------
>>> G = nx.Graph()
>>> H = nx.Graph()
>>> G.add_node(0, a1=True)
>>> H.add_node("a", a2="Spam")
>>> P = nx.cartesian_product(G, H)
>>> list(P)
[(0, 'a')]
Edge attributes and edge keys (for multigraphs) are also copied to the
new product graph
"""
GH = _init_product_graph(G, H)
GH.add_nodes_from(_node_product(G, H))
GH.add_edges_from(_edges_cross_nodes(G, H))
GH.add_edges_from(_nodes_cross_edges(G, H))
return GH
def lexicographic_product(G, H):
r"""Returns the lexicographic product of G and H.
The lexicographical product $P$ of the graphs $G$ and $H$ has a node set
that is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$.
$P$ has an edge $((u,v), (x,y))$ if and only if $(u,v)$ is an edge in $G$
or $u==v$ and $(x,y)$ is an edge in $H$.
Parameters
----------
G, H: graphs
Networkx graphs.
Returns
-------
P: NetworkX graph
The Cartesian product of G and H. P will be a multi-graph if either G
or H is a multi-graph. Will be a directed if G and H are directed,
and undirected if G and H are undirected.
Raises
------
NetworkXError
If G and H are not both directed or both undirected.
Notes
-----
Node attributes in P are two-tuple of the G and H node attributes.
Missing attributes are assigned None.
Examples
--------
>>> G = nx.Graph()
>>> H = nx.Graph()
>>> G.add_node(0, a1=True)
>>> H.add_node("a", a2="Spam")
>>> P = nx.lexicographic_product(G, H)
>>> list(P)
[(0, 'a')]
Edge attributes and edge keys (for multigraphs) are also copied to the
new product graph
"""
GH = _init_product_graph(G, H)
GH.add_nodes_from(_node_product(G, H))
# Edges in G regardless of H designation
GH.add_edges_from(_edges_cross_nodes_and_nodes(G, H))
# For each x in G, only if there is an edge in H
GH.add_edges_from(_nodes_cross_edges(G, H))
return GH
def strong_product(G, H):
r"""Returns the strong product of G and H.
The strong product $P$ of the graphs $G$ and $H$ has a node set that
is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$.
$P$ has an edge $((u,v), (x,y))$ if and only if
$u==v$ and $(x,y)$ is an edge in $H$, or
$x==y$ and $(u,v)$ is an edge in $G$, or
$(u,v)$ is an edge in $G$ and $(x,y)$ is an edge in $H$.
Parameters
----------
G, H: graphs
Networkx graphs.
Returns
-------
P: NetworkX graph
The Cartesian product of G and H. P will be a multi-graph if either G
or H is a multi-graph. Will be a directed if G and H are directed,
and undirected if G and H are undirected.
Raises
------
NetworkXError
If G and H are not both directed or both undirected.
Notes
-----
Node attributes in P are two-tuple of the G and H node attributes.
Missing attributes are assigned None.
Examples
--------
>>> G = nx.Graph()
>>> H = nx.Graph()
>>> G.add_node(0, a1=True)
>>> H.add_node("a", a2="Spam")
>>> P = nx.strong_product(G, H)
>>> list(P)
[(0, 'a')]
Edge attributes and edge keys (for multigraphs) are also copied to the
new product graph
"""
GH = _init_product_graph(G, H)
GH.add_nodes_from(_node_product(G, H))
GH.add_edges_from(_nodes_cross_edges(G, H))
GH.add_edges_from(_edges_cross_nodes(G, H))
GH.add_edges_from(_directed_edges_cross_edges(G, H))
if not GH.is_directed():
GH.add_edges_from(_undirected_edges_cross_edges(G, H))
return GH
@not_implemented_for("directed")
@not_implemented_for("multigraph")
def power(G, k):
"""Returns the specified power of a graph.
The $k$th power of a simple graph $G$, denoted $G^k$, is a
graph on the same set of nodes in which two distinct nodes $u$ and
$v$ are adjacent in $G^k$ if and only if the shortest path
distance between $u$ and $v$ in $G$ is at most $k$.
Parameters
----------
G : graph
A NetworkX simple graph object.
k : positive integer
The power to which to raise the graph `G`.
Returns
-------
NetworkX simple graph
`G` to the power `k`.
Raises
------
ValueError
If the exponent `k` is not positive.
NetworkXNotImplemented
If `G` is not a simple graph.
Examples
--------
The number of edges will never decrease when taking successive
powers:
>>> G = nx.path_graph(4)
>>> list(nx.power(G, 2).edges)
[(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)]
>>> list(nx.power(G, 3).edges)
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
The `k`th power of a cycle graph on *n* nodes is the complete graph
on *n* nodes, if `k` is at least ``n // 2``:
>>> G = nx.cycle_graph(5)
>>> H = nx.complete_graph(5)
>>> nx.is_isomorphic(nx.power(G, 2), H)
True
>>> G = nx.cycle_graph(8)
>>> H = nx.complete_graph(8)
>>> nx.is_isomorphic(nx.power(G, 4), H)
True
References
----------
.. [1] J. A. Bondy, U. S. R. Murty, *Graph Theory*. Springer, 2008.
Notes
-----
This definition of "power graph" comes from Exercise 3.1.6 of
*Graph Theory* by Bondy and Murty [1]_.
"""
if k <= 0:
raise ValueError("k must be a positive integer")
H = nx.Graph()
H.add_nodes_from(G)
# update BFS code to ignore self loops.
for n in G:
seen = {} # level (number of hops) when seen in BFS
level = 1 # the current level
nextlevel = G[n]
while nextlevel:
thislevel = nextlevel # advance to next level
nextlevel = {} # and start a new list (fringe)
for v in thislevel:
if v == n: # avoid self loop
continue
if v not in seen:
seen[v] = level # set the level of vertex v
nextlevel.update(G[v]) # add neighbors of v
if k <= level:
break
level += 1
H.add_edges_from((n, nbr) for nbr in seen)
return H
@not_implemented_for("multigraph")
def rooted_product(G, H, root):
""" Return the rooted product of graphs G and H rooted at root in H.
A new graph is constructed representing the rooted product of
the inputted graphs, G and H, with a root in H.
A rooted product duplicates H for each nodes in G with the root
of H corresponding to the node in G. Nodes are renamed as the direct
product of G and H. The result is a subgraph of the cartesian product.
Parameters
----------
G,H : graph
A NetworkX graph
root : node
A node in H
Returns
-------
R : The rooted product of G and H with a specified root in H
Notes
-----
The nodes of R are the Cartesian Product of the nodes of G and H.
The nodes of G and H are not relabeled.
"""
if root not in H:
raise nx.NetworkXError("root must be a vertex in H")
R = nx.Graph()
R.add_nodes_from(product(G, H))
R.add_edges_from(((e[0], root), (e[1], root)) for e in G.edges())
R.add_edges_from(((g, e[0]), (g, e[1])) for g in G for e in H.edges())
return R

View file

@ -0,0 +1,248 @@
import pytest
import networkx as nx
from networkx.testing import assert_edges_equal
def test_union_all_attributes():
g = nx.Graph()
g.add_node(0, x=4)
g.add_node(1, x=5)
g.add_edge(0, 1, size=5)
g.graph["name"] = "g"
h = g.copy()
h.graph["name"] = "h"
h.graph["attr"] = "attr"
h.nodes[0]["x"] = 7
j = g.copy()
j.graph["name"] = "j"
j.graph["attr"] = "attr"
j.nodes[0]["x"] = 7
ghj = nx.union_all([g, h, j], rename=("g", "h", "j"))
assert set(ghj.nodes()) == {"h0", "h1", "g0", "g1", "j0", "j1"}
for n in ghj:
graph, node = n
assert ghj.nodes[n] == eval(graph).nodes[int(node)]
assert ghj.graph["attr"] == "attr"
assert ghj.graph["name"] == "j" # j graph attributes take precendent
def test_intersection_all():
G = nx.Graph()
H = nx.Graph()
R = nx.Graph()
G.add_nodes_from([1, 2, 3, 4])
G.add_edge(1, 2)
G.add_edge(2, 3)
H.add_nodes_from([1, 2, 3, 4])
H.add_edge(2, 3)
H.add_edge(3, 4)
R.add_nodes_from([1, 2, 3, 4])
R.add_edge(2, 3)
R.add_edge(4, 1)
I = nx.intersection_all([G, H, R])
assert set(I.nodes()) == {1, 2, 3, 4}
assert sorted(I.edges()) == [(2, 3)]
def test_intersection_all_attributes():
g = nx.Graph()
g.add_node(0, x=4)
g.add_node(1, x=5)
g.add_edge(0, 1, size=5)
g.graph["name"] = "g"
h = g.copy()
h.graph["name"] = "h"
h.graph["attr"] = "attr"
h.nodes[0]["x"] = 7
gh = nx.intersection_all([g, h])
assert set(gh.nodes()) == set(g.nodes())
assert set(gh.nodes()) == set(h.nodes())
assert sorted(gh.edges()) == sorted(g.edges())
h.remove_node(0)
pytest.raises(nx.NetworkXError, nx.intersection, g, h)
def test_intersection_all_multigraph_attributes():
g = nx.MultiGraph()
g.add_edge(0, 1, key=0)
g.add_edge(0, 1, key=1)
g.add_edge(0, 1, key=2)
h = nx.MultiGraph()
h.add_edge(0, 1, key=0)
h.add_edge(0, 1, key=3)
gh = nx.intersection_all([g, h])
assert set(gh.nodes()) == set(g.nodes())
assert set(gh.nodes()) == set(h.nodes())
assert sorted(gh.edges()) == [(0, 1)]
assert sorted(gh.edges(keys=True)) == [(0, 1, 0)]
def test_union_all_and_compose_all():
K3 = nx.complete_graph(3)
P3 = nx.path_graph(3)
G1 = nx.DiGraph()
G1.add_edge("A", "B")
G1.add_edge("A", "C")
G1.add_edge("A", "D")
G2 = nx.DiGraph()
G2.add_edge("1", "2")
G2.add_edge("1", "3")
G2.add_edge("1", "4")
G = nx.union_all([G1, G2])
H = nx.compose_all([G1, G2])
assert_edges_equal(G.edges(), H.edges())
assert not G.has_edge("A", "1")
pytest.raises(nx.NetworkXError, nx.union, K3, P3)
H1 = nx.union_all([H, G1], rename=("H", "G1"))
assert sorted(H1.nodes()) == [
"G1A",
"G1B",
"G1C",
"G1D",
"H1",
"H2",
"H3",
"H4",
"HA",
"HB",
"HC",
"HD",
]
H2 = nx.union_all([H, G2], rename=("H", ""))
assert sorted(H2.nodes()) == [
"1",
"2",
"3",
"4",
"H1",
"H2",
"H3",
"H4",
"HA",
"HB",
"HC",
"HD",
]
assert not H1.has_edge("NB", "NA")
G = nx.compose_all([G, G])
assert_edges_equal(G.edges(), H.edges())
G2 = nx.union_all([G2, G2], rename=("", "copy"))
assert sorted(G2.nodes()) == [
"1",
"2",
"3",
"4",
"copy1",
"copy2",
"copy3",
"copy4",
]
assert sorted(G2.neighbors("copy4")) == []
assert sorted(G2.neighbors("copy1")) == ["copy2", "copy3", "copy4"]
assert len(G) == 8
assert nx.number_of_edges(G) == 6
E = nx.disjoint_union_all([G, G])
assert len(E) == 16
assert nx.number_of_edges(E) == 12
E = nx.disjoint_union_all([G1, G2])
assert sorted(E.nodes()) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
G1 = nx.DiGraph()
G1.add_edge("A", "B")
G2 = nx.DiGraph()
G2.add_edge(1, 2)
G3 = nx.DiGraph()
G3.add_edge(11, 22)
G4 = nx.union_all([G1, G2, G3], rename=("G1", "G2", "G3"))
assert sorted(G4.nodes()) == ["G1A", "G1B", "G21", "G22", "G311", "G322"]
def test_union_all_multigraph():
G = nx.MultiGraph()
G.add_edge(1, 2, key=0)
G.add_edge(1, 2, key=1)
H = nx.MultiGraph()
H.add_edge(3, 4, key=0)
H.add_edge(3, 4, key=1)
GH = nx.union_all([G, H])
assert set(GH) == set(G) | set(H)
assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True))
def test_input_output():
l = [nx.Graph([(1, 2)]), nx.Graph([(3, 4)])]
U = nx.disjoint_union_all(l)
assert len(l) == 2
C = nx.compose_all(l)
assert len(l) == 2
l = [nx.Graph([(1, 2)]), nx.Graph([(1, 2)])]
R = nx.intersection_all(l)
assert len(l) == 2
def test_mixed_type_union():
with pytest.raises(nx.NetworkXError):
G = nx.Graph()
H = nx.MultiGraph()
I = nx.Graph()
U = nx.union_all([G, H, I])
def test_mixed_type_disjoint_union():
with pytest.raises(nx.NetworkXError):
G = nx.Graph()
H = nx.MultiGraph()
I = nx.Graph()
U = nx.disjoint_union_all([G, H, I])
def test_mixed_type_intersection():
with pytest.raises(nx.NetworkXError):
G = nx.Graph()
H = nx.MultiGraph()
I = nx.Graph()
U = nx.intersection_all([G, H, I])
def test_mixed_type_compose():
with pytest.raises(nx.NetworkXError):
G = nx.Graph()
H = nx.MultiGraph()
I = nx.Graph()
U = nx.compose_all([G, H, I])
def test_empty_union():
with pytest.raises(ValueError):
nx.union_all([])
def test_empty_disjoint_union():
with pytest.raises(ValueError):
nx.disjoint_union_all([])
def test_empty_compose_all():
with pytest.raises(ValueError):
nx.compose_all([])
def test_empty_intersection_all():
with pytest.raises(ValueError):
nx.intersection_all([])

View file

@ -0,0 +1,403 @@
import pytest
import networkx as nx
from networkx.testing import assert_edges_equal
def test_union_attributes():
g = nx.Graph()
g.add_node(0, x=4)
g.add_node(1, x=5)
g.add_edge(0, 1, size=5)
g.graph["name"] = "g"
h = g.copy()
h.graph["name"] = "h"
h.graph["attr"] = "attr"
h.nodes[0]["x"] = 7
gh = nx.union(g, h, rename=("g", "h"))
assert set(gh.nodes()) == {"h0", "h1", "g0", "g1"}
for n in gh:
graph, node = n
assert gh.nodes[n] == eval(graph).nodes[int(node)]
assert gh.graph["attr"] == "attr"
assert gh.graph["name"] == "h" # h graph attributes take precendent
def test_intersection():
G = nx.Graph()
H = nx.Graph()
G.add_nodes_from([1, 2, 3, 4])
G.add_edge(1, 2)
G.add_edge(2, 3)
H.add_nodes_from([1, 2, 3, 4])
H.add_edge(2, 3)
H.add_edge(3, 4)
I = nx.intersection(G, H)
assert set(I.nodes()) == {1, 2, 3, 4}
assert sorted(I.edges()) == [(2, 3)]
def test_intersection_attributes():
g = nx.Graph()
g.add_node(0, x=4)
g.add_node(1, x=5)
g.add_edge(0, 1, size=5)
g.graph["name"] = "g"
h = g.copy()
h.graph["name"] = "h"
h.graph["attr"] = "attr"
h.nodes[0]["x"] = 7
gh = nx.intersection(g, h)
assert set(gh.nodes()) == set(g.nodes())
assert set(gh.nodes()) == set(h.nodes())
assert sorted(gh.edges()) == sorted(g.edges())
h.remove_node(0)
pytest.raises(nx.NetworkXError, nx.intersection, g, h)
def test_intersection_multigraph_attributes():
g = nx.MultiGraph()
g.add_edge(0, 1, key=0)
g.add_edge(0, 1, key=1)
g.add_edge(0, 1, key=2)
h = nx.MultiGraph()
h.add_edge(0, 1, key=0)
h.add_edge(0, 1, key=3)
gh = nx.intersection(g, h)
assert set(gh.nodes()) == set(g.nodes())
assert set(gh.nodes()) == set(h.nodes())
assert sorted(gh.edges()) == [(0, 1)]
assert sorted(gh.edges(keys=True)) == [(0, 1, 0)]
def test_difference():
G = nx.Graph()
H = nx.Graph()
G.add_nodes_from([1, 2, 3, 4])
G.add_edge(1, 2)
G.add_edge(2, 3)
H.add_nodes_from([1, 2, 3, 4])
H.add_edge(2, 3)
H.add_edge(3, 4)
D = nx.difference(G, H)
assert set(D.nodes()) == {1, 2, 3, 4}
assert sorted(D.edges()) == [(1, 2)]
D = nx.difference(H, G)
assert set(D.nodes()) == {1, 2, 3, 4}
assert sorted(D.edges()) == [(3, 4)]
D = nx.symmetric_difference(G, H)
assert set(D.nodes()) == {1, 2, 3, 4}
assert sorted(D.edges()) == [(1, 2), (3, 4)]
def test_difference2():
G = nx.Graph()
H = nx.Graph()
G.add_nodes_from([1, 2, 3, 4])
H.add_nodes_from([1, 2, 3, 4])
G.add_edge(1, 2)
H.add_edge(1, 2)
G.add_edge(2, 3)
D = nx.difference(G, H)
assert set(D.nodes()) == {1, 2, 3, 4}
assert sorted(D.edges()) == [(2, 3)]
D = nx.difference(H, G)
assert set(D.nodes()) == {1, 2, 3, 4}
assert sorted(D.edges()) == []
H.add_edge(3, 4)
D = nx.difference(H, G)
assert set(D.nodes()) == {1, 2, 3, 4}
assert sorted(D.edges()) == [(3, 4)]
def test_difference_attributes():
g = nx.Graph()
g.add_node(0, x=4)
g.add_node(1, x=5)
g.add_edge(0, 1, size=5)
g.graph["name"] = "g"
h = g.copy()
h.graph["name"] = "h"
h.graph["attr"] = "attr"
h.nodes[0]["x"] = 7
gh = nx.difference(g, h)
assert set(gh.nodes()) == set(g.nodes())
assert set(gh.nodes()) == set(h.nodes())
assert sorted(gh.edges()) == []
h.remove_node(0)
pytest.raises(nx.NetworkXError, nx.intersection, g, h)
def test_difference_multigraph_attributes():
g = nx.MultiGraph()
g.add_edge(0, 1, key=0)
g.add_edge(0, 1, key=1)
g.add_edge(0, 1, key=2)
h = nx.MultiGraph()
h.add_edge(0, 1, key=0)
h.add_edge(0, 1, key=3)
gh = nx.difference(g, h)
assert set(gh.nodes()) == set(g.nodes())
assert set(gh.nodes()) == set(h.nodes())
assert sorted(gh.edges()) == [(0, 1), (0, 1)]
assert sorted(gh.edges(keys=True)) == [(0, 1, 1), (0, 1, 2)]
def test_difference_raise():
G = nx.path_graph(4)
H = nx.path_graph(3)
pytest.raises(nx.NetworkXError, nx.difference, G, H)
pytest.raises(nx.NetworkXError, nx.symmetric_difference, G, H)
def test_symmetric_difference_multigraph():
g = nx.MultiGraph()
g.add_edge(0, 1, key=0)
g.add_edge(0, 1, key=1)
g.add_edge(0, 1, key=2)
h = nx.MultiGraph()
h.add_edge(0, 1, key=0)
h.add_edge(0, 1, key=3)
gh = nx.symmetric_difference(g, h)
assert set(gh.nodes()) == set(g.nodes())
assert set(gh.nodes()) == set(h.nodes())
assert sorted(gh.edges()) == 3 * [(0, 1)]
assert sorted(sorted(e) for e in gh.edges(keys=True)) == [
[0, 1, 1],
[0, 1, 2],
[0, 1, 3],
]
def test_union_and_compose():
K3 = nx.complete_graph(3)
P3 = nx.path_graph(3)
G1 = nx.DiGraph()
G1.add_edge("A", "B")
G1.add_edge("A", "C")
G1.add_edge("A", "D")
G2 = nx.DiGraph()
G2.add_edge("1", "2")
G2.add_edge("1", "3")
G2.add_edge("1", "4")
G = nx.union(G1, G2)
H = nx.compose(G1, G2)
assert_edges_equal(G.edges(), H.edges())
assert not G.has_edge("A", 1)
pytest.raises(nx.NetworkXError, nx.union, K3, P3)
H1 = nx.union(H, G1, rename=("H", "G1"))
assert sorted(H1.nodes()) == [
"G1A",
"G1B",
"G1C",
"G1D",
"H1",
"H2",
"H3",
"H4",
"HA",
"HB",
"HC",
"HD",
]
H2 = nx.union(H, G2, rename=("H", ""))
assert sorted(H2.nodes()) == [
"1",
"2",
"3",
"4",
"H1",
"H2",
"H3",
"H4",
"HA",
"HB",
"HC",
"HD",
]
assert not H1.has_edge("NB", "NA")
G = nx.compose(G, G)
assert_edges_equal(G.edges(), H.edges())
G2 = nx.union(G2, G2, rename=("", "copy"))
assert sorted(G2.nodes()) == [
"1",
"2",
"3",
"4",
"copy1",
"copy2",
"copy3",
"copy4",
]
assert sorted(G2.neighbors("copy4")) == []
assert sorted(G2.neighbors("copy1")) == ["copy2", "copy3", "copy4"]
assert len(G) == 8
assert nx.number_of_edges(G) == 6
E = nx.disjoint_union(G, G)
assert len(E) == 16
assert nx.number_of_edges(E) == 12
E = nx.disjoint_union(G1, G2)
assert sorted(E.nodes()) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
G = nx.Graph()
H = nx.Graph()
G.add_nodes_from([(1, {"a1": 1})])
H.add_nodes_from([(1, {"b1": 1})])
R = nx.compose(G, H)
assert R.nodes == {1: {"a1": 1, "b1": 1}}
def test_union_multigraph():
G = nx.MultiGraph()
G.add_edge(1, 2, key=0)
G.add_edge(1, 2, key=1)
H = nx.MultiGraph()
H.add_edge(3, 4, key=0)
H.add_edge(3, 4, key=1)
GH = nx.union(G, H)
assert set(GH) == set(G) | set(H)
assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True))
def test_disjoint_union_multigraph():
G = nx.MultiGraph()
G.add_edge(0, 1, key=0)
G.add_edge(0, 1, key=1)
H = nx.MultiGraph()
H.add_edge(2, 3, key=0)
H.add_edge(2, 3, key=1)
GH = nx.disjoint_union(G, H)
assert set(GH) == set(G) | set(H)
assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True))
def test_compose_multigraph():
G = nx.MultiGraph()
G.add_edge(1, 2, key=0)
G.add_edge(1, 2, key=1)
H = nx.MultiGraph()
H.add_edge(3, 4, key=0)
H.add_edge(3, 4, key=1)
GH = nx.compose(G, H)
assert set(GH) == set(G) | set(H)
assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True))
H.add_edge(1, 2, key=2)
GH = nx.compose(G, H)
assert set(GH) == set(G) | set(H)
assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True))
def test_full_join_graph():
# Simple Graphs
G = nx.Graph()
G.add_node(0)
G.add_edge(1, 2)
H = nx.Graph()
H.add_edge(3, 4)
U = nx.full_join(G, H)
assert set(U) == set(G) | set(H)
assert len(U) == len(G) + len(H)
assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H)
# Rename
U = nx.full_join(G, H, rename=("g", "h"))
assert set(U) == {"g0", "g1", "g2", "h3", "h4"}
assert len(U) == len(G) + len(H)
assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H)
# Rename graphs with string-like nodes
G = nx.Graph()
G.add_node("a")
G.add_edge("b", "c")
H = nx.Graph()
H.add_edge("d", "e")
U = nx.full_join(G, H, rename=("g", "h"))
assert set(U) == {"ga", "gb", "gc", "hd", "he"}
assert len(U) == len(G) + len(H)
assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H)
# DiGraphs
G = nx.DiGraph()
G.add_node(0)
G.add_edge(1, 2)
H = nx.DiGraph()
H.add_edge(3, 4)
U = nx.full_join(G, H)
assert set(U) == set(G) | set(H)
assert len(U) == len(G) + len(H)
assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2
# DiGraphs Rename
U = nx.full_join(G, H, rename=("g", "h"))
assert set(U) == {"g0", "g1", "g2", "h3", "h4"}
assert len(U) == len(G) + len(H)
assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2
def test_full_join_multigraph():
# MultiGraphs
G = nx.MultiGraph()
G.add_node(0)
G.add_edge(1, 2)
H = nx.MultiGraph()
H.add_edge(3, 4)
U = nx.full_join(G, H)
assert set(U) == set(G) | set(H)
assert len(U) == len(G) + len(H)
assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H)
# MultiGraphs rename
U = nx.full_join(G, H, rename=("g", "h"))
assert set(U) == {"g0", "g1", "g2", "h3", "h4"}
assert len(U) == len(G) + len(H)
assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H)
# MultiDiGraphs
G = nx.MultiDiGraph()
G.add_node(0)
G.add_edge(1, 2)
H = nx.MultiDiGraph()
H.add_edge(3, 4)
U = nx.full_join(G, H)
assert set(U) == set(G) | set(H)
assert len(U) == len(G) + len(H)
assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2
# MultiDiGraphs rename
U = nx.full_join(G, H, rename=("g", "h"))
assert set(U) == {"g0", "g1", "g2", "h3", "h4"}
assert len(U) == len(G) + len(H)
assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2
def test_mixed_type_union():
G = nx.Graph()
H = nx.MultiGraph()
pytest.raises(nx.NetworkXError, nx.union, G, H)
pytest.raises(nx.NetworkXError, nx.disjoint_union, G, H)
pytest.raises(nx.NetworkXError, nx.intersection, G, H)
pytest.raises(nx.NetworkXError, nx.difference, G, H)
pytest.raises(nx.NetworkXError, nx.symmetric_difference, G, H)
pytest.raises(nx.NetworkXError, nx.compose, G, H)

View file

@ -0,0 +1,426 @@
import pytest
import networkx as nx
from networkx.testing import assert_edges_equal
def test_tensor_product_raises():
with pytest.raises(nx.NetworkXError):
P = nx.tensor_product(nx.DiGraph(), nx.Graph())
def test_tensor_product_null():
null = nx.null_graph()
empty10 = nx.empty_graph(10)
K3 = nx.complete_graph(3)
K10 = nx.complete_graph(10)
P3 = nx.path_graph(3)
P10 = nx.path_graph(10)
# null graph
G = nx.tensor_product(null, null)
assert nx.is_isomorphic(G, null)
# null_graph X anything = null_graph and v.v.
G = nx.tensor_product(null, empty10)
assert nx.is_isomorphic(G, null)
G = nx.tensor_product(null, K3)
assert nx.is_isomorphic(G, null)
G = nx.tensor_product(null, K10)
assert nx.is_isomorphic(G, null)
G = nx.tensor_product(null, P3)
assert nx.is_isomorphic(G, null)
G = nx.tensor_product(null, P10)
assert nx.is_isomorphic(G, null)
G = nx.tensor_product(empty10, null)
assert nx.is_isomorphic(G, null)
G = nx.tensor_product(K3, null)
assert nx.is_isomorphic(G, null)
G = nx.tensor_product(K10, null)
assert nx.is_isomorphic(G, null)
G = nx.tensor_product(P3, null)
assert nx.is_isomorphic(G, null)
G = nx.tensor_product(P10, null)
assert nx.is_isomorphic(G, null)
def test_tensor_product_size():
P5 = nx.path_graph(5)
K3 = nx.complete_graph(3)
K5 = nx.complete_graph(5)
G = nx.tensor_product(P5, K3)
assert nx.number_of_nodes(G) == 5 * 3
G = nx.tensor_product(K3, K5)
assert nx.number_of_nodes(G) == 3 * 5
def test_tensor_product_combinations():
# basic smoke test, more realistic tests would be useful
P5 = nx.path_graph(5)
K3 = nx.complete_graph(3)
G = nx.tensor_product(P5, K3)
assert nx.number_of_nodes(G) == 5 * 3
G = nx.tensor_product(P5, nx.MultiGraph(K3))
assert nx.number_of_nodes(G) == 5 * 3
G = nx.tensor_product(nx.MultiGraph(P5), K3)
assert nx.number_of_nodes(G) == 5 * 3
G = nx.tensor_product(nx.MultiGraph(P5), nx.MultiGraph(K3))
assert nx.number_of_nodes(G) == 5 * 3
G = nx.tensor_product(nx.DiGraph(P5), nx.DiGraph(K3))
assert nx.number_of_nodes(G) == 5 * 3
def test_tensor_product_classic_result():
K2 = nx.complete_graph(2)
G = nx.petersen_graph()
G = nx.tensor_product(G, K2)
assert nx.is_isomorphic(G, nx.desargues_graph())
G = nx.cycle_graph(5)
G = nx.tensor_product(G, K2)
assert nx.is_isomorphic(G, nx.cycle_graph(10))
G = nx.tetrahedral_graph()
G = nx.tensor_product(G, K2)
assert nx.is_isomorphic(G, nx.cubical_graph())
def test_tensor_product_random():
G = nx.erdos_renyi_graph(10, 2 / 10.0)
H = nx.erdos_renyi_graph(10, 2 / 10.0)
GH = nx.tensor_product(G, H)
for (u_G, u_H) in GH.nodes():
for (v_G, v_H) in GH.nodes():
if H.has_edge(u_H, v_H) and G.has_edge(u_G, v_G):
assert GH.has_edge((u_G, u_H), (v_G, v_H))
else:
assert not GH.has_edge((u_G, u_H), (v_G, v_H))
def test_cartesian_product_multigraph():
G = nx.MultiGraph()
G.add_edge(1, 2, key=0)
G.add_edge(1, 2, key=1)
H = nx.MultiGraph()
H.add_edge(3, 4, key=0)
H.add_edge(3, 4, key=1)
GH = nx.cartesian_product(G, H)
assert set(GH) == {(1, 3), (2, 3), (2, 4), (1, 4)}
assert {(frozenset([u, v]), k) for u, v, k in GH.edges(keys=True)} == {
(frozenset([u, v]), k)
for u, v, k in [
((1, 3), (2, 3), 0),
((1, 3), (2, 3), 1),
((1, 3), (1, 4), 0),
((1, 3), (1, 4), 1),
((2, 3), (2, 4), 0),
((2, 3), (2, 4), 1),
((2, 4), (1, 4), 0),
((2, 4), (1, 4), 1),
]
}
def test_cartesian_product_raises():
with pytest.raises(nx.NetworkXError):
P = nx.cartesian_product(nx.DiGraph(), nx.Graph())
def test_cartesian_product_null():
null = nx.null_graph()
empty10 = nx.empty_graph(10)
K3 = nx.complete_graph(3)
K10 = nx.complete_graph(10)
P3 = nx.path_graph(3)
P10 = nx.path_graph(10)
# null graph
G = nx.cartesian_product(null, null)
assert nx.is_isomorphic(G, null)
# null_graph X anything = null_graph and v.v.
G = nx.cartesian_product(null, empty10)
assert nx.is_isomorphic(G, null)
G = nx.cartesian_product(null, K3)
assert nx.is_isomorphic(G, null)
G = nx.cartesian_product(null, K10)
assert nx.is_isomorphic(G, null)
G = nx.cartesian_product(null, P3)
assert nx.is_isomorphic(G, null)
G = nx.cartesian_product(null, P10)
assert nx.is_isomorphic(G, null)
G = nx.cartesian_product(empty10, null)
assert nx.is_isomorphic(G, null)
G = nx.cartesian_product(K3, null)
assert nx.is_isomorphic(G, null)
G = nx.cartesian_product(K10, null)
assert nx.is_isomorphic(G, null)
G = nx.cartesian_product(P3, null)
assert nx.is_isomorphic(G, null)
G = nx.cartesian_product(P10, null)
assert nx.is_isomorphic(G, null)
def test_cartesian_product_size():
# order(GXH)=order(G)*order(H)
K5 = nx.complete_graph(5)
P5 = nx.path_graph(5)
K3 = nx.complete_graph(3)
G = nx.cartesian_product(P5, K3)
assert nx.number_of_nodes(G) == 5 * 3
assert nx.number_of_edges(G) == nx.number_of_edges(P5) * nx.number_of_nodes(
K3
) + nx.number_of_edges(K3) * nx.number_of_nodes(P5)
G = nx.cartesian_product(K3, K5)
assert nx.number_of_nodes(G) == 3 * 5
assert nx.number_of_edges(G) == nx.number_of_edges(K5) * nx.number_of_nodes(
K3
) + nx.number_of_edges(K3) * nx.number_of_nodes(K5)
def test_cartesian_product_classic():
# test some classic product graphs
P2 = nx.path_graph(2)
P3 = nx.path_graph(3)
# cube = 2-path X 2-path
G = nx.cartesian_product(P2, P2)
G = nx.cartesian_product(P2, G)
assert nx.is_isomorphic(G, nx.cubical_graph())
# 3x3 grid
G = nx.cartesian_product(P3, P3)
assert nx.is_isomorphic(G, nx.grid_2d_graph(3, 3))
def test_cartesian_product_random():
G = nx.erdos_renyi_graph(10, 2 / 10.0)
H = nx.erdos_renyi_graph(10, 2 / 10.0)
GH = nx.cartesian_product(G, H)
for (u_G, u_H) in GH.nodes():
for (v_G, v_H) in GH.nodes():
if (u_G == v_G and H.has_edge(u_H, v_H)) or (
u_H == v_H and G.has_edge(u_G, v_G)
):
assert GH.has_edge((u_G, u_H), (v_G, v_H))
else:
assert not GH.has_edge((u_G, u_H), (v_G, v_H))
def test_lexicographic_product_raises():
with pytest.raises(nx.NetworkXError):
P = nx.lexicographic_product(nx.DiGraph(), nx.Graph())
def test_lexicographic_product_null():
null = nx.null_graph()
empty10 = nx.empty_graph(10)
K3 = nx.complete_graph(3)
K10 = nx.complete_graph(10)
P3 = nx.path_graph(3)
P10 = nx.path_graph(10)
# null graph
G = nx.lexicographic_product(null, null)
assert nx.is_isomorphic(G, null)
# null_graph X anything = null_graph and v.v.
G = nx.lexicographic_product(null, empty10)
assert nx.is_isomorphic(G, null)
G = nx.lexicographic_product(null, K3)
assert nx.is_isomorphic(G, null)
G = nx.lexicographic_product(null, K10)
assert nx.is_isomorphic(G, null)
G = nx.lexicographic_product(null, P3)
assert nx.is_isomorphic(G, null)
G = nx.lexicographic_product(null, P10)
assert nx.is_isomorphic(G, null)
G = nx.lexicographic_product(empty10, null)
assert nx.is_isomorphic(G, null)
G = nx.lexicographic_product(K3, null)
assert nx.is_isomorphic(G, null)
G = nx.lexicographic_product(K10, null)
assert nx.is_isomorphic(G, null)
G = nx.lexicographic_product(P3, null)
assert nx.is_isomorphic(G, null)
G = nx.lexicographic_product(P10, null)
assert nx.is_isomorphic(G, null)
def test_lexicographic_product_size():
K5 = nx.complete_graph(5)
P5 = nx.path_graph(5)
K3 = nx.complete_graph(3)
G = nx.lexicographic_product(P5, K3)
assert nx.number_of_nodes(G) == 5 * 3
G = nx.lexicographic_product(K3, K5)
assert nx.number_of_nodes(G) == 3 * 5
def test_lexicographic_product_combinations():
P5 = nx.path_graph(5)
K3 = nx.complete_graph(3)
G = nx.lexicographic_product(P5, K3)
assert nx.number_of_nodes(G) == 5 * 3
G = nx.lexicographic_product(nx.MultiGraph(P5), K3)
assert nx.number_of_nodes(G) == 5 * 3
G = nx.lexicographic_product(P5, nx.MultiGraph(K3))
assert nx.number_of_nodes(G) == 5 * 3
G = nx.lexicographic_product(nx.MultiGraph(P5), nx.MultiGraph(K3))
assert nx.number_of_nodes(G) == 5 * 3
# No classic easily found classic results for lexicographic product
def test_lexicographic_product_random():
G = nx.erdos_renyi_graph(10, 2 / 10.0)
H = nx.erdos_renyi_graph(10, 2 / 10.0)
GH = nx.lexicographic_product(G, H)
for (u_G, u_H) in GH.nodes():
for (v_G, v_H) in GH.nodes():
if G.has_edge(u_G, v_G) or (u_G == v_G and H.has_edge(u_H, v_H)):
assert GH.has_edge((u_G, u_H), (v_G, v_H))
else:
assert not GH.has_edge((u_G, u_H), (v_G, v_H))
def test_strong_product_raises():
with pytest.raises(nx.NetworkXError):
P = nx.strong_product(nx.DiGraph(), nx.Graph())
def test_strong_product_null():
null = nx.null_graph()
empty10 = nx.empty_graph(10)
K3 = nx.complete_graph(3)
K10 = nx.complete_graph(10)
P3 = nx.path_graph(3)
P10 = nx.path_graph(10)
# null graph
G = nx.strong_product(null, null)
assert nx.is_isomorphic(G, null)
# null_graph X anything = null_graph and v.v.
G = nx.strong_product(null, empty10)
assert nx.is_isomorphic(G, null)
G = nx.strong_product(null, K3)
assert nx.is_isomorphic(G, null)
G = nx.strong_product(null, K10)
assert nx.is_isomorphic(G, null)
G = nx.strong_product(null, P3)
assert nx.is_isomorphic(G, null)
G = nx.strong_product(null, P10)
assert nx.is_isomorphic(G, null)
G = nx.strong_product(empty10, null)
assert nx.is_isomorphic(G, null)
G = nx.strong_product(K3, null)
assert nx.is_isomorphic(G, null)
G = nx.strong_product(K10, null)
assert nx.is_isomorphic(G, null)
G = nx.strong_product(P3, null)
assert nx.is_isomorphic(G, null)
G = nx.strong_product(P10, null)
assert nx.is_isomorphic(G, null)
def test_strong_product_size():
K5 = nx.complete_graph(5)
P5 = nx.path_graph(5)
K3 = nx.complete_graph(3)
G = nx.strong_product(P5, K3)
assert nx.number_of_nodes(G) == 5 * 3
G = nx.strong_product(K3, K5)
assert nx.number_of_nodes(G) == 3 * 5
def test_strong_product_combinations():
P5 = nx.path_graph(5)
K3 = nx.complete_graph(3)
G = nx.strong_product(P5, K3)
assert nx.number_of_nodes(G) == 5 * 3
G = nx.strong_product(nx.MultiGraph(P5), K3)
assert nx.number_of_nodes(G) == 5 * 3
G = nx.strong_product(P5, nx.MultiGraph(K3))
assert nx.number_of_nodes(G) == 5 * 3
G = nx.strong_product(nx.MultiGraph(P5), nx.MultiGraph(K3))
assert nx.number_of_nodes(G) == 5 * 3
# No classic easily found classic results for strong product
def test_strong_product_random():
G = nx.erdos_renyi_graph(10, 2 / 10.0)
H = nx.erdos_renyi_graph(10, 2 / 10.0)
GH = nx.strong_product(G, H)
for (u_G, u_H) in GH.nodes():
for (v_G, v_H) in GH.nodes():
if (
(u_G == v_G and H.has_edge(u_H, v_H))
or (u_H == v_H and G.has_edge(u_G, v_G))
or (G.has_edge(u_G, v_G) and H.has_edge(u_H, v_H))
):
assert GH.has_edge((u_G, u_H), (v_G, v_H))
else:
assert not GH.has_edge((u_G, u_H), (v_G, v_H))
def test_graph_power_raises():
with pytest.raises(nx.NetworkXNotImplemented):
nx.power(nx.MultiDiGraph(), 2)
def test_graph_power():
# wikipedia example for graph power
G = nx.cycle_graph(7)
G.add_edge(6, 7)
G.add_edge(7, 8)
G.add_edge(8, 9)
G.add_edge(9, 2)
H = nx.power(G, 2)
assert_edges_equal(
list(H.edges()),
[
(0, 1),
(0, 2),
(0, 5),
(0, 6),
(0, 7),
(1, 9),
(1, 2),
(1, 3),
(1, 6),
(2, 3),
(2, 4),
(2, 8),
(2, 9),
(3, 4),
(3, 5),
(3, 9),
(4, 5),
(4, 6),
(5, 6),
(5, 7),
(6, 7),
(6, 8),
(7, 8),
(7, 9),
(8, 9),
],
)
def test_graph_power_negative():
with pytest.raises(ValueError):
nx.power(nx.Graph(), -1)
def test_rooted_product_raises():
with pytest.raises(nx.NetworkXError):
nx.rooted_product(nx.Graph(), nx.path_graph(2), 10)
def test_rooted_product():
G = nx.cycle_graph(5)
H = nx.Graph()
H.add_edges_from([("a", "b"), ("b", "c"), ("b", "d")])
R = nx.rooted_product(G, H, "a")
assert len(R) == len(G) * len(H)
assert R.size() == G.size() + len(G) * H.size()

View file

@ -0,0 +1,54 @@
import pytest
import networkx as nx
def test_complement():
null = nx.null_graph()
empty1 = nx.empty_graph(1)
empty10 = nx.empty_graph(10)
K3 = nx.complete_graph(3)
K5 = nx.complete_graph(5)
K10 = nx.complete_graph(10)
P2 = nx.path_graph(2)
P3 = nx.path_graph(3)
P5 = nx.path_graph(5)
P10 = nx.path_graph(10)
# complement of the complete graph is empty
G = nx.complement(K3)
assert nx.is_isomorphic(G, nx.empty_graph(3))
G = nx.complement(K5)
assert nx.is_isomorphic(G, nx.empty_graph(5))
# for any G, G=complement(complement(G))
P3cc = nx.complement(nx.complement(P3))
assert nx.is_isomorphic(P3, P3cc)
nullcc = nx.complement(nx.complement(null))
assert nx.is_isomorphic(null, nullcc)
b = nx.bull_graph()
bcc = nx.complement(nx.complement(b))
assert nx.is_isomorphic(b, bcc)
def test_complement_2():
G1 = nx.DiGraph()
G1.add_edge("A", "B")
G1.add_edge("A", "C")
G1.add_edge("A", "D")
G1C = nx.complement(G1)
assert sorted(G1C.edges()) == [
("B", "A"),
("B", "C"),
("B", "D"),
("C", "A"),
("C", "B"),
("C", "D"),
("D", "A"),
("D", "B"),
("D", "C"),
]
def test_reverse1():
# Other tests for reverse are done by the DiGraph and MultiDigraph.
G1 = nx.Graph()
pytest.raises(nx.NetworkXError, nx.reverse, G1)

View file

@ -0,0 +1,54 @@
"""Unary operations on graphs"""
import networkx as nx
__all__ = ["complement", "reverse"]
def complement(G):
"""Returns the graph complement of G.
Parameters
----------
G : graph
A NetworkX graph
Returns
-------
GC : A new graph.
Notes
------
Note that complement() does not create self-loops and also
does not produce parallel edges for MultiGraphs.
Graph, node, and edge data are not propagated to the new graph.
"""
R = G.__class__()
R.add_nodes_from(G)
R.add_edges_from(
((n, n2) for n, nbrs in G.adjacency() for n2 in G if n2 not in nbrs if n != n2)
)
return R
def reverse(G, copy=True):
"""Returns the reverse directed graph of G.
Parameters
----------
G : directed graph
A NetworkX directed graph
copy : bool
If True, then a new graph is returned. If False, then the graph is
reversed in place.
Returns
-------
H : directed graph
The reversed G.
"""
if not G.is_directed():
raise nx.NetworkXError("Cannot reverse an undirected graph.")
else:
return G.reverse(copy=copy)