diff --git a/doc/source/_apidoc/qutip_qip.decompose.rst b/doc/source/_apidoc/qutip_qip.decompose.rst index 1d0f4e18..8b59f0d7 100644 --- a/doc/source/_apidoc/qutip_qip.decompose.rst +++ b/doc/source/_apidoc/qutip_qip.decompose.rst @@ -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: diff --git a/src/qutip_qip/decompose/__init__.py b/src/qutip_qip/decompose/__init__.py index fa64a133..59068792 100644 --- a/src/qutip_qip/decompose/__init__.py +++ b/src/qutip_qip/decompose/__init__.py @@ -1 +1 @@ -from qutip_qip.decompose.decompose_single_qubit_gate import (ABC_decomposition, decompose_to_rotation_matrices) +from .single_qubit_gate import * diff --git a/src/qutip_qip/decompose/_utility.py b/src/qutip_qip/decompose/_utility.py index e8b6e42f..4b6b5175 100644 --- a/src/qutip_qip/decompose/_utility.py +++ b/src/qutip_qip/decompose/_utility.py @@ -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 diff --git a/src/qutip_qip/decompose/decompose_single_qubit_gate.py b/src/qutip_qip/decompose/single_qubit_gate.py similarity index 64% rename from src/qutip_qip/decompose/decompose_single_qubit_gate.py rename to src/qutip_qip/decompose/single_qubit_gate.py index ecd17b26..c89f4383 100644 --- a/src/qutip_qip/decompose/decompose_single_qubit_gate.py +++ b/src/qutip_qip/decompose/single_qubit_gate.py @@ -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 @@ -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`. @@ -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] @@ -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`. @@ -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] @@ -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:: @@ -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 @@ -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 @@ -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 @@ -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.") diff --git a/tests/gate_decompose/test_single_decompositions.py b/tests/decompose/test_single_decompositions.py similarity index 61% rename from tests/gate_decompose/test_single_decompositions.py rename to tests/decompose/test_single_decompositions.py index 51859ced..1a430c31 100644 --- a/tests/gate_decompose/test_single_decompositions.py +++ b/tests/decompose/test_single_decompositions.py @@ -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)) diff --git a/tests/gate_decompose/test_utility.py b/tests/decompose/test_utility.py similarity index 78% rename from tests/gate_decompose/test_utility.py rename to tests/decompose/test_utility.py index ee67af71..cd7c29db 100644 --- a/tests/gate_decompose/test_utility.py +++ b/tests/decompose/test_utility.py @@ -3,7 +3,7 @@ import pytest from qutip import Qobj, qeye -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.operations import rx, z_gate, t_gate from qutip_qip.circuit import QubitCircuit, Gate @@ -35,11 +35,3 @@ def test_check_gate_unitary_input(unitary): """ # No error raised if it passes. check_gate(unitary, num_qubits=1) - - -def test_extract_global_phase_valid_input(): - """Checks if global phase is correctly identified for multiplication. - """ - H = Qobj([[1/np.sqrt(2),1/np.sqrt(2)],[1/np.sqrt(2),-1/np.sqrt(2)]]) - H_global_phase = extract_global_phase(H,1) - assert(H_global_phase == np.pi/2) diff --git a/tests/test_circuit.py b/tests/test_circuit.py index f0afd731..6aea0d79 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -48,7 +48,7 @@ _para_gates ) -from qutip_qip.decompose.decompose_single_qubit_gate import _ZYZ_rotation +from qutip_qip.decompose.single_qubit_gate import _ZYZ_rotation import qutip as qp @@ -687,8 +687,8 @@ def test_decomposed_gates_to_circuit(invalid_input): H = Qobj([[1/np.sqrt(2),1/np.sqrt(2)],[1/np.sqrt(2),-1/np.sqrt(2)]]) sigmax = Qobj([[0,1],[1,0]]) -H_zyz_gates = _ZYZ_rotation(H, 1, 0) -sigmax_zyz_gates = _ZYZ_rotation(sigmax, 1, 0) +H_zyz_gates = _ZYZ_rotation(H, 0, 1) +sigmax_zyz_gates = _ZYZ_rotation(sigmax, 0, 1) @pytest.mark.parametrize("valid_input",[H_zyz_gates,sigmax_zyz_gates]) def test_decomposed_gates_to_circuit(valid_input):