Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use only discrete-basis translations in GateDirection #10786

Merged
merged 3 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]], [])
Comment on lines +105 to +110
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a performance PoV there isn't a huge difference either way. For each ECR gate we flip we end up doing two extra products here: https://github.com/Qiskit/qiskit/blob/main/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py#L197 but they're all 2x2 so it'll be fast. There might actually be an immeasurable speed up because we don't have to build 2 matrices for the ry gates when to_matrix() is called. From a memory overhead perspective I think with #10314 this is actually more efficient because 2 Ry(..) is about 1kb and this ends up being 6 pointers (assuming SGate() and SdgGate() are instantiated somewhere already, SXGate should be because it's always in the default IBM basis with ECR).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, that's actually true about memory usage, the gate arrays and the singleton objects - I hadn't been thinking about that at all. I think I wrote this PR before we landed any of the singleton-gate stuff, so it wasn't on my mind at the time.

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