def test_partial_trace_16_by_16(self): """Test for 16-by-16 matrix.""" test_input_mat = np.arange(1, 257).reshape(16, 16) res = partial_trace(test_input_mat, [1, 3], [2, 2, 2, 2]) expected_res = np.array([ [344, 348, 360, 364], [408, 412, 424, 428], [600, 604, 616, 620], [664, 668, 680, 684], ]) bool_mat = np.isclose(expected_res, res) self.assertEqual(np.all(bool_mat), True)
def test_partial_trace_16_by_16_2(self): """Test for 16-by-16 matrix.""" test_input_mat = np.arange(1, 257).reshape(16, 16) res = partial_trace(test_input_mat, [1, 2], [2, 2, 2, 2]) expected_res = np.array([ [412, 416, 420, 424], [476, 480, 484, 488], [540, 544, 548, 552], [604, 608, 612, 616], ]) bool_mat = np.isclose(expected_res, res) self.assertEqual(np.all(bool_mat), True)
def test_partial_trace(self): """ Standard call to partial_trace. By default, the partial_trace function takes the trace over the second subsystem. """ 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([[7, 11], [23, 27]]) res = partial_trace(test_input_mat) bool_mat = np.isclose(expected_res, res) self.assertEqual(np.all(bool_mat), True)
def test_partial_trace_sys_int_dim_int_2(self): """ Default second subsystem. By default, the partial_transpose function takes the trace over the second subsystem. """ test_input_mat = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) expected_res = 34 res = partial_trace(test_input_mat, 2, 1) bool_mat = np.isclose(expected_res, res) self.assertEqual(np.all(bool_mat), True)
def test_partial_trace_sys(self): """ Specify the `sys` argument. By specifying the `sys` argument, you can perform the partial trace the first subsystem instead: """ 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([[12, 14], [20, 22]]) res = partial_trace(test_input_mat, 1) bool_mat = np.isclose(expected_res, res) self.assertEqual(np.all(bool_mat), True)
def schmidt_rank( vec: np.ndarray, dim: Union[int, List[int], np.ndarray] = None ) -> float: r""" Compute the Schmidt rank. For complex Euclidean spaces :math:`\mathcal{X}` and :math:`\mathcal{Y}`, a pure state :math:`u \in \mathcal{X} \otimes \mathcal{Y}` possesses an expansion of the form: .. math:: u = \sum_{i} \lambda_i v_i w_i where :math:`v_i \in \mathcal{X}` and :math:`w_i \in \mathcal{Y}` are orthonormal states. The Schmidt coefficients are calculated from .. math:: A = \text{Tr}_{\mathcal{B}}(u^* u). The Schmidt rank is the number of non-zero eigenvalues of A. The Schmidt rank allows us to determine if a given state is entangled or separable. For instance: - If the Schmidt rank is 1: The state is separable - If the Schmidt rank > 1: The state is entangled. Compute the Schmidt rank of the vector `vec`, assumed to live in bipartite space, where both subsystems have dimension equal to `sqrt(len(vec))`. The dimension may be specified by the 1-by-2 vector `dim` and the rank in that case is determined as the number of Schmidt coefficients larger than `tol`. References: [1] Wikipedia: Schmidt rank https://en.wikipedia.org/wiki/Schmidt_decomposition#Schmidt_rank_and_entanglement :param vec: A bipartite vector to have its Schmidt rank computed. :param dim: A 1-by-2 vector. :return: The Schmidt rank of vector `vec`. """ eps = np.finfo(float).eps slv = int(np.round(np.sqrt(len(vec)))) if dim is None: dim = slv if isinstance(dim, int): dim = np.array([dim, len(vec) / dim]) if np.abs(dim[1] - np.round(dim[1])) >= 2 * len(vec) * eps: raise ValueError( "Invalid: The value of `dim` must evenly divide " "`len(vec)`; please provide a `dim` array " "containing the dimensions of the subsystems" ) dim[1] = np.round(dim[1]) rho = vec.conj().T * vec rho_a = partial_trace(rho, 2) # Return the number of non-zero eigenvalues of the # matrix that traced out the second party's portion. return len(np.nonzero(np.linalg.eigvalsh(rho_a))[0])
def test_non_square_matrix_dim_2(self): """Matrix must be square for partial trace.""" with self.assertRaises(ValueError): rho = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) partial_trace(rho, 2, [2])
def test_invalid_sys_arg(self): """The `sys` argument must be either a list or int.""" with self.assertRaises(ValueError): rho = np.array([[1 / 2, 0, 0, 1 / 2], [0, 0, 0, 0], [0, 0, 0, 0], [1 / 2, 0, 0, 1 / 2]]) partial_trace(rho, "invalid_input")