This page was generated from docs/examples/DataSet/Pedestrian example of subscribing to a DataSet.ipynb. Interactive online version: Binder badge.

Pedestrian example of subscribing to a DataSet

It is possible to subscribe to a dataset. Subscribing means adding a function to the dataset and having the dataset call that function every time a result is added to the dataset (or more rarely, see below).

Call signature

The subscribing function must have the following call signature:

fun(results: List[Tuple[Value]], length: int,
    state: Union[MutableSequence, MutableMapping]) -> None:
    """
    Args:
        results: A list of tuples where each tuple holds the results inserted into the dataset.
            For two scalar parameters, X and Y, results might look like [(x1, y1), (x2, y2), ...]
        length: The current length of the dataset.
        state: Any mutable sequence/mapping that can be used to hold information from call to call.
            In practice a list or a dict.
    """

Below we provide an example function that counts the number of times a voltage has exceeded a certain limit.

Frequency

Since calling the function every time an insertion is made may be too frequent, a min_wait and a min_count argument may be provided when subscribing. The dataset will then only call the function upon inserting a result if min_wait seconds have elapsed since the last call (or the start of the subscription, in the time before the first call) and min_count results have been added to the dataset since the last call (or the start of the subscription). All the results added in the meantime are queued and passed to the function in one go.

Order

The subscription must be set up after all parameters have been added to the dataset.

[1]:
from pathlib import Path
from time import sleep

import numpy as np

from qcodes.dataset import (
    ParamSpec,
    initialise_or_create_database_at,
    load_or_create_experiment,
    new_data_set,
)
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/240501-16382-qcodes.log

Example 1: A notification

We imagine scanning a frequency and reading out a noisy voltage. When the voltage has exceeded a threshold 5 times, we want to receive a warning. Let us initialise our database and create an experiment.

[2]:
initialise_or_create_database_at(Path.cwd() / "subscription_tutorial.db")
exp = load_or_create_experiment(experiment_name="subscription_tutorial", sample_name="no_sample")
[3]:
dataSet = new_data_set("test",
                       exp_id=exp.exp_id,
                       specs=[ParamSpec("x", "numeric", unit='Hz'),
                              ParamSpec("y", "numeric", unit='V')])
dataSet.mark_started()
[4]:
def threshold_notifier(results, length, state):
    if len(state) > 4:
        print(f'At step {length}: The voltage exceeded the limit 5 times! ')
        state.clear()
    for result in results:
        if result[1] > 0.8:
            state.append(result[1])
[5]:
# now perform the subscription
# since this is important safety info, we want our callback function called
# on EVERY insertion
sub_id = dataSet.subscribe(threshold_notifier, min_wait=0, min_count=1, state=[])
[6]:
for x in np.linspace(100, 200, 150):
    y = np.random.randn()
    dataSet.add_results([{"x": x, "y": y}])
At step 6: The voltage exceeded the limit 5 times!
At step 11: The voltage exceeded the limit 5 times!
At step 16: The voltage exceeded the limit 5 times!
At step 21: The voltage exceeded the limit 5 times!
At step 26: The voltage exceeded the limit 5 times!
At step 31: The voltage exceeded the limit 5 times!
At step 36: The voltage exceeded the limit 5 times!
At step 41: The voltage exceeded the limit 5 times!
At step 46: The voltage exceeded the limit 5 times!
At step 51: The voltage exceeded the limit 5 times!
At step 56: The voltage exceeded the limit 5 times!
At step 61: The voltage exceeded the limit 5 times!
At step 66: The voltage exceeded the limit 5 times!
At step 71: The voltage exceeded the limit 5 times!
At step 76: The voltage exceeded the limit 5 times!
At step 81: The voltage exceeded the limit 5 times!
At step 86: The voltage exceeded the limit 5 times!
At step 91: The voltage exceeded the limit 5 times!
At step 96: The voltage exceeded the limit 5 times!
At step 101: The voltage exceeded the limit 5 times!
At step 106: The voltage exceeded the limit 5 times!
At step 111: The voltage exceeded the limit 5 times!
At step 116: The voltage exceeded the limit 5 times!
At step 121: The voltage exceeded the limit 5 times!
At step 126: The voltage exceeded the limit 5 times!
At step 131: The voltage exceeded the limit 5 times!
At step 136: The voltage exceeded the limit 5 times!
At step 141: The voltage exceeded the limit 5 times!
At step 146: The voltage exceeded the limit 5 times!
[7]:
dataSet.unsubscribe_all()

Example 2: ASCII Plotter

While this example does not represent a data acqusition that one may deal with in a real experiment, it is a fun practice of subscribing to a dataset.

[8]:
dataSet = new_data_set("test", exp_id=exp.exp_id,
                       specs=[ParamSpec("blip", "numeric", unit='bit'),
                              ParamSpec("blop", "numeric", unit='bit')])
dataSet.mark_started()
[9]:
def ASCII_plotter_5bit(results, length, state):
    """
    Glorious 5-bit signal plotter

    Digitises the range (-1, 1) with 4 bits and plots it
    in stdout. Crashes and burns if given data outside that
    interval.
    """
    for result in results:
        plotline = ['.'] * 32
        yvalue = result[1]
        yvalue += 1
        yvalue /= 2
        yvalue = int(yvalue*31)
        plotline[yvalue] = 'O'
        print(''.join(plotline))

[10]:
sub_id = dataSet.subscribe(ASCII_plotter_5bit, min_wait=0, min_count=3, state=[])
[11]:
for x in np.linspace(0, 3*np.pi, 100):
    yvalue = 0.9*np.sin(x) + np.random.randn()*0.05
    dataSet.add_results([{"blip": x, "blop": yvalue}])
    sleep(0.1)
...............O................
................O...............
..................O.............
...................O............
.....................O..........
......................O.........
........................O.......
.........................O......
...........................O....
............................O...
..............................O.
...............................O
Exception in thread Thread-7:
Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/threading.py", line 1045, in _bootstrap_inner
    self.run()
  File "/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/qcodes/dataset/subscriber.py", line 90, in run
    self._loop()
  File "/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/qcodes/dataset/subscriber.py", line 113, in _loop
    self._call_callback_on_queue_data()
  File "/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/qcodes/dataset/subscriber.py", line 104, in _call_callback_on_queue_data
    self.callback(result_list, self._data_set_len, self.state)
  File "/tmp/ipykernel_16382/877923786.py", line 15, in ASCII_plotter_5bit
IndexError: list assignment index out of range
[ ]: