Skip to content

Commit

Permalink
Fix inner qubit mapping in UnitarySynthesis pass. (#10405) (#10431)
Browse files Browse the repository at this point in the history
(cherry picked from commit 100a997)

Co-authored-by: Kevin Hartman <kevin@hart.mn>
  • Loading branch information
mergify[bot] and kevinhartman authored Jul 14, 2023
1 parent 91cfe31 commit 2317c83
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 21 deletions.
52 changes: 31 additions & 21 deletions qiskit/transpiler/passes/synthesis/unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
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
Expand All @@ -41,7 +41,6 @@
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,
)
Expand Down Expand Up @@ -442,29 +441,40 @@ 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-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:
Expand All @@ -481,7 +491,7 @@ def _recurse(dag):
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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
32 changes: 32 additions & 0 deletions test/python/transpiler/test_unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,38 @@ def test_nested_control_flow(self):
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)
Expand Down

0 comments on commit 2317c83

Please sign in to comment.