qumat 0.0.1__py3-none-any.whl → 0.5.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.
- qumat/__init__.py +20 -0
- qumat/amazon_braket_backend.py +122 -17
- qumat/cirq_backend.py +105 -16
- qumat/qdp.py +63 -0
- qumat/qiskit_backend.py +125 -24
- qumat/qumat.py +508 -13
- qumat-0.5.0.dist-info/METADATA +111 -0
- qumat-0.5.0.dist-info/RECORD +11 -0
- {qumat-0.0.1.dist-info → qumat-0.5.0.dist-info}/WHEEL +1 -1
- {qumat-0.0.1.dist-info → qumat-0.5.0.dist-info/licenses}/LICENSE +0 -112
- qumat-0.5.0.dist-info/licenses/NOTICE +5 -0
- qumat-0.0.1.dist-info/METADATA +0 -77
- qumat-0.0.1.dist-info/NOTICE +0 -41
- qumat-0.0.1.dist-info/RECORD +0 -10
qumat/qumat.py
CHANGED
|
@@ -16,75 +16,570 @@
|
|
|
16
16
|
#
|
|
17
17
|
from importlib import import_module
|
|
18
18
|
|
|
19
|
+
|
|
19
20
|
class QuMat:
|
|
21
|
+
"""Unified interface for quantum circuit operations across multiple backends.
|
|
22
|
+
|
|
23
|
+
Provides a consistent API for creating and manipulating quantum circuits
|
|
24
|
+
using different quantum computing backends (Qiskit, Cirq, Amazon Braket).
|
|
25
|
+
Abstracts backend-specific details for gate operations, circuit execution,
|
|
26
|
+
and state measurement.
|
|
27
|
+
|
|
28
|
+
:param backend_config: Configuration dictionary for the quantum backend.
|
|
29
|
+
Must contain ``backend_name`` (str) and ``backend_options`` (dict).
|
|
30
|
+
The ``backend_options`` should include ``simulator_type`` and ``shots``.
|
|
31
|
+
:type backend_config: dict
|
|
32
|
+
"""
|
|
33
|
+
|
|
20
34
|
def __init__(self, backend_config):
|
|
35
|
+
"""Create a QuMat instance with the specified backend configuration.
|
|
36
|
+
|
|
37
|
+
:param backend_config: Configuration dictionary containing backend name
|
|
38
|
+
and options. Required keys:
|
|
39
|
+
- ``backend_name``: Name of the backend (e.g., "qiskit", "cirq", "amazon_braket")
|
|
40
|
+
- ``backend_options``: Dictionary with backend-specific options
|
|
41
|
+
:type backend_config: dict
|
|
42
|
+
:raises ImportError: If the specified backend module cannot be imported.
|
|
43
|
+
:raises ValueError: If backend_config is not a dictionary.
|
|
44
|
+
:raises KeyError: If required configuration keys are missing.
|
|
45
|
+
"""
|
|
46
|
+
if not isinstance(backend_config, dict):
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"backend_config must be a dictionary, got {type(backend_config).__name__}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if "backend_name" not in backend_config:
|
|
52
|
+
raise KeyError(
|
|
53
|
+
"backend_config is missing required key 'backend_name'. "
|
|
54
|
+
"Please provide a backend name (e.g., 'qiskit', 'cirq', 'amazon_braket')"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if "backend_options" not in backend_config:
|
|
58
|
+
raise KeyError(
|
|
59
|
+
"backend_config is missing required key 'backend_options'. "
|
|
60
|
+
"Please provide a dictionary with backend-specific options "
|
|
61
|
+
)
|
|
62
|
+
|
|
21
63
|
self.backend_config = backend_config
|
|
22
|
-
self.backend_name = backend_config[
|
|
23
|
-
self.backend_module = import_module(
|
|
64
|
+
self.backend_name = backend_config["backend_name"]
|
|
65
|
+
self.backend_module = import_module(
|
|
66
|
+
f".{self.backend_name}_backend", package="qumat"
|
|
67
|
+
)
|
|
24
68
|
self.backend = self.backend_module.initialize_backend(backend_config)
|
|
25
69
|
self.circuit = None
|
|
70
|
+
self.num_qubits = None
|
|
26
71
|
self.parameters = {}
|
|
27
72
|
|
|
28
|
-
def create_empty_circuit(self, num_qubits):
|
|
73
|
+
def create_empty_circuit(self, num_qubits: int | None = None):
|
|
74
|
+
"""Create an empty quantum circuit with the specified number of qubits.
|
|
75
|
+
|
|
76
|
+
Must be called before applying any gates or executing operations.
|
|
77
|
+
|
|
78
|
+
:param num_qubits: Number of qubits in the circuit. If ``None``,
|
|
79
|
+
creates a circuit without pre-allocated qubits.
|
|
80
|
+
:type num_qubits: int | None, optional
|
|
81
|
+
"""
|
|
82
|
+
self.num_qubits = num_qubits
|
|
29
83
|
self.circuit = self.backend_module.create_empty_circuit(num_qubits)
|
|
30
84
|
|
|
85
|
+
def _ensure_circuit_initialized(self):
|
|
86
|
+
"""Ensure the circuit has been created before operations.
|
|
87
|
+
|
|
88
|
+
Checks if the circuit has been initialized via ``create_empty_circuit()``.
|
|
89
|
+
Raises ``RuntimeError`` if not initialized.
|
|
90
|
+
|
|
91
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
92
|
+
"""
|
|
93
|
+
if self.circuit is None:
|
|
94
|
+
raise RuntimeError(
|
|
95
|
+
"circuit not initialized. call create_empty_circuit(num_qubits) "
|
|
96
|
+
"before applying gates or executing operations."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def _validate_qubit_index(self, qubit_index, param_name="qubit_index"):
|
|
100
|
+
"""validate qubit index is within circuit bounds.
|
|
101
|
+
|
|
102
|
+
:param qubit_index: the qubit index to validate.
|
|
103
|
+
:type qubit_index: int
|
|
104
|
+
:param param_name: name of the parameter for error messages.
|
|
105
|
+
:type param_name: str
|
|
106
|
+
:raises TypeError: if qubit_index is not an integer.
|
|
107
|
+
:raises ValueError: if qubit_index is negative or out of range.
|
|
108
|
+
"""
|
|
109
|
+
if not isinstance(qubit_index, int):
|
|
110
|
+
raise TypeError(
|
|
111
|
+
f"{param_name} must be an integer, got {type(qubit_index).__name__}"
|
|
112
|
+
)
|
|
113
|
+
if qubit_index < 0:
|
|
114
|
+
raise ValueError(f"{param_name} cannot be negative, got {qubit_index}")
|
|
115
|
+
if self.num_qubits is not None and qubit_index >= self.num_qubits:
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"{param_name} {qubit_index} out of range for circuit with "
|
|
118
|
+
f"{self.num_qubits} qubits (valid range: 0-{self.num_qubits - 1})"
|
|
119
|
+
)
|
|
120
|
+
|
|
31
121
|
def apply_not_gate(self, qubit_index):
|
|
122
|
+
"""Apply a NOT gate (Pauli-X gate) to the specified qubit.
|
|
123
|
+
|
|
124
|
+
Flips the qubit state from |0⟩ to |1⟩ or |1⟩ to |0⟩.
|
|
125
|
+
Equivalent to the Pauli-X gate.
|
|
126
|
+
|
|
127
|
+
:param qubit_index: Index of the qubit.
|
|
128
|
+
:type qubit_index: int
|
|
129
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
130
|
+
"""
|
|
131
|
+
self._ensure_circuit_initialized()
|
|
132
|
+
self._validate_qubit_index(qubit_index)
|
|
32
133
|
self.backend_module.apply_not_gate(self.circuit, qubit_index)
|
|
33
134
|
|
|
34
135
|
def apply_hadamard_gate(self, qubit_index):
|
|
136
|
+
"""Apply a Hadamard gate to the specified qubit.
|
|
137
|
+
|
|
138
|
+
Creates a superposition state, transforming |0⟩ to (|0⟩ + |1⟩)/√2
|
|
139
|
+
and |1⟩ to (|0⟩ - |1⟩)/√2.
|
|
140
|
+
|
|
141
|
+
:param qubit_index: Index of the qubit.
|
|
142
|
+
:type qubit_index: int
|
|
143
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
144
|
+
"""
|
|
145
|
+
self._ensure_circuit_initialized()
|
|
146
|
+
self._validate_qubit_index(qubit_index)
|
|
35
147
|
self.backend_module.apply_hadamard_gate(self.circuit, qubit_index)
|
|
36
148
|
|
|
37
149
|
def apply_cnot_gate(self, control_qubit_index, target_qubit_index):
|
|
38
|
-
|
|
150
|
+
"""Apply a Controlled-NOT (CNOT) gate between two qubits.
|
|
151
|
+
|
|
152
|
+
Fundamental for entangling qubits. Flips the target qubit if and only
|
|
153
|
+
if the control qubit is in the |1⟩ state.
|
|
154
|
+
|
|
155
|
+
:param control_qubit_index: Index of the control qubit.
|
|
156
|
+
:type control_qubit_index: int
|
|
157
|
+
:param target_qubit_index: Index of the target qubit.
|
|
158
|
+
:type target_qubit_index: int
|
|
159
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
160
|
+
"""
|
|
161
|
+
self._ensure_circuit_initialized()
|
|
162
|
+
self._validate_qubit_index(control_qubit_index, "control_qubit_index")
|
|
163
|
+
self._validate_qubit_index(target_qubit_index, "target_qubit_index")
|
|
164
|
+
self.backend_module.apply_cnot_gate(
|
|
165
|
+
self.circuit, control_qubit_index, target_qubit_index
|
|
166
|
+
)
|
|
39
167
|
|
|
40
|
-
def apply_toffoli_gate(
|
|
41
|
-
self
|
|
168
|
+
def apply_toffoli_gate(
|
|
169
|
+
self, control_qubit_index1, control_qubit_index2, target_qubit_index
|
|
170
|
+
):
|
|
171
|
+
"""Apply a Toffoli gate (CCX gate) to three qubits.
|
|
172
|
+
|
|
173
|
+
Acts as a quantum AND gate. Flips the target qubit if and only if
|
|
174
|
+
both control qubits are in the |1⟩ state.
|
|
175
|
+
|
|
176
|
+
:param control_qubit_index1: Index of the first control qubit.
|
|
177
|
+
:type control_qubit_index1: int
|
|
178
|
+
:param control_qubit_index2: Index of the second control qubit.
|
|
179
|
+
:type control_qubit_index2: int
|
|
180
|
+
:param target_qubit_index: Index of the target qubit.
|
|
181
|
+
:type target_qubit_index: int
|
|
182
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
183
|
+
"""
|
|
184
|
+
self._ensure_circuit_initialized()
|
|
185
|
+
self._validate_qubit_index(control_qubit_index1, "control_qubit_index1")
|
|
186
|
+
self._validate_qubit_index(control_qubit_index2, "control_qubit_index2")
|
|
187
|
+
self._validate_qubit_index(target_qubit_index, "target_qubit_index")
|
|
188
|
+
self.backend_module.apply_toffoli_gate(
|
|
189
|
+
self.circuit, control_qubit_index1, control_qubit_index2, target_qubit_index
|
|
190
|
+
)
|
|
42
191
|
|
|
43
192
|
def apply_swap_gate(self, qubit_index1, qubit_index2):
|
|
193
|
+
"""Swap the states of two qubits.
|
|
194
|
+
|
|
195
|
+
:param qubit_index1: Index of the first qubit.
|
|
196
|
+
:type qubit_index1: int
|
|
197
|
+
:param qubit_index2: Index of the second qubit.
|
|
198
|
+
:type qubit_index2: int
|
|
199
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
200
|
+
"""
|
|
201
|
+
self._ensure_circuit_initialized()
|
|
202
|
+
self._validate_qubit_index(qubit_index1, "qubit_index1")
|
|
203
|
+
self._validate_qubit_index(qubit_index2, "qubit_index2")
|
|
44
204
|
self.backend_module.apply_swap_gate(self.circuit, qubit_index1, qubit_index2)
|
|
45
205
|
|
|
206
|
+
def apply_cswap_gate(
|
|
207
|
+
self, control_qubit_index, target_qubit_index1, target_qubit_index2
|
|
208
|
+
):
|
|
209
|
+
"""Apply a controlled-SWAP (Fredkin) gate.
|
|
210
|
+
|
|
211
|
+
Swaps the states of two target qubits if and only if the control
|
|
212
|
+
qubit is in the |1⟩ state.
|
|
213
|
+
|
|
214
|
+
:param control_qubit_index: Index of the control qubit.
|
|
215
|
+
:type control_qubit_index: int
|
|
216
|
+
:param target_qubit_index1: Index of the first target qubit.
|
|
217
|
+
:type target_qubit_index1: int
|
|
218
|
+
:param target_qubit_index2: Index of the second target qubit.
|
|
219
|
+
:type target_qubit_index2: int
|
|
220
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
221
|
+
"""
|
|
222
|
+
self._ensure_circuit_initialized()
|
|
223
|
+
self._validate_qubit_index(control_qubit_index, "control_qubit_index")
|
|
224
|
+
self._validate_qubit_index(target_qubit_index1, "target_qubit_index1")
|
|
225
|
+
self._validate_qubit_index(target_qubit_index2, "target_qubit_index2")
|
|
226
|
+
self.backend_module.apply_cswap_gate(
|
|
227
|
+
self.circuit, control_qubit_index, target_qubit_index1, target_qubit_index2
|
|
228
|
+
)
|
|
229
|
+
|
|
46
230
|
def apply_pauli_x_gate(self, qubit_index):
|
|
231
|
+
"""Apply a Pauli-X gate to the specified qubit.
|
|
232
|
+
|
|
233
|
+
Equivalent to the NOT gate. Flips the qubit state from |0⟩ to |1⟩
|
|
234
|
+
or |1⟩ to |0⟩.
|
|
235
|
+
|
|
236
|
+
:param qubit_index: Index of the qubit.
|
|
237
|
+
:type qubit_index: int
|
|
238
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
239
|
+
"""
|
|
240
|
+
self._ensure_circuit_initialized()
|
|
241
|
+
self._validate_qubit_index(qubit_index)
|
|
47
242
|
self.backend_module.apply_pauli_x_gate(self.circuit, qubit_index)
|
|
48
243
|
|
|
49
244
|
def apply_pauli_y_gate(self, qubit_index):
|
|
245
|
+
"""Apply a Pauli-Y gate to the specified qubit.
|
|
246
|
+
|
|
247
|
+
Rotates the qubit around the Y-axis of the Bloch sphere, affecting
|
|
248
|
+
both phase and amplitude.
|
|
249
|
+
|
|
250
|
+
:param qubit_index: Index of the qubit.
|
|
251
|
+
:type qubit_index: int
|
|
252
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
253
|
+
"""
|
|
254
|
+
self._ensure_circuit_initialized()
|
|
255
|
+
self._validate_qubit_index(qubit_index)
|
|
50
256
|
self.backend_module.apply_pauli_y_gate(self.circuit, qubit_index)
|
|
51
257
|
|
|
52
258
|
def apply_pauli_z_gate(self, qubit_index):
|
|
259
|
+
"""Apply a Pauli-Z gate to the specified qubit.
|
|
260
|
+
|
|
261
|
+
Rotates the qubit around the Z-axis of the Bloch sphere, altering
|
|
262
|
+
the phase without changing the amplitude.
|
|
263
|
+
|
|
264
|
+
:param qubit_index: Index of the qubit.
|
|
265
|
+
:type qubit_index: int
|
|
266
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
267
|
+
"""
|
|
268
|
+
self._ensure_circuit_initialized()
|
|
269
|
+
self._validate_qubit_index(qubit_index)
|
|
53
270
|
self.backend_module.apply_pauli_z_gate(self.circuit, qubit_index)
|
|
54
271
|
|
|
272
|
+
def apply_t_gate(self, qubit_index):
|
|
273
|
+
"""Apply a T-gate (π/8 gate) to the specified qubit.
|
|
274
|
+
|
|
275
|
+
Applies a relative pi/4 phase (multiplies the |1> state by e^{i*pi/4}).
|
|
276
|
+
Essential for universal quantum computation when combined with
|
|
277
|
+
Hadamard and CNOT gates.
|
|
278
|
+
|
|
279
|
+
:param qubit_index: Index of the qubit.
|
|
280
|
+
:type qubit_index: int
|
|
281
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
282
|
+
"""
|
|
283
|
+
self._ensure_circuit_initialized()
|
|
284
|
+
self._validate_qubit_index(qubit_index)
|
|
285
|
+
self.backend_module.apply_t_gate(self.circuit, qubit_index)
|
|
286
|
+
|
|
55
287
|
def execute_circuit(self, parameter_values=None):
|
|
288
|
+
"""Execute the quantum circuit and return the measurement results.
|
|
289
|
+
|
|
290
|
+
Runs the circuit on the configured backend. For parameterized circuits,
|
|
291
|
+
provide parameter values to bind before execution.
|
|
292
|
+
|
|
293
|
+
:param parameter_values: Dictionary mapping parameter names to numerical
|
|
294
|
+
values. Binds these values to circuit parameters before execution.
|
|
295
|
+
:type parameter_values: dict, optional
|
|
296
|
+
:returns: Measurement results. Format depends on the backend:
|
|
297
|
+
- Qiskit/Braket: Dictionary with state strings as keys and counts as values
|
|
298
|
+
- Cirq: List of dictionaries with integer states as keys
|
|
299
|
+
:rtype: dict | list[dict]
|
|
300
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
301
|
+
"""
|
|
302
|
+
self._ensure_circuit_initialized()
|
|
303
|
+
if self.num_qubits == 0:
|
|
304
|
+
shots = self.backend_config["backend_options"].get("shots", 1)
|
|
305
|
+
if self.backend_name == "cirq":
|
|
306
|
+
return [{0: shots}]
|
|
307
|
+
else:
|
|
308
|
+
return {"": shots}
|
|
309
|
+
|
|
56
310
|
if parameter_values:
|
|
57
311
|
self.bind_parameters(parameter_values)
|
|
58
|
-
|
|
59
|
-
|
|
312
|
+
|
|
313
|
+
# Only pass bound parameters (non-None values) to backend
|
|
314
|
+
bound_parameters = {
|
|
315
|
+
param: value
|
|
316
|
+
for param, value in self.parameters.items()
|
|
317
|
+
if value is not None
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
# Check if there are unbound parameters in the circuit
|
|
321
|
+
if self.parameters and len(bound_parameters) < len(self.parameters):
|
|
322
|
+
unbound_params = [
|
|
323
|
+
p for p in self.parameters.keys() if self.parameters[p] is None
|
|
324
|
+
]
|
|
325
|
+
raise ValueError(
|
|
326
|
+
f"Circuit contains unbound parameters: {unbound_params}. "
|
|
327
|
+
f"Please provide parameter_values when executing the circuit."
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
self.backend_config["parameter_values"] = bound_parameters
|
|
331
|
+
return self.backend_module.execute_circuit(
|
|
332
|
+
self.circuit, self.backend, self.backend_config
|
|
333
|
+
)
|
|
60
334
|
|
|
61
335
|
def bind_parameters(self, parameter_values):
|
|
336
|
+
"""Bind numerical values to circuit parameters.
|
|
337
|
+
|
|
338
|
+
Assigns numerical values to symbolic parameters defined in parameterized
|
|
339
|
+
gates.
|
|
340
|
+
|
|
341
|
+
:param parameter_values: Dictionary mapping parameter names to numerical
|
|
342
|
+
values.
|
|
343
|
+
:type parameter_values: dict
|
|
344
|
+
:raises ValueError: If a parameter name is not found in the circuit's
|
|
345
|
+
parameter list.
|
|
346
|
+
"""
|
|
62
347
|
for param, value in parameter_values.items():
|
|
63
|
-
if param in self.parameters:
|
|
64
|
-
|
|
348
|
+
if param not in self.parameters:
|
|
349
|
+
raise ValueError(
|
|
350
|
+
f"parameter '{param}' not found in circuit. "
|
|
351
|
+
f"available parameters: {list(self.parameters.keys())}"
|
|
352
|
+
)
|
|
353
|
+
self.parameters[param] = value
|
|
65
354
|
|
|
66
|
-
# placeholder method for use in the testing suite
|
|
67
355
|
def get_final_state_vector(self):
|
|
68
|
-
|
|
356
|
+
"""Return the final state vector of the quantum circuit.
|
|
69
357
|
|
|
70
|
-
|
|
358
|
+
The complete quantum state vector after circuit execution,
|
|
359
|
+
representing the full quantum state of all qubits. For parameterized
|
|
360
|
+
circuits, call bind_parameters() first to set parameter values.
|
|
361
|
+
|
|
362
|
+
:returns: The final state vector as a numpy array.
|
|
363
|
+
:rtype: numpy.ndarray
|
|
364
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
365
|
+
:raises ValueError: If parameterized circuit has unbound parameters.
|
|
366
|
+
"""
|
|
367
|
+
self._ensure_circuit_initialized()
|
|
368
|
+
|
|
369
|
+
# Only pass bound parameters (non-None values) to backend
|
|
370
|
+
bound_parameters = {
|
|
371
|
+
param: value
|
|
372
|
+
for param, value in self.parameters.items()
|
|
373
|
+
if value is not None
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
# Check if there are unbound parameters in the circuit
|
|
377
|
+
if self.parameters and len(bound_parameters) < len(self.parameters):
|
|
378
|
+
unbound_params = [
|
|
379
|
+
p for p in self.parameters.keys() if self.parameters[p] is None
|
|
380
|
+
]
|
|
381
|
+
raise ValueError(
|
|
382
|
+
f"Circuit contains unbound parameters: {unbound_params}. "
|
|
383
|
+
f"Please call bind_parameters() before get_final_state_vector()."
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
self.backend_config["parameter_values"] = bound_parameters
|
|
387
|
+
return self.backend_module.get_final_state_vector(
|
|
388
|
+
self.circuit, self.backend, self.backend_config
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def draw_circuit(self):
|
|
392
|
+
"""Visualize the quantum circuit.
|
|
393
|
+
|
|
394
|
+
Generates a visual representation of the circuit. The output format
|
|
395
|
+
depends on the backend implementation.
|
|
396
|
+
|
|
397
|
+
:returns: Circuit visualization. The exact type depends on the backend.
|
|
398
|
+
:rtype: str | object
|
|
399
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
400
|
+
"""
|
|
401
|
+
self._ensure_circuit_initialized()
|
|
71
402
|
return self.backend_module.draw_circuit(self.circuit)
|
|
72
403
|
|
|
404
|
+
def draw(self):
|
|
405
|
+
"""Alias for draw_circuit() for convenience.
|
|
406
|
+
|
|
407
|
+
Provides a shorter method name that matches common quantum computing
|
|
408
|
+
library conventions and documentation examples.
|
|
409
|
+
|
|
410
|
+
:returns: Circuit visualization. The exact type depends on the backend.
|
|
411
|
+
:rtype: str | object
|
|
412
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
413
|
+
"""
|
|
414
|
+
return self.draw_circuit()
|
|
415
|
+
|
|
73
416
|
def apply_rx_gate(self, qubit_index, angle):
|
|
417
|
+
"""Apply a rotation around the X-axis to the specified qubit.
|
|
418
|
+
|
|
419
|
+
Rotates the qubit by the given angle around the X-axis of the Bloch
|
|
420
|
+
sphere. The angle can be a static value or a parameter name for
|
|
421
|
+
parameterized circuits.
|
|
422
|
+
|
|
423
|
+
:param qubit_index: Index of the qubit.
|
|
424
|
+
:type qubit_index: int
|
|
425
|
+
:param angle: Rotation angle in radians. Can be a float or a string
|
|
426
|
+
parameter name.
|
|
427
|
+
:type angle: float | str
|
|
428
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
429
|
+
"""
|
|
430
|
+
self._ensure_circuit_initialized()
|
|
431
|
+
self._validate_qubit_index(qubit_index)
|
|
74
432
|
self._handle_parameter(angle)
|
|
75
433
|
self.backend_module.apply_rx_gate(self.circuit, qubit_index, angle)
|
|
76
434
|
|
|
77
435
|
def apply_ry_gate(self, qubit_index, angle):
|
|
436
|
+
"""Apply a rotation around the Y-axis to the specified qubit.
|
|
437
|
+
|
|
438
|
+
Rotates the qubit by the given angle around the Y-axis of the Bloch
|
|
439
|
+
sphere. The angle can be a static value or a parameter name for
|
|
440
|
+
parameterized circuits.
|
|
441
|
+
|
|
442
|
+
:param qubit_index: Index of the qubit.
|
|
443
|
+
:type qubit_index: int
|
|
444
|
+
:param angle: Rotation angle in radians. Can be a float or a string
|
|
445
|
+
parameter name.
|
|
446
|
+
:type angle: float | str
|
|
447
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
448
|
+
"""
|
|
449
|
+
self._ensure_circuit_initialized()
|
|
450
|
+
self._validate_qubit_index(qubit_index)
|
|
78
451
|
self._handle_parameter(angle)
|
|
79
452
|
self.backend_module.apply_ry_gate(self.circuit, qubit_index, angle)
|
|
80
453
|
|
|
81
454
|
def apply_rz_gate(self, qubit_index, angle):
|
|
455
|
+
"""Apply a rotation around the Z-axis to the specified qubit.
|
|
456
|
+
|
|
457
|
+
Rotates the qubit by the given angle around the Z-axis of the Bloch
|
|
458
|
+
sphere. The angle can be a static value or a parameter name for
|
|
459
|
+
parameterized circuits.
|
|
460
|
+
|
|
461
|
+
:param qubit_index: Index of the qubit.
|
|
462
|
+
:type qubit_index: int
|
|
463
|
+
:param angle: Rotation angle in radians. Can be a float or a string
|
|
464
|
+
parameter name.
|
|
465
|
+
:type angle: float | str
|
|
466
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
467
|
+
"""
|
|
468
|
+
self._ensure_circuit_initialized()
|
|
469
|
+
self._validate_qubit_index(qubit_index)
|
|
82
470
|
self._handle_parameter(angle)
|
|
83
471
|
self.backend_module.apply_rz_gate(self.circuit, qubit_index, angle)
|
|
84
472
|
|
|
85
473
|
def _handle_parameter(self, param_name):
|
|
474
|
+
"""Register parameter names when parameterized gates are applied.
|
|
475
|
+
|
|
476
|
+
Automatically adds string parameter names to the parameters dictionary
|
|
477
|
+
if not already registered.
|
|
478
|
+
|
|
479
|
+
:param param_name: Parameter name to handle. If it's a string,
|
|
480
|
+
registers it as a parameter.
|
|
481
|
+
:type param_name: str | float
|
|
482
|
+
"""
|
|
86
483
|
if isinstance(param_name, str) and param_name not in self.parameters:
|
|
87
484
|
self.parameters[param_name] = None
|
|
88
485
|
|
|
89
486
|
def apply_u_gate(self, qubit_index, theta, phi, lambd):
|
|
487
|
+
"""Apply a U gate (universal single-qubit gate) to the specified qubit.
|
|
488
|
+
|
|
489
|
+
A universal single-qubit gate parameterized by three angles (theta,
|
|
490
|
+
phi, lambd) that can represent any single-qubit unitary operation.
|
|
491
|
+
|
|
492
|
+
:param qubit_index: Index of the qubit.
|
|
493
|
+
:type qubit_index: int
|
|
494
|
+
:param theta: First rotation angle in radians.
|
|
495
|
+
:type theta: float
|
|
496
|
+
:param phi: Second rotation angle in radians.
|
|
497
|
+
:type phi: float
|
|
498
|
+
:param lambd: Third rotation angle in radians.
|
|
499
|
+
:type lambd: float
|
|
500
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
501
|
+
"""
|
|
502
|
+
self._ensure_circuit_initialized()
|
|
503
|
+
self._validate_qubit_index(qubit_index)
|
|
90
504
|
self.backend_module.apply_u_gate(self.circuit, qubit_index, theta, phi, lambd)
|
|
505
|
+
|
|
506
|
+
def swap_test(self, ancilla_qubit, qubit1, qubit2):
|
|
507
|
+
"""Implement the swap test circuit for measuring overlap between two quantum states.
|
|
508
|
+
|
|
509
|
+
Measures the inner product between the states on ``qubit1`` and ``qubit2``.
|
|
510
|
+
The probability of measuring the ancilla qubit in state |0⟩ is related
|
|
511
|
+
to the overlap as: P(0) = (1 + |⟨ψ|φ⟩|²) / 2
|
|
512
|
+
|
|
513
|
+
:param ancilla_qubit: Index of the ancilla qubit (should be initialized to |0⟩).
|
|
514
|
+
:type ancilla_qubit: int
|
|
515
|
+
:param qubit1: Index of the first qubit containing state |ψ⟩.
|
|
516
|
+
:type qubit1: int
|
|
517
|
+
:param qubit2: Index of the second qubit containing state |φ⟩.
|
|
518
|
+
:type qubit2: int
|
|
519
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
520
|
+
"""
|
|
521
|
+
# Apply Hadamard to ancilla qubit
|
|
522
|
+
self.apply_hadamard_gate(ancilla_qubit)
|
|
523
|
+
|
|
524
|
+
# Apply controlled-SWAP (Fredkin gate) with ancilla as control
|
|
525
|
+
self.apply_cswap_gate(ancilla_qubit, qubit1, qubit2)
|
|
526
|
+
|
|
527
|
+
# Apply Hadamard to ancilla qubit again
|
|
528
|
+
self.apply_hadamard_gate(ancilla_qubit)
|
|
529
|
+
|
|
530
|
+
def measure_overlap(self, qubit1, qubit2, ancilla_qubit=0):
|
|
531
|
+
"""Measure the overlap (fidelity) between two quantum states using the swap test.
|
|
532
|
+
|
|
533
|
+
Creates a swap test circuit to calculate the similarity between the
|
|
534
|
+
quantum states on ``qubit1`` and ``qubit2``. Returns the squared overlap
|
|
535
|
+
|⟨ψ|φ⟩|², which represents the fidelity between the two states.
|
|
536
|
+
|
|
537
|
+
The swap test measures P(ancilla=0), related to overlap as:
|
|
538
|
+
P(0) = (1 + |⟨ψ|φ⟩|²) / 2
|
|
539
|
+
|
|
540
|
+
For certain states (especially identical excited states), global phase
|
|
541
|
+
effects may cause the ancilla to measure predominantly |1⟩ instead of |0⟩.
|
|
542
|
+
This method handles both cases by taking the measurement probability
|
|
543
|
+
closer to 1.
|
|
544
|
+
|
|
545
|
+
:param qubit1: Index of the first qubit containing state |ψ⟩.
|
|
546
|
+
:type qubit1: int
|
|
547
|
+
:param qubit2: Index of the second qubit containing state |φ⟩.
|
|
548
|
+
:type qubit2: int
|
|
549
|
+
:param ancilla_qubit: Index of the ancilla qubit. Default is 0. Should be
|
|
550
|
+
initialized to |0⟩.
|
|
551
|
+
:type ancilla_qubit: int, optional
|
|
552
|
+
:returns: The squared overlap |⟨ψ|φ⟩|² between the two states (fidelity),
|
|
553
|
+
clamped to the range [0.0, 1.0].
|
|
554
|
+
:rtype: float
|
|
555
|
+
:raises RuntimeError: If the circuit has not been initialized.
|
|
556
|
+
"""
|
|
557
|
+
# Perform the swap test
|
|
558
|
+
self.swap_test(ancilla_qubit, qubit1, qubit2)
|
|
559
|
+
results = self.execute_circuit()
|
|
560
|
+
|
|
561
|
+
# Calculate the probability of measuring ancilla in |0> state
|
|
562
|
+
prob_zero = self.calculate_prob_zero(results, ancilla_qubit)
|
|
563
|
+
prob_zero_or_one = max(prob_zero, 1 - prob_zero)
|
|
564
|
+
overlap_squared = 2 * prob_zero_or_one - 1
|
|
565
|
+
overlap_squared = max(0.0, min(1.0, overlap_squared))
|
|
566
|
+
|
|
567
|
+
return overlap_squared
|
|
568
|
+
|
|
569
|
+
def calculate_prob_zero(self, results, ancilla_qubit):
|
|
570
|
+
"""Calculate the probability of measuring the ancilla qubit in |0⟩ state.
|
|
571
|
+
|
|
572
|
+
Delegates to the backend-specific implementation. Different backends
|
|
573
|
+
may use different qubit ordering conventions (little-endian vs big-endian).
|
|
574
|
+
|
|
575
|
+
:param results: Measurement results from ``execute_circuit()``. Format
|
|
576
|
+
depends on the backend.
|
|
577
|
+
:type results: dict | list[dict]
|
|
578
|
+
:param ancilla_qubit: Index of the ancilla qubit.
|
|
579
|
+
:type ancilla_qubit: int
|
|
580
|
+
:returns: Probability of measuring the ancilla qubit in |0⟩ state.
|
|
581
|
+
:rtype: float
|
|
582
|
+
"""
|
|
583
|
+
return self.backend_module.calculate_prob_zero(
|
|
584
|
+
results, ancilla_qubit, self.num_qubits
|
|
585
|
+
)
|