Skip to content

Commit

Permalink
Use only discrete-basis translations in GateDirection (#10786)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jakelishman committed Oct 16, 2023
1 parent df9eae4 commit f87bb01
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 19 deletions.
29 changes: 21 additions & 8 deletions qiskit/transpiler/passes/utils/gate_direction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 ├
Expand Down Expand Up @@ -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]], [])
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
28 changes: 27 additions & 1 deletion test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
34 changes: 24 additions & 10 deletions test/python/transpiler/test_gate_direction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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_:
Expand Down Expand Up @@ -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_:
Expand Down

0 comments on commit f87bb01

Please sign in to comment.