quantum-debugger 0.1.1__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.
- quantum_debugger/__init__.py +22 -0
- quantum_debugger/core/__init__.py +7 -0
- quantum_debugger/core/circuit.py +269 -0
- quantum_debugger/core/gates.py +153 -0
- quantum_debugger/core/quantum_state.py +293 -0
- quantum_debugger/debugger/__init__.py +7 -0
- quantum_debugger/debugger/breakpoints.py +132 -0
- quantum_debugger/debugger/debugger.py +222 -0
- quantum_debugger/debugger/inspector.py +219 -0
- quantum_debugger/profiler/__init__.py +6 -0
- quantum_debugger/profiler/metrics.py +129 -0
- quantum_debugger/profiler/profiler.py +174 -0
- quantum_debugger/visualization/__init__.py +6 -0
- quantum_debugger/visualization/bloch_sphere.py +152 -0
- quantum_debugger/visualization/state_viz.py +205 -0
- quantum_debugger-0.1.1.dist-info/METADATA +176 -0
- quantum_debugger-0.1.1.dist-info/RECORD +20 -0
- quantum_debugger-0.1.1.dist-info/WHEEL +5 -0
- quantum_debugger-0.1.1.dist-info/licenses/LICENSE +21 -0
- quantum_debugger-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum state representation and operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from typing import List, Tuple, Optional
|
|
7
|
+
import copy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QuantumState:
|
|
11
|
+
"""Represents a quantum state vector"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, num_qubits: int, state_vector: Optional[np.ndarray] = None):
|
|
14
|
+
"""
|
|
15
|
+
Initialize a quantum state
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
num_qubits: Number of qubits
|
|
19
|
+
state_vector: Optional initial state vector (defaults to |0...0>)
|
|
20
|
+
"""
|
|
21
|
+
self.num_qubits = num_qubits
|
|
22
|
+
self.dim = 2 ** num_qubits
|
|
23
|
+
|
|
24
|
+
if state_vector is not None:
|
|
25
|
+
if len(state_vector) != self.dim:
|
|
26
|
+
raise ValueError(f"State vector size {len(state_vector)} doesn't match {self.dim}")
|
|
27
|
+
self.state_vector = np.array(state_vector, dtype=complex)
|
|
28
|
+
self._normalize()
|
|
29
|
+
else:
|
|
30
|
+
# Initialize to |0...0> state
|
|
31
|
+
self.state_vector = np.zeros(self.dim, dtype=complex)
|
|
32
|
+
self.state_vector[0] = 1.0
|
|
33
|
+
|
|
34
|
+
def _normalize(self):
|
|
35
|
+
"""Normalize the state vector"""
|
|
36
|
+
norm = np.linalg.norm(self.state_vector)
|
|
37
|
+
if norm > 0:
|
|
38
|
+
self.state_vector /= norm
|
|
39
|
+
|
|
40
|
+
def copy(self):
|
|
41
|
+
"""Create a deep copy of this state"""
|
|
42
|
+
return copy.deepcopy(self)
|
|
43
|
+
|
|
44
|
+
def apply_gate(self, gate_matrix: np.ndarray, target_qubits: List[int]):
|
|
45
|
+
"""
|
|
46
|
+
Apply a quantum gate to specific qubits
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
gate_matrix: Unitary matrix of the gate
|
|
50
|
+
target_qubits: List of qubit indices to apply gate to
|
|
51
|
+
"""
|
|
52
|
+
# Build full gate matrix for entire system
|
|
53
|
+
full_matrix = self._build_full_gate_matrix(gate_matrix, target_qubits)
|
|
54
|
+
|
|
55
|
+
# Apply gate
|
|
56
|
+
self.state_vector = full_matrix @ self.state_vector
|
|
57
|
+
self._normalize()
|
|
58
|
+
|
|
59
|
+
def _build_full_gate_matrix(self, gate_matrix: np.ndarray, target_qubits: List[int]) -> np.ndarray:
|
|
60
|
+
"""Build the full gate matrix for the entire quantum system"""
|
|
61
|
+
num_gate_qubits = int(np.log2(gate_matrix.shape[0]))
|
|
62
|
+
|
|
63
|
+
if num_gate_qubits == self.num_qubits and target_qubits == list(range(self.num_qubits)):
|
|
64
|
+
return gate_matrix
|
|
65
|
+
|
|
66
|
+
# For single and multi-qubit gates on subset of qubits
|
|
67
|
+
return self._expand_gate_to_full_space(gate_matrix, target_qubits)
|
|
68
|
+
|
|
69
|
+
def _expand_gate_to_full_space(self, gate_matrix: np.ndarray, target_qubits: List[int]) -> np.ndarray:
|
|
70
|
+
"""
|
|
71
|
+
Expand a gate acting on subset of qubits to full Hilbert space using tensor products.
|
|
72
|
+
|
|
73
|
+
This method builds the full gate matrix by inserting identity matrices for
|
|
74
|
+
non-target qubits and using Kronecker products to construct the complete operator.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
gate_matrix: The gate matrix to expand
|
|
78
|
+
target_qubits: List of qubit indices the gate acts on
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Full gate matrix for entire system
|
|
82
|
+
"""
|
|
83
|
+
num_gate_qubits = len(target_qubits)
|
|
84
|
+
|
|
85
|
+
# Build list of operators for each qubit
|
|
86
|
+
# For target qubits, we'll later insert the actual gate
|
|
87
|
+
# For non-target qubits, use identity
|
|
88
|
+
I = np.eye(2, dtype=complex)
|
|
89
|
+
|
|
90
|
+
# Sort target qubits to understand their positions
|
|
91
|
+
target_set = set(target_qubits)
|
|
92
|
+
|
|
93
|
+
# Build the full operator using tensor products
|
|
94
|
+
# We need to handle the gate acting on possibly non-consecutive qubits
|
|
95
|
+
|
|
96
|
+
# Method: Iterate through all basis states and apply gate where appropriate
|
|
97
|
+
full_matrix = np.zeros((self.dim, self.dim), dtype=complex)
|
|
98
|
+
|
|
99
|
+
for in_idx in range(self.dim):
|
|
100
|
+
for out_idx in range(self.dim):
|
|
101
|
+
# Extract individual qubit states
|
|
102
|
+
in_bits = [(in_idx >> q) & 1 for q in range(self.num_qubits)]
|
|
103
|
+
out_bits = [(out_idx >> q) & 1 for q in range(self.num_qubits)]
|
|
104
|
+
|
|
105
|
+
# Check if non-target qubits are unchanged
|
|
106
|
+
non_target_match = all(
|
|
107
|
+
in_bits[q] == out_bits[q]
|
|
108
|
+
for q in range(self.num_qubits)
|
|
109
|
+
if q not in target_set
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if non_target_match:
|
|
113
|
+
# Build gate indices from target qubit states
|
|
114
|
+
# Map physical qubit index to gate matrix index
|
|
115
|
+
gate_in_idx = 0
|
|
116
|
+
gate_out_idx = 0
|
|
117
|
+
|
|
118
|
+
for k, qubit in enumerate(target_qubits):
|
|
119
|
+
# Qubit k in the gate corresponds to target_qubits[k] in the system
|
|
120
|
+
gate_in_idx |= (in_bits[qubit] << k)
|
|
121
|
+
gate_out_idx |= (out_bits[qubit] << k)
|
|
122
|
+
|
|
123
|
+
# Get the matrix element from the gate
|
|
124
|
+
full_matrix[out_idx, in_idx] = gate_matrix[gate_out_idx, gate_in_idx]
|
|
125
|
+
|
|
126
|
+
return full_matrix
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def measure(self, qubit: int) -> int:
|
|
130
|
+
"""
|
|
131
|
+
Measure a qubit and collapse the state
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
qubit: Index of qubit to measure
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Measurement result (0 or 1)
|
|
138
|
+
"""
|
|
139
|
+
# Calculate probabilities
|
|
140
|
+
prob_0 = self.get_measurement_probability(qubit, 0)
|
|
141
|
+
|
|
142
|
+
# Randomly choose outcome
|
|
143
|
+
outcome = 0 if np.random.random() < prob_0 else 1
|
|
144
|
+
|
|
145
|
+
# Collapse state
|
|
146
|
+
self._collapse_state(qubit, outcome)
|
|
147
|
+
|
|
148
|
+
return outcome
|
|
149
|
+
|
|
150
|
+
def measure_all(self) -> List[int]:
|
|
151
|
+
"""Measure all qubits"""
|
|
152
|
+
return [self.measure(q) for q in range(self.num_qubits)]
|
|
153
|
+
|
|
154
|
+
def get_measurement_probability(self, qubit: int, outcome: int) -> float:
|
|
155
|
+
"""Get probability of measuring a specific outcome for a qubit"""
|
|
156
|
+
prob = 0.0
|
|
157
|
+
for i, amplitude in enumerate(self.state_vector):
|
|
158
|
+
if (i >> qubit) & 1 == outcome:
|
|
159
|
+
prob += abs(amplitude) ** 2
|
|
160
|
+
return prob
|
|
161
|
+
|
|
162
|
+
def get_probabilities(self) -> np.ndarray:
|
|
163
|
+
"""Get probability distribution over all basis states"""
|
|
164
|
+
return np.abs(self.state_vector) ** 2
|
|
165
|
+
|
|
166
|
+
def _collapse_state(self, qubit: int, outcome: int):
|
|
167
|
+
"""Collapse state after measurement"""
|
|
168
|
+
new_state = np.zeros_like(self.state_vector)
|
|
169
|
+
|
|
170
|
+
for i, amplitude in enumerate(self.state_vector):
|
|
171
|
+
if (i >> qubit) & 1 == outcome:
|
|
172
|
+
new_state[i] = amplitude
|
|
173
|
+
|
|
174
|
+
self.state_vector = new_state
|
|
175
|
+
self._normalize()
|
|
176
|
+
|
|
177
|
+
def fidelity(self, other: 'QuantumState') -> float:
|
|
178
|
+
"""
|
|
179
|
+
Calculate fidelity with another quantum state
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
other: Another quantum state
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Fidelity value between 0 and 1
|
|
186
|
+
"""
|
|
187
|
+
if self.num_qubits != other.num_qubits:
|
|
188
|
+
raise ValueError("States must have same number of qubits")
|
|
189
|
+
|
|
190
|
+
overlap = np.abs(np.vdot(self.state_vector, other.state_vector))
|
|
191
|
+
return overlap ** 2
|
|
192
|
+
|
|
193
|
+
def entropy(self) -> float:
|
|
194
|
+
"""Calculate von Neumann entropy"""
|
|
195
|
+
probabilities = self.get_probabilities()
|
|
196
|
+
# Filter out zero probabilities to avoid log(0)
|
|
197
|
+
probabilities = probabilities[probabilities > 1e-10]
|
|
198
|
+
return -np.sum(probabilities * np.log2(probabilities))
|
|
199
|
+
|
|
200
|
+
def is_entangled(self) -> bool:
|
|
201
|
+
"""
|
|
202
|
+
Check if state is entangled (simple check for 2-qubit systems)
|
|
203
|
+
|
|
204
|
+
For 2 qubits: state is entangled if it cannot be written as tensor product
|
|
205
|
+
"""
|
|
206
|
+
if self.num_qubits != 2:
|
|
207
|
+
# More complex check needed for >2 qubits
|
|
208
|
+
# For now, use entropy as heuristic
|
|
209
|
+
return self.entropy() > 0.1
|
|
210
|
+
|
|
211
|
+
# For 2 qubits: reshape state vector to 2x2 matrix
|
|
212
|
+
# State is |ψ⟩ = α|00⟩ + β|01⟩ + γ|10⟩ + δ|11⟩
|
|
213
|
+
# Separable if can be written as (a|0⟩ + b|1⟩) ⊗ (c|0⟩ + d|1⟩)
|
|
214
|
+
# This means the 2x2 matrix has rank 1
|
|
215
|
+
|
|
216
|
+
state_matrix = self.state_vector.reshape(2, 2)
|
|
217
|
+
|
|
218
|
+
# Check if matrix has rank 1 (separable) or rank 2 (entangled)
|
|
219
|
+
singular_values = np.linalg.svd(state_matrix, compute_uv=False)
|
|
220
|
+
|
|
221
|
+
# If second singular value is small, state is separable (rank 1)
|
|
222
|
+
# Use relative threshold to handle numerical precision
|
|
223
|
+
threshold = 1e-10 * max(singular_values)
|
|
224
|
+
return singular_values[1] > threshold
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def bloch_vector(self, qubit: int = 0) -> Tuple[float, float, float]:
|
|
228
|
+
"""
|
|
229
|
+
Get Bloch sphere coordinates for a single qubit
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
qubit: Index of qubit (for multi-qubit systems, traces out other qubits)
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
(x, y, z) coordinates on Bloch sphere
|
|
236
|
+
"""
|
|
237
|
+
if self.num_qubits == 1:
|
|
238
|
+
rho = np.outer(self.state_vector, self.state_vector.conj())
|
|
239
|
+
else:
|
|
240
|
+
# Partial trace to get single-qubit density matrix
|
|
241
|
+
rho = self._partial_trace(qubit)
|
|
242
|
+
|
|
243
|
+
# Pauli matrices
|
|
244
|
+
sigma_x = np.array([[0, 1], [1, 0]], dtype=complex)
|
|
245
|
+
sigma_y = np.array([[0, -1j], [1j, 0]], dtype=complex)
|
|
246
|
+
sigma_z = np.array([[1, 0], [0, -1]], dtype=complex)
|
|
247
|
+
|
|
248
|
+
x = np.real(np.trace(rho @ sigma_x))
|
|
249
|
+
y = np.real(np.trace(rho @ sigma_y))
|
|
250
|
+
z = np.real(np.trace(rho @ sigma_z))
|
|
251
|
+
|
|
252
|
+
return (x, y, z)
|
|
253
|
+
|
|
254
|
+
def _partial_trace(self, keep_qubit: int) -> np.ndarray:
|
|
255
|
+
"""Partial trace to get single-qubit density matrix"""
|
|
256
|
+
rho = np.zeros((2, 2), dtype=complex)
|
|
257
|
+
|
|
258
|
+
for i in range(self.dim):
|
|
259
|
+
for j in range(self.dim):
|
|
260
|
+
i_bit = (i >> keep_qubit) & 1
|
|
261
|
+
j_bit = (j >> keep_qubit) & 1
|
|
262
|
+
|
|
263
|
+
# Check if other qubits match
|
|
264
|
+
i_other = i & ~(1 << keep_qubit)
|
|
265
|
+
j_other = j & ~(1 << keep_qubit)
|
|
266
|
+
|
|
267
|
+
if i_other == j_other:
|
|
268
|
+
rho[i_bit, j_bit] += self.state_vector[i] * self.state_vector[j].conj()
|
|
269
|
+
|
|
270
|
+
return rho
|
|
271
|
+
|
|
272
|
+
def __repr__(self):
|
|
273
|
+
"""String representation of quantum state"""
|
|
274
|
+
state_str = []
|
|
275
|
+
for i, amplitude in enumerate(self.state_vector):
|
|
276
|
+
if abs(amplitude) > 1e-10:
|
|
277
|
+
binary = format(i, f'0{self.num_qubits}b')
|
|
278
|
+
real = np.real(amplitude)
|
|
279
|
+
imag = np.imag(amplitude)
|
|
280
|
+
|
|
281
|
+
if abs(imag) < 1e-10:
|
|
282
|
+
coef = f"{real:.3f}"
|
|
283
|
+
elif abs(real) < 1e-10:
|
|
284
|
+
coef = f"{imag:.3f}i"
|
|
285
|
+
else:
|
|
286
|
+
coef = f"{real:.3f}+{imag:.3f}i"
|
|
287
|
+
|
|
288
|
+
state_str.append(f"{coef}|{binary}>")
|
|
289
|
+
|
|
290
|
+
return " + ".join(state_str) if state_str else "0"
|
|
291
|
+
|
|
292
|
+
def __str__(self):
|
|
293
|
+
return self.__repr__()
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Debugger module for step-through quantum circuit execution"""
|
|
2
|
+
|
|
3
|
+
from quantum_debugger.debugger.debugger import QuantumDebugger
|
|
4
|
+
from quantum_debugger.debugger.breakpoints import BreakpointManager
|
|
5
|
+
from quantum_debugger.debugger.inspector import StateInspector
|
|
6
|
+
|
|
7
|
+
__all__ = ["QuantumDebugger", "BreakpointManager", "StateInspector"]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Breakpoint management for quantum circuit debugging
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Callable, List, Optional
|
|
6
|
+
from quantum_debugger.core.quantum_state import QuantumState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Breakpoint:
|
|
10
|
+
"""Represents a breakpoint in circuit execution"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, gate_index: Optional[int] = None,
|
|
13
|
+
condition: Optional[Callable[[QuantumState], bool]] = None,
|
|
14
|
+
description: str = ""):
|
|
15
|
+
"""
|
|
16
|
+
Initialize a breakpoint
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
gate_index: Index of gate to break at (None for conditional)
|
|
20
|
+
condition: Function that returns True when breakpoint should trigger
|
|
21
|
+
description: Human-readable description
|
|
22
|
+
"""
|
|
23
|
+
self.gate_index = gate_index
|
|
24
|
+
self.condition = condition
|
|
25
|
+
self.description = description
|
|
26
|
+
self.enabled = True
|
|
27
|
+
self.hit_count = 0
|
|
28
|
+
|
|
29
|
+
def should_break(self, current_gate: int, state: QuantumState) -> bool:
|
|
30
|
+
"""
|
|
31
|
+
Check if breakpoint should trigger
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
current_gate: Current gate index
|
|
35
|
+
state: Current quantum state
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
True if breakpoint should trigger
|
|
39
|
+
"""
|
|
40
|
+
if not self.enabled:
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
# Gate-based breakpoint
|
|
44
|
+
if self.gate_index is not None and current_gate == self.gate_index:
|
|
45
|
+
self.hit_count += 1
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
# Conditional breakpoint
|
|
49
|
+
if self.condition is not None:
|
|
50
|
+
try:
|
|
51
|
+
if self.condition(state):
|
|
52
|
+
self.hit_count += 1
|
|
53
|
+
return True
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
def __repr__(self):
|
|
60
|
+
if self.gate_index is not None:
|
|
61
|
+
return f"Breakpoint(gate={self.gate_index}, hits={self.hit_count})"
|
|
62
|
+
return f"Breakpoint(conditional, hits={self.hit_count})"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BreakpointManager:
|
|
66
|
+
"""Manages breakpoints for debugging"""
|
|
67
|
+
|
|
68
|
+
def __init__(self):
|
|
69
|
+
self.breakpoints: List[Breakpoint] = []
|
|
70
|
+
|
|
71
|
+
def add(self, gate_index: Optional[int] = None,
|
|
72
|
+
condition: Optional[Callable[[QuantumState], bool]] = None,
|
|
73
|
+
description: str = "") -> Breakpoint:
|
|
74
|
+
"""
|
|
75
|
+
Add a breakpoint
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
gate_index: Gate index to break at
|
|
79
|
+
condition: Condition function
|
|
80
|
+
description: Breakpoint description
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Created breakpoint
|
|
84
|
+
"""
|
|
85
|
+
bp = Breakpoint(gate_index, condition, description)
|
|
86
|
+
self.breakpoints.append(bp)
|
|
87
|
+
return bp
|
|
88
|
+
|
|
89
|
+
def remove(self, breakpoint: Breakpoint):
|
|
90
|
+
"""Remove a breakpoint"""
|
|
91
|
+
if breakpoint in self.breakpoints:
|
|
92
|
+
self.breakpoints.remove(breakpoint)
|
|
93
|
+
|
|
94
|
+
def clear(self):
|
|
95
|
+
"""Remove all breakpoints"""
|
|
96
|
+
self.breakpoints.clear()
|
|
97
|
+
|
|
98
|
+
def check(self, gate_index: int, state: QuantumState) -> Optional[Breakpoint]:
|
|
99
|
+
"""
|
|
100
|
+
Check if any breakpoint should trigger
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
gate_index: Current gate index
|
|
104
|
+
state: Current quantum state
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Triggered breakpoint or None
|
|
108
|
+
"""
|
|
109
|
+
for bp in self.breakpoints:
|
|
110
|
+
if bp.should_break(gate_index, state):
|
|
111
|
+
return bp
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def enable_all(self):
|
|
115
|
+
"""Enable all breakpoints"""
|
|
116
|
+
for bp in self.breakpoints:
|
|
117
|
+
bp.enabled = True
|
|
118
|
+
|
|
119
|
+
def disable_all(self):
|
|
120
|
+
"""Disable all breakpoints"""
|
|
121
|
+
for bp in self.breakpoints:
|
|
122
|
+
bp.enabled = False
|
|
123
|
+
|
|
124
|
+
def list_breakpoints(self) -> List[Breakpoint]:
|
|
125
|
+
"""Get list of all breakpoints"""
|
|
126
|
+
return self.breakpoints.copy()
|
|
127
|
+
|
|
128
|
+
def __len__(self):
|
|
129
|
+
return len(self.breakpoints)
|
|
130
|
+
|
|
131
|
+
def __repr__(self):
|
|
132
|
+
return f"BreakpointManager({len(self.breakpoints)} breakpoints)"
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main debugger class for step-through quantum circuit execution
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, List, Dict
|
|
6
|
+
from quantum_debugger.core.circuit import QuantumCircuit
|
|
7
|
+
from quantum_debugger.core.quantum_state import QuantumState
|
|
8
|
+
from quantum_debugger.debugger.breakpoints import BreakpointManager, Breakpoint
|
|
9
|
+
from quantum_debugger.debugger.inspector import StateInspector
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ExecutionState:
|
|
13
|
+
"""Represents the state at a point in circuit execution"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, gate_index: int, quantum_state: QuantumState, gate_name: str = ""):
|
|
16
|
+
self.gate_index = gate_index
|
|
17
|
+
self.quantum_state = quantum_state.copy()
|
|
18
|
+
self.gate_name = gate_name
|
|
19
|
+
|
|
20
|
+
def __repr__(self):
|
|
21
|
+
return f"ExecutionState(gate={self.gate_index}, {self.gate_name})"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class QuantumDebugger:
|
|
25
|
+
"""Interactive debugger for quantum circuits"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, circuit: QuantumCircuit, initial_state: Optional[QuantumState] = None):
|
|
28
|
+
"""
|
|
29
|
+
Initialize the debugger
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
circuit: Quantum circuit to debug
|
|
33
|
+
initial_state: Optional initial state (defaults to |0...0>)
|
|
34
|
+
"""
|
|
35
|
+
self.circuit = circuit
|
|
36
|
+
self.initial_state = initial_state or QuantumState(circuit.num_qubits)
|
|
37
|
+
self.current_state = self.initial_state.copy()
|
|
38
|
+
self.current_gate_index = 0
|
|
39
|
+
self.execution_history: List[ExecutionState] = []
|
|
40
|
+
self.breakpoints = BreakpointManager()
|
|
41
|
+
self.inspector = StateInspector()
|
|
42
|
+
|
|
43
|
+
# Store initial state in history
|
|
44
|
+
self.execution_history.append(
|
|
45
|
+
ExecutionState(0, self.current_state, "INITIAL")
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def reset(self):
|
|
49
|
+
"""Reset debugger to initial state"""
|
|
50
|
+
self.current_state = self.initial_state.copy()
|
|
51
|
+
self.current_gate_index = 0
|
|
52
|
+
self.execution_history = [
|
|
53
|
+
ExecutionState(0, self.current_state, "INITIAL")
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
def step(self, steps: int = 1) -> bool:
|
|
57
|
+
"""
|
|
58
|
+
Execute one or more gates
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
steps: Number of gates to execute
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
True if stepped successfully, False if at end
|
|
65
|
+
"""
|
|
66
|
+
for _ in range(steps):
|
|
67
|
+
if self.current_gate_index >= len(self.circuit.gates):
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
# Get current gate
|
|
71
|
+
gate = self.circuit.gates[self.current_gate_index]
|
|
72
|
+
|
|
73
|
+
# Apply gate
|
|
74
|
+
self.current_state.apply_gate(gate.matrix, gate.qubits)
|
|
75
|
+
|
|
76
|
+
# Update index
|
|
77
|
+
self.current_gate_index += 1
|
|
78
|
+
|
|
79
|
+
# Save state to history
|
|
80
|
+
self.execution_history.append(
|
|
81
|
+
ExecutionState(self.current_gate_index, self.current_state, str(gate))
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Check for breakpoints
|
|
85
|
+
bp = self.breakpoints.check(self.current_gate_index, self.current_state)
|
|
86
|
+
if bp:
|
|
87
|
+
print(f"⚠️ Breakpoint hit: {bp}")
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
def step_back(self, steps: int = 1) -> bool:
|
|
93
|
+
"""
|
|
94
|
+
Step backwards in execution history
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
steps: Number of steps to go back
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if stepped back successfully
|
|
101
|
+
"""
|
|
102
|
+
target_index = max(0, self.current_gate_index - steps)
|
|
103
|
+
|
|
104
|
+
if target_index < len(self.execution_history):
|
|
105
|
+
exec_state = self.execution_history[target_index]
|
|
106
|
+
self.current_state = exec_state.quantum_state.copy()
|
|
107
|
+
self.current_gate_index = exec_state.gate_index
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def run_to_end(self):
|
|
113
|
+
"""Execute all remaining gates"""
|
|
114
|
+
while self.step():
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
def run_until_breakpoint(self):
|
|
118
|
+
"""Execute until a breakpoint is hit or circuit ends"""
|
|
119
|
+
while self.current_gate_index < len(self.circuit.gates):
|
|
120
|
+
bp = self.breakpoints.check(self.current_gate_index, self.current_state)
|
|
121
|
+
if bp:
|
|
122
|
+
print(f"⚠️ Breakpoint hit at gate {self.current_gate_index}: {bp.description}")
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
if not self.step():
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
def set_breakpoint(self, gate: Optional[int] = None,
|
|
129
|
+
condition=None, description: str = "") -> Breakpoint:
|
|
130
|
+
"""
|
|
131
|
+
Set a breakpoint
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
gate: Gate index to break at
|
|
135
|
+
condition: Conditional function
|
|
136
|
+
description: Breakpoint description
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Created breakpoint
|
|
140
|
+
"""
|
|
141
|
+
return self.breakpoints.add(gate, condition, description)
|
|
142
|
+
|
|
143
|
+
def clear_breakpoints(self):
|
|
144
|
+
"""Remove all breakpoints"""
|
|
145
|
+
self.breakpoints.clear()
|
|
146
|
+
|
|
147
|
+
def inspect_state(self) -> Dict:
|
|
148
|
+
"""
|
|
149
|
+
Get detailed information about current state
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Dictionary with state information
|
|
153
|
+
"""
|
|
154
|
+
return self.inspector.get_state_summary(self.current_state)
|
|
155
|
+
|
|
156
|
+
def get_state(self) -> QuantumState:
|
|
157
|
+
"""Get current quantum state"""
|
|
158
|
+
return self.current_state.copy()
|
|
159
|
+
|
|
160
|
+
def visualize(self):
|
|
161
|
+
"""Print visualization of current state"""
|
|
162
|
+
print(f"\n{'='*60}")
|
|
163
|
+
print(f"Gate {self.current_gate_index}/{len(self.circuit.gates)}")
|
|
164
|
+
if self.current_gate_index < len(self.circuit.gates):
|
|
165
|
+
print(f"Next gate: {self.circuit.gates[self.current_gate_index]}")
|
|
166
|
+
else:
|
|
167
|
+
print("Circuit execution complete")
|
|
168
|
+
print(f"{'='*60}")
|
|
169
|
+
|
|
170
|
+
self.inspector.print_state_info(self.current_state)
|
|
171
|
+
|
|
172
|
+
def get_execution_trace(self) -> List[Dict]:
|
|
173
|
+
"""
|
|
174
|
+
Get full execution trace
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
List of execution states with information
|
|
178
|
+
"""
|
|
179
|
+
trace = []
|
|
180
|
+
for exec_state in self.execution_history:
|
|
181
|
+
trace.append({
|
|
182
|
+
'gate_index': exec_state.gate_index,
|
|
183
|
+
'gate_name': exec_state.gate_name,
|
|
184
|
+
'state_summary': self.inspector.get_state_summary(exec_state.quantum_state)
|
|
185
|
+
})
|
|
186
|
+
return trace
|
|
187
|
+
|
|
188
|
+
def compare_with_expected(self, expected_state: QuantumState) -> Dict:
|
|
189
|
+
"""
|
|
190
|
+
Compare current state with expected state
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
expected_state: Expected quantum state
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Comparison metrics
|
|
197
|
+
"""
|
|
198
|
+
return self.inspector.compare_states(self.current_state, expected_state)
|
|
199
|
+
|
|
200
|
+
def print_status(self):
|
|
201
|
+
"""Print current debugging status"""
|
|
202
|
+
print(f"\n{'='*60}")
|
|
203
|
+
print(f"DEBUGGER STATUS")
|
|
204
|
+
print(f"{'='*60}")
|
|
205
|
+
print(f"Circuit: {self.circuit.num_qubits} qubits, {len(self.circuit.gates)} gates")
|
|
206
|
+
print(f"Current position: Gate {self.current_gate_index}/{len(self.circuit.gates)}")
|
|
207
|
+
|
|
208
|
+
if self.current_gate_index < len(self.circuit.gates):
|
|
209
|
+
print(f"Next gate: {self.circuit.gates[self.current_gate_index]}")
|
|
210
|
+
else:
|
|
211
|
+
print("Status: Circuit execution complete")
|
|
212
|
+
|
|
213
|
+
print(f"\nBreakpoints: {len(self.breakpoints)}")
|
|
214
|
+
for bp in self.breakpoints.list_breakpoints():
|
|
215
|
+
status = "✓" if bp.enabled else "✗"
|
|
216
|
+
print(f" {status} {bp}")
|
|
217
|
+
|
|
218
|
+
print(f"\nExecution history: {len(self.execution_history)} states saved")
|
|
219
|
+
print(f"{'='*60}\n")
|
|
220
|
+
|
|
221
|
+
def __repr__(self):
|
|
222
|
+
return f"QuantumDebugger(gate {self.current_gate_index}/{len(self.circuit.gates)})"
|