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.

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.tests.instrument_mocks.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
initial_values:
ramp_rate: 1.0
setters:
X:
method: field_X.set_field
block: false

Writing example.yaml

[2]:

import qcodes as qc

[3]:

station = qc.Station(config_file="example.yaml")

[4]:

field_X = station.load_field_X()

[5]:

field.X()

[5]:

0.0

[6]:

field.X(1.)

[7]:

field.X()

[7]:

8.742014567057292e-05

[8]:

field.X()

[8]:

0.00020779371261596679

[9]:

field.X()

[9]:

0.0003273169199625651

[10]:

field.X()

[10]:

0.00044972896575927733


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.)
field_X.field(0.0)

[12]:

field.X()

[12]:

0.0

[13]:

import time
meas = qc.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:
time.sleep(0.01)
datasaver.flush_data_to_database()

Starting experimental run with id: 60.

[14]:

datasaver.dataset.to_pandas_dataframe().plot()

[14]:

<AxesSubplot:>


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. * 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]:

6.0

[19]:

field.X(0.1)

[20]:

field.ramp_time()

[20]:

5.994708061218262

[21]:

import time
time.sleep(1.)
field.ramp_time()

[21]:

4.986382484436035

[22]:

import time
time.sleep(1.)
field.ramp_time()

[22]:

3.978024482727051


# 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.tests.instrument_mocks.MockLockin

dac:
type: qcodes.tests.instrument_mocks.MockDAC

device:
type: qcodes.instrument.delegate.DelegateInstrument
init:
parameters:
channels:
gate_1: dac.ch01
initial_values:
gate_1.voltage.post_delay: 0.01

Overwriting example.yaml

[24]:

station = qc.Station(config_file="example.yaml")

[25]:

lockin = station.load_lockin()

[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.tests.instrument_mocks.MockLockin

dac:
type: qcodes.tests.instrument_mocks.MockDAC

device:
type: qcodes.instrument.delegate.DelegateInstrument
init:
parameters:
channels:
type: qcodes.tests.instrument_mocks.MockCustomChannel
gate_1:
channel: dac.ch01
current_valid_range: [-0.5, 0]
initial_values:

Overwriting example.yaml

[29]:

lockin.close()
dac.close()

[30]:

station = qc.Station(config_file="example.yaml")

[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]