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

add control flow support to UnitarySynthesis pass #8565

Merged
merged 5 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
28 changes: 25 additions & 3 deletions qiskit/transpiler/passes/synthesis/unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from qiskit.quantum_info.synthesis import one_qubit_decompose
from qiskit.quantum_info.synthesis.xx_decompose import XXDecomposer
from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitBasisDecomposer
from qiskit.circuit import ControlFlowOp
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.library.standard_gates import (
iSwapGate,
Expand All @@ -35,6 +36,7 @@
ECRGate,
)
from qiskit.transpiler.passes.synthesis import plugin
from qiskit.transpiler.passes.utils import control_flow
from qiskit.providers.models import BackendProperties


Expand Down Expand Up @@ -258,7 +260,6 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
plugin_method = DefaultUnitarySynthesis()
plugin_kwargs = {"config": self._plugin_config}
_gate_lengths = _gate_errors = None
dag_bit_indices = {}

if self.method == "default":
# If the method is the default, we only need to evaluate one set of keyword arguments.
Expand All @@ -278,8 +279,6 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
for method, kwargs in method_list:
if method.supports_basis_gates:
kwargs["basis_gates"] = self._basis_gates
if method.supports_coupling_map:
dag_bit_indices = dag_bit_indices or {bit: i for i, bit in enumerate(dag.qubits)}
if method.supports_natural_direction:
kwargs["natural_direction"] = self._natural_direction
if method.supports_pulse_optimize:
Expand All @@ -306,6 +305,29 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
if self.method == "default":
# pylint: disable=attribute-defined-outside-init
plugin_method._approximation_degree = self._approximation_degree
return self._run_main_loop(
dag, plugin_method, plugin_kwargs, default_method, default_kwargs
)

def _run_main_loop(self, dag, plugin_method, plugin_kwargs, default_method, default_kwargs):
"""Inner loop for the optimizer, after all DAG-idependent set-up has been completed."""

def _recurse(dag):
# This isn't quite a trivially recursive call because we need to close over the
# arguments to the function. The loop is sufficiently long that it's cleaner to do it
# in a separate method rather than define a helper closure within `self.run`.
return self._run_main_loop(
dag, plugin_method, plugin_kwargs, default_method, default_kwargs
)

for node in dag.op_nodes(ControlFlowOp):
node.op = control_flow.map_blocks(_recurse, node.op)

dag_bit_indices = (
{bit: i for i, bit in enumerate(dag.qubits)}
if plugin_method.supports_coupling_map or default_method.supports_coupling_map
else {}
)

for node in dag.named_nodes(*self._synth_gates):
if self._min_qubits is not None and len(node.qargs) < self._min_qubits:
Expand Down
46 changes: 44 additions & 2 deletions test/python/transpiler/test_unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
from qiskit.test import QiskitTestCase
from qiskit.providers.fake_provider import FakeVigo, FakeMumbaiFractionalCX
from qiskit.providers.fake_provider.fake_backend_v2 import FakeBackendV2, FakeBackend5QV2
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.library import QuantumVolume
from qiskit.converters import circuit_to_dag
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.transpiler.passes import UnitarySynthesis
from qiskit.quantum_info.operators import Operator
from qiskit.quantum_info.random import random_unitary
Expand Down Expand Up @@ -724,6 +724,48 @@ def test_reverse_direction(self, opt_level):
for instr in tqc.get_instructions("ecr"):
self.assertEqual((0, 1), (tqc_index[instr.qubits[0]], tqc_index[instr.qubits[1]]))

def test_if_simple(self):
"""Test a simple if statement."""
basis_gates = {"u", "cx"}
qr = QuantumRegister(2)
cr = ClassicalRegister(2)

qc_uni = QuantumCircuit(2)
qc_uni.h(0)
qc_uni.cx(0, 1)
qc_uni_mat = Operator(qc_uni)

qc_true_body = QuantumCircuit(2)
qc_true_body.unitary(qc_uni_mat, [0, 1])

qc = QuantumCircuit(qr, cr)
qc.if_test((cr, 1), qc_true_body, [0, 1], [0, 1])
dag = circuit_to_dag(qc)
cdag = UnitarySynthesis(basis_gates=basis_gates).run(dag)
cqc = dag_to_circuit(cdag)
cbody = cqc.data[0].operation.params[0]
self.assertEqual(cbody.count_ops().keys(), basis_gates)
self.assertEqual(qc_uni_mat, Operator(cbody))

def test_nested_control_flow(self):
"""Test unrolling nested control flow blocks."""
qr = QuantumRegister(2)
cr = ClassicalRegister(1)
qc_uni1 = QuantumCircuit(2)
qc_uni1.swap(0, 1)
qc_uni1_mat = Operator(qc_uni1)

qc = QuantumCircuit(qr, cr)
with qc.for_loop(range(3)):
with qc.while_loop((cr, 0)):
qc.unitary(qc_uni1_mat, [0, 1])
dag = circuit_to_dag(qc)
cdag = UnitarySynthesis(basis_gates=["u", "cx"]).run(dag)
cqc = dag_to_circuit(cdag)
cbody = cqc.data[0].operation.params[2].data[0].operation.params[0]
self.assertEqual(cbody.count_ops().keys(), {"u", "cx"})
self.assertEqual(qc_uni1_mat, Operator(cbody))


if __name__ == "__main__":
unittest.main()