Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Clip probabilities in QuantumState #9762

Merged
merged 12 commits into from
Mar 17, 2023
5 changes: 5 additions & 0 deletions qiskit/quantum_info/states/densitymatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,13 @@ def probabilities(self, qargs=None, decimals=None):
probs = self._subsystem_probabilities(
np.abs(self.data.diagonal()), self._op_shape.dims_l(), qargs=qargs
)

# to account for roundoff errors, we clip
probs = np.clip(probs, a_min=0, a_max=1)

if decimals is not None:
probs = probs.round(decimals=decimals)

return probs

def reset(self, qargs=None):
Expand Down
4 changes: 3 additions & 1 deletion qiskit/quantum_info/states/quantum_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ def probabilities_dict(self, qargs=None, decimals=None):
dict: The measurement probabilities in dict (ket) form.
"""
return self._vector_to_dict(
self.probabilities(qargs=qargs, decimals=decimals), self.dims(qargs), string_labels=True
self.probabilities(qargs=qargs, decimals=decimals),
self.dims(qargs),
string_labels=True,
)

def sample_memory(self, shots, qargs=None):
Expand Down
5 changes: 5 additions & 0 deletions qiskit/quantum_info/states/statevector.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,8 +575,13 @@ def probabilities(self, qargs=None, decimals=None):
probs = self._subsystem_probabilities(
np.abs(self.data) ** 2, self._op_shape.dims_l(), qargs=qargs
)

# to account for roundoff errors, we clip
probs = np.clip(probs, a_min=0, a_max=1)

if decimals is not None:
probs = probs.round(decimals=decimals)

return probs

def reset(self, qargs=None):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
fixes:
- |
Clip probabilities in the :meth:`.QuantumState.probabilities` and
:meth:`.QuantumState.probabilities_dict` methods to the interval ``[0, 1]``.
This fixes roundoff errors where probabilities could e.g. be larger than 1, leading
to errors in the shot emulation of the :class:`.Sampler`.
Fixed `#9761 <https://github.com/Qiskit/qiskit-terra/issues/9761>`__.
21 changes: 21 additions & 0 deletions test/python/quantum_info/states/test_densitymatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,27 @@ def test_drawings(self):
with self.subTest(msg=f"draw('{drawtype}')"):
dm.draw(drawtype)

def test_clip_probabilities(self):
"""Test probabilities are clipped to [0, 1]."""
dm = DensityMatrix([[1.1, 0], [0, 0]])

self.assertEqual(list(dm.probabilities()), [1.0, 0.0])
# The "1" key should be exactly zero and therefore omitted.
self.assertEqual(dm.probabilities_dict(), {"0": 1.0})

def test_round_probabilities(self):
"""Test probabilities are correctly rounded.

This is good to test to ensure clipping, renormalizing and rounding work together.
"""
p = np.sqrt(1 / 3)
amplitudes = [p, p, p, 0]
dm = DensityMatrix(np.outer(amplitudes, amplitudes))
expected = [0.33, 0.33, 0.33, 0]

# Exact floating-point check because fixing the rounding should ensure this is exact.
self.assertEqual(list(dm.probabilities(decimals=2)), expected)


if __name__ == "__main__":
unittest.main()
18 changes: 18 additions & 0 deletions test/python/quantum_info/states/test_statevector.py
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,24 @@ def test_statevector_len(self):
self.assertEqual(len(empty_vector), len(empty_sv))
self.assertEqual(len(dummy_vector), len(sv))

def test_clip_probabilities(self):
"""Test probabilities are clipped to [0, 1]."""
sv = Statevector([1.1, 0])

self.assertEqual(list(sv.probabilities()), [1.0, 0.0])
# The "1" key should be zero and therefore omitted.
self.assertEqual(sv.probabilities_dict(), {"0": 1.0})

def test_round_probabilities(self):
"""Test probabilities are correctly rounded.

This is good to test to ensure clipping, renormalizing and rounding work together.
"""
p = np.sqrt(1 / 3)
sv = Statevector([p, p, p, 0])
expected = [0.33, 0.33, 0.33, 0]
self.assertEqual(list(sv.probabilities(decimals=2)), expected)


if __name__ == "__main__":
unittest.main()