Exemple #1
0
def test_symmetric_extension_hierarchy_four_bell_with_resource_state():
    """Symmetric extension hierarchy for four Bell states and resource state."""
    e_0, e_1 = basis(2, 0), basis(2, 1)
    e_00, e_11 = np.kron(e_0, e_0), np.kron(e_1, e_1)

    eps = 1 / 2
    eps_state = np.sqrt((1 + eps) / 2) * e_00 + np.sqrt((1 - eps) / 2) * e_11
    eps_dm = eps_state * eps_state.conj().T

    states = [
        np.kron(bell(0) * bell(0).conj().T, eps_dm),
        np.kron(bell(1) * bell(1).conj().T, eps_dm),
        np.kron(bell(2) * bell(2).conj().T, eps_dm),
        np.kron(bell(3) * bell(3).conj().T, eps_dm),
    ]

    # Ensure we are checking the correct partition of the states.
    states = [
        swap(states[0], [2, 3], [2, 2, 2, 2]),
        swap(states[1], [2, 3], [2, 2, 2, 2]),
        swap(states[2], [2, 3], [2, 2, 2, 2]),
        swap(states[3], [2, 3], [2, 2, 2, 2]),
    ]

    res = symmetric_extension_hierarchy(states=states, probs=None, level=2)
    exp_res = 1 / 2 * (1 + np.sqrt(1 - eps**2))

    np.testing.assert_equal(np.isclose(res, exp_res), True)
Exemple #2
0
def _operator_is_product(rho: np.ndarray,
                         dim: Union[int, List[int]] = None) -> [int, bool]:
    """
    Determine if a given matrix is a product operator.

    Given an input `rho` provided as a matrix, determine if it is a product state.
    :param rho: The matrix to check.
    :param dim: The dimension of the matrix
    :return: :code:`True` if :code:`rho` is product and :code:`False` otherwise.
    """
    if dim is None:
        dim_x = rho.shape
        sqrt_dim = np.round(np.sqrt(dim_x))

        dim = np.array([[sqrt_dim[0], sqrt_dim[0]], [sqrt_dim[1],
                                                     sqrt_dim[1]]])

    if isinstance(dim, list):
        dim = np.array(dim)

    num_sys = len(dim)

    # Allow the user to enter a vector for `dim` if `rho` is square.
    if min(dim.shape) == 1 or len(dim.shape) == 1:
        dim = dim.T.flatten()
        dim = np.array([dim, dim])

    op_1 = rho.reshape(int(np.prod(np.prod(dim))), 1)
    perm = swap(np.array(list(range(2 * num_sys))), [1, 2], [2, num_sys]) + 1
    perm_dim = np.concatenate((dim[1, :].astype(int), dim[0, :].astype(int)))
    op_3 = permute_systems(op_1, perm, perm_dim).reshape(-1, 1)

    return is_product(op_3, np.prod(dim, axis=0).astype(int))
Exemple #3
0
def _operator_schmidt_rank(rho: np.ndarray, dim: Union[int, List[int], np.ndarray] = None) -> float:
    """
    Operator Schmidt rank of variable.

    If the input is provided as a density operator instead of a vector, compute the Schmidt rank.
    """
    if dim is None:
        dim_x = rho.shape
        sqrt_dim = np.round(np.sqrt(dim_x))

        dim = np.array([[sqrt_dim[0], sqrt_dim[0]], [sqrt_dim[1], sqrt_dim[1]]])

    if isinstance(dim, list):
        dim = np.array(dim)

    if isinstance(dim, int):
        dim = np.array([dim, len(rho) / dim], dtype=int)
        dim[1] = np.round(dim[1])

    if min(dim.shape) == 1 or len(dim.shape) == 1:
        dim = np.array([dim, dim])

    op_1 = rho.reshape(int(np.prod(np.prod(dim))), 1)
    swap_dim = np.concatenate((dim[1, :].astype(int), dim[0, :].astype(int)))
    op_2 = swap(op_1, [2, 3], swap_dim).reshape(-1, 1)

    return schmidt_rank(op_2, np.prod(dim, axis=0).astype(int))
Exemple #4
0
def swap_operator(dim: Union[List[int], int],
                  is_sparse: bool = False) -> np.ndarray:
    r"""
    Produce a unitary operator that swaps two subsystems.

    Provides the unitary operator that swaps two copies of :code:`dim`-dimensional space. If the two
    subsystems are not of the same dimension, :code:`dim` should be a 1-by-2 vector containing the
    dimension of the subsystems.

    Examples
    ==========

    The :math:`2`-dimensional swap operator is given by the following matrix

    .. math::
        X_2 =
        \begin{pmatrix}
            1 & 0 & 0 & 0 \\
            0 & 0 & 1 & 0 \\
            0 & 1 & 0 & 0 \\
            0 & 0 & 0 & 1
        \end{pmatrix}

    Using :code:`toqito` we can obtain this matrix as follows.

    >>> from toqito.perms import swap_operator
    >>> swap_operator(2)
    [[1., 0., 0., 0.],
     [0., 0., 1., 0.],
     [0., 1., 0., 0.],
     [0., 0., 0., 1.]]

    The :math:`3`-dimensional operator may be obtained using :code:`toqito` as follows.

    >>> from toqito.perms import swap_operator
    >>> swap_operator(3)
    [[1., 0., 0., 0., 0., 0., 0., 0., 0.],
     [0., 0., 0., 1., 0., 0., 0., 0., 0.],
     [0., 0., 0., 0., 0., 0., 1., 0., 0.],
     [0., 1., 0., 0., 0., 0., 0., 0., 0.],
     [0., 0., 0., 0., 1., 0., 0., 0., 0.],
     [0., 0., 0., 0., 0., 0., 0., 1., 0.],
     [0., 0., 1., 0., 0., 0., 0., 0., 0.],
     [0., 0., 0., 0., 0., 1., 0., 0., 0.],
     [0., 0., 0., 0., 0., 0., 0., 0., 1.]]

    :param dim: The dimensions of the subsystems.
    :param is_sparse: Sparse if :code:`True` and non-sparse if :code:`False`.
    :return: The swap operator of dimension :code:`dim`.
    """
    # Allow the user to enter a single number for dimension.
    if isinstance(dim, int):
        dim = np.array([dim, dim])

    # Swap the rows of the identity appropriately.
    return swap(iden(int(np.prod(dim)), is_sparse), [1, 2], dim, True)
Exemple #5
0
def test_swap_vector_1():
    """Tests swap operation on vector."""
    test_vec = np.array([1, 2, 3, 4])

    expected_res = np.array([1, 3, 2, 4])

    res = swap(test_vec)

    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)
Exemple #6
0
def test_swap_matrix():
    """Tests swap operation on matrix."""
    test_mat = np.array([[1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15],
                         [4, 8, 12, 16]])

    expected_res = np.array([[1, 9, 5, 13], [3, 11, 7, 15], [2, 10, 6, 14],
                             [4, 12, 8, 16]])

    res = swap(test_mat)

    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)
Exemple #7
0
def test_swap_int_dim():
    """Test swap operation when int is provided."""
    test_mat = np.array([[1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15],
                         [4, 8, 12, 16]])

    expected_res = np.array([[1, 9, 5, 13], [3, 11, 7, 15], [2, 10, 6, 14],
                             [4, 12, 8, 16]])

    res = swap(test_mat, [1, 2], 2)

    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)
Exemple #8
0
def test_swap_matrix_4_by_4():
    """Tests swap operation on a 4-by-4 matrix."""
    test_mat = np.arange(1, 17).reshape(4, 4).transpose()

    # Default argument (systems [1, 2]):
    expected_res = np.array([[1, 9, 5, 13], [3, 11, 7, 15], [2, 10, 6, 14],
                             [4, 12, 8, 16]])
    res = swap(test_mat)

    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Hard-coded argument (systems [1, 2]):
    res = swap(test_mat, [1, 2], [2, 2])
    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Hard-coded argument (systems [2, 1])--this should be identical to the prior two cases.
    res = swap(test_mat, [2, 1], [2, 2])
    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)
Exemple #9
0
def test_swap_8_by_8():
    """Test swap on an 8x8 matrix."""
    test_mat = np.arange(64).reshape(8, 8).T

    expected_res = np.array([[0, 16, 8, 24, 32, 48, 40, 56],
                             [2, 18, 10, 26, 34, 50, 42, 58],
                             [1, 17, 9, 25, 33, 49, 41, 57],
                             [3, 19, 11, 27, 35, 51, 43, 59],
                             [4, 20, 12, 28, 36, 52, 44, 60],
                             [6, 22, 14, 30, 38, 54, 46, 62],
                             [5, 21, 13, 29, 37, 53, 45, 61],
                             [7, 23, 15, 31, 39, 55, 47, 63]])
    res = swap(test_mat, [2, 3], [2, 2, 2])

    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)
def random_state_vector(dim: Union[List[int], int],
                        is_real: bool = False,
                        k_param: int = 0) -> np.ndarray:
    r"""Generate a random pure state vector.

    Examples
    ==========

    We may generate a random state vector. For instance, here is an example where we can generate a
    :math:`2`-dimensional random state vector.

    >>> from toqito.random import random_state_vector
    >>> vec = random_state_vector(2)
    >>> vec
    [[0.50993973+0.15292408j],
     [0.27787332+0.79960122j]]

    We can verify that this is in fact a valid state vector by computing the corresponding density
    matrix of the vector and checking if the density matrix is pure.

    >>> from toqito.state_props import is_pure
    >>> dm = vec.conj().T * vec
    >>> is_pure(dm)
    True

    :param dim: The number of rows (and columns) of the unitary matrix.
    :param is_real: Boolean denoting whether the returned matrix has real
                    entries or not. Default is :code:`False`.
    :param k_param: Default 0.
    :return: A :code:`dim`-by-:code:`dim` random unitary matrix.
    """
    # Schmidt rank plays a role.
    if 0 < k_param < np.min(dim):
        # Allow the user to enter a single number for dim.
        if isinstance(dim, int):
            dim = [dim, dim]

        # If you start with a separable state on a larger space and multiply
        # the extra `k_param` dimensions by a maximally entangled state, you
        # get a Schmidt rank `<= k_param` state.
        psi = max_entangled(k_param, True, False).toarray()

        a_param = np.random.rand(dim[0] * k_param, 1)
        b_param = np.random.rand(dim[1] * k_param, 1)

        if not is_real:
            a_param = a_param + 1j * np.random.rand(dim[0] * k_param, 1)
            b_param = b_param + 1j * np.random.rand(dim[1] * k_param, 1)

        mat_1 = np.kron(psi.conj().T, np.identity(int(np.prod(dim))))
        mat_2 = swap(
            np.kron(a_param, b_param),
            sys=[2, 3],
            dim=[k_param, dim[0], k_param, dim[1]],
        )

        ret_vec = mat_1 * mat_2
        return np.divide(ret_vec, np.linalg.norm(ret_vec))

    # Schmidt rank is full, so ignore it.
    ret_vec = np.random.rand(dim, 1)
    if not is_real:
        ret_vec = ret_vec + 1j * np.random.rand(dim, 1)
    return np.divide(ret_vec, np.linalg.norm(ret_vec))
Exemple #11
0
def test_swap_8_by_8():
    """Test swap on an 8-by-8 matrix."""
    test_mat = np.arange(1, 65).reshape(8, 8).transpose()

    # Swapping on systems "1" and "2":
    res_1_2 = swap(test_mat, [1, 2], [2, 2, 2])
    expected_res_1_2 = np.array([
        [1, 9, 33, 41, 17, 25, 49, 57],
        [2, 10, 34, 42, 18, 26, 50, 58],
        [5, 13, 37, 45, 21, 29, 53, 61],
        [6, 14, 38, 46, 22, 30, 54, 62],
        [3, 11, 35, 43, 19, 27, 51, 59],
        [4, 12, 36, 44, 20, 28, 52, 60],
        [7, 15, 39, 47, 23, 31, 55, 63],
        [8, 16, 40, 48, 24, 32, 56, 64],
    ])
    bool_mat = np.isclose(res_1_2, expected_res_1_2)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "2" and "1" (should be same as swapping on "1" and "2"):
    res_2_1 = swap(test_mat, [2, 1], [2, 2, 2])
    bool_mat = np.isclose(res_2_1, expected_res_1_2)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "1" and "3":
    res_1_3 = swap(test_mat, [1, 3], [2, 2, 2])
    expected_res_1_3 = np.array([
        [1, 33, 17, 49, 9, 41, 25, 57],
        [5, 37, 21, 53, 13, 45, 29, 61],
        [3, 35, 19, 51, 11, 43, 27, 59],
        [7, 39, 23, 55, 15, 47, 31, 63],
        [2, 34, 18, 50, 10, 42, 26, 58],
        [6, 38, 22, 54, 14, 46, 30, 62],
        [4, 36, 20, 52, 12, 44, 28, 60],
        [8, 40, 24, 56, 16, 48, 32, 64],
    ])
    bool_mat = np.isclose(res_1_3, expected_res_1_3)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "3" and "1" (should be same as swapping on "1" and "3"):
    res_3_1 = swap(test_mat, [3, 1], [2, 2, 2])
    bool_mat = np.isclose(res_3_1, expected_res_1_3)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "2" and "3":
    res_2_3 = swap(test_mat, [2, 3], [2, 2, 2])
    expected_res_2_3 = np.array([
        [1, 17, 9, 25, 33, 49, 41, 57],
        [3, 19, 11, 27, 35, 51, 43, 59],
        [2, 18, 10, 26, 34, 50, 42, 58],
        [4, 20, 12, 28, 36, 52, 44, 60],
        [5, 21, 13, 29, 37, 53, 45, 61],
        [7, 23, 15, 31, 39, 55, 47, 63],
        [6, 22, 14, 30, 38, 54, 46, 62],
        [8, 24, 16, 32, 40, 56, 48, 64],
    ])
    bool_mat = np.isclose(res_2_3, expected_res_2_3)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "3" and "2" (should be same as swapping on "2" and "3"):
    res_3_2 = swap(test_mat, [3, 2], [2, 2, 2])
    bool_mat = np.isclose(res_3_2, expected_res_2_3)
    np.testing.assert_equal(np.all(bool_mat), True)
Exemple #12
0
def realignment(input_mat: np.ndarray, dim=None) -> np.ndarray:
    r"""
    Compute the realignment of a bipartite operator [LAS08]_.

    Gives the realignment of the matrix :code:`input_mat`, where it is assumed
    that the number of rows and columns of :code:`input_mat` are both perfect
    squares and both subsystems have equal dimension. The realignment is defined
    by mapping the operator :math:`|ij \rangle \langle kl |` to
    :math:`|ik \rangle \langle jl |` and extending linearly.

    If :code:`input_mat` is non-square, different row and column dimensions can
    be specified by putting the row dimensions in the first row of :code:`dim`
    and the column dimensions in the second row of :code:`dim`.

    Examples
    ==========

    The standard realignment map

    Using :code:`toqito`, we can generate the standard realignment map as
    follows. When viewed as a map on block matrices, the realignment map takes
    each block of the original matrix and makes its vectorization the rows of
    the realignment matrix. This is illustrated by the following small example:

    >>> from toqito.channels import realignment
    >>> import numpy as np
    >>> test_input_mat = np.array(
    >>>     [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]
    >>> )
    >>> realignment(test_input_mat)
    [[ 1  2  5  6]
     [ 3  4  7  8]
     [ 9 10 13 14]
     [11 12 15 16]]

    References
    ==========
    .. [LAS08] Lupo, Cosmo, Paolo, Aniello, and Scardicchio, Antonello.
        "Bipartite quantum systems: on the realignment criterion and beyond."
        Journal of Physics A: Mathematical and Theoretical
        41.41 (2008): 415301.
        https://arxiv.org/abs/0802.2019

    :param input_mat: The input matrix.
    :param dim: Default has all equal dimensions.
    :return: The realignment map matrix.
    """
    eps = np.finfo(float).eps
    dim_mat = input_mat.shape
    round_dim = np.round(np.sqrt(dim_mat))
    if dim is None:
        dim = np.transpose(np.array([round_dim]))
    if isinstance(dim, list):
        dim = np.array(dim)

    if isinstance(dim, int):
        dim = np.array([int(dim), int(dim_mat[0] / dim)])
        if np.abs(dim[1] - np.round(dim[1])) >= 2 * dim_mat[0] * eps:
            raise ValueError("InvalidDim:")
        dim[1] = np.round(dim[1])

    # Dimension if row vector.
    if len(dim.shape) == 1:
        dim = dim[:].T
        dim = np.array([dim, dim])

    # Dimension is column vector.
    if min(dim.shape) == 1:
        dim = dim[:].T[0]
        dim = np.array([dim, dim])

    dim_x = np.array([[dim[0][1], dim[0][0]], [dim[1][0], dim[1][1]]])
    dim_y = np.array([[dim[1][0], dim[0][0]], [dim[0][1], dim[1][1]]])

    x_tmp = swap(input_mat, [1, 2], dim, True)
    y_tmp = partial_transpose(x_tmp, sys=1, dim=dim_x)

    return swap(y_tmp, [1, 2], dim_y, True)
Exemple #13
0
def dual_channel(
    phi_op: Union[np.ndarray, List[np.ndarray], List[List[np.ndarray]]],
    dims: List[int] = None
) -> Union[np.ndarray, List[List[np.ndarray]]]:
    r"""
    Compute the dual of a map (quantum channel) [WatDChan18]_.

    The map can be represented as a Choi matrix, with optional specification of input
    and output dimensions. In this case the Choi matrix of the dual channel is
    returned, obtained by swapping input and output (see :func:`toqito.perms.swap`),
    and complex conjugating all elements.

    The map can also be represented as a list of Kraus operators.
    A list of lists, each containing two elements, corresponds to the families
    of operators :math:`\{(A_a, B_a)\}` representing the map

    .. math::
        \Phi(X) = \sum_a A_a X B^*_a.

    The dual map is obtained by taking the Hermitian adjoint of each operator.
    If :code:`phi_op` is given as a one-dimensional list, :math:`\{A_a\}`,
    it is interpreted as the completely positive map

    .. math::
        \Phi(X) = \sum_a A_a X A^*_a.

    References
    ==========
    .. [WatDChan18] Watrous, John.
        The theory of quantum information.
        Section: Representations and characterizations of channels.
        Cambridge University Press, 2018.

    :param phi_op: A superoperator. It should be provided either as a Choi matrix,
                   or as a (1d or 2d) list of numpy arrays whose entries are its Kraus operators.
    :param dims: Dimension of the input and output systems, for Choi matrix representation.
                 If :code:`None`, try to infer them from :code:`phi_op.shape`.
    :return: The map dual to :code:`phi_op`, in the same representation.
    """
    # If phi_op is a list, assume it contains couples of Kraus operators
    # and take the Hermitian conjugate.
    if isinstance(phi_op, list):
        if isinstance(phi_op[0], list):
            return [[a.conj().T for a in x] for x in phi_op]
        if isinstance(phi_op[0], np.ndarray):
            return [a.conj().T for a in phi_op]

    # If phi_op is a `ndarray`, assume it is a Choi matrix.
    if isinstance(phi_op, np.ndarray):
        if len(phi_op.shape) == 2:
            if not is_square(phi_op):
                raise ValueError("Invalid: `phi_op` is not a valid Choi matrix (not square).")
            if dims is None:
                sqr = np.sqrt(phi_op.shape[0])
                if sqr.is_integer():
                    dims = [int(round(sqr))] * 2
                else:
                    raise ValueError(
                        "The dimensions `dims` of the input and output should be specified."
                        )
            return swap(phi_op.conj(), dim=dims)
    raise ValueError(
        "Invalid: The variable `phi_op` must either be a list of "
        "Kraus operators or as a Choi matrix."
    )
Exemple #14
0
def test_swap_invalid_sys_len():
    """Invalid sys parameters."""
    test_mat = np.array([[1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15],
                         [4, 8, 12, 16]])
    with np.testing.assert_raises(ValueError):
        swap(test_mat, [1], 2)
Exemple #15
0
def apply_map(mat: np.ndarray,
              phi_op: Union[np.ndarray, List[List[np.ndarray]]]) -> np.ndarray:
    r"""
    Apply a superoperator to an operator.

    Examples
    ==========

    The swap operator is the Choi matrix of the transpose map. The following is
    a (non-ideal, but illustrative) way of computing the transpose of a matrix.

    Consider the following matrix

    .. math::
        X = \begin{pmatrix}
                1 & 4 & 7 \\
                2 & 5 & 8 \\
                3 & 6 & 9
            \end{pmatrix}

    Applying the swap operator given as

    .. math::
        \Phi =
        \begin{pmatrix}
            1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\
            0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\
            0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1
         \end{pmatrix}

    to the matrix :math:`X`, we have the resulting matrix of

    .. math::
        \Phi(X) = \begin{pmatrix}
                        1 & 2 & 3 \\
                        4 & 5 & 6 \\
                        7 & 8 & 9
                   \end{pmatrix}

    Using :code:`toqito`, we can obtain the above matrices as follows.

    >>> from toqito.channel_ops import apply_map
    >>> from toqito.perms import swap_operator
    >>> import numpy as np
    >>> test_input_mat = np.array([[1, 4, 7], [2, 5, 8], [3, 6, 9]])
    >>> apply_map(test_input_mat, swap_operator(3))
    [[1., 2., 3.],
     [4., 5., 6.],
     [7., 8., 9.]]

    :param mat: A matrix.
    :param phi_op: A superoperator. :code:`phi_op` should be provided either as
                   a Choi matrix, or as a list of numpy arrays with either 1 or
                   2 columns whose entries are its Kraus operators.
    :return: The result of applying the superoperator :code:`phi_op` to the
             operator :code:`mat`.
    """
    # Both of the following methods of applying the superoperator are much
    # faster than naively looping through the Kraus operators or constructing
    # eigenvectors of a Choi matrix.

    # The superoperator was given as a list of Kraus operators:
    if isinstance(phi_op, list):
        s_phi_op = [len(phi_op), len(phi_op[0])]

        # Map is completely positive.
        if s_phi_op[1] == 1 or (s_phi_op[0] == 1 and s_phi_op[1] > 2):
            for i in range(s_phi_op[0]):
                phi_op[i][1] = phi_op[i][0].conj().T
        else:
            for i in range(s_phi_op[0]):
                phi_op[i][1] = phi_op[i][1].conj().T
        phi_0_list = []
        phi_1_list = []
        for i in range(s_phi_op[0]):
            phi_0_list.append(phi_op[i][0])
            phi_1_list.append(phi_op[i][1])

        k_1 = np.concatenate(phi_0_list, axis=1)
        k_2 = np.concatenate(phi_1_list, axis=0)

        a_mat = np.kron(np.identity(len(phi_op)), mat)
        return np.matmul(np.matmul(k_1, a_mat), k_2)

    # The superoperator was given as a Choi matrix:
    if isinstance(phi_op, np.ndarray):
        mat_size = np.array(list(mat.shape))
        phi_size = np.array(list(phi_op.shape)) / mat_size

        a_mat = np.kron(vec(mat).T[0], np.identity(int(phi_size[0])))
        b_mat = np.reshape(
            swap(
                phi_op.T,
                [1, 2],
                [[mat_size[1], phi_size[1]], [mat_size[0], phi_size[0]]],
                True,
            ).T,
            (int(phi_size[0] * np.prod(mat_size)), int(phi_size[1])),
        )
        return np.matmul(a_mat, b_mat)
    raise ValueError("Invalid: The variable `phi_op` must either be a list of "
                     "Kraus operators or as a Choi matrix.")
Exemple #16
0
def apply_channel(
    mat: np.ndarray, phi_op: Union[np.ndarray, List[List[np.ndarray]]]
) -> np.ndarray:
    r"""
    Apply a quantum channel to an operator [WatAChan18]_.

    Specifically, an application of the channel is defined as

    .. math::
        \Phi(X) = \text{Tr}_{\mathcal{X}} \left(J(\Phi)
        \left(\mathbb{I}_{\mathcal{Y}} \otimes X^{T}\right)\right),

    where

    .. math::
        J(\Phi): \text{T}(\mathcal{X}, \mathcal{Y}) \rightarrow
        \text{L}(\mathcal{Y} \otimes \mathcal{X})

    is the Choi representation of :math:`\Phi`.

    We assume the quantum channel given as :code:`phi_op` is provided as either
    the Choi matrix of the channel or a set of Kraus operators that define the
    quantum channel.

    This function is adapted from the QETLAB package.

    Examples
    ==========

    The swap operator is the Choi matrix of the transpose map. The following is
    a (non-ideal, but illustrative) way of computing the transpose of a matrix.

    Consider the following matrix

    .. math::
        X = \begin{pmatrix}
                1 & 4 & 7 \\
                2 & 5 & 8 \\
                3 & 6 & 9
            \end{pmatrix}

    Applying the swap operator given as

    .. math::
        \Phi =
        \begin{pmatrix}
            1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\
            0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\
            0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\
            0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1
         \end{pmatrix}

    to the matrix :math:`X`, we have the resulting matrix of

    .. math::
        \Phi(X) = \begin{pmatrix}
                        1 & 2 & 3 \\
                        4 & 5 & 6 \\
                        7 & 8 & 9
                   \end{pmatrix}

    Using :code:`toqito`, we can obtain the above matrices as follows.

    >>> from toqito.channel_ops import apply_channel
    >>> from toqito.perms import swap_operator
    >>> import numpy as np
    >>> test_input_mat = np.array([[1, 4, 7], [2, 5, 8], [3, 6, 9]])
    >>> apply_channel(test_input_mat, swap_operator(3))
    [[1., 2., 3.],
     [4., 5., 6.],
     [7., 8., 9.]]

    References
    ==========
    .. [WatAChan18] Watrous, John.
        The theory of quantum information.
        Section: Representations and characterizations of channels.
        Cambridge University Press, 2018.

    :param mat: A matrix.
    :param phi_op: A superoperator. :code:`phi_op` should be provided either as
                   a Choi matrix, or as a list of numpy arrays with either 1 or
                   2 columns whose entries are its Kraus operators.
    :return: The result of applying the superoperator :code:`phi_op` to the
             operator :code:`mat`.
    """
    # Both of the following methods of applying the superoperator are much
    # faster than naively looping through the Kraus operators or constructing
    # eigenvectors of a Choi matrix.

    # The superoperator was given as a list of Kraus operators:
    if isinstance(phi_op, list):
        s_phi_op = [len(phi_op), len(phi_op[0])]

        # Map is completely positive.
        if s_phi_op[1] == 1 or (s_phi_op[0] == 1 and s_phi_op[1] > 2):
            for i in range(s_phi_op[0]):
                phi_op[i][1] = phi_op[i][0].conj().T
        else:
            for i in range(s_phi_op[0]):
                phi_op[i][1] = phi_op[i][1].conj().T
        phi_0_list = []
        phi_1_list = []
        for i in range(s_phi_op[0]):
            phi_0_list.append(phi_op[i][0])
            phi_1_list.append(phi_op[i][1])

        k_1 = np.concatenate(phi_0_list, axis=1)
        k_2 = np.concatenate(phi_1_list, axis=0)

        a_mat = np.kron(np.identity(len(phi_op)), mat)
        return k_1 @ a_mat @ k_2

    # The superoperator was given as a Choi matrix:
    if isinstance(phi_op, np.ndarray):
        mat_size = np.array(list(mat.shape))
        phi_size = np.array(list(phi_op.shape)) / mat_size

        a_mat = np.kron(vec(mat).T[0], np.identity(int(phi_size[0])))
        b_mat = np.reshape(
            swap(
                phi_op.T,
                [1, 2],
                [[mat_size[1], phi_size[1]], [mat_size[0], phi_size[0]]],
                True,
            ).T,
            (int(phi_size[0] * np.prod(mat_size)), int(phi_size[1])),
        )
        return a_mat @ b_mat
    raise ValueError(
        "Invalid: The variable `phi_op` must either be a list of "
        "Kraus operators or as a Choi matrix."
    )
Exemple #17
0
def test_swap_16_by_16():
    """Test swap on an 16-by-16 matrix."""
    test_mat = np.arange(1, 257).reshape(16, 16).transpose()

    # Swapping on systems "1" and "2":
    res_1_2 = swap(test_mat, [1, 2], [2, 2, 2, 2])
    expected_res_1_2 = np.array([
        [
            1, 17, 33, 49, 129, 145, 161, 177, 65, 81, 97, 113, 193, 209, 225,
            241
        ],
        [
            2, 18, 34, 50, 130, 146, 162, 178, 66, 82, 98, 114, 194, 210, 226,
            242
        ],
        [
            3, 19, 35, 51, 131, 147, 163, 179, 67, 83, 99, 115, 195, 211, 227,
            243
        ],
        [
            4, 20, 36, 52, 132, 148, 164, 180, 68, 84, 100, 116, 196, 212, 228,
            244
        ],
        [
            9, 25, 41, 57, 137, 153, 169, 185, 73, 89, 105, 121, 201, 217, 233,
            249
        ],
        [
            10, 26, 42, 58, 138, 154, 170, 186, 74, 90, 106, 122, 202, 218,
            234, 250
        ],
        [
            11, 27, 43, 59, 139, 155, 171, 187, 75, 91, 107, 123, 203, 219,
            235, 251
        ],
        [
            12, 28, 44, 60, 140, 156, 172, 188, 76, 92, 108, 124, 204, 220,
            236, 252
        ],
        [
            5, 21, 37, 53, 133, 149, 165, 181, 69, 85, 101, 117, 197, 213, 229,
            245
        ],
        [
            6, 22, 38, 54, 134, 150, 166, 182, 70, 86, 102, 118, 198, 214, 230,
            246
        ],
        [
            7, 23, 39, 55, 135, 151, 167, 183, 71, 87, 103, 119, 199, 215, 231,
            247
        ],
        [
            8, 24, 40, 56, 136, 152, 168, 184, 72, 88, 104, 120, 200, 216, 232,
            248
        ],
        [
            13, 29, 45, 61, 141, 157, 173, 189, 77, 93, 109, 125, 205, 221,
            237, 253
        ],
        [
            14, 30, 46, 62, 142, 158, 174, 190, 78, 94, 110, 126, 206, 222,
            238, 254
        ],
        [
            15, 31, 47, 63, 143, 159, 175, 191, 79, 95, 111, 127, 207, 223,
            239, 255
        ],
        [
            16, 32, 48, 64, 144, 160, 176, 192, 80, 96, 112, 128, 208, 224,
            240, 256
        ],
    ])
    bool_mat = np.isclose(res_1_2, expected_res_1_2)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "2" and "1" (should be same as swapping on "1" and "2"):
    res_2_1 = swap(test_mat, [2, 1], [2, 2, 2, 2])
    bool_mat = np.isclose(res_2_1, expected_res_1_2)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "1" and "3":
    res_1_3 = swap(test_mat, [1, 3], [2, 2, 2, 2])
    expected_res_1_3 = np.array([
        [
            1, 17, 129, 145, 65, 81, 193, 209, 33, 49, 161, 177, 97, 113, 225,
            241
        ],
        [
            2, 18, 130, 146, 66, 82, 194, 210, 34, 50, 162, 178, 98, 114, 226,
            242
        ],
        [
            9, 25, 137, 153, 73, 89, 201, 217, 41, 57, 169, 185, 105, 121, 233,
            249
        ],
        [
            10, 26, 138, 154, 74, 90, 202, 218, 42, 58, 170, 186, 106, 122,
            234, 250
        ],
        [
            5, 21, 133, 149, 69, 85, 197, 213, 37, 53, 165, 181, 101, 117, 229,
            245
        ],
        [
            6, 22, 134, 150, 70, 86, 198, 214, 38, 54, 166, 182, 102, 118, 230,
            246
        ],
        [
            13, 29, 141, 157, 77, 93, 205, 221, 45, 61, 173, 189, 109, 125,
            237, 253
        ],
        [
            14, 30, 142, 158, 78, 94, 206, 222, 46, 62, 174, 190, 110, 126,
            238, 254
        ],
        [
            3, 19, 131, 147, 67, 83, 195, 211, 35, 51, 163, 179, 99, 115, 227,
            243
        ],
        [
            4, 20, 132, 148, 68, 84, 196, 212, 36, 52, 164, 180, 100, 116, 228,
            244
        ],
        [
            11, 27, 139, 155, 75, 91, 203, 219, 43, 59, 171, 187, 107, 123,
            235, 251
        ],
        [
            12, 28, 140, 156, 76, 92, 204, 220, 44, 60, 172, 188, 108, 124,
            236, 252
        ],
        [
            7, 23, 135, 151, 71, 87, 199, 215, 39, 55, 167, 183, 103, 119, 231,
            247
        ],
        [
            8, 24, 136, 152, 72, 88, 200, 216, 40, 56, 168, 184, 104, 120, 232,
            248
        ],
        [
            15, 31, 143, 159, 79, 95, 207, 223, 47, 63, 175, 191, 111, 127,
            239, 255
        ],
        [
            16, 32, 144, 160, 80, 96, 208, 224, 48, 64, 176, 192, 112, 128,
            240, 256
        ],
    ])
    bool_mat = np.isclose(res_1_3, expected_res_1_3)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "3" and "1" (should be same as swapping on "1" and "3"):
    res_3_1 = swap(test_mat, [3, 1], [2, 2, 2, 2])
    bool_mat = np.isclose(res_3_1, expected_res_1_3)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "1" and "4":
    res_1_4 = swap(test_mat, [1, 4], [2, 2, 2, 2])
    expected_res_1_4 = np.array([
        [
            1, 129, 33, 161, 65, 193, 97, 225, 17, 145, 49, 177, 81, 209, 113,
            241
        ],
        [
            9, 137, 41, 169, 73, 201, 105, 233, 25, 153, 57, 185, 89, 217, 121,
            249
        ],
        [
            3, 131, 35, 163, 67, 195, 99, 227, 19, 147, 51, 179, 83, 211, 115,
            243
        ],
        [
            11, 139, 43, 171, 75, 203, 107, 235, 27, 155, 59, 187, 91, 219,
            123, 251
        ],
        [
            5, 133, 37, 165, 69, 197, 101, 229, 21, 149, 53, 181, 85, 213, 117,
            245
        ],
        [
            13, 141, 45, 173, 77, 205, 109, 237, 29, 157, 61, 189, 93, 221,
            125, 253
        ],
        [
            7, 135, 39, 167, 71, 199, 103, 231, 23, 151, 55, 183, 87, 215, 119,
            247
        ],
        [
            15, 143, 47, 175, 79, 207, 111, 239, 31, 159, 63, 191, 95, 223,
            127, 255
        ],
        [
            2, 130, 34, 162, 66, 194, 98, 226, 18, 146, 50, 178, 82, 210, 114,
            242
        ],
        [
            10, 138, 42, 170, 74, 202, 106, 234, 26, 154, 58, 186, 90, 218,
            122, 250
        ],
        [
            4, 132, 36, 164, 68, 196, 100, 228, 20, 148, 52, 180, 84, 212, 116,
            244
        ],
        [
            12, 140, 44, 172, 76, 204, 108, 236, 28, 156, 60, 188, 92, 220,
            124, 252
        ],
        [
            6, 134, 38, 166, 70, 198, 102, 230, 22, 150, 54, 182, 86, 214, 118,
            246
        ],
        [
            14, 142, 46, 174, 78, 206, 110, 238, 30, 158, 62, 190, 94, 222,
            126, 254
        ],
        [
            8, 136, 40, 168, 72, 200, 104, 232, 24, 152, 56, 184, 88, 216, 120,
            248
        ],
        [
            16, 144, 48, 176, 80, 208, 112, 240, 32, 160, 64, 192, 96, 224,
            128, 256
        ],
    ])
    bool_mat = np.isclose(res_1_4, expected_res_1_4)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "4" and "1" (should be same as swapping on "1" and "4"):
    res_4_1 = swap(test_mat, [4, 1], [2, 2, 2, 2])
    bool_mat = np.isclose(res_4_1, expected_res_1_4)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "2" and "3":
    res_2_3 = swap(test_mat, [2, 3], [2, 2, 2, 2])
    expected_res_2_3 = np.array([
        [
            1, 17, 65, 81, 33, 49, 97, 113, 129, 145, 193, 209, 161, 177, 225,
            241
        ],
        [
            2, 18, 66, 82, 34, 50, 98, 114, 130, 146, 194, 210, 162, 178, 226,
            242
        ],
        [
            5, 21, 69, 85, 37, 53, 101, 117, 133, 149, 197, 213, 165, 181, 229,
            245
        ],
        [
            6, 22, 70, 86, 38, 54, 102, 118, 134, 150, 198, 214, 166, 182, 230,
            246
        ],
        [
            3, 19, 67, 83, 35, 51, 99, 115, 131, 147, 195, 211, 163, 179, 227,
            243
        ],
        [
            4, 20, 68, 84, 36, 52, 100, 116, 132, 148, 196, 212, 164, 180, 228,
            244
        ],
        [
            7, 23, 71, 87, 39, 55, 103, 119, 135, 151, 199, 215, 167, 183, 231,
            247
        ],
        [
            8, 24, 72, 88, 40, 56, 104, 120, 136, 152, 200, 216, 168, 184, 232,
            248
        ],
        [
            9, 25, 73, 89, 41, 57, 105, 121, 137, 153, 201, 217, 169, 185, 233,
            249
        ],
        [
            10, 26, 74, 90, 42, 58, 106, 122, 138, 154, 202, 218, 170, 186,
            234, 250
        ],
        [
            13, 29, 77, 93, 45, 61, 109, 125, 141, 157, 205, 221, 173, 189,
            237, 253
        ],
        [
            14, 30, 78, 94, 46, 62, 110, 126, 142, 158, 206, 222, 174, 190,
            238, 254
        ],
        [
            11, 27, 75, 91, 43, 59, 107, 123, 139, 155, 203, 219, 171, 187,
            235, 251
        ],
        [
            12, 28, 76, 92, 44, 60, 108, 124, 140, 156, 204, 220, 172, 188,
            236, 252
        ],
        [
            15, 31, 79, 95, 47, 63, 111, 127, 143, 159, 207, 223, 175, 191,
            239, 255
        ],
        [
            16, 32, 80, 96, 48, 64, 112, 128, 144, 160, 208, 224, 176, 192,
            240, 256
        ],
    ])
    bool_mat = np.isclose(res_2_3, expected_res_2_3)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "3" and "2" (should be same as swapping on "2" and "3"):
    res_3_2 = swap(test_mat, [3, 2], [2, 2, 2, 2])
    bool_mat = np.isclose(res_3_2, expected_res_2_3)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "2" and "4":
    res_2_4 = swap(test_mat, [2, 4], [2, 2, 2, 2])
    expected_res_2_4 = np.array([
        [
            1, 65, 33, 97, 17, 81, 49, 113, 129, 193, 161, 225, 145, 209, 177,
            241
        ],
        [
            5, 69, 37, 101, 21, 85, 53, 117, 133, 197, 165, 229, 149, 213, 181,
            245
        ],
        [
            3, 67, 35, 99, 19, 83, 51, 115, 131, 195, 163, 227, 147, 211, 179,
            243
        ],
        [
            7, 71, 39, 103, 23, 87, 55, 119, 135, 199, 167, 231, 151, 215, 183,
            247
        ],
        [
            2, 66, 34, 98, 18, 82, 50, 114, 130, 194, 162, 226, 146, 210, 178,
            242
        ],
        [
            6, 70, 38, 102, 22, 86, 54, 118, 134, 198, 166, 230, 150, 214, 182,
            246
        ],
        [
            4, 68, 36, 100, 20, 84, 52, 116, 132, 196, 164, 228, 148, 212, 180,
            244
        ],
        [
            8, 72, 40, 104, 24, 88, 56, 120, 136, 200, 168, 232, 152, 216, 184,
            248
        ],
        [
            9, 73, 41, 105, 25, 89, 57, 121, 137, 201, 169, 233, 153, 217, 185,
            249
        ],
        [
            13, 77, 45, 109, 29, 93, 61, 125, 141, 205, 173, 237, 157, 221,
            189, 253
        ],
        [
            11, 75, 43, 107, 27, 91, 59, 123, 139, 203, 171, 235, 155, 219,
            187, 251
        ],
        [
            15, 79, 47, 111, 31, 95, 63, 127, 143, 207, 175, 239, 159, 223,
            191, 255
        ],
        [
            10, 74, 42, 106, 26, 90, 58, 122, 138, 202, 170, 234, 154, 218,
            186, 250
        ],
        [
            14, 78, 46, 110, 30, 94, 62, 126, 142, 206, 174, 238, 158, 222,
            190, 254
        ],
        [
            12, 76, 44, 108, 28, 92, 60, 124, 140, 204, 172, 236, 156, 220,
            188, 252
        ],
        [
            16, 80, 48, 112, 32, 96, 64, 128, 144, 208, 176, 240, 160, 224,
            192, 256
        ],
    ])
    bool_mat = np.isclose(res_2_4, expected_res_2_4)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "4" and "2" (should be same as swapping on "2" and "4"):
    res_4_2 = swap(test_mat, [4, 2], [2, 2, 2, 2])
    bool_mat = np.isclose(res_4_2, expected_res_2_4)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "3" and "4":
    res_3_4 = swap(test_mat, [3, 4], [2, 2, 2, 2])
    expected_res_3_4 = np.array([
        [
            1, 33, 17, 49, 65, 97, 81, 113, 129, 161, 145, 177, 193, 225, 209,
            241
        ],
        [
            3, 35, 19, 51, 67, 99, 83, 115, 131, 163, 147, 179, 195, 227, 211,
            243
        ],
        [
            2, 34, 18, 50, 66, 98, 82, 114, 130, 162, 146, 178, 194, 226, 210,
            242
        ],
        [
            4, 36, 20, 52, 68, 100, 84, 116, 132, 164, 148, 180, 196, 228, 212,
            244
        ],
        [
            5, 37, 21, 53, 69, 101, 85, 117, 133, 165, 149, 181, 197, 229, 213,
            245
        ],
        [
            7, 39, 23, 55, 71, 103, 87, 119, 135, 167, 151, 183, 199, 231, 215,
            247
        ],
        [
            6, 38, 22, 54, 70, 102, 86, 118, 134, 166, 150, 182, 198, 230, 214,
            246
        ],
        [
            8, 40, 24, 56, 72, 104, 88, 120, 136, 168, 152, 184, 200, 232, 216,
            248
        ],
        [
            9, 41, 25, 57, 73, 105, 89, 121, 137, 169, 153, 185, 201, 233, 217,
            249
        ],
        [
            11, 43, 27, 59, 75, 107, 91, 123, 139, 171, 155, 187, 203, 235,
            219, 251
        ],
        [
            10, 42, 26, 58, 74, 106, 90, 122, 138, 170, 154, 186, 202, 234,
            218, 250
        ],
        [
            12, 44, 28, 60, 76, 108, 92, 124, 140, 172, 156, 188, 204, 236,
            220, 252
        ],
        [
            13, 45, 29, 61, 77, 109, 93, 125, 141, 173, 157, 189, 205, 237,
            221, 253
        ],
        [
            15, 47, 31, 63, 79, 111, 95, 127, 143, 175, 159, 191, 207, 239,
            223, 255
        ],
        [
            14, 46, 30, 62, 78, 110, 94, 126, 142, 174, 158, 190, 206, 238,
            222, 254
        ],
        [
            16, 48, 32, 64, 80, 112, 96, 128, 144, 176, 160, 192, 208, 240,
            224, 256
        ],
    ])
    bool_mat = np.isclose(res_3_4, expected_res_3_4)
    np.testing.assert_equal(np.all(bool_mat), True)

    # Swapping on systems "4" and "3" (should be same as swapping on "3" and "4"):
    res_4_3 = swap(test_mat, [4, 3], [2, 2, 2, 2])
    bool_mat = np.isclose(res_4_3, expected_res_3_4)
    np.testing.assert_equal(np.all(bool_mat), True)