Skip to content

Commit

Permalink
Merge pull request #25 from qdev-dk/param-parse
Browse files Browse the repository at this point in the history
Param parse
  • Loading branch information
alexcjohnson committed Feb 9, 2016
2 parents be178a7 + db00d08 commit 2f9d09c
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 62 deletions.
6 changes: 3 additions & 3 deletions docs/examples/toymodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def __init__(self, name, model):
label='Gate Channel {} (mV)'.format(i),
get_cmd=cmdbase + '?',
set_cmd=cmdbase + ' {:.4f}',
parse_function=float,
get_parser=float,
vals=Numbers(-100, 100))

self.add_function('reset', call_cmd='rst')
Expand All @@ -93,7 +93,7 @@ def __init__(self, name, model):
label='Source Amplitude (\u03bcV)',
get_cmd='ampl?',
set_cmd='ampl {:.4f}',
parse_function=float,
get_parser=float,
vals=Numbers(0, 10),
sweep_step=0.1,
sweep_delay=0.05)
Expand All @@ -106,7 +106,7 @@ def __init__(self, name, model):
self.add_parameter('amplitude',
label='Current (nA)',
get_cmd='ampl?',
parse_function=float)
get_parser=float)


class AverageGetter(Parameter):
Expand Down
39 changes: 30 additions & 9 deletions qcodes/instrument/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

class Function(Metadatable):
def __init__(self, instrument, name, call_cmd=None, async_call_cmd=None,
parameters=[], parse_function=None, **kwargs):
parameters=[], parameter_parser=None, return_parser=None,
parse_function=None, **kwargs):
'''
defines a function (with arbitrary parameters) that this instrument
can execute.
Expand All @@ -17,24 +18,41 @@ def __init__(self, instrument, name, call_cmd=None, async_call_cmd=None,
instrument: an instrument that handles this function
name: the local name of this parameter
call_cmd: command to execute on instrument
- a string (with positional fields to .format, "{}" or "{0}" etc)
- a function (with parameter count matching parameters list)
async_call_cmd: an async function to use for call_async, or for both
sync and async if call_cmd is missing or None.
parameters: list of Validator objects,
one for each parameter to the Function
parse_function: function to parse the return value of cmd,
may be a type casting function like int or float.
If None, will not wait for or read any response
parameter_parser: function to transform the input parameter(s)
to encoded value(s) sent to the instrument.
If there are multiple arguments, this function should accept all
the arguments in order, and return a tuple of values.
return_parser: function to transform the response from the instrument
to the final output value.
may be a type casting function like `int` or `float`.
If None (default), will not wait for or read any response
NOTE: parsers only apply if call_cmd is a string. The function forms
of call_cmd and async_call_cmd should do their own parsing.
parse_function: DEPRECATED - use return_parser instead
'''
super().__init__(**kwargs)

self._instrument = instrument
self.name = name

# push deprecated parse_function argument to get_parser
if return_parser is None:
return_parser = parse_function

self._set_params(parameters)
self._set_call(call_cmd, async_call_cmd, parse_function)
self._set_call(call_cmd, async_call_cmd,
parameter_parser, return_parser)

def _set_params(self, parameters):
for param in parameters:
Expand All @@ -43,16 +61,19 @@ def _set_params(self, parameters):
self._parameters = parameters
self._param_count = len(parameters)

def _set_call(self, call_cmd, async_call_cmd, parse_function):
def _set_call(self, call_cmd, async_call_cmd,
parameter_parser, return_parser):
ask_or_write = self._instrument.write
ask_or_write_async = self._instrument.write_async
if isinstance(call_cmd, str) and parse_function:
if isinstance(call_cmd, str) and return_parser:
ask_or_write = self._instrument.ask
ask_or_write_async = self._instrument.ask_async

self._call, self._call_async = syncable_command(
self._param_count, call_cmd, async_call_cmd,
ask_or_write, ask_or_write_async, parse_function)
param_count=self._param_count,
cmd=call_cmd, acmd=async_call_cmd,
exec_str=ask_or_write, aexec_str=ask_or_write_async,
input_parser=parameter_parser, output_parser=return_parser)

def validate(self, args):
'''
Expand Down
73 changes: 59 additions & 14 deletions qcodes/instrument/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from qcodes.utils.helpers import permissive_range, wait_secs
from qcodes.utils.metadata import Metadatable
from qcodes.utils.sync_async import syncable_command, NoCommandError
from qcodes.utils.validators import Validator, Numbers, Ints
from qcodes.utils.validators import Validator, Numbers, Ints, Enum
from qcodes.instrument.sweep_values import SweepFixedValues


Expand Down Expand Up @@ -176,35 +176,72 @@ def __getitem__(self, keys):

class InstrumentParameter(Parameter):
def __init__(self, instrument, name,
get_cmd=None, async_get_cmd=None, parse_function=None,
set_cmd=None, async_set_cmd=None,
get_cmd=None, async_get_cmd=None, get_parser=None,
parse_function=None, val_mapping=None,
set_cmd=None, async_set_cmd=None, set_parser=None,
sweep_step=None, sweep_delay=None, max_val_age=3600,
**kwargs):
vals=None, **kwargs):
'''
defines one measurement parameter
instrument: an instrument that handles this parameter
name: the local name of this parameter
get_cmd: a string or function to get this parameter
async_get_cmd: a function to use for async get, or for both sync
and async if get_cmd is missing or None
parse_function: function to transform the response from get
get_parser: function to transform the response from get
to the final output value.
NOTE: only applies if get_cmd is a string. The function forms
of get_cmd and async_get_cmd should do their own parsing
See also val_mapping
set_cmd: command to set this parameter, either:
- a string (containing one field to .format, like "{}" etc)
- a function (of one parameter)
async_set_cmd: a function to use for async set, or for both sync
and async if set_cmd is missing or None
set_parser: function to transform the input set value to an encoded
value sent to the instrument.
NOTE: only applies if set_cmd is a string. The function forms
of set_cmd and async_set_cmd should do their own parsing
See also val_mapping
parse_function: DEPRECATED - use get_parser instead
val_mapping: a bidirectional map from data/readable values to
instrument codes, expressed as a dict {data_val: instrument_code}
For example, if the instrument uses '0' to mean 1V and '1' to mean
10V, set val_mapping={1: '0', 10: '1'} and on the user side you
only see 1 and 10, never the coded '0' and '1'
If vals is omitted, will also construct a matching Enum validator.
NOTE: only applies to get if get_cmd is a string, and to set if
set_cmd is a string.
vals: a Validator object for this parameter
sweep_step: max increment of parameter value - larger changes
are broken into steps this size
sweep_delay: time (in seconds) to wait after each sweep step
max_val_age: max time (in seconds) to trust a saved value from
this parameter as the starting point of a sweep
'''
super().__init__(name=name, **kwargs)
# handle val_mapping before super init because it impacts
# vals / validation in the base class
if val_mapping:
if vals is None:
vals = Enum(*val_mapping.keys())

if get_parser is None:
self._get_mapping = {v: k for k, v in val_mapping.items()}
get_parser = self._get_mapping.__getitem__

if set_parser is None:
self._set_mapping = val_mapping
set_parser = self._set_mapping.__getitem__

super().__init__(name=name, vals=vals, **kwargs)

self._instrument = instrument

Expand All @@ -218,8 +255,12 @@ def __init__(self, instrument, name,
self.has_get = False
self.has_set = False

self._set_get(get_cmd, async_get_cmd, parse_function)
self._set_set(set_cmd, async_set_cmd)
# push deprecated parse_function argument to get_parser
if get_parser is None:
get_parser = parse_function

self._set_get(get_cmd, async_get_cmd, get_parser)
self._set_set(set_cmd, async_set_cmd, set_parser)
self.set_sweep(sweep_step, sweep_delay, max_val_age)

if not (self.has_get or self.has_set):
Expand Down Expand Up @@ -251,20 +292,24 @@ def get_async(self):
self._save_val(value)
return value

def _set_get(self, get_cmd, async_get_cmd, parse_function):
def _set_get(self, get_cmd, async_get_cmd, get_parser):
self._get, self._get_async = syncable_command(
0, get_cmd, async_get_cmd, self._instrument.ask,
self._instrument.ask_async, parse_function, no_func)
param_count=0, cmd=get_cmd, acmd=async_get_cmd,
exec_str=self._instrument.ask,
aexec_str=self._instrument.ask_async,
output_parser=get_parser, no_cmd_function=no_func)

if self._get is not no_func:
self.has_get = True

def _set_set(self, set_cmd, async_set_cmd):
def _set_set(self, set_cmd, async_set_cmd, set_parser):
# note: this does not set the final setter functions. that's handled
# in self.set_sweep, when we choose a swept or non-swept setter.
self._set, self._set_async = syncable_command(
1, set_cmd, async_set_cmd, self._instrument.write,
self._instrument.write_async, no_cmd_function=no_func)
param_count=1, cmd=set_cmd, acmd=async_set_cmd,
exec_str=self._instrument.write,
aexec_str=self._instrument.write_async,
input_parser=set_parser, no_cmd_function=no_func)

if self._set is not no_func:
self.has_set = True
Expand Down
51 changes: 44 additions & 7 deletions qcodes/tests/test_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from qcodes.instrument.base import Instrument
from qcodes.instrument.mock import MockInstrument
from qcodes.utils.validators import Numbers, Ints, Strings, MultiType
from qcodes.utils.validators import Numbers, Ints, Strings, MultiType, Enum
from qcodes.utils.sync_async import wait_for_async, NoCommandError


Expand All @@ -16,12 +16,16 @@ class AMockModel(object):
def __init__(self):
self._gates = [0.0, 0.0, 0.0]
self._excitation = 0.1
self._memory = {}

def write(self, instrument, parameter, value):
if instrument == 'gates' and parameter[0] == 'c':
self._gates[int(parameter[1:])] = float(value)
elif instrument == 'gates' and parameter == 'rst':
self._gates = [0.0, 0.0, 0.0]
elif instrument == 'gates' and parameter[:3] == 'mem':
slot = int(parameter[3:])
self._memory[slot] = value
elif instrument == 'source' and parameter == 'ampl':
try:
self._excitation = float(value)
Expand All @@ -37,6 +41,9 @@ def ask(self, instrument, parameter):

if instrument == 'gates' and parameter[0] == 'c':
v = gates[int(parameter[1:])]
elif instrument == 'gates' and parameter[:3] == 'mem':
slot = int(parameter[3:])
return self._memory[slot]
elif instrument == 'source' and parameter == 'ampl':
v = self._excitation
elif instrument == 'meter' and parameter == 'ampl':
Expand All @@ -63,29 +70,29 @@ def setUp(self):
cmdbase = 'c{}'.format(i)
self.gates.add_parameter('chan{}'.format(i), get_cmd=cmdbase + '?',
set_cmd=cmdbase + ' {:.4f}',
parse_function=float,
get_parser=float,
vals=Numbers(-10, 10))
self.gates.add_parameter('chan{}step'.format(i),
get_cmd=cmdbase + '?',
set_cmd=cmdbase + ' {:.4f}',
parse_function=float,
get_parser=float,
vals=Numbers(-10, 10),
sweep_step=0.1, sweep_delay=0.005)
self.gates.add_function('reset', call_cmd='rst')

self.source = MockInstrument('source', model=self.model, delay=0.001)
self.source.add_parameter('amplitude', get_cmd='ampl?',
set_cmd='ampl {:.4f}', parse_function=float,
set_cmd='ampl {:.4f}', get_parser=float,
vals=Numbers(0, 1),
sweep_step=0.2, sweep_delay=0.005)

self.meter = MockInstrument('meter', model=self.model, delay=0.001,
read_response=self.read_response)
self.meter.add_parameter('amplitude', get_cmd='ampl?',
parse_function=float)
get_parser=float)
self.meter.add_function('echo', call_cmd='echo {:.2f}?',
parameters=[Numbers(0, 1000)],
parse_function=float)
return_parser=float)

self.init_ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

Expand Down Expand Up @@ -224,7 +231,7 @@ def test_sweep_steps_edge_case(self):
# but we should handle it
source = self.source
source.add_parameter('amplitude2', get_cmd='ampl?',
set_cmd='ampl {}', parse_function=float,
set_cmd='ampl {}', get_parser=float,
vals=MultiType(Numbers(0, 1), Strings()),
sweep_step=0.2, sweep_delay=0.005)
self.assertEqual(len(source.history), 0)
Expand Down Expand Up @@ -290,6 +297,36 @@ def test_set_sweep_errors(self):
sweep_step=0.1, sweep_delay=0.01,
max_val_age=-1)

def test_val_mapping(self):
gates = self.gates
mem = self.model._memory

# memraw has no mappings - it just sets and gets what the instrument
# uses to encode this parameter
gates.add_parameter('memraw', set_cmd='mem0 {}', get_cmd='mem0?',
vals=Enum('zero', 'one'))

# memcoded maps the instrument codes ('zero' and 'one') into nicer
# user values 0 and 1
gates.add_parameter('memcoded', set_cmd='mem0 {}', get_cmd='mem0?',
val_mapping={0: 'zero', 1: 'one'})

gates.memcoded.set(0)
self.assertEqual(gates.memraw.get(), 'zero')
self.assertEqual(gates.memcoded.get(), 0)
self.assertEqual(mem[0], 'zero')

gates.memraw.set('one')
self.assertEqual(gates.memcoded.get(), 1)
self.assertEqual(gates.memraw.get(), 'one')
self.assertEqual(mem[0], 'one')

with self.assertRaises(ValueError):
gates.memraw.set(0)

with self.assertRaises(ValueError):
gates.memcoded.set('zero')

def test_snapshot(self):
self.assertEqual(self.meter.snapshot(), {
'parameters': {'amplitude': {}},
Expand Down
Loading

0 comments on commit 2f9d09c

Please sign in to comment.