Path: blob/main/latex-templates/templates/other/network_analysis.tex
51 views
unlisted
\documentclass[a4paper, 11pt]{article}1\usepackage[utf8]{inputenc}2\usepackage[T1]{fontenc}3\usepackage{amsmath, amssymb}4\usepackage{graphicx}5\usepackage{siunitx}6\usepackage{booktabs}7\usepackage{multirow}8\usepackage{float}9\usepackage[makestderr]{pythontex}1011\title{Network Science: Graph Metrics and Community Detection}12\author{Computational Science Templates}13\date{\today}1415\begin{document}16\maketitle1718\begin{abstract}19Network science provides tools for analyzing complex systems through graph theory. This document demonstrates computational methods for calculating centrality measures, detecting community structure, analyzing random graph models, and characterizing small-world and scale-free properties. Using PythonTeX, we implement algorithms for betweenness centrality, modularity optimization, Erdos-Renyi and Barabasi-Albert models, and compute characteristic path lengths and clustering coefficients.20\end{abstract}2122\section{Introduction}23Network analysis characterizes complex systems through graph metrics. Networks appear throughout science: social networks, biological networks (protein interactions, neural connections), infrastructure networks (power grids, transportation), and information networks (World Wide Web, citation graphs). This analysis computes centrality measures, identifies community structure, and compares random network models.2425\section{Mathematical Framework}2627\subsection{Centrality Measures}28Degree centrality measures local connectivity:29\begin{equation}30C_D(v) = \frac{\deg(v)}{n-1}31\end{equation}3233Betweenness centrality quantifies importance in shortest paths:34\begin{equation}35C_B(v) = \sum_{s \neq v \neq t} \frac{\sigma_{st}(v)}{\sigma_{st}}36\end{equation}37where $\sigma_{st}$ is the number of shortest paths from $s$ to $t$, and $\sigma_{st}(v)$ passes through $v$.3839Closeness centrality measures average distance to all nodes:40\begin{equation}41C_C(v) = \frac{n-1}{\sum_{u \neq v} d(v, u)}42\end{equation}4344Eigenvector centrality assigns importance based on neighbor importance:45\begin{equation}46x_v = \frac{1}{\lambda} \sum_{u \in N(v)} x_u47\end{equation}4849\subsection{Clustering and Transitivity}50Local clustering coefficient:51\begin{equation}52C_i = \frac{2|e_{jk} : v_j, v_k \in N_i, e_{jk} \in E|}{k_i(k_i-1)}53\end{equation}5455Global clustering coefficient (transitivity):56\begin{equation}57C = \frac{3 \times \text{number of triangles}}{\text{number of connected triples}}58\end{equation}5960\subsection{Modularity}61Modularity measures community quality:62\begin{equation}63Q = \frac{1}{2m} \sum_{ij} \left[ A_{ij} - \frac{k_i k_j}{2m} \right] \delta(c_i, c_j)64\end{equation}65where $A_{ij}$ is the adjacency matrix, $k_i$ is degree, and $\delta(c_i, c_j) = 1$ if nodes $i$ and $j$ are in the same community.6667\section{Environment Setup}6869\begin{pycode}70import numpy as np71import matplotlib.pyplot as plt72from scipy import sparse73from collections import deque74import warnings75warnings.filterwarnings('ignore')7677plt.rc('text', usetex=True)78plt.rc('font', family='serif')7980np.random.seed(42)8182def save_plot(filename, caption=""):83plt.savefig(filename, bbox_inches='tight', dpi=150)84print(r'\begin{figure}[htbp]')85print(r'\centering')86print(r'\includegraphics[width=0.95\textwidth]{' + filename + '}')87if caption:88print(r'\caption{' + caption + '}')89print(r'\end{figure}')90plt.close()91\end{pycode}9293\section{Network Generation and Basic Metrics}9495\begin{pycode}96# Generate random network (Erdos-Renyi model)97n = 5098p = 0.199adj = np.zeros((n, n))100for i in range(n):101for j in range(i+1, n):102if np.random.rand() < p:103adj[i, j] = adj[j, i] = 1104105# Compute degrees106degrees = np.sum(adj, axis=1).astype(int)107108# Degree centrality109degree_centrality = degrees / (n - 1)110111# Compute shortest paths using BFS112def bfs_shortest_paths(adj, source):113n = adj.shape[0]114dist = np.full(n, np.inf)115dist[source] = 0116queue = deque([source])117while queue:118u = queue.popleft()119for v in range(n):120if adj[u, v] == 1 and dist[v] == np.inf:121dist[v] = dist[u] + 1122queue.append(v)123return dist124125# Compute all-pairs shortest paths126all_distances = np.zeros((n, n))127for i in range(n):128all_distances[i] = bfs_shortest_paths(adj, i)129130# Closeness centrality (only for reachable nodes)131closeness_centrality = np.zeros(n)132for i in range(n):133reachable = all_distances[i] < np.inf134n_reachable = np.sum(reachable) - 1135if n_reachable > 0:136closeness_centrality[i] = n_reachable / np.sum(all_distances[i][reachable])137138# Local clustering coefficient139clustering = np.zeros(n)140for i in range(n):141neighbors = np.where(adj[i] == 1)[0]142k = len(neighbors)143if k >= 2:144edges = 0145for j in range(len(neighbors)):146for l in range(j+1, len(neighbors)):147if adj[neighbors[j], neighbors[l]] == 1:148edges += 1149clustering[i] = 2 * edges / (k * (k - 1))150151# Network statistics152n_edges = int(np.sum(adj) / 2)153avg_degree = np.mean(degrees)154avg_clustering = np.mean(clustering)155density = 2 * n_edges / (n * (n - 1))156157# Characteristic path length (average shortest path)158finite_distances = all_distances[all_distances < np.inf]159finite_distances = finite_distances[finite_distances > 0]160char_path_length = np.mean(finite_distances) if len(finite_distances) > 0 else np.inf161162# Count triangles163triangles = 0164for i in range(n):165neighbors = np.where(adj[i] == 1)[0]166for j in range(len(neighbors)):167for k in range(j+1, len(neighbors)):168if adj[neighbors[j], neighbors[k]] == 1:169triangles += 1170triangles = triangles // 3 # Each triangle counted 3 times171172# Global clustering coefficient173connected_triples = sum(k*(k-1)//2 for k in degrees)174global_clustering = 3 * triangles / connected_triples if connected_triples > 0 else 0175\end{pycode}176177\section{Betweenness Centrality}178179\begin{pycode}180# Compute betweenness centrality using Brandes algorithm181def compute_betweenness(adj):182n = adj.shape[0]183betweenness = np.zeros(n)184185for s in range(n):186# Single-source shortest paths187S = [] # Stack of vertices in order of non-increasing distance188P = [[] for _ in range(n)] # Predecessors189sigma = np.zeros(n) # Number of shortest paths190sigma[s] = 1191d = np.full(n, -1) # Distance from source192d[s] = 0193194Q = deque([s])195while Q:196v = Q.popleft()197S.append(v)198for w in range(n):199if adj[v, w] == 1:200if d[w] < 0:201Q.append(w)202d[w] = d[v] + 1203if d[w] == d[v] + 1:204sigma[w] += sigma[v]205P[w].append(v)206207# Accumulation208delta = np.zeros(n)209while S:210w = S.pop()211for v in P[w]:212delta[v] += (sigma[v] / sigma[w]) * (1 + delta[w])213if w != s:214betweenness[w] += delta[w]215216# Normalize217betweenness = betweenness / ((n-1) * (n-2))218return betweenness219220betweenness_centrality = compute_betweenness(adj)221222# Visualization223fig, axes = plt.subplots(2, 2, figsize=(12, 10))224225# Network visualization with degree centrality226theta = np.linspace(0, 2*np.pi, n, endpoint=False)227pos = np.column_stack([np.cos(theta), np.sin(theta)])228229for i in range(n):230for j in range(i+1, n):231if adj[i, j] == 1:232axes[0, 0].plot([pos[i, 0], pos[j, 0]], [pos[i, 1], pos[j, 1]],233'gray', linewidth=0.5, alpha=0.4)234235sizes = 50 + 400 * degree_centrality236sc = axes[0, 0].scatter(pos[:, 0], pos[:, 1], s=sizes, c=degree_centrality,237cmap='viridis', edgecolors='black', linewidth=0.5)238plt.colorbar(sc, ax=axes[0, 0], label='Degree Centrality')239axes[0, 0].set_title('Network (node size = degree)')240axes[0, 0].set_aspect('equal')241axes[0, 0].axis('off')242243# Degree distribution244unique_degrees, degree_counts = np.unique(degrees, return_counts=True)245axes[0, 1].bar(unique_degrees, degree_counts, alpha=0.7, edgecolor='black', color='steelblue')246axes[0, 1].set_xlabel('Degree $k$')247axes[0, 1].set_ylabel('Frequency')248axes[0, 1].set_title('Degree Distribution')249axes[0, 1].grid(True, alpha=0.3)250251# Expected Poisson distribution for ER graph252k_range = np.arange(0, max(degrees)+5)253from scipy.stats import poisson254expected_freq = n * poisson.pmf(k_range, avg_degree)255axes[0, 1].plot(k_range, expected_freq, 'r--', linewidth=2, label='Poisson')256axes[0, 1].legend()257258# Centrality comparison259axes[1, 0].scatter(degree_centrality, betweenness_centrality, alpha=0.6)260axes[1, 0].set_xlabel('Degree Centrality')261axes[1, 0].set_ylabel('Betweenness Centrality')262axes[1, 0].set_title('Degree vs Betweenness Centrality')263axes[1, 0].grid(True, alpha=0.3)264265# Correlation coefficient266corr = np.corrcoef(degree_centrality, betweenness_centrality)[0, 1]267axes[1, 0].text(0.05, 0.95, f'$r = {corr:.3f}$', transform=axes[1, 0].transAxes,268fontsize=10, verticalalignment='top')269270# Clustering vs degree271axes[1, 1].scatter(degrees, clustering, alpha=0.6, color='green')272axes[1, 1].set_xlabel('Degree $k$')273axes[1, 1].set_ylabel('Local Clustering $C_i$')274axes[1, 1].set_title('Degree vs Clustering Coefficient')275axes[1, 1].grid(True, alpha=0.3)276277# Expected clustering for ER graph278axes[1, 1].axhline(y=p, color='r', linestyle='--', label=f'Expected (p={p})')279axes[1, 1].legend()280281plt.tight_layout()282save_plot('network_basic_metrics.pdf', 'Basic network metrics for Erdos-Renyi random graph')283\end{pycode}284285\section{Eigenvector Centrality and PageRank}286287\begin{pycode}288# Eigenvector centrality using power iteration289def eigenvector_centrality(adj, max_iter=100, tol=1e-6):290n = adj.shape[0]291x = np.ones(n) / np.sqrt(n)292293for _ in range(max_iter):294x_new = adj @ x295norm = np.linalg.norm(x_new)296if norm > 0:297x_new = x_new / norm298if np.linalg.norm(x_new - x) < tol:299break300x = x_new301302return x_new303304# PageRank (random walk with damping)305def pagerank(adj, damping=0.85, max_iter=100, tol=1e-6):306n = adj.shape[0]307out_degree = np.sum(adj, axis=1)308309# Create transition matrix310M = np.zeros((n, n))311for i in range(n):312if out_degree[i] > 0:313M[:, i] = adj[:, i] / out_degree[i]314else:315M[:, i] = 1.0 / n # Dangling nodes316317# Power iteration318pr = np.ones(n) / n319for _ in range(max_iter):320pr_new = damping * M @ pr + (1 - damping) / n321if np.linalg.norm(pr_new - pr) < tol:322break323pr = pr_new324325return pr_new326327eig_centrality = eigenvector_centrality(adj)328pr = pagerank(adj)329330# Katz centrality331def katz_centrality(adj, alpha=0.1, beta=1.0):332n = adj.shape[0]333I = np.eye(n)334ones = np.ones(n)335try:336centrality = np.linalg.solve(I - alpha * adj.T, beta * ones)337except:338centrality = np.zeros(n)339return centrality / np.max(centrality) if np.max(centrality) > 0 else centrality340341katz_cent = katz_centrality(adj, alpha=0.05)342343# Visualization344fig, axes = plt.subplots(2, 2, figsize=(12, 10))345346# Compare centrality measures347centrality_measures = {348'Degree': degree_centrality,349'Betweenness': betweenness_centrality,350'Closeness': closeness_centrality,351'Eigenvector': eig_centrality352}353354# Heatmap of centrality correlations355names = list(centrality_measures.keys())356measures = np.array([centrality_measures[name] for name in names])357corr_matrix = np.corrcoef(measures)358359im = axes[0, 0].imshow(corr_matrix, cmap='RdYlBu_r', vmin=-1, vmax=1)360axes[0, 0].set_xticks(range(len(names)))361axes[0, 0].set_yticks(range(len(names)))362axes[0, 0].set_xticklabels(names, rotation=45)363axes[0, 0].set_yticklabels(names)364for i in range(len(names)):365for j in range(len(names)):366axes[0, 0].text(j, i, f'{corr_matrix[i, j]:.2f}', ha='center', va='center')367plt.colorbar(im, ax=axes[0, 0], label='Correlation')368axes[0, 0].set_title('Centrality Measure Correlations')369370# Network with eigenvector centrality371for i in range(n):372for j in range(i+1, n):373if adj[i, j] == 1:374axes[0, 1].plot([pos[i, 0], pos[j, 0]], [pos[i, 1], pos[j, 1]],375'gray', linewidth=0.5, alpha=0.4)376377sizes = 50 + 400 * (eig_centrality / max(eig_centrality))378sc = axes[0, 1].scatter(pos[:, 0], pos[:, 1], s=sizes, c=eig_centrality,379cmap='plasma', edgecolors='black', linewidth=0.5)380plt.colorbar(sc, ax=axes[0, 1], label='Eigenvector Centrality')381axes[0, 1].set_title('Network (size = eigenvector centrality)')382axes[0, 1].set_aspect('equal')383axes[0, 1].axis('off')384385# PageRank distribution386sorted_pr = np.sort(pr)[::-1]387axes[1, 0].bar(range(n), sorted_pr, alpha=0.7, color='coral')388axes[1, 0].set_xlabel('Node Rank')389axes[1, 0].set_ylabel('PageRank')390axes[1, 0].set_title('PageRank Distribution (sorted)')391axes[1, 0].grid(True, alpha=0.3)392393# Eigenvector vs PageRank394axes[1, 1].scatter(eig_centrality, pr, alpha=0.6, color='purple')395axes[1, 1].set_xlabel('Eigenvector Centrality')396axes[1, 1].set_ylabel('PageRank')397axes[1, 1].set_title('Eigenvector Centrality vs PageRank')398axes[1, 1].grid(True, alpha=0.3)399corr_pr = np.corrcoef(eig_centrality, pr)[0, 1]400axes[1, 1].text(0.05, 0.95, f'$r = {corr_pr:.3f}$', transform=axes[1, 1].transAxes,401fontsize=10, verticalalignment='top')402403plt.tight_layout()404save_plot('network_centrality_advanced.pdf', 'Advanced centrality measures')405406# Top nodes by centrality407top_k = 5408top_degree = np.argsort(degree_centrality)[-top_k:][::-1]409top_between = np.argsort(betweenness_centrality)[-top_k:][::-1]410top_eigen = np.argsort(eig_centrality)[-top_k:][::-1]411\end{pycode}412413\section{Community Detection}414415\begin{pycode}416# Simple community detection using label propagation417def label_propagation(adj, max_iter=100):418n = adj.shape[0]419labels = np.arange(n)420421for _ in range(max_iter):422order = np.random.permutation(n)423changed = False424for node in order:425neighbors = np.where(adj[node] == 1)[0]426if len(neighbors) > 0:427neighbor_labels = labels[neighbors]428unique, counts = np.unique(neighbor_labels, return_counts=True)429max_count = max(counts)430candidates = unique[counts == max_count]431new_label = np.random.choice(candidates)432if new_label != labels[node]:433labels[node] = new_label434changed = True435if not changed:436break437438# Relabel communities to 0, 1, 2, ...439unique_labels = np.unique(labels)440label_map = {old: new for new, old in enumerate(unique_labels)}441return np.array([label_map[l] for l in labels])442443# Compute modularity444def compute_modularity(adj, communities):445n = adj.shape[0]446m = np.sum(adj) / 2447degrees = np.sum(adj, axis=1)448449Q = 0450for i in range(n):451for j in range(n):452if communities[i] == communities[j]:453Q += adj[i, j] - degrees[i] * degrees[j] / (2 * m)454455return Q / (2 * m)456457# Spectral clustering for community detection458def spectral_clustering(adj, k=3):459n = adj.shape[0]460degrees = np.sum(adj, axis=1)461D = np.diag(degrees)462L = D - adj # Laplacian463464# Normalized Laplacian465D_inv_sqrt = np.diag(1.0 / np.sqrt(degrees + 1e-10))466L_norm = D_inv_sqrt @ L @ D_inv_sqrt467468# Eigendecomposition469eigenvalues, eigenvectors = np.linalg.eigh(L_norm)470471# Use first k eigenvectors (after 0)472features = eigenvectors[:, 1:k+1]473474# K-means clustering on eigenvector features475from scipy.cluster.vq import kmeans2476_, labels = kmeans2(features, k, minit='++')477478return labels, eigenvalues479480# Detect communities481communities_lp = label_propagation(adj)482n_communities_lp = len(np.unique(communities_lp))483484# Spectral clustering485k_communities = 4486communities_spec, laplacian_eigenvalues = spectral_clustering(adj, k=k_communities)487488# Compute modularities489mod_lp = compute_modularity(adj, communities_lp)490mod_spec = compute_modularity(adj, communities_spec)491492# Visualization493fig, axes = plt.subplots(2, 2, figsize=(12, 10))494495# Label propagation communities496colors_lp = plt.cm.Set1(communities_lp / (n_communities_lp - 1 + 0.01))497for i in range(n):498for j in range(i+1, n):499if adj[i, j] == 1:500axes[0, 0].plot([pos[i, 0], pos[j, 0]], [pos[i, 1], pos[j, 1]],501'gray', linewidth=0.5, alpha=0.3)502503axes[0, 0].scatter(pos[:, 0], pos[:, 1], s=100, c=communities_lp,504cmap='Set1', edgecolors='black', linewidth=0.5)505axes[0, 0].set_title(f'Label Propagation ({n_communities_lp} communities, Q={mod_lp:.3f})')506axes[0, 0].set_aspect('equal')507axes[0, 0].axis('off')508509# Spectral clustering communities510for i in range(n):511for j in range(i+1, n):512if adj[i, j] == 1:513axes[0, 1].plot([pos[i, 0], pos[j, 0]], [pos[i, 1], pos[j, 1]],514'gray', linewidth=0.5, alpha=0.3)515516axes[0, 1].scatter(pos[:, 0], pos[:, 1], s=100, c=communities_spec,517cmap='Set2', edgecolors='black', linewidth=0.5)518axes[0, 1].set_title(f'Spectral Clustering ({k_communities} communities, Q={mod_spec:.3f})')519axes[0, 1].set_aspect('equal')520axes[0, 1].axis('off')521522# Laplacian eigenvalues (spectral gap)523axes[1, 0].plot(laplacian_eigenvalues[:15], 'o-', markersize=8)524axes[1, 0].set_xlabel('Index')525axes[1, 0].set_ylabel('Eigenvalue')526axes[1, 0].set_title('Laplacian Eigenvalues (spectral gap)')527axes[1, 0].grid(True, alpha=0.3)528529# Community size distribution530for i, (comm, name) in enumerate([(communities_lp, 'Label Prop'),531(communities_spec, 'Spectral')]):532unique, counts = np.unique(comm, return_counts=True)533x_pos = np.arange(len(counts)) + i*0.4534axes[1, 1].bar(x_pos, counts, width=0.35, alpha=0.7, label=name)535536axes[1, 1].set_xlabel('Community')537axes[1, 1].set_ylabel('Size')538axes[1, 1].set_title('Community Size Distribution')539axes[1, 1].legend()540axes[1, 1].grid(True, alpha=0.3)541542plt.tight_layout()543save_plot('network_communities.pdf', 'Community detection using label propagation and spectral clustering')544\end{pycode}545546\section{Random Graph Models}547548\begin{pycode}549# Compare Erdos-Renyi and Barabasi-Albert models550n_compare = 100551552# Erdos-Renyi G(n, p)553p_er = 0.05554adj_er = np.zeros((n_compare, n_compare))555for i in range(n_compare):556for j in range(i+1, n_compare):557if np.random.rand() < p_er:558adj_er[i, j] = adj_er[j, i] = 1559560# Barabasi-Albert preferential attachment561def barabasi_albert(n, m):562adj = np.zeros((n, n))563# Start with m+1 fully connected nodes564for i in range(m+1):565for j in range(i+1, m+1):566adj[i, j] = adj[j, i] = 1567568degrees = np.sum(adj, axis=1)569570# Add remaining nodes571for new_node in range(m+1, n):572# Preferential attachment573probs = degrees[:new_node] / np.sum(degrees[:new_node])574targets = np.random.choice(new_node, size=m, replace=False, p=probs)575for target in targets:576adj[new_node, target] = adj[target, new_node] = 1577degrees = np.sum(adj, axis=1)578579return adj580581m_ba = 2582adj_ba = barabasi_albert(n_compare, m_ba)583584# Compute metrics for both585degrees_er = np.sum(adj_er, axis=1).astype(int)586degrees_ba = np.sum(adj_ba, axis=1).astype(int)587588avg_degree_er = np.mean(degrees_er)589avg_degree_ba = np.mean(degrees_ba)590591# Clustering coefficients592def compute_clustering(adj):593n = adj.shape[0]594degrees = np.sum(adj, axis=1).astype(int)595clustering = np.zeros(n)596for i in range(n):597neighbors = np.where(adj[i] == 1)[0]598k = len(neighbors)599if k >= 2:600edges = 0601for j in range(len(neighbors)):602for l in range(j+1, len(neighbors)):603if adj[neighbors[j], neighbors[l]] == 1:604edges += 1605clustering[i] = 2 * edges / (k * (k - 1))606return clustering607608clustering_er = compute_clustering(adj_er)609clustering_ba = compute_clustering(adj_ba)610611avg_clust_er = np.mean(clustering_er)612avg_clust_ba = np.mean(clustering_ba)613614# Visualization615fig, axes = plt.subplots(2, 2, figsize=(12, 10))616617# Degree distributions comparison618max_degree = max(max(degrees_er), max(degrees_ba))619bins = np.arange(0, max_degree + 2) - 0.5620621axes[0, 0].hist(degrees_er, bins=bins, alpha=0.5, label=f'ER (avg={avg_degree_er:.1f})',622edgecolor='blue', color='blue')623axes[0, 0].hist(degrees_ba, bins=bins, alpha=0.5, label=f'BA (avg={avg_degree_ba:.1f})',624edgecolor='red', color='red')625axes[0, 0].set_xlabel('Degree $k$')626axes[0, 0].set_ylabel('Frequency')627axes[0, 0].set_title('Degree Distribution Comparison')628axes[0, 0].legend()629axes[0, 0].grid(True, alpha=0.3)630631# Log-log degree distribution for BA (scale-free test)632unique_ba, counts_ba = np.unique(degrees_ba, return_counts=True)633axes[0, 1].loglog(unique_ba[unique_ba > 0], counts_ba[unique_ba > 0], 'ro', markersize=8, label='BA Data')634635# Power law fit636log_k = np.log(unique_ba[unique_ba > 1])637log_p = np.log(counts_ba[unique_ba > 1] / n_compare)638if len(log_k) > 2:639coeffs = np.polyfit(log_k, log_p, 1)640gamma = -coeffs[0]641k_fit = np.logspace(np.log10(2), np.log10(max(degrees_ba)), 50)642p_fit = np.exp(coeffs[1]) * n_compare * k_fit**(-gamma)643axes[0, 1].loglog(k_fit, p_fit, 'b--', linewidth=2, label=f'Power law $\\gamma={gamma:.2f}$')644645axes[0, 1].set_xlabel('Degree $k$')646axes[0, 1].set_ylabel('Frequency')647axes[0, 1].set_title('BA Degree Distribution (log-log)')648axes[0, 1].legend()649axes[0, 1].grid(True, alpha=0.3)650651# Clustering coefficient vs degree652axes[1, 0].scatter(degrees_er, clustering_er, alpha=0.4, label='Erdos-Renyi', s=30)653axes[1, 0].scatter(degrees_ba, clustering_ba, alpha=0.4, label='Barabasi-Albert', s=30)654axes[1, 0].set_xlabel('Degree $k$')655axes[1, 0].set_ylabel('Local Clustering $C_i$')656axes[1, 0].set_title('Clustering vs Degree')657axes[1, 0].legend()658axes[1, 0].grid(True, alpha=0.3)659660# Network metrics comparison661metrics_names = ['Nodes', 'Edges', 'Avg Degree', 'Avg Clustering']662metrics_er = [n_compare, int(np.sum(adj_er)/2), avg_degree_er, avg_clust_er * 10]663metrics_ba = [n_compare, int(np.sum(adj_ba)/2), avg_degree_ba, avg_clust_ba * 10]664665x = np.arange(len(metrics_names))666width = 0.35667axes[1, 1].bar(x - width/2, metrics_er, width, label='Erdos-Renyi', alpha=0.7)668axes[1, 1].bar(x + width/2, metrics_ba, width, label='Barabasi-Albert', alpha=0.7)669axes[1, 1].set_ylabel('Value (clustering x10)')670axes[1, 1].set_xticks(x)671axes[1, 1].set_xticklabels(metrics_names)672axes[1, 1].set_title('Network Metrics Comparison')673axes[1, 1].legend()674axes[1, 1].grid(True, alpha=0.3)675676plt.tight_layout()677save_plot('network_random_models.pdf', 'Comparison of Erdos-Renyi and Barabasi-Albert random graph models')678679# Store for summary680n_edges_er = int(np.sum(adj_er) / 2)681n_edges_ba = int(np.sum(adj_ba) / 2)682\end{pycode}683684\section{Small-World Networks}685686\begin{pycode}687# Watts-Strogatz small-world model688def watts_strogatz(n, k, p):689"""Generate Watts-Strogatz small-world network."""690adj = np.zeros((n, n))691692# Create ring lattice693for i in range(n):694for j in range(1, k//2 + 1):695adj[i, (i+j) % n] = adj[(i+j) % n, i] = 1696697# Rewire edges with probability p698for i in range(n):699for j in range(1, k//2 + 1):700if np.random.rand() < p:701neighbor = (i + j) % n702if adj[i, neighbor] == 1:703adj[i, neighbor] = adj[neighbor, i] = 0704# Choose new target705possible = [x for x in range(n) if x != i and adj[i, x] == 0]706if possible:707new_target = np.random.choice(possible)708adj[i, new_target] = adj[new_target, i] = 1709710return adj711712# Characteristic path length713def compute_path_length(adj):714n = adj.shape[0]715total_dist = 0716count = 0717for i in range(n):718dist = bfs_shortest_paths(adj, i)719for j in range(i+1, n):720if dist[j] < np.inf:721total_dist += dist[j]722count += 1723return total_dist / count if count > 0 else np.inf724725# Generate networks for different rewiring probabilities726n_ws = 50727k_ws = 6728p_values = [0, 0.01, 0.05, 0.1, 0.2, 0.5, 1.0]729730path_lengths = []731clusterings = []732733for p_ws in p_values:734adj_ws = watts_strogatz(n_ws, k_ws, p_ws)735pl = compute_path_length(adj_ws)736clust = np.mean(compute_clustering(adj_ws))737path_lengths.append(pl)738clusterings.append(clust)739740# Normalize by p=0 values741L0 = path_lengths[0]742C0 = clusterings[0]743L_norm = [L/L0 for L in path_lengths]744C_norm = [C/C0 for C in clusterings]745746# Generate examples for visualization747adj_lattice = watts_strogatz(30, 4, 0)748adj_small_world = watts_strogatz(30, 4, 0.1)749adj_random = watts_strogatz(30, 4, 1.0)750751# Visualization752fig, axes = plt.subplots(2, 2, figsize=(12, 10))753754# Small-world transition755axes[0, 0].semilogx(p_values[1:], L_norm[1:], 'o-', markersize=8, label='$L(p)/L(0)$')756axes[0, 0].semilogx(p_values[1:], C_norm[1:], 's-', markersize=8, label='$C(p)/C(0)$')757axes[0, 0].set_xlabel('Rewiring probability $p$')758axes[0, 0].set_ylabel('Normalized value')759axes[0, 0].set_title('Small-World Transition')760axes[0, 0].legend()761axes[0, 0].grid(True, alpha=0.3)762axes[0, 0].axvspan(0.01, 0.1, alpha=0.2, color='yellow', label='Small-world regime')763764# Plot the three network types765for idx, (adj_plot, title) in enumerate([(adj_lattice, 'Ring Lattice (p=0)'),766(adj_small_world, 'Small-World (p=0.1)'),767(adj_random, 'Random (p=1)')]):768if idx == 0:769ax = axes[0, 1]770elif idx == 1:771ax = axes[1, 0]772else:773ax = axes[1, 1]774775n_plot = adj_plot.shape[0]776theta = np.linspace(0, 2*np.pi, n_plot, endpoint=False)777pos_plot = np.column_stack([np.cos(theta), np.sin(theta)])778779for i in range(n_plot):780for j in range(i+1, n_plot):781if adj_plot[i, j] == 1:782ax.plot([pos_plot[i, 0], pos_plot[j, 0]],783[pos_plot[i, 1], pos_plot[j, 1]],784'gray', linewidth=0.5, alpha=0.5)785786ax.scatter(pos_plot[:, 0], pos_plot[:, 1], s=50, c='steelblue',787edgecolors='black', linewidth=0.5)788ax.set_title(title)789ax.set_aspect('equal')790ax.axis('off')791792plt.tight_layout()793save_plot('network_small_world.pdf', 'Watts-Strogatz small-world network model')794795# Small-world index796sw_index = (C_norm[3] / C_norm[-1]) / (L_norm[3] / L_norm[-1]) if L_norm[-1] > 0 else 0797\end{pycode}798799\section{Network Motifs and Structural Patterns}800801\begin{pycode}802# Analyze network motifs803# Count common subgraph patterns804805def count_triangles(adj):806n = adj.shape[0]807count = 0808for i in range(n):809for j in range(i+1, n):810for k in range(j+1, n):811if adj[i,j] == 1 and adj[j,k] == 1 and adj[i,k] == 1:812count += 1813return count814815def count_stars(adj, k=3):816"""Count k-stars (one hub connected to k nodes)."""817n = adj.shape[0]818degrees = np.sum(adj, axis=1).astype(int)819from math import comb820count = sum(comb(d, k) for d in degrees if d >= k)821return count822823def count_paths(adj, length=2):824"""Count paths of given length."""825if length == 2:826return count_stars(adj, 2) # 2-star = path of length 2827else:828return 0829830# Analyze original ER network831tri_count = count_triangles(adj)832star3_count = count_stars(adj, 3)833star2_count = count_stars(adj, 2)834835# Assortativity (degree correlation)836def compute_assortativity(adj):837n = adj.shape[0]838degrees = np.sum(adj, axis=1)839edges = []840for i in range(n):841for j in range(i+1, n):842if adj[i, j] == 1:843edges.append((degrees[i], degrees[j]))844845if len(edges) == 0:846return 0847848edges = np.array(edges)849x, y = edges[:, 0], edges[:, 1]850851# Pearson correlation852r = np.corrcoef(np.concatenate([x, y]), np.concatenate([y, x]))[0, 1]853return r854855assortativity = compute_assortativity(adj)856assortativity_ba = compute_assortativity(adj_ba)857858# Visualization859fig, axes = plt.subplots(2, 2, figsize=(12, 10))860861# Motif counts862motifs = ['Triangles', '3-Stars', '2-Paths']863counts = [tri_count, star3_count, star2_count]864colors = ['coral', 'steelblue', 'seagreen']865axes[0, 0].bar(motifs, counts, color=colors, alpha=0.7, edgecolor='black')866axes[0, 0].set_ylabel('Count')867axes[0, 0].set_title('Network Motif Counts')868axes[0, 0].grid(True, alpha=0.3)869870# Degree-degree correlation plot (assortativity visualization)871edges = []872for i in range(n):873for j in range(i+1, n):874if adj[i, j] == 1:875edges.append((degrees[i], degrees[j]))876877if edges:878edges = np.array(edges)879axes[0, 1].scatter(edges[:, 0], edges[:, 1], alpha=0.3, s=50)880axes[0, 1].set_xlabel('Degree of node $i$')881axes[0, 1].set_ylabel('Degree of node $j$')882axes[0, 1].set_title(f'Degree Correlation (assortativity r={assortativity:.3f})')883axes[0, 1].grid(True, alpha=0.3)884885# Rich-club coefficient886def rich_club_coefficient(adj):887degrees = np.sum(adj, axis=1).astype(int)888unique_degrees = np.unique(degrees)889phi = {}890891for k in unique_degrees:892# Nodes with degree > k893rich_nodes = np.where(degrees > k)[0]894n_rich = len(rich_nodes)895if n_rich < 2:896continue897898# Edges among rich nodes899edges_rich = 0900for i in range(len(rich_nodes)):901for j in range(i+1, len(rich_nodes)):902if adj[rich_nodes[i], rich_nodes[j]] == 1:903edges_rich += 1904905max_edges = n_rich * (n_rich - 1) / 2906if max_edges > 0:907phi[k] = edges_rich / max_edges908909return phi910911phi = rich_club_coefficient(adj)912k_vals = sorted(phi.keys())913phi_vals = [phi[k] for k in k_vals]914915axes[1, 0].plot(k_vals, phi_vals, 'o-', markersize=6)916axes[1, 0].set_xlabel('Degree threshold $k$')917axes[1, 0].set_ylabel('Rich-club coefficient $\\phi(k)$')918axes[1, 0].set_title('Rich-Club Organization')919axes[1, 0].grid(True, alpha=0.3)920921# Compare ER and BA assortativity922network_types = ['Erdos-Renyi', 'Barabasi-Albert']923assort_values = [assortativity, assortativity_ba]924colors = ['steelblue', 'coral']925axes[1, 1].bar(network_types, assort_values, color=colors, alpha=0.7, edgecolor='black')926axes[1, 1].axhline(y=0, color='black', linestyle='--', linewidth=1)927axes[1, 1].set_ylabel('Assortativity Coefficient')928axes[1, 1].set_title('Network Assortativity Comparison')929axes[1, 1].grid(True, alpha=0.3)930931plt.tight_layout()932save_plot('network_motifs.pdf', 'Network motifs, assortativity, and rich-club organization')933\end{pycode}934935\section{Results Summary}936937\begin{pycode}938# Create comprehensive results table939print(r'\begin{table}[H]')940print(r'\centering')941print(r'\caption{Network Analysis Results}')942print(r'\begin{tabular}{lcc}')943print(r'\toprule')944print(r'Metric & ER Network & BA Network \\')945print(r'\midrule')946print(f'Nodes & {n} & {n_compare} \\\\')947print(f'Edges & {n_edges} & {n_edges_ba} \\\\')948print(f'Average Degree & {avg_degree:.2f} & {avg_degree_ba:.2f} \\\\')949print(f'Average Clustering & {avg_clustering:.4f} & {avg_clust_ba:.4f} \\\\')950print(f'Characteristic Path Length & {char_path_length:.2f} & -- \\\\')951print(f'Global Clustering & {global_clustering:.4f} & -- \\\\')952print(f'Assortativity & {assortativity:.3f} & {assortativity_ba:.3f} \\\\')953print(r'\bottomrule')954print(r'\end{tabular}')955print(r'\end{table}')956957# Centrality summary table958print(r'\begin{table}[H]')959print(r'\centering')960print(r'\caption{Top Nodes by Centrality Measures}')961print(r'\begin{tabular}{lccc}')962print(r'\toprule')963print(r'Rank & Degree & Betweenness & Eigenvector \\')964print(r'\midrule')965for i in range(5):966print(f'{i+1} & Node {top_degree[i]} & Node {top_between[i]} & Node {top_eigen[i]} \\\\')967print(r'\bottomrule')968print(r'\end{tabular}')969print(r'\end{table}')970971# Community detection results972print(r'\begin{table}[H]')973print(r'\centering')974print(r'\caption{Community Detection Results}')975print(r'\begin{tabular}{lcc}')976print(r'\toprule')977print(r'Method & Communities & Modularity \\')978print(r'\midrule')979print(f'Label Propagation & {n_communities_lp} & {mod_lp:.4f} \\\\')980print(f'Spectral Clustering & {k_communities} & {mod_spec:.4f} \\\\')981print(r'\bottomrule')982print(r'\end{tabular}')983print(r'\end{table}')984985# Motif counts986print(r'\begin{table}[H]')987print(r'\centering')988print(r'\caption{Network Motif Counts}')989print(r'\begin{tabular}{lc}')990print(r'\toprule')991print(r'Motif Type & Count \\')992print(r'\midrule')993print(f'Triangles & {tri_count} \\\\')994print(f'3-Stars & {star3_count} \\\\')995print(f'2-Paths & {star2_count} \\\\')996print(r'\bottomrule')997print(r'\end{tabular}')998print(r'\end{table}')999\end{pycode}10001001\section{Statistical Summary}10021003Network analysis results:1004\begin{itemize}1005\item Nodes: \py{f"{n}"}, Edges: \py{f"{n_edges}"}1006\item Average degree: \py{f"{avg_degree:.2f}"}1007\item Average clustering: \py{f"{avg_clustering:.4f}"}1008\item Global clustering: \py{f"{global_clustering:.4f}"}1009\item Characteristic path length: \py{f"{char_path_length:.2f}"}1010\item Network density: \py{f"{density:.4f}"}1011\item Number of triangles: \py{f"{triangles}"}1012\item Assortativity coefficient: \py{f"{assortativity:.3f}"}1013\item Communities detected (LP): \py{f"{n_communities_lp}"}1014\item Modularity (LP): \py{f"{mod_lp:.4f}"}1015\item Modularity (Spectral): \py{f"{mod_spec:.4f}"}1016\end{itemize}10171018\section{Conclusion}10191020Network metrics reveal structural properties of complex systems. This analysis demonstrated:10211022\begin{enumerate}1023\item \textbf{Centrality measures}: Degree, betweenness, closeness, and eigenvector centrality capture different aspects of node importance. High correlation between measures indicates consistent network structure.10241025\item \textbf{Community detection}: Label propagation and spectral clustering identify community structure with modularity quantifying partition quality.10261027\item \textbf{Random graph models}: Erdos-Renyi produces homogeneous degree distributions, while Barabasi-Albert generates scale-free networks with power-law degree distributions through preferential attachment.10281029\item \textbf{Small-world networks}: Watts-Strogatz model shows that small rewiring probability creates networks with high clustering and short path lengths, characteristic of real-world networks.10301031\item \textbf{Network motifs}: Triangle counting and rich-club analysis reveal hierarchical organization and hub structure.1032\end{enumerate}10331034These tools apply to social networks, biological systems (protein interactions, neural networks), infrastructure (power grids, transportation), and information networks (WWW, citations). Understanding network topology enables prediction of system behavior, identification of critical nodes, and optimization of network performance.10351036\end{document}103710381039