Skip to content

Commit

Permalink
Fix global phase in OneQubitEulerDecomposer using PSX and ZSX basis a…
Browse files Browse the repository at this point in the history
…nd in Diagonal gate (#5474) (#5652)

* fix global phase in OneQubitEulerDecomposer using PSX and ZSX basis

* minor fixes

* update tests

* update randomized test

* set global_phase=True

* track global phase in diagonal gate

* fix dimension

* add test for special cases of zsx psx decomposition.

* linting

Co-authored-by: ewinston <[email protected]>
Co-authored-by: Kevin Krsulich <[email protected]>
(cherry picked from commit e98d456)

Co-authored-by: georgios-ts <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
mergify[bot] and georgios-ts authored Jan 19, 2021
1 parent 9478771 commit 6dffb52
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 13 deletions.
1 change: 1 addition & 0 deletions qiskit/circuit/library/generalized_gates/diagonal.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def __init__(self,
target_qubit = num_qubits - num_act_qubits
self.ucrz(angles_rz, ctrl_qubits, target_qubit)
n //= 2
self.global_phase += diag_phases[0]


# extract a Rz rotation (angle given by first output) such that exp(j*phase)*Rz(z_angle)
Expand Down
1 change: 1 addition & 0 deletions qiskit/extensions/quantum_initializer/diagonal.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def _dec_diag(self):
target_qubit = q[self.num_qubits - num_act_qubits]
circuit.ucrz(angles_rz, contr_qubits, target_qubit)
n //= 2
circuit.global_phase += diag_phases[0]
return circuit


Expand Down
2 changes: 1 addition & 1 deletion qiskit/quantum_info/operators/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ def _append_instruction(self, obj, qargs=None):
'definition is {} but expected QuantumCircuit.'.format(
obj.name, type(obj.definition)))
if obj.definition.global_phase:
dimension = 2 ** self.num_qubits
dimension = 2 ** obj.num_qubits
op = self.compose(
ScalarOp(dimension, np.exp(1j * float(obj.definition.global_phase))),
qargs=qargs)
Expand Down
16 changes: 14 additions & 2 deletions qiskit/quantum_info/synthesis/one_qubit_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,12 +338,13 @@ def _circuit_psx(theta,
# Phase(phi+pi).SX.Phase(theta+pi).SX.Phase(lam)
theta = _mod2pi(theta + np.pi)
phi = _mod2pi(phi + np.pi)
circuit = QuantumCircuit(1, global_phase=phase)
circuit = QuantumCircuit(1, global_phase=phase - np.pi / 2)
# Check for decomposition into minimimal number required SX gates
if simplify and np.isclose(abs(theta), np.pi, atol=atol):
if not np.isclose(_mod2pi(abs(lam + phi + theta)),
[0., 2*np.pi], atol=atol).any():
circuit.append(PhaseGate(_mod2pi(lam + phi + theta)), [0])
circuit.global_phase += np.pi / 2
elif simplify and np.isclose(abs(theta),
[np.pi/2, 3*np.pi/2], atol=atol).any():
if not np.isclose(_mod2pi(abs(lam + theta)),
Expand All @@ -353,6 +354,8 @@ def _circuit_psx(theta,
if not np.isclose(_mod2pi(abs(phi + theta)),
[0., 2*np.pi], atol=atol).any():
circuit.append(PhaseGate(_mod2pi(phi + theta)), [0])
if np.isclose(theta, [-np.pi / 2, 3 * np.pi / 2], atol=atol).any():
circuit.global_phase += np.pi / 2
else:
if not np.isclose(abs(lam), [0., 2*np.pi], atol=atol).any():
circuit.append(PhaseGate(lam), [0])
Expand All @@ -375,30 +378,39 @@ def _circuit_zsx(theta,
# RZ(phi+pi).SX.RZ(theta+pi).SX.RZ(lam)
theta = _mod2pi(theta + np.pi)
phi = _mod2pi(phi + np.pi)
circuit = QuantumCircuit(1, global_phase=phase)
circuit = QuantumCircuit(1, global_phase=phase - np.pi / 2)
# Check for decomposition into minimimal number required SX gates
if simplify and np.isclose(abs(theta), np.pi, atol=atol):
if not np.isclose(_mod2pi(abs(lam + phi + theta)),
[0., 2*np.pi], atol=atol).any():
circuit.append(RZGate(_mod2pi(lam + phi + theta)), [0])
circuit.global_phase += 0.5 * _mod2pi(lam + phi + theta)
circuit.global_phase += np.pi / 2
elif simplify and np.isclose(abs(theta),
[np.pi/2, 3*np.pi/2], atol=atol).any():
if not np.isclose(_mod2pi(abs(lam + theta)),
[0., 2*np.pi], atol=atol).any():
circuit.append(RZGate(_mod2pi(lam + theta)), [0])
circuit.global_phase += 0.5 * _mod2pi(lam + theta)
circuit.append(SXGate(), [0])
if not np.isclose(_mod2pi(abs(phi + theta)),
[0., 2*np.pi], atol=atol).any():
circuit.append(RZGate(_mod2pi(phi + theta)), [0])
circuit.global_phase += 0.5 * _mod2pi(phi + theta)
if np.isclose(theta, [-np.pi / 2, 3 * np.pi / 2], atol=atol).any():
circuit.global_phase += np.pi / 2
else:
if not np.isclose(abs(lam), [0., 2*np.pi], atol=atol).any():
circuit.append(RZGate(lam), [0])
circuit.global_phase += 0.5 * lam
circuit.append(SXGate(), [0])
if not np.isclose(abs(theta), [0., 2*np.pi], atol=atol).any():
circuit.append(RZGate(theta), [0])
circuit.global_phase += 0.5 * theta
circuit.append(SXGate(), [0])
if not np.isclose(abs(phi), [0., 2*np.pi], atol=atol).any():
circuit.append(RZGate(phi), [0])
circuit.global_phase += 0.5 * phi
return circuit

@staticmethod
Expand Down
7 changes: 4 additions & 3 deletions test/python/circuit/library/test_diagonal.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from qiskit.test.base import QiskitTestCase
from qiskit.circuit.library import Diagonal
from qiskit.quantum_info import Statevector, Operator
from qiskit.quantum_info.operators.predicates import matrix_equal


@ddt
Expand All @@ -37,10 +38,10 @@ def test_diag_gate(self, phases):
"""Test correctness of diagonal decomposition."""
diag = [np.exp(1j * ph) for ph in phases]
qc = Diagonal(diag)
simulated_diag = Statevector(Operator(qc).data.diagonal())
ref_diag = Statevector(diag)
simulated_diag = Statevector(Operator(qc).data.diagonal()).data
ref_diag = Statevector(diag).data

self.assertTrue(simulated_diag.equiv(ref_diag))
self.assertTrue(matrix_equal(simulated_diag, ref_diag, ignore_phase=False))


if __name__ == '__main__':
Expand Down
4 changes: 2 additions & 2 deletions test/python/circuit/test_diagonal_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ def test_diag_gate(self):
qc = QuantumCircuit(q)
qc.diagonal(diag, q[0:num_qubits])
# Decompose the gate
qc = transpile(qc, basis_gates=['u1', 'u3', 'u2', 'cx', 'id'])
qc = transpile(qc, basis_gates=['u1', 'u3', 'u2', 'cx', 'id'], optimization_level=0)
# Simulate the decomposed gate
simulator = BasicAer.get_backend('unitary_simulator')
result = execute(qc, simulator).result()
unitary = result.get_unitary(qc)
unitary_desired = _get_diag_gate_matrix(diag)
self.assertTrue(matrix_equal(unitary, unitary_desired, ignore_phase=True))
self.assertTrue(matrix_equal(unitary, unitary_desired, ignore_phase=False))


def _get_diag_gate_matrix(diag):
Expand Down
42 changes: 37 additions & 5 deletions test/python/quantum_info/test_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from qiskit import execute
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.extensions import UnitaryGate
from qiskit.circuit.library import (HGate, IGate, SdgGate, SGate, U3Gate,
from qiskit.circuit.library import (HGate, IGate, SdgGate, SGate, U3Gate, UGate,
XGate, YGate, ZGate, CXGate, CZGate,
iSwapGate, RXXGate)
from qiskit.providers.basicaer import UnitarySimulatorPy
Expand Down Expand Up @@ -79,7 +79,7 @@ class CheckDecompositions(QiskitTestCase):
"""Implements decomposition checkers."""

def check_one_qubit_euler_angles(self, operator, basis='U3', tolerance=1e-12,
phase_equal=False):
phase_equal=True):
"""Check OneQubitEulerDecomposer works for the given unitary"""
target_unitary = operator.data
if basis is None:
Expand Down Expand Up @@ -162,7 +162,7 @@ class TestOneQubitEulerDecomposer(CheckDecompositions):

def check_one_qubit_euler_angles(self, operator, basis='U3',
tolerance=1e-12,
phase_equal=False):
phase_equal=True):
"""Check euler_angles_1q works for the given unitary"""
decomposer = OneQubitEulerDecomposer(basis)
with self.subTest(operator=operator):
Expand All @@ -176,7 +176,7 @@ def check_one_qubit_euler_angles(self, operator, basis='U3',
maxdist = np.max(np.abs(target_unitary + decomp_unitary))
self.assertTrue(np.abs(maxdist) < tolerance, "Worst distance {}".format(maxdist))

@combine(basis=['U3', 'U1X', 'ZYZ', 'ZXZ', 'XYX', 'RR'],
@combine(basis=['U3', 'U1X', 'PSX', 'ZSX', 'ZYZ', 'ZXZ', 'XYX', 'RR'],
name='test_one_qubit_clifford_{basis}_basis')
def test_one_qubit_clifford_all_basis(self, basis):
"""Verify for {basis} basis and all Cliffords."""
Expand All @@ -188,6 +188,8 @@ def test_one_qubit_clifford_all_basis(self, basis):
('ZXZ', 1e-12),
('ZYZ', 1e-12),
('U1X', 1e-7),
('PSX', 1e-7),
('ZSX', 1e-7),
('RR', 1e-12)],
name='test_one_qubit_hard_thetas_{basis_tolerance[0]}_basis')
# Lower tolerance for U1X test since decomposition since it is
Expand All @@ -199,13 +201,43 @@ def test_one_qubit_hard_thetas_all_basis(self, basis_tolerance):
self.check_one_qubit_euler_angles(Operator(gate), basis_tolerance[0],
basis_tolerance[1])

@combine(basis=['U3', 'U1X', 'ZYZ', 'ZXZ', 'XYX', 'RR'], seed=range(50),
@combine(basis=['U3', 'U1X', 'PSX', 'ZSX', 'ZYZ', 'ZXZ', 'XYX', 'RR'], seed=range(50),
name='test_one_qubit_random_{basis}_basis_{seed}')
def test_one_qubit_random_all_basis(self, basis, seed):
"""Verify for {basis} basis and random_unitary (seed={seed})."""
unitary = random_unitary(2, seed=seed)
self.check_one_qubit_euler_angles(unitary, basis)

def test_psx_zsx_special_cases(self):
"""Test decompositions of psx and zsx at special values of parameters"""
oqed_psx = OneQubitEulerDecomposer(basis='PSX')
oqed_zsx = OneQubitEulerDecomposer(basis='ZSX')
theta = np.pi / 3
phi = np.pi / 5
lam = np.pi / 7
test_gates = [UGate(np.pi, phi, lam), UGate(-np.pi, phi, lam),
# test abs(lam + phi + theta) near 0
UGate(np.pi, np.pi / 3, 2 * np.pi / 3),
# test theta=pi/2
UGate(np.pi / 2, phi, lam),
# test theta=pi/2 and theta+lam=0
UGate(np.pi / 2, phi, -np.pi / 2),
# test theta close to 3*pi/2 and theta+phi=2*pi
UGate(3*np.pi / 2, np.pi / 2, lam),
# test theta 0
UGate(0, phi, lam),
# test phi 0
UGate(theta, 0, lam),
# test lam 0
UGate(theta, phi, 0)]

for gate in test_gates:
unitary = gate.to_matrix()
qc_psx = oqed_psx(unitary)
qc_zsx = oqed_zsx(unitary)
self.assertTrue(np.allclose(unitary, Operator(qc_psx).data))
self.assertTrue(np.allclose(unitary, Operator(qc_zsx).data))


# FIXME: streamline the set of test cases
class TestTwoQubitWeylDecomposition(CheckDecompositions):
Expand Down
2 changes: 2 additions & 0 deletions test/randomized/test_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def test_1q_random(self, seed):
self.check_one_qubit_euler_angles(unitary)
self.check_one_qubit_euler_angles(unitary, 'U3')
self.check_one_qubit_euler_angles(unitary, 'U1X')
self.check_one_qubit_euler_angles(unitary, 'PSX')
self.check_one_qubit_euler_angles(unitary, 'ZSX')
self.check_one_qubit_euler_angles(unitary, 'ZYZ')
self.check_one_qubit_euler_angles(unitary, 'ZXZ')
self.check_one_qubit_euler_angles(unitary, 'XYX')
Expand Down

0 comments on commit 6dffb52

Please sign in to comment.