Skip to content

Commit

Permalink
remoce extract global phase
Browse files Browse the repository at this point in the history
  • Loading branch information
purva-thakre committed Jul 15, 2021
1 parent 920c416 commit 9daf3e4
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 126 deletions.
6 changes: 3 additions & 3 deletions doc/source/_apidoc/qutip_qip.decompose.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ qutip\_qip.decompose package
Submodules
----------

qutip\_qip.decompose.decompose\_single\_qubit\_gate module
----------------------------------------------------------
qutip\_qip.decompose.single\_qubit\_gate module
-----------------------------------------------

.. automodule:: qutip_qip.decompose.decompose_single_qubit_gate
.. automodule:: qutip_qip.decompose.single_qubit_gate
:members:
:undoc-members:
:show-inheritance:
Expand Down
2 changes: 1 addition & 1 deletion src/qutip_qip/decompose/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from qutip_qip.decompose.decompose_single_qubit_gate import (ABC_decomposition, decompose_to_rotation_matrices)
from .single_qubit_gate import *
16 changes: 0 additions & 16 deletions src/qutip_qip/decompose/_utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,3 @@ def check_gate(gate, num_qubits):
raise ValueError("Input is not unitary.")
if gate.dims != [[2] * num_qubits] * 2:
raise ValueError(f"Input is not a unitary on {num_qubits} qubits.")


def extract_global_phase(input_gate, num_qubits):
""" Extracts some common constant from all the elements of the matrix. The output
is retuned in the complex exponential form.
Parameters
----------
input_gate : :class:`qutip.Qobj`
The matrix that's supposed to be decomposed should be a Qobj.
"""
input_array = input_gate.full()
determinant_of_input = np.linalg.det(input_array)
global_phase_angle = cmath.phase(determinant_of_input)
global_phase_angle = global_phase_angle / (2**num_qubits)
return global_phase_angle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import cmath

from qutip import Qobj
from qutip_qip.decompose._utility import (check_gate, extract_global_phase, MethodError, GateError)
from qutip_qip.decompose._utility import (check_gate, MethodError, GateError)

from qutip_qip.circuit import QubitCircuit, Gate

Expand All @@ -18,24 +18,26 @@ def _angles_for_ZYZ(input_gate, num_qubits=1):
The gate matrix that's supposed to be decomposed should be a Qobj.
"""
check_gate(input_gate, num_qubits)
global_phase_angle = extract_global_phase(input_gate, num_qubits)
input_array = input_gate.full()
input_array = input_array/cmath.rect(1,global_phase_angle)
# separate all the elements
a = input_array[0][0]
b = input_array[0][1]
a_star = input_array[1][1]
b_star = input_array[1][0]
normalization_constant = np.sqrt(np.linalg.det(input_array))
global_phase_angle = -cmath.phase(1/normalization_constant)
input_array = input_array*(1/normalization_constant)

# U = np.array([[a,b],[-b*,a*]])
# If a = x+iy and b = p+iq, alpha = inv_tan(-y/x) - inv_tan(-q/p)
a_negative = np.real(input_array[0][0]) -1j*np.imag(input_array[0][0])
b_negative = np.real(input_array[0][1]) -1j*np.imag(input_array[0][1])

# find alpha, beta and theta
alpha = cmath.phase(-a_star) + cmath.phase(b_star)
beta = cmath.phase(-a_star) - cmath.phase(b_star)
theta = 2*np.arctan2(np.absolute(b_star), np.absolute(a))
return(alpha, theta, beta, np.pi+global_phase_angle)
alpha = cmath.phase(a_negative) - cmath.phase(b_negative)
beta = cmath.phase(a_negative) + cmath.phase(b_negative)
theta = 2*np.arctan2(np.absolute(b_negative),np.absolute(a_negative))

return(alpha, -theta, beta, global_phase_angle)



def _ZYZ_rotation(input_gate, num_qubits, target):
def _ZYZ_rotation(input_gate, target, num_qubits=1):
r""" An input 1-qubit gate is expressed as a product of rotation matrices
:math:`\textrm{R}_z` and :math:`\textrm{R}_y`.
Expand All @@ -44,7 +46,7 @@ def _ZYZ_rotation(input_gate, num_qubits, target):
input_gate : :class:`qutip.Qobj`
The matrix that's supposed to be decomposed should be a Qobj.
"""
angle_list = _angles_for_ZYZ(input_gate, 1)
angle_list = _angles_for_ZYZ(input_gate, num_qubits)
alpha = angle_list[0]
beta = angle_list[2]
theta = angle_list[1]
Expand All @@ -57,13 +59,13 @@ def _ZYZ_rotation(input_gate, num_qubits, target):
global_phase_angle_string = global_phase_angle/np.pi

Phase_gate = Gate("GLOBALPHASE",targets=[target], arg_value=global_phase_angle, arg_label=r'{:0.2f} \times \pi'.format(global_phase_angle_string))
Rz_alpha = Gate("RZ",targets=[target], arg_value=alpha, arg_label=r'{:0.2f} \times \pi'.format(alpha_string))
Ry_theta = Gate("RY",targets=[target], arg_value=theta, arg_label=r'{:0.2f} \times \pi'.format(theta_string))
Rz_beta = Gate("RZ",targets=[target], arg_value=beta, arg_label=r'{:0.2f} \times \pi'.format(beta_string))
Ry_theta = Gate("RY",targets=[target], arg_value=theta, arg_label=r'{:0.2f} \times \pi'.format(theta_string))
Rz_alpha = Gate("RZ",targets=[target], arg_value=alpha, arg_label=r'{:0.2f} \times \pi'.format(alpha_string))

return(Rz_alpha, Ry_theta, Rz_beta, Phase_gate)

def _ZXZ_rotation(input_gate, num_qubits, target):
def _ZXZ_rotation(input_gate, target, num_qubits=1):
r""" An input 1-qubit gate is expressed as a product of rotation matrices
:math:`\textrm{R}_z` and :math:`\textrm{R}_x`.
Expand All @@ -72,7 +74,7 @@ def _ZXZ_rotation(input_gate, num_qubits, target):
input_gate : :class:`qutip.Qobj`
The matrix that's supposed to be decomposed should be a Qobj.
"""
angle_list = _angles_for_ZYZ(input_gate, 1)
angle_list = _angles_for_ZYZ(input_gate, num_qubits)
alpha = angle_list[0]
alpha = alpha - np.pi/2
beta = angle_list[2]
Expand Down Expand Up @@ -102,7 +104,9 @@ def decompose_to_rotation_matrices(input_gate, method, num_qubits, target=0):
r""" An input 1-qubit gate is expressed as a product of rotation matrices
:math:`\textrm{R}_i` and :math:`\textrm{R}_j`.
Here, :math:`i \neq j` and :math:`i, j \in {x, y, z}`
Here, :math:`i \neq j` and :math:`i, j \in {x, y, z}`.
Based on Lemma 4.1 of https://arxiv.org/abs/quant-ph/9503016v1
.. math::
Expand All @@ -113,7 +117,7 @@ def decompose_to_rotation_matrices(input_gate, method, num_qubits, target=0):
Parameters
----------
input_gate : :class:`qutip.Qobj`
input_gate : :class:`.Qobj`
The matrix that's supposed to be decomposed should be a Qobj.
num_qubits : int
Expand All @@ -124,7 +128,7 @@ def decompose_to_rotation_matrices(input_gate, method, num_qubits, target=0):
single qubit gate.
method : string
Name of the preferred decompositions method
Name of the preferred decomposition method
.. list-table::
:widths: auto
Expand Down Expand Up @@ -155,35 +159,98 @@ def decompose_to_rotation_matrices(input_gate, method, num_qubits, target=0):
key = _rotation_matrices_dictionary.keys()
if str(method) in key:
method = _rotation_matrices_dictionary[str(method)]
return(method(input_gate, num_qubits, target))
return(method(input_gate, target, 1))

else:
raise MethodError("Invalid method chosen.")

# new combinations need to be added by creating a dictionary for these.
# The fucntion below will decompose the amtrix as a product of Rz, Ry and Pauli X only.
def ABC_decomposition(input_gate, num_qubits, target):
# Functions for ABC_decomposition

def _ZYZ_pauli_X(input_gate, target, num_qubits=1):
"""Returns a 1 qubit unitary as a product of ZYZ rotation matrices and Pauli X.
"""
angle_list = _angles_for_ZYZ(input_gate, num_qubits)
alpha = angle_list[0]
beta = angle_list[2]
theta = angle_list[1]
global_phase_angle=angle_list[3]

# for string in circuit diagram
alpha_string = alpha/np.pi
beta_string = beta/np.pi
theta_string = theta/np.pi
global_phase_angle_string = global_phase_angle/np.pi

Phase_gate = Gate("GLOBALPHASE",targets=[0], arg_value=global_phase_angle, arg_label=r'{:0.2f} \times \pi'.format(global_phase_angle_string))
Rz_A = Gate("RZ",targets=[target], arg_value=alpha, arg_label=r'{:0.2f} \times \pi'.format(alpha_string))
Ry_A = Gate("RY",targets=[target], arg_value=theta/2, arg_label=r'{:0.2f} \times \pi'.format(theta_string/2))
Pauli_X = Gate("X",targets=[target])
Ry_B = Gate("RY",targets=[target], arg_value=-theta/2, arg_label=r'{:0.2f} \times \pi'.format(-theta_string/2))
Rz_B = Gate("RZ",targets=[target], arg_value=-(alpha+beta)/2, arg_label=r'{:0.2f} \times \pi'.format(-(alpha_string+beta_string)/2))
Rz_C = Gate("RZ",targets=[target], arg_value=(-alpha+beta)/2, arg_label=r'{:0.2f} \times \pi'.format((-alpha_string+beta_string)/2))

return(Rz_A, Ry_A, Pauli_X, Ry_B, Rz_B, Pauli_X, Rz_C, Phase_gate)


_rotation_pauli_matrices_dictionary ={"ZYZ_PauliX":_ZYZ_pauli_X,
} # other combinations to add here

def ABC_decomposition(input_gate, method, num_qubits, target=0):
r""" An input 1-qubit gate is expressed as a product of rotation matrices
:math:`\textrm{R}_z`, :math:`\textrm{R}_y` and Pauli :math:`\textrm{X}`.
:math:`\textrm{R}_i` and :math:`\textrm{R}_j` and Pauli :math:`\sigma_k`.
Here, :math:`i \neq j` and :math:`i, j, k \in {x, y, z}`.
Based on Lemma 4.3 of https://arxiv.org/abs/quant-ph/9503016v1
.. math::
U = \begin{bmatrix}
a & b \\
-b^* & a^* \\
\end{bmatrix} = \textrm{A} \textrm{X} \textrm{B} \textrm{X} \textrm{C}
\end{bmatrix} = \textrm{A} \sigma_k \textrm{B} \sigma_k \textrm{C}
Here,
Where,
- :math:`\textrm{A} = \textrm{R}_z(\alpha) \textrm{R}_y \left(\frac{\theta}{2} \right)`
- :math:`\textrm{B} = \textrm{R}_y \left(\frac{-\theta}{2} \right) \textrm{R}_z \left(\frac{- \left(\alpha + \beta \right)}{2} \right)`
- :math:`\textrm{C} = \textrm{R}_z \left(\frac{\left(-\alpha + \beta \right)}{2} \right)`
.. list-table::
:widths: auto
:header-rows: 1
* - Gate Label
- Gate Composition
* - :math:`\textrm{A}`
- :math:`\textrm{R}_i(\alpha) \textrm{R}_j \left(\frac{\theta}{2} \right)`
* - :math:`\textrm{B}`
- :math:`\textrm{R}_j \left(\frac{-\theta}{2} \right) \textrm{R}_i \left(\frac{- \left(\alpha + \beta \right)}{2} \right)`
* - :math:`\textrm{C}`
- :math:`\textrm{R}_i \left(\frac{\left(-\alpha + \beta \right)}{2} \right)`
Parameters
----------
input_gate : :class:`qutip.Qobj`
input_gate : :class:`.Qobj`
The matrix that's supposed to be decomposed should be a Qobj.
num_qubits : int
Number of qubits being acted upon by input gate
target : int
If the circuit contains more than 1 qubits then provide target for
single qubit gate.
method : string
Name of the preferred decomposition method
.. list-table::
:widths: auto
:header-rows: 1
* - Method Key
- Method
- :math:`(i,j,k)`
* - ZYZ_PauliX
- :math:`\textrm{A} \textrm{X} \textrm{B} \textrm{X} \textrm{C}`
- :math:`(z,y,x)`
Returns
-------
tuple
Expand All @@ -196,35 +263,13 @@ def ABC_decomposition(input_gate, num_qubits, target):
assert num_qubits == 1
except AssertionError:
if target is None and num_qubits >1:
raise GateError("This method is valid for single qubit gates only. Provide a target qubit for larger circuits. ")

check_gate(input_gate, num_qubits)
global_phase_angle = extract_global_phase(input_gate,1)
input_array = input_gate.full()
input_array = input_array/cmath.rect(1,global_phase_angle)
# separate all the elements
a = input_array[0][0]
b = input_array[0][1]
a_star = input_array[1][1]
b_star = input_array[1][0]

global_phase_angle=np.pi+global_phase_angle
global_phase_angle_string = global_phase_angle/np.pi
# find alpha, beta and theta
alpha = cmath.phase(-a_star) + cmath.phase(b_star)
alpha_string = alpha/np.pi # for string in circuit diagram
beta = cmath.phase(-a_star) - cmath.phase(b_star)
beta_string = beta/np.pi
theta = 2*np.arctan2(np.absolute(b_star), np.absolute(a))
theta_string = theta/np.pi
raise GateError("This method is valid for single qubit gates only. Provide a target qubit for single qubit gate.")


Phase_gate = Gate("GLOBALPHASE",targets=[0], arg_value=global_phase_angle, arg_label=r'{:0.2f} \times \pi'.format(global_phase_angle_string))
Rz_A = Gate("RZ",targets=[target], arg_value=alpha, arg_label=r'{:0.2f} \times \pi'.format(alpha_string))
Ry_A = Gate("RY",targets=[target], arg_value=theta/2, arg_label=r'{:0.2f} \times \pi'.format(theta_string/2))
Pauli_X = Gate("X",targets=[target])
Ry_B = Gate("RY",targets=[target], arg_value=-theta/2, arg_label=r'{:0.2f} \times \pi'.format(-theta_string/2))
Rz_B = Gate("RZ",targets=[target], arg_value=-(alpha+beta)/2, arg_label=r'{:0.2f} \times \pi'.format(-(alpha_string+beta_string)/2))
Rz_C = Gate("RZ",targets=[target], arg_value=(-alpha+beta)/2, arg_label=r'{:0.2f} \times \pi'.format((-alpha_string+beta_string)/2))
key = _rotation_pauli_matrices_dictionary.keys()
if str(method) in key:
method = _rotation_pauli_matrices_dictionary[str(method)]
return(method(input_gate, target, 1))

return(Rz_A, Ry_A, Pauli_X, Ry_B, Rz_B, Pauli_X, Rz_C, Phase_gate)
else:
raise MethodError("Invalid method chosen.")
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,64 @@
import cmath
import pytest

from qutip import Qobj, average_gate_fidelity
from qutip import Qobj, average_gate_fidelity, rand_unitary, sigmax, sigmay, sigmaz

from qutip_qip.decompose.decompose_single_qubit_gate import (_ZYZ_rotation, _ZXZ_rotation,
ABC_decomposition, decompose_to_rotation_matrices)
from qutip_qip.decompose.single_qubit_gate import (_ZYZ_rotation, _ZXZ_rotation,
ABC_decomposition, _ZYZ_pauli_X, decompose_to_rotation_matrices)

from qutip_qip.circuit import (decomposed_gates_to_circuit, compute_unitary)
from qutip_qip.operations.gates import snot, sqrtnot

# Fidelity closer to 1 means the two states are similar to each other
H = Qobj([[1/np.sqrt(2),1/np.sqrt(2)],[1/np.sqrt(2),-1/np.sqrt(2)]])
sigmax = Qobj([[0,1],[1,0]])
sigmay = Qobj([[0,-1j],[1j,0]])
sigmaz = Qobj([[1,0],[0,-1]])
SQRTNOT = Qobj([[1/np.sqrt(2),-1j/np.sqrt(2)],[-1j/np.sqrt(2),1/np.sqrt(2)]])
H = snot(1,0)
sigmax = sigmax()
sigmay = sigmay()
sigmaz = sigmaz()
SQRTNOT = sqrtnot(N=1, target=0)
T = Qobj([[1,0],[0,cmath.rect(1, np.pi/4)]])
S = Qobj([[1,0],[0,1j]])
target = 0
num_qubits = 1

@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T])
@pytest.mark.parametrize("method",[_ZYZ_rotation, _ZXZ_rotation, ABC_decomposition])
# Tests for private functions
@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T, rand_unitary(2)])
@pytest.mark.parametrize("method",[_ZYZ_rotation, _ZXZ_rotation, _ZYZ_pauli_X])
def test_single_qubit_to_rotations(gate, method):
"""Initial matrix and product of final decompositions are same within some phase."""
target = 0
gate_list = method(gate,1,target)
decomposed_gates_circuit = decomposed_gates_to_circuit(gate_list,1)
gate_list = method(gate,target,num_qubits)
decomposed_gates_circuit = decomposed_gates_to_circuit(gate_list,num_qubits)
decomposed_gates_final_matrix = compute_unitary(decomposed_gates_circuit)
fidelity_of_input_output = average_gate_fidelity(gate, decomposed_gates_final_matrix)
assert(np.isclose(fidelity_of_input_output,1.0))

@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T])
@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T,rand_unitary(2)])
@pytest.mark.parametrize("method",['ZXZ','ZYZ'])
def test_check_single_qubit_to_decompose_to_rotations(gate, method):
"""Initial matrix and product of final decompositions are same within some phase."""
target = 0
gate_list = decompose_to_rotation_matrices(gate,1,target,method)
decomposed_gates_circuit = decomposed_gates_to_circuit(gate_list,1)
gate_list = decompose_to_rotation_matrices(gate,method,target,num_qubits)
decomposed_gates_circuit = decomposed_gates_to_circuit(gate_list,num_qubits)
decomposed_gates_final_matrix = compute_unitary(decomposed_gates_circuit)
fidelity_of_input_output = average_gate_fidelity(gate, decomposed_gates_final_matrix)
assert(np.isclose(fidelity_of_input_output,1.0))

@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T])
@pytest.mark.parametrize("method",[_ZYZ_rotation, _ZXZ_rotation, ABC_decomposition])
@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T, rand_unitary(2)])
@pytest.mark.parametrize("method",[_ZYZ_rotation, _ZXZ_rotation, _ZYZ_pauli_X])
def test_output_is_tuple(gate, method):
"""Initial matrix and product of final decompositions are same within some phase."""
target = 0
gate_list = method(gate,1,target)
gate_list = method(gate,target,num_qubits)
assert(isinstance(gate_list, tuple))

@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T])
# Tests for public functions
@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T, rand_unitary(2)])
@pytest.mark.parametrize("method",['ZXZ','ZYZ'])
def test_check_single_qubit_to_decompose_to_rotations(gate, method):
"""Initial matrix and product of final decompositions are same within some phase."""
target = 0
gate_list = decompose_to_rotation_matrices(gate,method, 1,target)
gate_list = decompose_to_rotation_matrices(gate,method,num_qubits,target)
assert(isinstance(gate_list, tuple))

@pytest.mark.xfail
def test_sigmay_decomposition():
"""Output matrix of sigmay is off by a global phase of -1.
"""
target = 0
gate_list =_ZYZ_rotation(sigmay,1,target)
decomposed_gates_circuit = decomposed_gates_to_circuit(gate_list,1)
decomposed_gates_final_matrix = compute_unitary(decomposed_gates_circuit)
assert(decomposed_gates_final_matrix == sigmay)
@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T, rand_unitary(2)])
@pytest.mark.parametrize("method",['ZYZ_PauliX'])
def test_check_single_qubit_to_decompose_to_rotations(gate, method):
"""Initial matrix and product of final decompositions are same within some phase."""
gate_list = ABC_decomposition(gate,method, num_qubits,target)
assert(isinstance(gate_list, tuple))
Loading

0 comments on commit 9daf3e4

Please sign in to comment.