Example #1
0
def choi_to_kraus(choi_mat: np.ndarray, tol: float = 1e-9) -> List[List[np.ndarray]]:
    r"""
    Compute a list of Kraus operators from the Choi matrix [Rigetti20]_.

    Note that unlike the Choi or natural representation of operators, the Kraus representation is
    *not* unique.

    This function has been adapted from [Rigetti20]_.

    Examples
    ========

    Convert the Choi operator

    See Also
    ========
    kraus_to_choi

    References
    ==========
    .. [Rigetti20] Forest Benchmarking (Rigetti).
        https://github.com/rigetti/forest-benchmarking

    :param choi_mat: a dim**2 by dim**2 choi matrix
    :param tol: optional threshold parameter for eigenvalues/kraus ops to be discarded
    :return: List of Kraus operators
    """
    eigvals, v_mat = np.linalg.eigh(choi_mat)
    return [
        np.lib.scimath.sqrt(eigval) * unvec(np.array([evec]).T)
        for eigval, evec in zip(eigvals, v_mat.T)
        if abs(eigval) > tol
    ]
Example #2
0
def test_unvec():
    """Test standard unvec operation on a vector."""
    expected_res = np.array([[1, 2], [3, 4]])

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

    res = unvec(test_input_vec)

    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)
Example #3
0
def test_unvec_custom_dim():
    """Test standard unvec operation on a vector with custom dimension."""
    expected_res = np.array([[1], [3], [2], [4]])

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

    res = unvec(test_input_vec, [1, 4])

    bool_mat = np.isclose(res, expected_res)
    np.testing.assert_equal(np.all(bool_mat), True)
Example #4
0
def choi_to_kraus(choi_mat: np.ndarray,
                  tol: float = 1e-9) -> List[List[np.ndarray]]:
    r"""
    Compute a list of Kraus operators from the Choi matrix [Rigetti20]_.

    Note that unlike the Choi or natural representation of operators, the Kraus representation is
    *not* unique.

    This function has been adapted from [Rigetti20]_.

    Examples
    ========

    Consider taking the Kraus operators of the Choi matrix that characterizes the "swap operator"
    defined as

    .. math::
        \begin{pmatrix}
        \end{pmatrix}

    The corresponding Kraus operators of the swap operator are given as follows,

    .. math::
        \begin{equation}
            \begin{aligned}
                \frac{1}{\sqrt{2}}
                \begin{pmatrix}
                    0 & i \\ -i & 0
                \end{pmatrix}, &\quad
                \frac{1}{\sqrt{2}}
                \begin{pmatrix}
                    0 & 1 \\
                    1 & 0
                \end{pmatrix}, \\
                \begin{pmatrix}
                    1 & 0 \\
                    0 & 0
                \end{pmatrix}, &\quad
                \begin{pmatrix}
                    0 & 0 \\
                    0 & 1
                \end{pmatrix}.
            \end{aligned}
        \end{equation}

    This can be verified in :code:`toqito` as follows.

    >>> import numpy as np
    >>> from toqito.channel_ops import choi_to_kraus
    >>> choi_mat = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])
    >>> kraus_ops = choi_to_kraus(choi_mat)
    >>> kraus_ops
    [array([[ 0.+0.j        ,  0.+0.70710678j],
           [-0.-0.70710678j,  0.+0.j        ]]), array([[0.        , 0.70710678],
           [0.70710678, 0.        ]]), array([[1., 0.],
           [0., 0.]]), array([[0., 0.],
           [0., 1.]])]

    See Also
    ========
    kraus_to_choi

    References
    ==========
    .. [Rigetti20] Forest Benchmarking (Rigetti).
        https://github.com/rigetti/forest-benchmarking

    :param choi_mat: a dim**2 by dim**2 choi matrix
    :param tol: optional threshold parameter for eigenvalues/kraus ops to be discarded
    :return: List of Kraus operators
    """
    eigvals, v_mat = np.linalg.eigh(choi_mat)
    return [
        np.lib.scimath.sqrt(eigval) * unvec(np.array([evec]).T)
        for eigval, evec in zip(eigvals, v_mat.T) if abs(eigval) > tol
    ]
Example #5
0
def schmidt_decomposition(
        vec: np.ndarray,
        dim: Union[int, List[int], np.ndarray] = None,
        k_param: int = 0) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    r"""
    Compute the Schmidt decomposition of a bipartite vector [WikSD]_.

    Examples
    ==========

    Consider the :math:`3`-dimensional maximally entangled state

    .. math::
        u = \frac{1}{\sqrt{3}} \left( |000 \rangle + |111 \rangle + |222 \rangle \right)

    We can generate this state using the :code:`toqito` module as follows.

    >>> from toqito.states import max_entangled
    >>> max_entangled(3)
    [[0.57735027],
     [0.        ],
     [0.        ],
     [0.        ],
     [0.57735027],
     [0.        ],
     [0.        ],
     [0.        ],
     [0.57735027]]

    Computing the Schmidt decomposition of :math:`u`, we can obtain the corresponding singular
    values of :math:`u` as

    .. math::
        \frac{1}{\sqrt{3}} \left[1, 1, 1 \right]^{\text{T}}.

    >>> from toqito.states import max_entangled
    >>> from toqito.state_ops import schmidt_decomposition
    >>> singular_vals, u_mat, vt_mat = schmidt_decomposition(max_entangled(3))
    >>> singular_vals
    [[0.57735027]
     [0.57735027]
     [0.57735027]]
    >>> u_mat
    [[1. 0. 0.]
     [0. 1. 0.]
     [0. 0. 1.]]
    >>> vt_mat
    [[1. 0. 0.]
     [0. 1. 0.]
     [0. 0. 1.]]

    References
    ==========
    .. [WikSD] Wikipedia: Schmidt decomposition
        https://en.wikipedia.org/wiki/Schmidt_decomposition

    :param vec: A bipartite quantum state to compute the Schmidt decomposition of.
    :param dim: An array consisting of the dimensions of the subsystems (default gives subsystems
                equal dimensions).
    :param k_param: How many terms of the Schmidt decomposition should be computed (default is 0).
    :return: The Schmidt decomposition of the :code:`vec` input.
    """
    eps = np.finfo(float).eps

    if dim is None:
        dim = np.round(np.sqrt(len(vec)))
    if isinstance(dim, list):
        dim = np.array(dim)

    # Allow the user to enter a single number for `dim`.
    if isinstance(dim, float):
        dim = np.array([dim, len(vec) / dim])
        if np.abs(dim[1] - np.round(dim[1])) >= 2 * len(vec) * eps:
            raise ValueError(
                "InvalidDim: The value of `dim` must evenly divide"
                " `len(vec)`; please provide a `dim` array "
                "containing the dimensions of the subsystems.")
        dim[1] = np.round(dim[1])

    # Try to guess whether SVD or SVDS will be faster, and then perform the
    # appropriate singular value decomposition.
    adj = 20 + 1000 * (not issparse(vec))

    # Just a few Schmidt coefficients.
    if 0 < k_param <= np.ceil(np.min(dim) / adj):
        u_mat, singular_vals, vt_mat = linalg.svds(
            linalg.LinearOperator(unvec(vec), k_param))
        vt_mat = vt_mat.conj().T

    # Otherwise, use lots of Schmidt coefficients.
    else:
        # print("VEC\n", vec)
        # print("REV\n", np.reshape(vec, dim[::-1].astype(int)))
        # print("UNVEC\n", unvec(vec))
        u_mat, singular_vals, vt_mat = np.linalg.svd(unvec(vec))
        print(type(vec))
        # u_mat, singular_vals, vt_mat = np.linalg.svd(np.reshape(vec, dim[::-1].astype(int)))
        vt_mat = vt_mat.conj().T

    if k_param > 0:
        u_mat = u_mat[:, :k_param]
        singular_vals = singular_vals[:k_param]
        vt_mat = vt_mat[:, :k_param]

    # singular_vals = np.diag(singular_vals)
    singular_vals = singular_vals.reshape(-1, 1)
    if k_param == 0:
        # Schmidt rank.
        r_param = np.sum(
            singular_vals > np.max(dim) * np.spacing(singular_vals[0]))
        # Schmidt coefficients.
        singular_vals = singular_vals[:r_param]
        u_mat = u_mat[:, :r_param]
        vt_mat = vt_mat[:, :r_param]

    # u_mat = u_mat.conj().T
    return singular_vals, vt_mat, u_mat