Esempio n. 1
0
def realignment(input_mat: np.ndarray, dim=None) -> np.ndarray:
    r"""
    Compute the realignment of a bipartite operator.

    Gives the realignment of the matrix INPUT_MAT, where it is assumed that
    the number of rows and columns of 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 `input_mat` is non-square, different row and column dimensions can be
    specified by putting the row dimensions in the first row of `dim` and the
    column dimensions in the second row of `dim`.

    References:
        [1] Lupo, Cosmo, Paolo Aniello, and Antonello Scardicchio.
        "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([[dim], [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])
        dim = np.array([[1], [4]])

    if min(dim.shape) == 1:
        dim = dim[:].T
        dim = functools.reduce(operator.iconcat, dim, [])
        dim = np.array([dim, dim])
        # dim = functools.reduce(operator.iconcat, 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)
Esempio n. 2
0
def apply_map(mat: np.ndarray,
              phi_op: Union[np.ndarray, List[List[np.ndarray]]]) -> np.ndarray:
    """
    Apply a superoperator to an operator.

    :param mat: A matrix.
    :param phi_op: A superoperator.
    :return: The result of applying the superoperator `phi_op` to the operator
             `mat`.

    `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.
    """
    # 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.")
Esempio n. 3
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 `dim`-dimensional
    space. If the two subsystems are not of the same dimension, `dim` should
    be a 1-by-2 vector containing the dimension of the subsystems.

    Examples
    ==========

    The $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 `toqito` we can obtain this matrix as follows.

    >>> from toqito.perms.swap_operator import swap_operator
    >>> swap_operator(2)
    array([[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 `toqito` as
    follows.

    >>> from toqito.perms.swap_operator import swap_operator
    >>> swap_operator(3)
    array([[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 `True` and non-sparse if `False`.
    :return: The swap operator of dimension `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)
Esempio n. 4
0
    def test_swap_vector_1(self):
        """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)
        self.assertEqual(np.all(bool_mat), True)
Esempio n. 5
0
    def test_swap_matrix(self):
        """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)
        self.assertEqual(np.all(bool_mat), True)
Esempio n. 6
0
    def test_swap_int_dim(self):
        """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)
        self.assertEqual(np.all(bool_mat), True)
def random_state_vector(dim: Union[List[int], int],
                        is_real: bool = False,
                        k_param: int = 0) -> np.ndarray:
    """Generate a random pure state vector.

    :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 `False`.
    :param k_param: Default 0.
    :return: A `dim`-by-`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))
Esempio n. 8
0
def swap_operator(dim: Union[List[int], int],
                  is_sparse: bool = False) -> np.ndarray:
    """
    Produce a unitary operator that swaps two subsystems.

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

    :param dim: The dimensions of the subsystems.
    :param is_sparse: Sparse if `True` and non-sparse if `False`.
    :return: The swap operator of dimension `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)
Esempio n. 9
0
def realignment(input_mat: np.ndarray, dim=None) -> np.ndarray:
    r"""
    Compute the realignment of a bipartite operator [REALIGN]_.

    Gives the realignment of the matrix `input_mat`, where it is assumed that
    the number of rows and columns of `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 `input_mat` is non-square, different row and column dimensions can be
    specified by putting the row dimensions in the first row of `dim` and the
    column dimensions in the second row of `dim`.

    Examples
    ==========

    The standard realignment map

    Using `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.channels.realignment 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
    ==========
    .. [REALIGN] Lupo, Cosmo, Paolo Aniello, and Antonello Scardicchio.
        "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([[dim], [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])
        dim = np.array([[1], [4]])

    if min(dim.shape) == 1:
        dim = dim[:].T
        dim = functools.reduce(operator.iconcat, dim, [])
        dim = np.array([dim, dim])
        # dim = functools.reduce(operator.iconcat, 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)
Esempio n. 10
0
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
    ==========

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

    >>> from toqito.random.random_state_vector import random_state_vector
    >>> vec = random_state_vector(2)
    >>> vec
    array([[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.states.properties.is_pure 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 `False`.
    :param k_param: Default 0.
    :return: A `dim`-by-`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))
Esempio n. 11
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::
        \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::
        X_{swap} = \begin{pmatrix}
                        1 & 2 & 3 \\
                        4 & 5 & 6 \\
                        7 & 8 & 9
                   \end{pmatrix}

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

    >>> from toqito.channels.operations.apply_map import apply_map
    >>> from toqito.perms.swap_operator 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))
    array([[1., 2., 3.],
           [4., 5., 6.],
           [7., 8., 9.]])

    :param mat: A matrix.
    :param phi_op: A superoperator.
    :return: The result of applying the superoperator `phi_op` to the operator
             `mat`.

    `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.
    """
    # 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.")
Esempio n. 12
0
 def test_invalid_sys_len(self):
     """Invalid sys parameters."""
     test_mat = np.array([[1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15],
                          [4, 8, 12, 16]])
     with self.assertRaises(ValueError):
         swap(test_mat, [1], 2)