qiskit 2.0.3__cp39-abi3-macosx_11_0_arm64.whl → 2.1.0__cp39-abi3-macosx_11_0_arm64.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.
Files changed (180) hide show
  1. qiskit/VERSION.txt +1 -1
  2. qiskit/__init__.py +19 -1
  3. qiskit/_accelerate.abi3.so +0 -0
  4. qiskit/circuit/__init__.py +104 -20
  5. qiskit/circuit/_add_control.py +57 -31
  6. qiskit/circuit/_classical_resource_map.py +4 -0
  7. qiskit/circuit/annotation.py +504 -0
  8. qiskit/circuit/classical/expr/__init__.py +1 -1
  9. qiskit/circuit/classical/expr/expr.py +104 -446
  10. qiskit/circuit/classical/expr/visitors.py +6 -0
  11. qiskit/circuit/classical/types/types.py +7 -130
  12. qiskit/circuit/controlflow/box.py +32 -7
  13. qiskit/circuit/delay.py +11 -9
  14. qiskit/circuit/library/arithmetic/adders/adder.py +4 -4
  15. qiskit/circuit/library/arithmetic/multipliers/multiplier.py +2 -2
  16. qiskit/circuit/library/arithmetic/piecewise_chebyshev.py +8 -4
  17. qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py +23 -15
  18. qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py +22 -14
  19. qiskit/circuit/library/arithmetic/quadratic_form.py +6 -0
  20. qiskit/circuit/library/arithmetic/weighted_adder.py +43 -24
  21. qiskit/circuit/library/basis_change/qft.py +2 -2
  22. qiskit/circuit/library/blueprintcircuit.py +6 -0
  23. qiskit/circuit/library/boolean_logic/inner_product.py +2 -2
  24. qiskit/circuit/library/boolean_logic/quantum_and.py +2 -2
  25. qiskit/circuit/library/boolean_logic/quantum_or.py +3 -3
  26. qiskit/circuit/library/boolean_logic/quantum_xor.py +2 -2
  27. qiskit/circuit/library/data_preparation/_z_feature_map.py +2 -2
  28. qiskit/circuit/library/data_preparation/_zz_feature_map.py +2 -2
  29. qiskit/circuit/library/data_preparation/pauli_feature_map.py +2 -2
  30. qiskit/circuit/library/fourier_checking.py +2 -2
  31. qiskit/circuit/library/generalized_gates/diagonal.py +5 -1
  32. qiskit/circuit/library/generalized_gates/gms.py +5 -1
  33. qiskit/circuit/library/generalized_gates/linear_function.py +2 -2
  34. qiskit/circuit/library/generalized_gates/permutation.py +5 -1
  35. qiskit/circuit/library/generalized_gates/uc.py +1 -1
  36. qiskit/circuit/library/generalized_gates/unitary.py +21 -2
  37. qiskit/circuit/library/graph_state.py +2 -2
  38. qiskit/circuit/library/grover_operator.py +2 -2
  39. qiskit/circuit/library/hidden_linear_function.py +2 -2
  40. qiskit/circuit/library/iqp.py +2 -2
  41. qiskit/circuit/library/n_local/efficient_su2.py +2 -2
  42. qiskit/circuit/library/n_local/evolved_operator_ansatz.py +1 -1
  43. qiskit/circuit/library/n_local/excitation_preserving.py +7 -9
  44. qiskit/circuit/library/n_local/n_local.py +4 -3
  45. qiskit/circuit/library/n_local/pauli_two_design.py +2 -2
  46. qiskit/circuit/library/n_local/real_amplitudes.py +2 -2
  47. qiskit/circuit/library/n_local/two_local.py +2 -2
  48. qiskit/circuit/library/overlap.py +2 -2
  49. qiskit/circuit/library/pauli_evolution.py +3 -2
  50. qiskit/circuit/library/phase_estimation.py +2 -2
  51. qiskit/circuit/library/standard_gates/dcx.py +11 -12
  52. qiskit/circuit/library/standard_gates/ecr.py +21 -24
  53. qiskit/circuit/library/standard_gates/equivalence_library.py +232 -96
  54. qiskit/circuit/library/standard_gates/global_phase.py +5 -6
  55. qiskit/circuit/library/standard_gates/h.py +22 -45
  56. qiskit/circuit/library/standard_gates/i.py +1 -1
  57. qiskit/circuit/library/standard_gates/iswap.py +13 -31
  58. qiskit/circuit/library/standard_gates/p.py +19 -26
  59. qiskit/circuit/library/standard_gates/r.py +11 -17
  60. qiskit/circuit/library/standard_gates/rx.py +21 -45
  61. qiskit/circuit/library/standard_gates/rxx.py +7 -22
  62. qiskit/circuit/library/standard_gates/ry.py +21 -39
  63. qiskit/circuit/library/standard_gates/ryy.py +13 -28
  64. qiskit/circuit/library/standard_gates/rz.py +18 -35
  65. qiskit/circuit/library/standard_gates/rzx.py +7 -22
  66. qiskit/circuit/library/standard_gates/rzz.py +7 -19
  67. qiskit/circuit/library/standard_gates/s.py +44 -39
  68. qiskit/circuit/library/standard_gates/swap.py +25 -38
  69. qiskit/circuit/library/standard_gates/sx.py +34 -41
  70. qiskit/circuit/library/standard_gates/t.py +18 -27
  71. qiskit/circuit/library/standard_gates/u.py +8 -24
  72. qiskit/circuit/library/standard_gates/u1.py +28 -52
  73. qiskit/circuit/library/standard_gates/u2.py +9 -9
  74. qiskit/circuit/library/standard_gates/u3.py +24 -40
  75. qiskit/circuit/library/standard_gates/x.py +190 -336
  76. qiskit/circuit/library/standard_gates/xx_minus_yy.py +12 -50
  77. qiskit/circuit/library/standard_gates/xx_plus_yy.py +13 -52
  78. qiskit/circuit/library/standard_gates/y.py +19 -23
  79. qiskit/circuit/library/standard_gates/z.py +31 -38
  80. qiskit/circuit/parameter.py +14 -5
  81. qiskit/circuit/parameterexpression.py +109 -75
  82. qiskit/circuit/quantumcircuit.py +172 -99
  83. qiskit/circuit/quantumcircuitdata.py +1 -0
  84. qiskit/circuit/random/__init__.py +37 -2
  85. qiskit/circuit/random/utils.py +445 -56
  86. qiskit/circuit/tools/pi_check.py +5 -13
  87. qiskit/compiler/transpiler.py +1 -1
  88. qiskit/converters/circuit_to_instruction.py +2 -2
  89. qiskit/dagcircuit/dagnode.py +8 -3
  90. qiskit/primitives/__init__.py +2 -2
  91. qiskit/primitives/base/base_estimator.py +2 -2
  92. qiskit/primitives/containers/data_bin.py +0 -3
  93. qiskit/primitives/containers/observables_array.py +192 -108
  94. qiskit/primitives/primitive_job.py +29 -10
  95. qiskit/providers/fake_provider/generic_backend_v2.py +2 -0
  96. qiskit/qasm3/__init__.py +106 -12
  97. qiskit/qasm3/ast.py +15 -1
  98. qiskit/qasm3/exporter.py +59 -36
  99. qiskit/qasm3/printer.py +12 -0
  100. qiskit/qpy/__init__.py +182 -6
  101. qiskit/qpy/binary_io/circuits.py +256 -24
  102. qiskit/qpy/binary_io/parse_sympy_repr.py +5 -0
  103. qiskit/qpy/binary_io/schedules.py +12 -32
  104. qiskit/qpy/binary_io/value.py +36 -18
  105. qiskit/qpy/common.py +11 -3
  106. qiskit/qpy/formats.py +17 -1
  107. qiskit/qpy/interface.py +52 -12
  108. qiskit/qpy/type_keys.py +7 -1
  109. qiskit/quantum_info/__init__.py +10 -0
  110. qiskit/quantum_info/operators/__init__.py +1 -0
  111. qiskit/quantum_info/operators/symplectic/__init__.py +1 -0
  112. qiskit/quantum_info/operators/symplectic/clifford_circuits.py +26 -0
  113. qiskit/quantum_info/operators/symplectic/pauli.py +2 -2
  114. qiskit/result/sampled_expval.py +3 -1
  115. qiskit/synthesis/__init__.py +10 -0
  116. qiskit/synthesis/arithmetic/__init__.py +1 -1
  117. qiskit/synthesis/arithmetic/adders/__init__.py +1 -0
  118. qiskit/synthesis/arithmetic/adders/draper_qft_adder.py +6 -2
  119. qiskit/synthesis/arithmetic/adders/rv_ripple_carry_adder.py +156 -0
  120. qiskit/synthesis/discrete_basis/generate_basis_approximations.py +14 -126
  121. qiskit/synthesis/discrete_basis/solovay_kitaev.py +161 -121
  122. qiskit/synthesis/evolution/lie_trotter.py +10 -7
  123. qiskit/synthesis/evolution/product_formula.py +10 -7
  124. qiskit/synthesis/evolution/qdrift.py +10 -7
  125. qiskit/synthesis/evolution/suzuki_trotter.py +10 -7
  126. qiskit/synthesis/multi_controlled/__init__.py +4 -0
  127. qiskit/synthesis/multi_controlled/mcx_synthesis.py +402 -178
  128. qiskit/synthesis/multi_controlled/multi_control_rotation_gates.py +14 -15
  129. qiskit/synthesis/qft/qft_decompose_lnn.py +7 -25
  130. qiskit/synthesis/unitary/qsd.py +80 -9
  131. qiskit/transpiler/__init__.py +10 -3
  132. qiskit/transpiler/instruction_durations.py +2 -20
  133. qiskit/transpiler/passes/__init__.py +5 -2
  134. qiskit/transpiler/passes/layout/dense_layout.py +26 -6
  135. qiskit/transpiler/passes/layout/disjoint_utils.py +1 -166
  136. qiskit/transpiler/passes/layout/sabre_layout.py +22 -3
  137. qiskit/transpiler/passes/layout/sabre_pre_layout.py +1 -1
  138. qiskit/transpiler/passes/layout/vf2_layout.py +49 -13
  139. qiskit/transpiler/passes/layout/vf2_utils.py +10 -0
  140. qiskit/transpiler/passes/optimization/__init__.py +1 -1
  141. qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +2 -1
  142. qiskit/transpiler/passes/optimization/optimize_clifford_t.py +68 -0
  143. qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +3 -9
  144. qiskit/transpiler/passes/routing/sabre_swap.py +4 -2
  145. qiskit/transpiler/passes/routing/star_prerouting.py +106 -81
  146. qiskit/transpiler/passes/scheduling/__init__.py +1 -1
  147. qiskit/transpiler/passes/scheduling/alignments/check_durations.py +1 -1
  148. qiskit/transpiler/passes/scheduling/padding/__init__.py +1 -0
  149. qiskit/transpiler/passes/scheduling/padding/context_aware_dynamical_decoupling.py +876 -0
  150. qiskit/transpiler/passes/synthesis/__init__.py +1 -0
  151. qiskit/transpiler/passes/synthesis/clifford_unitary_synth_plugin.py +123 -0
  152. qiskit/transpiler/passes/synthesis/hls_plugins.py +494 -93
  153. qiskit/transpiler/passes/synthesis/plugin.py +4 -0
  154. qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py +27 -22
  155. qiskit/transpiler/passmanager_config.py +3 -0
  156. qiskit/transpiler/preset_passmanagers/builtin_plugins.py +149 -28
  157. qiskit/transpiler/preset_passmanagers/common.py +101 -0
  158. qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +6 -0
  159. qiskit/transpiler/preset_passmanagers/level3.py +2 -2
  160. qiskit/transpiler/target.py +15 -2
  161. qiskit/utils/optionals.py +6 -5
  162. qiskit/visualization/circuit/_utils.py +5 -3
  163. qiskit/visualization/circuit/latex.py +9 -2
  164. qiskit/visualization/circuit/matplotlib.py +26 -4
  165. qiskit/visualization/circuit/qcstyle.py +9 -157
  166. qiskit/visualization/dag/__init__.py +13 -0
  167. qiskit/visualization/dag/dagstyle.py +103 -0
  168. qiskit/visualization/dag/styles/__init__.py +13 -0
  169. qiskit/visualization/dag/styles/color.json +10 -0
  170. qiskit/visualization/dag/styles/plain.json +5 -0
  171. qiskit/visualization/dag_visualization.py +169 -98
  172. qiskit/visualization/style.py +223 -0
  173. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/METADATA +7 -6
  174. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/RECORD +178 -169
  175. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/entry_points.txt +6 -0
  176. qiskit/synthesis/discrete_basis/commutator_decompose.py +0 -265
  177. qiskit/synthesis/discrete_basis/gate_sequence.py +0 -421
  178. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/WHEEL +0 -0
  179. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/licenses/LICENSE.txt +0 -0
  180. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,876 @@
1
+ # This code is part of Qiskit.
2
+ #
3
+ # (C) Copyright IBM 2024.
4
+ #
5
+ # This code is licensed under the Apache License, Version 2.0. You may
6
+ # obtain a copy of this license in the LICENSE.txt file in the root directory
7
+ # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
+ #
9
+ # Any modifications or derivative works of this code must retain this
10
+ # copyright notice, and modified files need to carry a notice indicating
11
+ # that they have been altered from the originals.
12
+
13
+ """Context-aware dynamical decoupling."""
14
+
15
+ from __future__ import annotations
16
+ from enum import Enum
17
+
18
+ import itertools
19
+ import logging
20
+
21
+ from pprint import pformat
22
+ from dataclasses import dataclass, field
23
+ from collections import defaultdict
24
+ import numpy as np
25
+ import rustworkx as rx
26
+
27
+ from qiskit.circuit import QuantumCircuit, Qubit, Reset, Gate
28
+ from qiskit.circuit.library import CXGate, ECRGate, XGate
29
+ from qiskit.dagcircuit import DAGCircuit
30
+ from qiskit.converters import circuit_to_dag
31
+ from qiskit.dagcircuit import DAGOpNode, DAGInNode
32
+ from qiskit.transpiler.target import Target
33
+ from qiskit.transpiler.basepasses import TransformationPass
34
+ from qiskit.transpiler.coupling import CouplingMap
35
+ from qiskit.transpiler.exceptions import TranspilerError
36
+
37
+ from .pad_delay import PadDelay
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ class ContextAwareDynamicalDecoupling(TransformationPass):
43
+ """Implement an X-sequence dynamical decoupling considering the gate- and qubit-context.
44
+
45
+ This pass implements a context-aware dynamical decoupling (DD) [1], which ensures that
46
+
47
+ (1) simultaneously occurring DD sequences on device-adjacent qubits are mutually orthogonal, and
48
+ (2) DD sequences on spectator qubits of ECR and CX gates are orthogonal to the echo
49
+ pulses on the neighboring target/control qubits.
50
+
51
+ The mutually orthogonal DD sequences are currently Walsh-Hadamard sequences, consisting of only
52
+ X gates. In some cases it might therefore be beneficial to use :class:`.PadDynamicalDecoupling`
53
+ with more generic sequences, such as XY4.
54
+
55
+ This pass performs best if the two-qubit interactions have the same durations on the
56
+ device, as it allows to align delay sequences and take into account potential control and target
57
+ operations on neighboring qubits. However, it is still valid if this is not the case.
58
+
59
+ .. note::
60
+
61
+ If this pass is run within a pass manager (as in the example below), it will
62
+ automatically run :class:`.PadDelay` to allocate the delays. If instead it is run as
63
+ standalone (not inside a :class:`.PassManager`), the delays must already be inserted.
64
+
65
+
66
+ Example::
67
+
68
+ from qiskit.circuit import QuantumCircuit
69
+ from qiskit.circuit.library import QFTGate
70
+ from qiskit.transpiler import PassManager
71
+ from qiskit.transpiler.passes import ALAPScheduleAnalysis, ContextAwareDynamicalDecoupling
72
+ from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
73
+ from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
74
+
75
+ num_qubits = 10
76
+ circuit = QuantumCircuit(num_qubits)
77
+ circuit.append(QFTGate(num_qubits), circuit.qubits)
78
+ circuit.measure_all()
79
+
80
+ target = FakeSherbrooke().target
81
+
82
+ pm = generate_preset_pass_manager(optimization_level=2, target=target)
83
+ dd = PassManager([
84
+ ALAPScheduleAnalysis(target=target),
85
+ ContextAwareDynamicalDecoupling(target=target),
86
+ ])
87
+
88
+ transpiled = pm.run(circuit)
89
+ with_dd = dd.run(transpiled)
90
+
91
+ print(with_dd.draw(idle_wires=False))
92
+
93
+ References:
94
+
95
+ [1] A. Seif et al. (2024). Suppressing Correlated Noise in Quantum Computers via
96
+ Context-Aware Compiling, `arXiv:2403.06852 <https://arxiv.org/abs/2403.06852>`_.
97
+
98
+ """
99
+
100
+ def __init__(
101
+ self,
102
+ target: Target,
103
+ *,
104
+ min_duration: int | None = None,
105
+ skip_reset_qubits: bool = True,
106
+ skip_dd_threshold: float = 1.0,
107
+ pulse_alignment: int | None = None,
108
+ coloring_strategy: rx.ColoringStrategy = rx.ColoringStrategy.Saturation,
109
+ ) -> None:
110
+ """
111
+ Args:
112
+ target: The :class:`.Target` of the device to run the circuit.
113
+ min_duration: Minimal delay duration (in ``dt``) to insert a DD sequence. This
114
+ can be useful, e.g. if a big delay block would be interrupted and split into
115
+ smaller blocks due to a very short, adjacent delay. If ``None``, this is set
116
+ to be at least twice the difference of the longest/shortest CX or ECR gate.
117
+ skip_reset_qubits: Skip initial delays and delays after a reset.
118
+ skip_dd_threshold: Skip dynamical decoupling on an idle qubit, if the duration of
119
+ the decoupling sequence exceeds this fraction of the idle window. For example, to
120
+ skip a DD sequence if it would take up more than 95% of the idle time, set this
121
+ value to 0.95. A value of 1. means that the DD sequence is applied if it fits into
122
+ the window.
123
+ pulse_alignment: The hardware constraints (in ``dt``) for gate timing allocation.
124
+ If provided, the inserted gates will only be executed on integer multiples of
125
+ this value. This is usually provided on ``backend.configuration().timing_constraints``.
126
+ If ``None``, this is extracted from the ``target``.
127
+ coloring_strategy: The coloring strategy used for ``rx.greedy_graph_color``.
128
+ Defaults to a saturation strategy, which is optimal on bipartite graphs,
129
+ see Section 1.2.2.8 of [2].
130
+
131
+ References:
132
+
133
+ [2] A. Kosowski, and K. Manuszewski, Classical Coloring of Graphs, Graph Colorings,
134
+ 2-19, 2004. ISBN 0-8218-3458-4.
135
+
136
+ """
137
+ super().__init__()
138
+
139
+ if not 0 <= skip_dd_threshold <= 1:
140
+ raise ValueError(f"skip_dd_threshold must be in [0, 1], but is {skip_dd_threshold}")
141
+
142
+ if min_duration is None:
143
+ if target.dt is None:
144
+ min_duration = 0
145
+ else:
146
+ min_duration = 2 * _gate_length_variance(target) / target.dt
147
+
148
+ self._min_duration = min_duration
149
+ self._skip_reset_qubits = skip_reset_qubits
150
+ self._skip_dd_threshold = skip_dd_threshold
151
+ self._target = target
152
+ self._coupling_map = target.build_coupling_map() # build once and re-use for performance
153
+ self._pulse_alignment = (
154
+ target.pulse_alignment if pulse_alignment is None else pulse_alignment
155
+ )
156
+ self._coloring_strategy = coloring_strategy
157
+ self._sequence_generator = WalshHadamardSequence()
158
+
159
+ # Use PadDelay to insert Delay operations into the DAG before running this pass.
160
+ # This could be integrated into this pass as well, saving one iteration over the DAG,
161
+ # but this would repeat logic and is currently no bottleneck.
162
+ self.requires = [PadDelay(target=target)]
163
+
164
+ def run(self, dag: DAGCircuit) -> DAGCircuit:
165
+ # check the schedule analysis was run
166
+ if "node_start_time" not in self.property_set:
167
+ raise RuntimeError(
168
+ "node_start_time not found in the pass manager's property set. "
169
+ "Please run a scheduling pass before calling ContextAwareDynamicalDecoupling."
170
+ )
171
+
172
+ # 1) Find all delay instructions that qualify. We split them into explicit
173
+ # "begin" and "end" events and sort them.
174
+ sorted_delay_events = self._get_sorted_delays(dag)
175
+
176
+ # 2) Identify adjacent delays by overlap in time and adjacency on device.
177
+ qubit_map = {bit: idx for idx, bit in enumerate(dag.qubits)} # map qubits to their indices
178
+ adjacent_delay_blocks = self._collect_adjacent_delay_blocks(sorted_delay_events, qubit_map)
179
+ logger.debug("adjacent delay blocks: %s", pformat(adjacent_delay_blocks))
180
+
181
+ # 3) Split the blocks into multi-qubit delay layers
182
+ merged_delays = []
183
+ for block in adjacent_delay_blocks:
184
+ merged_delays_in_block = self._split_adjacent_block(block, qubit_map)
185
+ merged_delays += merged_delays_in_block
186
+
187
+ # 4) For each multi-qubit delay, find a coloring (that takes into account neighboring
188
+ # CX or ECR gates) and get the DD sequence
189
+ all_delays = set() # keep a list of all delays to iterate over them easily later
190
+
191
+ for merged_delay in merged_delays:
192
+ all_delays.update(merged_delay.ops)
193
+
194
+ # get coloring inside the n-qubit delay
195
+ coloring = self._get_wire_coloring(dag, merged_delay)
196
+
197
+ # get the DD sequence on the qubit
198
+ duration = merged_delay.end - merged_delay.start
199
+ checked_durations_cache = set()
200
+ for op in merged_delay.ops:
201
+ dd_circuit, start_times = self._get_dd_sequence(
202
+ coloring[op.index],
203
+ op.index,
204
+ duration,
205
+ checked_durations_cache,
206
+ )
207
+ op.replacement.compose(dd_circuit, inplace=True, copy=False)
208
+ if len(op.start_times) == 0:
209
+ op.start_times += start_times
210
+ else:
211
+ start_times = [op.start_times[-1] + time for time in start_times]
212
+
213
+ op.start_times += start_times
214
+
215
+ # 5) Replace each delay operation with its individual DD sequence
216
+ qubit_map = dict(enumerate(dag.qubits))
217
+ for delay in all_delays:
218
+ # replace it with its stored replacement
219
+ as_dag = circuit_to_dag(delay.replacement)
220
+ node_ids = [node._node_id for node in as_dag.topological_op_nodes()]
221
+ id_map = dag.substitute_node_with_dag(
222
+ delay.op, as_dag, {as_dag.qubits[0]: qubit_map[delay.index]}
223
+ )
224
+
225
+ # update node start times
226
+ for node_id, start_time in zip(node_ids, delay.start_times):
227
+ self.property_set["node_start_time"][id_map[node_id]] = start_time
228
+
229
+ return dag
230
+
231
+ def get_orthogonal_sequence(self, order: int) -> tuple[list[float], list[Gate]]:
232
+ """Return a DD sequence of given order, where different orders are orthogonal."""
233
+ spacing = self._sequence_generator.get_sequence(order)
234
+ return spacing, [XGate() for _ in spacing[:-1]]
235
+
236
+ def _get_sorted_delays(
237
+ self,
238
+ dag: DAGCircuit,
239
+ ) -> list[DelayEvent]:
240
+ """Get sorted DelayEvent objects for all eligible delay operations."""
241
+
242
+ def is_after_reset(node):
243
+ if not self._skip_reset_qubits:
244
+ return False # if we do not skip reset qubits, we always want to use the delay
245
+
246
+ predecessor = next(dag.predecessors(node)) # a delay has one 1 predecessor
247
+ return isinstance(predecessor, DAGInNode) or isinstance(predecessor.op, Reset)
248
+
249
+ qubit_map = {bit: index for index, bit in enumerate(dag.qubits)}
250
+
251
+ eligible_delays = [
252
+ DelayEvent(
253
+ event_type,
254
+ (
255
+ start_time
256
+ if event_type == EventType.BEGIN
257
+ else start_time + self._duration(node, qubit_map)
258
+ ),
259
+ node,
260
+ )
261
+ for node, start_time in self.property_set["node_start_time"].items()
262
+ if (
263
+ node.op.name == "delay"
264
+ and not is_after_reset(node)
265
+ and self._duration(node, qubit_map) > self._min_duration
266
+ )
267
+ for event_type in (EventType.BEGIN, EventType.END)
268
+ ]
269
+
270
+ sorted_events = sorted(eligible_delays, key=DelayEvent.sort_key)
271
+ logger.debug("Sorted delay events: %s", sorted_events)
272
+
273
+ return sorted_events
274
+
275
+ def _get_wire_coloring(self, dag: DAGCircuit, merged_delay: MultiDelayOp) -> dict[int, int]:
276
+ """Find a wire coloring for a multi-delay operation.
277
+
278
+ This function returns a dictionary that includes the coloring (as int) for the indices in the
279
+ ``merged_delay`` as ``{index: color}`` pairs. Spectator qubits are handled by assigning
280
+ neighboring qubits with a CX or ECR a color (0 if control, 1 if target) and including them
281
+ in the coloring problem.
282
+ """
283
+ # get neighboring wires, for which we will give initial colors
284
+ neighbors = set()
285
+ index_map = dict(enumerate(dag.qubits))
286
+ qubit_map = {bit: index for index, bit in enumerate(dag.qubits)}
287
+
288
+ for delay_op in merged_delay.ops:
289
+ # use coupling_map.graph.neighbors_undirected once Qiskit/rustworkx#1254 is in a release
290
+ new_neighbors = {
291
+ i
292
+ for i in range(dag.num_qubits())
293
+ if self._coupling_map.distance(i, delay_op.index) == 1
294
+ }
295
+ # do not add indices that are already in the merged delay
296
+ neighbors.update(new_neighbors.difference(merged_delay.indices))
297
+
298
+ # build a subgraph we will apply the coloring function on
299
+ wires = sorted(neighbors.union(merged_delay.indices))
300
+ subgraph = self._coupling_map.graph.subgraph(list(wires)).to_undirected()
301
+ glob2loc = dict(zip(wires, subgraph.node_indices()))
302
+ preset_coloring = {index: None for index in subgraph.node_indices()}
303
+
304
+ # find the neighbor wires and check if ctrl/tgt spectator
305
+ for wire in neighbors:
306
+ for op_node in dag.nodes_on_wire(dag.qubits[wire], only_ops=True):
307
+ # check if the operation occurs during the delay
308
+ op_start = self.property_set["node_start_time"][op_node]
309
+ op_end = op_start + self._duration(op_node, qubit_map)
310
+ if (
311
+ isinstance(op_node.op, (CXGate, ECRGate))
312
+ and op_start < merged_delay.end
313
+ and op_end > merged_delay.start
314
+ ):
315
+ # set coloring to 0 if ctrl, and to 1 if tgt
316
+ ctrl, tgt = op_node.qargs
317
+ if index_map[wire] == ctrl:
318
+ preset_coloring[glob2loc[wire]] = 0
319
+ if index_map[wire] == tgt:
320
+ preset_coloring[glob2loc[wire]] = 1
321
+
322
+ local_coloring = rx.graph_greedy_color(
323
+ subgraph,
324
+ preset_color_fn=lambda loc: preset_coloring[loc],
325
+ strategy=self._coloring_strategy,
326
+ )
327
+
328
+ # map the local indices of the subgraph back to our DAG indices
329
+ loc2glob = dict(zip(subgraph.node_indices(), wires))
330
+ coloring = {loc2glob[loc]: color for loc, color in local_coloring.items()}
331
+
332
+ # for debugging purposes, print the coloring of each block
333
+ logger.debug("Coloring for block %s: %s", merged_delay, coloring)
334
+
335
+ return coloring
336
+
337
+ def _get_dd_sequence(
338
+ self,
339
+ order: int,
340
+ index: int,
341
+ duration: int,
342
+ checked_durations_cache: set[int],
343
+ ) -> tuple[QuantumCircuit, list[int]]:
344
+ """Get a DD sequence of specified order on qubit with a given index.
345
+
346
+ Takes the gate durations, the pulse alignment and a set to cache, which gate lengths
347
+ we've already checked to match the pulse alignment. Returns the DD sequence and the
348
+ node start times as list.
349
+ """
350
+ instruction_durations = self._target.durations()
351
+ # check the X gate on the active qubit is compatible with pulse alignment
352
+ if index not in checked_durations_cache:
353
+ x_duration = instruction_durations.get("x", index)
354
+ if x_duration % self._pulse_alignment != 0:
355
+ raise TranspilerError(
356
+ f"X gate length on qubit with index {index} is {x_duration} which is not "
357
+ f"an integer multiple of the pulse alignment {self._pulse_alignment}."
358
+ )
359
+ checked_durations_cache.add(index)
360
+
361
+ spacing, dd_sequence = self.get_orthogonal_sequence(order=order)
362
+
363
+ # check if DD can be applied or if there is not enough time
364
+ dd_sequence_duration = sum(instruction_durations.get(gate, index) for gate in dd_sequence)
365
+ slack = duration - dd_sequence_duration
366
+ slack_fraction = slack / duration
367
+ if 1 - slack_fraction >= self._skip_dd_threshold: # dd doesn't fit
368
+ seq = QuantumCircuit(1)
369
+ seq.delay(duration, 0)
370
+ return seq, [0]
371
+
372
+ # compute actual spacings in between the delays, taking into account
373
+ # the pulse alignment restriction of the hardware
374
+ taus = self._constrain_spacing(spacing, slack)
375
+
376
+ # apply the DD gates
377
+ # tau has one more entry than the gate sequence
378
+ start_times = []
379
+ time = 0 # track the node start time
380
+ seq = QuantumCircuit(1) # if the DD sequence has a global phase, add it here
381
+ for tau, gate in itertools.zip_longest(taus, dd_sequence):
382
+ if tau > 0:
383
+ seq.delay(tau, 0)
384
+ start_times.append(time)
385
+ time += tau
386
+ if gate is not None:
387
+ seq.append(gate, [0])
388
+ start_times.append(time)
389
+ time += instruction_durations.get(gate, index)
390
+
391
+ return seq, start_times
392
+
393
+ def _constrain_spacing(self, spacing, slack):
394
+ def _constrained_length(values):
395
+ return self._pulse_alignment * np.floor(values / self._pulse_alignment)
396
+
397
+ taus = _constrained_length(slack * np.asarray(spacing))
398
+ unused_slack = slack - sum(taus) # unused, due to rounding to int multiples of dt
399
+ middle_index = int((len(taus) - 1) / 2) # arbitrary: redistribute to middle
400
+ to_middle = _constrained_length(unused_slack)
401
+ taus[middle_index] += to_middle # now we add up to original delay duration
402
+ if unused_slack - to_middle:
403
+ taus[-1] += unused_slack - to_middle
404
+
405
+ return taus
406
+
407
+ def _collect_adjacent_delay_blocks(
408
+ self,
409
+ sorted_delay_events: list[DelayEvent],
410
+ qubit_map: dict[Qubit, int],
411
+ ) -> list[AdjacentDelayBlock]:
412
+ """Collect delay events into adjacent blocks.
413
+
414
+ Events in an adjacent block are overlapping in time and adjacent on the device.
415
+ See also the dataclass ``AdjacentDelayBlock`` for more information.
416
+
417
+ Args:
418
+ sorted_delay_events: All eligible delay operations, sorted by time and type.
419
+ qubit_map: A map from qubit instance to qubit index.
420
+
421
+ Returns:
422
+ A list of adjacent delay blocks.
423
+ """
424
+ open_delay_blocks = []
425
+ closed_delay_blocks = []
426
+
427
+ def _open_delay_block(delay_event):
428
+ open_delay_blocks.append(
429
+ AdjacentDelayBlock(
430
+ events=[delay_event], active_qubits=set(delay_event.op_node.qargs)
431
+ )
432
+ )
433
+ return open_delay_blocks[-1]
434
+
435
+ def _update_delay_block(open_delay_block, delay_event):
436
+ """Add another delay event to an existing block to either extend or close it."""
437
+ open_delay_block.events.append(delay_event)
438
+
439
+ # at this point we know that delay_event.op_node.qargs is active
440
+ open_delay_block.active_qubits -= set(delay_event.op_node.qargs)
441
+
442
+ if not open_delay_block.active_qubits:
443
+ open_delay_blocks.remove(open_delay_block)
444
+ closed_delay_blocks.append(open_delay_block)
445
+
446
+ def _combine_delay_blocks(delay_blocks):
447
+ survivor, *doomed = delay_blocks
448
+
449
+ for doomed_delay_group in doomed:
450
+ # Add events and qubits from doomed block to survivor.
451
+ if logger.isEnabledFor(logging.DEBUG):
452
+ if survivor.active_qubits.intersection(doomed_delay_group.active_qubits):
453
+ logger.debug("More than one open delay on a qubit?")
454
+
455
+ survivor.events.extend(doomed_delay_group.events)
456
+ survivor.active_qubits.update(doomed_delay_group.active_qubits)
457
+
458
+ open_delay_blocks.remove(doomed_delay_group)
459
+ survivor.events.sort(key=DelayEvent.sort_key) # Maintain sorted event order
460
+
461
+ for delay_event in sorted_delay_events:
462
+ # This could be avoided by keeping a map of device qubit to open
463
+ # block and only considering neighbors of current event.
464
+ # use coupling_map.graph.neighbors_undirected once Qiskit/rustworkx#1254 is in a release
465
+ adjacent_open_delay_blocks = [
466
+ open_delay
467
+ for open_delay in open_delay_blocks
468
+ if any(
469
+ self._coupling_map.distance(
470
+ qubit_map[delay_event.op_node.qargs[0]], qubit_map[open_delay_qubit]
471
+ )
472
+ <= 1
473
+ for open_delay_qubit in open_delay.active_qubits
474
+ )
475
+ ]
476
+
477
+ if delay_event.type == EventType.BEGIN:
478
+ # If crossing a begin edge, check if there are any open delays that are adjacent.
479
+ # If so, add current event to that group.
480
+
481
+ if len(adjacent_open_delay_blocks) == 0:
482
+ # Make a new delay block
483
+ _open_delay_block(delay_event)
484
+ else:
485
+ # Make a new block and combine that with adjacent open blocks
486
+ new_block = _open_delay_block(delay_event)
487
+ _combine_delay_blocks(adjacent_open_delay_blocks + [new_block])
488
+
489
+ else:
490
+ if logger.isEnabledFor(logging.DEBUG):
491
+ # If crossing a end edge, remove this qubit from the actively delaying qubits"
492
+ if len(adjacent_open_delay_blocks) != 1:
493
+ logger.debug("Closing edge w/o an open delay?")
494
+
495
+ _update_delay_block(adjacent_open_delay_blocks[0], delay_event)
496
+
497
+ # log the open delays and the number of closed delays
498
+ logger.debug("-- post collect")
499
+ logger.debug("open delays: %s", open_delay_blocks)
500
+ logger.debug("len(closed delays): %s", len(closed_delay_blocks))
501
+
502
+ # validate the results, there should be no open delays and all active qubits
503
+ # should be accounted for
504
+ if logger.isEnabledFor(logging.DEBUG):
505
+ if len(open_delay_blocks) > 0:
506
+ logger.debug("Failed to close all open delays.")
507
+
508
+ for closed_delay in closed_delay_blocks:
509
+ if len(closed_delay.active_qubits) > 0:
510
+ logger.debug("Failed to remove active qubits on closed delay %s.", closed_delay)
511
+
512
+ closed_delay.validate()
513
+
514
+ return closed_delay_blocks
515
+
516
+ def _split_adjacent_block(
517
+ self,
518
+ adjacent_block: AdjacentDelayBlock,
519
+ qubit_map: dict[Qubit, int],
520
+ ) -> list[MultiDelayOp]:
521
+ """Split adjacent delay blocks in concurrent layers of maximum width.
522
+
523
+ This code performs the following steps:
524
+
525
+ 1) Find the atomic time windows where delays can begin and end.
526
+ 2) Find which connected qubit components are jointly idle during the windows.
527
+ 3) Merge time-adjacent qubit components.
528
+
529
+ """
530
+ # 1) Find times at which a new delay event is happening and we might have to break up
531
+ # the adjacent delays
532
+ breakpoints = []
533
+ # a dictionary of {qubit_index: [delay_op1, delay_op2, ...]}
534
+ all_delays = defaultdict(list)
535
+
536
+ for event in adjacent_block.events:
537
+ index = qubit_map[event.op_node.qargs[0]] # delays are 1q ops
538
+ if event.type == EventType.BEGIN:
539
+ delay = DelayOp(start=event.time, index=index, op=event.op_node)
540
+ all_delays[index].append(delay)
541
+ else: # find which delay to close
542
+ all_delays[index][-1].end = event.time
543
+
544
+ if len(breakpoints) == 0 or event.time > breakpoints[-1]:
545
+ breakpoints.append(event.time)
546
+
547
+ # 2) Find which delays are active during which time windows, where a time window
548
+ # is defined as (breakpoints[i], breakpoints[i + 1])
549
+ active_delays = {} # {window: [group1, group2]} where each group = (index1, index2, ..)
550
+ op_map = {} # {(qubit_index, window): delay operation as DAGOpNode}
551
+ windows = list(zip(breakpoints[:-1], breakpoints[1:]))
552
+ for window in windows:
553
+ active = []
554
+
555
+ # check which delays are active during the window
556
+ # this could be e.g. [0, 1, 2, 5, 6, 9]
557
+ for index, delays in all_delays.items():
558
+ for delay in delays:
559
+ # since the windows are atomic, we have three cases:
560
+ # (1) the delay starts at the time window, (2) it ends with it, (3) it contains it
561
+ if delay.start <= window[0] and delay.end >= window[1]:
562
+ active.append(index)
563
+ delay.add_window(window)
564
+ op_map[(index, window)] = delay
565
+
566
+ # check which are adjacent
567
+ # on a linear topology, we would get [[0, 1, 2], [5, 6], [9]]
568
+ visited = defaultdict(lambda: False)
569
+ grouped = []
570
+ for start_index in active:
571
+ if visited[start_index]:
572
+ continue
573
+
574
+ active_neighbors = {start_index}
575
+ _dfs(start_index, self._coupling_map, active_neighbors, active)
576
+
577
+ for index in active_neighbors:
578
+ visited[index] = True
579
+
580
+ group = tuple(sorted(active_neighbors)) # must be sorted to merge later
581
+ grouped.append(group)
582
+
583
+ # sanity check: groups must be disjoint
584
+ if logger.isEnabledFor(logging.DEBUG):
585
+ for i, g1 in enumerate(grouped):
586
+ for g2 in grouped[i + 1 :]:
587
+ if len(set(g1).intersection(g2)) > 0:
588
+ logger.debug("Groups not disjoint: %s and %s.", g1, g2)
589
+
590
+ active_delays[window] = grouped
591
+
592
+ # 3) Merge time-adjacent active delays
593
+ merged_delays = []
594
+ open_groups = {}
595
+ for window in windows:
596
+ next_groups = active_delays[window]
597
+
598
+ # check which opened groups are still active in this window
599
+ next_open_groups = {}
600
+ for open_group, delay in open_groups.items():
601
+ if open_group in next_groups:
602
+ # extend the delay operation and remove a breakpoint
603
+ delay.end = window[1]
604
+ for op in delay.ops:
605
+ op.breakpoints.remove(window[0])
606
+
607
+ if logger.isEnabledFor(logging.DEBUG):
608
+ involved_ops = {op_map[(index, window)] for index in open_group}
609
+ if len(involved_ops.difference(delay.ops)) > 0:
610
+ logger.debug("Not all involved operations are part of the joint delay.")
611
+
612
+ next_open_groups[open_group] = delay
613
+ else:
614
+ # the group has not been extended, close it!
615
+ merged_delays.append(delay)
616
+
617
+ # add the new open groups
618
+ for group in next_groups:
619
+ if group not in open_groups:
620
+ involved_ops = {op_map[(index, window)] for index in group}
621
+ next_open_groups[group] = MultiDelayOp(
622
+ start=window[0], end=window[1], indices=group, ops=involved_ops
623
+ )
624
+
625
+ open_groups = next_open_groups
626
+
627
+ # add all from the final layer
628
+ for open_group, delay in open_groups.items():
629
+ merged_delays.append(delay)
630
+
631
+ logger.debug("Split adjacent delay block into %s", "\n".join(map(str, merged_delays)))
632
+
633
+ return merged_delays
634
+
635
+ def _duration(self, node: DAGOpNode, qubit_map: dict[Qubit, int]) -> float:
636
+ # this is cached on the target, so we can repeatedly call it w/o penalty
637
+ instruction_durations = self._target.durations()
638
+ indices = [qubit_map[bit] for bit in node.qargs]
639
+
640
+ return instruction_durations.get(node.op, indices)
641
+
642
+
643
+ class EventType(Enum):
644
+ """Delay event type, which is either begin or end."""
645
+
646
+ BEGIN = 0
647
+ END = 1
648
+
649
+
650
+ @dataclass
651
+ class DelayEvent:
652
+ """Represent a single-qubit delay event, which is either begin or end of a delay instruction."""
653
+
654
+ type: EventType
655
+ time: int # Time in this circuit, in dt
656
+ op_node: DAGOpNode # The node for the circuit delay
657
+
658
+ @staticmethod
659
+ def sort_key(event: DelayEvent) -> tuple(int, int):
660
+ """Sort events, first by time then by type ('end' events come before 'begin' events)."""
661
+ return (
662
+ event.time, # Sort by event time
663
+ 0 if event.type == EventType.END else 1, # With 'end' events before 'begin'
664
+ )
665
+
666
+
667
+ @dataclass
668
+ class DelayOp:
669
+ """Represent a delay operation."""
670
+
671
+ start: int
672
+ index: int
673
+ op: DAGOpNode # the circuit op node this represents
674
+ end: int | None = None # None means currently unknown
675
+ breakpoints: list[int] = field(
676
+ default_factory=list
677
+ ) # timepoints at which the delay op is split
678
+ # circuit with which we will replace this delay
679
+ start_times: list[int] = field(default_factory=list)
680
+ replacement: QuantumCircuit = field(default_factory=lambda: QuantumCircuit(1))
681
+
682
+ def __hash__(self):
683
+ return hash(self.op)
684
+
685
+ def add_window(self, window: tuple[int, int]):
686
+ """Add a time window to the delay op.
687
+
688
+ This means the delay is active during this window and we add a potential breakpoint.
689
+ """
690
+ if self.end is None:
691
+ raise ValueError("Cannot add a window if DelayOp.end is None. Please set it.")
692
+
693
+ start, end = window
694
+ if self.start < start and start not in self.breakpoints:
695
+ self.breakpoints.append(start)
696
+ if self.end > end and end not in self.breakpoints:
697
+ self.breakpoints.append(end)
698
+
699
+ self.breakpoints = sorted(self.breakpoints)
700
+
701
+
702
+ @dataclass
703
+ class MultiDelayOp:
704
+ """A multi-qubit delay operation."""
705
+
706
+ start: int
707
+ end: int
708
+ indices: list[int]
709
+ ops: set[DelayOp]
710
+ replacing: set[DAGOpNode] = field(default_factory=set)
711
+
712
+ def __str__(self) -> str:
713
+ return f"MultiDelay({self.start}:{self.end} on {self.indices})"
714
+
715
+
716
+ @dataclass
717
+ class AdjacentDelayBlock:
718
+ """Group of circuit delays which are collectively adjacent in time and on device.
719
+
720
+ For example, here the 3 delay operations on q0, q1 and q2 form an adjacent delay block.
721
+
722
+ q0: -██████--------- | qubits q0,q1,q2 have adjacent delay
723
+ q1: ------███████--- | operations, since the delay operations
724
+ q2: --█████████----- | all overlap
725
+ q3: -----------████- -> this delay starts when delay on q2 ends, so they have no overlap
726
+ q4: ----████-------- -> this clearly has no overlap with something else
727
+
728
+ """
729
+
730
+ events: list[DelayEvent]
731
+ active_qubits: set[Qubit]
732
+
733
+ def validate(self, log: bool = True) -> None:
734
+ """Validate the list of delay events in the adjacent block.
735
+
736
+ Args:
737
+ log: If ``True`` log invalid blocks on DEBUG level. Otherwise raise an error if the
738
+ block is invalid.
739
+
740
+ Raises:
741
+ RuntimeError: If the blocks are not ordered by time and event type.
742
+ """
743
+
744
+ def notify(msg, *args):
745
+ if log:
746
+ logger.debug(msg, *args)
747
+ else:
748
+ raise RuntimeError(msg.format(*args))
749
+
750
+ for idx, event in enumerate(self.events[:-1]):
751
+ if event.time > self.events[idx + 1].time:
752
+ notify("adjacent_delay_block.events not ordered by time")
753
+
754
+ if event.time == self.events[idx + 1].time:
755
+ # At same time, can either be ('begin', 'begin'), ('end', 'begin') or ('end', 'end')
756
+ if (event.type, self.events[idx + 1].type) == (EventType.BEGIN, EventType.END):
757
+ notify(
758
+ "Events in the AdjacentDelayBlock are not correctly sorted by "
759
+ "event type. At same time, we can have either of (begin, begin), "
760
+ "(end, begin) or (end, end). This happened at time %s.",
761
+ event.time,
762
+ )
763
+
764
+
765
+ def _dfs(qubit, cmap: CouplingMap, visited, active_qubits):
766
+ """Depth-first search to get the widest group of idle qubits during a given time frame."""
767
+ # use coupling_map.graph.neighbors_undirected once Qiskit/rustworkx#1254 is in a release
768
+ neighbors = {other for other in active_qubits if cmap.distance(qubit, other) == 1}
769
+ for neighbor in neighbors:
770
+ if neighbor in active_qubits and neighbor not in visited:
771
+ visited.add(neighbor)
772
+ _dfs(neighbor, cmap, visited, active_qubits)
773
+
774
+
775
+ class WalshHadamardSequence:
776
+ """Get Walsh-Hadamard sequences for DD up to arbitrary order."""
777
+
778
+ def __init__(self, max_order: int = 5):
779
+ """
780
+ Args:
781
+ max_order: The maximal order for which the sequences are computed.
782
+ """
783
+ # these are set in set_max_order
784
+ self.sequences = None
785
+ self.max_order = None
786
+
787
+ self.set_max_order(max_order)
788
+
789
+ def set_max_order(self, max_order: int) -> None:
790
+ """Set the maximal available order."""
791
+ if self.max_order is not None:
792
+ if max_order <= self.max_order:
793
+ return
794
+
795
+ # get the dimension of the transformation matrix we need,
796
+ # this is given by the smallest power of 2 that includes max_order
797
+ num_krons = int(np.ceil(np.log2(max_order + 1)))
798
+ self.max_order = 2**num_krons - 1
799
+
800
+ rows = _get_transformation_matrix(num_krons).tolist()
801
+ distances = [_bitflips_to_timings(row) for row in rows]
802
+ num_flips = [len(distance) - 1 for distance in distances]
803
+
804
+ # sort by the number of flips and throw out the first one,
805
+ # which corresponds to the 000... bit-sequence, i.e., no flip
806
+ indices = np.argsort(num_flips)[1:]
807
+
808
+ self.sequences = [distances[i] for i in indices]
809
+
810
+ def get_sequence(self, order: int) -> list[float]:
811
+ """Get the Walsh-Hadamard sequence of given order (starts at 0)."""
812
+ if order > self.max_order:
813
+ self.set_max_order(order)
814
+
815
+ return self.sequences[order]
816
+
817
+
818
+ def _get_transformation_matrix(n):
819
+ """Get a 2^n x 2^n Walsh-Hadamard matrix with elements in [0, 1]."""
820
+
821
+ from qiskit.circuit.library import HGate
822
+
823
+ had = np.array(HGate()).real * np.sqrt(2)
824
+
825
+ def recurse(matrix, m):
826
+ # we build the matrix recursively, adding one Hadamard kronecker product per recursion
827
+ if m == 1:
828
+ # finally, map to [0, 1] by the mapping (1 - H) / 2
829
+ return ((1 - matrix) / 2).astype(int)
830
+
831
+ return recurse(np.kron(had, matrix), m - 1)
832
+
833
+ return recurse(had, n)
834
+
835
+
836
+ def _bitflips_to_timings(row):
837
+ num = len(row)
838
+ distances = []
839
+ count = 0
840
+ last = 0 # start in no flip state
841
+ for el in row:
842
+ if el == last:
843
+ count += 1
844
+ else:
845
+ distances.append(count / num)
846
+ last = el
847
+ count = 1
848
+
849
+ distances.append(count / num)
850
+
851
+ if len(distances) % 2 == 0:
852
+ return distances + [0]
853
+
854
+ return distances
855
+
856
+
857
+ def _gate_length_variance(target: Target) -> float:
858
+ max_length, min_length = None, None
859
+
860
+ for gate, properties in target.items():
861
+ if gate not in ["cx", "cz", "ecr"]:
862
+ continue
863
+
864
+ for prop in properties.values():
865
+ duration = prop.duration
866
+ if max_length is None or max_length < duration:
867
+ max_length = duration
868
+ if min_length is None or min_length > duration:
869
+ min_length = duration
870
+
871
+ # it could be that there are no 2q gates available, in this
872
+ # case we just return 0, which will mean we join all idle times
873
+ if max_length is None or min_length is None:
874
+ return 0
875
+
876
+ return max_length - min_length