qiskit 1.0.2__cp38-abi3-win32.whl → 1.1.0__cp38-abi3-win32.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 (263) hide show
  1. qiskit/VERSION.txt +1 -1
  2. qiskit/__init__.py +27 -16
  3. qiskit/_accelerate.pyd +0 -0
  4. qiskit/_numpy_compat.py +73 -0
  5. qiskit/assembler/__init__.py +5 -10
  6. qiskit/assembler/disassemble.py +5 -6
  7. qiskit/circuit/__init__.py +1061 -232
  8. qiskit/circuit/_classical_resource_map.py +10 -6
  9. qiskit/circuit/_utils.py +18 -8
  10. qiskit/circuit/annotated_operation.py +21 -0
  11. qiskit/circuit/barrier.py +10 -13
  12. qiskit/circuit/bit.py +0 -1
  13. qiskit/circuit/classical/__init__.py +2 -2
  14. qiskit/circuit/classical/expr/__init__.py +39 -5
  15. qiskit/circuit/classical/expr/constructors.py +84 -1
  16. qiskit/circuit/classical/expr/expr.py +83 -13
  17. qiskit/circuit/classical/expr/visitors.py +83 -0
  18. qiskit/circuit/classical/types/__init__.py +5 -4
  19. qiskit/circuit/classicalfunction/__init__.py +1 -0
  20. qiskit/circuit/commutation_checker.py +86 -51
  21. qiskit/circuit/controlflow/_builder_utils.py +9 -1
  22. qiskit/circuit/controlflow/break_loop.py +8 -22
  23. qiskit/circuit/controlflow/builder.py +116 -1
  24. qiskit/circuit/controlflow/continue_loop.py +8 -22
  25. qiskit/circuit/controlflow/control_flow.py +47 -8
  26. qiskit/circuit/controlflow/for_loop.py +8 -23
  27. qiskit/circuit/controlflow/if_else.py +13 -27
  28. qiskit/circuit/controlflow/switch_case.py +14 -21
  29. qiskit/circuit/controlflow/while_loop.py +9 -23
  30. qiskit/circuit/controlledgate.py +2 -2
  31. qiskit/circuit/delay.py +7 -5
  32. qiskit/circuit/gate.py +20 -7
  33. qiskit/circuit/instruction.py +31 -30
  34. qiskit/circuit/instructionset.py +9 -22
  35. qiskit/circuit/library/__init__.py +3 -13
  36. qiskit/circuit/library/arithmetic/integer_comparator.py +2 -2
  37. qiskit/circuit/library/arithmetic/quadratic_form.py +3 -2
  38. qiskit/circuit/library/blueprintcircuit.py +29 -7
  39. qiskit/circuit/library/data_preparation/state_preparation.py +6 -5
  40. qiskit/circuit/library/generalized_gates/diagonal.py +5 -4
  41. qiskit/circuit/library/generalized_gates/isometry.py +51 -254
  42. qiskit/circuit/library/generalized_gates/pauli.py +2 -2
  43. qiskit/circuit/library/generalized_gates/permutation.py +4 -1
  44. qiskit/circuit/library/generalized_gates/rv.py +15 -11
  45. qiskit/circuit/library/generalized_gates/uc.py +2 -98
  46. qiskit/circuit/library/generalized_gates/unitary.py +9 -4
  47. qiskit/circuit/library/hamiltonian_gate.py +11 -5
  48. qiskit/circuit/library/n_local/efficient_su2.py +5 -5
  49. qiskit/circuit/library/n_local/n_local.py +100 -49
  50. qiskit/circuit/library/n_local/two_local.py +3 -59
  51. qiskit/circuit/library/overlap.py +3 -3
  52. qiskit/circuit/library/phase_oracle.py +1 -1
  53. qiskit/circuit/library/quantum_volume.py +39 -38
  54. qiskit/circuit/library/standard_gates/equivalence_library.py +50 -0
  55. qiskit/circuit/library/standard_gates/global_phase.py +4 -2
  56. qiskit/circuit/library/standard_gates/i.py +1 -2
  57. qiskit/circuit/library/standard_gates/iswap.py +1 -2
  58. qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +11 -5
  59. qiskit/circuit/library/standard_gates/p.py +31 -15
  60. qiskit/circuit/library/standard_gates/r.py +4 -3
  61. qiskit/circuit/library/standard_gates/rx.py +7 -4
  62. qiskit/circuit/library/standard_gates/rxx.py +4 -3
  63. qiskit/circuit/library/standard_gates/ry.py +7 -4
  64. qiskit/circuit/library/standard_gates/ryy.py +4 -3
  65. qiskit/circuit/library/standard_gates/rz.py +7 -4
  66. qiskit/circuit/library/standard_gates/rzx.py +4 -3
  67. qiskit/circuit/library/standard_gates/rzz.py +4 -3
  68. qiskit/circuit/library/standard_gates/s.py +4 -8
  69. qiskit/circuit/library/standard_gates/t.py +2 -4
  70. qiskit/circuit/library/standard_gates/u.py +16 -11
  71. qiskit/circuit/library/standard_gates/u1.py +6 -2
  72. qiskit/circuit/library/standard_gates/u2.py +4 -2
  73. qiskit/circuit/library/standard_gates/u3.py +9 -5
  74. qiskit/circuit/library/standard_gates/x.py +22 -11
  75. qiskit/circuit/library/standard_gates/xx_minus_yy.py +4 -3
  76. qiskit/circuit/library/standard_gates/xx_plus_yy.py +7 -5
  77. qiskit/circuit/library/standard_gates/z.py +1 -2
  78. qiskit/circuit/measure.py +4 -1
  79. qiskit/circuit/operation.py +13 -8
  80. qiskit/circuit/parameter.py +11 -6
  81. qiskit/circuit/quantumcircuit.py +1910 -260
  82. qiskit/circuit/quantumcircuitdata.py +2 -2
  83. qiskit/circuit/reset.py +5 -2
  84. qiskit/circuit/store.py +95 -0
  85. qiskit/compiler/assembler.py +22 -22
  86. qiskit/compiler/transpiler.py +63 -112
  87. qiskit/converters/__init__.py +17 -2
  88. qiskit/converters/circuit_to_dag.py +7 -0
  89. qiskit/converters/circuit_to_dagdependency_v2.py +47 -0
  90. qiskit/converters/circuit_to_gate.py +2 -0
  91. qiskit/converters/circuit_to_instruction.py +22 -0
  92. qiskit/converters/dag_to_circuit.py +4 -0
  93. qiskit/converters/dag_to_dagdependency_v2.py +44 -0
  94. qiskit/dagcircuit/collect_blocks.py +15 -10
  95. qiskit/dagcircuit/dagcircuit.py +434 -124
  96. qiskit/dagcircuit/dagdependency.py +19 -12
  97. qiskit/dagcircuit/dagdependency_v2.py +641 -0
  98. qiskit/dagcircuit/dagdepnode.py +19 -16
  99. qiskit/dagcircuit/dagnode.py +14 -4
  100. qiskit/passmanager/passmanager.py +11 -11
  101. qiskit/primitives/__init__.py +22 -12
  102. qiskit/primitives/backend_estimator.py +3 -5
  103. qiskit/primitives/backend_estimator_v2.py +410 -0
  104. qiskit/primitives/backend_sampler_v2.py +287 -0
  105. qiskit/primitives/base/base_estimator.py +4 -9
  106. qiskit/primitives/base/base_sampler.py +2 -2
  107. qiskit/primitives/containers/__init__.py +6 -4
  108. qiskit/primitives/containers/bit_array.py +293 -2
  109. qiskit/primitives/containers/data_bin.py +123 -50
  110. qiskit/primitives/containers/estimator_pub.py +10 -3
  111. qiskit/primitives/containers/observables_array.py +2 -2
  112. qiskit/primitives/containers/pub_result.py +1 -1
  113. qiskit/primitives/containers/sampler_pub.py +19 -3
  114. qiskit/primitives/containers/sampler_pub_result.py +74 -0
  115. qiskit/primitives/containers/shape.py +4 -4
  116. qiskit/primitives/statevector_estimator.py +4 -4
  117. qiskit/primitives/statevector_sampler.py +7 -12
  118. qiskit/providers/__init__.py +65 -34
  119. qiskit/providers/backend.py +2 -2
  120. qiskit/providers/backend_compat.py +8 -10
  121. qiskit/providers/basic_provider/__init__.py +2 -23
  122. qiskit/providers/basic_provider/basic_provider_tools.py +67 -31
  123. qiskit/providers/basic_provider/basic_simulator.py +81 -21
  124. qiskit/providers/fake_provider/__init__.py +1 -1
  125. qiskit/providers/fake_provider/fake_1q.py +1 -1
  126. qiskit/providers/fake_provider/fake_backend.py +3 -408
  127. qiskit/providers/fake_provider/generic_backend_v2.py +26 -14
  128. qiskit/providers/models/__init__.py +2 -2
  129. qiskit/providers/provider.py +16 -0
  130. qiskit/pulse/builder.py +4 -1
  131. qiskit/pulse/parameter_manager.py +60 -4
  132. qiskit/pulse/schedule.py +29 -13
  133. qiskit/pulse/utils.py +61 -20
  134. qiskit/qasm2/__init__.py +1 -5
  135. qiskit/qasm2/parse.py +1 -4
  136. qiskit/qasm3/__init__.py +42 -5
  137. qiskit/qasm3/ast.py +19 -0
  138. qiskit/qasm3/exporter.py +178 -106
  139. qiskit/qasm3/printer.py +27 -5
  140. qiskit/qobj/converters/pulse_instruction.py +6 -6
  141. qiskit/qpy/__init__.py +299 -67
  142. qiskit/qpy/binary_io/circuits.py +216 -47
  143. qiskit/qpy/binary_io/schedules.py +42 -36
  144. qiskit/qpy/binary_io/value.py +201 -22
  145. qiskit/qpy/common.py +1 -1
  146. qiskit/qpy/exceptions.py +20 -0
  147. qiskit/qpy/formats.py +29 -0
  148. qiskit/qpy/type_keys.py +21 -0
  149. qiskit/quantum_info/analysis/distance.py +3 -3
  150. qiskit/quantum_info/analysis/make_observable.py +2 -1
  151. qiskit/quantum_info/analysis/z2_symmetries.py +2 -1
  152. qiskit/quantum_info/operators/channel/chi.py +9 -8
  153. qiskit/quantum_info/operators/channel/choi.py +10 -9
  154. qiskit/quantum_info/operators/channel/kraus.py +2 -1
  155. qiskit/quantum_info/operators/channel/ptm.py +10 -9
  156. qiskit/quantum_info/operators/channel/quantum_channel.py +2 -1
  157. qiskit/quantum_info/operators/channel/stinespring.py +2 -1
  158. qiskit/quantum_info/operators/channel/superop.py +12 -11
  159. qiskit/quantum_info/operators/channel/transformations.py +12 -11
  160. qiskit/quantum_info/operators/dihedral/dihedral.py +5 -4
  161. qiskit/quantum_info/operators/operator.py +43 -30
  162. qiskit/quantum_info/operators/scalar_op.py +10 -9
  163. qiskit/quantum_info/operators/symplectic/base_pauli.py +70 -59
  164. qiskit/quantum_info/operators/symplectic/clifford.py +36 -9
  165. qiskit/quantum_info/operators/symplectic/pauli.py +53 -6
  166. qiskit/quantum_info/operators/symplectic/pauli_list.py +36 -14
  167. qiskit/quantum_info/operators/symplectic/random.py +3 -2
  168. qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +61 -36
  169. qiskit/quantum_info/states/densitymatrix.py +13 -13
  170. qiskit/quantum_info/states/stabilizerstate.py +3 -3
  171. qiskit/quantum_info/states/statevector.py +14 -13
  172. qiskit/quantum_info/states/utils.py +5 -3
  173. qiskit/result/__init__.py +6 -0
  174. qiskit/result/mitigation/correlated_readout_mitigator.py +3 -2
  175. qiskit/result/mitigation/local_readout_mitigator.py +2 -1
  176. qiskit/result/mitigation/utils.py +3 -2
  177. qiskit/scheduler/__init__.py +10 -1
  178. qiskit/scheduler/methods/__init__.py +1 -8
  179. qiskit/synthesis/__init__.py +3 -6
  180. qiskit/synthesis/discrete_basis/commutator_decompose.py +2 -2
  181. qiskit/synthesis/evolution/lie_trotter.py +7 -14
  182. qiskit/synthesis/evolution/qdrift.py +3 -4
  183. qiskit/synthesis/linear/cnot_synth.py +1 -3
  184. qiskit/synthesis/linear/linear_circuits_utils.py +1 -1
  185. qiskit/synthesis/linear_phase/cz_depth_lnn.py +4 -18
  186. qiskit/synthesis/permutation/__init__.py +1 -0
  187. qiskit/synthesis/permutation/permutation_reverse_lnn.py +90 -0
  188. qiskit/synthesis/qft/qft_decompose_lnn.py +2 -6
  189. qiskit/synthesis/two_qubit/two_qubit_decompose.py +165 -954
  190. qiskit/synthesis/two_qubit/xx_decompose/circuits.py +13 -12
  191. qiskit/synthesis/two_qubit/xx_decompose/decomposer.py +7 -1
  192. qiskit/synthesis/unitary/aqc/__init__.py +1 -1
  193. qiskit/synthesis/unitary/aqc/cnot_structures.py +2 -1
  194. qiskit/synthesis/unitary/aqc/fast_gradient/fast_gradient.py +2 -1
  195. qiskit/synthesis/unitary/qsd.py +3 -2
  196. qiskit/transpiler/__init__.py +7 -3
  197. qiskit/transpiler/layout.py +140 -61
  198. qiskit/transpiler/passes/__init__.py +10 -2
  199. qiskit/transpiler/passes/basis/basis_translator.py +9 -4
  200. qiskit/transpiler/passes/basis/unroll_3q_or_more.py +1 -1
  201. qiskit/transpiler/passes/basis/unroll_custom_definitions.py +1 -1
  202. qiskit/transpiler/passes/calibration/rzx_builder.py +2 -1
  203. qiskit/transpiler/passes/layout/apply_layout.py +8 -3
  204. qiskit/transpiler/passes/layout/sabre_layout.py +15 -3
  205. qiskit/transpiler/passes/layout/set_layout.py +1 -1
  206. qiskit/transpiler/passes/optimization/__init__.py +2 -0
  207. qiskit/transpiler/passes/optimization/commutation_analysis.py +2 -2
  208. qiskit/transpiler/passes/optimization/commutative_cancellation.py +1 -1
  209. qiskit/transpiler/passes/optimization/consolidate_blocks.py +1 -1
  210. qiskit/transpiler/passes/optimization/cx_cancellation.py +10 -0
  211. qiskit/transpiler/passes/optimization/elide_permutations.py +114 -0
  212. qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +9 -3
  213. qiskit/transpiler/passes/optimization/optimize_annotated.py +248 -12
  214. qiskit/transpiler/passes/optimization/remove_final_reset.py +37 -0
  215. qiskit/transpiler/passes/optimization/template_matching/forward_match.py +1 -3
  216. qiskit/transpiler/passes/routing/__init__.py +1 -0
  217. qiskit/transpiler/passes/routing/basic_swap.py +13 -2
  218. qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +8 -1
  219. qiskit/transpiler/passes/routing/lookahead_swap.py +7 -1
  220. qiskit/transpiler/passes/routing/sabre_swap.py +10 -6
  221. qiskit/transpiler/passes/routing/star_prerouting.py +417 -0
  222. qiskit/transpiler/passes/routing/stochastic_swap.py +24 -8
  223. qiskit/transpiler/passes/scheduling/__init__.py +1 -1
  224. qiskit/transpiler/passes/scheduling/alap.py +1 -2
  225. qiskit/transpiler/passes/scheduling/alignments/align_measures.py +1 -2
  226. qiskit/transpiler/passes/scheduling/alignments/check_durations.py +9 -6
  227. qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py +8 -0
  228. qiskit/transpiler/passes/scheduling/alignments/reschedule.py +13 -4
  229. qiskit/transpiler/passes/scheduling/asap.py +1 -2
  230. qiskit/transpiler/passes/scheduling/base_scheduler.py +21 -2
  231. qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +26 -4
  232. qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +24 -2
  233. qiskit/transpiler/passes/scheduling/time_unit_conversion.py +28 -4
  234. qiskit/transpiler/passes/synthesis/aqc_plugin.py +2 -2
  235. qiskit/transpiler/passes/synthesis/high_level_synthesis.py +120 -13
  236. qiskit/transpiler/passes/synthesis/unitary_synthesis.py +162 -55
  237. qiskit/transpiler/passes/utils/gates_basis.py +3 -3
  238. qiskit/transpiler/passmanager.py +44 -1
  239. qiskit/transpiler/preset_passmanagers/__init__.py +3 -3
  240. qiskit/transpiler/preset_passmanagers/builtin_plugins.py +34 -16
  241. qiskit/transpiler/preset_passmanagers/common.py +4 -6
  242. qiskit/transpiler/preset_passmanagers/plugin.py +9 -1
  243. qiskit/utils/__init__.py +3 -2
  244. qiskit/utils/optionals.py +6 -2
  245. qiskit/utils/parallel.py +24 -15
  246. qiskit/visualization/array.py +1 -1
  247. qiskit/visualization/bloch.py +2 -3
  248. qiskit/visualization/circuit/matplotlib.py +44 -14
  249. qiskit/visualization/circuit/text.py +38 -18
  250. qiskit/visualization/counts_visualization.py +3 -6
  251. qiskit/visualization/dag_visualization.py +6 -7
  252. qiskit/visualization/gate_map.py +9 -1
  253. qiskit/visualization/pulse_v2/interface.py +8 -3
  254. qiskit/visualization/state_visualization.py +3 -2
  255. qiskit/visualization/timeline/interface.py +18 -8
  256. {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/METADATA +12 -8
  257. {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/RECORD +261 -251
  258. {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/WHEEL +1 -1
  259. qiskit/_qasm2.pyd +0 -0
  260. qiskit/_qasm3.pyd +0 -0
  261. {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/LICENSE.txt +0 -0
  262. {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/entry_points.txt +0 -0
  263. {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/top_level.txt +0 -0
@@ -61,7 +61,7 @@ class UnrollCustomDefinitions(TransformationPass):
61
61
  return dag
62
62
 
63
63
  if self._target is None:
64
- basic_insts = {"measure", "reset", "barrier", "snapshot", "delay"}
64
+ basic_insts = {"measure", "reset", "barrier", "snapshot", "delay", "store"}
65
65
  device_insts = basic_insts | set(self._basis_gates)
66
66
 
67
67
  for node in dag.op_nodes():
@@ -14,6 +14,7 @@
14
14
  from __future__ import annotations
15
15
 
16
16
  import enum
17
+ import math
17
18
  import warnings
18
19
  from collections.abc import Sequence
19
20
  from math import pi, erf
@@ -135,7 +136,7 @@ class RZXCalibrationBuilder(CalibrationBuilder):
135
136
  # The error function is used because the Gaussian may have chopped tails.
136
137
  # Area is normalized by amplitude.
137
138
  # This makes widths robust to the rounding error.
138
- risefall_area = params["sigma"] * np.sqrt(2 * pi) * erf(risefall_sigma_ratio)
139
+ risefall_area = params["sigma"] * math.sqrt(2 * pi) * erf(risefall_sigma_ratio)
139
140
  full_area = params["width"] + risefall_area
140
141
 
141
142
  # Get estimate of target area. Assume this is pi/2 controlled rotation.
@@ -56,11 +56,16 @@ class ApplyLayout(TransformationPass):
56
56
  raise TranspilerError("The 'layout' must be full (with ancilla).")
57
57
 
58
58
  post_layout = self.property_set["post_layout"]
59
-
60
59
  q = QuantumRegister(len(layout), "q")
61
60
 
62
61
  new_dag = DAGCircuit()
63
62
  new_dag.add_qreg(q)
63
+ for var in dag.iter_input_vars():
64
+ new_dag.add_input_var(var)
65
+ for var in dag.iter_captured_vars():
66
+ new_dag.add_captured_var(var)
67
+ for var in dag.iter_declared_vars():
68
+ new_dag.add_declared_var(var)
64
69
  new_dag.metadata = dag.metadata
65
70
  new_dag.add_clbits(dag.clbits)
66
71
  for creg in dag.cregs.values():
@@ -71,9 +76,9 @@ class ApplyLayout(TransformationPass):
71
76
  }
72
77
  for qreg in dag.qregs.values():
73
78
  self.property_set["layout"].add_register(qreg)
74
- virtual_phsyical_map = layout.get_virtual_bits()
79
+ virtual_physical_map = layout.get_virtual_bits()
75
80
  for node in dag.topological_op_nodes():
76
- qargs = [q[virtual_phsyical_map[qarg]] for qarg in node.qargs]
81
+ qargs = [q[virtual_physical_map[qarg]] for qarg in node.qargs]
77
82
  new_dag.apply_operation_back(node.op, qargs, node.cargs, check=False)
78
83
  else:
79
84
  # First build a new layout object going from:
@@ -35,8 +35,8 @@ from qiskit.transpiler.layout import Layout
35
35
  from qiskit.transpiler.basepasses import TransformationPass
36
36
  from qiskit.transpiler.exceptions import TranspilerError
37
37
  from qiskit._accelerate.nlayout import NLayout
38
- from qiskit._accelerate.sabre_layout import sabre_layout_and_routing
39
- from qiskit._accelerate.sabre_swap import (
38
+ from qiskit._accelerate.sabre import (
39
+ sabre_layout_and_routing,
40
40
  Heuristic,
41
41
  NeighborTable,
42
42
  )
@@ -308,11 +308,17 @@ class SabreLayout(TransformationPass):
308
308
  mapped_dag.add_clbits(dag.clbits)
309
309
  for creg in dag.cregs.values():
310
310
  mapped_dag.add_creg(creg)
311
+ for var in dag.iter_input_vars():
312
+ mapped_dag.add_input_var(var)
313
+ for var in dag.iter_captured_vars():
314
+ mapped_dag.add_captured_var(var)
315
+ for var in dag.iter_declared_vars():
316
+ mapped_dag.add_declared_var(var)
311
317
  mapped_dag._global_phase = dag._global_phase
312
318
  self.property_set["original_qubit_indices"] = {
313
319
  bit: index for index, bit in enumerate(dag.qubits)
314
320
  }
315
- self.property_set["final_layout"] = Layout(
321
+ final_layout = Layout(
316
322
  {
317
323
  mapped_dag.qubits[
318
324
  component.coupling_map.graph[initial]
@@ -321,6 +327,12 @@ class SabreLayout(TransformationPass):
321
327
  for initial, final in enumerate(component.final_permutation)
322
328
  }
323
329
  )
330
+ if self.property_set["final_layout"] is None:
331
+ self.property_set["final_layout"] = final_layout
332
+ else:
333
+ self.property_set["final_layout"] = final_layout.compose(
334
+ self.property_set["final_layout"], dag.qubits
335
+ )
324
336
  for component in components:
325
337
  # Sabre routing still returns all its swaps as on virtual qubits, so we need to expand
326
338
  # each component DAG with the virtual ancillas that were allocated to it, so the layout
@@ -63,7 +63,7 @@ class SetLayout(AnalysisPass):
63
63
  layout = None
64
64
  else:
65
65
  raise InvalidLayoutError(
66
- f"SetLayout was intialized with the layout type: {type(self.layout)}"
66
+ f"SetLayout was initialized with the layout type: {type(self.layout)}"
67
67
  )
68
68
  self.property_set["layout"] = layout
69
69
  return dag
@@ -24,6 +24,7 @@ from .cx_cancellation import CXCancellation
24
24
  from .optimize_1q_commutation import Optimize1qGatesSimpleCommutation
25
25
  from .optimize_swap_before_measure import OptimizeSwapBeforeMeasure
26
26
  from .remove_reset_in_zero_state import RemoveResetInZeroState
27
+ from .remove_final_reset import RemoveFinalReset
27
28
  from .remove_diagonal_gates_before_measure import RemoveDiagonalGatesBeforeMeasure
28
29
  from .hoare_opt import HoareOptimizer
29
30
  from .template_optimization import TemplateOptimization
@@ -34,5 +35,6 @@ from .collect_linear_functions import CollectLinearFunctions
34
35
  from .reset_after_measure_simplification import ResetAfterMeasureSimplification
35
36
  from .optimize_cliffords import OptimizeCliffords
36
37
  from .collect_cliffords import CollectCliffords
38
+ from .elide_permutations import ElidePermutations
37
39
  from .normalize_rx_angle import NormalizeRXAngle
38
40
  from .optimize_annotated import OptimizeAnnotated
@@ -47,7 +47,7 @@ class CommutationAnalysis(AnalysisPass):
47
47
  # self.property_set['commutation_set'][wire][(node, wire)] will give the
48
48
  # commutation set that contains node.
49
49
 
50
- for wire in dag.wires:
50
+ for wire in dag.qubits:
51
51
  self.property_set["commutation_set"][wire] = []
52
52
 
53
53
  # Add edges to the dictionary for each qubit
@@ -56,7 +56,7 @@ class CommutationAnalysis(AnalysisPass):
56
56
  self.property_set["commutation_set"][(node, edge_wire)] = -1
57
57
 
58
58
  # Construct the commutation set
59
- for wire in dag.wires:
59
+ for wire in dag.qubits:
60
60
 
61
61
  for current_gate in dag.nodes_on_wire(wire):
62
62
 
@@ -99,7 +99,7 @@ class CommutativeCancellation(TransformationPass):
99
99
  # - For 2qbit gates the key: (gate_type, first_qbit, sec_qbit, first commutation_set_id,
100
100
  # sec_commutation_set_id), the value is the list gates that share the same gate type,
101
101
  # qubits and commutation sets.
102
- for wire in dag.wires:
102
+ for wire in dag.qubits:
103
103
  wire_commutation_set = self.property_set["commutation_set"][wire]
104
104
 
105
105
  for com_set_idx, com_set in enumerate(wire_commutation_set):
@@ -131,7 +131,7 @@ class ConsolidateBlocks(TransformationPass):
131
131
  if ( # pylint: disable=too-many-boolean-expressions
132
132
  self.force_consolidate
133
133
  or unitary.num_qubits > 2
134
- or self.decomposer.num_basis_gates(unitary) < basis_count
134
+ or self.decomposer.num_basis_gates(matrix) < basis_count
135
135
  or len(block) > max_2q_depth
136
136
  or ((self.basis_gates is not None) and outside_basis)
137
137
  or ((self.target is not None) and outside_basis)
@@ -14,11 +14,21 @@
14
14
 
15
15
  from qiskit.transpiler.basepasses import TransformationPass
16
16
  from qiskit.transpiler.passes.utils import control_flow
17
+ from qiskit.utils.deprecation import deprecate_func
17
18
 
18
19
 
19
20
  class CXCancellation(TransformationPass):
20
21
  """Cancel back-to-back ``cx`` gates in dag."""
21
22
 
23
+ @deprecate_func(
24
+ additional_msg=(
25
+ "Instead, use :class:`~.InverseCancellation`, which is a more generic pass."
26
+ ),
27
+ since="1.1.0",
28
+ )
29
+ def __init__(self, *args, **kwargs):
30
+ super().__init__(*args, **kwargs)
31
+
22
32
  @control_flow.trivial_recurse
23
33
  def run(self, dag):
24
34
  """Run the CXCancellation pass on `dag`.
@@ -0,0 +1,114 @@
1
+ # This code is part of Qiskit.
2
+ #
3
+ # (C) Copyright IBM 2023
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
+
14
+ """Remove any swap gates in the circuit by pushing it through into a qubit permutation."""
15
+
16
+ import logging
17
+
18
+ from qiskit.circuit.library.standard_gates import SwapGate
19
+ from qiskit.circuit.library.generalized_gates import PermutationGate
20
+ from qiskit.transpiler.basepasses import TransformationPass
21
+ from qiskit.transpiler.layout import Layout
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class ElidePermutations(TransformationPass):
27
+ r"""Remove permutation operations from a pre-layout circuit
28
+
29
+ This pass is intended to be run before a layout (mapping virtual qubits
30
+ to physical qubits) is set during the transpilation pipeline. This
31
+ pass iterates over the :class:`~.DAGCircuit` and when a :class:`~.SwapGate`
32
+ or :class:`~.PermutationGate` are encountered it permutes the virtual qubits in
33
+ the circuit and removes the swap gate. This will effectively remove any
34
+ :class:`~SwapGate`\s or :class:`~PermutationGate` in the circuit prior to running
35
+ layout. If this pass is run after a layout has been set it will become a no-op
36
+ (and log a warning) as this optimization is not sound after physical qubits are
37
+ selected and there are connectivity constraints to adhere to.
38
+
39
+ For tracking purposes this pass sets 3 values in the property set if there
40
+ are any :class:`~.SwapGate` or :class:`~.PermutationGate` objects in the circuit
41
+ and the pass isn't a no-op.
42
+
43
+ * ``original_layout``: The trivial :class:`~.Layout` for the input to this pass being run
44
+ * ``original_qubit_indices``: The mapping of qubit objects to positional indices for the state
45
+ of the circuit as input to this pass.
46
+ * ``virtual_permutation_layout``: A :class:`~.Layout` object mapping input qubits to the output
47
+ state after eliding permutations.
48
+
49
+ These three properties are needed for the transpiler to track the permutations in the out
50
+ :attr:`.QuantumCircuit.layout` attribute. The elision of permutations is equivalent to a
51
+ ``final_layout`` set by routing and all three of these attributes are needed in the case
52
+ """
53
+
54
+ def run(self, dag):
55
+ """Run the ElidePermutations pass on ``dag``.
56
+
57
+ Args:
58
+ dag (DAGCircuit): the DAG to be optimized.
59
+
60
+ Returns:
61
+ DAGCircuit: the optimized DAG.
62
+ """
63
+ if self.property_set["layout"] is not None:
64
+ logger.warning(
65
+ "ElidePermutations is not valid after a layout has been set. This indicates "
66
+ "an invalid pass manager construction."
67
+ )
68
+ return dag
69
+
70
+ op_count = dag.count_ops(recurse=False)
71
+ if op_count.get("swap", 0) == 0 and op_count.get("permutation", 0) == 0:
72
+ return dag
73
+
74
+ new_dag = dag.copy_empty_like()
75
+ qubit_mapping = list(range(len(dag.qubits)))
76
+
77
+ def _apply_mapping(qargs):
78
+ return tuple(dag.qubits[qubit_mapping[dag.find_bit(qubit).index]] for qubit in qargs)
79
+
80
+ for node in dag.topological_op_nodes():
81
+ if not isinstance(node.op, (SwapGate, PermutationGate)):
82
+ new_dag.apply_operation_back(
83
+ node.op, _apply_mapping(node.qargs), node.cargs, check=False
84
+ )
85
+ elif getattr(node.op, "condition", None) is not None:
86
+ new_dag.apply_operation_back(
87
+ node.op, _apply_mapping(node.qargs), node.cargs, check=False
88
+ )
89
+ elif isinstance(node.op, SwapGate):
90
+ index_0 = dag.find_bit(node.qargs[0]).index
91
+ index_1 = dag.find_bit(node.qargs[1]).index
92
+ qubit_mapping[index_1], qubit_mapping[index_0] = (
93
+ qubit_mapping[index_0],
94
+ qubit_mapping[index_1],
95
+ )
96
+ elif isinstance(node.op, PermutationGate):
97
+ starting_indices = [qubit_mapping[dag.find_bit(qarg).index] for qarg in node.qargs]
98
+ pattern = node.op.params[0]
99
+ pattern_indices = [qubit_mapping[idx] for idx in pattern]
100
+ for i, j in zip(starting_indices, pattern_indices):
101
+ qubit_mapping[i] = j
102
+ input_qubit_mapping = {qubit: index for index, qubit in enumerate(dag.qubits)}
103
+ self.property_set["original_layout"] = Layout(input_qubit_mapping)
104
+ if self.property_set["original_qubit_indices"] is None:
105
+ self.property_set["original_qubit_indices"] = input_qubit_mapping
106
+
107
+ new_layout = Layout({dag.qubits[out]: idx for idx, out in enumerate(qubit_mapping)})
108
+ if current_layout := self.property_set["virtual_permutation_layout"]:
109
+ self.property_set["virtual_permutation_layout"] = new_layout.compose(
110
+ current_layout.inverse(dag.qubits, dag.qubits), dag.qubits
111
+ )
112
+ else:
113
+ self.property_set["virtual_permutation_layout"] = new_layout
114
+ return new_dag
@@ -34,6 +34,7 @@ from qiskit.circuit.library.standard_gates import (
34
34
  )
35
35
  from qiskit.circuit import Qubit
36
36
  from qiskit.dagcircuit.dagcircuit import DAGCircuit
37
+ from qiskit.dagcircuit.dagnode import DAGOpNode
37
38
 
38
39
 
39
40
  logger = logging.getLogger(__name__)
@@ -213,10 +214,15 @@ class Optimize1qGatesDecomposition(TransformationPass):
213
214
  if best_circuit_sequence is not None and self._substitution_checks(
214
215
  dag, run, best_circuit_sequence, basis, qubit
215
216
  ):
216
- new_dag = self._gate_sequence_to_dag(best_circuit_sequence)
217
- dag.substitute_node_with_dag(run[0], new_dag)
217
+ for gate_name, angles in best_circuit_sequence:
218
+ op = NAME_MAP[gate_name](*angles)
219
+ node = DAGOpNode(NAME_MAP[gate_name](*angles), run[0].qargs, dag=dag)
220
+ node._node_id = dag._multi_graph.add_node(node)
221
+ dag._increment_op(op)
222
+ dag._multi_graph.insert_node_on_in_edges(node._node_id, run[0]._node_id)
223
+ dag.global_phase += best_circuit_sequence.global_phase
218
224
  # Delete the other nodes in the run
219
- for current_node in run[1:]:
225
+ for current_node in run:
220
226
  dag.remove_op_node(current_node)
221
227
 
222
228
  return dag
@@ -12,12 +12,19 @@
12
12
 
13
13
  """Optimize annotated operations on a circuit."""
14
14
 
15
- from typing import Optional, List, Tuple
15
+ from typing import Optional, List, Tuple, Union
16
16
 
17
17
  from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES
18
18
  from qiskit.converters import circuit_to_dag, dag_to_circuit
19
19
  from qiskit.circuit.annotated_operation import AnnotatedOperation, _canonicalize_modifiers
20
- from qiskit.circuit import EquivalenceLibrary, ControlledGate, Operation, ControlFlowOp
20
+ from qiskit.circuit import (
21
+ QuantumCircuit,
22
+ Instruction,
23
+ EquivalenceLibrary,
24
+ ControlledGate,
25
+ Operation,
26
+ ControlFlowOp,
27
+ )
21
28
  from qiskit.transpiler.basepasses import TransformationPass
22
29
  from qiskit.transpiler.passes.utils import control_flow
23
30
  from qiskit.transpiler.target import Target
@@ -43,6 +50,11 @@ class OptimizeAnnotated(TransformationPass):
43
50
  ``g2 = AnnotatedOperation(g1, ControlModifier(2))``, then ``g2`` can be replaced with
44
51
  ``AnnotatedOperation(SwapGate(), [InverseModifier(), ControlModifier(2)])``.
45
52
 
53
+ * Applies conjugate reduction to annotated operations. As an example,
54
+ ``control - [P -- Q -- P^{-1}]`` can be rewritten as ``P -- control - [Q] -- P^{-1}``,
55
+ that is, only the middle part needs to be controlled. This also works for inverse
56
+ and power modifiers.
57
+
46
58
  """
47
59
 
48
60
  def __init__(
@@ -51,6 +63,7 @@ class OptimizeAnnotated(TransformationPass):
51
63
  equivalence_library: Optional[EquivalenceLibrary] = None,
52
64
  basis_gates: Optional[List[str]] = None,
53
65
  recurse: bool = True,
66
+ do_conjugate_reduction: bool = True,
54
67
  ):
55
68
  """
56
69
  OptimizeAnnotated initializer.
@@ -67,17 +80,19 @@ class OptimizeAnnotated(TransformationPass):
67
80
  not applied when neither is specified since such objects do not need to
68
81
  be synthesized). Setting this value to ``False`` precludes the recursion in
69
82
  every case.
83
+ do_conjugate_reduction: controls whether conjugate reduction should be performed.
70
84
  """
71
85
  super().__init__()
72
86
 
73
87
  self._target = target
74
88
  self._equiv_lib = equivalence_library
75
89
  self._basis_gates = basis_gates
90
+ self._do_conjugate_reduction = do_conjugate_reduction
76
91
 
77
92
  self._top_level_only = not recurse or (self._basis_gates is None and self._target is None)
78
93
 
79
94
  if not self._top_level_only and self._target is None:
80
- basic_insts = {"measure", "reset", "barrier", "snapshot", "delay"}
95
+ basic_insts = {"measure", "reset", "barrier", "snapshot", "delay", "store"}
81
96
  self._device_insts = basic_insts | set(self._basis_gates)
82
97
 
83
98
  def run(self, dag: DAGCircuit):
@@ -122,7 +137,11 @@ class OptimizeAnnotated(TransformationPass):
122
137
  # as they may remove annotated gates.
123
138
  dag, opt2 = self._recurse(dag)
124
139
 
125
- return dag, opt1 or opt2
140
+ opt3 = False
141
+ if not self._top_level_only and self._do_conjugate_reduction:
142
+ dag, opt3 = self._conjugate_reduction(dag)
143
+
144
+ return dag, opt1 or opt2 or opt3
126
145
 
127
146
  def _canonicalize(self, dag) -> Tuple[DAGCircuit, bool]:
128
147
  """
@@ -148,17 +167,219 @@ class OptimizeAnnotated(TransformationPass):
148
167
  did_something = True
149
168
  return dag, did_something
150
169
 
151
- def _recursively_process_definitions(self, op: Operation) -> bool:
170
+ def _conjugate_decomposition(
171
+ self, dag: DAGCircuit
172
+ ) -> Union[Tuple[DAGCircuit, DAGCircuit, DAGCircuit], None]:
152
173
  """
153
- Recursively applies optimizations to op's definition (or to op.base_op's
154
- definition if op is an annotated operation).
155
- Returns True if did something.
174
+ Decomposes a circuit ``A`` into 3 sub-circuits ``P``, ``Q``, ``R`` such that
175
+ ``A = P -- Q -- R`` and ``R = P^{-1}``.
176
+
177
+ This is accomplished by iteratively finding inverse nodes at the front and at the back of the
178
+ circuit.
156
179
  """
157
180
 
158
- # If op is an annotated operation, we descend into its base_op
159
- if isinstance(op, AnnotatedOperation):
160
- return self._recursively_process_definitions(op.base_op)
181
+ front_block = [] # nodes collected from the front of the circuit (aka P)
182
+ back_block = [] # nodes collected from the back of the circuit (aka R)
183
+
184
+ # Stores in- and out- degree for each node. These degrees are computed at the start of this
185
+ # function and are updated when nodes are collected into front_block or into back_block.
186
+ in_degree = {}
187
+ out_degree = {}
188
+
189
+ # We use dicts to track for each qubit a DAG node at the front of the circuit that involves
190
+ # this qubit and a DAG node at the end of the circuit that involves this qubit (when exist).
191
+ # Note that for the DAGCircuit structure for each qubit there can be at most one such front
192
+ # and such back node.
193
+ # This allows for an efficient way to find an inverse pair of gates (one from the front and
194
+ # one from the back of the circuit).
195
+ # A qubit that was never examined does not appear in these dicts, and a qubit that was examined
196
+ # but currently is not involved at the front (resp. at the back) of the circuit has the value of
197
+ # None.
198
+ front_node_for_qubit = {}
199
+ back_node_for_qubit = {}
200
+
201
+ # Keep the set of nodes that have been moved either to front_block or to back_block
202
+ processed_nodes = set()
203
+
204
+ # Keep the set of qubits that are involved in nodes at the front or at the back of the circuit.
205
+ # When looking for inverse pairs of gates we will only iterate over these qubits.
206
+ active_qubits = set()
207
+
208
+ # Keep pairs of nodes for which the inverse check was performed and the nodes
209
+ # were found to be not inverse to each other (memoization).
210
+ checked_node_pairs = set()
211
+
212
+ # compute in- and out- degree for every node
213
+ # also update information for nodes at the start and at the end of the circuit
214
+ for node in dag.op_nodes():
215
+ preds = list(dag.op_predecessors(node))
216
+ in_degree[node] = len(preds)
217
+ if len(preds) == 0:
218
+ for q in node.qargs:
219
+ front_node_for_qubit[q] = node
220
+ active_qubits.add(q)
221
+ succs = list(dag.op_successors(node))
222
+ out_degree[node] = len(succs)
223
+ if len(succs) == 0:
224
+ for q in node.qargs:
225
+ back_node_for_qubit[q] = node
226
+ active_qubits.add(q)
227
+
228
+ # iterate while there is a possibility to find more inverse pairs
229
+ while len(active_qubits) > 0:
230
+ to_check = active_qubits.copy()
231
+ active_qubits.clear()
232
+
233
+ # For each qubit q, check whether the gate at the front of the circuit that involves q
234
+ # and the gate at the end of the circuit that involves q are inverse
235
+ for q in to_check:
236
+
237
+ if (front_node := front_node_for_qubit.get(q, None)) is None:
238
+ continue
239
+ if (back_node := back_node_for_qubit.get(q, None)) is None:
240
+ continue
241
+
242
+ # front_node or back_node could be already collected when considering other qubits
243
+ if front_node in processed_nodes or back_node in processed_nodes:
244
+ continue
245
+
246
+ # it is possible that the same node is both at the front and at the back,
247
+ # it should not be collected
248
+ if front_node == back_node:
249
+ continue
250
+
251
+ # have been checked before
252
+ if (front_node, back_node) in checked_node_pairs:
253
+ continue
254
+
255
+ # fast check based on the arguments
256
+ if front_node.qargs != back_node.qargs or front_node.cargs != back_node.cargs:
257
+ continue
258
+
259
+ # in the future we want to include a more precise check whether a pair
260
+ # of nodes are inverse
261
+ if front_node.op == back_node.op.inverse():
262
+ # update front_node_for_qubit and back_node_for_qubit
263
+ for q in front_node.qargs:
264
+ front_node_for_qubit[q] = None
265
+ for q in back_node.qargs:
266
+ back_node_for_qubit[q] = None
267
+
268
+ # see which other nodes become at the front and update information
269
+ for node in dag.op_successors(front_node):
270
+ if node not in processed_nodes:
271
+ in_degree[node] -= 1
272
+ if in_degree[node] == 0:
273
+ for q in node.qargs:
274
+ front_node_for_qubit[q] = node
275
+ active_qubits.add(q)
276
+
277
+ # see which other nodes become at the back and update information
278
+ for node in dag.op_predecessors(back_node):
279
+ if node not in processed_nodes:
280
+ out_degree[node] -= 1
281
+ if out_degree[node] == 0:
282
+ for q in node.qargs:
283
+ back_node_for_qubit[q] = node
284
+ active_qubits.add(q)
285
+
286
+ # collect and mark as processed
287
+ front_block.append(front_node)
288
+ back_block.append(back_node)
289
+ processed_nodes.add(front_node)
290
+ processed_nodes.add(back_node)
291
+
292
+ else:
293
+ checked_node_pairs.add((front_node, back_node))
294
+
295
+ # if nothing is found, return None
296
+ if len(front_block) == 0:
297
+ return None
298
+
299
+ # create the output DAGs
300
+ front_circuit = dag.copy_empty_like()
301
+ middle_circuit = dag.copy_empty_like()
302
+ back_circuit = dag.copy_empty_like()
303
+ front_circuit.global_phase = 0
304
+ back_circuit.global_phase = 0
305
+
306
+ for node in front_block:
307
+ front_circuit.apply_operation_back(node.op, node.qargs, node.cargs)
308
+
309
+ for node in back_block:
310
+ back_circuit.apply_operation_front(node.op, node.qargs, node.cargs)
311
+
312
+ for node in dag.op_nodes():
313
+ if node not in processed_nodes:
314
+ middle_circuit.apply_operation_back(node.op, node.qargs, node.cargs)
315
+
316
+ return front_circuit, middle_circuit, back_circuit
317
+
318
+ def _conjugate_reduce_op(
319
+ self, op: AnnotatedOperation, base_decomposition: Tuple[DAGCircuit, DAGCircuit, DAGCircuit]
320
+ ) -> Operation:
321
+ """
322
+ We are given an annotated-operation ``op = M [ B ]`` (where ``B`` is the base operation and
323
+ ``M`` is the list of modifiers) and the "conjugate decomposition" of the definition of ``B``,
324
+ i.e. ``B = P * Q * R``, with ``R = P^{-1}`` (with ``P``, ``Q`` and ``R`` represented as
325
+ ``DAGCircuit`` objects).
326
+
327
+ Let ``IQ`` denote a new custom instruction with definitions ``Q``.
328
+
329
+ We return the operation ``op_new`` which a new custom instruction with definition
330
+ ``P * A * R``, where ``A`` is a new annotated-operation with modifiers ``M`` and
331
+ base gate ``IQ``.
332
+ """
333
+ p_dag, q_dag, r_dag = base_decomposition
334
+
335
+ q_instr = Instruction(
336
+ name="iq", num_qubits=op.base_op.num_qubits, num_clbits=op.base_op.num_clbits, params=[]
337
+ )
338
+ q_instr.definition = dag_to_circuit(q_dag)
339
+
340
+ op_new = Instruction(
341
+ "optimized", num_qubits=op.num_qubits, num_clbits=op.num_clbits, params=[]
342
+ )
343
+ num_control_qubits = op.num_qubits - op.base_op.num_qubits
344
+
345
+ circ = QuantumCircuit(op.num_qubits, op.num_clbits)
346
+ qubits = circ.qubits
347
+ circ.compose(
348
+ dag_to_circuit(p_dag), qubits[num_control_qubits : op.num_qubits], inplace=True
349
+ )
350
+ circ.append(
351
+ AnnotatedOperation(base_op=q_instr, modifiers=op.modifiers), range(op.num_qubits)
352
+ )
353
+ circ.compose(
354
+ dag_to_circuit(r_dag), qubits[num_control_qubits : op.num_qubits], inplace=True
355
+ )
356
+ op_new.definition = circ
357
+ return op_new
358
+
359
+ def _conjugate_reduction(self, dag) -> Tuple[DAGCircuit, bool]:
360
+ """
361
+ Looks for annotated operations whose base operation has a nontrivial conjugate decomposition.
362
+ In such cases, the modifiers of the annotated operation can be moved to the "middle" part of
363
+ the decomposition.
161
364
 
365
+ Returns the modified DAG and whether it did something.
366
+ """
367
+ did_something = False
368
+ for node in dag.op_nodes(op=AnnotatedOperation):
369
+ base_op = node.op.base_op
370
+ if not self._skip_definition(base_op):
371
+ base_dag = circuit_to_dag(base_op.definition, copy_operations=False)
372
+ base_decomposition = self._conjugate_decomposition(base_dag)
373
+ if base_decomposition is not None:
374
+ new_op = self._conjugate_reduce_op(node.op, base_decomposition)
375
+ dag.substitute_node(node, new_op)
376
+ did_something = True
377
+ return dag, did_something
378
+
379
+ def _skip_definition(self, op: Operation) -> bool:
380
+ """
381
+ Returns True if we should not recurse into a gate's definition.
382
+ """
162
383
  # Similar to HighLevelSynthesis transpiler pass, we do not recurse into a gate's
163
384
  # `definition` for a gate that is supported by the target or in equivalence library.
164
385
 
@@ -170,7 +391,22 @@ class OptimizeAnnotated(TransformationPass):
170
391
  else op.name in self._device_insts
171
392
  )
172
393
  if inst_supported or (self._equiv_lib is not None and self._equiv_lib.has_entry(op)):
173
- return False
394
+ return True
395
+ return False
396
+
397
+ def _recursively_process_definitions(self, op: Operation) -> bool:
398
+ """
399
+ Recursively applies optimizations to op's definition (or to op.base_op's
400
+ definition if op is an annotated operation).
401
+ Returns True if did something.
402
+ """
403
+
404
+ # If op is an annotated operation, we descend into its base_op
405
+ if isinstance(op, AnnotatedOperation):
406
+ return self._recursively_process_definitions(op.base_op)
407
+
408
+ if self._skip_definition(op):
409
+ return False
174
410
 
175
411
  try:
176
412
  # extract definition