Find the longest circle way in networkx - python

I am trying to found the longest way in networkx from node A to node A using only 10 other nodes in fully connected graph as shown below. Is it possible?
G = nx.Graph()
for i in range(len(hashlist)):
for j in range(len(hashlist)):
if i!=j:
connection=(i,j)
size=hashlist[i]-hashlist[j]
G.add_edge(i, j, weight=size)

This will find the longest cycle in a directed graph.
import networkx as nx
G = nx.Graph([(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]).to_directed()
a = sorted(list(nx.simple_cycles(G)), key = lambda s: len(s))
print(a[-1])
The same approach will work for weighted graph.
import networkx as nx
G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5), (2,3, 5), (1,3, 2.4)])
G = G.to_directed()
a = sorted(list(nx.simple_cycles(G)), key = lambda s: len(s))
print(a[-1])

Related

Adding multiple directed edges in networkx

I know this should be very basic but I have no clue how to do this using networkx. What I am trying to do is to create a MultiDiGraph with 20 nodes. There would be 2 edges connecting each nodes to each other, one away from the node and the other going towards the node. I am unable to create those edges. Any help would be greatly appreciated. It should look something like the picture I have attached.
You could create a graph, and then convert it to a directed graph. In this way you get edges in both directions:
import networkx as nx
g = nx.Graph()
g.add_edges_from([(0, 1), (1, 2), (1, 3)])
g = g.to_directed()
>>> g.edges
OutEdgeView([(0, 1), (1, 0), (1, 2), (1, 3), (2, 1), (3, 1)])
If you want to generate a complete directed graph with n nodes:
import networkx as nx
g = nx.complete_graph(4).to_directed()
>>> g.edges
OutEdgeView([(0, 1), (0, 2), (0, 3), (1, 0), (1, 2), (1, 3), (2, 0), (2, 1), (2, 3), (3, 0), (3, 1), (3, 2)])

How to get position of edge weights in a networkx graph?

Currently there is a function in networkx library for getting positions of all nodes: spring_layout. Quoting from the docs, it returns:
dict :
A dictionary of positions keyed by node
And can be used as:
G=nx.path_graph(4)
pos = nx.spring_layout(G)
I would like something similar to access the position of an edge-weight for a weighted graph. It should return the position of where the number for edge-weight would be placed, preferably at the center of the edge and just above the edge. (By above, I mean "outside" the graph, so for a horizontally-placed square graph's bottom-most edge, it would be just below the edge).
So the question is, is there anything in-built similar to spring_layout for achieving this? And if not, how to go about it yourself?
You can use nx.draw_edge_labels which returns a dictionary with edges as keys and (x, y, label) as values
import matplotlib.pyplot as plt
import networkx as nx
# Create a graph
G = nx.path_graph(10)
# Add 2 egdes with labels
G.add_edge(0, 8, name='n1')
G.add_edge(2, 7, name='n2')
# Get the layout
pos = nx.spring_layout(G)
# Draw the graph
nx.draw(G, pos=pos)
# Draw the edge labels
edge_labels = nx.draw_networkx_edge_labels(G, pos)
.
Now you can see the variables edge_labels
print(edge_labels)
# {(0, 1): Text(0.436919941201627, -0.2110471432994752, '{}'),
# (0, 8): Text(0.56941037628304, 0.08059107891826373, "{'name': 'n1'}"),
# (1, 2): Text(0.12712625526483384, -0.2901338796021985, '{}'),
# (2, 3): Text(-0.28017240645783603, -0.2947104829441387, '{}'),
# (2, 7): Text(0.007024254096114596, -0.029867791669433513, "{'name': 'n2'}"),
# (3, 4): Text(-0.6680363649371021, -0.26708812849092933, '{}'),
# (4, 5): Text(-0.8016944207643129, -0.0029986274715349814, '{}'),
# (5, 6): Text(-0.5673817462107436, 0.23808073918504968, '{}'),
# (6, 7): Text(-0.1465270298295821, 0.23883392944036055, '{}'),
# (7, 8): Text(0.33035539545007536, 0.2070939421162053, '{}'),
# (8, 9): Text(0.7914739158501038, 0.2699223242747882, '{}')}
Now to get the position of say, edge (2,7), you just need to do
print(edge_labels[(2,7)].get_position())
# Output: (0.007024254096114596, -0.029867791669433513)
You can read more about the documentation here.
If you want to extract the x,y coordinates of all the edges, you can try this:
edge_label_pos = { k: v.get_position()
for k, v in edge_labels.items()}
#{(0, 1): (0.436919941201627, -0.2110471432994752),
# (0, 8): (0.56941037628304, 0.08059107891826373),
# (1, 2): (0.12712625526483384, -0.2901338796021985),
# (2, 3): (-0.28017240645783603, -0.2947104829441387),
# (2, 7): (0.007024254096114596, -0.029867791669433513),
# (3, 4): (-0.6680363649371021, -0.26708812849092933),
# (4, 5): (-0.8016944207643129, -0.0029986274715349814),
# (5, 6): (-0.5673817462107436, 0.23808073918504968),
# (6, 7): (-0.1465270298295821, 0.23883392944036055),
# (7, 8): (0.33035539545007536, 0.2070939421162053),
# (8, 9): (0.7914739158501038, 0.2699223242747882)}

Generate Complete DFS Paths using networkx

I am trying to generate complete path list instead of the optimized one. Better explained using the below example.
import networkx as nx
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (2, 3)])
G.add_edges_from([(0, 1), (1, 2), (2, 4)])
G.add_edges_from([(0, 5), (5, 6)])
The above code create a Graph with edges 0=>1=>2=>3 and 0=>1=>2=>4 and 0=>5=>6
All I want is to extract all paths from 0.
I tried:
>> list(nx.dfs_edges(G, 0))
[(0, 1), (1, 2), (2, 3), (2, 4), (0, 5), (5, 6)]
All I want is:
[(0, 1, 2, 3), (0, 1, 2, 4), (0, 5, 6)]
Is there any pre-existing method from networkx which can be used? If not, any way to write an optimal method that can do the job?
Note: My problem is limited to the given example. No more corner cases possible.
Note2: For simplification the data is generated. In my case, the edges list is coming from data set. Assumption is given a graph and a node (Say 0), Can we generate all paths?
Give this a try:
import networkx as nx
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (2, 3)])
G.add_edges_from([(0, 1), (1, 2), (2, 4)])
G.add_edges_from([(0, 5), (5, 6)])
pathes = []
path = [0]
for edge in nx.dfs_edges(G, 0):
if edge[0] == path[-1]:
# node of path
path.append(edge[1])
else:
# new path
pathes.append(path)
search_index = 2
while search_index <= len(path):
if edge[0] == path[-search_index]:
path = path[:-search_index + 1] + [edge[1]]
break
search_index += 1
else:
raise Exception("Wrong path structure?", path, edge)
# append last path
pathes.append(path)
print(pathes)
# [[0, 1, 2, 3], [0, 1, 2, 4], [0, 5, 6]]

Counting edges between nodes in networkx

I have a graph where my nodes can have multiple edges between them in both directions and I want to set the width between the nodes based on the sum of all edges between them.
import networkx as nx
nodes = [0,1]
edges = [(0,1),(1,0)]
G = nx.Graph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)
weights = [2,3]
nx.draw(G, width = weights)
I would like to have the width between 0 and 1 set to 5 as that is the summed weight.
First you need to create a MultiDiGraph and add all possible edges to it. This is because it supports multiple directed egdes between the same set of nodes including self-loops.
import networkx as nx
nodes = [0, 1, 2, 3, 4, 5]
edges = [(0,1), (1,0), (1, 0),(0, 1), (2, 3), (2, 3), (2, 3), (2, 3),
(4, 1), (4, 1), (4, 1), (4, 1), (4, 1), (4, 1), (4, 5), (5, 0)]
G = nx.MultiDiGraph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)
Next, create a dictionary containing counts of each edges
from collections import Counter
width_dict = Counter(G.edges())
edge_width = [ (u, v, {'width': value})
for ((u, v), value) in width_dict.items()]
Now create a new DiGraph from the edge_width dictionary created above
G_new = nx.DiGraph()
G_new.add_edges_from(edge_width)
Plotting using thickened edges
This is an extension of answer mentioned here.
edges = G_new.edges()
weights = [G_new[u][v]['width'] for u,v in edges]
nx.draw(G_new, edges=edges, width=weights)
Add Edge labels
See this answer for more info.
pos = nx.spring_layout(G_new)
nx.draw(G_new, pos)
edge_labels=dict([((u,v,),d['width'])
for u,v,d in G_new.edges(data=True)])
nx.draw_networkx_edges(G_new, pos=pos)
nx.draw_networkx_edge_labels(G_new, pos, edge_labels=edge_labels,
label_pos=0.25, font_size=10)
You can also view this Google Colab Notebook with working code.
References
https://stackoverflow.com/a/25651827/8160718
https://stackoverflow.com/a/22862610/8160718
Drawing networkX edges
MultiDiGraph in NetworkX
Count occurrences of List items

Getting subgraph of nodes between two nodes?

I have this graph:
%matplotlib inline
import networkx as nx
G = nx.Graph()
G.add_edge(1, 2)
G.add_edge(2, 3)
G.add_edge(3, 4)
G.add_edge(3, 5)
G.add_edge(4, 6)
G.add_edge(5, 6)
G.add_edge(3, 7)
G.add_edge(7, 6)
G.add_edge(6, 8)
G.add_edge(8, 9)
nx.draw(G, pos=nx.spring_layout(G), with_labels=True)
Is it possible to get the subgraph between the nodes 3 and 6 without using nx.subgraph(G, [3,4,5,6,7]). I mean, what if I know that there is this subgraph, but I don't know e.g. about the 5?
My answer is very similar to back2basics, but more directly finds the nodes between the two. If there is a path from source to target, that path will be found by nx.all_simple_paths(G, source=source, target=target) which returns a generator for the paths.
import networkx as nx
G = nx.Graph()
G.add_edges_from([(1, 2), (2, 3), (3, 4), (3, 5), (4, 6), (5, 6), (3, 7), (7, 6), (6, 8), (8, 9)])
paths_between_generator = nx.all_simple_paths(G,source=3,target=6)
nodes_between_set = {node for path in paths_between_generator for node in path}
SG = G.subgraph(nodes_between_set)
the nodes_between_set = ... uses a "set generator". It is equivalent to
nodes_between_set = set()
for path in paths_beween_generator:
for node in path:
nodes_between_set.add(node)
The first 3 lines help made the list that you need to make the subset.
import networkx as nx
c_score = nx.algorithms.betweenness_centrality_subset(G,(3,), (6,))
nodes_between = [x for x in c_score if c_score[x]!=0.0]
nodes_between.extend((3,6)) #add on the ends
SG = G.subgraph(nodes_between)
nx.draw(SG, pos=nx.spring_layout(SG), with_labels=True)
One caveat: The subgraph points are defined as being in a path from point 3 to point 6
This works on the principle that
any node in the new subgraph is reachable from source or destination, and
any node on the path by definition has at least one predecessor and one successor (for a directed graph) or two neighbors (for an undirected graph).
So, first we find the subgraph of nodes that we can reach, and then recursively remove nodes without at least one predecessor and one successor until there is only the existing subgraph.
import networkx as nx
def subgraph_from_connections(G, source, target, directed = None):
included_nodes = [x for x in G.node if nx.has_path(G, source, x) and nx.has_path(G, x, target)]
G2 = G.subgraph(included_nodes)
# If this is a undirected graph, we only need to know if it only has 1 neighbor
# If this is a directed graph, then it needs at least 1 predecessor and at least 1 successor
if directed == True or (directed is None and type(G) == nx.classes.digraph.DiGraph):
removals = [x for x in G2.node if len(G2.predecessors(x)) == 0 or len(G2.successors(x)) == 0]
while len(removals) > 0:
G2.remove_nodes_from(removals)
removals = [x for x in G.node if len(G2.predecessors(x)) == 0 or len(G2.successors(x)) == 0]
else:
removals = [x for x in G2.node if len(G2.neighbors(x)) < 2]
while len(removals) > 0:
G2.remove_nodes_from(removals)
removals = [x for x in G2.node if len(G2.neighbors(x)) < 2]
return G2
Not extensively tested, but it worked for the few cases outlined here, and it includes 10/11 when those were included from Joel's test. The algorithm is fast enough - 130 ms for my 1000/10 node random test from before (maybe I shouldn't have deleted that after all).

Categories

Resources