Advanced use of Loop
Throughout, we have used the loop1d
and loop2d
functions to create loops for 1D and 2D measurements. However, you can also create loops manually using the qc.Loop
class, which gives you more control over the measurement process and allows for e.g. multidimensional loops, non-linear sweeps, etc.
The general scheme is:
create a (potentially nested)
qc.Loop
, which defines the sweep setpoints and delaysactivate the
Loop
(which changes it to anActiveLoop
object), by attaching one or more actions to it, using the .each method. Actions can be: parameters to measure, tasks to run, waits, or other loops.Associate the
ActiveLoop
with aDataSetPP
, which will hold the data collected, using theget_data_set
method.Run the
ActiveLoop
with the.run
method, which additionally can be passed parameters to be plotted usinglive_plot
.
The most basic examples, which are equivalent to the loop1d
and loop2d
functions, are:
1D loop, specifying parameters to measure and plot
loop=qc.Loop(sweep_parameter.sweep(0,1,num=101), delay=0.1).each(measure_param1, measure_param2)
data=loop.get_data_set(name='My 1D loop')
loop.run([measure_param1, measure_param2])
2D loop, using station.set_measurement to set the default measurement
station.set_measurement(measure_param1, measure_param2)
loop=qc.Loop(sweep_parameter1.sweep(0,1,num=11), delay=0.1).loop(sweep_parameter2.sweep(-1,0,num=101),delay=0.1).each(*station.measure())
data=loop.get_data_set(name='My 2D loop')
loop.run([measure_param1, measure_param2])
2D (and higher dimension) loops can (also) be constructed by nesting loops in .each, e.g.
loop1d=qc.Loop(sweep_parameter2.sweep(-1,0,num=101), delay=0.1).each(*station.measure())
loop=qc.Loop(sweep_parameter1.sweep(0,1,num=11), delay=0.1).each(loop1d)
data=loop.get_data_set(name='My nested 2D loop')
loop.run([measure_param1, measure_param2])
Some examples: Sweeping the inner loop up and down many times at each point of the outer loop:
loopup=qc.Loop(instrument.output1.sweep(-1,0,num=101), delay=0.1).each(*station.measure())
loopdown=qc.Loop(instrument.output1.sweep(0,-1,num=101), delay=0.1).each(*station.measure())
actions=[]
for i in range(10):
actions.append(loopup)
actions.append(loopdown)
loop=qc.Loop(instrument.output2.sweep(0,1,num=11), delay=0.1).each(*actions)
A 3d loop would look something like the below:
loop1d=qc.Loop(sweep_parameter3.sweep(0,1,num=11), delay=0.1).each(*station.measure())
loop2d=qc.Loop(sweep_parameter2.sweep(-1,0,num=101), delay=0.1).each(loop3d)
loop=qc.Loop(sweep_parameter1.sweep(0,1,num=11), delay=0.1).each(loop2d)
data=loop.get_data_set(name='My 3D loop')
loop.run()
The data generated by the last example is unfortunately beyond the bounds of both live_plot
and offline_plotting
, which both only can interpret 1D or 2D rectangular arrays, so you will have to plot the data manually after the measurement is done. Usually I would recommend to actually embed a series of 2d Loop
s inside a python loop, and create a series of 2D DataSetPP
s, which can then be plotted together using live_plot
. Let’s pretend we want to checkout how the lock in frequency affects the conductance of a device in a series of 2d loops.
station.set_measurement(lockin.input1,lockin.input2,current,conductance)
pp=qc.live_plot()
lockin_freqs=[33,73,133,223,228]
for i, freq in enumerate(lockin_freqs):
lockin.frequency(freq)
loop=qc.Loop(voltage_source.output1.sweep(-1,1,num=101),delay=0.03).each(*station.measure())
outerloop=qc.Loop(voltage_source.output2.sweep(-0.5,-0.3,num=21),delay=0.1).each(loop)
data=outerloop.get_data_set(name=f'Sample XYZ Vout1(-1 1)V Vout2(-0.5 -0.3) freq={freq}')
data.publisher=pp
pp.add(data.conductance,subplot=i)
outerloop.run()
or, completely equivalently, but using the built-in qc.loop2d
to help automatically name the data files:
station.set_measurement(lockin.input1,lockin.input2,current,conductance)
pp=qc.live_plot()
lockin_freqs=[33,73,133,223,228]
for i, freq in enumerate(lockin_freqs):
lockin.frequency(freq)
loop=qc.loop2d(sweep_parameter=voltage_source.output1
start=-1,stop=1,num=101,delay=0.01,
step_parameter=voltage_source.output2
step_start=-0.5,step_stop=-0.3,step_num=21,step_delay=0.03,
device_info='Sample XYZ', instrument_info=f'freq={freq}Hz')
loop.data_set.publisher=pp
pp.add(loop.data_set.conductance,subplot=i)
loop.run()
Sweep types
The setpoints of each Loop
have so far been generated by the parameter.sweep
method, which generates a linear list of values. There are three more sweep types. Firstly, returnsweep
offers the possibility to return to the start value once the stop value is reached.
loopdownup=qc.Loop(keithley.volt.returnloop(0,1,num=101),delay=0.1).each(*station.measure())
The above will return a loop with 201 steps, as the stop value is not repeated.
Secondly, there is logsweep
, which uses numpy’s geomspace to generate points evenly spaced on a log scale (useful for things such as frequency and in some cases temperature).
freqloop=qc.Loop(lockin.frequency.logsweep(0.01,1000,num=1001),delay=0.1).each(*station.measure())
For everything else, there is arbsweep
, where you can pass an arbitrary list of values for the instrument to sweep. For example:
keithley.volt.arbsweep([0,0.1,0.5,0.66,0.45,0.456,1.11])
keithley.volt.arbsweep([i**2 for i in range(101)])
Another usecase for arbsweep
could be sweeps with regions of low and high data point densities. For example if you know the location of narrow peaks, you might want to create a list with a high density of data points around those points, while moving quickly between them.