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.
@@ -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)})"