blob: 0fa8a17b047a2a59855eb59eab57ff0fcc129d59 [file] [log] [blame]
"""
Graph products.
"""
# Copyright (C) 2011 by
# Aric Hagberg <hagberg@lanl.gov>
# Dan Schult <dschult@colgate.edu>
# Pieter Swart <swart@lanl.gov>
# All rights reserved.
# BSD license.
import networkx as nx
from itertools import product
__author__ = """\n""".join(['Aric Hagberg (hagberg@lanl.gov)',
'Pieter Swart (swart@lanl.gov)',
'Dan Schult(dschult@colgate.edu)'
'Ben Edwards(bedwards@cs.unm.edu)'])
__all__ = ['tensor_product','cartesian_product',
'lexicographic_product', 'strong_product']
def _dict_product(d1,d2):
return dict((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.node[u], H.node[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_iter(data=True):
for x,y,d in H.edges_iter(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_iter(data=True):
for x,y,k,d in H.edges_iter(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_iter(data=True,keys=True):
for x,y,d in H.edges_iter(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_iter(data=True,keys=True):
for x,y,k,d in H.edges_iter(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_iter(data=True):
for x,y,d in H.edges_iter(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_iter(data=True):
for x,y,k,d in H.edges_iter(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_iter(data=True,keys=True):
for x,y,d in H.edges_iter(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_iter(data=True,keys=True):
for x,y,k,d in H.edges_iter(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_iter(data=True,keys=True):
for x in H:
yield (u,x),(v,x),k,d
else:
for u,v,d in G.edges_iter(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_iter(data=True,keys=True):
yield (x,u),(x,v),k,d
else:
for x in G:
for u,v,d in H.edges_iter(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_iter(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_iter(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():
raise nx.NetworkXError("G and H must be both directed or",
"both undirected")
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"""Return the tensor product of G and H.
The tensor 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
and (x,y) is an edge in H.
Sometimes referred to as the categorical product.
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.
For example
>>> G = nx.Graph()
>>> H = nx.Graph()
>>> G.add_node(0,a1=True)
>>> H.add_node('a',a2='Spam')
>>> P = nx.tensor_product(G,H)
>>> P.nodes()
[(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))
GH.name = "Tensor product("+G.name+","+H.name+")"
return GH
def cartesian_product(G,H):
"""Return the Cartesian product of G and H.
The tensor 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
and x==y or and (x,y) is an edge in H and 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.
For example
>>> G = nx.Graph()
>>> H = nx.Graph()
>>> G.add_node(0,a1=True)
>>> H.add_node('a',a2='Spam')
>>> P = nx.cartesian_product(G,H)
>>> P.nodes()
[(0, 'a')]
Edge attributes and edge keys (for multigraphs) are also copied to the
new product graph
"""
if not G.is_directed() == H.is_directed():
raise nx.NetworkXError("G and H must be both directed or",
"both undirected")
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))
GH.name = "Cartesian product("+G.name+","+H.name+")"
return GH
def lexicographic_product(G,H):
"""Return 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.
For example
>>> G = nx.Graph()
>>> H = nx.Graph()
>>> G.add_node(0,a1=True)
>>> H.add_node('a',a2='Spam')
>>> P = nx.lexicographic_product(G,H)
>>> P.nodes()
[(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))
GH.name = "Lexicographic product("+G.name+","+H.name+")"
return GH
def strong_product(G,H):
"""Return 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.
For example
>>> G = nx.Graph()
>>> H = nx.Graph()
>>> G.add_node(0,a1=True)
>>> H.add_node('a',a2='Spam')
>>> P = nx.strong_product(G,H)
>>> P.nodes()
[(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))
GH.name = "Strong product("+G.name+","+H.name+")"
return GH