Closed guenp closed 8 years ago
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 :)
@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 MeasurementSet
s.
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()
@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()
@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
@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?
edit: hit enter too soon :astonished: 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()
@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.
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.
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()
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)
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')
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.
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.
@AdriaanRol thanks for your long story :grin: 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?
@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 :smiling_imp: 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
__next__
finish()
is embedded in __next__
right before you raise StopIteration
which is the standard python way to terminate an iterator.@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.
@alexcjohnson
though wouldn't
source
normally already have afrequency
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 samemeasurement_name
, but would have different measured parameter names within that. Someasurement_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?")...
@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.
@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')
@alexcjohnson , Thanks, that does indeed clear up quite a lot.
I think you have addressed most of my concerns
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.
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 :)
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).
@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 havemeasure
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
:+1:
@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?
.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.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?
Continuing discussion from #2 Should we have 1 sweep function or multiple nested (user-defined) measurement functions?
E.g. for each B, apply my_pulse 10.000x and monitor the system for 10 minutes
measurement.sweep((B[-6:6:0.1], AWG.applier('my_pulse.dat', n=10000)), 60, repeat(600), 1)
or
measurement.sweep(B[-6:6:0.1],60).apply_pulse_scheme('my_pulse.dat', n=10000).monitor(t=600).run()
and e.g. 2D sweep measurement
measurement.sweep(B[-6:6:0.1], 60, c0[-20:20:0.1], 0.2)
or
measurement.sweep(B[-6:6:0.1], 60).sweep(c0[-20:20:0.1], 0.2).run()
I would prefer to chop the syntax up in parts:
.run()
allows recycling of the measurement objectany thoughts/ideas?