Ejemplo n.º 1
0
def test_vec():
    """Test standard vec operation on a matrix."""
    expected_res = np.array([[1], [3], [2], [4]])

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

    res = vec(test_input_mat)

    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)
Ejemplo n.º 2
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."
    )
Ejemplo n.º 3
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.")
Ejemplo n.º 4
0
def gen_bell(k_1: int, k_2: int, dim: int) -> np.ndarray:
    r"""
    Produce a generalized Bell state [DL09]_.

    Produces a generalized Bell state. Note that the standard Bell states can be recovered as:

    - `bell(0)` : `gen_bell(0, 0, 2)`
    - `bell(1)` : `gen_bell(0, 1, 2)`
    - `bell(2)` : `gen_bell(1, 0, 2)`
    - `bell(3)` : `gen_bell(1, 1, 2)`

    Examples
    ==========

    For :math:`d = 2` and :math:`k_1 = k_2 = 0`, this generates the following matrix

    .. math::
        G = \frac{1}{2} \begin{pmatrix}
                        1 & 0 & 0 & 1 \\
                        0 & 0 & 0 & 0 \\
                        0 & 0 & 0 & 0 \\
                        1 & 0 & 0 & 1
                    \end{pmatrix}

    which is equivalent to :math:`|\phi_0 \rangle \langle \phi_0 |` where

    .. math::
        |\phi_0\rangle = \frac{1}{\sqrt{2}} \left( |00 \rangle + |11 \rangle \right)

    is one of the four standard Bell states. This can be computed via :code:`toqito` as follows.

    >>> from toqito.states import gen_bell
    >>> dim = 2
    >>> k_1 = 0
    >>> k_2 = 0
    >>> gen_bell(k_1, k_2, dim)
    [[0.5+0.j, 0. +0.j, 0. +0.j, 0.5+0.j],
     [0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
     [0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
     [0.5+0.j, 0. +0.j, 0. +0.j, 0.5+0.j]]

    It is possible for us to consider higher dimensional Bell states. For instance, we can consider
    the :math:`3`-dimensional Bell state for :math:`k_1 = k_2 = 0` as follows.

    >>> from toqito.states import gen_bell
    >>> dim = 3
    >>> k_1 = 0
    >>> k_2 = 0
    >>> gen_bell(k_1, k_2, dim)
    [[0.33333333+0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.33333333+0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.33333333+0.j],
     [0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j],
     [0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j],
     [0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j],
     [0.33333333+0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.33333333+0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.33333333+0.j],
     [0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j],
     [0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j],
     [0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.        +0.j],
     [0.33333333+0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.33333333+0.j, 0.        +0.j, 0.        +0.j, 0.        +0.j,
      0.33333333+0.j]]

    References
    ==========
    .. [DL09] Sych, Denis, and Gerd Leuchs.
        "A complete basis of generalized Bell states."
        New Journal of Physics 11.1 (2009): 013006.

    :param k_1: An integer 0 <= k_1 <= n.
    :param k_2: An integer 0 <= k_2 <= n.
    :param dim: The dimension of the generalized Bell state.
    """
    gen_pauli_w = gen_pauli(k_1, k_2, dim)
    return 1 / dim * vec(gen_pauli_w) * vec(gen_pauli_w).conj().T
Ejemplo n.º 5
0
def permute_systems(
    input_mat: np.ndarray,
    perm: Union[np.ndarray, List[int]],
    dim: Union[np.ndarray, List[int]] = None,
    row_only: bool = False,
    inv_perm: bool = False,
) -> np.ndarray:
    r"""
    Permute subsystems within a state or operator.

    Permutes the order of the subsystems of the vector or matrix :code:`input_mat` according to the
    permutation vector :code:`perm`, where the dimensions of the subsystems are given by the vector
    :code:`dim`. If :code:`input_mat` is non-square and not a vector, different row and column
    dimensions can be specified by putting the row dimensions in the first row of :code:`dim` and
    the columns dimensions in the second row of :code:`dim`.

    If :code:`row_only = True`, then only the rows of :code:`input_mat` are permuted, but not the
    columns -- this is equivalent to multiplying :code:`input_mat` on the left by the corresponding
    permutation operator, but not on the right.

    If :code:`row_only = False`, then :code:`dim` only needs to contain the row dimensions of the
    subsystems, even if :code:`input_mat` is not square. If :code:`inv_perm = True`, then the
    inverse permutation of :code:`perm` is applied instead of :code:`perm` itself.

    Examples
    ==========

    For spaces :math:`\mathcal{A}` and :math:`\mathcal{B}` where
    :math:`\text{dim}(\mathcal{A}) = \text{dim}(\mathcal{B}) = 2` we may consider an operator
    :math:`X \in \mathcal{A} \otimes \mathcal{B}`. Applying the `permute_systems` function with
    vector :math:`[2,1]` on :math:`X`, we may reorient the spaces such that
    :math:`X \in \mathcal{B} \otimes \mathcal{A}`.

    For example, if we define :math:`X \in \mathcal{A} \otimes \mathcal{B}` as

    .. math::
        X = \begin{pmatrix}
            1 & 2 & 3 & 4 \\
            5 & 6 & 7 & 8 \\
            9 & 10 & 11 & 12 \\
            13 & 14 & 15 & 16
        \end{pmatrix},

    then applying the `permute_systems` function on :math:`X` to obtain
    :math:`X \in \mathcal{B} \otimes \mathcal{A}` yield the following matrix

    .. math::
        X_{[2,1]} = \begin{pmatrix}
            1 & 3 & 2 & 4 \\
            9 & 11 & 10 & 12 \\
            5 & 7 & 6 & 8 \\
            13 & 15 & 14 & 16
        \end{pmatrix}.

    >>> from toqito.perms import permute_systems
    >>> 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]]
    >>> )
    >>> permute_systems(test_input_mat, [2, 1])
    [[ 1  3  2  4]
     [ 9 11 10 12]
     [ 5  7  6  8]
     [13 15 14 16]]

    For spaces :math:`\mathcal{A}, \mathcal{B}`, and :math:`\mathcal{C}` where
    :math:`\text{dim}(\mathcal{A}) = \text{dim}(\mathcal{B}) = \text{dim}(\mathcal{C}) = 2` we may
    consider an operator :math:`X \in \mathcal{A} \otimes \mathcal{B} \otimes \mathcal{C}`. Applying
    the :code:`permute_systems` function with vector :math:`[2,3,1]` on :math:`X`, we may reorient
    the spaces such that :math:`X \in \mathcal{B} \otimes \mathcal{C} \otimes \mathcal{A}`.

    For example, if we define :math:`X \in \mathcal{A} \otimes \mathcal{B} \otimes \mathcal{C}` as

    .. math::
        X =
        \begin{pmatrix}
            1 & 2 & 3 & 4, 5 & 6 & 7 & 8 \\
            9 & 10 & 11 & 12 & 13 & 14 & 15 & 16 \\
            17 & 18 & 19 & 20 & 21 & 22 & 23 & 24 \\
            25 & 26 & 27 & 28 & 29 & 30 & 31 & 32 \\
            33 & 34 & 35 & 36 & 37 & 38 & 39 & 40 \\
            41 & 42 & 43 & 44 & 45 & 46 & 47 & 48 \\
            49 & 50 & 51 & 52 & 53 & 54 & 55 & 56 \\
            57 & 58 & 59 & 60 & 61 & 62 & 63 & 64
        \end{pmatrix},

    then applying the `permute_systems` function on :math:`X` to obtain
    :math:`X \in \mathcal{B} \otimes \mathcal{C} \otimes \mathcal{C}` yield the following matrix

    .. math::
        X_{[2, 3, 1]} =
        \begin{pmatrix}
            1 & 5 & 2 & 6 & 3 & 7 & 4, 8 \\
            33 & 37 & 34 & 38 & 35 & 39 & 36 & 40 \\
            9 & 13 & 10 & 14 & 11 & 15 & 12 & 16 \\
            41 & 45 & 42 & 46 & 43 & 47 & 44 & 48 \\
            17 & 21 & 18 & 22 & 19 & 23 & 20 & 24 \\
            49 & 53 & 50 & 54 & 51 & 55 & 52 & 56 \\
            25 & 29 & 26 & 30 & 27 & 31 & 28 & 32 \\
            57 & 61 & 58 & 62 & 59 & 63 & 60 & 64
        \end{pmatrix}.

    >>> from toqito.perms import permute_systems
    >>> 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],
    >>>        [17, 18, 19, 20, 21, 22, 23, 24],
    >>>        [25, 26, 27, 28, 29, 30, 31, 32],
    >>>        [33, 34, 35, 36, 37, 38, 39, 40],
    >>>        [41, 42, 43, 44, 45, 46, 47, 48],
    >>>        [49, 50, 51, 52, 53, 54, 55, 56],
    >>>        [57, 58, 59, 60, 61, 62, 63, 64],
    >>>    ]
    >>> )
    >>> permute_systems(test_input_mat, [2, 3, 1])
    [[ 1  5  2  6  3  7  4  8]
     [33 37 34 38 35 39 36 40]
     [ 9 13 10 14 11 15 12 16]
     [41 45 42 46 43 47 44 48]
     [17 21 18 22 19 23 20 24]
     [49 53 50 54 51 55 52 56]
     [25 29 26 30 27 31 28 32]
     [57 61 58 62 59 63 60 64]]

    :param input_mat: The vector or matrix.
    :param perm: A permutation vector.
    :param dim: The default has all subsystems of equal dimension.
    :param row_only: Default: :code:`False`
    :param inv_perm: Default: :code:`True`
    :return: The matrix or vector that has been permuted.
    """
    if len(input_mat.shape) == 1:
        input_mat_dims = (1, input_mat.shape[0])
    else:
        input_mat_dims = input_mat.shape

    is_vec = np.min(input_mat_dims) == 1
    num_sys = len(perm)

    if dim is None:
        x_tmp = input_mat_dims[0] ** (1 / num_sys) * np.ones(num_sys)
        y_tmp = input_mat_dims[1] ** (1 / num_sys) * np.ones(num_sys)
        dim = np.array([x_tmp, y_tmp])

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

    if is_vec:
        # 1 if column vector
        if len(input_mat.shape) > 1:
            vec_orien = 0
        # 2 if row vector
        elif len(input_mat.shape) == 1:
            vec_orien = 1
        else:
            raise ValueError(
                "InvalidMat: Length of tuple of dimensions "
                "specifying the input matrix can only be of "
                "length 1 or length 2."
            )

    if len(dim.shape) == 1:
        # Force dim to be a row vector.
        dim_tmp = dim[:].T
        if is_vec:
            dim = np.ones((2, len(dim)))
            dim[vec_orien, :] = dim_tmp
        else:
            dim = np.array([[dim_tmp], [dim_tmp]])

    prod_dim_r = int(np.prod(dim[0, :]))
    prod_dim_c = int(np.prod(dim[1, :]))

    if len(perm) != num_sys:
        raise ValueError("InvalidPerm: `len(perm)` must be equal to " "`len(dim)`.")
    if sorted(perm) != list(range(1, num_sys + 1)):
        raise ValueError("InvalidPerm: `perm` must be a permutation vector.")
    if input_mat_dims[0] != prod_dim_r or (not row_only and input_mat_dims[1] != prod_dim_c):
        raise ValueError(
            "InvalidDim: The dimensions specified in DIM do not " "agree with the size of X."
        )
    if is_vec:
        if inv_perm:
            permuted_mat_1 = input_mat.reshape(dim[vec_orien, ::-1].astype(int), order="F")
            permuted_mat = vec(np.transpose(permuted_mat_1, num_sys - np.array(perm[::-1]))).T
            # We need to flatten out the array.
            permuted_mat = functools.reduce(operator.iconcat, permuted_mat, [])
        else:
            permuted_mat_1 = input_mat.reshape(dim[vec_orien, ::-1].astype(int), order="F")
            permuted_mat = vec(np.transpose(permuted_mat_1, num_sys - np.array(perm[::-1]))).T
            # We need to flatten out the array.
            permuted_mat = functools.reduce(operator.iconcat, permuted_mat, [])
        return np.array(permuted_mat)

    vec_arg = np.array(list(range(0, input_mat_dims[0])))

    # If the dimensions are specified, ensure they are given to the
    # recursive calls as flattened lists.
    if len(dim[0][:]) == 1:
        dim = functools.reduce(operator.iconcat, dim, [])

    row_perm = permute_systems(vec_arg, perm, dim[0][:], False, inv_perm)

    # This condition is only necessary if the `input_mat` variable is sparse.
    if isinstance(input_mat, (sparse.csr_matrix, sparse.dia_matrix)):
        input_mat = input_mat.toarray()
        permuted_mat = input_mat[row_perm, :]
        permuted_mat = np.array(permuted_mat)
    else:
        permuted_mat = input_mat[row_perm, :]

    if not row_only:
        vec_arg = np.array(list(range(0, input_mat_dims[1])))
        col_perm = permute_systems(vec_arg, perm, dim[1][:], False, inv_perm)
        permuted_mat = permuted_mat[:, col_perm]

    return permuted_mat