-
Notifications
You must be signed in to change notification settings - Fork 156
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add plotter for ExecutionSpans (#1923)
* initial commit * changes * add tests * fix imports * change * appease mypy * add reno * Update qiskit_ibm_runtime/execution_span/execution_spans.py Co-authored-by: Samuele Ferracin <sam.ferracin@ibm.com> * Update qiskit_ibm_runtime/execution_span/execution_spans.py Co-authored-by: Samuele Ferracin <sam.ferracin@ibm.com> * rename input argument * Apply suggestions from code review Co-authored-by: Samuele Ferracin <sam.ferracin@ibm.com> * add line_width option * Add names option * appease linting * try again * fix --------- Co-authored-by: Samuele Ferracin <sam.ferracin@ibm.com> Co-authored-by: Kevin Tian <kevin.tian@ibm.com>
- Loading branch information
1 parent
69f615b
commit 7da4024
Showing
11 changed files
with
362 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,6 +50,7 @@ test/python/*.log | |
test/python/*.pdf | ||
test/python/*.prof | ||
.stestr/ | ||
.test_artifacts | ||
|
||
# Translations | ||
*.mo | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
qiskit_ibm_runtime/visualization/draw_execution_spans.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2024. | ||
# | ||
# 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. | ||
|
||
"""Functions to visualize :class:`~.ExecutionSpans` objects.""" | ||
|
||
from __future__ import annotations | ||
|
||
from itertools import cycle | ||
from datetime import datetime, timedelta | ||
from typing import Iterable, TYPE_CHECKING | ||
|
||
from ..execution_span import ExecutionSpan, ExecutionSpans | ||
from .utils import plotly_module | ||
|
||
if TYPE_CHECKING: | ||
from plotly.graph_objects import Figure as PlotlyFigure | ||
|
||
|
||
HOVER_TEMPLATE = "<br>".join( | ||
[ | ||
"<b>{name}[{idx}]</b>", | ||
"<b> Start:</b> {span.start:%Y-%m-%d %H:%M:%S.%f}", | ||
"<b> Stop:</b> {span.stop:%Y-%m-%d %H:%M:%S.%f}", | ||
"<b> Size:</b> {span.size}", | ||
"<b> Pub Indexes:</b> {idxs}", | ||
] | ||
) | ||
|
||
|
||
def _get_idxs(span: ExecutionSpan, limit: int = 10) -> str: | ||
if len(idxs := span.pub_idxs) <= limit: | ||
return str(idxs) | ||
else: | ||
return f"[{', '.join(map(str, idxs[:limit]))}, ...]" | ||
|
||
|
||
def _get_id(spans: ExecutionSpans, multiple: bool) -> str: | ||
return f"<{hex(id(spans))}>" if multiple else "" | ||
|
||
|
||
def draw_execution_spans( | ||
*spans: ExecutionSpans, | ||
names: str | Iterable[str] | None = None, | ||
common_start: bool = False, | ||
normalize_y: bool = False, | ||
line_width: int = 4, | ||
show_legend: bool = None, | ||
) -> PlotlyFigure: | ||
"""Draw one or more :class:`~.ExecutionSpans` on a bar plot. | ||
Args: | ||
spans: One or more :class:`~.ExecutionSpans`. | ||
names: Name or names to assign to respective ``spans``. | ||
common_start: Whether to shift all collections of spans so that their first span's start is | ||
at :math:`t=0`. | ||
normalize_y: Whether to display the y-axis units as a percentage of work complete, rather | ||
than cumulative shots completed. | ||
line_width: The thickness of line segments. | ||
show_legend: Whether to show a legend. By default, this choice is automatic. | ||
Returns: | ||
A plotly figure. | ||
""" | ||
go = plotly_module(".graph_objects") | ||
colors = plotly_module(".colors").qualitative.Plotly | ||
|
||
fig = go.Figure() | ||
|
||
# assign a name to each span | ||
all_names = [] | ||
if names is None: | ||
show_legend = False if show_legend is None else show_legend | ||
else: | ||
show_legend = True if show_legend is None else show_legend | ||
if isinstance(names, str): | ||
all_names = [names] | ||
else: | ||
all_names.extend(names) | ||
|
||
# make sure there are always at least as many names as span sets | ||
all_names.extend( | ||
f"ExecutionSpans{_get_id(single_span, len(spans)>1)}" | ||
for single_span in spans[len(all_names) :] | ||
) | ||
|
||
# loop through and make a trace in the figure for each ExecutionSpans | ||
for single_spans, color, name in zip(spans, cycle(colors), all_names): | ||
if not single_spans: | ||
continue | ||
|
||
# sort the spans but remember their original order | ||
sorted_spans = sorted(enumerate(single_spans), key=lambda x: x[1]) | ||
|
||
offset = timedelta() | ||
if common_start: | ||
# plotly doesn't have a way to display timedeltas or relative times on a axis. the | ||
# standard workaround i've found is to shift times to t=0 (ie unix epoch) and suppress | ||
# showing the year/month in the tick labels. | ||
first_start = sorted_spans[0][1].start.replace(tzinfo=None) | ||
offset = first_start - datetime(year=1970, month=1, day=1) | ||
|
||
# gather x/y/text data for each span | ||
total_size = sum(span.size for span in single_spans) if normalize_y else 1 | ||
y_value = 0.0 | ||
x_data = [] | ||
y_data = [] | ||
text_data = [] | ||
for idx, span in sorted_spans: | ||
y_value += span.size / total_size | ||
text = HOVER_TEMPLATE.format(span=span, idx=idx, idxs=_get_idxs(span), name=name) | ||
|
||
x_data.extend([span.start - offset, span.stop - offset, None]) | ||
y_data.extend([y_value, y_value, None]) | ||
text_data.append(text) | ||
|
||
# add the data to the plot | ||
fig.add_trace( | ||
go.Scatter( | ||
x=x_data, | ||
y=y_data, | ||
mode="lines", | ||
line={"width": line_width, "color": color}, | ||
text=text_data, | ||
hoverinfo="text", | ||
name=name, | ||
) | ||
) | ||
|
||
# axis and layout settings | ||
fig.update_layout( | ||
xaxis={"title": "Time", "type": "date"}, | ||
showlegend=show_legend, | ||
legend={"yanchor": "bottom", "y": 0.01, "xanchor": "right", "x": 0.99}, | ||
margin={"l": 70, "r": 20, "t": 20, "b": 70}, | ||
) | ||
|
||
if normalize_y: | ||
fig.update_yaxes(title="Completed Workload", tickformat=".0%") | ||
else: | ||
fig.update_yaxes(title="Shots Completed") | ||
|
||
if common_start: | ||
fig.update_xaxes(tickformat="%H:%M:%S.%f") | ||
|
||
return fig |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
Add :func:`~.draw_execution_spans` function for creating a Plotly figure that | ||
visualizes one or more :class:`~.ExecutionSpans` objects. Also add the convenience | ||
method :meth:`~.ExecutionSpans.draw` for invoking the drawing function on a | ||
particular instance. | ||
|
||
.. code::python | ||
from qiskit_ibm_runtime.visualization import draw_execution_spans | ||
# use the drawing function on spans from sampler job data | ||
spans1 = sampler_job1.result().metadata["execution"]["execution_spans"] | ||
spans2 = sampler_job2.result().metadata["execution"]["execution_spans"] | ||
draw_execution_spans(spans1, spans2) | ||
# convenience to plot just spans1 | ||
spans1.draw() |
Oops, something went wrong.