mirror of https://github.com/Qiskit/qiskit.git
Fix inner qubit mapping in UnitarySynthesis pass. (#10405)
This commit is contained in:
parent
645598a4c0
commit
100a997440
|
@ -19,7 +19,7 @@ from itertools import product
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import numpy as np
|
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 import CouplingMap, Target
|
||||||
from qiskit.transpiler.basepasses import TransformationPass
|
from qiskit.transpiler.basepasses import TransformationPass
|
||||||
from qiskit.transpiler.exceptions import TranspilerError
|
from qiskit.transpiler.exceptions import TranspilerError
|
||||||
|
@ -41,7 +41,6 @@ from qiskit.circuit.library.standard_gates import (
|
||||||
ECRGate,
|
ECRGate,
|
||||||
)
|
)
|
||||||
from qiskit.transpiler.passes.synthesis import plugin
|
from qiskit.transpiler.passes.synthesis import plugin
|
||||||
from qiskit.transpiler.passes.utils import control_flow
|
|
||||||
from qiskit.transpiler.passes.optimization.optimize_1q_decomposition import (
|
from qiskit.transpiler.passes.optimization.optimize_1q_decomposition import (
|
||||||
Optimize1qGatesDecomposition,
|
Optimize1qGatesDecomposition,
|
||||||
)
|
)
|
||||||
|
@ -442,29 +441,40 @@ class UnitarySynthesis(TransformationPass):
|
||||||
if self.method == "default":
|
if self.method == "default":
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# pylint: disable=attribute-defined-outside-init
|
||||||
plugin_method._approximation_degree = self._approximation_degree
|
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):
|
qubit_indices = (
|
||||||
"""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 = (
|
|
||||||
{bit: i for i, bit in enumerate(dag.qubits)}
|
{bit: i for i, bit in enumerate(dag.qubits)}
|
||||||
if plugin_method.supports_coupling_map or default_method.supports_coupling_map
|
if plugin_method.supports_coupling_map or default_method.supports_coupling_map
|
||||||
else {}
|
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):
|
for node in dag.named_nodes(*self._synth_gates):
|
||||||
if self._min_qubits is not None and len(node.qargs) < self._min_qubits:
|
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:
|
if method.supports_coupling_map:
|
||||||
kwargs["coupling_map"] = (
|
kwargs["coupling_map"] = (
|
||||||
self._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)
|
synth_dag = method.run(unitary, **kwargs)
|
||||||
if synth_dag is not None:
|
if synth_dag is not None:
|
||||||
|
|
|
@ -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.
|
|
@ -809,6 +809,38 @@ class TestUnitarySynthesis(QiskitTestCase):
|
||||||
self.assertEqual(cbody.count_ops().keys(), {"u", "cx"})
|
self.assertEqual(cbody.count_ops().keys(), {"u", "cx"})
|
||||||
self.assertEqual(qc_uni1_mat, Operator(cbody))
|
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):
|
def test_single_qubit_with_target(self):
|
||||||
"""Test input circuit with only 1q works with target."""
|
"""Test input circuit with only 1q works with target."""
|
||||||
qc = QuantumCircuit(1)
|
qc = QuantumCircuit(1)
|
||||||
|
|
Loading…
Reference in New Issue