QCoDeS tutorial¶
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¶
[1]:
# 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¶
[2]:
# 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)
[3]:
# 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¶
[4]:
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.
[5]:
# 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()
[5]:
8
Setting IMMEDIATELY changes a value. For voltages, that is sometimes undesired. The value can instead be ramped by stepping and waiting.
[6]:
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: 1. You describe what parameter(s) to vary and how. This is the creation of a Loop
object: loop = Loop(sweep_values, ...)
2. You describe what to do at each step in the loop. This is loop.each(*actions)
- measurements (any object with a .get
method 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
specialized Task
just to wait a certain time. - BreakIf
: some condition that, if it returns truthy, breaks (this level of) the loop
[16]:
# 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
[9]:
# The plot may be recalled easily
plot_1d
[9]:
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.
[10]:
dac.snapshot()
[10]:
{'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.
[11]:
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.
[12]:
location = data.location
loaded_data = load_data(location)
plot = MatPlot(loaded_data.dmm_voltage)
Example: multiple 2D measurements with live plotting¶
[13]:
# 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')
[14]:
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
[15]:
plot
[15]: