QWnet 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- QWnet/__init__.py +73 -0
- QWnet/graphs.py +330 -0
- QWnet/lattice.py +195 -0
- qwnet-2.0.0.dist-info/METADATA +38 -0
- qwnet-2.0.0.dist-info/RECORD +7 -0
- qwnet-2.0.0.dist-info/WHEEL +5 -0
- qwnet-2.0.0.dist-info/top_level.txt +1 -0
QWnet/__init__.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from .lattice import LatticeQuantumWalk
|
|
2
|
+
from .graphs import run_chawla_ranking, run_szegedy_service, run_community_detection, run_quantum_pagerank, run_hadamard
|
|
3
|
+
|
|
4
|
+
def run_simulation(model: str, **kwargs):
|
|
5
|
+
"""
|
|
6
|
+
Unified entry point for Quantum Walk simulations.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
model (str): The type of simulation to run.
|
|
10
|
+
Options: '1d', '2d', '3d' (Lattice Walks)
|
|
11
|
+
'ChawlaQW' (Chawla Directed Ranking)
|
|
12
|
+
'QPageRank' (Quantum PageRank)
|
|
13
|
+
'HadamardQW' (Hadamard Edge Importance)
|
|
14
|
+
'SzegedyQW' (Szegedy Walk)
|
|
15
|
+
'community' (Community Detection)
|
|
16
|
+
'directSumQW' (Direct Sum Quantum Walk)
|
|
17
|
+
**kwargs: Parameters specific to the chosen model.
|
|
18
|
+
"""
|
|
19
|
+
# 处理模型名称(不区分大小写)
|
|
20
|
+
model_upper = model
|
|
21
|
+
|
|
22
|
+
# Lattice Walks
|
|
23
|
+
if model_upper in ['1d', '2d', '3d']:
|
|
24
|
+
dim_map = {'1d': 1, '2d': 2, '3d': 3}
|
|
25
|
+
dimension = dim_map[model_upper]
|
|
26
|
+
network_size = kwargs.get('network_size', 100 if dimension==1 else 20 if dimension==2 else 25)
|
|
27
|
+
steps = kwargs.get('steps', 100)
|
|
28
|
+
initial_dist = kwargs.get('initial_dist', 'uniform')
|
|
29
|
+
compare_classical = kwargs.get('compare_classical', False)
|
|
30
|
+
if dimension in [1, 2] and 'compare_classical' not in kwargs: compare_classical = True
|
|
31
|
+
|
|
32
|
+
qw = LatticeQuantumWalk(dimension=dimension, network_size=network_size)
|
|
33
|
+
qw.run(steps=steps, initial_dist=initial_dist, compare_classical=compare_classical)
|
|
34
|
+
qw.visualize()
|
|
35
|
+
return qw.results
|
|
36
|
+
|
|
37
|
+
# Graph Walks
|
|
38
|
+
elif model_upper == 'ChawlaQW':
|
|
39
|
+
return run_chawla_ranking(file_path=kwargs.get('file_path'),
|
|
40
|
+
steps=kwargs.get('steps', 100),
|
|
41
|
+
coin_type=kwargs.get('coin_type', 'fourier'))
|
|
42
|
+
|
|
43
|
+
elif model_upper == 'SzegedyQW':
|
|
44
|
+
return run_szegedy_service(G=kwargs.get('G'),
|
|
45
|
+
steps=kwargs.get('steps', 10),
|
|
46
|
+
initial_type=kwargs.get('initial_type', 'uniform'),
|
|
47
|
+
initial_node=kwargs.get('initial_node'))
|
|
48
|
+
|
|
49
|
+
elif model_upper == 'community':
|
|
50
|
+
return run_community_detection(G=kwargs.get('G'),
|
|
51
|
+
initial_state=kwargs.get('initial_state', 'uniform'),
|
|
52
|
+
coin_type=kwargs.get('coin_type', 'fourier'))
|
|
53
|
+
|
|
54
|
+
elif model_upper == 'QPageRank':
|
|
55
|
+
return run_quantum_pagerank(file_path=kwargs.get('file_path'),
|
|
56
|
+
steps=kwargs.get('steps', 20),
|
|
57
|
+
alpha=kwargs.get('alpha', 0.85),
|
|
58
|
+
initial_type=kwargs.get('initial_type', 'uniform'))
|
|
59
|
+
|
|
60
|
+
elif model_upper == 'HadamardQW':
|
|
61
|
+
return run_hadamard(steps=kwargs.get('steps', 100),
|
|
62
|
+
top_p=kwargs.get('top_p', 0.05))
|
|
63
|
+
|
|
64
|
+
elif model_upper == 'directSumQW':
|
|
65
|
+
# 暂时使用 run_chawla_ranking 作为 directSumQW 的实现
|
|
66
|
+
# 后续可以根据需要添加专门的实现
|
|
67
|
+
return run_chawla_ranking(file_path=kwargs.get('file_path'),
|
|
68
|
+
steps=kwargs.get('steps', 100),
|
|
69
|
+
coin_type=kwargs.get('coin_type', 'fourier'))
|
|
70
|
+
|
|
71
|
+
else:
|
|
72
|
+
available_models = ['1d', '2d', '3d', 'ChawlaQW', 'QPageRank', 'HadamardQW', 'SzegedyQW', 'community', 'directSumQW']
|
|
73
|
+
raise ValueError(f"Unknown model type: {model}. Available: {', '.join(available_models)}")
|
QWnet/graphs.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import networkx as nx
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from matplotlib.colors import Normalize
|
|
5
|
+
from matplotlib.cm import ScalarMappable
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
def run_chawla_ranking(file_path=None, steps=100, coin_type='fourier'):
|
|
9
|
+
"""
|
|
10
|
+
Discrete-time quantum walk algorithm for ranking nodes on a network
|
|
11
|
+
Chawla, P ; Mangal, R ; Chandrashekar, CM
|
|
12
|
+
QUANTUM INFORMATION PROCESSING
|
|
13
|
+
19(5): 158, 2020.
|
|
14
|
+
"""
|
|
15
|
+
if file_path and os.path.exists(file_path):
|
|
16
|
+
G = nx.read_edgelist(file_path, create_using=nx.DiGraph())
|
|
17
|
+
else:
|
|
18
|
+
G = nx.gnp_random_graph(15, 0.2, directed=True, seed=42)
|
|
19
|
+
|
|
20
|
+
nodes = list(G.nodes())
|
|
21
|
+
N = len(nodes)
|
|
22
|
+
mapping = {node: i for i, node in enumerate(nodes)}
|
|
23
|
+
edges = list(G.edges())
|
|
24
|
+
M = len(edges)
|
|
25
|
+
|
|
26
|
+
if M == 0: raise ValueError("Graph must have edges.")
|
|
27
|
+
|
|
28
|
+
idx_map = {edge: i for i, edge in enumerate(edges)}
|
|
29
|
+
U = np.zeros((M, M), dtype=np.complex128)
|
|
30
|
+
|
|
31
|
+
for v in nodes:
|
|
32
|
+
in_edges = [e for e in edges if e[1] == v]
|
|
33
|
+
out_edges = [e for e in edges if e[0] == v]
|
|
34
|
+
d_in = len(in_edges); d_out = len(out_edges)
|
|
35
|
+
|
|
36
|
+
if d_in > 0 and d_out > 0:
|
|
37
|
+
if coin_type == 'fourier':
|
|
38
|
+
for k in range(d_out):
|
|
39
|
+
for l in range(d_in):
|
|
40
|
+
phase = np.exp(2j * np.pi * k * l / max(d_in, d_out))
|
|
41
|
+
U[idx_map[out_edges[k]], idx_map[in_edges[l]]] = phase / np.sqrt(d_in)
|
|
42
|
+
else:
|
|
43
|
+
for k in range(d_out):
|
|
44
|
+
for l in range(d_in):
|
|
45
|
+
U[idx_map[out_edges[k]], idx_map[in_edges[l]]] = np.sqrt(1.0 / d_in)
|
|
46
|
+
|
|
47
|
+
state = np.ones(M, dtype=np.complex128) / np.sqrt(M)
|
|
48
|
+
prob_accumulator = np.zeros(M)
|
|
49
|
+
curr_state = state
|
|
50
|
+
|
|
51
|
+
for t in range(steps):
|
|
52
|
+
curr_state = np.dot(U, curr_state)
|
|
53
|
+
norm = np.linalg.norm(curr_state)
|
|
54
|
+
if norm > 0: curr_state /= norm
|
|
55
|
+
prob_accumulator += np.abs(curr_state) ** 2
|
|
56
|
+
|
|
57
|
+
node_ranks = np.zeros(N)
|
|
58
|
+
for i, (u, v) in enumerate(edges):
|
|
59
|
+
node_ranks[mapping[v]] += prob_accumulator[i] / steps
|
|
60
|
+
|
|
61
|
+
pos = nx.kamada_kawai_layout(G)
|
|
62
|
+
fig, ax = plt.subplots(figsize=(6, 4))
|
|
63
|
+
cmap = plt.cm.YlOrRd
|
|
64
|
+
norm = Normalize(vmin=node_ranks.min(), vmax=node_ranks.max())
|
|
65
|
+
|
|
66
|
+
nx.draw_networkx_edges(G, pos, ax=ax, edge_color='#dddddd', alpha=0.7, arrowsize=12, connectionstyle='arc3,rad=0.1')
|
|
67
|
+
nx.draw_networkx_nodes(G, pos, ax=ax, node_size=220, node_color=node_ranks, cmap=cmap, edgecolors='#444444', linewidths=0.8)
|
|
68
|
+
|
|
69
|
+
for i, node in enumerate(nodes):
|
|
70
|
+
score = node_ranks[i]
|
|
71
|
+
rgb = cmap(norm(score))[:3]
|
|
72
|
+
brightness = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]
|
|
73
|
+
t_color = 'white' if brightness < 0.45 else 'black'
|
|
74
|
+
ax.text(pos[node][0], pos[node][1], s=str(node), color=t_color, fontsize=8, ha='center', va='center', fontweight='bold')
|
|
75
|
+
|
|
76
|
+
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
|
|
77
|
+
cb = plt.colorbar(sm, ax=ax, fraction=0.046, pad=0.04)
|
|
78
|
+
cb.outline.set_visible(False)
|
|
79
|
+
cb.ax.tick_params(labelsize=8)
|
|
80
|
+
ax.axis('off')
|
|
81
|
+
plt.tight_layout()
|
|
82
|
+
plt.show()
|
|
83
|
+
return node_ranks
|
|
84
|
+
|
|
85
|
+
def run_szegedy_service(G=None, steps=10, initial_type='uniform', initial_node=None):
|
|
86
|
+
"""
|
|
87
|
+
Quantum speed-up of Markov chain based algorithms
|
|
88
|
+
Szegedy M
|
|
89
|
+
Proceedings of the 45th Annual IEEE Symposium on Foundations of Computer Science
|
|
90
|
+
2004: 32–41.
|
|
91
|
+
"""
|
|
92
|
+
if G is None: G = nx.karate_club_graph()
|
|
93
|
+
nodes = list(G.nodes())
|
|
94
|
+
N = len(nodes)
|
|
95
|
+
mapping = {node: i for i, node in enumerate(nodes)}
|
|
96
|
+
A = nx.to_numpy_array(G, nodelist=nodes)
|
|
97
|
+
degrees = np.sum(A, axis=0)
|
|
98
|
+
degrees[degrees == 0] = 1
|
|
99
|
+
P = A / degrees
|
|
100
|
+
|
|
101
|
+
psi = np.zeros((N * N, N), dtype=np.complex128)
|
|
102
|
+
for i in range(N):
|
|
103
|
+
vec_psi_i = np.zeros((N, N), dtype=np.complex128)
|
|
104
|
+
vec_psi_i[:, i] = np.sqrt(P[:, i])
|
|
105
|
+
psi[:, i] = vec_psi_i.flatten()
|
|
106
|
+
|
|
107
|
+
S = 2 * np.dot(psi, psi.conj().T) - np.eye(N * N)
|
|
108
|
+
SWAP = np.zeros((N * N, N * N))
|
|
109
|
+
for i in range(N):
|
|
110
|
+
for j in range(N): SWAP[i * N + j, j * N + i] = 1
|
|
111
|
+
U = np.dot(SWAP, S)
|
|
112
|
+
|
|
113
|
+
if initial_type == 'node' and initial_node in mapping:
|
|
114
|
+
idx = mapping[initial_node]
|
|
115
|
+
init_matrix = np.zeros((N, N), dtype=np.complex128)
|
|
116
|
+
init_matrix[:, idx] = np.sqrt(P[:, idx])
|
|
117
|
+
state_vec = init_matrix.flatten()
|
|
118
|
+
else:
|
|
119
|
+
state_vec = np.sum(psi, axis=1) / np.sqrt(N)
|
|
120
|
+
|
|
121
|
+
for _ in range(steps):
|
|
122
|
+
state_vec = np.dot(U, state_vec)
|
|
123
|
+
|
|
124
|
+
node_probs = np.sum(np.abs(state_vec.reshape((N, N))) ** 2, axis=1)
|
|
125
|
+
node_probs /= np.sum(node_probs)
|
|
126
|
+
|
|
127
|
+
pos = nx.spring_layout(G, seed=42)
|
|
128
|
+
fig, ax = plt.subplots(figsize=(6, 4))
|
|
129
|
+
cmap = plt.cm.YlGnBu
|
|
130
|
+
norm = Normalize(vmin=node_probs.min(), vmax=node_probs.max())
|
|
131
|
+
nx.draw_networkx_edges(G, pos, ax=ax, edge_color='#bbbbbb', width=1.0)
|
|
132
|
+
nx.draw_networkx_nodes(G, pos, ax=ax, node_size=180, node_color=node_probs, cmap=cmap, edgecolors='#444444', linewidths=1.0)
|
|
133
|
+
|
|
134
|
+
for node in nodes:
|
|
135
|
+
prob = node_probs[mapping[node]]
|
|
136
|
+
rgb = cmap(norm(prob))[:3]
|
|
137
|
+
brightness = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]
|
|
138
|
+
t_color = 'white' if brightness < 0.38 else 'black'
|
|
139
|
+
ax.text(pos[node][0], pos[node][1], s=str(node), color=t_color, fontsize=9, ha='center', va='center')
|
|
140
|
+
|
|
141
|
+
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
|
|
142
|
+
sm.set_array([])
|
|
143
|
+
plt.colorbar(sm, ax=ax, fraction=0.046, pad=0.04).outline.set_visible(False)
|
|
144
|
+
ax.axis('off')
|
|
145
|
+
plt.tight_layout()
|
|
146
|
+
plt.show()
|
|
147
|
+
return node_probs
|
|
148
|
+
|
|
149
|
+
def run_community_detection(G=None, initial_state='uniform', coin_type='fourier'):
|
|
150
|
+
"""
|
|
151
|
+
Discrete-time quantum walk on complex networks for community detection
|
|
152
|
+
Kanae Mukai and Naomichi Hatano
|
|
153
|
+
Phys. Rev. Research 2, 023378 (2020)
|
|
154
|
+
"""
|
|
155
|
+
if G is None: G = nx.karate_club_graph()
|
|
156
|
+
nodes = list(G.nodes())
|
|
157
|
+
N = len(nodes)
|
|
158
|
+
mapping = {node: i for i, node in enumerate(nodes)}
|
|
159
|
+
edges = list(G.edges())
|
|
160
|
+
|
|
161
|
+
dir_edges = []
|
|
162
|
+
for u, v in edges:
|
|
163
|
+
dir_edges.append((u, v)); dir_edges.append((v, u))
|
|
164
|
+
idx_map = {edge: i for i, edge in enumerate(dir_edges)}
|
|
165
|
+
dim = len(dir_edges)
|
|
166
|
+
|
|
167
|
+
C = np.zeros((dim, dim), dtype=np.complex128)
|
|
168
|
+
for j in nodes:
|
|
169
|
+
neighbors = list(G.neighbors(j))
|
|
170
|
+
d_j = len(neighbors)
|
|
171
|
+
if d_j == 0: continue
|
|
172
|
+
|
|
173
|
+
if coin_type.lower() == 'fourier':
|
|
174
|
+
coin_matrix = np.exp(2j * np.pi * np.outer(np.arange(d_j), np.arange(d_j)) / d_j) / np.sqrt(d_j)
|
|
175
|
+
elif coin_type.lower() == 'grover':
|
|
176
|
+
coin_matrix = np.full((d_j, d_j), 2.0 / d_j) - np.eye(d_j)
|
|
177
|
+
else: raise ValueError("Unsupported coin_type")
|
|
178
|
+
|
|
179
|
+
for k in range(d_j):
|
|
180
|
+
for l in range(d_j):
|
|
181
|
+
row = idx_map[(j, neighbors[k])]
|
|
182
|
+
col = idx_map[(neighbors[l], j)]
|
|
183
|
+
C[row, col] = coin_matrix[k, l]
|
|
184
|
+
|
|
185
|
+
S = np.zeros((dim, dim))
|
|
186
|
+
for i, j in dir_edges:
|
|
187
|
+
S[idx_map[(j, i)], idx_map[(i, j)]] = 1
|
|
188
|
+
U = S @ C
|
|
189
|
+
|
|
190
|
+
if initial_state == 'uniform':
|
|
191
|
+
phi_0 = np.ones(dim, dtype=np.complex128) / np.sqrt(dim)
|
|
192
|
+
else:
|
|
193
|
+
phi_0 = np.zeros(dim, dtype=np.complex128)
|
|
194
|
+
# Note: 'initial_state' when not uniform implies an index or node logic, keeping simple here
|
|
195
|
+
# Assuming initial_state passed is a valid node index if it's an integer
|
|
196
|
+
if isinstance(initial_state, int) and 0 <= initial_state < N:
|
|
197
|
+
seed_neighbors = list(G.neighbors(nodes[initial_state]))
|
|
198
|
+
for nb in seed_neighbors:
|
|
199
|
+
phi_0[idx_map[(nodes[initial_state], nb)]] = 1.0 / np.sqrt(len(seed_neighbors))
|
|
200
|
+
|
|
201
|
+
vals, vecs = np.linalg.eig(U)
|
|
202
|
+
unique_vals, inverse_indices = np.unique(np.round(vals, 10), return_inverse=True)
|
|
203
|
+
inf_probs = np.zeros(dim)
|
|
204
|
+
|
|
205
|
+
for i in range(len(unique_vals)):
|
|
206
|
+
indices = np.where(inverse_indices == i)[0]
|
|
207
|
+
subspace_vecs = vecs[:, indices]
|
|
208
|
+
proj = subspace_vecs @ (np.conj(subspace_vecs.T) @ phi_0)
|
|
209
|
+
inf_probs += np.abs(proj) ** 2
|
|
210
|
+
|
|
211
|
+
node_scores = np.zeros(N)
|
|
212
|
+
for i, (u, v) in enumerate(dir_edges):
|
|
213
|
+
node_scores[mapping[v]] += np.real(inf_probs[i])
|
|
214
|
+
|
|
215
|
+
return dict(zip(nodes, node_scores))
|
|
216
|
+
|
|
217
|
+
def run_quantum_pagerank(file_path=None, steps=20, alpha=0.85, initial_type='uniform'):
|
|
218
|
+
"""
|
|
219
|
+
Quantum Google in a Complex Network (Paparo et al.)
|
|
220
|
+
"""
|
|
221
|
+
if file_path and os.path.exists(file_path):
|
|
222
|
+
G = nx.read_edgelist(file_path, create_using=nx.Graph())
|
|
223
|
+
else:
|
|
224
|
+
G = nx.karate_club_graph()
|
|
225
|
+
|
|
226
|
+
nodes = list(G.nodes())
|
|
227
|
+
N = len(nodes)
|
|
228
|
+
mapping = {node: i for i, node in enumerate(nodes)}
|
|
229
|
+
A = nx.to_numpy_array(G, nodelist=nodes)
|
|
230
|
+
degrees = np.sum(A, axis=1)
|
|
231
|
+
|
|
232
|
+
P_classic = np.zeros((N, N))
|
|
233
|
+
for i in range(N):
|
|
234
|
+
if degrees[i] > 0: P_classic[:, i] = A[i, :] / degrees[i]
|
|
235
|
+
else: P_classic[:, i] = 1.0 / N
|
|
236
|
+
P = alpha * P_classic + (1 - alpha) / N * np.ones((N, N))
|
|
237
|
+
|
|
238
|
+
psi = np.zeros((N * N, N), dtype=np.complex128)
|
|
239
|
+
for j in range(N):
|
|
240
|
+
column_j = np.zeros((N, N), dtype=np.complex128)
|
|
241
|
+
column_j[:, j] = np.sqrt(P[:, j])
|
|
242
|
+
psi[:, j] = column_j.flatten()
|
|
243
|
+
|
|
244
|
+
S = 2 * np.dot(psi, psi.conj().T) - np.eye(N * N)
|
|
245
|
+
SWAP = np.zeros((N * N, N * N))
|
|
246
|
+
for i in range(N):
|
|
247
|
+
for j in range(N): SWAP[i * N + j, j * N + i] = 1
|
|
248
|
+
U = np.dot(SWAP, S)
|
|
249
|
+
|
|
250
|
+
state = np.sum(psi, axis=1) / np.sqrt(N) if initial_type == 'uniform' else psi[:, 0]
|
|
251
|
+
prob_accumulator = np.zeros(N)
|
|
252
|
+
curr_state = state
|
|
253
|
+
|
|
254
|
+
for t in range(1, steps + 1):
|
|
255
|
+
curr_state = np.dot(U, curr_state)
|
|
256
|
+
probs = np.sum(np.abs(curr_state.reshape((N, N))) ** 2, axis=1)
|
|
257
|
+
prob_accumulator += probs
|
|
258
|
+
q_pagerank_scores = prob_accumulator / steps
|
|
259
|
+
|
|
260
|
+
pos = nx.spring_layout(G, seed=42)
|
|
261
|
+
fig, ax = plt.subplots(figsize=(6, 4))
|
|
262
|
+
cmap = plt.cm.YlGnBu
|
|
263
|
+
norm = Normalize(vmin=q_pagerank_scores.min(), vmax=q_pagerank_scores.max())
|
|
264
|
+
nx.draw_networkx_edges(G, pos, ax=ax, edge_color='#bbbbbb', alpha=0.4, arrows=False)
|
|
265
|
+
nx.draw_networkx_nodes(G, pos, ax=ax, node_size=180, node_color=q_pagerank_scores, cmap=cmap, edgecolors='#444444', linewidths=0.8)
|
|
266
|
+
|
|
267
|
+
for node in nodes:
|
|
268
|
+
score = q_pagerank_scores[mapping[node]]
|
|
269
|
+
rgb = cmap(norm(score))[:3]
|
|
270
|
+
brightness = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]
|
|
271
|
+
t_color = 'white' if brightness < 0.38 else 'black'
|
|
272
|
+
ax.text(pos[node][0], pos[node][1], s=str(node), color=t_color, fontsize=8, ha='center', va='center')
|
|
273
|
+
|
|
274
|
+
plt.colorbar(plt.cm.ScalarMappable(cmap=cmap, norm=norm), ax=ax, fraction=0.046, pad=0.04)
|
|
275
|
+
ax.axis('off')
|
|
276
|
+
plt.tight_layout()
|
|
277
|
+
plt.show()
|
|
278
|
+
return q_pagerank_scores
|
|
279
|
+
|
|
280
|
+
def run_hadamard(steps=100, top_p=0.05):
|
|
281
|
+
"""
|
|
282
|
+
A Hadamard walk model and its application in identification of important edges in complex networks
|
|
283
|
+
Liang et al.
|
|
284
|
+
"""
|
|
285
|
+
G = nx.karate_club_graph()
|
|
286
|
+
nodes, edges = list(G.nodes()), list(G.edges())
|
|
287
|
+
N, M = len(nodes), len(edges)
|
|
288
|
+
|
|
289
|
+
directed_edges = []
|
|
290
|
+
for u, v in edges: directed_edges.extend([(u, v), (v, u)])
|
|
291
|
+
dim = len(directed_edges)
|
|
292
|
+
idx_map = {edge: i for i, edge in enumerate(directed_edges)}
|
|
293
|
+
|
|
294
|
+
H = np.array([[1, 1], [1, -1]]) / np.sqrt(2)
|
|
295
|
+
C = np.zeros((dim, dim))
|
|
296
|
+
for i in range(0, dim, 2): C[i:i + 2, i:i + 2] = H
|
|
297
|
+
S = np.zeros((dim, dim))
|
|
298
|
+
for u, v in directed_edges: S[idx_map[(v, u)], idx_map[(u, v)]] = 1
|
|
299
|
+
U = S @ C
|
|
300
|
+
|
|
301
|
+
psi = np.ones(dim, dtype=np.complex128) / np.sqrt(dim)
|
|
302
|
+
node_probs_accum = np.zeros(N)
|
|
303
|
+
for _ in range(steps):
|
|
304
|
+
psi = U @ psi
|
|
305
|
+
curr_p = np.abs(psi) ** 2
|
|
306
|
+
for i, (u, v) in enumerate(directed_edges):
|
|
307
|
+
node_probs_accum[nodes.index(v)] += curr_p[i]
|
|
308
|
+
node_avg_p = node_probs_accum / steps
|
|
309
|
+
|
|
310
|
+
edge_scores = np.array([node_avg_p[nodes.index(u)] + node_avg_p[nodes.index(v)] for u, v in edges])
|
|
311
|
+
num_top = max(1, int(M * top_p))
|
|
312
|
+
top_indices = np.argsort(edge_scores)[-num_top:]
|
|
313
|
+
top_edges = [edges[i] for i in top_indices]
|
|
314
|
+
top_scores = edge_scores[top_indices]
|
|
315
|
+
|
|
316
|
+
pos = nx.spring_layout(G, seed=42)
|
|
317
|
+
fig, ax = plt.subplots(figsize=(10, 7))
|
|
318
|
+
target_cmap = plt.cm.plasma
|
|
319
|
+
norm = Normalize(vmin=top_scores.min(), vmax=top_scores.max())
|
|
320
|
+
|
|
321
|
+
nx.draw_networkx_nodes(G, pos, ax=ax, node_size=250, node_color='green', edgecolors='white', linewidths=0.6)
|
|
322
|
+
nx.draw_networkx_edges(G, pos, ax=ax, edgelist=edges, edge_color='#444444', width=0.8, alpha=0.3, arrows=False)
|
|
323
|
+
nx.draw_networkx_edges(G, pos, ax=ax, edgelist=top_edges, edge_color=top_scores, edge_cmap=target_cmap,
|
|
324
|
+
edge_vmin=top_scores.min(), edge_vmax=top_scores.max(), width=2.5, alpha=1.0, arrows=False)
|
|
325
|
+
nx.draw_networkx_labels(G, pos, font_size=8, font_color='white', font_weight='bold')
|
|
326
|
+
ax.set_title(f"Hadamard Walk: Top {num_top}", fontsize=12)
|
|
327
|
+
ax.axis('off')
|
|
328
|
+
plt.tight_layout()
|
|
329
|
+
plt.show()
|
|
330
|
+
return edge_scores
|
QWnet/lattice.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
from matplotlib.colors import LogNorm
|
|
4
|
+
|
|
5
|
+
class LatticeQuantumWalk:
|
|
6
|
+
"""
|
|
7
|
+
Unified class for 1D, 2D, and 3D Quantum Walk Simulations on regular lattices.
|
|
8
|
+
"""
|
|
9
|
+
def __init__(self, dimension: int, network_size: int = 100):
|
|
10
|
+
if dimension not in [1, 2, 3]:
|
|
11
|
+
raise ValueError("Dimension must be 1, 2, or 3.")
|
|
12
|
+
|
|
13
|
+
self.dimension = dimension
|
|
14
|
+
self.N = network_size
|
|
15
|
+
self.LSIZE = 2 * self.N + 1
|
|
16
|
+
self.results = None
|
|
17
|
+
self.meta = {}
|
|
18
|
+
|
|
19
|
+
def run(self, steps: int = 100, initial_dist: str = 'uniform', compare_classical: bool = False) -> dict:
|
|
20
|
+
self.meta = {
|
|
21
|
+
"steps": steps,
|
|
22
|
+
"initial_dist": initial_dist,
|
|
23
|
+
"compare_classical": compare_classical,
|
|
24
|
+
"network_size": self.N
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if self.dimension == 1:
|
|
28
|
+
self.results = self._run_1d(steps, initial_dist, compare_classical)
|
|
29
|
+
elif self.dimension == 2:
|
|
30
|
+
self.results = self._run_2d(steps, initial_dist, compare_classical)
|
|
31
|
+
elif self.dimension == 3:
|
|
32
|
+
self.results = self._run_3d(steps, initial_dist)
|
|
33
|
+
|
|
34
|
+
return self.results
|
|
35
|
+
|
|
36
|
+
def _run_1d(self, steps, initial_dist, compare_classical):
|
|
37
|
+
N = self.N
|
|
38
|
+
LSIZE = self.LSIZE
|
|
39
|
+
COINS = 1
|
|
40
|
+
TOTAL_DIM = LSIZE * COINS * 2
|
|
41
|
+
|
|
42
|
+
TOSS = np.zeros(shape=(TOTAL_DIM, TOTAL_DIM), dtype=complex)
|
|
43
|
+
STEP = np.zeros(shape=(TOTAL_DIM, TOTAL_DIM), dtype=complex)
|
|
44
|
+
|
|
45
|
+
for row in range(TOTAL_DIM):
|
|
46
|
+
for col in range(TOTAL_DIM):
|
|
47
|
+
if (row == col) and (row < LSIZE): TOSS[row, col] = 1/np.sqrt(2)
|
|
48
|
+
elif (row == col) and (row >= LSIZE): TOSS[row, col] = -1/np.sqrt(2)
|
|
49
|
+
elif (row == col + LSIZE): TOSS[row, col] = 1/np.sqrt(2)
|
|
50
|
+
elif (col == row + LSIZE): TOSS[row, col] = 1/np.sqrt(2)
|
|
51
|
+
|
|
52
|
+
if (row == col + 1) and (row < LSIZE): STEP[row, col] = 1
|
|
53
|
+
elif (col == row + 1) and (row >= LSIZE): STEP[row, col] = 1
|
|
54
|
+
|
|
55
|
+
state0 = np.zeros(shape=(TOTAL_DIM, 1), dtype=complex)
|
|
56
|
+
def get_indices(lattice_x):
|
|
57
|
+
base_idx = int(lattice_x + N)
|
|
58
|
+
return [base_idx, base_idx + LSIZE]
|
|
59
|
+
center_indices = get_indices(0)
|
|
60
|
+
|
|
61
|
+
if initial_dist == 'uniform':
|
|
62
|
+
state0[center_indices[0]] = 1/np.sqrt(2)
|
|
63
|
+
state0[center_indices[1]] = (0+1j)/np.sqrt(2)
|
|
64
|
+
elif initial_dist == 'biased':
|
|
65
|
+
state0[center_indices[0]] = 1.0
|
|
66
|
+
|
|
67
|
+
current_state = state0
|
|
68
|
+
for t in range(steps):
|
|
69
|
+
current_state = np.dot(TOSS, current_state)
|
|
70
|
+
current_state = np.dot(STEP, current_state)
|
|
71
|
+
|
|
72
|
+
lattice_grid = np.arange(-N, N + 1)
|
|
73
|
+
quantum_probs = np.zeros(LSIZE)
|
|
74
|
+
for i, x in enumerate(lattice_grid):
|
|
75
|
+
locs = get_indices(x)
|
|
76
|
+
quantum_probs[i] = (np.abs(current_state[locs[0]])**2 + np.abs(current_state[locs[1]])**2).item()
|
|
77
|
+
|
|
78
|
+
classical_probs = None
|
|
79
|
+
if compare_classical:
|
|
80
|
+
walkers = 5000
|
|
81
|
+
positions = np.zeros(walkers)
|
|
82
|
+
for _ in range(steps):
|
|
83
|
+
positions += np.random.choice([-1, 1], size=walkers)
|
|
84
|
+
c_counts, _ = np.histogram(positions, bins=np.arange(-N, N + 2) - 0.5, density=True)
|
|
85
|
+
classical_probs = c_counts
|
|
86
|
+
|
|
87
|
+
return {"type": "1D", "grid": lattice_grid, "quantum_probs": quantum_probs, "classical_probs": classical_probs}
|
|
88
|
+
|
|
89
|
+
def _run_2d(self, steps, initial_dist, compare_classical):
|
|
90
|
+
N = self.N
|
|
91
|
+
LSIZE = self.LSIZE
|
|
92
|
+
state = np.zeros((2, 2, LSIZE, LSIZE), dtype=complex)
|
|
93
|
+
if initial_dist == 'uniform':
|
|
94
|
+
state[0, 0, N, N], state[0, 1, N, N] = 0.5, 0.5j
|
|
95
|
+
state[1, 0, N, N], state[1, 1, N, N] = -0.5j, 0.5
|
|
96
|
+
else:
|
|
97
|
+
state[0, 0, N, N] = 1.0
|
|
98
|
+
|
|
99
|
+
for _ in range(steps):
|
|
100
|
+
s = state
|
|
101
|
+
new_s = np.empty_like(s)
|
|
102
|
+
new_s[0, 0] = 0.5 * (s[0, 0] + s[0, 1] + s[1, 0] + s[1, 1])
|
|
103
|
+
new_s[0, 1] = 0.5 * (s[0, 0] - s[0, 1] + s[1, 0] - s[1, 1])
|
|
104
|
+
new_s[1, 0] = 0.5 * (s[0, 0] + s[0, 1] - s[1, 0] - s[1, 1])
|
|
105
|
+
new_s[1, 1] = 0.5 * (s[0, 0] - s[0, 1] - s[1, 0] + s[1, 1])
|
|
106
|
+
state[0, 0] = np.roll(new_s[0, 0], shift=(1, 1), axis=(0, 1))
|
|
107
|
+
state[0, 1] = np.roll(new_s[0, 1], shift=(1, -1), axis=(0, 1))
|
|
108
|
+
state[1, 0] = np.roll(new_s[1, 0], shift=(-1, 1), axis=(0, 1))
|
|
109
|
+
state[1, 1] = np.roll(new_s[1, 1], shift=(-1, -1), axis=(0, 1))
|
|
110
|
+
|
|
111
|
+
q_probs = np.sum(np.abs(state) ** 2, axis=(0, 1))
|
|
112
|
+
c_probs = None
|
|
113
|
+
if compare_classical:
|
|
114
|
+
walkers = 10000
|
|
115
|
+
x, y = np.zeros(walkers, dtype=int), np.zeros(walkers, dtype=int)
|
|
116
|
+
for _ in range(steps):
|
|
117
|
+
x += np.random.choice([-1, 1], size=walkers)
|
|
118
|
+
y += np.random.choice([-1, 1], size=walkers)
|
|
119
|
+
c_probs, _, _ = np.histogram2d(x, y, bins=[np.arange(-N, N + 2) - 0.5] * 2, density=True)
|
|
120
|
+
c_probs = c_probs.T
|
|
121
|
+
return {"type": "2D", "q_data": q_probs, "c_data": c_probs}
|
|
122
|
+
|
|
123
|
+
def _run_3d(self, steps, initial_dist):
|
|
124
|
+
N = self.N
|
|
125
|
+
LSIZE = self.LSIZE
|
|
126
|
+
COIN_DIM = 6
|
|
127
|
+
state = np.zeros((COIN_DIM, LSIZE, LSIZE, LSIZE), dtype=np.complex128)
|
|
128
|
+
center = N
|
|
129
|
+
if initial_dist == 'uniform':
|
|
130
|
+
state[:, center, center, center] = 1.0 / np.sqrt(COIN_DIM)
|
|
131
|
+
else:
|
|
132
|
+
state[0, center, center, center] = 1.0
|
|
133
|
+
grover_coin = (2.0 / COIN_DIM) * np.ones((COIN_DIM, COIN_DIM)) - np.eye(COIN_DIM)
|
|
134
|
+
|
|
135
|
+
for _ in range(steps):
|
|
136
|
+
state = np.einsum('ij,jxyz->ixyz', grover_coin, state)
|
|
137
|
+
new_state = np.zeros_like(state)
|
|
138
|
+
new_state[0] = np.roll(state[0], 1, 0)
|
|
139
|
+
new_state[1] = np.roll(state[1], -1, 0)
|
|
140
|
+
new_state[2] = np.roll(state[2], 1, 1)
|
|
141
|
+
new_state[3] = np.roll(state[3], -1, 1)
|
|
142
|
+
new_state[4] = np.roll(state[4], 1, 2)
|
|
143
|
+
new_state[5] = np.roll(state[5], -1, 2)
|
|
144
|
+
state = new_state
|
|
145
|
+
q_probs = np.sum(np.abs(state) ** 2, axis=0)
|
|
146
|
+
return {"type": "3D", "q_data": q_probs}
|
|
147
|
+
|
|
148
|
+
def visualize(self):
|
|
149
|
+
if self.results is None: raise ValueError("No results. Run simulation first.")
|
|
150
|
+
if self.dimension == 1: self._visualize_1d()
|
|
151
|
+
elif self.dimension == 2: self._visualize_2d()
|
|
152
|
+
elif self.dimension == 3: self._visualize_3d()
|
|
153
|
+
|
|
154
|
+
def _visualize_1d(self):
|
|
155
|
+
result = self.results
|
|
156
|
+
plt.figure()
|
|
157
|
+
plt.plot(result['grid'], result['quantum_probs'], label='Quantum')
|
|
158
|
+
if result['classical_probs'] is not None:
|
|
159
|
+
plt.plot(result['grid'], result['classical_probs'], label='Classical', linestyle='--')
|
|
160
|
+
plt.legend()
|
|
161
|
+
plt.title(f"1D Quantum Walk (N={self.meta['network_size']}, T={self.meta['steps']})")
|
|
162
|
+
plt.show()
|
|
163
|
+
|
|
164
|
+
def _visualize_2d(self):
|
|
165
|
+
result = self.results
|
|
166
|
+
fig, axes = plt.subplots(1, 2 if self.meta['compare_classical'] else 1, figsize=(12, 5))
|
|
167
|
+
ax_q = axes[0] if self.meta['compare_classical'] else axes
|
|
168
|
+
im_q = ax_q.imshow(result['q_data'], cmap='Blues', origin='lower', extent=[-self.N, self.N, -self.N, self.N])
|
|
169
|
+
ax_q.set_title(f"Quantum Walk (Steps={self.meta['steps']})")
|
|
170
|
+
plt.colorbar(im_q, ax=ax_q)
|
|
171
|
+
if self.meta['compare_classical']:
|
|
172
|
+
ax_c = axes[1]
|
|
173
|
+
im_c = ax_c.imshow(result['c_data'], cmap='Blues', origin='lower', extent=[-self.N, self.N, -self.N, self.N])
|
|
174
|
+
ax_c.set_title("Classical Random Walk")
|
|
175
|
+
plt.colorbar(im_c, ax=ax_c)
|
|
176
|
+
plt.tight_layout()
|
|
177
|
+
plt.show()
|
|
178
|
+
|
|
179
|
+
def _visualize_3d(self):
|
|
180
|
+
q_probs = self.results['q_data']
|
|
181
|
+
N = self.N
|
|
182
|
+
center = N
|
|
183
|
+
xy = q_probs[:, :, center]
|
|
184
|
+
yz = q_probs[center, :, :]
|
|
185
|
+
xz = q_probs[:, center, :]
|
|
186
|
+
fig, axes = plt.subplots(1, 3, figsize=(18, 5), dpi=100)
|
|
187
|
+
cmap = 'YlGnBu'
|
|
188
|
+
norm = LogNorm(vmin=max(q_probs.min(), 1e-12), vmax=q_probs.max())
|
|
189
|
+
|
|
190
|
+
for ax, data, title, xlabel, ylabel in zip(axes, [xy, yz, xz], ["XY Slice", "YZ Slice", "XZ Slice"], ["x","y","x"], ["y","z","z"]):
|
|
191
|
+
im = ax.imshow(data.T, cmap=cmap, norm=norm, origin='lower', extent=[-N, N, -N, N])
|
|
192
|
+
ax.set_title(title); ax.set_xlabel(xlabel); ax.set_ylabel(ylabel)
|
|
193
|
+
fig.colorbar(axes[2].images[0], ax=axes.ravel().tolist(), pad=0.02).set_label('Raw Probability Density')
|
|
194
|
+
plt.suptitle(f"3D Quantum Walk Analysis (Steps={self.meta['steps']})")
|
|
195
|
+
plt.show()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: QWnet
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: A unified Quantum Walk simulation package
|
|
5
|
+
Project-URL: Homepage, https://example.com/QWnet
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: matplotlib
|
|
10
|
+
Requires-Dist: networkx
|
|
11
|
+
|
|
12
|
+
# QWnet Package
|
|
13
|
+
|
|
14
|
+
This package provides a unified interface for various Quantum Walk simulations.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install QWnet
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import QWnet as qw
|
|
26
|
+
|
|
27
|
+
# 1D/2D/3D Lattice Walks
|
|
28
|
+
qw.run_simulation('1d', steps=100)
|
|
29
|
+
qw.run_simulation('2d', steps=50)
|
|
30
|
+
qw.run_simulation('3d', steps=20)
|
|
31
|
+
|
|
32
|
+
# Graph Walks
|
|
33
|
+
qw.run_simulation('ranking', file_path='graph.edgelist')
|
|
34
|
+
qw.run_simulation('pagerank')
|
|
35
|
+
qw.run_simulation('hadamard')
|
|
36
|
+
qw.run_simulation('szegedy')
|
|
37
|
+
qw.run_simulation('community')
|
|
38
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
QWnet/__init__.py,sha256=el2pSuqEri_X-e6eMn57uxiQ7XUsXTYXK2oQiN94pK0,3777
|
|
2
|
+
QWnet/graphs.py,sha256=N-tP9lUEVwiM4iRJJazA0oL4Udl4_BxXu4KRk8SMhSo,13167
|
|
3
|
+
QWnet/lattice.py,sha256=wYjIyh1rhYB_ONgxmWfMAWtncOc0sJ13W2jMjEBYQ7Y,8852
|
|
4
|
+
qwnet-2.0.0.dist-info/METADATA,sha256=gHVzVUkmMQ8brwIJlungRp8KG41hKCkEWf7lYsAzuSw,832
|
|
5
|
+
qwnet-2.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
+
qwnet-2.0.0.dist-info/top_level.txt,sha256=h1QjXkEfsiVSIVldoV7OoylAVPE6lfyiFDfYB89yVwM,6
|
|
7
|
+
qwnet-2.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
QWnet
|