From f87bb0132ddb00b923df63f0130639e18ff796f4 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 16 Oct 2023 18:59:17 +0100 Subject: [PATCH] Use only discrete-basis translations in `GateDirection` (#10786) * Use only discrete-basis translations in `GateDirection` This makes it a little more likely that it'll be possible to use the transpiler when targetting a discrete basis, such as a Clifford simulator. We still in general do not offer full support for discrete-basis translation, and certainly not for optimal discrete-basis transpilation, but this is a relatively easy change that makes us marginally more reliable with them. * Fix global phase in tests --- .../transpiler/passes/utils/gate_direction.py | 29 +++++++++++----- ...-basis-gatedirection-bdffad3b47c1c532.yaml | 8 +++++ test/python/compiler/test_transpiler.py | 28 ++++++++++++++- test/python/transpiler/test_gate_direction.py | 34 +++++++++++++------ 4 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 releasenotes/notes/discrete-basis-gatedirection-bdffad3b47c1c532.yaml diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index a259106d3ed4..98b471f6f7f0 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -21,7 +21,9 @@ from qiskit.circuit import QuantumRegister, ControlFlowOp from qiskit.dagcircuit import DAGCircuit, DAGOpNode from qiskit.circuit.library.standard_gates import ( - RYGate, + SGate, + SdgGate, + SXGate, HGate, CXGate, CZGate, @@ -49,11 +51,14 @@ class GateDirection(TransformationPass): q_1: ┤ X ├ q_1: ┤ H ├──■──┤ H ├ └───┘ └───┘ └───┘ - ┌──────┐ ┌───────────┐┌──────┐┌───┐ - q_0: ┤0 ├ q_0: ┤ RY(-pi/2) ├┤1 ├┤ H ├ - │ ECR │ = └┬──────────┤│ ECR │├───┤ - q_1: ┤1 ├ q_1: ─┤ RY(pi/2) ├┤0 ├┤ H ├ - └──────┘ └──────────┘└──────┘└───┘ + + global phase: 3π/2 + ┌──────┐ ┌───┐ ┌────┐┌─────┐┌──────┐┌───┐ + q_0: ┤0 ├ q_0: ─┤ S ├─┤ √X ├┤ Sdg ├┤1 ├┤ H ├ + │ ECR │ = ┌┴───┴┐├────┤└┬───┬┘│ Ecr │├───┤ + q_1: ┤1 ├ q_1: ┤ Sdg ├┤ √X ├─┤ S ├─┤0 ├┤ H ├ + └──────┘ └─────┘└────┘ └───┘ └──────┘└───┘ + ┌──────┐ ┌───┐┌──────┐┌───┐ q_0: ┤0 ├ q_0: ┤ H ├┤1 ├┤ H ├ @@ -90,11 +95,19 @@ def __init__(self, coupling_map, target=None): self._cx_dag.apply_operation_back(HGate(), [qr[0]], []) self._cx_dag.apply_operation_back(HGate(), [qr[1]], []) + # This is done in terms of less-efficient S/SX/Sdg gates instead of the more natural + # `RY(pi /2)` so we have a chance for basis translation to keep things in a discrete basis + # during resynthesis, if that's what's being asked for. self._ecr_dag = DAGCircuit() qr = QuantumRegister(2) + self._ecr_dag.global_phase = -pi / 2 self._ecr_dag.add_qreg(qr) - self._ecr_dag.apply_operation_back(RYGate(-pi / 2), [qr[0]], []) - self._ecr_dag.apply_operation_back(RYGate(pi / 2), [qr[1]], []) + self._ecr_dag.apply_operation_back(SGate(), [qr[0]], []) + self._ecr_dag.apply_operation_back(SXGate(), [qr[0]], []) + self._ecr_dag.apply_operation_back(SdgGate(), [qr[0]], []) + self._ecr_dag.apply_operation_back(SdgGate(), [qr[1]], []) + self._ecr_dag.apply_operation_back(SXGate(), [qr[1]], []) + self._ecr_dag.apply_operation_back(SGate(), [qr[1]], []) self._ecr_dag.apply_operation_back(ECRGate(), [qr[1], qr[0]], []) self._ecr_dag.apply_operation_back(HGate(), [qr[0]], []) self._ecr_dag.apply_operation_back(HGate(), [qr[1]], []) diff --git a/releasenotes/notes/discrete-basis-gatedirection-bdffad3b47c1c532.yaml b/releasenotes/notes/discrete-basis-gatedirection-bdffad3b47c1c532.yaml new file mode 100644 index 000000000000..c3d4bbe63861 --- /dev/null +++ b/releasenotes/notes/discrete-basis-gatedirection-bdffad3b47c1c532.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + The :class:`.GateDirection` transpiler pass will now use discrete-basis translations rather than + relying on a continuous :class:`.RYGate`, which should help make some discrete-basis-set targets + slightly more reliable. In general, :func:`.transpile` only has partial support for basis sets + that do not contain a continuously-parametrised operation, and so it may not always succeed in + these situations, and will almost certainly not produce optimal results. diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index f41f0a3dd16f..9a54bdc7da0b 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -51,16 +51,20 @@ from qiskit.circuit.library import ( CXGate, CZGate, + ECRGate, HGate, RXGate, RYGate, RZGate, + SGate, SXGate, + SXdgGate, + SdgGate, U1Gate, U2Gate, UGate, XGate, - SGate, + ZGate, ) from qiskit.circuit.measure import Measure from qiskit.compiler import transpile @@ -417,6 +421,28 @@ def test_transpile_bell(self): circuits = transpile(qc, backend) self.assertIsInstance(circuits, QuantumCircuit) + def test_transpile_bell_discrete_basis(self): + """Test that it's possible to transpile a very simple circuit to a discrete stabiliser-like + basis. In general, we do not make any guarantees about the possibility or quality of + transpilation in these situations, but this is at least useful as a check that stuff that + _could_ be possible remains so.""" + + target = Target(num_qubits=2) + for one_q in [XGate(), SXGate(), SXdgGate(), SGate(), SdgGate(), ZGate()]: + target.add_instruction(one_q, {(0,): None, (1,): None}) + # This is only in one direction, and not the direction we're going to attempt to lay it out + # onto, so we can test the basis translation. + target.add_instruction(ECRGate(), {(1, 0): None}) + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + + # Try with the initial layout in both directions to ensure we're dealing with the basis + # having only a single direction. + self.assertIsInstance(transpile(qc, target=target, initial_layout=[0, 1]), QuantumCircuit) + self.assertIsInstance(transpile(qc, target=target, initial_layout=[1, 0]), QuantumCircuit) + def test_transpile_one(self): """Test transpile a single circuit. diff --git a/test/python/transpiler/test_gate_direction.py b/test/python/transpiler/test_gate_direction.py index ef23ea53f5f1..172c74e0c292 100644 --- a/test/python/transpiler/test_gate_direction.py +++ b/test/python/transpiler/test_gate_direction.py @@ -172,12 +172,16 @@ def test_ecr_flip(self): # ├─────────┴┐│ Ecr │├───┤ # qr_1: ┤ Ry(-π/2) ├┤1 ├┤ H ├ # └──────────┘└──────┘└───┘ - expected = QuantumCircuit(qr) - expected.ry(pi / 2, qr[0]) - expected.ry(-pi / 2, qr[1]) - expected.ecr(qr[0], qr[1]) - expected.h(qr[0]) - expected.h(qr[1]) + expected = QuantumCircuit(qr, global_phase=-pi / 2) + expected.s(1) + expected.sx(1) + expected.sdg(1) + expected.sdg(0) + expected.sx(0) + expected.s(0) + expected.ecr(0, 1) + expected.h(0) + expected.h(1) pass_ = GateDirection(coupling) after = pass_.run(dag) @@ -405,8 +409,13 @@ def test_coupling_map_control_flow(self): expected.h([0, 1]) expected.cx(0, 1) with expected.if_test((circuit.clbits[0], True)) as else_: - expected.ry(pi / 2, 2) - expected.ry(-pi / 2, 3) + expected.global_phase -= pi / 2 + expected.sdg(2) + expected.sx(2) + expected.s(2) + expected.s(3) + expected.sx(3) + expected.sdg(3) expected.ecr(2, 3) expected.h([2, 3]) with else_: @@ -442,8 +451,13 @@ def test_target_control_flow(self): expected.h([0, 1]) expected.cx(0, 1) with expected.if_test((circuit.clbits[0], True)) as else_: - expected.ry(pi / 2, 2) - expected.ry(-pi / 2, 3) + expected.global_phase -= pi / 2 + expected.sdg(2) + expected.sx(2) + expected.s(2) + expected.s(3) + expected.sx(3) + expected.sdg(3) expected.ecr(2, 3) expected.h([2, 3]) with else_: