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

EchoRZXWeylDecomposition Transpiler Pass #6784

Merged
merged 66 commits into from
Oct 1, 2021
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
55514cd
Added new EchoRZXWeylDecomposition transpiler pass
catornow Jul 21, 2021
b60171d
Adapted two_qubit_decompose and calibration_creators and added tests
catornow Jul 21, 2021
73de71b
Black
catornow Jul 21, 2021
8f4ebf7
Small modification
catornow Jul 21, 2021
add5413
Adapted two_qubit_decompose to match latest upstream/main version
catornow Jul 23, 2021
76936f6
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Jul 25, 2021
294804a
Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposit…
catornow Jul 25, 2021
c13538d
Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposit…
catornow Jul 25, 2021
8bd3c22
Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposit…
catornow Jul 25, 2021
a8fc088
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Jul 25, 2021
e71d098
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Jul 25, 2021
f93d5e5
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Jul 25, 2021
43de98b
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Jul 25, 2021
5b7523e
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Jul 25, 2021
713b536
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Jul 25, 2021
a484c9c
Implemented Daniel's suggestions
catornow Jul 25, 2021
d600452
Small modifications in calibration_creators
catornow Jul 25, 2021
2bcb15e
Style and lint errors
catornow Jul 26, 2021
1aa8f80
Rewrote TwoQubitWeylEchoRZX to remove lint error
catornow Jul 26, 2021
e1cefd0
Black
catornow Jul 26, 2021
9bcb060
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
eggerdj Jul 28, 2021
0e9f637
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
eggerdj Jul 28, 2021
4139093
Implemented additional tests
catornow Jul 28, 2021
e7d06e2
Black
catornow Jul 28, 2021
c148aaa
Small modifications of the tests
catornow Jul 28, 2021
ae7df3b
Added release note
catornow Jul 28, 2021
562af6a
Added the class TwoQubitControlledUDecomposer to generalize TwoQubitW…
catornow Aug 18, 2021
148e8e9
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Aug 24, 2021
2210b73
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Aug 24, 2021
e5a4370
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Aug 24, 2021
7ed3b1a
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Aug 24, 2021
d996963
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Aug 24, 2021
6903d22
Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposit…
catornow Aug 24, 2021
97e0221
Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposit…
catornow Aug 24, 2021
49bc0de
Modified TwoQubitControlledUDecomposer to work for CPhaseGate, CRZGat…
catornow Aug 27, 2021
60316e6
Added tests for TwoQubitControlledUDecomposer
catornow Aug 27, 2021
b359a4d
Moved is_native to EchoRZXWeylDecomposition transpiler pass
catornow Aug 27, 2021
2c26d6f
Removed TwoQubitWeylEchoRZX tests (since this class does not exist an…
catornow Aug 27, 2021
6f76d18
Merge branch 'main' into EchoRZXWeylDecomposition
catornow Aug 27, 2021
a7018a4
Small modification
catornow Aug 27, 2021
8051753
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Aug 27, 2021
5cf54da
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Aug 27, 2021
2dd8097
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Aug 27, 2021
cb33ef5
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Aug 27, 2021
a2f52d5
Fixed style and lint error
catornow Aug 27, 2021
69bf841
Removed ```node.type == "op"``` in EchoRZXWeylDecomposition transpile…
catornow Aug 27, 2021
3537478
Merge branch 'main' into EchoRZXWeylDecomposition
catornow Aug 28, 2021
a684107
Merge branch 'main' of github.com:Qiskit/qiskit-terra into EchoRZXWey…
catornow Sep 1, 2021
2b2fb01
Merge branch 'EchoRZXWeylDecomposition' of github.com:catornow/qiskit…
catornow Sep 1, 2021
fe50279
Merge branch 'main' of github.com:Qiskit/qiskit-terra into EchoRZXWey…
catornow Sep 1, 2021
8dff106
Changed ```gate=node.op.name``` back to ```gate=node.op``` in Calibra…
catornow Sep 1, 2021
e0f0c2e
Changed the argument of EchoRZXWeylDecomposition from inst_map to bac…
catornow Sep 1, 2021
4fe00f8
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Sep 6, 2021
4630b11
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Sep 6, 2021
c328dd0
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Sep 6, 2021
a778fd3
Update qiskit/quantum_info/synthesis/two_qubit_decompose.py
catornow Sep 6, 2021
f3a0888
Fixed bug and slightly modified correct decomposition check
catornow Sep 6, 2021
3506c53
Removed (raise-missing-from) pylint error
catornow Sep 7, 2021
382ab9a
Adapted tests.
catornow Sep 7, 2021
9cb9b6c
Black
catornow Sep 7, 2021
de3622b
Modified docstrings
catornow Sep 29, 2021
bd41b3f
Modified and added tests to also check the RZX gate angles
catornow Sep 30, 2021
949fa01
Merge branch 'main' into EchoRZXWeylDecomposition
catornow Sep 30, 2021
1e777a3
Merge branch 'main' into EchoRZXWeylDecomposition
catornow Oct 1, 2021
84b4af7
Merge branch 'main' into EchoRZXWeylDecomposition
eggerdj Oct 1, 2021
9d3bdaa
Merge branch 'main' into EchoRZXWeylDecomposition
eggerdj Oct 1, 2021
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
151 changes: 148 additions & 3 deletions qiskit/quantum_info/synthesis/two_qubit_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@
import io
import base64
import warnings
from typing import ClassVar, Optional
from typing import ClassVar, Optional, Type

import logging

import numpy as np
import scipy.linalg as la

from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.library.standard_gates import CXGate, RXGate, RYGate, RZGate
from qiskit.circuit.quantumcircuit import QuantumCircuit, Gate
from qiskit.circuit.library.standard_gates import CXGate, RXGate, RYGate, RZGate, RXXGate
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators import Operator
from qiskit.quantum_info.synthesis.weyl import weyl_coordinates, transform_to_magic_basis
Expand Down Expand Up @@ -556,6 +556,151 @@ def specialize(self):
self.K2r = np.asarray(RYGate(k2rtheta)) @ np.asarray(RXGate(k2rlambda))


class TwoQubitControlledUDecomposer:
"""Decompose two-qubit unitary in terms of a desired U ~ Ud(α, 0, 0) ~ Ctrl-U gate
that is locally equivalent to an RXXGate."""

def __init__(self, rxx_equivalent_gate: Type[Gate]):
"""Initialize the KAK decomposition.

Args:
rxx_equivalent_gate: Gate that is locally equivalent to an RXXGate:
U ~ Ud(α, 0, 0) ~ Ctrl-U gate.
Raises:
QiskitError: If the gate is not locally equivalent to an RXXGate.
"""
# Do a few spot checks that the KAK decomposition gives (x, 0, 0)
catornow marked this conversation as resolved.
Show resolved Hide resolved
for test_angle in [0.2, 0.3, np.pi / 2]:
decomp = TwoQubitWeylDecomposition(Operator(rxx_equivalent_gate(test_angle)).data)
if not np.allclose([decomp.b, decomp.c], [0, 0]):
catornow marked this conversation as resolved.
Show resolved Hide resolved
raise QiskitError(
f"{rxx_equivalent_gate.__name__} is not equivalent to an RXXGate."
)

self.rxx_equivalent_gate = rxx_equivalent_gate
catornow marked this conversation as resolved.
Show resolved Hide resolved

def __call__(self, unitary, *, atol=DEFAULT_ATOL) -> QuantumCircuit:
"""Returns the Weyl decomposition in circuit form.

Note: atol ist passed to OneQubitEulerDecomposer.
"""

catornow marked this conversation as resolved.
Show resolved Hide resolved
# pylint: disable=attribute-defined-outside-init
self.decomposer = TwoQubitWeylDecomposition(unitary)

oneq_decompose = OneQubitEulerDecomposer("ZYZ")
c1l, c1r, c2l, c2r = (
oneq_decompose(k, atol=atol)
for k in (
self.decomposer.K1l,
self.decomposer.K1r,
self.decomposer.K2l,
self.decomposer.K2r,
)
)
circ = QuantumCircuit(2, global_phase=self.decomposer.global_phase)
circ.compose(c2r, [0], inplace=True)
circ.compose(c2l, [1], inplace=True)
self._weyl_gate(circ)
circ.compose(c1r, [0], inplace=True)
circ.compose(c1l, [1], inplace=True)
return circ

def _to_rxx_gate(self, angle: float):
"""
Takes an angle and returns the circuit equivalent to an RXXGate with the
RXX equivalent gate as the two-qubit unitary.

Args:
angle: Rotation angle (in this case one of the Weyl parameters a, b, or c)

Returns:
Circuit: Circuit equivalent to an RXXGate.

Raises:
QiskitError: If the circuit is not equivalent to an RXXGate.
"""

# The user-provided RXXGate equivalent gate may be locally equivalent to the RXXGate
# but with some scaling in the rotation angle. For example, RXXGate(angle) has Weyl
# parameters (angle, 0, 0) for angle in [0, pi/2] but the user provided gate, i.e.
# :code:`self.rxx_equivalent_gate(angle)` might produce the Weyl parameters
# (scale * angle, 0, 0) where scale != 1. This is the case for the CPhaseGate.
circ = QuantumCircuit(2)
circ.rxx(angle, 0, 1)
decomposer_rxx = TwoQubitWeylControlledEquiv(Operator(circ).data)

circ = QuantumCircuit(2)
circ.append(self.rxx_equivalent_gate(angle), qargs=[0, 1])
decomposer_equiv = TwoQubitWeylControlledEquiv(Operator(circ).data)

scale = decomposer_rxx.a / decomposer_equiv.a

circ = QuantumCircuit(2)
circ.append(self.rxx_equivalent_gate(scale * angle), qargs=[0, 1])
decomposer_inv = TwoQubitWeylControlledEquiv(Operator(circ).data)

oneq_decompose = OneQubitEulerDecomposer("ZYZ")

# Express the RXXGate in terms of the user-provided RXXGate equivalent gate.
rxx_circ = QuantumCircuit(2, global_phase=-decomposer_inv.global_phase)
rxx_circ.compose(oneq_decompose(decomposer_inv.K2r).inverse(), inplace=True, qubits=[0])
rxx_circ.compose(oneq_decompose(decomposer_inv.K2l).inverse(), inplace=True, qubits=[1])
rxx_circ.compose(circ, inplace=True)
rxx_circ.compose(oneq_decompose(decomposer_inv.K1r).inverse(), inplace=True, qubits=[0])
rxx_circ.compose(oneq_decompose(decomposer_inv.K1l).inverse(), inplace=True, qubits=[1])

# Ensure the circuit is equivalent to an RXXGate.
qc_rxx = QuantumCircuit(2)
qc_rxx.append(RXXGate(angle), qargs=[0, 1])
if not np.allclose(Operator(rxx_circ).data, Operator(qc_rxx).data):
raise QiskitError(f"Failed to reproduce RXXGate for angle {angle}.")
catornow marked this conversation as resolved.
Show resolved Hide resolved

return rxx_circ

def _weyl_gate(self, circ: QuantumCircuit, atol=1.0e-13):
"""Appends Ud(a, b, c) to the circuit."""

circ_rxx = self._to_rxx_gate(-2 * self.decomposer.a)
circ.compose(circ_rxx, inplace=True)

# translate the RYYGate(b) into a circuit based on the desired Ctrl-U gate.
if abs(self.decomposer.b) > atol:
circ_ryy = QuantumCircuit(2)
circ_ryy.sdg(0)
circ_ryy.sdg(1)
circ_ryy.compose(self._to_rxx_gate(-2 * self.decomposer.b), inplace=True)
circ_ryy.s(0)
circ_ryy.s(1)
circ.compose(circ_ryy, inplace=True)

# translate the RZZGate(c) into a circuit based on the desired Ctrl-U gate.
if abs(self.decomposer.c) > atol:
# Since the Weyl chember is here defined as a > b > |c| we may have
catornow marked this conversation as resolved.
Show resolved Hide resolved
# negative c. This will cause issues in _to_rxx_gate
# as TwoQubitWeylControlledEquiv will map (c, 0, 0) to (|c|, 0, 0).
# We therefore produce RZZGate(|c|) and append its inverse to the
# circuit if c < 0.
gamma, invert = -2 * self.decomposer.c, False
if gamma > 0:
gamma *= -1
invert = True

circ_rzz = QuantumCircuit(2)
circ_rzz.h(0)
circ_rzz.h(1)
circ_rzz.compose(self._to_rxx_gate(gamma), inplace=True)
circ_rzz.h(0)
circ_rzz.h(1)

if invert:
circ.compose(circ_rzz.inverse(), inplace=True)
else:
circ.compose(circ_rzz, inplace=True)

return circ


class TwoQubitWeylMirrorControlledEquiv(TwoQubitWeylDecomposition):
"""U ~ Ud(𝜋/4, 𝜋/4, α) ~ SWAP . Ctrl-U

Expand Down
127 changes: 127 additions & 0 deletions qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Weyl decomposition of two-qubit gates in terms of echoed cross-resonance gates."""

from typing import Tuple

from qiskit import QuantumRegister
from qiskit.circuit.library.standard_gates import RZXGate, HGate, XGate

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.layout import Layout

from qiskit.dagcircuit import DAGCircuit
from qiskit.converters import circuit_to_dag

from qiskit.providers import basebackend

import qiskit.quantum_info as qi
from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitControlledUDecomposer


class EchoRZXWeylDecomposition(TransformationPass):
"""Rewrite two-qubit gates using the Weyl decomposition.

This transpiler pass rewrites two-qubit gates in terms of echoed cross-resonance gates according
to the Weyl decomposition. A two-qubit gate will be replaced with at most six non-echoed RZXGates.
Each pair of RZXGates forms an echoed RZXGate.
"""

def __init__(self, backend: basebackend):
"""EchoRZXWeylDecomposition pass."""
self._inst_map = backend.defaults().instruction_schedule_map
super().__init__()

catornow marked this conversation as resolved.
Show resolved Hide resolved
def _is_native(self, qubit_pair: Tuple) -> bool:
"""Return the direction of the qubit pair that is native, i.e. with the shortest schedule."""
cx1 = self._inst_map.get("cx", qubit_pair)
cx2 = self._inst_map.get("cx", qubit_pair[::-1])
return cx1.duration < cx2.duration

@staticmethod
def _echo_rzx_dag(theta):
rzx_dag = DAGCircuit()
qr = QuantumRegister(2)
rzx_dag.add_qreg(qr)
rzx_dag.apply_operation_back(RZXGate(theta / 2), [qr[0], qr[1]], [])
rzx_dag.apply_operation_back(XGate(), [qr[0]], [])
rzx_dag.apply_operation_back(RZXGate(-theta / 2), [qr[0], qr[1]], [])
rzx_dag.apply_operation_back(XGate(), [qr[0]], [])
return rzx_dag

@staticmethod
def _reverse_echo_rzx_dag(theta):
reverse_rzx_dag = DAGCircuit()
qr = QuantumRegister(2)
reverse_rzx_dag.add_qreg(qr)
reverse_rzx_dag.apply_operation_back(HGate(), [qr[0]], [])
reverse_rzx_dag.apply_operation_back(HGate(), [qr[1]], [])
reverse_rzx_dag.apply_operation_back(RZXGate(theta / 2), [qr[1], qr[0]], [])
reverse_rzx_dag.apply_operation_back(XGate(), [qr[1]], [])
reverse_rzx_dag.apply_operation_back(RZXGate(-theta / 2), [qr[1], qr[0]], [])
reverse_rzx_dag.apply_operation_back(XGate(), [qr[1]], [])
reverse_rzx_dag.apply_operation_back(HGate(), [qr[0]], [])
reverse_rzx_dag.apply_operation_back(HGate(), [qr[1]], [])
return reverse_rzx_dag

def run(self, dag: DAGCircuit):
"""Run the EchoRZXWeylDecomposition pass on `dag`.

Rewrites two-qubit gates in an arbitrary circuit in terms of echoed cross-resonance
gates by computing the Weyl decomposition of the corresponding unitary. Modifies the
input dag.

Args:
dag (DAGCircuit): DAG to map.
catornow marked this conversation as resolved.
Show resolved Hide resolved

Returns:
DAGCircuit: The rearranged dag.
catornow marked this conversation as resolved.
Show resolved Hide resolved

Raises:
TranspilerError: If the circuit cannot be mapped.
catornow marked this conversation as resolved.
Show resolved Hide resolved
"""

if len(dag.qregs) > 1:
raise TranspilerError(
"EchoRZXWeylDecomposition expects a single qreg input DAG,"
f"but input DAG had qregs: {dag.qregs}."
)

trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values())
eggerdj marked this conversation as resolved.
Show resolved Hide resolved

catornow marked this conversation as resolved.
Show resolved Hide resolved
decomposer = TwoQubitControlledUDecomposer(RZXGate)

for node in dag.two_qubit_ops():

unitary = qi.Operator(node.op).data
dag_weyl = circuit_to_dag(decomposer(unitary))
dag.substitute_node_with_dag(node, dag_weyl)

for node in dag.two_qubit_ops():
if node.name == "rzx":
control = node.qargs[0]
target = node.qargs[1]

physical_q0 = trivial_layout[control]
physical_q1 = trivial_layout[target]

is_native = self._is_native((physical_q0, physical_q1))
eggerdj marked this conversation as resolved.
Show resolved Hide resolved

theta = node.op.params[0]
if is_native:
dag.substitute_node_with_dag(node, self._echo_rzx_dag(theta))
else:
dag.substitute_node_with_dag(node, self._reverse_echo_rzx_dag(theta))

return dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
features:
- |
Added a new transpiler pass :class:`qiskit.transpiler.passes.optimization.EchoRZXWeylDecomposition` that allows users to decompose an arbitrary two-qubit gate in terms of echoed RZX-gates by leveraging Cartan's decomposition. In combination with other transpiler passes this can be used to transpile arbitrary circuits to RZX-gate-based and pulse-efficient circuits that implement the same unitary.
37 changes: 37 additions & 0 deletions test/python/quantum_info/test_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@
CXGate,
CZGate,
iSwapGate,
SwapGate,
RXXGate,
RYYGate,
RZZGate,
RZXGate,
CPhaseGate,
CRZGate,
RXGate,
RYGate,
RZGate,
Expand All @@ -60,6 +66,7 @@
TwoQubitWeylGeneral,
two_qubit_cnot_decompose,
TwoQubitBasisDecomposer,
TwoQubitControlledUDecomposer,
Ud,
decompose_two_qubit_product_gate,
)
Expand Down Expand Up @@ -1302,6 +1309,36 @@ def test_approx_supercontrolled_decompose_phase_3_use_random(self, seed, delta=0
self.check_approx_decomposition(tgt_unitary, decomposer, num_basis_uses=3)


@ddt
class TestTwoQubitControlledUDecompose(CheckDecompositions):
"""Test TwoQubitControlledUDecomposer() for exact decompositions and raised exceptions"""

@combine(seed=range(10), name="seed_{seed}")
def test_correct_unitary(self, seed):
"""Verify unitary for different gates in the decomposition"""
unitary = random_unitary(4, seed=seed)
for gate in [RXXGate, RYYGate, RZZGate, RZXGate, CPhaseGate, CRZGate]:
decomposer = TwoQubitControlledUDecomposer(gate)
circ = decomposer(unitary)
self.assertEqual(Operator(unitary), Operator(circ))

def test_rxx_not_reproducible(self):
"""Test that an exception is raised if RXXGate cannot be reproduced"""
unitary = random_unitary(4)
for gate in [CXGate, CZGate]:
decomposer = TwoQubitControlledUDecomposer(gate)
with self.assertRaises(QiskitError) as exc:
decomposer(unitary)
self.assertIn("Failed to reproduce RXXGate for angle", exc.exception.message)

def test_not_rxx_equivalent(self):
"""Test that an exception is raised if the gate is not equivalent to an RXXGate"""
gate = SwapGate
with self.assertRaises(QiskitError) as exc:
TwoQubitControlledUDecomposer(gate)
self.assertIn("is not equivalent to an RXXGate.", exc.exception.message)


class TestDecomposeProductRaises(QiskitTestCase):
"""Check that exceptions are raised when 2q matrix is not a product of 1q unitaries"""

Expand Down
Loading