Skip to content

Commit

Permalink
Smaller changes to parameters (#788)
Browse files Browse the repository at this point in the history
* fix:save scaled value, perform initial get

* fix: self.get return raw_value when possible

* fix: the get function when get_cmd=None did not take any scaling into account

* fix: pop raw_value when snapshot_value=False

* fix: return self._latest['value'] if self.scale is None

* fix:save scaled value, perform initial get

* fix: self.get return raw_value when possible

* fix: the get function when get_cmd=None did not take any scaling into account

* fix: pop raw_value when snapshot_value=False

* fix: return self._latest['value'] if self.scale is None

* add test of paramter issue

* Add tests cleanup

* try to write parameter code a bit cleaner

* annotate parameters

* fix typechecking

* set private variable in init

This is how the official documentation does it and avoids a number of linter warnings

* Rename unwrapped get to get_raw to avoid confusion

* transform to step size before mapping, scaling and parsing@

* one ome deprecate ManualParameter

* revert setting parameter via _private value

* Try to wrap get/set_raw first in baseclass
  • Loading branch information
jenshnielsen authored and WilliamHPNielsen committed Oct 16, 2017
1 parent 6433dbc commit 6bfb22d
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 55 deletions.
148 changes: 102 additions & 46 deletions qcodes/instrument/parameter.py
Expand Up @@ -57,6 +57,7 @@
import os
import collections
import warnings
from typing import Optional, Sequence, TYPE_CHECKING, Union, Callable
from functools import partial, wraps
import numpy

Expand All @@ -70,6 +71,9 @@
from qcodes.instrument.sweep_values import SweepFixedValues
from qcodes.data.data_array import DataArray

if TYPE_CHECKING:
from .base import Instrument


class _BaseParameter(Metadatable, DeferredOperations):
"""
Expand Down Expand Up @@ -144,10 +148,20 @@ class _BaseParameter(Metadatable, DeferredOperations):
JSON snapshot of the parameter
"""

def __init__(self, name, instrument, snapshot_get=True, metadata=None,
step=None, scale=None, inter_delay=0, post_delay=0,
val_mapping=None, get_parser=None, set_parser=None,
snapshot_value=True, max_val_age=None, vals=None):
def __init__(self, name: str,
instrument: Optional['Instrument'],
snapshot_get: bool=True,
metadata: Optional[dict]=None,
step: Optional[Union[int, float]]=None,
scale: Optional[Union[int, float]]=None,
inter_delay: Union[int, float]=0,
post_delay: Union[int, float]=0,
val_mapping: Optional[dict]=None,
get_parser: Optional[Callable]=None,
set_parser: Optional[Callable]=None,
snapshot_value: bool=True,
max_val_age: Optional[float]=None,
vals: Optional[Validator]=None):
super().__init__(metadata)
self.name = str(name)
self._instrument = instrument
Expand Down Expand Up @@ -178,17 +192,27 @@ def __init__(self, name, instrument, snapshot_get=True, metadata=None,
# record of latest value and when it was set or measured
# what exactly this means is different for different subclasses
# but they all use the same attributes so snapshot is consistent.
self._latest = {'value': None, 'ts': None}
self._latest = {'value': None, 'ts': None, 'raw_value': None}
self.get_latest = GetLatest(self, max_val_age=max_val_age)

if hasattr(self, 'get'):
if hasattr(self, 'get_raw'):
self.get = self._wrap_get(self.get_raw)
elif hasattr(self, 'get'):
warnings.warn('Wrapping get method, original get method will not '
'be directly accessible. It is recommended to '
'define get_raw in your subclass instead.' )
self.get = self._wrap_get(self.get)
if hasattr(self, 'set'):
if hasattr(self, 'set_raw'):
self.set = self._wrap_get(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.' )
self.set = self._wrap_set(self.set)

# subclasses should extend this list with extra attributes they
# want automatically included in the snapshot
self._meta_attrs = ['name', 'instrument', 'step', 'scale', 'raw_value',
self._meta_attrs = ['name', 'instrument', 'step', 'scale',
'inter_delay', 'post_delay', 'val_mapping', 'vals']

# Specify time of last set operation, used when comparing to delay to
Expand Down Expand Up @@ -220,14 +244,16 @@ def __call__(self, *args, **kwargs):
raise NotImplementedError('no set cmd found in' +
' Parameter {}'.format(self.name))

def snapshot_base(self, update=False):
def snapshot_base(self, update: bool=False,
params_to_skip_update: Sequence[str]=None) -> dict:
"""
State of the parameter as a JSON-compatible dict.
Args:
update (bool): If True, update the state by calling
parameter.get().
If False, just use the latest values in memory.
params_to_skip_update: No effect but may be passed from super Class:
Returns:
dict: base snapshot
Expand All @@ -243,6 +269,7 @@ def snapshot_base(self, update=False):

if not self._snapshot_value:
state.pop('value')
state.pop('raw_value', None)

if isinstance(state['ts'], datetime):
state['ts'] = state['ts'].strftime('%Y-%m-%d %H:%M:%S')
Expand All @@ -265,9 +292,18 @@ def snapshot_base(self, update=False):
return state

def _save_val(self, value, validate=False):
"""
Update latest
"""
if validate:
self.validate(value)
self._latest = {'value': value, 'ts': datetime.now()}
if (self.get_parser is None and
self.set_parser is None and
self.val_mapping is None and
self.scale is None):
self.raw_value = value
self._latest = {'value': value, 'ts': datetime.now(),
'raw_value': self.raw_value}

def _wrap_get(self, get_function):
@wraps(get_function)
Expand Down Expand Up @@ -295,11 +331,11 @@ def get_wrapper(*args, **kwargs):
if self.val_mapping is not None:
if value in self.inverse_val_mapping:
value = self.inverse_val_mapping[value]
elif int(value) in self.inverse_val_mapping:
value = self.inverse_val_mapping[int(value)]
else:
raise KeyError("'{}' not in val_mapping".format(value))

try:
value = self.inverse_val_mapping[int(value)]
except (ValueError, KeyError):
raise KeyError("'{}' not in val_mapping".format(value))
self._save_val(value)
return value
except Exception as e:
Expand All @@ -314,26 +350,33 @@ def set_wrapper(value, **kwargs):
try:
self.validate(value)

if self.val_mapping is not None:
# Convert set values using val_mapping dictionary
value = self.val_mapping[value]

if self.scale is not None:
if isinstance(self.scale, collections.Iterable):
# Scale contains multiple elements, one for each value
value = tuple(val * scale for val, scale
in zip(value, self.scale))
else:
# Use single scale for all values
value *= self.scale

if self.set_parser is not None:
value = self.set_parser(value)

# In some cases intermediate sweep values must be used.
# Unless `self.step` is defined, get_sweep_values will return
# a list containing only `value`.
for val in self.get_ramp_values(value, step=self.step):
steps = self.get_ramp_values(value, step=self.step)

for val_step in steps:
if self.val_mapping is not None:
# Convert set values using val_mapping dictionary
mapped_value = self.val_mapping[val_step]
else:
mapped_value = val_step

if self.scale is not None:
if isinstance(self.scale, collections.Iterable):
# Scale contains multiple elements, one for each value
scaled_mapped_value = tuple(val * scale for val, scale
in zip(mapped_value, self.scale))
else:
# Use single scale for all values
scaled_mapped_value = mapped_value*self.scale
else:
scaled_mapped_value = mapped_value

if self.set_parser is not None:
parsed_scaled_mapped_value = self.set_parser(scaled_mapped_value)
else:
parsed_scaled_mapped_value = scaled_mapped_value

# Check if delay between set operations is required
t_elapsed = time.perf_counter() - self._t_last_set
Expand All @@ -345,10 +388,11 @@ def set_wrapper(value, **kwargs):
# Start timer to measure execution time of set_function
t0 = time.perf_counter()

set_function(val, **kwargs)
self.raw_value = val
self._save_val(val, validate=(self.val_mapping is None and
self.set_parser is None))
set_function(parsed_scaled_mapped_value, **kwargs)
self.raw_value = parsed_scaled_mapped_value
self._save_val(val_step,
validate=(self.val_mapping is None and
self.set_parser is None))

# Update last set time (used for calculating delays)
self._t_last_set = time.perf_counter()
Expand Down Expand Up @@ -379,7 +423,11 @@ def get_ramp_values(self, value, step=None):
if step is None:
return [value]
else:
start_value = self.get_latest()
if isinstance(value, collections.Iterable) and len(value) > 1:
raise RuntimeError("Don't know how to step a parameter with more than one value")
if self.get_latest() is None:
self.get()
start_value = self.raw_value

self.validate(start_value)

Expand Down Expand Up @@ -638,9 +686,17 @@ class Parameter(_BaseParameter):
"""

def __init__(self, name, instrument=None, label=None, unit=None,
get_cmd=None, set_cmd=False, initial_value=None,
max_val_age=None, vals=None, docstring=None, **kwargs):
def __init__(self, name: str,
instrument: Optional['Instrument']=None,
label: Optional[str]=None,
unit: Optional[str]=None,
get_cmd: Optional[Union[str, Callable, bool]]=None,
set_cmd: Optional[Union[str, Callable, bool]]=False,
initial_value: Optional[Union[float, int, str]]=None,
max_val_age: Optional[float]=None,
vals: Optional[str]=None,
docstring: Optional[str]=None,
**kwargs):
super().__init__(name=name, instrument=instrument, vals=vals, **kwargs)

# Enable set/get methods if get_cmd/set_cmd is given
Expand All @@ -650,27 +706,27 @@ def __init__(self, name, instrument=None, label=None, unit=None,
if max_val_age is not None:
raise SyntaxError('Must have get method or specify get_cmd '
'when max_val_age is set')
self.get = self.get_latest
self.get_raw = lambda: self._latest['raw_value']
else:
exec_str = instrument.ask if instrument else None
self.get = Command(arg_count=0, cmd=get_cmd, exec_str=exec_str)
self.get = self._wrap_get(self.get)
self.get_raw = Command(arg_count=0, cmd=get_cmd, exec_str=exec_str)
self.get = self._wrap_get(self.get_raw)

if not hasattr(self, 'set') and set_cmd is not False:
if set_cmd is None:
self.set = partial(self._save_val, validate=False)
self.set_raw = partial(self._save_val, validate=False)
else:
exec_str = instrument.write if instrument else None
self.set = Command(arg_count=1, cmd=set_cmd, exec_str=exec_str)
self.set = self._wrap_set(self.set)
self.set_raw = Command(arg_count=1, cmd=set_cmd, exec_str=exec_str)
self.set = self._wrap_set(self.set_raw)

self._meta_attrs.extend(['label', 'unit', 'vals'])

self.label = name if label is None else label
self.unit = unit if unit is not None else ''

if initial_value is not None:
self._save_val(initial_value, validate=True)
self.set(initial_value)

# generate default docstring
self.__doc__ = os.linesep.join((
Expand Down
7 changes: 4 additions & 3 deletions qcodes/measure.py
@@ -1,6 +1,6 @@
from datetime import datetime

from qcodes.instrument.parameter import ManualParameter
from qcodes.instrument.parameter import Parameter
from qcodes.loops import Loop
from qcodes.actions import _actions_snapshot
from qcodes.utils.helpers import full_class
Expand All @@ -18,8 +18,9 @@ class Measure(Metadatable):
Scalars returned by an action will be saved as length-1 arrays,
with a dummy setpoint for consistency with other DataSets.
"""
dummy_parameter = ManualParameter(name='single',
label='Single Measurement')
dummy_parameter = Parameter(name='single',
label='Single Measurement',
set_cmd=None, get_cmd=None)

def __init__(self, *actions):
super().__init__()
Expand Down
38 changes: 32 additions & 6 deletions qcodes/tests/test_parameter.py
Expand Up @@ -622,14 +622,40 @@ def test_val_mapping_with_parsers(self):
self._p = 'PVAL: 1'
self.assertEqual(p(), 'on')

class TestManualParameterValMapping(TestCase):
def setUp(self):
self.instrument = DummyInstrument('dummy_holder')

def tearDown(self):
self.instrument.close()
del self.instrument


def test_val_mapping(self):
self.instrument.add_parameter('myparameter', set_cmd=None, get_cmd=None, val_mapping={'A': 0, 'B': 1})
self.instrument.myparameter('A')
assert self.instrument.myparameter() == 'A'
assert self.instrument.myparameter() == 'A'
assert self.instrument.myparameter.raw_value == 0



class TestInstrumentRefParameter(TestCase):

def setUp(self):
self.a = DummyInstrument('dummy_holder')
self.d = DummyInstrument('dummy')

def test_get_instr(self):
a = DummyInstrument('dummy_holder')
d = DummyInstrument('dummy')
a.add_parameter('test', parameter_class=InstrumentRefParameter)
self.a.add_parameter('test', parameter_class=InstrumentRefParameter)

self.a.test.set(self.d.name)

a.test.set(d.name)
self.assertEqual(self.a.test.get(), self.d.name)
self.assertEqual(self.a.test.get_instr(), self.d)

self.assertEqual(a.test.get(), d.name)
self.assertEqual(a.test.get_instr(), d)
def tearDown(self):
self.a.close()
self.d.close()
del self.a
del self.d

0 comments on commit 6bfb22d

Please sign in to comment.