From 9b1d2b30539c4161e5d8513c8bafc1b1fb69fb09 Mon Sep 17 00:00:00 2001 From: Emin Berker <45400544+eminberker@users.noreply.github.com> Date: Thu, 20 Jun 2024 00:26:04 +0100 Subject: [PATCH] Updated __calc_path_preference to use Floyd Warshall The existing __calc_path_preference function in profile.py runs in exponential time (in fact O(m!)) since every recursive call can have up to m recursive calls. I am guessing this is the reason the chairs decided not to include __calc_path_preference in the profile initialization. The widest paths (i.e., strengths) for Schulze can also be done using the Floyd Warshall algorithms, which runs in O(n^3) time. In my local runs, this version __calc_path_preference runs much much much faster than the original version. --- compsoc/profile.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/compsoc/profile.py b/compsoc/profile.py index 6d5dbfd..79cf4c2 100644 --- a/compsoc/profile.py +++ b/compsoc/profile.py @@ -60,6 +60,7 @@ def __init__(self, pairs: Set[Tuple[int, Tuple[int, ...]]], num_candidates: Opti self.__calc_votes_per_candidate() # Initialize a Path Preference Graph self.path_preference_graph = {candidate: {} for candidate in self.candidates} + self.__calc_path_preference() # --------------------------------------------- # Comparison routines @@ -223,25 +224,42 @@ def __calc_votes_per_candidate(self): def __calc_path_preference(self): """ - Computes paths' strengths for the Schulze method. + Computes paths' strengths for the Schulze method, using the Floyd-Warshall algorithm. """ # Create an iterable for candidates candidates = list(self.candidates) - # Number of candidates - n_candidates = len(candidates) - for i in range(n_candidates): + + """ + Implementation of Floyd-Warshall algorithm to find the widest path (maximum capacity path) between all pairs of nodes. + + :param graph: A 2D list or matrix representing the graph where graph[i][j] is the capacity of the edge from node i to node j. + :return: A 2D list where the element at [i][j] represents the maximum capacity of the widest path from node i to node j. + """ + # Number of vertices in the graph + n = len(self.candidates) + + # Initialize the distance matrix with the input graph capacities + # Also handle the case where there is no direct edge between nodes by initializing to 0 (or any negative value) + + widest_paths = [[0 if i != j and self.net_preference_graph[candidates[i]][candidates[j]] == 0 else self.net_preference_graph[candidates[i]][candidates[j]] for j in range(n)] for i in range(n)] + + # Apply Floyd-Warshall algorithm to find the widest paths + for k in range(n): + for i in range(n): + for j in range(n): + # Update the widest path to be the maximum capacity path through an intermediate node k + widest_paths[i][j] = max(widest_paths[i][j], min(widest_paths[i][k], widest_paths[k][j])) + for i in range(n): # Get candidate1 candidate1 = candidates[i] - for j in range(i + 1, n_candidates): + for j in range(i + 1, n): # Get candidate2 candidate2 = candidates[j] # Get strengths of candidate1 VS candidate2 - strength1 = self.__calc_strength(candidate1, candidate2) - # Get strengths of candidate2 VS candidate1 - strength2 = self.__calc_strength(candidate2, candidate1) # Save strengths - self.path_preference_graph[candidate1][candidate2] = strength1 - self.path_preference_graph[candidate2][candidate1] = strength2 + self.path_preference_graph[candidate1][candidate2] = widest_paths[i][j] + self.path_preference_graph[candidate2][candidate1] = widest_paths[j][i] + def __calc_strength(self, candidate1, candidate2): """