This page was generated from docs/examples/driver_examples/QCoDeS example with DelegateInstrument.ipynb. Interactive online version: .
Qcodes example with DelegateInstrument driver¶
This notebooks explains how to use the DelegateInstrument
driver.
About¶
The goal of the DelegateInstrument
driver is to make it easier to combine different parameters together into a new “virtual” instrument. Each parameter on a DelegateInstrument
can point to one or more parameters on other instruments in the station.
Usage¶
The way it’s used is mainly by specifying an entry in the station YAML. For instance, let’s say you want to use a magnetic field coil. The driver has a method set_field(value, block), that by default is set to block=True, which means the field is ramped in a way that blocks further execution until the desired value is reached. However, let’s say you are creating a measurement in which you want the parameter to be set, and while the value is ramping, you want to measure other parameters. This can
be done by using DelegateInstrument
and specifying a custom setter
for the parameter that gets and sets the magnetic field.
By default, each parameter is represented by a DelegateParameter
. The DelegateInstrument
also supports passing multiple source parameters to a given parameter. In order to do this, simply specify multiple parameters in the dictionary values under the parameters
key.
It can also add instrument channels, specified under a separate key channels
, shown in the second half of the notebook.
[1]:
%%writefile example.yaml
instruments:
field_X:
type: qcodes.instrument_drivers.mock_instruments.MockField
field:
type: qcodes.instrument.delegate.DelegateInstrument
init:
parameters:
X:
- field_X.field
ramp_rate:
- field_X.ramp_rate
combined:
- field_X.field
- field_X.ramp_rate
set_initial_values_on_load: true
initial_values:
ramp_rate: 1.0
setters:
X:
method: field_X.set_field
block: false
Writing example.yaml
[2]:
import qcodes as qc
from qcodes.dataset import (
Measurement,
initialise_or_create_database_at,
load_or_create_experiment,
)
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/241118-9133-qcodes.log
[3]:
station = qc.Station(config_file="example.yaml")
[4]:
field_X = station.load_field_X()
field = station.load_field(station=station)
[5]:
field.X()
[5]:
0.0
[6]:
field.X(1.0)
[7]:
field.X()
[7]:
6.685654322306316e-05
[8]:
field.X()
[8]:
0.0001501917839050293
[9]:
field.X()
[9]:
0.00023767550786336263
[10]:
field.X()
[10]:
0.00032152732213338214
As you can see, the field is now ramped in the background with the specified ramp rate. Now, let’s try to create a measurement that uses this ability, and ramps the field in the background while measuring:
[11]:
field.ramp_rate(10.0)
field_X.field(0.0)
[12]:
field.X()
[12]:
0.0
[13]:
import time
initialise_or_create_database_at("delegate_instrument_example.db")
load_or_create_experiment("delegate_instrument_experiment")
meas = Measurement(station=station)
meas.register_parameter(field.X)
with meas.run() as datasaver:
for B in [0.1, 0.0]:
field.X(B)
while field.X() != B:
datasaver.add_result((field.X, field.X()))
time.sleep(0.01)
datasaver.flush_data_to_database()
Starting experimental run with id: 1.
[14]:
datasaver.dataset.to_pandas_dataframe().plot()
[14]:
<Axes: >
When specifying multiple source parameters on a given parameter, the grouped parameter will automatically return a namedtuple
that returns both values.
[15]:
field.combined()
[15]:
combined(field=0.0, ramp_rate=10.0)
We can now also create a custom parameter that does a simple calculation based on the current parameters.
[16]:
import numpy as np
def calculate_ramp_time(X, ramp_rate):
"""Calculate ramp time in seconds"""
dfield = np.abs(field.target_field - X)
return 60.0 * dfield / ramp_rate
[17]:
field._create_and_add_parameter(
group_name="ramp_time",
station=station,
paths=["field_X.field", "field_X.ramp_rate"],
formatter=calculate_ramp_time,
)
[18]:
field.ramp_rate(1.0)
field.target_field = 0.1
field.ramp_time()
[18]:
np.float64(6.0)
[19]:
field.X(0.1)
[20]:
field.ramp_time()
[20]:
np.float64(5.9959728717803955)
[21]:
import time
time.sleep(1.0)
field.ramp_time()
[21]:
np.float64(4.9905736446380615)
[22]:
import time
time.sleep(1.0)
field.ramp_time()
[22]:
np.float64(3.9847424030303955)
Devices with channels¶
The YAML file below specifies the instruments with the channels/parameters we wish to group into a new instrument, here called “device”. The first example simply adds the channel ‘as is’ using self.add_submodule, while the readout parameter is added as a DelegateParameter.
[23]:
%%writefile example.yaml
instruments:
lockin:
type: qcodes.instrument_drivers.mock_instruments.MockLockin
dac:
type: qcodes.instrument_drivers.mock_instruments.MockDAC
device:
type: qcodes.instrument.delegate.DelegateInstrument
init:
parameters:
readout: lockin.X
channels:
gate_1: dac.ch01
set_initial_values_on_load: true
initial_values:
readout: 1e-5
gate_1.voltage.post_delay: 0.01
Overwriting example.yaml
[24]:
station = qc.Station(config_file="example.yaml")
[25]:
lockin = station.load_lockin()
dac = station.load_dac()
device = station.load_device(station=station)
[26]:
print(device.gate_1)
print(device.gate_1.voltage.post_delay)
<MockDACChannel: dac_ch01 of MockDAC: dac>
0.01
[27]:
print(device.gate_1.voltage())
device.gate_1.voltage(-0.6)
device.gate_1.voltage()
0.0
[27]:
-0.6
The second example adds a channel using a custom channel class, which takes the initial channel and its name as input and has a parameter current_valid_ranges.
[28]:
%%writefile example.yaml
instruments:
lockin:
type: qcodes.instrument_drivers.mock_instruments.MockLockin
dac:
type: qcodes.instrument_drivers.mock_instruments.MockDAC
device:
type: qcodes.instrument.delegate.DelegateInstrument
init:
parameters:
readout: lockin.X
channels:
type: qcodes.instrument_drivers.mock_instruments.MockCustomChannel
gate_1:
channel: dac.ch01
current_valid_range: [-0.5, 0]
set_initial_values_on_load: true
initial_values:
readout: 1e-5
Overwriting example.yaml
[29]:
lockin.close()
dac.close()
[30]:
station = qc.Station(config_file="example.yaml")
lockin = station.load_lockin()
dac = station.load_dac()
[31]:
device = station.load_device(station=station)
[32]:
device.gate_1
[32]:
<MockCustomChannel: dac_gate_1 of MockDAC: dac>
[33]:
device.gate_1.voltage(-0.3)
[34]:
device.gate_1.voltage()
[34]:
-0.3
The MockCustomChannel has a parameter current_valid_range
.
[35]:
device.gate_1.current_valid_range()
[35]:
[-0.5, 0]