Skip to content

Near-Time Callback Functions and 3rd-Party Devices

LabOne Q programs the full stack of instruments that are part of the Zurich Instruments QCCS family. Additionally, it can call any Python function from within the near-time section of an experiment and thus change or influence the experiment flow. In particular, this allows to integrate non-QCCS instruments, for instance, in a sweep.

Callback Functions in Near-Time Experiments

Near-time callback functions in LabOne Q allow interaction with the current experiment, at any point within its near-time execution (see Averaging and Sweeping for more explanation on real-time and near-time sweeps). A near-time callback function

  • can be any function that is defined in Python,
  • can accept input values passed on from the LabOne Q controller, and
  • can return values.

Any values the function returns will be included in the result object after experiment execution.

Definition of Near-Time Callback Functions

Near-time callbacks are defined like any other function in Python, and can contain named arguments and return values. Every function is embedded in the scope of the current LabOne Q session, thus the first argument is always the current session.

An example of a near-time callback is the following:

def my_func(session, frequency, amplitude):
    """
    Arbitrary Python function with named arguments and return value

    Args:
        session: LabOne Q session instance (forwarded by LabOne Q controller)
        frequency: named argument 1
        amplitude: named argument 2

    Returns:
        Product of the two input values.
    """

    ## arbitrary operations are allowed e.g., print a value, mathematical operation
    print f"frequency={frequency}, amplitude={amplitude}"

    ## returned values will be part of the result object
    return frequency*amplitude

Since near-time callback functions have access to the current session, they also have access to the session results. This allows them to read results during a near-time sweep and to process these partial results inside the callback function.

Using Near-Time Callbacks in a Sweep

Near-time callbacks can be used in the experiment at any point inside a near-time loop using the call instruction, the function name and the input arguments. The LabOne Q session object is implicitly forwarded and does not need to be stated by the user in the experiment definition. Input arguments to the callback function can be float values but also a LabOne Q sweep parameter. The latter also enables near-time sweeps of settings on non-QCCS instruments, and will be discussed in more detail in the following section on Controlling Non-QCCS Devices.

An example call for the function defined above is the following:

exp.call("my_func", frequency = sweep_parameter, amplitude = 0.1)

Here, the frequency input is a sweep parameter, while the amplitude value is fixed.

A full example experiment definition using near-time callback functions is shown below. It shows different ways to refer to the function, as commented in the code snippet. During the experiment execution, the order of calls to user-defined functions is maintained. In this way it is possible, for instance, to have a near-time callback function before and after the real-time averaging loop.

## outer loop - near-time sweep
with exp.sweep(uid="outer_sweep", parameter=sweep_parameter):
    ## Only named arguments are supported.
    ## Arguments to `exp.call` must match those
    ## of the respective function definition.

    ## Variant 1: Use python function name as reference.
    exp.call(my_func, frequency=500e6, amplitude=sweep_parameter)

    ## Variant 2: Use custom name as reference.
    exp.call("calc_power", amplitude=sweep_parameter, gain=1.0)

    ## Calling same function multiple times allowed,
    ## results will be appended to the same result
    ## list in order of execution.
    exp.call("calc_power", amplitude=sweep_parameter, gain=2.0)

    ## The same python function may be registered
    ## with different reference names,
    ## in which case it is treated as a separate function,
    ## producing its own result list.
    exp.call("calc_power_alt", amplitude=sweep_parameter, gain=4.0)

    ## inner loop - near-time sweep
    with exp.sweep(uid="inner_sweep", parameter=inner_arbitrary_sweep):
        ## Variant 2: Use custom name as reference.
        exp.call("inner_neartime_callback", param=inner_arbitrary_sweep)

        ## innermost loop - real-time pulse sequence with averaging
        with exp.acquire_loop_rt(...):
        ## ...

    ## The call order of near-time callbacks is preserved relative to the nested sections
    exp.call("after_inner_neartime_callback")

Registering Near-Time Callback Functions

As seen in the example above, near-time callbacks can be referred to by either using the function name as defined or by a specified reference name. The latter is defined during the registration of the near-time callback function. The registration is required for every near-time callback that is used in the experiment and ensures the inclusion of these external functions in the scope of the LabOne Q session.

The following example shows how to register near-time callbacks according to the different ways that they are used in the above example experiment definition.

## Variant 1: Use python function name as reference
session.register_neartime_callback(my_func)

## Variant 2: Give the name explicitly
session.register_neartime_callback(my_power_func, "calc_power")

## Same python function may be registered multiple times with different names
session.register_neartime_callback(my_power_func, "calc_power_alt")

session.register_neartime_callback(inner_neartime_callback, "inner_neartime_callback")
session.register_neartime_callback(after_inner_neartime_callback, "after_inner_neartime_callback")

Retrieving Results from Near-Time Callback Functions

Near-time callbacks used inside LabOne Q can return values which are attached to the result object of the session as a list. Every call of the function results in a new entry in the list. The key to access the results is the registered name of the callback function:

## Return values of near-time callbacks upon execution are available per function, use function name as a key.
session.results.neartime_callback_results["my_func"]
## Two calls per iteration to `calc_power` result in two adjacent entries in the results
session.results.neartime_callback_results["calc_power"]

Aborting execution

A near-time callback can abort the execution of the remaining near-time steps.

Simply call session.abort_execution(). The LabOne Q controller will then immediately and gracefully terminate the experiment.

Note that abort_execution() does not return, it immediately hands control back to LabOne Q. Any code in the near-time callback after it is not executed.

Controlling Non-QCCS Devices

The use of near-time callbacks allows one to also control instruments in an experiment loop which are not part of the QCCS, as long as there is a Python driver available. Examples are, amongst others, microwave sources, DC voltage or current sources and magnets.

Leveraging near-time callback functions, we can also incorporate Zurich Instruments lock-in amplifiers as the UHFLI or MFLI into experiments that are run with LabOne Q. To do so, we can use the Python API or Zurich Instruments Toolkit to connect to the instruments.

Zurich Instruments Toolkit

zhinst.toolkit can be used to control individual devices and node values during experiments.

Danger

Toolkit functions cannot be called during an emulated run!

Danger

When LabOne Q controls devices by running an experiment via session.run(), never use subscribe/poll over zhinst.toolkit (neither in the main script before/after session.run(), nor in the user functions)

An example of a Toolkit near-time callback function is the following:

def toolkit_awg_output_amplitude(session, amplitude: float):
    """
    Change HDAWG AWG 0 output 0 amplitude.

    Args:
        session: LabOne Q session instance (forwarded by LabOne Q controller)
        amplitude: Amplitude for the selected AWG output
    """
    session.devices["device_hdawg"].awgs[0].outputs[0].amplitude(amplitude)

Now the function toolkit_awg_output_amplitude can be utilized like any other near-time callback during an experiment.