Update `ProbDistribution` and `QuasiDistribution` to store the number of bits if bitstrings w/o prefix are given (#8464)

* Update ProbDistribution and QuasiDistribution to store data as bit-strings

* fix sampler tests

* Revert the internal data from bitstrings to integers

* Store the information of number of bits when bitstrings w/o prefix,
  e.g., "00101", are given.

* update reno

* Revised `num_bits`, comments, and tests.

Co-authored-by: Julien Gacon <gaconju@gmail.com>

Co-authored-by: Julien Gacon <gaconju@gmail.com>
Co-authored-by: Paul Nation <nonhermitian@gmail.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Takashi Imamichi 2022-09-22 11:45:05 +09:00 committed by GitHub
parent 418b7cc802
commit 007cc25bc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 179 additions and 24 deletions

View File

@ -45,20 +45,23 @@ class ProbDistribution(dict):
ValueError: If the string format of the keys is incorrect
"""
self.shots = shots
self._num_bits = 0
if data:
first_key = next(iter(data.keys()))
if isinstance(first_key, int):
pass
# `self._num_bits` is not always the exact number of qubits measured,
# but the number of bits to represent the largest key.
self._num_bits = len(bin(max(data.keys()))) - 2
elif isinstance(first_key, str):
if first_key.startswith("0x"):
hex_raw = data
data = {int(key, 0): value for key, value in hex_raw.items()}
elif first_key.startswith("0b"):
bin_raw = data
data = {int(key, 0): value for key, value in bin_raw.items()}
if first_key.startswith("0x") or first_key.startswith("0b"):
data = {int(key, 0): value for key, value in data.items()}
# `self._num_bits` is not always the exact number of qubits measured,
# but the number of bits to represent the largest key.
self._num_bits = len(bin(max(data.keys()))) - 2
elif self._bitstring_regex.search(first_key):
bin_raw = data
data = {int("0b" + key, 0): value for key, value in bin_raw.items()}
# `self._num_bits` is the exact number of qubits measured.
self._num_bits = max(len(key) for key in data)
data = {int(key, 2): value for key, value in data.items()}
else:
raise ValueError(
"The input keys are not a valid string format, must either "
@ -74,14 +77,17 @@ class ProbDistribution(dict):
Parameters:
num_bits (int): number of bits in the binary bitstrings (leading
zeros will be padded). If None, the length will be derived
from the largest key present.
zeros will be padded). If None, a default value will be used.
If keys are given as integers or strings with binary or hex prefix,
the default value will be derived from the largest key present.
If keys are given as bitstrings without prefix,
the default value will be derived from the largest key length.
Returns:
dict: A dictionary where the keys are binary strings in the format
``"0110"``
"""
n = len(bin(max(self.keys(), default=0))) - 2 if num_bits is None else num_bits
n = self._num_bits if num_bits is None else num_bits
return {format(key, "b").zfill(n): value for key, value in self.items()}
def hex_probabilities(self):

View File

@ -50,20 +50,23 @@ class QuasiDistribution(dict):
"""
self.shots = shots
self._stddev_upper_bound = stddev_upper_bound
self._num_bits = 0
if data:
first_key = next(iter(data.keys()))
if isinstance(first_key, int):
pass
# `self._num_bits` is not always the exact number of qubits measured,
# but the number of bits to represent the largest key.
self._num_bits = len(bin(max(data.keys()))) - 2
elif isinstance(first_key, str):
if first_key.startswith("0x"):
hex_raw = data
data = {int(key, 0): value for key, value in hex_raw.items()}
elif first_key.startswith("0b"):
bin_raw = data
data = {int(key, 0): value for key, value in bin_raw.items()}
if first_key.startswith("0x") or first_key.startswith("0b"):
data = {int(key, 0): value for key, value in data.items()}
# `self._num_bits` is not always the exact number of qubits measured,
# but the number of bits to represent the largest key.
self._num_bits = len(bin(max(data.keys()))) - 2
elif self._bitstring_regex.search(first_key):
bin_raw = data
data = {int("0b" + key, 0): value for key, value in bin_raw.items()}
# `self._num_bits` is the exact number of qubits measured.
self._num_bits = max(len(key) for key in data)
data = {int(key, 2): value for key, value in data.items()}
else:
raise ValueError(
"The input keys are not a valid string format, must either "
@ -112,14 +115,17 @@ class QuasiDistribution(dict):
Parameters:
num_bits (int): number of bits in the binary bitstrings (leading
zeros will be padded). If None, the length will be derived
from the largest key present.
zeros will be padded). If None, a default value will be used.
If keys are given as integers or strings with binary or hex prefix,
the default value will be derived from the largest key present.
If keys are given as bitstrings without prefix,
the default value will be derived from the largest key length.
Returns:
dict: A dictionary where the keys are binary strings in the format
``"0110"``
"""
n = len(bin(max(self.keys(), default=0))) - 2 if num_bits is None else num_bits
n = self._num_bits if num_bits is None else num_bits
return {format(key, "b").zfill(n): value for key, value in self.items()}
def hex_probabilities(self):

View File

@ -0,0 +1,21 @@
---
upgrade:
- |
Updated :class:`~qiskit.result.ProbDistribution` and :class:`~qiskit.result.QuasiDistribution`
to store the information of the number of bits if bitstrings without prefix "0b" are given.
:meth:`~qiskit.result.ProbDistribution.binary_probabilities` and
:meth:`~qiskit.result.QuasiDistribution.binary_probabilities` use the stored number of bits
as the default value of the number of bits.
.. code-block: python
import qiskit.result import ProbDistribution, QuasiDistribution
prob = ProbDistribution({"00": 0.5, "01": 0.5})
quasi = QuasiDistribution({"00": 0.5, "01": 0.5})
print(prob.binary_probabilities())
# {'00': 0.5, '01': 0.5}
print(quasi.binary_probabilities())
# {'00': 0.5, '01': 0.5}

View File

@ -93,6 +93,59 @@ class TestProbDistribution(QiskitTestCase):
probs = ProbDistribution(in_probs)
self.assertEqual(in_probs, probs.binary_probabilities())
def test_bin_no_prefix_w_heading_zeros_probs_bin_out(self):
"""Test binary input without a 0b prefix with heading 0 and binary output."""
in_probs = {"00000": 2 / 7, "00001": 1 / 7, "00010": 1 / 7, "00011": 1 / 7, "00100": 2 / 7}
probs = ProbDistribution(in_probs)
self.assertEqual(in_probs, probs.binary_probabilities())
def test_bin_no_prefix_w_diff_heading_zero_probs_bin_out(self):
"""Test binary input without a 0b prefix with heading 0 of different sizes and binary output."""
in_probs = {
"0": 3 / 5,
"01": 1 / 2,
"10": 7 / 20,
"011": 1 / 10,
"00100": -11 / 20,
}
probs = ProbDistribution(in_probs)
expected = {
"00000": 3 / 5,
"00001": 1 / 2,
"00010": 7 / 20,
"00011": 1 / 10,
"00100": -11 / 20,
}
self.assertEqual(expected, probs.binary_probabilities())
def test_bin_no_prefix_w_diff_heading_zero_probs_bin_out_padded(self):
"""Test binary input without a 0b prefix with heading 0 of different sizes and binary output,
padded with zeros."""
in_probs = {
"0": 3 / 5,
"01": 1 / 2,
"10": 7 / 20,
"011": 1 / 10,
"00100": -11 / 20,
}
probs = ProbDistribution(in_probs)
expected = {
"0000000": 3 / 5,
"0000001": 1 / 2,
"0000010": 7 / 20,
"0000011": 1 / 10,
"0000100": -11 / 20,
}
self.assertEqual(expected, probs.binary_probabilities(7))
def test_bin_no_prefix_out_padded(self):
"""Test binary input without a 0b prefix, padded with zeros."""
n = 5
in_probs = {"0": 1}
probs = ProbDistribution(in_probs)
expected = {"0" * n: 1}
self.assertEqual(expected, probs.binary_probabilities(num_bits=n))
def test_hex_probs_bin_out_padded(self):
"""Test hexadecimal input and binary output, padded with zeros."""
in_probs = {"0x0": 2 / 7, "0x1": 1 / 7, "0x2": 1 / 7, "0x3": 1 / 7, "0x4": 2 / 7}
@ -115,6 +168,11 @@ class TestProbDistribution(QiskitTestCase):
probs = ProbDistribution({})
self.assertEqual(probs.binary_probabilities(), {})
def test_empty_bin_out_padding(self):
"""Test empty input with binary output and padding."""
probs = ProbDistribution({})
self.assertEqual(probs.binary_probabilities(5), {})
def test_invalid_keys(self):
"""Test invalid key type raises."""
with self.assertRaises(TypeError):

View File

@ -84,6 +84,65 @@ class TestQuasi(QiskitTestCase):
quasi = QuasiDistribution(qprobs)
self.assertEqual(qprobs, quasi.binary_probabilities())
def test_bin_no_prefix_w_heading_zero_quasi_bin_out(self):
"""Test binary input without a 0b prefix with heading 0 and binary output."""
qprobs = {
"00000": 3 / 5,
"00001": 1 / 2,
"00010": 7 / 20,
"00011": 1 / 10,
"00100": -11 / 20,
}
quasi = QuasiDistribution(qprobs)
self.assertEqual(qprobs, quasi.binary_probabilities())
def test_bin_no_prefix_w_diff_heading_zero_quasi_bin_out(self):
"""Test binary input without a 0b prefix with heading 0 of different sizes and binary output."""
qprobs = {
"0": 3 / 5,
"01": 1 / 2,
"10": 7 / 20,
"011": 1 / 10,
"00100": -11 / 20,
}
quasi = QuasiDistribution(qprobs)
expected = {
"00000": 3 / 5,
"00001": 1 / 2,
"00010": 7 / 20,
"00011": 1 / 10,
"00100": -11 / 20,
}
self.assertEqual(expected, quasi.binary_probabilities())
def test_bin_no_prefix_w_diff_heading_zero_quasi_bin_out_padded(self):
"""Test binary input without a 0b prefix with heading 0 of different sizes and binary output,
padded with zeros."""
qprobs = {
"0": 3 / 5,
"01": 1 / 2,
"10": 7 / 20,
"011": 1 / 10,
"00100": -11 / 20,
}
quasi = QuasiDistribution(qprobs)
expected = {
"0000000": 3 / 5,
"0000001": 1 / 2,
"0000010": 7 / 20,
"0000011": 1 / 10,
"0000100": -11 / 20,
}
self.assertEqual(expected, quasi.binary_probabilities(7))
def test_bin_no_prefix_out_padded(self):
"""Test binary input without a 0b prefix, padded with zeros."""
n = 5
qprobs = {"0": 1}
quasi = QuasiDistribution(qprobs)
expected = {"0" * n: 1}
self.assertEqual(expected, quasi.binary_probabilities(num_bits=n))
def test_hex_quasi_bin_out_padded(self):
"""Test hexadecimal input and binary output, padded with zeros."""
qprobs = {"0x0": 3 / 5, "0x1": 1 / 2, "0x2": 7 / 20, "0x3": 1 / 10, "0x4": -11 / 20}
@ -106,6 +165,11 @@ class TestQuasi(QiskitTestCase):
quasi = QuasiDistribution({})
self.assertEqual(quasi.binary_probabilities(), {})
def test_empty_bin_out_padding(self):
"""Test empty input with binary output and padding."""
quasi = QuasiDistribution({})
self.assertEqual(quasi.binary_probabilities(5), {})
def test_invalid_keys(self):
"""Test invalid key type raises."""
with self.assertRaises(TypeError):