# Repository: https://gitlab.com/quantify-os/quantify-scheduler
# Licensed according to the LICENCE file on the main branch
"""Plotting functions used in the visualization backend of the sequencer."""
from __future__ import annotations
from copy import deepcopy
from typing import Dict, List, Optional, Tuple, Union
from matplotlib.axes import Axes
from matplotlib.figure import Figure
import quantify_scheduler.visualization.pulse_scheme as ps
from quantify_core.utilities import deprecated
from quantify_scheduler.helpers.importers import import_python_object_from_string
from quantify_scheduler.visualization import constants
[docs]def gate_box(ax: Axes, time: float, qubit_idxs: List[int], text: str, **kw):
"""
A box for a single gate containing a label.
Parameters
----------
ax
time
qubit_idxs
text
"""
for qubit_idx in qubit_idxs:
ps.box_text(
ax,
x0=time,
y0=qubit_idx,
text=text,
fillcolor=constants.COLOR_LAZURE,
width=0.8,
height=0.5,
**kw,
)
[docs]def pulse_baseband(ax: Axes, time: float, qubit_idxs: List[int], text: str, **kw):
"""
Adds a visual indicator for a Baseband pulse to the `mathplotlib.axes.Axis`
instance.
Parameters
----------
ax
time
qubit_idxs
text
"""
cartoon_width = 0.6
for qubit_idx in qubit_idxs:
ps.fluxPulse(
ax,
pos=time - cartoon_width / 2,
y_offs=qubit_idx,
width=cartoon_width,
s=0.0025,
amp=0.33,
**kw,
)
ax.text(time, qubit_idx + 0.45, text, ha="center", va="center", zorder=6)
[docs]def pulse_modulated(ax: Axes, time: float, qubit_idxs: List[int], text: str, **kw):
"""
Adds a visual indicator for a Modulated pulse to the `mathplotlib.axes.Axis`
instance.
Parameters
----------
ax
time
qubit_idxs
text
"""
cartoon_width = 0.6
for qubit_idx in qubit_idxs:
ps.mwPulse(
ax,
pos=time - cartoon_width / 2,
y_offs=qubit_idx,
width=cartoon_width,
amp=0.33,
**kw,
)
ax.text(time, qubit_idx + 0.45, text, ha="center", va="center", zorder=6)
# pylint: disable=unused-argument
[docs]def meter(ax: Axes, time: float, qubit_idxs: List[int], text: str, **kw):
"""
A simple meter to depict a measurement.
Parameters
----------
ax
time
qubit_idxs
text
"""
for qubit_idx in qubit_idxs:
ps.meter(
ax,
x0=time,
y0=qubit_idx,
fillcolor=constants.COLOR_GREY,
y_offs=0,
width=0.8,
height=0.5,
**kw,
)
# pylint: disable=unused-argument
[docs]def acq_meter(ax: Axes, time: float, qubit_idxs: List[int], text: str, **kw):
"""
Variation of the meter to depict a acquisition.
Parameters
----------
ax
time
qubit_idxs
text
"""
for qubit_idx in qubit_idxs:
ps.meter(
ax,
x0=time,
y0=qubit_idx,
fillcolor="white",
y_offs=0.0,
width=0.8,
height=0.5,
framewidth=constants.ACQ_METER_LINEWIDTH,
**kw,
)
[docs]def acq_meter_text(ax: Axes, time: float, qubit_idxs: List[int], text: str, **kw):
"""
Same as acq_meter, but also displays text.
Parameters
----------
ax
time
qubit_idxs
text
"""
acq_meter(ax, time, qubit_idxs, text, **kw)
ax.text(time, max(qubit_idxs) + 0.45, text, ha="center", va="center", zorder=6)
# pylint: disable=unused-argument
[docs]def cnot(ax: Axes, time: float, qubit_idxs: List[int], text: str, **kw):
"""
Markers to denote a CNOT gate between two qubits.
Parameters
----------
ax
time
qubit_idxs
text
"""
ax.plot(
[time, time], qubit_idxs, marker="o", markersize=15, color=constants.COLOR_BLUE
)
ax.plot([time], qubit_idxs[1], marker="+", markersize=12, color="white")
# pylint: disable=unused-argument, invalid-name
[docs]def cz(ax: Axes, time: float, qubit_idxs: List[int], text: str, **kw):
"""
Markers to denote a CZ gate between two qubits.
Parameters
----------
ax
time
qubit_idxs
text
"""
ax.plot(
[time, time], qubit_idxs, marker="o", markersize=15, color=constants.COLOR_BLUE
)
[docs]def reset(ax: Axes, time: float, qubit_idxs: List[int], text: str, **kw):
"""
A broken line to denote qubit initialization.
Parameters
----------
ax
matplotlib axis object.
time
x position to draw the reset on
qubit_idxs
indices of the qubits that the reset is performed on.
text
"""
for qubit_idx in qubit_idxs:
ps.box_text(
ax,
x0=time,
y0=qubit_idx,
text=text,
color="white",
fillcolor="white",
width=0.4,
height=0.5,
**kw,
)
[docs]def _locate_qubit_in_address(qubit_map, address):
"""
Returns the name of a qubit in a pulse address.
"""
if address is None:
raise ValueError(f"Could not resolve address '{address}'")
for sub_addr in address.split(":"):
if sub_addr in qubit_map:
return sub_addr
raise ValueError(f"Could not resolve address '{address}'")
# pylint disabled because func was implemented before pylint was adopted
# pylint: disable=too-many-locals, too-many-branches, too-many-statements
@deprecated(
"0.14.0",
"To plot a circuit diagram, please call `ScheduleBase.plot_circuit_diagram()` from"
"`quantify_scheduler.schedules.schedule.py` instead.",
)
[docs]def circuit_diagram_matplotlib(
schedule: Schedule,
figsize: Tuple[int, int] = None,
ax: Optional[Axes] = None,
) -> Tuple[Figure, Union[Axes, List[Axes]]]:
"""
Creates a circuit diagram visualization of a schedule using matplotlib.
Each gate, pulse, measurement, and operation are plotted in the order of execution, but the exact timing is not visible here.
.. warning::
This function is deprecated and will be removed after `quantify-scheduler>=0.14`.
To plot a circuit diagram, please call :func:`~quantify_scheduler.schedules.schedule.ScheduleBase.plot_circuit_diagram()`
from :class:`~quantify_scheduler.schedules.schedule.ScheduleBase` instead.
Parameters
----------
schedule
the schedule to render.
figsize
matplotlib figsize.
ax
Axis handle to use for plotting.
Returns
-------
fig
matplotlib figure object.
ax
matplotlib axis object.
.. admonition:: Example
:class: tip
.. jupyter-execute::
from quantify_scheduler import Schedule
from quantify_scheduler.operations.gate_library import Reset, X90, CZ, Rxy, Measure
sched = Schedule(f"Bell experiment on q0-q1")
sched.add(Reset("q0", "q1"))
sched.add(X90("q0"))
sched.add(X90("q1"), ref_pt="start", rel_time=0)
sched.add(CZ(qC="q0", qT="q1"))
sched.add(Rxy(theta=45, phi=0, qubit="q0") )
sched.add(Measure("q0", acq_index=0))
sched.add(Measure("q1", acq_index=0), ref_pt="start")
sched.plot_circuit_diagram();
.. note::
Gates that are started simultaneously on the same qubit will overlap.
.. jupyter-execute::
from quantify_scheduler import Schedule
from quantify_scheduler.operations.gate_library import X90, Measure
sched = Schedule(f"overlapping gates")
sched.add(X90("q0"))
sched.add(Measure("q0"), ref_pt="start", rel_time=0)
sched.plot_circuit_diagram();
.. note::
If the pulse's port address was not found then the pulse will be plotted on the 'other' timeline.
"""
# to prevent the original input schedule from being modified.
schedule = deepcopy(schedule)
# pylint: disable=import-outside-toplevel
# importing inside function scope to prevent circular import
from quantify_scheduler.compilation import determine_absolute_timing
schedule = determine_absolute_timing(schedule, "ideal")
qubit_map: Dict[str, int] = {}
qubits: List[str] = set()
for _, operation in schedule.operations.items():
if operation.valid_gate:
qubits.update(operation.data["gate_info"]["qubits"])
for index, qubit in enumerate(sorted(qubits)):
qubit_map[qubit] = index
# Validate pulses
# Note: needs to be done before creating figure and axhline
# in order to avoid unnecessary redraws.
for schedulable in schedule.schedulables.values():
operation = schedule.operations[schedulable["operation_repr"]]
if operation.valid_pulse:
try:
for pulse_info in operation["pulse_info"]:
_locate_qubit_in_address(qubit_map, pulse_info["port"])
except ValueError:
for key in qubit_map:
qubit_map[key] += 1
qubit_map["other"] = 0
break
if operation.valid_acquisition:
try:
for acq_info in operation["acquisition_info"]:
_locate_qubit_in_address(qubit_map, acq_info["port"])
except ValueError:
for key in qubit_map:
qubit_map[key] += 1
qubit_map["other"] = 0
break
if figsize is None:
figsize = (10, len(qubit_map))
fig, ax = ps.new_pulse_fig(figsize=figsize, ax=ax)
ax.set_title(schedule.data["name"])
ax.set_aspect("equal")
ax.set_ylim(-0.5, len(qubit_map) - 0.5)
ax.axhline(0, color="0.1", linewidth=0.9)
for qubit in qubits:
ax.axhline(qubit_map[qubit], color="0.1", linewidth=0.9)
# plot the qubit names on the y-axis
ax.set_yticks(list(qubit_map.values()))
ax.set_yticklabels(qubit_map.keys())
t0, tf, time = 0, 0, 0
for schedulable in sorted(
schedule.schedulables.values(), key=lambda sch: sch["abs_time"]
):
operation = schedule.operations[schedulable["operation_repr"]]
tf = schedulable["abs_time"]
time += 1 if tf != t0 else 0
t0 = tf
if operation.valid_gate:
plot_func = import_python_object_from_string(
operation["gate_info"]["plot_func"]
)
idxs = [qubit_map[qubit] for qubit in operation["gate_info"]["qubits"]]
plot_func(
ax, time=time, qubit_idxs=idxs, text=operation["gate_info"]["tex"]
)
elif operation.valid_pulse:
idxs: List[int]
try:
idxs = [
qubit_map[_locate_qubit_in_address(qubit_map, pulse_info["port"])]
for pulse_info in operation["pulse_info"]
]
except ValueError:
# The pulse port was not found in the qubit_map
# move this pulse to the 'other' timeline
idxs = [0]
for pulse_info in operation["pulse_info"]:
clock_id: str = pulse_info["clock"]
clock_resource: dict = schedule.data["resource_dict"][clock_id]
if clock_resource["freq"] == 0:
pulse_baseband(ax, time=time, qubit_idxs=idxs, text=operation.name)
else:
pulse_modulated(ax, time=time, qubit_idxs=idxs, text=operation.name)
elif operation.valid_acquisition:
idxs: List[int]
try:
idxs = [
qubit_map[_locate_qubit_in_address(qubit_map, acq_info["port"])]
for acq_info in operation["acquisition_info"]
]
except ValueError:
# The pulse port was not found in the qubit_map
# move this pulse to the 'other' timeline
idxs = [0]
for _ in operation["acquisition_info"]:
acq_meter(ax, time=time, qubit_idxs=idxs, text=operation.name)
else:
raise ValueError("Unknown operation")
ax.set_xlim(-1, time + 1)
return fig, ax