Skip to content

Commit

Permalink
EchoRZXWeylDecomposition Transpiler Pass (#6784)
Browse files Browse the repository at this point in the history
* 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]>
3 people authored Oct 1, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 75c0e46 commit a8a0d1b
Showing 5 changed files with 558 additions and 2 deletions.
167 changes: 165 additions & 2 deletions qiskit/quantum_info/synthesis/two_qubit_decompose.py
Original file line number Diff line number Diff line change
@@ -28,15 +28,15 @@
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.quantumcircuit import QuantumCircuit, Gate
from qiskit.circuit.library.standard_gates import CXGate, RXGate, RYGate, RZGate
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators import Operator
@@ -556,6 +556,169 @@ 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.
"""
atol = DEFAULT_ATOL

scales, test_angles, scale = [], [0.2, 0.3, np.pi / 2], None

for test_angle in test_angles:
# Check that gate takes a single angle parameter
try:
rxx_equivalent_gate(test_angle, label="foo")
except TypeError as _:
raise QiskitError("Equivalent gate needs to take exactly 1 angle parameter.") from _
decomp = TwoQubitWeylDecomposition(rxx_equivalent_gate(test_angle))

circ = QuantumCircuit(2)
circ.rxx(test_angle, 0, 1)
decomposer_rxx = TwoQubitWeylControlledEquiv(Operator(circ).data)

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

scale = decomposer_rxx.a / decomposer_equiv.a

if (
not isinstance(decomp, TwoQubitWeylControlledEquiv)
or abs(decomp.a * 2 - test_angle / scale) > atol
):
raise QiskitError(
f"{rxx_equivalent_gate.__name__} is not equivalent to an RXXGate."
)

scales.append(scale)

# Check that all three tested angles give the same scale
if not np.allclose(scales, [scale] * len(test_angles)):
raise QiskitError(
f"Cannot initialize {self.__class__.__name__}: with gate {rxx_equivalent_gate}. "
"Inconsistent scaling parameters in checks."
)

self.scale = scales[0]

self.rxx_equivalent_gate = rxx_equivalent_gate

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

# 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.append(self.rxx_equivalent_gate(self.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])

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 chamber is here defined as a > b > |c| we may have
# 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
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__()

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
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.
30 changes: 30 additions & 0 deletions test/python/quantum_info/test_synthesis.py
Original file line number Diff line number Diff line change
@@ -37,7 +37,13 @@
CXGate,
CZGate,
iSwapGate,
SwapGate,
RXXGate,
RYYGate,
RZZGate,
RZXGate,
CPhaseGate,
CRZGate,
RXGate,
RYGate,
RZGate,
@@ -60,6 +66,7 @@
TwoQubitWeylGeneral,
two_qubit_cnot_decompose,
TwoQubitBasisDecomposer,
TwoQubitControlledUDecomposer,
Ud,
decompose_two_qubit_product_gate,
)
@@ -1302,6 +1309,29 @@ 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_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(
"Equivalent gate needs to take exactly 1 angle parameter.", exc.exception.message
)


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

232 changes: 232 additions & 0 deletions test/python/transpiler/test_echo_rzx_weyl_decomposition.py
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()

0 comments on commit a8a0d1b

Please sign in to comment.