Source code for qcodes.parameters.scaled_paramter
from __future__ import annotations
import enum
from operator import xor
from typing import cast
from .parameter import ManualParameter, Parameter
[docs]
class ScaledParameter(Parameter):
    """
    :class:`.Parameter` Scaler
    To be used when you use a physical voltage divider or an amplifier to set
    or get a quantity.
    Initialize the parameter by passing the parameter to be measured/set
    and the value of the division OR the gain.
    The scaling value can be either a scalar value or a Qcodes Parameter.
    The parameter scaler acts a your original parameter, but will set the right
    value, and store the gain/division in the metadata.
    Examples:
        Resistive voltage divider
        >>> vd = ScaledParameter(dac.chan0, division = 10)
        Voltage multiplier
        >>> vb = ScaledParameter(dac.chan0, gain = 30, name = 'Vb')
        Transimpedance amplifier
        >>> Id = ScaledParameter(multimeter.amplitude,
        ...                      division = 1e6, name = 'Id', unit = 'A')
    Args:
        output: Physical Parameter that need conversion.
        division: The division value.
        gain: The gain value.
        label: Label of this parameter, by default uses 'output' label
            but attaches _amplified or _attenuated depending if gain
            or division has been specified.
        name: Name of this parameter, by default uses 'output' name
            but attaches _amplified or _attenuated depending if gain
            or division has been specified.
        unit: Resulting unit. It uses the one of 'output' by default.
    """
[docs]
    class Role(enum.Enum):
        GAIN = enum.auto()
        DIVISION = enum.auto() 
    def __init__(
        self,
        output: Parameter,
        division: float | Parameter | None = None,
        gain: float | Parameter | None = None,
        name: str | None = None,
        label: str | None = None,
        unit: str | None = None,
    ) -> None:
        # Set label
        if label:
            self.label = label
        elif name:
            self.label = name
        else:
            self.label = f"{output.label}_scaled"
        # Set the name
        if not name:
            name = f"{output.name}_scaled"
        # Set the unit
        if unit:
            self.unit = unit
        else:
            self.unit = output.unit
        super().__init__(name=name, label=self.label, unit=self.unit)
        self._wrapped_parameter = output
        self._wrapped_instrument = getattr(output, "_instrument", None)
        # Set the role, either as divider or amplifier
        # Raise an error if nothing is specified
        is_divider = division is not None
        is_amplifier = gain is not None
        if not xor(is_divider, is_amplifier):
            raise ValueError("Provide only division OR gain")
        if division is not None:
            self.role = ScaledParameter.Role.DIVISION
            self._multiplier = division
        elif gain is not None:
            self.role = ScaledParameter.Role.GAIN
            self._multiplier = gain
        # extend metadata
        self._meta_attrs.extend(["division"])
        self._meta_attrs.extend(["gain"])
        self._meta_attrs.extend(["role"])
        self.metadata["wrapped_parameter"] = self._wrapped_parameter.name
        if self._wrapped_instrument:
            wrapped_instr_name = getattr(self._wrapped_instrument, "name", None)
            self.metadata["wrapped_instrument"] = wrapped_instr_name
    # Internal handling of the multiplier
    # can be either a Parameter or a scalar
    @property
    def _multiplier(self) -> Parameter:
        if self._multiplier_parameter is None:
            raise RuntimeError(
                "Cannot get multiplier when multiplier parameter in unknown."
            )
        return self._multiplier_parameter
    @_multiplier.setter
    def _multiplier(self, multiplier: float | Parameter) -> None:
        if isinstance(multiplier, Parameter):
            self._multiplier_parameter = multiplier
            multiplier_name = self._multiplier_parameter.name
            self.metadata["variable_multiplier"] = multiplier_name
        else:
            self._multiplier_parameter = ManualParameter(
                "multiplier", initial_value=multiplier
            )
            self.metadata["variable_multiplier"] = False
    # Division of the scaler
    @property
    def division(self) -> float:
        value = cast("float", self._multiplier())
        if self.role == ScaledParameter.Role.DIVISION:
            return value
        elif self.role == ScaledParameter.Role.GAIN:
            return 1 / value
        else:
            raise ValueError(f"Unexpected role {self.role}")
    @division.setter
    def division(self, division: float | Parameter) -> None:
        self.role = ScaledParameter.Role.DIVISION
        self._multiplier = division
    # Gain of the scaler
    @property
    def gain(self) -> float:
        value = cast("float", self._multiplier())
        if self.role == ScaledParameter.Role.GAIN:
            return value
        elif self.role == ScaledParameter.Role.DIVISION:
            return 1 / value
        else:
            raise ValueError(f"Unexpected role {self.role}")
    @gain.setter
    def gain(self, gain: float | Parameter) -> None:
        self.role = ScaledParameter.Role.GAIN
        self._multiplier = gain
    # Getter and setter for the real value
[docs]
    def get_raw(self) -> float:
        """
        Returns:
            value at which was set at the sample
        """
        wrapped_value = cast("float", self._wrapped_parameter())
        multiplier = cast("float", self._multiplier())
        if self.role == ScaledParameter.Role.GAIN:
            value = wrapped_value * multiplier
        elif self.role == ScaledParameter.Role.DIVISION:
            value = wrapped_value / multiplier
        else:
            raise RuntimeError(
                f"ScaledParameter must be either a"
                f"Multiplier or Divisor; got {self.role}"
            )
        return value 
    @property
    def wrapped_parameter(self) -> Parameter:
        """
        The attached unscaled parameter
        """
        return self._wrapped_parameter
[docs]
    def get_wrapped_parameter_value(self) -> float:
        """
        Returns:
            value at which the attached parameter is (i.e. does
            not account for the scaling)
        """
        return self._wrapped_parameter.get() 
[docs]
    def set_raw(self, value: float) -> None:
        """
        Set the value on the wrapped parameter, accounting for the scaling
        """
        multiplier_value = cast("float", self._multiplier())
        if self.role == ScaledParameter.Role.GAIN:
            instrument_value = value / multiplier_value
        elif self.role == ScaledParameter.Role.DIVISION:
            instrument_value = value * multiplier_value
        else:
            raise RuntimeError(
                f"ScaledParameter must be either a"
                f"Multiplier or Divisor; got {self.role}"
            )
        self._wrapped_parameter.set(instrument_value)