This page was generated from docs/examples/DataSet/Offline Plotting Tutorial.ipynb. Interactive online version: Binder badge.

Offline Plotting Tutorial

The dataset comes with a tool for offline (i.e. not live as the data are coming in) plotting. This notebook explains how to use it and what it is capable of plotting. NOTE: This notebook only covers the plotting of numerical data. For categorical (string-valued) data, please see Offline plotting with categorical data.

The function that is going to be used in this tutorial is the plot_dataset. For convenience it is also possible to directly plot the dataset from the captured_run_id. Apart from the first argument plot_by_id behaves exactly like the plot_dataset. All customizations shown below can also be applied to plot_by_id.

[1]:
%matplotlib inline
from pathlib import Path

import matplotlib
import matplotlib.pyplot as plt
import numpy as np

from qcodes.dataset import (
    Measurement,
    initialise_or_create_database_at,
    load_or_create_experiment,
    plot_dataset,
)
from qcodes.parameters import Parameter
Logging hadn't been started.
Activating auto-logging. Current session state plus future input saved.
Filename       : /home/runner/.qcodes/logs/command_history.log
Mode           : append
Output logging : True
Raw input log  : False
Timestamping   : True
State          : active
Qcodes Logfile : /home/runner/.qcodes/logs/241121-16147-qcodes.log

Let us first initialise our database and create an experiment which shall produce the data to be visualise.

[2]:
initialise_or_create_database_at(Path.cwd() / "offline_plotting_example.db")
exp = load_or_create_experiment("offline_plotting_experiment", "nosample")

Next we make a handful of parameters to be used in the examples of this notebook.

For those curious, setting set_cmd=None and get_cmd=None makes the Parameters settable and gettable without them being hooked up to any external/auxiliary action (in old QCoDeS versions, this was known as a ManualParameter).

[3]:
# Make a handful of parameters to be used in the examples

x = Parameter(name="x", label="Voltage", unit="V", set_cmd=None, get_cmd=None)
t = Parameter(name="t", label="Time", unit="s", set_cmd=None, get_cmd=None)
y = Parameter(name="y", label="Voltage", unit="V", set_cmd=None, get_cmd=None)
y2 = Parameter(name="y2", label="Current", unit="A", set_cmd=None, get_cmd=None)
z = Parameter(
    name="z", label="Majorana number", unit="Anyonic charge", set_cmd=None, get_cmd=None
)

A single, simple 1D sweep

For the sake of simplicity, let us perform a single, 1D sweep:

[4]:
meas = Measurement(exp=exp)
meas.register_parameter(x)
meas.register_parameter(y, setpoints=(x,))

xvals = np.linspace(-3.4, 4.2, 250)

# Randomly shuffle the values in order to test the plot
# that is to be created for this data is a correct line
# that does not depend on the order of the data.
np.random.shuffle(xvals)

with meas.run() as datasaver:
    for xnum in xvals:
        noise = np.random.randn() * 0.1  # multiplicative noise yeah yeah
        datasaver.add_result(
            (x, xnum), (y, 2 * (xnum + noise) ** 3 - 5 * (xnum + noise) ** 2)
        )

dataid = datasaver.run_id
dataset = datasaver.dataset
Starting experimental run with id: 1.

Now let us plot that run. The function plot_dataset takes the dataset created by the run to plot as a positional argument. Furthermore, the user may specify the matplotlib axis object (or list of axis objects) to plot on.

If no axes are specified, the function creates new axis object(s). The function returns a tuple of a list of the axes and a list of the colorbar axes (just Nones if there are no colorbars).

[5]:
axes, cbaxes = plot_dataset(dataset)
../../_images/examples_DataSet_Offline_Plotting_Tutorial_11_0.png

Using the returned axis, we can change, among other things, the plot linewidth and color. We refer to the matplotlib documentation for details on matplotlib plot customization.

[6]:
my_ax = axes[0]
line = my_ax.lines[0]
line.set_color("#223344")
line.set_linewidth(3)

Rescaling units and ticks

The plot_dataset function can conveniently rescale the units and ticks of the plot. For example, if one of the axes is voltage in units of V, but the values are in the range of millivolts, then plot_dataset will rescale the ticks of the axis to show 5 instead of 0.005, and the unit in the axis label will be adjusted from V to mV.

This feature works with the relevant SI units, and some others. In case the units of the parameter are not from that list, or are simply not specified, ticks and labels are left intact.

The feature can be explicitly turned off by passing rescale_axes=False to the function.

The following plot demontrates the feature.

[7]:
meas = Measurement(exp=exp)
meas.register_parameter(t)
meas.register_parameter(y, setpoints=(t,))

with meas.run() as datasaver:
    for tnum in np.linspace(-3.4, 4.2, 50):
        noise = np.random.randn() * 0.1
        datasaver.add_result(
            (t, tnum * 1e-6),
            (y, (2 * (tnum + noise) ** 3 - 5 * (tnum + noise) ** 2) * 1e3),
        )

dataset = datasaver.dataset
Starting experimental run with id: 2.
[8]:
plot_dataset(dataset)
[8]:
([<Axes: title={'center': 'Run #2, Experiment offline_plotting_experiment (nosample)'}, xlabel='Time (μs)', ylabel='Voltage (kV)'>],
 [None])
../../_images/examples_DataSet_Offline_Plotting_Tutorial_16_1.png

Two interleaved 1D sweeps

Now we make a run where two parameters are measured as a function of the same parameter.

[9]:
meas = Measurement(exp=exp)
meas.register_parameter(x)
meas.register_parameter(y, setpoints=[x])
meas.register_parameter(y2, setpoints=[x])

xvals = np.linspace(-5, 5, 250)

with meas.run() as datasaver:
    for xnum in xvals:
        datasaver.add_result((x, xnum), (y, xnum**2))
        datasaver.add_result((x, xnum), (y2, -(xnum**2)))

dataset = datasaver.dataset
Starting experimental run with id: 3.

In such a situation, the plot_dataset by default creates a new axis for each dependent parameter. Sometimes this is not desirable; we’d rather have both plots on the same axis. If this is the case, then, we might pass the same axis twice to the plot_dataset.

[10]:
axes, cbaxes = plot_dataset(dataset)
../../_images/examples_DataSet_Offline_Plotting_Tutorial_20_0.png
../../_images/examples_DataSet_Offline_Plotting_Tutorial_20_1.png

Let’s do that now

[11]:
fig, ax = plt.subplots(1)
axes, cbaxes = plot_dataset(dataset, axes=[ax, ax])
../../_images/examples_DataSet_Offline_Plotting_Tutorial_22_0.png

Regular 2D rectangular sweep scan

For 2D plots, a colorbar is usually present. As mentioned above, the plot_dataset function returns a colorbar.

[12]:
meas = Measurement(exp=exp)

meas.register_parameter(x)
meas.register_parameter(t)
meas.register_parameter(z, setpoints=(x, t))

xvals = np.linspace(-4, 5, 50)
tvals = np.linspace(-500, 1500, 25)

with meas.run() as datasaver:
    for xv in xvals:
        for tv in tvals:
            # just some arbitrary semi good looking function
            zv = np.sin(2 * np.pi * xv) * np.cos(2 * np.pi * 0.001 * tv) + 0.001 * tv
            datasaver.add_result((x, xv), (t, tv), (z, zv))

dataset = datasaver.dataset
Starting experimental run with id: 4.
[13]:
axes, colorbars = plot_dataset(dataset)
../../_images/examples_DataSet_Offline_Plotting_Tutorial_25_0.png

A fairly normal situation is that the colorbar was somehow mislabelled. Using the returned colorbar, the label can be overwritten.

[14]:
colorbar = colorbars[0]
colorbar.set_label("Correct science label")

Customisations

In addition to tweaking the returned axes and colorbars, it is possible to customise the plot directly via the call to the plot_dataset. This is done by the plot_dataset via passing on the keyword arguments to the corresponding matplotlib function that runs under the hood.

A few examples:

Changing the colormap

Any matplotlib colormap can be used. The relevant keyword argument is cmap.

[15]:
cmap = matplotlib.colormaps.get_cmap("hot")
axes, colorbars = plot_dataset(dataset, cmap=cmap)
../../_images/examples_DataSet_Offline_Plotting_Tutorial_30_0.png

Setting the colorscale to logarithmic

To set the colorscale to logarithmic, we can use the keyword argument norm along with a matplotlib Norm.

[16]:
from matplotlib.colors import LogNorm

norm = LogNorm()
axes, colorbars = plot_dataset(dataset, norm=norm)
../../_images/examples_DataSet_Offline_Plotting_Tutorial_32_0.png

Setting limits on the colorscale

The keyword arguments vmin and vmax come in handy, if we need to set limits on the colorscale.

[17]:
axes, colorbars = plot_dataset(dataset, vmin=0.2, vmax=0.5)
../../_images/examples_DataSet_Offline_Plotting_Tutorial_34_0.png

Warped 2D rectangular sweep scan

A nice feature of the plot_dataset is that the grid may be warped; it makes no difference. Here we warp the x axis of the previous scan to increase the resolution in the right half plane.

[18]:
xvals = np.linspace(-4, 5, 50) + np.cos(-1 / 6 * np.pi * xvals)
tvals = np.linspace(-500, 1500, 25)

with meas.run() as datasaver:
    for xv in xvals:
        for tv in tvals:
            zv = np.sin(2 * np.pi * xv) * np.cos(2 * np.pi * 0.001 * tv) + 0.001 * tv
            datasaver.add_result((x, xv), (t, tv), (z, zv))

dataset = datasaver.dataset
Starting experimental run with id: 5.
[19]:
axes, cbaxes = plot_dataset(dataset)
../../_images/examples_DataSet_Offline_Plotting_Tutorial_37_0.png

Interrupted 2D scans (a hole in the cheese)

In case a sweep in interrupted, the entire grid will not be filled out. This is also supported, in fact, any single rectangular hole is allowed.

[20]:
xvals = np.linspace(-4, 5, 50) + np.cos(2 / 9 * np.pi * xvals + np.pi / 4)
tvals = np.linspace(-500, 1500, 25)

# define two small forbidden range functions


def no_x(xv):
    if xv > 0 and xv < 3:
        return True
    else:
        return False


def no_t(tv):
    if tv > 0 and tv < 450:
        return True
    else:
        return False


with meas.run() as datasaver:
    for xv in xvals:
        for tv in tvals:
            if no_x(xv) and no_t(tv):
                continue
            else:
                zv = (
                    np.sin(2 * np.pi * xv) * np.cos(2 * np.pi * 0.001 * tv) + 0.001 * tv
                )
                datasaver.add_result((x, xv), (t, tv), (z, zv))

dataset = datasaver.dataset
Starting experimental run with id: 6.
[21]:
axes, colorbars = plot_dataset(dataset)
../../_images/examples_DataSet_Offline_Plotting_Tutorial_40_0.png

Fancy plotting

As a final example, let us combine several plots in one window.

We first make a little grid of axes.

[22]:
fig, figaxes = plt.subplots(2, 2)
../../_images/examples_DataSet_Offline_Plotting_Tutorial_43_0.png

Next, we make some runs (shamelessly copy-pasting from above).

[23]:
# First run
meas = Measurement(exp=exp)
meas.register_parameter(x)
meas.register_parameter(y, setpoints=(x,))

xvals = np.linspace(-3.4, 4.2, 250)

with meas.run() as datasaver:
    for xnum in xvals:
        noise = np.random.randn() * 0.1  # multiplicative noise yeah yeah
        datasaver.add_result(
            (x, xnum), (y, 2 * (xnum + noise) ** 3 - 5 * (xnum + noise) ** 2)
        )

ds1 = datasaver.dataset

# Second run

meas = Measurement(exp=exp)

meas.register_parameter(x)
meas.register_parameter(t)
meas.register_parameter(z, setpoints=(x, t))

xvals = np.linspace(-4, 5, 50)
tvals = np.linspace(-500, 1500, 25)

with meas.run() as datasaver:
    for xv in xvals:
        for tv in tvals:
            # just some arbitrary semi good looking function
            zv = np.sin(2 * np.pi * xv) * np.cos(2 * np.pi * 0.001 * tv) + 0.001 * tv
            datasaver.add_result((x, xv), (t, tv), (z, zv))

ds2 = datasaver.dataset
Starting experimental run with id: 7.
Starting experimental run with id: 8.

And then we put them just where we please.

[24]:
axes, colorbars = plot_dataset(ds1, figaxes[0, 0])
[25]:
axes, colorbars = plot_dataset(ds2, figaxes[1, 1], colorbars)

Note that if we want to replot on an axis with a colorbar we probably also want to reuse the colorbar.

[26]:
axes, colorbars = plot_dataset(ds2, figaxes[1, 1], colorbars)
[27]:
fig.tight_layout()

Rasterizing

By default matplotlib renders each individual data point as a separate square in 2D plots when storing in a vector format (pdf, svg). This is not a problem for small data sets, but the time needed to generate a pdf increases rapidly with the number of data points. Therefore, the plot_dataset will automatically raster the data (lines, ticks and labels are still stored as text) if more than 5000 data points are plotted. The particular value of the rasterization threshold can be set in the qcodesrc.json config file.

Alternatively the rasterized keyword can be passed to the plot_dataset function.

[28]:
meas = Measurement(exp=exp)

meas.register_parameter(x)
meas.register_parameter(t)
meas.register_parameter(z, setpoints=(x, t))

xvals = np.linspace(-4, 5, 100)
tvals = np.linspace(-500, 1500, 500)

with meas.run() as datasaver:
    for xv in xvals:
        for tv in tvals:
            # just some arbitrary semi good looking function
            zv = np.sin(2 * np.pi * xv) * np.cos(2 * np.pi * 0.001 * tv) + 0.001 * tv
            datasaver.add_result((x, xv), (t, tv), (z, zv))

dataset = datasaver.dataset
Starting experimental run with id: 9.

To get a feeling for the time difference between rasterzing and not, we time the two approaches here.

[29]:
%%time
axeslist, _ = plot_dataset(dataset)
axeslist[0].figure.savefig(f"test_plot_dataset_{dataid}.pdf")
CPU times: user 197 ms, sys: 11.7 ms, total: 209 ms
Wall time: 233 ms
../../_images/examples_DataSet_Offline_Plotting_Tutorial_56_1.png
[30]:
%%time
axeslist, _ = plot_dataset(dataset, rasterized=False)
axeslist[0].figure.savefig(f"test_plot_dataset_{dataid}.pdf")
CPU times: user 3.63 s, sys: 36 ms, total: 3.67 s
Wall time: 3.67 s
../../_images/examples_DataSet_Offline_Plotting_Tutorial_57_1.png