diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index eac2fb027c..9ee57772ee 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -2175,6 +2175,12 @@ To Chi .. autofunction:: qibo.quantum_info.to_chi +To Stinespring +"""""""""""""" + +.. autofunction:: qibo.quantum_info.to_stinespring + + Choi to Liouville """"""""""""""""" diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index da277cb272..fa7d2902ba 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -1,7 +1,7 @@ """Module with the most commom superoperator transformations.""" import warnings -from typing import Optional +from typing import List, Optional, Tuple, Union import numpy as np from scipy.optimize import minimize @@ -303,6 +303,50 @@ def to_chi( return channel +def to_stinespring( + channel, + partition: Optional[Union[List[int], Tuple[int, ...]]] = None, + nqubits: Optional[int] = None, + initial_state_env=None, + backend=None, +): + """Convert quantum ``channel`` :math:`U` to its Stinespring representation :math:`U_{0}`. + + It uses the Kraus representation as an intermediate step. + + Args: + channel (ndarray): quantum channel. + nqubits (int, optional): total number of qubits in the system that is + interacting with the environment. Must be equal or greater than + the number of qubits ``channel`` acts on. If ``None``, + defaults to the number of qubits in ``channel``. + Defauts to ``None``. + initial_state_env (ndarray, optional): statevector representing the + initial state of the enviroment. If ``None``, it assumes the + environment in its ground state. Defaults to ``None``. + 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: Quantum channel in its Stinespring representation :math:`U_{0}`. + """ + backend = _check_backend(backend) + + if partition is None: + nqubits_channel = int(np.log2(channel.shape[-1])) + partition = tuple(range(nqubits_channel)) + + channel = kraus_to_stinespring( + [(partition, channel)], + nqubits=nqubits, + initial_state_env=initial_state_env, + backend=backend, + ) + + return channel + + def choi_to_liouville(choi_super_op, order: str = "row", backend=None): """Converts Choi representation :math:`\\Lambda` of quantum channel to its Liouville representation :math:`\\mathcal{E}`. @@ -320,7 +364,6 @@ def choi_to_liouville(choi_super_op, order: str = "row", backend=None): \\Lambda_{\\alpha\\beta, \\, \\gamma\\delta} \\mapsto \\Lambda_{\\delta\\beta, \\, \\gamma\\alpha} \\equiv \\mathcal{E} - Args: choi_super_op: Choi representation of quantum channel. order (str, optional): If ``"row"``, reshuffling is performed @@ -640,7 +683,7 @@ def choi_to_stinespring( if nqubits is None: nqubits = int(np.log2(kraus_ops[0].shape[0])) - nqubits_list = [tuple(range(nqubits)) for _ in range(len(kraus_ops))] + nqubits_list = [tuple(range(nqubits))] * len(kraus_ops) kraus_ops = list(zip(nqubits_list, kraus_ops)) diff --git a/tests/test_quantum_info_superoperator_transformations.py b/tests/test_quantum_info_superoperator_transformations.py index 4f382b6300..03f0ae2baf 100644 --- a/tests/test_quantum_info_superoperator_transformations.py +++ b/tests/test_quantum_info_superoperator_transformations.py @@ -3,6 +3,7 @@ from qibo import matrices from qibo.config import PRECISION_TOL +from qibo.quantum_info.linalg_operations import partial_trace from qibo.quantum_info.random_ensembles import random_density_matrix, random_statevector from qibo.quantum_info.superoperator_transformations import ( chi_to_choi, @@ -40,6 +41,7 @@ to_choi, to_liouville, to_pauli_liouville, + to_stinespring, unvectorization, vectorization, ) @@ -363,6 +365,28 @@ def test_to_chi(backend, normalize, order, pauli_order): backend.assert_allclose(chi, test_chi, atol=PRECISION_TOL) +@pytest.mark.parametrize("partition", [None, (0,)]) +@pytest.mark.parametrize("test_a0", [test_a0]) +def test_to_stinespring(backend, test_a0, partition): + test_a0_ = backend.cast(test_a0) + state = random_density_matrix(2, seed=8, backend=backend) + + target = test_a0_ @ state @ backend.np.conj(test_a0_.T) + + environment = (1, 2) + + global_state = backend.identity_density_matrix(len(environment), normalize=True) + global_state = backend.np.kron(state, global_state) + + stinespring = to_stinespring( + test_a0_, partition=partition, nqubits=len(environment) + 1, backend=backend + ) + stinespring = stinespring @ global_state @ backend.np.conj(stinespring.T) + stinespring = partial_trace(stinespring, traced_qubits=environment, backend=backend) + + backend.assert_allclose(stinespring, target, atol=PRECISION_TOL) + + @pytest.mark.parametrize("test_superop", [test_superop]) @pytest.mark.parametrize("order", ["row", "column"]) def test_choi_to_liouville(backend, order, test_superop):