diff --git a/src/qutip_qip/_decomposition_functions/__init__.py b/src/qutip_qip/_decomposition_functions/__init__.py deleted file mode 100644 index b30af1b9..00000000 --- a/src/qutip_qip/_decomposition_functions/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._single_qubit_gate import * diff --git a/src/qutip_qip/_decomposition_functions/_single_qubit_gate.py b/src/qutip_qip/_decomposition_functions/_single_qubit_gate.py deleted file mode 100644 index 5ebff508..00000000 --- a/src/qutip_qip/_decomposition_functions/_single_qubit_gate.py +++ /dev/null @@ -1,198 +0,0 @@ -import numpy as np -import cmath - -from qutip import Qobj -from qutip_qip._decomposition_functions._utility import ( - check_gate, - MethodError, - GateError, -) - -from qutip_qip.circuit import QubitCircuit, Gate - - -# Functions for decompose_to_rotation_matrices -def _angles_for_ZYZ(input_gate, num_qubits=1): - """Finds and returns the angles for ZYZ rotation matrix. These are - used to change ZYZ to other combinations. - - Parameters - ---------- - input_gate : :class:`qutip.Qobj` - The gate matrix that's supposed to be decomposed should be a Qobj. - """ - check_gate(input_gate, num_qubits) - input_array = input_gate.full() - 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_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, 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`. - - Parameters - ---------- - input_gate : :class:`qutip.Qobj` - The matrix that's supposed to be decomposed should be a Qobj. - """ - 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=[target], - arg_value=global_phase_angle, - arg_label=r"{:0.2f} \times \pi".format(global_phase_angle_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, 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`. - - Parameters - ---------- - input_gate : :class:`qutip.Qobj` - The matrix that's supposed to be decomposed should be a Qobj. - """ - angle_list = _angles_for_ZYZ(input_gate, num_qubits) - alpha = angle_list[0] - alpha = alpha - np.pi / 2 - beta = angle_list[2] - beta = beta + np.pi / 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=[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), - ) - Rx_theta = Gate( - "RX", - 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), - ) - - return (Rz_alpha, Rx_theta, Rz_beta, Phase_gate) - - -# 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) diff --git a/src/qutip_qip/circuit.py b/src/qutip_qip/circuit.py index 0a6c4e04..248cb4ee 100644 --- a/src/qutip_qip/circuit.py +++ b/src/qutip_qip/circuit.py @@ -2131,29 +2131,32 @@ def _apply_measurement(self, operation): raise NotImplementedError( "mode {} is not available.".format(self.mode)) + # For Decomposition functions -def decomposed_gates_to_circuit(decomposed_gate,num_qubits): +def decomposed_gates_to_circuit(decomposed_gate, num_qubits): """This function takes the input from a decomposition function and returns a quantum circuit. + + Parameters + ---------- + decomposed_gate : tuple + The output from some decomposition function in a tuple form. """ - # as decomposed_gate contains information about targets/control, there's no - # additional input of target here. - # In addition, there's no check if the gates are valid for number of qubits - # because this is done in a decomposition function before output. - if isinstance(decomposed_gate,tuple) == True: - q_circuit = QubitCircuit(num_qubits, reverse_states=False) - for i in decomposed_gate: - q_circuit.add_gate(i) - return(q_circuit) - else: + # there's no check if the gates are valid for number of qubits + # because this is done in a decomposition function before output + if not isinstance(decomposed_gate, tuple): raise TypeError("Input is not a tuple of gates.") + q_circuit = QubitCircuit(num_qubits, reverse_states=False) + for i in decomposed_gate: + q_circuit.add_gate(i) + return(q_circuit) + def compute_unitary(quantum_circuit): - """Evaluates all the gates in the quantum circuit. + """Evaluates all the gates in the quantum circuit of decomposed gates. """ - if isinstance(quantum_circuit, QubitCircuit) == True: - gate_list = quantum_circuit.propagators() - matrix_of_all_gates = gate_sequence_product(gate_list) - return(matrix_of_all_gates) - else: + if not isinstance(quantum_circuit, QubitCircuit): raise TypeError("Input is not of type QubitCircuit.") + gate_list = quantum_circuit.propagators() + matrix_of_all_gates = gate_sequence_product(gate_list) + return(matrix_of_all_gates) diff --git a/src/qutip_qip/decompose.py b/src/qutip_qip/decompose.py deleted file mode 100644 index 44b98352..00000000 --- a/src/qutip_qip/decompose.py +++ /dev/null @@ -1,115 +0,0 @@ -import numpy as np -import cmath - -from qutip import Qobj -from qutip_qip._decomposition_functions._utility import ( - check_gate, - MethodError, - GateError, -) - -from qutip_qip.circuit import QubitCircuit, Gate - -from qutip_qip._decomposition_functions._single_qubit_gate import ( - _ZYZ_rotation, - _ZXZ_rotation, - _ZYZ_pauli_X, -) - -_single_decompositions_dictionary = { - "ZYZ": _ZYZ_rotation, - "ZXZ": _ZXZ_rotation, - "ZYZ_PauliX": _ZYZ_pauli_X, -} # other combinations to add here - - -def decompose_one_qubit_gate(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` or as a product of rotation matrices - :math:`\textrm{R}_i` and :math:`\textrm{R}_j` and a Pauli :math:`\sigma_k`. - - Here, :math:`i \neq j` and :math:`i, j, k \in {x, y, z}`. - - Based on Lemma 4.1 and Lemma 4.3 of https://arxiv.org/abs/quant-ph/9503016v1 respectively. - - .. math:: - - U = \begin{bmatrix} - a & b \\ - -b^* & a^* \\ - \end{bmatrix} = \textrm{R}_i(\alpha) \textrm{R}_j(\theta) \textrm{R}_i(\beta) = \textrm{A} \sigma_k \textrm{B} \sigma_k \textrm{C} - - Here, - - * :math:`\textrm{A} = \textrm{R}_i(\alpha) \textrm{R}_j \left(\frac{\theta}{2} \right)` - - * :math:`\textrm{B} = \textrm{R}_j \left(\frac{-\theta}{2} \right) \textrm{R}_i \left(\frac{- \left(\alpha + \beta \right)}{2} \right)` - - * :math:`\textrm{C} = \textrm{R}_i \left(\frac{\left(-\alpha + \beta \right)}{2} \right)` - - - Parameters - ---------- - input_gate : :class:`qutip.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 - * - ZYZ - - :math:`\textrm{R}_z(\alpha) \textrm{R}_y(\theta) \textrm{R}_z(\beta)` - * - ZXZ - - :math:`\textrm{R}_z(\alpha) \textrm{R}_x(\theta) \textrm{R}_z(\beta)` - * - ZYZ_PauliX - - :math:`\textrm{A} \sigma_k \textrm{B} \sigma_k \textrm{C}` :math:`\forall k =x, i =z, j=y` - - - .. note:: - This function is under construction. As more combinations are - added, above table will be updated with their respective keys. - - - Returns - ------- - tuple - The gates in the decomposition are returned as a tuple of :class:`Gate` - objects. - - When the input gate is decomposed to product of rotation matrices - tuple - will contain 4 elements per each :math:`1 \times 1` - qubit gate - :math:`\textrm{R}_i(\alpha)`, :math:`\textrm{R}_j(\theta)`, - :math:`\textrm{R}_i(\beta)`, and some global phase gate. - - When the input gate is decomposed to product of rotation matrices and Pauli - - tuple will contain 6 elements per each :math:`1 \times 1` - qubit gate - 2 gates forming :math:`\textrm{A}`, 2 gates forming :math:`\textrm{B}`, - 1 gates forming :math:`\textrm{C}`, and some global phase gate. - """ - try: - 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 single qubit gate." - ) - - key = _single_decompositions_dictionary.keys() - if str(method) in key: - method = _single_decompositions_dictionary[str(method)] - return method(input_gate, target, 1) - - else: - raise MethodError("Invalid method chosen.") diff --git a/src/qutip_qip/decompose/__init__.py b/src/qutip_qip/decompose/__init__.py new file mode 100644 index 00000000..c32a39e2 --- /dev/null +++ b/src/qutip_qip/decompose/__init__.py @@ -0,0 +1 @@ +from .decompose_single_qubit_gate import * diff --git a/src/qutip_qip/_decomposition_functions/_utility.py b/src/qutip_qip/decompose/_utility.py similarity index 79% rename from src/qutip_qip/_decomposition_functions/_utility.py rename to src/qutip_qip/decompose/_utility.py index 8053f689..99ca8f90 100644 --- a/src/qutip_qip/_decomposition_functions/_utility.py +++ b/src/qutip_qip/decompose/_utility.py @@ -1,6 +1,3 @@ -import numpy as np -import cmath - from qutip import Qobj @@ -10,14 +7,6 @@ class MethodError(Exception): pass -class GateError(Exception): - """When chosen method cannot be applied to the input gate, this error - is raised. - """ - - pass - - def check_gate(gate, num_qubits): """Verifies input is a valid quantum gate. @@ -26,7 +15,7 @@ def check_gate(gate, num_qubits): gate : :class:`qutip.Qobj` The matrix that's supposed to be decomposed should be a Qobj. num_qubits: - Number of qubits in the circuit. + Total number of qubits in the circuit. Raises ------ TypeError diff --git a/src/qutip_qip/decompose/decompose_single_qubit_gate.py b/src/qutip_qip/decompose/decompose_single_qubit_gate.py new file mode 100644 index 00000000..81387000 --- /dev/null +++ b/src/qutip_qip/decompose/decompose_single_qubit_gate.py @@ -0,0 +1,256 @@ +import numpy as np +import cmath + + +from qutip_qip.decompose._utility import ( + check_gate, + MethodError, +) + +from qutip_qip.circuit import Gate + + +def _angles_for_ZYZ(input_gate): + """Finds and returns the angles for ZYZ rotation matrix. These are + used to change ZYZ to other combinations. + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The gate matrix that's supposed to be decomposed should be a Qobj. + """ + input_array = input_gate.full() + 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_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): + r"""An input 1-qubit gate is expressed as a product of rotation matrices + :math:`\textrm{R}_z` and :math:`\textrm{R}_y`. + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The matrix that's supposed to be decomposed should be a Qobj. + """ + check_gate(input_gate, num_qubits=1) + alpha, theta, beta, global_phase_angle = _angles_for_ZYZ(input_gate) + + Phase_gate = Gate( + "GLOBALPHASE", + targets=[0], + arg_value=global_phase_angle, + arg_label=r"{:0.2f} \times \pi".format(global_phase_angle / np.pi), + ) + Rz_beta = Gate( + "RZ", + targets=[0], + arg_value=beta, + arg_label=r"{:0.2f} \times \pi".format(beta / np.pi), + ) + Ry_theta = Gate( + "RY", + targets=[0], + arg_value=theta, + arg_label=r"{:0.2f} \times \pi".format(theta / np.pi), + ) + Rz_alpha = Gate( + "RZ", + targets=[0], + arg_value=alpha, + arg_label=r"{:0.2f} \times \pi".format(alpha / np.pi), + ) + + return (Rz_alpha, Ry_theta, Rz_beta, Phase_gate) + + +def ZXZ_rotation(input_gate): + r"""An input 1-qubit gate is expressed as a product of rotation matrices + :math:`\textrm{R}_z` and :math:`\textrm{R}_x`. + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The matrix that's supposed to be decomposed should be a Qobj. + """ + check_gate(input_gate, num_qubits=1) + alpha, theta, beta, global_phase_angle = _angles_for_ZYZ(input_gate) + alpha = alpha - np.pi / 2 + beta = beta + np.pi / 2 + # theta and global phase are same as ZYZ values + + Phase_gate = Gate( + "GLOBALPHASE", + targets=[0], + arg_value=global_phase_angle, + arg_label=r"{:0.2f} \times \pi".format(global_phase_angle / np.pi), + ) + Rz_alpha = Gate( + "RZ", + targets=[0], + arg_value=alpha, + arg_label=r"{:0.2f} \times \pi".format(alpha / np.pi), + ) + Rx_theta = Gate( + "RX", + targets=[0], + arg_value=theta, + arg_label=r"{:0.2f} \times \pi".format(theta / np.pi), + ) + Rz_beta = Gate( + "RZ", + targets=[0], + arg_value=beta, + arg_label=r"{:0.2f} \times \pi".format(beta / np.pi), + ) + + return (Rz_alpha, Rx_theta, Rz_beta, Phase_gate) + + +# Functions for ABC_decomposition + + +def ZYZ_pauli_X(input_gate): + """Returns a 1 qubit unitary as a product of ZYZ rotation matrices and + Pauli X.""" + check_gate(input_gate, num_qubits=1) + alpha, theta, beta, global_phase_angle = _angles_for_ZYZ(input_gate) + + Phase_gate = Gate( + "GLOBALPHASE", + targets=[0], + arg_value=global_phase_angle, + arg_label=r"{:0.2f} \times \pi".format(global_phase_angle / np.pi), + ) + Rz_A = Gate( + "RZ", + targets=[0], + arg_value=alpha, + arg_label=r"{:0.2f} \times \pi".format(alpha / np.pi), + ) + Ry_A = Gate( + "RY", + targets=[0], + arg_value=theta / 2, + arg_label=r"{:0.2f} \times \pi".format(theta / np.pi), + ) + Pauli_X = Gate("X", targets=[0]) + Ry_B = Gate( + "RY", + targets=[0], + arg_value=-theta / 2, + arg_label=r"{:0.2f} \times \pi".format(-theta / np.pi), + ) + Rz_B = Gate( + "RZ", + targets=[0], + arg_value=-(alpha + beta) / 2, + arg_label=r"{:0.2f} \times \pi".format(-(alpha + beta) / (2*np.pi)), + ) + Rz_C = Gate( + "RZ", + targets=[0], + arg_value=(-alpha + beta) / 2, + arg_label=r"{:0.2f} \times \pi".format((-alpha + beta) / (2*np.pi)), + ) + + return (Rz_A, Ry_A, Pauli_X, Ry_B, Rz_B, Pauli_X, Rz_C, Phase_gate) + + +_single_decompositions_dictionary = { + "ZYZ": ZYZ_rotation, + "ZXZ": ZXZ_rotation, + "ZYZ_PauliX": ZYZ_pauli_X, +} # other combinations to add here + + +def decompose_one_qubit_gate(input_gate, method): + r""" An input 1-qubit gate is expressed as a product of rotation matrices + :math:`\textrm{R}_i` and :math:`\textrm{R}_j` or as a product of rotation + matrices :math:`\textrm{R}_i` and :math:`\textrm{R}_j` and a + Pauli :math:`\sigma_k`. + + Here, :math:`i \neq j` and :math:`i, j, k \in {x, y, z}`. + + Based on Lemma 4.1 and Lemma 4.3 of + https://arxiv.org/abs/quant-ph/9503016v1 respectively. + + .. math:: + + U = \begin{bmatrix} + a & b \\ + -b^* & a^* \\ + \end{bmatrix} = \textrm{R}_i(\alpha) \textrm{R}_j(\theta) \textrm{R}_i(\beta) = \textrm{A} \sigma_k \textrm{B} \sigma_k \textrm{C} + + Here, + + * :math:`\textrm{A} = \textrm{R}_i(\alpha) \textrm{R}_j\left(\frac{\theta}{2} \right)` + + * :math:`\textrm{B} = \textrm{R}_j \left(\frac{-\theta}{2} \right)\textrm{R}_i \left(\frac{- \left(\alpha + \beta \right)}{2} \right)` + + * :math:`\textrm{C} = \textrm{R}_i \left(\frac{\left(-\alpha + \beta\right)}{2} \right)` + + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The matrix that's supposed to be decomposed should be a Qobj. + + + method : string + Name of the preferred decomposition method + + .. list-table:: + :widths: auto + :header-rows: 1 + + * - Method Key + - Method + * - ZYZ + - :math:`\textrm{R}_z(\alpha) \textrm{R}_y(\theta) \textrm{R}_z(\beta)` + * - ZXZ + - :math:`\textrm{R}_z(\alpha) \textrm{R}_x(\theta) \textrm{R}_z(\beta)` + * - ZYZ_PauliX + - :math:`\textrm{A} \sigma_k \textrm{B} \sigma_k \textrm{C}` :math:`\forall k =x, i =z, j=y` + + + .. note:: + This function is under construction. As more combinations are + added, above table will be updated with their respective keys. + + + Returns + ------- + tuple + The gates in the decomposition are returned as a tuple of :class:`Gate` + objects. + + When the input gate is decomposed to product of rotation matrices - + tuple will contain 4 elements per each :math:`1 \times 1` + qubit gate - :math:`\textrm{R}_i(\alpha)`, :math:`\textrm{R}_j(\theta)` + , :math:`\textrm{R}_i(\beta)`, and some global phase gate. + + When the input gate is decomposed to product of rotation matrices and + Pauli - tuple will contain 6 elements per each :math:`1 \times 1` + qubit gate - 2 gates forming :math:`\textrm{A}`, 2 gates forming + :math:`\textrm{B}`, 1 gates forming :math:`\textrm{C}`, and some global + phase gate. + """ + check_gate(input_gate, num_qubits=1) + f = _single_decompositions_dictionary.get(method, None) + if f is None: + raise MethodError(f"Invalid decomposition method: {method!r}") + return f(input_gate) diff --git a/tests/decomposition_functions/test_single_qubit_gate_decompositions.py b/tests/decomposition_functions/test_single_qubit_gate_decompositions.py index 2583cf3b..b74e9b3b 100644 --- a/tests/decomposition_functions/test_single_qubit_gate_decompositions.py +++ b/tests/decomposition_functions/test_single_qubit_gate_decompositions.py @@ -2,12 +2,13 @@ import cmath import pytest -from qutip import Qobj, average_gate_fidelity, rand_unitary, sigmax, sigmay, sigmaz - -from qutip_qip._decomposition_functions._single_qubit_gate import ( - _ZYZ_rotation, - _ZXZ_rotation, - _ZYZ_pauli_X, +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, + ZYZ_pauli_X, ) from qutip_qip.decompose import decompose_one_qubit_gate from qutip_qip.circuit import decomposed_gates_to_circuit, compute_unitary @@ -24,15 +25,21 @@ target = 0 num_qubits = 1 + # 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]) +@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.""" - gate_list = method(gate, target, num_qubits) - decomposed_gates_circuit = decomposed_gates_to_circuit(gate_list, num_qubits) + """Initial matrix and product of final decompositions are same within some + phase.""" + gate_list = method(gate) + 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 @@ -45,9 +52,11 @@ def test_single_qubit_to_rotations(gate, method): ) @pytest.mark.parametrize("method", ["ZXZ", "ZYZ", "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 = decompose_one_qubit_gate(gate, method, target, num_qubits) - decomposed_gates_circuit = decomposed_gates_to_circuit(gate_list, num_qubits) + """Initial matrix and product of final decompositions are same within some + phase.""" + gate_list = decompose_one_qubit_gate(gate, method) + 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 @@ -58,10 +67,13 @@ def test_check_single_qubit_to_decompose_to_rotations(gate, method): @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]) +@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.""" - gate_list = method(gate, target, num_qubits) + """Initial matrix and product of final decompositions are same within some + phase.""" + gate_list = method(gate) assert isinstance(gate_list, tuple) @@ -70,7 +82,8 @@ def test_output_is_tuple(gate, method): "gate", [H, sigmax, sigmay, sigmaz, SQRTNOT, S, T, rand_unitary(2)] ) @pytest.mark.parametrize("method", ["ZXZ", "ZYZ", "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 = decompose_one_qubit_gate(gate, method, num_qubits, target) +def test_check_single_qubit_to_decompose_to_rotations_tuple(gate, method): + """Initial matrix and product of final decompositions are same within some + phase.""" + gate_list = decompose_one_qubit_gate(gate, method) assert isinstance(gate_list, tuple) diff --git a/tests/decomposition_functions/test_utility.py b/tests/decomposition_functions/test_utility.py index b2a85790..b4e97433 100644 --- a/tests/decomposition_functions/test_utility.py +++ b/tests/decomposition_functions/test_utility.py @@ -1,15 +1,11 @@ import numpy as np -import cmath import pytest from qutip import Qobj, qeye -from qutip_qip._decomposition_functions._utility import ( +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 + # Tests for check_gate @pytest.mark.parametrize( @@ -26,7 +22,8 @@ ], ) def test_check_gate_non_qobj(invalid_input): - """Checks if correct value is returned or not when the input is not a Qobj.""" + """Checks if correct value is returned or not when the input is not a Qobj + .""" with pytest.raises(TypeError, match="The input matrix is not a Qobj."): check_gate(invalid_input, num_qubits=1) @@ -38,11 +35,12 @@ def test_check_gate_non_unitary(non_unitary): check_gate(non_unitary, num_qubits=1) -@pytest.mark.parametrize("non_qubit_unitary", [qeye(4)]) -def test_check_gate_non_unitary(non_qubit_unitary): +@pytest.mark.parametrize("non_1qubit_unitary", [qeye(4)]) +def test_check_gate_non_1qubit(non_1qubit_unitary): """Checks if non-unitary input is correctly identified.""" - with pytest.raises(ValueError, match="Input is not a unitary on 2 qubits."): - check_gate(non_qubit_unitary, num_qubits=2) + num_qubits = 1 + with pytest.raises(ValueError, match=f"Input is not a unitary on {num_qubits} qubits."): + check_gate(non_1qubit_unitary, num_qubits) @pytest.mark.parametrize("unitary", [Qobj([[1, 0], [0, -1]])]) diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 278b119f..c2cbfd38 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -48,7 +48,7 @@ _para_gates ) -from qutip_qip._decomposition_functions._single_qubit_gate import _ZYZ_rotation +from qutip_qip.decompose.decompose_single_qubit_gate import ZYZ_rotation import qutip as qp @@ -677,41 +677,52 @@ def test_latex_code_teleportation_circuit(self): "", ]) + # Tests for decomposed_gates_to_circuit -@pytest.mark.parametrize("invalid_input",[np.array([[1,1],[1,1]]),([[1,1],[1,1]]),1.5,3]) +@pytest.mark.parametrize("invalid_input", [ + np.array([[1, 1], [1, 1]]), ([[1, 1], [1, 1]]), 1.5, 3]) def test_decomposed_gates_to_circuit(invalid_input): - """Checks if correct error is raised when input is anything but a tuple of gates. + """Checks if correct error is raised when input is anything but a tuple of + gates. """ - with pytest.raises(TypeError,match="Input is not a tuple of gates."): - decomposed_gates_to_circuit(invalid_input,1) + with pytest.raises(TypeError, match="Input is not a tuple of gates."): + decomposed_gates_to_circuit(invalid_input, 1) + + +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) +sigmax_zyz_gates = ZYZ_rotation(sigmax) -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, 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): +@pytest.mark.parametrize("valid_input", [H_zyz_gates, sigmax_zyz_gates]) +def test_decomposed_gates_to_circuit_input_valid(valid_input): """Checks if output is of type QubitCircuit. """ - assert(isinstance(decomposed_gates_to_circuit(valid_input,1),QubitCircuit)) + assert(isinstance(decomposed_gates_to_circuit( + valid_input, 1), QubitCircuit)) H_zyz_quantum_circuit = decomposed_gates_to_circuit(H_zyz_gates, 1) sigmax_zyz_quantum_circuit = decomposed_gates_to_circuit(sigmax_zyz_gates, 1) sigmax_zyz_output = (sigmax_zyz_quantum_circuit) # Tests for matrix_of_decomposed_gates -@pytest.mark.parametrize("invalid_input",[np.array([[1,1],[1,1]]),([[1,1],[1,1]]),1.5,3]) + + +@pytest.mark.parametrize("invalid_input", [ + np.array([[1, 1], [1, 1]]), ([[1, 1], [1, 1]]), 1.5, 3]) def test_matrix_of_decomposed_gates(invalid_input): - """Checks if correct error is raised when input is anything but a quantum circuit. + """Checks if correct error is raised when input is anything but a quantum + circuit. """ - with pytest.raises(TypeError,match="Input is not of type QubitCircuit."): + with pytest.raises(TypeError, match="Input is not of type QubitCircuit."): compute_unitary(invalid_input) -@pytest.mark.parametrize("valid_input",[H_zyz_quantum_circuit,sigmax_zyz_quantum_circuit]) -def test_matrix_of_decomposed_gates(valid_input): +@pytest.mark.parametrize("valid_input", [ + H_zyz_quantum_circuit, sigmax_zyz_quantum_circuit]) +def test_matrix_of_decomposed_gates_isqobj(valid_input): """Checks if final output is a Qobj. """ - final_output=compute_unitary(valid_input) + final_output = compute_unitary(valid_input) assert(isinstance(final_output, Qobj))