Giter Club home page Giter Club logo

Comments (16)

MerlinSmiles avatar MerlinSmiles commented on July 17, 2024

I haven't followed the previous discussion so much, but in terms of the syntax, or how as a User I'd like to do things, I do have a few comments.

I see a sweep as more than just
measurement.sweep(B[-6:6:0.1],60)
I can imagine a more intelligent sweep which in many situations would look more complicated
measurement.sweep(B[-6:6:0.1],do_before_step,do_after_step,pause_for_x_in_case_of_T)
For a multidimensional sweep this would be unreadable, as @guenp mentions, some of us like to have a readable and more flexible version to work with.

also the nested measurements can do much more
measurement.sweep(B[-6:6:0.1],60).list(m.monitor(60),m.sweep(A[0:42:1]),m.monitor(60),m.set_async(A[0])).run()
i.e. for each step in B a list of stuff would be excecuted, ie. monitor for a while, sweep A, monitor again, set A to zero and continue with B...

just a few thoughts :)

from qcodes.

alexcjohnson avatar alexcjohnson commented on July 17, 2024

@MerlinSmiles good point about potentially doing multiple measurements in sequence (which may have different dimensionality, and certainly different size) during an inner loop. That's certainly not going to work well with the syntax as it stands now. It also shows that the whole concept of the sweep being a method of a MeasurementSet is limiting - a single task can involve several different MeasurementSets.

But I also don't think we want to create a lot of methods within whatever object is constructing the sweep that all amount to either "do something repeatedly" or "at each point, do X and then..." - that would require the sweep constructor to know about everything we might want to do during a sweep. Much better I would think to have objects that are arguments to a couple of basic method calls, so that users can make new objects, as long as they conform to the API we define, to accomplish any specialized task.

I'm coming around on having to end the call with .run(). I actually don't want to encourage recycling sweeps, I think it makes the experiment record harder to understand (because it's less explicit) and encourages less purposeful and in certain cases even more error-prone measurement. But it does have its place, and certainly makes composing complicated procedures more flexible.

@guenp would calling it a loop or something instead of a sweep make it more palatable to think of monitoring over time in the same framework as stepping a parameter? It still needs a number of points and a delay time, not just a total duration.

So what about a syntax like:

Loop(c0[1:3:0.1], 0.2).measure(measurement).run()  # simple 1D sweep

Loop(B[-6:6:0.1], 60).loop(c0[1:3:0.1], 0.2).measure(measurement).run()  # simple 2D sweep

Loop(B[-6:6:0.1], 60).each(  # do a sequence of things at each outer step
    measurement1,  # a MeasurementSet to measure just once per outer step
    Loop(repeat(100), 1).measure(measurement2),  # 100 measurements, 1 sec each (time sweep)
    Task(c1.set, 3),  # some way to provide a function with args and kwargs but not run it yet
    Loop(c0[1:3:0.1], 0.2).measure(measurement2),
    Task(c0.set, 2),
    Loop(c1[2:4:0.1], 0.2).measure(measurement2) ).run()

from qcodes.

alexcjohnson avatar alexcjohnson commented on July 17, 2024

@AdriaanRol points out in #5 the use case of acquiring data in 1D slices rather than point-by-point. While I'm sure we could shoehorn this into a Loop().measure() syntax, with all the action happening in the initialization and the results just regurgitated by measurement.get, this would be ugly.

We could also let a MeasurementSet parameter support array results for .get - but that would make the rest of the code much more complicated, as the dimensionality would be ill-defined.

Seems like another class is in order, whose API looks like an already measured loop.
Something like:

# just get this array as a 1D data set
GetArray(fn_that_returns_sequence, name, length).run()

# and inside another loop to make a 2D result
Loop(B[-6:6:0.1], 60).each(
    GetArray(...) ).run()

from qcodes.

MerlinSmiles avatar MerlinSmiles commented on July 17, 2024

@alexcjohnson i like that syntax better, it looks much more flexible and there are several situations I would have used this already.
Loop(B[-6:6:0.1], 60).each( # do a sequence of things at each outer step Wave(k1, [1:100:10000]), # upload a wave to instrument, or just select a saved wave Loop(repeat(100), 1).measure(measurement2), # measure something while wave is running GetArray(DMM1,trig), # get data from instrument triggered by the wave instrument GetArray(DMM2,trig), # and from a second instrument that is also triggered GetArray(DMM3,1000,0.1), # and from a third instrument that is not triggered but just gets x datapoints in y time ).run()
now there is somehow a data-mix from a traditional loop and arrays from instruments, these somehow need to end up in my data storage thing in the right order / timestamp so that I can plot them against eachother

from qcodes.

guenp avatar guenp commented on July 17, 2024

@alexcjohnson I agree with @MerlinSmiles, this syntax looks much nicer & more flexible! I like the .each syntax - it's very intuitive. Also - I like the idea of adding a 'runnable' measurement class in the example you give for GetArray in which users can define their own measurement, e.g. finding the qubit resonance.
I still do like the .run() command. I agree with you it doesn't look as nice as running a one-line method, but it does give me the flexibility to create my own measurement runs and re-run them which could de-clutter the code a lot. I'll give an example below.

@AdriaanRol what do you think? Could you give a pseudocode example of a measurement you'd do and see if this syntax would work for you?

e.g. here's a measurement I'd do for scanning SQUID:

measurement = station.create_measurement(squid['susceptibility'])
ImageScan = Do( #? same as loop, but just one step, so I can stack tasks into a runnable object
     Task(Xstage.set, -100),
     Task(Ystage.set, -100),
     Loop(Xstage[-100,100,1])
       .loop(Ystage[-100,100,1])
       .measure(measurement))

# Temperature dependence
ApproachSample(settle=60).run()
Loop(T[1:10e-3:-10e-3], 2*60).each(
     ImageScan).run()
WithdrawSample().run()

Would that make sense?

from qcodes.

guenp avatar guenp commented on July 17, 2024

edit: hit enter too soon 😲
Just had a short discussion - for scanning SQUID, we need to measure the sample inclination as well, and instead of setting the piezos individually we apply a ramp on the niDAQ channel, and sample the signal.
So in pseudocode:

measurement = station.create_measurement(GetWaveform(squid['susceptibility'],samplerate=1e5))
ImageScan = Do(Task(Xstage.set, -100), Task(YStage.set,-100), 
SetWaveform(Xstage, 'waveform_X.wfm'),
SetWaveform(Ystage, 'waveform_Y.wfm')
Loop(Xstage[-100,100,1],Ystage[-100,100,1]).each(
     RunWaveform(Xstage),
     RunWaveform(Ystage))
     .measure(measurement)

xslope, yslope = measureSamplePlane().run() #touchdown on 9 different places on chip, get sample plane
ApproachSample().run() #move XY stage to sample and touchdown
ImageScan(xslope,yslope).run()
WithdrawSample().run()

from qcodes.

AdriaanRol avatar AdriaanRol commented on July 17, 2024

@guenp I can give you some examples of the loops we currently use and why we chose for the different functions.
We use a custom MeasurementControl instrument that we give the variable name MC.
@alexcjohnson I'll also try to point out how we deal with different sized arrays that get returned and how we deal with adaptive measurements (e.g. numerically optimizing).

Comment after rereading my comment, it seems I am utterly incapable of writing short posts. I hope the examples of how we constructed our measurement loop can be useful for coming up with the best possible syntax for the new measurement flow.

Let me know what you guys think, I think there are some interesting use cases in my examples in addition to some clear areas where our syntax and structure can be improved.

Measurement Control

To run our experiments we have created a Measurement Control instrument in QTLab.
The measurement control is an instrument (object) to which we attach sweep and detector objects with the set_sweep_function and set_detector_function functions. The measurement control then takes care of looping over the sweep points and repeatedly calling the sweep and detector functions. By having them as objects with predefined functions we can execute any code we want inside the sweep and detector functions.

In the example below both sweep and detector functions come from libraries that we created.

1. The 1D sweep trivial example

MC.set_sweep_function(swf.Source_frequency_GHz(source=source))
MC.set_sweep_points(np.arange(freq_start,freq_stop,freq_step))
MC.set_detector_function(det.HomodyneDetector())
MC.run()
  • Sweep function
    • init specifies the name and units of our sweep and in this example also on which instrument to sweep this parameter
    • In the function itself there is a prepare(), set() and finish() statement, In the case of a simple dac sweep most of these will contain a pass but it can be very useful to for example turn on and off sources
  • sweep_points(), by taking in an array of values it is more versatile than a predifined syntax that behaves like np.arange or np.linspace, a concrete use case would be if you want to scan certain regions finely and others more coarsely.
    • init specifies the names and units of values that the detector will measure along with any other required parameters,
    • contains a prepare, get_value and finish statement
  • detector function, specifies how to measure the parameter of interest or multiple parameters. In this example the Homodyne detector returns both the I and Q value.
    • init specifies value_names and units
    • contains prepare, get_value and finish statement
  • .run() starts the experiment, takes optional arguments
    • name: specifies the savename for the datafile
    • mode: 2D -> i'll explain 2D below

2. The 2D sweep trivial example

MC.set_sweep_functions([swf.Source_frequency_GHz(source=source),
                                          swf.Source_power_dBm(source=source),])
MC.set_sweep_points(np.arange(freq_start,freq_stop,freq_step))
MC.set_sweep_points_2D(np.arange(start_power, stop_power, power_step))
MC.set_detector_function(det.HomodyneDetector())
MC.run(mode='2D')

Same example as above, mode=2D takes care reshaping the sweep points such that it has an inner and outer loop.
It is also possible to specify any n-dimensional sweep by handing a list of sweep functions to the set_sweep_functions command as long as the shape of the sweep points is consistent with it. (n*m array where n == len(sweep_functions) and m the total number of points to measure)

3. Adaptive measurements

Less pretty syntax, because a detector function just retruns values it is possible to create something we call a measurement_function

def measurement_function(sweep_points): 
    for i, sweep_function in enumerate sweep_functions:
         sweep_function.set_sweep_point(sweep_points[i])
    return detector_function.get_value()

By passing this measurement function to an adaptive function we can make use of the existing scipy optimization functions or any other kind of adaptive algorithm.

Code that we use for running this looks like this:

MC.set_sweep_functions(sweepfunctions)
MC.set_detector_function(cdet.AllXY_deviation_detector())
MC.set_measurement_mode('adaptive')
MC.set_measurement_mode (adaptive_function='optimization',  method='Powell') # We built the optimization function into the MC, better to pass the function that is to be used
MC.set_adaptive_function_parameters(dict_containing_params_for_optimization_func)
MC.run(name='Numerical_motzoi_vs_deviation')

4. Composite measurements

As you might have guessed by now the fact that we can execute any piece of code in our detector function allows us to create a nested measurement. An example of such a nested detector would be a "qubit characterization detector"

MC.set_sweep_function(swf.dac_voltage_mV())
MC.set_sweep_points(sweep_pts)
MC.set_detector_function(cdet.qubit_char_detector())
MC.run()

This composite detector runs multiple measurements, in this case, find resonator, find qubit, tune pulse amplitude and then perform a T1, Ramsey and Echo experiment. Finally it returns multiple values.

The way we do this is by creating a nested MC instrument on which we perform these other experiments, allowing us to reuse our tested code for these experiments. The thing that we have not yet solved is how to deal with data saving, currently each nested loop creates its own datafile, making analysis easy but also creating a lot files.

5. Hardware controlled measurements

Something I have omitted up to now are what we call 'hard' measurements, as opposed to 'soft'(ware) controlled measurements the points are no longer set one by one in the measurement control but are controlled by some piece of hardware which runs multiple points and afterwards returns an array of values. Although the nr of points might seem ill defined it does always return a predefined nr of point, thereby making it very possible to deal with this in well controlled way for datasaving.

from qcodes.

guenp avatar guenp commented on July 17, 2024

@AdriaanRol thanks for your long story 😁
So if I would compare this to the Qcodes syntax that @alexcjohnson proposes:

  • detector_function would be equivalent to MeasurementSet
  • set_sweep_function and set_sweep_points would be equivalent to Loop and .loop

So translating your above code would become something like this:

1D sweep trivial example:

measurement = station.create_measurement(det['HomodyneDetector'])
Loop(Source_frequency_GHz(source)[freq_start:freq_stop:freq_step]).measure(measurement).run()

@alexcjohnson re: Source_frequency_GHz(source) not sure if the sweep parameter can take input parameters though?

2D sweep trivial example:

measurement = station.create_measurement(det['HomodyneDetector'])
Loop(Source_frequency_GHz(source)[freq_start:freq_stop:freq_step])
     .loop(Source_power_dBm(source)[start_power:stop_power:power_step])
     .measure(measurement)
     .run()

Adaptive measurement, don't know enough details for this one so just freestyling here:

measurement = station.create_measurement(cdet['AllXY_deviation_detector'])
LoopMultiple(*sweepfunctions) #Somehow this should run .loop for each set of sweep function/parameters dynamically
     .each(AdaptiveMeasurement(func='optimization', method='Powell', 
          params=dict_containing_params_for_optimization_func)).
     .measure(measurement)
     .run('Numerical_motzoi_vs_deviation')

@alexcjohnson do we already have a way to define the measurement name? would you also make it an input parameter to .run()?

@AdriaanRol What do you think? Does the translation I made above make sense and would it be a nice syntax for you to use?

from qcodes.

alexcjohnson avatar alexcjohnson commented on July 17, 2024

@AdriaanRol thanks for the detailed comments! I think that the syntax I've proposed can accommodate all of the use cases you've enumerated, along the lines of the translations @guenp has provided - working on the implementation now. One difference is I've tried to do things using built-in python constructs whenever possible in place of explicit methods.

Take SweepValues as an example, which replaces MC.set_sweep_function and MC.set_sweep_points with one object (that's usually constructed from a parameter but doesn't need to be): the looping code will do something like:

for value in sweep_values:
    sweep_values.set(value)
    sleep(delay)
    measured_values = measure(...)
    sweep_values.feedback(measured_values)

All SweepValues objects need to have a set method which normally just maps to whatever parameter you're creating the sweep out of. (as an aside, if the sweeping code were really as simple as the pseudocode above, setting could have been accomplished by the iterator... but of course it's not that simple 😈 eg for nested loops, so I used an explicit setter). And then more complex cases like adaptive sampling need to know what was measured, so they have a feedback method as well. But the rest (providing the values, and prepare() and finish()) are in built-in constructs:

For the standard case SweepFixedValues which handles any collection of fixed setpoints, not necessarily linearly spaced, the for ... in construct just iterates its _values sequence.

For more complex scenarios (like my toy AdaptiveSweep) you create an iterator in the canonical python way:

  • prepare() is the meat of __iter__ which gets called at the beginning of for ... in
  • values are provided by __next__
  • finish() is embedded in __next__ right before you raise StopIteration which is the standard python way to terminate an iterator.

from qcodes.

alexcjohnson avatar alexcjohnson commented on July 17, 2024

@guenp

re: Source_frequency_GHz(source) not sure if the sweep parameter can take input parameters though?

Sure, if Source_frequency_GHz(source) is a factory function that creates an object with a .set and a .__getitem__ method - though wouldn't source normally already have a frequency parameter that one could use directly?

do we already have a way to define the measurement name? would you also make it an input parameter to .run()?

good point - yes, I think that should be in .run(). A side effect of that is that everything that happens together (if you have several things in the Loop.each() will get the same measurement_name, but would have different measured parameter names within that. So measurement_name would map to a folder name or something, and the parameters would be separate files (or maybe just columns) inside that.

from qcodes.

guenp avatar guenp commented on July 17, 2024

@alexcjohnson

though wouldn't source normally already have a frequency parameter that one could use directly?

No idea, ask @AdriaanRol :) I guess this is up to the user. My point was that input parameters would be handy in this case.

everything that happens together (if you have several things in the Loop.each() will get the same measurement_name, but would have different measured parameter names within that. So measurement_name would map to a folder name or something, and the parameters would be separate files (or maybe just columns) inside that.

True! I would just put everything in 1 tab-delimited file, like QTLab. Each column is a parameter, 1 header line specifying the column names, a block separated by two carriage returns for each outer loop iteration (basically GNUplot format which is also used for SpyView). This is also easy to read with pandas. E.g.:

<file:20151023_142501_my_param1_vs_my_param2.dat>

#time, temperature, my_param1, my_param2, ...
00:00:00   48.239841e-3   1   1 ...
00:00:01   48.239841e-3   1   2 ...
00:00:02   48.239841e-3   1   3 ...

00:00:03   48.239841e-3   2   1 ...
00:00:04   46.345433e-3   2   2 ...
00:00:05   46.345433e-3   2   3 ...

...

In the folder there could be more stuff (instrument param snapshot, incremental Monitor log, some graphs).

I would even allow people to append several measurements to the same data folder and file, if they would want to. I did a measurement yesterday where I was looking for a critical magnetic field, and instead of starting a measurement from B=0T to B=14T I first went to 6T, then to 8T, then to 14T, just cause I wanted to have a feeling first of where Bc more or less was before having to abort a long measurement halfway. All these measurements were appended to the same datafile (I used a Quantum Design PPMS in AC transport mode, using the in-built software. It basically just appends all measurements to 1 file unless you change the filename).

So this was my measurement in QCodes syntax:

Rxx = station.create_measurement(hallbar['Rxx']) #AC resistivity, dVxx/dI
Vxx = station.create_measurement(hallbar['Vxx']) #DC longitudinal voltage drop
quadrant = [0:4e-3:5e-4] + [4e-3:-4e-3:5e-4] + [-4e-3:0:5e-4] #not sure if this syntax would work, but basically for an IV curve to measure superconducting critical current I want to go from 0 to Imax, to -Imax, to zero
IV_curve = Loop(I[quadrant]).measure(Vxx)

Loop(B[0:6:0.5])
     .each(IV_curve, Rxx)
     .run('Sweep B, sample N3, device C')

Loop(B[6.5:8:0.5])
     .each(IV_curve, Rxx)
     .run('Sweep B, sample N3, device C')

...

etc. Specifying the same measurement_name for subsequent measurements should add the data to the same data file (or alert the user "This file name already exists. Do you want to start a new data file or append to the existing one?")...

from qcodes.

AdriaanRol avatar AdriaanRol commented on July 17, 2024

@guenp

@AdriaanRol What do you think? Does the translation I made above make sense and would it be a nice syntax for you to use?

I think your translation is generally correct, however I very much dislike the syntax, mostly from a readability point of view. I do not think that our way is the only way (or best for that matter), however I do think that having all the .sweep .measure linked together in one statement can become very cluttered very quickly.
I like our method because it has a single line for each statement, an alternative would be to have a single function call with multiple arguments as this also allows lines to be split and makes it clear what is happening.

With respect to setting the sweep points as follows, I think that that also introduces an extra constraint, I'd much rather give them either explicitly or as an iterator as this gives more freedom to the user (e.g. non uniformly spaced sweeps, rough scan for most, fine around certain known regions).

.loop(Source_power_dBm(source)[start_power:stop_power:power_step])

@alexcjohnson

Sure, if Source_frequency_GHz(source) is a factory function that creates an object with a .set and a .getitem method - though wouldn't source normally already have a frequency parameter that one could use directly?

This is indeed true, the choice of making it a sweep object is a very deliberate one though. By making it an object this allows us to set any kind of code we like within the sweep function object, thereby giving far more freedom to the user while still providing the rigidity that is required to standardise loops and data storage.

To provide a (simple) use-case where this might be handy, this weekend we found out that agilent sources reset the phase whenever a frequency is set, this makes sense as for a different frequency phase will change, however even when setting the frequency back to the same value the phase is lost. By having a sweep function in which we can as a user add stuff like get and set phase around the set frequency, this problem can be circumvented without having to access the measurement loop/control code and /or the instrument driver.

Now obviously there are other ways to work around this but this way of implementing it removes artificial constraints on the user and also allows more elaborate sweep functions which might not fit into the instrument and parameter paradigm (ones that program sequences into the AWG for example).

The same logic applies to why we hand detector functions as objects to the measurement control.

from qcodes.

alexcjohnson avatar alexcjohnson commented on July 17, 2024

@AdriaanRol the syntax is certainly quite different from QTLab but maybe the patterns I describe below can assuage your concerns? I think in the end it will allow quite some better flexibility.

I very much dislike the syntax, mostly from a readability point of view. I do not think that our way is the only way (or best for that matter), however I do think that having all the .sweep .measure linked together in one statement can become very cluttered very quickly.

You can certainly break these statements up, though as I'm envisioning right now pretty much all of the method calls make new objects, rather than modifying the existing one:

bsweep = Loop(B[0:6:0.5])
bsweep = bsweep.each(IV_curve, Rxx)  # overwrite bsweep with a new object that has actions attached
bsweep.run('Sweep B, sample N3, device C')

It's more verbose because you have to hold onto the new objects, but the advantage of this over everything being a method of and modifying MC is that you can have several things defined at once and call them repeatedly, nesting them different ways. So for example the IV_curve object @guenp constructed above can be run by itself any time as a 1D sweep, or nested inside a B sweep (or any other loop) to make a 2D sweep - and in this example, that's happening in the same B sweep as a 1D Rxx vs B curve is being measured. (without having had to create a separate measurement function that includes separate Rxx and I/V measurements).

That being said, one advantage of your syntax that I don't have yet: most of the time your measurement would be the same across many sweeps, and your way MC remembers the detector_function. One thing we could do is define a default measurement that gets used if you run a Loop with no actions:

Loop.set_measurement(Rxx, Rxy)  # class method, not instance method, so it sets the default?
Loop(B[0:6:0.5]).run('simple B sweep')  # no .each, so it uses the default

With respect to setting the sweep points as follows, I think that that also introduces an extra constraint, I'd much rather give them either explicitly or as an iterator as this gives more freedom to the user (e.g. non uniformly spaced sweeps, rough scan for most, fine around certain known regions).

I think the current implementation gives you the flexibility you want. The code documents it here: https://github.com/qdev-dk/Qcodes/blob/master/qcodes/sweep_values.py#L77-L91

So re @guenp 's quadrant, a slice can't be created in quite that notation but you have many options:

# several slices in a row
quadrant = I[0:4e-3:5e-4, 4e-3:-4e-3:5e-4, -4e-3:0:5e-4]
# add together several SweepFixedValues objects
quadrant = I[0:4e-3:5e-4] + I[4e-3:-4e-3:5e-4] + I[-4e-3:0:5e-4]
IV_curve = Loop(quadrant).measure(Vxx)

or as a sequence of (built-in) slice objects - this is useful in case you want the same sequence of values available to different parameters:

quadrant = (slice(0, 4e-3, 5e-4), slice(4e-3, -4e-3, 5e-4), slice(-4e-3, 0, 5e-4))
IV_curve = Loop(I[quadrant]).measure(Vxx)

you can in principle pass any iterator inside the [...], but note that it will be immediately evaluated to a list, so that you know its length and you can use it as many times as you want. If you want some sequence of values that you don't know ahead of time, you can make another SweepValues subclass like AdaptiveSweep that implements its own iterator with feedback.

it removes artificial constraints on the user and also allows more elaborate sweep functions which might not fit into the instrument and parameter paradigm (ones that program sequences into the AWG for example).
The same logic applies to why we hand detector functions as objects to the measurement control.

I think the QTLab syntax and what I'm proposing are actually not that different in this respect - a parameter doesn't have to be tied to an instrument, it's just anything that has a set (if you're sweeping it) and/or a get (if you're measuring it) and soon, when I get to making pretty plots out of all this, also something about units/labels. One difference though is that with the .each syntax it will be easy to compose multiple actions that you've already defined into a sequence to nest within a new sweep.

Loop(B[0:6:0.5], 0).each(
    Task(v_g.set, 1.5),
    Task(wait_for_temperature, 50),  # after changing field, wait for 50mK
    Loop(v_sd[-3:3:0.05], 0.01),  # bias sweep at vg=1.5V using the default measurement
    Task(v_sd.set, 0),
    Loop(v_g[0.5:2.5:0.02], 0.02)  # gate sweep at zero bias
).run('B sweep with several nested 1D sweeps waiting for cooling at each step')

So for your phase example, you wouldn't need a whole new measurement function or sweep function, just the new piece, and then you list each thing you want to happen within the loop as additional arguments to .each:

def reset_phase():
    freq = agilent1['frequency'].get()
    phase = lookup_phase(freq)
    agilent1['phase'].set(phase)

Loop(agilent1['frequency'][1e6:1e7:1e5], 0.1).each(
    Task(reset_phase),
    measure_something
).run('new frequency sweep')

Then if you find you're doing the same things all the time, you can create a parameter out of it:

class FreqWithPhase:
    def __init__(self, freq_parameter, phase_parameter):
        self._freq_parameter = freq_parameter
        self._phase_parameter = phase_parameter

    def set(self, freq):
        phase = lookup_phase(freq)
        self._freq_parameter.set(freq)
        self._phase_parameter.set(phase)

    def __getItem__(self, keys):
        # maybe we make a Parameter base class that this can just be inherited from...
        return SweepFixedValues(self, keys)

freq1 = FreqWithPhase(agilent1['frequency'], agilent1['phase'])

Loop(freq1[1e6:1e7:1e5], 0.1).run('frequency sweep 1')

from qcodes.

AdriaanRol avatar AdriaanRol commented on July 17, 2024

@alexcjohnson , Thanks, that does indeed clear up quite a lot.

I think you have addressed most of my concerns

  • setting sweep points, I kind of like your implementation as it allows multiple ways of setting the points yet still allowing them to be specified explicitly.
  • Your example of the combined loop is very nice, the way we currently do nested measurements is done very explicitly by nesting. I am not sure yet how to achieve that in your syntax as the actions at each step (which you seperate by a comma) are not predetermined but depend on the outcome of one of the previous measurements in that loop.

I can imagine several workarounds, the first being that we abuse some kind of parameter that is being set and get in each of the loop to achieve some kind of forwarding of information between comma separated values in your loop and the other is putting it all within the set or get part of one of the objects that you loop over.

  • I really like your example of the "class FreqWithPhase:" as this is very similar to what we call a "sweep_function", we basically have the same object that we specify to our loop with the addition of a mandatory (sometimes empty) prepare and finish statement.

It is still unclear to me how the "measure()" command works. If it works with a similar object as input as your .sweep command I think that that can be rather versatile. I am wondering how you deal with .measure() functions that return multiple variables (e.g. I and Q quadratures or res_freq, qub_freq and T1,T2 etc) or return multiple values of said variables at once. It seems to me that the .each syntax starts becoming unnatural here.

Another thing which I am worried about is the ease of implementing adaptive functions. Altough I think that with what you describe it is possible to write adaptive functions it is rather hard to use standard out of the box adaptive functions (e.g. scipy.optimize ). Those adaptive functions usually require a function that they can call to which they pass some parameters and get some value back. By combining the .set (sweep point) and .get (measure?) parts in one function that gets repeatedly called it is possible to pass that specific function. Maybe this is already what you have in mind but I thought I'd point it out as this gives nice forward compatibility with all sorts of libraries.

Also the syntax I'm comparing to is more our own lab standard than that it's QTLab :)

from qcodes.

guenp avatar guenp commented on July 17, 2024

It is still unclear to me how the "measure()" command works. If it works with a similar object as input as your .sweep command I think that that can be rather versatile. I am wondering how you deal with .measure() functions that return multiple variables (e.g. I and Q quadratures or res_freq, qub_freq and T1,T2 etc) or return multiple values of said variables at once. It seems to me that the .each syntax starts becoming unnatural here.

@AdriaanRol That shouldn't have to be a problem. Every time a measurement is done, the results are written to the data file (one data point = one line in the file). In the case of multiple different measurements, you can just save everything in 1 file and enter NaN where there's no values saved. In my example the datafile could look like this:

<file:20151023_142501_Bsweep.dat>

#time, B, Rxx, Vxx, I, ...
00:00:00   0   30   NaN   NaN   ...
00:00:01   0   NaN   0    0   ...
00:00:02   0   NaN   1e-3    5e-4   ...
00:00:03   0   NaN   2e-3    1e-3   ...
00:00:04   0   NaN   3e-3    1.5e-3   ...
00:00:05   0   NaN   4e-3    2e-3   ...
...

You could also choose to save it in multiple different files as @alexcjohnson suggested before, however, I prefer to have everything in one single file, but it should be up to the user.

Another thing which I am worried about is the ease of implementing adaptive functions. Altough I think that with what you describe it is possible to write adaptive functions it is rather hard to use standard out of the box adaptive functions (e.g. scipy.optimize ). Those adaptive functions usually require a function that they can call to which they pass some parameters and get some value back.

@AdriaanRol I still don't have a very clear picture of your adaptive measurement since you just provided a brief pseudocode example. If I understand it correctly - you measure a waveform, then use scipy.optimize to extract some value, which you enter into the subsequent measurement?
I would say that the AdaptiveMeasurement class in this case should do a measurement every time .get is called:

class AdaptiveMeasurement():
     def __init__(func='optimization', method='Powell', params=dict_containing_params_for_optimization_func, measurement = measurement):
          self.func = func
          self.method = method
          self.params = dict_containing_params_for_optimization_func
          self.measurement = measurement

     def get(self):
          meas = self.measurement.run()
          result = ... #do stuff here with scipy.optimize
          return result

This would make the code look like this:

sweep_values = SweepValues(...)
measurement = station.create_measurement(cdet['AllXY_deviation_detector'])
resonance = AdaptiveMeasurement(func='optimization', method='Powell', params=dict_containing_params_for_optimization_func, measurement = measurement)
for value in sweep_values:
    sweep_values.set(value)
    measured_values = measure(resonance).run()
    sweep_values.feedback(measured_values)

Does this make sense?

@alexcjohnson perhaps .measure and .each are getting redundant now. They both basically do the same thing, except that .each takes multiple input arguments. I would say just merge them and have measure take multiple input arguments & drop .each (or vice versa).

from qcodes.

alexcjohnson avatar alexcjohnson commented on July 17, 2024

@guenp

perhaps .measure and .each are getting redundant now. They both basically do the same thing, except that .each takes multiple input arguments. I would say just merge them and have measure take multiple input arguments & drop .each (or vice versa).

haha I've already dropped .measure in the code I'm developing around this discussion, and just have .each 👍

@AdriaanRol re: adaptive measurements: I haven't used scipy.optimize, but I can imagine two classes of adaptive algorithms - do your example and other uses of adaptive sampling you can think of fit into these categories?

  1. Algorithms in which the set points of dependent variable(s) are modified by the values measured within that same loop - this is what I had in mind with a .feedback method - you initialize the algorithm with some parameters, start measuring based on these defaults, then the loop feeds measurements back into the algorithm as they arrive using .feedback. In my example, which is a monotonic feature finder, you just make the step size larger or smaller based on changes in one measured variable, but you could in fact keep a history of data points and pass that whole list to the algorithm.
  2. Algorithms in which the set points of dependent variables for one loop are determined by the results of some previous measurement. To give a concrete example: you have some feature that's a ridge as a function of v1 and v2 - you measure the peak of that ridge as a function of v1 at two different v2 values, then you want to sweep along the top of the ridge on a diagonal in v1/v2 space. I'm not quite sure the right way to handle this... the algorithm needs to base its initialization on a whole array that was measured in a separate loop, but then the set points are known after that. Perhaps the object gets a .prior_data method, and if this method exists, the loop passes in any other data that was taken previously in the same .each block?
  3. yeah, I said two... but you could also imagine algorithms that use both of these features at the same time.

from qcodes.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.