Fix inner qubit mapping in UnitarySynthesis pass. (#10405)

This commit is contained in:
Kevin Hartman 2023-07-13 17:26:56 -04:00 committed by GitHub
parent 645598a4c0
commit 100a997440
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 21 deletions

View File

@ -19,7 +19,7 @@ from itertools import product
from functools import partial
import numpy as np
from qiskit.converters import circuit_to_dag
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.transpiler import CouplingMap, Target
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
@ -41,7 +41,6 @@ from qiskit.circuit.library.standard_gates import (
ECRGate,
)
from qiskit.transpiler.passes.synthesis import plugin
from qiskit.transpiler.passes.utils import control_flow
from qiskit.transpiler.passes.optimization.optimize_1q_decomposition import (
Optimize1qGatesDecomposition,
)
@ -442,29 +441,40 @@ class UnitarySynthesis(TransformationPass):
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-independent 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 = (
qubit_indices = (
{bit: i for i, bit in enumerate(dag.qubits)}
if plugin_method.supports_coupling_map or default_method.supports_coupling_map
else {}
)
return self._run_main_loop(
dag, qubit_indices, plugin_method, plugin_kwargs, default_method, default_kwargs
)
def _run_main_loop(
self, dag, qubit_indices, plugin_method, plugin_kwargs, default_method, default_kwargs
):
"""Inner loop for the optimizer, after all DAG-independent set-up has been completed."""
for node in dag.op_nodes(ControlFlowOp):
node.op = node.op.replace_blocks(
[
dag_to_circuit(
self._run_main_loop(
circuit_to_dag(block),
{
inner: qubit_indices[outer]
for inner, outer in zip(block.qubits, node.qargs)
},
plugin_method,
plugin_kwargs,
default_method,
default_kwargs,
),
copy_operations=False,
)
for block in node.op.blocks
]
)
for node in dag.named_nodes(*self._synth_gates):
if self._min_qubits is not None and len(node.qargs) < self._min_qubits:
@ -481,7 +491,7 @@ class UnitarySynthesis(TransformationPass):
if method.supports_coupling_map:
kwargs["coupling_map"] = (
self._coupling_map,
[dag_bit_indices[x] for x in node.qargs],
[qubit_indices[x] for x in node.qargs],
)
synth_dag = method.run(unitary, **kwargs)
if synth_dag is not None:

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Fixed an issue with :class:`.UnitarySynthesis` when using the ``target``
parameter where circuits with control flow were not properly mapped
to the target.

View File

@ -809,6 +809,38 @@ class TestUnitarySynthesis(QiskitTestCase):
self.assertEqual(cbody.count_ops().keys(), {"u", "cx"})
self.assertEqual(qc_uni1_mat, Operator(cbody))
def test_mapping_control_flow(self):
"""Test that inner dags use proper qubit mapping."""
qr = QuantumRegister(3, "q")
qc = QuantumCircuit(qr)
# Create target that supports CX only between 0 and 2.
fake_target = Target()
fake_target.add_instruction(CXGate(), {(0, 2): None})
fake_target.add_instruction(
UGate(Parameter("t"), Parameter("p"), Parameter("l")),
{
(0,): None,
(1,): None,
(2,): None,
},
)
qc_uni1 = QuantumCircuit(2)
qc_uni1.swap(0, 1)
qc_uni1_mat = Operator(qc_uni1)
loop_body = QuantumCircuit(2)
loop_body.unitary(qc_uni1_mat, [0, 1])
# Loop body uses qubits 0 and 2, mapped to 0 and 1 in the block.
# If synthesis doesn't handle recursive mapping, it'll incorrectly
# look for a CX on (0, 1) instead of on (0, 2).
qc.for_loop((0,), None, loop_body, [0, 2], [])
dag = circuit_to_dag(qc)
UnitarySynthesis(basis_gates=["u", "cx"], target=fake_target).run(dag)
def test_single_qubit_with_target(self):
"""Test input circuit with only 1q works with target."""
qc = QuantumCircuit(1)