From 9d017793171b07a8b504c33865834fb8a57d0bd7 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Wed, 25 Oct 2017 10:44:43 +0200 Subject: [PATCH 01/18] Fix typo in wrapping of set_raw method (#815) --- 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 71b9dd255cb..b2ca23817ca 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -209,11 +209,11 @@ def __init__(self, name: str, 'define get_raw in your subclass instead.' ) self.get = self._wrap_get(self.get) if hasattr(self, 'set_raw'): - self.set = self._wrap_get(self.set_raw) + self.set = self._wrap_set(self.set_raw) elif hasattr(self, 'set'): warnings.warn('Wrapping set method, original set method will not ' 'be directly accessible. It is recommended to ' - 'define get_raw in your subclass instead.' ) + 'define set_raw in your subclass instead.' ) self.set = self._wrap_set(self.set) # subclasses should extend this list with extra attributes they From 3d7baef4b7c4e7b1ae815965490ef9dfe7adf2da Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Thu, 26 Oct 2017 14:46:53 +0200 Subject: [PATCH 02/18] setarr is a numpy array its not safe to cast that to a bool (#818) --- qcodes/plots/pyqtgraph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 7512a4ba0c6..9a106f95c62 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -549,7 +549,7 @@ def fixUnitScaling(self, startranges: Optional[Dict[str, Dict[str, Union[float,i setarr = getattr(self.traces[i]['config'][axis], 'ndarray', None) arrmin = None arrmax = None - if setarr and not np.all(np.isnan(setarr)): + if setarr is not None and not np.all(np.isnan(setarr)): arrmax = setarr.max() arrmin = setarr.min() elif startranges is not None: From 9f5552916fbef813b9a18c9fa0e2b894495b79d9 Mon Sep 17 00:00:00 2001 From: ThorvaldLarsen Date: Thu, 26 Oct 2017 17:02:05 +0200 Subject: [PATCH 03/18] RS SGS100A driver (#817) * SGS100A Driver Adding parameter to SGS100A driver to allow on/off of IQ modulation. * Change parameter name Forgot to change a few lines in previous commit. * Strip trailing whitespace * remove .DS_Store files * Ignore DS_Store --- .gitignore | 5 ++++- qcodes/instrument_drivers/rohde_schwarz/SGS100A.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index dde5f156f2a..f727e6ed339 100644 --- a/.gitignore +++ b/.gitignore @@ -79,4 +79,7 @@ tmp/ docs/examples/data/* -.idea/ \ No newline at end of file +.idea/ + +# Mac files +.DS_Store diff --git a/qcodes/instrument_drivers/rohde_schwarz/SGS100A.py b/qcodes/instrument_drivers/rohde_schwarz/SGS100A.py index f4c2857ab0c..bb487cd730d 100644 --- a/qcodes/instrument_drivers/rohde_schwarz/SGS100A.py +++ b/qcodes/instrument_drivers/rohde_schwarz/SGS100A.py @@ -56,6 +56,11 @@ def __init__(self, name, address, **kwargs): set_cmd=self.set_status, get_parser=self.parse_on_off, vals=vals.Strings()) + self.add_parameter('IQ_state', + get_cmd=':IQ:STAT?', + set_cmd=self.set_IQ_state, + get_parser=self.parse_on_off, + vals=vals.Strings()) self.add_parameter('pulsemod_state', get_cmd=':SOUR:PULM:STAT?', set_cmd=self.set_pulsemod_state, @@ -102,6 +107,13 @@ def set_status(self, stat): raise ValueError('Unable to set status to %s, ' % stat + 'expected "ON" or "OFF"') + def set_IQ_state(self, stat): + if stat.upper() in ('ON', 'OFF'): + self.write(':IQ:STAT %s' % stat) + else: + raise ValueError('Unable to set status to %s, ' % stat + + 'expected "ON" or "OFF"') + def set_pulsemod_state(self, stat): if stat.upper() in ('ON', 'OFF'): self.write(':PULM:SOUR EXT') From e741fbde1664b24dbbb60ed9c38e05c0a29d4d4f Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Fri, 27 Oct 2017 10:48:23 +0200 Subject: [PATCH 04/18] Travis run plot tests (#808) * Travis try to run plot tests * Setting this to false does nothing so remove it * dont need explicit install --- .travis.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index f51396fff9d..41dcc9daace 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,12 +18,6 @@ branches: only: - master -# signal that we are on travis -# to disable things that expect an -# X Display -# -env: - - TRAVISCI=true # command to install dependencies install: - pip install --upgrade pip @@ -31,7 +25,12 @@ install: - pip install -r test_requirements.txt --upgrade - pip install -r docs_requirements.txt - pip install -e . -# command to run tests + +before_script: # configure a headless display to test plot generation + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + - sleep 3 # give xvfb some time to start + script: - cd qcodes - py.test --cov=qcodes --cov-report xml --cov-config=.coveragerc From 0cd61ac0ed2b2c65558fa83fed2a927aa363ac0f Mon Sep 17 00:00:00 2001 From: "William H.P. Nielsen" Date: Fri, 27 Oct 2017 12:47:54 +0200 Subject: [PATCH 05/18] add debugging and make the final write more robust (#822) * add debugging and make the final write more robust * add test based on bug report * logging: update old loggers * rename instrument to avoid error in test suite * save even in case of keyboardinterrupt * insert argument in all places --- qcodes/data/data_set.py | 29 +++++++++++++++++--------- qcodes/data/format.py | 8 ++++++-- qcodes/data/gnuplot_format.py | 23 +++++++++++++++++---- qcodes/data/hdf5_format.py | 13 ++++++++---- qcodes/loops.py | 38 ++++++++++++++++++++++++----------- qcodes/tests/test_loop.py | 28 ++++++++++++++++++++++++-- 6 files changed, 106 insertions(+), 33 deletions(-) diff --git a/qcodes/data/data_set.py b/qcodes/data/data_set.py index 097dcc1de70..0213d0b4867 100644 --- a/qcodes/data/data_set.py +++ b/qcodes/data/data_set.py @@ -11,6 +11,8 @@ from .location import FormatLocation from qcodes.utils.helpers import DelegateAttributes, full_class, deep_update +log = logging.getLogger(__name__) + def new_data(location=None, loc_record=None, name=None, overwrite=False, io=None, **kwargs): @@ -246,14 +248,14 @@ def complete(self, delay=1.5): Args: delay (float): seconds between iterations. Default 1.5 """ - logging.info( + log.info( 'waiting for DataSet <{}> to complete'.format(self.location)) failing = {key: False for key in self.background_functions} completed = False while True: - logging.info('DataSet: {:.0f}% complete'.format( + log.info('DataSet: {:.0f}% complete'.format( self.fraction_complete() * 100)) # first check if we're done @@ -264,13 +266,13 @@ def complete(self, delay=1.5): # because we want things like live plotting to get the final data for key, fn in list(self.background_functions.items()): try: - logging.debug('calling {}: {}'.format(key, repr(fn))) + log.debug('calling {}: {}'.format(key, repr(fn))) fn() failing[key] = False except Exception: - logging.info(format_exc()) + log.info(format_exc()) if failing[key]: - logging.warning( + log.warning( 'background function {} failed twice in a row, ' 'removing it'.format(key)) del self.background_functions[key] @@ -282,7 +284,7 @@ def complete(self, delay=1.5): # but only sleep if we're not already finished time.sleep(delay) - logging.info('DataSet <{}> is complete'.format(self.location)) + log.info('DataSet <{}> is complete'.format(self.location)) def get_changes(self, synced_indices): """ @@ -387,8 +389,11 @@ def store(self, loop_indices, ids_values): self.last_store = time.time() if (self.write_period is not None and time.time() > self.last_write + self.write_period): + log.debug('Attempting to write') self.write() self.last_write = time.time() + else: + log.debug('.store method: This is not the right time to write') def default_parameter_name(self, paramname='amplitude'): """ Return name of default parameter for plotting @@ -464,7 +469,7 @@ def read_metadata(self): return self.formatter.read_metadata(self) - def write(self, write_metadata=False): + def write(self, write_metadata=False, only_complete=True): """ Writes updates to the DataSet to storage. N.B. it is recommended to call data_set.finalize() when a DataSet is @@ -472,6 +477,9 @@ def write(self, write_metadata=False): Args: write_metadata (bool): write the metadata to disk + only_complete (bool): passed on to the match_save_range inside + self.formatter.write. Used to ensure that all new data gets + saved even when some columns are strange. """ if self.location is False: return @@ -479,7 +487,8 @@ def write(self, write_metadata=False): self.formatter.write(self, self.io, self.location, - write_metadata=write_metadata) + write_metadata=write_metadata, + only_complete=only_complete) def write_copy(self, path=None, io_manager=None, location=None): """ @@ -560,7 +569,9 @@ def finalize(self): Also closes the data file(s), if the ``Formatter`` we're using supports that. """ - self.write() + log.debug('Finalising the DataSet. Writing.') + # write all new data, not only (to?) complete columns + self.write(only_complete=False) if hasattr(self.formatter, 'close_file'): self.formatter.close_file(self) diff --git a/qcodes/data/format.py b/qcodes/data/format.py index cb0b8fe6756..c3fc5831593 100644 --- a/qcodes/data/format.py +++ b/qcodes/data/format.py @@ -48,7 +48,7 @@ class Formatter: ArrayGroup = namedtuple('ArrayGroup', 'shape set_arrays data name') def write(self, data_set, io_manager, location, write_metadata=True, - force_write=False): + force_write=False, only_complete=True): """ Write the DataSet to storage. @@ -63,6 +63,8 @@ def write(self, data_set, io_manager, location, write_metadata=True, location (str): the file location within the io_manager. write_metadata (bool): if True, then the metadata is written to disk force_write (bool): if True, then the data is written to disk + only_complete (bool): Used only by the gnuplot formatter's + overridden version of this method """ raise NotImplementedError @@ -190,7 +192,9 @@ def match_save_range(self, group, file_exists, only_complete=True): Returns: Tuple(int, int): the first and last raveled indices that should - be saved. + be saved. Returns None if: + * no data is present + * no new data can be found """ inner_setpoint = group.set_arrays[-1] full_dim_data = (inner_setpoint, ) + group.data diff --git a/qcodes/data/gnuplot_format.py b/qcodes/data/gnuplot_format.py index b121495a2c7..c2124572968 100644 --- a/qcodes/data/gnuplot_format.py +++ b/qcodes/data/gnuplot_format.py @@ -2,12 +2,16 @@ import re import math import json +import logging from qcodes.utils.helpers import deep_update, NumpyJSONEncoder from .data_array import DataArray from .format import Formatter +log = logging.getLogger(__name__) + + class GNUPlotFormat(Formatter): """ Saves data in one or more gnuplot-format files. We make one file for @@ -239,7 +243,8 @@ def _get_labels(self, labelstr): parts = re.split('"\s+"', labelstr[1:-1]) return [l.replace('\\"', '"').replace('\\\\', '\\') for l in parts] - def write(self, data_set, io_manager, location, force_write=False, write_metadata=True): + def write(self, data_set, io_manager, location, force_write=False, + write_metadata=True, only_complete=True): """ Write updates in this DataSet to storage. @@ -249,6 +254,11 @@ def write(self, data_set, io_manager, location, force_write=False, write_metadat data_set (DataSet): the data we're storing io_manager (io_manager): the base location to write to location (str): the file location within io_manager + only_complete (bool): passed to match_save_range, answers the + following question: Should we write all available new data, + or only complete rows? Is used to make sure that everything + gets written when the DataSet is finalised, even if some + dataarrays are strange (like, full of nans) """ arrays = data_set.arrays @@ -257,16 +267,20 @@ def write(self, data_set, io_manager, location, force_write=False, write_metadat existing_files = set(io_manager.list(location)) written_files = set() - # Every group gets it's own datafile + # Every group gets its own datafile for group in groups: + log.debug('Attempting to write the following ' + 'group: {}'.format(group)) fn = io_manager.join(location, group.name + self.extension) written_files.add(fn) file_exists = fn in existing_files - save_range = self.match_save_range(group, file_exists) + save_range = self.match_save_range(group, file_exists, + only_complete=only_complete) if save_range is None: + log.debug('Cannot match save range, skipping this group.') continue overwrite = save_range[0] == 0 or force_write @@ -276,6 +290,7 @@ def write(self, data_set, io_manager, location, force_write=False, write_metadat with io_manager.open(fn, open_mode) as f: if overwrite: f.write(self._make_header(group)) + log.debug('Wrote header to file') for i in range(save_range[0], save_range[1] + 1): indices = np.unravel_index(i, shape) @@ -291,7 +306,7 @@ def write(self, data_set, io_manager, location, force_write=False, write_metadat one_point = self._data_point(group, indices) f.write(self.separator.join(one_point) + self.terminator) - + log.debug('Wrote to file') # now that we've saved the data, mark it as such in the data. # we mark the data arrays and the inner setpoint array. Outer # setpoint arrays have different dimension (so would need a diff --git a/qcodes/data/hdf5_format.py b/qcodes/data/hdf5_format.py index 8e059019ddb..08a510262dd 100644 --- a/qcodes/data/hdf5_format.py +++ b/qcodes/data/hdf5_format.py @@ -126,7 +126,8 @@ def _create_data_object(self, data_set, io_manager=None, return data_set._h5_base_group def write(self, data_set, io_manager=None, location=None, - force_write=False, flush=True, write_metadata=True): + force_write=False, flush=True, write_metadata=True, + only_complete=False): """ Writes a data_set to an hdf5 file. @@ -137,12 +138,16 @@ def write(self, data_set, io_manager=None, location=None, force_write (bool): if True creates a new file to write to flush (bool) : whether to flush after writing, can be disabled for testing or performance reasons + only_complete (bool): Not used by this formatter, but must be + included in the call signature to avoid an "unexpected + keyword argument" TypeError. N.B. It is recommended to close the file after writing, this can be done by calling ``HDF5Format.close_file(data_set)`` or - ``data_set.finalize()`` if the data_set formatter is set to an hdf5 formatter. - Note that this is not required if the dataset is created from a Loop as this - includes a data_set.finalize() statement. + ``data_set.finalize()`` if the data_set formatter is set to an + hdf5 formatter. Note that this is not required if the dataset + is created from a Loop as this includes a data_set.finalize() + statement. The write function consists of two parts, writing DataArrays and writing metadata. diff --git a/qcodes/loops.py b/qcodes/loops.py index 5a898e0ceec..1d765a680bb 100644 --- a/qcodes/loops.py +++ b/qcodes/loops.py @@ -63,9 +63,11 @@ log = logging.getLogger(__name__) + def active_loop(): return ActiveLoop.active_loop + def active_data_set(): loop = active_loop() if loop is not None and loop.data_set is not None: @@ -73,6 +75,7 @@ def active_data_set(): else: return None + class Loop(Metadatable): """ The entry point for creating measurement loops @@ -780,16 +783,16 @@ def _compile_one(self, action, new_action_indices): return action def _run_wrapper(self, *args, **kwargs): - # try: - self._run_loop(*args, **kwargs) - # finally: - if hasattr(self, 'data_set'): - # TODO (giulioungaretti) WTF? - # somehow this does not show up in the data_set returned by - # run(), but it is saved to the metadata - ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - self.data_set.add_metadata({'loop': {'ts_end': ts}}) - self.data_set.finalize() + try: + self._run_loop(*args, **kwargs) + finally: + if hasattr(self, 'data_set'): + # TODO (giulioungaretti) WTF? + # somehow this does not show up in the data_set returned by + # run(), but it is saved to the metadata + ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + self.data_set.add_metadata({'loop': {'ts_end': ts}}) + self.data_set.finalize() def _run_loop(self, first_delay=0, action_indices=(), loop_indices=(), current_values=(), @@ -835,12 +838,15 @@ def _run_loop(self, first_delay=0, action_indices=(), new_values = current_values + (value,) data_to_store = {} - if hasattr(self.sweep_values, "parameters"): # combined parameter + if hasattr(self.sweep_values, "parameters"): # combined parameter set_name = self.data_set.action_id_map[action_indices] if hasattr(self.sweep_values, 'aggregate'): value = self.sweep_values.aggregate(*set_val) + log.debug('Calling .store method of DataSet because ' + 'sweep_values.parameters exist') self.data_set.store(new_indices, {set_name: value}) - for j, val in enumerate(set_val): # set_val list of values to set [param1_setpoint, param2_setpoint ..] + # set_val list of values to set [param1_setpoint, param2_setpoint ..] + for j, val in enumerate(set_val): set_index = action_indices + (j+n_callables, ) set_name = (self.data_set.action_id_map[set_index]) data_to_store[set_name] = val @@ -848,6 +854,8 @@ def _run_loop(self, first_delay=0, action_indices=(), set_name = self.data_set.action_id_map[action_indices] data_to_store[set_name] = value + log.debug('Calling .store method of DataSet because a sweep step' + ' was taken') self.data_set.store(new_indices, data_to_store) if not self._nest_first: @@ -856,6 +864,8 @@ def _run_loop(self, first_delay=0, action_indices=(), try: for f in callables: + log.debug('Going through callables at this sweep step.' + ' Calling {}'.format(f)) f(first_delay=delay, loop_indices=new_indices, current_values=new_values) @@ -889,14 +899,18 @@ def _run_loop(self, first_delay=0, action_indices=(), # run the background task one last time to catch the last setpoint(s) if self.bg_task is not None: + log.debug('Running the background task one last time.') self.bg_task() # the loop is finished - run the .then actions + log.debug('Finishing loop, running the .then actions...') for f in self._compile_actions(self.then_actions, ()): + log.debug('...running .then action {}'.format(f)) f() # run the bg_final_task from the bg_task: if self.bg_final_task is not None: + log.debug('Running the bg_final_task') self.bg_final_task() def _wait(self, delay): diff --git a/qcodes/tests/test_loop.py b/qcodes/tests/test_loop.py index 833192ce02e..1716e2aabd3 100644 --- a/qcodes/tests/test_loop.py +++ b/qcodes/tests/test_loop.py @@ -3,16 +3,29 @@ from unittest import TestCase import numpy as np from unittest.mock import patch +import os from qcodes.loops import Loop from qcodes.actions import Task, Wait, BreakIf, _QcodesBreak from qcodes.station import Station from qcodes.data.data_array import DataArray -from qcodes.instrument.parameter import Parameter +from qcodes.instrument.parameter import Parameter, MultiParameter from qcodes.utils.validators import Numbers from qcodes.utils.helpers import LogCapture -from .instrument_mocks import MultiGetter +from .instrument_mocks import MultiGetter, DummyInstrument + + +class NanReturningParameter(MultiParameter): + + def __init__(self, name, instrument, names=('first', 'second'), + shapes=((), ())): + + super().__init__(name=name, names=names, shapes=shapes, + instrument=instrument) + + def get_raw(self): # this results in a nan-filled DataArray + return (13,) class TestLoop(TestCase): @@ -21,6 +34,8 @@ def setUpClass(cls): cls.p1 = Parameter('p1', get_cmd=None, set_cmd=None, vals=Numbers(-10, 10)) cls.p2 = Parameter('p2', get_cmd=None, set_cmd=None, vals=Numbers(-10, 10)) cls.p3 = Parameter('p3', get_cmd=None, set_cmd=None, vals=Numbers(-10, 10)) + instr = DummyInstrument('dummy_bunny') + cls.p4_crazy = NanReturningParameter('p4_crazy', instrument=instr) Station().set_measurement(cls.p2, cls.p3) def test_nesting(self): @@ -93,6 +108,15 @@ def test_repr(self): ' Measured | p1 | p1 | (2,)') self.assertEqual(data.__repr__(), expected) + def test_measurement_with_many_nans(self): + loop = Loop(self.p1.sweep(0, 1, num=10), + delay=0.05).each(self.p4_crazy) + ds = loop.get_data_set() + loop.run() + + # assert that both the snapshot and the datafile are there + self.assertEqual(len(os.listdir(ds.location)), 2) + def test_default_measurement(self): self.p2.set(4) self.p3.set(5) From bb025d1d0f31cabbfec58028a24da18f921d1eec Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Tue, 31 Oct 2017 11:37:28 +0100 Subject: [PATCH 06/18] change default hold_after_set to True (#835) --- qcodes/instrument_drivers/oxford/mercuryiPS.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcodes/instrument_drivers/oxford/mercuryiPS.py b/qcodes/instrument_drivers/oxford/mercuryiPS.py index cdad5e9ae88..325d9e8219d 100644 --- a/qcodes/instrument_drivers/oxford/mercuryiPS.py +++ b/qcodes/instrument_drivers/oxford/mercuryiPS.py @@ -82,7 +82,7 @@ def __init__(self, name, address=None, port=7020, axes=None, **kwargs): self.add_parameter('hold_after_set', get_cmd=None, set_cmd=None, vals=Bool(), - initial_value=False, + initial_value=True, docstring='Should the driver block while waiting for the Magnet power supply ' 'to go into hold mode.' ) From a1beec9c48dce976353aaff5d230898eaed3d719 Mon Sep 17 00:00:00 2001 From: Dominik Vogel <30660470+Dominik-Vogel@users.noreply.github.com> Date: Wed, 1 Nov 2017 09:29:22 +0100 Subject: [PATCH 07/18] added method to Measure: get_data_set (#837) --- qcodes/measure.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qcodes/measure.py b/qcodes/measure.py index 098b1222450..b82dec2bce5 100644 --- a/qcodes/measure.py +++ b/qcodes/measure.py @@ -32,6 +32,9 @@ def run_temp(self, **kwargs): """ return self.run(quiet=True, location=False, **kwargs) + def get_data_set(self, *args, **kwargs): + return self._dummyLoop.get_data_set(*args, **kwargs) + def run(self, use_threads=False, quiet=False, station=None, **kwargs): """ Run the actions in this measurement and return their data as a DataSet From 956ffe49927ed3c7b8b5a823bb402cf616d11326 Mon Sep 17 00:00:00 2001 From: Dominik Vogel <30660470+Dominik-Vogel@users.noreply.github.com> Date: Wed, 1 Nov 2017 12:51:44 +0100 Subject: [PATCH 08/18] added slot_mode_default to decadac channel (#838) --- qcodes/instrument_drivers/Harvard/Decadac.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qcodes/instrument_drivers/Harvard/Decadac.py b/qcodes/instrument_drivers/Harvard/Decadac.py index 2d031e43914..0b60e5a9ca4 100644 --- a/qcodes/instrument_drivers/Harvard/Decadac.py +++ b/qcodes/instrument_drivers/Harvard/Decadac.py @@ -292,6 +292,7 @@ class DacSlot(InstrumentChannel, DacReader): A single DAC Slot of the DECADAC """ _SLOT_VAL = vals.Ints(0, 5) + SLOT_MODE_DEFAULT = "Coarse" def __init__(self, parent, name, slot, min_val=-5, max_val=5): super().__init__(parent, name) @@ -327,7 +328,7 @@ def __init__(self, parent, name, slot, min_val=-5, max_val=5): val_mapping=slot_modes) # Enable all slots in coarse mode. - self.slot_mode.set("Coarse") + self.slot_mode.set(DacSlot.SLOT_MODE_DEFAULT) def write(self, cmd): """ From 67826b576eabde52d425380f11ea2f93557a9df6 Mon Sep 17 00:00:00 2001 From: "William H.P. Nielsen" Date: Thu, 2 Nov 2017 15:40:34 +0100 Subject: [PATCH 09/18] make print_readable_snapshot work with value-less parameters (#844) * make print_readable_snapshot work with value-less parameters * Update docstring, don't use try-except --- qcodes/instrument/base.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qcodes/instrument/base.py b/qcodes/instrument/base.py index e41cddb3408..251430e3411 100644 --- a/qcodes/instrument/base.py +++ b/qcodes/instrument/base.py @@ -227,7 +227,12 @@ def print_readable_snapshot(self, update=False, max_chars=80): for par in sorted(snapshot['parameters']): name = snapshot['parameters'][par]['name'] msg = '{0:<{1}}:'.format(name, par_field_len) - val = snapshot['parameters'][par]['value'] + + # in case of e.g. ArrayParameters, that usually have + # snapshot_value == False, the parameter may not have + # a value in the snapshot + val = snapshot['parameters'][par].get('value', 'Not available') + unit = snapshot['parameters'][par].get('unit', None) if unit is None: # this may be a multi parameter From 7ddae2b1e1dab877d752dec536c66032e41a5feb Mon Sep 17 00:00:00 2001 From: "William H.P. Nielsen" Date: Thu, 2 Nov 2017 15:40:57 +0100 Subject: [PATCH 10/18] Add driver and example notebook for HP8753D (#843) --- .../Qcodes example with HP8753D.ipynb | 1047 +++++++++++++++++ qcodes/instrument_drivers/HP/HP8753D.py | 331 ++++++ 2 files changed, 1378 insertions(+) create mode 100644 docs/examples/driver_examples/Qcodes example with HP8753D.ipynb create mode 100644 qcodes/instrument_drivers/HP/HP8753D.py diff --git a/docs/examples/driver_examples/Qcodes example with HP8753D.ipynb b/docs/examples/driver_examples/Qcodes example with HP8753D.ipynb new file mode 100644 index 00000000000..ecf144d942e --- /dev/null +++ b/docs/examples/driver_examples/Qcodes example with HP8753D.ipynb @@ -0,0 +1,1047 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Qcodes example with HP8753D\n", + "\n", + "This is the example notebook illustrating how to use the QCoDeS driver\n", + "for the HP 8753D Network Analyzer.\n", + "\n", + "Throughout the notebook, we assume that a Mini-Circuits SLP-550+ Low Pass\n", + "filter is connected as the DUT." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# allow in-notebook matplotlib plots\n", + "%matplotlib notebook\n", + "\n", + "# import QCoDeS\n", + "\n", + "import qcodes as qc\n", + "from qcodes.instrument_drivers.HP.HP8753D import HP8753D\n", + "\n", + "# we'll need this later\n", + "from functools import partial\n", + "\n", + "# import logging\n", + "# logging.basicConfig(level=logging.DEBUG)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connected to: HEWLETT PACKARD 8753D (serial:0, firmware:6.14) in 0.23s\n" + ] + } + ], + "source": [ + "# Instantiate the instrument\n", + "vna = HP8753D('vna', 'GPIB0::6::INSTR')" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "vna:\n", + "\tparameter value\n", + "--------------------------------------------------------------------------------\n", + "IDN :\t{'vendor': 'HEWLETT PACKARD', 'model': '8753D', 'serial'...\n", + "averaging :\tOFF \n", + "display_format :\tLog mag \n", + "display_reference :\t0 (dim. less)\n", + "display_scale :\t10 (dim. less)\n", + "number_of_averages :\t16 \n", + "output_power :\t0 (dBm)\n", + "s_parameter :\tS11 \n", + "start_freq :\t30000 (Hz)\n", + "stop_freq :\t6e+09 (Hz)\n", + "sweep_time :\t0.175 (s)\n", + "timeout :\t10 (s)\n", + "trace :\tNot available \n", + "trace_points :\t201 \n" + ] + } + ], + "source": [ + "# for the sake of this tutorial, we reset the instrument\n", + "vna.reset()\n", + "\n", + "# The following functions gives a nice self-explanatory\n", + "# overview of the available instrument settings\n", + "\n", + "vna.print_readable_snapshot(update=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "## Single trace measurement" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Let's get a single trace of S21 with 10 averages\n", + "# with a frequency range from 100 kHz to 1 GHz\n", + "# on a linear scale\n", + "vna.display_format('Lin mag')\n", + "vna.s_parameter('S21')\n", + "vna.start_freq(100e3)\n", + "vna.stop_freq(1e9)\n", + "\n", + "# and let's adjust the y-scale to fit\n", + "vna.display_scale(0.12)\n", + "vna.display_reference(-0.1)\n", + "\n", + "# and finally enable averaging\n", + "vna.averaging('ON')\n", + "vna.number_of_averages(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet:\n", + " location = 'data/2017-11-02/#011_{name}_14-33-04'\n", + " | | | \n", + " Measured | vna_trace | trace | (201,)\n", + "acquired at 2017-11-02 14:33:04\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('