Source code for toqito.channel_ops.choi_to_kraus

"""Computes a list of Kraus operators from the Choi matrix."""

import numpy as np

from toqito.channel_props.channel_dim import channel_dim
from toqito.matrix_ops import unvec
from toqito.matrix_props import is_hermitian, is_positive_semidefinite


[docs] def choi_to_kraus( choi_mat: np.ndarray, tol: float = 1e-9, dim: int | list[int] | np.ndarray | None = None ) -> list[np.ndarray] | list[list[np.ndarray]]: r"""Compute a list of Kraus operators from the Choi matrix from [@Rigetti_2022_Forest]. Note that unlike the Choi or natural representation of operators, the Kraus representation is *not* unique. If the input channel maps \(M_{r,c}\) to \(M_{x,y}\) then `dim` should be the list `[[r,x], [c,y]]`. If it maps \(M_m\) to \(M_n\), then `dim` can simply be the vector `[m,n]`. For completely positive maps the output is a single flat list of numpy arrays since the left and right Kraus maps are the same. This function has been adapted from [@Rigetti_2022_Forest] and QETLAB [@QETLAB_link]. Examples: Consider taking the Kraus operators of the Choi matrix that characterizes the "swap operator" defined as \[ \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} \] The corresponding Kraus operators of the swap operator are given as follows, \[ \begin{equation} \big[ \frac{1}{\sqrt{2}} \begin{pmatrix} 0 & 1 \\ -1 & 0 \end{pmatrix}, \frac{1}{\sqrt{2}} \begin{pmatrix} 0 & -1 \\ 1 & 0 \end{pmatrix} \big], \big[ \frac{1}{\sqrt{2}} \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}, \frac{1}{\sqrt{2}} \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} \big], \big[ \begin{pmatrix} 1 & 0 \\ 0 & 0 \end{pmatrix}, \begin{pmatrix} 1 & 0 \\ 0 & 0 \end{pmatrix} \big], \big[ \begin{pmatrix} 0 & 0 \\ 0 & 1 \end{pmatrix}, \begin{pmatrix} 0 & 0 \\ 0 & 1 \end{pmatrix} \big] \end{equation} \] This can be verified in `|toqito⟩` as follows. ```python exec="1" source="above" 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) for i, pair in enumerate(kraus_ops): print(f"\nKraus Pair {i+1}:") for j, op in enumerate(pair): print(f" Operator {j+1}:\n{np.array_str(op, precision=4, suppress_small=True)}") ``` !!! See Also [kraus_to_choi][toqito.channel_ops.kraus_to_choi.kraus_to_choi] Args: choi_mat: A Choi matrix tol: optional threshold parameter for eigenvalues/kraus ops to be discarded dim: A scalar, vector or matrix containing the input and output dimensions of Choi matrix. Returns: List of Kraus operators """ d_in, d_out, _ = channel_dim(choi_mat, dim=dim, compute_env_dim=False) if is_hermitian(choi_mat): eigvals, v_mat = np.linalg.eigh(choi_mat) kraus_0 = [ np.sqrt(abs(eigval)) * unvec(evec, shape=(d_out[0], d_in[0])) for eigval, evec in zip(eigvals, v_mat.T) if abs(eigval) > tol ] if is_positive_semidefinite(choi_mat): return kraus_0 kraus_1 = [ np.sign(eigval) * k_mat for eigval, k_mat in zip(filter(lambda eigval: abs(eigval) > tol, eigvals), kraus_0) ] else: u_mat, singular_values, vh_mat = np.linalg.svd(choi_mat, full_matrices=False) kraus_0 = [ np.sqrt(s_val) * unvec(evec, shape=(d_out[0], d_in[0])) for s_val, evec in zip(singular_values, u_mat.T) if abs(s_val) > tol ] kraus_1 = [ np.sqrt(s_val) * unvec(evec.conj(), shape=(d_out[1], d_in[1])) for s_val, evec in zip(singular_values, vh_mat) if abs(s_val) > tol ] return [[ka, kb] for ka, kb in zip(kraus_0, kraus_1)]