diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 3d17f55e09d8..80e6956015fc 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1276,10 +1276,14 @@ def to_gate(self, parameter_map=None, label=None): return circuit_to_gate(self, parameter_map, label=label) - def decompose(self): + def decompose(self, gates_to_decompose=None): """Call a decomposition pass on this circuit, to decompose one level (shallow decompose). + Args: + gates_to_decompose (str or list(str)): optional subset of gates to decompose. + Defaults to all gates in circuit. + Returns: QuantumCircuit: a circuit one level decomposed """ @@ -1288,7 +1292,7 @@ def decompose(self): from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.converters.dag_to_circuit import dag_to_circuit - pass_ = Decompose() + pass_ = Decompose(gates_to_decompose=gates_to_decompose) decomposed_dag = pass_.run(circuit_to_dag(self)) return dag_to_circuit(decomposed_dag) diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index 82599cb0aaa1..b34f7446547b 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -11,26 +11,69 @@ # that they have been altered from the originals. """Expand a gate in a circuit using its decomposition rules.""" +import warnings +from typing import Type, Union, List, Optional +from fnmatch import fnmatch -from typing import Type - -from qiskit.circuit.gate import Gate from qiskit.transpiler.basepasses import TransformationPass from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.converters.circuit_to_dag import circuit_to_dag +from qiskit.circuit.gate import Gate +from qiskit.utils.deprecation import deprecate_arguments class Decompose(TransformationPass): """Expand a gate in a circuit using its decomposition rules.""" - def __init__(self, gate: Type[Gate] = None): + @deprecate_arguments({"gate": "gates_to_decompose"}) + def __init__( + self, + gate: Optional[Type[Gate]] = None, + gates_to_decompose: Optional[Union[Type[Gate], List[Type[Gate]], List[str], str]] = None, + ) -> None: """Decompose initializer. Args: - gate: gate to decompose. + gate: DEPRECATED gate to decompose. + gates_to_decompose: optional subset of gates to be decomposed, + identified by gate label, name or type. Defaults to all gates. """ super().__init__() - self.gate = gate + + if gate is not None: + self.gates_to_decompose = gate + else: + self.gates_to_decompose = gates_to_decompose + + @property + def gate(self) -> Gate: + """Returns the gate""" + warnings.warn( + "The gate argument is deprecated as of 0.18.0, and " + "will be removed no earlier than 3 months after that " + "release date. You should use the gates_to_decompose argument " + "instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.gates_to_decompose + + @gate.setter + def gate(self, value): + """Sets the gate + + Args: + value (Gate): new value for gate + """ + warnings.warn( + "The gate argument is deprecated as of 0.18.0, and " + "will be removed no earlier than 3 months after that " + "release date. You should use the gates_to_decompose argument " + "instead.", + DeprecationWarning, + stacklevel=2, + ) + self.gates_to_decompose = value def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the Decompose pass on `dag`. @@ -42,18 +85,52 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: output dag where ``gate`` was expanded. """ # Walk through the DAG and expand each non-basis node - for node in dag.op_nodes(self.gate): - # opaque or built-in gates are not decomposable - if not node.op.definition: - continue - # TODO: allow choosing among multiple decomposition rules - rule = node.op.definition.data - - if len(rule) == 1 and len(node.qargs) == len(rule[0][1]) == 1: - if node.op.definition.global_phase: - dag.global_phase += node.op.definition.global_phase - dag.substitute_node(node, rule[0][0], inplace=True) - else: - decomposition = circuit_to_dag(node.op.definition) - dag.substitute_node_with_dag(node, decomposition) + for node in dag.op_nodes(): + if self._should_decompose(node): + if not node.op.definition: + continue + # TODO: allow choosing among multiple decomposition rules + rule = node.op.definition.data + if len(rule) == 1 and len(node.qargs) == len(rule[0][1]) == 1: + if node.op.definition.global_phase: + dag.global_phase += node.op.definition.global_phase + dag.substitute_node(node, rule[0][0], inplace=True) + else: + decomposition = circuit_to_dag(node.op.definition) + dag.substitute_node_with_dag(node, decomposition) + return dag + + def _should_decompose(self, node) -> bool: + """Call a decomposition pass on this circuit, + to decompose one level (shallow decompose).""" + if self.gates_to_decompose is None: # check if no gates given + return True + + has_label = False + + if not isinstance(self.gates_to_decompose, list): + gates = [self.gates_to_decompose] + else: + gates = self.gates_to_decompose + + strings_list = [s for s in gates if isinstance(s, str)] + gate_type_list = [g for g in gates if isinstance(g, type)] + + if hasattr(node.op, "label") and node.op.label is not None: + has_label = True + + if has_label and ( # check if label or label wildcard is given + node.op.label in gates or any(fnmatch(node.op.label, p) for p in strings_list) + ): + return True + elif not has_label and ( # check if name or name wildcard is given + node.name in gates or any(fnmatch(node.name, p) for p in strings_list) + ): + return True + elif not has_label and ( # check if Gate type given + any(isinstance(node.op, op) for op in gate_type_list) + ): + return True + else: + return False diff --git a/releasenotes/notes/QuantumCircuit.decompose-takes-which-gate-to-decompose-d857da5d0c41fb66.yaml b/releasenotes/notes/QuantumCircuit.decompose-takes-which-gate-to-decompose-d857da5d0c41fb66.yaml new file mode 100644 index 000000000000..8a144f7b51c0 --- /dev/null +++ b/releasenotes/notes/QuantumCircuit.decompose-takes-which-gate-to-decompose-d857da5d0c41fb66.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + Allows specification of gates to decompose inside `circuit.decompose()` (and + in the :class:`qiskit.transpiler.passes.basis.Decompose` pass). + It takes input as a list of strings or gates as well as single strings or gates + and also supports wildcards. + + For example:: + circ.decompose(['cz','h']) + + See `#2906 `__ for more information. + +deprecations: + - | + There is a deprecation of the `gate` param for + the :class:`qiskit.transpiler.passes.basis.Decompose` and is replaced by `gates_to_decompose`. diff --git a/test/python/transpiler/test_decompose.py b/test/python/transpiler/test_decompose.py index ecf4193c962a..b0df721a214c 100644 --- a/test/python/transpiler/test_decompose.py +++ b/test/python/transpiler/test_decompose.py @@ -25,6 +25,41 @@ class TestDecompose(QiskitTestCase): """Tests the decompose pass.""" + def setUp(self): + super().setUp() + # example complex circuit + # ┌────────┐ ┌───┐┌─────────────┐ + # q2_0: ┤0 ├────────────■──┤ H ├┤0 ├ + # │ │ │ └───┘│ circuit-57 │ + # q2_1: ┤1 gate1 ├────────────■───────┤1 ├ + # │ │┌────────┐ │ └─────────────┘ + # q2_2: ┤2 ├┤0 ├──■────────────────────── + # └────────┘│ │ │ + # q2_3: ──────────┤1 gate2 ├──■────────────────────── + # │ │┌─┴─┐ + # q2_4: ──────────┤2 ├┤ X ├──────────────────── + # └────────┘└───┘ + circ1 = QuantumCircuit(3) + circ1.h(0) + circ1.t(1) + circ1.x(2) + my_gate = circ1.to_gate(label="gate1") + circ2 = QuantumCircuit(3) + circ2.h(0) + circ2.cx(0, 1) + circ2.x(2) + my_gate2 = circ2.to_gate(label="gate2") + circ3 = QuantumCircuit(2) + circ3.x(0) + q_bits = QuantumRegister(5) + qc = QuantumCircuit(q_bits) + qc.append(my_gate, q_bits[:3]) + qc.append(my_gate2, q_bits[2:]) + qc.mct(q_bits[:4], q_bits[4]) + qc.h(0) + qc.append(circ3, [0, 1]) + self.complex_circuit = qc + def test_basic(self): """Test decompose a single H into u2.""" qr = QuantumRegister(1, "qr") @@ -37,6 +72,20 @@ def test_basic(self): self.assertEqual(len(op_nodes), 1) self.assertEqual(op_nodes[0].name, "u2") + def test_decompose_none(self): + """Test decompose a single H into u2.""" + qr = QuantumRegister(1, "qr") + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + dag = circuit_to_dag(circuit) + pass_ = Decompose(HGate) + with self.assertWarns(DeprecationWarning): + pass_.gate = None + after_dag = pass_.run(dag) + op_nodes = after_dag.op_nodes() + self.assertEqual(len(op_nodes), 1) + self.assertEqual(op_nodes[0].name, "u2") + def test_decompose_only_h(self): """Test to decompose a single H, without the rest""" qr = QuantumRegister(2, "qr") @@ -100,9 +149,9 @@ def test_decompose_oversized_instruction(self): def test_decomposition_preserves_qregs_order(self): """Test decomposing a gate preserves it's definition registers order""" qr = QuantumRegister(2, "qr1") - qc = QuantumCircuit(qr) - qc.cx(1, 0) - gate = qc.to_gate() + qc1 = QuantumCircuit(qr) + qc1.cx(1, 0) + gate = qc1.to_gate() qr2 = QuantumRegister(2, "qr2") qc2 = QuantumCircuit(qr2) @@ -115,20 +164,20 @@ def test_decomposition_preserves_qregs_order(self): def test_decompose_global_phase_1q(self): """Test decomposition of circuit with global phase""" - qc = QuantumCircuit(1) - qc.rz(0.1, 0) - qc.ry(0.5, 0) - qc.global_phase += pi / 4 - qcd = qc.decompose() - self.assertEqual(Operator(qc), Operator(qcd)) + qc1 = QuantumCircuit(1) + qc1.rz(0.1, 0) + qc1.ry(0.5, 0) + qc1.global_phase += pi / 4 + qcd = qc1.decompose() + self.assertEqual(Operator(qc1), Operator(qcd)) def test_decompose_global_phase_2q(self): """Test decomposition of circuit with global phase""" - qc = QuantumCircuit(2, global_phase=pi / 4) - qc.rz(0.1, 0) - qc.rxx(0.2, 0, 1) - qcd = qc.decompose() - self.assertEqual(Operator(qc), Operator(qcd)) + qc1 = QuantumCircuit(2, global_phase=pi / 4) + qc1.rz(0.1, 0) + qc1.rxx(0.2, 0, 1) + qcd = qc1.decompose() + self.assertEqual(Operator(qc1), Operator(qcd)) def test_decompose_global_phase_1q_composite(self): """Test decomposition of circuit with global phase in a composite gate.""" @@ -137,7 +186,102 @@ def test_decompose_global_phase_1q_composite(self): circ.h(0) v = circ.to_gate() - qc = QuantumCircuit(1) - qc.append(v, [0]) - qcd = qc.decompose() - self.assertEqual(Operator(qc), Operator(qcd)) + qc1 = QuantumCircuit(1) + qc1.append(v, [0]) + qcd = qc1.decompose() + self.assertEqual(Operator(qc1), Operator(qcd)) + + def test_decompose_only_h_gate(self): + """Test decomposition parameters so that only a certain gate is decomposed.""" + circ = QuantumCircuit(2, 1) + circ.h(0) + circ.cz(0, 1) + decom_circ = circ.decompose(["h"]) + dag = circuit_to_dag(decom_circ) + self.assertEqual(len(dag.op_nodes()), 2) + self.assertEqual(dag.op_nodes()[0].name, "u2") + self.assertEqual(dag.op_nodes()[1].name, "cz") + + def test_decompose_only_given_label(self): + """Test decomposition parameters so that only a given label is decomposed.""" + decom_circ = self.complex_circuit.decompose(["gate2"]) + dag = circuit_to_dag(decom_circ) + + self.assertEqual(len(dag.op_nodes()), 7) + self.assertEqual(dag.op_nodes()[0].op.label, "gate1") + self.assertEqual(dag.op_nodes()[1].name, "h") + self.assertEqual(dag.op_nodes()[2].name, "cx") + self.assertEqual(dag.op_nodes()[3].name, "x") + self.assertEqual(dag.op_nodes()[4].name, "mcx") + self.assertEqual(dag.op_nodes()[5].name, "h") + self.assertRegex(dag.op_nodes()[6].name, "circuit-") + + def test_decompose_only_given_name(self): + """Test decomposition parameters so that only given name is decomposed.""" + decom_circ = self.complex_circuit.decompose(["mcx"]) + dag = circuit_to_dag(decom_circ) + + self.assertEqual(len(dag.op_nodes()), 13) + self.assertEqual(dag.op_nodes()[0].op.label, "gate1") + self.assertEqual(dag.op_nodes()[1].op.label, "gate2") + self.assertEqual(dag.op_nodes()[2].name, "h") + self.assertEqual(dag.op_nodes()[3].name, "cu1") + self.assertEqual(dag.op_nodes()[4].name, "rcccx") + self.assertEqual(dag.op_nodes()[5].name, "h") + self.assertEqual(dag.op_nodes()[6].name, "h") + self.assertEqual(dag.op_nodes()[7].name, "cu1") + self.assertEqual(dag.op_nodes()[8].name, "rcccx_dg") + self.assertEqual(dag.op_nodes()[9].name, "h") + self.assertEqual(dag.op_nodes()[10].name, "c3sx") + self.assertEqual(dag.op_nodes()[11].name, "h") + self.assertRegex(dag.op_nodes()[12].name, "circuit-") + + def test_decompose_mixture_of_names_and_labels(self): + """Test decomposition parameters so that mixture of names and labels is decomposed""" + decom_circ = self.complex_circuit.decompose(["mcx", "gate2"]) + dag = circuit_to_dag(decom_circ) + + self.assertEqual(len(dag.op_nodes()), 15) + self.assertEqual(dag.op_nodes()[0].op.label, "gate1") + self.assertEqual(dag.op_nodes()[1].name, "h") + self.assertEqual(dag.op_nodes()[2].name, "cx") + self.assertEqual(dag.op_nodes()[3].name, "x") + self.assertEqual(dag.op_nodes()[4].name, "h") + self.assertEqual(dag.op_nodes()[5].name, "cu1") + self.assertEqual(dag.op_nodes()[6].name, "rcccx") + self.assertEqual(dag.op_nodes()[7].name, "h") + self.assertEqual(dag.op_nodes()[8].name, "h") + self.assertEqual(dag.op_nodes()[9].name, "cu1") + self.assertEqual(dag.op_nodes()[10].name, "rcccx_dg") + self.assertEqual(dag.op_nodes()[11].name, "h") + self.assertEqual(dag.op_nodes()[12].name, "c3sx") + self.assertEqual(dag.op_nodes()[13].name, "h") + self.assertRegex(dag.op_nodes()[14].name, "circuit-") + + def test_decompose_name_wildcards(self): + """Test decomposition parameters so that name wildcards is decomposed""" + decom_circ = self.complex_circuit.decompose(["circuit-*"]) + dag = circuit_to_dag(decom_circ) + + self.assertEqual(len(dag.op_nodes()), 5) + self.assertEqual(dag.op_nodes()[0].op.label, "gate1") + self.assertEqual(dag.op_nodes()[1].op.label, "gate2") + self.assertEqual(dag.op_nodes()[2].name, "mcx") + self.assertEqual(dag.op_nodes()[3].name, "h") + self.assertRegex(dag.op_nodes()[4].name, "x") + + def test_decompose_label_wildcards(self): + """Test decomposition parameters so that label wildcards is decomposed""" + decom_circ = self.complex_circuit.decompose(["gate*"]) + dag = circuit_to_dag(decom_circ) + + self.assertEqual(len(dag.op_nodes()), 9) + self.assertEqual(dag.op_nodes()[0].name, "h") + self.assertEqual(dag.op_nodes()[1].name, "t") + self.assertEqual(dag.op_nodes()[2].name, "x") + self.assertEqual(dag.op_nodes()[3].name, "h") + self.assertEqual(dag.op_nodes()[4].name, "cx") + self.assertEqual(dag.op_nodes()[5].name, "x") + self.assertEqual(dag.op_nodes()[6].name, "mcx") + self.assertEqual(dag.op_nodes()[7].name, "h") + self.assertRegex(dag.op_nodes()[8].name, "circuit-")