From e6ac534bff260b6a609cef2235efd4b8fa4f6f74 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 3 Sep 2018 15:42:29 +0200 Subject: [PATCH 01/82] initial commit from code from qdev wrappers --- qcodes/config/station_configurator.py | 222 ++++++++++++++++++++++++++ qcodes/instrument/parameter.py | 25 +++ 2 files changed, 247 insertions(+) create mode 100644 qcodes/config/station_configurator.py diff --git a/qcodes/config/station_configurator.py b/qcodes/config/station_configurator.py new file mode 100644 index 00000000000..e6cd5e694f0 --- /dev/null +++ b/qcodes/config/station_configurator.py @@ -0,0 +1,222 @@ +from contextlib import suppress +from typing import Optional +from functools import partial +import importlib +import logging +import warnings +import os +from copy import deepcopy +import qcodes +from qcodes.instrument.base import Instrument +from qcodes.station import Station +import qcodes.utils.validators as validators +from qcodes.instrument.parameter import Parameter +from qcodes.monitor.monitor import Monitor +from qcodes.instrument.parameter import DelegateParameter +use_pyyaml = False +try: + from ruamel.yaml import YAML +except ImportError: + use_pyyaml = True + warnings.warn("ruamel yaml not found station configurator is falling back to pyyaml. " + "It's highly recommended to install ruamel.yaml. This fixes issues with " + "scientific notation and duplicate instruments in the YAML file") + +log = logging.getLogger(__name__) + +# config from the qcodesrc.json file (working directory, home or qcodes dir) +enable_forced_reconnect = qcodes.config["station_configurator"]["enable_forced_reconnect"] +default_folder = qcodes.config["station_configurator"]["default_folder"] +default_file = qcodes.config["station_configurator"]["default_file"] + + +class StationConfigurator: + """ + The StationConfig class enables the easy creation of intstruments from a + yaml file. + Example: + >>> scfg = StationConfig('exampleConfig.yaml') + >>> dmm1 = scfg.create_instrument('dmm1') + """ + + PARAMETER_ATTRIBUTES = ['label', 'unit', 'scale', 'inter_delay', 'delay', + 'step', 'offset'] + + def __init__(self, filename: Optional[str] = None, + station: Optional[Station] = None) -> None: + self.monitor_parameters = {} + + if station is None: + station = Station.default or Station() + self.station = station + self.filename = filename + + self.load_file(self.filename) + for instrument_name in self._instrument_config.keys(): + # TODO: check if name is valid (does not start with digit, contain + # dot, other signs etc.) + method_name = f'load_{instrument_name}' + if method_name.isidentifier(): + setattr(self, method_name, + partial(self.load_instrument, + identifier=instrument_name)) + else: + log.warning(f'Invalid identifier: ' + + f'for the instrument {instrument_name} no ' + + f'lazy loading method {method_name} could be ' + + 'created in the StationConfigurator') + + def load_file(self, filename: Optional[str] = None): + if use_pyyaml: + import yaml + else: + yaml=YAML() + if filename is None: + filename = default_file + try: + with open(filename, 'r') as f: + self.config = yaml.load(f) + except FileNotFoundError as e: + if not os.path.isabs(filename) and default_folder is not None: + try: + with open(os.path.join(default_folder, filename), + 'r') as f: + self.config = yaml.load(f) + except FileNotFoundError: + raise e + else: + raise e + + self._instrument_config = self.config['instruments'] + + class ConfigComponent: + def __init__(self, data): + self.data = data + + def snapshot(self, update=True): + return self.data + + # this overwrites any previous station + # configurator but does not call the snapshot + self.station.components['StationConfigurator'] = ConfigComponent(self.config) + + def load_instrument(self, identifier: str, + **kwargs) -> Instrument: + """ + Creates an instrument driver as described by the loaded config file. + + Args: + identifier: the identfying string that is looked up in the yaml + configuration file, which identifies the instrument to be added + **kwargs: additional keyword arguments that get passed on to the + __init__-method of the instrument to be added. + """ + + # load file + self.load_file(self.filename) + + # load from config + if identifier not in self._instrument_config.keys(): + raise RuntimeError('Instrument {} not found in config.' + .format(identifier)) + instr_cfg = self._instrument_config[identifier] + + # config is not parsed for errors. On errors qcodes should be able to + # to report them + + # check if instrument is already defined and close connection + if instr_cfg.get('enable_forced_reconnect', + enable_forced_reconnect): + # save close instrument and remove from monitor list + with suppress(KeyError): + instr = Instrument.find_instrument(identifier) + # remove parameters related to this instrument from the monitor list + self.monitor_parameters = {k:v for k,v in self.monitor_parameters.items() if v.root_instrument is not instr} + instr.close() + # remove instrument from station snapshot + self.station.components.pop(instr.name) + + # instantiate instrument + module = importlib.import_module(instr_cfg['driver']) + instr_class = getattr(module, instr_cfg['type']) + + init_kwargs = instr_cfg.get('init',{}) + # somebody might have a empty init section in the config + init_kwargs = {} if init_kwargs is None else init_kwargs + if 'address' in instr_cfg: + init_kwargs['address'] = instr_cfg['address'] + if 'port' in instr_cfg: + init_kwargs['port'] = instr_cfg['port'] + # make explicitly passed arguments overide the ones from the config file + # the intuitive line: + + # We are mutating the dict below + # so make a copy to ensure that any changes + # does not leek into the station config object + # specifically we may be passing non pickleable + # instrument instances via kwargs + instr_kwargs = deepcopy(init_kwargs) + instr_kwargs.update(kwargs) + + instr = instr_class(name=identifier, **instr_kwargs) + # setup + + # local function to refactor common code from defining new parameter + # and setting existing one + def setup_parameter_from_dict(parameter, options_dict): + for attr, val in options_dict.items(): + if attr in self.PARAMETER_ATTRIBUTES: + # set the attributes of the parameter, that map 1 to 1 + setattr(parameter, attr, val) + # extra attributes that need parsing + elif attr == 'limits': + lower, upper = [float(x) for x in val.split(',')] + parameter.vals = validators.Numbers(lower, upper) + elif attr == 'monitor' and val is True: + self.monitor_parameters[id(parameter)] = parameter + elif attr == 'alias': + setattr(instr, val, parameter) + elif attr == 'initial_value': + # skip value attribute so that it gets set last + # when everything else has been set up + pass + else: + log.warning(f'Attribute {attr} no recognized when' + f' instatiating parameter \"{parameter.name}\"') + if 'initial_value' in options_dict: + parameter.set(options_dict['initial_value']) + + # setup existing parameters + for name, options in instr_cfg.get('parameters', {}).items(): + # get the parameter object from its name: + p = instr + for level in name.split('.'): + p = getattr(p, level) + setup_parameter_from_dict(p, options) + + # setup new parameters + for name, options in instr_cfg.get('add_parameters', {}).items(): + # allow only top level paremeters for now + # pop source only temporarily + source = options.pop('source', False) + if source: + source_p = instr + for level in source.split('.'): + source_p = getattr(source_p, level) + instr.add_parameter(name, + DelegateParameter, + source=source_p) + else: + instr.add_parameter(name, Parameter) + p = getattr(instr, name) + setup_parameter_from_dict(p, options) + # restore source + options['source'] = source + + # add the instrument to the station + self.station.add_component(instr) + + # restart Monitor + Monitor(*self.monitor_parameters.values()) + + return instr diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 0012a893f1e..9ebb309032a 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -1738,3 +1738,28 @@ def set_raw(self, value: Union[int, float]) -> None: self._save_val(value) self._wrapped_parameter.set(instrument_value) + + +class DelegateParameter(Parameter): + """ + The `DelegateParameter` wraps a given `source`-parameter. Setting/getting + it results in a set/get of the source parameter with the provided + arguments. + + The reason for using a `DelegateParameter` instead of the source parameter + is to provide all the functionality of the Parameter base class without + overwriting properties of the source: for example to set a different + Scaling factor and unit on the `DelegateParameter` without changing those + in the source parameter + """ + + def __init__(self, name: str, source: Parameter, *args, **kwargs): + self.source = source + super().__init__(name=name, *args, **kwargs) + + def get_raw(self, *args, **kwargs): + return self.source.get(*args, **kwargs) + + def set_raw(self, *args, **kwargs): + self.source(*args, **kwargs) + From f51c3276814d9e16950ecd14c04dd413275dac05 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 3 Sep 2018 15:47:51 +0200 Subject: [PATCH 02/82] add ruamel to environment --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 351724628e3..1dc8ce28eee 100644 --- a/environment.yml +++ b/environment.yml @@ -19,3 +19,4 @@ dependencies: - pytest-runner - spyder - pyzmq + - ruamel.yaml From fa3715c9f72ff2d56246d70adc8d86415e136bea Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 3 Sep 2018 16:50:54 +0200 Subject: [PATCH 03/82] add revive option --- qcodes/config/station_configurator.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/qcodes/config/station_configurator.py b/qcodes/config/station_configurator.py index e6cd5e694f0..c3a6b985b75 100644 --- a/qcodes/config/station_configurator.py +++ b/qcodes/config/station_configurator.py @@ -18,9 +18,10 @@ from ruamel.yaml import YAML except ImportError: use_pyyaml = True - warnings.warn("ruamel yaml not found station configurator is falling back to pyyaml. " - "It's highly recommended to install ruamel.yaml. This fixes issues with " - "scientific notation and duplicate instruments in the YAML file") + warnings.warn( + "ruamel yaml not found station configurator is falling back to pyyaml. " + "It's highly recommended to install ruamel.yaml. This fixes issues with " + "scientific notation and duplicate instruments in the YAML file") log = logging.getLogger(__name__) @@ -70,7 +71,7 @@ def load_file(self, filename: Optional[str] = None): if use_pyyaml: import yaml else: - yaml=YAML() + yaml = YAML() if filename is None: filename = default_file try: @@ -101,6 +102,7 @@ def snapshot(self, update=True): self.station.components['StationConfigurator'] = ConfigComponent(self.config) def load_instrument(self, identifier: str, + revive_instance: bool=False, **kwargs) -> Instrument: """ Creates an instrument driver as described by the loaded config file. @@ -108,9 +110,14 @@ def load_instrument(self, identifier: str, Args: identifier: the identfying string that is looked up in the yaml configuration file, which identifies the instrument to be added + revive_instance: If true, try to return an instrument with the + specified name instead of closing it and creating a new one. **kwargs: additional keyword arguments that get passed on to the __init__-method of the instrument to be added. """ + # try to revive the instrument + if revive_instance and Instrument.exist(identifier): + return Instrument.find_instrument(identifier) # load file self.load_file(self.filename) @@ -130,8 +137,11 @@ def load_instrument(self, identifier: str, # save close instrument and remove from monitor list with suppress(KeyError): instr = Instrument.find_instrument(identifier) - # remove parameters related to this instrument from the monitor list - self.monitor_parameters = {k:v for k,v in self.monitor_parameters.items() if v.root_instrument is not instr} + # remove parameters related to this instrument from the + # monitor list + self.monitor_parameters = { + k:v for k,v in self.monitor_parameters.items() + if v.root_instrument is not instr} instr.close() # remove instrument from station snapshot self.station.components.pop(instr.name) From 6e5d8cbbecf9481d12eaa7ab98362915614733dd Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Fri, 7 Sep 2018 10:06:21 +0200 Subject: [PATCH 04/82] add ruamel.yaml dependency the right way --- environment.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 9db77a69063..5d641b1abe9 100644 --- a/environment.yml +++ b/environment.yml @@ -19,4 +19,5 @@ dependencies: - pytest-runner - spyder - pyzmq - - ruamel.yaml + - pip: + ruamel.yaml From 31b32391cc23a720d5c102c6bbb3b5c868a4ecfd Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Fri, 7 Sep 2018 13:29:38 +0200 Subject: [PATCH 05/82] fix dynamic method creation in load_file --- qcodes/config/station_configurator.py | 47 ++++++++++++++++++--------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/qcodes/config/station_configurator.py b/qcodes/config/station_configurator.py index c3a6b985b75..cdcfa9e8a2a 100644 --- a/qcodes/config/station_configurator.py +++ b/qcodes/config/station_configurator.py @@ -5,6 +5,7 @@ import logging import warnings import os +from types import MethodType from copy import deepcopy import qcodes from qcodes.instrument.base import Instrument @@ -51,23 +52,14 @@ def __init__(self, filename: Optional[str] = None, station = Station.default or Station() self.station = station self.filename = filename + # a list of method names that got added by :meth:`load_file` + self._added_methods: List[str] = [] self.load_file(self.filename) - for instrument_name in self._instrument_config.keys(): - # TODO: check if name is valid (does not start with digit, contain - # dot, other signs etc.) - method_name = f'load_{instrument_name}' - if method_name.isidentifier(): - setattr(self, method_name, - partial(self.load_instrument, - identifier=instrument_name)) - else: - log.warning(f'Invalid identifier: ' + - f'for the instrument {instrument_name} no ' + - f'lazy loading method {method_name} could be ' + - 'created in the StationConfigurator') def load_file(self, filename: Optional[str] = None): + + # 1. load config from file if use_pyyaml: import yaml else: @@ -90,6 +82,7 @@ def load_file(self, filename: Optional[str] = None): self._instrument_config = self.config['instruments'] + # 2. add config to snapshot componenents class ConfigComponent: def __init__(self, data): self.data = data @@ -97,10 +90,34 @@ def __init__(self, data): def snapshot(self, update=True): return self.data - # this overwrites any previous station - # configurator but does not call the snapshot + # this overwrites any previous configuration + # but does invoke snapshoting self.station.components['StationConfigurator'] = ConfigComponent(self.config) + + # 3. create shortcut methods to instantiate instruments via + # `load_()` so that autocompletion can be used + # first remove methods that have been added by a previous `load_file` + # call + for method_name in self._added_methods: + delattr(self, method_name) + + # add shortcut methods + for instrument_name in self._instrument_config.keys(): + # TODO: check if name is valid (does not start with digit, contain + # dot, other signs etc.) + method_name = f'load_{instrument_name}' + if method_name.isidentifier(): + setattr(self, method_name, MethodType( + partial(self.load_instrument, identifier=instrument_name), + self)) + self._added_methods.append(method_name) + else: + log.warning(f'Invalid identifier: ' + + f'for the instrument {instrument_name} no ' + + f'lazy loading method {method_name} could be ' + + 'created in the StationConfigurator') + def load_instrument(self, identifier: str, revive_instance: bool=False, **kwargs) -> Instrument: From 97a587a6048e2441b3bf1b3e9f733df6d4a9423b Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Fri, 7 Sep 2018 14:53:24 +0200 Subject: [PATCH 06/82] add as partial not method type, remove methods on the fly --- qcodes/config/station_configurator.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/qcodes/config/station_configurator.py b/qcodes/config/station_configurator.py index cdcfa9e8a2a..a4faba9f086 100644 --- a/qcodes/config/station_configurator.py +++ b/qcodes/config/station_configurator.py @@ -5,7 +5,6 @@ import logging import warnings import os -from types import MethodType from copy import deepcopy import qcodes from qcodes.instrument.base import Instrument @@ -99,8 +98,8 @@ def snapshot(self, update=True): # `load_()` so that autocompletion can be used # first remove methods that have been added by a previous `load_file` # call - for method_name in self._added_methods: - delattr(self, method_name) + while len(self._added_methods): + delattr(self, self._added_methods.pop()) # add shortcut methods for instrument_name in self._instrument_config.keys(): @@ -108,9 +107,9 @@ def snapshot(self, update=True): # dot, other signs etc.) method_name = f'load_{instrument_name}' if method_name.isidentifier(): - setattr(self, method_name, MethodType( - partial(self.load_instrument, identifier=instrument_name), - self)) + setattr(self, method_name, partial( + self.load_instrument, identifier=instrument_name)) + self._added_methods.append(method_name) else: log.warning(f'Invalid identifier: ' + From 94053da0bae18d681dbb135615d4d7f8aff6f364 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Fri, 7 Sep 2018 14:54:12 +0200 Subject: [PATCH 07/82] Treat monitor parameters as list again --- qcodes/config/station_configurator.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/qcodes/config/station_configurator.py b/qcodes/config/station_configurator.py index a4faba9f086..ed423b44754 100644 --- a/qcodes/config/station_configurator.py +++ b/qcodes/config/station_configurator.py @@ -45,7 +45,7 @@ class StationConfigurator: def __init__(self, filename: Optional[str] = None, station: Optional[Station] = None) -> None: - self.monitor_parameters = {} + self.monitor_parameters = [] if station is None: station = Station.default or Station() @@ -155,12 +155,13 @@ def load_instrument(self, identifier: str, instr = Instrument.find_instrument(identifier) # remove parameters related to this instrument from the # monitor list - self.monitor_parameters = { - k:v for k,v in self.monitor_parameters.items() - if v.root_instrument is not instr} - instr.close() + self.monitor_parameters = [v for v in self.monitor_parameters + if v.root_instrument is not instr] # remove instrument from station snapshot self.station.components.pop(instr.name) + # del will remove weakref and close the instrument + instr.close() + del instr # instantiate instrument module = importlib.import_module(instr_cfg['driver']) @@ -199,7 +200,7 @@ def setup_parameter_from_dict(parameter, options_dict): lower, upper = [float(x) for x in val.split(',')] parameter.vals = validators.Numbers(lower, upper) elif attr == 'monitor' and val is True: - self.monitor_parameters[id(parameter)] = parameter + self.monitor_parameters.append(parameter) elif attr == 'alias': setattr(instr, val, parameter) elif attr == 'initial_value': @@ -243,6 +244,6 @@ def setup_parameter_from_dict(parameter, options_dict): self.station.add_component(instr) # restart Monitor - Monitor(*self.monitor_parameters.values()) + Monitor(*self.monitor_parameters) return instr From 7ff32f97724843412113480ce5ab596ce84da5ff Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Fri, 7 Sep 2018 15:33:33 +0200 Subject: [PATCH 08/82] move station configurator to station --- qcodes/config/station_configurator.py | 249 -------------------------- qcodes/station.py | 236 +++++++++++++++++++++++- 2 files changed, 234 insertions(+), 251 deletions(-) delete mode 100644 qcodes/config/station_configurator.py diff --git a/qcodes/config/station_configurator.py b/qcodes/config/station_configurator.py deleted file mode 100644 index ed423b44754..00000000000 --- a/qcodes/config/station_configurator.py +++ /dev/null @@ -1,249 +0,0 @@ -from contextlib import suppress -from typing import Optional -from functools import partial -import importlib -import logging -import warnings -import os -from copy import deepcopy -import qcodes -from qcodes.instrument.base import Instrument -from qcodes.station import Station -import qcodes.utils.validators as validators -from qcodes.instrument.parameter import Parameter -from qcodes.monitor.monitor import Monitor -from qcodes.instrument.parameter import DelegateParameter -use_pyyaml = False -try: - from ruamel.yaml import YAML -except ImportError: - use_pyyaml = True - warnings.warn( - "ruamel yaml not found station configurator is falling back to pyyaml. " - "It's highly recommended to install ruamel.yaml. This fixes issues with " - "scientific notation and duplicate instruments in the YAML file") - -log = logging.getLogger(__name__) - -# config from the qcodesrc.json file (working directory, home or qcodes dir) -enable_forced_reconnect = qcodes.config["station_configurator"]["enable_forced_reconnect"] -default_folder = qcodes.config["station_configurator"]["default_folder"] -default_file = qcodes.config["station_configurator"]["default_file"] - - -class StationConfigurator: - """ - The StationConfig class enables the easy creation of intstruments from a - yaml file. - Example: - >>> scfg = StationConfig('exampleConfig.yaml') - >>> dmm1 = scfg.create_instrument('dmm1') - """ - - PARAMETER_ATTRIBUTES = ['label', 'unit', 'scale', 'inter_delay', 'delay', - 'step', 'offset'] - - def __init__(self, filename: Optional[str] = None, - station: Optional[Station] = None) -> None: - self.monitor_parameters = [] - - if station is None: - station = Station.default or Station() - self.station = station - self.filename = filename - # a list of method names that got added by :meth:`load_file` - self._added_methods: List[str] = [] - - self.load_file(self.filename) - - def load_file(self, filename: Optional[str] = None): - - # 1. load config from file - if use_pyyaml: - import yaml - else: - yaml = YAML() - if filename is None: - filename = default_file - try: - with open(filename, 'r') as f: - self.config = yaml.load(f) - except FileNotFoundError as e: - if not os.path.isabs(filename) and default_folder is not None: - try: - with open(os.path.join(default_folder, filename), - 'r') as f: - self.config = yaml.load(f) - except FileNotFoundError: - raise e - else: - raise e - - self._instrument_config = self.config['instruments'] - - # 2. add config to snapshot componenents - class ConfigComponent: - def __init__(self, data): - self.data = data - - def snapshot(self, update=True): - return self.data - - # this overwrites any previous configuration - # but does invoke snapshoting - self.station.components['StationConfigurator'] = ConfigComponent(self.config) - - - # 3. create shortcut methods to instantiate instruments via - # `load_()` so that autocompletion can be used - # first remove methods that have been added by a previous `load_file` - # call - while len(self._added_methods): - delattr(self, self._added_methods.pop()) - - # add shortcut methods - for instrument_name in self._instrument_config.keys(): - # TODO: check if name is valid (does not start with digit, contain - # dot, other signs etc.) - method_name = f'load_{instrument_name}' - if method_name.isidentifier(): - setattr(self, method_name, partial( - self.load_instrument, identifier=instrument_name)) - - self._added_methods.append(method_name) - else: - log.warning(f'Invalid identifier: ' + - f'for the instrument {instrument_name} no ' + - f'lazy loading method {method_name} could be ' + - 'created in the StationConfigurator') - - def load_instrument(self, identifier: str, - revive_instance: bool=False, - **kwargs) -> Instrument: - """ - Creates an instrument driver as described by the loaded config file. - - Args: - identifier: the identfying string that is looked up in the yaml - configuration file, which identifies the instrument to be added - revive_instance: If true, try to return an instrument with the - specified name instead of closing it and creating a new one. - **kwargs: additional keyword arguments that get passed on to the - __init__-method of the instrument to be added. - """ - # try to revive the instrument - if revive_instance and Instrument.exist(identifier): - return Instrument.find_instrument(identifier) - - # load file - self.load_file(self.filename) - - # load from config - if identifier not in self._instrument_config.keys(): - raise RuntimeError('Instrument {} not found in config.' - .format(identifier)) - instr_cfg = self._instrument_config[identifier] - - # config is not parsed for errors. On errors qcodes should be able to - # to report them - - # check if instrument is already defined and close connection - if instr_cfg.get('enable_forced_reconnect', - enable_forced_reconnect): - # save close instrument and remove from monitor list - with suppress(KeyError): - instr = Instrument.find_instrument(identifier) - # remove parameters related to this instrument from the - # monitor list - self.monitor_parameters = [v for v in self.monitor_parameters - if v.root_instrument is not instr] - # remove instrument from station snapshot - self.station.components.pop(instr.name) - # del will remove weakref and close the instrument - instr.close() - del instr - - # instantiate instrument - module = importlib.import_module(instr_cfg['driver']) - instr_class = getattr(module, instr_cfg['type']) - - init_kwargs = instr_cfg.get('init',{}) - # somebody might have a empty init section in the config - init_kwargs = {} if init_kwargs is None else init_kwargs - if 'address' in instr_cfg: - init_kwargs['address'] = instr_cfg['address'] - if 'port' in instr_cfg: - init_kwargs['port'] = instr_cfg['port'] - # make explicitly passed arguments overide the ones from the config file - # the intuitive line: - - # We are mutating the dict below - # so make a copy to ensure that any changes - # does not leek into the station config object - # specifically we may be passing non pickleable - # instrument instances via kwargs - instr_kwargs = deepcopy(init_kwargs) - instr_kwargs.update(kwargs) - - instr = instr_class(name=identifier, **instr_kwargs) - # setup - - # local function to refactor common code from defining new parameter - # and setting existing one - def setup_parameter_from_dict(parameter, options_dict): - for attr, val in options_dict.items(): - if attr in self.PARAMETER_ATTRIBUTES: - # set the attributes of the parameter, that map 1 to 1 - setattr(parameter, attr, val) - # extra attributes that need parsing - elif attr == 'limits': - lower, upper = [float(x) for x in val.split(',')] - parameter.vals = validators.Numbers(lower, upper) - elif attr == 'monitor' and val is True: - self.monitor_parameters.append(parameter) - elif attr == 'alias': - setattr(instr, val, parameter) - elif attr == 'initial_value': - # skip value attribute so that it gets set last - # when everything else has been set up - pass - else: - log.warning(f'Attribute {attr} no recognized when' - f' instatiating parameter \"{parameter.name}\"') - if 'initial_value' in options_dict: - parameter.set(options_dict['initial_value']) - - # setup existing parameters - for name, options in instr_cfg.get('parameters', {}).items(): - # get the parameter object from its name: - p = instr - for level in name.split('.'): - p = getattr(p, level) - setup_parameter_from_dict(p, options) - - # setup new parameters - for name, options in instr_cfg.get('add_parameters', {}).items(): - # allow only top level paremeters for now - # pop source only temporarily - source = options.pop('source', False) - if source: - source_p = instr - for level in source.split('.'): - source_p = getattr(source_p, level) - instr.add_parameter(name, - DelegateParameter, - source=source_p) - else: - instr.add_parameter(name, Parameter) - p = getattr(instr, name) - setup_parameter_from_dict(p, options) - # restore source - options['source'] = source - - # add the instrument to the station - self.station.add_component(instr) - - # restart Monitor - Monitor(*self.monitor_parameters) - - return instr diff --git a/qcodes/station.py b/qcodes/station.py index ba8138b7807..4bb93cf4266 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -1,5 +1,12 @@ """Station objects - collect all the equipment you use to do an experiment.""" +from contextlib import suppress from typing import Dict, List, Optional, Sequence, Any +from functools import partial +import importlib +import logging +import warnings +import os +from copy import deepcopy from qcodes.utils.metadata import Metadatable from qcodes.utils.helpers import make_unique, DelegateAttributes @@ -8,9 +15,33 @@ from qcodes.instrument.parameter import Parameter from qcodes.instrument.parameter import ManualParameter from qcodes.instrument.parameter import StandardParameter +import qcodes.utils.validators as validators +from qcodes.monitor.monitor import Monitor +from qcodes.config.config import Config from qcodes.actions import _actions_snapshot +use_pyyaml = False +try: + from ruamel.yaml import YAML +except ImportError: + use_pyyaml = True + warnings.warn( + "ruamel yaml not found station configurator is falling back to pyyaml. " + "It's highly recommended to install ruamel.yaml. This fixes issues with " + "scientific notation and duplicate instruments in the YAML file") + +log = logging.getLogger(__name__) + +# config from the qcodesrc.json file (working directory, home or qcodes dir) +cfg = Config() +ENABLE_FORCED_RECONNECT = cfg["station_configurator"]["enable_forced_reconnect"] +DEFAULT_CONFIG_FOLDER = cfg["station_configurator"]["default_folder"] +DEFAULT_CONFIG_FILE = cfg["station_configurator"]["default_file"] + +PARAMETER_ATTRIBUTES = ['label', 'unit', 'scale', 'inter_delay', 'delay', + 'step', 'offset'] + class Station(Metadatable, DelegateAttributes): @@ -44,7 +75,8 @@ class Station(Metadatable, DelegateAttributes): default = None # type: 'Station' def __init__(self, *components: Metadatable, - monitor: Any=None, default: bool=True, + config_file: Optional[str]=None, + monitor: Monitor=None, default: bool=True, update_snapshot: bool=True, **kwargs) -> None: super().__init__(**kwargs) @@ -62,8 +94,14 @@ def __init__(self, *components: Metadatable, self.add_component(item, update_snapshot=update_snapshot) self.monitor = monitor + self.config_file = config_file + + self.default_measurement: List[Any] = [] + self._added_methods: List[str] = [] + self._monitor_parameters = [] + + self.load_config_file(self.config_file) - self.default_measurement = [] # type: List def snapshot_base(self, update: bool=False, params_to_skip_update: Sequence[str]=None) -> Dict: @@ -177,3 +215,197 @@ def __getitem__(self, key): return self.components[key] delegate_attr_dicts = ['components'] + + + def load_config_file(self, filename: Optional[str] = None): + + # 1. load config from file + if use_pyyaml: + import yaml + else: + yaml = YAML() + if filename is None: + filename = DEFAULT_CONFIG_FILE + try: + with open(filename, 'r') as f: + self._config = yaml.load(f) + except FileNotFoundError as e: + if not os.path.isabs(filename) and DEFAULT_CONFIG_FOLDER is not None: + try: + with open(os.path.join(DEFAULT_CONFIG_FOLDER, filename), + 'r') as f: + self._config = yaml.load(f) + except FileNotFoundError: + raise e + else: + raise e + + self._instrument_config = self._config['instruments'] + + # 2. add config to snapshot componenents + class ConfigComponent: + def __init__(self, data): + self.data = data + + def snapshot(self, update=True): + return self.data + + # this overwrites any previous configuration + # but does invoke snapshoting + self.components['StationConfigurator'] = ConfigComponent(self._config) + + + # 3. create shortcut methods to instantiate instruments via + # `load_()` so that autocompletion can be used + # first remove methods that have been added by a previous + # `load_config_file` call + while len(self._added_methods): + delattr(self, self._added_methods.pop()) + + # add shortcut methods + for instrument_name in self._instrument_config.keys(): + # TODO: check if name is valid (does not start with digit, contain + # dot, other signs etc.) + method_name = f'load_{instrument_name}' + if method_name.isidentifier(): + setattr(self, method_name, partial( + self.load_instrument, identifier=instrument_name)) + + self._added_methods.append(method_name) + else: + log.warning(f'Invalid identifier: ' + + f'for the instrument {instrument_name} no ' + + f'lazy loading method {method_name} could be ' + + 'created in the StationConfigurator') + + + def load_instrument(self, identifier: str, + revive_instance: bool=False, + **kwargs) -> Instrument: + """ + Creates an instrument driver as described by the loaded config file. + + Args: + identifier: the identfying string that is looked up in the yaml + configuration file, which identifies the instrument to be added + revive_instance: If true, try to return an instrument with the + specified name instead of closing it and creating a new one. + **kwargs: additional keyword arguments that get passed on to the + __init__-method of the instrument to be added. + """ + # try to revive the instrument + if revive_instance and Instrument.exist(identifier): + return Instrument.find_instrument(identifier) + + # load file + self.load_config_file(self.config_file) + + # load from config + if identifier not in self._instrument_config.keys(): + raise RuntimeError('Instrument {} not found in config.' + .format(identifier)) + instr_cfg = self._instrument_config[identifier] + + # config is not parsed for errors. On errors qcodes should be able to + # to report them + + # check if instrument is already defined and close connection + if instr_cfg.get('ENABLE_FORCED_RECONNECT', + ENABLE_FORCED_RECONNECT): + # save close instrument and remove from monitor list + with suppress(KeyError): + instr = Instrument.find_instrument(identifier) + # remove parameters related to this instrument from the + # monitor list + self._monitor_parameters = [v for v in self._monitor_parameters + if v.root_instrument is not instr] + # remove instrument from station snapshot + self.station.components.pop(instr.name) + # del will remove weakref and close the instrument + instr.close() + del instr + + # instantiate instrument + module = importlib.import_module(instr_cfg['driver']) + instr_class = getattr(module, instr_cfg['type']) + + init_kwargs = instr_cfg.get('init',{}) + # somebody might have a empty init section in the config + init_kwargs = {} if init_kwargs is None else init_kwargs + if 'address' in instr_cfg: + init_kwargs['address'] = instr_cfg['address'] + if 'port' in instr_cfg: + init_kwargs['port'] = instr_cfg['port'] + # make explicitly passed arguments overide the ones from the config file + # the intuitive line: + + # We are mutating the dict below + # so make a copy to ensure that any changes + # does not leek into the station config object + # specifically we may be passing non pickleable + # instrument instances via kwargs + instr_kwargs = deepcopy(init_kwargs) + instr_kwargs.update(kwargs) + + instr = instr_class(name=identifier, **instr_kwargs) + # setup + + # local function to refactor common code from defining new parameter + # and setting existing one + def setup_parameter_from_dict(parameter, options_dict): + for attr, val in options_dict.items(): + if attr in PARAMETER_ATTRIBUTES: + # set the attributes of the parameter, that map 1 to 1 + setattr(parameter, attr, val) + # extra attributes that need parsing + elif attr == 'limits': + lower, upper = [float(x) for x in val.split(',')] + parameter.vals = validators.Numbers(lower, upper) + elif attr == 'monitor' and val is True: + self._monitor_parameters.append(parameter) + elif attr == 'alias': + setattr(instr, val, parameter) + elif attr == 'initial_value': + # skip value attribute so that it gets set last + # when everything else has been set up + pass + else: + log.warning(f'Attribute {attr} no recognized when' + f' instatiating parameter \"{parameter.name}\"') + if 'initial_value' in options_dict: + parameter.set(options_dict['initial_value']) + + # setup existing parameters + for name, options in instr_cfg.get('parameters', {}).items(): + # get the parameter object from its name: + p = instr + for level in name.split('.'): + p = getattr(p, level) + setup_parameter_from_dict(p, options) + + # setup new parameters + for name, options in instr_cfg.get('add_parameters', {}).items(): + # allow only top level paremeters for now + # pop source only temporarily + source = options.pop('source', False) + if source: + source_p = instr + for level in source.split('.'): + source_p = getattr(source_p, level) + instr.add_parameter(name, + DelegateParameter, + source=source_p) + else: + instr.add_parameter(name, Parameter) + p = getattr(instr, name) + setup_parameter_from_dict(p, options) + # restore source + options['source'] = source + + # add the instrument to the station + self.add_component(instr) + + # restart Monitor + Monitor(*self._monitor_parameters) + + return instr From ef30dafd0670a6461a7f9ce09c1b010dba948530 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 10 Sep 2018 13:42:33 +0200 Subject: [PATCH 09/82] fixing get_raw/set_raw ci for `Parameter` --- qcodes/instrument/parameter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 8dce792024e..5f1f10082f8 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -855,7 +855,8 @@ def __init__(self, name: str, # Enable set/get methods if get_cmd/set_cmd is given # Called first so super().__init__ can wrap get/set methods - if not hasattr(self, 'get') and get_cmd is not False: + if (not (hasattr(self, 'get') or hasattr(self, 'get_raw')) + and get_cmd is not False): if get_cmd is None: if max_val_age is not None: raise SyntaxError('Must have get method or specify get_cmd ' @@ -866,7 +867,8 @@ def __init__(self, name: str, self.get_raw = Command(arg_count=0, cmd=get_cmd, exec_str=exec_str_ask) self.get = self._wrap_get(self.get_raw) - if not hasattr(self, 'set') and set_cmd is not False: + if (not (hasattr(self, 'set') or hasattr(self, 'set_raw')) + and set_cmd is not False): if set_cmd is None: self.set_raw = partial(self._save_val, validate=False)# type: Callable else: From 1201efe1de92f25cce11bd0bc7b25ce7b4ea3185 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 10 Sep 2018 15:16:34 +0200 Subject: [PATCH 10/82] disable warning of hidden method get_raw, set_raw --- qcodes/instrument/parameter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 5f1f10082f8..307a0287da7 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -1819,9 +1819,11 @@ def __init__(self, name: str, source: Parameter, *args, **kwargs): self.source = source super().__init__(name=name, *args, **kwargs) + # pylint: disable=method-hidden def get_raw(self, *args, **kwargs): return self.source.get(*args, **kwargs) + # pylint: disable=method-hidden def set_raw(self, *args, **kwargs): self.source(*args, **kwargs) From 0a2ba5f9a21aaf305fdf68759ffeefeb2cbb49fc Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 10 Sep 2018 15:45:45 +0200 Subject: [PATCH 11/82] revert changes to get/set wrapping and make comment explicit --- qcodes/instrument/parameter.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 307a0287da7..18f011b37f8 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -853,10 +853,12 @@ def __init__(self, name: str, **kwargs) -> None: super().__init__(name=name, instrument=instrument, vals=vals, **kwargs) - # Enable set/get methods if get_cmd/set_cmd is given - # Called first so super().__init__ can wrap get/set methods - if (not (hasattr(self, 'get') or hasattr(self, 'get_raw')) - and get_cmd is not False): + # Enable set/get methods from get_cmd/set_cmd if given and + # no `get`/`set` or `get_raw`/`set_raw` methods have been defined + # in the scope of this class. + # (previous call to `super().__init__` wraps existing get_raw/set_raw to + # get/set methods) + if not hasattr(self, 'get') and get_cmd is not False: if get_cmd is None: if max_val_age is not None: raise SyntaxError('Must have get method or specify get_cmd ' @@ -867,8 +869,7 @@ def __init__(self, name: str, self.get_raw = Command(arg_count=0, cmd=get_cmd, exec_str=exec_str_ask) self.get = self._wrap_get(self.get_raw) - if (not (hasattr(self, 'set') or hasattr(self, 'set_raw')) - and set_cmd is not False): + if not hasattr(self, 'set') and set_cmd is not False: if set_cmd is None: self.set_raw = partial(self._save_val, validate=False)# type: Callable else: From 1470363ae3a722a4df06d50366e42c1a65f683ba Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 10 Sep 2018 16:21:03 +0200 Subject: [PATCH 12/82] comment pylint disable --- qcodes/instrument/parameter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 18f011b37f8..0bec42dbb4a 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -1820,10 +1820,13 @@ def __init__(self, name: str, source: Parameter, *args, **kwargs): self.source = source super().__init__(name=name, *args, **kwargs) + # Disable the warnings until MultiParameter and ArrayParameter have been + # replaced and name/label/unit can live in _BaseParameter # pylint: disable=method-hidden def get_raw(self, *args, **kwargs): return self.source.get(*args, **kwargs) + # same as for `get_raw` # pylint: disable=method-hidden def set_raw(self, *args, **kwargs): self.source(*args, **kwargs) From df697a71e88d891294d29a1357532f5841532a70 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 10 Sep 2018 16:22:13 +0200 Subject: [PATCH 13/82] enable creation of station without config --- qcodes/station.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 4bb93cf4266..e4f0a5766ec 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -230,15 +230,18 @@ def load_config_file(self, filename: Optional[str] = None): with open(filename, 'r') as f: self._config = yaml.load(f) except FileNotFoundError as e: - if not os.path.isabs(filename) and DEFAULT_CONFIG_FOLDER is not None: - try: + try: + if not os.path.isabs(filename) and DEFAULT_CONFIG_FOLDER is not None: with open(os.path.join(DEFAULT_CONFIG_FOLDER, filename), 'r') as f: self._config = yaml.load(f) - except FileNotFoundError: + else: raise e - else: - raise e + except FileNotFoundError: + log.warning('Could not load default config for Station: ' + + e.msg) + return + self._instrument_config = self._config['instruments'] @@ -298,6 +301,8 @@ def load_instrument(self, identifier: str, return Instrument.find_instrument(identifier) # load file + # try to reload file on every call. This makes script execution a + # little slower but makes the overall workflow more convenient. self.load_config_file(self.config_file) # load from config From 313a8a3956b6caac3b9c47fdb61b260fbf307e6a Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 11 Sep 2018 13:03:47 +0200 Subject: [PATCH 14/82] fix additional `.station.` --- qcodes/station.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/station.py b/qcodes/station.py index 2f3fb6d8c5c..c7558834a11 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -327,7 +327,7 @@ def load_instrument(self, identifier: str, self._monitor_parameters = [v for v in self._monitor_parameters if v.root_instrument is not instr] # remove instrument from station snapshot - self.station.components.pop(instr.name) + self.components.pop(instr.name) # del will remove weakref and close the instrument instr.close() del instr From a24adac1f2218ca1900af3afa07e4529f65cac4d Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Tue, 11 Sep 2018 13:14:11 +0200 Subject: [PATCH 15/82] include Mikails changes for snapshotting and validation --- qcodes/instrument/parameter.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 0bec42dbb4a..65f16172b81 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -1818,6 +1818,24 @@ class DelegateParameter(Parameter): def __init__(self, name: str, source: Parameter, *args, **kwargs): self.source = source + + if 'unit' not in kwargs: + kwargs.update({'unit': self._source_parameter.unit}) + if 'label' not in kwargs: + kwargs.update({'label': self._source_parameter.label}) + if 'snapshot_value' not in kwargs: + kwargs.update( + {'snapshot_value': self._source_parameter._snapshot_value}) + + if 'set_cmd' in kwargs: + raise KeyError('It is not allowed to set "set_cmd" of a ' + 'DelegateParameter because the one of the source ' + 'parameter is supposed to be used.') + if 'get_cmd' in kwargs: + raise KeyError('It is not allowed to set "get_cmd" of a ' + 'DelegateParameter because the one of the source ' + 'parameter is supposed to be used.') + super().__init__(name=name, *args, **kwargs) # Disable the warnings until MultiParameter and ArrayParameter have been @@ -1831,3 +1849,14 @@ def get_raw(self, *args, **kwargs): def set_raw(self, *args, **kwargs): self.source(*args, **kwargs) + def snapshot_base(self, + update: bool = False, + params_to_skip_update: Sequence[str] = None): + snapshot = super().snapshot_base( + update=update, + params_to_skip_update=params_to_skip_update + ) + snapshot.update( + {'source_parameter': self._source_parameter.snapshot(update=update)} + ) + return snapshot From cbed36c8551f0ff476c8f0db4d21a6a107edffd8 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 11 Sep 2018 13:28:00 +0200 Subject: [PATCH 16/82] add DRYness --- qcodes/instrument/parameter.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 65f16172b81..58958997dd4 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -1819,22 +1819,15 @@ class DelegateParameter(Parameter): def __init__(self, name: str, source: Parameter, *args, **kwargs): self.source = source - if 'unit' not in kwargs: - kwargs.update({'unit': self._source_parameter.unit}) - if 'label' not in kwargs: - kwargs.update({'label': self._source_parameter.label}) - if 'snapshot_value' not in kwargs: - kwargs.update( - {'snapshot_value': self._source_parameter._snapshot_value}) - - if 'set_cmd' in kwargs: - raise KeyError('It is not allowed to set "set_cmd" of a ' - 'DelegateParameter because the one of the source ' - 'parameter is supposed to be used.') - if 'get_cmd' in kwargs: - raise KeyError('It is not allowed to set "get_cmd" of a ' - 'DelegateParameter because the one of the source ' - 'parameter is supposed to be used.') + for ka, param in zip(('unit', 'label', 'snapshot_value'), + ('unit', 'label', '_snapshot_value')): + kwargs[ka] = kwargs.get(ka, getattr(self._source_parameter, param)) + + for cmd in ('set_cmd', 'get_cmd'): + if cmd in kwargs: + raise KeyError(f'It is not allowed to set "{cmd}" of a ' + f'DelegateParameter because the one of the ' + f'source parameter is supposed to be used.') super().__init__(name=name, *args, **kwargs) From 1aad9ddf9c6acfb2f29c2b589f6f27044f06e68b Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 11 Sep 2018 16:34:53 +0200 Subject: [PATCH 17/82] remove whitespace --- qcodes/station.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/station.py b/qcodes/station.py index c7558834a11..38ad9555675 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -240,7 +240,7 @@ def load_config_file(self, filename: Optional[str] = None): else: raise e except FileNotFoundError: - log.warning('Could not load default config for Station: ' + + log.warning('Could not load default config for Station: ' + e.msg) return From 629ed829baddd6124e7306c1293e756cacf4e9f5 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 25 Apr 2019 15:38:01 +0200 Subject: [PATCH 18/82] remove pyyaml --- qcodes/station.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index ffcbd1b13c8..79dc7a82eaa 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -7,6 +7,7 @@ import warnings import os from copy import deepcopy +from ruamel.yaml import YAML from qcodes.utils.metadata import Metadatable from qcodes.utils.helpers import make_unique, DelegateAttributes @@ -21,15 +22,6 @@ from qcodes.actions import _actions_snapshot -use_pyyaml = False -try: - from ruamel.yaml import YAML -except ImportError: - use_pyyaml = True - warnings.warn( - "ruamel yaml not found station configurator is falling back to pyyaml. " - "It's highly recommended to install ruamel.yaml. This fixes issues with " - "scientific notation and duplicate instruments in the YAML file") log = logging.getLogger(__name__) @@ -256,32 +248,26 @@ def __getitem__(self, key): delegate_attr_dicts = ['components'] - def load_config_file(self, filename: Optional[str] = None): # 1. load config from file - if use_pyyaml: - import yaml - else: - yaml = YAML() if filename is None: filename = DEFAULT_CONFIG_FILE try: with open(filename, 'r') as f: - self._config = yaml.load(f) + self._config = YAML().load(f) except FileNotFoundError as e: try: if not os.path.isabs(filename) and DEFAULT_CONFIG_FOLDER is not None: with open(os.path.join(DEFAULT_CONFIG_FOLDER, filename), 'r') as f: - self._config = yaml.load(f) + self._config = YAML().load(f) else: raise e except FileNotFoundError: log.warning('Could not load default config for Station: ' + e.msg) return - self._instrument_config = self._config['instruments'] From 4986b293c8f7b9e46d8a82166cf2517fd63452d3 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 25 Apr 2019 16:11:09 +0200 Subject: [PATCH 19/82] extract YAML importer to qcodes utils --- qcodes/dataset/descriptions.py | 21 ++------------------- qcodes/tests/dataset/test_descriptions.py | 8 +------- qcodes/utils/helpers.py | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/qcodes/dataset/descriptions.py b/qcodes/dataset/descriptions.py index b8ff39ff057..3b759dde97e 100644 --- a/qcodes/dataset/descriptions.py +++ b/qcodes/dataset/descriptions.py @@ -4,6 +4,8 @@ from qcodes.dataset.dependencies import (InterDependencies, InterDependencies_) +from qcodes.utils.helpers import YAML + SomeDeps = Union[InterDependencies, InterDependencies_] @@ -60,26 +62,10 @@ def deserialize(cls, ser: Dict[str, Any]) -> 'RunDescriber': return rundesc - @staticmethod - def _ruamel_importer(): - try: - from ruamel_yaml import YAML - except ImportError: - try: - from ruamel.yaml import YAML - except ImportError: - raise ImportError('No ruamel module found. Please install ' - 'either ruamel.yaml or ruamel_yaml to ' - 'use the methods to_yaml and from_yaml') - return YAML - def to_yaml(self) -> str: """ Output the run description as a yaml string """ - - YAML = self._ruamel_importer() - yaml = YAML() with io.StringIO() as stream: yaml.dump(self.serialize(), stream=stream) @@ -99,9 +85,6 @@ def from_yaml(cls, yaml_str: str) -> 'RunDescriber': Parse a yaml string (the return of `to_yaml`) into a RunDescriber object """ - - YAML = cls._ruamel_importer() - yaml = YAML() # yaml.load returns an OrderedDict, but we need a dict ser = dict(yaml.load(yaml_str)) diff --git a/qcodes/tests/dataset/test_descriptions.py b/qcodes/tests/dataset/test_descriptions.py index 8c3dce2dfb4..0b61581c101 100644 --- a/qcodes/tests/dataset/test_descriptions.py +++ b/qcodes/tests/dataset/test_descriptions.py @@ -4,6 +4,7 @@ from qcodes.dataset.descriptions import RunDescriber from qcodes.dataset.dependencies import InterDependencies +from qcodes.utils.helpers import YAML @pytest.fixture def some_paramspecs(): @@ -86,14 +87,7 @@ def test_serialization_and_back(some_paramspecs): def test_yaml_creation_and_loading(some_paramspecs): - - try: - YAML = RunDescriber._ruamel_importer() - except ImportError: - pytest.skip('No ruamel module installed, skipping test') - yaml = YAML() - for group in some_paramspecs.values(): paramspecs = group.values() idp = InterDependencies(*paramspecs) diff --git a/qcodes/utils/helpers.py b/qcodes/utils/helpers.py index a4703c1248d..90712dbd7ec 100644 --- a/qcodes/utils/helpers.py +++ b/qcodes/utils/helpers.py @@ -703,3 +703,18 @@ def abstractmethod(funcobj): funcobj.__qcodes_is_abstract_method__ = True return funcobj + +def _ruamel_importer(): + try: + from ruamel_yaml import YAML + except ImportError: + try: + from ruamel.yaml import YAML + except ImportError: + raise ImportError('No ruamel module found. Please install ' + 'either ruamel.yaml or ruamel_yaml to ' + 'use the methods to_yaml and from_yaml') + return YAML + + +YAML = _ruamel_importer() From ad03d8eb050e01d8a0e7442d624bb358409af7c5 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 25 Apr 2019 16:12:30 +0200 Subject: [PATCH 20/82] give station some code love --- qcodes/station.py | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 79dc7a82eaa..9e7082cc6bd 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -4,18 +4,15 @@ from functools import partial import importlib import logging -import warnings import os from copy import deepcopy -from ruamel.yaml import YAML from qcodes.utils.metadata import Metadatable -from qcodes.utils.helpers import make_unique, DelegateAttributes +from qcodes.utils.helpers import make_unique, DelegateAttributes, YAML from qcodes.instrument.base import Instrument -from qcodes.instrument.parameter import Parameter -from qcodes.instrument.parameter import ManualParameter -from qcodes.instrument.parameter import StandardParameter +from qcodes.instrument.parameter import ( + Parameter, ManualParameter, StandardParameter, DelegateParameter) import qcodes.utils.validators as validators from qcodes.monitor.monitor import Monitor from qcodes.config.config import Config @@ -66,12 +63,12 @@ class Station(Metadatable, DelegateAttributes): treated as attributes of self """ - default = None # type: 'Station' + default = None # type: 'Station' def __init__(self, *components: Metadatable, - config_file: Optional[str]=None, - monitor: Monitor=None, default: bool=True, - update_snapshot: bool=True, **kwargs) -> None: + config_file: Optional[str] = None, + monitor: Monitor = None, default: bool = True, + update_snapshot: bool = True, **kwargs) -> None: super().__init__(**kwargs) # when a new station is defined, store it in a class variable @@ -83,7 +80,7 @@ def __init__(self, *components: Metadatable, if default: Station.default = self - self.components = {} # type: Dict[str, Metadatable] + self.components: Dict[str, Metadatable] = {} for item in components: self.add_component(item, update_snapshot=update_snapshot) @@ -96,9 +93,8 @@ def __init__(self, *components: Metadatable, self.load_config_file(self.config_file) - - def snapshot_base(self, update: bool=False, - params_to_skip_update: Sequence[str]=None) -> Dict: + def snapshot_base(self, update: bool = False, + params_to_skip_update: Sequence[str] = None) -> Dict: """ State of the station as a JSON-compatible dict. @@ -249,10 +245,8 @@ def __getitem__(self, key): delegate_attr_dicts = ['components'] def load_config_file(self, filename: Optional[str] = None): - # 1. load config from file - if filename is None: - filename = DEFAULT_CONFIG_FILE + filename = filename or DEFAULT_CONFIG_FILE try: with open(filename, 'r') as f: self._config = YAML().load(f) From b447456bb992f9cc6edc4b36ae98d66305e1c083 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Fri, 26 Apr 2019 16:14:09 +0200 Subject: [PATCH 21/82] refactor load_config_file --- qcodes/station.py | 109 ++++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 9e7082cc6bd..18c35a8dd37 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -245,62 +245,67 @@ def __getitem__(self, key): delegate_attr_dicts = ['components'] def load_config_file(self, filename: Optional[str] = None): - # 1. load config from file - filename = filename or DEFAULT_CONFIG_FILE - try: - with open(filename, 'r') as f: - self._config = YAML().load(f) - except FileNotFoundError as e: - try: - if not os.path.isabs(filename) and DEFAULT_CONFIG_FOLDER is not None: - with open(os.path.join(DEFAULT_CONFIG_FOLDER, filename), - 'r') as f: - self._config = YAML().load(f) + def get_config_file_path( + filename: Optional[str] = None) -> Optional[str]: + filename = filename or DEFAULT_CONFIG_FILE + search_list = [filename] + if (not os.path.isabs(filename) and + DEFAULT_CONFIG_FOLDER is not None): + search_list += os.path.join(DEFAULT_CONFIG_FOLDER, filename) + for p in search_list: + if os.path.isfile(p): + return p + return None + + def _update_station_configuration_snapshot(self): + class ConfigComponent: + def __init__(self, data): + self.data = data + + def snapshot(self, update=True): + return self.data + + self.components['StationConfigurator'] = ConfigComponent( + self._config) + + def _update_load_instrument_methods(self): + # create shortcut methods to instantiate instruments via + # `load_()` so that autocompletion can be used + # first remove methods that have been added by a previous + # `load_config_file` call + while len(self._added_methods): + delattr(self, self._added_methods.pop()) + + # add shortcut methods + for instrument_name in self._instrument_config.keys(): + method_name = f'load_{instrument_name}' + if method_name.isidentifier(): + setattr(self, method_name, partial( + self.load_instrument, identifier=instrument_name)) + self._added_methods.append(method_name) else: - raise e - except FileNotFoundError: - log.warning('Could not load default config for Station: ' + - e.msg) + log.warning(f'Invalid identifier: ' + + f'for the instrument {instrument_name} no ' + + f'lazy loading method {method_name} could ' + + 'be created in the StationConfigurator') + + path = get_config_file_path(filename) + if path is None: + if filename is not None: + raise FileNotFoundError(path) + else: + log.warning( + 'Could not load default instrument config for Station: \n' + f'File {DEFAULT_CONFIG_FILE} not found. \n' + 'You can change the default config file in ' + '`qcodesrc.json`.') return + with open(path, 'r') as f: + self._config = YAML().load(f) self._instrument_config = self._config['instruments'] - - # 2. add config to snapshot componenents - class ConfigComponent: - def __init__(self, data): - self.data = data - - def snapshot(self, update=True): - return self.data - - # this overwrites any previous configuration - # but does invoke snapshoting - self.components['StationConfigurator'] = ConfigComponent(self._config) - - - # 3. create shortcut methods to instantiate instruments via - # `load_()` so that autocompletion can be used - # first remove methods that have been added by a previous - # `load_config_file` call - while len(self._added_methods): - delattr(self, self._added_methods.pop()) - - # add shortcut methods - for instrument_name in self._instrument_config.keys(): - # TODO: check if name is valid (does not start with digit, contain - # dot, other signs etc.) - method_name = f'load_{instrument_name}' - if method_name.isidentifier(): - setattr(self, method_name, partial( - self.load_instrument, identifier=instrument_name)) - - self._added_methods.append(method_name) - else: - log.warning(f'Invalid identifier: ' + - f'for the instrument {instrument_name} no ' + - f'lazy loading method {method_name} could be ' + - 'created in the StationConfigurator') - + self._update_station_configuration_snapshot() + self._update_load_instrument_methods() def load_instrument(self, identifier: str, revive_instance: bool=False, From e4adf76ad7e8571ba13989ef8d394936243f048f Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 30 Apr 2019 13:47:06 +0200 Subject: [PATCH 22/82] load config dynamically, extract load_config (from string) --- qcodes/station.py | 71 ++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 18c35a8dd37..7bcb36b624c 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -6,7 +6,10 @@ import logging import os from copy import deepcopy +from io import IOBase +from typing import Union +import qcodes from qcodes.utils.metadata import Metadatable from qcodes.utils.helpers import make_unique, DelegateAttributes, YAML @@ -15,23 +18,28 @@ Parameter, ManualParameter, StandardParameter, DelegateParameter) import qcodes.utils.validators as validators from qcodes.monitor.monitor import Monitor -from qcodes.config.config import Config from qcodes.actions import _actions_snapshot log = logging.getLogger(__name__) -# config from the qcodesrc.json file (working directory, home or qcodes dir) -cfg = Config() -ENABLE_FORCED_RECONNECT = cfg["station_configurator"]["enable_forced_reconnect"] -DEFAULT_CONFIG_FOLDER = cfg["station_configurator"]["default_folder"] -DEFAULT_CONFIG_FILE = cfg["station_configurator"]["default_file"] - PARAMETER_ATTRIBUTES = ['label', 'unit', 'scale', 'inter_delay', 'delay', 'step', 'offset'] +def get_config_enable_forced_reconnect() -> bool: + return qcodes.config["station_configurator"]["enable_forced_reconnect"] + + +def get_config_default_folder() -> Optional[str]: + return qcodes.config["station_configurator"]["default_folder"] + + +def get_config_default_file() -> Optional[str]: + return qcodes.config["station_configurator"]["default_file"] + + class Station(Metadatable, DelegateAttributes): """ @@ -247,17 +255,35 @@ def __getitem__(self, key): def load_config_file(self, filename: Optional[str] = None): def get_config_file_path( filename: Optional[str] = None) -> Optional[str]: - filename = filename or DEFAULT_CONFIG_FILE + filename = filename or get_config_default_file() search_list = [filename] if (not os.path.isabs(filename) and - DEFAULT_CONFIG_FOLDER is not None): - search_list += os.path.join(DEFAULT_CONFIG_FOLDER, filename) + get_config_default_folder() is not None): + search_list += [os.path.join(get_config_default_folder(), + filename)] for p in search_list: if os.path.isfile(p): return p return None - def _update_station_configuration_snapshot(self): + + path = get_config_file_path(filename) + if path is None: + if filename is not None: + raise FileNotFoundError(path) + else: + log.warning( + 'Could not load default instrument config for Station: \n' + f'File {get_config_default_file()} not found. \n' + 'You can change the default config file in ' + '`qcodesrc.json`.') + return + + with open(path, 'r') as f: + self.load_config(f) + + def load_config(self, config: Union[str, IOBase]) -> None: + def update_station_configuration_snapshot(): class ConfigComponent: def __init__(self, data): self.data = data @@ -268,7 +294,7 @@ def snapshot(self, update=True): self.components['StationConfigurator'] = ConfigComponent( self._config) - def _update_load_instrument_methods(self): + def update_load_instrument_methods(): # create shortcut methods to instantiate instruments via # `load_()` so that autocompletion can be used # first remove methods that have been added by a previous @@ -288,24 +314,11 @@ def _update_load_instrument_methods(self): f'for the instrument {instrument_name} no ' + f'lazy loading method {method_name} could ' + 'be created in the StationConfigurator') - - path = get_config_file_path(filename) - if path is None: - if filename is not None: - raise FileNotFoundError(path) - else: - log.warning( - 'Could not load default instrument config for Station: \n' - f'File {DEFAULT_CONFIG_FILE} not found. \n' - 'You can change the default config file in ' - '`qcodesrc.json`.') - return - - with open(path, 'r') as f: - self._config = YAML().load(f) + self._config = YAML().load(config) self._instrument_config = self._config['instruments'] - self._update_station_configuration_snapshot() - self._update_load_instrument_methods() + update_station_configuration_snapshot() + update_load_instrument_methods() + def load_instrument(self, identifier: str, revive_instance: bool=False, From 1156e8b876f897385e61c9f53a889d7aa727e432 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 30 Apr 2019 13:48:02 +0200 Subject: [PATCH 23/82] fix enable_forced_reconnect bug --- qcodes/station.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 7bcb36b624c..77181611dfc 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -353,8 +353,8 @@ def load_instrument(self, identifier: str, # to report them # check if instrument is already defined and close connection - if instr_cfg.get('ENABLE_FORCED_RECONNECT', - ENABLE_FORCED_RECONNECT): + if instr_cfg.get('enable_forced_reconnect', + get_config_enable_forced_reconnect()): # save close instrument and remove from monitor list with suppress(KeyError): instr = Instrument.find_instrument(identifier) @@ -372,7 +372,7 @@ def load_instrument(self, identifier: str, module = importlib.import_module(instr_cfg['driver']) instr_class = getattr(module, instr_cfg['type']) - init_kwargs = instr_cfg.get('init',{}) + init_kwargs = instr_cfg.get('init', {}) # somebody might have a empty init section in the config init_kwargs = {} if init_kwargs is None else init_kwargs if 'address' in instr_cfg: From c1844f9eadbc1c59422df36c8604e77bcf45aae3 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 30 Apr 2019 13:48:48 +0200 Subject: [PATCH 24/82] add tests: instatiation, snapshot, forced_reconnect --- qcodes/tests/test_station.py | 160 +++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 3ffcefb886e..b67e0e1674e 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -1,11 +1,20 @@ import pytest +import tempfile +from pathlib import Path +from typing import Union +import qcodes from qcodes import Instrument from qcodes.station import Station from qcodes.tests.instrument_mocks import DummyInstrument from qcodes.tests.test_combined_par import DumyPar from qcodes.instrument.parameter import Parameter +from qcodes.tests.test_config import default_config +@pytest.fixture(autouse=True) +def use_default_config(): + with default_config(): + yield @pytest.fixture(autouse=True) def set_default_station_to_none(): @@ -201,3 +210,154 @@ def test_station_after_instrument_is_closed(): with pytest.raises(KeyError, match='Component bob is not part of the ' 'station'): station.remove_component('bob') + +@pytest.fixture +def example_station_config() -> str: + """ + Returns path to temp yaml file with station config. + """ + sims_path = f'{qcodes.__path__[0]}\\instrument\\sims\\' + test_config = f""" +instruments: + lakeshore: + driver: qcodes.instrument_drivers.Lakeshore.Model_336 + type: Model_336 + enable_forced_reconnect: true + address: GPIB::2::65535::INSTR + init: + visalib: '{sims_path}Lakeshore_model336.yaml@sim' + mock_dac: + driver: qcodes.tests.instrument_mocks + type: DummyInstrument + enable_forced_reconnect: true + init: + gates: {{"ch1", "ch2"}} + mock_dac2: + driver: qcodes.tests.instrument_mocks + type: DummyInstrument + """ + with tempfile.TemporaryDirectory() as tmpdirname: + filename = Path(tmpdirname, 'station_config.yaml') + with filename.open('w') as f: + f.write(test_config) + yield str(filename) + +def station_from_config_str(config: str) -> Station: + st = Station(config_file=None) + st.load_config(config) + return st + + +def has_station_config_been_loaded(st: Station) -> bool: + return "StationConfigurator" in st.components.keys() + + +@pytest.fixture +def example_station(example_station_config) -> Station: + return Station(config_file=example_station_config) + + +# instrument loading related tests +def test_station_config_path_resolution(example_station_config): + config = qcodes.config["station_configurator"] + + assert not has_station_config_been_loaded(Station()) + + path = Path(example_station_config) + config["default_file"] = str(path) + assert has_station_config_been_loaded(Station()) + + config["default_file"] = path.name + config["default_folder"] = str(path.parent) + assert has_station_config_been_loaded(Station()) + + config["default_file"] = 'random.yml' + config["default_folder"] = str(path.parent) + assert not has_station_config_been_loaded(Station()) + + config["default_file"] = str(path) + config["default_folder"] = r'C:\SomeOtherFolder' + assert has_station_config_been_loaded(Station()) + + config["default_file"] = None + config["default_folder"] = str(path.parent) + assert has_station_config_been_loaded(Station(config_file=path.name)) + + config["default_file"] = None + config["default_folder"] = None + assert has_station_config_been_loaded(Station(config_file=str(path))) + + +def test_station_creation(example_station): + assert "StationConfigurator" in example_station.components.keys() + +SIMPLE_MOCK_CONFIG = """ +instruments: + mock: + driver: qcodes.tests.instrument_mocks + type: DummyInstrument +""" + +@pytest.fixture +def simple_mock_station(example_station_config) -> Station: + yield station_from_config_str(SIMPLE_MOCK_CONFIG) + +def test_simple_mock_config(simple_mock_station): + st = simple_mock_station + assert has_station_config_been_loaded(st) + assert hasattr(st, 'load_mock') + mock_snapshot = st.snapshot()['components']['StationConfigurator']\ + ['instruments']['mock'] + assert mock_snapshot['driver'] == "qcodes.tests.instrument_mocks" + assert mock_snapshot['type'] == "DummyInstrument" + + +def test_simple_mock_load_mock(simple_mock_station): + st = simple_mock_station + mock = st.load_mock() + assert type(mock) is DummyInstrument + + +def test_simple_mock_load_instrument(simple_mock_station): + st = simple_mock_station + mock = st.load_instrument('mock') + assert type(mock) is DummyInstrument + + +def test_enable_force_reconnect() -> None: + def get_instrument_config(enable_forced_reconnect: Union[bool, None]) -> str: + return f""" +instruments: + mock: + driver: qcodes.tests.instrument_mocks + type: DummyInstrument + {f'enable_forced_reconnect: {enable_forced_reconnect}' + if enable_forced_reconnect is not None else ''} + init: + gates: {{"ch1", "ch2"}} + """ + + def assert_on_reconnect(user_config_val: Union[bool, None], + instrument_config_val: Union[bool, None], + expect_failure: bool) -> None: + qcodes.config["station_configurator"]\ + ['enable_forced_reconnect'] = user_config_val + st = station_from_config_str( + get_instrument_config(instrument_config_val)) + st.load_instrument('mock') + if expect_failure: + with pytest.raises(KeyError) as excinfo: + st.load_instrument('mock') + assert ("Another instrument has the name: mock" + in str(excinfo.value)) + else: + st.load_instrument('mock') + Instrument.close_all() + + for user_config_val in [None, True, False]: + assert_on_reconnect(user_config_val, False, True) + assert_on_reconnect(user_config_val, True, False) + + assert_on_reconnect(True, None, False) + assert_on_reconnect(False, None, True) + assert_on_reconnect(None, None, True) From 36d8fe557427db85144441fcc51ff156b5705c36 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 30 Apr 2019 16:50:20 +0200 Subject: [PATCH 25/82] fix delegate parameter --- qcodes/instrument/parameter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 0d191778698..ded10a7ee66 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -2033,7 +2033,7 @@ def __init__(self, name: str, source: Parameter, *args, **kwargs): for ka, param in zip(('unit', 'label', 'snapshot_value'), ('unit', 'label', '_snapshot_value')): - kwargs[ka] = kwargs.get(ka, getattr(self._source_parameter, param)) + kwargs[ka] = kwargs.get(ka, getattr(self.source, param)) for cmd in ('set_cmd', 'get_cmd'): if cmd in kwargs: @@ -2062,7 +2062,7 @@ def snapshot_base(self, params_to_skip_update=params_to_skip_update ) snapshot.update( - {'source_parameter': self._source_parameter.snapshot(update=update)} + {'source_parameter': self.source.snapshot(update=update)} ) return snapshot From f3617f2d5ed34addca782b37185a073b5cac7dbe Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 30 Apr 2019 16:51:16 +0200 Subject: [PATCH 26/82] improve error message for instrument not found in config --- qcodes/station.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 77181611dfc..a42538d19d4 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -345,8 +345,8 @@ def load_instrument(self, identifier: str, # load from config if identifier not in self._instrument_config.keys(): - raise RuntimeError('Instrument {} not found in config.' - .format(identifier)) + raise RuntimeError(f'Instrument {identifier} not found in ' + 'instrument config file') instr_cfg = self._instrument_config[identifier] # config is not parsed for errors. On errors qcodes should be able to From e43ccf15fad4b2c40a73f240876bcbb3e0b99e53 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 30 Apr 2019 16:52:05 +0200 Subject: [PATCH 27/82] enable to change instrument name on init --- qcodes/station.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qcodes/station.py b/qcodes/station.py index a42538d19d4..72bd515051c 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -389,8 +389,9 @@ def load_instrument(self, identifier: str, # instrument instances via kwargs instr_kwargs = deepcopy(init_kwargs) instr_kwargs.update(kwargs) + name = instr_kwargs.pop('name', identifier) - instr = instr_class(name=identifier, **instr_kwargs) + instr = instr_class(name, **instr_kwargs) # setup # local function to refactor common code from defining new parameter From 960ff0fa07abafc470fbf7ddc98a65fd990c0380 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 30 Apr 2019 16:52:30 +0200 Subject: [PATCH 28/82] add a bunch of tests --- qcodes/tests/test_station.py | 158 ++++++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 1 deletion(-) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index b67e0e1674e..8fe62f1398b 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -1,12 +1,16 @@ import pytest import tempfile +import json from pathlib import Path from typing import Union import qcodes +import qcodes.utils.validators as validators +from qcodes.instrument.parameter import DelegateParameter from qcodes import Instrument from qcodes.station import Station -from qcodes.tests.instrument_mocks import DummyInstrument +from qcodes.tests.instrument_mocks import ( + DummyInstrument, DummyChannelInstrument) from qcodes.tests.test_combined_par import DumyPar from qcodes.instrument.parameter import Parameter from qcodes.tests.test_config import default_config @@ -316,12 +320,16 @@ def test_simple_mock_load_mock(simple_mock_station): st = simple_mock_station mock = st.load_mock() assert type(mock) is DummyInstrument + assert mock.name == 'mock' + assert st.components['mock'] is mock def test_simple_mock_load_instrument(simple_mock_station): st = simple_mock_station mock = st.load_instrument('mock') assert type(mock) is DummyInstrument + assert mock.name == 'mock' + assert st.components['mock'] is mock def test_enable_force_reconnect() -> None: @@ -361,3 +369,151 @@ def assert_on_reconnect(user_config_val: Union[bool, None], assert_on_reconnect(True, None, False) assert_on_reconnect(False, None, True) assert_on_reconnect(None, None, True) + + +def test_init_parameters(): + st = station_from_config_str(""" +instruments: + mock: + driver: qcodes.tests.instrument_mocks + type: DummyInstrument + enable_forced_reconnect: true + init: + gates: {"ch1", "ch2"} + """) + mock = st.load_instrument('mock') + for ch in ["ch1", "ch2"]: + assert ch in mock.parameters.keys() + assert len(mock.parameters) == 3 # there is also IDN + + # Overwrite parameter + mock = st.load_instrument('mock', gates=["TestGate"]) + assert "TestGate" in mock.parameters.keys() + assert len(mock.parameters) == 2 # there is also IDN + + # special case of `name` + mock = st.load_instrument('mock', name='test') + assert mock.name == 'test' + assert st.components['test'] is mock + + # test address + sims_path = f'{qcodes.__path__[0]}\\instrument\\sims\\' + st = station_from_config_str(f""" +instruments: + lakeshore: + driver: qcodes.instrument_drivers.Lakeshore.Model_336 + type: Model_336 + enable_forced_reconnect: true + address: GPIB::2::INSTR + init: + visalib: '{sims_path}Lakeshore_model336.yaml@sim' + """) + ls = st.load_instrument('lakeshore') + + +def test_setup_alias_parameters(): + st = station_from_config_str(""" +instruments: + mock: + driver: qcodes.tests.instrument_mocks + type: DummyInstrument + enable_forced_reconnect: true + init: + gates: {"ch1"} + parameters: + ch1: + unit: mV + label: main gate + scale: 2 + offset: 1 + limits: -10, 10 + alias: gate_a + initial_value: 9 + + """) + mock = st.load_instrument('mock') + p = getattr(mock, 'gate_a') + assert isinstance(p, qcodes.Parameter) + assert p.unit == 'mV' + assert p.label == 'main gate' + assert p.scale == 2 + assert p.offset == 1 + assert isinstance(p.vals, validators.Numbers) + assert str(p.vals) == '' + assert p() == 9 + mock.ch1(1) + assert p() == 1 + p(3) + assert mock.ch1() == 3 + assert p.raw_value == 7 + assert mock.ch1.raw_value == 7 + +def test_setup_delegate_parameters(): + st = station_from_config_str(""" +instruments: + mock: + driver: qcodes.tests.instrument_mocks + type: DummyInstrument + enable_forced_reconnect: true + init: + gates: {"ch1"} + parameters: + ch1: + unit: V + label: ch1 + scale: 1 + offset: 0 + limits: -10, 10 + add_parameters: + gate_a: + source: ch1 + unit: mV + label: main gate + scale: 2 + offset: 1 + limits: -6, 6 + initial_value: 2 + + """) + mock = st.load_instrument('mock') + p = getattr(mock, 'gate_a') + assert isinstance(p, DelegateParameter) + assert p.unit == 'mV' + assert p.label == 'main gate' + assert p.scale == 2 + assert p.offset == 1 + assert isinstance(p.vals, validators.Numbers) + assert str(p.vals) == '' + assert p() == 2 + assert mock.ch1.unit == 'V' + assert mock.ch1.label == 'ch1' + assert mock.ch1.scale == 1 + assert mock.ch1.offset == 0 + assert isinstance(p.vals, validators.Numbers) + assert str(mock.ch1.vals) == '' + assert mock.ch1() == 5 + mock.ch1(7) + assert p() == 3 + assert p.raw_value == 7 + assert mock.ch1.raw_value == 7 + assert (json.dumps(mock.ch1.snapshot()) == + json.dumps(p.snapshot()['source_parameter'])) + + +def test_channel_instrument(): + st = station_from_config_str(""" +instruments: + mock: + driver: qcodes.tests.instrument_mocks + type: DummyChannelInstrument + enable_forced_reconnect: true + parameters: + A.temperature: + unit: mK + add_parameters: + T: + source: A.temperature + """) + mock = st.load_instrument('mock') + assert mock.A.temperature.unit == 'mK' + assert mock.T.unit == 'mK' From 8b1adb09f07123a888d03d581f4543dda934843d Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Wed, 1 May 2019 11:40:27 +0200 Subject: [PATCH 29/82] Introduce `get_qcodes_path` which is ci save --- qcodes/tests/test_station.py | 5 +++-- qcodes/utils/helpers.py | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 8fe62f1398b..50f39204a34 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -6,6 +6,7 @@ import qcodes import qcodes.utils.validators as validators +from qcodes.utils.helpers import get_qcodes_path from qcodes.instrument.parameter import DelegateParameter from qcodes import Instrument from qcodes.station import Station @@ -216,11 +217,11 @@ def test_station_after_instrument_is_closed(): station.remove_component('bob') @pytest.fixture -def example_station_config() -> str: +def example_station_config(): """ Returns path to temp yaml file with station config. """ - sims_path = f'{qcodes.__path__[0]}\\instrument\\sims\\' + sims_path = get_qcodes_path('instrument', 'sims') test_config = f""" instruments: lakeshore: diff --git a/qcodes/utils/helpers.py b/qcodes/utils/helpers.py index 90712dbd7ec..65e18e0d7c6 100644 --- a/qcodes/utils/helpers.py +++ b/qcodes/utils/helpers.py @@ -16,6 +16,7 @@ import numpy as np +import qcodes from qcodes.utils.deprecate import deprecate @@ -718,3 +719,8 @@ def _ruamel_importer(): YAML = _ruamel_importer() + + +def get_qcodes_path(*subfolder) -> str: + path = os.sep.join(qcodes.__file__.split(os.sep)[:-1]) + return os.path.join(path, *subfolder) From 22489dcaf9fe9ba87879703ece40dda3b79d0528 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Wed, 1 May 2019 11:42:27 +0200 Subject: [PATCH 30/82] Fix typing --- qcodes/instrument/parameter.py | 2 +- qcodes/station.py | 40 +++++++++++++++++++++------------- qcodes/tests/test_station.py | 4 ++-- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index ded10a7ee66..723523e5010 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -2041,7 +2041,7 @@ def __init__(self, name: str, source: Parameter, *args, **kwargs): f'DelegateParameter because the one of the ' f'source parameter is supposed to be used.') - super().__init__(name=name, *args, **kwargs) + super().__init__(name, *args, **kwargs) # Disable the warnings until MultiParameter and ArrayParameter have been # replaced and name/label/unit can live in _BaseParameter diff --git a/qcodes/station.py b/qcodes/station.py index 72bd515051c..e1fd766bb68 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -1,6 +1,6 @@ """Station objects - collect all the equipment you use to do an experiment.""" from contextlib import suppress -from typing import Dict, List, Optional, Sequence, Any +from typing import Dict, List, Optional, Sequence, Any, cast, AnyStr, IO from functools import partial import importlib import logging @@ -15,7 +15,8 @@ from qcodes.instrument.base import Instrument from qcodes.instrument.parameter import ( - Parameter, ManualParameter, StandardParameter, DelegateParameter) + Parameter, ManualParameter, StandardParameter, + DelegateParameter) import qcodes.utils.validators as validators from qcodes.monitor.monitor import Monitor @@ -97,7 +98,7 @@ def __init__(self, *components: Metadatable, self.default_measurement: List[Any] = [] self._added_methods: List[str] = [] - self._monitor_parameters = [] + self._monitor_parameters: List[Parameter] = [] self.load_config_file(self.config_file) @@ -256,11 +257,13 @@ def load_config_file(self, filename: Optional[str] = None): def get_config_file_path( filename: Optional[str] = None) -> Optional[str]: filename = filename or get_config_default_file() + if filename is None: + return None search_list = [filename] if (not os.path.isabs(filename) and get_config_default_folder() is not None): - search_list += [os.path.join(get_config_default_folder(), - filename)] + config_folder = cast(str, get_config_default_folder()) + search_list += [os.path.join(config_folder, filename)] for p in search_list: if os.path.isfile(p): return p @@ -282,7 +285,7 @@ def get_config_file_path( with open(path, 'r') as f: self.load_config(f) - def load_config(self, config: Union[str, IOBase]) -> None: + def load_config(self, config: Union[str, IO[AnyStr]]) -> None: def update_station_configuration_snapshot(): class ConfigComponent: def __init__(self, data): @@ -396,7 +399,7 @@ def load_instrument(self, identifier: str, # local function to refactor common code from defining new parameter # and setting existing one - def setup_parameter_from_dict(parameter, options_dict): + def setup_parameter_from_dict(parameter: Parameter, options_dict): for attr, val in options_dict.items(): if attr in PARAMETER_ATTRIBUTES: # set the attributes of the parameter, that map 1 to 1 @@ -419,13 +422,22 @@ def setup_parameter_from_dict(parameter, options_dict): if 'initial_value' in options_dict: parameter.set(options_dict['initial_value']) + def resolve_parameter_identifier(instrument: Instrument, + identifier: str) -> Parameter: + p = instrument + for level in identifier.split('.'): + p = getattr(p, level) + if not isinstance(p, Parameter): + raise RuntimeError( + f'Cannot resolve parameter identifier `{name}` to ' + 'a parameter.') + return cast(Parameter, p) + # setup existing parameters for name, options in instr_cfg.get('parameters', {}).items(): - # get the parameter object from its name: - p = instr - for level in name.split('.'): - p = getattr(p, level) - setup_parameter_from_dict(p, options) + setup_parameter_from_dict( + resolve_parameter_identifier(instr, name), + options) # setup new parameters for name, options in instr_cfg.get('add_parameters', {}).items(): @@ -433,9 +445,7 @@ def setup_parameter_from_dict(parameter, options_dict): # pop source only temporarily source = options.pop('source', False) if source: - source_p = instr - for level in source.split('.'): - source_p = getattr(source_p, level) + source_p = resolve_parameter_identifier(instr, source) instr.add_parameter(name, DelegateParameter, source=source_p) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 50f39204a34..4ba545e7a4b 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -258,7 +258,7 @@ def has_station_config_been_loaded(st: Station) -> bool: @pytest.fixture -def example_station(example_station_config) -> Station: +def example_station(example_station_config): return Station(config_file=example_station_config) @@ -304,7 +304,7 @@ def test_station_creation(example_station): """ @pytest.fixture -def simple_mock_station(example_station_config) -> Station: +def simple_mock_station(example_station_config): yield station_from_config_str(SIMPLE_MOCK_CONFIG) def test_simple_mock_config(simple_mock_station): From 56065c42500174bca57b6a4877aab3655a966558 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 2 May 2019 10:42:34 +0200 Subject: [PATCH 31/82] Apply fixes from codacy --- qcodes/station.py | 1 - qcodes/tests/test_station.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index e1fd766bb68..48988c4856f 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -6,7 +6,6 @@ import logging import os from copy import deepcopy -from io import IOBase from typing import Union import qcodes diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 4ba545e7a4b..b719cb17117 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -320,7 +320,7 @@ def test_simple_mock_config(simple_mock_station): def test_simple_mock_load_mock(simple_mock_station): st = simple_mock_station mock = st.load_mock() - assert type(mock) is DummyInstrument + assert isinstance(mock, DummyInstrument) assert mock.name == 'mock' assert st.components['mock'] is mock @@ -328,7 +328,7 @@ def test_simple_mock_load_mock(simple_mock_station): def test_simple_mock_load_instrument(simple_mock_station): st = simple_mock_station mock = st.load_instrument('mock') - assert type(mock) is DummyInstrument + assert isinstance(mock, DummyInstrument) assert mock.name == 'mock' assert st.components['mock'] is mock @@ -409,7 +409,7 @@ def test_init_parameters(): init: visalib: '{sims_path}Lakeshore_model336.yaml@sim' """) - ls = st.load_instrument('lakeshore') + st.load_instrument('lakeshore') def test_setup_alias_parameters(): From c4a143802833ec0261f65e27f78a24d49e980e18 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 2 May 2019 10:52:20 +0200 Subject: [PATCH 32/82] Remove unused import --- qcodes/tests/test_station.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index b719cb17117..3d0a8976a66 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -11,7 +11,7 @@ from qcodes import Instrument from qcodes.station import Station from qcodes.tests.instrument_mocks import ( - DummyInstrument, DummyChannelInstrument) + DummyInstrument) from qcodes.tests.test_combined_par import DumyPar from qcodes.instrument.parameter import Parameter from qcodes.tests.test_config import default_config From 8cef3b094a4a09f98040e9ab02efeefde8db6279 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 2 May 2019 11:23:39 +0200 Subject: [PATCH 33/82] Fix signatures of `get_raw` and `set_raw` --- qcodes/instrument/parameter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 723523e5010..0bf3ceb0023 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -2046,13 +2046,13 @@ def __init__(self, name: str, source: Parameter, *args, **kwargs): # Disable the warnings until MultiParameter and ArrayParameter have been # replaced and name/label/unit can live in _BaseParameter # pylint: disable=method-hidden - def get_raw(self, *args, **kwargs): - return self.source.get(*args, **kwargs) + def get_raw(self): + return self.source.get() # same as for `get_raw` # pylint: disable=method-hidden - def set_raw(self, *args, **kwargs): - self.source(*args, **kwargs) + def set_raw(self, value): + self.source(value) def snapshot_base(self, update: bool = False, From 011492a886307af433459d9c8c4b2c9d9976be97 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 2 May 2019 11:26:35 +0200 Subject: [PATCH 34/82] Add `ruamel.yaml` to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 1ca4a326446..0e6543a309b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ h5py==2.9.0 PyQt5==5.9.* QtPy jsonschema +ruamel.yaml pyzmq>=16.0.2 broadbean>=0.9.1 wrapt From f7b52b003e5e5904c60b4d0f016819c4dbb11ced Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 2 May 2019 11:34:42 +0200 Subject: [PATCH 35/82] Exclusively use ruamel.yaml i.e pip version --- environment.yml | 2 +- setup.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/environment.yml b/environment.yml index 645e6fd0f25..3101a55b249 100644 --- a/environment.yml +++ b/environment.yml @@ -22,7 +22,6 @@ dependencies: - pyyaml - lxml - scipy - - ruamel_yaml - gitpython - pandas - testpath>=0.4.2 # 0.4.1 is bad due to https://github.com/conda-forge/testpath-feedstock/issues/7 @@ -30,3 +29,4 @@ dependencies: - pip: - websockets>=7.0 - broadbean>=0.9.1 + - ruamel.yaml diff --git a/setup.py b/setup.py index 7cfd93514e0..6d63f16cf8f 100644 --- a/setup.py +++ b/setup.py @@ -67,6 +67,7 @@ def readme(): 'h5py>=2.6', 'websockets>=7.0', 'jsonschema', + 'ruamel.yaml', 'pyzmq', 'wrapt', 'pandas', @@ -125,4 +126,4 @@ def readme(): print(valueerror_template.format( module_name, module.__version__, min_version, extra)) except: - print(othererror_template.format(module_name)) \ No newline at end of file + print(othererror_template.format(module_name)) From 3d5c26990d6038541d78cdd372a242f6bce02f9d Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 2 May 2019 13:13:13 +0200 Subject: [PATCH 36/82] Use `get_qcodes_path` in all tests --- qcodes/tests/test_station.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 3d0a8976a66..a416533bf74 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -398,7 +398,7 @@ def test_init_parameters(): assert st.components['test'] is mock # test address - sims_path = f'{qcodes.__path__[0]}\\instrument\\sims\\' + sims_path = get_qcodes_path('instrument', 'sims') st = station_from_config_str(f""" instruments: lakeshore: From b9b9be73abc631bf229d4abc7ca3b7d6635159f6 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 2 May 2019 14:04:09 +0200 Subject: [PATCH 37/82] Add separator at the end of `get_qcodes_path` --- qcodes/utils/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/utils/helpers.py b/qcodes/utils/helpers.py index 65e18e0d7c6..f15e086b507 100644 --- a/qcodes/utils/helpers.py +++ b/qcodes/utils/helpers.py @@ -723,4 +723,4 @@ def _ruamel_importer(): def get_qcodes_path(*subfolder) -> str: path = os.sep.join(qcodes.__file__.split(os.sep)[:-1]) - return os.path.join(path, *subfolder) + return os.path.join(path, *subfolder) + os.sep From b132a7644a794aacac0400a41a71d053d6150530 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Fri, 3 May 2019 09:34:06 +0200 Subject: [PATCH 38/82] Fix case for sims file --- qcodes/tests/test_station.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index a416533bf74..4bdba0c32c8 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -230,7 +230,7 @@ def example_station_config(): enable_forced_reconnect: true address: GPIB::2::65535::INSTR init: - visalib: '{sims_path}Lakeshore_model336.yaml@sim' + visalib: '{sims_path}lakeshore_model336.yaml@sim' mock_dac: driver: qcodes.tests.instrument_mocks type: DummyInstrument @@ -407,7 +407,7 @@ def test_init_parameters(): enable_forced_reconnect: true address: GPIB::2::INSTR init: - visalib: '{sims_path}Lakeshore_model336.yaml@sim' + visalib: '{sims_path}lakeshore_model336.yaml@sim' """) st.load_instrument('lakeshore') From eb08805c34a8b811145dcc6b554a3d6b7bfbef74 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Fri, 3 May 2019 14:06:27 +0200 Subject: [PATCH 39/82] Reformulate comment `StationConfigurator->Station` Co-Authored-By: Dominik-Vogel <30660470+Dominik-Vogel@users.noreply.github.com> --- qcodes/station.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/station.py b/qcodes/station.py index 48988c4856f..dbd2fd105bc 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -315,7 +315,7 @@ def update_load_instrument_methods(): log.warning(f'Invalid identifier: ' + f'for the instrument {instrument_name} no ' + f'lazy loading method {method_name} could ' + - 'be created in the StationConfigurator') + 'be created in the Station.') self._config = YAML().load(config) self._instrument_config = self._config['instruments'] update_station_configuration_snapshot() From b2771936cb9e5a9dffff87db88256da53de27756 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Fri, 3 May 2019 14:25:37 +0200 Subject: [PATCH 40/82] Apply suggestions from code review Co-Authored-By: Dominik-Vogel <30660470+Dominik-Vogel@users.noreply.github.com> --- qcodes/station.py | 18 ++++++++---------- qcodes/utils/helpers.py | 3 +-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index dbd2fd105bc..95ff5760564 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -326,7 +326,7 @@ def load_instrument(self, identifier: str, revive_instance: bool=False, **kwargs) -> Instrument: """ - Creates an instrument driver as described by the loaded config file. + Creates an instrument driver instance as described by the loaded config file. Args: identifier: the identfying string that is looked up in the yaml @@ -357,15 +357,15 @@ def load_instrument(self, identifier: str, # check if instrument is already defined and close connection if instr_cfg.get('enable_forced_reconnect', get_config_enable_forced_reconnect()): - # save close instrument and remove from monitor list + # safely close instrument and remove from monitor list with suppress(KeyError): instr = Instrument.find_instrument(identifier) # remove parameters related to this instrument from the # monitor list self._monitor_parameters = [v for v in self._monitor_parameters - if v.root_instrument is not instr] + if v.root_instrument is not instr] # remove instrument from station snapshot - self.components.pop(instr.name) + self.remove_component(instr.name) # del will remove weakref and close the instrument instr.close() del instr @@ -382,8 +382,6 @@ def load_instrument(self, identifier: str, if 'port' in instr_cfg: init_kwargs['port'] = instr_cfg['port'] # make explicitly passed arguments overide the ones from the config file - # the intuitive line: - # We are mutating the dict below # so make a copy to ensure that any changes # does not leek into the station config object @@ -403,7 +401,7 @@ def setup_parameter_from_dict(parameter: Parameter, options_dict): if attr in PARAMETER_ATTRIBUTES: # set the attributes of the parameter, that map 1 to 1 setattr(parameter, attr, val) - # extra attributes that need parsing + # extra attributes that need parsing elif attr == 'limits': lower, upper = [float(x) for x in val.split(',')] parameter.vals = validators.Numbers(lower, upper) @@ -416,7 +414,7 @@ def setup_parameter_from_dict(parameter: Parameter, options_dict): # when everything else has been set up pass else: - log.warning(f'Attribute {attr} no recognized when' + log.warning(f'Attribute {attr} not recognized when' f' instatiating parameter \"{parameter.name}\"') if 'initial_value' in options_dict: parameter.set(options_dict['initial_value']) @@ -428,8 +426,8 @@ def resolve_parameter_identifier(instrument: Instrument, p = getattr(p, level) if not isinstance(p, Parameter): raise RuntimeError( - f'Cannot resolve parameter identifier `{name}` to ' - 'a parameter.') + f'Cannot resolve parameter identifier `{identifier}` to ' + f'a parameter on instrument {instrument!r}.') return cast(Parameter, p) # setup existing parameters diff --git a/qcodes/utils/helpers.py b/qcodes/utils/helpers.py index f15e086b507..46e4fbc0717 100644 --- a/qcodes/utils/helpers.py +++ b/qcodes/utils/helpers.py @@ -713,8 +713,7 @@ def _ruamel_importer(): from ruamel.yaml import YAML except ImportError: raise ImportError('No ruamel module found. Please install ' - 'either ruamel.yaml or ruamel_yaml to ' - 'use the methods to_yaml and from_yaml') + 'either ruamel.yaml or ruamel_yaml.') return YAML From 55c2230a7b49cc3cc8bc6bc6ad1fa5a1d2299707 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 6 May 2019 10:04:43 +0200 Subject: [PATCH 41/82] Make station adhere to pep8 --- qcodes/station.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 95ff5760564..8c4f57f5526 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -150,8 +150,8 @@ def snapshot_base(self, update: bool = False, return snap - def add_component(self, component: Metadatable, name: str=None, - update_snapshot: bool=True) -> str: + def add_component(self, component: Metadatable, name: str = None, + update_snapshot: bool = True) -> str: """ Record one component as part of this Station. @@ -162,8 +162,8 @@ def add_component(self, component: Metadatable, name: str=None, of each component as it is added to the Station, default true Returns: - str: The name assigned this component, which may have been changed to - make it unique among previously added components. + str: The name assigned this component, which may have been changed + to make it unique among previously added components. """ try: @@ -268,7 +268,6 @@ def get_config_file_path( return p return None - path = get_config_file_path(filename) if path is None: if filename is not None: @@ -321,12 +320,11 @@ def update_load_instrument_methods(): update_station_configuration_snapshot() update_load_instrument_methods() - def load_instrument(self, identifier: str, - revive_instance: bool=False, + revive_instance: bool = False, **kwargs) -> Instrument: """ - Creates an instrument driver instance as described by the loaded config file. + Creates an `Instrument` instance as described by the loaded config file Args: identifier: the identfying string that is looked up in the yaml @@ -381,7 +379,8 @@ def load_instrument(self, identifier: str, init_kwargs['address'] = instr_cfg['address'] if 'port' in instr_cfg: init_kwargs['port'] = instr_cfg['port'] - # make explicitly passed arguments overide the ones from the config file + # make explicitly passed arguments overide the ones from the config + # file. # We are mutating the dict below # so make a copy to ensure that any changes # does not leek into the station config object @@ -414,8 +413,8 @@ def setup_parameter_from_dict(parameter: Parameter, options_dict): # when everything else has been set up pass else: - log.warning(f'Attribute {attr} not recognized when' - f' instatiating parameter \"{parameter.name}\"') + log.warning(f'Attribute {attr} not recognized when ' + f'instatiating parameter \"{parameter.name}\"') if 'initial_value' in options_dict: parameter.set(options_dict['initial_value']) @@ -427,7 +426,7 @@ def resolve_parameter_identifier(instrument: Instrument, if not isinstance(p, Parameter): raise RuntimeError( f'Cannot resolve parameter identifier `{identifier}` to ' - f'a parameter on instrument {instrument!r}.') + f'a parameter on instrument {instrument!r}.') return cast(Parameter, p) # setup existing parameters From 050c5e06b7fb9e0e860a09333d1cea9f9f036527 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Mon, 6 May 2019 11:10:14 +0200 Subject: [PATCH 42/82] Update qcodes/tests/test_station.py Co-Authored-By: Dominik-Vogel <30660470+Dominik-Vogel@users.noreply.github.com> --- qcodes/tests/test_station.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 4bdba0c32c8..72a3e7e8a6f 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -502,6 +502,7 @@ def test_setup_delegate_parameters(): def test_channel_instrument(): + """Test that parameters from instrument's submodule also get configured correctly""" st = station_from_config_str(""" instruments: mock: From 64285d40bbf1be7afddfdd2e82dd5a55c15471ab Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 6 May 2019 11:20:50 +0200 Subject: [PATCH 43/82] Operate on copy of options when adding parameter --- qcodes/station.py | 72 +++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 8c4f57f5526..8d3f26211f9 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -5,7 +5,7 @@ import importlib import logging import os -from copy import deepcopy +from copy import deepcopy, copy from typing import Union import qcodes @@ -391,12 +391,24 @@ def load_instrument(self, identifier: str, name = instr_kwargs.pop('name', identifier) instr = instr_class(name, **instr_kwargs) - # setup # local function to refactor common code from defining new parameter # and setting existing one - def setup_parameter_from_dict(parameter: Parameter, options_dict): - for attr, val in options_dict.items(): + def resolve_parameter_identifier(instrument: Instrument, + identifier: str) -> Parameter: + p = instrument + for level in identifier.split('.'): + p = getattr(p, level) + if not isinstance(p, Parameter): + raise RuntimeError( + f'Cannot resolve parameter identifier `{identifier}` to ' + f'a parameter on instrument {instrument!r}.') + return cast(Parameter, p) + + def setup_parameter_from_dict(instr: Parameter, name: str, + options: Dict[str, Any]): + parameter = resolve_parameter_identifier(instr, name) + for attr, val in options.items(): if attr in PARAMETER_ATTRIBUTES: # set the attributes of the parameter, that map 1 to 1 setattr(parameter, attr, val) @@ -415,44 +427,30 @@ def setup_parameter_from_dict(parameter: Parameter, options_dict): else: log.warning(f'Attribute {attr} not recognized when ' f'instatiating parameter \"{parameter.name}\"') - if 'initial_value' in options_dict: - parameter.set(options_dict['initial_value']) - - def resolve_parameter_identifier(instrument: Instrument, - identifier: str) -> Parameter: - p = instrument - for level in identifier.split('.'): - p = getattr(p, level) - if not isinstance(p, Parameter): - raise RuntimeError( - f'Cannot resolve parameter identifier `{identifier}` to ' - f'a parameter on instrument {instrument!r}.') - return cast(Parameter, p) + if 'initial_value' in options: + parameter.set(options['initial_value']) + + def add_parameter_from_dict(instr: Parameter, name: str, + options: Dict[str, Any]): + # keep the original dictionray intact for snapshot + options = copy(options) + if 'source' in options: + instr.add_parameter( + name, + DelegateParameter, + source=resolve_parameter_identifier(instr, + options['source'])) + options.pop('source') + else: + instr.add_parameter(name, Parameter) + setup_parameter_from_dict(instr, name, options) - # setup existing parameters for name, options in instr_cfg.get('parameters', {}).items(): - setup_parameter_from_dict( - resolve_parameter_identifier(instr, name), - options) + setup_parameter_from_dict(instr, name, options) - # setup new parameters for name, options in instr_cfg.get('add_parameters', {}).items(): - # allow only top level paremeters for now - # pop source only temporarily - source = options.pop('source', False) - if source: - source_p = resolve_parameter_identifier(instr, source) - instr.add_parameter(name, - DelegateParameter, - source=source_p) - else: - instr.add_parameter(name, Parameter) - p = getattr(instr, name) - setup_parameter_from_dict(p, options) - # restore source - options['source'] = source + add_parameter_from_dict(instr, name, options) - # add the instrument to the station self.add_component(instr) # restart Monitor From 2710531f3712483cd5951ef795f17b12ceacf189 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 6 May 2019 11:27:00 +0200 Subject: [PATCH 44/82] Rename `station_configurator` to station everywhere including config --- qcodes/config/qcodesrc.json | 2 +- qcodes/config/qcodesrc_schema.json | 2 +- qcodes/station.py | 8 ++++---- qcodes/tests/test_config.py | 2 +- qcodes/tests/test_station.py | 10 +++++----- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/qcodes/config/qcodesrc.json b/qcodes/config/qcodesrc.json index 611a6d3f7a7..a0873e9a8d9 100644 --- a/qcodes/config/qcodesrc.json +++ b/qcodes/config/qcodesrc.json @@ -62,7 +62,7 @@ "scriptfolder": ".", "mainfolder": "." }, - "station_configurator": { + "station": { "enable_forced_reconnect": false, "default_folder": ".", "default_file": "instrument_config.yml" diff --git a/qcodes/config/qcodesrc_schema.json b/qcodes/config/qcodesrc_schema.json index a64b5f480b6..0f24fa2d3f3 100644 --- a/qcodes/config/qcodesrc_schema.json +++ b/qcodes/config/qcodesrc_schema.json @@ -235,7 +235,7 @@ }, "description": "Optional feature for qdev-wrappers package: controls user settings of qcodes" }, - "station_configurator": { + "station": { "type": "object", "properties": { "enable_forced_reconnect": { diff --git a/qcodes/station.py b/qcodes/station.py index 8d3f26211f9..d0f8e635cee 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -29,15 +29,15 @@ def get_config_enable_forced_reconnect() -> bool: - return qcodes.config["station_configurator"]["enable_forced_reconnect"] + return qcodes.config["station"]["enable_forced_reconnect"] def get_config_default_folder() -> Optional[str]: - return qcodes.config["station_configurator"]["default_folder"] + return qcodes.config["station"]["default_folder"] def get_config_default_file() -> Optional[str]: - return qcodes.config["station_configurator"]["default_file"] + return qcodes.config["station"]["default_file"] class Station(Metadatable, DelegateAttributes): @@ -292,7 +292,7 @@ def __init__(self, data): def snapshot(self, update=True): return self.data - self.components['StationConfigurator'] = ConfigComponent( + self.components['Station'] = ConfigComponent( self._config) def update_load_instrument_methods(): diff --git a/qcodes/tests/test_config.py b/qcodes/tests/test_config.py index e9c7b620cc1..5de5ff643f5 100644 --- a/qcodes/tests/test_config.py +++ b/qcodes/tests/test_config.py @@ -331,7 +331,7 @@ def test_update_from_path(path_to_config_file_on_disk): # check that the settings NOT specified in our config file on path # are still saved as configurations assert cfg['gui']['notebook'] is True - assert cfg['station_configurator']['default_folder'] == '.' + assert cfg['station']['default_folder'] == '.' expected_path = os.path.join(path_to_config_file_on_disk, 'qcodesrc.json') diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 72a3e7e8a6f..7519165b3cf 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -254,7 +254,7 @@ def station_from_config_str(config: str) -> Station: def has_station_config_been_loaded(st: Station) -> bool: - return "StationConfigurator" in st.components.keys() + return "Station" in st.components.keys() @pytest.fixture @@ -264,7 +264,7 @@ def example_station(example_station_config): # instrument loading related tests def test_station_config_path_resolution(example_station_config): - config = qcodes.config["station_configurator"] + config = qcodes.config["station"] assert not has_station_config_been_loaded(Station()) @@ -294,7 +294,7 @@ def test_station_config_path_resolution(example_station_config): def test_station_creation(example_station): - assert "StationConfigurator" in example_station.components.keys() + assert "Station" in example_station.components.keys() SIMPLE_MOCK_CONFIG = """ instruments: @@ -311,7 +311,7 @@ def test_simple_mock_config(simple_mock_station): st = simple_mock_station assert has_station_config_been_loaded(st) assert hasattr(st, 'load_mock') - mock_snapshot = st.snapshot()['components']['StationConfigurator']\ + mock_snapshot = st.snapshot()['components']['Station']\ ['instruments']['mock'] assert mock_snapshot['driver'] == "qcodes.tests.instrument_mocks" assert mock_snapshot['type'] == "DummyInstrument" @@ -349,7 +349,7 @@ def get_instrument_config(enable_forced_reconnect: Union[bool, None]) -> str: def assert_on_reconnect(user_config_val: Union[bool, None], instrument_config_val: Union[bool, None], expect_failure: bool) -> None: - qcodes.config["station_configurator"]\ + qcodes.config["station"]\ ['enable_forced_reconnect'] = user_config_val st = station_from_config_str( get_instrument_config(instrument_config_val)) From 77c142f950fdf9183e561d8465ed0fd95a7982eb Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 6 May 2019 11:29:48 +0200 Subject: [PATCH 45/82] Fix indent `assert` for `pytest.raises` --- qcodes/tests/test_station.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 7519165b3cf..e64208a4d94 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -357,8 +357,8 @@ def assert_on_reconnect(user_config_val: Union[bool, None], if expect_failure: with pytest.raises(KeyError) as excinfo: st.load_instrument('mock') - assert ("Another instrument has the name: mock" - in str(excinfo.value)) + assert ("Another instrument has the name: mock" + in str(excinfo.value)) else: st.load_instrument('mock') Instrument.close_all() From 3629aa5c6ee74338c268b9b3eb765cca76ddf492 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 6 May 2019 11:33:49 +0200 Subject: [PATCH 46/82] Use `Optional` over `Union[x, None]` --- qcodes/tests/test_station.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index e64208a4d94..b99e06a2ed3 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -2,7 +2,7 @@ import tempfile import json from pathlib import Path -from typing import Union +from typing import Optional import qcodes import qcodes.utils.validators as validators @@ -334,7 +334,7 @@ def test_simple_mock_load_instrument(simple_mock_station): def test_enable_force_reconnect() -> None: - def get_instrument_config(enable_forced_reconnect: Union[bool, None]) -> str: + def get_instrument_config(enable_forced_reconnect: Optional[bool]) -> str: return f""" instruments: mock: @@ -346,8 +346,8 @@ def get_instrument_config(enable_forced_reconnect: Union[bool, None]) -> str: gates: {{"ch1", "ch2"}} """ - def assert_on_reconnect(user_config_val: Union[bool, None], - instrument_config_val: Union[bool, None], + def assert_on_reconnect(user_config_val: Optional[bool], + instrument_config_val: Optional[bool], expect_failure: bool) -> None: qcodes.config["station"]\ ['enable_forced_reconnect'] = user_config_val From f6cfa6a2f5c0529ebae0f273d37e98d180caa3aa Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 6 May 2019 11:45:42 +0200 Subject: [PATCH 47/82] Rename to `station_config_has_been_loaded` --- qcodes/tests/test_station.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index b99e06a2ed3..b8ff3045ac3 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -253,7 +253,7 @@ def station_from_config_str(config: str) -> Station: return st -def has_station_config_been_loaded(st: Station) -> bool: +def station_config_has_been_loaded(st: Station) -> bool: return "Station" in st.components.keys() @@ -266,31 +266,31 @@ def example_station(example_station_config): def test_station_config_path_resolution(example_station_config): config = qcodes.config["station"] - assert not has_station_config_been_loaded(Station()) + assert not station_config_has_been_loaded(Station()) path = Path(example_station_config) config["default_file"] = str(path) - assert has_station_config_been_loaded(Station()) + assert station_config_has_been_loaded(Station()) config["default_file"] = path.name config["default_folder"] = str(path.parent) - assert has_station_config_been_loaded(Station()) + assert station_config_has_been_loaded(Station()) config["default_file"] = 'random.yml' config["default_folder"] = str(path.parent) - assert not has_station_config_been_loaded(Station()) + assert not station_config_has_been_loaded(Station()) config["default_file"] = str(path) config["default_folder"] = r'C:\SomeOtherFolder' - assert has_station_config_been_loaded(Station()) + assert station_config_has_been_loaded(Station()) config["default_file"] = None config["default_folder"] = str(path.parent) - assert has_station_config_been_loaded(Station(config_file=path.name)) + assert station_config_has_been_loaded(Station(config_file=path.name)) config["default_file"] = None config["default_folder"] = None - assert has_station_config_been_loaded(Station(config_file=str(path))) + assert station_config_has_been_loaded(Station(config_file=str(path))) def test_station_creation(example_station): @@ -309,7 +309,7 @@ def simple_mock_station(example_station_config): def test_simple_mock_config(simple_mock_station): st = simple_mock_station - assert has_station_config_been_loaded(st) + assert station_config_has_been_loaded(st) assert hasattr(st, 'load_mock') mock_snapshot = st.snapshot()['components']['Station']\ ['instruments']['mock'] From 05822ce243b8584a1b06858d1c656979ed1fcff9 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 6 May 2019 11:46:59 +0200 Subject: [PATCH 48/82] Fix indentation --- qcodes/station.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/station.py b/qcodes/station.py index d0f8e635cee..b71a351216f 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -260,7 +260,7 @@ def get_config_file_path( return None search_list = [filename] if (not os.path.isabs(filename) and - get_config_default_folder() is not None): + get_config_default_folder() is not None): config_folder = cast(str, get_config_default_folder()) search_list += [os.path.join(config_folder, filename)] for p in search_list: From e87fed205bc7f6d8d5883b97d4d8f7f16bac7597 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 6 May 2019 12:52:24 +0200 Subject: [PATCH 49/82] Add comments to path resolution test --- qcodes/tests/test_station.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index b8ff3045ac3..9c410e66cdb 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -266,30 +266,56 @@ def example_station(example_station_config): def test_station_config_path_resolution(example_station_config): config = qcodes.config["station"] + # There is no default yaml file present that defines a station + # so we expect the station config not to be loaded. assert not station_config_has_been_loaded(Station()) path = Path(example_station_config) config["default_file"] = str(path) + # Now the default file with the station configuration is specified, and + # this file exists, hence we expect the Station to have the station + # configuration loaded upon initialization. assert station_config_has_been_loaded(Station()) config["default_file"] = path.name config["default_folder"] = str(path.parent) + # Here the default_file setting contains only the file name, and the + # default_folder contains the path to the folder where this file is + # located, hence we again expect that the station configuration is loaded + # upon station initialization. assert station_config_has_been_loaded(Station()) config["default_file"] = 'random.yml' config["default_folder"] = str(path.parent) + # In this case, the station configuration file specified in the qcodes + # config does not exist, hence the initialized station is not expected to + # have station configuration loaded. assert not station_config_has_been_loaded(Station()) config["default_file"] = str(path) config["default_folder"] = r'C:\SomeOtherFolder' + # In this case, the default_file setting of the qcodes config contains + # absolute path to the station configuration file, while the default_folder + # setting is set to some non-existent folder. + # In this situation, the value of the default_folder will be ignored, + # but because the file specified in default_file setting exists, + # the station will be initialized with the loaded configuration. assert station_config_has_been_loaded(Station()) config["default_file"] = None config["default_folder"] = str(path.parent) + # When qcodes config has only the default_folder setting specified to an + # existing folder, and default_file setting is not specified, then + # passing the name of a station configuration file, that exists in that + # default_folder, as an argument to the Station is expected to result + # in a station with loaded configuration. assert station_config_has_been_loaded(Station(config_file=path.name)) config["default_file"] = None config["default_folder"] = None + # In case qcodes config does not have default_file and default_folder + # settings specified, passing an absolute file path as an argument to the + # station is expected to result in a station with loaded configuration. assert station_config_has_been_loaded(Station(config_file=str(path))) From 0c951827794a2b6800ccc4070db5ea143df0e45b Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 6 May 2019 14:43:52 +0200 Subject: [PATCH 50/82] Fix `simple_mock_station` dependency on example_config --- qcodes/tests/test_station.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 9c410e66cdb..0f698e0c36d 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -322,16 +322,16 @@ def test_station_config_path_resolution(example_station_config): def test_station_creation(example_station): assert "Station" in example_station.components.keys() -SIMPLE_MOCK_CONFIG = """ + +@pytest.fixture +def simple_mock_station(): + yield station_from_config_str( + """ instruments: mock: driver: qcodes.tests.instrument_mocks type: DummyInstrument -""" - -@pytest.fixture -def simple_mock_station(example_station_config): - yield station_from_config_str(SIMPLE_MOCK_CONFIG) + """) def test_simple_mock_config(simple_mock_station): st = simple_mock_station From b692d4368e7380ec08df0bb86aa0d5fb625314fc Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Mon, 6 May 2019 14:46:06 +0200 Subject: [PATCH 51/82] Update qcodes/tests/test_station.py Co-Authored-By: Dominik-Vogel <30660470+Dominik-Vogel@users.noreply.github.com> --- qcodes/tests/test_station.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 72a3e7e8a6f..0b9bf81d079 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -293,7 +293,7 @@ def test_station_config_path_resolution(example_station_config): assert has_station_config_been_loaded(Station(config_file=str(path))) -def test_station_creation(example_station): +def test_station_configuration_is_a_component_of_station(example_station): assert "StationConfigurator" in example_station.components.keys() SIMPLE_MOCK_CONFIG = """ From d2e18891e813ed005a48c7e7db7df34d2f3b79f8 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Mon, 6 May 2019 15:51:58 +0200 Subject: [PATCH 52/82] Rename component from `Station` to `Config` --- qcodes/station.py | 2 +- qcodes/tests/test_station.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index b71a351216f..c025f63e46e 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -292,7 +292,7 @@ def __init__(self, data): def snapshot(self, update=True): return self.data - self.components['Station'] = ConfigComponent( + self.components['Config'] = ConfigComponent( self._config) def update_load_instrument_methods(): diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 0f698e0c36d..f10a55d7671 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -254,7 +254,7 @@ def station_from_config_str(config: str) -> Station: def station_config_has_been_loaded(st: Station) -> bool: - return "Station" in st.components.keys() + return "Config" in st.components.keys() @pytest.fixture @@ -337,7 +337,7 @@ def test_simple_mock_config(simple_mock_station): st = simple_mock_station assert station_config_has_been_loaded(st) assert hasattr(st, 'load_mock') - mock_snapshot = st.snapshot()['components']['Station']\ + mock_snapshot = st.snapshot()['components']['Config']\ ['instruments']['mock'] assert mock_snapshot['driver'] == "qcodes.tests.instrument_mocks" assert mock_snapshot['type'] == "DummyInstrument" From bb315dcd056d97b4170d8722f60c23d8326a8de3 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Tue, 7 May 2019 09:24:30 +0200 Subject: [PATCH 53/82] Update qcodes/station.py Co-Authored-By: Dominik-Vogel <30660470+Dominik-Vogel@users.noreply.github.com> --- qcodes/station.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/station.py b/qcodes/station.py index 95ff5760564..8864f11e2ad 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -24,7 +24,7 @@ log = logging.getLogger(__name__) -PARAMETER_ATTRIBUTES = ['label', 'unit', 'scale', 'inter_delay', 'delay', +PARAMETER_ATTRIBUTES = ['label', 'unit', 'scale', 'inter_delay', 'post_delay', 'step', 'offset'] From a91a44f675ae767b01d190d506ee57dee1d14bc7 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 7 May 2019 09:29:31 +0200 Subject: [PATCH 54/82] Implement Mikhails suggestions --- qcodes/station.py | 11 +++----- qcodes/tests/test_station.py | 52 +++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index c025f63e46e..611bfe3682b 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -6,6 +6,7 @@ import logging import os from copy import deepcopy, copy +from collections import UserDict from typing import Union import qcodes @@ -285,15 +286,11 @@ def get_config_file_path( def load_config(self, config: Union[str, IO[AnyStr]]) -> None: def update_station_configuration_snapshot(): - class ConfigComponent: - def __init__(self, data): - self.data = data - + class StationConfig(UserDict): def snapshot(self, update=True): - return self.data + return self - self.components['Config'] = ConfigComponent( - self._config) + self.components['config'] = StationConfig(self._config) def update_load_instrument_methods(): # create shortcut methods to instantiate instruments via diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index f10a55d7671..4f1eff6b1cb 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -254,7 +254,7 @@ def station_from_config_str(config: str) -> Station: def station_config_has_been_loaded(st: Station) -> bool: - return "Config" in st.components.keys() + return "config" in st.components.keys() @pytest.fixture @@ -320,8 +320,7 @@ def test_station_config_path_resolution(example_station_config): def test_station_creation(example_station): - assert "Station" in example_station.components.keys() - + assert station_config_has_been_loaded(example_station) @pytest.fixture def simple_mock_station(): @@ -337,10 +336,11 @@ def test_simple_mock_config(simple_mock_station): st = simple_mock_station assert station_config_has_been_loaded(st) assert hasattr(st, 'load_mock') - mock_snapshot = st.snapshot()['components']['Config']\ + mock_snapshot = st.snapshot()['components']['config']\ ['instruments']['mock'] assert mock_snapshot['driver'] == "qcodes.tests.instrument_mocks" assert mock_snapshot['type'] == "DummyInstrument" + assert 'mock' in st.config['instruments'] def test_simple_mock_load_mock(simple_mock_station): @@ -372,13 +372,13 @@ def get_instrument_config(enable_forced_reconnect: Optional[bool]) -> str: gates: {{"ch1", "ch2"}} """ - def assert_on_reconnect(user_config_val: Optional[bool], - instrument_config_val: Optional[bool], + def assert_on_reconnect(*, use_user_cfg: Optional[bool], + use_instr_cfg: Optional[bool], expect_failure: bool) -> None: qcodes.config["station"]\ - ['enable_forced_reconnect'] = user_config_val + ['enable_forced_reconnect'] = use_user_cfg st = station_from_config_str( - get_instrument_config(instrument_config_val)) + get_instrument_config(use_instr_cfg)) st.load_instrument('mock') if expect_failure: with pytest.raises(KeyError) as excinfo: @@ -389,13 +389,23 @@ def assert_on_reconnect(user_config_val: Optional[bool], st.load_instrument('mock') Instrument.close_all() - for user_config_val in [None, True, False]: - assert_on_reconnect(user_config_val, False, True) - assert_on_reconnect(user_config_val, True, False) - - assert_on_reconnect(True, None, False) - assert_on_reconnect(False, None, True) - assert_on_reconnect(None, None, True) + for use_user_cfg in [None, True, False]: + assert_on_reconnect(use_user_cfg=use_user_cfg, + use_instr_cfg=False, + expect_failure=True) + assert_on_reconnect(use_user_cfg=use_user_cfg, + use_instr_cfg=True, + expect_failure=False) + + assert_on_reconnect(use_user_cfg=True, + use_instr_cfg=None, + expect_failure=False) + assert_on_reconnect(use_user_cfg=False, + use_instr_cfg=None, + expect_failure=True) + assert_on_reconnect(use_user_cfg=None, + use_instr_cfg=None, + expect_failure=True) def test_init_parameters(): @@ -418,10 +428,6 @@ def test_init_parameters(): assert "TestGate" in mock.parameters.keys() assert len(mock.parameters) == 2 # there is also IDN - # special case of `name` - mock = st.load_instrument('mock', name='test') - assert mock.name == 'test' - assert st.components['test'] is mock # test address sims_path = get_qcodes_path('instrument', 'sims') @@ -438,6 +444,14 @@ def test_init_parameters(): st.load_instrument('lakeshore') +def test_name_init_kwarg(simple_mock_station): + # special case of `name` as kwarg + st = simple_mock_station + mock = st.load_instrument('mock', name='test') + assert mock.name == 'test' + assert st.components['test'] is mock + + def test_setup_alias_parameters(): st = station_from_config_str(""" instruments: From 723c3c86c47db3f00f7a935cf5ac1ab934759cf7 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 7 May 2019 09:59:56 +0200 Subject: [PATCH 55/82] Add test for revive instance --- qcodes/tests/test_station.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 424536e7f62..4a58e547306 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -408,6 +408,27 @@ def assert_on_reconnect(*, use_user_cfg: Optional[bool], expect_failure=True) +def test_revive_instance(): + st = station_from_config_str(""" +instruments: + mock: + driver: qcodes.tests.instrument_mocks + type: DummyInstrument + enable_forced_reconnect: true + init: + gates: {"ch1"} + """) + mock = st.load_instrument('mock') + mock2 = st.load_instrument('mock') + assert mock is not mock2 + assert mock is not st.mock + assert mock2 is st.mock + + mock3 = st.load_instrument('mock', revive_instance=True) + assert mock3 == mock2 + assert mock3 == st.mock + + def test_init_parameters(): st = station_from_config_str(""" instruments: @@ -559,3 +580,7 @@ def test_channel_instrument(): mock = st.load_instrument('mock') assert mock.A.temperature.unit == 'mK' assert mock.T.unit == 'mK' + + + + From 0c17813720f9fb41a1dfac2fb70c08676d1f12ce Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 7 May 2019 10:56:31 +0200 Subject: [PATCH 56/82] Extract method: `close_and_remove_instrument` --- qcodes/station.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index cef6e58e508..8402d0f55bb 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -317,6 +317,24 @@ def update_load_instrument_methods(): update_station_configuration_snapshot() update_load_instrument_methods() + def close_and_remove_instrument(self, + instrument: Union[Instrument, str]) -> None: + """ + Safely close instrument and remove from station and monitor list + """ + # remove parameters related to this instrument from the + # monitor list + if isinstance(instrument, str): + instrument = Instrument.find_instrument(instrument) + + self._monitor_parameters = [v for v in self._monitor_parameters + if v.root_instrument is not instrument] + # remove instrument from station snapshot + self.remove_component(instrument.name) + # del will remove weakref and close the instrument + instrument.close() + del instrument + def load_instrument(self, identifier: str, revive_instance: bool = False, **kwargs) -> Instrument: @@ -352,19 +370,8 @@ def load_instrument(self, identifier: str, # check if instrument is already defined and close connection if instr_cfg.get('enable_forced_reconnect', get_config_enable_forced_reconnect()): - # safely close instrument and remove from monitor list with suppress(KeyError): - instr = Instrument.find_instrument(identifier) - # remove parameters related to this instrument from the - # monitor list - self._monitor_parameters = [v for v in self._monitor_parameters - if v.root_instrument is not instr] - # remove instrument from station snapshot - self.remove_component(instr.name) - # del will remove weakref and close the instrument - instr.close() - del instr - + self.close_and_remove_instrument(identifier) # instantiate instrument module = importlib.import_module(instr_cfg['driver']) instr_class = getattr(module, instr_cfg['type']) @@ -437,6 +444,7 @@ def add_parameter_from_dict(instr: Parameter, name: str, DelegateParameter, source=resolve_parameter_identifier(instr, options['source'])) + options['source'])) options.pop('source') else: instr.add_parameter(name, Parameter) From 2cf7a0fecebc571c1e1d9e62c34e31c4939dc1af Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 7 May 2019 12:42:46 +0200 Subject: [PATCH 57/82] Refactor `load_instrument` --- qcodes/station.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 8402d0f55bb..d229d1f69ff 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -372,10 +372,9 @@ def load_instrument(self, identifier: str, get_config_enable_forced_reconnect()): with suppress(KeyError): self.close_and_remove_instrument(identifier) - # instantiate instrument - module = importlib.import_module(instr_cfg['driver']) - instr_class = getattr(module, instr_cfg['type']) + + # instantiate instrument init_kwargs = instr_cfg.get('init', {}) # somebody might have a empty init section in the config init_kwargs = {} if init_kwargs is None else init_kwargs From bc5882bc985454eeb7db994697fef3e7ed76bd4f Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 7 May 2019 12:43:36 +0200 Subject: [PATCH 58/82] Refactor `load_instrument` --- qcodes/station.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qcodes/station.py b/qcodes/station.py index d229d1f69ff..281c76aa08f 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -393,6 +393,8 @@ def load_instrument(self, identifier: str, instr_kwargs.update(kwargs) name = instr_kwargs.pop('name', identifier) + module = importlib.import_module(instr_cfg['driver']) + instr_class = getattr(module, instr_cfg['type']) instr = instr_class(name, **instr_kwargs) # local function to refactor common code from defining new parameter @@ -443,7 +445,6 @@ def add_parameter_from_dict(instr: Parameter, name: str, DelegateParameter, source=resolve_parameter_identifier(instr, options['source'])) - options['source'])) options.pop('source') else: instr.add_parameter(name, Parameter) From c0bca085e3122dcedcca40a7ad13d7806066b99c Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 7 May 2019 12:45:10 +0200 Subject: [PATCH 59/82] Add `test_dynamic_reload_of_file` --- qcodes/tests/test_station.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 4a58e547306..6cfe9e4fe77 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -247,6 +247,20 @@ def example_station_config(): f.write(test_config) yield str(filename) + +def test_dynamic_reload_of_file(example_station_config): + st = Station(config_file=example_station_config) + mock_dac = st.load_instrument('mock_dac') + assert 'ch1' in mock_dac.parameters + with open(example_station_config, 'r') as f: + filedata = f.read().replace('ch1', 'gate1') + with open(example_station_config, 'w') as f: + f.write(filedata) + mock_dac = st.load_instrument('mock_dac') + assert 'ch1' not in mock_dac.parameters + assert 'gate1' in mock_dac.parameters + + def station_from_config_str(config: str) -> Station: st = Station(config_file=None) st.load_config(config) From 191b8c907b28a8b06844800c52c0e88425d5a234 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Tue, 7 May 2019 15:43:24 +0200 Subject: [PATCH 60/82] Add tests for DelegateParameter --- qcodes/tests/test_parameter.py | 59 ++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/qcodes/tests/test_parameter.py b/qcodes/tests/test_parameter.py index 1b9448c5b20..b3df2dff702 100644 --- a/qcodes/tests/test_parameter.py +++ b/qcodes/tests/test_parameter.py @@ -14,7 +14,7 @@ from qcodes import Function from qcodes.instrument.parameter import ( Parameter, ArrayParameter, MultiParameter, ManualParameter, - InstrumentRefParameter, ScaledParameter) + InstrumentRefParameter, ScaledParameter, DelegateParameter) import qcodes.utils.validators as vals from qcodes.tests.instrument_mocks import DummyInstrument from qcodes.utils.helpers import create_on_off_val_mapping @@ -1241,9 +1241,64 @@ def test_deprecated_param_warns(): assert a.get() == 11 assert a.get_count == 2 assert a.set_count == 1 - # check that wrapper functionality works e.g stepping is performed correctly + # check that wrapper functionality works e.g stepping is performed + # correctly a.step = 1 a.set(20) assert a.set_count == 1+9 assert a.get() == 20 assert a.get_count == 3 + + +def test_delegate_parameter_init(): + """ + Test that the lable and unit get used from source parameter if not + specified otherwise. + """ + p = Parameter('testparam', set_cmd=None, get_cmd=None, + label='Test Parameter', unit='V') + d = DelegateParameter('test_delegate_parameter', p) + assert d.label == p.label + assert d.unit == p.unit + + d = DelegateParameter('test_delegate_parameter', p, unit='Ohm') + assert d.label == p.label + assert not d.unit == p.unit + assert d.unit == 'Ohm' + + +def test_delegate_parameter_get_set_raises(): + """ + Test that providing a get/set_cmd kwarg raises an error. + """ + p = Parameter('testparam', set_cmd=None, get_cmd=None) + for kwargs in ({'set_cmd': None}, {'get_cmd': None}): + with pytest.raises(KeyError) as e: + DelegateParameter('test_delegate_parameter', p, **kwargs) + assert str(e.value).startswith('\'It is not allowed to set') + + +def test_delegate_parameter_scaling(): + p = Parameter('testparam', set_cmd=None, get_cmd=None, offset=1, scale=2) + d = DelegateParameter('test_delegate_parameter', p, offset=3, scale=5) + + p(1) + assert p.raw_value == 3 + assert d() == (1-3)/5 + + d(2) + assert d.raw_value == 2*5+3 + assert d.raw_value == p() + + +def test_delegate_parameter_snapshot(): + p = Parameter('testparam', set_cmd=None, get_cmd=None, + offset=1, scale=2, initial_value=1) + d = DelegateParameter('test_delegate_parameter', p, offset=3, scale=5, + initial_value=2) + + snapshot = d.snapshot() + source_snapshot = snapshot.pop('source_parameter') + assert source_snapshot == p.snapshot() + assert snapshot['value'] == 2 + assert source_snapshot['value'] == 13 From 658ff3c1c88d93c295224f7f3255cc3ee6a8a5ae Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Wed, 8 May 2019 18:21:57 +0200 Subject: [PATCH 61/82] Export DelegateParameter to qcodes root init --- qcodes/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qcodes/__init__.py b/qcodes/__init__.py index f09c3ff247e..d012a9c49ab 100644 --- a/qcodes/__init__.py +++ b/qcodes/__init__.py @@ -63,6 +63,7 @@ ArrayParameter, MultiParameter, ParameterWithSetpoints, + DelegateParameter, StandardParameter, ManualParameter, ScaledParameter, From 9b4a7d01e65d1a5ca1b6183be17d18c83ccbf204 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Wed, 8 May 2019 18:22:58 +0200 Subject: [PATCH 62/82] DelegateParameter to parameter.py module docstring --- qcodes/instrument/parameter.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 0bf3ceb0023..e5611652f83 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -34,6 +34,11 @@ legacy :class:`qcodes.loops.Loop` and :class:`qcodes.measure.Measure` measurement types. +- :class:`.DelegateParameter` is intended proxy-ing other parameters. + It forwards its ``get`` and ``set`` to the underlying source parameter, + while allowing to specify label/unit/etc that is different from the + source parameter. + - :class:`.ArrayParameter` is an older base class for array-valued parameters. For any new driver we strongly recommend using :class:`.ParameterWithSetpoints` which is both more flexible and From 63702c454e39348073dee85cac58d923efb362b9 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Wed, 8 May 2019 18:23:29 +0200 Subject: [PATCH 63/82] Add example notebook about Station Includes YAML configuration help info --- docs/examples/Station.ipynb | 608 ++++++++++++++++++++++++++++++++++++ 1 file changed, 608 insertions(+) create mode 100644 docs/examples/Station.ipynb diff --git a/docs/examples/Station.ipynb b/docs/examples/Station.ipynb new file mode 100644 index 00000000000..c6c92571d97 --- /dev/null +++ b/docs/examples/Station.ipynb @@ -0,0 +1,608 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Station\n", + "\n", + "Here, the following topics are going to be covered:\n", + "* What is a station\n", + "* How to create it, and work with it\n", + "* Snapshot of a station\n", + "* Configuring station using a YAML configuration file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Useful imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from pprint import pprint # for pretty-printing python variables like 'dict'\n", + "\n", + "import qcodes\n", + "from qcodes import Parameter, Station\n", + "from qcodes.tests.instrument_mocks import DummyInstrument" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What is a Station\n", + "\n", + "Experimental setups are large, comprising of many instruments; and instruments tend to be quite complex in that they comprise many parameters and other stateful parts. It deems useful to have a bucket where all of them can be conveniently stored, and accessed.\n", + "\n", + "Here is where the concept of station comes into play. Instruments, parameters, and other \"components\" can be added to a station. As a result, the user gets an station instance that can be referred to in order to access those \"components\".\n", + "\n", + "Moreover, stations are very helpful when capturing the state of the experimental setup, known as snapshot. Refer to the respective section about station snapshot below.\n", + "\n", + "Last but not least, station can be configured in a text file which simplifies initialization of the instruments. Read more of this below." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## How to create Station and work with it\n", + "\n", + "For further sections we will need a dummy parameter, and a dummy instrument." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# A dummy self-standing parameter\n", + "p = Parameter('p', label='Parameter P', unit='kg', set_cmd=None, get_cmd=None)\n", + "p.set(123)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# A dummy instrument with three parameters\n", + "instr = DummyInstrument('instr', gates=['input', 'output', 'gain'])\n", + "instr.gain(42)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating a Station" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Could not load default instrument config for Station: \n", + "File instrument_config.yml not found. \n", + "You can change the default config file in `qcodesrc.json`.\n" + ] + }, + { + "data": { + "text/plain": [ + "'instr'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "station = Station()\n", + "\n", + "station.add_component(p)\n", + "station.add_component(instr)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'p': ,\n", + " 'instr': }" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Now station contains both `p` and `instr`\n", + "station.components" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that it is also possible to add components to a station via arguments of its constructor, like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Could not load default instrument config for Station: \n", + "File instrument_config.yml not found. \n", + "You can change the default config file in `qcodesrc.json`.\n" + ] + } + ], + "source": [ + "station = Station(p, instr)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Access Station components" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that the components have beed added to the station, it is possible to access them as attributes of the station (using the \"dot\" notation). With this feature, users can use tab-completion to find the instrument in the station they'd like to access." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# Let's confirm that station's `p` is \n", + "# actually the `p` parameter defined above\n", + "assert station.p is p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Removing component from Station" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Removing components from the station should be done with `remove_component` method - just pass it a name of the component you'd like to remove:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "station.remove_component('p')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'instr': }" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Now station contains only `instr`\n", + "station.components" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Default Station\n", + "\n", + "The `Station` class is designed in such a way that it always contains a reference to a `default` station object (the `Station.default` attribute). The constructor of the station object has a `default` keyword argument that allows to specify whether the resulting instance shall be stored as a default station, or not.\n", + "\n", + "This feature is a convenience. Other objects which consume an instance of `Station` as an argument (for example, `Measurement`) can now implement a logic to resort to `Station.default` in case an `Station` instance was not explicitly given to them." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Snapshot of a Station\n", + "\n", + "Experimental setups are large, and instruments tend to be quite complex in that they comprise many parameters and other stateful parts. It would be very time-consuming for the user to manually go through every instrument and parameter, and collect the snapshot data.\n", + "\n", + "The station comes to help here as well. Instruments, parameters, and other submodules can be added to a station. In turn, the station has its `snapshot` method that allows to create a collective, single snapshot of all the instruments, parameters, and submodules.\n", + "\n", + "For example, the `Measurement` object accepts a station argument exactly for the purpose of storing a snapshot of the whole experimental setup next to the measured data.\n", + "\n", + "Read more about snapshots in general, how to work with them, station's snapshot in particular, and more -- in [__Working with snapshots__ example notebook](DataSet/Working with snapshots.ipynb) ([nbviewer.jupyter.org link](https://nbviewer.jupyter.org/github/QCoDeS/Qcodes/tree/master/docs/examples/DataSet/Working with snapshots.ipynb))." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configuring Station using YAML configuration file\n", + "\n", + "The initialization part of the code where one instantiates instruments, set's up proper initial values for parameters, etc. can be quite long and tedious to maintain. For example, when a certain instrument is no longer needed, usually users just comment out the lines of initialization script which are related to the said instrument, and re-run the initialization script. Sharing initialization scripts is also difficult because each user may have a different expecation on the format it.\n", + "\n", + "These (and more) concerns are to be solved by YAML configuration of the `Station`. This feature originally comes from [qdev-dk](https://github.com/qdev-dk/qdev-wrappers) users where it was developed under the name `StationConfigurator`.\n", + "\n", + "The YAML configuration file allows to statically and uniformly specify settings of all the instruments (and their parameters) that the measurement setup (the \"physical\" station) consists of, and load them with those settings on demand. The `Station` object implements convenient methods for this.\n", + "\n", + "The YAML configuration, if used, is stored in the station as a component with name `config`, and is thus included in the snapshot of the whole station.\n", + "\n", + "Note that one is not obliged to use the YAML configuration for setting up one's `Station` (for example, as at the top of this notebook).\n", + "\n", + "Below the following is discussed:\n", + "* The structure of the YAML configuration file\n", + "* `Station`s methods realted to working with the YAML configuration\n", + "* Entries in QCoDeS config that are related to `Station`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example of YAML Station configuration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```yaml\n", + "# Example YAML Station configuration file\n", + "#\n", + "# This file gets snapshotted and can be read back from the JSON \n", + "# snapshot for every experiment run.\n", + "#\n", + "# All fields are optional unless explicitly mentioned otherwise.\n", + "#\n", + "# As in all YAML files a one-line notation can also be used\n", + "# instead of nesting notation.\n", + "#\n", + "# The file starts with a list of loadable instruments instances,\n", + "# i.e. there can be two entries for two instruments of the same\n", + "# type if you want to specify two different use cases \n", + "# e.g. \"dmm1-readout\" and \"dmm1-calibration\".\n", + "#\n", + "instruments:\n", + " # Each instrument is specified by its name.\n", + " # This name is what is looked up by the `load_instrument`\n", + " # method of `Station`.\n", + " # Simulated instruments can also be specified here, just put\n", + " # the path to the similation .yaml file as the value of the\n", + " # \"init\"->\"visalib\" field (see below for an example of the\n", + " # \"init\" section as well as an example of specifying \n", + " # a simulated instrument).\n", + " qdac:\n", + " # Full import path to the python module that contains\n", + " # the driver class of the instrument.\n", + " # Required field.\n", + " driver: qcodes.instrument_drivers.QDev.QDac_channels\n", + " # The name of the class of the instrument driver.\n", + " # Required field.\n", + " type: QDac\n", + " # Visa address of the instrument.\n", + " # Note that this field can also be specified in the\n", + " # \"init\" section (see below) but the address specified \n", + " # here will overrule the address from the \"init\" section.\n", + " # Essentially, specifying address here allows avoiding\n", + " # the \"init\" section completely when address is the only\n", + " # neccesary argument that the instrument driver needs.\n", + " # For obvious reasons, this field is required for VISA\n", + " # instruments.\n", + " address: ASRL4::INSTR\n", + " # If an instrument with this name is already instantiated,\n", + " # and this field is true, then the existing instrument \n", + " # instance will be closed before instantiating this new one.\n", + " # If this field is false, or left out, closing will not\n", + " # happen.\n", + " enable_forced_reconnect: true\n", + " #\n", + " # The \"init\" section specifies constant arguments that are\n", + " # to be passed to the __init__ function of the instrument.\n", + " # Note that it is the instrument's driver class that defines\n", + " # the allowed arguments, for example, here \"update_currents\"\n", + " # is an argument that is specific to \"QDac\" driver.\n", + " init:\n", + " terminator: \\n\n", + " update_currents: false\n", + " #\n", + " # Setting up properties of parameters that already exist on\n", + " # the instrument.\n", + " parameters:\n", + " # Each parameter is specified by its name from the\n", + " # instrument driver class.\n", + " # Note that \"dot: notation can be used to specify \n", + " # parameters in (sub)channels and submodules.\n", + " ch01.v:\n", + " # If an alias is specified, the paramater becomes \n", + " # accessible under another name, so that you can write\n", + " # `qdac.cutter_gate(0.2)` instead of `qdac.ch01.v(0.2)`.\n", + " # Note that the parameter instance does not get copied,\n", + " # so that `(qdac.ch01.v is qdac.cutter_gate) == True`.\n", + " alias: cutter_gate\n", + " # Set new label.\n", + " label: Cutter Gate Voltage\n", + " # Set new unit.\n", + " unit: mV\n", + " # Set new scale.\n", + " scale: 0.001\n", + " # Set new post_delay.\n", + " post_delay: 0\n", + " # Set new inter_delay.\n", + " inter_delay: 0.01\n", + " # Set new step.\n", + " step: 0.0001\n", + " # If this field is given, and contains two \n", + " # comma-separated numbers like here, then the parameter\n", + " # gets a new `Numbers` validator with these values as\n", + " # lower and upper limits, respectively (in this case, it\n", + " # is `Numbers(-0.1, 0.1)`).\n", + " limits: -0.1,0.1\n", + " # Set the parameter to this given initial value upon\n", + " # instrument initialization.\n", + " # Note that if the current value on the physical\n", + " # instrument is different, the parameter will be ramped\n", + " # with the delays and step specified in this file.\n", + " initial_value: 0.01\n", + " # In case this values equals to true, upon loading this\n", + " # instrument from this configuration this parameter will\n", + " # be appended to the list of parameters that are \n", + " # displayed in QCoDeS `Monitor`.\n", + " monitor: true\n", + " # As in all YAML files a one-line notation can also be \n", + " # used, here is an example.\n", + " ch03.v: {alias: Q1lplg1, label: my label}\n", + " ch04.v: {monitor: true}\n", + " #\n", + " # This section allows to add new parameters to the\n", + " # instrument instance which are based on existing parameters\n", + " # of the instrument. This functionality is based on the use\n", + " # of the `DelegateParameter` class.\n", + " add_parameters:\n", + " # For example, here we define a parameter that represents\n", + " # magnetic field control. Setting and getting this \n", + " # parameter will actually set/get a specific DAC channel.\n", + " # So this new magnetic field parameter is playing a role\n", + " # of a convenient proxy - it is much more convenient to \n", + " # perform a measurement where \"Bx\" is changed in tesla as\n", + " # opposed to where some channel of some DAC is changed in\n", + " # volts and one has to clutter the measurement code with\n", + " # the mess of conversion factors and more.\n", + " # Every new parameter definition starts with a name of\n", + " # the new parameter.\n", + " Bx:\n", + " # This field specifies the parameter which \"getter\" and \n", + " # \"setter\" will be used when calling `get`/`set` on this\n", + " # new parameter.\n", + " # Required field.\n", + " source: ch02.v\n", + " # Set the label. Otherwise, the one of the source parameter\n", + " # will be used.\n", + " label: Magnetic Field X-Component\n", + " # Set the unit. Otherwise, the one of the source parameter\n", + " # will be used.\n", + " unit: T\n", + " # Other fields have the same purpose and behavior as for\n", + " # the entries in the `add_parameter` section.\n", + " scale: 123.45\n", + " inter_delay: 0.001\n", + " post_delay: 0.05\n", + " step: 1.0\n", + " limits: 20.0,150.0\n", + " initial_value: 70.0\n", + " # For the sake of example, we decided not to monitor this\n", + " # parameter in QCoDeS `Monitor`.\n", + " #monitor: true\n", + " #\n", + " # More example instruments, just for the sake of example.\n", + " # Note that\n", + " dmm1:\n", + " driver: qcodes.instrument_drivers.agilent.Agilent_34400A\n", + " type: Agilent_34400A\n", + " enable_forced_reconnect: true\n", + " address: GPIB::1::65535::INSTR\n", + " init:\n", + " visalib: 'Agilent_34400A.yaml@sim'\n", + " parameters:\n", + " volt: {monitor: true}\n", + " mock_dac:\n", + " driver: qcodes.tests.instrument_mocks\n", + " type: DummyInstrument\n", + " enable_forced_reconnect: true\n", + " init:\n", + " # To pass an list of items use {}.\n", + " gates: {\"ch1\", \"ch2\"}\n", + " add_parameters:\n", + " Bx: {source: ch1, label: Bx, unit: T,\n", + " scale: 28, limits: \"-1,1\", monitor: true}\n", + " mock_dac2:\n", + " driver: qcodes.tests.instrument_mocks\n", + " type: DummyInstrument\n", + " enable_forced_reconnect: true\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### QCoDeS config entries related to Station" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "QCoDeS config contains entries that are related to the `Station` and its YAML configuration. Refer to [the description of the `'station'` section in QCoDeS config](http://qcodes.github.io/Qcodes/user/configuration.html?highlight=station#default-config) for more specific information." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using Station with YAML configuration file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This section is going to briefly describe the usage of `Station` with the YAML configuration file. For details, refer to the docstrings of the methods of the `Station` class.\n", + "\n", + "A `Station` with a YAML configuration file can be created by passing the file name (or file name with absolute path) to `Station`s constructor. File name and location resolution also takes into account related entries in the `'station'` section of the QCoDeS config, refer to their documentation for more information.\n", + "\n", + "```python\n", + "station = Station(config_file='qutech_station_25.yaml')\n", + "```\n", + "\n", + "Alternatively, `load_config_file` method can be called on an already instantiated station to load the config file.\n", + "\n", + "```python\n", + "station = Station()\n", + "stataion.load_config_file=r'Q:\\\\station_data\\\\qutech_station_25.yaml')\n", + "```\n", + "\n", + "In case the configuration is already available as a YAML string, then that configuration can be loaded using `Station`'s `load_config` method, refer to it's docstring and signature for more information.\n", + "\n", + "Once the YAML configuration is loaded, the `load_instrument` method of the `Station` can be used to instantiate a particular instrument that is described in the YAML configuration. Calling this method not only will return an instance of the linstantiated instrument, but will also add it to the station object.\n", + "\n", + "For example, to instantiate the `qdac` instrument from the YAML configuration example from the section above, just execute the following:\n", + "```python\n", + "loaded_qdac = station.load_instrument('qdac')\n", + "```\n", + "\n", + "Note the `load_instrument`'s `revive_instance` argument, as well as `enable_force_reconnect` setting from the YAML configuration - these define what to do in case an instrument with the given name has already been instantiated in this python session.\n", + "\n", + "There is a more convenient way to load the instruments. Upon load of the YAML configuration, convenient `load_` methods are being generated on the `Station` object. Users can make use of tab-completion in their development environments to list what instruments can be loads by the station object from the loaded YAML configuration. For example, loading the QDac above can also be done like this:\n", + "```python\n", + "conveniently_loaded_qdac = station.load_qdac()\n", + "```\n", + "\n", + "Note that instruments are instantiated only when `load_*` methods are called. This means that loading a YAML configuration does NOT automatically instantiate anything.\n", + "\n", + "For the instruments loaded with the `load_*` methods, it is recommended to use `Station`'s `close_and_remove_instrument` method for closing and removing those from the station." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [default]", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From e34d72bacb6ef3192395f2580996119c3f717f93 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Wed, 8 May 2019 18:24:00 +0200 Subject: [PATCH 64/82] Refer Station example notebook in community guide --- docs/community/objects.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/community/objects.rst b/docs/community/objects.rst index 0e0cb451cd3..f127ad35658 100644 --- a/docs/community/objects.rst +++ b/docs/community/objects.rst @@ -36,7 +36,11 @@ to use. Normal text shows the container includes and uses of this object. Station ------- -Read more about :ref:`station_api`. +A convenient container for instruments, parameters, and more. + +More information: +- [`Station` example notebook](../examples/Station.ipynb) +- :ref:`station_api` .. todo:: is this how we want it ? or like the one below ? From acded1eb709cdee6d77468e9de0eaa77334905fc Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Wed, 8 May 2019 18:24:29 +0200 Subject: [PATCH 65/82] Refer to Station notebook in snapshots notebook --- docs/examples/DataSet/Working with snapshots.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/examples/DataSet/Working with snapshots.ipynb b/docs/examples/DataSet/Working with snapshots.ipynb index b80de68a3b3..3d212f1e5d1 100644 --- a/docs/examples/DataSet/Working with snapshots.ipynb +++ b/docs/examples/DataSet/Working with snapshots.ipynb @@ -272,9 +272,9 @@ "\n", "Experimental setups are large, and instruments tend to be quite complex in that they comprise many parameters and other stateful parts. It would be very time-consuming for the user to manually go through every instrument and parameter, and collect the snapshot data.\n", "\n", - "Here is where the concept of station comes into play. Instruments, parameters, and other submodules can be added to a station. In turn, the station has its `snapshot` method that allows to create a collective, single snapshot of all the instruments, parameters, and submodules.\n", + "Here is where the concept of station comes into play. Instruments, parameters, and other submodules can be added to a [`Station` object](../Station.ipynb) ([nbviewer.jupyter.org link](https://nbviewer.jupyter.org/github/QCoDeS/Qcodes/tree/master/docs/examples/Station.ipynb)). In turn, the station has its `snapshot` method that allows to create a collective, single snapshot of all the instruments, parameters, and submodules.\n", "\n", - "Note that in this article the focus is on the snapshot feature of the QCoDeS `Station`, while it has some other (mostly legacy) features." + "Note that in this article the focus is on the snapshot feature of the QCoDeS `Station`, while it has some other features (also some legacy once)." ] }, { @@ -799,7 +799,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python [default]", "language": "python", "name": "python3" }, @@ -813,7 +813,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.6" + "version": "3.6.8" }, "toc": { "base_numbering": 1, From 6f3a94fa4e4116b6a50764b3b215a2565943147e Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 9 May 2019 10:50:02 +0200 Subject: [PATCH 66/82] Make `Monitor` optional --- qcodes/config/qcodesrc.json | 3 ++- qcodes/config/qcodesrc_schema.json | 6 +++++ qcodes/station.py | 19 +++++++++++---- qcodes/tests/test_station.py | 37 +++++++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/qcodes/config/qcodesrc.json b/qcodes/config/qcodesrc.json index a0873e9a8d9..ce7cfe1fe06 100644 --- a/qcodes/config/qcodesrc.json +++ b/qcodes/config/qcodesrc.json @@ -65,7 +65,8 @@ "station": { "enable_forced_reconnect": false, "default_folder": ".", - "default_file": "instrument_config.yml" + "default_file": "instrument_config.yml", + "use_monitor": false }, "GUID_components": { "location": 0, diff --git a/qcodes/config/qcodesrc_schema.json b/qcodes/config/qcodesrc_schema.json index 0f24fa2d3f3..aa9b5aa5806 100644 --- a/qcodes/config/qcodesrc_schema.json +++ b/qcodes/config/qcodesrc_schema.json @@ -252,7 +252,13 @@ "type": ["string", "null"], "default": null, "description": "default file name, specifying the file to load, when none is specified. Can be a relative or absolute path" + }, + "use_monitor": { + "type": "boolean", + "default": false, + "description": "Update the monitor based on the monitor attribute specified in the instruments section of the station config yaml file." } + }, "description": "Optional feature for qdev-wrappers package: Setting for the StationConfigurator." }, diff --git a/qcodes/station.py b/qcodes/station.py index 281c76aa08f..b917b0b7cf0 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -41,6 +41,10 @@ def get_config_default_file() -> Optional[str]: return qcodes.config["station"]["default_file"] +def get_config_use_monitor() -> Optional[str]: + return qcodes.config["station"]["use_monitor"] + + class Station(Metadatable, DelegateAttributes): """ @@ -76,7 +80,7 @@ class Station(Metadatable, DelegateAttributes): def __init__(self, *components: Metadatable, config_file: Optional[str] = None, - monitor: Monitor = None, default: bool = True, + use_monitor: Optional[bool] = None, default: bool = True, update_snapshot: bool = True, **kwargs) -> None: super().__init__(**kwargs) @@ -93,7 +97,7 @@ def __init__(self, *components: Metadatable, for item in components: self.add_component(item, update_snapshot=update_snapshot) - self.monitor = monitor + self.use_monitor = use_monitor self.config_file = config_file self.default_measurement: List[Any] = [] @@ -373,7 +377,6 @@ def load_instrument(self, identifier: str, with suppress(KeyError): self.close_and_remove_instrument(identifier) - # instantiate instrument init_kwargs = instr_cfg.get('init', {}) # somebody might have a empty init section in the config @@ -450,6 +453,12 @@ def add_parameter_from_dict(instr: Parameter, name: str, instr.add_parameter(name, Parameter) setup_parameter_from_dict(instr, name, options) + def update_monitor(): + if ((self.use_monitor is None and get_config_use_monitor()) + or self.use_monitor): + # restart Monitor + Monitor(*self._monitor_parameters) + for name, options in instr_cfg.get('parameters', {}).items(): setup_parameter_from_dict(instr, name, options) @@ -458,7 +467,7 @@ def add_parameter_from_dict(instr: Parameter, name: str, self.add_component(instr) - # restart Monitor - Monitor(*self._monitor_parameters) + update_monitor() + # Monitor(*self._monitor_parameters) return instr diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 6cfe9e4fe77..97ea984510e 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -10,10 +10,11 @@ from qcodes.instrument.parameter import DelegateParameter from qcodes import Instrument from qcodes.station import Station +from qcodes.instrument.parameter import Parameter +from qcodes.monitor.monitor import Monitor from qcodes.tests.instrument_mocks import ( DummyInstrument) from qcodes.tests.test_combined_par import DumyPar -from qcodes.instrument.parameter import Parameter from qcodes.tests.test_config import default_config @pytest.fixture(autouse=True) @@ -237,6 +238,9 @@ def example_station_config(): enable_forced_reconnect: true init: gates: {{"ch1", "ch2"}} + parameters: + ch1: + monitor: true mock_dac2: driver: qcodes.tests.instrument_mocks type: DummyInstrument @@ -596,5 +600,36 @@ def test_channel_instrument(): assert mock.T.unit == 'mK' +def test_monitor_not_loaded_by_default(example_station_config): + st = Station(config_file=example_station_config) + st.load_instrument('mock_dac') + assert Monitor.running is None + + +def test_monitor_loaded_if_specified(example_station_config): + st = Station(config_file=example_station_config, use_monitor=True) + st.load_instrument('mock_dac') + assert Monitor.running is not None + assert len(Monitor.running._parameters) == 1 + assert Monitor.running._parameters[0].name == 'ch1' + Monitor.running.stop() + + +def test_monitor_loaded_by_default_if_in_config(example_station_config): + qcodes.config["station"]['use_monitor'] = True + st = Station(config_file=example_station_config) + st.load_instrument('mock_dac') + assert Monitor.running is not None + assert len(Monitor.running._parameters) == 1 + assert Monitor.running._parameters[0].name == 'ch1' + Monitor.running.stop() + + +def test_monitor_not_loaded_if_specified(example_station_config): + st = Station(config_file=example_station_config, use_monitor=False) + st.load_instrument('mock_dac') + assert Monitor.running is None + + From 7d343f9ea4cefa7b4b669041d1fdf1664139c198 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 9 May 2019 10:54:52 +0200 Subject: [PATCH 67/82] Remove trailing whitespaces --- qcodes/instrument/parameter.py | 4 ++-- qcodes/tests/test_station.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index e5611652f83..2b703363d49 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -35,8 +35,8 @@ measurement types. - :class:`.DelegateParameter` is intended proxy-ing other parameters. - It forwards its ``get`` and ``set`` to the underlying source parameter, - while allowing to specify label/unit/etc that is different from the + It forwards its ``get`` and ``set`` to the underlying source parameter, + while allowing to specify label/unit/etc that is different from the source parameter. - :class:`.ArrayParameter` is an older base class for array-valued parameters. diff --git a/qcodes/tests/test_station.py b/qcodes/tests/test_station.py index 97ea984510e..14cdf281dc6 100644 --- a/qcodes/tests/test_station.py +++ b/qcodes/tests/test_station.py @@ -325,7 +325,7 @@ def test_station_config_path_resolution(example_station_config): # When qcodes config has only the default_folder setting specified to an # existing folder, and default_file setting is not specified, then # passing the name of a station configuration file, that exists in that - # default_folder, as an argument to the Station is expected to result + # default_folder, as an argument to the Station is expected to result # in a station with loaded configuration. assert station_config_has_been_loaded(Station(config_file=path.name)) @@ -445,7 +445,7 @@ def test_revive_instance(): mock3 = st.load_instrument('mock', revive_instance=True) assert mock3 == mock2 assert mock3 == st.mock - + def test_init_parameters(): st = station_from_config_str(""" From aa63d5bbdf5b3ecc27ce3e9b176cda63884f8301 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 9 May 2019 13:31:16 +0200 Subject: [PATCH 68/82] Fix typing, add `checked_getattr` --- qcodes/station.py | 33 ++++++++++++++++++++++----------- qcodes/utils/helpers.py | 14 +++++++++++++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index b917b0b7cf0..6ce31ec5942 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -1,6 +1,7 @@ """Station objects - collect all the equipment you use to do an experiment.""" from contextlib import suppress -from typing import Dict, List, Optional, Sequence, Any, cast, AnyStr, IO +from typing import ( + Dict, List, Optional, Sequence, Any, cast, AnyStr, IO) from functools import partial import importlib import logging @@ -11,9 +12,10 @@ import qcodes from qcodes.utils.metadata import Metadatable -from qcodes.utils.helpers import make_unique, DelegateAttributes, YAML +from qcodes.utils.helpers import ( + make_unique, DelegateAttributes, YAML, checked_getattr) -from qcodes.instrument.base import Instrument +from qcodes.instrument.base import Instrument, InstrumentBase from qcodes.instrument.parameter import ( Parameter, ManualParameter, StandardParameter, DelegateParameter) @@ -402,18 +404,27 @@ def load_instrument(self, identifier: str, # local function to refactor common code from defining new parameter # and setting existing one - def resolve_parameter_identifier(instrument: Instrument, + def resolve_parameter_identifier(instrument: InstrumentBase, identifier: str) -> Parameter: - p = instrument - for level in identifier.split('.'): - p = getattr(p, level) - if not isinstance(p, Parameter): + + parts = identifier.split('.') + try: + for level in parts[:-1]: + instrument = checked_getattr(instrument, level, + InstrumentBase) + except TypeError: + raise RuntimeError( + f'Cannot resolve `{level}` in {identifier} to an ' + f'instrument/channel for base instrument ' + f'{instrument!r}.') + try: + return checked_getattr(instrument, parts[-1], Parameter) + except TypeError: raise RuntimeError( f'Cannot resolve parameter identifier `{identifier}` to ' f'a parameter on instrument {instrument!r}.') - return cast(Parameter, p) - def setup_parameter_from_dict(instr: Parameter, name: str, + def setup_parameter_from_dict(instr: Instrument, name: str, options: Dict[str, Any]): parameter = resolve_parameter_identifier(instr, name) for attr, val in options.items(): @@ -438,7 +449,7 @@ def setup_parameter_from_dict(instr: Parameter, name: str, if 'initial_value' in options: parameter.set(options['initial_value']) - def add_parameter_from_dict(instr: Parameter, name: str, + def add_parameter_from_dict(instr: Instrument, name: str, options: Dict[str, Any]): # keep the original dictionray intact for snapshot options = copy(options) diff --git a/qcodes/utils/helpers.py b/qcodes/utils/helpers.py index 46e4fbc0717..e323481af81 100644 --- a/qcodes/utils/helpers.py +++ b/qcodes/utils/helpers.py @@ -7,7 +7,7 @@ import os from collections.abc import Iterator, Sequence, Mapping from copy import deepcopy -from typing import Dict, List, Any +from typing import Dict, List, Any, TypeVar, Type from contextlib import contextmanager from asyncio import iscoroutinefunction from inspect import signature @@ -723,3 +723,15 @@ def _ruamel_importer(): def get_qcodes_path(*subfolder) -> str: path = os.sep.join(qcodes.__file__.split(os.sep)[:-1]) return os.path.join(path, *subfolder) + os.sep + + +X = TypeVar('X') + + +def checked_getattr(instance: Any, + attribute: str, + expected_type: Type[X]) -> X: + attr: Any = getattr(instance, attribute) + if not isinstance(attr, expected_type): + raise TypeError() + return attr From b351b7fc08c4facfc27bd9795cdad3022005a0a3 Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 9 May 2019 13:48:29 +0200 Subject: [PATCH 69/82] Clean up comment --- qcodes/station.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 6ce31ec5942..3936fa2432f 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -472,13 +472,8 @@ def update_monitor(): for name, options in instr_cfg.get('parameters', {}).items(): setup_parameter_from_dict(instr, name, options) - for name, options in instr_cfg.get('add_parameters', {}).items(): add_parameter_from_dict(instr, name, options) - self.add_component(instr) - update_monitor() - # Monitor(*self._monitor_parameters) - return instr From 2f52bdf95daa7a46dfc1a313a19d14999e21c25c Mon Sep 17 00:00:00 2001 From: Dominik Vogel Date: Thu, 9 May 2019 14:52:44 +0200 Subject: [PATCH 70/82] Remove warning when creating station without default config --- qcodes/config/qcodesrc.json | 2 +- qcodes/station.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/qcodes/config/qcodesrc.json b/qcodes/config/qcodesrc.json index ce7cfe1fe06..550798cdb2c 100644 --- a/qcodes/config/qcodesrc.json +++ b/qcodes/config/qcodesrc.json @@ -65,7 +65,7 @@ "station": { "enable_forced_reconnect": false, "default_folder": ".", - "default_file": "instrument_config.yml", + "default_file": null, "use_monitor": false }, "GUID_components": { diff --git a/qcodes/station.py b/qcodes/station.py index 3936fa2432f..5419fa9ab70 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -280,11 +280,12 @@ def get_config_file_path( if filename is not None: raise FileNotFoundError(path) else: - log.warning( - 'Could not load default instrument config for Station: \n' - f'File {get_config_default_file()} not found. \n' - 'You can change the default config file in ' - '`qcodesrc.json`.') + if get_config_default_file() is not None: + log.warning( + 'Could not load default config for Station: \n' + f'File {get_config_default_file()} not found. \n' + 'You can change the default config file in ' + '`qcodesrc.json`.') return with open(path, 'r') as f: From ea3e8dfd3a3e16e8bc4cac438b224210bc67a7b1 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Thu, 9 May 2019 15:21:30 +0200 Subject: [PATCH 71/82] Dont convert non-grid dataframe to xarray - slow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mikhail Astafev. When building docs locally, the “dataset context manager” example notebook takes too much time to execute to_xarray in the scatter plot example. is it because xarray is trying to make a grid out of 5000 ungridded points? It's weird because the conversion is executed on a 10-rows dataframe - a very small one. But the resulting xarray has 5000 in its dimensions, as if it was made out of the original huge dataframe. I tried with "copy" - still the same problem. to_dict, to_json, etc - work on a copy, no problem, but to_xarray goes nuts and processes all the 5000 rows from the parent dataframe which it should not know about since I made a “copy”. --- docs/examples/DataSet/Dataset Context Manager.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/DataSet/Dataset Context Manager.ipynb b/docs/examples/DataSet/Dataset Context Manager.ipynb index 28b262b819f..4e55c914d0a 100644 --- a/docs/examples/DataSet/Dataset Context Manager.ipynb +++ b/docs/examples/DataSet/Dataset Context Manager.ipynb @@ -6706,7 +6706,7 @@ } ], "source": [ - "datasaver.dataset.get_data_as_pandas_dataframe()['dmm_v1'][0:10].to_xarray()" + "#datasaver.dataset.get_data_as_pandas_dataframe()['dmm_v1'][0:10].to_xarray()" ] }, { From fc000c62be418fd5e19079159aae6945cee4df8e Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Thu, 9 May 2019 15:23:17 +0200 Subject: [PATCH 72/82] Fix link to Station example notebook in RST docs --- docs/community/objects.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/community/objects.rst b/docs/community/objects.rst index f127ad35658..9d1d8257a2f 100644 --- a/docs/community/objects.rst +++ b/docs/community/objects.rst @@ -39,8 +39,9 @@ Station A convenient container for instruments, parameters, and more. More information: -- [`Station` example notebook](../examples/Station.ipynb) -- :ref:`station_api` + +- `Station example notebook <../examples/Station.ipynb>`_ +- :ref:`station_api` API reference .. todo:: is this how we want it ? or like the one below ? From 20befaf7dc028d79841f7cf8a3f17177cce9c6d5 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Thu, 9 May 2019 15:24:16 +0200 Subject: [PATCH 73/82] Fix lists and links sphinx rendering of Station example notebook --- docs/examples/Station.ipynb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/examples/Station.ipynb b/docs/examples/Station.ipynb index c6c92571d97..97e97e19329 100644 --- a/docs/examples/Station.ipynb +++ b/docs/examples/Station.ipynb @@ -7,10 +7,11 @@ "# Station\n", "\n", "Here, the following topics are going to be covered:\n", - "* What is a station\n", - "* How to create it, and work with it\n", - "* Snapshot of a station\n", - "* Configuring station using a YAML configuration file" + "\n", + "- What is a station\n", + "- How to create it, and work with it\n", + "- Snapshot of a station\n", + "- Configuring station using a YAML configuration file" ] }, { @@ -269,7 +270,7 @@ "\n", "For example, the `Measurement` object accepts a station argument exactly for the purpose of storing a snapshot of the whole experimental setup next to the measured data.\n", "\n", - "Read more about snapshots in general, how to work with them, station's snapshot in particular, and more -- in [__Working with snapshots__ example notebook](DataSet/Working with snapshots.ipynb) ([nbviewer.jupyter.org link](https://nbviewer.jupyter.org/github/QCoDeS/Qcodes/tree/master/docs/examples/DataSet/Working with snapshots.ipynb))." + "Read more about snapshots in general, how to work with them, station's snapshot in particular, and more -- in [\"Working with snapshots\" example notebook](DataSet/Working with snapshots.ipynb) ([nbviewer.jupyter.org link](https://nbviewer.jupyter.org/github/QCoDeS/Qcodes/tree/master/docs/examples/DataSet/Working with snapshots.ipynb))." ] }, { @@ -289,9 +290,10 @@ "Note that one is not obliged to use the YAML configuration for setting up one's `Station` (for example, as at the top of this notebook).\n", "\n", "Below the following is discussed:\n", - "* The structure of the YAML configuration file\n", - "* `Station`s methods realted to working with the YAML configuration\n", - "* Entries in QCoDeS config that are related to `Station`" + "\n", + "- The structure of the YAML configuration file\n", + "- `Station`s methods realted to working with the YAML configuration\n", + "- Entries in QCoDeS config that are related to `Station`" ] }, { @@ -491,7 +493,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "QCoDeS config contains entries that are related to the `Station` and its YAML configuration. Refer to [the description of the `'station'` section in QCoDeS config](http://qcodes.github.io/Qcodes/user/configuration.html?highlight=station#default-config) for more specific information." + "QCoDeS config contains entries that are related to the `Station` and its YAML configuration. Refer to [the description of the 'station' section in QCoDeS config](http://qcodes.github.io/Qcodes/user/configuration.html?highlight=station#default-config) for more specific information." ] }, { From bdc38a874c1a056c85f8f81eecafce509ec6ab0f Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Thu, 9 May 2019 15:24:48 +0200 Subject: [PATCH 74/82] Fix lists and link rendering in sphinx again now for snapshot notebook --- docs/examples/DataSet/Working with snapshots.ipynb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/examples/DataSet/Working with snapshots.ipynb b/docs/examples/DataSet/Working with snapshots.ipynb index 3d212f1e5d1..de090dec833 100644 --- a/docs/examples/DataSet/Working with snapshots.ipynb +++ b/docs/examples/DataSet/Working with snapshots.ipynb @@ -7,10 +7,11 @@ "# Working with snapshots\n", "\n", "Here, the following topics are going to be covered:\n", - "* What is a snapshot\n", - "* How to create it\n", - "* How it is saved next to the measurement data\n", - "* How to extract snapshot from the dataset" + "\n", + "- What is a snapshot\n", + "- How to create it\n", + "- How it is saved next to the measurement data\n", + "- How to extract snapshot from the dataset" ] }, { @@ -272,7 +273,7 @@ "\n", "Experimental setups are large, and instruments tend to be quite complex in that they comprise many parameters and other stateful parts. It would be very time-consuming for the user to manually go through every instrument and parameter, and collect the snapshot data.\n", "\n", - "Here is where the concept of station comes into play. Instruments, parameters, and other submodules can be added to a [`Station` object](../Station.ipynb) ([nbviewer.jupyter.org link](https://nbviewer.jupyter.org/github/QCoDeS/Qcodes/tree/master/docs/examples/Station.ipynb)). In turn, the station has its `snapshot` method that allows to create a collective, single snapshot of all the instruments, parameters, and submodules.\n", + "Here is where the concept of station comes into play. Instruments, parameters, and other submodules can be added to a [Station object](../Station.ipynb) ([nbviewer.jupyter.org link](https://nbviewer.jupyter.org/github/QCoDeS/Qcodes/tree/master/docs/examples/Station.ipynb)). In turn, the station has its `snapshot` method that allows to create a collective, single snapshot of all the instruments, parameters, and submodules.\n", "\n", "Note that in this article the focus is on the snapshot feature of the QCoDeS `Station`, while it has some other features (also some legacy once)." ] From 23d9e3becba1617df7c63ddbdc741e8406f24c89 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Thu, 9 May 2019 15:25:31 +0200 Subject: [PATCH 75/82] Use 'station' qcodes config entry not the old 'station_configurator' --- docs/examples/Configuring_QCoDeS.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/examples/Configuring_QCoDeS.ipynb b/docs/examples/Configuring_QCoDeS.ipynb index 288bb24cf7b..466357a4fa7 100644 --- a/docs/examples/Configuring_QCoDeS.ipynb +++ b/docs/examples/Configuring_QCoDeS.ipynb @@ -49,7 +49,7 @@ " 'defaultcolormap': 'hot'},\n", " 'user': {'scriptfolder': 'c:\\\\Users\\\\jenielse\\\\myscripts\\\\',\n", " 'mainfolder': 'c:\\\\Users\\\\jenielse\\\\mymainfolder\\\\'},\n", - " 'station_configurator': {'enable_forced_reconnect': False,\n", + " 'station': {'enable_forced_reconnect': False,\n", " 'default_folder': 'c:\\\\Users\\\\jenielse\\\\mymainfolder\\\\',\n", " 'default_file': 'instrument_config.yaml'}}" ] @@ -82,7 +82,7 @@ " 'pyqtmaxplots': 100,\n", " 'defaultcolormap': 'hot'},\n", " 'user': {'scriptfolder': '.', 'mainfolder': '.'},\n", - " 'station_configurator': {'enable_forced_reconnect': False,\n", + " 'station': {'enable_forced_reconnect': False,\n", " 'default_folder': '.',\n", " 'default_file': 'instrument_config.yml'}}" ] @@ -235,7 +235,7 @@ " 'defaultcolormap': 'hot'},\n", " 'user': {'scriptfolder': 'c:\\\\Users\\\\jenielse\\\\myscripts\\\\',\n", " 'mainfolder': 'c:\\\\Users\\\\jenielse\\\\mymainfolder\\\\'},\n", - " 'station_configurator': {'enable_forced_reconnect': False,\n", + " 'station': {'enable_forced_reconnect': False,\n", " 'default_folder': 'c:\\\\Users\\\\jenielse\\\\mymainfolder\\\\',\n", " 'default_file': 'instrument_config.yaml'}}" ] From d32badd59d8f8a5425fc6946778447cb9cf0d3dd Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Thu, 9 May 2019 15:26:12 +0200 Subject: [PATCH 76/82] Improve qcodes config schema for 'station' field --- qcodes/config/qcodesrc_schema.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qcodes/config/qcodesrc_schema.json b/qcodes/config/qcodesrc_schema.json index aa9b5aa5806..a4a707cb242 100644 --- a/qcodes/config/qcodesrc_schema.json +++ b/qcodes/config/qcodesrc_schema.json @@ -241,26 +241,25 @@ "enable_forced_reconnect": { "type": "boolean", "default": false, - "description": "if set to true, on instantiation of an existing instrument, the existing will be disconnected." + "description": "If set to true, on instantiation of an existing instrument from station configuration, the existing instrument instance will be closed." }, "default_folder": { "type": ["string", "null"], "default": null, - "description": "default folder where to look for a station configurator config file" + "description": "Default folder where to look for a YAML station configuration file" }, "default_file": { "type": ["string", "null"], "default": null, - "description": "default file name, specifying the file to load, when none is specified. Can be a relative or absolute path" + "description": "Default file name, specifying a YAML station configuration file to load, when none is specified. Can be a relative or absolute path" }, "use_monitor": { "type": "boolean", "default": false, "description": "Update the monitor based on the monitor attribute specified in the instruments section of the station config yaml file." } - }, - "description": "Optional feature for qdev-wrappers package: Setting for the StationConfigurator." + "description": "Settings for QCoDeS Station." }, "GUID_components":{ "type": "object", From bfc15744561d3c5b0bf0a1733efc050ac6acf2aa Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Thu, 9 May 2019 15:46:03 +0200 Subject: [PATCH 77/82] Add proper fix for slicing multiindex dataframe ... in Dataset context manager example notebook. Based on this https://github.com/pandas-dev/pandas/issues/3686. --- docs/examples/DataSet/Dataset Context Manager.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/examples/DataSet/Dataset Context Manager.ipynb b/docs/examples/DataSet/Dataset Context Manager.ipynb index 4e55c914d0a..b1847d8b26b 100644 --- a/docs/examples/DataSet/Dataset Context Manager.ipynb +++ b/docs/examples/DataSet/Dataset Context Manager.ipynb @@ -6706,7 +6706,9 @@ } ], "source": [ - "#datasaver.dataset.get_data_as_pandas_dataframe()['dmm_v1'][0:10].to_xarray()" + "#df_sliced = datasaver.dataset.get_data_as_pandas_dataframe()['dmm_v1'].sort_index()[0:10]\n", + "#df_sliced.index = df_sliced.index.remove_unused_levels()\n", + "#df_sliced.to_xarray()" ] }, { From 6329a17d41bfc217532685d8f7c364a230763b98 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Thu, 9 May 2019 16:02:28 +0200 Subject: [PATCH 78/82] Remove warnings from station notebook, minor fixes --- docs/examples/Station.ipynb | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/docs/examples/Station.ipynb b/docs/examples/Station.ipynb index 97e97e19329..91163042dd7 100644 --- a/docs/examples/Station.ipynb +++ b/docs/examples/Station.ipynb @@ -92,15 +92,6 @@ "execution_count": 4, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Could not load default instrument config for Station: \n", - "File instrument_config.yml not found. \n", - "You can change the default config file in `qcodesrc.json`.\n" - ] - }, { "data": { "text/plain": [ @@ -127,7 +118,7 @@ { "data": { "text/plain": [ - "{'p': ,\n", + "{'p': ,\n", " 'instr': }" ] }, @@ -152,17 +143,7 @@ "cell_type": "code", "execution_count": 6, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Could not load default instrument config for Station: \n", - "File instrument_config.yml not found. \n", - "You can change the default config file in `qcodesrc.json`.\n" - ] - } - ], + "outputs": [], "source": [ "station = Station(p, instr)" ] @@ -214,7 +195,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 8, @@ -264,9 +245,7 @@ "source": [ "## Snapshot of a Station\n", "\n", - "Experimental setups are large, and instruments tend to be quite complex in that they comprise many parameters and other stateful parts. It would be very time-consuming for the user to manually go through every instrument and parameter, and collect the snapshot data.\n", - "\n", - "The station comes to help here as well. Instruments, parameters, and other submodules can be added to a station. In turn, the station has its `snapshot` method that allows to create a collective, single snapshot of all the instruments, parameters, and submodules.\n", + "The station has a `snapshot` method that allows to create a collective, single snapshot of all the instruments, parameters, and submodules that have been added to it. It would be very time-consuming for the user to manually go through every instrument and parameter, and collect the snapshot data.\n", "\n", "For example, the `Measurement` object accepts a station argument exactly for the purpose of storing a snapshot of the whole experimental setup next to the measured data.\n", "\n", @@ -281,7 +260,7 @@ "\n", "The initialization part of the code where one instantiates instruments, set's up proper initial values for parameters, etc. can be quite long and tedious to maintain. For example, when a certain instrument is no longer needed, usually users just comment out the lines of initialization script which are related to the said instrument, and re-run the initialization script. Sharing initialization scripts is also difficult because each user may have a different expecation on the format it.\n", "\n", - "These (and more) concerns are to be solved by YAML configuration of the `Station`. This feature originally comes from [qdev-dk](https://github.com/qdev-dk/qdev-wrappers) users where it was developed under the name `StationConfigurator`.\n", + "These (and more) concerns are to be solved by YAML configuration of the `Station` (formerly known to some users under the name `StationConfigurator`).\n", "\n", "The YAML configuration file allows to statically and uniformly specify settings of all the instruments (and their parameters) that the measurement setup (the \"physical\" station) consists of, and load them with those settings on demand. The `Station` object implements convenient methods for this.\n", "\n", From 33ebe86879054e69cf338dd548bd0d8a7869ddf2 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Thu, 9 May 2019 16:59:56 +0200 Subject: [PATCH 79/82] Add important clarifications on add_parameter and exponential numbers format --- docs/examples/Station.ipynb | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/examples/Station.ipynb b/docs/examples/Station.ipynb index 91163042dd7..93c1db82817 100644 --- a/docs/examples/Station.ipynb +++ b/docs/examples/Station.ipynb @@ -282,6 +282,22 @@ "### Example of YAML Station configuration" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Below is an example YAML station configuration file. All the fields are explained in the inline comments. Read it through.\n", + "\n", + "When exploring the YAML file capabilities, please note the difference between `parameters` section and `add_parameters` section. In the example file below, for the `QDac` instrument, the `Bx` parameter is going to be a new, additional parameter. This new `Bx` parameter will have its `limits`, `scale`, etc. __different__ from its \"source\" parameter `ch02.v` that it controls. Specifically this means that when setting `Bx` to `2.0`:\n", + "\n", + "1. the value of `2.0` is being validated against the limits of `Bx` (`0.0, 3.0`),\n", + "2. then the raw (\"scaled\") value of `130.468` (`= 2.0 * 65.234`) is passed to the `ch02.v` parameter,\n", + "3. then that value of `130.468` is validated against the limits of `ch02.v` (`0.0, 1.5e+3`),\n", + "4. then the raw (\"scaled\") value of `1.30468` (`= 130.468 * 0.01`) is finally passed to the physical instrument.\n", + "\n", + "Please also note that when trying in numbers in exponential form +e333dfbg" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -370,7 +386,7 @@ " # Set new inter_delay.\n", " inter_delay: 0.01\n", " # Set new step.\n", - " step: 0.0001\n", + " step: 1e-4\n", " # If this field is given, and contains two \n", " # comma-separated numbers like here, then the parameter\n", " # gets a new `Numbers` validator with these values as\n", @@ -390,8 +406,8 @@ " monitor: true\n", " # As in all YAML files a one-line notation can also be \n", " # used, here is an example.\n", - " ch03.v: {alias: Q1lplg1, label: my label}\n", - " ch04.v: {monitor: true}\n", + " ch02.v: {scale: 0.01, limits: '0.0,1.5e+3', label: my label}\n", + " ch04.v: {alias: Q1lplg1, monitor: true}\n", " #\n", " # This section allows to add new parameters to the\n", " # instrument instance which are based on existing parameters\n", @@ -423,12 +439,12 @@ " unit: T\n", " # Other fields have the same purpose and behavior as for\n", " # the entries in the `add_parameter` section.\n", - " scale: 123.45\n", + " scale: 65.243\n", " inter_delay: 0.001\n", " post_delay: 0.05\n", - " step: 1.0\n", - " limits: 20.0,150.0\n", - " initial_value: 70.0\n", + " step: 0.001\n", + " limits: 0.0,3.0\n", + " initial_value: 0.0\n", " # For the sake of example, we decided not to monitor this\n", " # parameter in QCoDeS `Monitor`.\n", " #monitor: true\n", From b7608cbfd248dcccf62c0418d163bda3214f00b2 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Fri, 10 May 2019 10:29:34 +0200 Subject: [PATCH 80/82] Note exponental numbers formal in station notebook --- docs/examples/Station.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/Station.ipynb b/docs/examples/Station.ipynb index 93c1db82817..9824ee94dcc 100644 --- a/docs/examples/Station.ipynb +++ b/docs/examples/Station.ipynb @@ -295,7 +295,7 @@ "3. then that value of `130.468` is validated against the limits of `ch02.v` (`0.0, 1.5e+3`),\n", "4. then the raw (\"scaled\") value of `1.30468` (`= 130.468 * 0.01`) is finally passed to the physical instrument.\n", "\n", - "Please also note that when trying in numbers in exponential form +e333dfbg" + "Please also note that when trying in numbers in exponential form, it is required to provide `+` and `-` signs after `e`, for example, `7.8334e+5` and `2.5e-23`. Refer to YAML file format specification for more information." ] }, { From a1713e02ae2cfe73167069256f9b50b60eb96e29 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Fri, 10 May 2019 10:31:56 +0200 Subject: [PATCH 81/82] Note simulated instrument in station notebook --- docs/examples/Station.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/examples/Station.ipynb b/docs/examples/Station.ipynb index 9824ee94dcc..91bcf58574d 100644 --- a/docs/examples/Station.ipynb +++ b/docs/examples/Station.ipynb @@ -450,7 +450,8 @@ " #monitor: true\n", " #\n", " # More example instruments, just for the sake of example.\n", - " # Note that\n", + " # Note that configuring simulated instruments also works,\n", + " # see the use of 'visalib' argument field below\n", " dmm1:\n", " driver: qcodes.instrument_drivers.agilent.Agilent_34400A\n", " type: Agilent_34400A\n", From 3768f2fee21f5ecfd131d8bf2acf35f8ad2453b3 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Fri, 10 May 2019 10:33:54 +0200 Subject: [PATCH 82/82] Fix style in station.py --- qcodes/station.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qcodes/station.py b/qcodes/station.py index 5419fa9ab70..8550cc4ccb6 100644 --- a/qcodes/station.py +++ b/qcodes/station.py @@ -325,7 +325,8 @@ def update_load_instrument_methods(): update_load_instrument_methods() def close_and_remove_instrument(self, - instrument: Union[Instrument, str]) -> None: + instrument: Union[Instrument, str] + ) -> None: """ Safely close instrument and remove from station and monitor list """ @@ -470,7 +471,7 @@ def update_monitor(): or self.use_monitor): # restart Monitor Monitor(*self._monitor_parameters) - + for name, options in instr_cfg.get('parameters', {}).items(): setup_parameter_from_dict(instr, name, options) for name, options in instr_cfg.get('add_parameters', {}).items():