Feature/Measurement loop
This pull request introduces a new way of doing measurements, called the MeasurementLoop, that sits somewhere in between the current Measurement class and the do1d/do2d/dond functions. The key idea of this measurement scheme is to offer flexibility in measurements while having as much as possible be implicit.
The MeasurementLoop introduced here is ported from the UNSW fork Measurement class.
Although the basic functionality is working, this PR is still very much a work in progress, but I thought of starting the PR now. Is this something that could fit into QCoDeS? Tagging @jenshnielsen @WilliamHPNielsen @RasmusBC59
Features
- Implicit setpoints using
Sweepclass - Implicit registering of measurements the first time a parameter is measured
- Allows execution of arbitrary python code in measurements
- Masking of parameters/attributes within a loop/measurement
- Sweep/measure values without needing it to be a parameter
- Allows nested measurements.
Nested measurements recognize that they are nested and attach themselves to the parent measurement.
This enables functions that perform a measurement. If the function is called within a measurement, it becomes part of the larger measurement
Limitations
- Currently incompatible with measurements without a predefined array shape (e.g. keep measuring until X).
This can be implemented.
Illustrative example
from qcodes import ManualParameter
from qcodes.utils.dataset.doNd import LinSweep
from qcodes.dataset.measurement_loop import MeasurementLoop, Sweep
# Initialize parameters
# Note that we do not need to register them in the measurement
p1_set = ManualParameter('outer_sweep_parameter')
p2_set = ManualParameter('inner_sweep_parameter')
p1_get = ManualParameter('parameter_get')
p_sum_above_ten = ManualParameter('sum_above_ten')
with MeasurementLoop('test') as msmt:
# We encapsulate the LinSweep in a Sweep
# This registers the sweep as being a setpoint for everything measured inside
# This sweep also automatically sets the parameter
for val1 in Sweep(LinSweep(p1_set, 0, 1, 11)):
for val2 in Sweep(LinSweep(p2_set, 0, 1, 11)):
p1_get(val1+val2)
# Measuring this parameter automatically recognizes that it is inside
# a 2D loop and automatically attaches those setpoints
# Note that we also here don't need to preregister p1_get
msmt.measure(p1_get)
# Also include measurement outside inner loop
p_sum_above_ten(p1_get() > 10)
msmt.measure(p_sum_above_ten)
To do items
- [x] convert notebook imports to public API
- [x] Populate
__all__ofmeasurement_loop.py - [x] Sweep should inherit from Abstract sweep
- [x] Add args to
Sweep.execute() - [x] add type hints to args and variables
- [x] Write docstring for functions
Additions in future PR's
- [ ] Save arrays
- [ ] Add handling of ArrayParameter
- [ ] Save complex results
- [ ] Add plot functionality to sweep.execute
- [ ] add progress bar to Measurement loop
- [ ] Add metadata to
MeasurementLoop
Tests to add
- [x] Use Sweep in dond
- [x] Pass args to sweep.execute
- [x] Pass LinSweep to MeasurementLoop
@nulinspiratie Thanks a lot, I am super busy this week but I promise to get back to this ASAP
@jenshnielsen sounds good. I think at this point it doesn't make sense yet to do a full review. If there's the sense that this could/should be part of Qcodes I can go ahead and write a tutorial on it's features. That'll make it easier to understand it's use
So far I've been using it on our measurement setup. It's been working quite well, and I'm now updating the Sweep functionality so it's easier to create.
I'll probably have some more time over the upcoming weeks to work on this. @jenshnielsen can you give me an idea of what needs to happen before this PR can be accepted? I suspect that all the tests need to be converted to pytest, correct? What other things need to be taken care of?
@nulinspiratie I will have a quick look tomorrow
@nulinspiratie I will have a quick look tomorrow
Great! I've begun on a tutorial at docs/examples/DataSet/MeasurementLoop.ipynb, it explains most of the features and also restrictions. I still need to write about some of the more advanced features, but the important parts are there.
Thanks @nulinspiratie
The notebook looks great. A few points on that:
I have been working on a more consistent API docs for qcodes. The aim is that everything public should be imported from the second level trying to make qcodes more try to the promise of being modular.
For the notebook that means:
Please update all imports to be from the second level. E.g. from qcodes.parameters import ManualParameter
The api is reflected here https://qcodes.github.io/Qcodes/api/index.html#qcodes-api
For the new code please add the classes and functions that you think should be public to qcodes/dataset/__init__.py and __all__ therein and they will be part of the api docs.
Note these changes are not reflected in the current notebooks. I am waiting til after the next release with starting an update of those but for new notebooks I would like to stick to this.
I had a quick look at the code. Github seems to have some issue rendering the diff at the moment so no inline comments.
Overall looks good.
- We generally add types to all code within qcodes. I am happy to help with this if its a blocker
- I would like to think / understand more explicitly how much of this we consider public api
- I am a little bit worried that we have many different sweep classes. E.g in dond, here and the sweep thats already bound to the parameter (for use with the loop). I would like to think about how that can eventually be unified if possible. But perhaps the new sweeps here need not be public?
Hey @jenshnielsen thanks for looking over it. Your comments all seem quite manageable, I can go ahead and start adding type hints and proper imports.
When you refer to "how much we should consider public api", do you mean which classes / functions should be placed in the `DataSet.init.py'?
The two important classes are MeasurementLoop and Sweep, I think it's worth making those part of the public API, though I agree that the number of sweeps is getting overcrowded. We could make the new Sweep a subclass of AbstractSweep, in which case I think it can be used in dond as well. In this case, since LinSweep, LogSweep, and ArraySweep can all be implemented straightforwardly by Sweep, one option could be to make Sweep the default. However, this would be quite a major change so I'm not sure if it's a good idea.
Another option would indeed be to not make Sweep part of the public API. However, it is a necessary object for the MeasurementLoop. One could consider attaching it to the MeasurementLoop like msmt.sweep(), though it can be useful to create a Sweep outside of a MeasurementLoop as it allows one to pass it as an argument in measurement functions.
As for Parameter.sweep that is tied to the original Loop, I see no easy way of unifying it with the rest. But is it still being actively used?
When you refer to "how much we should consider public api", do you mean which classes / functions should be placed in the `DataSet.init.py'?
Yes the goal is that users should only import stuff that is available from the second level e.g. users should do from qcodes.parameters import Parameter but not from qcodes.parameters.parameter import parameter
I would like to eventually get rid of the top level imports (from qcodes import Parameter since that is very confusing)
But I don't expect to be able to remove that anytime soon if ever but plan to stop using it in examples.
That way we are free to reorganize the code internally without breaking stuff e.g. I moved the parameters to a new module instead of being part of instrument. And QCoDeS can finally start living up to the promise of being modular
The two important classes are MeasurementLoop and Sweep, I think it's worth making those part of the public API, though I agree that the number of sweeps is getting overcrowded. We could make the new Sweep a subclass of AbstractSweep, in which case I think it can be used in dond as well. In this case, since LinSweep, LogSweep, and ArraySweep can all be implemented straightforwardly by Sweep, one option could be to make Sweep the default. However, this would be quite a major change so I'm not sure if it's a good idea.
I think its worthwile investigating letting dond take your sweeps. Initially it can take those as a supplement to the existing ones and we can eventually migrate away from them
I think the basic functionality of the MeasurementLoop has now been implemented. The tests I've written are working locally.
I see that MyPy is raising typing errors, I can fix that. But other than that, @jenshnielsen what needs to happen before accepting this PR?
@nulinspiratie Sorry for the delay. I will do my best to have a look on Monday