Source code for qcodes.parameters.parameter

# TODO (alexcjohnson) update this with the real duck-typing requirements or
# create an ABC for Parameter and MultiParameter - or just remove this statement
# if everyone is happy to use these classes.
from __future__ import annotations

import logging
import os
from types import MethodType
from typing import TYPE_CHECKING, Any, Literal

from .command import Command
from .parameter_base import ParamDataType, ParameterBase, ParamRawDataType
from .sweep_values import SweepFixedValues

if TYPE_CHECKING:
    from collections.abc import Callable

    from qcodes.instrument.base import InstrumentBase
    from qcodes.logger.instrument_logger import InstrumentLoggerAdapter
    from qcodes.validators import Validator


log = logging.getLogger(__name__)


[docs] class Parameter(ParameterBase): """ A parameter represents a single degree of freedom. Most often, this is the standard parameter for Instruments, though it can also be used as a variable, i.e. storing/retrieving a value, or be subclassed for more complex uses. By default only gettable, returning its last value. This behaviour can be modified in two ways: 1. Providing a ``get_cmd``/``set_cmd``, which can do the following: a. callable, with zero args for get_cmd, one arg for set_cmd b. VISA command string c. None, in which case it retrieves its last value for ``get_cmd``, and stores a value for ``set_cmd`` d. False, in which case trying to get/set will raise an error. 2. Creating a subclass with an explicit :meth:`get_raw`/:meth:`set_raw` method. This enables more advanced functionality. The :meth:`get_raw` and :meth:`set_raw` methods are automatically wrapped to provide ``get`` and ``set``. It is an error to do both 1 and 2. E.g supply a ``get_cmd``/``set_cmd`` and implement ``get_raw``/``set_raw`` To detect if a parameter is gettable or settable check the attributes :py:attr:`~gettable` and :py:attr:`~settable` on the parameter. Parameters have a ``cache`` object that stores internally the current ``value`` and ``raw_value`` of the parameter. Calling ``cache.get()`` (or ``cache()``) simply returns the most recent set or measured value of the parameter. Parameter also has a ``.get_latest`` method that duplicates the behavior of ``cache()`` call, as in, it also simply returns the most recent set or measured value. Args: name: 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: The instrument this parameter belongs to, if any. label: Normally used as the axis label when this parameter is graphed, along with ``unit``. unit: The unit of measure. Use ``''`` for unitless. snapshot_get: ``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, or if the parameter is only meant for measurements hence calling get on it during snapshot may be an error. Default True. snapshot_value: ``False`` prevents parameter value to be stored in the snapshot. Useful if the value is large. snapshot_exclude: ``True`` prevents parameter to be included in the snapshot. Useful if there are many of the same parameter which are clogging up the snapshot. Default ``False``. step: Max increment of parameter value. Larger changes are broken into multiple steps this size. When combined with delays, this acts as a ramp. scale: Scale to multiply value with before performing set. the internally multiplied value is stored in ``cache.raw_value``. Can account for a voltage divider. inter_delay: Minimum time (in seconds) between successive sets. If the previous set was less than this, it will wait until the condition is met. Can be set to 0 to go maximum speed with no errors. post_delay: Time (in seconds) to wait after the *start* of each set, whether part of a sweep or not. Can be set to 0 to go maximum speed with no errors. val_mapping: A bi-directional map 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. You can use ``val_mapping`` with ``get_parser``, in which case ``get_parser`` acts on the return value from the instrument first, then ``val_mapping`` is applied (in reverse). get_parser: Function to transform the response from get to the final output value. See also `val_mapping`. set_parser: Function to transform the input set value to an encoded value sent to the instrument. See also `val_mapping`. vals: Allowed values for setting this parameter. Only relevant if settable. Defaults to ``Numbers()``. max_val_age: The max time (in seconds) to trust a saved value obtained from ``cache()`` (or ``cache.get()``, or ``get_latest()``. If this parameter has not been set or measured more recently than this, perform an additional measurement. initial_value: Value to set the parameter to at the end of its initialization (this is equivalent to calling ``parameter.set(initial_value)`` after parameter initialization). Cannot be passed together with ``initial_cache_value`` argument. initial_cache_value: Value to set the cache of the parameter to at the end of its initialization (this is equivalent to calling ``parameter.cache.set(initial_cache_value)`` after parameter initialization). Cannot be passed together with ``initial_value`` argument. docstring: Documentation string for the ``__doc__`` field of the object. The ``__doc__`` field of the instance is used by some help systems, but not all. metadata: Extra information to include with the JSON snapshot of the parameter. abstract: Specifies if this parameter is abstract or not. Default is False. If the parameter is 'abstract', it *must* be overridden by a non-abstract parameter before the instrument containing this parameter can be instantiated. We override a parameter by adding one with the same name and unit. An abstract parameter can be added in a base class and overridden in a subclass. bind_to_instrument: Should the parameter be registered as a delegate attribute on the instrument passed via the instrument argument. """ def __init__( self, name: str, instrument: InstrumentBase | None = None, label: str | None = None, unit: str | None = None, get_cmd: str | Callable[..., Any] | Literal[False] | None = None, set_cmd: str | Callable[..., Any] | Literal[False] | None = False, initial_value: float | str | None = None, max_val_age: float | None = None, vals: Validator[Any] | None = None, docstring: str | None = None, initial_cache_value: float | str | None = None, bind_to_instrument: bool = True, **kwargs: Any, ) -> None: def _get_manual_parameter(self: Parameter) -> ParamRawDataType: if self.root_instrument is not None: mylogger: InstrumentLoggerAdapter | logging.Logger = ( self.root_instrument.log ) else: mylogger = log mylogger.debug( "Getting raw value of parameter: %s as %s", self.full_name, self.cache.raw_value, ) return self.cache.raw_value def _set_manual_parameter( self: Parameter, x: ParamRawDataType ) -> ParamRawDataType: mylogger = self._get_logger() mylogger.debug( "Setting raw value of parameter: %s to %s", self.full_name, x ) self.cache._set_from_raw_value(x) return x if instrument is not None and bind_to_instrument: existing_parameter = instrument.parameters.get(name, None) if existing_parameter: # this check is redundant since its also in the baseclass # but if we do not put it here it would be an api break # as parameter duplication check won't be done first, # hence for parameters that are duplicates and have # wrong units, users will be getting ValueError where # they used to have KeyError before. if not existing_parameter.abstract: raise KeyError( f"Duplicate parameter name {name} on instrument {instrument}" ) existing_unit = getattr(existing_parameter, "unit", None) if existing_unit != unit: raise ValueError( f"The unit of the parameter '{name}' is '{unit}'. " f"This is inconsistent with the unit defined in the " f"base class" ) super().__init__( name=name, instrument=instrument, vals=vals, max_val_age=max_val_age, bind_to_instrument=bind_to_instrument, **kwargs, ) no_instrument_get = not self._implements_get_raw and ( get_cmd is None or get_cmd is False ) # TODO: a matching check should be in ParameterBase but # due to the current limited design the ParameterBase cannot # know if this subclass will supply a get_cmd # To work around this a RunTime check is put into get of GetLatest # and into get of _Cache if max_val_age is not None and no_instrument_get: raise SyntaxError( "Must have get method or specify get_cmd when max_val_age is set" ) # Enable set/get methods from get_cmd/set_cmd if given and # no `get`/`set` or `get_raw`/`set_raw` methods have been defined # in the scope of this class. # (previous call to `super().__init__` wraps existing # get_raw/set_raw into get/set methods) if self._implements_get_raw and get_cmd not in (None, False): raise TypeError( "Supplying a not None or False `get_cmd` to a Parameter" " that already implements" " get_raw is an error." ) elif not self._implements_get_raw and get_cmd is not False: if get_cmd is None: # ignore typeerror since mypy does not allow setting a method dynamically self.get_raw = MethodType(_get_manual_parameter, self) # type: ignore[method-assign] else: if isinstance(get_cmd, str) and instrument is None: raise TypeError( f"Cannot use a str get_cmd without " f"binding to an instrument. " f"Got: get_cmd {get_cmd} for parameter {name}" ) exec_str_ask = getattr(instrument, "ask", None) if instrument else None # TODO get_raw should also be a method here. This should probably be done by wrapping # it with MethodType like above # ignore typeerror since mypy does not allow setting a method dynamically self.get_raw = Command( # type: ignore[method-assign] arg_count=0, cmd=get_cmd, exec_str=exec_str_ask, ) self._gettable = True # mypy resolves the type of self.get_raw to object here. # this may be resolvable if Command above is correctly wrapped in MethodType self.get = self._wrap_get(self.get_raw) # type: ignore[arg-type] if self._implements_set_raw and set_cmd not in (None, False): raise TypeError( "Supplying a not None or False `set_cmd` to a Parameter" " that already implements" " set_raw is an error." ) elif not self._implements_set_raw and set_cmd is not False: if set_cmd is None: # ignore typeerror since mypy does not allow setting a method dynamically self.set_raw = MethodType(_set_manual_parameter, self) # type: ignore[method-assign] else: if isinstance(set_cmd, str) and instrument is None: raise TypeError( f"Cannot use a str set_cmd without " f"binding to an instrument. " f"Got: set_cmd {set_cmd} for parameter {name}" ) exec_str_write = ( getattr(instrument, "write", None) if instrument else None ) # TODO get_raw should also be a method here. This should probably be done by wrapping # it with MethodType like above # ignore typeerror since mypy does not allow setting a method dynamically self.set_raw = Command( # type: ignore[assignment] arg_count=1, cmd=set_cmd, exec_str=exec_str_write ) self._settable = True 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._label: str self.unit = unit if unit is not None else "" self._unitval: str if initial_value is not None and initial_cache_value is not None: raise SyntaxError( "It is not possible to specify both of the " "`initial_value` and `initial_cache_value` " "keyword arguments." ) if initial_value is not None: self.set(initial_value) if initial_cache_value is not None: self.cache.set(initial_cache_value) self._docstring = docstring self.__doc__ = self._build__doc__() def _build__doc__(self) -> str: if len(self.validators) == 0: validator_docstrings = ["* `vals` None"] else: validator_docstrings = [ f"* `vals` {validator!r}" for validator in self.validators ] # generate default docstring doc = os.linesep.join( ( "Parameter class:", "", f"* `name` {self.name}", f"* `label` {self.label}", f"* `unit` {self.unit}", *validator_docstrings, ) ) if self._docstring is not None: doc = os.linesep.join((self._docstring, "", doc)) return doc @property def unit(self) -> str: """ The unit of measure. Use ``''`` (the empty string) for unitless. """ return self._unitval @unit.setter def unit(self, unit: str) -> None: self._unitval = unit @property def label(self) -> str: """ Label of the data used for plots etc. """ return self._label @label.setter def label(self, label: str) -> None: self._label = label
[docs] def __getitem__(self, keys: Any) -> SweepFixedValues: """ Slice a Parameter to get a SweepValues object to iterate over during a sweep """ return SweepFixedValues(self, keys)
[docs] def increment(self, value: ParamDataType) -> None: """Increment the parameter with a value Args: value: Value to be added to the parameter. """ self.set(self.get() + value)
[docs] def sweep( self, start: float, stop: float, step: float | None = None, num: int | None = None, ) -> SweepFixedValues: """ 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: The starting value of the sequence. stop: The end value of the sequence. step: Spacing between values. num: 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)
[docs] class ManualParameter(Parameter): def __init__( self, name: str, instrument: InstrumentBase | None = None, initial_value: Any = None, **kwargs: Any, ): """ A simple alias for a parameter that does not have a set or a get function. Useful for parameters that do not have a direct instrument mapping. """ super().__init__( name=name, instrument=instrument, get_cmd=None, set_cmd=None, initial_value=initial_value, **kwargs, )