From 8628cf042ac9e2dc346d660bf74fefbb70504b61 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Sat, 26 Jun 2021 14:56:55 -0500 Subject: [PATCH 01/17] zyz and abc --- src/qutip_qip/decompositions/__init__.py | 4 + .../decompositions/circuit_diagram.py | 18 +++ .../decompositions/general_decompositions.py | 111 ++++++++++++++++++ .../decompositions/single_decompositions.py | 100 ++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 src/qutip_qip/decompositions/__init__.py create mode 100644 src/qutip_qip/decompositions/circuit_diagram.py create mode 100644 src/qutip_qip/decompositions/general_decompositions.py create mode 100644 src/qutip_qip/decompositions/single_decompositions.py diff --git a/src/qutip_qip/decompositions/__init__.py b/src/qutip_qip/decompositions/__init__.py new file mode 100644 index 000000000..df0061499 --- /dev/null +++ b/src/qutip_qip/decompositions/__init__.py @@ -0,0 +1,4 @@ +from qutip_qip.decompositions.general_decompositions import (check_input, +check_input_shape, convert_qobj_gate_to_array, extract_global_phase) + +from qutip_qip.decompositions.single_decompositions import (ZYZ_rotation, ABC_decomposition) diff --git a/src/qutip_qip/decompositions/circuit_diagram.py b/src/qutip_qip/decompositions/circuit_diagram.py new file mode 100644 index 000000000..ea0674540 --- /dev/null +++ b/src/qutip_qip/decompositions/circuit_diagram.py @@ -0,0 +1,18 @@ +from qutip_qip.decompositions.general_decompositions import (check_input, +check_input_shape, convert_qobj_gate_to_array) + +from qutip_qip.decompositions.single_qubit_decompositions import (ZYZ_rotation) +from qutip import * +from qutip_qip.operations import * +from qutip_qip.circuit import QubitCircuit, Gate +from numpy import pi +import numpy as np + +def decomposed_gate_to_circuit(decomposed_gate,num_of_qubits): + if isinstance(decomposed_gate,tuple) == True: + q_circuit = QubitCircuit(num_of_qubits, reverse_states=False) + for i in decomposed_gate: + q_circuit.add_gate(i) + return(q_circuit) + else: + raise TypeError("The list of gates is not a tuple.") diff --git a/src/qutip_qip/decompositions/general_decompositions.py b/src/qutip_qip/decompositions/general_decompositions.py new file mode 100644 index 000000000..14277f6f9 --- /dev/null +++ b/src/qutip_qip/decompositions/general_decompositions.py @@ -0,0 +1,111 @@ +import numpy as np +import cmath + +from qutip.qobj import Qobj + +def check_input(input_gate): + """Verifies input is a valid quantum gate. + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The matrix that's supposed to be decomposed should be a Qobj. + + Returns + ------- + bool + If the input is a valid quantum gate matrix, returns "True". + In the case of "False" being returned, this function will ensure no + decomposition scheme can proceed. + """ + # check if input is a qobj + qobj_check = isinstance(input_gate,Qobj) + if qobj_check == False: + raise TypeError("The input matrix is not a Qobj.") + else: + # check if input is square and a unitary + input_shape = input_gate.shape + # check if input is 1 row and 1 column matrix + if input_shape[0]==1: + raise ValueError("A 1-D Qobj is not a valid quantum gate.") + # check if the input is a rectangular matrix + if input_shape[0] != input_shape[1]: + raise ValueError("Input is not a square matrix.") + else: + unitary_check = Qobj.check_isunitary(input_gate) + return unitary_check + + +def check_input_shape(input_gate, num_of_qubits): + """Check if the shape of input gate is valid to act on input number of qubits + + If :math:`n` is the number of qubits in the circuit then a valid quantum gate + acting on these qubits must be of dimension :math:`2^n \\times 2^n`. + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The matrix that's supposed to be decomposed should be a Qobj. + + num_of_qubits : int + Number of qubits in the circuit. + + Returns + ------- + bool + Returns "True" if the shape of input gate is valid to act on the number + of qubits in the circuit. + + """ + input_check_bool = check_input(input_gate) + if input_check_bool == True: + input_shape = input_gate.shape + try: + assert input_shape[0] == 2**num_of_qubits + except AssertionError: + return(False) + return(True) + else: + raise ValueError("Input is not unitary.") + + +def convert_qobj_gate_to_array(input_gate): + """Converts a valid unitary quantum gate to a numpy array. + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The matrix that's supposed to be decomposed should be a Qobj. + + Returns + ------- + input_gate : `np.array` + The input is returned as a converted numpy array. + """ + input_check_bool = check_input(input_gate) + if input_check_bool == True: + input_to_array = Qobj.full(input_gate) + try: + isinstance(input_to_array, np.ndarray) + except: + raise ConversionError("Input Qobj could not be converted to a numpy array.") + return(input_to_array) + else: + raise ValueError("Input is not unitary.") + + +def extract_global_phase(input_gate, num_of_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 = convert_qobj_gate_to_array(input_gate) + determinant_of_input = np.linalg.det(input_array) + global_phase_angle = cmath.phase(determinant_of_input) + global_phase_angle = global_phase_angle/(2**num_of_qubits) + return(global_phase_angle) diff --git a/src/qutip_qip/decompositions/single_decompositions.py b/src/qutip_qip/decompositions/single_decompositions.py new file mode 100644 index 000000000..f0a483541 --- /dev/null +++ b/src/qutip_qip/decompositions/single_decompositions.py @@ -0,0 +1,100 @@ +import numpy as np +import cmath + +from qutip.qobj import Qobj +from .general_decompositions import (check_input, check_input_shape, +convert_qobj_gate_to_array, extract_global_phase) + +from qutip_qip.operations import * +from qutip_qip.circuit import QubitCircuit, Gate + +class MethodError(): + pass + +class GateError(): + pass + +def ZYZ_rotation(input_gate, num_of_qubits): + """ An input 1-qubit gate is expressed as a product of rotation matrices + :math:`R_z` and :math:`R_y`. + """ + try: + assert num_of_qubits == 1 + except AssertionError: + raise MethodError("This method is only valid for 1 qubit gates.") + if check_input_shape(input_gate,num_of_qubits)==True: + global_phase_angle = extract_global_phase(input_gate,1) + global_phase_angle_string = global_phase_angle/np.pi + + input_array = np.dot(phasegate(global_phase_angle),convert_qobj_gate_to_array(input_gate)) + + # 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] + + # find alpha, beta and theta + alpha = cmath.phase(a_star/a) + cmath.phase(-b_star/b) + alpha_string = alpha/np.pi + theta = 2*cmath.phase(complex(np.absolute(b),np.absolute(a))) + theta_string = theta/np.pi + beta = cmath.phase(a_star/a) - cmath.phase(-b_star/b) + beta_string = beta/np.pi + + # input array was changed with a global gate created from + # positive value of global phase + # now undo it with a negative value of same phase angle global gate. + Phase_gate = Gate("GLOBALPHASE",targets=[0], arg_value=-global_phase_angle, arg_label=r'{} \times \pi'.format(-global_phase_angle_string)) + Rz_alpha = Gate("RZ",targets=[0], arg_value=alpha, arg_label=r'{} \times \pi'.format(alpha_string)) + Ry_theta = Gate("RY",targets=[0], arg_value=theta, arg_label=r'{} \times \pi'.format(theta_string)) + Rz_beta = Gate("RZ",targets=[0], arg_value=beta, arg_label=r'{} \times \pi'.format(beta_string)) + + return(Rz_alpha, Ry_theta, Rz_beta, Phase_gate) + + else: + raise GateError("Shape of input does not match with the number of qubits.") + +def ABC_decomposition(input_gate, num_of_qubits): + """ An input 1-qubit gate is expressed as a product of rotation matrices + :math:`R_z` and :math:`R_y` and Pauli X. + """ + try: + assert num_of_qubits == 1 + except AssertionError: + raise MethodError("This method is only valid for 1 qubit gates.") + if check_input_shape(input_gate,num_of_qubits)==True: + global_phase_angle = extract_global_phase(input_gate,1) + global_phase_angle_string = global_phase_angle/np.pi + + input_array = np.dot(phasegate(global_phase_angle),convert_qobj_gate_to_array(input_gate)) + + # 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] + + # find alpha, beta and theta + alpha = cmath.phase(a_star/a) + cmath.phase(-b_star/b) + alpha_string = alpha/np.pi + theta = 2*cmath.phase(complex(np.absolute(b),np.absolute(a))) + theta_string = theta/np.pi + beta = cmath.phase(a_star/a) - cmath.phase(-b_star/b) + beta_string = beta/np.pi + + # input array was changed with a global gate created from + # positive value of global phase + # now undo it with a negative value of same phase angle global gate. + Phase_gate = Gate("GLOBALPHASE",targets=[0], arg_value=-global_phase_angle, arg_label=r'{} \times \pi'.format(-global_phase_angle_string)) + Rz_A = Gate("RZ",targets=[0], arg_value=alpha, arg_label=r'{} \times \pi'.format(alpha_string)) + Ry_A = Gate("RY",targets=[0], arg_value=theta/2, arg_label=r'{} \times \pi'.format(theta_string/2)) + Pauli_X = Gate("X",targets=[0]) + Ry_B = Gate("RY",targets=[0], arg_value=-theta/2, arg_label=r'{} \times \pi'.format(-theta_string/2)) + Rz_B = Gate("RZ",targets=[0], arg_value=-(alpha+beta)/2, arg_label=r'{} \times \pi'.format(-(alpha_string+beta_string)/2)) + Rz_C = Gate("RZ",targets=[0], arg_value=(-alpha+beta)/2, arg_label=r'{} \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) + + else: + raise GateError("Shape of input does not match with the number of qubits.") From 849a08f379f8dace87db812ee9bd73641fce7a2d Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Sat, 26 Jun 2021 20:58:25 -0500 Subject: [PATCH 02/17] global phase issue with j --- .../_apidoc/qutip_qip.decompositions.rst | 37 ++++++++++ doc/source/_apidoc/qutip_qip.rst | 9 +++ src/qutip_qip/decompositions/__init__.py | 5 +- .../decompositions/circuit_diagram.py | 18 ----- .../decompositions/decompositions_extras.py | 26 +++++++ .../decompositions/single_decompositions.py | 72 +++++++++++++++---- 6 files changed, 135 insertions(+), 32 deletions(-) create mode 100644 doc/source/_apidoc/qutip_qip.decompositions.rst delete mode 100644 src/qutip_qip/decompositions/circuit_diagram.py create mode 100644 src/qutip_qip/decompositions/decompositions_extras.py diff --git a/doc/source/_apidoc/qutip_qip.decompositions.rst b/doc/source/_apidoc/qutip_qip.decompositions.rst new file mode 100644 index 000000000..8860433ba --- /dev/null +++ b/doc/source/_apidoc/qutip_qip.decompositions.rst @@ -0,0 +1,37 @@ +qutip\_qip.decompositions package +================================= + +Submodules +---------- + +qutip\_qip.decompositions.decompositions\_extras module +------------------------------------------------------- + +.. automodule:: qutip_qip.decompositions.decompositions_extras + :members: + :undoc-members: + :show-inheritance: + +qutip\_qip.decompositions.general\_decompositions module +-------------------------------------------------------- + +.. automodule:: qutip_qip.decompositions.general_decompositions + :members: + :undoc-members: + :show-inheritance: + +qutip\_qip.decompositions.single\_decompositions module +------------------------------------------------------- + +.. automodule:: qutip_qip.decompositions.single_decompositions + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: qutip_qip.decompositions + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/_apidoc/qutip_qip.rst b/doc/source/_apidoc/qutip_qip.rst index a438e8c89..c1df378fa 100644 --- a/doc/source/_apidoc/qutip_qip.rst +++ b/doc/source/_apidoc/qutip_qip.rst @@ -11,6 +11,7 @@ Subpackages qutip_qip.compiler qutip_qip.device qutip_qip.operations + qutip_qip.decompositions qutip_qip.transpiler Submodules @@ -72,6 +73,14 @@ qutip\_qip.qubits module :undoc-members: :show-inheritance: +qutip\_qip.version module +------------------------- + +.. automodule:: qutip_qip.version + :members: + :undoc-members: + :show-inheritance: + Module contents --------------- diff --git a/src/qutip_qip/decompositions/__init__.py b/src/qutip_qip/decompositions/__init__.py index df0061499..b4049b00a 100644 --- a/src/qutip_qip/decompositions/__init__.py +++ b/src/qutip_qip/decompositions/__init__.py @@ -1,4 +1,7 @@ from qutip_qip.decompositions.general_decompositions import (check_input, check_input_shape, convert_qobj_gate_to_array, extract_global_phase) -from qutip_qip.decompositions.single_decompositions import (ZYZ_rotation, ABC_decomposition) +from qutip_qip.decompositions.single_decompositions import (ZYZ_rotation, + ABC_decomposition) + +from qutip_qip.decompositions.decompositions_extras import (decomposed_gates_to_circuit, matrix_of_decomposed_gates) diff --git a/src/qutip_qip/decompositions/circuit_diagram.py b/src/qutip_qip/decompositions/circuit_diagram.py deleted file mode 100644 index ea0674540..000000000 --- a/src/qutip_qip/decompositions/circuit_diagram.py +++ /dev/null @@ -1,18 +0,0 @@ -from qutip_qip.decompositions.general_decompositions import (check_input, -check_input_shape, convert_qobj_gate_to_array) - -from qutip_qip.decompositions.single_qubit_decompositions import (ZYZ_rotation) -from qutip import * -from qutip_qip.operations import * -from qutip_qip.circuit import QubitCircuit, Gate -from numpy import pi -import numpy as np - -def decomposed_gate_to_circuit(decomposed_gate,num_of_qubits): - if isinstance(decomposed_gate,tuple) == True: - q_circuit = QubitCircuit(num_of_qubits, reverse_states=False) - for i in decomposed_gate: - q_circuit.add_gate(i) - return(q_circuit) - else: - raise TypeError("The list of gates is not a tuple.") diff --git a/src/qutip_qip/decompositions/decompositions_extras.py b/src/qutip_qip/decompositions/decompositions_extras.py new file mode 100644 index 000000000..8bb43da61 --- /dev/null +++ b/src/qutip_qip/decompositions/decompositions_extras.py @@ -0,0 +1,26 @@ +from qutip_qip.decompositions.general_decompositions import (check_input, +check_input_shape, convert_qobj_gate_to_array) + +from qutip_qip.decompositions.single_decompositions import (ZYZ_rotation, ABC_decomposition) +from qutip import * +from qutip_qip.operations import * +from qutip_qip.circuit import QubitCircuit, Gate +from numpy import pi +import numpy as np + +def decomposed_gates_to_circuit(decomposed_gate,num_of_qubits): + if isinstance(decomposed_gate,tuple) == True: + q_circuit = QubitCircuit(num_of_qubits, reverse_states=False) + for i in decomposed_gate: + q_circuit.add_gate(i) + return(q_circuit) + else: + raise TypeError("Input is not a list of gates.") + +def matrix_of_decomposed_gates(quantum_circuit): + 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: + raise TypeError("Input is not of type QubitCircuit.") diff --git a/src/qutip_qip/decompositions/single_decompositions.py b/src/qutip_qip/decompositions/single_decompositions.py index f0a483541..94f171b6e 100644 --- a/src/qutip_qip/decompositions/single_decompositions.py +++ b/src/qutip_qip/decompositions/single_decompositions.py @@ -15,8 +15,28 @@ class GateError(): pass def ZYZ_rotation(input_gate, num_of_qubits): - """ An input 1-qubit gate is expressed as a product of rotation matrices - :math:`R_z` and :math:`R_y`. + r""" An input 1-qubit gate is expressed as a product of rotation matrices + :math:`\textrm{R}_z` and :math:`\textrm{R}_y`. + + .. math:: + + U = \begin{bmatrix} + a & b \\ + -b^* & a^* \\ + \end{bmatrix} = \textrm{R}_z(\alpha) \textrm{R}_y(\theta) \textrm{R}_z(\beta) + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The matrix that's supposed to be decomposed should be a Qobj. + + Returns + ------- + tuple + The gates in the decomposition are returned as a tuple of :class:`Gate` + objects. This tuple will contain 4 elements per each :math:`1 \times 1` + qubit gate - :math:`\textrm{R}_z(\alpha)`, :math:`\textrm{R}_y(\theta)`, + :math:`\textrm{R}_z(\beta)`, and some global phase gate. """ try: assert num_of_qubits == 1 @@ -35,20 +55,20 @@ def ZYZ_rotation(input_gate, num_of_qubits): b_star = input_array[1][0] # find alpha, beta and theta - alpha = cmath.phase(a_star/a) + cmath.phase(-b_star/b) + alpha = cmath.phase(-a_star) + cmath.phase(b_star) + beta = cmath.phase(-a_star) - cmath.phase(b_star) alpha_string = alpha/np.pi - theta = 2*cmath.phase(complex(np.absolute(b),np.absolute(a))) - theta_string = theta/np.pi - beta = cmath.phase(a_star/a) - cmath.phase(-b_star/b) beta_string = beta/np.pi + theta = 2*np.arctan2(np.absolute(b_star), np.absolute(a)) + theta_string = theta/np.pi + # input array was changed with a global gate created from # positive value of global phase - # now undo it with a negative value of same phase angle global gate. - Phase_gate = Gate("GLOBALPHASE",targets=[0], arg_value=-global_phase_angle, arg_label=r'{} \times \pi'.format(-global_phase_angle_string)) - Rz_alpha = Gate("RZ",targets=[0], arg_value=alpha, arg_label=r'{} \times \pi'.format(alpha_string)) - Ry_theta = Gate("RY",targets=[0], arg_value=theta, arg_label=r'{} \times \pi'.format(theta_string)) - Rz_beta = Gate("RZ",targets=[0], arg_value=beta, arg_label=r'{} \times \pi'.format(beta_string)) + Phase_gate = Gate("GLOBALPHASE",targets=[0], arg_value=global_phase_angle, arg_label=r'{:0.2f} \times \pi'.format(global_phase_angle_string)) + Rz_alpha = Gate("RZ",targets=[0], arg_value=alpha, arg_label=r'{:0.2f} \times \pi'.format(alpha_string)) + Ry_theta = Gate("RY",targets=[0], arg_value=theta, arg_label=r'{:0.2f} \times \pi'.format(theta_string)) + Rz_beta = Gate("RZ",targets=[0], arg_value=beta, arg_label=r'{:0.2f} \times \pi'.format(beta_string)) return(Rz_alpha, Ry_theta, Rz_beta, Phase_gate) @@ -56,8 +76,34 @@ def ZYZ_rotation(input_gate, num_of_qubits): raise GateError("Shape of input does not match with the number of qubits.") def ABC_decomposition(input_gate, num_of_qubits): - """ An input 1-qubit gate is expressed as a product of rotation matrices - :math:`R_z` and :math:`R_y` and Pauli X. + 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:: + + U = \begin{bmatrix} + a & b \\ + -b^* & a^* \\ + \end{bmatrix} = \textrm{A} \textrm{X} \textrm{B} \textrm{X} \textrm{C} + + 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)` + + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The matrix that's supposed to be decomposed should be a Qobj. + + Returns + ------- + tuple + The gates in the decomposition are returned as a tuple of :class:`Gate` + objects. This 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_of_qubits == 1 From bbcfff6bdd27511f0c4eea96caa669da9a40bab3 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Sun, 27 Jun 2021 11:22:02 -0500 Subject: [PATCH 03/17] before adding decomposition tests --- doc/source/_apidoc/qutip_qip.rst | 2 +- src/qutip_qip/decompositions/__init__.py | 2 +- .../decompositions/decompositions_extras.py | 2 +- .../decompositions/general_decompositions.py | 4 +- .../decompositions/single_decompositions.py | 169 +++++++++++------- tests/test_general_decompositions.py | 78 ++++++++ tests/test_single_decompositions.py | 0 tests/tests_general_decompositions.py | 0 8 files changed, 191 insertions(+), 66 deletions(-) create mode 100644 tests/test_general_decompositions.py create mode 100644 tests/test_single_decompositions.py create mode 100644 tests/tests_general_decompositions.py diff --git a/doc/source/_apidoc/qutip_qip.rst b/doc/source/_apidoc/qutip_qip.rst index c1df378fa..87792ef16 100644 --- a/doc/source/_apidoc/qutip_qip.rst +++ b/doc/source/_apidoc/qutip_qip.rst @@ -9,9 +9,9 @@ Subpackages qutip_qip.algorithms qutip_qip.compiler + qutip_qip.decompositions qutip_qip.device qutip_qip.operations - qutip_qip.decompositions qutip_qip.transpiler Submodules diff --git a/src/qutip_qip/decompositions/__init__.py b/src/qutip_qip/decompositions/__init__.py index b4049b00a..8c4a115ed 100644 --- a/src/qutip_qip/decompositions/__init__.py +++ b/src/qutip_qip/decompositions/__init__.py @@ -1,7 +1,7 @@ from qutip_qip.decompositions.general_decompositions import (check_input, check_input_shape, convert_qobj_gate_to_array, extract_global_phase) -from qutip_qip.decompositions.single_decompositions import (ZYZ_rotation, +from qutip_qip.decompositions.single_decompositions import (product_of_rotation_matrices, _ZYZ_rotation, ABC_decomposition) from qutip_qip.decompositions.decompositions_extras import (decomposed_gates_to_circuit, matrix_of_decomposed_gates) diff --git a/src/qutip_qip/decompositions/decompositions_extras.py b/src/qutip_qip/decompositions/decompositions_extras.py index 8bb43da61..48afbeb85 100644 --- a/src/qutip_qip/decompositions/decompositions_extras.py +++ b/src/qutip_qip/decompositions/decompositions_extras.py @@ -1,7 +1,7 @@ from qutip_qip.decompositions.general_decompositions import (check_input, check_input_shape, convert_qobj_gate_to_array) -from qutip_qip.decompositions.single_decompositions import (ZYZ_rotation, ABC_decomposition) +from qutip_qip.decompositions.single_decompositions import (_ZYZ_rotation, ABC_decomposition) from qutip import * from qutip_qip.operations import * from qutip_qip.circuit import QubitCircuit, Gate diff --git a/src/qutip_qip/decompositions/general_decompositions.py b/src/qutip_qip/decompositions/general_decompositions.py index 14277f6f9..132dddb81 100644 --- a/src/qutip_qip/decompositions/general_decompositions.py +++ b/src/qutip_qip/decompositions/general_decompositions.py @@ -106,6 +106,8 @@ def extract_global_phase(input_gate, num_of_qubits): """ input_array = convert_qobj_gate_to_array(input_gate) determinant_of_input = np.linalg.det(input_array) - global_phase_angle = cmath.phase(determinant_of_input) + y = np.imag(determinant_of_input) + x = np.real(determinant_of_input) + global_phase_angle = np.arctan2(y,x) global_phase_angle = global_phase_angle/(2**num_of_qubits) return(global_phase_angle) diff --git a/src/qutip_qip/decompositions/single_decompositions.py b/src/qutip_qip/decompositions/single_decompositions.py index 94f171b6e..41605ad65 100644 --- a/src/qutip_qip/decompositions/single_decompositions.py +++ b/src/qutip_qip/decompositions/single_decompositions.py @@ -14,16 +14,61 @@ class MethodError(): class GateError(): pass -def ZYZ_rotation(input_gate, num_of_qubits): +# Functions for product_of_rotation_matrices +def _ZYZ_rotation(input_gate, num_of_qubits): 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. + """ + global_phase_angle = extract_global_phase(input_gate,1) + global_phase_angle_string = global_phase_angle/np.pi + input_array = cmath.rect(1,global_phase_angle)*convert_qobj_gate_to_array(input_gate) + + # 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] # upto this line the code could be moved to the + # main function and change the input parameters for _ZYZ_rotation + # Will be done once the issue with global phase function is corrected. + + # 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 + + + Phase_gate = Gate("GLOBALPHASE",targets=[0], arg_value=global_phase_angle, arg_label=r'{:0.2f} \times \pi'.format(global_phase_angle_string)) + Rz_alpha = Gate("RZ",targets=[0], arg_value=alpha, arg_label=r'{:0.2f} \times \pi'.format(alpha_string)) + Ry_theta = Gate("RY",targets=[0], arg_value=theta, arg_label=r'{:0.2f} \times \pi'.format(theta_string)) + Rz_beta = Gate("RZ",targets=[0], arg_value=beta, arg_label=r'{:0.2f} \times \pi'.format(beta_string)) + + return(Rz_alpha, Ry_theta, Rz_beta, Phase_gate) + + +def ABC_decomposition(input_gate, num_of_qubits): + 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:: U = \begin{bmatrix} a & b \\ -b^* & a^* \\ - \end{bmatrix} = \textrm{R}_z(\alpha) \textrm{R}_y(\theta) \textrm{R}_z(\beta) + \end{bmatrix} = \textrm{A} \textrm{X} \textrm{B} \textrm{X} \textrm{C} + + 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)` + Parameters ---------- @@ -34,9 +79,9 @@ def ZYZ_rotation(input_gate, num_of_qubits): ------- tuple The gates in the decomposition are returned as a tuple of :class:`Gate` - objects. This tuple will contain 4 elements per each :math:`1 \times 1` - qubit gate - :math:`\textrm{R}_z(\alpha)`, :math:`\textrm{R}_y(\theta)`, - :math:`\textrm{R}_z(\beta)`, and some global phase gate. + objects. This 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_of_qubits == 1 @@ -45,102 +90,102 @@ def ZYZ_rotation(input_gate, num_of_qubits): if check_input_shape(input_gate,num_of_qubits)==True: global_phase_angle = extract_global_phase(input_gate,1) global_phase_angle_string = global_phase_angle/np.pi - - input_array = np.dot(phasegate(global_phase_angle),convert_qobj_gate_to_array(input_gate)) + input_array = cmath.rect(1,global_phase_angle)*convert_qobj_gate_to_array(input_gate) # 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] + b_star = input_array[1][0] # upto this line the code could be moved to the + # main function and change the input parameters for _ZYZ_rotation + # Will be done once the issue with global phase function is corrected. # 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) - alpha_string = alpha/np.pi beta_string = beta/np.pi - theta = 2*np.arctan2(np.absolute(b_star), np.absolute(a)) theta_string = theta/np.pi - # input array was changed with a global gate created from - # positive value of global phase + Phase_gate = Gate("GLOBALPHASE",targets=[0], arg_value=global_phase_angle, arg_label=r'{:0.2f} \times \pi'.format(global_phase_angle_string)) - Rz_alpha = Gate("RZ",targets=[0], arg_value=alpha, arg_label=r'{:0.2f} \times \pi'.format(alpha_string)) - Ry_theta = Gate("RY",targets=[0], arg_value=theta, arg_label=r'{:0.2f} \times \pi'.format(theta_string)) - Rz_beta = Gate("RZ",targets=[0], arg_value=beta, arg_label=r'{:0.2f} \times \pi'.format(beta_string)) + Rz_A = Gate("RZ",targets=[0], arg_value=alpha, arg_label=r'{:0.2f} \times \pi'.format(alpha_string)) + Ry_A = Gate("RY",targets=[0], arg_value=theta/2, arg_label=r'{:0.2f} \times \pi'.format(theta_string/2)) + 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_string/2)) + Rz_B = Gate("RZ",targets=[0], arg_value=-(alpha+beta)/2, arg_label=r'{:0.2f} \times \pi'.format(-(alpha_string+beta_string)/2)) + Rz_C = Gate("RZ",targets=[0], arg_value=(-alpha+beta)/2, arg_label=r'{:0.2f} \times \pi'.format((-alpha_string+beta_string)/2)) - return(Rz_alpha, Ry_theta, Rz_beta, Phase_gate) + return(Rz_A, Ry_A, Pauli_X, Ry_B, Rz_B, Pauli_X, Rz_C, Phase_gate) else: raise GateError("Shape of input does not match with the number of qubits.") -def ABC_decomposition(input_gate, num_of_qubits): +_rotation_matrices_dictionary ={"ZYZ": _ZYZ_rotation} + +def product_of_rotation_matrices(input_gate, num_of_qubits, method): 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`. + + Here, :math:`i \neq j` and :math:`i, j \in {x, y, z}` .. math:: U = \begin{bmatrix} a & b \\ -b^* & a^* \\ - \end{bmatrix} = \textrm{A} \textrm{X} \textrm{B} \textrm{X} \textrm{C} - - 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)` - + \end{bmatrix} = \textrm{R}_i(\alpha) \textrm{R}_j(\theta) \textrm{R}_i(\beta) Parameters ---------- input_gate : :class:`qutip.Qobj` The matrix that's supposed to be decomposed should be a Qobj. + num_of_qubits : int + Number of qubits being acted upon by input gate + + method : string + Name of the preferred decompositions 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)` + * - "YXY" + - :math:`\textrm{R}_y(\alpha) \textrm{R}_x(\theta) \textrm{R}_y(\beta)` + * - "YZY" + - :math:`\textrm{R}_y(\alpha) \textrm{R}_x(\theta) \textrm{R}_y(\beta)` + * - "XYX" + - :math:`\textrm{R}_x(\alpha) \textrm{R}_y(\theta) \textrm{R}_x(\beta)` + * - "XZX" + - :math:`\textrm{R}_x(\alpha) \textrm{R}_z(\theta) \textrm{R}_x(\beta)` + + Returns ------- tuple The gates in the decomposition are returned as a tuple of :class:`Gate` - objects. This 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. + objects. This 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. """ try: assert num_of_qubits == 1 except AssertionError: raise MethodError("This method is only valid for 1 qubit gates.") - if check_input_shape(input_gate,num_of_qubits)==True: - global_phase_angle = extract_global_phase(input_gate,1) - global_phase_angle_string = global_phase_angle/np.pi - - input_array = np.dot(phasegate(global_phase_angle),convert_qobj_gate_to_array(input_gate)) - # 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] - - # find alpha, beta and theta - alpha = cmath.phase(a_star/a) + cmath.phase(-b_star/b) - alpha_string = alpha/np.pi - theta = 2*cmath.phase(complex(np.absolute(b),np.absolute(a))) - theta_string = theta/np.pi - beta = cmath.phase(a_star/a) - cmath.phase(-b_star/b) - beta_string = beta/np.pi - - # input array was changed with a global gate created from - # positive value of global phase - # now undo it with a negative value of same phase angle global gate. - Phase_gate = Gate("GLOBALPHASE",targets=[0], arg_value=-global_phase_angle, arg_label=r'{} \times \pi'.format(-global_phase_angle_string)) - Rz_A = Gate("RZ",targets=[0], arg_value=alpha, arg_label=r'{} \times \pi'.format(alpha_string)) - Ry_A = Gate("RY",targets=[0], arg_value=theta/2, arg_label=r'{} \times \pi'.format(theta_string/2)) - Pauli_X = Gate("X",targets=[0]) - Ry_B = Gate("RY",targets=[0], arg_value=-theta/2, arg_label=r'{} \times \pi'.format(-theta_string/2)) - Rz_B = Gate("RZ",targets=[0], arg_value=-(alpha+beta)/2, arg_label=r'{} \times \pi'.format(-(alpha_string+beta_string)/2)) - Rz_C = Gate("RZ",targets=[0], arg_value=(-alpha+beta)/2, arg_label=r'{} \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) - - else: - raise GateError("Shape of input does not match with the number of qubits.") + for key in _rotation_matrices_dictionary.items(): + if method not in key: + raise KeyError("Invalid method.") + else: + if check_input_shape(input_gate,num_of_qubits)==True: + return(_rotation_matrices_dictionary[method](input_gate, num_of_qubits)) + else: + raise GateError("Shape of input does not match with the number of qubits.") diff --git a/tests/test_general_decompositions.py b/tests/test_general_decompositions.py new file mode 100644 index 000000000..ccc665164 --- /dev/null +++ b/tests/test_general_decompositions.py @@ -0,0 +1,78 @@ + +import numpy as np +import cmath + +from qutip.qobj import Qobj +from .general_decompositions import (check_input, check_input_shape, +convert_qobj_gate_to_array, extract_global_phase) + +from qutip_qip.operations import * +from qutip_qip.circuit import QubitCircuit, Gate + +@pytest.mark.parametrize("invalid_input",[np.array([[1,1],[1,1]]),([[1,1],[1,1]]),1.5,3,(1,2,3,4),np.array([[],[]]),([[],[]]),()]) +def test_check_input_non_qobj(invalid_input): + """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_input(invalid_input) + +@pytest.mark.parametrize("matrix",[Qobj([[1, 2], [3, 4],[5,6]])]) +def test_non_square_matrix(matrix): + """Rectangular Qobj are identified correctly.""" + with pytest.raises(ValueError, match="Input is not a square matrix."): + check_input(matrix) + +@pytest.mark.parametrize("matrix",[Qobj([[1,4,3]]),Qobj([[1]]),Qobj([[2]]),Qobj([[]])]) +def test_1d_matrix(matrix): + """A 1D object is identified as invalid quantum gate""" + with pytest.raises(ValueError, match="A 1-D Qobj is not a valid quantum gate."): + check_input(matrix) + + +# TO DO : CHECK FOR LARGER NUMBER OF QUBITS +@pytest.mark.parametrize("unitary",[Qobj([[1,0],[0,-1]])]) +def test_check_input_non_qobj(unitary): + """Checks if unitary innput is correctly identified. + """ + assert(check_input(unitary)==True) + +@pytest.mark.parametrize("non_unitary",[Qobj([[1,1],[0,1]])]) +def test_check_input_non_qobj(non_unitary): + """Checks if non-unitary input is correctly identified. + """ + assert(check_input(non_unitary)==False) + + +@pytest.mark.parametrize("valid_input",[Qobj([[1,0],[0,-1]]),rx(np.pi/2),z_gate(1),t_gate(1)]) +def test_one_qubit_gates(valid_input): + """Checks if number of qubits the gate will act on are identified properly + for one qubit gates. + """ + assert(find_qubits_in_circuit(valid_input)==1) + +@pytest.mark.parametrize("valid_input",[rx(np.pi/2,2),z_gate(2),t_gate(2)]) +def test_two_qubit_gates(valid_input): + """Checks if number of qubits the gate will act on are identified properly + for two qubit gates. + """ + assert(find_qubits_in_circuit(valid_input)==2) + +@pytest.mark.parametrize("valid_input",[rx(np.pi/2,3),z_gate(3),t_gate(3)]) +def test_three_qubit_gates(valid_input): + """Checks if number of qubits the gate will act on are identified properly + for three qubit gates. + """ + assert(find_qubits_in_circuit(valid_input)==3) + +@pytest.mark.parametrize("valid_input",[Qobj([[1,0,0],[0,1,0],[0,0,1]])]) +def test_one_qutrit_gates(valid_input): + """Checks if qutrit gate is identified. + """ + with pytest.raises(ValueError,match="Input is operationg on odd dimensional qudit state."): + find_qubits_in_circuit(valid_input) + +@pytest.mark.parametrize("valid_input",[Qobj([[1,0,0],[0,1,0],[0,0,1]]),rx(np.pi/2,3),z_gate(3),t_gate(3)]) +def test_one_qutrit_gates(valid_input): + """Checks if Qobj is converted to a numpy array. + """ + assert(isinstance(convert_qobj_gate_to_array(valid_input),np.ndarray)) diff --git a/tests/test_single_decompositions.py b/tests/test_single_decompositions.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/tests_general_decompositions.py b/tests/tests_general_decompositions.py new file mode 100644 index 000000000..e69de29bb From 7d7c1ab8f831a316ec10eaff87d9eb0503f355e7 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Mon, 28 Jun 2021 05:11:14 -0500 Subject: [PATCH 04/17] tests --- tests/test_general_decompositions.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/tests/test_general_decompositions.py b/tests/test_general_decompositions.py index ccc665164..2e284cda0 100644 --- a/tests/test_general_decompositions.py +++ b/tests/test_general_decompositions.py @@ -43,33 +43,6 @@ def test_check_input_non_qobj(non_unitary): assert(check_input(non_unitary)==False) -@pytest.mark.parametrize("valid_input",[Qobj([[1,0],[0,-1]]),rx(np.pi/2),z_gate(1),t_gate(1)]) -def test_one_qubit_gates(valid_input): - """Checks if number of qubits the gate will act on are identified properly - for one qubit gates. - """ - assert(find_qubits_in_circuit(valid_input)==1) - -@pytest.mark.parametrize("valid_input",[rx(np.pi/2,2),z_gate(2),t_gate(2)]) -def test_two_qubit_gates(valid_input): - """Checks if number of qubits the gate will act on are identified properly - for two qubit gates. - """ - assert(find_qubits_in_circuit(valid_input)==2) - -@pytest.mark.parametrize("valid_input",[rx(np.pi/2,3),z_gate(3),t_gate(3)]) -def test_three_qubit_gates(valid_input): - """Checks if number of qubits the gate will act on are identified properly - for three qubit gates. - """ - assert(find_qubits_in_circuit(valid_input)==3) - -@pytest.mark.parametrize("valid_input",[Qobj([[1,0,0],[0,1,0],[0,0,1]])]) -def test_one_qutrit_gates(valid_input): - """Checks if qutrit gate is identified. - """ - with pytest.raises(ValueError,match="Input is operationg on odd dimensional qudit state."): - find_qubits_in_circuit(valid_input) @pytest.mark.parametrize("valid_input",[Qobj([[1,0,0],[0,1,0],[0,0,1]]),rx(np.pi/2,3),z_gate(3),t_gate(3)]) def test_one_qutrit_gates(valid_input): From a5e01da69e59c35cfebd39b7d205052046a551fe Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Sun, 4 Jul 2021 17:31:26 -0500 Subject: [PATCH 05/17] finish up single --- src/qutip_qip/decompositions/__init__.py | 4 +- .../decompositions/decompositions_extras.py | 4 +- .../decompositions/general_decompositions.py | 29 +- .../decompositions/single_decompositions.py | 247 ++++++++++-------- .../test_decomposition_extras.py} | 0 .../test_general_decompositions.py | 13 +- .../test_single_decompositions.py} | 0 7 files changed, 170 insertions(+), 127 deletions(-) rename tests/{test_single_decompositions.py => gate_decompositions/test_decomposition_extras.py} (100%) rename tests/{ => gate_decompositions}/test_general_decompositions.py (87%) rename tests/{tests_general_decompositions.py => gate_decompositions/test_single_decompositions.py} (100%) diff --git a/src/qutip_qip/decompositions/__init__.py b/src/qutip_qip/decompositions/__init__.py index 8c4a115ed..065aa261f 100644 --- a/src/qutip_qip/decompositions/__init__.py +++ b/src/qutip_qip/decompositions/__init__.py @@ -1,7 +1,7 @@ from qutip_qip.decompositions.general_decompositions import (check_input, check_input_shape, convert_qobj_gate_to_array, extract_global_phase) -from qutip_qip.decompositions.single_decompositions import (product_of_rotation_matrices, _ZYZ_rotation, - ABC_decomposition) +from qutip_qip.decompositions.single_decompositions import (_ZYZ_rotation, _ZXZ_rotation, _rotation_matrices_dictionary, + ABC_decomposition, decompose_to_rotation_matrices) from qutip_qip.decompositions.decompositions_extras import (decomposed_gates_to_circuit, matrix_of_decomposed_gates) diff --git a/src/qutip_qip/decompositions/decompositions_extras.py b/src/qutip_qip/decompositions/decompositions_extras.py index 48afbeb85..7dc2a4a97 100644 --- a/src/qutip_qip/decompositions/decompositions_extras.py +++ b/src/qutip_qip/decompositions/decompositions_extras.py @@ -1,8 +1,8 @@ from qutip_qip.decompositions.general_decompositions import (check_input, check_input_shape, convert_qobj_gate_to_array) -from qutip_qip.decompositions.single_decompositions import (_ZYZ_rotation, ABC_decomposition) -from qutip import * +from qutip_qip.decompositions.single_decompositions import (_ZYZ_rotation, _ZXZ_rotation, _rotation_matrices_dictionary, decompose_to_rotation_matrices, ABC_decomposition) +from qutip.qobj import Qobj from qutip_qip.operations import * from qutip_qip.circuit import QubitCircuit, Gate from numpy import pi diff --git a/src/qutip_qip/decompositions/general_decompositions.py b/src/qutip_qip/decompositions/general_decompositions.py index 132dddb81..2086a4e65 100644 --- a/src/qutip_qip/decompositions/general_decompositions.py +++ b/src/qutip_qip/decompositions/general_decompositions.py @@ -3,6 +3,12 @@ from qutip.qobj import Qobj +class MethodError(Exception): + pass + +class GateError(Exception): + pass + def check_input(input_gate): """Verifies input is a valid quantum gate. @@ -60,11 +66,7 @@ def check_input_shape(input_gate, num_of_qubits): input_check_bool = check_input(input_gate) if input_check_bool == True: input_shape = input_gate.shape - try: - assert input_shape[0] == 2**num_of_qubits - except AssertionError: - return(False) - return(True) + return input_shape[0] == 2**num_of_qubits else: raise ValueError("Input is not unitary.") @@ -104,10 +106,13 @@ def extract_global_phase(input_gate, num_of_qubits): The matrix that's supposed to be decomposed should be a Qobj. """ - input_array = convert_qobj_gate_to_array(input_gate) - determinant_of_input = np.linalg.det(input_array) - y = np.imag(determinant_of_input) - x = np.real(determinant_of_input) - global_phase_angle = np.arctan2(y,x) - global_phase_angle = global_phase_angle/(2**num_of_qubits) - return(global_phase_angle) + if check_input_shape(input_gate, num_of_qubits) == True: + input_array = convert_qobj_gate_to_array(input_gate) + determinant_of_input = np.linalg.det(input_array) + y = np.imag(determinant_of_input) + x = np.real(determinant_of_input) + global_phase_angle = np.arctan2(y,x) + global_phase_angle = global_phase_angle/(2**num_of_qubits) + return(global_phase_angle) + else: + raise GateError("Gate shape does not match to the number of qubits in the circuit. ") diff --git a/src/qutip_qip/decompositions/single_decompositions.py b/src/qutip_qip/decompositions/single_decompositions.py index 41605ad65..b409f9779 100644 --- a/src/qutip_qip/decompositions/single_decompositions.py +++ b/src/qutip_qip/decompositions/single_decompositions.py @@ -3,128 +3,98 @@ from qutip.qobj import Qobj from .general_decompositions import (check_input, check_input_shape, -convert_qobj_gate_to_array, extract_global_phase) +convert_qobj_gate_to_array, extract_global_phase, MethodError, GateError) from qutip_qip.operations import * from qutip_qip.circuit import QubitCircuit, Gate -class MethodError(): - pass -class GateError(): - pass - -# Functions for product_of_rotation_matrices -def _ZYZ_rotation(input_gate, num_of_qubits): - 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. +# Functions for decompose_to_rotation_matrices +def _angles_for_ZYZ(input_gate, 1): + """ Finds and returns the angles for ZYZ rotation matrix. These are + used to change ZYZ to other combinations. """ global_phase_angle = extract_global_phase(input_gate,1) - global_phase_angle_string = global_phase_angle/np.pi input_array = cmath.rect(1,global_phase_angle)*convert_qobj_gate_to_array(input_gate) # 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] # upto this line the code could be moved to the - # main function and change the input parameters for _ZYZ_rotation - # Will be done once the issue with global phase function is corrected. + b_star = input_array[1][0] # 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 + return(alpha, theta, beta, global_phase_angle) - Phase_gate = Gate("GLOBALPHASE",targets=[0], arg_value=global_phase_angle, arg_label=r'{:0.2f} \times \pi'.format(global_phase_angle_string)) - Rz_alpha = Gate("RZ",targets=[0], arg_value=alpha, arg_label=r'{:0.2f} \times \pi'.format(alpha_string)) - Ry_theta = Gate("RY",targets=[0], arg_value=theta, arg_label=r'{:0.2f} \times \pi'.format(theta_string)) - Rz_beta = Gate("RZ",targets=[0], arg_value=beta, arg_label=r'{:0.2f} \times \pi'.format(beta_string)) - - return(Rz_alpha, Ry_theta, Rz_beta, Phase_gate) - -def ABC_decomposition(input_gate, num_of_qubits): +def _ZYZ_rotation(input_gate, num_of_qubits, target): 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}_z` and :math:`\textrm{R}_y`. - .. math:: + 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, 1) + 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 - U = \begin{bmatrix} - a & b \\ - -b^* & a^* \\ - \end{bmatrix} = \textrm{A} \textrm{X} \textrm{B} \textrm{X} \textrm{C} + 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)) - 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)` + return(Rz_alpha, Ry_theta, Rz_beta, Phase_gate) +def _ZXZ_rotation(input_gate, num_of_qubits, target): + 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. - - Returns - ------- - tuple - The gates in the decomposition are returned as a tuple of :class:`Gate` - objects. This 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_of_qubits == 1 - except AssertionError: - raise MethodError("This method is only valid for 1 qubit gates.") - if check_input_shape(input_gate,num_of_qubits)==True: - global_phase_angle = extract_global_phase(input_gate,1) - global_phase_angle_string = global_phase_angle/np.pi - input_array = cmath.rect(1,global_phase_angle)*convert_qobj_gate_to_array(input_gate) - - # 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] # upto this line the code could be moved to the - # main function and change the input parameters for _ZYZ_rotation - # Will be done once the issue with global phase function is corrected. - - # 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 - - - 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=[0], arg_value=alpha, arg_label=r'{:0.2f} \times \pi'.format(alpha_string)) - Ry_A = Gate("RY",targets=[0], arg_value=theta/2, arg_label=r'{:0.2f} \times \pi'.format(theta_string/2)) - 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_string/2)) - Rz_B = Gate("RZ",targets=[0], arg_value=-(alpha+beta)/2, arg_label=r'{:0.2f} \times \pi'.format(-(alpha_string+beta_string)/2)) - Rz_C = Gate("RZ",targets=[0], 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) + angle_list = _angles_for_ZYZ(input_gate, 1) + 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 - else: - raise GateError("Shape of input does not match with the number of qubits.") + 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) -_rotation_matrices_dictionary ={"ZYZ": _ZYZ_rotation} -def product_of_rotation_matrices(input_gate, num_of_qubits, method): +_rotation_matrices_dictionary ={"ZYZ": _ZYZ_rotation, + "ZXZ": _ZXZ_rotation, + } + +def decompose_to_rotation_matrices(input_gate, num_of_qubits, target, method): r""" An input 1-qubit gate is expressed as a product of rotation matrices :math:`\textrm{R}_i` and :math:`\textrm{R}_j`. @@ -145,6 +115,10 @@ def product_of_rotation_matrices(input_gate, num_of_qubits, method): num_of_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 decompositions method @@ -156,17 +130,8 @@ def product_of_rotation_matrices(input_gate, num_of_qubits, method): - Method * - ZYZ - :math:`\textrm{R}_z(\alpha) \textrm{R}_y(\theta) \textrm{R}_z(\beta)` - * - "ZXZ" + * - ZXZ - :math:`\textrm{R}_z(\alpha) \textrm{R}_x(\theta) \textrm{R}_z(\beta)` - * - "YXY" - - :math:`\textrm{R}_y(\alpha) \textrm{R}_x(\theta) \textrm{R}_y(\beta)` - * - "YZY" - - :math:`\textrm{R}_y(\alpha) \textrm{R}_x(\theta) \textrm{R}_y(\beta)` - * - "XYX" - - :math:`\textrm{R}_x(\alpha) \textrm{R}_y(\theta) \textrm{R}_x(\beta)` - * - "XZX" - - :math:`\textrm{R}_x(\alpha) \textrm{R}_z(\theta) \textrm{R}_x(\beta)` - Returns ------- @@ -179,13 +144,81 @@ def product_of_rotation_matrices(input_gate, num_of_qubits, method): try: assert num_of_qubits == 1 except AssertionError: - raise MethodError("This method is only valid for 1 qubit gates.") - - for key in _rotation_matrices_dictionary.items(): - if method not in key: - raise KeyError("Invalid method.") - else: - if check_input_shape(input_gate,num_of_qubits)==True: - return(_rotation_matrices_dictionary[method](input_gate, num_of_qubits)) - else: - raise GateError("Shape of input does not match with the number of qubits.") + if target is None and num_of_qubits >1: + raise GateError("This method is valid for single qubit gates only. Provide a target qubit for single qubit gate.") + + + key = _rotation_matrices_dictionary.keys() + if str(method) in key: + method = _rotation_matrices_dictionary[str(method)] + return(method(input_gate, num_of_qubits, target)) + + else: + raise MethodError("Invalid method chosen.") + + +def ABC_decomposition(input_gate, num_of_qubits, target): + 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:: + + U = \begin{bmatrix} + a & b \\ + -b^* & a^* \\ + \end{bmatrix} = \textrm{A} \textrm{X} \textrm{B} \textrm{X} \textrm{C} + + 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)` + + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The matrix that's supposed to be decomposed should be a Qobj. + + Returns + ------- + tuple + The gates in the decomposition are returned as a tuple of :class:`Gate` + objects. This 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_of_qubits == 1 + except AssertionError: + if target is None and num_of_qubits >1: + raise GateError("This method is valid for single qubit gates only. Provide a target qubit for larger circuits. ") + + + global_phase_angle = extract_global_phase(input_gate,1) + global_phase_angle_string = global_phase_angle/np.pi + input_array = cmath.rect(1,global_phase_angle)*convert_qobj_gate_to_array(input_gate) + + # 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] + + # 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 + + + 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/tests/test_single_decompositions.py b/tests/gate_decompositions/test_decomposition_extras.py similarity index 100% rename from tests/test_single_decompositions.py rename to tests/gate_decompositions/test_decomposition_extras.py diff --git a/tests/test_general_decompositions.py b/tests/gate_decompositions/test_general_decompositions.py similarity index 87% rename from tests/test_general_decompositions.py rename to tests/gate_decompositions/test_general_decompositions.py index 2e284cda0..f2fa7bee0 100644 --- a/tests/test_general_decompositions.py +++ b/tests/gate_decompositions/test_general_decompositions.py @@ -9,6 +9,8 @@ from qutip_qip.operations import * from qutip_qip.circuit import QubitCircuit, Gate +# Tests for check_input_shape + @pytest.mark.parametrize("invalid_input",[np.array([[1,1],[1,1]]),([[1,1],[1,1]]),1.5,3,(1,2,3,4),np.array([[],[]]),([[],[]]),()]) def test_check_input_non_qobj(invalid_input): """Checks if correct value is returned or not when the input is not a Qobj. @@ -17,13 +19,13 @@ def test_check_input_non_qobj(invalid_input): check_input(invalid_input) @pytest.mark.parametrize("matrix",[Qobj([[1, 2], [3, 4],[5,6]])]) -def test_non_square_matrix(matrix): +def test_check_input_non_square_matrix(matrix): """Rectangular Qobj are identified correctly.""" with pytest.raises(ValueError, match="Input is not a square matrix."): check_input(matrix) @pytest.mark.parametrize("matrix",[Qobj([[1,4,3]]),Qobj([[1]]),Qobj([[2]]),Qobj([[]])]) -def test_1d_matrix(matrix): +def test_check_input_1d_matrix(matrix): """A 1D object is identified as invalid quantum gate""" with pytest.raises(ValueError, match="A 1-D Qobj is not a valid quantum gate."): check_input(matrix) @@ -31,7 +33,7 @@ def test_1d_matrix(matrix): # TO DO : CHECK FOR LARGER NUMBER OF QUBITS @pytest.mark.parametrize("unitary",[Qobj([[1,0],[0,-1]])]) -def test_check_input_non_qobj(unitary): +def test_check_input_valid_qobj(unitary): """Checks if unitary innput is correctly identified. """ assert(check_input(unitary)==True) @@ -42,10 +44,13 @@ def test_check_input_non_qobj(non_unitary): """ assert(check_input(non_unitary)==False) +# Tests for check_input_shape - +# Tests for convert_qobj_gate_to_array @pytest.mark.parametrize("valid_input",[Qobj([[1,0,0],[0,1,0],[0,0,1]]),rx(np.pi/2,3),z_gate(3),t_gate(3)]) def test_one_qutrit_gates(valid_input): """Checks if Qobj is converted to a numpy array. """ assert(isinstance(convert_qobj_gate_to_array(valid_input),np.ndarray)) + +# Tests for extract_global_phase diff --git a/tests/tests_general_decompositions.py b/tests/gate_decompositions/test_single_decompositions.py similarity index 100% rename from tests/tests_general_decompositions.py rename to tests/gate_decompositions/test_single_decompositions.py From 196be453208d3ac49753b515055c897a71a695f8 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Sun, 4 Jul 2021 23:29:21 -0500 Subject: [PATCH 06/17] all tests --- .../decompositions/decompositions_extras.py | 11 +++- .../decompositions/general_decompositions.py | 12 ++-- .../decompositions/single_decompositions.py | 26 ++++---- .../test_decomposition_extras.py | 55 +++++++++++++++++ .../test_general_decompositions.py | 37 +++++++++++- .../test_single_decompositions.py | 59 +++++++++++++++++++ 6 files changed, 181 insertions(+), 19 deletions(-) diff --git a/src/qutip_qip/decompositions/decompositions_extras.py b/src/qutip_qip/decompositions/decompositions_extras.py index 7dc2a4a97..c8c6d86cf 100644 --- a/src/qutip_qip/decompositions/decompositions_extras.py +++ b/src/qutip_qip/decompositions/decompositions_extras.py @@ -9,15 +9,24 @@ import numpy as np def decomposed_gates_to_circuit(decomposed_gate,num_of_qubits): + """This function takes the input from a decomposition function and creates + a circuit diagram. + """ + # 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_of_qubits, reverse_states=False) for i in decomposed_gate: q_circuit.add_gate(i) return(q_circuit) else: - raise TypeError("Input is not a list of gates.") + raise TypeError("Input is not a tuple of gates.") def matrix_of_decomposed_gates(quantum_circuit): + """Evaluates all the gates in the circuit. + """ if isinstance(quantum_circuit, QubitCircuit) == True: gate_list = quantum_circuit.propagators() matrix_of_all_gates = gate_sequence_product(gate_list) diff --git a/src/qutip_qip/decompositions/general_decompositions.py b/src/qutip_qip/decompositions/general_decompositions.py index 2086a4e65..2a47f8f13 100644 --- a/src/qutip_qip/decompositions/general_decompositions.py +++ b/src/qutip_qip/decompositions/general_decompositions.py @@ -4,9 +4,14 @@ from qutip.qobj import Qobj class MethodError(Exception): + """When invalid method is chosen, this error is raised. + """ pass class GateError(Exception): + """When chosen method cannot be applied to the input gate, this error + is raised. + """ pass def check_input(input_gate): @@ -89,7 +94,8 @@ def convert_qobj_gate_to_array(input_gate): input_to_array = Qobj.full(input_gate) try: isinstance(input_to_array, np.ndarray) - except: + except: # Not sure if this error has to be included. If an error occurs, + # then it ought to covered in the main qutip module. raise ConversionError("Input Qobj could not be converted to a numpy array.") return(input_to_array) else: @@ -109,9 +115,7 @@ def extract_global_phase(input_gate, num_of_qubits): if check_input_shape(input_gate, num_of_qubits) == True: input_array = convert_qobj_gate_to_array(input_gate) determinant_of_input = np.linalg.det(input_array) - y = np.imag(determinant_of_input) - x = np.real(determinant_of_input) - global_phase_angle = np.arctan2(y,x) + global_phase_angle = cmath.phase(determinant_of_input) global_phase_angle = global_phase_angle/(2**num_of_qubits) return(global_phase_angle) else: diff --git a/src/qutip_qip/decompositions/single_decompositions.py b/src/qutip_qip/decompositions/single_decompositions.py index b409f9779..e7743dd0b 100644 --- a/src/qutip_qip/decompositions/single_decompositions.py +++ b/src/qutip_qip/decompositions/single_decompositions.py @@ -2,7 +2,7 @@ import cmath from qutip.qobj import Qobj -from .general_decompositions import (check_input, check_input_shape, +from qutip_qip.decompositions.general_decompositions import (check_input, check_input_shape, convert_qobj_gate_to_array, extract_global_phase, MethodError, GateError) from qutip_qip.operations import * @@ -10,13 +10,14 @@ # Functions for decompose_to_rotation_matrices -def _angles_for_ZYZ(input_gate, 1): +single_qubit = 1 +def _angles_for_ZYZ(input_gate, single_qubit): """ Finds and returns the angles for ZYZ rotation matrix. These are used to change ZYZ to other combinations. """ - global_phase_angle = extract_global_phase(input_gate,1) - input_array = cmath.rect(1,global_phase_angle)*convert_qobj_gate_to_array(input_gate) - + global_phase_angle = extract_global_phase(input_gate,single_qubit) + input_array = convert_qobj_gate_to_array(input_gate) + input_array = input_array/cmath.rect(1,global_phase_angle) # separate all the elements a = input_array[0][0] b = input_array[0][1] @@ -27,7 +28,7 @@ def _angles_for_ZYZ(input_gate, 1): 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, global_phase_angle) + return(alpha, theta, beta, np.pi+global_phase_angle) @@ -40,7 +41,7 @@ def _ZYZ_rotation(input_gate, num_of_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, single_qubit) alpha = angle_list[0] beta = angle_list[2] theta = angle_list[1] @@ -68,7 +69,7 @@ def _ZXZ_rotation(input_gate, num_of_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, single_qubit) alpha = angle_list[0] alpha = alpha - np.pi/2 beta = angle_list[2] @@ -194,16 +195,17 @@ def ABC_decomposition(input_gate, num_of_qubits, target): raise GateError("This method is valid for single qubit gates only. Provide a target qubit for larger circuits. ") - global_phase_angle = extract_global_phase(input_gate,1) - global_phase_angle_string = global_phase_angle/np.pi - input_array = cmath.rect(1,global_phase_angle)*convert_qobj_gate_to_array(input_gate) - + global_phase_angle = extract_global_phase(input_gate,single_qubit) + input_array = convert_qobj_gate_to_array(input_gate) + 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 diff --git a/tests/gate_decompositions/test_decomposition_extras.py b/tests/gate_decompositions/test_decomposition_extras.py index e69de29bb..3016b637d 100644 --- a/tests/gate_decompositions/test_decomposition_extras.py +++ b/tests/gate_decompositions/test_decomposition_extras.py @@ -0,0 +1,55 @@ +import numpy as np +import cmath +import pytest + +from qutip.qobj import Qobj +from qutip_qip.operations import * +from qutip_qip.circuit import QubitCircuit, Gate + +from qutip_qip.decompositions.general_decompositions import (check_input, +check_input_shape, convert_qobj_gate_to_array, extract_global_phase) + +from qutip_qip.decompositions.single_decompositions import (_ZYZ_rotation, _ZXZ_rotation, _rotation_matrices_dictionary, + ABC_decomposition, decompose_to_rotation_matrices) + +from qutip_qip.decompositions.decompositions_extras import (decomposed_gates_to_circuit, matrix_of_decomposed_gates) + + +# Tests for decomposed_gates_to_circuit +@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. + """ + 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, 1, 0) +sigmax_zyz_gates = _ZYZ_rotation(sigmax, 1, 0) + +@pytest.mark.parametrize("valid_input",[H_zyz_gates,sigmax_zyz_gates]) +def test_decomposed_gates_to_circuit(valid_input): + """Checks if output is of type 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]) +def test_matrix_of_decomposed_gates(invalid_input): + """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."): + matrix_of_decomposed_gates(invalid_input) + + +@pytest.mark.parametrize("valid_input",[H_zyz_quantum_circuit,sigmax_zyz_quantum_circuit]) +def test_matrix_of_decomposed_gates(valid_input): + """Checks if final output is a Qobj. + """ + final_output=matrix_of_decomposed_gates(valid_input) + assert(isinstance(final_output, Qobj)) diff --git a/tests/gate_decompositions/test_general_decompositions.py b/tests/gate_decompositions/test_general_decompositions.py index f2fa7bee0..1c1ee7ad2 100644 --- a/tests/gate_decompositions/test_general_decompositions.py +++ b/tests/gate_decompositions/test_general_decompositions.py @@ -1,10 +1,11 @@ import numpy as np import cmath +import pytest from qutip.qobj import Qobj -from .general_decompositions import (check_input, check_input_shape, -convert_qobj_gate_to_array, extract_global_phase) +from qutip_qip.decompositions.general_decompositions import (check_input, check_input_shape, +convert_qobj_gate_to_array, extract_global_phase, MethodError, GateError) from qutip_qip.operations import * from qutip_qip.circuit import QubitCircuit, Gate @@ -45,6 +46,18 @@ def test_check_input_non_qobj(non_unitary): assert(check_input(non_unitary)==False) # Tests for check_input_shape +@pytest.mark.parametrize("unitary",[Qobj([[1,0],[0,-1]])]) +def test_check_input_shape_unitary_input(unitary): + """Checks if shape of input is correctly identified. + """ + assert(check_input_shape(unitary,1)==True) + +@pytest.mark.parametrize("non_unitary",[Qobj([[1,1],[0,1]])]) +def test_check_input_non_qobj(non_unitary): + """Checks if non-unitary input is correctly identified. + """ + with pytest.raises(ValueError, match="Input is not unitary."): + check_input_shape(non_unitary,1) # Tests for convert_qobj_gate_to_array @pytest.mark.parametrize("valid_input",[Qobj([[1,0,0],[0,1,0],[0,0,1]]),rx(np.pi/2,3),z_gate(3),t_gate(3)]) @@ -53,4 +66,24 @@ def test_one_qutrit_gates(valid_input): """ assert(isinstance(convert_qobj_gate_to_array(valid_input),np.ndarray)) +@pytest.mark.parametrize("non_unitary",[Qobj([[1,1],[0,1]])]) +def test_convert_qobj_gate_to_array(non_unitary): + """Checks if non-unitary input is correctly identified. + """ + with pytest.raises(ValueError, match="Input is not unitary."): + convert_qobj_gate_to_array(non_unitary) + # Tests for extract_global_phase +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) + +def test_extract_global_phase_valid_input_incorrect_number_of_qubits(): + """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)]]) + with pytest.raises(GateError, match="Gate shape does not match to the number of qubits in the circuit. "): + extract_global_phase(H,2) diff --git a/tests/gate_decompositions/test_single_decompositions.py b/tests/gate_decompositions/test_single_decompositions.py index e69de29bb..d23f48621 100644 --- a/tests/gate_decompositions/test_single_decompositions.py +++ b/tests/gate_decompositions/test_single_decompositions.py @@ -0,0 +1,59 @@ +import numpy as np +import cmath +import pytest + +from qutip.qobj import Qobj +from qutip.metrics import average_gate_fidelity + + +from qutip_qip.decompositions.single_decompositions import (_ZYZ_rotation, _ZXZ_rotation, + ABC_decomposition, decompose_to_rotation_matrices) + +from qutip_qip.decompositions.decompositions_extras import (decomposed_gates_to_circuit, matrix_of_decomposed_gates) + +# 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)]]) +T = Qobj([[1,0],[0,cmath.rect(1, np.pi/4)]]) +S = Qobj([[1,0],[0,1j]]) + +@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T]) +@pytest.mark.parametrize("method",[_ZYZ_rotation, _ZXZ_rotation, ABC_decomposition]) +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) + decomposed_gates_final_matrix = matrix_of_decomposed_gates(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",['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) + decomposed_gates_final_matrix = matrix_of_decomposed_gates(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]) +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) + assert(isinstance(gate_list, tuple)) + +@pytest.mark.parametrize("gate",[H, sigmax, sigmay, sigmaz, SQRTNOT, S, T]) +@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) + assert(isinstance(gate_list, tuple)) From 6b2721fa0a84802332666326fa8e7edac10e9da0 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Fri, 9 Jul 2021 14:04:35 -0500 Subject: [PATCH 07/17] rename and move things --- src/qutip_qip/circuit.py | 29 +++++++++- src/qutip_qip/decompose/__init__.py | 1 + .../_utility.py} | 12 +--- .../single_decompositions.py | 28 ++++++---- src/qutip_qip/decompositions/__init__.py | 7 --- .../decompositions/decompositions_extras.py | 35 ------------ .../test_single_decompositions.py | 12 ++-- .../test_utility.py} | 5 +- .../test_decomposition_extras.py | 55 ------------------- tests/test_circuit.py | 43 ++++++++++++++- 10 files changed, 97 insertions(+), 130 deletions(-) create mode 100644 src/qutip_qip/decompose/__init__.py rename src/qutip_qip/{decompositions/general_decompositions.py => decompose/_utility.py} (91%) rename src/qutip_qip/{decompositions => decompose}/single_decompositions.py (91%) delete mode 100644 src/qutip_qip/decompositions/__init__.py delete mode 100644 src/qutip_qip/decompositions/decompositions_extras.py rename tests/{gate_decompositions => gate_decompose}/test_single_decompositions.py (84%) rename tests/{gate_decompositions/test_general_decompositions.py => gate_decompose/test_utility.py} (96%) delete mode 100644 tests/gate_decompositions/test_decomposition_extras.py diff --git a/src/qutip_qip/circuit.py b/src/qutip_qip/circuit.py index 5a3f33c66..0a6c4e04a 100644 --- a/src/qutip_qip/circuit.py +++ b/src/qutip_qip/circuit.py @@ -49,7 +49,7 @@ ) from .operations.gates import _gate_label from qutip import basis, ket2dm, qeye -from qutip.qobj import Qobj +from qutip import Qobj from qutip.measurement import measurement_statistics @@ -2130,3 +2130,30 @@ def _apply_measurement(self, operation): else: raise NotImplementedError( "mode {} is not available.".format(self.mode)) + +# For Decomposition functions +def decomposed_gates_to_circuit(decomposed_gate,num_qubits): + """This function takes the input from a decomposition function and returns + a quantum circuit. + """ + # 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: + raise TypeError("Input is not a tuple of gates.") + +def compute_unitary(quantum_circuit): + """Evaluates all the gates in the quantum circuit. + """ + 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: + raise TypeError("Input is not of type QubitCircuit.") diff --git a/src/qutip_qip/decompose/__init__.py b/src/qutip_qip/decompose/__init__.py new file mode 100644 index 000000000..1a6bea18c --- /dev/null +++ b/src/qutip_qip/decompose/__init__.py @@ -0,0 +1 @@ +from qutip_qip.decompose.single_decompositions import (ABC_decomposition, decompose_to_rotation_matrices) diff --git a/src/qutip_qip/decompositions/general_decompositions.py b/src/qutip_qip/decompose/_utility.py similarity index 91% rename from src/qutip_qip/decompositions/general_decompositions.py rename to src/qutip_qip/decompose/_utility.py index 2a47f8f13..2a43d8e13 100644 --- a/src/qutip_qip/decompositions/general_decompositions.py +++ b/src/qutip_qip/decompose/_utility.py @@ -1,7 +1,7 @@ import numpy as np import cmath -from qutip.qobj import Qobj +from qutip import Qobj class MethodError(Exception): """When invalid method is chosen, this error is raised. @@ -10,7 +10,7 @@ class MethodError(Exception): class GateError(Exception): """When chosen method cannot be applied to the input gate, this error - is raised. + is raised. """ pass @@ -48,7 +48,7 @@ def check_input(input_gate): def check_input_shape(input_gate, num_of_qubits): - """Check if the shape of input gate is valid to act on input number of qubits + """Check if the shape of input gate is valid to act on input number of qubits. If :math:`n` is the number of qubits in the circuit then a valid quantum gate acting on these qubits must be of dimension :math:`2^n \\times 2^n`. @@ -92,11 +92,6 @@ def convert_qobj_gate_to_array(input_gate): input_check_bool = check_input(input_gate) if input_check_bool == True: input_to_array = Qobj.full(input_gate) - try: - isinstance(input_to_array, np.ndarray) - except: # Not sure if this error has to be included. If an error occurs, - # then it ought to covered in the main qutip module. - raise ConversionError("Input Qobj could not be converted to a numpy array.") return(input_to_array) else: raise ValueError("Input is not unitary.") @@ -110,7 +105,6 @@ def extract_global_phase(input_gate, num_of_qubits): ---------- input_gate : :class:`qutip.Qobj` The matrix that's supposed to be decomposed should be a Qobj. - """ if check_input_shape(input_gate, num_of_qubits) == True: input_array = convert_qobj_gate_to_array(input_gate) diff --git a/src/qutip_qip/decompositions/single_decompositions.py b/src/qutip_qip/decompose/single_decompositions.py similarity index 91% rename from src/qutip_qip/decompositions/single_decompositions.py rename to src/qutip_qip/decompose/single_decompositions.py index e7743dd0b..fcca22478 100644 --- a/src/qutip_qip/decompositions/single_decompositions.py +++ b/src/qutip_qip/decompose/single_decompositions.py @@ -1,21 +1,24 @@ import numpy as np import cmath -from qutip.qobj import Qobj -from qutip_qip.decompositions.general_decompositions import (check_input, check_input_shape, +from qutip import Qobj +from qutip_qip.decompose._utility import (check_input, check_input_shape, convert_qobj_gate_to_array, extract_global_phase, MethodError, GateError) -from qutip_qip.operations import * from qutip_qip.circuit import QubitCircuit, Gate # Functions for decompose_to_rotation_matrices -single_qubit = 1 -def _angles_for_ZYZ(input_gate, single_qubit): +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. """ - global_phase_angle = extract_global_phase(input_gate,single_qubit) + global_phase_angle = extract_global_phase(input_gate,num_qubits) input_array = convert_qobj_gate_to_array(input_gate) input_array = input_array/cmath.rect(1,global_phase_angle) # separate all the elements @@ -41,7 +44,7 @@ def _ZYZ_rotation(input_gate, num_of_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, single_qubit) + angle_list = _angles_for_ZYZ(input_gate, 1) alpha = angle_list[0] beta = angle_list[2] theta = angle_list[1] @@ -69,7 +72,7 @@ def _ZXZ_rotation(input_gate, num_of_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, single_qubit) + angle_list = _angles_for_ZYZ(input_gate, 1) alpha = angle_list[0] alpha = alpha - np.pi/2 beta = angle_list[2] @@ -93,9 +96,9 @@ def _ZXZ_rotation(input_gate, num_of_qubits, target): _rotation_matrices_dictionary ={"ZYZ": _ZYZ_rotation, "ZXZ": _ZXZ_rotation, - } + } # other combinations to add here -def decompose_to_rotation_matrices(input_gate, num_of_qubits, target, method): +def decompose_to_rotation_matrices(input_gate, method, num_of_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`. @@ -157,7 +160,8 @@ def decompose_to_rotation_matrices(input_gate, num_of_qubits, target, method): 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_of_qubits, target): 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}`. @@ -195,7 +199,7 @@ def ABC_decomposition(input_gate, num_of_qubits, target): raise GateError("This method is valid for single qubit gates only. Provide a target qubit for larger circuits. ") - global_phase_angle = extract_global_phase(input_gate,single_qubit) + global_phase_angle = extract_global_phase(input_gate,1) input_array = convert_qobj_gate_to_array(input_gate) input_array = input_array/cmath.rect(1,global_phase_angle) # separate all the elements diff --git a/src/qutip_qip/decompositions/__init__.py b/src/qutip_qip/decompositions/__init__.py deleted file mode 100644 index 065aa261f..000000000 --- a/src/qutip_qip/decompositions/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from qutip_qip.decompositions.general_decompositions import (check_input, -check_input_shape, convert_qobj_gate_to_array, extract_global_phase) - -from qutip_qip.decompositions.single_decompositions import (_ZYZ_rotation, _ZXZ_rotation, _rotation_matrices_dictionary, - ABC_decomposition, decompose_to_rotation_matrices) - -from qutip_qip.decompositions.decompositions_extras import (decomposed_gates_to_circuit, matrix_of_decomposed_gates) diff --git a/src/qutip_qip/decompositions/decompositions_extras.py b/src/qutip_qip/decompositions/decompositions_extras.py deleted file mode 100644 index c8c6d86cf..000000000 --- a/src/qutip_qip/decompositions/decompositions_extras.py +++ /dev/null @@ -1,35 +0,0 @@ -from qutip_qip.decompositions.general_decompositions import (check_input, -check_input_shape, convert_qobj_gate_to_array) - -from qutip_qip.decompositions.single_decompositions import (_ZYZ_rotation, _ZXZ_rotation, _rotation_matrices_dictionary, decompose_to_rotation_matrices, ABC_decomposition) -from qutip.qobj import Qobj -from qutip_qip.operations import * -from qutip_qip.circuit import QubitCircuit, Gate -from numpy import pi -import numpy as np - -def decomposed_gates_to_circuit(decomposed_gate,num_of_qubits): - """This function takes the input from a decomposition function and creates - a circuit diagram. - """ - # 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_of_qubits, reverse_states=False) - for i in decomposed_gate: - q_circuit.add_gate(i) - return(q_circuit) - else: - raise TypeError("Input is not a tuple of gates.") - -def matrix_of_decomposed_gates(quantum_circuit): - """Evaluates all the gates in the circuit. - """ - 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: - raise TypeError("Input is not of type QubitCircuit.") diff --git a/tests/gate_decompositions/test_single_decompositions.py b/tests/gate_decompose/test_single_decompositions.py similarity index 84% rename from tests/gate_decompositions/test_single_decompositions.py rename to tests/gate_decompose/test_single_decompositions.py index d23f48621..c4c6ce314 100644 --- a/tests/gate_decompositions/test_single_decompositions.py +++ b/tests/gate_decompose/test_single_decompositions.py @@ -2,14 +2,12 @@ import cmath import pytest -from qutip.qobj import Qobj -from qutip.metrics import average_gate_fidelity +from qutip import Qobj, average_gate_fidelity - -from qutip_qip.decompositions.single_decompositions import (_ZYZ_rotation, _ZXZ_rotation, +from qutip_qip.decompose.single_decompositions import (_ZYZ_rotation, _ZXZ_rotation, ABC_decomposition, decompose_to_rotation_matrices) -from qutip_qip.decompositions.decompositions_extras import (decomposed_gates_to_circuit, matrix_of_decomposed_gates) +from qutip_qip.circuit import (decomposed_gates_to_circuit, compute_unitary) # 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)]]) @@ -27,7 +25,7 @@ def test_single_qubit_to_rotations(gate, method): target = 0 gate_list = method(gate,1,target) decomposed_gates_circuit = decomposed_gates_to_circuit(gate_list,1) - decomposed_gates_final_matrix = matrix_of_decomposed_gates(decomposed_gates_circuit) + 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)) @@ -38,7 +36,7 @@ def test_check_single_qubit_to_decompose_to_rotations(gate, method): target = 0 gate_list = decompose_to_rotation_matrices(gate,1,target,method) decomposed_gates_circuit = decomposed_gates_to_circuit(gate_list,1) - decomposed_gates_final_matrix = matrix_of_decomposed_gates(decomposed_gates_circuit) + 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)) diff --git a/tests/gate_decompositions/test_general_decompositions.py b/tests/gate_decompose/test_utility.py similarity index 96% rename from tests/gate_decompositions/test_general_decompositions.py rename to tests/gate_decompose/test_utility.py index 1c1ee7ad2..27d2943d4 100644 --- a/tests/gate_decompositions/test_general_decompositions.py +++ b/tests/gate_decompose/test_utility.py @@ -4,10 +4,9 @@ import pytest from qutip.qobj import Qobj -from qutip_qip.decompositions.general_decompositions import (check_input, check_input_shape, +from qutip_qip.decompose._utility import (check_input, check_input_shape, convert_qobj_gate_to_array, extract_global_phase, MethodError, GateError) - -from qutip_qip.operations import * +from qutip_qip.operations import rx, z_gate, t_gate from qutip_qip.circuit import QubitCircuit, Gate # Tests for check_input_shape diff --git a/tests/gate_decompositions/test_decomposition_extras.py b/tests/gate_decompositions/test_decomposition_extras.py deleted file mode 100644 index 3016b637d..000000000 --- a/tests/gate_decompositions/test_decomposition_extras.py +++ /dev/null @@ -1,55 +0,0 @@ -import numpy as np -import cmath -import pytest - -from qutip.qobj import Qobj -from qutip_qip.operations import * -from qutip_qip.circuit import QubitCircuit, Gate - -from qutip_qip.decompositions.general_decompositions import (check_input, -check_input_shape, convert_qobj_gate_to_array, extract_global_phase) - -from qutip_qip.decompositions.single_decompositions import (_ZYZ_rotation, _ZXZ_rotation, _rotation_matrices_dictionary, - ABC_decomposition, decompose_to_rotation_matrices) - -from qutip_qip.decompositions.decompositions_extras import (decomposed_gates_to_circuit, matrix_of_decomposed_gates) - - -# Tests for decomposed_gates_to_circuit -@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. - """ - 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, 1, 0) -sigmax_zyz_gates = _ZYZ_rotation(sigmax, 1, 0) - -@pytest.mark.parametrize("valid_input",[H_zyz_gates,sigmax_zyz_gates]) -def test_decomposed_gates_to_circuit(valid_input): - """Checks if output is of type 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]) -def test_matrix_of_decomposed_gates(invalid_input): - """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."): - matrix_of_decomposed_gates(invalid_input) - - -@pytest.mark.parametrize("valid_input",[H_zyz_quantum_circuit,sigmax_zyz_quantum_circuit]) -def test_matrix_of_decomposed_gates(valid_input): - """Checks if final output is a Qobj. - """ - final_output=matrix_of_decomposed_gates(valid_input) - assert(isinstance(final_output, Qobj)) diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 1ede8f2d5..9d7257a70 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -38,7 +38,7 @@ from qutip_qip.circuit import ( - QubitCircuit, CircuitSimulator, Measurement) + QubitCircuit, CircuitSimulator, Measurement, compute_unitary, decomposed_gates_to_circuit) from qutip import (tensor, Qobj, ptrace, rand_ket, fock_dm, basis, rand_dm, bell_state, ket2dm, identity) from qutip_qip.qasm import read_qasm @@ -48,6 +48,8 @@ _para_gates ) +from qutip_qip.decompose.single_decompositions import _ZYZ_rotation + import qutip as qp @@ -674,3 +676,42 @@ def test_latex_code_teleportation_circuit(self): r" \gate{{\rm H}} & \meter & \qw & \qw & \qw & \qw \\ ", "", ]) + +# Tests for decomposed_gates_to_circuit +@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. + """ + 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, 1, 0) +sigmax_zyz_gates = _ZYZ_rotation(sigmax, 1, 0) + +@pytest.mark.parametrize("valid_input",[H_zyz_gates,sigmax_zyz_gates]) +def test_decomposed_gates_to_circuit(valid_input): + """Checks if output is of type 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]) +def test_matrix_of_decomposed_gates(invalid_input): + """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."): + 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): + """Checks if final output is a Qobj. + """ + final_output=compute_unitary(valid_input) + assert(isinstance(final_output, Qobj)) From 8fa0d6b508fe922b3e14feb3ab0133b7c11ab9ec Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Mon, 12 Jul 2021 03:13:52 -0500 Subject: [PATCH 08/17] xfail --- tests/gate_decompose/test_single_decompositions.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/gate_decompose/test_single_decompositions.py b/tests/gate_decompose/test_single_decompositions.py index c4c6ce314..7c2a6ffac 100644 --- a/tests/gate_decompose/test_single_decompositions.py +++ b/tests/gate_decompose/test_single_decompositions.py @@ -55,3 +55,13 @@ def test_check_single_qubit_to_decompose_to_rotations(gate, method): target = 0 gate_list = decompose_to_rotation_matrices(gate,1,target,method) 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) From 1ad4f58f9b685b2b56c5c11baf5d528e4e6282f4 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Mon, 12 Jul 2021 05:25:57 -0500 Subject: [PATCH 09/17] method/target --- tests/gate_decompose/test_single_decompositions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gate_decompose/test_single_decompositions.py b/tests/gate_decompose/test_single_decompositions.py index 7c2a6ffac..3754228cf 100644 --- a/tests/gate_decompose/test_single_decompositions.py +++ b/tests/gate_decompose/test_single_decompositions.py @@ -53,7 +53,7 @@ def test_output_is_tuple(gate, method): 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) + gate_list = decompose_to_rotation_matrices(gate,method, 1,target) assert(isinstance(gate_list, tuple)) @pytest.mark.xfail From 1575b1dba32a1b25e8cf8625994bba3982a68d9f Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Mon, 12 Jul 2021 10:23:25 -0500 Subject: [PATCH 10/17] rename --- .../{single_decompositions.py => decompose_single_qubit_gate.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/qutip_qip/decompose/{single_decompositions.py => decompose_single_qubit_gate.py} (100%) diff --git a/src/qutip_qip/decompose/single_decompositions.py b/src/qutip_qip/decompose/decompose_single_qubit_gate.py similarity index 100% rename from src/qutip_qip/decompose/single_decompositions.py rename to src/qutip_qip/decompose/decompose_single_qubit_gate.py From 7b96f50c565364579b4e3380ca5a381c6ba5e50a Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Tue, 13 Jul 2021 11:52:14 -0500 Subject: [PATCH 11/17] change import --- src/qutip_qip/decompose/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qutip_qip/decompose/__init__.py b/src/qutip_qip/decompose/__init__.py index 1a6bea18c..fa64a1338 100644 --- a/src/qutip_qip/decompose/__init__.py +++ b/src/qutip_qip/decompose/__init__.py @@ -1 +1 @@ -from qutip_qip.decompose.single_decompositions import (ABC_decomposition, decompose_to_rotation_matrices) +from qutip_qip.decompose.decompose_single_qubit_gate import (ABC_decomposition, decompose_to_rotation_matrices) From 4d608fd44622593781311b8ff0dff82e6a1d74b9 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Wed, 14 Jul 2021 08:38:36 -0500 Subject: [PATCH 12/17] docs --- doc/source/_apidoc/qutip_qip.decompose.rst | 21 +++++++++++++++++++++ doc/source/_apidoc/qutip_qip.rst | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 doc/source/_apidoc/qutip_qip.decompose.rst diff --git a/doc/source/_apidoc/qutip_qip.decompose.rst b/doc/source/_apidoc/qutip_qip.decompose.rst new file mode 100644 index 000000000..1d0f4e18e --- /dev/null +++ b/doc/source/_apidoc/qutip_qip.decompose.rst @@ -0,0 +1,21 @@ +qutip\_qip.decompose package +============================ + +Submodules +---------- + +qutip\_qip.decompose.decompose\_single\_qubit\_gate module +---------------------------------------------------------- + +.. automodule:: qutip_qip.decompose.decompose_single_qubit_gate + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: qutip_qip.decompose + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/_apidoc/qutip_qip.rst b/doc/source/_apidoc/qutip_qip.rst index 87792ef16..1c3bf8d5b 100644 --- a/doc/source/_apidoc/qutip_qip.rst +++ b/doc/source/_apidoc/qutip_qip.rst @@ -9,7 +9,7 @@ Subpackages qutip_qip.algorithms qutip_qip.compiler - qutip_qip.decompositions + qutip_qip.decompose qutip_qip.device qutip_qip.operations qutip_qip.transpiler From 8edd1843910e519ead33016d5e1b5bb49f81ff88 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Wed, 14 Jul 2021 08:42:07 -0500 Subject: [PATCH 13/17] single decompose --- tests/gate_decompose/test_single_decompositions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gate_decompose/test_single_decompositions.py b/tests/gate_decompose/test_single_decompositions.py index 3754228cf..51859cedb 100644 --- a/tests/gate_decompose/test_single_decompositions.py +++ b/tests/gate_decompose/test_single_decompositions.py @@ -4,7 +4,7 @@ from qutip import Qobj, average_gate_fidelity -from qutip_qip.decompose.single_decompositions import (_ZYZ_rotation, _ZXZ_rotation, +from qutip_qip.decompose.decompose_single_qubit_gate import (_ZYZ_rotation, _ZXZ_rotation, ABC_decomposition, decompose_to_rotation_matrices) from qutip_qip.circuit import (decomposed_gates_to_circuit, compute_unitary) From 2ed82ddbb14efca90e2807fbabf79bcedcbc4863 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Wed, 14 Jul 2021 08:43:38 -0500 Subject: [PATCH 14/17] remove decompositions from docs --- .../_apidoc/qutip_qip.decompositions.rst | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 doc/source/_apidoc/qutip_qip.decompositions.rst diff --git a/doc/source/_apidoc/qutip_qip.decompositions.rst b/doc/source/_apidoc/qutip_qip.decompositions.rst deleted file mode 100644 index 8860433ba..000000000 --- a/doc/source/_apidoc/qutip_qip.decompositions.rst +++ /dev/null @@ -1,37 +0,0 @@ -qutip\_qip.decompositions package -================================= - -Submodules ----------- - -qutip\_qip.decompositions.decompositions\_extras module -------------------------------------------------------- - -.. automodule:: qutip_qip.decompositions.decompositions_extras - :members: - :undoc-members: - :show-inheritance: - -qutip\_qip.decompositions.general\_decompositions module --------------------------------------------------------- - -.. automodule:: qutip_qip.decompositions.general_decompositions - :members: - :undoc-members: - :show-inheritance: - -qutip\_qip.decompositions.single\_decompositions module -------------------------------------------------------- - -.. automodule:: qutip_qip.decompositions.single_decompositions - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: qutip_qip.decompositions - :members: - :undoc-members: - :show-inheritance: From 0bd556ef18504ae8f7cca85fbb5a9cf3f727f7a4 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Wed, 14 Jul 2021 08:46:34 -0500 Subject: [PATCH 15/17] decompose in circuit.py --- tests/test_circuit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 9d7257a70..f0afd731f 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -48,7 +48,7 @@ _para_gates ) -from qutip_qip.decompose.single_decompositions import _ZYZ_rotation +from qutip_qip.decompose.decompose_single_qubit_gate import _ZYZ_rotation import qutip as qp From 920c41615ba0cb3e9310346a44d48df7c8b6a1a8 Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Wed, 14 Jul 2021 17:20:57 +0200 Subject: [PATCH 16/17] simplify the check functions --- src/qutip_qip/decompose/_utility.py | 103 ++++-------------- .../decompose/decompose_single_qubit_gate.py | 32 +++--- tests/gate_decompose/test_utility.py | 77 +++---------- 3 files changed, 54 insertions(+), 158 deletions(-) diff --git a/src/qutip_qip/decompose/_utility.py b/src/qutip_qip/decompose/_utility.py index 2a43d8e13..e8b6e42f6 100644 --- a/src/qutip_qip/decompose/_utility.py +++ b/src/qutip_qip/decompose/_utility.py @@ -14,90 +14,32 @@ class GateError(Exception): """ pass -def check_input(input_gate): - """Verifies input is a valid quantum gate. - - Parameters - ---------- - input_gate : :class:`qutip.Qobj` - The matrix that's supposed to be decomposed should be a Qobj. - - Returns - ------- - bool - If the input is a valid quantum gate matrix, returns "True". - In the case of "False" being returned, this function will ensure no - decomposition scheme can proceed. - """ - # check if input is a qobj - qobj_check = isinstance(input_gate,Qobj) - if qobj_check == False: - raise TypeError("The input matrix is not a Qobj.") - else: - # check if input is square and a unitary - input_shape = input_gate.shape - # check if input is 1 row and 1 column matrix - if input_shape[0]==1: - raise ValueError("A 1-D Qobj is not a valid quantum gate.") - # check if the input is a rectangular matrix - if input_shape[0] != input_shape[1]: - raise ValueError("Input is not a square matrix.") - else: - unitary_check = Qobj.check_isunitary(input_gate) - return unitary_check - - -def check_input_shape(input_gate, num_of_qubits): - """Check if the shape of input gate is valid to act on input number of qubits. - If :math:`n` is the number of qubits in the circuit then a valid quantum gate - acting on these qubits must be of dimension :math:`2^n \\times 2^n`. +def check_gate(gate, num_qubits): + """Verifies input is a valid quantum gate. Parameters ---------- - input_gate : :class:`qutip.Qobj` + gate : :class:`qutip.Qobj` The matrix that's supposed to be decomposed should be a Qobj. - - num_of_qubits : int + num_qubits: Number of qubits in the circuit. - - Returns - ------- - bool - Returns "True" if the shape of input gate is valid to act on the number - of qubits in the circuit. - + Raises + ------ + TypeError + If the gate is not a Qobj. + ValueError + If the gate is not a unitary operator on qubits. """ - input_check_bool = check_input(input_gate) - if input_check_bool == True: - input_shape = input_gate.shape - return input_shape[0] == 2**num_of_qubits - else: - raise ValueError("Input is not unitary.") - - -def convert_qobj_gate_to_array(input_gate): - """Converts a valid unitary quantum gate to a numpy array. - - Parameters - ---------- - input_gate : :class:`qutip.Qobj` - The matrix that's supposed to be decomposed should be a Qobj. - - Returns - ------- - input_gate : `np.array` - The input is returned as a converted numpy array. - """ - input_check_bool = check_input(input_gate) - if input_check_bool == True: - input_to_array = Qobj.full(input_gate) - return(input_to_array) - else: + if not isinstance(gate, Qobj): + raise TypeError("The input matrix is not a Qobj.") + if not gate.check_isunitary(): 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_of_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. @@ -106,11 +48,8 @@ def extract_global_phase(input_gate, num_of_qubits): input_gate : :class:`qutip.Qobj` The matrix that's supposed to be decomposed should be a Qobj. """ - if check_input_shape(input_gate, num_of_qubits) == True: - input_array = convert_qobj_gate_to_array(input_gate) - determinant_of_input = np.linalg.det(input_array) - global_phase_angle = cmath.phase(determinant_of_input) - global_phase_angle = global_phase_angle/(2**num_of_qubits) - return(global_phase_angle) - else: - raise GateError("Gate shape does not match to the number of qubits in the circuit. ") + 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/decompose_single_qubit_gate.py index fcca22478..ecd17b266 100644 --- a/src/qutip_qip/decompose/decompose_single_qubit_gate.py +++ b/src/qutip_qip/decompose/decompose_single_qubit_gate.py @@ -2,8 +2,7 @@ import cmath from qutip import Qobj -from qutip_qip.decompose._utility import (check_input, check_input_shape, -convert_qobj_gate_to_array, extract_global_phase, MethodError, GateError) +from qutip_qip.decompose._utility import (check_gate, extract_global_phase, MethodError, GateError) from qutip_qip.circuit import QubitCircuit, Gate @@ -18,8 +17,9 @@ def _angles_for_ZYZ(input_gate, num_qubits=1): input_gate : :class:`qutip.Qobj` The gate matrix that's supposed to be decomposed should be a Qobj. """ - global_phase_angle = extract_global_phase(input_gate,num_qubits) - input_array = convert_qobj_gate_to_array(input_gate) + 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] @@ -35,7 +35,7 @@ def _angles_for_ZYZ(input_gate, num_qubits=1): -def _ZYZ_rotation(input_gate, num_of_qubits, target): +def _ZYZ_rotation(input_gate, num_qubits, target): r""" An input 1-qubit gate is expressed as a product of rotation matrices :math:`\textrm{R}_z` and :math:`\textrm{R}_y`. @@ -63,7 +63,7 @@ def _ZYZ_rotation(input_gate, num_of_qubits, target): return(Rz_alpha, Ry_theta, Rz_beta, Phase_gate) -def _ZXZ_rotation(input_gate, num_of_qubits, target): +def _ZXZ_rotation(input_gate, num_qubits, target): r""" An input 1-qubit gate is expressed as a product of rotation matrices :math:`\textrm{R}_z` and :math:`\textrm{R}_x`. @@ -98,7 +98,7 @@ def _ZXZ_rotation(input_gate, num_of_qubits, target): "ZXZ": _ZXZ_rotation, } # other combinations to add here -def decompose_to_rotation_matrices(input_gate, method, num_of_qubits, target=0): +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`. @@ -116,7 +116,7 @@ def decompose_to_rotation_matrices(input_gate, method, num_of_qubits, target=0): input_gate : :class:`qutip.Qobj` The matrix that's supposed to be decomposed should be a Qobj. - num_of_qubits : int + num_qubits : int Number of qubits being acted upon by input gate target : int @@ -146,23 +146,23 @@ def decompose_to_rotation_matrices(input_gate, method, num_of_qubits, target=0): :math:`\textrm{R}_i(\beta)`, and some global phase gate. """ try: - assert num_of_qubits == 1 + assert num_qubits == 1 except AssertionError: - if target is None and num_of_qubits >1: + 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 = _rotation_matrices_dictionary.keys() if str(method) in key: method = _rotation_matrices_dictionary[str(method)] - return(method(input_gate, num_of_qubits, target)) + return(method(input_gate, num_qubits, target)) 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_of_qubits, target): +def ABC_decomposition(input_gate, num_qubits, target): 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}`. @@ -193,14 +193,14 @@ def ABC_decomposition(input_gate, num_of_qubits, target): 1 gates forming :math:`\textrm{C}`, and some global phase gate. """ try: - assert num_of_qubits == 1 + assert num_qubits == 1 except AssertionError: - if target is None and num_of_qubits >1: + 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 = convert_qobj_gate_to_array(input_gate) + input_array = input_gate.full() input_array = input_array/cmath.rect(1,global_phase_angle) # separate all the elements a = input_array[0][0] diff --git a/tests/gate_decompose/test_utility.py b/tests/gate_decompose/test_utility.py index 27d2943d4..ee67af71b 100644 --- a/tests/gate_decompose/test_utility.py +++ b/tests/gate_decompose/test_utility.py @@ -1,88 +1,45 @@ - import numpy as np import cmath import pytest -from qutip.qobj import Qobj -from qutip_qip.decompose._utility import (check_input, check_input_shape, -convert_qobj_gate_to_array, extract_global_phase, MethodError, GateError) +from qutip import Qobj, qeye +from qutip_qip.decompose._utility import (check_gate, extract_global_phase, MethodError, GateError) from qutip_qip.operations import rx, z_gate, t_gate from qutip_qip.circuit import QubitCircuit, Gate -# Tests for check_input_shape - +# Tests for check_gate @pytest.mark.parametrize("invalid_input",[np.array([[1,1],[1,1]]),([[1,1],[1,1]]),1.5,3,(1,2,3,4),np.array([[],[]]),([[],[]]),()]) -def test_check_input_non_qobj(invalid_input): +def test_check_gate_non_qobj(invalid_input): """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_input(invalid_input) - -@pytest.mark.parametrize("matrix",[Qobj([[1, 2], [3, 4],[5,6]])]) -def test_check_input_non_square_matrix(matrix): - """Rectangular Qobj are identified correctly.""" - with pytest.raises(ValueError, match="Input is not a square matrix."): - check_input(matrix) - -@pytest.mark.parametrize("matrix",[Qobj([[1,4,3]]),Qobj([[1]]),Qobj([[2]]),Qobj([[]])]) -def test_check_input_1d_matrix(matrix): - """A 1D object is identified as invalid quantum gate""" - with pytest.raises(ValueError, match="A 1-D Qobj is not a valid quantum gate."): - check_input(matrix) - - -# TO DO : CHECK FOR LARGER NUMBER OF QUBITS -@pytest.mark.parametrize("unitary",[Qobj([[1,0],[0,-1]])]) -def test_check_input_valid_qobj(unitary): - """Checks if unitary innput is correctly identified. - """ - assert(check_input(unitary)==True) + check_gate(invalid_input, num_qubits=1) @pytest.mark.parametrize("non_unitary",[Qobj([[1,1],[0,1]])]) -def test_check_input_non_qobj(non_unitary): +def test_check_gate_non_unitary(non_unitary): """Checks if non-unitary input is correctly identified. """ - assert(check_input(non_unitary)==False) - -# Tests for check_input_shape -@pytest.mark.parametrize("unitary",[Qobj([[1,0],[0,-1]])]) -def test_check_input_shape_unitary_input(unitary): - """Checks if shape of input is correctly identified. - """ - assert(check_input_shape(unitary,1)==True) + with pytest.raises(ValueError, match="Input is not unitary."): + check_gate(non_unitary, num_qubits=1) -@pytest.mark.parametrize("non_unitary",[Qobj([[1,1],[0,1]])]) -def test_check_input_non_qobj(non_unitary): +@pytest.mark.parametrize("non_qubit_unitary",[qeye(4)]) +def test_check_gate_non_unitary(non_qubit_unitary): """Checks if non-unitary input is correctly identified. """ - with pytest.raises(ValueError, match="Input is not unitary."): - check_input_shape(non_unitary,1) + with pytest.raises(ValueError, match="Input is not a unitary on 2 qubits."): + check_gate(non_qubit_unitary, num_qubits=2) -# Tests for convert_qobj_gate_to_array -@pytest.mark.parametrize("valid_input",[Qobj([[1,0,0],[0,1,0],[0,0,1]]),rx(np.pi/2,3),z_gate(3),t_gate(3)]) -def test_one_qutrit_gates(valid_input): - """Checks if Qobj is converted to a numpy array. +@pytest.mark.parametrize("unitary",[Qobj([[1,0],[0,-1]])]) +def test_check_gate_unitary_input(unitary): + """Checks if shape of input is correctly identified. """ - assert(isinstance(convert_qobj_gate_to_array(valid_input),np.ndarray)) + # No error raised if it passes. + check_gate(unitary, num_qubits=1) -@pytest.mark.parametrize("non_unitary",[Qobj([[1,1],[0,1]])]) -def test_convert_qobj_gate_to_array(non_unitary): - """Checks if non-unitary input is correctly identified. - """ - with pytest.raises(ValueError, match="Input is not unitary."): - convert_qobj_gate_to_array(non_unitary) -# Tests for extract_global_phase 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) - -def test_extract_global_phase_valid_input_incorrect_number_of_qubits(): - """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)]]) - with pytest.raises(GateError, match="Gate shape does not match to the number of qubits in the circuit. "): - extract_global_phase(H,2) From 9daf3e43f424d76b6e236eb7a5b0e4ee36cb576e Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Thu, 15 Jul 2021 13:18:28 -0500 Subject: [PATCH 17/17] remoce extract global phase --- doc/source/_apidoc/qutip_qip.decompose.rst | 6 +- src/qutip_qip/decompose/__init__.py | 2 +- src/qutip_qip/decompose/_utility.py | 16 -- ...gle_qubit_gate.py => single_qubit_gate.py} | 167 +++++++++++------- .../test_single_decompositions.py | 64 ++++--- .../test_utility.py | 10 +- tests/test_circuit.py | 6 +- 7 files changed, 145 insertions(+), 126 deletions(-) rename src/qutip_qip/decompose/{decompose_single_qubit_gate.py => single_qubit_gate.py} (64%) rename tests/{gate_decompose => decompose}/test_single_decompositions.py (61%) rename tests/{gate_decompose => decompose}/test_utility.py (78%) diff --git a/doc/source/_apidoc/qutip_qip.decompose.rst b/doc/source/_apidoc/qutip_qip.decompose.rst index 1d0f4e18e..8b59f0d71 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 fa64a1338..590687920 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 e8b6e42f6..4b6b5175f 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 ecd17b266..c89f43836 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 51859cedb..1a430c31d 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 ee67af71b..cd7c29db9 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 f0afd731f..6aea0d79a 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):