Calibration¶
Purpose¶
The calibration class in LabOne Q allows to hierarchically organize settings in order to
- apply settings to the instruments of the device setup
- alter those settings temporarily for the duration of an experiment
- sweep the value of specific nodes in real-time or near-time sweeps
Objects of this class are typically structured hierarchically and need to be compatible with the device setup or experiment signals they are applied to.
Usage Scenarios and Learning Goals¶
In the following tutorial you will first learn how to access the elements of a device setup to which calibrations can be applied. This is then used to construct and apply calibration objects to the device setup. Subsequently, you will see how experiment signals can be calibrated and how these calibrations can be used to implement sweeps or specific signal settings.
Imports and Initialization¶
The Calibration
class is one of the primary components of LabOne Q's DSL and there available from the standard LabOne Q import.
from laboneq.simple import *
To get started, we will furthermore use an externally defined device setup object.
from tutorials_device_setups import device_setup_02 as device_setup
The Calibration
objects used to change settings of this device setup need to contain components with compatible paths and types.
We therefore look at the calibratable elements of the device setup first and then construct a matching Calibration
instance from this information.
The get_calibration
function of a device setup object returns a Calibration
instance that represents an up to date device setup configuration (see the Device Setup Tutorial).
calibration = device_setup.get_calibration()
The calibration objects obtained this way serve as a baseline calibration of the hardware setup.
Calibration objects are internally organized in terms of calibration items. We can access the paths of these items as follows:
for calibration_item in calibration.calibration_items:
print(calibration_item)
For each of these calibration items we can obtain its respective base line calibration from the device_setup
directly. For example:
calibration_item = "logical_signal_groups/q0/acquire"
print(device_setup.get_calibration(calibration_item))
Note, that the device_setup
instance used here is still uncalibrated so that the get_calibration
function returns None
at this point.
Calibratables¶
Looking at the above list of calibration items in device_setup
, we notice two types of such "calibratables", Logical Signals and Physical Channels.
Logical Signals¶
Logical signals are organized by logical signal groups and can e.g. be accessed and inspected as follows.
def list_ls_calibratables(device_setup):
# header
print(f"{'LOGICAL SIGNAL':^33s} | {'CALIBRATED':<10} | {'TYPE':^12s}")
# loop over logical signal groups
for g in device_setup.logical_signal_groups:
logical_signal_group = device_setup.logical_signal_groups[g]
# loop over logical signals
for ls in logical_signal_group.logical_signals:
logical_signal = logical_signal_group.logical_signals[ls]
# inspect and show information
print(
f"{logical_signal.path:<33s} | {str(logical_signal.is_calibrated()):<10} | {type(logical_signal).__name__}"
)
list_ls_calibratables(device_setup)
Physical Channels¶
Physical signals are organized by physical channel groups and can be accessed and inspected analogously to logical signals.
def list_pc_calibratables(device_setup):
# header
print(f"{'PHYSICAL CHANNEL':^50s} | {'CALIBRATED':<10} | {'TYPE'}")
# loop over physical channel groups
for g in device_setup.physical_channel_groups:
physical_channel_group = device_setup.physical_channel_groups[g]
# loop over physical channels
for pc in physical_channel_group.channels:
physical_channel = physical_channel_group.channels[pc]
# list information
print(
f"{physical_channel.path:<50s} | {str(physical_channel.is_calibrated()):<10} | {type(physical_channel).__name__}"
)
list_pc_calibratables(device_setup)
The calibration of physical channels refers to actual instrument settings. In most cases, such settings need to be changed for channels that are associated with one or more logical signals. We can directly access the calibration of a physical channel from its logical signal as follows.
logical_signal = device_setup.logical_signal_groups["q0"].logical_signals["drive"]
logical_signal.physical_channel.calibration
Signal Calibration¶
Logical signals and physical channels can both be calibrated with SignalCalibration
objects.
We can define an example instance for the logical signal drive
as follows.
drive_calibration = SignalCalibration(range=0)
We can also set a new value for range
and any other option of the SignalCalibration
by explicit assignment.
drive_calibration.range = 5
The manual provides the details regarding which signal calibration properties are supported for logical signals and physical channels, respectively.
Some signal calibration properties have values which in turn contain other calibration settings.
MixerCalibration
, Precompensation
, and Oscillator
are the most common example of such types.
The latter can be used to calibrate digital oscillators...
drive_calibration.oscillator = (
Oscillator(
uid="q0_drive_ge_osc",
frequency=-250000000.0,
modulation_type=ModulationType.AUTO,
carrier_type=None,
),
)
and local oscillator settings.
drive_calibration.local_oscillator = (
Oscillator(
uid="q0_drive_local_osc",
frequency=4000000000.0,
modulation_type=ModulationType.AUTO,
carrier_type=None,
),
)
We can assign the assembled SignalCalibration
instance under the appropriate path in the Calibration
object...
calibration["/logical_signal_groups/q0/drive"] = drive_calibration
and confirm that the drive
signal of the logical signal group q0
now has the correct SignalCalibration
assigned.
calibration
Calibrating the Device Setup¶
The settings of a calibration object can be set directly to the device setup.
device_setup.set_calibration(calibration)
We can apply such Calibration
objects repeatedly to fill or update the calibratables in the device setup.
A new Calibration
instance containing only the readout
signal of logical signal group q0
is assigned below.
calibratable = device_setup.logical_signal_groups["q0"].logical_signals["measure"].path
calibration_item = SignalCalibration(
oscillator=Oscillator(
uid="q0_readout_acquire_osc",
frequency=-250000000.0,
modulation_type=ModulationType.AUTO,
),
local_oscillator=Oscillator(
uid="q0_readout_local_osc",
frequency=6000000000.0,
modulation_type=ModulationType.AUTO,
),
port_delay=4e-08,
range=10,
)
device_setup.set_calibration(Calibration({calibratable: calibration_item}))
We can verify that both logical signals are now calibrated...
list_ls_calibratables(device_setup)
while noting that the settings are also propagated to the corresponding physical channels.
list_pc_calibratables(device_setup)
The physical channel associated with the signal drive
of group q0
has therefore a calibrated local oscillator.
path = (
device_setup.logical_signal_groups["q0"]
.logical_signals["drive"]
.physical_channel.path
)
device_setup.get_calibration(path)
Calibrating Experiments¶
Calibration
instances can also be applied to experimental signals in Experiment
objects.
To examine this in more detail, we import a simple example of an experiment object.
from tutorials_experiments import experiment_02 as experiment
Here we will only discuss the aspects of this object relevant to the calibration.
However, you can find out more about the details and functionality of the Experiment
class in the next tutorials and the manual.
Temporary Calibration of Experiment Signals¶
Experimental signals act as calibratables in an experiment.
for id, signal in experiment.signals.items():
print(f"{id:8s} | {type(signal).__name__}")
These experimental signals will ultimately be mapped to compatible logical signals.
We can therefore use the same types of Calibration
and SignalCalibration
objects to calibrate experimental signals are we did above for logical signals.
For example, to change the range
parameter of the drive
signal for the duration of the expeirment we first instantiate a new Calibration
object...
experiment_calibration = Calibration({"drive": SignalCalibration(range=-5)})
and apply it then directly to the experiment.
experiment.set_calibration(experiment_calibration)
We can now inspect the calibration of the experiment signal to confirm that these changes have indeed been applied correctly.
experiment.signals["drive"].calibration
Sweep Calibrations¶
Applying Calibration
objects to experiments also allows you enable sweeps of individual calibration nodes.
Before being able to execute the above experiment, we need to calibrate the frequency sweep of the drive
signal.
For this we require the same SweepParameter
that is used in the definition of the experiment. Here, we simply import this object...
from tutorials_experiments import freq_sweep
and confirm that it sweeps the frequency from -100 to +100 MHz around the center frequency:
freq_sweep
With this SweepParameter
object we can then construct a new signal calibration property for the digital oscillator of the drive signal and assign it to the calibration item.
experiment_calibration["drive"].oscillator = Oscillator(
uid="q0_drive_sweep",
frequency=freq_sweep,
modulation_type=ModulationType.AUTO,
carrier_type=None,
)
Assigning the calibration again to the experiment...
experiment.set_calibration(experiment_calibration)
now assigns the SweepParameter
object to the frequency of the drive
signal...
experiment.signals["drive"].calibration
which enables LabOne Q to sweep this hardware node. See the manual table for an overview of the calibration nodes that support sweeping.