diff --git a/doc/source/_apidoc/qutip_qip.decompose.rst b/doc/source/_apidoc/qutip_qip.decompose.rst deleted file mode 100644 index 8b59f0d7..00000000 --- a/doc/source/_apidoc/qutip_qip.decompose.rst +++ /dev/null @@ -1,21 +0,0 @@ -qutip\_qip.decompose package -============================ - -Submodules ----------- - -qutip\_qip.decompose.single\_qubit\_gate module ------------------------------------------------ - -.. automodule:: qutip_qip.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 1c3bf8d5..55d395e1 100644 --- a/doc/source/_apidoc/qutip_qip.rst +++ b/doc/source/_apidoc/qutip_qip.rst @@ -9,7 +9,6 @@ Subpackages qutip_qip.algorithms qutip_qip.compiler - qutip_qip.decompose qutip_qip.device qutip_qip.operations qutip_qip.transpiler @@ -33,6 +32,14 @@ qutip\_qip.circuit\_latex module :undoc-members: :show-inheritance: +qutip\_qip.decompose module +--------------------------- + +.. automodule:: qutip_qip.decompose + :members: + :undoc-members: + :show-inheritance: + qutip\_qip.gates module ----------------------- diff --git a/src/qutip_qip/_decomposition_functions/__init__.py b/src/qutip_qip/_decomposition_functions/__init__.py new file mode 100644 index 00000000..b30af1b9 --- /dev/null +++ b/src/qutip_qip/_decomposition_functions/__init__.py @@ -0,0 +1 @@ +from ._single_qubit_gate import * diff --git a/src/qutip_qip/decompose/single_qubit_gate.py b/src/qutip_qip/_decomposition_functions/_single_qubit_gate.py similarity index 53% rename from src/qutip_qip/decompose/single_qubit_gate.py rename to src/qutip_qip/_decomposition_functions/_single_qubit_gate.py index c0b98381..5ebff508 100644 --- a/src/qutip_qip/decompose/single_qubit_gate.py +++ b/src/qutip_qip/_decomposition_functions/_single_qubit_gate.py @@ -2,7 +2,11 @@ import cmath from qutip import Qobj -from qutip_qip.decompose._utility import check_gate, MethodError, GateError +from qutip_qip._decomposition_functions._utility import ( + check_gate, + MethodError, + GateError, +) from qutip_qip.circuit import QubitCircuit, Gate @@ -136,78 +140,6 @@ def _ZXZ_rotation(input_gate, target, num_qubits=1): return (Rz_alpha, Rx_theta, Rz_beta, Phase_gate) -_rotation_matrices_dictionary = { - "ZYZ": _ZYZ_rotation, - "ZXZ": _ZXZ_rotation, -} # other combinations to add here - - -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}`. - - Based on Lemma 4.1 of https://arxiv.org/abs/quant-ph/9503016v1 - - .. math:: - - U = \begin{bmatrix} - a & b \\ - -b^* & a^* \\ - \end{bmatrix} = \textrm{R}_i(\alpha) \textrm{R}_j(\theta) \textrm{R}_i(\beta) - - Parameters - ---------- - 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 - * - 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)` - - 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}_i(\alpha)`, :math:`\textrm{R}_j(\theta)`, - :math:`\textrm{R}_i(\beta)`, and some global phase gate. - """ - try: - assert num_qubits == 1 - except AssertionError: - if target is None and num_qubits > 1: - raise GateError( - "This method is valid for single qubit gates only. Provide a target qubit for single qubit gate." - ) - - key = _rotation_matrices_dictionary.keys() - if str(method) in key: - method = _rotation_matrices_dictionary[str(method)] - return method(input_gate, target, 1) - - else: - raise MethodError("Invalid method chosen.") - - # Functions for ABC_decomposition @@ -264,89 +196,3 @@ def _ZYZ_pauli_X(input_gate, target, num_qubits=1): ) 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}_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} \sigma_k \textrm{B} \sigma_k \textrm{C} - - Here, - - .. 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:`.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 - 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_qubits == 1 - except AssertionError: - if target is None and num_qubits > 1: - raise GateError( - "This method is valid for single qubit gates only. Provide a target qubit for single qubit gate." - ) - - key = _rotation_pauli_matrices_dictionary.keys() - if str(method) in key: - method = _rotation_pauli_matrices_dictionary[str(method)] - return method(input_gate, target, 1) - - else: - raise MethodError("Invalid method chosen.") diff --git a/src/qutip_qip/decompose/_utility.py b/src/qutip_qip/_decomposition_functions/_utility.py similarity index 100% rename from src/qutip_qip/decompose/_utility.py rename to src/qutip_qip/_decomposition_functions/_utility.py diff --git a/src/qutip_qip/decompose.py b/src/qutip_qip/decompose.py new file mode 100644 index 00000000..44b98352 --- /dev/null +++ b/src/qutip_qip/decompose.py @@ -0,0 +1,115 @@ +import numpy as np +import cmath + +from qutip import Qobj +from qutip_qip._decomposition_functions._utility import ( + check_gate, + MethodError, + GateError, +) + +from qutip_qip.circuit import QubitCircuit, Gate + +from qutip_qip._decomposition_functions._single_qubit_gate import ( + _ZYZ_rotation, + _ZXZ_rotation, + _ZYZ_pauli_X, +) + +_single_decompositions_dictionary = { + "ZYZ": _ZYZ_rotation, + "ZXZ": _ZXZ_rotation, + "ZYZ_PauliX": _ZYZ_pauli_X, +} # other combinations to add here + + +def decompose_one_qubit_gate(input_gate, method, num_qubits, target=0): + r""" An input 1-qubit gate is expressed as a product of rotation matrices + :math:`\textrm{R}_i` and :math:`\textrm{R}_j` or as a product of rotation matrices + :math:`\textrm{R}_i` and :math:`\textrm{R}_j` and a Pauli :math:`\sigma_k`. + + Here, :math:`i \neq j` and :math:`i, j, k \in {x, y, z}`. + + Based on Lemma 4.1 and Lemma 4.3 of https://arxiv.org/abs/quant-ph/9503016v1 respectively. + + .. math:: + + U = \begin{bmatrix} + a & b \\ + -b^* & a^* \\ + \end{bmatrix} = \textrm{R}_i(\alpha) \textrm{R}_j(\theta) \textrm{R}_i(\beta) = \textrm{A} \sigma_k \textrm{B} \sigma_k \textrm{C} + + Here, + + * :math:`\textrm{A} = \textrm{R}_i(\alpha) \textrm{R}_j \left(\frac{\theta}{2} \right)` + + * :math:`\textrm{B} = \textrm{R}_j \left(\frac{-\theta}{2} \right) \textrm{R}_i \left(\frac{- \left(\alpha + \beta \right)}{2} \right)` + + * :math:`\textrm{C} = \textrm{R}_i \left(\frac{\left(-\alpha + \beta \right)}{2} \right)` + + + Parameters + ---------- + input_gate : :class:`qutip.Qobj` + The matrix that's supposed to be decomposed should be a Qobj. + + num_qubits : int + Number of qubits being acted upon by input gate + + target : int + If the circuit contains more than 1 qubits then provide target for + single qubit gate. + + method : string + Name of the preferred decomposition method + + .. list-table:: + :widths: auto + :header-rows: 1 + + * - Method Key + - Method + * - ZYZ + - :math:`\textrm{R}_z(\alpha) \textrm{R}_y(\theta) \textrm{R}_z(\beta)` + * - ZXZ + - :math:`\textrm{R}_z(\alpha) \textrm{R}_x(\theta) \textrm{R}_z(\beta)` + * - ZYZ_PauliX + - :math:`\textrm{A} \sigma_k \textrm{B} \sigma_k \textrm{C}` :math:`\forall k =x, i =z, j=y` + + + .. note:: + This function is under construction. As more combinations are + added, above table will be updated with their respective keys. + + + Returns + ------- + tuple + The gates in the decomposition are returned as a tuple of :class:`Gate` + objects. + + When the input gate is decomposed to product of rotation matrices - tuple + will contain 4 elements per each :math:`1 \times 1` + qubit gate - :math:`\textrm{R}_i(\alpha)`, :math:`\textrm{R}_j(\theta)`, + :math:`\textrm{R}_i(\beta)`, and some global phase gate. + + When the input gate is decomposed to product of rotation matrices and Pauli - + tuple will contain 6 elements per each :math:`1 \times 1` + qubit gate - 2 gates forming :math:`\textrm{A}`, 2 gates forming :math:`\textrm{B}`, + 1 gates forming :math:`\textrm{C}`, and some global phase gate. + """ + try: + assert num_qubits == 1 + except AssertionError: + if target is None and num_qubits > 1: + raise GateError( + "This method is valid for single qubit gates only. Provide a target qubit for single qubit gate." + ) + + key = _single_decompositions_dictionary.keys() + if str(method) in key: + method = _single_decompositions_dictionary[str(method)] + return method(input_gate, target, 1) + + else: + raise MethodError("Invalid method chosen.") diff --git a/src/qutip_qip/decompose/__init__.py b/src/qutip_qip/decompose/__init__.py deleted file mode 100644 index 59068792..00000000 --- a/src/qutip_qip/decompose/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .single_qubit_gate import * diff --git a/tests/decompose/test_single_qubit_gate_decompositions.py b/tests/decomposition_functions/test_single_qubit_gate_decompositions.py similarity index 76% rename from tests/decompose/test_single_qubit_gate_decompositions.py rename to tests/decomposition_functions/test_single_qubit_gate_decompositions.py index 0d21c5c3..2583cf3b 100644 --- a/tests/decompose/test_single_qubit_gate_decompositions.py +++ b/tests/decomposition_functions/test_single_qubit_gate_decompositions.py @@ -4,14 +4,12 @@ from qutip import Qobj, average_gate_fidelity, rand_unitary, sigmax, sigmay, sigmaz -from qutip_qip.decompose.single_qubit_gate import ( +from qutip_qip._decomposition_functions._single_qubit_gate import ( _ZYZ_rotation, _ZXZ_rotation, - ABC_decomposition, _ZYZ_pauli_X, - decompose_to_rotation_matrices, ) - +from qutip_qip.decompose import decompose_one_qubit_gate from qutip_qip.circuit import decomposed_gates_to_circuit, compute_unitary from qutip_qip.operations.gates import snot, sqrtnot @@ -45,10 +43,10 @@ def test_single_qubit_to_rotations(gate, method): @pytest.mark.parametrize( "gate", [H, sigmax, sigmay, sigmaz, SQRTNOT, S, T, rand_unitary(2)] ) -@pytest.mark.parametrize("method", ["ZXZ", "ZYZ"]) +@pytest.mark.parametrize("method", ["ZXZ", "ZYZ", "ZYZ_PauliX"]) def test_check_single_qubit_to_decompose_to_rotations(gate, method): """Initial matrix and product of final decompositions are same within some phase.""" - gate_list = decompose_to_rotation_matrices(gate, method, target, num_qubits) + gate_list = decompose_one_qubit_gate(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( @@ -71,18 +69,8 @@ def test_output_is_tuple(gate, method): @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.""" - gate_list = decompose_to_rotation_matrices(gate, method, num_qubits, target) - assert isinstance(gate_list, tuple) - - -@pytest.mark.parametrize( - "gate", [H, sigmax, sigmay, sigmaz, SQRTNOT, S, T, rand_unitary(2)] -) -@pytest.mark.parametrize("method", ["ZYZ_PauliX"]) +@pytest.mark.parametrize("method", ["ZXZ", "ZYZ", "ZYZ_PauliX"]) def test_check_single_qubit_to_decompose_to_rotations(gate, method): """Initial matrix and product of final decompositions are same within some phase.""" - gate_list = ABC_decomposition(gate, method, num_qubits, target) + gate_list = decompose_one_qubit_gate(gate, method, num_qubits, target) assert isinstance(gate_list, tuple) diff --git a/tests/decompose/test_utility.py b/tests/decomposition_functions/test_utility.py similarity index 93% rename from tests/decompose/test_utility.py rename to tests/decomposition_functions/test_utility.py index 0ecc9c7f..b2a85790 100644 --- a/tests/decompose/test_utility.py +++ b/tests/decomposition_functions/test_utility.py @@ -3,7 +3,11 @@ import pytest from qutip import Qobj, qeye -from qutip_qip.decompose._utility import check_gate, MethodError, GateError +from qutip_qip._decomposition_functions._utility import ( + check_gate, + MethodError, + GateError, +) from qutip_qip.operations import rx, z_gate, t_gate from qutip_qip.circuit import QubitCircuit, Gate diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 6aea0d79..278b119f 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -48,7 +48,7 @@ _para_gates ) -from qutip_qip.decompose.single_qubit_gate import _ZYZ_rotation +from qutip_qip._decomposition_functions._single_qubit_gate import _ZYZ_rotation import qutip as qp