diff --git a/symmer/operators/anticommuting_op.py b/symmer/operators/anticommuting_op.py index 7c550d0..fae497e 100644 --- a/symmer/operators/anticommuting_op.py +++ b/symmer/operators/anticommuting_op.py @@ -20,8 +20,9 @@ def __init__(self, super().__init__(AC_op_symp_matrix, coeff_list) # check all operators anticommute - anti_comm_check = self.adjacency_matrix.astype(int) - np.eye(self.adjacency_matrix.shape[0]) - assert (np.sum(anti_comm_check) == 0), 'operator needs to be made of anti-commuting Pauli operators' + adj_mat = self.adjacency_matrix + adj_mat[np.diag_indices_from(adj_mat)] = False + assert ~np.any(adj_mat), 'operator needs to be made of anti-commuting Pauli operators' self.X_sk_rotations = [] self.R_LCU = None diff --git a/symmer/operators/base.py b/symmer/operators/base.py index 82249f4..526a654 100644 --- a/symmer/operators/base.py +++ b/symmer/operators/base.py @@ -975,6 +975,14 @@ def commutes_termwise(self, np.array: A Boolean array indicating the term-wise commutation. """ assert (self.n_qubits == PwordOp.n_qubits), 'Pauliwords defined for different number of qubits' + + ### sparse code + # adjacency_matrix = ( + # csr_matrix(self.symp_matrix.astype(int)) @ csr_matrix(np.hstack((PwordOp.Z_block, PwordOp.X_block)).astype(int)).T) + # adjacency_matrix.data = ((adjacency_matrix.data % 2) != 0) + # return np.logical_not(adjacency_matrix.toarray()) + + ### dense code Omega_PwordOp_symp = np.hstack((PwordOp.Z_block, PwordOp.X_block)).astype(int) return (self.symp_matrix @ Omega_PwordOp_symp.T) % 2 == 0 @@ -1089,6 +1097,14 @@ def is_noncontextual(self) -> bool: bool: True if the operator is noncontextual, False if contextual. """ # if generators of operator (self) are noncontextual then whole of operator (self) must be too! + if self.n_terms<= 2*self.n_qubits+1: + # check edge case where all terms are anticommuting + adj_mat = self.adjacency_matrix + adj_mat[np.diag_indices_from(adj_mat)] = False + if ~np.any(adj_mat): + # operator is all anticommuting + return True + from symmer.utils import get_generators_including_xz_products xyz_generators = get_generators_including_xz_products(self) diff --git a/tests/test_operators/test_base.py b/tests/test_operators/test_base.py index f271698..8c87cb4 100644 --- a/tests/test_operators/test_base.py +++ b/tests/test_operators/test_base.py @@ -650,6 +650,25 @@ def test_is_noncontextual_generators(): # assert check_adjmat_noncontextual(Hnc.generators.adjacency_matrix), 'noncontexutal operator is being correctly defined as noncontextual' assert Hnc.is_noncontextual, 'noncontexutal operator is being correctly defined as noncontextual' +def test_is_noncontextual_anticommuting_H(): + """ + noncontextual test that breaks if only 2n generators are used rather than 3n generators + Returns: + + """ + Hnc = PauliwordOp.from_dictionary({ + 'ZZZI': (1.2532436410975218-0j), + 'IIXI': (0.8935108507410493-0j), + 'ZIYI': (-1.1362909076230914+0j), + 'IXZI': (-0.05373661687140326+0j), + 'ZYZI': (-1.0012312990477774+0j), + 'XXYI': (-0.045809456087963205+0j), + 'YXYZ': (0.21569499626612557-0j), + 'YXYX': (-0.5806963175396661+0j), + 'YXYY': (0.3218493853030614-0j)}) + + assert Hnc.is_noncontextual, 'noncontexutal operator is being correctly defined as noncontextual' + def test_multiplication_1(): """ Tests multiplication and the OpenFermion conversion """