QCoDeS tutorial — qcodes_loop documentation
Basic overview of QCoDeS
Table of Contents¶
Typical QCodes workflow¶
(back to ToC)
Start up an interactive python session (e.g. using jupyter)
import desired modules
instantiate required instruments
experiment!
Importing¶
# usually, one imports QCoDeS and some instruments # In this tutorial, we import the dummy instrument from qcodes.instrument_drivers.mock_instruments import DummyInstrument from qcodes.station import Station import qcodes_loop.data import qcodes_loop.data.data_set from qcodes_loop.data.data_set import load_data from qcodes_loop.loops import Loop from qcodes_loop.plots.pyqtgraph import QtPlot from qcodes_loop.plots.qcmatplotlib import MatPlot # real instruments are imported in a similar way, e.g. # from qcodes.instrument_drivers.Keysight.KeysightAgilent_33XXX import WaveformGenerator_33XXX
Logging hadn't been started. Activating auto-logging. Current session state plus future input saved. Filename : C:\Users\a-halakh\.qcodes\logs\command_history.log Mode : append Output logging : True Raw input log : False Timestamping : True State : active Qcodes Logfile : C:\Users\a-halakh\.qcodes\logs\200325-16084-qcodes.log
Instantiation of instruments¶
# It is not enough to import the instruments, they must also be instantiated # Note that this can only be done once. If you try to re-instantiate an existing instrument, QCoDeS will # complain that 'Another instrument has the name'. # In this turotial, we consider a simple situation: A single DAC outputting voltages to a Digital Multi Meter dac = DummyInstrument(name="dac", gates=['ch1', 'ch2']) # The DAC voltage source dmm = DummyInstrument(name="dmm", gates=['voltage']) # The DMM voltage reader # the default dummy instrument returns always a constant value, in the following line we make it random # just for the looks 💅 import random dmm.voltage.get = lambda: random.randint(0, 100) # Finally, the instruments should be bound to a Station. Only instruments bound to the Station get recorded in the # measurement metadata, so your metadata is blind to any instrument not in the Station. station = Station(dac, dmm)
# For the tutorial, we add a parameter that loudly prints what it is being set to # (It is used below) chX = 0 def myget(): return chX def myset(x): global chX chX = x print(f'Setting to {x}') return None dac.add_parameter('verbose_channel', label='Verbose Channel', unit='V', get_cmd=myget, set_cmd=myset)
The location provider can be set globally¶
loc_provider = qcodes_loop.data.location.FormatLocation(fmt='data/{date}/#{counter}_{name}_{time}') qcodes_loop.data.data_set.DataSet.location_provider=loc_provider
We are now ready to play with the instruments!
Basic instrument interaction¶
(back to ToC)
The interaction with instruments mainly consists of setting and getting the instruments’ parameters. A parameter can be anything from the frequency of a signal generator over the output impedance of an AWG to the traces from a lock-in amplifier. In this tutorial we –for didactical reasons– only consider scalar parameters.
# The voltages output by the dac can be set like so dac.ch1.set(8) # Now the output is 8 V. We can read this value back dac.ch1.get()
Setting IMMEDIATELY changes a value. For voltages, that is sometimes undesired. The value can instead be ramped by stepping and waiting.
dac.verbose_channel.set(0) dac.verbose_channel.set(9) # immediate voltage jump of 9 Volts (!) # first set a step size dac.verbose_channel.step = 0.1 # and a wait time dac.verbose_channel.inter_delay = 0.01 # in seconds # now a "staircase ramp" is performed by setting dac.verbose_channel.set(5) # after such a ramp, it is a good idea to reset the step and delay dac.verbose_channel.step = 0 # and a wait time dac.verbose_channel.inter_delay = 0
Setting to 0 Setting to 9 Setting to 8.9 Setting to 8.8 Setting to 8.7 Setting to 8.6 Setting to 8.5 Setting to 8.4 Setting to 8.3 Setting to 8.2 Setting to 8.1 Setting to 8.0 Setting to 7.9 Setting to 7.8 Setting to 7.7 Setting to 7.6 Setting to 7.5 Setting to 7.4 Setting to 7.3 Setting to 7.2 Setting to 7.1 Setting to 7.0 Setting to 6.9 Setting to 6.8 Setting to 6.699999999999999 Setting to 6.6 Setting to 6.5 Setting to 6.4 Setting to 6.3 Setting to 6.199999999999999 Setting to 6.1 Setting to 6.0 Setting to 5.9 Setting to 5.8 Setting to 5.699999999999999 Setting to 5.6 Setting to 5.5 Setting to 5.4 Setting to 5.3 Setting to 5.199999999999999 Setting to 5.1 Setting to 5
NOTE: that is ramp is blocking and has a low resolution since each set on a real instrument has a latency on the order of ms. Some instrument drivers support native-resolution asynchronous ramping. Always refer to your instrument driver if you need high performance of an instrument.
Measuring¶
(back to ToC)
1D Loop example¶
Defining the Loop and actions¶
Before you run a measurement loop you do two things:
You describe what parameter(s) to vary and how. This is the creation of a
Loopobject:loop = Loop(sweep_values, ...)You describe what to do at each step in the loop. This is
loop.each(*actions)
measurements (any object with a
.getmethod will be interpreted as a measurement)Task: some callable (which can have arguments with it) to be executed each time through the loop. Does not generate data.Wait: a specializedTaskjust to wait a certain time.BreakIf: some condition that, if it returns truthy, breaks (this level of) the loop
# For instance, sweep a dac voltage and record with the dmm loop = Loop(dac.ch1.sweep(0, 20, 0.1), delay=0.001).each(dmm.voltage) data = loop.get_data_set(name='testsweep')
plot_1d = QtPlot() # create a plot plot_1d.add(data.dmm_voltage) # add a graph to the plot _ = loop.with_bg_task(plot_1d.update, plot_1d.save).run() # run the loop
# The plot may be recalled easily plot_1d
Output of the loop¶
A loop returns a dataset.
The representation of the dataset shows what arrays it contains and where it is saved.
The dataset initially starts out empty (filled with NAN’s) and get’s filled while the Loop get’s executed.
Once the measurement is done, take a look at the file in finder/explorer (the dataset.location should give you the relative path). Note also the snapshot that captures the settings of all instruments at the start of the Loop. This metadata is also accesible from the dataset and captures a snapshot of each instrument listed in the station.
{'functions': {},
'submodules': {},
'__class__': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',
'parameters': {'IDN': {'value': {'vendor': None,
'model': 'dac',
'serial': None,
'firmware': None},
'raw_value': {'vendor': None,
'model': 'dac',
'serial': None,
'firmware': None},
'ts': '2020-03-25 12:33:24',
'__class__': 'qcodes.instrument.parameter.Parameter',
'full_name': 'dac_IDN',
'unit': '',
'label': 'IDN',
'vals': '<Anything>',
'post_delay': 0,
'name': 'IDN',
'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',
'instrument_name': 'dac',
'inter_delay': 0},
'ch1': {'value': 20.0,
'raw_value': 20.0,
'ts': '2020-03-25 12:33:44',
'__class__': 'qcodes.instrument.parameter.Parameter',
'full_name': 'dac_ch1',
'unit': 'V',
'label': 'Gate ch1',
'vals': '<Numbers -800<=v<=400>',
'post_delay': 0,
'name': 'ch1',
'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',
'instrument_name': 'dac',
'inter_delay': 0},
'ch2': {'value': 0,
'raw_value': 0,
'ts': '2020-03-25 12:33:24',
'__class__': 'qcodes.instrument.parameter.Parameter',
'full_name': 'dac_ch2',
'unit': 'V',
'label': 'Gate ch2',
'vals': '<Numbers -800<=v<=400>',
'post_delay': 0,
'name': 'ch2',
'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',
'instrument_name': 'dac',
'inter_delay': 0},
'verbose_channel': {'value': 5,
'raw_value': 5,
'ts': '2020-03-25 12:33:24',
'__class__': 'qcodes.instrument.parameter.Parameter',
'full_name': 'dac_verbose_channel',
'unit': 'V',
'label': 'Verbose Channel',
'post_delay': 0,
'name': 'verbose_channel',
'instrument': 'qcodes.instrument_drivers.mock_instruments.DummyInstrument',
'instrument_name': 'dac',
'inter_delay': 0}},
'name': 'dac'}
There is also a more human-readable version of the essential information.
dac.print_readable_snapshot()
dac:
parameter value
--------------------------------------------------------------------------------
IDN : {'vendor': None, 'model': 'dac', 'serial': None, 'firmware'...
ch1 : 20 (V)
ch2 : 0 (V)
verbose_channel : 5 (V)
Loading data¶
The dataset knows its own location, which we may use to load data.
location = data.location loaded_data = load_data(location) plot = MatPlot(loaded_data.dmm_voltage)
Example: multiple 2D measurements with live plotting¶
# Loops can be nested, so that a new sweep runs for each point in the outer loop loop = Loop(dac.ch1.sweep(0, 5, 1), 0.1).loop(dac.ch2.sweep(0, 5, 1), 0.1).each( dmm.voltage ) data = loop.get_data_set(name='2D_test')
plot = QtPlot() plot.add(data.dmm_voltage, figsize=(1200, 500)) _ = loop.with_bg_task(plot.update, plot.save).run()
Started at 2020-03-25 12:33:46 DataSet: location = 'data/2020-03-25/#014_2D_test_12-33-46' <Type> | <array_id> | <array.name> | <array.shape> Setpoint | dac_ch1_set | ch1 | (6,) Setpoint | dac_ch2_set | ch2 | (6, 6) Measured | dmm_voltage | voltage | (6, 6) Finished at 2020-03-25 12:33:51