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,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
QuantumDebugger - Interactive debugging and profiling for quantum circuits
|
|
3
|
+
|
|
4
|
+
A powerful Python library for step-through debugging, state inspection,
|
|
5
|
+
and performance analysis of quantum circuits.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__author__ = "Your Name"
|
|
10
|
+
__license__ = "MIT"
|
|
11
|
+
|
|
12
|
+
from quantum_debugger.core.circuit import QuantumCircuit
|
|
13
|
+
from quantum_debugger.core.quantum_state import QuantumState
|
|
14
|
+
from quantum_debugger.debugger.debugger import QuantumDebugger
|
|
15
|
+
from quantum_debugger.profiler.profiler import CircuitProfiler
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"QuantumCircuit",
|
|
19
|
+
"QuantumState",
|
|
20
|
+
"QuantumDebugger",
|
|
21
|
+
"CircuitProfiler",
|
|
22
|
+
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Core quantum computing components"""
|
|
2
|
+
|
|
3
|
+
from quantum_debugger.core.quantum_state import QuantumState
|
|
4
|
+
from quantum_debugger.core.circuit import QuantumCircuit
|
|
5
|
+
from quantum_debugger.core.gates import GateLibrary
|
|
6
|
+
|
|
7
|
+
__all__ = ["QuantumState", "QuantumCircuit", "GateLibrary"]
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum circuit representation and execution
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from typing import List, Optional, Union
|
|
7
|
+
from quantum_debugger.core.quantum_state import QuantumState
|
|
8
|
+
from quantum_debugger.core.gates import GateLibrary, Gate
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class QuantumCircuit:
|
|
12
|
+
"""Quantum circuit with gate operations"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, num_qubits: int, num_classical: int = 0):
|
|
15
|
+
"""
|
|
16
|
+
Initialize a quantum circuit
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
num_qubits: Number of quantum bits
|
|
20
|
+
num_classical: Number of classical bits for measurements
|
|
21
|
+
"""
|
|
22
|
+
self.num_qubits = num_qubits
|
|
23
|
+
self.num_classical = num_classical if num_classical > 0 else num_qubits
|
|
24
|
+
self.gates: List[Gate] = []
|
|
25
|
+
self.measurements: List[tuple] = []
|
|
26
|
+
self._initial_state = QuantumState(num_qubits)
|
|
27
|
+
|
|
28
|
+
def _add_gate(self, name: str, matrix: np.ndarray, qubits: Union[int, List[int]], params: dict = None):
|
|
29
|
+
"""Add a gate to the circuit"""
|
|
30
|
+
if isinstance(qubits, int):
|
|
31
|
+
qubits = [qubits]
|
|
32
|
+
|
|
33
|
+
gate = Gate(name, matrix, qubits, params)
|
|
34
|
+
self.gates.append(gate)
|
|
35
|
+
return self
|
|
36
|
+
|
|
37
|
+
# Single-qubit gates
|
|
38
|
+
def h(self, qubit: int):
|
|
39
|
+
"""Apply Hadamard gate"""
|
|
40
|
+
return self._add_gate('H', GateLibrary.H, qubit)
|
|
41
|
+
|
|
42
|
+
def x(self, qubit: int):
|
|
43
|
+
"""Apply Pauli-X (NOT) gate"""
|
|
44
|
+
return self._add_gate('X', GateLibrary.X, qubit)
|
|
45
|
+
|
|
46
|
+
def y(self, qubit: int):
|
|
47
|
+
"""Apply Pauli-Y gate"""
|
|
48
|
+
return self._add_gate('Y', GateLibrary.Y, qubit)
|
|
49
|
+
|
|
50
|
+
def z(self, qubit: int):
|
|
51
|
+
"""Apply Pauli-Z gate"""
|
|
52
|
+
return self._add_gate('Z', GateLibrary.Z, qubit)
|
|
53
|
+
|
|
54
|
+
def s(self, qubit: int):
|
|
55
|
+
"""Apply S (phase) gate"""
|
|
56
|
+
return self._add_gate('S', GateLibrary.S, qubit)
|
|
57
|
+
|
|
58
|
+
def t(self, qubit: int):
|
|
59
|
+
"""Apply T gate"""
|
|
60
|
+
return self._add_gate('T', GateLibrary.T, qubit)
|
|
61
|
+
|
|
62
|
+
def rx(self, theta: float, qubit: int):
|
|
63
|
+
"""Apply RX rotation gate"""
|
|
64
|
+
return self._add_gate('RX', GateLibrary.RX(theta), qubit, {'theta': theta})
|
|
65
|
+
|
|
66
|
+
def ry(self, theta: float, qubit: int):
|
|
67
|
+
"""Apply RY rotation gate"""
|
|
68
|
+
return self._add_gate('RY', GateLibrary.RY(theta), qubit, {'theta': theta})
|
|
69
|
+
|
|
70
|
+
def rz(self, theta: float, qubit: int):
|
|
71
|
+
"""Apply RZ rotation gate"""
|
|
72
|
+
return self._add_gate('RZ', GateLibrary.RZ(theta), qubit, {'theta': theta})
|
|
73
|
+
|
|
74
|
+
def phase(self, theta: float, qubit: int):
|
|
75
|
+
"""Apply phase shift gate"""
|
|
76
|
+
return self._add_gate('PHASE', GateLibrary.PHASE(theta), qubit, {'theta': theta})
|
|
77
|
+
|
|
78
|
+
# Two-qubit gates
|
|
79
|
+
def cnot(self, control: int, target: int):
|
|
80
|
+
"""Apply CNOT (controlled-NOT) gate"""
|
|
81
|
+
return self._add_gate('CNOT', GateLibrary.CNOT, [control, target])
|
|
82
|
+
|
|
83
|
+
def cx(self, control: int, target: int):
|
|
84
|
+
"""Alias for CNOT"""
|
|
85
|
+
return self.cnot(control, target)
|
|
86
|
+
|
|
87
|
+
def cz(self, control: int, target: int):
|
|
88
|
+
"""Apply CZ (controlled-Z) gate"""
|
|
89
|
+
return self._add_gate('CZ', GateLibrary.CZ, [control, target])
|
|
90
|
+
|
|
91
|
+
def swap(self, qubit1: int, qubit2: int):
|
|
92
|
+
"""Apply SWAP gate"""
|
|
93
|
+
return self._add_gate('SWAP', GateLibrary.SWAP, [qubit1, qubit2])
|
|
94
|
+
|
|
95
|
+
# Three-qubit gates
|
|
96
|
+
def toffoli(self, control1: int, control2: int, target: int):
|
|
97
|
+
"""Apply Toffoli (CCNOT) gate"""
|
|
98
|
+
return self._add_gate('TOFFOLI', GateLibrary.TOFFOLI, [control1, control2, target])
|
|
99
|
+
|
|
100
|
+
def ccx(self, control1: int, control2: int, target: int):
|
|
101
|
+
"""Alias for Toffoli"""
|
|
102
|
+
return self.toffoli(control1, control2, target)
|
|
103
|
+
|
|
104
|
+
# Measurements
|
|
105
|
+
def measure(self, qubit: int, classical_bit: int = None):
|
|
106
|
+
"""
|
|
107
|
+
Measure a qubit
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
qubit: Qubit index to measure
|
|
111
|
+
classical_bit: Classical bit to store result (defaults to same as qubit)
|
|
112
|
+
"""
|
|
113
|
+
if classical_bit is None:
|
|
114
|
+
classical_bit = qubit
|
|
115
|
+
self.measurements.append((qubit, classical_bit))
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
def measure_all(self):
|
|
119
|
+
"""Measure all qubits"""
|
|
120
|
+
for i in range(self.num_qubits):
|
|
121
|
+
self.measure(i, i)
|
|
122
|
+
return self
|
|
123
|
+
|
|
124
|
+
# Circuit information
|
|
125
|
+
def depth(self) -> int:
|
|
126
|
+
"""Calculate circuit depth (number of gate layers)"""
|
|
127
|
+
if not self.gates:
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
# Track when each qubit is last used
|
|
131
|
+
qubit_times = [0] * self.num_qubits
|
|
132
|
+
|
|
133
|
+
for gate in self.gates:
|
|
134
|
+
# Get max time of qubits involved
|
|
135
|
+
max_time = max(qubit_times[q] for q in gate.qubits)
|
|
136
|
+
|
|
137
|
+
# Update all involved qubits
|
|
138
|
+
for q in gate.qubits:
|
|
139
|
+
qubit_times[q] = max_time + 1
|
|
140
|
+
|
|
141
|
+
return max(qubit_times)
|
|
142
|
+
|
|
143
|
+
def size(self) -> int:
|
|
144
|
+
"""Total number of gates"""
|
|
145
|
+
return len(self.gates)
|
|
146
|
+
|
|
147
|
+
def count_gates(self, gate_name: str = None) -> int:
|
|
148
|
+
"""
|
|
149
|
+
Count gates of specific type
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
gate_name: Name of gate to count (None for all gates)
|
|
153
|
+
"""
|
|
154
|
+
if gate_name is None:
|
|
155
|
+
return len(self.gates)
|
|
156
|
+
return sum(1 for g in self.gates if g.name == gate_name)
|
|
157
|
+
|
|
158
|
+
# Execution
|
|
159
|
+
def run(self, shots: int = 1, initial_state: Optional[QuantumState] = None) -> dict:
|
|
160
|
+
"""
|
|
161
|
+
Execute the circuit
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
shots: Number of times to run the circuit
|
|
165
|
+
initial_state: Optional initial state (defaults to |0...0>)
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Dictionary with measurement results and statistics
|
|
169
|
+
"""
|
|
170
|
+
results = []
|
|
171
|
+
|
|
172
|
+
for _ in range(shots):
|
|
173
|
+
state = initial_state.copy() if initial_state else QuantumState(self.num_qubits)
|
|
174
|
+
|
|
175
|
+
# Apply all gates
|
|
176
|
+
for gate in self.gates:
|
|
177
|
+
state.apply_gate(gate.matrix, gate.qubits)
|
|
178
|
+
|
|
179
|
+
# Perform measurements
|
|
180
|
+
classical_bits = [0] * self.num_classical
|
|
181
|
+
for qubit, classical_bit in self.measurements:
|
|
182
|
+
classical_bits[classical_bit] = state.measure(qubit)
|
|
183
|
+
|
|
184
|
+
results.append(classical_bits)
|
|
185
|
+
|
|
186
|
+
# Analyze results
|
|
187
|
+
counts = {}
|
|
188
|
+
for result in results:
|
|
189
|
+
key = ''.join(map(str, result))
|
|
190
|
+
counts[key] = counts.get(key, 0) + 1
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
'counts': counts,
|
|
194
|
+
'results': results,
|
|
195
|
+
'shots': shots
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
def get_statevector(self, initial_state: Optional[QuantumState] = None) -> QuantumState:
|
|
199
|
+
"""
|
|
200
|
+
Get final state vector without measurements
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
initial_state: Optional initial state
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Final quantum state
|
|
207
|
+
"""
|
|
208
|
+
state = initial_state.copy() if initial_state else QuantumState(self.num_qubits)
|
|
209
|
+
|
|
210
|
+
for gate in self.gates:
|
|
211
|
+
state.apply_gate(gate.matrix, gate.qubits)
|
|
212
|
+
|
|
213
|
+
return state
|
|
214
|
+
|
|
215
|
+
# Visualization helpers
|
|
216
|
+
def draw(self, output: str = 'text') -> str:
|
|
217
|
+
"""
|
|
218
|
+
Draw the circuit
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
output: Output format ('text' for ASCII art)
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
String representation of circuit
|
|
225
|
+
"""
|
|
226
|
+
if output == 'text':
|
|
227
|
+
return self._draw_text()
|
|
228
|
+
return str(self)
|
|
229
|
+
|
|
230
|
+
def _draw_text(self) -> str:
|
|
231
|
+
"""Draw circuit as ASCII art"""
|
|
232
|
+
lines = []
|
|
233
|
+
|
|
234
|
+
# Header
|
|
235
|
+
lines.append(f"Circuit with {self.num_qubits} qubits, {len(self.gates)} gates")
|
|
236
|
+
lines.append("")
|
|
237
|
+
|
|
238
|
+
# Qubit lines
|
|
239
|
+
for q in range(self.num_qubits):
|
|
240
|
+
line = f"q{q}: |0>─"
|
|
241
|
+
|
|
242
|
+
for i, gate in enumerate(self.gates):
|
|
243
|
+
if q in gate.qubits:
|
|
244
|
+
# This qubit is involved in this gate
|
|
245
|
+
if len(gate.qubits) == 1:
|
|
246
|
+
# Single-qubit gate
|
|
247
|
+
gate_str = f"[{gate.name}]"
|
|
248
|
+
elif gate.qubits[0] == q:
|
|
249
|
+
# First qubit (control or first target)
|
|
250
|
+
gate_str = "●" if gate.name in ['CNOT', 'CZ'] else "┬"
|
|
251
|
+
else:
|
|
252
|
+
# Target qubit
|
|
253
|
+
gate_str = "⊕" if gate.name == 'CNOT' else "┴"
|
|
254
|
+
|
|
255
|
+
line += gate_str + "─"
|
|
256
|
+
else:
|
|
257
|
+
# Not involved, just continue line
|
|
258
|
+
line += "─" * (len(gate.name) + 3)
|
|
259
|
+
|
|
260
|
+
lines.append(line)
|
|
261
|
+
|
|
262
|
+
return "\n".join(lines)
|
|
263
|
+
|
|
264
|
+
def __repr__(self):
|
|
265
|
+
gate_str = ", ".join(str(g) for g in self.gates)
|
|
266
|
+
return f"QuantumCircuit({self.num_qubits} qubits, gates=[{gate_str}])"
|
|
267
|
+
|
|
268
|
+
def __str__(self):
|
|
269
|
+
return self.draw()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum gate definitions and operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from typing import Dict, Callable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GateLibrary:
|
|
10
|
+
"""Library of standard quantum gates"""
|
|
11
|
+
|
|
12
|
+
# Single-qubit gates
|
|
13
|
+
I = np.array([[1, 0], [0, 1]], dtype=complex) # Identity
|
|
14
|
+
X = np.array([[0, 1], [1, 0]], dtype=complex) # Pauli-X (NOT)
|
|
15
|
+
Y = np.array([[0, -1j], [1j, 0]], dtype=complex) # Pauli-Y
|
|
16
|
+
Z = np.array([[1, 0], [0, -1]], dtype=complex) # Pauli-Z
|
|
17
|
+
H = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2) # Hadamard
|
|
18
|
+
S = np.array([[1, 0], [0, 1j]], dtype=complex) # Phase gate
|
|
19
|
+
T = np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]], dtype=complex) # T gate
|
|
20
|
+
|
|
21
|
+
# Rotation gates (parameterized)
|
|
22
|
+
@staticmethod
|
|
23
|
+
def RX(theta: float) -> np.ndarray:
|
|
24
|
+
"""Rotation around X-axis"""
|
|
25
|
+
return np.array([
|
|
26
|
+
[np.cos(theta / 2), -1j * np.sin(theta / 2)],
|
|
27
|
+
[-1j * np.sin(theta / 2), np.cos(theta / 2)]
|
|
28
|
+
], dtype=complex)
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def RY(theta: float) -> np.ndarray:
|
|
32
|
+
"""Rotation around Y-axis"""
|
|
33
|
+
return np.array([
|
|
34
|
+
[np.cos(theta / 2), -np.sin(theta / 2)],
|
|
35
|
+
[np.sin(theta / 2), np.cos(theta / 2)]
|
|
36
|
+
], dtype=complex)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def RZ(theta: float) -> np.ndarray:
|
|
40
|
+
"""Rotation around Z-axis"""
|
|
41
|
+
return np.array([
|
|
42
|
+
[np.exp(-1j * theta / 2), 0],
|
|
43
|
+
[0, np.exp(1j * theta / 2)]
|
|
44
|
+
], dtype=complex)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def PHASE(theta: float) -> np.ndarray:
|
|
48
|
+
"""Phase shift gate"""
|
|
49
|
+
return np.array([
|
|
50
|
+
[1, 0],
|
|
51
|
+
[0, np.exp(1j * theta)]
|
|
52
|
+
], dtype=complex)
|
|
53
|
+
|
|
54
|
+
# Two-qubit gates (little-endian: qubit 0 is LSB)
|
|
55
|
+
# CNOT: control=qubit 0, target=qubit 1
|
|
56
|
+
# Flips target when control is 1: |10⟩↔|11⟩ (indices 1↔3)
|
|
57
|
+
CNOT = np.array([
|
|
58
|
+
[1, 0, 0, 0], # |00⟩ → |00⟩
|
|
59
|
+
[0, 0, 0, 1], # |10⟩ → |11⟩
|
|
60
|
+
[0, 0, 1, 0], # |01⟩ → |01⟩
|
|
61
|
+
[0, 1, 0, 0], # |11⟩ → |10⟩
|
|
62
|
+
], dtype=complex)
|
|
63
|
+
|
|
64
|
+
CZ = np.array([
|
|
65
|
+
[1, 0, 0, 0],
|
|
66
|
+
[0, 1, 0, 0],
|
|
67
|
+
[0, 0, 1, 0],
|
|
68
|
+
[0, 0, 0, -1]
|
|
69
|
+
], dtype=complex)
|
|
70
|
+
|
|
71
|
+
SWAP = np.array([
|
|
72
|
+
[1, 0, 0, 0],
|
|
73
|
+
[0, 0, 1, 0],
|
|
74
|
+
[0, 1, 0, 0],
|
|
75
|
+
[0, 0, 0, 1]
|
|
76
|
+
], dtype=complex)
|
|
77
|
+
|
|
78
|
+
# Three-qubit gates
|
|
79
|
+
# Toffoli (CCNOT) for little-endian: controls on qubits 0,1; target is qubit 2
|
|
80
|
+
# Flips target when both controls are 1 (indices 3↔7 in little-endian)
|
|
81
|
+
TOFFOLI = np.array([
|
|
82
|
+
[1, 0, 0, 0, 0, 0, 0, 0],
|
|
83
|
+
[0, 1, 0, 0, 0, 0, 0, 0],
|
|
84
|
+
[0, 0, 1, 0, 0, 0, 0, 0],
|
|
85
|
+
[0, 0, 0, 0, 0, 0, 0, 1], # |110⟩ ↔ |111⟩
|
|
86
|
+
[0, 0, 0, 0, 1, 0, 0, 0],
|
|
87
|
+
[0, 0, 0, 0, 0, 1, 0, 0],
|
|
88
|
+
[0, 0, 0, 0, 0, 0, 1, 0],
|
|
89
|
+
[0, 0, 0, 1, 0, 0, 0, 0], # |111⟩ ↔ |110⟩
|
|
90
|
+
], dtype=complex)
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def controlled_gate(gate: np.ndarray) -> np.ndarray:
|
|
94
|
+
"""Create a controlled version of a single-qubit gate"""
|
|
95
|
+
n = gate.shape[0]
|
|
96
|
+
controlled = np.eye(2 * n, dtype=complex)
|
|
97
|
+
controlled[n:, n:] = gate
|
|
98
|
+
return controlled
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def tensor_product(*gates) -> np.ndarray:
|
|
102
|
+
"""Compute tensor product of multiple gates"""
|
|
103
|
+
result = gates[0]
|
|
104
|
+
for gate in gates[1:]:
|
|
105
|
+
result = np.kron(result, gate)
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class Gate:
|
|
110
|
+
"""Represents a quantum gate operation"""
|
|
111
|
+
|
|
112
|
+
def __init__(self, name: str, matrix: np.ndarray, qubits: list, params: dict = None):
|
|
113
|
+
"""
|
|
114
|
+
Initialize a gate
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
name: Gate name (e.g., 'H', 'CNOT', 'RX')
|
|
118
|
+
matrix: Unitary matrix representing the gate
|
|
119
|
+
qubits: List of qubit indices the gate acts on
|
|
120
|
+
params: Optional parameters (e.g., rotation angles)
|
|
121
|
+
"""
|
|
122
|
+
self.name = name
|
|
123
|
+
self.matrix = matrix
|
|
124
|
+
self.qubits = qubits if isinstance(qubits, list) else [qubits]
|
|
125
|
+
self.params = params or {}
|
|
126
|
+
|
|
127
|
+
def __repr__(self):
|
|
128
|
+
qubit_str = ','.join(map(str, self.qubits))
|
|
129
|
+
if self.params:
|
|
130
|
+
param_str = ','.join(f"{k}={v}" for k, v in self.params.items())
|
|
131
|
+
return f"{self.name}({param_str})[q{qubit_str}]"
|
|
132
|
+
return f"{self.name}[q{qubit_str}]"
|
|
133
|
+
|
|
134
|
+
def __str__(self):
|
|
135
|
+
return self.__repr__()
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def num_qubits(self):
|
|
139
|
+
"""Number of qubits this gate acts on"""
|
|
140
|
+
return len(self.qubits)
|
|
141
|
+
|
|
142
|
+
def is_controlled(self):
|
|
143
|
+
"""Check if this is a controlled gate"""
|
|
144
|
+
return self.num_qubits > 1 and self.name.startswith('C')
|
|
145
|
+
|
|
146
|
+
def dagger(self):
|
|
147
|
+
"""Return the conjugate transpose of this gate"""
|
|
148
|
+
return Gate(
|
|
149
|
+
name=f"{self.name}†",
|
|
150
|
+
matrix=self.matrix.conj().T,
|
|
151
|
+
qubits=self.qubits,
|
|
152
|
+
params=self.params
|
|
153
|
+
)
|