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.
- qiskit/VERSION.txt +1 -1
- qiskit/__init__.py +27 -16
- qiskit/_accelerate.pyd +0 -0
- qiskit/_numpy_compat.py +73 -0
- qiskit/assembler/__init__.py +5 -10
- qiskit/assembler/disassemble.py +5 -6
- qiskit/circuit/__init__.py +1061 -232
- qiskit/circuit/_classical_resource_map.py +10 -6
- qiskit/circuit/_utils.py +18 -8
- qiskit/circuit/annotated_operation.py +21 -0
- qiskit/circuit/barrier.py +10 -13
- qiskit/circuit/bit.py +0 -1
- qiskit/circuit/classical/__init__.py +2 -2
- qiskit/circuit/classical/expr/__init__.py +39 -5
- qiskit/circuit/classical/expr/constructors.py +84 -1
- qiskit/circuit/classical/expr/expr.py +83 -13
- qiskit/circuit/classical/expr/visitors.py +83 -0
- qiskit/circuit/classical/types/__init__.py +5 -4
- qiskit/circuit/classicalfunction/__init__.py +1 -0
- qiskit/circuit/commutation_checker.py +86 -51
- qiskit/circuit/controlflow/_builder_utils.py +9 -1
- qiskit/circuit/controlflow/break_loop.py +8 -22
- qiskit/circuit/controlflow/builder.py +116 -1
- qiskit/circuit/controlflow/continue_loop.py +8 -22
- qiskit/circuit/controlflow/control_flow.py +47 -8
- qiskit/circuit/controlflow/for_loop.py +8 -23
- qiskit/circuit/controlflow/if_else.py +13 -27
- qiskit/circuit/controlflow/switch_case.py +14 -21
- qiskit/circuit/controlflow/while_loop.py +9 -23
- qiskit/circuit/controlledgate.py +2 -2
- qiskit/circuit/delay.py +7 -5
- qiskit/circuit/gate.py +20 -7
- qiskit/circuit/instruction.py +31 -30
- qiskit/circuit/instructionset.py +9 -22
- qiskit/circuit/library/__init__.py +3 -13
- qiskit/circuit/library/arithmetic/integer_comparator.py +2 -2
- qiskit/circuit/library/arithmetic/quadratic_form.py +3 -2
- qiskit/circuit/library/blueprintcircuit.py +29 -7
- qiskit/circuit/library/data_preparation/state_preparation.py +6 -5
- qiskit/circuit/library/generalized_gates/diagonal.py +5 -4
- qiskit/circuit/library/generalized_gates/isometry.py +51 -254
- qiskit/circuit/library/generalized_gates/pauli.py +2 -2
- qiskit/circuit/library/generalized_gates/permutation.py +4 -1
- qiskit/circuit/library/generalized_gates/rv.py +15 -11
- qiskit/circuit/library/generalized_gates/uc.py +2 -98
- qiskit/circuit/library/generalized_gates/unitary.py +9 -4
- qiskit/circuit/library/hamiltonian_gate.py +11 -5
- qiskit/circuit/library/n_local/efficient_su2.py +5 -5
- qiskit/circuit/library/n_local/n_local.py +100 -49
- qiskit/circuit/library/n_local/two_local.py +3 -59
- qiskit/circuit/library/overlap.py +3 -3
- qiskit/circuit/library/phase_oracle.py +1 -1
- qiskit/circuit/library/quantum_volume.py +39 -38
- qiskit/circuit/library/standard_gates/equivalence_library.py +50 -0
- qiskit/circuit/library/standard_gates/global_phase.py +4 -2
- qiskit/circuit/library/standard_gates/i.py +1 -2
- qiskit/circuit/library/standard_gates/iswap.py +1 -2
- qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +11 -5
- qiskit/circuit/library/standard_gates/p.py +31 -15
- qiskit/circuit/library/standard_gates/r.py +4 -3
- qiskit/circuit/library/standard_gates/rx.py +7 -4
- qiskit/circuit/library/standard_gates/rxx.py +4 -3
- qiskit/circuit/library/standard_gates/ry.py +7 -4
- qiskit/circuit/library/standard_gates/ryy.py +4 -3
- qiskit/circuit/library/standard_gates/rz.py +7 -4
- qiskit/circuit/library/standard_gates/rzx.py +4 -3
- qiskit/circuit/library/standard_gates/rzz.py +4 -3
- qiskit/circuit/library/standard_gates/s.py +4 -8
- qiskit/circuit/library/standard_gates/t.py +2 -4
- qiskit/circuit/library/standard_gates/u.py +16 -11
- qiskit/circuit/library/standard_gates/u1.py +6 -2
- qiskit/circuit/library/standard_gates/u2.py +4 -2
- qiskit/circuit/library/standard_gates/u3.py +9 -5
- qiskit/circuit/library/standard_gates/x.py +22 -11
- qiskit/circuit/library/standard_gates/xx_minus_yy.py +4 -3
- qiskit/circuit/library/standard_gates/xx_plus_yy.py +7 -5
- qiskit/circuit/library/standard_gates/z.py +1 -2
- qiskit/circuit/measure.py +4 -1
- qiskit/circuit/operation.py +13 -8
- qiskit/circuit/parameter.py +11 -6
- qiskit/circuit/quantumcircuit.py +1910 -260
- qiskit/circuit/quantumcircuitdata.py +2 -2
- qiskit/circuit/reset.py +5 -2
- qiskit/circuit/store.py +95 -0
- qiskit/compiler/assembler.py +22 -22
- qiskit/compiler/transpiler.py +63 -112
- qiskit/converters/__init__.py +17 -2
- qiskit/converters/circuit_to_dag.py +7 -0
- qiskit/converters/circuit_to_dagdependency_v2.py +47 -0
- qiskit/converters/circuit_to_gate.py +2 -0
- qiskit/converters/circuit_to_instruction.py +22 -0
- qiskit/converters/dag_to_circuit.py +4 -0
- qiskit/converters/dag_to_dagdependency_v2.py +44 -0
- qiskit/dagcircuit/collect_blocks.py +15 -10
- qiskit/dagcircuit/dagcircuit.py +434 -124
- qiskit/dagcircuit/dagdependency.py +19 -12
- qiskit/dagcircuit/dagdependency_v2.py +641 -0
- qiskit/dagcircuit/dagdepnode.py +19 -16
- qiskit/dagcircuit/dagnode.py +14 -4
- qiskit/passmanager/passmanager.py +11 -11
- qiskit/primitives/__init__.py +22 -12
- qiskit/primitives/backend_estimator.py +3 -5
- qiskit/primitives/backend_estimator_v2.py +410 -0
- qiskit/primitives/backend_sampler_v2.py +287 -0
- qiskit/primitives/base/base_estimator.py +4 -9
- qiskit/primitives/base/base_sampler.py +2 -2
- qiskit/primitives/containers/__init__.py +6 -4
- qiskit/primitives/containers/bit_array.py +293 -2
- qiskit/primitives/containers/data_bin.py +123 -50
- qiskit/primitives/containers/estimator_pub.py +10 -3
- qiskit/primitives/containers/observables_array.py +2 -2
- qiskit/primitives/containers/pub_result.py +1 -1
- qiskit/primitives/containers/sampler_pub.py +19 -3
- qiskit/primitives/containers/sampler_pub_result.py +74 -0
- qiskit/primitives/containers/shape.py +4 -4
- qiskit/primitives/statevector_estimator.py +4 -4
- qiskit/primitives/statevector_sampler.py +7 -12
- qiskit/providers/__init__.py +65 -34
- qiskit/providers/backend.py +2 -2
- qiskit/providers/backend_compat.py +8 -10
- qiskit/providers/basic_provider/__init__.py +2 -23
- qiskit/providers/basic_provider/basic_provider_tools.py +67 -31
- qiskit/providers/basic_provider/basic_simulator.py +81 -21
- qiskit/providers/fake_provider/__init__.py +1 -1
- qiskit/providers/fake_provider/fake_1q.py +1 -1
- qiskit/providers/fake_provider/fake_backend.py +3 -408
- qiskit/providers/fake_provider/generic_backend_v2.py +26 -14
- qiskit/providers/models/__init__.py +2 -2
- qiskit/providers/provider.py +16 -0
- qiskit/pulse/builder.py +4 -1
- qiskit/pulse/parameter_manager.py +60 -4
- qiskit/pulse/schedule.py +29 -13
- qiskit/pulse/utils.py +61 -20
- qiskit/qasm2/__init__.py +1 -5
- qiskit/qasm2/parse.py +1 -4
- qiskit/qasm3/__init__.py +42 -5
- qiskit/qasm3/ast.py +19 -0
- qiskit/qasm3/exporter.py +178 -106
- qiskit/qasm3/printer.py +27 -5
- qiskit/qobj/converters/pulse_instruction.py +6 -6
- qiskit/qpy/__init__.py +299 -67
- qiskit/qpy/binary_io/circuits.py +216 -47
- qiskit/qpy/binary_io/schedules.py +42 -36
- qiskit/qpy/binary_io/value.py +201 -22
- qiskit/qpy/common.py +1 -1
- qiskit/qpy/exceptions.py +20 -0
- qiskit/qpy/formats.py +29 -0
- qiskit/qpy/type_keys.py +21 -0
- qiskit/quantum_info/analysis/distance.py +3 -3
- qiskit/quantum_info/analysis/make_observable.py +2 -1
- qiskit/quantum_info/analysis/z2_symmetries.py +2 -1
- qiskit/quantum_info/operators/channel/chi.py +9 -8
- qiskit/quantum_info/operators/channel/choi.py +10 -9
- qiskit/quantum_info/operators/channel/kraus.py +2 -1
- qiskit/quantum_info/operators/channel/ptm.py +10 -9
- qiskit/quantum_info/operators/channel/quantum_channel.py +2 -1
- qiskit/quantum_info/operators/channel/stinespring.py +2 -1
- qiskit/quantum_info/operators/channel/superop.py +12 -11
- qiskit/quantum_info/operators/channel/transformations.py +12 -11
- qiskit/quantum_info/operators/dihedral/dihedral.py +5 -4
- qiskit/quantum_info/operators/operator.py +43 -30
- qiskit/quantum_info/operators/scalar_op.py +10 -9
- qiskit/quantum_info/operators/symplectic/base_pauli.py +70 -59
- qiskit/quantum_info/operators/symplectic/clifford.py +36 -9
- qiskit/quantum_info/operators/symplectic/pauli.py +53 -6
- qiskit/quantum_info/operators/symplectic/pauli_list.py +36 -14
- qiskit/quantum_info/operators/symplectic/random.py +3 -2
- qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +61 -36
- qiskit/quantum_info/states/densitymatrix.py +13 -13
- qiskit/quantum_info/states/stabilizerstate.py +3 -3
- qiskit/quantum_info/states/statevector.py +14 -13
- qiskit/quantum_info/states/utils.py +5 -3
- qiskit/result/__init__.py +6 -0
- qiskit/result/mitigation/correlated_readout_mitigator.py +3 -2
- qiskit/result/mitigation/local_readout_mitigator.py +2 -1
- qiskit/result/mitigation/utils.py +3 -2
- qiskit/scheduler/__init__.py +10 -1
- qiskit/scheduler/methods/__init__.py +1 -8
- qiskit/synthesis/__init__.py +3 -6
- qiskit/synthesis/discrete_basis/commutator_decompose.py +2 -2
- qiskit/synthesis/evolution/lie_trotter.py +7 -14
- qiskit/synthesis/evolution/qdrift.py +3 -4
- qiskit/synthesis/linear/cnot_synth.py +1 -3
- qiskit/synthesis/linear/linear_circuits_utils.py +1 -1
- qiskit/synthesis/linear_phase/cz_depth_lnn.py +4 -18
- qiskit/synthesis/permutation/__init__.py +1 -0
- qiskit/synthesis/permutation/permutation_reverse_lnn.py +90 -0
- qiskit/synthesis/qft/qft_decompose_lnn.py +2 -6
- qiskit/synthesis/two_qubit/two_qubit_decompose.py +165 -954
- qiskit/synthesis/two_qubit/xx_decompose/circuits.py +13 -12
- qiskit/synthesis/two_qubit/xx_decompose/decomposer.py +7 -1
- qiskit/synthesis/unitary/aqc/__init__.py +1 -1
- qiskit/synthesis/unitary/aqc/cnot_structures.py +2 -1
- qiskit/synthesis/unitary/aqc/fast_gradient/fast_gradient.py +2 -1
- qiskit/synthesis/unitary/qsd.py +3 -2
- qiskit/transpiler/__init__.py +7 -3
- qiskit/transpiler/layout.py +140 -61
- qiskit/transpiler/passes/__init__.py +10 -2
- qiskit/transpiler/passes/basis/basis_translator.py +9 -4
- qiskit/transpiler/passes/basis/unroll_3q_or_more.py +1 -1
- qiskit/transpiler/passes/basis/unroll_custom_definitions.py +1 -1
- qiskit/transpiler/passes/calibration/rzx_builder.py +2 -1
- qiskit/transpiler/passes/layout/apply_layout.py +8 -3
- qiskit/transpiler/passes/layout/sabre_layout.py +15 -3
- qiskit/transpiler/passes/layout/set_layout.py +1 -1
- qiskit/transpiler/passes/optimization/__init__.py +2 -0
- qiskit/transpiler/passes/optimization/commutation_analysis.py +2 -2
- qiskit/transpiler/passes/optimization/commutative_cancellation.py +1 -1
- qiskit/transpiler/passes/optimization/consolidate_blocks.py +1 -1
- qiskit/transpiler/passes/optimization/cx_cancellation.py +10 -0
- qiskit/transpiler/passes/optimization/elide_permutations.py +114 -0
- qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +9 -3
- qiskit/transpiler/passes/optimization/optimize_annotated.py +248 -12
- qiskit/transpiler/passes/optimization/remove_final_reset.py +37 -0
- qiskit/transpiler/passes/optimization/template_matching/forward_match.py +1 -3
- qiskit/transpiler/passes/routing/__init__.py +1 -0
- qiskit/transpiler/passes/routing/basic_swap.py +13 -2
- qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +8 -1
- qiskit/transpiler/passes/routing/lookahead_swap.py +7 -1
- qiskit/transpiler/passes/routing/sabre_swap.py +10 -6
- qiskit/transpiler/passes/routing/star_prerouting.py +417 -0
- qiskit/transpiler/passes/routing/stochastic_swap.py +24 -8
- qiskit/transpiler/passes/scheduling/__init__.py +1 -1
- qiskit/transpiler/passes/scheduling/alap.py +1 -2
- qiskit/transpiler/passes/scheduling/alignments/align_measures.py +1 -2
- qiskit/transpiler/passes/scheduling/alignments/check_durations.py +9 -6
- qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py +8 -0
- qiskit/transpiler/passes/scheduling/alignments/reschedule.py +13 -4
- qiskit/transpiler/passes/scheduling/asap.py +1 -2
- qiskit/transpiler/passes/scheduling/base_scheduler.py +21 -2
- qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +26 -4
- qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +24 -2
- qiskit/transpiler/passes/scheduling/time_unit_conversion.py +28 -4
- qiskit/transpiler/passes/synthesis/aqc_plugin.py +2 -2
- qiskit/transpiler/passes/synthesis/high_level_synthesis.py +120 -13
- qiskit/transpiler/passes/synthesis/unitary_synthesis.py +162 -55
- qiskit/transpiler/passes/utils/gates_basis.py +3 -3
- qiskit/transpiler/passmanager.py +44 -1
- qiskit/transpiler/preset_passmanagers/__init__.py +3 -3
- qiskit/transpiler/preset_passmanagers/builtin_plugins.py +34 -16
- qiskit/transpiler/preset_passmanagers/common.py +4 -6
- qiskit/transpiler/preset_passmanagers/plugin.py +9 -1
- qiskit/utils/__init__.py +3 -2
- qiskit/utils/optionals.py +6 -2
- qiskit/utils/parallel.py +24 -15
- qiskit/visualization/array.py +1 -1
- qiskit/visualization/bloch.py +2 -3
- qiskit/visualization/circuit/matplotlib.py +44 -14
- qiskit/visualization/circuit/text.py +38 -18
- qiskit/visualization/counts_visualization.py +3 -6
- qiskit/visualization/dag_visualization.py +6 -7
- qiskit/visualization/gate_map.py +9 -1
- qiskit/visualization/pulse_v2/interface.py +8 -3
- qiskit/visualization/state_visualization.py +3 -2
- qiskit/visualization/timeline/interface.py +18 -8
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/METADATA +12 -8
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/RECORD +261 -251
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/WHEEL +1 -1
- qiskit/_qasm2.pyd +0 -0
- qiskit/_qasm3.pyd +0 -0
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/LICENSE.txt +0 -0
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/entry_points.txt +0 -0
- {qiskit-1.0.2.dist-info → qiskit-1.1.0.dist-info}/top_level.txt +0 -0
qiskit/dagcircuit/dagcircuit.py
CHANGED
@@ -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
|
23
|
+
from __future__ import annotations
|
24
|
+
|
24
25
|
import copy
|
26
|
+
import enum
|
27
|
+
import itertools
|
25
28
|
import math
|
26
|
-
from
|
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
|
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
|
101
|
+
# Map from wire to input nodes of the graph
|
81
102
|
self.input_map = OrderedDict()
|
82
103
|
|
83
|
-
# Map from wire
|
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
|
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:
|
101
|
-
self.clbits:
|
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:
|
108
|
-
self._clbit_indices:
|
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
|
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
|
543
|
-
"""Check the values of a list of
|
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
|
549
|
-
amap
|
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"
|
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(
|
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
|
750
|
+
if _may_have_additional_wires(op):
|
663
751
|
# This is the slow path; most of the time, this won't happen.
|
664
|
-
|
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.
|
671
|
-
self.
|
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,
|
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(
|
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
|
802
|
+
if _may_have_additional_wires(op):
|
708
803
|
# This is the slow path; most of the time, this won't happen.
|
709
|
-
|
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.
|
716
|
-
self.
|
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,
|
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(
|
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
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
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(
|
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
|
1407
|
+
if not propagate_condition and _may_have_additional_wires(node.op):
|
1211
1408
|
node_wire_order += [
|
1212
|
-
|
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
|
-
#
|
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
|
1315
|
-
if
|
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
|
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(
|
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,
|
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,
|
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
|