# -*- coding: utf-8 -*-
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2018.
#
# 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.
# pylint: disable=import-error
"""
Visualization functions for measurement counts.
"""
from collections import Counter, OrderedDict
import functools
import numpy as np
from .matplotlib import HAS_MATPLOTLIB
from .exceptions import VisualizationError
if HAS_MATPLOTLIB:
from matplotlib import get_backend
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
def hamming_distance(str1, str2):
"""Calculate the Hamming distance between two bit strings
Args:
str1 (str): First string.
str2 (str): Second string.
Returns:
int: Distance between strings.
Raises:
VisualizationError: Strings not same length
"""
if len(str1) != len(str2):
raise VisualizationError('Strings not same length.')
return sum(s1 != s2 for s1, s2 in zip(str1, str2))
VALID_SORTS = ['asc', 'desc', 'hamming']
DIST_MEAS = {'hamming': hamming_distance}
[docs]def plot_histogram(data, figsize=(7, 5), color=None, number_to_keep=None,
sort='asc', target_string=None,
legend=None, bar_labels=True, title=None, ax=None):
"""Plot a histogram of data.
Args:
data (list or dict): This is either a list of dictionaries or a single
dict containing the values to represent (ex {'001': 130})
figsize (tuple): Figure size in inches.
color (list or str): String or list of strings for histogram bar colors.
number_to_keep (int): The number of terms to plot and rest
is made into a single bar called 'rest'.
sort (string): Could be 'asc', 'desc', or 'hamming'.
target_string (str): Target string if 'sort' is a distance measure.
legend(list): A list of strings to use for labels of the data.
The number of entries must match the length of data (if data is a
list or 1 if it's a dict)
bar_labels (bool): Label each bar in histogram with probability value.
title (str): A string to use for the plot title
ax (matplotlib.axes.Axes): An optional Axes object to be used for
the visualization output. If none is specified a new matplotlib
Figure will be created and used. Additionally, if specified there
will be no returned Figure since it is redundant.
Returns:
matplotlib.Figure:
A figure for the rendered histogram, if the ``ax``
kwarg is not set.
Raises:
ImportError: Matplotlib not available.
VisualizationError: When legend is provided and the length doesn't
match the input data.
Example:
.. jupyter-execute::
from qiskit import QuantumCircuit, BasicAer, execute
from qiskit.visualization import plot_histogram
%matplotlib inline
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])
backend = BasicAer.get_backend('qasm_simulator')
job = execute(qc, backend)
plot_histogram(job.result().get_counts(), color='midnightblue', title="New Histogram")
"""
if not HAS_MATPLOTLIB:
raise ImportError('Must have Matplotlib installed.')
if sort not in VALID_SORTS:
raise VisualizationError("Value of sort option, %s, isn't a "
"valid choice. Must be 'asc', "
"'desc', or 'hamming'")
if sort in DIST_MEAS.keys() and target_string is None:
err_msg = 'Must define target_string when using distance measure.'
raise VisualizationError(err_msg)
if isinstance(data, dict):
data = [data]
if legend and len(legend) != len(data):
raise VisualizationError("Length of legendL (%s) doesn't match "
"number of input executions: %s" %
(len(legend), len(data)))
# Set bar colors
if color is None:
color = ['#648fff', '#dc267f', '#785ef0', '#ffb000', '#fe6100']
elif isinstance(color, str):
color = [color]
if ax is None:
fig, ax = plt.subplots(figsize=figsize)
else:
fig = None
labels = list(sorted(
functools.reduce(lambda x, y: x.union(y.keys()), data, set())))
if number_to_keep is not None:
labels.append('rest')
if sort in DIST_MEAS.keys():
dist = []
for item in labels:
dist.append(DIST_MEAS[sort](item, target_string))
labels = [list(x) for x in zip(*sorted(zip(dist, labels),
key=lambda pair: pair[0]))][1]
length = len(data)
width = 1/(len(data)+1) # the width of the bars
labels_dict, all_pvalues, all_inds = _plot_histogram_data(data,
labels,
number_to_keep)
rects = []
for item, _ in enumerate(data):
for idx, val in enumerate(all_pvalues[item]):
label = None
if not idx and legend:
label = legend[item]
if val >= 0:
rects.append(ax.bar(idx+item*width, val, width, label=label,
color=color[item % len(color)],
zorder=2))
bar_center = (width / 2) * (length - 1)
ax.set_xticks(all_inds[item] + bar_center)
ax.set_xticklabels(labels_dict.keys(), fontsize=14, rotation=70)
# attach some text labels
if bar_labels:
for rect in rects:
for rec in rect:
height = rec.get_height()
if height >= 1e-3:
ax.text(rec.get_x() + rec.get_width() / 2., 1.05 * height,
'%.3f' % float(height),
ha='center', va='bottom', zorder=3)
else:
ax.text(rec.get_x() + rec.get_width() / 2., 1.05 * height,
'0',
ha='center', va='bottom', zorder=3)
# add some text for labels, title, and axes ticks
ax.set_ylabel('Probabilities', fontsize=14)
all_vals = np.concatenate(all_pvalues).ravel()
ax.set_ylim([0., min([1.2, max([1.2 * val for val in all_vals])])])
if sort == 'desc':
ax.invert_xaxis()
ax.yaxis.set_major_locator(MaxNLocator(5))
for tick in ax.yaxis.get_major_ticks():
tick.label.set_fontsize(14)
plt.grid(which='major', axis='y', zorder=0, linestyle='--')
if title:
plt.title(title)
if legend:
ax.legend(loc='upper left', bbox_to_anchor=(1.01, 1.0), ncol=1,
borderaxespad=0, frameon=True, fontsize=12)
if fig:
if get_backend() in ['module://ipykernel.pylab.backend_inline',
'nbAgg']:
plt.close(fig)
return fig
def _plot_histogram_data(data, labels, number_to_keep):
"""Generate the data needed for plotting counts.
Parameters:
data (list or dict): This is either a list of dictionaries or a single
dict containing the values to represent (ex {'001': 130})
labels (list): The list of bitstring labels for the plot.
number_to_keep (int): The number of terms to plot and rest
is made into a single bar called 'rest'.
Returns:
tuple: tuple containing:
(dict): The labels actually used in the plotting.
(list): List of ndarrays for the bars in each experiment.
(list): Indices for the locations of the bars for each
experiment.
"""
labels_dict = OrderedDict()
all_pvalues = []
all_inds = []
for execution in data:
if number_to_keep is not None:
data_temp = dict(Counter(execution).most_common(number_to_keep))
data_temp["rest"] = sum(execution.values()) - sum(data_temp.values())
execution = data_temp
values = []
for key in labels:
if key not in execution:
if number_to_keep is None:
labels_dict[key] = 1
values.append(0)
else:
values.append(-1)
else:
labels_dict[key] = 1
values.append(execution[key])
values = np.array(values, dtype=float)
where_idx = np.where(values >= 0)[0]
pvalues = values[where_idx] / sum(values[where_idx])
all_pvalues.append(pvalues)
numelem = len(values[where_idx])
ind = np.arange(numelem) # the x locations for the groups
all_inds.append(ind)
return labels_dict, all_pvalues, all_inds