This page was generated from docs/examples/driver_examples/QCoDeS example with DelegateInstrument.ipynb. Interactive online version: Binder badge.

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: >
../../_images/examples_driver_examples_QCoDeS_example_with_DelegateInstrument_15_1.png

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]