diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py index 10d76f8719af..4621019a9d81 100644 --- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py @@ -256,6 +256,8 @@ def paulis(self, value): raise ValueError( f"incorrect number of operators: expected {len(self.paulis)}, got {len(value)}" ) + self.coeffs *= (-1j) ** value.phase + value.phase = 0 self._pauli_list = value @property diff --git a/releasenotes/notes/fix-sparsepauliop-phase-bug-2b24f4b775ca564f.yaml b/releasenotes/notes/fix-sparsepauliop-phase-bug-2b24f4b775ca564f.yaml new file mode 100644 index 000000000000..ff1cf96dd711 --- /dev/null +++ b/releasenotes/notes/fix-sparsepauliop-phase-bug-2b24f4b775ca564f.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixed a bug when :attr:`.SparsePauliOp.paulis` is set to be a :class:`.PauliList` with nonzero + phase, where subsequent calls to several :class:`.SparsePauliOp` methods would produce + incorrect results. Now when :attr:`.SparsePauliOp.paulis` is set to a :class:`.PauliList` with + nonzero phase, the phase is absorbed into :attr:`.SparsePauliOp.coeffs`, and the phase of the + input :class:`.PauliList` is set to zero. diff --git a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py index 4964b0c6a609..dedd84279a8d 100644 --- a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py +++ b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py @@ -1097,6 +1097,25 @@ def test_paulis_setter_rejects_bad_inputs(self): with self.assertRaisesRegex(ValueError, "incorrect number of operators"): op.paulis = PauliList([Pauli("XY"), Pauli("ZX"), Pauli("YZ")]) + def test_paulis_setter_absorbs_phase(self): + """Test that the setter for `paulis` absorbs `paulis.phase` to `self.coeffs`.""" + coeffs_init = np.array([1, 1j]) + op = SparsePauliOp(["XY", "ZX"], coeffs=coeffs_init) + paulis_new = PauliList(["-1jXY", "1jZX"]) + op.paulis = paulis_new + # Paulis attribute should have no phase: + self.assertEqual(op.paulis, PauliList(["XY", "ZX"])) + # Coeffs attribute should now include that phase: + self.assertTrue(np.allclose(op.coeffs, coeffs_init * np.array([-1j, 1j]))) + # The phase of the input array is now zero: + self.assertTrue(np.allclose(paulis_new.phase, np.array([0, 0]))) + + def test_paulis_setter_absorbs_phase_2(self): + """Test that `paulis` setter followed by `simplify()` handle phase OK.""" + spo = SparsePauliOp(["X", "X"]) + spo.paulis = ["X", "-X"] + self.assertEqual(spo.simplify(), SparsePauliOp(["I"], coeffs=[0.0 + 0.0j])) + def test_apply_layout_with_transpile(self): """Test the apply_layout method with a transpiler layout.""" psi = EfficientSU2(4, reps=4, entanglement="circular")