def plot_right_panel(ax, parts): """Plots CHAMP domains and halfspace intersection from sampled partitions""" G = ig.Graph.Famous("Zachary") twom = 2 * G.ecount() domains = CHAMP_2D(G, [p.membership for p in parts], PLOT_XLIM[0], PLOT_XLIM[1]) # Assume that all but the gamma=1.0 domain were optimal xs1 = [domains[0][0], domains[0][1]] xs3 = [domains[1][0], domains[1][1]] xs4 = [domains[2][0], domains[2][1]] ys1 = [parts[0].quality(resolution_parameter=g) / twom for g in xs1] ys3 = [parts[2].quality(resolution_parameter=g) / twom for g in xs3] ys4 = [parts[3].quality(resolution_parameter=g) / twom for g in xs4] # Plot halfspaces where the corresponding partition is optimal ax.plot(xs1, ys1, c="C0") ax.plot(xs3, ys3, c="C2") ax.plot(xs4, ys4, c="C3") # Project domains of optimality onto gamma axis ax.fill_between(xs1, [0] * len(xs1), [0.01] * len(xs1), color="C0") ax.fill_between(xs3, [0] * len(xs3), [0.01] * len(xs3), color="C2") ax.fill_between(xs4, [0] * len(xs4), [0.01] * len(xs4), color="C3") ax.axvline(xs1[-1], ymin=0, ymax=ys1[-1] / 0.8, c='black', linestyle='--') ax.axvline(xs3[-1], ymin=0, ymax=ys3[-1] / 0.8, c='black', linestyle='--') ax.set_xlabel(r"$\gamma$", fontsize=14) ax.set_title("CHAMP Optimal Partitions and Domains", fontsize=14) ax.set_xlim(PLOT_XLIM) ax.set_ylim(PLOT_YLIM)
def plot_gamma_estimates(all_parts): G = ig.Graph.Famous("Zachary") # Print details on the CHAMP sets when the number of communities is restricted # print(len(all_parts), "unique partitions in total") # for K in range(2, 9): # restricted_parts = {p for p in all_parts if num_communities(p) == K} # print(f"{len(restricted_parts)} unique partitions with {K} communities") # ranges = CHAMP_2D(G, restricted_parts, GAMMA_START, GAMMA_END) # print(f"{len(ranges)} unique partitions in {K}-community CHAMP set") # print("=" * 50) ranges = CHAMP_2D(G, all_parts, GAMMA_START, GAMMA_END) gamma_estimates = ranges_to_gamma_estimates(G, ranges) # Print details on the CHAMP set when the number of communities is not restricted # community_counts = [0] * 9 # for _, _, membership, _ in gamma_estimates: # community_counts[num_communities(membership)] += 1 # for k, count in enumerate(community_counts): # print(f"{count} unique partitions with {k} communities in total CHAMP set") # Plot gamma estimates and domains of optimality when the number of communities is not restricted plt.rc('text', usetex=True) plt.rc('font', family='serif') plot_estimates(gamma_estimates) plt.title(r"Karate Club CHAMP Domains of Optimality and $\gamma$ Estimates", fontsize=14) plt.xlabel(r"$\gamma$", fontsize=14) plt.ylabel("Number of communities", fontsize=14) plt.savefig("karate_club_CHAMP_gamma_estimates.pdf")
def test_champ_correctness_igraph_famous_louvain(self): """Test CHAMP correctness on various famous graphs while obtaining partitions via Louvain. The correctness of the CHAMP domains are checked for the original undirected and (symmetric) directed variants. """ for G in generate_igraph_famous(): gammas = generate_random_values(100, start_value=0, end_value=5) partitions = repeated_louvain_from_gammas(G, gammas) champ_ranges = CHAMP_2D(G, partitions, gamma_0=0, gamma_f=5) self.assert_best_partitions_match_champ_set( G, partitions, champ_ranges, gammas) G.to_directed() # check the directed version of the graph as well partitions = repeated_louvain_from_gammas(G, gammas) champ_ranges = CHAMP_2D(G, partitions, gamma_0=0, gamma_f=5) self.assert_best_partitions_match_champ_set( G, partitions, champ_ranges, gammas)
def assert_champ_correctness_unweighted_ER(self, n=100, m=1000, directed=False, num_partitions=10, num_gammas=100, K_max=5, gamma_start=0.0, gamma_end=2.0): G = generate_connected_ER(n=n, m=m, directed=directed) partitions = generate_random_partitions(num_nodes=n, num_partitions=num_partitions, K_max=K_max) gammas = generate_random_values(num_gammas, gamma_start, gamma_end) champ_ranges = CHAMP_2D(G, partitions, gamma_start, gamma_end) self.assert_best_partitions_match_champ_set(G, partitions, champ_ranges, gammas)
def plot_stable_partitions(all_parts): G = ig.Graph.Famous("Zachary") # Store shared force-directed layout to make later plotting layouts consistent layout = G.layout_fruchterman_reingold(niter=1000) # Plot stable partitions when the number of communities is restricted to 2-4 for K in range(2, 5): restricted_parts = {p for p in all_parts if num_communities(p) == K} if len(restricted_parts) > 0: ranges = CHAMP_2D(G, restricted_parts, GAMMA_START, GAMMA_END) gamma_estimates = ranges_to_gamma_estimates(G, ranges) stable_parts = gamma_estimates_to_stable_partitions(gamma_estimates) for i, p in enumerate(stable_parts): ig.plot(louvain.RBConfigurationVertexPartition(G, initial_membership=p), f"karate_club_{K}_stable{i}.png", bbox=(1000, 1000), layout=layout)
def test(gamma_0=0.0, gamma_f=2.0, louvain_iterations=100, timeout=60): """Runs a simple benchmark on the Karate Club of our parallel louvain implementation vs. CHAMP's :param num_processors: number of CPUs to use :param gamma_0: starting gamma :param gamma_f: final gamma :param louvain_iterations: number of initial louvain iterations :param timeout: maximum allowed time (in seconds) for a set of louvain runs """ xs = [] ys1 = [] ys2 = [] our_duration = 0 champ_duration = 0 num_processors = cpu_count() print(f"Test with {num_processors} CPUs") print(f'{"# partitions":>15} {"Our time (s)":>15} {"CHAMP time (s)":>15}') while our_duration < timeout: xs.append(louvain_iterations) print(f"{louvain_iterations:>15} ", end='', flush=True) start = time() all_parts = repeated_parallel_louvain_from_gammas(G, gammas=np.linspace(gamma_0, gamma_f, louvain_iterations), show_progress=False) _ = CHAMP_2D(G, all_parts, gamma_0, gamma_f) our_duration = time() - start ys1.append(our_duration) print(f"{our_duration:>15.2f} ", end='', flush=True) if champ_duration < timeout: start = time() _ = parallel_louvain(G, gamma_0, gamma_f, numruns=louvain_iterations, numprocesses=num_processors) champ_duration = time() - start ys2.append(champ_duration) print(f"{champ_duration:>15.2f}") else: print(f"{0:>15.2f}") # the number of louvain iterations roughly increases by 1.5x each iteration louvain_iterations = louvain_iterations + louvain_iterations // 2
def run_CHAMP(G, all_parts, gamma_start=GAMMA_START, gamma_end=GAMMA_END): ranges = CHAMP_2D(G, all_parts, gamma_start, gamma_end) gamma_estimates = ranges_to_gamma_estimates(G, ranges) return gamma_estimates
p2 = sorted_tuple(tuple(randint(0, 2) for _ in range(G.vcount()))) g1 = gamma_estimate(G, p1) g2 = gamma_estimate(G, p2) if g1 is None or g2 is None or np.isnan(g1) or np.isnan(g2): continue part1 = louvain_part_with_membership(G, p1) part2 = louvain_part_with_membership(G, p2) if part1.quality(resolution_parameter=g2) > part2.quality(resolution_parameter=g2): if part2.quality(resolution_parameter=g1) > part1.quality(resolution_parameter=g1): layout = G.layout_fruchterman_reingold(niter=1000) out = ig.plot(part1, layout=layout) out.save("estimation_loop1.png") out = ig.plot(part2, layout=layout) out.save("estimation_loop2.png") ranges = CHAMP_2D(G, [p1, p2], 0, 2) gamma_estimates = ranges_to_gamma_estimates(G, ranges) plt.rc('text', usetex=True) plt.rc('font', family='serif') plot_estimates(gamma_estimates) plt.title(r"Domains of Optimality with Loop in $\gamma$ Estimates", fontsize=14) plt.ylabel("Number of communities", fontsize=14) plt.xlabel(r"$\gamma$", fontsize=14) plt.savefig("estimation_loop_estimates.pdf") break
def generate_bistable_SBM_test_output(): print("Running bistable SBM test...") N = 2400 p2s = (np.linspace(0.01, 0.02, 5).tolist() + np.linspace(0.02, 0.025, 15).tolist() + np.linspace(0.025, 0.0475, 5).tolist() + np.linspace(0.0475, 0.0525, 15).tolist() + np.linspace(0.0525, 0.06, 5).tolist()) c2s = [] # probability of K=2 stability c3s = [] # probability of K=3 stability cboths = [] # probability of bistability progress = Progress(TRIALS_PER_DELTA * len(p2s)) for p_out2 in p2s: total = 0 count_2stable = 0 count_3stable = 0 count_both_stable = 0 while total < TRIALS_PER_DELTA: p_in1 = 10 / 99 # m_in = 10/3 p_in2 = p_in1 * 0.75 # m_in = 5/2 for each block p_out1 = 0.25 / 40 # m_out = 1.25 # p_out2 in outer loop pref_matrix = [[p_in1, p_out1, p_out1], [p_out1, p_in2, p_out2], [p_out1, p_out2, p_in2]] block_sizes = [N // 3] * 3 G = ig.Graph.SBM(N, pref_matrix, block_sizes) if not G.is_connected(): print("\rDisconnected graph. Skipping...", end='', flush=True) continue ground_truth = tuple(i // block_sizes[0] for i in range(N)) ground_truth2 = tuple( min(1, i // block_sizes[0]) for i in range(N)) true_gamma = gamma_estimate(G, ground_truth) true_gamma2 = gamma_estimate(G, ground_truth2) if true_gamma is None or true_gamma2 is None: print("\rDegenerate ground truth estimate. Skipping...", end='', flush=True) continue all_parts = repeated_parallel_louvain_from_gammas( G, gammas=np.linspace(GAMMA_START, GAMMA_END, LOUVAIN_ITERATIONS_PER_DELTA), show_progress=False) ranges = CHAMP_2D(G, all_parts, GAMMA_START, GAMMA_END) gamma_estimates = ranges_to_gamma_estimates(G, ranges) stable2, stable3 = False, False for g_start, g_end, membership, g_est in gamma_estimates: if g_est is not None and g_start <= g_est <= g_end: if num_communities(membership) == 2: stable2 = True elif num_communities(membership) == 3: stable3 = True if stable2: count_2stable += 1 if stable3: count_3stable += 1 if stable2 and stable3: count_both_stable += 1 total += 1 progress.increment() c2s.append(count_2stable / total) c3s.append(count_3stable / total) cboths.append(count_both_stable / total) progress.done() with open("SBM_constant_probs.out", "w") as f: print(N, file=f) print(p2s, file=f) print(c2s, file=f) print(c3s, file=f) print(cboths, file=f)
except ig.InternalError: continue if not G.is_connected(): continue gammas = np.linspace(GAMMA_START, GAMMA_END, GAMMA_ITERS) all_parts = [singlelayer_louvain(G, g) for g in gammas] if len(all_parts) == 0: continue # Print details on the CHAMP set when the number of communities is not restricted ranges = CHAMP_2D(G, all_parts, GAMMA_START, GAMMA_END, single_threaded=True) gamma_estimates = ranges_to_gamma_estimates(G, ranges) def gamma_to_domain(gamma): for gamma_start, gamma_end, part, gamma_est in gamma_estimates: if gamma_start <= gamma <= gamma_end: return part, gamma_est return None, None for i in range(len(gamma_estimates)): _, _, old_membership, old_estimate = gamma_estimates[i] for update_iteration in range(1000): if old_estimate is None: