# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""A container class for counts from a circuit execution."""
import re
from qiskit.result import postprocess
from qiskit import exceptions
# NOTE: A dict subclass should not overload any dunder methods like __getitem__
# this can cause unexpected behavior and issues as the cPython dict
# implementation has many standard methods in C for performance and the dunder
# methods are not always used as expected. For example, update() doesn't call
# __setitem__ so overloading __setitem__ would not always provide the expected
# result
[docs]class Counts(dict):
"""A class to store a counts result from a circuit execution."""
bitstring_regex = re.compile(r"^[01\s]+$")
def __init__(self, data, time_taken=None, creg_sizes=None, memory_slots=None):
"""Build a counts object
Args:
data (dict): The dictionary input for the counts. Where the keys
represent a measured classical value and the value is an
integer the number of shots with that result.
The keys can be one of several formats:
* A hexadecimal string of the form ``'0x4a'``
* A bit string prefixed with ``0b`` for example ``'0b1011'``
* A bit string formatted across register and memory slots.
For example, ``'00 10'``.
* A dit string, for example ``'02'``. Note for objects created
with dit strings the ``creg_sizes`` and ``memory_slots``
kwargs don't work and :meth:`hex_outcomes` and
:meth:`int_outcomes` also do not work.
time_taken (float): The duration of the experiment that generated
the counts in seconds.
creg_sizes (list): a nested list where the inner element is a list
of tuples containing both the classical register name and
classical register size. For example,
``[('c_reg', 2), ('my_creg', 4)]``.
memory_slots (int): The number of total ``memory_slots`` in the
experiment.
Raises:
TypeError: If the input key type is not an ``int`` or ``str``.
QiskitError: If a dit string key is input with ``creg_sizes`` and/or
``memory_slots``.
"""
bin_data = None
data = dict(data)
if not data:
self.int_raw = {}
self.hex_raw = {}
bin_data = {}
else:
first_key = next(iter(data.keys()))
if isinstance(first_key, int):
self.int_raw = data
self.hex_raw = {hex(key): value for key, value in self.int_raw.items()}
elif isinstance(first_key, str):
if first_key.startswith("0x"):
self.hex_raw = data
self.int_raw = {int(key, 0): value for key, value in self.hex_raw.items()}
elif first_key.startswith("0b"):
self.int_raw = {int(key, 0): value for key, value in data.items()}
self.hex_raw = {hex(key): value for key, value in self.int_raw.items()}
else:
if not creg_sizes and not memory_slots:
self.hex_raw = None
self.int_raw = None
bin_data = data
else:
hex_dict = {}
int_dict = {}
for bitstring, value in data.items():
if not self.bitstring_regex.search(bitstring):
raise exceptions.QiskitError(
"Counts objects with dit strings do not "
"currently support dit string formatting parameters "
"creg_sizes or memory_slots"
)
int_key = self._remove_space_underscore(bitstring)
int_dict[int_key] = value
hex_dict[hex(int_key)] = value
self.hex_raw = hex_dict
self.int_raw = int_dict
else:
raise TypeError(
"Invalid input key type %s, must be either an int "
"key or string key with hexademical value or bit string"
)
header = {}
self.creg_sizes = creg_sizes
if self.creg_sizes:
header["creg_sizes"] = self.creg_sizes
self.memory_slots = memory_slots
if self.memory_slots:
header["memory_slots"] = self.memory_slots
if not bin_data:
bin_data = postprocess.format_counts(self.hex_raw, header=header)
super().__init__(bin_data)
self.time_taken = time_taken
[docs] def most_frequent(self):
"""Return the most frequent count
Returns:
str: The bit string for the most frequent result
Raises:
QiskitError: when there is >1 count with the same max counts, or
an empty object.
"""
if not self:
raise exceptions.QiskitError("Can not return a most frequent count on an empty object")
max_value = max(self.values())
max_values_counts = [x[0] for x in self.items() if x[1] == max_value]
if len(max_values_counts) != 1:
raise exceptions.QiskitError(
"Multiple values have the same maximum counts: %s" % ",".join(max_values_counts)
)
return max_values_counts[0]
[docs] def hex_outcomes(self):
"""Return a counts dictionary with hexadecimal string keys
Returns:
dict: A dictionary with the keys as hexadecimal strings instead of
bitstrings
Raises:
QiskitError: If the Counts object contains counts for dit strings
"""
if self.hex_raw:
return {key.lower(): value for key, value in self.hex_raw.items()}
else:
out_dict = {}
for bitstring, value in self.items():
if not self.bitstring_regex.search(bitstring):
raise exceptions.QiskitError(
"Counts objects with dit strings do not "
"currently support conversion to hexadecimal"
)
int_key = self._remove_space_underscore(bitstring)
out_dict[hex(int_key)] = value
return out_dict
[docs] def int_outcomes(self):
"""Build a counts dictionary with integer keys instead of count strings
Returns:
dict: A dictionary with the keys as integers instead of bitstrings
Raises:
QiskitError: If the Counts object contains counts for dit strings
"""
if self.int_raw:
return self.int_raw
else:
out_dict = {}
for bitstring, value in self.items():
if not self.bitstring_regex.search(bitstring):
raise exceptions.QiskitError(
"Counts objects with dit strings do not "
"currently support conversion to integer"
)
int_key = self._remove_space_underscore(bitstring)
out_dict[int_key] = value
return out_dict
@staticmethod
def _remove_space_underscore(bitstring):
"""Removes all spaces and underscores from bitstring"""
return int(bitstring.replace(" ", "").replace("_", ""), 2)
[docs] def shots(self):
"""Return the number of shots"""
return sum(self.values())