def linear_constraint_Alice(self): # Create dimension tuple for the partial tracing sub_dim = (self.dimT, self.dimT**(self.n1 + self.n2 - 1) * self.dimS**2) for q1 in range(self.dimQ1): for index_else in self.indices_but_A1Q1: indices_A1q1a2q2_ext = [ np.append(np.array([a1, q1]), index_else) for a1 in range(self.dimA1) ] indices_A1Q1a2q2_ext = [ np.append(index_A1Q1, index_else) for index_A1Q1 in self.indices_A1Q1 ] lhs = sum([ self.rho_variable[self.StI(index)] for index in indices_A1q1a2q2_ext ]) rhs_variable = sum([ self.rho_variable[self.StI(index)] for index in indices_A1Q1a2q2_ext ]) rhs_partial = nlg.partial_trace(rhs_variable, sub_dim) rhs = self.probQ1[q1] * cp.kron(self.rhoT, rhs_partial) self.constraints.append(lhs - rhs == 0)
def test_partial_trace(): cvx_rho_ABC = cp.Variable(shape=rho_ABC.shape, complex=True) cvx_rho_AB = cp.Variable(shape=rho_AB.shape, complex=True) cvx_rho_AC = cp.Variable(shape=rho_AC.shape, complex=True) cvx_rho_ABC.value = rho_ABC cvx_rho_AB.value = rho_AB cvx_rho_AC.value = rho_AC obtained_rho_AB = nlg.partial_trace(cvx_rho_ABC, [4, 3, 2], axis=2) obtained_rho_AC = nlg.partial_trace(cvx_rho_ABC, [4, 3, 2], axis=1) obtained_rho_A = nlg.partial_trace(cvx_rho_AB, [4, 3], axis=1) obtained_rho_B = nlg.partial_trace(cvx_rho_AB, [4, 3]) obtained_rho_C = nlg.partial_trace(cvx_rho_AC, [4, 2]) np.testing.assert_allclose(obtained_rho_AB.value, rho_AB) np.testing.assert_allclose(obtained_rho_AC.value, rho_AC) np.testing.assert_allclose(obtained_rho_A.value, rho_A) np.testing.assert_allclose(obtained_rho_B.value, rho_B) np.testing.assert_allclose(obtained_rho_C.value, rho_C)
def linear_constraint_Bob(self): # Permutation matrix (T1...Tn1)(T1...Tn2)(SS) -> (Tn2...T1)(T1...Tn1)(SS) order = np.arange(self.n1 + self.n2 + 2) maskA = order[:self.n1] maskB = np.flip(order[self.n1:self.n1 + self.n2]) maskS = order[self.n1 + self.n2:] mask = np.concatenate((maskB, maskA, maskS)) P = cp.Constant(nlg.permutation_matrix(order, mask, self.subs_TTSS_ext)) # Create dimension tuple for the partial tracing sub_dim = (self.dimT, self.dimT**(self.n1 + self.n2 - 1) * self.dimS**2) for q2 in range(self.dimQ2): for index_else in self.indices_but_A2Q2: indices_a1q1A2q2_ext = [ np.append(index_else, np.array([a2, q2])) for a2 in range(self.dimA2) ] indices_a1q1A2Q2_ext = [ np.append(index_else, index_A2Q2) for index_A2Q2 in self.indices_A2Q2 ] lhs_variable = sum([ self.rho_variable[self.StI(index)] for index in indices_a1q1A2q2_ext ]) lhs = P @ lhs_variable @ P.T rhs_variable = sum([ self.rho_variable[self.StI(index)] for index in indices_a1q1A2Q2_ext ]) rhs_permuted = P @ rhs_variable @ P.T rhs_partial = nlg.partial_trace(rhs_permuted, sub_dim) rhs = self.probQ2[q2] * cp.kron(self.rhoT, rhs_partial) self.constraints.append(lhs - rhs == 0)
def rho_TTSS(self, a1, q1, a2, q2): # Build the indices for the extended state index_a1q1 = np.array([[a1, q1]]) index_a2q2 = np.array([[a2, q2]]) indices_but_a2q2 = nlg.fuse_arrays(index_a1q1, self.indices_but_A1Q1A2Q2) indices_a1q1a2q2_ext = nlg.fuse_arrays(indices_but_a2q2, index_a2q2) # Reduce the classical part rho_reduced = sum([ self.rho_variable[self.StI(index)] for index in indices_a1q1a2q2_ext ]) # Reduce the quanutm part if self.n1 != 1 or self.n2 != 1: dim_subsys = (self.dimT, self.dimT**(self.n1 + self.n2 - 2), self.dimT * self.dimS**2) rho_reduced = nlg.partial_trace(rho_reduced, dim_subsys, axis=1) return rho_reduced
def test_partial_trace_no_squared_matrix(): wrong_input = cp.bmat([[1., 2., 3.], [4., 5., 6.]]) with assert_raises(ValueError): nlg.partial_trace(wrong_input, [3])
def test_partial_trace_no_cvx_expression(): wrong_input = np.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]) with assert_raises(TypeError): nlg.partial_trace(wrong_input, [3])