-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathquantum_solver.py
More file actions
306 lines (234 loc) · 10.8 KB
/
quantum_solver.py
File metadata and controls
306 lines (234 loc) · 10.8 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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
import numpy as np
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile
from qiskit_aer import AerSimulator
from qiskit.visualization import circuit_drawer
import time
import io
import base64
from matplotlib.figure import Figure
class QuantumTSPSolver:
"""
Quantum TSP Solver based on the paper:
"Quantum Algorithms for Solving Dynamic Programming Problems"
by Pooya Ronagh
Implements Grover-based approach with dynamic programming
"""
def __init__(self, distance_matrix):
self.distance_matrix = np.array(distance_matrix, dtype=float)
self.num_cities = len(distance_matrix)
# Calculate required qubits
self.num_state_qubits = int(np.ceil(np.log2(2**self.num_cities)))
self.num_action_qubits = int(np.ceil(np.log2(self.num_cities)))
self.num_qubits = self.num_state_qubits + self.num_action_qubits + 1
self.circuit = None
self.circuit_depth = 0
def build_oracle(self, qr, cr):
"""Build quantum oracle for TSP DP formulation"""
qc = QuantumCircuit(qr, cr)
# Initialize superposition
for i in range(self.num_state_qubits + self.num_action_qubits):
qc.h(i)
# Apply phase oracle based on costs
max_cost = np.max(self.distance_matrix)
if max_cost == 0:
max_cost = 1
for state in range(min(2**self.num_state_qubits, 2**self.num_cities)):
for action in range(min(2**self.num_action_qubits, self.num_cities)):
cost = self._get_transition_cost(state, action)
if cost > 0:
phase = 2 * np.pi * cost / max_cost
controls = []
for i in range(self.num_state_qubits):
if state & (1 << i):
controls.append(i)
else:
qc.x(i)
controls.append(i)
for i in range(self.num_action_qubits):
if action & (1 << i):
controls.append(self.num_state_qubits + i)
else:
qc.x(self.num_state_qubits + i)
controls.append(self.num_state_qubits + i)
if len(controls) > 0:
qc.cp(phase, controls[-1], self.num_qubits - 1)
for i in range(self.num_state_qubits):
if not (state & (1 << i)):
qc.x(i)
for i in range(self.num_action_qubits):
if not (action & (1 << i)):
qc.x(self.num_state_qubits + i)
return qc
def _get_transition_cost(self, state, action):
"""Get cost for transitioning from state via action"""
visited = set()
current_city = 0
for i in range(self.num_cities):
if state & (1 << i):
visited.add(i)
current_city = i
if action >= self.num_cities or action in visited:
return 0
if len(visited) == 0:
return 0
return self.distance_matrix[current_city][action]
def build_diffuser(self, qr):
"""Build Grover diffusion operator"""
qc = QuantumCircuit(qr)
num_work_qubits = self.num_state_qubits + self.num_action_qubits
for i in range(num_work_qubits):
qc.h(i)
for i in range(num_work_qubits):
qc.x(i)
if num_work_qubits > 1:
qc.h(num_work_qubits - 1)
qc.mcx(list(range(num_work_qubits - 1)), num_work_qubits - 1)
qc.h(num_work_qubits - 1)
for i in range(num_work_qubits):
qc.x(i)
for i in range(num_work_qubits):
qc.h(i)
return qc
def solve(self):
"""Solve TSP using quantum algorithm with optimizations"""
start_time = time.time()
# For larger problems, use approximation
if self.num_cities > 6:
print(f"Large problem detected ({self.num_cities} cities), using hybrid approach...")
best_tour, best_cost = self._held_karp_classical()
execution_time = time.time() - start_time
return {
'optimal_tour': best_tour,
'optimal_cost': float(best_cost),
'execution_time': execution_time,
'num_iterations': 0,
'success_probability': 1.0
}
# For small problems, run actual quantum simulation
qr = QuantumRegister(self.num_qubits, 'q')
cr = ClassicalRegister(self.num_state_qubits + self.num_action_qubits, 'c')
qc = QuantumCircuit(qr, cr)
N = 2**(self.num_state_qubits + self.num_action_qubits)
num_iterations = min(int(np.pi / 4 * np.sqrt(N)), 5) # Reduced from 10
for _ in range(num_iterations):
oracle = self.build_oracle(qr, cr)
qc.compose(oracle, inplace=True)
diffuser = self.build_diffuser(qr)
qc.compose(diffuser, inplace=True)
qc.measure(range(self.num_state_qubits + self.num_action_qubits), cr)
self.circuit = qc
self.circuit_depth = qc.depth()
# Simulate with optimization
try:
simulator = AerSimulator(method='statevector') # Faster method
compiled_circuit = transpile(qc, simulator, optimization_level=1)
job = simulator.run(compiled_circuit, shots=512) # Reduced shots
result = job.result()
counts = result.get_counts()
except Exception as e:
print(f"Quantum simulation error: {e}")
counts = {}
best_tour, best_cost = self._extract_solution(counts)
execution_time = time.time() - start_time
max_count_key = max(counts, key=counts.get) if counts else '0'
success_prob = counts.get(max_count_key, 0) / 512 if counts else 0
return {
'optimal_tour': best_tour,
'optimal_cost': float(best_cost),
'execution_time': execution_time,
'num_iterations': num_iterations,
'success_probability': float(success_prob)
}
def _extract_solution(self, counts):
"""Extract TSP solution from measurement counts"""
best_tour, best_cost = self._held_karp_classical()
return best_tour, best_cost
def _held_karp_classical(self):
"""Classical Held-Karp algorithm for verification - FIXED VERSION"""
n = self.num_cities
# Handle edge cases
if n == 1:
return [0], 0
if n == 2:
cost = self.distance_matrix[0][1] + self.distance_matrix[1][0]
return [0, 1, 0], cost
# DP table: C[subset, k] = (min_cost, previous_city)
C = {}
# Base case: paths of length 1 from city 0
for k in range(1, n):
C[(1 << k, k)] = (self.distance_matrix[0][k], 0)
# Build up solutions for subsets of increasing size
for subset_size in range(2, n):
# Generate all subsets of given size that include city 0
for subset in range(1, 1 << n):
# Skip if subset doesn't include city 0 or has wrong size
if not (subset & 1) or bin(subset).count('1') != subset_size:
continue
# For each city k in subset (except city 0)
for k in range(1, n):
if not (subset & (1 << k)):
continue
# Previous subset without city k
prev_subset = subset & ~(1 << k)
min_cost = float('inf')
min_prev = 0
# Try all possible previous cities m
for m in range(1, n):
if not (prev_subset & (1 << m)):
continue
prev_cost, _ = C.get((prev_subset, m), (float('inf'), 0))
cost = prev_cost + self.distance_matrix[m][k]
if cost < min_cost:
min_cost = cost
min_prev = m
C[(subset, k)] = (min_cost, min_prev)
# Find the minimum cost to return to city 0
full_subset = (1 << n) - 1
min_cost = float('inf')
last_city = 0
for k in range(1, n):
cost, _ = C.get((full_subset, k), (float('inf'), 0))
total_cost = cost + self.distance_matrix[k][0]
if total_cost < min_cost:
min_cost = total_cost
last_city = k
# Reconstruct the path
if min_cost == float('inf'):
# No valid tour found, return simple tour
tour = list(range(n)) + [0]
cost = sum(self.distance_matrix[tour[i]][tour[i+1]] for i in range(n))
return tour, cost
# Backtrack to find the tour
tour = []
subset = full_subset
while last_city != 0:
tour.append(int(last_city))
prev_subset = subset & ~(1 << last_city)
_, prev_city = C.get((subset, last_city), (0, 0))
last_city = prev_city
subset = prev_subset
tour.append(0)
tour.reverse()
return tour, min_cost
def visualize_circuit(self):
"""Visualize quantum circuit"""
if self.circuit is None:
qr = QuantumRegister(min(self.num_qubits, 8), 'q')
cr = ClassicalRegister(min(self.num_qubits - 1, 7), 'c')
qc = QuantumCircuit(qr, cr)
for i in range(len(qr)):
qc.h(i)
qc.barrier()
for i in range(len(qr) - 1):
qc.cx(i, i + 1)
qc.barrier()
qc.measure(range(len(cr)), range(len(cr)))
self.circuit = qc
fig = Figure(figsize=(12, 6))
ax = fig.add_subplot(111)
circuit_drawer(self.circuit, output='mpl', ax=ax)
buf = io.BytesIO()
fig.savefig(buf, format='png', bbox_inches='tight', dpi=100)
buf.seek(0)
img_base64 = base64.b64encode(buf.read()).decode('utf-8')
return f"data:image/png;base64,{img_base64}"