From 8c805af09b239f40f6c58e0fcd6a91f68e8b53d0 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 23 Sep 2024 12:30:49 +0400 Subject: [PATCH 01/11] api ref --- doc/source/api-reference/qibo.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index 8e4d156269..d56c17318b 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -1958,6 +1958,12 @@ Partial trace .. autofunction:: qibo.quantum_info.partial_trace +Partial transpose +""""""""""""""""" + +.. autofunction:: qibo.quantum_info.partial_transpose + + Matrix exponentiation """"""""""""""""""""" From 053059bb55afd4511a67f74c806ee08038d45f79 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 23 Sep 2024 12:31:04 +0400 Subject: [PATCH 02/11] function --- src/qibo/quantum_info/linalg_operations.py | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/qibo/quantum_info/linalg_operations.py b/src/qibo/quantum_info/linalg_operations.py index 6fc48e6e74..b2c993b929 100644 --- a/src/qibo/quantum_info/linalg_operations.py +++ b/src/qibo/quantum_info/linalg_operations.py @@ -159,6 +159,29 @@ def partial_trace(state, traced_qubits: Union[List[int], Tuple[int]], backend=No return backend.np.einsum("abac->bc", state) +def partial_transpose(state, partition, backend=None): + """""" + backend = _check_backend(backend) + + nqubits = math.log2(state.shape[0]) + + if not nqubits.is_integer(): + raise_error(ValueError, f"dimensions of ``state`` must be a power of 2.") + + nqubits = int(nqubits) + + new_shape = list(range(2 * nqubits)) + for ind in partition: + new_shape[ind] = ind + nqubits + new_shape[ind + nqubits] = ind + new_shape = tuple(new_shape) + + reshaped = backend.np.reshape(state, [2] * (2 * nqubits)) + reshaped = backend.np.transpose(reshaped, new_shape) + + return backend.np.reshape(reshaped, state.shape) + + def matrix_exponentiation( phase: Union[float, complex], matrix, From 37504e51515fc5f5267f7f8ec5db4857435e07d6 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 23 Sep 2024 12:34:57 +0400 Subject: [PATCH 03/11] function --- src/qibo/quantum_info/linalg_operations.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/qibo/quantum_info/linalg_operations.py b/src/qibo/quantum_info/linalg_operations.py index b2c993b929..ca278424b3 100644 --- a/src/qibo/quantum_info/linalg_operations.py +++ b/src/qibo/quantum_info/linalg_operations.py @@ -160,7 +160,16 @@ def partial_trace(state, traced_qubits: Union[List[int], Tuple[int]], backend=No def partial_transpose(state, partition, backend=None): - """""" + """_summary_ + + Args: + state (ndarray): density matrix or statevector. + partition (_type_): _description_ + backend (_type_, optional): _description_. Defaults to None. + + Returns: + _type_: _description_ + """ backend = _check_backend(backend) nqubits = math.log2(state.shape[0]) @@ -170,6 +179,10 @@ def partial_transpose(state, partition, backend=None): nqubits = int(nqubits) + statevector = bool(len(state.shape) == 1) + if statevector: + state = backend.np.outer(state, backend.np.conj(state.T)) + new_shape = list(range(2 * nqubits)) for ind in partition: new_shape[ind] = ind + nqubits From a0f5ea95a31182573d411502a5a18283da495f16 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 23 Sep 2024 12:55:19 +0400 Subject: [PATCH 04/11] docstring --- src/qibo/quantum_info/linalg_operations.py | 23 ++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/qibo/quantum_info/linalg_operations.py b/src/qibo/quantum_info/linalg_operations.py index ca278424b3..ba264bcd2c 100644 --- a/src/qibo/quantum_info/linalg_operations.py +++ b/src/qibo/quantum_info/linalg_operations.py @@ -160,15 +160,30 @@ def partial_trace(state, traced_qubits: Union[List[int], Tuple[int]], backend=No def partial_transpose(state, partition, backend=None): - """_summary_ + """Return matrix resulting from the partial transposition of ``partition`` qubits in ``state``. + + Given quantum state :math:`\\rho \\in \\mathcal{H}_{A} \\otimes \\mathcal{H}_{B}`, + the partial transpose with respect to ``partition`` :math:`B` is given by + + .. math:: + \\begin{align} + \\rho^{T_{B}} &= \\sum_{jklm} \\, \\rho_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\ketbra{l}{m}^{T} \\\\ + &= \\sum_{jklm} \\, \\rho_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\ketbra{m}{l} \\\\ + &= \\sum_{jklm} \\, \\rho_{lm}^{kl} \\, \\ketbra{j}{k} \\otimes \\ketbra{l}{m} \\, , + \\end{align} + + where the superscript :math:`T` indicates the transposition operation, + and :math:`T_{B}` indicates transposition on ``partition`` :math:`B`. Args: state (ndarray): density matrix or statevector. - partition (_type_): _description_ - backend (_type_, optional): _description_. Defaults to None. + traced_qubits (Union[List[int], Tuple[int]]): indices of qubits to be transposed. + backend (:class:`qibo.backends.abstract.Backend`, optional): backend + to be used in the execution. If ``None``, it uses + :class:`qibo.backends.GlobalBackend`. Defaults to ``None``. Returns: - _type_: _description_ + ndarray: partially transposed operator :math:`\\rho^{T_{B}}`. """ backend = _check_backend(backend) From b883f0e3366a33e2adc92c7709209dd31f0be9c8 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 23 Sep 2024 13:23:28 +0400 Subject: [PATCH 05/11] typing --- src/qibo/quantum_info/linalg_operations.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/qibo/quantum_info/linalg_operations.py b/src/qibo/quantum_info/linalg_operations.py index ba264bcd2c..d852e42ba4 100644 --- a/src/qibo/quantum_info/linalg_operations.py +++ b/src/qibo/quantum_info/linalg_operations.py @@ -97,7 +97,9 @@ def anticommutator(operator_1, operator_2): return operator_1 @ operator_2 + operator_2 @ operator_1 -def partial_trace(state, traced_qubits: Union[List[int], Tuple[int]], backend=None): +def partial_trace( + state, traced_qubits: Union[List[int], Tuple[int, ...]], backend=None +): """Returns the density matrix resulting from tracing out ``traced_qubits`` from ``state``. Total number of qubits is inferred by the shape of ``state``. @@ -159,7 +161,9 @@ def partial_trace(state, traced_qubits: Union[List[int], Tuple[int]], backend=No return backend.np.einsum("abac->bc", state) -def partial_transpose(state, partition, backend=None): +def partial_transpose( + state, partition: Union[List[int], Tuple[int, ...]], backend=None +): """Return matrix resulting from the partial transposition of ``partition`` qubits in ``state``. Given quantum state :math:`\\rho \\in \\mathcal{H}_{A} \\otimes \\mathcal{H}_{B}`, @@ -167,7 +171,7 @@ def partial_transpose(state, partition, backend=None): .. math:: \\begin{align} - \\rho^{T_{B}} &= \\sum_{jklm} \\, \\rho_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\ketbra{l}{m}^{T} \\\\ + \\rho^{T_{B}} &= \\sum_{jklm} \\, \\rho_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\left(\\ketbra{l}{m}\\right)^{T} \\\\ &= \\sum_{jklm} \\, \\rho_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\ketbra{m}{l} \\\\ &= \\sum_{jklm} \\, \\rho_{lm}^{kl} \\, \\ketbra{j}{k} \\otimes \\ketbra{l}{m} \\, , \\end{align} From 71dcad9a31e33c27bc9ff56055a470dea0da0142 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 23 Sep 2024 14:30:01 +0400 Subject: [PATCH 06/11] docstring --- src/qibo/quantum_info/linalg_operations.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qibo/quantum_info/linalg_operations.py b/src/qibo/quantum_info/linalg_operations.py index d852e42ba4..ff91bb94fe 100644 --- a/src/qibo/quantum_info/linalg_operations.py +++ b/src/qibo/quantum_info/linalg_operations.py @@ -171,13 +171,15 @@ def partial_transpose( .. math:: \\begin{align} - \\rho^{T_{B}} &= \\sum_{jklm} \\, \\rho_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\left(\\ketbra{l}{m}\\right)^{T} \\\\ + \\rho^{T_{B}} &= \\sum_{jklm} \\, \\rho_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes + \\left(\\ketbra{l}{m}\\right)^{T} \\\\ &= \\sum_{jklm} \\, \\rho_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\ketbra{m}{l} \\\\ &= \\sum_{jklm} \\, \\rho_{lm}^{kl} \\, \\ketbra{j}{k} \\otimes \\ketbra{l}{m} \\, , \\end{align} where the superscript :math:`T` indicates the transposition operation, and :math:`T_{B}` indicates transposition on ``partition`` :math:`B`. + The total number of qubits is inferred by the shape of ``state``. Args: state (ndarray): density matrix or statevector. From 6dca6feeaef42bab7b119a153817dce63e208e1d Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 23 Sep 2024 14:30:06 +0400 Subject: [PATCH 07/11] test --- tests/test_quantum_info_operations.py | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/test_quantum_info_operations.py b/tests/test_quantum_info_operations.py index 3d97f1f63a..9c4e62e46a 100644 --- a/tests/test_quantum_info_operations.py +++ b/tests/test_quantum_info_operations.py @@ -7,6 +7,7 @@ commutator, matrix_power, partial_trace, + partial_transpose, ) from qibo.quantum_info.metrics import purity from qibo.quantum_info.random_ensembles import random_density_matrix, random_statevector @@ -113,6 +114,74 @@ def test_partial_trace(backend, density_matrix): backend.assert_allclose(traced, Id) +def _werner_state(p, backend): + zero, one = np.array([1, 0], dtype=complex), np.array([0, 1], dtype=complex) + psi = (np.kron(zero, one) - np.kron(one, zero)) / np.sqrt(2) + psi = np.outer(psi, np.conj(psi.T)) + psi = backend.cast(psi, dtype=psi.dtype) + + state = p * psi + (1 - p) * backend.identity_density_matrix(2, normalize=True) + + # partial transpose of two-qubit werner state is known analytically + transposed = (1 / 4) * np.array( + [ + [1 - p, 0, 0, -2 * p], + [0, p + 1, 0, 0], + [0, 0, p + 1, 0], + [-2 * p, 0, 0, 1 - p], + ], + dtype=complex, + ) + transposed = backend.cast(transposed, dtype=transposed.dtype) + + return state, transposed + + +@pytest.mark.parametrize("statevector", [False, True]) +@pytest.mark.parametrize("p", [1 / 5, 1 / 3, 1.0]) +def test_partial_transpose(backend, p, statevector): + with pytest.raises(ValueError): + state = random_density_matrix(3, backend=backend) + test = partial_transpose(state, [0], backend) + + zero, one = np.array([1, 0], dtype=complex), np.array([0, 1], dtype=complex) + psi = (np.kron(zero, one) - np.kron(one, zero)) / np.sqrt(2) + + if statevector: + # testing statevector + target = np.zeros((4, 4), dtype=complex) + target[0, 3] = -1 / 2 + target[1, 1] = 1 / 2 + target[2, 2] = 1 / 2 + target[3, 0] = -1 / 2 + target = backend.cast(target, dtype=target.dtype) + + psi = backend.cast(psi, dtype=psi.dtype) + + transposed = partial_transpose(psi, [0], backend=backend) + backend.assert_allclose(transposed, target) + else: + psi = np.outer(psi, np.conj(psi.T)) + psi = backend.cast(psi, dtype=psi.dtype) + + state = p * psi + (1 - p) * backend.identity_density_matrix(2, normalize=True) + + # partial transpose of two-qubit werner state is known analytically + target = (1 / 4) * np.array( + [ + [1 - p, 0, 0, -2 * p], + [0, p + 1, 0, 0], + [0, 0, p + 1, 0], + [-2 * p, 0, 0, 1 - p], + ], + dtype=complex, + ) + target = backend.cast(target, dtype=target.dtype) + + transposed = partial_transpose(state, [1], backend) + backend.assert_allclose(transposed, target) + + @pytest.mark.parametrize("power", [2, 2.0, "2"]) def test_matrix_power(backend, power): nqubits = 2 From a0df4bd2a8505508acc53b0b75eeecc954afb0eb Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 3 Oct 2024 16:25:39 +0400 Subject: [PATCH 08/11] batches --- src/qibo/quantum_info/linalg_operations.py | 58 +++++++++++++++------- tests/test_quantum_info_operations.py | 30 +++++++++-- 2 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/qibo/quantum_info/linalg_operations.py b/src/qibo/quantum_info/linalg_operations.py index ff91bb94fe..282a0bfdbe 100644 --- a/src/qibo/quantum_info/linalg_operations.py +++ b/src/qibo/quantum_info/linalg_operations.py @@ -162,58 +162,80 @@ def partial_trace( def partial_transpose( - state, partition: Union[List[int], Tuple[int, ...]], backend=None + operator, partition: Union[List[int], Tuple[int, ...]], backend=None ): - """Return matrix resulting from the partial transposition of ``partition`` qubits in ``state``. + """Return matrix after the partial transposition of ``partition`` qubits in ``operator``. - Given quantum state :math:`\\rho \\in \\mathcal{H}_{A} \\otimes \\mathcal{H}_{B}`, + Given a :math:`n`-qubit operator :math:`O \\in \\mathcal{H}_{A} \\otimes \\mathcal{H}_{B}`, the partial transpose with respect to ``partition`` :math:`B` is given by .. math:: \\begin{align} - \\rho^{T_{B}} &= \\sum_{jklm} \\, \\rho_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes + O^{T_{B}} &= \\sum_{jklm} \\, O_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\left(\\ketbra{l}{m}\\right)^{T} \\\\ - &= \\sum_{jklm} \\, \\rho_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\ketbra{m}{l} \\\\ - &= \\sum_{jklm} \\, \\rho_{lm}^{kl} \\, \\ketbra{j}{k} \\otimes \\ketbra{l}{m} \\, , + &= \\sum_{jklm} \\, O_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\ketbra{m}{l} \\\\ + &= \\sum_{jklm} \\, O_{lm}^{kl} \\, \\ketbra{j}{k} \\otimes \\ketbra{l}{m} \\, , \\end{align} where the superscript :math:`T` indicates the transposition operation, and :math:`T_{B}` indicates transposition on ``partition`` :math:`B`. - The total number of qubits is inferred by the shape of ``state``. + The total number of qubits is inferred by the shape of ``operator``. Args: - state (ndarray): density matrix or statevector. - traced_qubits (Union[List[int], Tuple[int]]): indices of qubits to be transposed. + operator (ndarray): :math:`1`- or :math:`2`-dimensional operator, or an array of + :math:`1`- or :math:`2`-dimensional operators, + partition (Union[List[int], Tuple[int, ...]]): indices of qubits to be transposed. backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used in the execution. If ``None``, it uses :class:`qibo.backends.GlobalBackend`. Defaults to ``None``. Returns: - ndarray: partially transposed operator :math:`\\rho^{T_{B}}`. + ndarray: Partially transposed operator(s) :math:`\\O^{T_{B}}`. """ backend = _check_backend(backend) - nqubits = math.log2(state.shape[0]) + shape = operator.shape + nstates = shape[0] + dims = shape[-1] + nqubits = math.log2(dims) if not nqubits.is_integer(): - raise_error(ValueError, f"dimensions of ``state`` must be a power of 2.") + raise_error( + ValueError, + f"dimensions of ``state`` (or states in a batch) must be a power of 2.", + ) + + if (len(shape) > 3) or (nstates == 0) or (len(shape) == 2 and nstates != dims): + raise_error( + TypeError, + "``operator`` must have dims either (k,), (k, k), (N, 1, k) or (N, k, k), " + + f"but has dims {shape}.", + ) nqubits = int(nqubits) - statevector = bool(len(state.shape) == 1) - if statevector: - state = backend.np.outer(state, backend.np.conj(state.T)) + if len(shape) == 1: + operator = backend.np.outer(operator, backend.np.conj(operator.T)) + elif len(shape) == 3 and shape[1] == 1: + operator = backend.np.einsum( + "aij,akl->aijkl", operator, backend.np.conj(operator) + ).reshape(nstates, dims, dims) - new_shape = list(range(2 * nqubits)) + new_shape = list(range(2 * nqubits + 1)) for ind in partition: + ind += 1 new_shape[ind] = ind + nqubits new_shape[ind + nqubits] = ind new_shape = tuple(new_shape) - reshaped = backend.np.reshape(state, [2] * (2 * nqubits)) + reshaped = backend.np.reshape(operator, [-1] + [2] * (2 * nqubits)) reshaped = backend.np.transpose(reshaped, new_shape) - return backend.np.reshape(reshaped, state.shape) + final_shape = (dims, dims) + if len(operator.shape) == 3: + final_shape = (nstates,) + final_shape + + return backend.np.reshape(reshaped, final_shape) def matrix_exponentiation( diff --git a/tests/test_quantum_info_operations.py b/tests/test_quantum_info_operations.py index 9c4e62e46a..e36e438499 100644 --- a/tests/test_quantum_info_operations.py +++ b/tests/test_quantum_info_operations.py @@ -137,12 +137,18 @@ def _werner_state(p, backend): return state, transposed +@pytest.mark.parametrize("batch", [False, True]) @pytest.mark.parametrize("statevector", [False, True]) @pytest.mark.parametrize("p", [1 / 5, 1 / 3, 1.0]) -def test_partial_transpose(backend, p, statevector): +def test_partial_transpose(backend, p, statevector, batch): with pytest.raises(ValueError): state = random_density_matrix(3, backend=backend) test = partial_transpose(state, [0], backend) + with pytest.raises(TypeError): + state = np.random.rand(2, 2, 2, 2).astype(complex) + state += 1j * np.random.rand(2, 2, 2, 2) + state = backend.cast(state, dtype=state.dtype) + test = partial_transpose(state, [1], backend=backend) zero, one = np.array([1, 0], dtype=complex), np.array([0, 1], dtype=complex) psi = (np.kron(zero, one) - np.kron(one, zero)) / np.sqrt(2) @@ -158,14 +164,26 @@ def test_partial_transpose(backend, p, statevector): psi = backend.cast(psi, dtype=psi.dtype) + if batch: + # the inner cast is required because of torch + psi = backend.cast([backend.cast([psi]) for _ in range(2)]) + transposed = partial_transpose(psi, [0], backend=backend) - backend.assert_allclose(transposed, target) + + if batch: + for j in range(2): + backend.assert_allclose(transposed[j], target) + else: + backend.assert_allclose(transposed, target) else: psi = np.outer(psi, np.conj(psi.T)) psi = backend.cast(psi, dtype=psi.dtype) state = p * psi + (1 - p) * backend.identity_density_matrix(2, normalize=True) + if batch: + state = backend.cast([state for _ in range(2)]) + # partial transpose of two-qubit werner state is known analytically target = (1 / 4) * np.array( [ @@ -179,7 +197,13 @@ def test_partial_transpose(backend, p, statevector): target = backend.cast(target, dtype=target.dtype) transposed = partial_transpose(state, [1], backend) - backend.assert_allclose(transposed, target) + + if batch: + print(transposed.shape) + for j in range(2): + backend.assert_allclose(transposed[j], target) + else: + backend.assert_allclose(transposed, target) @pytest.mark.parametrize("power", [2, 2.0, "2"]) From 1232ff166e8be2ce773a67dda35b91b1c2949c5c Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 04:46:58 +0000 Subject: [PATCH 09/11] Update tests/test_quantum_info_operations.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- tests/test_quantum_info_operations.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_quantum_info_operations.py b/tests/test_quantum_info_operations.py index e36e438499..d17596b2b6 100644 --- a/tests/test_quantum_info_operations.py +++ b/tests/test_quantum_info_operations.py @@ -199,7 +199,6 @@ def test_partial_transpose(backend, p, statevector, batch): transposed = partial_transpose(state, [1], backend) if batch: - print(transposed.shape) for j in range(2): backend.assert_allclose(transposed[j], target) else: From b9392910e6a119d60c303ad6ca4896e76df33b3d Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 10:19:52 +0400 Subject: [PATCH 10/11] use the function --- tests/test_quantum_info_operations.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/test_quantum_info_operations.py b/tests/test_quantum_info_operations.py index d17596b2b6..4d25e5c4a6 100644 --- a/tests/test_quantum_info_operations.py +++ b/tests/test_quantum_info_operations.py @@ -150,10 +150,10 @@ def test_partial_transpose(backend, p, statevector, batch): state = backend.cast(state, dtype=state.dtype) test = partial_transpose(state, [1], backend=backend) - zero, one = np.array([1, 0], dtype=complex), np.array([0, 1], dtype=complex) - psi = (np.kron(zero, one) - np.kron(one, zero)) / np.sqrt(2) - if statevector: + zero, one = np.array([1, 0], dtype=complex), np.array([0, 1], dtype=complex) + psi = (np.kron(zero, one) - np.kron(one, zero)) / np.sqrt(2) + # testing statevector target = np.zeros((4, 4), dtype=complex) target[0, 3] = -1 / 2 @@ -176,11 +176,7 @@ def test_partial_transpose(backend, p, statevector, batch): else: backend.assert_allclose(transposed, target) else: - psi = np.outer(psi, np.conj(psi.T)) - psi = backend.cast(psi, dtype=psi.dtype) - - state = p * psi + (1 - p) * backend.identity_density_matrix(2, normalize=True) - + state, target = _werner_state(p, backend) if batch: state = backend.cast([state for _ in range(2)]) From f594d4bfe3b17e8ae0e0e9d933055dd59a3241e3 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 09:12:06 +0000 Subject: [PATCH 11/11] Update src/qibo/quantum_info/linalg_operations.py Co-authored-by: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> --- src/qibo/quantum_info/linalg_operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/quantum_info/linalg_operations.py b/src/qibo/quantum_info/linalg_operations.py index 282a0bfdbe..971088a28c 100644 --- a/src/qibo/quantum_info/linalg_operations.py +++ b/src/qibo/quantum_info/linalg_operations.py @@ -174,7 +174,7 @@ def partial_transpose( O^{T_{B}} &= \\sum_{jklm} \\, O_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\left(\\ketbra{l}{m}\\right)^{T} \\\\ &= \\sum_{jklm} \\, O_{lm}^{jk} \\, \\ketbra{j}{k} \\otimes \\ketbra{m}{l} \\\\ - &= \\sum_{jklm} \\, O_{lm}^{kl} \\, \\ketbra{j}{k} \\otimes \\ketbra{l}{m} \\, , + &= \\sum_{jklm} \\, O_{ml}^{jk} \\, \\ketbra{j}{k} \\otimes \\ketbra{l}{m} \\, , \\end{align} where the superscript :math:`T` indicates the transposition operation,