### Install PANINIpy from source Source: https://paninipy.readthedocs.io/en/latest/_sources/Papers/installation.rst.txt Clone the repository and install PANINIpy and its dependencies using the requirements.txt file for manual dependency management. ```console git clone https://github.com/HKU-Complex-Networks-Lab/PANINIpy.git cd PANINIpy pip install -r requirements.txt ``` -------------------------------- ### Install PANINIpy using pip Source: https://paninipy.readthedocs.io/en/latest/Papers/installation.html Use this command to install PANINIpy and its dependencies directly from PyPI. ```bash pip install paninipy ``` -------------------------------- ### Manual Installation with requirements.txt Source: https://paninipy.readthedocs.io/en/latest/Papers/installation.html Clone the repository and install dependencies manually using the provided requirements.txt file. This method is for users who prefer to manage dependencies explicitly. ```bash git clone https://github.com/HKU-Complex-Networks-Lab/PANINIpy.git cd PANINIpy pip install -r requirements.txt ``` -------------------------------- ### Install Dependencies and Pytest for Testing Source: https://paninipy.readthedocs.io/en/latest/Papers/installation.html Install all necessary packages, including pytest, for running the PANINIpy test suite. This command should be run after cloning the repository. ```bash pip install -r requirements.txt pip install paninipy pip install pytest ``` -------------------------------- ### Install dependencies and PANINIpy for testing Source: https://paninipy.readthedocs.io/en/latest/_sources/Papers/installation.rst.txt Install all necessary packages, including pytest, required for running the PANINIpy test suite. ```console pip install -r requirements.txt pip install paninipy pip install pytest ``` -------------------------------- ### Minimal Working Example for PANINIpy Source: https://paninipy.readthedocs.io/en/latest/Papers/installation.html This Python code demonstrates how to use the MDL_backboning module to compute global and local backbones. Ensure your Python version is between 3.9 and 3.12. ```python from paninipy.mdl_backboning import MDL_backboning # Define a weighted edge list elist = [ (0, 1, 12), (0, 3, 20), (0, 4, 8), (1, 2, 1), (1, 4, 3), (2, 0, 1), (2, 1, 3), (3, 2, 3), (3, 4, 1), (4, 3, 1) ] # Compute backbones using out-edges backbone_global, backbone_local, compression_global, compression_local = MDL_backboning( elist, directed=True, out_edges=True ) # Display results print('Global Backbone:', backbone_global) print('Inverse Compression Ratio (Global):', compression_global) print('Local Backbone:', backbone_local) print('Inverse Compression Ratio (Local):', compression_local) ``` -------------------------------- ### Initialize Strengths and Clusters Source: https://paninipy.readthedocs.io/en/latest/_sources/Code/partial_rankings.rst.txt Initializes dictionaries for clusters, their sizes, and strengths. Each node starts as its own cluster with a strength of 1. ```python R = N # Initialise number of unique ranks to the total number of nodes # sigmas = np.ones(N) # Initialise unique rankings clusters, n_c, sigmas = ( {}, {}, {}, ) # dictionaries for clusters, their sizes, and the strengths for k in set(e_out.keys()).union(set(e_in.keys())): # initialise sigmas sigmas[k] = 1 n_c[k] = 1 clusters[k] = set([k]) ``` -------------------------------- ### Import Libraries for MDL Backboning Source: https://paninipy.readthedocs.io/en/latest/Papers/mdl_backboning.html Import necessary libraries for network analysis and MDL backboning. Ensure networkx and matplotlib are installed. ```python import networkx as nx import matplotlib.pyplot as plt from paninipy.mdl_backboning import MDL_backboning ``` -------------------------------- ### Compute Network Backbones with MDL_backboning Source: https://paninipy.readthedocs.io/en/latest/_sources/Papers/mdl_backboning.rst.txt Compute the global and local MDL-optimal backbones and their compression ratios from the edge list. This example uses directed edges and tracks out-edges. ```python # Compute backbones using out-edges backbone_global, backbone_local, compression_global, compression_local = MDL_backboning( elist, directed=True, out_edges=True ) ``` -------------------------------- ### DefaultDict.__getitem__ Source: https://paninipy.readthedocs.io/en/latest/Papers/community_regionalization.html Get an item from the DefaultDict. ```APIDOC ## DefaultDict.__getitem__(key) ### Description Get an item from the DefaultDict. ### Parameters - **key**: The key to get the item for. ### Returns - **value** (any): The value associated with the key. ``` -------------------------------- ### Print Hypergraph Binning Results Source: https://paninipy.readthedocs.io/en/latest/Papers/hypergraph_binning.html Prints the compression ratio, MDL-optimal event partition, number of time steps, and runtime for both the exact and greedy algorithms. ```python print('Exact Dynamic Program Results: ') print(' compression ratio =', round(results_exact[0], 4)) print(' MDL-optimal event partition =', results_exact[1]) print(' number of time steps =', results_exact[2]) print(' runtime =', round(runtime_exact, 4)) print('Greedy Algorithm Results: ') print(' compression ratio =', round(results_greedy[0], 4)) print(' MDL-optimal event partition =', results_greedy[1]) print(' number of time steps =', results_greedy[2]) print(' runtime =', round(runtime_greedy, 4)) ``` -------------------------------- ### DefaultDict.__init__ Source: https://paninipy.readthedocs.io/en/latest/Papers/community_regionalization.html Initialize the DefaultDict class. ```APIDOC ## DefaultDict.__init__(default_factory, **kwargs) ### Description Initialize the DefaultDict class. ### Parameters - **default_factory**: The default factory function. - **kwargs**: Additional keyword arguments. ``` -------------------------------- ### Run PANINIpy Tests Manually Source: https://paninipy.readthedocs.io/en/latest/Papers/installation.html Execute all tests within the PANINIpy project using the pytest command. This helps verify the installation and code integrity. ```bash pytest Tests/ ``` -------------------------------- ### Community Detection Algorithm Source: https://paninipy.readthedocs.io/en/latest/Code/community_regionalization.html This code implements a hierarchical clustering algorithm to find communities. It iteratively merges the pair of clusters that results in the greatest decrease in description length. The process stops when no further merges improve the description length or when the description length starts to increase. ```python DL = total_dl() DLs, partitions = [DL], [clusters.copy()] for B in range(N, 1, -1): best_pair = (np.inf, np.inf) best_ddl = np.inf for i in ddl_c: for j in ddl_c[i]: if i < j: ddl = delta_dl(i, j) if ddl < best_ddl: best_ddl = ddl best_pair = (i, j) r, s = best_pair if r == s == np.inf: return DLs, partitions DL = merge_updates(r, s, DL) if DL > DLs[-1]: return DLs, partitions DLs.append(DL) partitions.append(list(clusters.copy().values())) return DLs, partitions ``` -------------------------------- ### MDL_populations: Initialize Clusters Source: https://paninipy.readthedocs.io/en/latest/_sources/Code/population_clustering.rst.txt Initializes K0 random clusters and calculates their modes and the total description length for the initial configuration. This is a prerequisite for running the clustering algorithm. ```python def initialize_clusters(self): """ initialize K0 random clusters and find their modes as well as the total description length of this configuration """ for i,s in enumerate(np.random.permutation(range(self.S))): k = str(i % self.K0) if k in self.C: self.C[k].add(s) else: self.C[k] = set() self.C[k].add(s) for k in range(self.K0): k = str(k) self.E[k] = self.generate_Ek(self.C[k]) self.A[k] = self.update_mode(self.E[k],len(self.C[k])) self.L = sum([self.Lk(self.A[k],self.E[k],len(self.C[k])) for k in self.C]) ``` -------------------------------- ### MDL_populations.__init__ Source: https://paninipy.readthedocs.io/en/latest/_sources/Papers/population_clustering.rst.txt Initializes the MDL_populations class with network data and parameters for clustering. ```APIDOC ## MDL_populations.__init__ (constructor) ### Description Initialize the MDL_populations class. ### Parameters - **edgesets** (List[Set]) - Required - List of sets. The s-th set contains all the edges (i, j) in the s-th network in the sample (do not include the other direction (j, i) if the network is undirected). - **N** (int) - Required - Number of nodes in each network. - **K0** (int) - Optional - Initial number of clusters (for discontiguous clustering, usually K0 = 1 works well; for contiguous clustering, it does not matter). Defaults to 1. - **n_fails** (int) - Optional - Number of failed reassign/merge/split/merge-split moves before terminating the algorithm. Defaults to 100. - **bipartite** (Optional) - 'None' for unipartite network populations, array [# of nodes of type 1, # of nodes of type 2] otherwise. - **directed** (bool) - Optional - Boolean indicating whether edgesets contain directed edges. Defaults to False. - **max_runs** (float) - Optional - Maximum number of allowed moves, regardless of the number of fails. Defaults to np.inf. ``` -------------------------------- ### MDL_populations.initialize_clusters Source: https://paninipy.readthedocs.io/en/latest/Papers/population_clustering.html Initializes K0 random clusters and calculates their modes and total description length. ```APIDOC ## MDL_populations.initialize_clusters() ### Description Initialize K0 random clusters and find their modes as well as the total description length of this configuration. ``` -------------------------------- ### Run MDL Network Population Clustering Source: https://paninipy.readthedocs.io/en/latest/Papers/population_clustering.html Initializes and runs the MDL network population clustering algorithm on the generated network data. Sets parameters like number of failures and maximum runs. ```python mdl_pop = MDL_populations(edgesets=nets, N=node_num, K0=1, n_fails=100, directed=False, max_runs=np.inf) mdl_pop.initialize_clusters() clusters, modes, L = mdl_pop.run_sims() ``` -------------------------------- ### Clone PANINIpy Repository Source: https://paninipy.readthedocs.io/en/latest/_sources/Papers/contributing.rst.txt Clone your forked repository to your local machine to begin making changes. ```console git clone https://github.com/[username]/PANINIpy.git ``` -------------------------------- ### Initialize Clusters and Sigmas Source: https://paninipy.readthedocs.io/en/latest/Code/partial_rankings.html Initializes dictionaries for clusters, their sizes, and strengths. Sets initial sigma, n_c, and cluster values for each key. ```python R = N # Initialise number of unique ranks to the total number of nodes # sigmas = np.ones(N) # Initialise unique rankings clusters, n_c, sigmas = ( {}, {}, {}, ) # dictionaries for clusters, their sizes, and the strengths for k in set(e_out.keys()).union(set(e_in.keys())): # initialise sigmas sigmas[k] = 1 n_c[k] = 1 clusters[k] = set([k]) ``` -------------------------------- ### Clone Repository Source: https://paninipy.readthedocs.io/en/latest/Papers/contributing.html Clone your personal fork of the PANINIpy repository to your local machine to begin making changes. ```bash git clone https://github.com/[username]/PANINIpy.git ``` -------------------------------- ### MDL_populations.__init__ Source: https://paninipy.readthedocs.io/en/latest/Papers/population_clustering.html Initializes the MDL_populations class for population clustering. ```APIDOC ## MDL_populations.__init__(edgesets, N, K0=1, n_fails=100, bipartite=None, directed=False, max_runs=np.inf) ### Description Initialize the MDL_populations class. ### Parameters - **edgesets** (list of sets) - The s-th set contains all the edges (i, j) in the s-th network in the sample (do not include the other direction (j, i) if the network is undirected). - **N** (int) - Number of nodes in each network. - **K0** (int, optional) - Initial number of clusters. Defaults to 1. - **n_fails** (int, optional) - Number of failed reassign/merge/split/merge-split moves before terminating the algorithm. Defaults to 100. - **bipartite** (array or None, optional) - 'None' for unipartite network populations, array [# of nodes of type 1, # of nodes of type 2] otherwise. Defaults to None. - **directed** (bool, optional) - Boolean indicating whether edgesets contain directed edges. Defaults to False. - **max_runs** (float, optional) - Maximum number of allowed moves, regardless of the number of fails. Defaults to np.inf. ``` -------------------------------- ### MDL_regionalization.__init__ Source: https://paninipy.readthedocs.io/en/latest/Papers/distributional_regionalization.html Initializes the MDL_regionalization class. ```APIDOC ## MDL_regionalization.__init__ ### Description Initialize the MDL_regionalization class. ### Parameters - **name** (str) - The name for the regionalization instance. ``` -------------------------------- ### Exact Dynamic Programming for MDL Configuration Source: https://paninipy.readthedocs.io/en/latest/Code/hypergraph_binning.html Implements the exact dynamic programming solution to identify the Minimum Description Length (MDL) configuration. It calculates the best MDL by iterating through possible segmentations. ```python if exact: """ exact dynamic programming solution to identify MDL configuration """ LMDL,LMDL_int = {0:0},{} for j in range(1,T+1): best_MDL = np.inf for i in range(1,j+1): L = LMDL[i-1] + DL(i,j) if L < best_MDL: best_MDL = L LMDL_int[j] = i LMDL[j] = best_MDL j = T i = np.inf MDL_boundaries = [] while i > 1: i = LMDL_int[j] MDL_boundaries.append((i,j)) j = i-1 MDL_boundaries = sorted(MDL_boundaries,key = lambda I:I[0]) best_MDL = LMDL[T] ``` -------------------------------- ### Import necessary libraries Source: https://paninipy.readthedocs.io/en/latest/Code/hub_identification.html Imports NumPy for numerical operations, loggamma from SciPy for logarithmic gamma function, and Counter from collections for counting degree occurrences. ```python import numpy as np from scipy.special import loggamma from collections import Counter ``` -------------------------------- ### Import necessary libraries Source: https://paninipy.readthedocs.io/en/latest/Code/hypergraph_binning.html Imports NumPy for numerical operations and `lgamma` from the `math` module for logarithmic gamma function calculations. ```python import numpy as np from math import lgamma ``` -------------------------------- ### Initialize Clusters for MDL Populations Source: https://paninipy.readthedocs.io/en/latest/Code/population_clustering.html Initializes K0 random clusters and calculates their modes and the total description length. Used within the MDL_populations class. ```python def initialize_clusters(self): """ initialize K0 random clusters and find their modes as well as the total description length of this configuration """ for i,s in enumerate(np.random.permutation(range(self.S))): k = str(i % self.K0) if k in self.C: self.C[k].add(s) else: self.C[k] = set() self.C[k].add(s) for k in range(self.K0): k = str(k) self.E[k] = self.generate_Ek(self.C[k]) self.A[k] = self.update_mode(self.E[k],len(self.C[k])) self.L = sum([self.Lk(self.A[k],self.E[k],len(self.C[k])) for k in self.C]) ``` -------------------------------- ### Partial Rankings Algorithm Summary Output Source: https://paninipy.readthedocs.io/en/latest/Code/partial_rankings.html This code block prints a summary of the partial rankings algorithm's execution after convergence. It displays key metrics such as the number of iterations, final partial rankings, initial and minimum divergence likelihood, and convergence ratio. It also handles returning either a trace list or a summary dictionary. ```python # Print summary print(f"Converged in {iter_count} iterations", file=stderr) print(f"Partial Rankings: {min_R}", file=stderr) print(f"Initial DL: {initial_dl}", file=stderr) print(f"Min DL: {min_dl}", file=stderr) print(f"BT DL: {bt_dl}", file=stderr) print(f"LPOR: {bt_dl - min_dl}", file=stderr) print(f"CR: {min_dl / initial_dl}", file=stderr) if full_trace: return trace_list return { "N": N, "M": M, "": M / N, "R": min_R, "BT_R": BT_R, "DL": min_dl, "BT_DL": bt_dl, "LPOR": bt_dl - min_dl, "CR": min_dl / initial_dl, "Strengths": min_sigmas, "Clusters": min_clusters, } ``` -------------------------------- ### Run MDL Regionalization Algorithm Source: https://paninipy.readthedocs.io/en/latest/Papers/distributional_regionalization.html Initializes the MDL_regionalization algorithm and executes the regionalization process using the prepared adjacency list, distance data, and population data. Prints the inverse compression ratio and cluster labels. ```python mdl_instance = MDL_regionalization("example") inverse_compression_ratio, cluster_labels, dists = mdl_instance.MDL_regionalization(adjlist, dists, pops) print(inverse_compression_ratio) print(cluster_labels) ``` -------------------------------- ### MDL Populations Class Initialization Source: https://paninipy.readthedocs.io/en/latest/Code/population_clustering.html Initializes the MDL_populations class with network data, number of nodes, and clustering parameters. Handles bipartite and directed graph configurations. ```python class MDL_populations(): """ MDL population clustering class Inputs: edgesets: list of sets. the s-th set contains all the edges (i,j) in the s-th network in the sample (do not include the other direction (j,i) if network is undirected). the order of edgesets within D only matters for contiguous clustering, where we want the edgesets to be in order of the samples in time N: number of nodes in each network K0: initial number of clusters (for discontiguous clustering, usually K0 = 1 works well; for contiguous clustering it doesn't matter) n_fails: number of failed reassign/merge/split/merge-split moves before terminating algorithm bipartite: 'None' for unipartite network populations, array [# of nodes of type 1, # of nodes of type 2] otherwise directed: boolean indicating whether edgesets contain directed edges max_runs: maximum number of allowed moves, regardless of number of fails Outputs of 'run_sims' (unconstrained description length optimization) and 'dynamic_contiguous' (restriction to contiguous clusters): C: dictionary with items (cluster label):(set of indices corresponding to networks in cluster) A: dictionary with items (cluster label):(set of edges corresponding to mode of cluster) L: inverse compression ratio (description length after clustering)/(description length of naive transmission) """ def __init__(self, edgesets, N, K0 = 1, n_fails = 100, bipartite = None, directed = False, max_runs = np.inf): """ initialize class attributes """ self.edgesets = edgesets self.K0 = K0 self.n_fails = n_fails self.S = len(self.edgesets) self.N = N self.max_runs = max_runs if bipartite is not None: self.NC2 = bipartite[0]*bipartite[1] #bipartite networks only differentiated from unipartite ones through this term if directed: self.NC2 = self.N*(self.N-1) #directed networks only differentiated from undirected ones through this term else: self.NC2 = self.N*(self.N-1)/2 self.C,self.E,self.A = {},{},{} self.attmerges,self.attsplits,self.attmergesplits = set(),set(),set() ``` -------------------------------- ### MDL Backboning Algorithm Implementation Source: https://paninipy.readthedocs.io/en/latest/Code/mdl_backboning.html Implements the Minimum Description Length (MDL) backboning algorithm for extracting network backbones. Supports directed/undirected graphs, in/out-edge focus, and adjustable compression ratio types. ```python def MDL_backboning(elist,directed=True,out_edges=True,allow_empty=True,CR_type='Max'): """ input: elist consisting of directed tuples [(i,j,w_ij)] for edges i --> j with weight w_ij 'directed' arg tells us whether input edge list is directed or undirected 'out_edges' arg tells us whether to track out-edges or in-edges attached to each node in the local pruning method 'allow_empty' arg tells us whether or not we allow empty backbones (empty and full are equivalent by symmetry) 'CR_type' arg allows adjusting the type of compression ratio. 'Relative': divides MDL by DL of corresponding (global or local) naive model 'Max': divides MDL by max(DL of naive global model,DL of naive local model) output: edge lists for MDL backbones for global and local methods, corresponding inverse compression ratios """ def fglobal(W,E,Wb,Eb): """ global description length objective """ initial_cost = np.log(E+1) + np.log(W-E+1) return initial_cost + logchoose(E,Eb) + logchoose(Wb-1,Eb-1) + logchoose(W-Wb-1,E-Eb-1) def flocal(si,ki,sbi,kbi): """ local description length objective at node-level """ initial_cost = np.log(ki+1) + np.log(si-ki+1) return initial_cost + logchoose(ki,kbi) + logchoose(sbi-1,kbi-1) + logchoose(si-sbi-1,ki-kbi-1) def naiveglobal(W,E): """ naive global description length objective """ return fglobal(W,E,0,0) def naivelocal(si,ki): """ naive local description length objective at node-level """ return flocal(si,ki,0,0) #add two directed edges for each undirected edge if input is undirected. don't duplicate self-edges. if not(directed): self_edge_indices = set([i for i,e in enumerate(elist) if e[0] == e[1]]) elist = list(elist) + [(e[1],e[0],e[2]) for i,e in enumerate(elist) if not(i in self_edge_indices)] #reverse edge order if we want the local pruning method to focus on in-degrees and in-strengths #does not make any difference for undirected networks if not(out_edges): elist = [(e[1],e[0],e[2]) for e in elist] #computational complexity bottleneck: sort edge list by decreasing weight in O(ElogE) time elist = sorted(elist,key = lambda e:e[-1],reverse=True) #initialize variables for input network W = sum([e[-1] for e in elist]) E = len(elist) adj_edges,adj_weights = {},{} for e in elist: i,j,w_ij = e if not(i in adj_edges): adj_edges[i] = [] if not(i in adj_weights): adj_weights[i] = [] adj_edges[i].append(j) adj_weights[i].append(w_ij) nodes = set([e[0] for e in elist]+[e[1] for e in elist]) N = len(nodes) #greedily add edges to global backbone and track total description length Lglobal0 = naiveglobal(W,E) Lglobal = fglobal(W,E,0,0) min_DL_global = Lglobal backbone_Eb = 0 Wb,Eb = 0,0 for e in elist: i,j,w_ij = e Eb += 1 Wb += w_ij Lglobal += fglobal(W,E,Wb,Eb) - fglobal(W,E,Wb-w_ij,Eb-1) if Lglobal < min_DL_global: min_DL_global = Lglobal backbone_Eb = Eb if (backbone_Eb == 0) and not(allow_empty): backbone_Eb = E #by symmetry, DL is equivalent, so can choose to keep all edges #greedily add edges to local backbone and track description length at each node Llocal0,min_DL_local = logchoose(N+W-E-1,W-E),logchoose(N+W-E-1,W-E) backbone_degrees = {} for i in adj_edges: si,ki,sbi,kbi = sum(adj_weights[i]),len(adj_edges[i]),0,0 Llocali = flocal(si,ki,0,0) Llocal0 += naivelocal(si,ki) ``` -------------------------------- ### Initialize and Process Match List for Visualization Source: https://paninipy.readthedocs.io/en/latest/Papers/partial_rankings.html Initializes the RankingPlotGenerator, loads and preprocesses the match list, and creates a graph representation. This step is crucial before visualizing rankings. ```python # Initialize and process the match list generator = RankingPlotGenerator("wolf.txt") generator.load_and_preprocess() generator.create_graph() # Retrieve and process the graph for both BT and Partial Rankings g_pr = generator.graph.copy() g_bt = generator.graph.copy() # Get sigmas pr_sigmas = generator.sigmas bt_sigmas = generator.bt_sigmas # Dctionary mapping node labels to pr sigmas label_to_pr_sigma = {} for node in g_pr.nodes(): for key, cluster in generator.clusters.items(): if node in cluster: label_to_pr_sigma[node] = pr_sigmas[key] # Initialise node coordinates for i, node in enumerate(g_pr.nodes()): x = -i y_pr = np.log(label_to_pr_sigma[node]) y_bt = np.log(bt_sigmas[node]) g_pr.nodes[node]['pos'] = (x, y_pr) g_bt.nodes[node]['pos'] = (x, y_bt) # Adjust positions of nodes in the same cluster for key, cluster in generator.clusters.items(): if len(cluster) == 1: # Set x-coordinate to zero g_pr.nodes[list(cluster)[0]]['pos'] = (0, g_pr.nodes[list(cluster)[0]]['pos'][1]) g_bt.nodes[list(cluster)[0]]['pos'] = (0, g_bt.nodes[list(cluster)[0]]['pos'][1]) elif len(cluster) == 2: # Evenly space in [-a, a] interval a = 2 x_coords = np.linspace(-a, a, len(cluster)) for i, node in enumerate(cluster): g_pr.nodes[node]['pos'] = (x_coords[i], g_pr.nodes[node]['pos'][1]) g_bt.nodes[node]['pos'] = (x_coords[i], g_bt.nodes[node]['pos'][1]) else: # Evenly space in [-a, a] interval a = 6 x_coords = np.linspace(-a, a, len(cluster)) for i, node in enumerate(cluster): g_pr.nodes[node]['pos'] = (x_coords[i], g_pr.nodes[node]['pos'][1]) g_bt.nodes[node]['pos'] = (x_coords[i], g_bt.nodes[node]['pos'][1]) ``` -------------------------------- ### Initialize and Plot Partial Rankings Source: https://paninipy.readthedocs.io/en/latest/_sources/Papers/partial_rankings.rst.txt Initializes a RankingPlotGenerator, loads and preprocesses data, creates a graph, and then adjusts node positions and colors for both BT and Partial Rankings before plotting. This is useful for visualizing and comparing ranking algorithms. ```python # Initialize and process the match list generator = RankingPlotGenerator("wolf.txt") generator.load_and_preprocess() generator.create_graph() # Retrieve and process the graph for both BT and Partial Rankings g_pr = generator.graph.copy() g_bt = generator.graph.copy() # Get sigmas pr_sigmas = generator.sigmas bt_sigmas = generator.bt_sigmas # Dctionary mapping node labels to pr sigmas label_to_pr_sigma = {} for node in g_pr.nodes(): for key, cluster in generator.clusters.items(): if node in cluster: label_to_pr_sigma[node] = pr_sigmas[key] # Initialise node coordinates for i, node in enumerate(g_pr.nodes()): x = -i y_pr = np.log(label_to_pr_sigma[node]) y_bt = np.log(bt_sigmas[node]) g_pr.nodes[node]['pos'] = (x, y_pr) g_bt.nodes[node]['pos'] = (x, y_bt) # Adjust positions of nodes in the same cluster for key, cluster in generator.clusters.items(): if len(cluster) == 1: # Set x-coordinate to zero g_pr.nodes[list(cluster)[0]]['pos'] = (0, g_pr.nodes[list(cluster)[0]]['pos'][1]) g_bt.nodes[list(cluster)[0]]['pos'] = (0, g_bt.nodes[list(cluster)[0]]['pos'][1]) elif len(cluster) == 2: # Evenly space in [-a, a] interval a = 2 x_coords = np.linspace(-a, a, len(cluster)) for i, node in enumerate(cluster): g_pr.nodes[node]['pos'] = (x_coords[i], g_pr.nodes[node]['pos'][1]) g_bt.nodes[node]['pos'] = (x_coords[i], g_bt.nodes[node]['pos'][1]) else: # Evenly space in [-a, a] interval a = 6 x_coords = np.linspace(-a, a, len(cluster)) for i, node in enumerate(cluster): g_pr.nodes[node]['pos'] = (x_coords[i], g_pr.nodes[node]['pos'][1]) g_bt.nodes[node]['pos'] = (x_coords[i], g_bt.nodes[node]['pos'][1]) # Define node colors vertex_fill_color = {} for cluster_label, cluster_nodes in generator.clusters.items(): for node in cluster_nodes: if node in generator.graph.nodes: if cluster_label in generator.sigmas: vertex_fill_color[f"pr_{node}"] = np.log(generator.sigmas[cluster_label]) if cluster_label in generator.bt_sigmas: vertex_fill_color[f"bt_{node}"] = np.log(generator.bt_sigmas[cluster_label]) for node, strength in generator.sigmas.items(): if node in generator.graph.nodes: vertex_fill_color[f"pr_{node}"] = np.log(strength) for node, strength in generator.bt_sigmas.items(): if node in generator.graph.nodes(): vertex_fill_color[f"bt_{node}"] = np.log(strength) vmin = min(vertex_fill_color.values()) vmax = max(vertex_fill_color.values()) norm = mcolors.Normalize(vmin=vmin, vmax=vmax) colormap = plt.cm.Reds node_colors = {node: colormap(norm(color)) for node, color in vertex_fill_color.items()} # Plot the graph fig, ax = plt.subplots(1, 2, figsize=(10, 6)) plt.subplots_adjust(wspace=0.5) pos_pr = nx.get_node_attributes(g_pr, 'pos') pos_bt = nx.get_node_attributes(g_bt, 'pos') node_colors_pr = [node_colors[f"pr_{node}"] for node in g_pr.nodes() if f"pr_{node}" in node_colors] node_colors_bt = [node_colors[f"bt_{node}"] for node in g_bt.nodes() if f"bt_{node}" in node_colors] nx.draw( g_bt, pos_bt, with_labels=True, node_size=500, font_size=12, edgecolors='black', node_color=node_colors_pr, ax=ax[0] ) nx.draw( g_pr, pos_pr, with_labels=True, node_size=500, font_size=12, edgecolors='black', node_color=node_colors_bt, ax=ax[1] ) # Add colorbar cbar_ax = fig.add_axes([0.475, 0.15, 0.02, 0.7]) # Position: [left, bottom, width, height] norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax) cbar = fig.colorbar(plt.cm.ScalarMappable(norm=norm, cmap=plt.cm.Reds), cax=cbar_ax) cbar.set_label('Log-Strength', fontsize=12) cbar.ax.tick_params(labelsize=12) # Add titles and labels ax[0].set_title("BT Rankings", fontsize=14) ax[0].text(0.5, -0.05, r"$R = N = 15$", ha="center", va="center", fontsize=14, transform=ax[0].transAxes) ax[0].text(0.5, -0.18, "(a)", ha="center", va="center", fontsize=14, transform=ax[0].transAxes) ax[1].set_title("Partial Rankings", fontsize=14) ax[1].text(0.5, -0.05, r"$N = 15,~R = 10$", ha="center", va="center", fontsize=14, transform=ax[1].transAxes) ax[1].text(0.5, -0.18, "(b)", ha="center", va="center", fontsize=14, transform=ax[1].transAxes) plt.savefig("bt_pr_network_example.png", dpi=400) ``` -------------------------------- ### Run Exact Dynamic Programming Algorithm Source: https://paninipy.readthedocs.io/en/latest/Papers/hypergraph_binning.html Executes the exact dynamic programming algorithm for hypergraph binning and measures its runtime. ```python start_exact = time.time() results_exact = MDL_hypergraph_binning(X, dt, exact=True) runtime_exact = time.time() - start_exact ``` -------------------------------- ### Compute MDL Backbones and Compression Ratios Source: https://paninipy.readthedocs.io/en/latest/Papers/mdl_backboning.html Compute the global and local MDL-optimal network backbones and their inverse compression ratios. Adjust parameters like `directed`, `out_edges`, and `allow_empty` as needed. ```python # Compute backbones and compression ratios backbone_global, backbone_local, compression_global, compression_local = MDL_backboning( elist, directed=True, out_edges=True, allow_empty=True ) ``` -------------------------------- ### MDL_populations Class Initialization Source: https://paninipy.readthedocs.io/en/latest/_sources/Code/population_clustering.rst.txt Initializes the MDL_populations class for network clustering. Sets up parameters like number of nodes, initial clusters, and network properties (bipartite, directed). ```python class MDL_populations(): """ MDL population clustering class Inputs: edgesets: list of sets. the s-th set contains all the edges (i,j) in the s-th network in the sample (do not include the other direction (j,i) if network is undirected). the order of edgesets within D only matters for contiguous clustering, where we want the edgesets to be in order of the samples in time N: number of nodes in each network K0: initial number of clusters (for discontiguous clustering, usually K0 = 1 works well; for contiguous clustering it doesn't matter) n_fails: number of failed reassign/merge/split/merge-split moves before terminating algorithm bipartite: 'None' for unipartite network populations, array [# of nodes of type 1, # of nodes of type 2] otherwise directed: boolean indicating whether edgesets contain directed edges max_runs: maximum number of allowed moves, regardless of number of fails Outputs of 'run_sims' (unconstrained description length optimization) and 'dynamic_contiguous' (restriction to contiguous clusters): C: dictionary with items (cluster label):(set of indices corresponding to networks in cluster) A: dictionary with items (cluster label):(set of edges corresponding to mode of cluster) L: inverse compression ratio (description length after clustering)/(description length of naive transmission) """ def __init__(self, edgesets, N, K0 = 1, n_fails = 100, bipartite = None, directed = False, max_runs = np.inf): """ initialize class attributes """ self.edgesets = edgesets self.K0 = K0 self.n_fails = n_fails self.S = len(self.edgesets) self.N = N self.max_runs = max_runs if bipartite is not None: self.NC2 = bipartite[0]*bipartite[1] #bipartite networks only differentiated from unipartite ones through this term if directed: self.NC2 = self.N*(self.N-1) #directed networks only differentiated from undirected ones through this term else: self.NC2 = self.N*(self.N-1)/2 self.C,self.E,self.A = {},{},{} self.attmerges,self.attsplits,self.attmergesplits = set(),set(),set() ``` -------------------------------- ### Visualize Exact Dynamic Programming Results Source: https://paninipy.readthedocs.io/en/latest/Papers/hypergraph_binning.html Calls the visualization function to display the binning results obtained from the exact dynamic programming algorithm. ```python visualize_binning(X, results_exact, 'exact dynamic programming') ``` -------------------------------- ### Clone PANINIpy repository for testing Source: https://paninipy.readthedocs.io/en/latest/_sources/Papers/installation.rst.txt Clone the GitHub repository to access the test suite. This is necessary for running tests manually. ```console git clone https://github.com/HKU-Complex-Networks-Lab/PANINIpy.git cd PANINIpy ``` -------------------------------- ### Initialize and Iterate Community Merges Source: https://paninipy.readthedocs.io/en/latest/_sources/Code/community_regionalization.rst.txt Initializes the delta description length dictionary and then iterates to find and merge the best pair of clusters based on the minimum delta description length. It stores the description length and partition at each step. ```python ddl_c = ( {} ) # dictionary for past merges, indexed by only spatially adjacent neighbors for e in spatial_elist: i, j = e ddl_c[i][j] = delta_dl(i, j) ddl_c[j][i] = ddl_c[i][j] # Initialize the description length and the list of partitions DL = total_dl() DLs, partitions = [DL], [clusters.copy()] # Iterate over the number of clusters and find the best pair to merge for B in range(N, 1, -1): best_pair = (np.inf, np.inf) best_ddl = np.inf for i in ddl_c: for j in ddl_c[i]: if i < j: ddl = delta_dl(i, j) if ddl < best_ddl: best_ddl = ddl best_pair = (i, j) r, s = best_pair if r == s == np.inf: # If no more merges are possible return the partitions return DLs, partitions # Merge the best pair and update the description length DL = merge_updates(r, s, DL) # Uncomment to stop algorithm when the description length increases if DL > DLs[-1]: return DLs, partitions # Store the description length and the partition DLs.append(DL) partitions.append(list(clusters.copy().values())) return DLs, partitions ``` -------------------------------- ### Import Libraries for Network Analysis Source: https://paninipy.readthedocs.io/en/latest/Papers/hub_identification.html Imports matplotlib for plotting, Network_hubs from paninipy for hub identification, and networkx for graph manipulation. ```python import matplotlib.pyplot as plt from paninipy.hub_identification import Network_hubs import networkx as nx ``` -------------------------------- ### Store Initial Results for Full Trace Source: https://paninipy.readthedocs.io/en/latest/Code/partial_rankings.html If full tracing is enabled, this code block stores the initial state of the network and model parameters into a results dictionary, which is then appended to a trace list. ```python # If full trace, append resulst dictioanry to trace_list if full_trace: results_dict = { "N": N, # Number of nodes "M": M, # Number of edges "": M / N, # Average degree "R": R, # Number of unique ranks "BT_R": BT_R, # Number of unique ranks inferred by BT model "DL": dl, # Description length "BT_DL": bt_dl, # Description length of BT model "LPOR": bt_dl - dl, # Log posterior odds ratio "CR": 1, # Compression ratio "Strengths": sigmas, # Strengths of each cluster "Clusters": deepcopy(clusters), # Clusters } trace_list = [results_dict] ``` -------------------------------- ### Clone PANINIpy Repository for Testing Source: https://paninipy.readthedocs.io/en/latest/Papers/installation.html Clone the official PANINIpy GitHub repository to access the test suite. This is a prerequisite for running manual tests. ```bash git clone https://github.com/HKU-Complex-Networks-Lab/PANINIpy.git cd PANINIpy ``` -------------------------------- ### logOmega(rs, cs, swap=True) Source: https://paninipy.readthedocs.io/en/latest/Papers/hypergraph_binning.html Compute the logarithm of the number of non-negative integer matrices with specified row and column sums. ```APIDOC ## logOmega(rs, cs, swap=True) ### Description Compute the logarithm of the number of non-negative integer matrices with specified row and column sums. ### Parameters #### Path Parameters - **rs** (array) - Required - Array of row sums. - **cs** (array) - Required - Array of column sums. - **swap** (bool) - Optional - Boolean to swap the definition of rows and columns for a minor accuracy improvement. ### Returns - **float** - Logarithm of the number of non-negative integer matrices. ``` -------------------------------- ### Approximate Greedy Agglomerative Solution for MDL Configuration Source: https://paninipy.readthedocs.io/en/latest/_sources/Code/hypergraph_binning.rst.txt Implements an approximate greedy agglomerative solution to identify the Minimum Description Length (MDL) configuration. It iteratively merges segments based on the greatest reduction in description length. ```python else: """ approximate greedy agglomerative solution to identify MDL configuration """ MDL_boundaries = [(i,i) for i in range(1,T+1)] current_boundaries = [(i,i) for i in range(1,T+1)] for K in np.arange(T,1,-1).astype('int'): best_k,best_delta_dl = -100,np.inf for k in range(K-1): i1,i2 = current_boundaries[k] i3,i4 = current_boundaries[k+1] delta_dl = DL(i1,i4) - DL(i1,i2) - DL(i3,i4) if delta_dl < best_delta_dl: best_delta_dl = delta_dl best_k = k i1,i2 = current_boundaries[best_k] i3,i4 = current_boundaries[best_k+1] current_boundaries = current_boundaries[:best_k] + [(i1,i4)] + current_boundaries[best_k+2:] if best_delta_dl < 0: MDL_boundaries = [b for b in current_boundaries] best_MDL = sum([DL(b[0],b[1]) for b in MDL_boundaries]) MDLK1 = DL(1,T) if MDLK1 < best_MDL: best_MDL = MDLK1 MDL_boundaries = [(1,T)] ```