Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Driver/lakeshore336 and lakeshore 372 #972

Merged

Conversation

jenshnielsen
Copy link
Collaborator

@jenshnielsen jenshnielsen commented Feb 19, 2018

A driver for Lakeshore Temperature Controllers 336 and 372. From QDev probe station by @Dominik-Vogel.

Includes a great new GroupParamter+Group classes for commands that set/get more than one parameter at once.

https://www.lakeshore.com/Documents/372_Manual.pdf
https://www.lakeshore.com/Documents/336_Manual.pdf

ToDo:

  • initial work by William, Jens, and Dominik
  • testing by William
  • testing by Mikhail
  • decide what to do with INTYPE current/voltage ranges - postpone once users need it
  • test waiting for set point
  • check difference in OUTMODE command between the models and adjust the code accordingly
  • add heaters for 336 model
  • add docstrings/labels/units
  • implement sensor status parameter correctly (as sum of statuses)
  • make _has_pid a kwarg
  • check base class features are supported by both models (esp 336)
  • move Parameter Group to some "common" part of instrument_drivers?
  • add docstrings
  • go thorugh TODOs
  • revisit blocking_t part
  • fix PID comments
  • revisit `set_setpoint_and_rangeq: range should be selected according to current temperature, not according to current setpoint. right?
  • wait_until_set_point_reached simplify and remove debug
  • PID or more verbose names?
  • wait_until_set_point_reached - if t is lower than sensor range, it keeps on waiting - add a note
  • model 336 - add heaters 3 and 4 (they are different that 1 and 2, for example, list of modes is different)
  • add type annotations
  • dont forget to refer to manual when necessary
  • make groupparameter inherit from parameter (not _baseparameter)
  • add more info to the notebook about the parameters
  • add info to the notebook about units of the setpoint
  • add simple plot-monitoring cell to the notebook
  • fix minor problems from the comments in this PR
  • see if mypy bug is still relevant
  • final testing of 336 by Jens/William
  • final testing of 372 by Mikhail

WontDoInThisPR:

  • tests: separate and explain what mock and what yaml file is used for
  • add test for model 336
  • agree on names for channels/inputs/measurement-inputs and sensors/outputs and refactor the code
  • what are min/max values of SETP?
  • different units for PID parameters between models (units)
  • different setting resolutions for PID between heater channels (docstring)
  • set vals for excitation_range_number based on excitation_mode (and then remove the big comment in the code)
  • model 336 -> sensor -> range - add tables for each sensor type and switch them once sensor type is changed

r: "{}"
setter:
q: "INNAME A,\"{}\""
r: OK
Copy link
Contributor

Choose a reason for hiding this comment

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

This "OK" response is now obsolete (#968).

Copy link
Contributor

Choose a reason for hiding this comment

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

OK

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I dont know if there was an actual test file using this file that I missed in the rebase

from qcodes.utils.validators import Enum, Strings
from qcodes.utils.validators import Enum as QCEnum, Strings

class ChannelDescriptor(Enum):
Copy link
Contributor

Choose a reason for hiding this comment

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

I may be missing something important, but this seems like an unnecessary complication to add. The cons are: reduced code readability (renaming Enum to QCEnum, having to look up ChannelDescriptor).

What are the pros?

In any case, the naming conflict is easily resolved by either just importing enum or importing qcodes.utils.validators as vals (or both). QCEnum is an abonimation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, QCEnum is a really bad name - the state of the code is really WIP.
But how is it a complication? I agree that it could also just be a dictionary. But does this make it simpler?

Copy link
Contributor

Choose a reason for hiding this comment

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

Just for the record: we agreed that changing the import names and keeping using the enum from the standard library is the way to go.

@codecov
Copy link

codecov bot commented Mar 6, 2018

Codecov Report

Merging #972 into master will increase coverage by 0.06%.
The diff coverage is 87.5%.

@@            Coverage Diff             @@
##           master     #972      +/-   ##
==========================================
+ Coverage   70.92%   70.99%   +0.06%     
==========================================
  Files          74       74              
  Lines        8352     8375      +23     
==========================================
+ Hits         5924     5946      +22     
- Misses       2428     2429       +1

@jenshnielsen
Copy link
Collaborator Author

There are some missing from typing import Dict due to the refactor and mypy error we should fix otherwise I guess we should get this in asap

@Dominik-Vogel
Copy link
Contributor

The driver is in a usable state now. It will need some more refactoring. The two models are not that similar after all. There are a few things that have to be moved out of the base class into the 336 like the setpoints.
Furthermore I want to change the parameters such that they can also accept strings instead of Enums so that they can be set from the StationConfiugrator.

set_cmd=f'RANGE {heater_index}, {{}}',
get_cmd=f'RANGE? {heater_index}')

self.add_parameter('setpoint', get_parser=lambda x: Range(int(x)),
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This has get parser twice

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks...

@jenshnielsen
Copy link
Collaborator Author

@Dominik-Vogel This will need a bit of work to get the tests passing before we can review it

@Dominik-Vogel
Copy link
Contributor

It is actually not as bad as it seems... I think it is only the repeated keyword (I simply forgot to delete the first get_cmd in the last commit) and the unused imports. Or do you see other errors?
Currently I am removing the Enums agiain, so that the driver can be used with the StationConfigurator. Additionally it needs some thinking how to get the two lakeshores onto the same basis.

@Dominik-Vogel Dominik-Vogel changed the title [WIP] Driver/lakeshore 336 improvements [WIP] Driver/lakeshore336 and lakeshore 372 May 22, 2018
@jenshnielsen
Copy link
Collaborator Author

Currently both the tests and mypy fails

==================================== ERRORS ====================================
___________ ERROR collecting qcodes/tests/drivers/test_lakeshore.py ____________
tests/drivers/test_lakeshore.py:8: in <module>
    from qcodes.instrument_drivers.Lakeshore.Model_336 import Model_336
instrument_drivers/Lakeshore/Model_336.py:6: in <module>
    class Output_372(BaseOutput):
E   NameError: name 'BaseOutput' is not defined
----------- coverage: platform linux, python 3.6.3-final-0 -----------
Coverage XML written to file coverage.xml
!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!
=========================== 1 error in 8.85 seconds ============================
The command "py.test --cov=qcodes --cov-report xml --cov-config=.coveragerc" exited with 2.
20.21s$ cd ..
mypy qcodes --ignore-missing-imports --no-strict-optional
qcodes/instrument_drivers/Lakeshore/lakeshore_base.py:65: error: Need type annotation for 'MODES'
qcodes/instrument_drivers/Lakeshore/lakeshore_base.py:66: error: Need type annotation for 'RANGES'
qcodes/instrument_drivers/Lakeshore/lakeshore_base.py:146: error: The return type of "__init__" must be None
qcodes/instrument_drivers/Lakeshore/Model_372.py:59: error: Name 'Model_372_Channel' is not defined
qcodes/instrument_drivers/Lakeshore/Model_372.py:65: error: Name 'Heater' is not defined
qcodes/instrument_drivers/Lakeshore/Model_372.py:66: error: Name 'Heater' is not defined
qcodes/instrument_drivers/Lakeshore/Model_372.py:67: error: Name 'Heater' is not defined
qcodes/instrument_drivers/Lakeshore/Model_336.py:6: error: Name 'BaseOutput' is not defined
qcodes/instrument_drivers/Lakeshore/Model_336.py:26: error: Name 'BaseSensorChannel' is not defined
qcodes/tests/drivers/test_lakeshore.py:19: error: Too many arguments for "__init__" of "object"
qcodes/tests/drivers/test_lakeshore.py:22: error: Need type annotation for 'cmds'
qcodes/tests/drivers/test_lakeshore.py:24: error: Need type annotation for 'queries'
qcodes/tests/drivers/test_lakeshore.py:100: error: Need type annotation for 'heaters'

@Dominik-Vogel
Copy link
Contributor

Dominik-Vogel commented May 23, 2018

This is not very surprising, my last commit is also labeled WIP and is in the middle of refactoring.

@jenshnielsen
Copy link
Collaborator Author

Yes but the comment before that implied that it was ready for review, hence my message

@Dominik-Vogel Dominik-Vogel changed the title [WIP] Driver/lakeshore336 and lakeshore 372 Driver/lakeshore336 and lakeshore 372 Jul 3, 2018
@Dominik-Vogel
Copy link
Contributor

@QCoDeS/core ready for review

@astafan8 astafan8 self-requested a review July 3, 2018 10:40
Copy link
Contributor

@astafan8 astafan8 left a comment

Choose a reason for hiding this comment

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

As discussed with @Dominik-Vogel, i will test it in the lab with the actual fridge. Once i'm done with the testing, I'll remove the "request changes" note.

self.I.vals = vals.Numbers(0.1, 1000)
self.D.vals = vals.Numbers(0, 200)

self.range_limits.vals = validators.Sequence(
Copy link
Member

Choose a reason for hiding this comment

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

Error: validators is unknown. This should be vals.Sequence(...

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed

log = logging.getLogger(__name__)

class Output_336(BaseOutput):

Copy link
Member

Choose a reason for hiding this comment

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

Can we add a docstring describing the usefulness of this class? This is not clear to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

done

'off': 0,
'low': 1,
'medium': 2,
'heigh': 3}
Copy link
Member

Choose a reason for hiding this comment

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

typo in "heigh". Should be "high"

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed

self.parameters = OrderedDict((p.name, p) for p in parameters)
self.instrument = parameters[0].root_instrument
for p in parameters:
p.group = self
Copy link
Member

Choose a reason for hiding this comment

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

There should be a public method called set_group on the GroupParameter class to do this. Do not assign attributes directly like this.

Copy link
Member

@sohailc sohailc Jul 16, 2018

Choose a reason for hiding this comment

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

In fact, if you make a parameter group like so:

self._group_param = Group(
	set_cmd="bla {{a}} {{b}}"
)

self.add_parameter(
	"a", 
	group=self._group_param, 
	parameter_class=GroupParameter
)

Then line 35 will not be necessary and we will not need to set the group attribute because we pass it to the constructor. I think this is more clear.

Copy link
Contributor

Choose a reason for hiding this comment

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

There is, as far as I can see, a mutual dependence of the parameter and the group: The parameter has to know the group, and the group has to know the parameter.
This is because we want to call set/get on the parameter and it and needs the values of the other parameters to accomplish that.
So this double link is created in an init method. This can equally happen in the parameter or in the group. In your example the parameter is aware of the group but the group is not aware of the parameter.
I chose to put this linking into the group because it makes the code a little more compact and readable for my taste... But I can be convinced otherwise if you include the previous aspect in your argument.
concerning the attribute I wrote you a PM.

Copy link
Member

Choose a reason for hiding this comment

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

I think you are right about this. I think if you clean this code up a little (e.g. add docstrings, proper type hinting) it can be approved and merged. But I am still unsure if we should have a setter for the group or not. I read the link you send me on PM, but I still think having a setter will make things more clear to fellow programmers.

self.add_parameter('powerup_enable',
val_mapping={True: 1, False: 0},
parameter_class=GroupParameter)
self.output_group = Group([self.mode, self.input_channel,
Copy link
Member

Choose a reason for hiding this comment

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

I quite like the way the group parameter works. I think we should make this available generally to other instruments as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you @sohailc . I was also thinking that and wanted to use the Lakeshore as a testground first. I think it could maybe make a module together with the alazar parameters with the context manager for syncing that @jenshnielsen introduced. The way of defining a group is similar and there could be a keyword to determine whether parameters get synced on demand (like in alazar) or automatically (like here)

for name, p in self.parameters.items()}
calling_dict[set_parameter.name] = value
command_str = self.set_cmd.format(**calling_dict)
set_parameter.root_instrument.write(command_str)
Copy link
Member

Choose a reason for hiding this comment

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

why not self.instrument.write(command_str)?

Copy link
Contributor

Choose a reason for hiding this comment

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

to keep things simple, i will use instrument. also, i will add to the docstring that it is assumed that all parameters in the group belong to the same instrument

calling_dict = {name: p.raw_value
for name, p in self.parameters.items()}
calling_dict[set_parameter.name] = value
command_str = self.set_cmd.format(**calling_dict)
Copy link
Contributor

Choose a reason for hiding this comment

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

this means that the Group only supports string commands, right? Shall we generalize it to callables as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe later, but not in this PR

return dict(zip(keys, values))
return parser

def set(self, set_parameter, value):
Copy link
Contributor

@astafan8 astafan8 Jul 17, 2018

Choose a reason for hiding this comment

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

is get method missing? does it mean that instead of using param_group.get() which presumable returns a param-values dict, do i need to call get of every parameter in the group? in this case, will the "update" from the instrument happen for every parameter?

Copy link
Contributor

Choose a reason for hiding this comment

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

param_group.get() does not seem to be a use case. and indeed, update is called every time one of the parameter's get method is called.

@WilliamHPNielsen
Copy link
Contributor

Testing of this driver for some simply/quick measurements has begun on QT3. As you can see, we already found one (rather crucial) bug. If the driver suddenly becomes important, I might take over and fix the issues pointed out by @astafan8 above.

from contextlib import suppress
import time

# from qcodes.instrument_drivers.Lakeshore.Model_336 import Model_336
Copy link
Contributor

Choose a reason for hiding this comment

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

commented out?

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed

self.wait_equilibration_time(0.5)


self.add_parameter('blocking_T',
Copy link
Member

@sohailc sohailc Aug 8, 2018

Choose a reason for hiding this comment

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

shouldn't this be a method? (by astafan8)

Copy link
Contributor

Choose a reason for hiding this comment

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

old Loop needs a parameter, so we end up with a parameter on top of a method instead of a method-only.

@astafan8
Copy link
Contributor

astafan8 commented Aug 9, 2018

Today I've executed the driver on an actual Lakeshore Model 372 instrument. Outcome:

  • the driver seems to work
  • wait_until_set_point_reached method has a bug in log.debug (left a separate comment about it)
  • scientists requested the ability to turn the channels on and off. This is available via INSET command (but it also has multiple inputs...). The rationale for turning the channels off is the following: when Lakeshore measures values at sensors, it goes through the enabled ones one by one, sets proper ranges in its internal lock-in, etc. The more channels you have enabled, the more time it takes between measurements of values for a given sensor. Since the heater uses only one sensor, the other sensors can be turned off to increase the effective time of the heater's feedback loop "response". A minor detail here is that this command also allows to change the "curve" (temperature-resistance) of the sensor - users either should not be able to change it, or there should be huge warnings and banners and prompts before changing it, because you do not want to suddenly change the curve for which the sensor was calibrated for.

start_time_in_tolerance_zone = None

log.debug(f'waiting to reach setpoint: temp at '
f'{t_reading}, delta:{delta}')
Copy link
Contributor

Choose a reason for hiding this comment

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

this line throws exception about unknown variable delta in case t_reading is None/False/etc. Also, why would we have if t_reading: condition in the first place?

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed, if t_reading: is gone

@astafan8
Copy link
Contributor

Dear @QCoDeS/core , I would like to wrap up my work on this driver for now. See the description for the list of items that have been done (and more). Please, review and leave your comments.

I propose that after testing the two drivers on the real devices, we merge this PR, and create an issue for the bullet items which are not covered in this PR. This is bad, I know, but this will allow our users to use the new driver (at the moment, they are using it via merging this branch into their other branches).

@astafan8
Copy link
Contributor

astafan8 commented Oct 3, 2018

@QCoDeS/core The 372 driver is in active use now at two stations in Delft, everything works as expected and the users are passing their gratitude to those who worked on the driver :)

So shall we merge it "as is" (see my previous message for details)?

@WilliamHPNielsen
Copy link
Contributor

@astafan8, can we make the CI pass first?

@Dominik-Vogel
Copy link
Contributor

Sorry guys I didn't have time to get back to this one... Let me see if I can get the ci through and then merge it

@Dominik-Vogel
Copy link
Contributor

Now all ci passes. It would be great to get a confirming review ;-) so that I can merge

@WilliamHPNielsen
Copy link
Contributor

You don't need that for merging.

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.

But why not, I also like the all-green look.

@WilliamHPNielsen WilliamHPNielsen merged commit b39b800 into microsoft:master Oct 4, 2018
giulioungaretti pushed a commit that referenced this pull request Oct 4, 2018
Merge: 056d596 1755f22
Author: William H.P. Nielsen <whpn@mailbox.org>

    Merge pull request #972 from jenshnielsen/driver/Lakeshore_366_rebased
@jenshnielsen jenshnielsen deleted the driver/Lakeshore_366_rebased branch October 4, 2018 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants