예제 #1
0
    def test_max(self):
        for max_val in [-1e20, -1, -0.1, 0, 0.1, 10]:
            n = Numbers(max_value=max_val)

            n.validate(n.valid_values[0])
            for v in self.numbers:
                if v <= max_val:
                    n.validate(v)
                else:
                    with self.assertRaises(ValueError):
                        n.validate(v)

        for v in self.not_numbers:
            with self.assertRaises(TypeError):
                n.validate(v)

        with self.assertRaises(ValueError):
            n.validate(float('nan'))
예제 #2
0
def test_min():
    for min_val in [-1e20, -1, -0.1, 0, 0.1, 10]:
        n = Numbers(min_value=min_val)

        n.validate(n.valid_values[0])
        for v in numbers:
            if v >= min_val:
                n.validate(v)
            else:
                with pytest.raises(ValueError):
                    n.validate(v)

    for v in not_numbers:
        with pytest.raises(TypeError):
            n.validate(v)

    with pytest.raises(ValueError):
        n.validate(float('nan'))
예제 #3
0
    def test_unlimited(self):
        n = Numbers()

        for v in self.numbers:
            n.validate(v)

        for v in self.not_numbers:
            with self.assertRaises(TypeError):
                n.validate(v)

        # special case - nan now raises a ValueError rather than a TypeError
        with self.assertRaises(ValueError):
            n.validate(float('nan'))

        n.validate(n.valid_values[0])
예제 #4
0
    def test_min(self):
        for min_val in [-1e20, -1, -0.1, 0, 0.1, 10]:
            n = Numbers(min_value=min_val)
            for v in self.numbers:
                if v >= min_val:
                    n.validate(v)
                else:
                    with self.assertRaises(ValueError):
                        n.validate(v)

        for v in self.not_numbers:
            with self.assertRaises(TypeError):
                n.validate(v)

        with self.assertRaises(ValueError):
            n.validate(float('nan'))
예제 #5
0
    def test_range(self):
        n = Numbers(0.1, 3.5)

        for v in self.numbers:
            if 0.1 <= v <= 3.5:
                n.validate(v)
            else:
                with self.assertRaises(ValueError):
                    n.validate(v)

        for v in self.not_numbers:
            with self.assertRaises(TypeError):
                n.validate(v)

        with self.assertRaises(ValueError):
            n.validate(float('nan'))

        self.assertEqual(repr(n), '<Numbers 0.1<=v<=3.5>')
예제 #6
0
def test_range():
    n = Numbers(0.1, 3.5)

    for v in numbers:
        if 0.1 <= v <= 3.5:
            n.validate(v)
        else:
            with pytest.raises(ValueError):
                n.validate(v)

    for v in not_numbers:
        with pytest.raises(TypeError):
            n.validate(v)

    with pytest.raises(ValueError):
        n.validate(float('nan'))

    assert repr(n) == '<Numbers 0.1<=v<=3.5>'
예제 #7
0
 def test_valid_values(self):
     val = Numbers()
     for vval in val.valid_values:
         val.validate(vval)
예제 #8
0
class Parameter(Metadatable, DeferredOperations):
    """
    Define one generic parameter, not necessarily part of
    an instrument. can be settable and/or gettable.

    A settable Parameter has a .set method, and supports only a single value
    at a time (see below)

    A gettable Parameter has a .get method, which may return:

    1.  a single value
    2.  a sequence of values with different names (for example,
        raw and interpreted, I and Q, several fit parameters...)
    3.  an array of values all with the same name, but at different
        setpoints (for example, a time trace or fourier transform that
        was acquired in the hardware and all sent to the computer at once)
    4.  2 & 3 together: a sequence of arrays. All arrays should be the same
        shape.
    5.  a sequence of differently shaped items

    Because .set only supports a single value, if a Parameter is both
    gettable AND settable, .get should return a single value too (case 1)

    Parameters have a .get_latest method that simply returns the most recent
    set or measured value. This can either be called ( param.get_latest() )
    or used in a Loop as if it were a (gettable-only) parameter itself:
        Loop(...).each(param.get_latest)


    The constructor arguments change somewhat between these cases:

    Todo:
        no idea how to document such a constructor

    Args:
        name: (1&3) the local name of this parameter, should be a valid
            identifier, ie no spaces or special characters

        names: (2,4,5) a tuple of names

        label: (1&3) string to use as an axis label for this parameter
            defaults to name

        labels: (2,4,5) a tuple of labels

        units: (1&3) string that indicates units of parameter for use in axis
            label and snapshot

        shape: (3&4) a tuple of integers for the shape of array returned by
            .get().

        shapes: (5) a tuple of tuples, each one as in `shape`.
            Single values should be denoted by None or ()

        setpoints: (3,4,5) the setpoints for the returned array of values.
            3&4 - a tuple of arrays. The first array is be 1D, the second 2D,
                etc.
            5 - a tuple of tuples of arrays
            Defaults to integers from zero in each respective direction
            Each may be either a DataArray, a numpy array, or a sequence
            (sequences will be converted to numpy arrays)
            NOTE: if the setpoints will be different each measurement, leave
            this out and return the setpoints (with extra names) in the get.

        setpoint_names: (3,4,5) one identifier (like `name`) per setpoint
            array. Ignored if `setpoints` are DataArrays, which already have
            names.

        setpoint_labels: (3&4) one label (like `label`) per setpoint array.
            Overridden if `setpoints` are DataArrays and already have labels.

        vals: allowed values for setting this parameter (only relevant
            if it has a setter),  defaults to Numbers()

        docstring (Optional[string]): documentation string for the __doc__
            field of the object. The __doc__ field of the instance is used by
            some help systems, but not all

        snapshot_get (bool): Prevent any update to the parameter
          for example if it takes too long to update

    """
    def __init__(self,
                 name=None,
                 names=None,
                 label=None,
                 labels=None,
                 units=None,
                 shape=None,
                 shapes=None,
                 setpoints=None,
                 setpoint_names=None,
                 setpoint_labels=None,
                 vals=None,
                 docstring=None,
                 snapshot_get=True,
                 **kwargs):
        super().__init__(**kwargs)
        self._snapshot_get = snapshot_get

        self.has_get = hasattr(self, 'get')
        self.has_set = hasattr(self, 'set')
        self._meta_attrs = ['setpoint_names', 'setpoint_labels']

        # always let the parameter have a single name (in fact, require this!)
        # even if it has names too
        self.name = str(name)

        if names is not None:
            # check for names first - that way you can provide both name
            # AND names for instrument parameters - name is how you get the
            # object (from the parameters dict or the delegated attributes),
            # and names are the items it returns
            self.names = names
            self.labels = names if labels is None else names
            self.units = units if units is not None else [''] * len(names)

            self.set_validator(vals or Anything())
            self.__doc__ = os.linesep.join(
                ('Parameter class:', '* `names` %s' % ', '.join(self.names),
                 '* `labels` %s' % ', '.join(self.labels),
                 '* `units` %s' % ', '.join(self.units)))
            self._meta_attrs.extend(['names', 'labels', 'units'])

        elif name is not None:
            self.label = name if label is None else label
            self.units = units if units is not None else ''

            # vals / validate only applies to simple single-value parameters
            self.set_validator(vals)

            # generate default docstring
            self.__doc__ = os.linesep.join((
                'Parameter class:',
                '* `name` %s' % self.name,
                '* `label` %s' % self.label,
                # TODO is this unit s a typo? shouldnt that be unit?
                '* `units` %s' % self.units,
                '* `vals` %s' % repr(self._vals)))
            self._meta_attrs.extend(['name', 'label', 'units', 'vals'])

        else:
            raise ValueError('either name or names is required')

        if shape is not None or shapes is not None:
            nt = type(None)

            if shape is not None:
                if not is_sequence_of(shape, int):
                    raise ValueError('shape must be a tuple of ints, not ' +
                                     repr(shape))
                self.shape = shape
                depth = 1
                container_str = 'tuple'
            else:
                if not is_sequence_of(shapes, int, depth=2):
                    raise ValueError('shapes must be a tuple of tuples '
                                     'of ints, not ' + repr(shape))
                self.shapes = shapes
                depth = 2
                container_str = 'tuple of tuples'

            sp_types = (nt, DataArray, collections.Sequence,
                        collections.Iterator)
            if (setpoints is not None
                    and not is_sequence_of(setpoints, sp_types, depth)):
                raise ValueError(
                    'setpoints must be a {} of arrays'.format(container_str))
            if (setpoint_names is not None
                    and not is_sequence_of(setpoint_names, (nt, str), depth)):
                raise ValueError('setpoint_names must be a {} '
                                 'of strings'.format(container_str))
            if (setpoint_labels is not None
                    and not is_sequence_of(setpoint_labels, (nt, str), depth)):
                raise ValueError('setpoint_labels must be a {} '
                                 'of strings'.format(container_str))

            self.setpoints = setpoints
            self.setpoint_names = setpoint_names
            self.setpoint_labels = setpoint_labels

        # 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
        self._latest_ts = None

        if docstring is not None:
            self.__doc__ = docstring + os.linesep + self.__doc__

        self.get_latest = GetLatest(self)

    def __repr__(self):
        return named_repr(self)

    def __call__(self, *args):
        if len(args) == 0:
            if self.has_get:
                return self.get()
            else:
                raise NoCommandError('no get cmd found in' +
                                     ' Parameter {}'.format(self.name))
        else:
            if self.has_set:
                self.set(*args)
            else:
                raise NoCommandError('no set cmd found in' +
                                     ' Parameter {}'.format(self.name))

    def _latest(self):
        return {'value': self._latest_value, 'ts': self._latest_ts}

    # get_attrs ignores leading underscores, unless they're in this list
    _keep_attrs = ['__doc__', '_vals']

    def get_attrs(self):
        """
        Attributes recreated as properties in the RemoteParameter proxy.

        Grab the names of all attributes that the RemoteParameter needs
        to function like the main one (in loops etc)

        Returns:
            list: All public attribute names, plus docstring and _vals
        """
        out = []

        for attr in dir(self):
            if ((attr[0] == '_' and attr not in self._keep_attrs)
                    or callable(getattr(self, attr))):
                continue
            out.append(attr)

        return out

    def snapshot_base(self, update=False):
        """
        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.

        Returns:
            dict: base snapshot
        """

        if self.has_get and self._snapshot_get and update:
            self.get()

        state = self._latest()
        state['__class__'] = full_class(self)

        if isinstance(state['ts'], datetime):
            state['ts'] = state['ts'].strftime('%Y-%m-%d %H:%M:%S')

        for attr in set(self._meta_attrs):
            if attr == 'instrument' and getattr(self, '_instrument', None):
                state.update({
                    'instrument': full_class(self._instrument),
                    'instrument_name': self._instrument.name
                })

            elif hasattr(self, attr):
                state[attr] = getattr(self, attr)

        return state

    def _save_val(self, value):
        self._latest_value = value
        self._latest_ts = datetime.now()

    def set_validator(self, vals):
        """
        Set a validator `vals` for this parameter.

        Args:
            vals (Validator):  validator to set
        """
        if vals is None:
            self._vals = Numbers()
        elif isinstance(vals, Validator):
            self._vals = vals
        else:
            raise TypeError('vals must be a Validator')

    def validate(self, value):
        """
        Validate value

        Args:
            value (any): value to validate

        """
        if hasattr(self, '_instrument'):
            context = (getattr(self._instrument, 'name', '')
                       or str(self._instrument.__class__)) + '.' + self.name
        else:
            context = self.name

        self._vals.validate(value, 'Parameter: ' + context)

    def sweep(self, start, stop, step=None, num=None):
        """
        Create a collection of parameter values to be iterated over.
        Requires `start` and `stop` and (`step` or `num`)
        The sign of `step` is not relevant.

        Args:
            start (Union[int, float]): The starting value of the sequence.
            stop (Union[int, float]): The end value of the sequence.
            step (Optional[Union[int, float]]):  Spacing between values.
            num (Optional[int]): Number of values to generate.

        Returns:
            SweepFixedValues: collection of parameter values to be
                iterated over

        Examples:
            >>> sweep(0, 10, num=5)
             [0.0, 2.5, 5.0, 7.5, 10.0]
            >>> sweep(5, 10, step=1)
            [5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
            >>> sweep(15, 10.5, step=1.5)
            >[15.0, 13.5, 12.0, 10.5]
        """
        return SweepFixedValues(self,
                                start=start,
                                stop=stop,
                                step=step,
                                num=num)

    def __getitem__(self, keys):
        """
        Slice a Parameter to get a SweepValues object
        to iterate over during a sweep
        """
        return SweepFixedValues(self, keys)

    @property
    def full_name(self):
        """Include the instrument name with the Parameter name if possible."""
        if getattr(self, 'name', None) is None:
            return None

        try:
            inst_name = self._instrument.name
            if inst_name:
                return inst_name + '_' + self.name
        except AttributeError:
            pass

        return self.name

    @property
    def full_names(self):
        """Include the instrument name with the Parameter names if possible."""
        if getattr(self, 'names', None) is None:
            return None

        try:
            inst_name = self._instrument.name
            if inst_name:
                return [inst_name + '_' + name for name in self.names]
        except AttributeError:
            pass

        return self.names
예제 #9
0
class Parameter(_BaseParameter):
    """
    A parameter that represents a single degree of freedom.
    Not necessarily part of an instrument.

    Subclasses should define either a ``set`` method, a ``get`` method, or
    both.

    Parameters have a ``.get_latest`` method that simply returns the most
    recent set or measured value. This can be called ( ``param.get_latest()`` )
    or used in a ``Loop`` as if it were a (gettable-only) parameter itself:

        ``Loop(...).each(param.get_latest)``

    Note: If you want ``.get`` or ``.set`` to save the measurement for
    ``.get_latest``, you must explicitly call ``self._save_val(value)``
    inside ``.get`` and ``.set``.

    Args:
        name (str): the local name of the parameter. Should be a valid
            identifier, ie no spaces or special characters. If this parameter
            is part of an Instrument or Station, this is how it will be
            referenced from that parent, ie ``instrument.name`` or
            ``instrument.parameters[name]``

        instrument (Optional[Instrument]): the instrument this parameter
            belongs to, if any

        label (Optional[str]): Normally used as the axis label when this
            parameter is graphed, along with ``unit``.

        unit (Optional[str]): The unit of measure. Use ``''`` for unitless.

        units (Optional[str]): DEPRECATED, redirects to ``unit``.

        vals (Optional[Validator]): Allowed values for setting this parameter.
            Only relevant if settable. Defaults to ``Numbers()``

        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

        snapshot_get (Optional[bool]): False prevents any update to the
            parameter during a snapshot, even if the snapshot was called with
            ``update=True``, for example if it takes too long to update.
            Default True.

        metadata (Optional[dict]): extra information to include with the
            JSON snapshot of the parameter
    """
    def __init__(self, name, instrument=None, label=None,
                 unit=None, units=None, vals=None, docstring=None,
                 snapshot_get=True, snapshot_value=True, metadata=None):
        super().__init__(name, instrument, snapshot_get, metadata,
                         snapshot_value=snapshot_value)

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

        self.label = name if label is None else label

        if units is not None:
            warn_units('Parameter', self)
            if unit is None:
                unit = units
        self.unit = unit if unit is not None else ''

        self.set_validator(vals)

        # generate default docstring
        self.__doc__ = os.linesep.join((
            'Parameter class:',
            '',
            '* `name` %s' % self.name,
            '* `label` %s' % self.label,
            '* `unit` %s' % self.unit,
            '* `vals` %s' % repr(self._vals)))

        if docstring is not None:
            self.__doc__ = os.linesep.join((
                docstring,
                '',
                self.__doc__))

    def set_validator(self, vals):
        """
        Set a validator `vals` for this parameter.

        Args:
            vals (Validator):  validator to set
        """
        if vals is None:
            self._vals = Numbers()
        elif isinstance(vals, Validator):
            self._vals = vals
        else:
            raise TypeError('vals must be a Validator')

    def validate(self, value):
        """
        Validate value

        Args:
            value (any): value to validate

        """
        if self._instrument:
            context = (getattr(self._instrument, 'name', '') or
                       str(self._instrument.__class__)) + '.' + self.name
        else:
            context = self.name

        self._vals.validate(value, 'Parameter: ' + context)

    def sweep(self, start, stop, step=None, num=None):
        """
        Create a collection of parameter values to be iterated over.
        Requires `start` and `stop` and (`step` or `num`)
        The sign of `step` is not relevant.

        Args:
            start (Union[int, float]): The starting value of the sequence.
            stop (Union[int, float]): The end value of the sequence.
            step (Optional[Union[int, float]]):  Spacing between values.
            num (Optional[int]): Number of values to generate.

        Returns:
            SweepFixedValues: collection of parameter values to be
                iterated over

        Examples:
            >>> sweep(0, 10, num=5)
             [0.0, 2.5, 5.0, 7.5, 10.0]
            >>> sweep(5, 10, step=1)
            [5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
            >>> sweep(15, 10.5, step=1.5)
            >[15.0, 13.5, 12.0, 10.5]
        """
        return SweepFixedValues(self, start=start, stop=stop,
                                step=step, num=num)

    def __getitem__(self, keys):
        """
        Slice a Parameter to get a SweepValues object
        to iterate over during a sweep
        """
        return SweepFixedValues(self, keys)

    @property
    def units(self):
        warn_units('Parameter', self)
        return self.unit