Calibration Reference¶
In this notebook, you'll learn how to calibrate a DeviceSetup and Experiment object in LabOne Q in multiple ways, as well as get, set, reset, and serialize your calibration.
Calibration Types¶
Actual calibration data is stored in so called CalibrationItem objects. These objects are assigned to the calibration property of a Calibratable object which is a data entity in a DeviceSetup or Experiment object tree.
For example, a Calibratable data entity is a LogicalSignal or an ExperimentSignal object. The corresponding CalibrationItem objects is of type SignalCalibration.
These are the currently available Calibratables and CalibrationItems:
| Calibratable | CalibrationItem |
|---|---|
LogicalSignal |
SignalCalibration |
ExperimentSignal |
SignalCalibration |
Overriding Mechanism¶
The calibration of a DeviceSetup is considered as the baseline calibration that is used for experiment execution.
This baseline calibration can be overridden non-destructively with calibration on an Experiment. If a SignalCalibration is defined i.e. not None on an ExperimentSignal, then actual values from that SignalCalibration are considered while the corresponding values in the SignalCalibration on the LogicalSignal are ignored and left unmodified. If there are values in the SignalCalibration on the ExperimentSignal that are set to None, these values are not considered and the corresponding values in the baseline SignalCalibration on the corresponding LogicalSignal remain effective.
For example, the oscillator defined for a LogicalSignal can be overridden by an oscillator in the corresponding ExperimentSignals's SignalCalibration. If this SignalCalibration only defines an oscillator but leaves all other values to None, only this oscillator will override the baseline oscillator. All values set to None e.g. like the mixer calibration values will leave the baseline values effective.
Device Calibration¶
You'll start by importing LabOne Q, and defining a descriptor, here with an HDAWG, UHFQA, and PQSC.
# LabOne Q:
# pretty printing
from pprint import pprint
from laboneq.simple import *
descriptor = """\
instruments:
HDAWG:
- address: DEV1001
uid: device_hdawg
UHFQA:
- address: DEV2001
uid: device_uhfqa
PQSC:
- address: DEV3001
uid: device_pqsc
connections:
device_hdawg:
- iq_signal: q0/drive_line
ports: [SIGOUTS/0, SIGOUTS/1]
- to: device_uhfqa
port: DIOS/0
device_uhfqa:
- iq_signal: q0/measure_line
ports: [SIGOUTS/0, SIGOUTS/1]
- acquire_signal: q0/acquire_line
"""
Define Uncalibrated DeviceSetup¶
Using the descriptor, the DeviceSetup can be created. Initially, it is uncalibrated.
device_setup = DeviceSetup.from_descriptor(
descriptor,
server_host="111.22.33.44",
server_port="8004",
setup_name="ZI_QCCS",
)
List Calibratable Data Entities in the DeviceSetup¶
In order to get a list of all available data entities in a DeviceSetup that can be calibrated, the following function can be called. The result is a dict where key is the path to the data entity and value is a dict giving the type of the calibration item and a boolean whether there is a calibration set as a value.
device_setup.list_calibratables()
Calibrate DeviceSetup¶
Below, you'll use different methods to use calibrations, either by setting the calibration directly to the DeviceSetup or by creating a Calibration object.
Variant 1: Direct Calibration on Existing DeviceSetup Object¶
Assigning CalibrationItem objects to the calibration property of a Calibratable:
q0_signals = device_setup.logical_signal_groups["q0"].logical_signals
q0_signals["drive_line"].calibration = SignalCalibration(
oscillator=Oscillator(
uid="drive_osc", frequency=1e8, modulation_type=ModulationType.HARDWARE
),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.0, 0.0],
correction_matrix=[
[1.0, 0.0],
[0.0, 1.0],
],
),
# global and static delay of logical signal line: use to align pulses and compensate skew
port_delay=0, # applied to corresponding instrument node, bound to hardware limits
delay_signal=0, # inserted in sequencer code, bound to waveform granularity
)
q0_signals["measure_line"].calibration = SignalCalibration(
oscillator=Oscillator(
uid="measure_osc", frequency=1e8, modulation_type=ModulationType.SOFTWARE
),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.02, 0.01],
),
delay_signal=0,
)
q0_signals["acquire_line"].calibration = SignalCalibration(
oscillator=Oscillator(
uid="acquire_osc", frequency=1e8, modulation_type=ModulationType.HARDWARE
),
port_delay=0,
delay_signal=0,
)
Assign calibration using forwarded properties on a Calibratable object. This implicitly sets the CalibrationItem object on the calibration property if there is no CalibrationItem set yet. The forwarded properties on the Calibratable set the corresponding values in the CalibrationItem object assigned to the calibration property of the Calibratable object:
q0_signals = device_setup.logical_signal_groups["q0"].logical_signals
ls = q0_signals["drive_line"]
ls.oscillator = Oscillator(
uid="drive_osc", frequency=1e8, modulation_type=ModulationType.HARDWARE
)
ls.mixer_calibration = MixerCalibration(
voltage_offsets=[0.02, 0.05],
correction_matrix=[
[1.0, 0.03492077],
[0.0, 1.11178838],
],
)
ls = q0_signals["measure_line"]
ls.oscillator = Oscillator(
uid="measure_osc", frequency=1e8, modulation_type=ModulationType.SOFTWARE
)
ls.mixer_calibration = MixerCalibration(
voltage_offsets=[0.02, 0.05],
)
q0_signals["acquire_line"].oscillator = Oscillator(
uid="acquire_osc", frequency=1e8, modulation_type=ModulationType.HARDWARE
)
Variant 2: Define Calibration and Apply to Existing DeviceSetup Object¶
Define the calibration:
device_calib = Calibration()
device_calib["/logical_signal_groups/q0/drive_line"] = SignalCalibration(
oscillator=Oscillator(
uid="drive_osc", frequency=1e8, modulation_type=ModulationType.HARDWARE
),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.02, 0.05],
correction_matrix=[
[1.0, 0.03492077],
[0.0, 1.11178838],
],
),
)
device_calib["/logical_signal_groups/q0/measure_line"] = SignalCalibration(
oscillator=Oscillator(
uid="measure_osc", frequency=1e8, modulation_type=ModulationType.SOFTWARE
),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.02, 0.05],
),
)
device_calib["/logical_signal_groups/q0/acquire_line"] = SignalCalibration(
oscillator=Oscillator(
uid="acquire_osc", frequency=1e8, modulation_type=ModulationType.HARDWARE
)
)
Finally, apply calibration:
device_setup.set_calibration(device_calib)
Get, Set, and Reset Device Calibration¶
print("\nGet device calibration:")
device_calib = device_setup.get_calibration()
print(device_calib)
print("\nReset device calibration:")
device_setup.reset_calibration() # clear all calibration items
print(device_setup.get_calibration())
device_setup.reset_calibration(
device_calib
) # clear all calibration items and set given calibration
print("\nSet device calibration:")
device_setup.set_calibration(device_calib)
print(device_setup.get_calibration())
assert device_calib == device_setup.get_calibration()
Experiment Calibration¶
Define Uncalibrated and Unmapped Experiment¶
exp = Experiment(
uid="My experiment",
signals=[
ExperimentSignal("q0_drive"),
ExperimentSignal("q0_measure"),
ExperimentSignal("q0_acquire"),
],
)
# or with UIDs only
exp = Experiment(
uid="My experiment",
signals=[
"q0_drive",
"q0_measure",
"q0_acquire",
],
)
List Calibratable Data Entities in the Experiment¶
In order to get a list of all available data entities in an Experiment that can be calibrated, the following function can be called. The result is a dict where key is the path to the data entity and value is a dict giving the type of the calibration item and a boolean whether there is a calibration set as a value.
exp.list_calibratables()
Calibrate Experiment¶
Variant 1: Direct Calibration at Experiment Definition Time¶
The constructor of the ExperimentSignal accepts calibration values of an ExperimentSignalCalibration and forwards them internally when assigning a ExperimentSignalCalibration object to the calibration property:
exp = Experiment(
uid="My experiment",
signals=[
ExperimentSignal(
uid="q0_drive",
oscillator=Oscillator(frequency=1.0e6),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.02, 0.05],
correction_matrix=[
[1.0, 0.03492077],
[0.0, 1.11178838],
],
),
),
ExperimentSignal(
uid="q0_measure",
oscillator=Oscillator(frequency=1.0e6),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.02, 0.05],
),
),
ExperimentSignal(uid="q0_acquire", oscillator=Oscillator(frequency=3.0e6)),
],
)
Alternatively, the CalibrationItem can be given as an object:
exp = Experiment(
uid="My experiment",
signals=[
ExperimentSignal(
uid="q0_drive",
calibration=SignalCalibration(
oscillator=Oscillator(frequency=1.0e6),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.02, 0.05],
correction_matrix=[
[1.0, 0.03492077],
[0.0, 1.11178838],
],
),
),
),
ExperimentSignal(
uid="q0_measure",
calibration=SignalCalibration(
oscillator=Oscillator(frequency=2.0e6),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.02, 0.05],
),
),
),
ExperimentSignal(
uid="q0_acquire",
calibration=SignalCalibration(oscillator=Oscillator(frequency=3.0e6)),
),
],
)
Variant 2: Direct Calibration on Existing Experiment Object¶
Assigning CalibrationItem objects to the calibration property of a Calibratable:
exp.signals["q0_drive"].calibration = SignalCalibration(
oscillator=Oscillator(frequency=1.0e6),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.02, 0.05],
correction_matrix=[
[1.0, 0.03492077],
[0.0, 1.11178838],
],
),
)
exp.signals["q0_measure"].calibration = SignalCalibration(
oscillator=Oscillator(frequency=2.0e6),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.02, 0.05],
),
)
exp.signals["q0_acquire"].calibration = SignalCalibration(
oscillator=Oscillator(frequency=3.0e6)
)
Assign calibration using forwarded properties on a Calibratable object. This implicitly sets the CalibrationItem object on the calibration property if there is no CalibrationItem set yet. The forwarded properties on the Calibratable set the corresponding values in the CalibrationItem object assigned to the calibration property of the Calibratable object:
exp.reset_calibration()
es = exp.signals["q0_drive"]
es.oscillator = Oscillator(frequency=1.0e6)
es.mixer_calibration = MixerCalibration(
voltage_offsets=[0.02, 0.05],
correction_matrix=[
[1.0, 0.03492077],
[0.0, 1.11178838],
],
)
es = exp.signals["q0_measure"]
es.oscillator = Oscillator(frequency=2.0e6)
es.mixer_calibration = MixerCalibration(
voltage_offsets=[0.02, 0.05],
)
es = exp.signals["q0_acquire"]
es.oscillator = Oscillator(frequency=3.0e6)
Variant 3: Define Calibration and Apply to Existing Experiment Object¶
Define calibration:
exp_calib = Calibration()
exp_calib["q0_drive"] = SignalCalibration(
oscillator=Oscillator(frequency=1.0e6),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.02, 0.05],
correction_matrix=[
[1.0, 0.03492077],
[0.0, 1.11178838],
],
),
)
exp_calib["q0_measure"] = SignalCalibration(oscillator=Oscillator(frequency=2.0e6))
exp_calib["q0_acquire"] = SignalCalibration(oscillator=Oscillator(frequency=3.0e6))
Apply calibration:
exp.set_calibration(exp_calib)
Get, Set, and Reset Experiment Calibration¶
print("\nGet experiment calibration:")
exp_calib = exp.get_calibration()
print(exp_calib)
print("\nReset experiment calibration:")
exp.reset_calibration() # clear all calibration items
print(exp.get_calibration())
exp.reset_calibration(
exp_calib
) # clear all calibration items and set given calibration
print("\nSet experiment calibration:")
exp.set_calibration(exp_calib)
print(exp.get_calibration())
Modify Experiment Calibration on Experiment¶
Modifying the ExperimentSignalCalibration object assigned to the calibration property:
from copy import deepcopy
exp_calib_copy = deepcopy(exp.get_calibration())
exp.signals["q0_drive"].calibration.oscillator.frequency = 3.0
exp.signals["q0_drive"].calibration.voltage_offset = 0.2
assert exp_calib == exp.get_calibration() # we modified a reference above
assert exp_calib != exp_calib_copy
assert exp_calib_copy != exp.get_calibration()
exp.set_calibration(exp_calib)
assert exp_calib == exp.get_calibration()
Modifying forwarded properties on ExperimentSignalfrom the SignalCalibration. The forwarded properties set the corresponding values in the SignalCalibration object assigned to the calibration property of the ExperimentSignal object:
exp.signals["q0_drive"].oscillator.frequency = 1.0
exp.signals["q0_drive"]
Map Experiment Signals¶
Variant 1: Map Signals at Experiment Definition Time¶
exp_direct_mapping = Experiment(
uid="My experiment",
signals=[
ExperimentSignal(
"q0_drive",
map_to="/logical_signal_groups/q0/drive_line",
),
ExperimentSignal(
"q0_measure",
map_to="/logical_signal_groups/q0/measure_line",
),
ExperimentSignal(
"q0_acquire",
# it is also possible to reference the logical signal object:
map_to=device_setup.logical_signal_groups["q0"].logical_signals[
"acquire_line"
],
),
],
)
Variant 2: Direct Mapping on Existing Experiment Object¶
Map signals directly on experiment object by referencing the LogicalSignal object:
q0 = device_setup.logical_signal_groups["q0"]
exp.map_signal("q0_drive", q0.logical_signals["drive_line"])
exp.map_signal("q0_measure", q0.logical_signals["measure_line"])
exp.map_signal("q0_acquire", q0.logical_signals["acquire_line"])
or by path strings of a LogicalSignal:
exp.map_signal("q0_drive", "/logical_signal_groups/q0/drive_line")
exp.map_signal("q0_measure", "/logical_signal_groups/q0/measure_line")
exp.map_signal("q0_acquire", "/logical_signal_groups/q0/acquire_line")
Variant 3: Set Entire Signal Map as a Dictionary on Experiment Object¶
Use dictionary with experiment signal uid to logical signal path strings:
exp.set_signal_map(
{
"q0_drive": "/logical_signal_groups/q0/drive_line",
"q0_measure": "/logical_signal_groups/q0/measure_line",
"q0_acquire": "/logical_signal_groups/q0/acquire_line",
}
)
Use dict with experiment signal uid to LogicalSignal object:
q0 = device_setup.logical_signal_groups["q0"]
exp.set_signal_map(
{
"q0_drive": q0.logical_signals["drive_line"],
"q0_measure": q0.logical_signals["measure_line"],
"q0_acquire": q0.logical_signals["acquire_line"],
}
)
Alternatively, use the path property of a LogicalSignal object:
q0 = device_setup.logical_signal_groups["q0"]
exp.set_signal_map(
{
"q0_drive": q0.logical_signals["drive_line"].path,
"q0_measure": q0.logical_signals["measure_line"].path,
"q0_acquire": q0.logical_signals["acquire_line"].path,
}
)
Get, Set, and Reset Signal Mapping¶
print("\nGet signal map:")
signal_map = exp.get_signal_map()
pprint(signal_map)
pprint(exp.signal_mapping_status)
print("\nReset signal map:")
exp.reset_signal_map() # unmap all signals
pprint(exp.get_signal_map())
pprint(exp.signal_mapping_status)
assert exp.get_signal_map() != signal_map
exp.reset_signal_map(signal_map) # unmap all signals and set given mapping
assert exp.get_signal_map() == signal_map
print("\nSet original signal map again:")
exp.set_signal_map(signal_map)
pprint(exp.get_signal_map())
pprint(exp.signal_mapping_status)
assert exp.get_signal_map() == signal_map
print("\nSet signal map by logical signal UIDs:")
exp.reset_signal_map()
exp.set_signal_map(
{
"q0_drive": "/logical_signal_groups/q0/drive_line",
"q0_measure": "/logical_signal_groups/q0/measure_line",
"q0_acquire": "/logical_signal_groups/q0/acquire_line",
}
)
pprint(exp.get_signal_map())
pprint(exp.signal_mapping_status)
print("\nSet signal map by logical signal objects:")
exp.reset_signal_map()
q0 = device_setup.logical_signal_groups["q0"]
exp.set_signal_map(
{
"q0_drive": q0.logical_signals["drive_line"],
"q0_measure": q0.logical_signals["measure_line"],
"q0_acquire": q0.logical_signals["acquire_line"],
}
)
pprint(exp.get_signal_map())
pprint(exp.signal_mapping_status)
Serialization of Calibration¶
device_calib = device_setup.get_calibration()
save(device_calib, "my_device_calib")
device_calib_loaded = load("my_device_calib")
assert device_calib == device_calib_loaded
exp_calib = exp.get_calibration()
save(exp_calib, "my_exp_calib.json")
exp_calib_loaded = load("my_exp_calib.json")
assert exp_calib == exp_calib_loaded