def test_max_ent_2(self): """Generate maximally entangled state: `1/sqrt(2) * (|00> + |11>)`.""" e_0, e_1 = ket(2, 0), ket(2, 1) expected_res = 1 / np.sqrt(2) * (np.kron(e_0, e_0) + np.kron(e_1, e_1)) res = max_entangled(2) bool_mat = np.isclose(res, expected_res) self.assertEqual(np.all(bool_mat), True)
def test_max_ent_2_0_0(self): """Generate maximally entangled state: `|00> + |11>`.""" e_0, e_1 = ket(2, 0), ket(2, 1) expected_res = 1 * (np.kron(e_0, e_0) + np.kron(e_1, e_1)) res = max_entangled(2, False, False) bool_mat = np.isclose(res, expected_res) self.assertEqual(np.all(bool_mat), True)
def test_schmidt_decomp_max_ent(self): """Schmidt decomposition of the 3-D maximally entangled state.""" singular_vals, u_mat, vt_mat = schmidt_decomposition(max_entangled(3)) expected_u_mat = np.identity(3) expected_vt_mat = np.identity(3) expected_singular_vals = 1 / np.sqrt(3) * np.array([[1], [1], [1]]) bool_mat = np.isclose(expected_u_mat, u_mat) self.assertEqual(np.all(bool_mat), True) bool_mat = np.isclose(expected_vt_mat, vt_mat) self.assertEqual(np.all(bool_mat), True) bool_mat = np.isclose(expected_singular_vals, singular_vals) self.assertEqual(np.all(bool_mat), True)
def reduction_map(dim: int, k: int = 1) -> np.ndarray: r""" Produce the reduction map. If `k = 1`, this returns the Choi matrix of the reduction map which is a positive map on `dim`-by-`dim` matrices. For a different value of `k`, this yields the Choi matrix of the map defined by: .. math:: R(X) = k * \text{Tr}(X) * \mathbb{I} - X, where :math:`\mathbb{I}` is the identity matrix. This map is :math:`k`-positive. Examples ========== Using `toqito`, we can generate the $3$-dimensional (or standard) reduction map as follows. >>> from toqito.channels.channels.reduction_map import reduction_map >>> reduction_map(3).toarray() array([[ 0., 0., 0., 0., -1., 0., 0., 0., -1.], [ 0., 1., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 1., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 1., 0., 0., 0., 0., 0.], [-1., 0., 0., 0., 0., 0., 0., 0., -1.], [ 0., 0., 0., 0., 0., 1., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 1., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 1., 0.], [-1., 0., 0., 0., -1., 0., 0., 0., 0.]]) :param dim: A positive integer (the dimension of the reduction map). :param k: If this positive integer is provided, the script will instead return the Choi matrix of the following linear map: Phi(X) := K * Tr(X)I - X. :return: The reduction map. """ psi = max_entangled(dim, True, False) return k * identity(dim ** 2) - psi * psi.conj().T
def dephasing(dim: int, param_p: float = 0) -> np.ndarray: r""" Produce the partially dephasing channel [WatDeph18]_. The Choi matrix of the completely dephasing channel that acts on `dim`-by-`dim` matrices. Let :math:`\Sigma` be an alphabet and let :math:`\mathcal{X} = \mathbb{C}^{\Sigma}`. The map :math:`\Delta \in \text{T}(\mathcal{X})` defined as .. math:: \Delta(X) = \sum_{a \in \Sigma} X(a, a) E_{a,a} for every :math:`X \in \text{L}(\mathcal{X})` is defined as the *completely dephasing channel*. Examples ========== The completely dephasing channel maps kills everything off the diagonal. Consider the following matrix .. math:: \rho = \begin{pmatrix} 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 \\ 9 & 10 & 11 & 12 \\ 13 & 14 & 15 & 16 \end{pmatrix}. Applying the dephasing channel to :math:`\rho` we have that .. math:: \Phi(\rho) = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 6 & 0 & 0 \\ 0 & 0 & 11 & 0 \\ 0 & 0 & 0 & 16 \end{pmatrix}. This can be observed in `toqito` as follows. >>> from toqito.channels.operations.apply_map import apply_map >>> from toqito.channels.channels.dephasing import dephasing >>> import numpy as np >>> test_input_mat = np.array( >>> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] >>> ) >>> apply_map(test_input_mat, dephasing(4)) [[ 1., 0., 0., 0.], [ 0., 6., 0., 0.], [ 0., 0., 11., 0.], [ 0., 0., 0., 16.]]) We may also consider setting the parameter `p = 0.5`. >>> from toqito.channels.operations.apply_map import apply_map >>> from toqito.channels.channels.dephasing import dephasing >>> import numpy as np >>> test_input_mat = np.array( >>> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] >>> ) >>> apply_map(test_input_mat, dephasing(4, 0.5)) [[17.5 0. 0. 0. ] [ 0. 20. 0. 0. ] [ 0. 0. 22.5 0. ] [ 0. 0. 0. 25. ]] References ========== .. [WatDeph18] Watrous, John. "The theory of quantum information." Section: "The completely dephasing channel". Cambridge University Press, 2018. :param dim: The dimensionality on which the channel acts. :param param_p: Default is 0. :return: The Choi matrix of the dephasing channel. """ # Compute the Choi matrix of the dephasing channel. # Gives a sparse non-normalized state. psi = max_entangled(dim=dim, is_sparse=False, is_normalized=False) return (1 - param_p) * np.diag(np.diag(psi * psi.conj().T)) + param_p * ( psi * psi.conj().T )
def depolarizing(dim: int, param_p: float = 0) -> np.ndarray: r""" Produce the partially depolarizng channel [WikDepo]_, [WatDepo18]_. The Choi matrix of the completely depolarizing channel that acts on `dim`-by-`dim` matrices. The *completely depolarizing channel* is defined as .. math:: \Omega(X) = \text{Tr}(X) \omega for all :math:`X \in \text{L}(\mathcal{X})`, where .. math:: \omega = \frac{\mathbb{I}_{\mathcal{X}}}{\text{dim}(\mathcal{X})} denotes the completely mixed stated defined with respect to the space :math:`\mathcal{X}`. Examples ========== The completely depolarizing channel maps every density matrix to the maximally-mixed state. For example, consider the density operator .. math:: \rho = \frac{1}{2} \begin{pmatrix} 1 & 0 & 0 & 1 \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ 1 & 0 & 0 & 1 \end{pmatrix} corresponding to one of the Bell states. Applying the depolarizing channel to :math:`\rho` we have that .. math:: \Phi(\rho) = \frac{1}{4} \begin{pmatrix} \frac{1}{2} & 0 & 0 & \frac{1}{2} \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ \frac{1}{2} & 0 & 0 & \frac{1}{2} \end{pmatrix}. This can be observed in `toqito` as follows. >>> from toqito.channels.operations.apply_map import apply_map >>> from toqito.channels.channels.depolarizing import depolarizing >>> import numpy as np >>> 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]] >>> ) >>> apply_map(test_input_mat, depolarizing(4)) [[0.125 0. 0. 0.125] [0. 0. 0. 0. ] [0. 0. 0. 0. ] [0.125 0. 0. 0.125]] >>> from toqito.channels.operations.apply_map import apply_map >>> from toqito.channels.channels.depolarizing import depolarizing >>> import numpy as np >>> test_input_mat = np.array( >>> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] >>> ) >>> apply_map(test_input_mat, depolarizing(4, 0.5)) [[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. ]] References ========== .. [WikDepo] Wikipedia: Quantum depolarizing channel https://en.wikipedia.org/wiki/Quantum_depolarizing .. [WatDepo18] Watrous, John. "The theory of quantum information." Section: "Replacement channels and the completely depolarizing channel". Cambridge University Press, 2018. :param dim: The dimensionality on which the channel acts. :param param_p: Default 0. :return: The Choi matrix of the completely depolarizing channel. """ # Compute the Choi matrix of the depolarizng channel. # Gives a sparse non-normalized state. psi = max_entangled(dim=dim, is_sparse=False, is_normalized=False) return (1 - param_p) * identity(dim** 2) / dim + param_p * (psi * psi.conj().T)
def partial_map( 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 map 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 `phi_map` should be provided as a Choi matrix. Examples ========== >>> from toqito.channels.channels.partial_map import partial_map >>> from toqito.channels.channels.depolarizing 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_map(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.channels.channels.partial_map import partial_map >>> from toqito.channels.channels.depolarizing 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_map(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 `None`, all dimensions are assumed to be equal. :return: The partial map `phi_map` applied to matrix `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:])) # Note: In the case where the Kraus operators refer to a CP map, this # approach of appending to the list may not work. if isinstance(phi_map, list): # The `phi_map` variable is provided as a list of Kraus operators. 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_map(rho, phi) 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_map(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 random_state_vector( dim: Union[List[int], int], is_real: bool = False, k_param: int = 0 ) -> np.ndarray: r"""Generate a random pure state vector. Examples ========== Using `toqito`, we may generate a random state vector. For instance, here is an example where we can genereate a :math:`2`-dimensional random state vector. >>> from toqito.random.random_state_vector import random_state_vector >>> vec = random_state_vector(2) >>> vec array([[0.50993973+0.15292408j], [0.27787332+0.79960122j]]) We can verify that this is in fact a valid state vector by computing the corresponding density matrix of the vector and checking if the density matrix is pure. >>> from toqito.states.properties.is_pure import is_pure >>> dm = vec.conj().T * vec >>> is_pure(dm) True :param dim: The number of rows (and columns) of the unitary matrix. :param is_real: Boolean denoting whether the returned matrix has real entries or not. Default is `False`. :param k_param: Default 0. :return: A `dim`-by-`dim` random unitary matrix. """ # Schmidt rank plays a role. if 0 < k_param < np.min(dim): # Allow the user to enter a single number for dim. if isinstance(dim, int): dim = [dim, dim] # If you start with a separable state on a larger space and multiply # the extra `k_param` dimensions by a maximally entangled state, you # get a Schmidt rank `<= k_param` state. psi = max_entangled(k_param, True, False).toarray() a_param = np.random.rand(dim[0] * k_param, 1) b_param = np.random.rand(dim[1] * k_param, 1) if not is_real: a_param = a_param + 1j * np.random.rand(dim[0] * k_param, 1) b_param = b_param + 1j * np.random.rand(dim[1] * k_param, 1) mat_1 = np.kron(psi.conj().T, np.identity(int(np.prod(dim)))) mat_2 = swap( np.kron(a_param, b_param), sys=[2, 3], dim=[k_param, dim[0], k_param, dim[1]], ) ret_vec = mat_1 * mat_2 return np.divide(ret_vec, np.linalg.norm(ret_vec)) # Schmidt rank is full, so ignore it. ret_vec = np.random.rand(dim, 1) if not is_real: ret_vec = ret_vec + 1j * np.random.rand(dim, 1) return np.divide(ret_vec, np.linalg.norm(ret_vec))
def test_is_product_entangled_state_3_sys(self): """Check that dimension argument as list is supported.""" ent_vec = max_entangled(4) self.assertEqual(is_product_vector(ent_vec, dim=[2, 2, 2, 2]), False)
def test_is_product_entangled_state(self): """Check that is_product_vector returns False for an entangled state.""" ent_vec = max_entangled(3) self.assertEqual(is_product_vector(ent_vec), False)
def choi_map(a_var: int = 1, b_var: int = 1, c_var: int = 0) -> np.ndarray: r""" Produce the Choi map or one of its generalizations [Choi92]_. The *Choi map* is a positive map on 3-by-3 matrices that is capable of detecting some entanglement that the transpose map is not. The standard Choi map defined with `a=1`, `b=1`, and `c=0` is the Choi matrix of the positive map defined in [Choi92]_. Many of these maps are capable of detecting PPT entanglement. Examples ========== The standard Choi map is given as .. math:: \begin{pmatrix} 1 & 0 & 0 & 0 & -1 & 0 & 0 & 0 & -1 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ -1 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & -1 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ -1 & 0 & 0 & 0 & -1 & 0 & 0 & 0 & 1 \end{pmatrix} We can generate the Choi map in `toqito` as follows. >>> from toqito.channels.channels.choi_map import choi_map >>> import numpy as np >>> choi_map() [[ 1., 0., 0., 0., -1., 0., 0., 0., -1.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 1., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 1., 0., 0., 0., 0., 0.], [-1., 0., 0., 0., 1., 0., 0., 0., -1.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 1., 0.], [-1., 0., 0., 0., -1., 0., 0., 0., 1.]]) The reduction map is the map :math:`R` defined by: .. math:: R(X) = \text{Tr}(X) \mathbb{I} - X. The matrix correspond to this is given as .. math:: \begin{pmatrix} 0 & 0 & 0 & 0 & -1 & 0 & 0 & 0 & -1 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ -1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & -1 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ -1 & 0 & 0 & 0 & -1 & 0 & 0 & 0 & 0 \end{pmatrix} The reduction map is the Choi map that arises when :math:`a = 0` and when :math:`b = c = 1`. We can obtain this matrix using `toqito` as follows. >>> from toqito.channels.channels.choi_map import choi_map >>> import numpy as np >>> choi_map(0, 1, 1) [[ 0., 0., 0., 0., -1., 0., 0., 0., -1.], [ 0., 1., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 1., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 1., 0., 0., 0., 0., 0.], [-1., 0., 0., 0., 0., 0., 0., 0., -1.], [ 0., 0., 0., 0., 0., 1., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 1., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 1., 0.], [-1., 0., 0., 0., -1., 0., 0., 0., 0.]]) References ========== .. [Choi92] Cho, Sung Je, Seung-Hyeok Kye, and Sa Ge Lee. "Generalized Choi maps in three-dimensional matrix algebra." Linear algebra and its applications 171 (1992): 213-224. https://www.sciencedirect.com/science/article/pii/002437959290260H :param a_var: Default integer for standard Choi map. :param b_var: Default integer for standard Choi map. :param c_var: Default integer for standard Choi map. :return: The Choi map (or one of its generalizations). """ psi = max_entangled(3, False, False) return ( np.diag( [a_var + 1, c_var, b_var, b_var, a_var + 1, c_var, c_var, b_var, a_var + 1] ) - psi * psi.conj().T )
def kraus_to_choi(kraus_ops: List[List[np.ndarray]], sys: int = 2) -> np.ndarray: r""" Compute the Choi matrix of a list of Kraus operators [WatKraus18]_. The Choi matrix of the list of Kraus operators, `kraus_ops`. The default convention is that the Choi matrix is the result of applying the map to the second subsystem of the standard maximally entangled (unnormalized) state. The Kraus operators are expected to be input as a list of numpy arrays. This function was adapted from the QETLAB package. Examples ========== The transpose map The Choi matrix of the transpose map is the swap operator. >>> import numpy as np >>> from toqito.channels.operations.kraus_to_choi import kraus_to_choi >>> kraus_1 = np.array([[1, 0], [0, 0]]) >>> kraus_2 = np.array([[1, 0], [0, 0]]).conj().T >>> kraus_3 = np.array([[0, 1], [0, 0]]) >>> kraus_4 = np.array([[0, 1], [0, 0]]).conj().T >>> kraus_5 = np.array([[0, 0], [1, 0]]) >>> kraus_6 = np.array([[0, 0], [1, 0]]).conj().T >>> kraus_7 = np.array([[0, 0], [0, 1]]) >>> kraus_8 = np.array([[0, 0], [0, 1]]).conj().T >>> >>> kraus_ops = [ >>> [kraus_1, kraus_2], >>> [kraus_3, kraus_4], >>> [kraus_5, kraus_6], >>> [kraus_7, kraus_8], >>> ] >>> kraus_to_choi(kraus_ops) [[1. 0. 0. 0.] [0. 0. 1. 0.] [0. 1. 0. 0.] [0. 0. 0. 1.]] References ========== .. [WatKraus18] Watrous, John. "The theory of quantum information." Section: "Kraus representations". Cambridge University Press, 2018. :param kraus_ops: A list of Kraus operators. :param sys: The dimension of the system (default is 2). :return: The corresponding Choi matrix of the provided Kraus operators. """ dim_op_1 = kraus_ops[0][0].shape[0] dim_op_2 = kraus_ops[0][0].shape[1] choi_mat = partial_map( max_entangled(dim_op_1, False, False) * max_entangled(dim_op_2, False, False).conj().T, kraus_ops, sys, np.array([[dim_op_1, dim_op_1], [dim_op_2, dim_op_2]]), ) return choi_mat