-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
EchoRZXWeylDecomposition Transpiler Pass (#6784)
* Added new EchoRZXWeylDecomposition transpiler pass * Adapted two_qubit_decompose and calibration_creators and added tests * Black * Small modification * Adapted two_qubit_decompose to match latest upstream/main version * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Implemented Daniel's suggestions * Small modifications in calibration_creators * Style and lint errors * Rewrote TwoQubitWeylEchoRZX to remove lint error * Black * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py * Implemented additional tests * Black * Small modifications of the tests * Added release note * Added the class TwoQubitControlledUDecomposer to generalize TwoQubitWeylEchoRZX. * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <[email protected]> * Modified TwoQubitControlledUDecomposer to work for CPhaseGate, CRZGate; removed TwoQubitWeylEchoRZX * Added tests for TwoQubitControlledUDecomposer * Moved is_native to EchoRZXWeylDecomposition transpiler pass * Removed TwoQubitWeylEchoRZX tests (since this class does not exist anymore) * Small modification * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <[email protected]> * Fixed style and lint error * Removed ```node.type == "op"``` in EchoRZXWeylDecomposition transpiler pass to remove deprecation warning * Changed ```gate=node.op.name``` back to ```gate=node.op``` in CalibrationBuilder * Changed the argument of EchoRZXWeylDecomposition from inst_map to backend * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Lev Bishop <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Lev Bishop <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Lev Bishop <[email protected]> * Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Lev Bishop <[email protected]> * Fixed bug and slightly modified correct decomposition check * Removed (raise-missing-from) pylint error * Adapted tests. * Black * Modified docstrings * Modified and added tests to also check the RZX gate angles Co-authored-by: Daniel Egger <[email protected]> Co-authored-by: Lev Bishop <[email protected]>
- 1.3.1
- 1.3.0
- 1.3.0rc2
- 1.3.0rc1
- 1.3.0b1
- 1.2.4
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.2.0rc1
- 1.1.2
- 1.1.1
- 1.1.0
- 1.1.0rc1
- 1.0.2
- 1.0.1
- 1.0.0
- 1.0.0rc1
- 1.0.0b1
- 0.46.3
- 0.46.2
- 0.46.1
- 0.46.0
- 0.45.3
- 0.45.2
- 0.45.1
- 0.45.0
- 0.45.0rc1
- 0.25.3
- 0.25.2.1
- 0.25.2
- 0.25.1
- 0.25.0
- 0.25.0rc1
- 0.24.2
- 0.24.1
- 0.24.0
- 0.24.0rc1
- 0.23.3
- 0.23.2
- 0.23.1
- 0.23.0
- 0.23.0rc1
- 0.22.4
- 0.22.3
- 0.22.2
- 0.22.1
- 0.22.0
- 0.22.0rc1
- 0.21.2
- 0.21.1
- 0.21.0
- 0.21.0rc1
- 0.20.2
- 0.20.1
- 0.20.0
- 0.19.2
- 0.19.1
- 0.19.0
1 parent
75c0e46
commit a8a0d1b
Showing
5 changed files
with
558 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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__() | ||
|
||
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 rewrite. | ||
Returns: | ||
DAGCircuit: The modified dag. | ||
Raises: | ||
TranspilerError: If the circuit cannot be rewritten. | ||
""" | ||
|
||
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()) | ||
|
||
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)) | ||
|
||
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 |
4 changes: 4 additions & 0 deletions
4
releasenotes/notes/echo-rzx-weyl-decomposition-ef72345a58bea9e0.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
232 changes: 232 additions & 0 deletions
232
test/python/transpiler/test_echo_rzx_weyl_decomposition.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
# 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. | ||
|
||
"""Test the EchoRZXWeylDecomposition pass""" | ||
|
||
import unittest | ||
from math import pi | ||
import numpy as np | ||
|
||
from qiskit import QuantumRegister, QuantumCircuit | ||
|
||
from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import ( | ||
EchoRZXWeylDecomposition, | ||
) | ||
from qiskit.converters import circuit_to_dag, dag_to_circuit | ||
from qiskit.test import QiskitTestCase | ||
from qiskit.test.mock import FakeParis | ||
|
||
import qiskit.quantum_info as qi | ||
|
||
from qiskit.quantum_info.synthesis.two_qubit_decompose import ( | ||
TwoQubitWeylDecomposition, | ||
) | ||
|
||
|
||
class TestEchoRZXWeylDecomposition(QiskitTestCase): | ||
"""Tests the EchoRZXWeylDecomposition pass.""" | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.backend = FakeParis() | ||
|
||
def assertRZXgates(self, unitary_circuit, after): | ||
"""Check the number of rzx gates""" | ||
alpha = TwoQubitWeylDecomposition(unitary_circuit).a | ||
beta = TwoQubitWeylDecomposition(unitary_circuit).b | ||
gamma = TwoQubitWeylDecomposition(unitary_circuit).c | ||
|
||
expected_rzx_number = 0 | ||
if not alpha == 0: | ||
expected_rzx_number += 2 | ||
if not beta == 0: | ||
expected_rzx_number += 2 | ||
if not gamma == 0: | ||
expected_rzx_number += 2 | ||
|
||
circuit_rzx_number = QuantumCircuit.count_ops(after)["rzx"] | ||
|
||
self.assertEqual(expected_rzx_number, circuit_rzx_number) | ||
|
||
@staticmethod | ||
def count_gate_number(gate, circuit): | ||
"""Count the number of a specific gate type in a circuit""" | ||
if gate not in QuantumCircuit.count_ops(circuit): | ||
gate_number = 0 | ||
else: | ||
gate_number = QuantumCircuit.count_ops(circuit)[gate] | ||
return gate_number | ||
|
||
def test_rzx_number_native_weyl_decomposition(self): | ||
"""Check the number of RZX gates for a hardware-native cx""" | ||
qr = QuantumRegister(2, "qr") | ||
circuit = QuantumCircuit(qr) | ||
circuit.cx(qr[0], qr[1]) | ||
|
||
unitary_circuit = qi.Operator(circuit).data | ||
|
||
after = EchoRZXWeylDecomposition(self.backend)(circuit) | ||
|
||
unitary_after = qi.Operator(after).data | ||
|
||
self.assertTrue(np.allclose(unitary_circuit, unitary_after)) | ||
|
||
# check whether the after circuit has the correct number of rzx gates. | ||
self.assertRZXgates(unitary_circuit, after) | ||
|
||
def test_h_number_non_native_weyl_decomposition_1(self): | ||
"""Check the number of added Hadamard gates for a native and non-native rzz gate""" | ||
theta = pi / 11 | ||
qr = QuantumRegister(2, "qr") | ||
# rzz gate in native direction | ||
circuit = QuantumCircuit(qr) | ||
circuit.rzz(theta, qr[0], qr[1]) | ||
|
||
# rzz gate in non-native direction | ||
circuit_non_native = QuantumCircuit(qr) | ||
circuit_non_native.rzz(theta, qr[1], qr[0]) | ||
|
||
dag = circuit_to_dag(circuit) | ||
pass_ = EchoRZXWeylDecomposition(self.backend) | ||
after = dag_to_circuit(pass_.run(dag)) | ||
|
||
dag_non_native = circuit_to_dag(circuit_non_native) | ||
pass_ = EchoRZXWeylDecomposition(self.backend) | ||
after_non_native = dag_to_circuit(pass_.run(dag_non_native)) | ||
|
||
circuit_rzx_number = self.count_gate_number("rzx", after) | ||
|
||
circuit_h_number = self.count_gate_number("h", after) | ||
circuit_non_native_h_number = self.count_gate_number("h", after_non_native) | ||
|
||
# for each pair of rzx gates four hadamard gates have to be added in | ||
# the case of a non-hardware-native directed gate. | ||
self.assertEqual( | ||
(circuit_rzx_number / 2) * 4, circuit_non_native_h_number - circuit_h_number | ||
) | ||
|
||
def test_h_number_non_native_weyl_decomposition_2(self): | ||
"""Check the number of added Hadamard gates for a swap gate""" | ||
qr = QuantumRegister(2, "qr") | ||
# swap gate in native direction. | ||
circuit = QuantumCircuit(qr) | ||
circuit.swap(qr[0], qr[1]) | ||
|
||
# swap gate in non-native direction. | ||
circuit_non_native = QuantumCircuit(qr) | ||
circuit_non_native.swap(qr[1], qr[0]) | ||
|
||
dag = circuit_to_dag(circuit) | ||
pass_ = EchoRZXWeylDecomposition(self.backend) | ||
after = dag_to_circuit(pass_.run(dag)) | ||
|
||
dag_non_native = circuit_to_dag(circuit_non_native) | ||
pass_ = EchoRZXWeylDecomposition(self.backend) | ||
after_non_native = dag_to_circuit(pass_.run(dag_non_native)) | ||
|
||
circuit_rzx_number = self.count_gate_number("rzx", after) | ||
|
||
circuit_h_number = self.count_gate_number("h", after) | ||
circuit_non_native_h_number = self.count_gate_number("h", after_non_native) | ||
|
||
# for each pair of rzx gates four hadamard gates have to be added in | ||
# the case of a non-hardware-native directed gate. | ||
self.assertEqual( | ||
(circuit_rzx_number / 2) * 4, circuit_non_native_h_number - circuit_h_number | ||
) | ||
|
||
def test_weyl_decomposition_gate_angles(self): | ||
"""Check the number and angles of the RZX gates for different gates""" | ||
thetas = [pi / 9, 2.1, -0.2] | ||
|
||
qr = QuantumRegister(2, "qr") | ||
circuit_rxx = QuantumCircuit(qr) | ||
circuit_rxx.rxx(thetas[0], qr[1], qr[0]) | ||
|
||
circuit_ryy = QuantumCircuit(qr) | ||
circuit_ryy.ryy(thetas[1], qr[0], qr[1]) | ||
|
||
circuit_rzz = QuantumCircuit(qr) | ||
circuit_rzz.rzz(thetas[2], qr[1], qr[0]) | ||
|
||
circuits = [circuit_rxx, circuit_ryy, circuit_rzz] | ||
|
||
for circuit in circuits: | ||
|
||
unitary_circuit = qi.Operator(circuit).data | ||
|
||
dag = circuit_to_dag(circuit) | ||
pass_ = EchoRZXWeylDecomposition(self.backend) | ||
after = dag_to_circuit(pass_.run(dag)) | ||
dag_after = circuit_to_dag(after) | ||
|
||
unitary_after = qi.Operator(after).data | ||
|
||
# check whether the unitaries are equivalent. | ||
self.assertTrue(np.allclose(unitary_circuit, unitary_after)) | ||
|
||
# check whether the after circuit has the correct number of rzx gates. | ||
self.assertRZXgates(unitary_circuit, after) | ||
|
||
alpha = TwoQubitWeylDecomposition(unitary_circuit).a | ||
|
||
rzx_angles = [] | ||
for node in dag_after.two_qubit_ops(): | ||
if node.name == "rzx": | ||
rzx_angle = node.op.params[0] | ||
# check whether the absolute values of the RZX gate angles | ||
# are equivalent to the corresponding Weyl parameter. | ||
self.assertAlmostEqual(np.abs(rzx_angle), alpha) | ||
rzx_angles.append(rzx_angle) | ||
|
||
# check whether the angles of every RZX gate pair of an echoed RZX gate | ||
# have opposite signs. | ||
for idx in range(1, len(rzx_angles), 2): | ||
self.assertAlmostEqual(rzx_angles[idx - 1], -rzx_angles[idx]) | ||
|
||
def test_weyl_unitaries_random_circuit(self): | ||
"""Weyl decomposition for a random two-qubit circuit.""" | ||
theta = pi / 9 | ||
epsilon = 5 | ||
delta = -1 | ||
eta = 0.2 | ||
qr = QuantumRegister(2, "qr") | ||
circuit = QuantumCircuit(qr) | ||
|
||
# random two-qubit circuit. | ||
circuit.rzx(theta, 0, 1) | ||
circuit.rzz(epsilon, 0, 1) | ||
circuit.rz(eta, 0) | ||
circuit.swap(1, 0) | ||
circuit.h(0) | ||
circuit.rzz(delta, 1, 0) | ||
circuit.swap(0, 1) | ||
circuit.cx(1, 0) | ||
circuit.swap(0, 1) | ||
circuit.h(1) | ||
circuit.rxx(theta, 0, 1) | ||
circuit.ryy(theta, 1, 0) | ||
circuit.ecr(0, 1) | ||
|
||
unitary_circuit = qi.Operator(circuit).data | ||
|
||
dag = circuit_to_dag(circuit) | ||
pass_ = EchoRZXWeylDecomposition(self.backend) | ||
after = dag_to_circuit(pass_.run(dag)) | ||
|
||
unitary_after = qi.Operator(after).data | ||
|
||
self.assertTrue(np.allclose(unitary_circuit, unitary_after)) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |