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, Generic, Literal

from typing_extensions import TypedDict

from .command import Command
from .parameter_base import (
    InstrumentTypeVar_co,
    ParameterBase,
    ParameterBaseKWArgs,
    ParameterDataTypeVar,
    ParamRawDataType,
)
from .sweep_values import SweepFixedValues

if TYPE_CHECKING:
    from collections.abc import Callable, Iterable, Mapping
    from typing import NotRequired

    from typing_extensions import Unpack

    from qcodes.logger.instrument_logger import InstrumentLoggerAdapter
    from qcodes.parameters import ParamSpecBase
    from qcodes.validators import Validator


log = logging.getLogger(__name__)


[docs] class ParameterKWArgs( TypedDict, Generic[ParameterDataTypeVar, InstrumentTypeVar_co], ): """ This TypedDict defines the type of the kwargs that can be passed to the ``Parameter`` class. A subclass of ``Parameter`` should take ``**kwargs: Unpack[ParameterKWArgs]`` as input and forward this to the super class to ensure that it can accept all the arguments defined here. """ # Members from ParameterBaseKWArgs are redeclared here # so that Sphinx can discover and document them. instrument: NotRequired[InstrumentTypeVar_co] """ The instrument this parameter belongs to, if any. """ snapshot_get: NotRequired[bool] """ False prevents any update to the parameter during a snapshot, even if the snapshot was called with ``update=True``. Default True. """ metadata: NotRequired[Mapping[Any, Any] | None] """ Additional static metadata to add to this parameter's JSON snapshot. """ step: NotRequired[float | None] """ Max increment of parameter value. Larger changes are broken into multiple steps this size. When combined with delays, this acts as a ramp. """ scale: NotRequired[float | Iterable[float] | None] """ Scale to multiply value with before performing set. The internally multiplied value is stored in ``cache.raw_value``. Can account for a voltage divider. """ offset: NotRequired[float | Iterable[float] | None] """ Compensate for a parameter specific offset. get value = raw value - offset. set value = argument + offset. """ inter_delay: NotRequired[float] """ 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: NotRequired[float] """ 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: NotRequired[Mapping[Any, Any] | None] """ A bidirectional map of data/readable values to instrument codes, expressed as a dict: ``{data_val: instrument_code}``. """ get_parser: NotRequired[Callable[..., Any] | None] """ Function to transform the response from get to the final output value. See also ``val_mapping``. """ set_parser: NotRequired[Callable[..., Any] | None] """ Function to transform the input set value to an encoded value sent to the instrument. See also ``val_mapping``. """ snapshot_value: NotRequired[bool] """ False prevents parameter value to be stored in the snapshot. Useful if the value is large. Default True. """ snapshot_exclude: NotRequired[bool] """ 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. """ max_val_age: NotRequired[float | None] """ The max time (in seconds) to trust a saved value obtained from ``cache.get`` (or ``get_latest``). If this parameter has not been set or measured more recently than this, perform an additional measurement. """ vals: NotRequired[Validator[Any] | None] """ A Validator object for this parameter. """ abstract: NotRequired[bool | None] """ 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. """ bind_to_instrument: NotRequired[bool] """ Should the parameter be registered as a delegate attribute on the instrument passed via the instrument argument. """ register_name: NotRequired[str | None] """ Specifies if the parameter should be registered in datasets using a different name than the parameter's ``full_name``. """ on_set_callback: NotRequired[ Callable[[ParameterBase, ParameterDataTypeVar], None] | None ] """ Callback called when the parameter value is set. """ # Members specific to ParameterKWArgs label: NotRequired[str | None] """ Normally used as the axis label when this parameter is graphed, along with ``unit``. """ unit: NotRequired[str | None] """ The unit of measure. Use ``''`` for unitless. """ get_cmd: NotRequired[str | Callable[..., Any] | Literal[False] | None] """ A command to issue to the instrument to retrieve the value of this parameter. Can be a callable with zero args, a VISA command string, ``None`` to use ``get_raw``, or ``False`` to disable getting. """ set_cmd: NotRequired[str | Callable[..., Any] | Literal[False] | None] """ A command to issue to the instrument to set the value of this parameter. Can be a callable with one arg, a VISA command string, ``None`` to use ``set_raw``, or ``False`` to disable setting. Default ``False``. """ initial_value: NotRequired[ParameterDataTypeVar | None] """ 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. """ docstring: NotRequired[str | None] """ Documentation string for the ``__doc__`` field of the object. """ initial_cache_value: NotRequired[ParameterDataTypeVar | None] """ Value to set the cache of the parameter to at the end of its initialization. Cannot be passed together with ``initial_value`` argument. """
[docs] class Parameter( ParameterBase[ParameterDataTypeVar, InstrumentTypeVar_co], Generic[ParameterDataTypeVar, InstrumentTypeVar_co], ): """ 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]``. label: Normally used as the axis label when this parameter is graphed, along with ``unit``. unit: The unit of measure. Use ``''`` for unitless. get_cmd: A command to issue to the instrument to retrieve the value of this parameter. Can be a callable with zero args, a VISA command string, ``None`` to use ``get_raw``, or ``False`` to disable getting. set_cmd: A command to issue to the instrument to set the value of this parameter. Can be a callable with one arg, a VISA command string, ``None`` to use ``set_raw``, or ``False`` to disable setting. Default ``False``. 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. 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. 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. **kwargs: Forwarded to the ``ParameterBase`` base class. See :class:`ParameterBaseKWArgs` for details. """ def __init__( self, name: str, *, 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: ParameterDataTypeVar | None = None, docstring: str | None = None, initial_cache_value: ParameterDataTypeVar | None = None, **kwargs: Unpack[ ParameterBaseKWArgs[ParameterDataTypeVar, InstrumentTypeVar_co] ], ) -> 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 instrument = kwargs.get("instrument") bind_to_instrument = kwargs.get("bind_to_instrument", True) max_val_age = kwargs.get("max_val_age") 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, **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 self.get = self._wrap_get(self.get_raw) 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: ParameterDataTypeVar) -> None: """Increment the parameter with a value Args: value: Value to be added to the parameter. """ # this method only works with parameters that support addition # however we don't currently enforce that via typing self.set(self.get() + value) # type: ignore[operator]
[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)
@property def param_spec(self) -> ParamSpecBase: paramspecbase = super().param_spec # Sets the name and paramtype paramspecbase.label = self.label paramspecbase.unit = self.unit return paramspecbase
[docs] class ManualParameter(Parameter): def __init__( self, name: str, **kwargs: Unpack[ParameterKWArgs], ): """ 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. Args: name: The local name of the parameter. **kwargs: Forwarded to the ``Parameter`` base class. See :class:`ParameterKWArgs` for details. Note that ``get_cmd`` and ``set_cmd`` are not allowed since ManualParameter hardcodes these to ``None``. Raises: ValueError: If ``get_cmd`` or ``set_cmd`` is provided. """ forbidden_kwargs = ("get_cmd", "set_cmd") for fk in forbidden_kwargs: if fk in kwargs: raise ValueError( f'It is not allowed to set "{fk}" for a ManualParameter.' ) kwargs["get_cmd"] = None kwargs["set_cmd"] = None super().__init__( name=name, **kwargs, )