Fix setter so that `SparsePauliOp.paulis.phase` stays zero (#12884)

* absorb `phase` into `coeffs` in `paulis()` setter

* do not mutate input array

* add test that paulis setter absorbs phase

* lint

* If input paulis have phase, mutate and warn

* add release note

* release-note formatting

* add test with `simplify()`

* remove phase-warning from paulis setter

* lint

* remove unused import

* update reno
This commit is contained in:
aeddins-ibm 2024-08-15 03:42:41 -04:00 committed by GitHub
parent 48cca36078
commit 8c74a49dcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 29 additions and 0 deletions

View File

@ -256,6 +256,8 @@ class SparsePauliOp(LinearOp):
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

View File

@ -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.

View File

@ -1097,6 +1097,25 @@ class TestSparsePauliOpMethods(QiskitTestCase):
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")