diff --git a/nx_parallel/algorithms/shortest_paths/__init__.py b/nx_parallel/algorithms/shortest_paths/__init__.py new file mode 100644 index 0000000..10a565d --- /dev/null +++ b/nx_parallel/algorithms/shortest_paths/__init__.py @@ -0,0 +1 @@ +from .weighted import * diff --git a/nx_parallel/algorithms/shortest_paths/weighted.py b/nx_parallel/algorithms/shortest_paths/weighted.py new file mode 100644 index 0000000..68d4559 --- /dev/null +++ b/nx_parallel/algorithms/shortest_paths/weighted.py @@ -0,0 +1,71 @@ +from joblib import Parallel, delayed +from networkx.algorithms.shortest_paths.weighted import single_source_bellman_ford_path + +import nx_parallel as nxp + +__all__ = ["all_pairs_bellman_ford_path"] + + +def all_pairs_bellman_ford_path(G, weight="weight"): + """Compute shortest paths between all nodes in a weighted graph. + + Parameters + ---------- + G : NetworkX graph + + weight : string or function (default="weight") + If this is a string, then edge weights will be accessed via the + edge attribute with this key (that is, the weight of the edge + joining `u` to `v` will be ``G.edges[u, v][weight]``). If no + such edge attribute exists, the weight of the edge is assumed to + be one. + + If this is a function, the weight of an edge is the value + returned by the function. The function must accept exactly three + positional arguments: the two endpoints of an edge and the + dictionary of edge attributes for that edge. The function must + return a number. + + Returns + ------- + paths : iterator + (source, dictionary) iterator with dictionary keyed by target and + shortest path as the key value. + + Notes + ----- + Edge weight attributes must be numerical. + Distances are calculated as sums of weighted edges traversed. + + Examples + -------- + >>> import networkx as nx + >>> G = nx.Graph() + >>> G.add_weighted_edges_from([(1, 0, 1), (1, 2, 1), (2, 0, 3)]) + >>> path = dict(nx.all_pairs_bellman_ford_path(G)) + >>> path[0][2] + [0, 1, 2] + >>> parallel_path = dict(nx.all_pairs_bellman_ford_path(G, backend="parallel")) + >>> parallel_path[0][2] + [0, 1, 2] + >>> import nx_parallel as nxp + >>> parallel_path_ = dict(nx.all_pairs_bellman_ford_path(nxp.ParallelGraph(G))) + >>> parallel_path_ + {1: {1: [1], 0: [1, 0], 2: [1, 2]}, 0: {0: [0], 1: [0, 1], 2: [0, 1, 2]}, 2: {2: [2], 1: [2, 1], 0: [2, 1, 0]}} + + """ + + def _calculate_shortest_paths_subset(source): + return (source, single_source_bellman_ford_path(G, source, weight=weight)) + + if hasattr(G, "graph_object"): + G = G.graph_object + + nodes = G.nodes + + total_cores = nxp.cpu_count() + + paths = Parallel(n_jobs=total_cores, return_as="generator")( + delayed(_calculate_shortest_paths_subset)(source) for source in nodes + ) + return paths diff --git a/nx_parallel/interface.py b/nx_parallel/interface.py index a60b3c1..38b090d 100644 --- a/nx_parallel/interface.py +++ b/nx_parallel/interface.py @@ -1,7 +1,6 @@ from nx_parallel.algorithms.centrality.betweenness import betweenness_centrality -from nx_parallel.algorithms.efficiency_measures import ( - local_efficiency, -) +from nx_parallel.algorithms.shortest_paths.weighted import all_pairs_bellman_ford_path +from nx_parallel.algorithms.efficiency_measures import local_efficiency from nx_parallel.algorithms.isolate import number_of_isolates from nx_parallel.algorithms.tournament import ( is_reachable, @@ -24,6 +23,7 @@ def is_multigraph(self): def is_directed(self): return self.graph_object.is_directed() + class Dispatcher: # ============================= @@ -43,6 +43,9 @@ class Dispatcher: # Efficiency local_efficiency = local_efficiency + # Shortest Paths : all pairs shortest paths(bellman_ford) + all_pairs_bellman_ford_path = all_pairs_bellman_ford_path + # ============================= @staticmethod diff --git a/timing/heatmap_all_pairs_bellman_ford_path_timing.png b/timing/heatmap_all_pairs_bellman_ford_path_timing.png new file mode 100644 index 0000000..3c78818 Binary files /dev/null and b/timing/heatmap_all_pairs_bellman_ford_path_timing.png differ diff --git a/timing/timing_comparison.md b/timing/timing_comparison.md index cbbd0ae..9c09545 100644 --- a/timing/timing_comparison.md +++ b/timing/timing_comparison.md @@ -27,3 +27,6 @@ local_efficiency tournament is_reachable ![alt text](heatmap_is_reachable_timing.png) + +all_pairs_bellman_ford_path +![alt text](heatmap_all_pairs_bellman_ford_path_timing.png) diff --git a/timing/timing_individual_function.py b/timing/timing_individual_function.py index 0de014e..7c14681 100644 --- a/timing/timing_individual_function.py +++ b/timing/timing_individual_function.py @@ -1,4 +1,6 @@ import time +import random +import types import networkx as nx import pandas as pd @@ -11,34 +13,39 @@ heatmapDF = pd.DataFrame() number_of_nodes_list = [10, 50, 100, 300, 500] pList = [1, 0.8, 0.6, 0.4, 0.2] -currFun = nx.betweenness_centrality -for i in range(0, len(pList)): - p = pList[i] - for j in range(0, len(number_of_nodes_list)): - num = number_of_nodes_list[j] - +currFun = nx.all_pairs_bellman_ford_path +for p in pList: + for num in number_of_nodes_list: # create original and parallel graphs - G = nx.fast_gnp_random_graph(num, p, directed=False) + G = nx.fast_gnp_random_graph(num, p, seed=42, directed=False) + + # for weighted graphs + random.seed(42) + for u, v in G.edges(): + G[u][v]["weight"] = random.random() + H = nx_parallel.ParallelGraph(G) # time both versions and update heatmapDF t1 = time.time() c = currFun(H) + if type(c) == types.GeneratorType: + d = dict(c) t2 = time.time() parallelTime = t2 - t1 t1 = time.time() c = currFun(G) + if type(c) == types.GeneratorType: + d = dict(c) t2 = time.time() stdTime = t2 - t1 timesFaster = stdTime / parallelTime - heatmapDF.at[j, i] = timesFaster + heatmapDF.at[num, p] = timesFaster print("Finished " + str(currFun)) # Code to create for row of heatmap specifically for tournaments -# for i in range(0, len(pList)): -# p = pList[i] -# for j in range(0, len(number_of_nodes_list)): -# num = number_of_nodes_list[j] +# for p in pList: +# for num in number_of_nodes_list): # G = nx.tournament.random_tournament(num) # H = nx_parallel.ParallelDiGraph(G) # t1 = time.time() @@ -50,7 +57,7 @@ # t2 = time.time() # stdTime = t2-t1 # timesFaster = stdTime/parallelTime -# heatmapDF.at[j, 3] = timesFaster +# heatmapDF.at[num, 3] = timesFaster # plotting the heatmap with numbers and a green color scheme plt.figure(figsize=(20, 4))