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
@@ -20,10 +20,15 @@ to the input of B. The object's methods allow circuits to be constructed,
20
20
  composed, and modified. Some natural properties like depth can be computed
21
21
  directly from the graph.
22
22
  """
23
- from collections import OrderedDict, defaultdict, deque, namedtuple
23
+ from __future__ import annotations
24
+
24
25
  import copy
26
+ import enum
27
+ import itertools
25
28
  import math
26
- from typing import Dict, Generator, Any, List
29
+ from collections import OrderedDict, defaultdict, deque, namedtuple
30
+ from collections.abc import Callable, Sequence, Generator, Iterable
31
+ from typing import Any, Literal
27
32
 
28
33
  import numpy as np
29
34
  import rustworkx as rx
@@ -35,7 +40,10 @@ from qiskit.circuit import (
35
40
  WhileLoopOp,
36
41
  SwitchCaseOp,
37
42
  _classical_resource_map,
43
+ Operation,
44
+ Store,
38
45
  )
46
+ from qiskit.circuit.classical import expr
39
47
  from qiskit.circuit.controlflow import condition_resources, node_resources, CONTROL_FLOW_OP_NAMES
40
48
  from qiskit.circuit.quantumregister import QuantumRegister, Qubit
41
49
  from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
@@ -45,9 +53,11 @@ from qiskit.circuit.parameterexpression import ParameterExpression
45
53
  from qiskit.dagcircuit.exceptions import DAGCircuitError
46
54
  from qiskit.dagcircuit.dagnode import DAGNode, DAGOpNode, DAGInNode, DAGOutNode
47
55
  from qiskit.circuit.bit import Bit
48
-
56
+ from qiskit.pulse import Schedule
49
57
 
50
58
  BitLocations = namedtuple("BitLocations", ("index", "registers"))
59
+ # The allowable arguments to :meth:`DAGCircuit.copy_empty_like`'s ``vars_mode``.
60
+ _VarsMode = Literal["alike", "captures", "drop"]
51
61
 
52
62
 
53
63
  class DAGCircuit:
@@ -74,13 +84,24 @@ class DAGCircuit:
74
84
  # Cache of dag op node sort keys
75
85
  self._key_cache = {}
76
86
 
77
- # Set of wires (Register,idx) in the dag
87
+ # Set of wire data in the DAG. A wire is an owned unit of data. Qubits are the primary
88
+ # wire type (and the only data that has _true_ wire properties from a read/write
89
+ # perspective), but clbits and classical `Var`s are too. Note: classical registers are
90
+ # _not_ wires because the individual bits are the more fundamental unit. We treat `Var`s
91
+ # as the entire wire (as opposed to individual bits of them) for scalability reasons; if a
92
+ # parametric program wants to parametrize over 16-bit angles, we can't scale to 1000s of
93
+ # those by tracking all 16 bits individually.
94
+ #
95
+ # Classical variables shouldn't be "wires"; it should be possible to have multiple reads
96
+ # without implying ordering. The initial addition of the classical variables uses the
97
+ # existing wire structure as an MVP; we expect to handle this better in a new version of the
98
+ # transpiler IR that also handles control flow more properly.
78
99
  self._wires = set()
79
100
 
80
- # Map from wire (Register,idx) to input nodes of the graph
101
+ # Map from wire to input nodes of the graph
81
102
  self.input_map = OrderedDict()
82
103
 
83
- # Map from wire (Register,idx) to output nodes of the graph
104
+ # Map from wire to output nodes of the graph
84
105
  self.output_map = OrderedDict()
85
106
 
86
107
  # Directed multigraph whose nodes are inputs, outputs, or operations.
@@ -88,7 +109,7 @@ class DAGCircuit:
88
109
  # additional data about the operation, including the argument order
89
110
  # and parameter values.
90
111
  # Input nodes have out-degree 1 and output nodes have in-degree 1.
91
- # Edges carry wire labels (reg,idx) and each operation has
112
+ # Edges carry wire labels and each operation has
92
113
  # corresponding in- and out-edges with the same wire labels.
93
114
  self._multi_graph = rx.PyDAG()
94
115
 
@@ -97,18 +118,28 @@ class DAGCircuit:
97
118
  self.cregs = OrderedDict()
98
119
 
99
120
  # List of Qubit/Clbit wires that the DAG acts on.
100
- self.qubits: List[Qubit] = []
101
- self.clbits: List[Clbit] = []
121
+ self.qubits: list[Qubit] = []
122
+ self.clbits: list[Clbit] = []
102
123
 
103
124
  # Dictionary mapping of Qubit and Clbit instances to a tuple comprised of
104
125
  # 0) corresponding index in dag.{qubits,clbits} and
105
126
  # 1) a list of Register-int pairs for each Register containing the Bit and
106
127
  # its index within that register.
107
- self._qubit_indices: Dict[Qubit, BitLocations] = {}
108
- self._clbit_indices: Dict[Clbit, BitLocations] = {}
128
+ self._qubit_indices: dict[Qubit, BitLocations] = {}
129
+ self._clbit_indices: dict[Clbit, BitLocations] = {}
130
+ # Tracking for the classical variables used in the circuit. This contains the information
131
+ # needed to insert new nodes. This is keyed by the name rather than the `Var` instance
132
+ # itself so we can ensure we don't allow shadowing or redefinition of names.
133
+ self._vars_info: dict[str, _DAGVarInfo] = {}
134
+ # Convenience stateful tracking for the individual types of nodes to allow things like
135
+ # comparisons between circuits to take place without needing to disambiguate the
136
+ # graph-specific usage information.
137
+ self._vars_by_type: dict[_DAGVarType, set[expr.Var]] = {
138
+ type_: set() for type_ in _DAGVarType
139
+ }
109
140
 
110
- self._global_phase = 0
111
- self._calibrations = defaultdict(dict)
141
+ self._global_phase: float | ParameterExpression = 0.0
142
+ self._calibrations: dict[str, dict[tuple, Schedule]] = defaultdict(dict)
112
143
 
113
144
  self._op_names = {}
114
145
 
@@ -118,7 +149,11 @@ class DAGCircuit:
118
149
  @property
119
150
  def wires(self):
120
151
  """Return a list of the wires in order."""
121
- return self.qubits + self.clbits
152
+ return (
153
+ self.qubits
154
+ + self.clbits
155
+ + [var for vars in self._vars_by_type.values() for var in vars]
156
+ )
122
157
 
123
158
  @property
124
159
  def node_counter(self):
@@ -133,7 +168,7 @@ class DAGCircuit:
133
168
  return self._global_phase
134
169
 
135
170
  @global_phase.setter
136
- def global_phase(self, angle):
171
+ def global_phase(self, angle: float | ParameterExpression):
137
172
  """Set the global phase of the circuit.
138
173
 
139
174
  Args:
@@ -150,7 +185,7 @@ class DAGCircuit:
150
185
  self._global_phase = angle % (2 * math.pi)
151
186
 
152
187
  @property
153
- def calibrations(self):
188
+ def calibrations(self) -> dict[str, dict[tuple, Schedule]]:
154
189
  """Return calibration dictionary.
155
190
 
156
191
  The custom pulse definition of a given gate is of the form
@@ -159,7 +194,7 @@ class DAGCircuit:
159
194
  return dict(self._calibrations)
160
195
 
161
196
  @calibrations.setter
162
- def calibrations(self, calibrations):
197
+ def calibrations(self, calibrations: dict[str, dict[tuple, Schedule]]):
163
198
  """Set the circuit calibration data from a dictionary of calibration definition.
164
199
 
165
200
  Args:
@@ -293,6 +328,57 @@ class DAGCircuit:
293
328
  )
294
329
  self._add_wire(creg[j])
295
330
 
331
+ def add_input_var(self, var: expr.Var):
332
+ """Add an input variable to the circuit.
333
+
334
+ Args:
335
+ var: the variable to add."""
336
+ if self._vars_by_type[_DAGVarType.CAPTURE]:
337
+ raise DAGCircuitError("cannot add inputs to a circuit with captures")
338
+ self._add_var(var, _DAGVarType.INPUT)
339
+
340
+ def add_captured_var(self, var: expr.Var):
341
+ """Add a captured variable to the circuit.
342
+
343
+ Args:
344
+ var: the variable to add."""
345
+ if self._vars_by_type[_DAGVarType.INPUT]:
346
+ raise DAGCircuitError("cannot add captures to a circuit with inputs")
347
+ self._add_var(var, _DAGVarType.CAPTURE)
348
+
349
+ def add_declared_var(self, var: expr.Var):
350
+ """Add a declared local variable to the circuit.
351
+
352
+ Args:
353
+ var: the variable to add."""
354
+ self._add_var(var, _DAGVarType.DECLARE)
355
+
356
+ def _add_var(self, var: expr.Var, type_: _DAGVarType):
357
+ """Inner function to add any variable to the DAG. ``location`` should be a reference one of
358
+ the ``self._vars_*`` tracking dictionaries.
359
+ """
360
+ # The setup of the initial graph structure between an "in" and an "out" node is the same as
361
+ # the bit-related `_add_wire`, but this logically needs to do different bookkeeping around
362
+ # tracking the properties.
363
+ if not var.standalone:
364
+ raise DAGCircuitError(
365
+ "cannot add variables that wrap `Clbit` or `ClassicalRegister` instances"
366
+ )
367
+ if (previous := self._vars_info.get(var.name, None)) is not None:
368
+ if previous.var == var:
369
+ raise DAGCircuitError(f"'{var}' is already present in the circuit")
370
+ raise DAGCircuitError(
371
+ f"cannot add '{var}' as its name shadows the existing '{previous.var}'"
372
+ )
373
+ in_node = DAGInNode(wire=var)
374
+ out_node = DAGOutNode(wire=var)
375
+ in_node._node_id, out_node._node_id = self._multi_graph.add_nodes_from((in_node, out_node))
376
+ self._multi_graph.add_edge(in_node._node_id, out_node._node_id, var)
377
+ self.input_map[var] = in_node
378
+ self.output_map[var] = out_node
379
+ self._vars_by_type[type_].add(var)
380
+ self._vars_info[var.name] = _DAGVarInfo(var, type_, in_node, out_node)
381
+
296
382
  def _add_wire(self, wire):
297
383
  """Add a qubit or bit to the circuit.
298
384
 
@@ -539,14 +625,14 @@ class DAGCircuit:
539
625
  if not set(resources.clbits).issubset(self.clbits):
540
626
  raise DAGCircuitError(f"invalid clbits in condition for {name}")
541
627
 
542
- def _check_bits(self, args, amap):
543
- """Check the values of a list of (qu)bit arguments.
628
+ def _check_wires(self, args: Iterable[Bit | expr.Var], amap: dict[Bit | expr.Var, Any]):
629
+ """Check the values of a list of wire arguments.
544
630
 
545
631
  For each element of args, check that amap contains it.
546
632
 
547
633
  Args:
548
- args (list[Bit]): the elements to be checked
549
- amap (dict): a dictionary keyed on Qubits/Clbits
634
+ args: the elements to be checked
635
+ amap: a dictionary keyed on Qubits/Clbits
550
636
 
551
637
  Raises:
552
638
  DAGCircuitError: if a qubit is not contained in amap
@@ -554,46 +640,7 @@ class DAGCircuit:
554
640
  # Check for each wire
555
641
  for wire in args:
556
642
  if wire not in amap:
557
- raise DAGCircuitError(f"(qu)bit {wire} not found in {amap}")
558
-
559
- @staticmethod
560
- def _bits_in_operation(operation):
561
- """Return an iterable over the classical bits that are inherent to an instruction. This
562
- includes a `condition`, or the `target` of a :class:`.ControlFlowOp`.
563
-
564
- Args:
565
- instruction: the :class:`~.circuit.Instruction` instance for a node.
566
-
567
- Returns:
568
- Iterable[Clbit]: the :class:`.Clbit`\\ s involved.
569
- """
570
- # If updating this, also update the fast-path checker `DAGCirucit._operation_may_have_bits`.
571
- if (condition := getattr(operation, "condition", None)) is not None:
572
- yield from condition_resources(condition).clbits
573
- if isinstance(operation, SwitchCaseOp):
574
- target = operation.target
575
- if isinstance(target, Clbit):
576
- yield target
577
- elif isinstance(target, ClassicalRegister):
578
- yield from target
579
- else:
580
- yield from node_resources(target).clbits
581
-
582
- @staticmethod
583
- def _operation_may_have_bits(operation) -> bool:
584
- """Return whether a given :class:`.Operation` may contain any :class:`.Clbit` instances
585
- in itself (e.g. a control-flow operation).
586
-
587
- Args:
588
- operation (qiskit.circuit.Operation): the operation to check.
589
- """
590
- # This is separate to `_bits_in_operation` because most of the time there won't be any bits,
591
- # so we want a fast path to be able to skip creating and testing a generator for emptiness.
592
- #
593
- # If updating this, also update `DAGCirucit._bits_in_operation`.
594
- return getattr(operation, "condition", None) is not None or isinstance(
595
- operation, SwitchCaseOp
596
- )
643
+ raise DAGCircuitError(f"wire {wire} not found in {amap}")
597
644
 
598
645
  def _increment_op(self, op):
599
646
  if op.name in self._op_names:
@@ -607,14 +654,32 @@ class DAGCircuit:
607
654
  else:
608
655
  self._op_names[op.name] -= 1
609
656
 
610
- def copy_empty_like(self):
657
+ def copy_empty_like(self, *, vars_mode: _VarsMode = "alike"):
611
658
  """Return a copy of self with the same structure but empty.
612
659
 
613
660
  That structure includes:
614
661
  * name and other metadata
615
662
  * global phase
616
663
  * duration
617
- * all the qubits and clbits, including the registers.
664
+ * all the qubits and clbits, including the registers
665
+ * all the classical variables, with a mode defined by ``vars_mode``.
666
+
667
+ Args:
668
+ vars_mode: The mode to handle realtime variables in.
669
+
670
+ alike
671
+ The variables in the output DAG will have the same declaration semantics as
672
+ in the original circuit. For example, ``input`` variables in the source will be
673
+ ``input`` variables in the output DAG.
674
+
675
+ captures
676
+ All variables will be converted to captured variables. This is useful when you
677
+ are building a new layer for an existing DAG that you will want to
678
+ :meth:`compose` onto the base, since :meth:`compose` can inline captures onto
679
+ the base circuit (but not other variables).
680
+
681
+ drop
682
+ The output DAG will have no variables defined.
618
683
 
619
684
  Returns:
620
685
  DAGCircuit: An empty copy of self.
@@ -635,9 +700,31 @@ class DAGCircuit:
635
700
  for creg in self.cregs.values():
636
701
  target_dag.add_creg(creg)
637
702
 
703
+ if vars_mode == "alike":
704
+ for var in self.iter_input_vars():
705
+ target_dag.add_input_var(var)
706
+ for var in self.iter_captured_vars():
707
+ target_dag.add_captured_var(var)
708
+ for var in self.iter_declared_vars():
709
+ target_dag.add_declared_var(var)
710
+ elif vars_mode == "captures":
711
+ for var in self.iter_vars():
712
+ target_dag.add_captured_var(var)
713
+ elif vars_mode == "drop":
714
+ pass
715
+ else: # pragma: no cover
716
+ raise ValueError(f"unknown vars_mode: '{vars_mode}'")
717
+
638
718
  return target_dag
639
719
 
640
- def apply_operation_back(self, op, qargs=(), cargs=(), *, check=True):
720
+ def apply_operation_back(
721
+ self,
722
+ op: Operation,
723
+ qargs: Iterable[Qubit] = (),
724
+ cargs: Iterable[Clbit] = (),
725
+ *,
726
+ check: bool = True,
727
+ ) -> DAGOpNode:
641
728
  """Apply an operation to the output of the circuit.
642
729
 
643
730
  Args:
@@ -658,17 +745,17 @@ class DAGCircuit:
658
745
  """
659
746
  qargs = tuple(qargs)
660
747
  cargs = tuple(cargs)
748
+ additional = ()
661
749
 
662
- if self._operation_may_have_bits(op):
750
+ if _may_have_additional_wires(op):
663
751
  # This is the slow path; most of the time, this won't happen.
664
- all_cbits = set(self._bits_in_operation(op)).union(cargs)
665
- else:
666
- all_cbits = cargs
752
+ additional = set(_additional_wires(op)).difference(cargs)
667
753
 
668
754
  if check:
669
755
  self._check_condition(op.name, getattr(op, "condition", None))
670
- self._check_bits(qargs, self.output_map)
671
- self._check_bits(all_cbits, self.output_map)
756
+ self._check_wires(qargs, self.output_map)
757
+ self._check_wires(cargs, self.output_map)
758
+ self._check_wires(additional, self.output_map)
672
759
 
673
760
  node = DAGOpNode(op=op, qargs=qargs, cargs=cargs, dag=self)
674
761
  node._node_id = self._multi_graph.add_node(node)
@@ -679,11 +766,18 @@ class DAGCircuit:
679
766
  # and adding new edges from the operation node to each output node
680
767
  self._multi_graph.insert_node_on_in_edges_multiple(
681
768
  node._node_id,
682
- [self.output_map[bit]._node_id for bits in (qargs, all_cbits) for bit in bits],
769
+ [self.output_map[bit]._node_id for bits in (qargs, cargs, additional) for bit in bits],
683
770
  )
684
771
  return node
685
772
 
686
- def apply_operation_front(self, op, qargs=(), cargs=(), *, check=True):
773
+ def apply_operation_front(
774
+ self,
775
+ op: Operation,
776
+ qargs: Sequence[Qubit] = (),
777
+ cargs: Sequence[Clbit] = (),
778
+ *,
779
+ check: bool = True,
780
+ ) -> DAGOpNode:
687
781
  """Apply an operation to the input of the circuit.
688
782
 
689
783
  Args:
@@ -703,17 +797,17 @@ class DAGCircuit:
703
797
  """
704
798
  qargs = tuple(qargs)
705
799
  cargs = tuple(cargs)
800
+ additional = ()
706
801
 
707
- if self._operation_may_have_bits(op):
802
+ if _may_have_additional_wires(op):
708
803
  # This is the slow path; most of the time, this won't happen.
709
- all_cbits = set(self._bits_in_operation(op)).union(cargs)
710
- else:
711
- all_cbits = cargs
804
+ additional = set(_additional_wires(op)).difference(cargs)
712
805
 
713
806
  if check:
714
807
  self._check_condition(op.name, getattr(op, "condition", None))
715
- self._check_bits(qargs, self.input_map)
716
- self._check_bits(all_cbits, self.input_map)
808
+ self._check_wires(qargs, self.output_map)
809
+ self._check_wires(cargs, self.output_map)
810
+ self._check_wires(additional, self.output_map)
717
811
 
718
812
  node = DAGOpNode(op=op, qargs=qargs, cargs=cargs, dag=self)
719
813
  node._node_id = self._multi_graph.add_node(node)
@@ -724,11 +818,13 @@ class DAGCircuit:
724
818
  # and adding new edges to the operation node from each input node
725
819
  self._multi_graph.insert_node_on_out_edges_multiple(
726
820
  node._node_id,
727
- [self.input_map[bit]._node_id for bits in (qargs, all_cbits) for bit in bits],
821
+ [self.input_map[bit]._node_id for bits in (qargs, cargs, additional) for bit in bits],
728
822
  )
729
823
  return node
730
824
 
731
- def compose(self, other, qubits=None, clbits=None, front=False, inplace=True):
825
+ def compose(
826
+ self, other, qubits=None, clbits=None, front=False, inplace=True, *, inline_captures=False
827
+ ):
732
828
  """Compose the ``other`` circuit onto the output of this circuit.
733
829
 
734
830
  A subset of input wires of ``other`` are mapped
@@ -742,6 +838,18 @@ class DAGCircuit:
742
838
  clbits (list[Clbit|int]): clbits of self to compose onto.
743
839
  front (bool): If True, front composition will be performed (not implemented yet)
744
840
  inplace (bool): If True, modify the object. Otherwise return composed circuit.
841
+ inline_captures (bool): If ``True``, variables marked as "captures" in the ``other`` DAG
842
+ will inlined onto existing uses of those same variables in ``self``. If ``False``,
843
+ all variables in ``other`` are required to be distinct from ``self``, and they will
844
+ be added to ``self``.
845
+
846
+ ..
847
+ Note: unlike `QuantumCircuit.compose`, there's no `var_remap` argument here. That's
848
+ because the `DAGCircuit` inner-block structure isn't set up well to allow the recursion,
849
+ and `DAGCircuit.compose` is generally only used to rebuild a DAG from layers within
850
+ itself than to join unrelated circuits. While there's no strong motivating use-case
851
+ (unlike the `QuantumCircuit` equivalent), it's safer and more performant to not provide
852
+ the option.
745
853
 
746
854
  Returns:
747
855
  DAGCircuit: the composed dag (returns None if inplace==True).
@@ -804,27 +912,52 @@ class DAGCircuit:
804
912
  for gate, cals in other.calibrations.items():
805
913
  dag._calibrations[gate].update(cals)
806
914
 
915
+ # This is all the handling we need for realtime variables, if there's no remapping. They:
916
+ #
917
+ # * get added to the DAG and then operations involving them get appended on normally.
918
+ # * get inlined onto an existing variable, then operations get appended normally.
919
+ # * there's a clash or a failed inlining, and we just raise an error.
920
+ #
921
+ # Notably if there's no remapping, there's no need to recurse into control-flow or to do any
922
+ # Var rewriting during the Expr visits.
923
+ for var in other.iter_input_vars():
924
+ dag.add_input_var(var)
925
+ if inline_captures:
926
+ for var in other.iter_captured_vars():
927
+ if not dag.has_var(var):
928
+ raise DAGCircuitError(
929
+ f"Variable '{var}' to be inlined is not in the base DAG."
930
+ " If you wanted it to be automatically added, use `inline_captures=False`."
931
+ )
932
+ else:
933
+ for var in other.iter_captured_vars():
934
+ dag.add_captured_var(var)
935
+ for var in other.iter_declared_vars():
936
+ dag.add_declared_var(var)
937
+
807
938
  # Ensure that the error raised here is a `DAGCircuitError` for backwards compatibility.
808
939
  def _reject_new_register(reg):
809
940
  raise DAGCircuitError(f"No register with '{reg.bits}' to map this expression onto.")
810
941
 
811
942
  variable_mapper = _classical_resource_map.VariableMapper(
812
- dag.cregs.values(), edge_map, _reject_new_register
943
+ dag.cregs.values(), edge_map, add_register=_reject_new_register
813
944
  )
814
945
  for nd in other.topological_nodes():
815
946
  if isinstance(nd, DAGInNode):
816
- # if in edge_map, get new name, else use existing name
817
- m_wire = edge_map.get(nd.wire, nd.wire)
818
- # the mapped wire should already exist
819
- if m_wire not in dag.output_map:
820
- raise DAGCircuitError(
821
- "wire %s[%d] not in self" % (m_wire.register.name, m_wire.index)
822
- )
823
- if nd.wire not in other._wires:
824
- raise DAGCircuitError(
825
- "inconsistent wire type for %s[%d] in other"
826
- % (nd.register.name, nd.wire.index)
827
- )
947
+ if isinstance(nd.wire, Bit):
948
+ # if in edge_map, get new name, else use existing name
949
+ m_wire = edge_map.get(nd.wire, nd.wire)
950
+ # the mapped wire should already exist
951
+ if m_wire not in dag.output_map:
952
+ raise DAGCircuitError(
953
+ "wire %s[%d] not in self" % (m_wire.register.name, m_wire.index)
954
+ )
955
+ if nd.wire not in other._wires:
956
+ raise DAGCircuitError(
957
+ "inconsistent wire type for %s[%d] in other"
958
+ % (nd.register.name, nd.wire.index)
959
+ )
960
+ # If it's a Var wire, we already checked that it exists in the destination.
828
961
  elif isinstance(nd, DAGOutNode):
829
962
  # ignore output nodes
830
963
  pass
@@ -1012,6 +1145,52 @@ class DAGCircuit:
1012
1145
  """Compute how many components the circuit can decompose into."""
1013
1146
  return rx.number_weakly_connected_components(self._multi_graph)
1014
1147
 
1148
+ @property
1149
+ def num_vars(self):
1150
+ """Total number of classical variables tracked by the circuit."""
1151
+ return len(self._vars_info)
1152
+
1153
+ @property
1154
+ def num_input_vars(self):
1155
+ """Number of input classical variables tracked by the circuit."""
1156
+ return len(self._vars_by_type[_DAGVarType.INPUT])
1157
+
1158
+ @property
1159
+ def num_captured_vars(self):
1160
+ """Number of captured classical variables tracked by the circuit."""
1161
+ return len(self._vars_by_type[_DAGVarType.CAPTURE])
1162
+
1163
+ @property
1164
+ def num_declared_vars(self):
1165
+ """Number of declared local classical variables tracked by the circuit."""
1166
+ return len(self._vars_by_type[_DAGVarType.DECLARE])
1167
+
1168
+ def iter_vars(self):
1169
+ """Iterable over all the classical variables tracked by the circuit."""
1170
+ return itertools.chain.from_iterable(self._vars_by_type.values())
1171
+
1172
+ def iter_input_vars(self):
1173
+ """Iterable over the input classical variables tracked by the circuit."""
1174
+ return iter(self._vars_by_type[_DAGVarType.INPUT])
1175
+
1176
+ def iter_captured_vars(self):
1177
+ """Iterable over the captured classical variables tracked by the circuit."""
1178
+ return iter(self._vars_by_type[_DAGVarType.CAPTURE])
1179
+
1180
+ def iter_declared_vars(self):
1181
+ """Iterable over the declared local classical variables tracked by the circuit."""
1182
+ return iter(self._vars_by_type[_DAGVarType.DECLARE])
1183
+
1184
+ def has_var(self, var: str | expr.Var) -> bool:
1185
+ """Is this realtime variable in the DAG?
1186
+
1187
+ Args:
1188
+ var: the variable or name to check.
1189
+ """
1190
+ if isinstance(var, str):
1191
+ return var in self._vars_info
1192
+ return (info := self._vars_info.get(var.name, False)) and info.var is var
1193
+
1015
1194
  def __eq__(self, other):
1016
1195
  # Try to convert to float, but in case of unbound ParameterExpressions
1017
1196
  # a TypeError will be raise, fallback to normal equality in those
@@ -1029,6 +1208,11 @@ class DAGCircuit:
1029
1208
  if self.calibrations != other.calibrations:
1030
1209
  return False
1031
1210
 
1211
+ # We don't do any semantic equivalence between Var nodes, as things stand; DAGs can only be
1212
+ # equal in our mind if they use the exact same UUID vars.
1213
+ if self._vars_by_type != other._vars_by_type:
1214
+ return False
1215
+
1032
1216
  self_bit_indices = {bit: idx for idx, bit in enumerate(self.qubits + self.clbits)}
1033
1217
  other_bit_indices = {bit: idx for idx, bit in enumerate(other.qubits + other.clbits)}
1034
1218
 
@@ -1075,7 +1259,7 @@ class DAGCircuit:
1075
1259
 
1076
1260
  return iter(rx.lexicographical_topological_sort(self._multi_graph, key=key))
1077
1261
 
1078
- def topological_op_nodes(self, key=None) -> Generator[DAGOpNode, Any, Any]:
1262
+ def topological_op_nodes(self, key: Callable | None = None) -> Generator[DAGOpNode, Any, Any]:
1079
1263
  """
1080
1264
  Yield op nodes in topological order.
1081
1265
 
@@ -1092,7 +1276,9 @@ class DAGCircuit:
1092
1276
  """
1093
1277
  return (nd for nd in self.topological_nodes(key) if isinstance(nd, DAGOpNode))
1094
1278
 
1095
- def replace_block_with_op(self, node_block, op, wire_pos_map, cycle_check=True):
1279
+ def replace_block_with_op(
1280
+ self, node_block: list[DAGOpNode], op: Operation, wire_pos_map, cycle_check=True
1281
+ ):
1096
1282
  """Replace a block of nodes with a single node.
1097
1283
 
1098
1284
  This is used to consolidate a block of DAGOpNodes into a single
@@ -1110,7 +1296,8 @@ class DAGCircuit:
1110
1296
  multiple gates in the combined single op node. If a :class:`.Bit` is not in the
1111
1297
  dictionary, it will not be added to the args; this can be useful when dealing with
1112
1298
  control-flow operations that have inherent bits in their ``condition`` or ``target``
1113
- fields.
1299
+ fields. :class:`.expr.Var` wires similarly do not need to be in this map, since
1300
+ they will never be in ``qargs`` or ``cargs``.
1114
1301
  cycle_check (bool): When set to True this method will check that
1115
1302
  replacing the provided ``node_block`` with a single node
1116
1303
  would introduce a cycle (which would invalidate the
@@ -1177,12 +1364,22 @@ class DAGCircuit:
1177
1364
 
1178
1365
  Args:
1179
1366
  node (DAGOpNode): node to substitute
1180
- input_dag (DAGCircuit): circuit that will substitute the node
1367
+ input_dag (DAGCircuit): circuit that will substitute the node.
1181
1368
  wires (list[Bit] | Dict[Bit, Bit]): gives an order for (qu)bits
1182
1369
  in the input circuit. If a list, then the bits refer to those in the ``input_dag``,
1183
1370
  and the order gets matched to the node wires by qargs first, then cargs, then
1184
1371
  conditions. If a dictionary, then a mapping of bits in the ``input_dag`` to those
1185
1372
  that the ``node`` acts on.
1373
+
1374
+ Standalone :class:`~.expr.Var` nodes cannot currently be remapped as part of the
1375
+ substitution; the ``input_dag`` should be defined over the correct set of variables
1376
+ already.
1377
+
1378
+ ..
1379
+ The rule about not remapping `Var`s is to avoid performance pitfalls and reduce
1380
+ complexity; the creator of the input DAG should easily be able to arrange for
1381
+ the correct `Var`s to be used, and doing so avoids us needing to recurse through
1382
+ control-flow operations to do deep remappings.
1186
1383
  propagate_condition (bool): If ``True`` (default), then any ``condition`` attribute on
1187
1384
  the operation within ``node`` is propagated to each node in the ``input_dag``. If
1188
1385
  ``False``, then the ``input_dag`` is assumed to faithfully implement suitable
@@ -1207,9 +1404,9 @@ class DAGCircuit:
1207
1404
  node_wire_order = list(node.qargs) + list(node.cargs)
1208
1405
  # If we're not propagating it, the number of wires in the input DAG should include the
1209
1406
  # condition as well.
1210
- if not propagate_condition and self._operation_may_have_bits(node.op):
1407
+ if not propagate_condition and _may_have_additional_wires(node.op):
1211
1408
  node_wire_order += [
1212
- bit for bit in self._bits_in_operation(node.op) if bit not in node_cargs
1409
+ wire for wire in _additional_wires(node.op) if wire not in node_cargs
1213
1410
  ]
1214
1411
  if len(wires) != len(node_wire_order):
1215
1412
  raise DAGCircuitError(
@@ -1221,12 +1418,27 @@ class DAGCircuit:
1221
1418
  for input_dag_wire, our_wire in wire_map.items():
1222
1419
  if our_wire not in self.input_map:
1223
1420
  raise DAGCircuitError(f"bit mapping invalid: {our_wire} is not in this DAG")
1421
+ if isinstance(our_wire, expr.Var) or isinstance(input_dag_wire, expr.Var):
1422
+ raise DAGCircuitError("`Var` nodes cannot be remapped during substitution")
1224
1423
  # Support mapping indiscriminately between Qubit and AncillaQubit, etc.
1225
1424
  check_type = Qubit if isinstance(our_wire, Qubit) else Clbit
1226
1425
  if not isinstance(input_dag_wire, check_type):
1227
1426
  raise DAGCircuitError(
1228
1427
  f"bit mapping invalid: {input_dag_wire} and {our_wire} are different bit types"
1229
1428
  )
1429
+ if _may_have_additional_wires(node.op):
1430
+ node_vars = {var for var in _additional_wires(node.op) if isinstance(var, expr.Var)}
1431
+ else:
1432
+ node_vars = set()
1433
+ dag_vars = set(input_dag.iter_vars())
1434
+ if dag_vars - node_vars:
1435
+ raise DAGCircuitError(
1436
+ "Cannot replace a node with a DAG with more variables."
1437
+ f" Variables in node: {node_vars}."
1438
+ f" Variables in DAG: {dag_vars}."
1439
+ )
1440
+ for var in dag_vars:
1441
+ wire_map[var] = var
1230
1442
 
1231
1443
  reverse_wire_map = {b: a for a, b in wire_map.items()}
1232
1444
  # It doesn't make sense to try and propagate a condition from a control-flow op; a
@@ -1305,14 +1517,22 @@ class DAGCircuit:
1305
1517
  node._node_id, lambda edge, wire=self_wire: edge == wire
1306
1518
  )[0]
1307
1519
  self._multi_graph.add_edge(pred._node_id, succ._node_id, self_wire)
1520
+ for contracted_var in node_vars - dag_vars:
1521
+ pred = self._multi_graph.find_predecessors_by_edge(
1522
+ node._node_id, lambda edge, wire=contracted_var: edge == wire
1523
+ )[0]
1524
+ succ = self._multi_graph.find_successors_by_edge(
1525
+ node._node_id, lambda edge, wire=contracted_var: edge == wire
1526
+ )[0]
1527
+ self._multi_graph.add_edge(pred._node_id, succ._node_id, contracted_var)
1308
1528
 
1309
1529
  # Exlude any nodes from in_dag that are not a DAGOpNode or are on
1310
- # bits outside the set specified by the wires kwarg
1530
+ # wires outside the set specified by the wires kwarg
1311
1531
  def filter_fn(node):
1312
1532
  if not isinstance(node, DAGOpNode):
1313
1533
  return False
1314
- for qarg in node.qargs:
1315
- if qarg not in wire_map:
1534
+ for _, _, wire in in_dag.edges(node):
1535
+ if wire not in wire_map:
1316
1536
  return False
1317
1537
  return True
1318
1538
 
@@ -1349,7 +1569,7 @@ class DAGCircuit:
1349
1569
  self._decrement_op(node.op)
1350
1570
 
1351
1571
  variable_mapper = _classical_resource_map.VariableMapper(
1352
- self.cregs.values(), wire_map, self.add_creg
1572
+ self.cregs.values(), wire_map, add_register=self.add_creg
1353
1573
  )
1354
1574
  # Iterate over nodes of input_circuit and update wires in node objects migrated
1355
1575
  # from in_dag
@@ -1381,7 +1601,7 @@ class DAGCircuit:
1381
1601
 
1382
1602
  return {k: self._multi_graph[v] for k, v in node_map.items()}
1383
1603
 
1384
- def substitute_node(self, node, op, inplace=False, propagate_condition=True):
1604
+ def substitute_node(self, node: DAGOpNode, op, inplace: bool = False, propagate_condition=True):
1385
1605
  """Replace an DAGOpNode with a single operation. qargs, cargs and
1386
1606
  conditions for the new operation will be inferred from the node to be
1387
1607
  replaced. The new operation will be checked to match the shape of the
@@ -1421,21 +1641,12 @@ class DAGCircuit:
1421
1641
  # This might include wires that are inherent to the node, like in its `condition` or
1422
1642
  # `target` fields, so might be wider than `node.op.num_{qu,cl}bits`.
1423
1643
  current_wires = {wire for _, _, wire in self.edges(node)}
1424
- new_wires = set(node.qargs) | set(node.cargs)
1425
- if (new_condition := getattr(op, "condition", None)) is not None:
1426
- new_wires.update(condition_resources(new_condition).clbits)
1427
- elif isinstance(op, SwitchCaseOp):
1428
- if isinstance(op.target, Clbit):
1429
- new_wires.add(op.target)
1430
- elif isinstance(op.target, ClassicalRegister):
1431
- new_wires.update(op.target)
1432
- else:
1433
- new_wires.update(node_resources(op.target).clbits)
1644
+ new_wires = set(node.qargs) | set(node.cargs) | set(_additional_wires(op))
1434
1645
 
1435
1646
  if propagate_condition and not (
1436
1647
  isinstance(node.op, ControlFlowOp) or isinstance(op, ControlFlowOp)
1437
1648
  ):
1438
- if new_condition is not None:
1649
+ if getattr(op, "condition", None) is not None:
1439
1650
  raise DAGCircuitError(
1440
1651
  "Cannot propagate a condition to an operation that already has one."
1441
1652
  )
@@ -1471,13 +1682,17 @@ class DAGCircuit:
1471
1682
  self._decrement_op(node.op)
1472
1683
  return new_node
1473
1684
 
1474
- def separable_circuits(self, remove_idle_qubits=False) -> List["DAGCircuit"]:
1685
+ def separable_circuits(
1686
+ self, remove_idle_qubits: bool = False, *, vars_mode: _VarsMode = "alike"
1687
+ ) -> list["DAGCircuit"]:
1475
1688
  """Decompose the circuit into sets of qubits with no gates connecting them.
1476
1689
 
1477
1690
  Args:
1478
1691
  remove_idle_qubits (bool): Flag denoting whether to remove idle qubits from
1479
1692
  the separated circuits. If ``False``, each output circuit will contain the
1480
1693
  same number of qubits as ``self``.
1694
+ vars_mode: how any realtime :class:`~.expr.Var` nodes should be handled in the output
1695
+ DAGs. See :meth:`copy_empty_like` for details on the modes.
1481
1696
 
1482
1697
  Returns:
1483
1698
  List[DAGCircuit]: The circuits resulting from separating ``self`` into sets
@@ -1502,7 +1717,7 @@ class DAGCircuit:
1502
1717
  # Create new DAGCircuit objects from each of the rustworkx subgraph objects
1503
1718
  decomposed_dags = []
1504
1719
  for subgraph in disconnected_subgraphs:
1505
- new_dag = self.copy_empty_like()
1720
+ new_dag = self.copy_empty_like(vars_mode=vars_mode)
1506
1721
  new_dag.global_phase = 0
1507
1722
  subgraph_is_classical = True
1508
1723
  for node in rx.lexicographical_topological_sort(subgraph, key=_key):
@@ -1664,6 +1879,14 @@ class DAGCircuit:
1664
1879
  """Returns iterator of the predecessors of a node as DAGOpNodes and DAGInNodes."""
1665
1880
  return iter(self._multi_graph.predecessors(node._node_id))
1666
1881
 
1882
+ def op_successors(self, node):
1883
+ """Returns iterator of "op" successors of a node in the dag."""
1884
+ return (succ for succ in self.successors(node) if isinstance(succ, DAGOpNode))
1885
+
1886
+ def op_predecessors(self, node):
1887
+ """Returns the iterator of "op" predecessors of a node in the dag."""
1888
+ return (pred for pred in self.predecessors(node) if isinstance(pred, DAGOpNode))
1889
+
1667
1890
  def is_successor(self, node, node_succ):
1668
1891
  """Checks if a second node is in the successors of node."""
1669
1892
  return self._multi_graph.has_edge(node._node_id, node_succ._node_id)
@@ -1686,7 +1909,7 @@ class DAGCircuit:
1686
1909
  connected by a classical edge as DAGOpNodes and DAGInNodes."""
1687
1910
  return iter(
1688
1911
  self._multi_graph.find_predecessors_by_edge(
1689
- node._node_id, lambda edge_data: isinstance(edge_data, Clbit)
1912
+ node._node_id, lambda edge_data: not isinstance(edge_data, Qubit)
1690
1913
  )
1691
1914
  )
1692
1915
 
@@ -1719,7 +1942,7 @@ class DAGCircuit:
1719
1942
  connected by a classical edge as DAGOpNodes and DAGInNodes."""
1720
1943
  return iter(
1721
1944
  self._multi_graph.find_successors_by_edge(
1722
- node._node_id, lambda edge_data: isinstance(edge_data, Clbit)
1945
+ node._node_id, lambda edge_data: not isinstance(edge_data, Qubit)
1723
1946
  )
1724
1947
  )
1725
1948
 
@@ -1784,7 +2007,7 @@ class DAGCircuit:
1784
2007
 
1785
2008
  return op_nodes
1786
2009
 
1787
- def layers(self):
2010
+ def layers(self, *, vars_mode: _VarsMode = "captures"):
1788
2011
  """Yield a shallow view on a layer of this DAGCircuit for all d layers of this circuit.
1789
2012
 
1790
2013
  A layer is a circuit whose gates act on disjoint qubits, i.e.,
@@ -1801,6 +2024,10 @@ class DAGCircuit:
1801
2024
  TODO: Gates that use the same cbits will end up in different
1802
2025
  layers as this is currently implemented. This may not be
1803
2026
  the desired behavior.
2027
+
2028
+ Args:
2029
+ vars_mode: how any realtime :class:`~.expr.Var` nodes should be handled in the output
2030
+ DAGs. See :meth:`copy_empty_like` for details on the modes.
1804
2031
  """
1805
2032
  graph_layers = self.multigraph_layers()
1806
2033
  try:
@@ -1825,7 +2052,7 @@ class DAGCircuit:
1825
2052
  return
1826
2053
 
1827
2054
  # Construct a shallow copy of self
1828
- new_layer = self.copy_empty_like()
2055
+ new_layer = self.copy_empty_like(vars_mode=vars_mode)
1829
2056
 
1830
2057
  for node in op_nodes:
1831
2058
  # this creates new DAGOpNodes in the new_layer
@@ -1840,14 +2067,18 @@ class DAGCircuit:
1840
2067
 
1841
2068
  yield {"graph": new_layer, "partition": support_list}
1842
2069
 
1843
- def serial_layers(self):
2070
+ def serial_layers(self, *, vars_mode: _VarsMode = "captures"):
1844
2071
  """Yield a layer for all gates of this circuit.
1845
2072
 
1846
2073
  A serial layer is a circuit with one gate. The layers have the
1847
2074
  same structure as in layers().
2075
+
2076
+ Args:
2077
+ vars_mode: how any realtime :class:`~.expr.Var` nodes should be handled in the output
2078
+ DAGs. See :meth:`copy_empty_like` for details on the modes.
1848
2079
  """
1849
2080
  for next_node in self.topological_op_nodes():
1850
- new_layer = self.copy_empty_like()
2081
+ new_layer = self.copy_empty_like(vars_mode=vars_mode)
1851
2082
 
1852
2083
  # Save the support of the operation we add to the layer
1853
2084
  support_list = []
@@ -1892,7 +2123,7 @@ class DAGCircuit:
1892
2123
  group_list = rx.collect_runs(self._multi_graph, filter_fn)
1893
2124
  return {tuple(x) for x in group_list}
1894
2125
 
1895
- def collect_1q_runs(self):
2126
+ def collect_1q_runs(self) -> list[list[DAGOpNode]]:
1896
2127
  """Return a set of non-conditional runs of 1q "op" nodes."""
1897
2128
 
1898
2129
  def filter_fn(node):
@@ -2103,3 +2334,82 @@ class DAGCircuit:
2103
2334
  from qiskit.visualization.dag_visualization import dag_drawer
2104
2335
 
2105
2336
  return dag_drawer(dag=self, scale=scale, filename=filename, style=style)
2337
+
2338
+
2339
+ class _DAGVarType(enum.Enum):
2340
+ INPUT = enum.auto()
2341
+ CAPTURE = enum.auto()
2342
+ DECLARE = enum.auto()
2343
+
2344
+
2345
+ class _DAGVarInfo:
2346
+ __slots__ = ("var", "type", "in_node", "out_node")
2347
+
2348
+ def __init__(self, var: expr.Var, type_: _DAGVarType, in_node: DAGInNode, out_node: DAGOutNode):
2349
+ self.var = var
2350
+ self.type = type_
2351
+ self.in_node = in_node
2352
+ self.out_node = out_node
2353
+
2354
+
2355
+ def _may_have_additional_wires(operation) -> bool:
2356
+ """Return whether a given :class:`.Operation` may contain references to additional wires
2357
+ locations within itself. If this is ``False``, it doesn't necessarily mean that the operation
2358
+ _will_ access memory inherently, but a ``True`` return guarantees that it won't.
2359
+
2360
+ The memory might be classical bits or classical variables, such as a control-flow operation or a
2361
+ store.
2362
+
2363
+ Args:
2364
+ operation (qiskit.circuit.Operation): the operation to check.
2365
+ """
2366
+ # This is separate to `_additional_wires` because most of the time there won't be any extra
2367
+ # wires beyond the explicit `qargs` and `cargs` so we want a fast path to be able to skip
2368
+ # creating and testing a generator for emptiness.
2369
+ #
2370
+ # If updating this, you most likely also need to update `_additional_wires`.
2371
+ return getattr(operation, "condition", None) is not None or isinstance(
2372
+ operation, (ControlFlowOp, Store)
2373
+ )
2374
+
2375
+
2376
+ def _additional_wires(operation) -> Iterable[Clbit | expr.Var]:
2377
+ """Return an iterable over the additional tracked memory usage in this operation. These
2378
+ additional wires include (for example, non-exhaustive) bits referred to by a ``condition`` or
2379
+ the classical variables involved in control-flow operations.
2380
+
2381
+ Args:
2382
+ operation: the :class:`~.circuit.Operation` instance for a node.
2383
+
2384
+ Returns:
2385
+ Iterable: the additional wires inherent to this operation.
2386
+ """
2387
+ # If updating this, you likely need to update `_may_have_additional_wires` too.
2388
+ if (condition := getattr(operation, "condition", None)) is not None:
2389
+ if isinstance(condition, expr.Expr):
2390
+ yield from _wires_from_expr(condition)
2391
+ else:
2392
+ yield from condition_resources(condition).clbits
2393
+ if isinstance(operation, ControlFlowOp):
2394
+ yield from operation.iter_captured_vars()
2395
+ if isinstance(operation, SwitchCaseOp):
2396
+ target = operation.target
2397
+ if isinstance(target, Clbit):
2398
+ yield target
2399
+ elif isinstance(target, ClassicalRegister):
2400
+ yield from target
2401
+ else:
2402
+ yield from _wires_from_expr(target)
2403
+ elif isinstance(operation, Store):
2404
+ yield from _wires_from_expr(operation.lvalue)
2405
+ yield from _wires_from_expr(operation.rvalue)
2406
+
2407
+
2408
+ def _wires_from_expr(node: expr.Expr) -> Iterable[Clbit | expr.Var]:
2409
+ for var in expr.iter_vars(node):
2410
+ if isinstance(var.var, Clbit):
2411
+ yield var.var
2412
+ elif isinstance(var.var, ClassicalRegister):
2413
+ yield from var.var
2414
+ else:
2415
+ yield var