def test_bell_state_pt(self): """Test partial transpose on a Bell state.""" rho = bell(2) * bell(2).conj().T expected_res = np.array([[0, 0, 0, 1 / 2], [0, 1 / 2, 0, 0], [0, 0, 1 / 2, 0], [1 / 2, 0, 0, 0]]) res = partial_transpose(rho) self.assertEqual(np.allclose(res, expected_res), True)
def test_partial_transpose_sys_vec_dim_vec(self): """Variables `sys` and `dim` defined as vector.""" test_input_mat = np.arange(1, 17).reshape(4, 4) expected_res = np.array([[1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15], [4, 8, 12, 16]]) res = partial_transpose(test_input_mat, [1, 2], [2, 2]) bool_mat = np.isclose(res, expected_res) self.assertEqual(np.all(bool_mat), True)
def negativity(rho: np.ndarray, dim: Union[List[int], int] = None) -> float: r""" Compute the negativity of a bipartite quantum state. The negativity of a subsystem can be defined in terms of a density matrix :math:`\rho`: .. math:: \mathcal{N}(\rho) \equiv \frac{||\rho^{\Gamma_A}||_1-1}{2} Calculate the negativity of the quantum state `rho`, assuming that the two subsystems on which `rho` acts are of equal dimension (if the local dimensions are unequal, specify them in the optional `dim` argument). The negativity of `rho` is the sum of the absolute value of the negative eigenvalues of the partial transpose of `rho`. References: [1] Wikipedia page for negativity (quantum mechanics): https://en.wikipedia.org/wiki/Negativity_(quantum_mechanics) :param rho: A density matrix of a pure state vector. :param dim: The default has both subsystems of equal dimension. :return: A value between 0 and 1 that corresponds to the negativity of :math:`\rho`. """ # Allow the user to input either a pure state vector or a density matrix. rho = pure_to_mixed(rho) rho_dims = rho.shape round_dim = np.round(np.sqrt(rho_dims)) if dim is None: dim = np.array([round_dim]) dim = dim.T if isinstance(dim, list): dim = np.array(dim) # Allow the user to enter a single number for dim. if isinstance(dim, int): dim = np.array([dim, rho_dims[0] / dim]) if abs(dim[1] - np.round(dim[1])) >= 2 * rho_dims[0] * np.finfo(float).eps: raise ValueError("InvalidDim: If `dim` is a scalar, `rho` must be " "square and `dim` must evenly divide `len(rho)`. " "Please provide the `dim` array containing the " "dimensions of the subsystems.") dim[1] = np.round(dim[1]) if np.prod(dim) != rho_dims[0]: raise ValueError("InvalidDim: Please provide local dimensions in the " "argument `dim` that match the size of `rho`.") # Compute the negativity. return (lin_alg.norm(partial_transpose(rho, 2, dim), ord="nuc") - 1) / 2
def realignment(input_mat: np.ndarray, dim=None) -> np.ndarray: r""" Compute the realignment of a bipartite operator. Gives the realignment of the matrix INPUT_MAT, where it is assumed that the number of rows and columns of INPUT_MAT are both perfect squares and both subsystems have equal dimension. The realignment is defined by mapping the operator :math:`|ij \rangle \langle kl |` to :math:`|ik \rangle \langle jl |` and extending linearly. If `input_mat` is non-square, different row and column dimensions can be specified by putting the row dimensions in the first row of `dim` and the column dimensions in the second row of `dim`. References: [1] Lupo, Cosmo, Paolo Aniello, and Antonello Scardicchio. "Bipartite quantum systems: on the realignment criterion and beyond." Journal of Physics A: Mathematical and Theoretical 41.41 (2008): 415301. https://arxiv.org/abs/0802.2019 :param input_mat: The input matrix. :param dim: Default has all equal dimensions. :return: The realignment map matrix. """ eps = np.finfo(float).eps dim_mat = input_mat.shape round_dim = np.round(np.sqrt(dim_mat)) if dim is None: dim = np.transpose(np.array([round_dim])) if isinstance(dim, list): dim = np.array(dim) if isinstance(dim, int): dim = np.array([[dim], [dim_mat[0] / dim]]) if np.abs(dim[1] - np.round(dim[1])) >= 2 * dim_mat[0] * eps: raise ValueError("InvalidDim:") dim[1] = np.round(dim[1]) dim = np.array([[1], [4]]) if min(dim.shape) == 1: dim = dim[:].T dim = functools.reduce(operator.iconcat, dim, []) dim = np.array([dim, dim]) # dim = functools.reduce(operator.iconcat, dim, []) dim_x = np.array([[dim[0][1], dim[0][0]], [dim[1][0], dim[1][1]]]) dim_y = np.array([[dim[1][0], dim[0][0]], [dim[0][1], dim[1][1]]]) x_tmp = swap(input_mat, [1, 2], dim, True) y_tmp = partial_transpose(x_tmp, sys=1, dim=dim_x) return swap(y_tmp, [1, 2], dim_y, True)
def test_partial_transpose_16_by_16(self): """Partial transpose on a 16-by-16 matrix.""" test_input_mat = np.arange(1, 257).reshape(16, 16) res = partial_transpose(test_input_mat, [1, 3], [2, 2, 2, 2]) first_expected_row = np.array([ 1, 2, 33, 34, 5, 6, 37, 38, 129, 130, 161, 162, 133, 134, 165, 166 ]) first_expected_col = np.array( [1, 17, 3, 19, 65, 81, 67, 83, 9, 25, 11, 27, 73, 89, 75, 91]) self.assertEqual(np.allclose(res[0, :], first_expected_row), True) self.assertEqual(np.allclose(res[:, 0], first_expected_col), True)
def test_partial_transpose_norm_diff(self): """ Apply partial transpose to first and second subsystem. Applying the transpose to both the first and second subsystems results in the standard transpose of the matrix. """ test_input_mat = np.arange(1, 17).reshape(4, 4) res = np.linalg.norm( partial_transpose(test_input_mat, [1, 2]) - test_input_mat.conj().T) expected_res = 0 self.assertEqual(np.isclose(res, expected_res), True)
def test_partial_transpose_sys(self): """ Default partial transpose `sys` argument. By specifying the `sys` argument, you can perform the transposition on the first subsystem instead: """ test_input_mat = np.arange(1, 17).reshape(4, 4) expected_res = np.array([[1, 2, 9, 10], [5, 6, 13, 14], [3, 4, 11, 12], [7, 8, 15, 16]]) res = partial_transpose(test_input_mat, 1) bool_mat = np.isclose(res, expected_res) self.assertEqual(np.all(bool_mat), True)
def test_partial_transpose(self): """ Default partial_transpose. By default, the partial_transpose function performs the transposition on the second subsystem. """ test_input_mat = np.arange(1, 17).reshape(4, 4) expected_res = np.array([[1, 5, 3, 7], [2, 6, 4, 8], [9, 13, 11, 15], [10, 14, 12, 16]]) res = partial_transpose(test_input_mat) bool_mat = np.isclose(expected_res, res) self.assertEqual(np.all(bool_mat), True)
def is_ppt(mat: np.ndarray, sys: int = 2, dim: Union[int, List[int]] = None, tol: float = None) -> bool: """ Determine whether or not a matrix has positive partial transpose. Yields either `True` or `False`, indicating that `mat` does or does not have positive partial transpose (within numerical error). The variable `mat` is assumed to act on bipartite space. For shared systems of 2 ⊗ 2 or 2 ⊗ 3, the PPT criterion serves as a method to determine whether a given state is entangled or separable. Therefore, for systems of this size, the return value "True" would indicate that the state is separable and a value of "False" would indicate the state is entangled. References: [1] Quantiki: Positive partial transpose https://www.quantiki.org/wiki/positive-partial-transpose :param mat: A square matrix. :param sys: Scalar or vector indicating which subsystems the transpose should be applied on. :param dim: The dimension is a vector containing the dimensions of the subsystems on which `mat` acts. :param tol: Tolerance with which to check whether `mat` is PPT. :return: True if `mat` is PPT and False if not. """ eps = np.finfo(float).eps sqrt_rho_dims = np.round(np.sqrt(list(mat.shape))) if dim is None: dim = np.array([[sqrt_rho_dims[0], sqrt_rho_dims[0]], [sqrt_rho_dims[1], sqrt_rho_dims[1]]]) if tol is None: tol = np.sqrt(eps) return is_psd(partial_transpose(mat, sys, dim), tol)
def ppt_distinguishability(states: List[np.ndarray], probs: List[float] = None) -> float: r""" Compute probability of distinguishing a state via PPT measurements. Implements the semidefinite program (SDP) whose optimal value is equal to the maximum probability of perfectly distinguishing orthogonal maximally entangled states using any PPT measurement; a measurement whose operators are positive under partial transpose. This SDP was explicitly provided in [1]. Specifically, the function implements the dual problem (as this is computationally more efficient) and is defined as: .. math:: \begin{equation} \begin{aligned} \text{minimize:} \quad & \frac{1}{k} \text{Tr}(Y) \\ \text{subject to:} \quad & Y \geq \text{T}_{\mathcal{A}} (\rho_j), \quad j = 1, \ldots, k, \\ & Y \in \text{Herm}(\mathcal{A} \otimes \mathcal{B}). \end{aligned} \end{equation} References: [1] Cosentino, Alessandro. "Positive-partial-transpose-indistinguishable states via semidefinite programming." Physical Review A 87.1 (2013): 012321. https://arxiv.org/abs/1205.1031 :param states: A list of density operators (matrices) corresponding to quantum states. :param probs: A list of probabilities where `probs[i]` corresponds to the probability that `states[i]` is selected by Alice. :return: The optimal probability with which the states can be distinguished via PPT measurements. """ # Assume that at least one state is provided. if states is None or states == []: raise ValueError("InvalidStates: There must be at least one state " "provided.") # Assume uniform probability if no specific distribution is given. if probs is None: probs = [1 / len(states)] * len(states) if not np.isclose(sum(probs), 1): raise ValueError("Invalid: Probabilities must sum to 1.") dim_x, dim_y = states[0].shape # The variable `states` is provided as a list of vectors. Transform them # into density matrices. if dim_y == 1: for i, state_ket in enumerate(states): states[i] = state_ket * state_ket.conj().T constraints = [] y_var = cvxpy.Variable((dim_x, dim_x), hermitian=True) objective = 1 / len(states) * cvxpy.Minimize(cvxpy.trace( cvxpy.real(y_var))) dim = int(np.log2(dim_x)) dim_list = [2] * int(np.log2(dim_x)) sys_list = list(range(1, dim, 2)) for i, _ in enumerate(states): constraints.append( cvxpy.real(y_var) >> partial_transpose( states[i], sys=sys_list, dim=dim_list)) problem = cvxpy.Problem(objective, constraints) sol_default = problem.solve() return sol_default
def test_non_square_matrix_2(self): """Matrix must be square.""" with self.assertRaises(ValueError): rho = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) partial_transpose(rho, 2, [2])
def test_non_square_matrix(self): """Matrix must be square.""" with self.assertRaises(ValueError): test_input_mat = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [13, 14, 15, 16]]) partial_transpose(test_input_mat)