-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathQFERNv4.py
More file actions
145 lines (116 loc) · 5.48 KB
/
QFERNv4.py
File metadata and controls
145 lines (116 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# -*- coding: utf-8 -*-
"""
Created on Sat Jan 18 19:59:18 2025
@author: ektop
"""
# -*- coding: utf-8 -*-
"""
Created on Wed Sep 25 18:26:44 2024
@author: ektop
"""
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
import random
from itertools import combinations
# Define the number of nodes in each component
NUM_NODES_A = 10 # Number of nodes in group A
NUM_NODES_B = 10 # Number of nodes in group B
# Create a structured directed acyclic graph (DAG)
dag = nx.DiGraph()
def create_random_bipartite_structure(num_nodes_a, num_nodes_b):
"""Create a random bipartite-like structure."""
edges = [(i, num_nodes_a + j) for i in range(num_nodes_a) for j in range(num_nodes_b)]
random.shuffle(edges)
selected_edges = random.sample(edges, k=random.randint(1, len(edges)))
return selected_edges
# Add random edges to the graph
dag.add_edges_from(create_random_bipartite_structure(NUM_NODES_A, NUM_NODES_B))
dag.add_edges_from(create_random_bipartite_structure(NUM_NODES_A, NUM_NODES_B))
# Generate connections between the nodes
component_connections = []
connections_from_first_to_second = random.sample(
[(i, NUM_NODES_A + j) for i in range(NUM_NODES_A) for j in range(NUM_NODES_B)],
k=random.randint(1, 2))
component_connections.extend(connections_from_first_to_second)
# Create additional connections for the second section of nodes
second_section_nodes = list(range(NUM_NODES_A, NUM_NODES_A + NUM_NODES_B))
new_node = NUM_NODES_A + NUM_NODES_B
for node in second_section_nodes:
additional_connections = random.sample([new_node] + second_section_nodes,
k=random.randint(1, len(second_section_nodes)))
component_connections += [(node, conn) for conn in additional_connections if conn != node]
dag.add_edges_from(component_connections)
def compute_cheeger_constant(G):
"""Calculate the Cheeger constant of the graph."""
n = len(G.nodes)
cuts = []
for cut_nodes in combinations(range(n), n // 2):
cut_size = len([edge for edge in G.edges if (edge[0] in cut_nodes) ^ (edge[1] in cut_nodes)])
cuts.append(cut_size)
return min(cuts) / min(sum(1 for node in cut_nodes), sum(1 for node in G.nodes if node not in cut_nodes))
# Calculate the initial Cheeger constant
initial_cheeger_constant = compute_cheeger_constant(dag)
print(f"Initial Cheeger Constant: {initial_cheeger_constant}")
def laplacian_matrix(A):
"""Compute the normalized Laplacian matrix."""
D = np.diag(np.sum(A, axis=1))
return D - A
def effective_resistance(u, v, eigenvalues, fiedler_vector):
"""Calculate the effective resistance between two nodes."""
if len(eigenvalues) == 0 or len(fiedler_vector) == 0:
return np.inf
sum_term = 0
for i in range(len(eigenvalues)):
if eigenvalues[i] > 0:
sum_term += (fiedler_vector[u] * fiedler_vector[v]) / eigenvalues[i]
return sum_term
def optimize_graph(g, iterations=100):
"""Optimize the graph to minimize effective resistance and maximize Cheeger constant."""
previous_cheeger_constant = compute_cheeger_constant(g)
for _ in range(iterations):
# Step 1: Remove a random edge
if len(g.edges) > 0:
edge = random.choice(list(g.edges))
g.remove_edge(*edge)
# Step 2: Add a new random edge
possible_edges = [(i, j) for i in range(len(g.nodes)) for j in range(len(g.nodes))
if i != j and not g.has_edge(i, j)]
if possible_edges:
new_edge = random.choice(possible_edges)
g.add_edge(*new_edge)
# Step 3: Calculate the new Cheeger constant
new_cheeger_constant = compute_cheeger_constant(g)
print(f"Current Cheeger Constant: {new_cheeger_constant}")
# Check for convergence
if new_cheeger_constant == previous_cheeger_constant:
break
previous_cheeger_constant = new_cheeger_constant
# Run the optimization process
optimize_graph(dag)
# Final graph attributes and effective resistance calculation
num_nodes = dag.number_of_nodes()
adjacency = nx.to_numpy_array(dag)
normalized_laplacian = laplacian_matrix(adjacency)
# Eigenvalue decomposition for effective resistance calculation
eigenvalues, eigenvectors = np.linalg.eig(normalized_laplacian)
order = np.argsort(eigenvalues)
fiedler_vector = np.real(eigenvectors[:, order[1]])
# Compute effective resistances for all node pairs
effective_resistances = np.zeros((num_nodes, num_nodes))
for u, v in combinations(range(num_nodes), 2):
effective_resistances[u, v] = effective_resistance(u, v, eigenvalues[order[1:]], fiedler_vector)
effective_resistances[v, u] = effective_resistances[u, v]
# Visualization of effective resistances
plt.figure(figsize=(10, 8))
pos = nx.spring_layout(dag) # Positioning for the nodes
node_color = [np.mean(effective_resistances[node]) for node in range(num_nodes)]
# Draw the graph with effective resistances represented as colors
nodes = nx.draw(dag, pos, node_color=node_color, with_labels=True, cmap=plt.cm.viridis, node_size=500)
# Create the ScalarMappable for color representation
sm = plt.cm.ScalarMappable(cmap=plt.cm.viridis)
sm.set_array(node_color)
# Add colorbar to the plot
plt.colorbar(sm, label='Effective Resistance Level')
plt.title('Graph Visualization of Effective Resistances')
plt.show()