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

Support target and basis gates in Unroll3qOrMore transpiler pass #7997

Merged
merged 6 commits into from
May 4, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
30 changes: 30 additions & 0 deletions qiskit/transpiler/passes/basis/unroll_3q_or_more.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@
class Unroll3qOrMore(TransformationPass):
"""Recursively expands 3q+ gates until the circuit only contains 2q or 1q gates."""

def __init__(self, target=None, basis_gates=None):
"""Initialize the Unroll3qOrMore pass

Args:
target (Target): The target object reprsenting the compilation
target. If specified any multiqubit instructions in the
circuit when the pass is run that are supported by the target
device will be left in place. If both this and ``basis_gates``
are specified only the target will be checked.
basis_gates (list): A list of basis gate names that the target
device supports. If specified any gate names in the circuit
which are present in this list will not be unrolled. If both
this and ``target`` are specified only the target will be used
for checking which gates are supported.
"""
super().__init__()
self.target = target
self.basis_gates = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.basis_gates = None
self.basis_gates = set(basis_gates) if basis_gates is not None or target

Why we need to keep target? Seems like both define __contains__ thus we can simplify. This doesn't block your PR. Just my curiosity.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's mostly leftover from an earlier local attempt. I originally wanted to use target.instruction_supported(node.name, qargs) to check that the multiqubit gate is defined on the qubits in the circuit. But the problem with that was when this pass is run before the layout phase we don't actually know which physical device qubits the gate will run on so we can't check that in the target.

I think longer term it makes sense to keep them separate because after #7807 merges we can use instruction_supported() to check based on gate class and potentially parameterizations too instead of just using the name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this PR looks good to me.

if basis_gates is not None:
self.basis_gates = set(basis_gates)

def run(self, dag):
"""Run the Unroll3qOrMore pass on `dag`.

Expand All @@ -33,6 +54,15 @@ def run(self, dag):
for node in dag.multi_qubit_ops():
if dag.has_calibration_for(node):
continue
if self.target is not None:
# Treat target instructions as global since this pass can be run
# prior to layout and routing we don't have phsyical qubits from
# the circuit yet
if node.name in self.target:
continue
elif self.basis_gates is not None and node.name in self.basis_gates:
continue

# TODO: allow choosing other possible decompositions
rule = node.op.definition.data
if not rule:
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level0.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
plugin_config=unitary_synthesis_plugin_config,
target=target,
),
Unroll3qOrMore(),
Unroll3qOrMore(target=target, basis_gates=basis_gates),
]

# 2. Choose an initial layout if not set by user (default: trivial layout)
Expand Down Expand Up @@ -190,7 +190,7 @@ def _swap_condition(property_set):
plugin_config=unitary_synthesis_plugin_config,
target=target,
),
Unroll3qOrMore(),
Unroll3qOrMore(target=target, basis_gates=basis_gates),
Collect2qBlocks(),
Collect1qRuns(),
ConsolidateBlocks(basis_gates=basis_gates, target=target),
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def _vf2_match_not_found(property_set):
plugin_config=unitary_synthesis_plugin_config,
target=target,
),
Unroll3qOrMore(),
Unroll3qOrMore(target=target, basis_gates=basis_gates),
]

# 3. Use a better layout on densely connected qubits, if circuit needs swaps
Expand Down Expand Up @@ -253,7 +253,7 @@ def _swap_condition(property_set):
min_qubits=3,
target=target,
),
Unroll3qOrMore(),
Unroll3qOrMore(target=target, basis_gates=basis_gates),
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates, target=target),
UnitarySynthesis(
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
plugin_config=unitary_synthesis_plugin_config,
target=target,
),
Unroll3qOrMore(),
Unroll3qOrMore(target=target, basis_gates=basis_gates),
]

# 2. Search for a perfect layout, or choose a dense layout, if no layout given
Expand Down Expand Up @@ -238,7 +238,7 @@ def _swap_condition(property_set):
min_qubits=3,
target=target,
),
Unroll3qOrMore(),
Unroll3qOrMore(target=target, basis_gates=basis_gates),
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates, target=target),
UnitarySynthesis(
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
min_qubits=3,
target=target,
),
Unroll3qOrMore(),
Unroll3qOrMore(target=target, basis_gates=basis_gates),
]

# 2. Layout on good qubits if calibration info available, otherwise on dense links
Expand Down Expand Up @@ -236,7 +236,7 @@ def _swap_condition(property_set):
min_qubits=3,
target=target,
),
Unroll3qOrMore(),
Unroll3qOrMore(target=target, basis_gates=basis_gates),
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates, target=target),
UnitarySynthesis(
Expand Down
10 changes: 10 additions & 0 deletions releasenotes/notes/unroll3q-target-bf57cc4365808862.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
features:
- |
The constructor for the :class:`~.Unroll3qOrMore` transpiler pass has
two new optional keyword arguments, ``target`` and ``basis`` gates. These
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
options enable you to specify the :class:`~.Target` or supported basis
gates respectively to describe the target backend. If any of the operations
in the circuit are in the ``target`` or ``basis_gates`` those will not
be unrolled by the pass as the target device has native support for the
operation.
39 changes: 39 additions & 0 deletions test/python/transpiler/test_unroll_3q_or_more.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@

"""Test the Unroll3qOrMore pass"""
import numpy as np

from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.circuit.library import CCXGate, RCCXGate
from qiskit.transpiler.passes import Unroll3qOrMore
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.quantum_info.operators import Operator
from qiskit.quantum_info.random import random_unitary
from qiskit.test import QiskitTestCase
from qiskit.extensions import UnitaryGate
from qiskit.transpiler import Target


class TestUnroll3qOrMore(QiskitTestCase):
Expand Down Expand Up @@ -91,3 +94,39 @@ def test_identity(self):
after_dag = pass_.run(dag)
after_circ = dag_to_circuit(after_dag)
self.assertTrue(Operator(circuit).equiv(Operator(after_circ)))

def test_target(self):
"""Test target is respected by the unroll 3q or more pass."""
target = Target(num_qubits=3)
target.add_instruction(CCXGate())
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.append(RCCXGate(), [0, 1, 2])
unroll_pass = Unroll3qOrMore(target=target)
res = unroll_pass(qc)
self.assertIn('ccx', res.count_ops())
self.assertNotIn('rccx', res.count_ops())

def test_basis_gates(self):
"""Test basis_gates are respected by the unroll 3q or more pass."""
basis_gates = ["rccx"]
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.append(RCCXGate(), [0, 1, 2])
unroll_pass = Unroll3qOrMore(basis_gates=basis_gates)
res = unroll_pass(qc)
self.assertNotIn('ccx', res.count_ops())
self.assertIn('rccx', res.count_ops())

def test_target_over_basis_gates(self):
"""Test target is respected over basis_gates by the unroll 3q or more pass."""
target = Target(num_qubits=3)
basis_gates = ['rccx']
target.add_instruction(CCXGate())
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.append(RCCXGate(), [0, 1, 2])
unroll_pass = Unroll3qOrMore(target=target, basis_gates=basis_gates)
res = unroll_pass(qc)
self.assertIn('ccx', res.count_ops())
self.assertNotIn('rccx', res.count_ops())