• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / qiskit / 14046787710

24 Mar 2025 09:58PM UTC coverage: 87.757% (-0.3%) from 88.068%
14046787710

Pull #14068

github

web-flow
Merge bbb032454 into 4d0c382f5
Pull Request #14068: Port classical expressions (`Expr`) to Rust.

121 of 415 new or added lines in 6 files covered. (29.16%)

613 existing lines in 10 files now uncovered.

72687 of 82828 relevant lines covered (87.76%)

370544.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.92
/qiskit/visualization/counts_visualization.py
1
# This code is part of Qiskit.
2
#
3
# (C) Copyright IBM 2017, 2018.
4
#
5
# This code is licensed under the Apache License, Version 2.0. You may
6
# obtain a copy of this license in the LICENSE.txt file in the root directory
7
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
#
9
# Any modifications or derivative works of this code must retain this
10
# copyright notice, and modified files need to carry a notice indicating
11
# that they have been altered from the originals.
12

13
"""
14
Visualization functions for measurement counts.
15
"""
16

17
from collections import OrderedDict
1✔
18
import functools
1✔
19

20
import numpy as np
1✔
21

22
from qiskit.utils import optionals as _optionals
1✔
23
from qiskit.result import QuasiDistribution, ProbDistribution
1✔
24
from .exceptions import VisualizationError
1✔
25
from .utils import matplotlib_close_if_inline
1✔
26

27

28
def hamming_distance(str1, str2):
1✔
29
    """Calculate the Hamming distance between two bit strings
30

31
    Args:
32
        str1 (str): First string.
33
        str2 (str): Second string.
34
    Returns:
35
        int: Distance between strings.
36
    Raises:
37
        VisualizationError: Strings not same length
38
    """
39
    if len(str1) != len(str2):
×
40
        raise VisualizationError("Strings not same length.")
×
41
    return sum(s1 != s2 for s1, s2 in zip(str1, str2))
×
42

43

44
VALID_SORTS = ["asc", "desc", "hamming", "value", "value_desc"]
1✔
45
DIST_MEAS = {"hamming": hamming_distance}
1✔
46

47

48
def _is_deprecated_data_format(data) -> bool:
1✔
49
    if not isinstance(data, list):
×
50
        data = [data]
×
51
    for dat in data:
×
52
        if isinstance(dat, (QuasiDistribution, ProbDistribution)) or isinstance(
×
53
            next(iter(dat.values())), float
54
        ):
55
            return True
×
56
    return False
×
57

58

59
def plot_histogram(
1✔
60
    data,
61
    figsize=None,
62
    color=None,
63
    number_to_keep=None,
64
    sort="asc",
65
    target_string=None,
66
    legend=None,
67
    bar_labels=True,
68
    title=None,
69
    ax=None,
70
    filename=None,
71
):
72
    """Plot a histogram of input counts data.
73

74
    Args:
75
        data (list or dict): This is either a list of dictionaries or a single
76
            dict containing the values to represent (ex ``{'001': 130}``)
77

78
        figsize (tuple): Figure size in inches.
79
        color (list or str): String or list of strings for histogram bar colors.
80
        number_to_keep (int): The number of terms to plot per dataset.  The rest is made into a
81
            single bar called 'rest'.  If multiple datasets are given, the ``number_to_keep``
82
            applies to each dataset individually, which may result in more bars than
83
            ``number_to_keep + 1``.  The ``number_to_keep`` applies to the total values, rather than
84
            the x-axis sort.
85
        sort (string): Could be `'asc'`, `'desc'`, `'hamming'`, `'value'`, or
86
            `'value_desc'`. If set to `'value'` or `'value_desc'` the x axis
87
            will be sorted by the number of counts for each bitstring.
88
            Defaults to `'asc'`.
89
        target_string (str): Target string if 'sort' is a distance measure.
90
        legend(list): A list of strings to use for labels of the data.
91
            The number of entries must match the length of data (if data is a
92
            list or 1 if it's a dict)
93
        bar_labels (bool): Label each bar in histogram with counts value.
94
        title (str): A string to use for the plot title
95
        ax (matplotlib.axes.Axes): An optional Axes object to be used for
96
            the visualization output. If none is specified a new matplotlib
97
            Figure will be created and used. Additionally, if specified there
98
            will be no returned Figure since it is redundant.
99
        filename (str): file path to save image to.
100

101
    Returns:
102
        matplotlib.Figure:
103
            A figure for the rendered histogram, if the ``ax``
104
            kwarg is not set.
105

106
    Raises:
107
        MissingOptionalLibraryError: Matplotlib not available.
108
        VisualizationError: When legend is provided and the length doesn't
109
            match the input data.
110
        VisualizationError: Input must be Counts or a dict
111

112
    Examples:
113
        .. plot::
114
           :alt: Output from the previous code.
115
           :include-source:
116

117
            # Plot two counts in the same figure with legends and colors specified.
118

119
            from qiskit.visualization import plot_histogram
120

121
            counts1 = {'00': 525, '11': 499}
122
            counts2 = {'00': 511, '11': 514}
123

124
            legend = ['First execution', 'Second execution']
125

126
            plot_histogram([counts1, counts2], legend=legend, color=['crimson','midnightblue'],
127
                            title="New Histogram")
128

129
            # You can sort the bitstrings using different methods.
130

131
            counts = {'001': 596, '011': 211, '010': 50, '000': 117, '101': 33, '111': 8,
132
                    '100': 6, '110': 3}
133

134
            # Sort by the counts in descending order
135
            hist1 = plot_histogram(counts, sort='value_desc')
136

137
            # Sort by the hamming distance (the number of bit flips to change from
138
            # one bitstring to the other) from a target string.
139
            hist2 = plot_histogram(counts, sort='hamming', target_string='001')
140
    """
141
    if not isinstance(data, list):
1✔
142
        data = [data]
1✔
143

144
    kind = "counts"
1✔
145
    for dat in data:
1✔
146
        if isinstance(dat, (QuasiDistribution, ProbDistribution)) or isinstance(
1✔
147
            next(iter(dat.values())), float
148
        ):
149
            kind = "distribution"
1✔
150
    return _plotting_core(
1✔
151
        data,
152
        figsize,
153
        color,
154
        number_to_keep,
155
        sort,
156
        target_string,
157
        legend,
158
        bar_labels,
159
        title,
160
        ax,
161
        filename,
162
        kind=kind,
163
    )
164

165

166
def plot_distribution(
1✔
167
    data,
168
    figsize=(7, 5),
169
    color=None,
170
    number_to_keep=None,
171
    sort="asc",
172
    target_string=None,
173
    legend=None,
174
    bar_labels=True,
175
    title=None,
176
    ax=None,
177
    filename=None,
178
):
179
    """Plot a distribution from input sampled data.
180

181
    Args:
182
        data (list or dict): This is either a list of dictionaries or a single
183
            dict containing the values to represent (ex {'001': 130})
184
        figsize (tuple): Figure size in inches.
185
        color (list or str): String or list of strings for distribution bar colors.
186
        number_to_keep (int): The number of terms to plot per dataset.  The rest is made into a
187
            single bar called 'rest'.  If multiple datasets are given, the ``number_to_keep``
188
            applies to each dataset individually, which may result in more bars than
189
            ``number_to_keep + 1``.  The ``number_to_keep`` applies to the total values, rather than
190
            the x-axis sort.
191
        sort (string): Could be `'asc'`, `'desc'`, `'hamming'`, `'value'`, or
192
            `'value_desc'`. If set to `'value'` or `'value_desc'` the x axis
193
            will be sorted by the maximum probability for each bitstring.
194
            Defaults to `'asc'`.
195
        target_string (str): Target string if 'sort' is a distance measure.
196
        legend(list): A list of strings to use for labels of the data.
197
            The number of entries must match the length of data (if data is a
198
            list or 1 if it's a dict)
199
        bar_labels (bool): Label each bar in histogram with probability value.
200
        title (str): A string to use for the plot title
201
        ax (matplotlib.axes.Axes): An optional Axes object to be used for
202
            the visualization output. If none is specified a new matplotlib
203
            Figure will be created and used. Additionally, if specified there
204
            will be no returned Figure since it is redundant.
205
        filename (str): file path to save image to.
206

207
    Returns:
208
        matplotlib.Figure:
209
            A figure for the rendered distribution, if the ``ax``
210
            kwarg is not set.
211

212
    Raises:
213
        MissingOptionalLibraryError: Matplotlib not available.
214
        VisualizationError: When legend is provided and the length doesn't
215
            match the input data.
216

217
    Examples:
218
        .. plot::
219
           :alt: Output from the previous code.
220
           :include-source:
221

222
            # Plot two counts in the same figure with legends and colors specified.
223

224
            from qiskit.visualization import plot_distribution
225

226
            counts1 = {'00': 525, '11': 499}
227
            counts2 = {'00': 511, '11': 514}
228

229
            legend = ['First execution', 'Second execution']
230

231
            plot_distribution([counts1, counts2], legend=legend, color=['crimson','midnightblue'],
232
                            title="New Distribution")
233

234
            # You can sort the bitstrings using different methods.
235

236
            counts = {'001': 596, '011': 211, '010': 50, '000': 117, '101': 33, '111': 8,
237
                    '100': 6, '110': 3}
238

239
            # Sort by the counts in descending order
240
            dist1 = plot_distribution(counts, sort='value_desc')
241

242
            # Sort by the hamming distance (the number of bit flips to change from
243
            # one bitstring to the other) from a target string.
244
            dist2 = plot_distribution(counts, sort='hamming', target_string='001')
245

246
    """
247
    return _plotting_core(
×
248
        data,
249
        figsize,
250
        color,
251
        number_to_keep,
252
        sort,
253
        target_string,
254
        legend,
255
        bar_labels,
256
        title,
257
        ax,
258
        filename,
259
        kind="distribution",
260
    )
261

262

263
@_optionals.HAS_MATPLOTLIB.require_in_call
1✔
264
def _plotting_core(
1✔
265
    data,
266
    figsize=(7, 5),
267
    color=None,
268
    number_to_keep=None,
269
    sort="asc",
270
    target_string=None,
271
    legend=None,
272
    bar_labels=True,
273
    title=None,
274
    ax=None,
275
    filename=None,
276
    kind="counts",
277
):
278
    import matplotlib.pyplot as plt
1✔
279
    from matplotlib.ticker import MaxNLocator
1✔
280

281
    if sort not in VALID_SORTS:
1✔
282
        raise VisualizationError(
×
283
            "Value of sort option, %s, isn't a "
284
            "valid choice. Must be 'asc', "
285
            "'desc', 'hamming', 'value', 'value_desc'"
286
        )
287
    if sort in DIST_MEAS and target_string is None:
1✔
288
        err_msg = "Must define target_string when using distance measure."
×
289
        raise VisualizationError(err_msg)
×
290

291
    if isinstance(data, dict):
1✔
292
        data = [data]
×
293

294
    if legend and len(legend) != len(data):
1✔
295
        raise VisualizationError(
×
296
            f"Length of legend ({len(legend)}) doesn't match number of input executions ({len(data)})."
297
        )
298

299
    # Set bar colors
300
    if color is None:
1✔
301
        color = plt.rcParams["axes.prop_cycle"].by_key()["color"]
1✔
302
    elif isinstance(color, str):
×
303
        color = [color]
×
304

305
    if ax is None:
1✔
306
        fig, ax = plt.subplots(figsize=figsize)
1✔
307
    else:
308
        fig = None
×
309

310
    labels = sorted(functools.reduce(lambda x, y: x.union(y.keys()), data, set()))
1✔
311
    if number_to_keep is not None:
1✔
312
        labels.append("rest")
1✔
313

314
    if sort in DIST_MEAS:
1✔
315
        dist = []
×
316
        for item in labels:
×
317
            dist.append(DIST_MEAS[sort](item, target_string) if item != "rest" else 0)
×
318

319
        labels = [list(x) for x in zip(*sorted(zip(dist, labels), key=lambda pair: pair[0]))][1]
×
320
    elif "value" in sort:
1✔
321
        combined_counts = {}
×
322
        if isinstance(data, dict):
×
323
            combined_counts = data
×
324
        else:
325
            for counts in data:
×
326
                for count in counts:
×
327
                    prev_count = combined_counts.get(count, 0)
×
328
                    combined_counts[count] = max(prev_count, counts[count])
×
329
        labels = sorted(combined_counts.keys(), key=lambda key: combined_counts[key])
×
330

331
    length = len(data)
1✔
332
    width = 1 / (len(data) + 1)  # the width of the bars
1✔
333

334
    labels_dict, all_pvalues, all_inds = _plot_data(data, labels, number_to_keep, kind=kind)
1✔
335
    rects = []
1✔
336
    for item, _ in enumerate(data):
1✔
337
        label = None
1✔
338
        for idx, val in enumerate(all_pvalues[item]):
1✔
339
            if not idx and legend:
1✔
340
                label = legend[item]
1✔
341
            if val > 0:
1✔
342
                rects.append(
1✔
343
                    ax.bar(
344
                        idx + item * width,
345
                        val,
346
                        width,
347
                        label=label,
348
                        color=color[item % len(color)],
349
                        zorder=2,
350
                    )
351
                )
352
                label = None
1✔
353
        bar_center = (width / 2) * (length - 1)
1✔
354
        ax.set_xticks(all_inds[item] + bar_center)
1✔
355
        ax.set_xticklabels(labels_dict.keys(), rotation=70, ha="right", rotation_mode="anchor")
1✔
356
        # attach some text labels
357
        if bar_labels:
1✔
358
            for rect in rects:
1✔
359
                for rec in rect:
1✔
360
                    height = rec.get_height()
1✔
361
                    if kind == "distribution":
1✔
362
                        height = round(height, 3)
1✔
363
                    if height >= 1e-3:
1✔
364
                        ax.text(
1✔
365
                            rec.get_x() + rec.get_width() / 2.0,
366
                            1.05 * height,
367
                            str(height),
368
                            ha="center",
369
                            va="bottom",
370
                            zorder=3,
371
                        )
372
                    else:
373
                        ax.text(
1✔
374
                            rec.get_x() + rec.get_width() / 2.0,
375
                            1.05 * height,
376
                            "0",
377
                            ha="center",
378
                            va="bottom",
379
                            zorder=3,
380
                        )
381

382
    # add some text for labels, title, and axes ticks
383
    if kind == "counts":
1✔
384
        ax.set_ylabel("Count", fontsize=14)
1✔
385
    else:
386
        ax.set_ylabel("Quasi-probability", fontsize=14)
1✔
387
    all_vals = np.concatenate(all_pvalues).ravel()
1✔
388
    min_ylim = 0.0
1✔
389
    if kind == "distribution":
1✔
390
        min_ylim = min(0.0, min(1.1 * val for val in all_vals))
1✔
391
    ax.set_ylim([min_ylim, min([1.1 * sum(all_vals), max(1.1 * val for val in all_vals)])])
1✔
392
    if "desc" in sort:
1✔
393
        ax.invert_xaxis()
×
394

395
    ax.yaxis.set_major_locator(MaxNLocator(5))
1✔
396
    plt.grid(which="major", axis="y", zorder=0, linestyle="--")
1✔
397
    if title:
1✔
398
        plt.title(title)
×
399

400
    if legend:
1✔
401
        ax.legend(
1✔
402
            loc="upper left",
403
            bbox_to_anchor=(1.01, 1.0),
404
            ncol=1,
405
            borderaxespad=0,
406
            frameon=True,
407
        )
408
    if fig:
1✔
409
        matplotlib_close_if_inline(fig)
1✔
410
    if filename is None:
1✔
411
        try:
1✔
412
            fig.tight_layout()
1✔
413
        except AttributeError:
×
UNCOV
414
            pass
×
415
        return fig
1✔
416
    else:
UNCOV
417
        return fig.savefig(filename)
×
418

419

420
def _keep_largest_items(execution, number_to_keep):
1✔
421
    """Keep only the largest values in a dictionary, and sum the rest into a new key 'rest'."""
422
    sorted_counts = sorted(execution.items(), key=lambda p: p[1])
1✔
423
    rest = sum(count for key, count in sorted_counts[:-number_to_keep])
1✔
424
    return dict(sorted_counts[-number_to_keep:], rest=rest)
1✔
425

426

427
def _unify_labels(data):
1✔
428
    """Make all dictionaries in data have the same set of keys, using 0 for missing values."""
429
    data = tuple(data)
1✔
430
    all_labels = set().union(*(execution.keys() for execution in data))
1✔
431
    base = {label: 0 for label in all_labels}
1✔
432
    out = []
1✔
433
    for execution in data:
1✔
434
        new_execution = base.copy()
1✔
435
        new_execution.update(execution)
1✔
436
        out.append(new_execution)
1✔
437
    return out
1✔
438

439

440
def _plot_data(data, labels, number_to_keep, kind="counts"):
1✔
441
    """Generate the data needed for plotting counts.
442

443
    Parameters:
444
        data (list or dict): This is either a list of dictionaries or a single
445
            dict containing the values to represent (ex {'001': 130})
446
        labels (list): The list of bitstring labels for the plot.
447
        number_to_keep (int): The number of terms to plot and rest
448
            is made into a single bar called 'rest'.
449
        kind (str): One of 'counts' or 'distribution`
450

451
    Returns:
452
        tuple: tuple containing:
453
            (dict): The labels actually used in the plotting.
454
            (list): List of ndarrays for the bars in each experiment.
455
            (list): Indices for the locations of the bars for each
456
                    experiment.
457
    """
458
    labels_dict = OrderedDict()
1✔
459
    all_pvalues = []
1✔
460
    all_inds = []
1✔
461

462
    if isinstance(data, dict):
1✔
UNCOV
463
        data = [data]
×
464
    if number_to_keep is not None:
1✔
465
        data = _unify_labels(_keep_largest_items(execution, number_to_keep) for execution in data)
1✔
466

467
    for execution in data:
1✔
468
        values = []
1✔
469
        for key in labels:
1✔
470
            if key not in execution:
1✔
471
                if number_to_keep is None:
1✔
472
                    labels_dict[key] = 1
1✔
473
                    values.append(0)
1✔
474
            else:
475
                labels_dict[key] = 1
1✔
476
                values.append(execution[key])
1✔
477
        if kind == "counts":
1✔
478
            pvalues = np.array(values, dtype=int)
1✔
479
        else:
480
            pvalues = np.array(values, dtype=float)
1✔
481
            pvalues /= np.sum(pvalues)
1✔
482
        all_pvalues.append(pvalues)
1✔
483
        numelem = len(values)
1✔
484
        ind = np.arange(numelem)  # the x locations for the groups
1✔
485
        all_inds.append(ind)
1✔
486

487
    return labels_dict, all_pvalues, all_inds
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc