microsoft/Qcodes

dond to support time-dependence measurements (perhaps with ElapsedTimeParameter)

astafan8 opened this issue · 4 comments

something like

etp = ElapsedTimeParameter()
dond(etp, stuff_to_sweep_and_measure_here, ...)

first of all, ElapsedTimeParameter does not have a set method, so it can't be used in dond as an independent parameter. Secondly, the ask is to run a measurement until elapsed time is more than a specified value (e.g. until elapsed time is more than 10 minutes).

One quick workaround would be to use a dummy parameter as the doNd set parameter and include the ElapsedTimeParameter in the list of meas_params.

Originally requested by @guenp @samantha-ho

guenp commented

Thanks @astafan8 for creating this issue!
The reason why I initially thought it would be great for dond to support time as a parameter is because in the users' mind, time is just another dimension along which we want to measure a set of parameters or rasters. I'm not fixated on using dond for this if it is not within the package's design principles, but the convenience of being able to set up a measurement with dond, then decide "What if I monitor this thing over time"? and only having to make a small change will be great to be able to cover somehow.

Another request for this just came in: for every value of an outer parameter, measure some other parameter as a function of time by acquiring a value every 10 seconds for 5 minutes. I suggested the following solution:

from qcodes.dataset import dond, LinSweep
from qcodes.parameters import ElapsedTimeParameter, Parameter
from qcodes.dataset import new_experiment

new_experiment("some_experiment", "some_sample")

outer_p = Parameter("outer", set_cmd=None, get_cmd=None)

time_p = ElapsedTimeParameter("time_p")

outer_sweep = LinSweep(
    outer_p,
    0,
    2,
    10,
    # reset the elapsed time parameter
    # for every new value of this sweep
    post_actions=(time_p.reset_clock,)
)

# For use with LinSweep we need to make another parameter
# that can be `set` that will wrap the time parameter because
# the ElapsedTimeParameter does not have a `set` and `dond` call
# would break if we use ElapsedTimeParameter directly
time_p_with_set = Parameter(
    "time",
    get_cmd=time_p.get,  # returns the value of the time parameter
    set_cmd=None  # can be set, but we will ignore this value in the dond
)

time_sweep = LinSweep(
    time_p_with_set,  # we use the wrapping time parameter
    start=0,  # this value does not matter
    stop=5,  # this value does not matter
    num_points=5*60/10,  # 5 mins / 10 seconds, this is the number of times the measurement will be done
    delay=10,  # seconds, the delay between the time measurements
    get_after_set=True,  # thanks to #4759 
)

# Let's create a fake measurement parameter to use in this example:
meas_p = Parameter("meas", set_cmd=False, get_cmd=lambda: outer_p.get()+time_p.get()*2)

# And this is the measurement!
dond(outer_sweep, time_sweep, meas_p)

my personal opinion is that this snippet is not graceful at all. so i'd welcome a more readable solution.

guenp commented

@astafan8, this issue just came up again. This is the workaround that @ThorvaldLarsen came up with:

time_param = ElapsedTimeParameter('time')
time_param._settable = True
time_param.set = lambda x: None
time_sweep = LinSweep(time_param, 1, 1000, 1000, 0, get_after_set=True)

time_param.reset_clock()
ds, _, _ = dond(time_sweep, *params_to_measure)

This solution is not ideal because we have to pick the number of repetitions based on how long we want the measurement to run, instead of just specifying "please measure for 10 minutes".

We were wondering if it might make sense to implement something like this that behaves more like a while loop:

time_param = ElapsedTimeParameter('time')
time_sweep = TimeSweep(time_param, start=0, stop=10*60, delay=0)
ds, _, _ = dond(time_sweep , *params_to_measure)

Now that they're released, it makes more sense to use the datasaver_builder and dond_into extension than try to make this work in the base dond method.

In rough pseudocode, it would look like this:

time_param = ElapsedTimeParameter('time')
start_time = time.now()
with datasaver_builder(dataset_definition) as datasavers:
  while time_param < start_time + duration:
    dond_into(datasavers[0], *params_to_measure, additional_setpoints=(time_param,))
    time.sleep(delay)
  ds = datasavers[0].dataset