Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QuantumCircuit.decompose() does not take a single gate, but a set of gates to decompose. #6587

Merged
merged 44 commits into from
Jul 14, 2021
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
bbfdd99
fixes issue 2906
Jun 16, 2021
6cf1508
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into fi…
javabster Jun 16, 2021
7934b9b
Decompose by label then name
javabster Jun 16, 2021
d26cd53
fixes issue 2906
Jun 17, 2021
2a88d39
Fixed test & reorganised decompose conditions
javabster Jun 21, 2021
9f6877c
Added an example test
javabster Jun 22, 2021
ffc2a9d
added tests
Jun 24, 2021
6c01392
added tests for decompose
Jun 25, 2021
9c2e3d9
fixed the pylint errors
Jun 25, 2021
5fc34b9
corrected the tests in test_decompose.py
Jun 28, 2021
e9ad30a
fixed the error
Jun 28, 2021
0525f46
fixed style and lint errors
Jun 29, 2021
415d0a9
Merge branch 'main' into fixissue2906
fs1132429 Jun 29, 2021
618e6e9
release note added
Jun 30, 2021
b0d2136
Merge branch 'fixissue2906' of https://github.com/fs1132429/qiskit-te…
Jun 30, 2021
af64f3a
Delete Untitled6.ipynb
fs1132429 Jun 30, 2021
6c90c5d
Delete Untitled1.ipynb
fs1132429 Jun 30, 2021
7fd1346
Delete Untitled3.ipynb
fs1132429 Jun 30, 2021
93d1c50
Delete fixissue2906.ipynb
fs1132429 Jun 30, 2021
c52a79a
Delete Untitled.ipynb
fs1132429 Jun 30, 2021
9351e17
Delete Untitled4.ipynb
fs1132429 Jun 30, 2021
0f2b8f7
Delete Untitled.ipynb
fs1132429 Jun 30, 2021
aa7825f
Delete Untitled2.ipynb
fs1132429 Jun 30, 2021
46aa78b
added release note
Jun 30, 2021
3d20536
Merge branch 'fixissue2906' of https://github.com/fs1132429/qiskit-te…
Jun 30, 2021
1b04b3e
Merge branch 'main' into fixissue2906
javabster Jul 1, 2021
db32d03
rename reno
1ucian0 Jul 1, 2021
1f8df1c
reno
1ucian0 Jul 1, 2021
4e62606
arg order
1ucian0 Jul 1, 2021
3cbd29b
Tidied up tests
javabster Jul 2, 2021
9d905dd
Merge branch 'main' into fixissue2906
fs1132429 Jul 4, 2021
e907ab8
Made method private and linted
javabster Jul 7, 2021
ceabde1
Added getter and setter, linted
javabster Jul 7, 2021
e8c0be8
:sparkles: lint :sparkles:
javabster Jul 8, 2021
f3572ea
Addressed PR comments
javabster Jul 8, 2021
000b586
Removed [None] logic
javabster Jul 8, 2021
4799721
Tidied up gates list logic
javabster Jul 8, 2021
dbde898
Refactored None check
javabster Jul 12, 2021
23e905f
Removed list wrapping in setter & added test
javabster Jul 12, 2021
8a44e7f
:sparkles: Lint :sparkles:
javabster Jul 12, 2021
e30fda7
Fixed getter bug
javabster Jul 12, 2021
588b89c
Merge branch 'main' into fixissue2906
javabster Jul 13, 2021
ed51d59
Update qiskit/circuit/quantumcircuit.py
Cryoris Jul 13, 2021
567f43f
Merge branch 'main' into fixissue2906
mergify[bot] Jul 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1262,10 +1262,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 (list(str)): optional subset of gates to decompose.
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
Defaults to all gates in circuit.

Returns:
QuantumCircuit: a circuit one level decomposed
"""
Expand All @@ -1274,7 +1278,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)

Expand Down
79 changes: 60 additions & 19 deletions qiskit/transpiler/passes/basis/decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,39 @@
# that they have been altered from the originals.

"""Expand a gate in a circuit using its decomposition rules."""
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,
gates_to_decompose: Optional[Union[Type[Gate], List[Type[Gate]], List[str], str]] = None,
gate: Optional[Type[Gate]] = None,
) -> None:
"""Decompose initializer.

Args:
gate: gate to decompose.
gates_to_decompose: optional subset of gates to be decomposed,
identified by gate label, name or type. Defaults to all gates.
gate: DEPRECATED gate to decompose.
"""
super().__init__()

if not isinstance(gates_to_decompose, list):
gates_to_decompose = [gates_to_decompose]

self.gate = gate
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
self.gates = gates_to_decompose

def run(self, dag: DAGCircuit) -> DAGCircuit:
"""Run the Decompose pass on `dag`.
Expand All @@ -42,18 +55,46 @@ 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:
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
"""Call a decomposition pass on this circuit,
to decompose one level (shallow decompose)."""
has_label = False
strings_list = [s for s in self.gates if isinstance(s, str)]
gate_type_list = [g for g in self.gates if isinstance(g, type)]

if hasattr(node.op, "label") and node.op.label is not None:
has_label = True

if self.gates == [None] and self.gate is None: # check if no gates given
return True
elif has_label and ( # check if label or label wildcard is given
node.op.label in self.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 self.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)
or (self.gate is not None and isinstance(node.op, self.gate))
):
return True
else:
return False
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
features:
- |
Allows specification of gates to decompose inside `circuit.decompose()`.
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 <https://github.com/Qiskit/qiskit-terra/issues/2906>`__ for more information.

deprecations:
- |
There is a deprecation of the `gate` param for
the :class:`Decompose` and is replaced by `gates_to_decompose`.
164 changes: 146 additions & 18 deletions test/python/transpiler/test_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,39 @@
from qiskit.quantum_info.operators import Operator
from qiskit.test import QiskitTestCase

# 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])
COMPLEX_CIRC = qc
Cryoris marked this conversation as resolved.
Show resolved Hide resolved


class TestDecompose(QiskitTestCase):
"""Tests the decompose pass."""
Expand Down Expand Up @@ -100,9 +133,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()
Cryoris marked this conversation as resolved.
Show resolved Hide resolved

qr2 = QuantumRegister(2, "qr2")
qc2 = QuantumCircuit(qr2)
Expand All @@ -115,20 +148,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."""
Expand All @@ -137,7 +170,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 = COMPLEX_CIRC.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 = COMPLEX_CIRC.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 = COMPLEX_CIRC.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 = COMPLEX_CIRC.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 = COMPLEX_CIRC.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-")