diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index fb7099e2b7..7e9d508bfa 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -1912,6 +1912,24 @@ Frame Potential .. autofunction:: qibo.quantum_info.frame_potential +Linear Algebra Operations +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Collection of linear algebra operations that are commonly used in quantum information theory. + + +Commutator +"""""""""" + +.. autofunction:: qibo.quantum_info.commutator + + +Anticommutator +"""""""""""""" + +.. autofunction:: qibo.quantum_info.anticommutator + + Quantum Networks ^^^^^^^^^^^^^^^^ diff --git a/src/qibo/quantum_info/__init__.py b/src/qibo/quantum_info/__init__.py index 8d9400104e..6756df73aa 100644 --- a/src/qibo/quantum_info/__init__.py +++ b/src/qibo/quantum_info/__init__.py @@ -3,6 +3,7 @@ from qibo.quantum_info.entanglement import * from qibo.quantum_info.entropies import * from qibo.quantum_info.metrics import * +from qibo.quantum_info.operations import * from qibo.quantum_info.quantum_networks import * from qibo.quantum_info.random_ensembles import * from qibo.quantum_info.superoperator_transformations import * diff --git a/src/qibo/quantum_info/operations.py b/src/qibo/quantum_info/operations.py new file mode 100644 index 0000000000..29e4ea08f1 --- /dev/null +++ b/src/qibo/quantum_info/operations.py @@ -0,0 +1,93 @@ +"""Module with the most common linear algebra operations for quantum information.""" + +from qibo.config import raise_error + + +def commutator(operator_1, operator_2): + """Returns the commutator of ``operator_1`` and ``operator_2``. + + The commutator of two matrices :math:`A` and :math:`B` is given by + + .. math:: + [A, B] = A \\, B - B \\, A \\,. + + Args: + operator_1 (ndarray): First operator. + operator_2 (ndarray): Second operator. + + Returns: + ndarray: Commutator of ``operator_1`` and ``operator_2``. + """ + if ( + (len(operator_1.shape) >= 3) + or (len(operator_1) == 0) + or (len(operator_1.shape) == 2 and operator_1.shape[0] != operator_1.shape[1]) + ): + raise_error( + TypeError, + f"``operator_1`` must have shape (k,k), but have shape {operator_1.shape}.", + ) + + if ( + (len(operator_2.shape) >= 3) + or (len(operator_2) == 0) + or (len(operator_2.shape) == 2 and operator_2.shape[0] != operator_2.shape[1]) + ): + raise_error( + TypeError, + f"``operator_2`` must have shape (k,k), but have shape {operator_2.shape}.", + ) + + if operator_1.shape != operator_2.shape: + raise_error( + TypeError, + "``operator_1`` and ``operator_2`` must have the same shape, " + + f"but {operator_1.shape} != {operator_2.shape}", + ) + + return operator_1 @ operator_2 - operator_2 @ operator_1 + + +def anticommutator(operator_1, operator_2): + """Returns the anticommutator of ``operator_1`` and ``operator_2``. + + The anticommutator of two matrices :math:`A` and :math:`B` is given by + + .. math:: + \\{A, B\\} = A \\, B + B \\, A \\,. + + Args: + operator_1 (ndarray): First operator. + operator_2 (ndarray): Second operator. + + Returns: + ndarray: Anticommutator of ``operator_1`` and ``operator_2``. + """ + if ( + (len(operator_1.shape) >= 3) + or (len(operator_1) == 0) + or (len(operator_1.shape) == 2 and operator_1.shape[0] != operator_1.shape[1]) + ): + raise_error( + TypeError, + f"``operator_1`` must have shape (k,k), but have shape {operator_1.shape}.", + ) + + if ( + (len(operator_2.shape) >= 3) + or (len(operator_2) == 0) + or (len(operator_2.shape) == 2 and operator_2.shape[0] != operator_2.shape[1]) + ): + raise_error( + TypeError, + f"``operator_2`` must have shape (k,k), but have shape {operator_2.shape}.", + ) + + if operator_1.shape != operator_2.shape: + raise_error( + TypeError, + "``operator_1`` and ``operator_2`` must have the same shape, " + + f"but {operator_1.shape} != {operator_2.shape}", + ) + + return operator_1 @ operator_2 + operator_2 @ operator_1 diff --git a/tests/test_quantum_info_operations.py b/tests/test_quantum_info_operations.py new file mode 100644 index 0000000000..0dae19ac74 --- /dev/null +++ b/tests/test_quantum_info_operations.py @@ -0,0 +1,77 @@ +import numpy as np +import pytest + +from qibo import matrices +from qibo.quantum_info.operations import anticommutator, commutator + + +def test_commutator(backend): + matrix_1 = np.random.rand(2, 2, 2) + matrix_1 = backend.cast(matrix_1, dtype=matrix_1.dtype) + + matrix_2 = np.random.rand(2, 2) + matrix_2 = backend.cast(matrix_2, dtype=matrix_2.dtype) + + matrix_3 = np.random.rand(3, 3) + matrix_3 = backend.cast(matrix_3, dtype=matrix_3.dtype) + + with pytest.raises(TypeError): + test = commutator(matrix_1, matrix_2) + with pytest.raises(TypeError): + test = commutator(matrix_2, matrix_1) + with pytest.raises(TypeError): + test = commutator(matrix_2, matrix_3) + + I, X, Y, Z = matrices.I, matrices.X, matrices.Y, matrices.Z + I = backend.cast(I, dtype=I.dtype) + X = backend.cast(X, dtype=X.dtype) + Y = backend.cast(Y, dtype=Y.dtype) + Z = backend.cast(Z, dtype=Z.dtype) + + comm = commutator(X, I) + backend.assert_allclose(comm, 0.0) + + comm = commutator(X, X) + backend.assert_allclose(comm, 0.0) + + comm = commutator(X, Y) + backend.assert_allclose(comm, 2j * Z) + + comm = commutator(X, Z) + backend.assert_allclose(comm, -2j * Y) + + +def test_anticommutator(backend): + matrix_1 = np.random.rand(2, 2, 2) + matrix_1 = backend.cast(matrix_1, dtype=matrix_1.dtype) + + matrix_2 = np.random.rand(2, 2) + matrix_2 = backend.cast(matrix_2, dtype=matrix_2.dtype) + + matrix_3 = np.random.rand(3, 3) + matrix_3 = backend.cast(matrix_3, dtype=matrix_3.dtype) + + with pytest.raises(TypeError): + test = anticommutator(matrix_1, matrix_2) + with pytest.raises(TypeError): + test = anticommutator(matrix_2, matrix_1) + with pytest.raises(TypeError): + test = anticommutator(matrix_2, matrix_3) + + I, X, Y, Z = matrices.I, matrices.X, matrices.Y, matrices.Z + I = backend.cast(I, dtype=I.dtype) + X = backend.cast(X, dtype=X.dtype) + Y = backend.cast(Y, dtype=Y.dtype) + Z = backend.cast(Z, dtype=Z.dtype) + + anticomm = anticommutator(X, I) + backend.assert_allclose(anticomm, 2 * X) + + anticomm = anticommutator(X, X) + backend.assert_allclose(anticomm, 2 * I) + + anticomm = anticommutator(X, Y) + backend.assert_allclose(anticomm, 0.0) + + anticomm = anticommutator(X, Z) + backend.assert_allclose(anticomm, 0.0)