qiskit 1.4.2__cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl → 1.4.3__cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.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.
- qiskit/VERSION.txt +1 -1
- qiskit/_accelerate.abi3.so +0 -0
- qiskit/circuit/duration.py +16 -16
- qiskit/circuit/library/standard_gates/r.py +4 -3
- qiskit/circuit/library/standard_gates/x.py +1 -2
- qiskit/circuit/quantumcircuit.py +9 -9
- qiskit/converters/circuit_to_dag.py +2 -2
- qiskit/converters/dag_to_circuit.py +2 -3
- qiskit/dagcircuit/dagdependency_v2.py +3 -2
- qiskit/primitives/statevector_estimator.py +1 -1
- qiskit/qpy/binary_io/circuits.py +5 -0
- qiskit/synthesis/discrete_basis/commutator_decompose.py +30 -6
- qiskit/synthesis/discrete_basis/gate_sequence.py +10 -4
- qiskit/synthesis/discrete_basis/generate_basis_approximations.py +1 -1
- qiskit/synthesis/discrete_basis/solovay_kitaev.py +36 -13
- qiskit/transpiler/passes/layout/sabre_layout.py +4 -1
- qiskit/transpiler/passes/layout/vf2_utils.py +2 -5
- qiskit/transpiler/passes/scheduling/alap.py +2 -2
- qiskit/transpiler/passes/scheduling/alignments/align_measures.py +3 -3
- qiskit/transpiler/passes/scheduling/asap.py +2 -2
- qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +1 -1
- qiskit/transpiler/passes/scheduling/padding/base_padding.py +2 -2
- qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +5 -5
- qiskit/transpiler/passes/scheduling/padding/pad_delay.py +1 -1
- qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py +29 -19
- qiskit/visualization/timeline/core.py +1 -1
- {qiskit-1.4.2.dist-info → qiskit-1.4.3.dist-info}/METADATA +4 -3
- {qiskit-1.4.2.dist-info → qiskit-1.4.3.dist-info}/RECORD +710 -710
- {qiskit-1.4.2.dist-info → qiskit-1.4.3.dist-info}/WHEEL +1 -1
- {qiskit-1.4.2.dist-info → qiskit-1.4.3.dist-info}/entry_points.txt +0 -0
- {qiskit-1.4.2.dist-info → qiskit-1.4.3.dist-info/licenses}/LICENSE.txt +0 -0
- {qiskit-1.4.2.dist-info → qiskit-1.4.3.dist-info}/top_level.txt +0 -0
qiskit/VERSION.txt
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.4.
|
1
|
+
1.4.3
|
qiskit/_accelerate.abi3.so
CHANGED
Binary file
|
qiskit/circuit/duration.py
CHANGED
@@ -65,29 +65,29 @@ def convert_durations_to_dt(qc: QuantumCircuit, dt_in_sec: float, inplace=True):
|
|
65
65
|
|
66
66
|
for instruction in circ.data:
|
67
67
|
operation = instruction.operation
|
68
|
-
if operation.
|
68
|
+
if operation._unit == "dt" or operation._duration is None:
|
69
69
|
continue
|
70
70
|
|
71
|
-
if not operation.
|
72
|
-
raise CircuitError(f"Invalid time unit: '{operation.
|
71
|
+
if not operation._unit.endswith("s"):
|
72
|
+
raise CircuitError(f"Invalid time unit: '{operation._unit}'")
|
73
73
|
|
74
|
-
duration = operation.
|
75
|
-
if operation.
|
76
|
-
duration = apply_prefix(duration, operation.
|
74
|
+
duration = operation._duration
|
75
|
+
if operation._unit != "s":
|
76
|
+
duration = apply_prefix(duration, operation._unit)
|
77
77
|
|
78
|
-
operation.
|
79
|
-
operation.
|
78
|
+
operation._duration = duration_in_dt(duration, dt_in_sec)
|
79
|
+
operation._unit = "dt"
|
80
80
|
|
81
|
-
if circ.
|
82
|
-
if not circ.
|
83
|
-
raise CircuitError(f"Invalid time unit: '{circ.
|
81
|
+
if circ._duration is not None and circ._unit != "dt":
|
82
|
+
if not circ._unit.endswith("s"):
|
83
|
+
raise CircuitError(f"Invalid time unit: '{circ._unit}'")
|
84
84
|
|
85
|
-
duration = circ.
|
86
|
-
if circ.
|
87
|
-
duration = apply_prefix(duration, circ.
|
85
|
+
duration = circ._duration
|
86
|
+
if circ._unit != "s":
|
87
|
+
duration = apply_prefix(duration, circ._unit)
|
88
88
|
|
89
|
-
circ.
|
90
|
-
circ.
|
89
|
+
circ._duration = duration_in_dt(duration, dt_in_sec)
|
90
|
+
circ._unit = "dt"
|
91
91
|
|
92
92
|
if not inplace:
|
93
93
|
return circ
|
@@ -306,8 +306,7 @@ class CCXGate(SingletonControlledGate):
|
|
306
306
|
r"""CCX gate, also known as Toffoli gate.
|
307
307
|
|
308
308
|
Can be applied to a :class:`~qiskit.circuit.QuantumCircuit`
|
309
|
-
with the :meth:`~qiskit.circuit.QuantumCircuit.ccx`
|
310
|
-
:meth:`~qiskit.circuit.QuantumCircuit.toffoli` methods.
|
309
|
+
with the :meth:`~qiskit.circuit.QuantumCircuit.ccx` method.
|
311
310
|
|
312
311
|
**Circuit symbol:**
|
313
312
|
|
qiskit/circuit/quantumcircuit.py
CHANGED
@@ -1546,8 +1546,8 @@ class QuantumCircuit:
|
|
1546
1546
|
for instruction in reversed(self.data):
|
1547
1547
|
reverse_circ._append(instruction.replace(operation=instruction.operation.reverse_ops()))
|
1548
1548
|
|
1549
|
-
reverse_circ.
|
1550
|
-
reverse_circ.
|
1549
|
+
reverse_circ._duration = self._duration
|
1550
|
+
reverse_circ._unit = self._unit
|
1551
1551
|
return reverse_circ
|
1552
1552
|
|
1553
1553
|
def reverse_bits(self) -> "QuantumCircuit":
|
@@ -2620,8 +2620,8 @@ class QuantumCircuit:
|
|
2620
2620
|
"""
|
2621
2621
|
if _standard_gate:
|
2622
2622
|
self._data.append(instruction)
|
2623
|
-
self.
|
2624
|
-
self.
|
2623
|
+
self._duration = None
|
2624
|
+
self._unit = "dt"
|
2625
2625
|
return instruction
|
2626
2626
|
|
2627
2627
|
old_style = not isinstance(instruction, CircuitInstruction)
|
@@ -2643,8 +2643,8 @@ class QuantumCircuit:
|
|
2643
2643
|
self._data.append_manual_params(instruction, params)
|
2644
2644
|
|
2645
2645
|
# Invalidate whole circuit duration if an instruction is added
|
2646
|
-
self.
|
2647
|
-
self.
|
2646
|
+
self._duration = None
|
2647
|
+
self._unit = "dt"
|
2648
2648
|
return instruction.operation if old_style else instruction
|
2649
2649
|
|
2650
2650
|
@typing.overload
|
@@ -6584,7 +6584,7 @@ class QuantumCircuit:
|
|
6584
6584
|
Raises:
|
6585
6585
|
CircuitError: if ``self`` is a not-yet scheduled circuit.
|
6586
6586
|
"""
|
6587
|
-
if self.
|
6587
|
+
if self._duration is None:
|
6588
6588
|
# circuit has only delays, this is kind of scheduled
|
6589
6589
|
for instruction in self._data:
|
6590
6590
|
if not isinstance(instruction.operation, Delay):
|
@@ -6626,7 +6626,7 @@ class QuantumCircuit:
|
|
6626
6626
|
Raises:
|
6627
6627
|
CircuitError: if ``self`` is a not-yet scheduled circuit.
|
6628
6628
|
"""
|
6629
|
-
if self.
|
6629
|
+
if self._duration is None:
|
6630
6630
|
# circuit has only delays, this is kind of scheduled
|
6631
6631
|
for instruction in self._data:
|
6632
6632
|
if not isinstance(instruction.operation, Delay):
|
@@ -6637,7 +6637,7 @@ class QuantumCircuit:
|
|
6637
6637
|
|
6638
6638
|
qubits = [self.qubits[q] if isinstance(q, int) else q for q in qubits]
|
6639
6639
|
|
6640
|
-
stops = {q: self.
|
6640
|
+
stops = {q: self._duration for q in qubits}
|
6641
6641
|
dones = {q: False for q in qubits}
|
6642
6642
|
for instruction in reversed(self._data):
|
6643
6643
|
for q in qubits:
|
@@ -73,6 +73,6 @@ def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_ord
|
|
73
73
|
|
74
74
|
dagcircuit = core_circuit_to_dag(circuit, copy_operations, qubit_order, clbit_order)
|
75
75
|
|
76
|
-
dagcircuit.
|
77
|
-
dagcircuit.
|
76
|
+
dagcircuit._duration = circuit._duration
|
77
|
+
dagcircuit._unit = circuit._unit
|
78
78
|
return dagcircuit
|
@@ -74,7 +74,6 @@ def dag_to_circuit(dag, copy_operations=True):
|
|
74
74
|
circuit._calibrations_prop = dag._calibrations_prop
|
75
75
|
|
76
76
|
circuit._data = circuit_data
|
77
|
-
|
78
|
-
circuit.
|
79
|
-
circuit._unit = dag.unit
|
77
|
+
circuit._duration = dag._duration
|
78
|
+
circuit._unit = dag._unit
|
80
79
|
return circuit
|
@@ -525,8 +525,6 @@ class _DAGDependencyV2:
|
|
525
525
|
target_dag = _DAGDependencyV2()
|
526
526
|
target_dag.name = self.name
|
527
527
|
target_dag._global_phase = self._global_phase
|
528
|
-
target_dag.duration = self.duration
|
529
|
-
target_dag.unit = self.unit
|
530
528
|
target_dag.metadata = self.metadata
|
531
529
|
target_dag._key_cache = self._key_cache
|
532
530
|
target_dag.comm_checker = self.comm_checker
|
@@ -534,6 +532,9 @@ class _DAGDependencyV2:
|
|
534
532
|
target_dag.add_qubits(self.qubits)
|
535
533
|
target_dag.add_clbits(self.clbits)
|
536
534
|
|
535
|
+
target_dag.duration = self.duration
|
536
|
+
target_dag.unit = self.unit
|
537
|
+
|
537
538
|
for qreg in self.qregs.values():
|
538
539
|
target_dag.add_qreg(qreg)
|
539
540
|
for creg in self.cregs.values():
|
@@ -33,7 +33,7 @@ class StatevectorEstimator(BaseEstimatorV2):
|
|
33
33
|
Simple implementation of :class:`BaseEstimatorV2` with full state vector simulation.
|
34
34
|
|
35
35
|
This class is implemented via :class:`~.Statevector` which turns provided circuits into
|
36
|
-
pure state vectors. These states are subsequently acted on by :class
|
36
|
+
pure state vectors. These states are subsequently acted on by :class:`~.SparsePauliOp`,
|
37
37
|
which implies that, at present, this implementation is only compatible with Pauli-based
|
38
38
|
observables.
|
39
39
|
|
qiskit/qpy/binary_io/circuits.py
CHANGED
@@ -776,6 +776,11 @@ def _write_instruction(
|
|
776
776
|
custom_operations[gate_class_name] = instruction.operation
|
777
777
|
custom_operations_list.append(gate_class_name)
|
778
778
|
|
779
|
+
elif isinstance(instruction.operation, library.MCMTGate):
|
780
|
+
gate_class_name = instruction.operation.name + "_" + str(uuid.uuid4())
|
781
|
+
custom_operations[gate_class_name] = instruction.operation
|
782
|
+
custom_operations_list.append(gate_class_name)
|
783
|
+
|
779
784
|
condition_type = type_keys.Condition.NONE
|
780
785
|
condition_register = b""
|
781
786
|
condition_value = 0
|
@@ -53,16 +53,40 @@ def _compute_rotation_axis(matrix: np.ndarray) -> np.ndarray:
|
|
53
53
|
"""
|
54
54
|
_check_is_so3(matrix)
|
55
55
|
|
56
|
+
# If theta represents the rotation angle, then trace = 1 + 2cos(theta).
|
56
57
|
trace = _compute_trace_so3(matrix)
|
57
|
-
|
58
|
-
if
|
59
|
-
|
60
|
-
y = 1 / (2 * math.sin(theta)) * (matrix[0][2] - matrix[2][0])
|
61
|
-
z = 1 / (2 * math.sin(theta)) * (matrix[1][0] - matrix[0][1])
|
62
|
-
else:
|
58
|
+
|
59
|
+
if trace >= 3 - 1e-10:
|
60
|
+
# The matrix is the identity (rotation by 0)
|
63
61
|
x = 1.0
|
64
62
|
y = 0.0
|
65
63
|
z = 0.0
|
64
|
+
|
65
|
+
elif trace <= -1 + 1e-10:
|
66
|
+
# The matrix is the 180-degree rotation
|
67
|
+
squares = (1 + np.diagonal(matrix)) / 2
|
68
|
+
index_of_max = np.argmax(squares)
|
69
|
+
|
70
|
+
if index_of_max == 0:
|
71
|
+
x = math.sqrt(squares[0])
|
72
|
+
y = matrix[0][1] / (2 * x)
|
73
|
+
z = matrix[0][2] / (2 * x)
|
74
|
+
elif index_of_max == 1:
|
75
|
+
y = math.sqrt(squares[1])
|
76
|
+
x = matrix[0][1] / (2 * y)
|
77
|
+
z = matrix[1][2] / (2 * y)
|
78
|
+
else:
|
79
|
+
z = math.sqrt(squares[2])
|
80
|
+
x = matrix[0][2] / (2 * z)
|
81
|
+
y = matrix[1][2] / (2 * z)
|
82
|
+
|
83
|
+
else:
|
84
|
+
# The matrix is the rotation by theta with sin(theta)!=0
|
85
|
+
theta = math.acos(0.5 * (trace - 1))
|
86
|
+
x = 1 / (2 * math.sin(theta)) * (matrix[2][1] - matrix[1][2])
|
87
|
+
y = 1 / (2 * math.sin(theta)) * (matrix[0][2] - matrix[2][0])
|
88
|
+
z = 1 / (2 * math.sin(theta)) * (matrix[1][0] - matrix[0][1])
|
89
|
+
|
66
90
|
return np.array([x, y, z])
|
67
91
|
|
68
92
|
|
@@ -55,7 +55,6 @@ class GateSequence:
|
|
55
55
|
self.name = " ".join(self.labels)
|
56
56
|
self.global_phase = global_phase
|
57
57
|
self.product = so3_matrix
|
58
|
-
self.product_su2 = su2_matrix
|
59
58
|
|
60
59
|
def remove_cancelling_pair(self, indices: Sequence[int]) -> None:
|
61
60
|
"""Remove a pair of indices that cancel each other and *do not* change the matrices."""
|
@@ -106,6 +105,16 @@ class GateSequence:
|
|
106
105
|
|
107
106
|
return circuit
|
108
107
|
|
108
|
+
def _to_u2(self):
|
109
|
+
"""Creates the U2 matrix corresponding to the stored sequence of gates
|
110
|
+
and the global phase.
|
111
|
+
"""
|
112
|
+
u2 = np.eye(2, dtype=complex)
|
113
|
+
for mat in self.gates:
|
114
|
+
u2 = mat.to_matrix().dot(u2)
|
115
|
+
u2 = np.exp(1j * self.global_phase) * u2
|
116
|
+
return u2
|
117
|
+
|
109
118
|
def to_dag(self):
|
110
119
|
"""Convert to a :class:`.DAGCircuit`.
|
111
120
|
|
@@ -149,7 +158,6 @@ class GateSequence:
|
|
149
158
|
so3 = _convert_su2_to_so3(su2)
|
150
159
|
|
151
160
|
self.product = so3.dot(self.product)
|
152
|
-
self.product_su2 = su2.dot(self.product_su2)
|
153
161
|
self.global_phase = self.global_phase + phase
|
154
162
|
|
155
163
|
self.gates.append(gate)
|
@@ -172,7 +180,6 @@ class GateSequence:
|
|
172
180
|
adjoint.labels = [inv.name for inv in adjoint.gates]
|
173
181
|
adjoint.name = " ".join(adjoint.labels)
|
174
182
|
adjoint.product = np.conj(self.product).T
|
175
|
-
adjoint.product_su2 = np.conj(self.product_su2).T
|
176
183
|
adjoint.global_phase = -self.global_phase
|
177
184
|
|
178
185
|
return adjoint
|
@@ -190,7 +197,6 @@ class GateSequence:
|
|
190
197
|
out.matrices = self.matrices.copy()
|
191
198
|
out.global_phase = self.global_phase
|
192
199
|
out.product = self.product.copy()
|
193
|
-
out.product_su2 = self.product_su2.copy()
|
194
200
|
out.name = self.name
|
195
201
|
out._eulers = self._eulers
|
196
202
|
return out
|
@@ -76,7 +76,7 @@ def _check_candidate_greedy(candidate, existing_sequences, tol=1e-10):
|
|
76
76
|
return False
|
77
77
|
|
78
78
|
for existing in existing_sequences:
|
79
|
-
if matrix_equal(existing.
|
79
|
+
if matrix_equal(existing.product, candidate.product, ignore_phase=True, atol=tol):
|
80
80
|
# is the new sequence less or more efficient?
|
81
81
|
return len(candidate.gates) < len(existing.gates)
|
82
82
|
return True
|
@@ -24,7 +24,7 @@ from .generate_basis_approximations import generate_basic_approximations, _1q_ga
|
|
24
24
|
class SolovayKitaevDecomposition:
|
25
25
|
"""The Solovay Kitaev discrete decomposition algorithm.
|
26
26
|
|
27
|
-
This class is called recursively by the transpiler pass, which is why it is
|
27
|
+
This class is called recursively by the transpiler pass, which is why it is separated.
|
28
28
|
See :class:`qiskit.transpiler.passes.SolovayKitaev` for more information.
|
29
29
|
"""
|
30
30
|
|
@@ -33,7 +33,7 @@ class SolovayKitaevDecomposition:
|
|
33
33
|
) -> None:
|
34
34
|
"""
|
35
35
|
Args:
|
36
|
-
basic_approximations: A specification of the basic
|
36
|
+
basic_approximations: A specification of the basic SO(3) approximations in terms
|
37
37
|
of discrete gates. At each iteration this algorithm, the remaining error is
|
38
38
|
approximated with the closest sequence of gates in this set.
|
39
39
|
If a ``str``, this specifies a ``.npy`` filename from which to load the
|
@@ -116,23 +116,33 @@ class SolovayKitaevDecomposition:
|
|
116
116
|
"""
|
117
117
|
# make input matrix SU(2) and get the according global phase
|
118
118
|
z = 1 / np.sqrt(np.linalg.det(gate_matrix))
|
119
|
-
|
119
|
+
|
120
|
+
gate_matrix_su2 = z * gate_matrix
|
121
|
+
gate_matrix_as_sequence = GateSequence.from_matrix(gate_matrix_su2)
|
120
122
|
global_phase = np.arctan2(np.imag(z), np.real(z))
|
121
123
|
|
122
124
|
# get the decomposition as GateSequence type
|
123
|
-
decomposition = self._recurse(
|
125
|
+
decomposition = self._recurse(
|
126
|
+
gate_matrix_as_sequence, recursion_degree, check_input=check_input
|
127
|
+
)
|
124
128
|
|
125
129
|
# simplify
|
126
130
|
_remove_identities(decomposition)
|
127
131
|
_remove_inverse_follows_gate(decomposition)
|
128
132
|
|
133
|
+
# adjust to the correct SU(2) phase
|
134
|
+
adjust_phase = (
|
135
|
+
np.pi if _should_adjust_phase(decomposition._to_u2(), gate_matrix_su2) else 0.0
|
136
|
+
)
|
137
|
+
|
129
138
|
# convert to a circuit and attach the right phases
|
130
139
|
if return_dag:
|
131
140
|
out = decomposition.to_dag()
|
132
141
|
else:
|
133
142
|
out = decomposition.to_circuit()
|
134
143
|
|
135
|
-
out.global_phase
|
144
|
+
out.global_phase += adjust_phase
|
145
|
+
out.global_phase -= global_phase
|
136
146
|
|
137
147
|
return out
|
138
148
|
|
@@ -155,17 +165,20 @@ class SolovayKitaevDecomposition:
|
|
155
165
|
raise ValueError("Shape of U must be (3, 3) but is", sequence.shape)
|
156
166
|
|
157
167
|
if n == 0:
|
158
|
-
|
168
|
+
res = self.find_basic_approximation(sequence)
|
159
169
|
|
160
|
-
|
170
|
+
else:
|
171
|
+
u_n1 = self._recurse(sequence, n - 1, check_input=check_input)
|
161
172
|
|
162
|
-
|
163
|
-
|
164
|
-
|
173
|
+
v_n, w_n = commutator_decompose(
|
174
|
+
sequence.dot(u_n1.adjoint()).product, check_input=check_input
|
175
|
+
)
|
176
|
+
|
177
|
+
v_n1 = self._recurse(v_n, n - 1, check_input=check_input)
|
178
|
+
w_n1 = self._recurse(w_n, n - 1, check_input=check_input)
|
179
|
+
res = v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1)
|
165
180
|
|
166
|
-
|
167
|
-
w_n1 = self._recurse(w_n, n - 1, check_input=check_input)
|
168
|
-
return v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1)
|
181
|
+
return res
|
169
182
|
|
170
183
|
def find_basic_approximation(self, sequence: GateSequence) -> GateSequence:
|
171
184
|
"""Find ``GateSequence`` in ``self._basic_approximations`` that approximates ``sequence``.
|
@@ -215,3 +228,13 @@ def _remove_identities(sequence):
|
|
215
228
|
sequence.gates.pop(index)
|
216
229
|
else:
|
217
230
|
index += 1
|
231
|
+
|
232
|
+
|
233
|
+
def _should_adjust_phase(computed: np.ndarray, target: np.ndarray) -> bool:
|
234
|
+
"""
|
235
|
+
The implemented SolovayKitaevDecomposition has a global phase uncertainty of +-1,
|
236
|
+
due to approximating not the original SU(2) matrix but its projection onto SO(3).
|
237
|
+
This function returns ``True`` if the global phase of the computed approximation
|
238
|
+
should be adjusted (by adding pi) to better much the target.
|
239
|
+
"""
|
240
|
+
return np.linalg.norm(-computed - target) < np.linalg.norm(computed - target)
|
@@ -305,6 +305,9 @@ class SabreLayout(TransformationPass):
|
|
305
305
|
# the layout and routing together as part of resolving the Sabre result.
|
306
306
|
physical_qubits = QuantumRegister(self.coupling_map.size(), "q")
|
307
307
|
mapped_dag = DAGCircuit()
|
308
|
+
mapped_dag.name = dag.name
|
309
|
+
mapped_dag.metadata = dag.metadata
|
310
|
+
mapped_dag.global_phase = dag.global_phase
|
308
311
|
mapped_dag.add_qreg(physical_qubits)
|
309
312
|
mapped_dag.add_clbits(dag.clbits)
|
310
313
|
for creg in dag.cregs.values():
|
@@ -315,7 +318,7 @@ class SabreLayout(TransformationPass):
|
|
315
318
|
mapped_dag.add_captured_var(var)
|
316
319
|
for var in dag.iter_declared_vars():
|
317
320
|
mapped_dag.add_declared_var(var)
|
318
|
-
|
321
|
+
|
319
322
|
self.property_set["original_qubit_indices"] = {
|
320
323
|
bit: index for index, bit in enumerate(dag.qubits)
|
321
324
|
}
|
@@ -203,11 +203,8 @@ def build_average_error_map(target, properties, coupling_map):
|
|
203
203
|
coupling_map = target.build_coupling_map()
|
204
204
|
if not built and coupling_map is not None:
|
205
205
|
for qubit in range(num_qubits):
|
206
|
-
|
207
|
-
|
208
|
-
(coupling_map.graph.out_degree(qubit) + coupling_map.graph.in_degree(qubit))
|
209
|
-
/ num_qubits,
|
210
|
-
)
|
206
|
+
degree = len(set(coupling_map.graph.neighbors_undirected(qubit)))
|
207
|
+
avg_map.add_error((qubit, qubit), degree / num_qubits)
|
211
208
|
for edge in coupling_map.graph.edge_list():
|
212
209
|
avg_map.add_error(edge, (avg_map[edge[0], edge[0]] + avg_map[edge[1], edge[1]]) / 2)
|
213
210
|
built = True
|
@@ -147,7 +147,7 @@ class ALAPSchedule(BaseSchedulerTransform):
|
|
147
147
|
new_dag._calibrations_prop = dag._calibrations_prop
|
148
148
|
|
149
149
|
# set circuit duration and unit to indicate it is scheduled
|
150
|
-
new_dag.
|
151
|
-
new_dag.
|
150
|
+
new_dag._duration = circuit_duration
|
151
|
+
new_dag._unit = time_unit
|
152
152
|
|
153
153
|
return new_dag
|
@@ -129,7 +129,7 @@ class AlignMeasures(TransformationPass):
|
|
129
129
|
return dag
|
130
130
|
|
131
131
|
# if circuit is not yet scheduled, schedule with ALAP method
|
132
|
-
if dag.
|
132
|
+
if dag._duration is None:
|
133
133
|
raise TranspilerError(
|
134
134
|
f"This circuit {dag.name} may involve a delay instruction violating the "
|
135
135
|
"pulse controller alignment. To adjust instructions to "
|
@@ -201,8 +201,8 @@ class AlignMeasures(TransformationPass):
|
|
201
201
|
new_dag.metadata = dag.metadata
|
202
202
|
|
203
203
|
# set circuit duration and unit to indicate it is scheduled
|
204
|
-
new_dag.
|
205
|
-
new_dag.
|
204
|
+
new_dag._duration = circuit_duration
|
205
|
+
new_dag._unit = time_unit
|
206
206
|
|
207
207
|
return new_dag
|
208
208
|
|
@@ -170,6 +170,6 @@ class ASAPSchedule(BaseSchedulerTransform):
|
|
170
170
|
new_dag._calibrations_prop = dag._calibrations_prop
|
171
171
|
|
172
172
|
# set circuit duration and unit to indicate it is scheduled
|
173
|
-
new_dag.
|
174
|
-
new_dag.
|
173
|
+
new_dag._duration = circuit_duration
|
174
|
+
new_dag._unit = time_unit
|
175
175
|
return new_dag
|
@@ -168,7 +168,7 @@ class DynamicalDecoupling(TransformationPass):
|
|
168
168
|
if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None:
|
169
169
|
raise TranspilerError("DD runs on physical circuits only.")
|
170
170
|
|
171
|
-
if dag.
|
171
|
+
if dag._duration is None:
|
172
172
|
raise TranspilerError("DD runs after circuit is scheduled.")
|
173
173
|
|
174
174
|
durations = self._update_inst_durations(dag)
|
@@ -98,7 +98,7 @@ class BasePadding(TransformationPass):
|
|
98
98
|
|
99
99
|
new_dag.name = dag.name
|
100
100
|
new_dag.metadata = dag.metadata
|
101
|
-
new_dag.
|
101
|
+
new_dag._unit = self.property_set["time_unit"]
|
102
102
|
new_dag._calibrations_prop = dag._calibrations_prop
|
103
103
|
new_dag.global_phase = dag.global_phase
|
104
104
|
|
@@ -161,7 +161,7 @@ class BasePadding(TransformationPass):
|
|
161
161
|
prev_node=prev_node,
|
162
162
|
)
|
163
163
|
|
164
|
-
new_dag.
|
164
|
+
new_dag._duration = circuit_duration
|
165
165
|
|
166
166
|
return new_dag
|
167
167
|
|
@@ -348,14 +348,14 @@ class PadDynamicalDecoupling(BasePadding):
|
|
348
348
|
|
349
349
|
if not self.__is_dd_qubit(dag.qubits.index(qubit)):
|
350
350
|
# Target physical qubit is not the target of this DD sequence.
|
351
|
-
self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.
|
351
|
+
self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag._unit), qubit)
|
352
352
|
return
|
353
353
|
|
354
354
|
if self._skip_reset_qubits and (
|
355
355
|
isinstance(prev_node, DAGInNode) or isinstance(prev_node.op, Reset)
|
356
356
|
):
|
357
357
|
# Previous node is the start edge or reset, i.e. qubit is ground state.
|
358
|
-
self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.
|
358
|
+
self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag._unit), qubit)
|
359
359
|
return
|
360
360
|
|
361
361
|
slack = time_interval - np.sum(self._dd_sequence_lengths[qubit])
|
@@ -363,7 +363,7 @@ class PadDynamicalDecoupling(BasePadding):
|
|
363
363
|
|
364
364
|
if slack <= 0:
|
365
365
|
# Interval too short.
|
366
|
-
self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.
|
366
|
+
self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag._unit), qubit)
|
367
367
|
return
|
368
368
|
|
369
369
|
if len(self._dd_sequence) == 1:
|
@@ -389,7 +389,7 @@ class PadDynamicalDecoupling(BasePadding):
|
|
389
389
|
sequence_gphase += phase
|
390
390
|
else:
|
391
391
|
# Don't do anything if there's no single-qubit gate to absorb the inverse
|
392
|
-
self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.
|
392
|
+
self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag._unit), qubit)
|
393
393
|
return
|
394
394
|
|
395
395
|
def _constrained_length(values):
|
@@ -425,7 +425,7 @@ class PadDynamicalDecoupling(BasePadding):
|
|
425
425
|
if dd_ind < len(taus):
|
426
426
|
tau = taus[dd_ind]
|
427
427
|
if tau > 0:
|
428
|
-
self._apply_scheduled_op(dag, idle_after, Delay(tau, dag.
|
428
|
+
self._apply_scheduled_op(dag, idle_after, Delay(tau, dag._unit), qubit)
|
429
429
|
idle_after += tau
|
430
430
|
if dd_ind < len(self._dd_sequence):
|
431
431
|
gate = self._dd_sequence[dd_ind]
|
@@ -201,7 +201,7 @@ class SolovayKitaevSynthesis(UnitarySynthesisPlugin):
|
|
201
201
|
|
202
202
|
Supported parameters in the dictionary:
|
203
203
|
|
204
|
-
|
204
|
+
basic_approximations (str | dict):
|
205
205
|
The basic approximations for the finding the best discrete decomposition at the root of the
|
206
206
|
recursion. If a string, it specifies the ``.npy`` file to load the approximations from.
|
207
207
|
If a dictionary, it contains ``{label: SO(3)-matrix}`` pairs. If None, a default based on
|
@@ -209,19 +209,31 @@ class SolovayKitaevSynthesis(UnitarySynthesisPlugin):
|
|
209
209
|
|
210
210
|
basis_gates (list):
|
211
211
|
A list of strings specifying the discrete basis gates to decompose to. If None,
|
212
|
-
defaults to ``["h", "t", "tdg"]``.
|
212
|
+
it defaults to ``["h", "t", "tdg"]``. If ``basic_approximations`` is not None,
|
213
|
+
``basis_set`` is required to correspond to the basis set that was used to
|
214
|
+
generate it.
|
213
215
|
|
214
216
|
depth (int):
|
215
217
|
The gate-depth of the basic approximations. All possible, unique combinations of the
|
216
218
|
basis gates up to length ``depth`` are considered. If None, defaults to 10.
|
219
|
+
If ``basic_approximations`` is not None, ``depth`` is required to correspond to the
|
220
|
+
depth that was used to generate it.
|
217
221
|
|
218
222
|
recursion_degree (int):
|
219
223
|
The number of times the decomposition is recursively improved. If None, defaults to 3.
|
220
224
|
"""
|
221
225
|
|
222
|
-
#
|
223
|
-
#
|
226
|
+
# Generating basic approximations of single-qubit gates is computationally expensive.
|
227
|
+
# We cache the instance of the Solovay-Kitaev class (which contains the approximations),
|
228
|
+
# as well as the basis gates and the depth (used to generate it).
|
229
|
+
# When the plugin is called again, we check if the specified basis gates and depth are
|
230
|
+
# the same as before. If so, the stored basic approximations are reused, and if not, the
|
231
|
+
# approximations are re-generated. In practice (when the plugin is run as a part of the
|
232
|
+
# UnitarySynthesis transpiler pass), the basis gates and the depth do not change, and
|
233
|
+
# basic approximations are not re-generated.
|
224
234
|
_sk = None
|
235
|
+
_basis_gates = None
|
236
|
+
_depth = None
|
225
237
|
|
226
238
|
@property
|
227
239
|
def max_qubits(self):
|
@@ -271,27 +283,25 @@ class SolovayKitaevSynthesis(UnitarySynthesisPlugin):
|
|
271
283
|
return False
|
272
284
|
|
273
285
|
def run(self, unitary, **options):
|
286
|
+
"""Run the SolovayKitaevSynthesis synthesis plugin on the given unitary."""
|
274
287
|
|
275
|
-
# Runtime imports to avoid the overhead of these imports for
|
276
|
-
# plugin discovery and only use them if the plugin is run/used
|
277
288
|
config = options.get("config") or {}
|
278
|
-
|
289
|
+
basis_gates = options.get("basis_gates", ["h", "t", "tdg"])
|
290
|
+
depth = config.get("depth", 10)
|
291
|
+
basic_approximations = config.get("basic_approximations", None)
|
279
292
|
recursion_degree = config.get("recursion_degree", 3)
|
280
293
|
|
281
|
-
# if we didn't yet construct the Solovay-Kitaev instance
|
282
|
-
# the basic approximations
|
283
|
-
if SolovayKitaevSynthesis._sk is None
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
# if the basic approximations are not generated and not given,
|
288
|
-
# try to generate them if the basis set is specified
|
294
|
+
# Check if we didn't yet construct the Solovay-Kitaev instance (which contains the basic
|
295
|
+
# approximations) or if the basic approximations need need to be recomputed.
|
296
|
+
if (SolovayKitaevSynthesis._sk is None) or (
|
297
|
+
(basis_gates != SolovayKitaevSynthesis._basis_gates)
|
298
|
+
or (depth != SolovayKitaevSynthesis._depth)
|
299
|
+
):
|
289
300
|
if basic_approximations is None:
|
290
|
-
depth = config.get("depth", 10)
|
291
301
|
basic_approximations = generate_basic_approximations(basis_gates, depth)
|
292
302
|
|
303
|
+
SolovayKitaevSynthesis._basis_gates = basis_gates
|
304
|
+
SolovayKitaevSynthesis._depth = depth
|
293
305
|
SolovayKitaevSynthesis._sk = SolovayKitaevDecomposition(basic_approximations)
|
294
|
-
|
295
306
|
approximate_circuit = SolovayKitaevSynthesis._sk.run(unitary, recursion_degree)
|
296
|
-
|
297
|
-
return dag_circuit
|
307
|
+
return circuit_to_dag(approximate_circuit)
|