Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

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

Merged
merged 14 commits into from
Aug 15, 2024
2 changes: 2 additions & 0 deletions qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add a test to check the simplify() behavior you described in your original issue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

"""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")
Expand Down
Loading