I am using networkx to find the maximum cardinality matching of a bipartite graph.
The matched edges are not unique for the particular graph.
Is there a way for me to find all the maximum matchings?
For the following example, all edges below can be the maximum matching:
{1: 2, 2: 1} or {1: 3, 3: 1} or {1: 4, 4: 1}
import networkx as nx
import matplotlib.pyplot as plt
G = nx.MultiDiGraph()
edges = [(1,3), (1,4), (1,2)]
nx.is_bipartite(G)
True
nx.draw(G, with_labels=True)
plt.show()
Unfortunately,
nx.bipartite.maximum_matching(G)
only returns
{1: 2, 2: 1}
Is there a way I can get the other combinations as well?
The paper "Algorithms for Enumerating All Perfect, Maximum and Maximal Matchings in Bipartite Graphs" by Takeaki Uno has an algorithm for this. http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.107.8179&rep=rep1&type=pdf
Theorem 2 says
"Maximum matchings in a bipartite graph can be enumerated in O(mn^1/2+
nNm) time and O(m) space, where Nm is the number of maximum matchings in G."
I'v read Uno's work and tried to come up with an implementation. Below is my very lengthy code with a working example. In this particular case there are 4 "feasible" vertices (according to Uno's terminology), so switching each with an already covered vertex, you have all together 2^4 = 16 different possible maximum matchings.
I've to admit that I'm very new to graph theory and I wasn't following Uno's processes exactly, there are minor differences and mostly I didn't attempt to do any optimizations. I did struggle in understanding the paper as I think the explanations are not quite perfect and the figures may have errors in them. So please do use with care and if you can help optimize it that will be real great!
import networkx as nx
from networkx import bipartite
def plotGraph(graph):
import matplotlib.pyplot as plt
fig=plt.figure()
ax=fig.add_subplot(111)
pos=[(ii[1],ii[0]) for ii in graph.nodes()]
pos_dict=dict(zip(graph.nodes(),pos))
nx.draw(graph,pos=pos_dict,ax=ax,with_labels=True)
plt.show(block=False)
return
def formDirected(g,match):
'''Form directed graph D from G and matching M.
<g>: undirected bipartite graph. Nodes are separated by their
'bipartite' attribute.
<match>: list of edges forming a matching of <g>.
Return <d>: directed graph, with edges in <match> pointing from set-0
(bipartite attribute ==0) to set-1 (bipartite attrbiute==1),
and the other edges in <g> but not in <matching> pointing
from set-1 to set-0.
'''
d=nx.DiGraph()
for ee in g.edges():
if ee in match or (ee[1],ee[0]) in match:
if g.node[ee[0]]['bipartite']==0:
d.add_edge(ee[0],ee[1])
else:
d.add_edge(ee[1],ee[0])
else:
if g.node[ee[0]]['bipartite']==0:
d.add_edge(ee[1],ee[0])
else:
d.add_edge(ee[0],ee[1])
return d
def enumMaximumMatching(g):
'''Find all maximum matchings in an undirected bipartite graph.
<g>: undirected bipartite graph. Nodes are separated by their
'bipartite' attribute.
Return <all_matches>: list, each is a list of edges forming a maximum
matching of <g>.
'''
all_matches=[]
#----------------Find one matching M----------------
match=bipartite.hopcroft_karp_matching(g)
#---------------Re-orient match arcs---------------
match2=[]
for kk,vv in match.items():
if g.node[kk]['bipartite']==0:
match2.append((kk,vv))
match=match2
all_matches.append(match)
#-----------------Enter recursion-----------------
all_matches=enumMaximumMatchingIter(g,match,all_matches,None)
return all_matches
def enumMaximumMatchingIter(g,match,all_matches,add_e=None):
'''Recurively search maximum matchings.
<g>: undirected bipartite graph. Nodes are separated by their
'bipartite' attribute.
<match>: list of edges forming one maximum matching of <g>.
<all_matches>: list, each is a list of edges forming a maximum
matching of <g>. Newly found matchings will be appended
into this list.
<add_e>: tuple, the edge used to form subproblems. If not None,
will be added to each newly found matchings.
Return <all_matches>: updated list of all maximum matchings.
'''
#---------------Form directed graph D---------------
d=formDirected(g,match)
#-----------------Find cycles in D-----------------
cycles=list(nx.simple_cycles(d))
if len(cycles)==0:
#---------If no cycle, find a feasible path---------
all_uncovered=set(g.node).difference(set([ii[0] for ii in match]))
all_uncovered=all_uncovered.difference(set([ii[1] for ii in match]))
all_uncovered=list(all_uncovered)
#--------------If no path, terminiate--------------
if len(all_uncovered)==0:
return all_matches
#----------Find a length 2 feasible path----------
idx=0
uncovered=all_uncovered[idx]
while True:
if uncovered not in nx.isolates(g):
paths=nx.single_source_shortest_path(d,uncovered,cutoff=2)
len2paths=[vv for kk,vv in paths.items() if len(vv)==3]
if len(len2paths)>0:
reversed=False
break
#----------------Try reversed path----------------
paths_rev=nx.single_source_shortest_path(d.reverse(),uncovered,cutoff=2)
len2paths=[vv for kk,vv in paths_rev.items() if len(vv)==3]
if len(len2paths)>0:
reversed=True
break
idx+=1
if idx>len(all_uncovered)-1:
return all_matches
uncovered=all_uncovered[idx]
#-------------Create a new matching M'-------------
len2path=len2paths[0]
if reversed:
len2path=len2path[::-1]
len2path=zip(len2path[:-1],len2path[1:])
new_match=[]
for ee in d.edges():
if ee in len2path:
if g.node[ee[1]]['bipartite']==0:
new_match.append((ee[1],ee[0]))
else:
if g.node[ee[0]]['bipartite']==0:
new_match.append(ee)
if add_e is not None:
for ii in add_e:
new_match.append(ii)
all_matches.append(new_match)
#---------------------Select e---------------------
e=set(len2path).difference(set(match))
e=list(e)[0]
#-----------------Form subproblems-----------------
g_plus=g.copy()
g_minus=g.copy()
g_plus.remove_node(e[0])
g_plus.remove_node(e[1])
g_minus.remove_edge(e[0],e[1])
add_e_new=[e,]
if add_e is not None:
add_e_new.extend(add_e)
all_matches=enumMaximumMatchingIter(g_minus,match,all_matches,add_e)
all_matches=enumMaximumMatchingIter(g_plus,new_match,all_matches,add_e_new)
else:
#----------------Find a cycle in D----------------
cycle=cycles[0]
cycle.append(cycle[0])
cycle=zip(cycle[:-1],cycle[1:])
#-------------Create a new matching M'-------------
new_match=[]
for ee in d.edges():
if ee in cycle:
if g.node[ee[1]]['bipartite']==0:
new_match.append((ee[1],ee[0]))
else:
if g.node[ee[0]]['bipartite']==0:
new_match.append(ee)
if add_e is not None:
for ii in add_e:
new_match.append(ii)
all_matches.append(new_match)
#-----------------Choose an edge E-----------------
e=set(match).intersection(set(cycle))
e=list(e)[0]
#-----------------Form subproblems-----------------
g_plus=g.copy()
g_minus=g.copy()
g_plus.remove_node(e[0])
g_plus.remove_node(e[1])
g_minus.remove_edge(e[0],e[1])
add_e_new=[e,]
if add_e is not None:
add_e_new.extend(add_e)
all_matches=enumMaximumMatchingIter(g_plus,match,all_matches,add_e_new)
all_matches=enumMaximumMatchingIter(g_minus,new_match,all_matches,add_e)
return all_matches
if __name__=='__main__':
g=nx.Graph()
edges=[
[(1,0), (0,0)],
[(1,1), (0,0)],
[(1,2), (0,2)],
[(1,3), (0,2)],
[(1,4), (0,3)],
[(1,4), (0,5)],
[(1,5), (0,2)],
[(1,5), (0,4)],
[(1,6), (0,1)],
[(1,6), (0,4)],
[(1,6), (0,6)]
]
for ii in edges:
g.add_node(ii[0],bipartite=0)
g.add_node(ii[1],bipartite=1)
g.add_edges_from(edges)
plotGraph(g)
all_matches=enumMaximumMatching(g)
for mm in all_matches:
g_match=nx.Graph()
for ii in mm:
g_match.add_edge(ii[0],ii[1])
plotGraph(g_match)
Related
I would like to generate undirected graphs without self-loops using networkx library using the following idea: in the beginning we have two nodes and one edge that is connecting them. Then on the next iterations we create new node and connect it with two edges to random chosen nodes of the graph. So then next iteration will be 3 nodes and three edges (circle), then 4 nodes and 5 edges, 5 nodes and 7 edges, etc.
Here is my approach:
import random
import networkx as nx
def generateGraph(n):
G = nx.Graph()
G.add_node(0)
G.add_node(1)
G.add_edge(0, 1)
nodes = []
while G.number_of_nodes() < n:
new_node = G.number_of_nodes()
new_edge = G.number_of_edges()
G.add_node(new_node)
destination = random.choice(new_node)
nodes.append(destination)
G.add_edge(new_node, new_edge)
G.add_edge(node, new_edge)
return G
What am I missing here and how can I change my approach? The problem that there are only n nodes and n+1 edges using this model but it should be n nodes and +2 edges(after 5 nodes). Thank you
Probably, this should follow your algorithm
def generate_graph(n):
G = nx.Graph([ (0, 1) ]) # put list of edges
for n in range(2, n):
# take two nodes randomly
nodes_to_take = random.sample(list(G.nodes), 2)
G.add_edges_from([ # connect new node 'n' with two chosen nodes from graph
(n, nodes_to_take[0]), (n, nodes_to_take[1])
])
return G
Result:
gr = generate_graph(7)
nx.draw_networkx(gr)
I have a large graph in which I want to find a subgraph isomorphism using the built-in VF2 algorithm in NetworkX. Both the 'haystack' as well as 'needle' graphs are directed. Take the following trivial example:
from networkx.algorithms.isomorphism import DiGraphMatcher
G1 = nx.complete_graph(20, nx.DiGraph)
G2 = nx.DiGraph()
G2.add_edge(1, 2)
list(DiGraphMatcher(G1, G2).subgraph_isomorphisms_iter())
The final line returns an empty list [].
My understanding is that this should return all edges in the graph, and indeed, if I substitute GraphMatcher for DiGraphMatcher, I get a list of all edges.
Is there something wrong with DiGraphMatcher, or perhaps something wrong with my understanding of what DiGraphMatcher should be doing?
Versions:
Python: 3.7.7
NetworkX: 2.4
Example undirected graph code (replaces all DiGraph with Graph, otherwise the same):
from networkx.algorithms.isomorphism import GraphMatcher
G1 = nx.complete_graph(20, nx.Graph)
G2 = nx.Graph()
G2.add_edge(1, 2)
list(GraphMatcher(G1, G2).subgraph_isomorphisms_iter())
Answering my own question after many hours of sorrow. I was hoping this was going to be an interesting technical question. Turns out it's just a run-of-the-mill nomenclature question!
NetworkX defines a subgraph isomorphism as the following:
If G'=(N',E') is a node-induced subgraph, then:
N' is a subset of N
E' is the subset of edges in E relating nodes in N'
(Taken from networkx inline code comments.)
It defines a mono​morphism as the following:
If G'=(N',E') is a monomorphism, then:
N' is a subset of N
E' is a subset of the set of edges in E relating nodes in N'
And further, notes:
Note that if G' is a node-induced subgraph of G, then it is always a
subgraph monomorphism of G, but the opposite is not always true, as a
monomorphism can have fewer edges.
In other words, because there are other edges involved in this graph than are described by the G2 graph, the DiGraphMatcher considers the set of edges E' to be not equal to the subset of edges in E relating nodes in N'.
Instead, the edges in E' are a subset of the set of edges in E relating nodes in N', and so networkx calls this a monomorphism instead.
To better illustrate this point, consider the following:
from networkx.algorithms.isomorphism import DiGraphMatcher
G1 = nx.DiGraph()
G1.add_edge(1, 2)
G1.add_edge(2, 1)
G2 = nx.DiGraph()
G2.add_edge(1, 2)
print(list(DiGraphMatcher(G1, G2).subgraph_isomorphisms_iter()))
print(list(DiGraphMatcher(G1, G2).subgraph_monomorphisms_iter()))
This will print the following:
[{1: 1, 2: 2}, {2: 1, 1: 2}] # subgraph MONOmorphism
[] # subgraph ISOmorphism
I'm using Networkx to compute some measures of a graph such as diameter, clustering coefficient, etc. It's straight forward how to do this for graph as a whole. What I'm interested in is finding these measures between nodes that have same attribute(say color). I'm thinking if I could partition the graph into different sub graphs, where nodes in each sub graph are of the same color, then I could accomplish go ahead and measure diameter in this sub graph. So my question is: Is there a way to partition a graph into sub graphs which contain nodes of same color?
I would really appreciate any insight.
Use Graph.subgraph(nodes)
NetworkX 2.x+:
Demo
import networkx as nx
G = nx.Graph()
G.add_nodes_from([1, 2, 3], color="red")
G.add_nodes_from([4, 5, 6])
G.nodes # NodeView((1, 2, 3, 4, 5, 6))
# create generator
nodes = (
node
for node, data
in G.nodes(data=True)
if data.get("color") == "red"
)
subgraph = G.subgraph(nodes)
subgraph.nodes # NodeView((1, 2, 3))
older NetworkX's
Iterate over (Graph.iter_nodes()) and filter the nodes based on your criteria. Pass that list to Graph.subgraph() and it'll return a copy of those nodes and their internal edges.
For example:
G = nx.Graph()
# ... build or do whatever to the graph
nodes = (n for n, d in G.nodes_iter(data=True)) if d.get('color') == 'red')
subgraph = G.subgraph(nodes)
I'm working on a graph with multiple edges between the same nodes (edges are having different values). In order to model this graph I need to use MultiGraph instead of normal Graph. Unfortunately, it's not possible to run PageRank algo on it.
Any workarounds known ?
NetworkXNotImplemented: not implemented for multigraph type
You could create make a graph without parallel edges and then run pagerank.
Here is an example of summing edge weights of parallel edges to make a simple graph:
import networkx as nx
G = nx.MultiGraph()
G.add_edge(1,2,weight=7)
G.add_edge(1,2,weight=10)
G.add_edge(2,3,weight=9)
# make new graph with sum of weights on each edge
H = nx.Graph()
for u,v,d in G.edges(data=True):
w = d['weight']
if H.has_edge(u,v):
H[u][v]['weight'] += w
else:
H.add_edge(u,v,weight=w)
print H.edges(data=True)
#[(1, 2, {'weight': 17}), (2, 3, {'weight': 9})]
print nx.pagerank(H)
#{1: 0.32037465332634, 2: 0.4864858243244209, 3: 0.1931395223492388}
You can still compose a Digraph by combining the edges
while adding their weights.
# combining edges using defaultdict
# input-- combined list of all edges
# ouput-- list of edges with summed weights for duplicate edges
from collections import defaultdict
def combine_edges(combined_edge_list):
ddict = defaultdict(list)
for edge in combined_edge_list:
n1,n2,w = edge
ddict[(n1,n2)].append(w)
for k in ddict.keys():
ddict[k] = sum(ddict[k])
edges = list(zip( ddict.keys(), ddict.values() ) )
return [(n1,n2,w) for (n1,n2),w in edges]
I want to compare nodes of different edges in the graph. How can I get the nodes(n1 and n2) from the edge(n1,n2)?
An edge in NetworkX is defined by its nodes, so I'm not really sure what you're asking here. A specific edge in the graph is just a tuple of nodes, with an optional weighting.
import networkx as nx
g = nx.Graph()
g.add_edge(1,2)
g.add_edge(2,3)
g.edges()
gives
[(1, 2), (2, 3)]
As you can see, the list of edges explicitly provides the nodes of each edge.
Update: Does this do what you want?
#!/usr/bin/python
import networkx as nx
import random
g = nx.Graph()
g.add_edges_from([(1,2),(2,3),(1,4),(2,5)])
random_edge = random.choice(g.edges())
print 'Randomly selected edge is:', random_edge
print 'Nodes are', random_edge[0], 'and', random_edge[1]
The answer to what I think was the intended question is:
graph = networkx.read_graphml('some_fully_loaded_graph.graphml')
edge0 = list(graph.edges(data=True))[0]
subgraph = graph.edge_subgraph([edge0[:2]])
nodes0 = list(subgraph.nodes(data=True))