Skip to content

Conversation

@nulinspiratie
Copy link
Contributor

This PR adds the possibility to create QCoDeS loops using the Python for loop structure, which makes writing a Loop feel more Pythonic.
This is achieved using python cell magic: a measurement cell should be started with %%measurement (see below for an example).
The cell magic internally converts the for loop in a cell to the qc.Loop construct before executing the code.
Since iPython magic is used, it unfortunately only works in iPython (easiest in Jupyter Notebook).
The magic command has additional options, such as printing the executed code.

Example of new looping method

Old method to create a loop

Creating a Loop typically starts with creating a Loop object using qc.Loop.
After this, a dataset is created with the measurement_name

loop = qcodes.Loop({sweep_vals}).each(
    {measure_parameter1},
    {measure_parameter2},
    qcodes.Loop({sweep_vals2}).each(
        {measure_parameter3}))
data = loop.get_data_set(name={measurement_name})

{Additional code}

Using for loops

This can be replaced with for loops.
The first line should start with %%measurement followed by the measurement_name (and possible options).
After this, the Loop can be created by a for loop. Loops can also be nested same as you would nest for loops.

%%measurement {-options} {measurement_name}
for {sweep_vals}:
    {measure_parameter1}
    {measure_parameter2}
    for {sweep_vals2}:
        {measure_parameter3}

{Additional code}

Additional changes

The PR contains some additional changes related to magic. These are mainly to create a construct such that any future magic can be nicely integrated into QCoDeS

  • Instead of simply registering the %%measurement magic, it is contained in the magics class QCoDeSMagic. This is done to have access to additional magic-related features, such as parsing. It also makes it easier to stop a specific magic from being registered
  • The function register_magic_class is added, which handles the registering of a Magics class. The kwarg magic_commands allows only specific magics to be registered.
  • The item register_magic is added to config.core. It can be set to True, False, or a list (True by default).
  • The QCoDeS init checks if register_magic is not set to False and if the kernel is iPython. If so, then it will register magics. If register_magic=True, it will register all the magics in QCoDeSMagic. If register_magic is a list, it will only register those that are in the list.

@giulioungaretti @jenshnielsen @WilliamHPNielsen @core

@jenshnielsen
Copy link
Collaborator

Cool

Can we have an explicit example? I assumed that the code you were meant to write would be
regular python code for i in {some generator} I had to try a few times to understand how it works

The code is only python 3.6 compatible because of the fstrings. We will likely break python 3.5 support soon but in the mean time it would be good to wrap the import in a check for python 3.6 and up.

@nulinspiratie
Copy link
Contributor Author

@jenshnielsen I almost copied the Python for loop structure, but I don't instantiate a variable (i in your example). Instead, the for loop is for {some generator}, the reason being that the variable i can't actually be used in a Loop.

As an explicit example, say we have initialized parameter p = qc.ManualParameter('test_parameter'). Writing a loop could then be:

%%measurement test_measurement
for p.sweep(0, 10, step=1):
    p

which would translate to

loop = qc.Loop(p.sweep(0, 10, step=1)).each(
    p)
data = loop.get_data_set('test_measurement')

I'll add a check for Python 3.6

@jenshnielsen
Copy link
Collaborator

Yes I have figured out how it works, but your docstring could be more clear. I think you will need to document it with an explicit example.
Currently it's confusing because you write Create qcodes.Loop measurement using Python for syntax using iPython magic. which is not strictly true as for p.sweep(0, 10, step=1): can never be valid python. The formal definition of a for loop is for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
see https://docs.python.org/3/reference/grammar.html A for loop is not valid python with out the exprlint 'in' part

@nulinspiratie
Copy link
Contributor Author

@jenshnielsen Yeah good point, I should've made it clearer. I'd love if we could actually use the setpoints as variables in a loop, in which case it would make sense to follow the same syntax as python for loops. I'll update the docstring to make this more clear. Other than that any comments?

@jenshnielsen
Copy link
Collaborator

jenshnielsen commented Sep 14, 2017

I think the current version is a great start. I am hoping that when I finally get back to the new dataset which will be more disentangled from the loop that we can use for loops in a better way. Perhaps replace the magic with a context manager?

Only one tiny nitpick detail: it's actually IPython not iPython since it's not an apple product :)

@nulinspiratie
Copy link
Contributor Author

nulinspiratie commented Sep 15, 2017 via email

@jenshnielsen
Copy link
Collaborator

I agree, My thinking is more in the direction of actually running the for loops as they are but having a context manager that takes care of all the dataset setup and so on but this will require quite some work and I am not sure if its even possible at this stage

@sohailc
Copy link
Member

sohailc commented Sep 15, 2017 via email

@sohailc
Copy link
Member

sohailc commented Sep 15, 2017

Why do we want to couple QCodes so tightly with IPython notebooks? What if we do not want to use notebooks as a way of running our experiments (as is indeed the talk here in Delft, that is that we should switch to regular python scripts)?

@WilliamHPNielsen
Copy link
Contributor

WilliamHPNielsen commented Sep 15, 2017

@sohailc, generally, we don't want this strong coupling. QCoDeS should be useable without a notebook, and that is also the current state of affairs. In Copenhagen some people use Spyder in the lab. I agree with you that QCoDeS core functionality (drivers, datasets, loops) should not depend on the notebooks, but I think it is fine if people make notebook extensions of QCoDeS (like widgets or this magic).

@sohailc
Copy link
Member

sohailc commented Sep 15, 2017 via email

@jenshnielsen
Copy link
Collaborator

yes the title of this pull request is slightly miss leading. Its not a replacement but an alternative

@sohailc
Copy link
Member

sohailc commented Sep 15, 2017 via email

@jenshnielsen
Copy link
Collaborator

Its the way we want to go and also my original suggestion. My point is that we need a smart way to ensure that the data is captured and stored correctly so that when you do a for loop in a measurement you dont have to write too much boilerplate code to capture the data and setup plotting and what not

@sohailc
Copy link
Member

sohailc commented Sep 15, 2017 via email

@nulinspiratie
Copy link
Contributor Author

@sohailc being able to use iterators/generators with a simple for loop structure would definitely result in measurements feeling much more Pythonic. I'm wondering how this can be implemented though. It seems to me -that meta information about the measurements is needed, such as its structure, the loop dimensionality etc. This enables creating an accompanying dataset. Since a for loop is a statement, it cannot be converted to an object, and so I'm not seeing how meta information can be gathered from this. Any thoughts?

@sohailc
Copy link
Member

sohailc commented Sep 18, 2017

Hi @nulinspiratie thank you for your interesting thoughts and comments.

I think this can be accomplished by the sweep generator yielding a stateful object instead of just measurement numbers. What about doing somethings like this:

(A class method on the Parameter class):

def sweep(self, iterable):
  for value in iterable:
    self.set(iterable):
    yield self.instrument

In this case instrument is a stateful object. If we want to make a measurement dataset, we could make a measurement class which can be different for different measurement types. This measurement class takes as argument a generator which it will unrole when calling "run". The init method of this class can accept as argument a list of properties or methods which will enable it to extract measurement data from the stateful object for each time the generator yields the next value. For instance:

class Measurement:
  def init(self, properties): # and other useful info about the measurement, like data format
     self._properties = properties

  def run(self, generator): 
     for instrument in generator(): 
        for property in self._properties: 
            value = instrument.get(property)
            .... # code to store the property and value at this time. 

So the complete measurement looks like:

m = qc.Measurement(["gate1", "sd1"])
m.run(instrument.sweep(gate1=np.linspace(0, 1, 10)))

I know this looks a bit like what we have already, but i think what is better about this design is that the run method can except any generator. Consider for example this:

m.run((instrument.set(value) for value in [0, 1, 2]))

Assuming that the set method returns "self". Another interesting thing you could do is perform non-linear sweeps. For example, finding the resonance frequency by stepping the frequency and iteratively finding the maximum value with newtons method:

def find_max_frequency(instrument, init):
  max_estimate = init
  while(True): # yield values until we have found the max
    .... # code to update the estimate of the max by measuring a local derivative. Adaptive stepping!
  yield instrument

My idea's have not matured, but consider how we could "zip" such a generator with a linear sweep to, for instance, flexibly program a measurement which finds a resonance at each gate voltage (for example).

@sohailc
Copy link
Member

sohailc commented Sep 18, 2017

Another thing I should have pointed out is that I intended for the measurement class to be subclassed for each measurement type. So we can have (for example) "ResonanceMeasurement" and "MicrowaveMeasurement". So it should be easy to subclass the measurement class for non-programmers.

If we try to make a one-size-fits all class, I think we will fail.

@nulinspiratie
Copy link
Contributor Author

Hi @sohailc very interesting ideas about future measurements, and thanks for taking the time to explain them! I've got a couple of questions related to it, I was hoping you could clarify.

First of all, what exactly is this stateful instrument that a parameter sweep returns? Is it an actual qc.Instrument object? If so, would this force parameters to belong to an instrument? Because most of ours do not belong to instruments. It also seems that the instrument is in charge of performing get operations. on each of the properties. How would the instrument know about these properties? On first glance, it seems as though this would force a link between the parameters that are swept (setters) and those that are measured (getters), is this correct?

Please correct me if I'm wrong, but is the main issue that's trying to be solved that we want to be able to specify the sweeping values adaptively, and that these may depend on previous measurement results? If so, the added benefit of an instrument is unclear to me.
For adaptive sweeping, would it not be sufficient if loops could accept generators of undefined length, and for parameters to have custom sweeps, which would be generators?
For this adaptive sweeping to be able to depend on measurement results, the Measurement (currently qc.Loop) would have to pass the measured values along to the parameter before the next sweep generator call.

Related to this, why would the Measurement be sublassed? Would it not make sense to simply create Measurement instances, such as the qc.Loop. Each instance would perform a specific measurement, such as measuring the resonance frequency. These can then be combined for more complex measurements.

Please let me know if anything is unclear, looking forward to your thoughts!

@nulinspiratie
Copy link
Contributor Author

@jenshnielsen @WilliamHPNielsen @sohailc As for the PR, is this something we want to include in QCoDeS, or not? If so, I'll update the docstring to include a more concrete example

@jenshnielsen
Copy link
Collaborator

jenshnielsen commented Sep 21, 2017

Im happy to include it. In this form it's optional and I can see it's usefulness. In the future we may be able to do something similar in a more clever way

@WilliamHPNielsen
Copy link
Contributor

@nulinspiratie @jenshnielsen Is this still relevant? I guess we're waiting for the docstring update.

@nulinspiratie
Copy link
Contributor Author

nulinspiratie commented Oct 16, 2017 via email

@nulinspiratie
Copy link
Contributor Author

@WilliamHPNielsen @jenshnielsen I updated the docstring to mention the differing syntax. If you guys approve, I think it's ready to be merged

@nulinspiratie
Copy link
Contributor Author

@jenshnielsen @WilliamHPNielsen any verdict?

@WilliamHPNielsen
Copy link
Contributor

I haven't found the time to try it out, but since this is a parallel feature that people can use or ignore, I think it would not hurt merging it. @jenshnielsen do you disagree with that?

@jenshnielsen
Copy link
Collaborator

I think it's ok to merge

@codecov
Copy link

codecov bot commented Dec 13, 2017

Codecov Report

Merging #723 into master will not change coverage.
The diff coverage is n/a.

@@           Coverage Diff           @@
##           master     #723   +/-   ##
=======================================
  Coverage   78.63%   78.63%           
=======================================
  Files          33       33           
  Lines        4536     4536           
=======================================
  Hits         3567     3567           
  Misses        969      969

Copy link
Contributor

@WilliamHPNielsen WilliamHPNielsen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go!

@jenshnielsen jenshnielsen merged commit a7dbd3a into microsoft:master Dec 13, 2017
giulioungaretti pushed a commit that referenced this pull request Dec 13, 2017
Author: Serwan Asaad <[email protected]>

    Feature/Replace qc.Loop with for loop using iPython magic (#723)
giulioungaretti pushed a commit that referenced this pull request Dec 13, 2017
Author: Serwan Asaad <[email protected]>

    Feature/Replace qc.Loop with for loop using iPython magic (#723)
@nulinspiratie nulinspiratie deleted the feature/for_loop_magic branch March 25, 2020 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants