def is_unital( phi: Union[np.ndarray, List[List[np.ndarray]]], rtol: float = 1e-05, atol: float = 1e-08, ) -> bool: r""" Determine whether the given channel is unital [WatUnital18]_. A map :math:`\Phi \in \text{T} \left(\mathcal{X}, \mathcal{Y} \right)` is *unital* if it holds that .. math:: \Phi(\mathbb{I}_{\mathcal{X}}) = \mathbb{I}_{\mathcal{Y}}. Examples ========== Consider the channel whose Choi matrix is the swap operator. This channel is an example of a unital channel. >>> from toqito.perms import swap_operator >>> from toqito.channel_props import is_unital >>> >>> choi = swap_operator(3) >>> is_unital(choi) True Alternatively, the channel whose Choi matrix is the depolarizing channel is an example of a non-unital channel. >>> from toqito.channels import depolarizing >>> from toqito.channel_props import is_unital >>> >>> choi = depolarizing(4) >>> is_unital(choi) False References ========== .. [WatUnital18] Watrous, John. "The theory of quantum information." Chapter: Unital channels and majorization Cambridge University Press, 2018. :param phi: The channel provided as either a Choi matrix or a list of Kraus operators. :param rtol: The relative tolerance parameter (default 1e-05). :param atol: The absolute tolerance parameter (default 1e-08). :return: :code:`True` if the channel is unital, and :code:`False` otherwise. """ # If the variable `phi` is provided as a list, we assume this is a list of Kraus operators. if isinstance(phi, list): phi = kraus_to_choi(phi) dim = int(np.sqrt(phi.shape[0])) # Channel is unital if :code:`mat` is the identity matrix. mat = apply_channel(np.identity(dim), phi) return is_identity(mat, rtol=rtol, atol=atol)
def test_dephasing_partially_dephasing(): """The partially dephasing channel for `p = 0.5`.""" test_input_mat = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) expected_res = np.array([[17.5, 0, 0, 0], [0, 20, 0, 0], [0, 0, 22.5, 0], [0, 0, 0, 25]]) res = apply_channel(test_input_mat, dephasing(4, 0.5)) bool_mat = np.isclose(expected_res, res) np.testing.assert_equal(np.all(bool_mat), True)
def test_dephasing_completely_dephasing(): """The completely dephasing channel kills everything off diagonal.""" test_input_mat = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) expected_res = np.array([[1, 0, 0, 0], [0, 6, 0, 0], [0, 0, 11, 0], [0, 0, 0, 16]]) res = apply_channel(test_input_mat, dephasing(4)) bool_mat = np.isclose(expected_res, res) np.testing.assert_equal(np.all(bool_mat), True)
def test_depolarizing_complete_depolarizing(): """Maps every density matrix to the maximally-mixed state.""" test_input_mat = np.array( [[1 / 2, 0, 0, 1 / 2], [0, 0, 0, 0], [0, 0, 0, 0], [1 / 2, 0, 0, 1 / 2]] ) expected_res = ( 1 / 4 * np.array([[1 / 2, 0, 0, 1 / 2], [0, 0, 0, 0], [0, 0, 0, 0], [1 / 2, 0, 0, 1 / 2]]) ) res = apply_channel(test_input_mat, depolarizing(4)) bool_mat = np.isclose(expected_res, res) np.testing.assert_equal(np.all(bool_mat), True)
def test_apply_channel_choi(): """ The swap operator is the Choi matrix of the transpose map. The following test is a (non-ideal, but illustrative) way of computing the transpose of a matrix. """ test_input_mat = np.array([[1, 4, 7], [2, 5, 8], [3, 6, 9]]) expected_res = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) res = apply_channel(test_input_mat, swap_operator(3)) bool_mat = np.isclose(res, expected_res) np.testing.assert_equal(np.all(bool_mat), True)
def test_depolarizing_partially_depolarizing(): """The partially depolarizing channel for `p = 0.5`.""" test_input_mat = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) expected_res = np.array( [ [17.125, 0.25, 0.375, 0.5], [0.625, 17.75, 0.875, 1], [1.125, 1.25, 18.375, 1.5], [1.625, 1.75, 1.875, 19], ] ) res = apply_channel(test_input_mat, depolarizing(4, 0.5)) bool_mat = np.isclose(expected_res, res) np.testing.assert_equal(np.all(bool_mat), True)
def test_apply_channel_kraus(): """ Apply Kraus map. The following test computes PHI(X) where X = [[1, 2], [3, 4]] and where PHI is the superoperator defined by: Phi(X) = [[1,5],[1,0],[0,2]] X [[0,1][2,3][4,5]].conj().T - [[1,0],[0,0],[0,1]] X [[0,0][1,1],[0,0]].conj().T """ test_input_mat = np.array([[1, 2], [3, 4]]) kraus_1 = np.array([[1, 5], [1, 0], [0, 2]]) kraus_2 = np.array([[0, 1], [2, 3], [4, 5]]) kraus_3 = np.array([[-1, 0], [0, 0], [0, -1]]) kraus_4 = np.array([[0, 0], [1, 1], [0, 0]]) expected_res = np.array([[22, 95, 174], [2, 8, 14], [8, 29, 64]]) res = apply_channel(test_input_mat, [[kraus_1, kraus_2], [kraus_3, kraus_4]]) bool_mat = np.isclose(res, expected_res) np.testing.assert_equal(np.all(bool_mat), True)
def partial_channel( rho: np.ndarray, phi_map: Union[np.ndarray, List[List[np.ndarray]]], sys: int = 2, dim: Union[List[int], np.ndarray] = None, ) -> np.ndarray: r"""Apply channel to a subsystem of an operator [WatPMap18]_. Applies the operator .. math:: \left(\mathbb{I} \otimes \Phi \right) \left(\rho \right). In other words, it is the result of applying the channel :math:`\Phi` to the second subsystem of :math:`\rho`, which is assumed to act on two subsystems of equal dimension. The input :code:`phi_map` should be provided as a Choi matrix. This function is adapted from the QETLAB package. Examples ========== >>> from toqito.channel_ops import partial_channel >>> from toqito.channels import depolarizing >>> rho = np.array([[0.3101, -0.0220-0.0219*1j, -0.0671-0.0030*1j, -0.0170-0.0694*1j], >>> [-0.0220+0.0219*1j, 0.1008, -0.0775+0.0492*1j, -0.0613+0.0529*1j], >>> [-0.0671+0.0030*1j, -0.0775-0.0492*1j, 0.1361, 0.0602 + 0.0062*1j], >>> [-0.0170+0.0694*1j, -0.0613-0.0529*1j, 0.0602-0.0062*1j, 0.4530]]) >>> phi_x = partial_channel(rho, depolarizing(2)) [[ 0.20545+0.j 0. +0.j -0.0642 +0.02495j 0. +0.j ] [ 0. +0.j 0.20545+0.j 0. +0.j -0.0642 +0.02495j] [-0.0642 -0.02495j 0. +0.j 0.29455+0.j 0. +0.j ] [ 0. +0.j -0.0642 -0.02495j 0. +0.j 0.29455+0.j ]] >>> from toqito.channel_ops import partial_channel >>> from toqito.channels import depolarizing >>> rho = np.array([[0.3101, -0.0220-0.0219*1j, -0.0671-0.0030*1j, -0.0170-0.0694*1j], >>> [-0.0220+0.0219*1j, 0.1008, -0.0775+0.0492*1j, -0.0613+0.0529*1j], >>> [-0.0671+0.0030*1j, -0.0775-0.0492*1j, 0.1361, 0.0602 + 0.0062*1j], >>> [-0.0170+0.0694*1j, -0.0613-0.0529*1j, 0.0602-0.0062*1j, 0.4530]]) >>> phi_x = partial_channel(rho, depolarizing(2), 1) [[0.2231+0.j 0.0191-0.00785j 0. +0.j 0. +0.j ] [0.0191+0.00785j 0.2769+0.j 0. +0.j 0. +0.j ] [0. +0.j 0. +0.j 0.2231+0.j 0.0191-0.00785j] [0. +0.j 0. +0.j 0.0191+0.00785j 0.2769+0.j ]] References ========== .. [WatPMap18] Watrous, John. The theory of quantum information. Cambridge University Press, 2018. :param rho: A matrix. :param phi_map: The map to partially apply. :param sys: Scalar or vector specifying the size of the subsystems. :param dim: Dimension of the subsystems. If :code:`None`, all dimensions are assumed to be equal. :return: The partial map :code:`phi_map` applied to matrix :code:`rho`. """ if dim is None: dim = np.round(np.sqrt(list(rho.shape))).conj().T * np.ones(2) if isinstance(dim, list): dim = np.array(dim) # Force dim to be a row vector. if dim.ndim == 1: dim = dim.T.flatten() dim = np.array([dim, dim]) prod_dim_r1 = int(np.prod(dim[0, :sys - 1])) prod_dim_c1 = int(np.prod(dim[1, :sys - 1])) prod_dim_r2 = int(np.prod(dim[0, sys:])) prod_dim_c2 = int(np.prod(dim[1, sys:])) if isinstance(phi_map, list): # Compute the Kraus operators on the full system. s_phi_1, s_phi_2 = len(phi_map), len(phi_map[0]) # Map is completely positive. if s_phi_2 == 1 or s_phi_1 == 1 and s_phi_2 > 2: phi = [] for i, _ in enumerate(phi_map): phi.append( np.kron( np.kron(np.identity(prod_dim_r1), phi_map[i]), np.identity(prod_dim_r2), )) phi_x = apply_channel(rho, phi) else: phi_1 = [] for i, _ in enumerate(phi_map): phi_1.append( np.kron( np.kron(np.identity(prod_dim_r1), phi_map[i][0]), np.identity(prod_dim_r2), )) phi_2 = [] for i, _ in enumerate(phi_map): phi_2.append( np.kron( np.kron(np.identity(prod_dim_c1), phi_map[i][1]), np.identity(prod_dim_c2), )) phi_x = [list(l) for l in zip(phi_1, phi_2)] phi_x = apply_channel(rho, phi_x) return phi_x # The `phi_map` variable is provided as a Choi matrix. if isinstance(phi_map, np.ndarray): dim_phi = phi_map.shape dim = np.array([ [ prod_dim_r2, prod_dim_r2, int(dim[0, sys - 1]), int(dim_phi[0] / dim[0, sys - 1]), prod_dim_r1, prod_dim_r1, ], [ prod_dim_c2, prod_dim_c2, int(dim[1, sys - 1]), int(dim_phi[1] / dim[1, sys - 1]), prod_dim_c1, prod_dim_c1, ], ]) psi_r1 = max_entangled(prod_dim_r2, False, False) psi_c1 = max_entangled(prod_dim_c2, False, False) psi_r2 = max_entangled(prod_dim_r1, False, False) psi_c2 = max_entangled(prod_dim_c1, False, False) phi_map = permute_systems( np.kron(np.kron(psi_r1 * psi_c1.conj().T, phi_map), psi_r2 * psi_c2.conj().T), [1, 3, 5, 2, 4, 6], dim, ) phi_x = apply_channel(rho, phi_map) return phi_x raise ValueError("The `phi_map` variable is assumed to be provided as " "either a Choi matrix or a list of Kraus operators.")
def test_apply_channel_invalid_input(): """Invalid input for apply map.""" with np.testing.assert_raises(ValueError): apply_channel(np.array([[1, 2], [3, 4]]), 2)