diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py index 3ab4fb14e7e..7a6e579b9c4 100644 --- a/qcodes/instrument/parameter.py +++ b/qcodes/instrument/parameter.py @@ -433,7 +433,9 @@ class ArrayParameter(_BaseParameter): per setpoint array. Ignored if a setpoint is a DataArray, which already has a label. - TODO (alexcjohnson) we need setpoint_units (and in MultiParameter) + setpoint_units (Optional[Tuple[str]]): one label (like ``v``) + per setpoint array. Ignored if a setpoint is a DataArray, which + already has a unit. docstring (Optional[str]): documentation string for the __doc__ field of the object. The __doc__ field of the instance is used by @@ -448,14 +450,14 @@ class ArrayParameter(_BaseParameter): def __init__(self, name, shape, instrument=None, label=None, unit=None, units=None, setpoints=None, setpoint_names=None, setpoint_labels=None, - docstring=None, snapshot_get=True, metadata=None): + setpoint_units=None, docstring=None, snapshot_get=True, metadata=None): super().__init__(name, instrument, snapshot_get, metadata) if self.has_set: # TODO (alexcjohnson): can we support, ala Combine? raise AttributeError('ArrayParameters do not support set ' 'at this time.') - self._meta_attrs.extend(['setpoint_names', 'setpoint_labels', + self._meta_attrs.extend(['setpoint_names', 'setpoint_labels', 'setpoint_units', 'label', 'unit']) self.label = name if label is None else label @@ -488,10 +490,15 @@ def __init__(self, name, shape, instrument=None, not is_sequence_of(setpoint_labels, (nt, str), shape=sp_shape)): raise ValueError('setpoint_labels must be a tuple of strings') + if (setpoint_units is not None and + not is_sequence_of(setpoint_units, (nt, str), + shape=sp_shape)): + raise ValueError('setpoint_units must be a tuple of strings') self.setpoints = setpoints self.setpoint_names = setpoint_names self.setpoint_labels = setpoint_labels + self.setpoint_units = setpoint_units self.__doc__ = os.linesep.join(( 'Parameter class:', @@ -591,6 +598,10 @@ class MultiParameter(_BaseParameter): ``labels``) per setpoint array. Ignored if a setpoint is a DataArray, which already has a label. + setpoint_units (Optional[Tuple[Tuple[str]]]): one unit (like + ``V``) per setpoint array. Ignored if a setpoint is a + DataArray, which already has a unit. + docstring (Optional[str]): documentation string for the __doc__ field of the object. The __doc__ field of the instance is used by some help systems, but not all @@ -604,6 +615,7 @@ class MultiParameter(_BaseParameter): def __init__(self, name, names, shapes, instrument=None, labels=None, units=None, setpoints=None, setpoint_names=None, setpoint_labels=None, + setpoint_units=None, docstring=None, snapshot_get=True, metadata=None): super().__init__(name, instrument, snapshot_get, metadata) @@ -611,7 +623,7 @@ def __init__(self, name, names, shapes, instrument=None, raise AttributeError('MultiParameters do not support set ' 'at this time.') - self._meta_attrs.extend(['setpoint_names', 'setpoint_labels', + self._meta_attrs.extend(['setpoint_names', 'setpoint_labels', 'setpoint_units', 'names', 'labels', 'units']) if not is_sequence_of(names, str): @@ -643,9 +655,14 @@ def __init__(self, name, names, shapes, instrument=None, raise ValueError( 'setpoint_labels must be a tuple of tuples of strings') + if not _is_nested_sequence_or_none(setpoint_units, (nt, str), shapes): + raise ValueError( + 'setpoint_units must be a tuple of tuples of strings') + self.setpoints = setpoints self.setpoint_names = setpoint_names self.setpoint_labels = setpoint_labels + self.setpoint_units = setpoint_units self.__doc__ = os.linesep.join(( 'MultiParameter class:', diff --git a/qcodes/loops.py b/qcodes/loops.py index 191bb09dca7..ebec726a46c 100644 --- a/qcodes/loops.py +++ b/qcodes/loops.py @@ -556,18 +556,25 @@ def _parameter_arrays(self, action): action_indices = ((),) else: raise ValueError('a gettable parameter must have .name or .names') - + if hasattr(action, 'names') and hasattr(action, 'units'): + units = action.units + elif hasattr(action, 'unit'): + units = (action.unit,) + else: + units = tuple(['']*len(names)) num_arrays = len(names) shapes = getattr(action, 'shapes', None) sp_vals = getattr(action, 'setpoints', None) sp_names = getattr(action, 'setpoint_names', None) sp_labels = getattr(action, 'setpoint_labels', None) + sp_units = getattr(action, 'setpoint_units', None) if shapes is None: shapes = (getattr(action, 'shape', ()),) * num_arrays sp_vals = (sp_vals,) * num_arrays sp_names = (sp_names,) * num_arrays sp_labels = (sp_labels,) * num_arrays + sp_units = (sp_units,) * num_arrays else: sp_blank = (None,) * num_arrays # _fill_blank both supplies defaults and tests length @@ -576,26 +583,28 @@ def _parameter_arrays(self, action): sp_vals = self._fill_blank(sp_vals, sp_blank) sp_names = self._fill_blank(sp_names, sp_blank) sp_labels = self._fill_blank(sp_labels, sp_blank) + sp_units = self._fill_blank(sp_units, sp_blank) # now loop through these all, to make the DataArrays # record which setpoint arrays we've made, so we don't duplicate all_setpoints = {} - for name, full_name, label, shape, i, sp_vi, sp_ni, sp_li in zip( - names, full_names, labels, shapes, action_indices, - sp_vals, sp_names, sp_labels): + for name, full_name, label, unit, shape, i, sp_vi, sp_ni, sp_li, sp_ui in zip( + names, full_names, labels, units, shapes, action_indices, + sp_vals, sp_names, sp_labels, sp_units): if shape is None or shape == (): - shape, sp_vi, sp_ni, sp_li = (), (), (), () + shape, sp_vi, sp_ni, sp_li, sp_ui= (), (), (), (), () else: sp_blank = (None,) * len(shape) sp_vi = self._fill_blank(sp_vi, sp_blank) sp_ni = self._fill_blank(sp_ni, sp_blank) sp_li = self._fill_blank(sp_li, sp_blank) + sp_ui = self._fill_blank(sp_ui, sp_blank) setpoints = () # loop through dimensions of shape to make the setpoint arrays - for j, (vij, nij, lij) in enumerate(zip(sp_vi, sp_ni, sp_li)): - sp_def = (shape[: 1 + j], j, setpoints, vij, nij, lij) + for j, (vij, nij, lij, uij) in enumerate(zip(sp_vi, sp_ni, sp_li, sp_ui)): + sp_def = (shape[: 1 + j], j, setpoints, vij, nij, lij, uij) if sp_def not in all_setpoints: all_setpoints[sp_def] = self._make_setpoint_array(*sp_def) out.append(all_setpoints[sp_def]) @@ -603,7 +612,7 @@ def _parameter_arrays(self, action): # finally, make the output data array with these setpoints out.append(DataArray(name=name, full_name=full_name, label=label, - shape=shape, action_indices=i, + shape=shape, action_indices=i, unit=unit, set_arrays=setpoints, parameter=action)) return out @@ -617,7 +626,7 @@ def _fill_blank(self, inputs, blanks): raise ValueError('Wrong number of inputs supplied') def _make_setpoint_array(self, shape, i, prev_setpoints, vals, name, - label): + label, unit): if vals is None: vals = self._default_setpoints(shape) elif isinstance(vals, DataArray): @@ -645,7 +654,7 @@ def _make_setpoint_array(self, shape, i, prev_setpoints, vals, name, name = 'index{}'.format(i) return DataArray(name=name, label=label, set_arrays=prev_setpoints, - shape=shape, preset_data=vals) + shape=shape, preset_data=vals, unit=unit, is_setpoint=True) def _default_setpoints(self, shape): if len(shape) == 1: diff --git a/qcodes/tests/instrument_mocks.py b/qcodes/tests/instrument_mocks.py index f7bd5927a51..3163d2e95b6 100644 --- a/qcodes/tests/instrument_mocks.py +++ b/qcodes/tests/instrument_mocks.py @@ -324,3 +324,31 @@ def __init__(self, **kwargs): def get(self): return self._return + + +class MultiSetPointParam(MultiParameter): + """ + Multiparameter which only purpose it to test that units, setpoints + and so on are copied correctly to the individual arrays in the datarray. + """ + def __init__(self): + name = 'testparameter' + shapes = ((5,), (5,)) + names = ('this', 'that') + labels = ('this label', 'that label') + units = ('this unit', 'that unit') + sp_base = tuple(np.linspace(5, 9, 5)) + setpoints = ((sp_base,), (sp_base,)) + setpoint_names = (('this_setpoint',), ('this_setpoint',)) + setpoint_labels = (('this setpoint',), ('this setpoint',)) + setpoint_units = (('this setpointunit',), ('this setpointunit',)) + super().__init__(name, names, shapes, + labels=labels, + units=units, + setpoints=setpoints, + setpoint_labels=setpoint_labels, + setpoint_names=setpoint_names, + setpoint_units=setpoint_units) + + def get(self): + return np.zeros(5), np.ones(5) \ No newline at end of file diff --git a/qcodes/tests/test_loop.py b/qcodes/tests/test_loop.py index b05276ba4ef..fff3414ba82 100644 --- a/qcodes/tests/test_loop.py +++ b/qcodes/tests/test_loop.py @@ -215,7 +215,7 @@ def test_sync_no_overwrite(self): self.gates.chan2[0:1:1], 0.000001)) data = loop.get_data_set(name='testsweep', write_period=0.01) _ = loop.with_bg_task(data.sync).run() - assert not np.isnan(data.chan2).any() + assert not np.isnan(data.chan2_set).any() def sleeper(t): time.sleep(t) @@ -467,7 +467,7 @@ def test_composite_params(self): self.assertEqual(data.p1_set.tolist(), [1, 2]) self.assertEqual(data.one.tolist(), [1, 1]) self.assertEqual(data.onetwo.tolist(), [[1, 2]] * 2) - self.assertEqual(data.index0.tolist(), [[0, 1]] * 2) + self.assertEqual(data.index0_set.tolist(), [[0, 1]] * 2) # give it setpoints, names, and labels mg.setpoints = (None, ((10, 11),)) @@ -478,8 +478,8 @@ def test_composite_params(self): data = loop.run_temp() - self.assertEqual(data.highest.tolist(), [[10, 11]] * 2) - self.assertEqual(data.highest.label, sp_label) + self.assertEqual(data.highest_set.tolist(), [[10, 11]] * 2) + self.assertEqual(data.highest_set.label, sp_label) # setpoints as DataArray - name and label here override # setpoint_names and setpoint_labels attributes @@ -490,8 +490,8 @@ def test_composite_params(self): mg.setpoints = (None, (sp_dataarray,)) data = loop.run_temp() - self.assertEqual(data.bgn.tolist(), [[6, 7]] * 2) - self.assertEqual(data.bgn.label, new_sp_label) + self.assertEqual(data.bgn_set.tolist(), [[6, 7]] * 2) + self.assertEqual(data.bgn_set.label, new_sp_label) # muck things up and test for errors mg.setpoints = (None, ((1, 2, 3),)) @@ -522,7 +522,7 @@ def test_composite_params(self): self.assertEqual(data.p1_set.tolist(), [1, 2]) self.assertEqual(data.arr.tolist(), [[4, 5, 6]] * 2) - self.assertEqual(data.index0.tolist(), [[0, 1, 2]] * 2) + self.assertEqual(data.index0_set.tolist(), [[0, 1, 2]] * 2) mg = MultiGetter(arr2d=((21, 22), (23, 24))) loop = Loop(self.p1[1:3:1], 0.001).each(mg) @@ -530,8 +530,8 @@ def test_composite_params(self): self.assertEqual(data.p1_set.tolist(), [1, 2]) self.assertEqual(data.arr2d.tolist(), [[[21, 22], [23, 24]]] * 2) - self.assertEqual(data.index0.tolist(), [[0, 1]] * 2) - self.assertEqual(data.index1.tolist(), [[[0, 1]] * 2] * 2) + self.assertEqual(data.index0_set.tolist(), [[0, 1]] * 2) + self.assertEqual(data.index1_set.tolist(), [[[0, 1]] * 2] * 2) def test_bad_actors(self): def f(): diff --git a/qcodes/tests/test_measure.py b/qcodes/tests/test_measure.py index e72f3880167..71d3c45d4c4 100644 --- a/qcodes/tests/test_measure.py +++ b/qcodes/tests/test_measure.py @@ -4,8 +4,10 @@ from qcodes.instrument.parameter import ManualParameter from qcodes.measure import Measure -from .instrument_mocks import MultiGetter +from .instrument_mocks import MultiGetter, MultiSetPointParam +import numpy as np +from numpy.testing import assert_array_equal class TestMeasure(TestCase): def setUp(self): @@ -34,7 +36,7 @@ def test_simple_scalar(self): def test_simple_array(self): data = Measure(MultiGetter(arr=(1.2, 3.4))).run_temp() - self.assertEqual(data.index0.tolist(), [0, 1]) + self.assertEqual(data.index0_set.tolist(), [0, 1]) self.assertEqual(data.arr.tolist(), [1.2, 3.4]) self.assertEqual(len(data.arrays), 2, data.arrays) @@ -44,6 +46,35 @@ def test_array_and_scalar(self): self.assertEqual(data.single_set.tolist(), [0]) self.assertEqual(data.P1.tolist(), [42]) - self.assertEqual(data.index0.tolist(), [0, 1]) + self.assertEqual(data.index0_set.tolist(), [0, 1]) self.assertEqual(data.arr.tolist(), [5, 6]) self.assertEqual(len(data.arrays), 4, data.arrays) + + +class TestMeasureMulitParameter(TestCase): + def setUp(self): + self.p1 = MultiSetPointParam() + + + def test_metadata(self): + c = Measure(self.p1).run() + self.assertEqual(c.metadata['arrays']['this']['unit'], 'this unit') + self.assertEqual(c.metadata['arrays']['this']['name'], 'this') + self.assertEqual(c.metadata['arrays']['this']['label'], 'this label') + self.assertEqual(c.metadata['arrays']['this']['is_setpoint'], False) + self.assertEqual(c.metadata['arrays']['this']['shape'], (5,)) + assert_array_equal(c.this.ndarray, np.zeros(5)) + + self.assertEqual(c.metadata['arrays']['that']['unit'],'that unit') + self.assertEqual(c.metadata['arrays']['that']['name'], 'that') + self.assertEqual(c.metadata['arrays']['that']['label'], 'that label') + self.assertEqual(c.metadata['arrays']['that']['is_setpoint'], False) + self.assertEqual(c.metadata['arrays']['that']['shape'], (5,)) + assert_array_equal(c.that.ndarray, np.ones(5)) + + self.assertEqual(c.metadata['arrays']['this_setpoint_set']['unit'], 'this setpointunit') + self.assertEqual(c.metadata['arrays']['this_setpoint_set']['name'], 'this_setpoint') + self.assertEqual(c.metadata['arrays']['this_setpoint_set']['label'], 'this setpoint') + self.assertEqual(c.metadata['arrays']['this_setpoint_set']['is_setpoint'], True) + self.assertEqual(c.metadata['arrays']['this_setpoint_set']['shape'], (5,)) + assert_array_equal(c.this_setpoint_set.ndarray, np.linspace(5, 9, 5)) \ No newline at end of file