def rand_map_with_BCSZ_dist(D, K):
    """
    Given a Hilbert space dimension $D$ and a Kraus rank $K$, returns a
    $D^2 × D^2$ Choi matrix $J(Λ)$ of a channel drawn from the BCSZ distribution 
    with Kraus rank $K$.

    [RQO] Random quantum operations,
          Bruzda et al.,
          Physics Letters A 373, 320 (2009).
          https://doi.org/10.1016/j.physleta.2008.11.043
          https://arxiv.org/abs/0804.2361
    
    :param D: Hilbert space dimension (scalar).
    :param K: The rank of a state (scalar).
    :return: D^2 × D^2 Choi matrix, drawn from the BCSZ distribution with Kraus rank K.
    """
    # TODO: this ^^ is CPTP, might want a flag that allows for just CP quantum operations.
    X = ginibre_matrix_complex(D**2, K)
    rho = X @ X.conj().T
    rho_red = partial_trace(rho, [0], [D, D])
    # Note that Eqn. 8 of [RQO] uses a *row* stacking convention so in that case we would write
    # Q = np.kron(np.eye(D), sqrtm(la.inv(rho_red)))
    # But as we use column stacking we need:
    Q = np.kron(sqrtm(la.inv(rho_red)), np.eye(D))
    Z = Q @ rho @ Q
    return Z
def proj_choi_to_trace_preserving(choi: np.ndarray) -> np.ndarray:
    """
    Projects the Choi representation of a process to the closest processes in the space of trace
    preserving maps.

    Equation 12 of [PGD], but without vecing the Choi matrix. See choi_is_trace_preserving for
    comparison.

    :param choi: Choi representation of a process
    :return: Choi representation of the projected trace preserving process
    """
    dim = int(np.sqrt(choi.shape[0]))

    # trace out the output Hilbert space, keep the input space at index 0
    pt = partial_trace(choi, dims=[dim, dim], keep=[0])
    # isolate the part the violates the condition we want, namely pt = Id
    diff = pt - np.eye(dim)
    # we want to subtract off the violation from the larger operator, so 'invert' the partial_trace
    subtract = np.kron(diff / dim, np.eye(dim))
    return choi - subtract
def choi_is_trace_preserving(choi: np.ndarray,
                             rtol: float = 1e-05,
                             atol: float = 1e-08) -> bool:
    """
    Checks if  a quantum process, specified by a Choi matrix, is trace-preserving.

    :param choi: A dim**2 by dim**2 Choi matrix
    :param rtol: The relative tolerance parameter in np.allclose
    :param atol: The absolute tolerance parameter in np.allclose
    :return: Returns True if the quantum channel is trace-preserving with the given tolerance;
        False otherwise.
    """
    rows, cols = choi.shape
    dim = int(np.sqrt(rows))
    # the choi matrix acts on the Hilbert space H_{in} \otimes H_{out}.
    # We want to trace out H_{out} and so keep the H_{in} space at index 0.
    keep = [0]
    id_iff_tp = partial_trace(choi, keep, [dim, dim])
    # Equation 3.33 of [GRAPTN]
    return np.allclose(id_iff_tp, np.identity(dim), rtol=rtol, atol=atol)
예제 #4
0
def proj_to_tni(choi_vec):
    """
    Projects the vectorized Choi matrix of a process into the space of trace non-increasing maps. Equation 33 of [PGD]
    :param choi_vec: vectorized Choi representation of a process
    :return: The vectorized Choi representation of the projected TNI process
    """
    dim = int(np.sqrt(np.sqrt(choi_vec.size)))

    # trace out the output Hilbert space
    pt = partial_trace(unvec(choi_vec), dims=[dim, dim], keep=[0])

    hermitian = (pt + pt.conj().T) / 2  # enforce Hermiticity
    d, v = np.linalg.eigh(hermitian)
    d[d > 1] = 1  # enforce trace preserving
    D = np.diag(d)
    projection = v @ D @ v.conj().T

    trace_increasing_part = np.kron((pt - projection) / dim, np.eye(dim))

    return choi_vec - vec(trace_increasing_part)
def proj_choi_to_trace_non_increasing(choi: np.ndarray) -> np.ndarray:
    """
    Projects the Choi matrix of a process into the space of trace non-increasing maps.

    Equation 33 of [PGD]

    :param choi: Choi representation of a process
    :return: Choi representation of the projected trace non-increasing process
    """
    dim = int(np.sqrt(choi.shape[0]))

    # trace out the output Hilbert space
    pt = partial_trace(choi, dims=[dim, dim], keep=[0])

    hermitian = (pt + pt.conj().T) / 2  # enforce Hermiticity
    d, v = np.linalg.eigh(hermitian)
    d[d > 1] = 1  # enforce trace preserving
    D = np.diag(d)
    projection = v @ D @ v.conj().T

    trace_increasing_part = np.kron((pt - projection) / dim, np.eye(dim))

    return choi - trace_increasing_part
def apply_choi_matrix_2_state(choi: np.ndarray,
                              state: np.ndarray) -> np.ndarray:
    r"""
    Apply a quantum channel, specified by a Choi matrix (using the column stacking convention),
    to a state.

    The Choi matrix is a dim**2 by dim**2 matrix and the state rho is a dim by dim matrix. The
    output state is

    rho_{out} = Tr_{A_{in}}[(rho^T \otimes Id) Choi_matrix ],

    where T denotes transposition and Tr_{A_{in}} is the partial trace over input Hilbert space H_{
    A_{in}}; the Choi matrix representing a process mapping rho in H_{A_{in}} to rho_{out}
    in H_{B_{out}} is regarded as an operator on the space H_{A_{in}} \otimes H_{B_{out}}.


    :param choi: a dim**2 by dim**2 matrix
    :param state: A dim by dim ndarray which is the density matrix for the state
    :return: a dim by dim matrix.
    """
    dim = int(np.sqrt(np.asarray(choi).shape[0]))
    dims = [dim, dim]
    tot_matrix = np.kron(state.transpose(), np.identity(dim)) @ choi
    return partial_trace(tot_matrix, [1], dims)
def test_proj_to_tni():
    state = np.array([[0., 0., 0., 0.], [0., 1.01, 1.01, 0.], [0., 1., 1., 0.], [0., 0., 0., 0.]])
    trace_non_increasing = unvec(proj_to_tni(vec(state)))
    pt = partial_trace(trace_non_increasing, dims=[2, 2], keep=[0])
    assert np.allclose(pt, np.eye(2))