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 ]
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)
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)
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 ]
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