From 8864baaaae407831d9332646b1face61136e2860 Mon Sep 17 00:00:00 2001 From: "Tobias Hangleiter (Valhalla)" Date: Fri, 17 Jan 2025 12:47:10 +0100 Subject: [PATCH 1/5] Check for numeric type using numbers.Real isinstance(np.int32(0), int) evaluates to false for example. --- .../instrument_drivers/american_magnetics/AMI430.py | 2 +- .../american_magnetics/AMI430_visa.py | 2 +- src/qcodes/math_utils/field_vector.py | 7 ++++--- src/qcodes/parameters/parameter_base.py | 11 ++++++----- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/qcodes/instrument_drivers/american_magnetics/AMI430.py b/src/qcodes/instrument_drivers/american_magnetics/AMI430.py index 38ef5bfe8f9..c160ee496cf 100644 --- a/src/qcodes/instrument_drivers/american_magnetics/AMI430.py +++ b/src/qcodes/instrument_drivers/american_magnetics/AMI430.py @@ -978,7 +978,7 @@ def _raise_if_not_same_field_and_ramp_rate_units(self) -> tuple[str, str]: def _verify_safe_setpoint( self, setpoint_values: tuple[float, float, float] ) -> bool: - if isinstance(self._field_limit, (int, float)): + if isinstance(self._field_limit, numbers.Real): return bool(np.linalg.norm(setpoint_values) < self._field_limit) answer = any( diff --git a/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py b/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py index 1e5fbe15090..ccff40b68d1 100644 --- a/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py +++ b/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py @@ -1073,7 +1073,7 @@ def _raise_if_not_same_field_and_ramp_rate_units(self) -> tuple[str, str]: def _verify_safe_setpoint( self, setpoint_values: tuple[float, float, float] ) -> bool: - if isinstance(self._field_limit, (int, float)): + if isinstance(self._field_limit, numbers.Real): return bool(np.linalg.norm(setpoint_values) < self._field_limit) answer = any( diff --git a/src/qcodes/math_utils/field_vector.py b/src/qcodes/math_utils/field_vector.py index 97c8a145e41..0de928067bb 100644 --- a/src/qcodes/math_utils/field_vector.py +++ b/src/qcodes/math_utils/field_vector.py @@ -5,6 +5,7 @@ from __future__ import annotations +import numbers from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeVar import numpy as np @@ -292,7 +293,7 @@ def __setitem__(self, component: str, value: float) -> None: self.set_component(**{component: value}) def __mul__(self, other: Any) -> FieldVector: - if not isinstance(other, (float, int)): + if not isinstance(other, numbers.Real): return NotImplemented return FieldVector( @@ -300,13 +301,13 @@ def __mul__(self, other: Any) -> FieldVector: ) def __rmul__(self, other: Any) -> FieldVector: - if not isinstance(other, (int, float)): + if not isinstance(other, numbers.Real): return NotImplemented return self * other def __truediv__(self, other: Any) -> FieldVector: - if not isinstance(other, (int, float)): + if not isinstance(other, numbers.Real): return NotImplemented return self * (1.0 / other) diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index 4f112054f5b..adfa5e73ec0 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -2,6 +2,7 @@ import collections.abc import logging +import numbers import time import warnings from contextlib import contextmanager @@ -809,8 +810,8 @@ def get_ramp_values( self.get() start_value = self.get_latest() if not ( - isinstance(start_value, (int, float)) - and isinstance(value, (int, float)) + isinstance(start_value, numbers.Real) + and isinstance(value, numbers.Real) ): # parameter is numeric but either one of the endpoints # is not or the starting point is unknown. The later @@ -888,7 +889,7 @@ def step(self, step: float | None) -> None: self._step: float | None = step elif not all(getattr(vals, "is_numeric", True) for vals in self._vals): raise TypeError("you can only step numeric parameters") - elif not isinstance(step, (int, float)): + elif not isinstance(step, numbers.Real): raise TypeError("step must be a number") elif step == 0: self._step = None @@ -928,7 +929,7 @@ def post_delay(self) -> float: @post_delay.setter def post_delay(self, post_delay: float) -> None: - if not isinstance(post_delay, (int, float)): + if not isinstance(post_delay, numbers.Real): raise TypeError(f"post_delay ({post_delay}) must be a number") if post_delay < 0: raise ValueError(f"post_delay ({post_delay}) must not be negative") @@ -957,7 +958,7 @@ def inter_delay(self) -> float: @inter_delay.setter def inter_delay(self, inter_delay: float) -> None: - if not isinstance(inter_delay, (int, float)): + if not isinstance(inter_delay, numbers.Real): raise TypeError(f"inter_delay ({inter_delay}) must be a number") if inter_delay < 0: raise ValueError(f"inter_delay ({inter_delay}) must not be negative") From c722257a9c53bbd946f6fcc2552f25ebf033da86 Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Mon, 27 Jan 2025 15:27:44 +0100 Subject: [PATCH 2/5] Introduce custom TypeAlias for numbers --- .../instrument_drivers/american_magnetics/AMI430.py | 6 +++--- .../american_magnetics/AMI430_visa.py | 10 ++++------ src/qcodes/math_utils/field_vector.py | 9 +++++---- src/qcodes/parameters/parameter_base.py | 11 +++++------ src/qcodes/utils/types.py | 4 ++++ 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/qcodes/instrument_drivers/american_magnetics/AMI430.py b/src/qcodes/instrument_drivers/american_magnetics/AMI430.py index c160ee496cf..89771d512a9 100644 --- a/src/qcodes/instrument_drivers/american_magnetics/AMI430.py +++ b/src/qcodes/instrument_drivers/american_magnetics/AMI430.py @@ -4,7 +4,6 @@ import collections.abc import logging -import numbers import time import warnings from collections import defaultdict @@ -20,6 +19,7 @@ from qcodes.math_utils import FieldVector from qcodes.parameters import Parameter from qcodes.utils import QCoDeSDeprecationWarning +from qcodes.utils.types import NumberType from qcodes.validators import Anything, Bool, Enum, Ints, Numbers if TYPE_CHECKING: @@ -635,7 +635,7 @@ def find_ami430_with_name(ami430_name: str) -> AMI430: self._field_limit: float | Iterable[CartesianFieldLimitFunction] if isinstance(field_limit, collections.abc.Iterable): self._field_limit = field_limit - elif isinstance(field_limit, numbers.Real): + elif isinstance(field_limit, NumberType): # Conversion to float makes related driver logic simpler self._field_limit = float(field_limit) else: @@ -978,7 +978,7 @@ def _raise_if_not_same_field_and_ramp_rate_units(self) -> tuple[str, str]: def _verify_safe_setpoint( self, setpoint_values: tuple[float, float, float] ) -> bool: - if isinstance(self._field_limit, numbers.Real): + if isinstance(self._field_limit, NumberType): return bool(np.linalg.norm(setpoint_values) < self._field_limit) answer = any( diff --git a/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py b/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py index ccff40b68d1..e0394679f9f 100644 --- a/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py +++ b/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -import numbers import time import warnings from collections import defaultdict @@ -24,6 +23,7 @@ from qcodes.math_utils import FieldVector from qcodes.parameters import Parameter from qcodes.utils import QCoDeSDeprecationWarning +from qcodes.utils.types import NumberType from qcodes.validators import Anything, Bool, Enum, Ints, Numbers if TYPE_CHECKING: @@ -730,7 +730,7 @@ def find_ami430_with_name(ami430_name: str) -> AMIModel430: self._field_limit: float | Iterable[CartesianFieldLimitFunction] if isinstance(field_limit, Iterable): self._field_limit = field_limit - elif isinstance(field_limit, numbers.Real): + elif isinstance(field_limit, NumberType): # Conversion to float makes related driver logic simpler self._field_limit = float(field_limit) else: @@ -1073,7 +1073,7 @@ def _raise_if_not_same_field_and_ramp_rate_units(self) -> tuple[str, str]: def _verify_safe_setpoint( self, setpoint_values: tuple[float, float, float] ) -> bool: - if isinstance(self._field_limit, numbers.Real): + if isinstance(self._field_limit, NumberType): return bool(np.linalg.norm(setpoint_values) < self._field_limit) answer = any( @@ -1240,9 +1240,7 @@ def pause(self) -> None: ): axis_instrument.pause() - def _request_field_change( - self, instrument: AMIModel430, value: numbers.Real - ) -> None: + def _request_field_change(self, instrument: AMIModel430, value: NumberType) -> None: """ This method is called by the child x/y/z magnets if they are set individually. It results in additional safety checks being diff --git a/src/qcodes/math_utils/field_vector.py b/src/qcodes/math_utils/field_vector.py index 0de928067bb..bdc3d6306e1 100644 --- a/src/qcodes/math_utils/field_vector.py +++ b/src/qcodes/math_utils/field_vector.py @@ -5,11 +5,12 @@ from __future__ import annotations -import numbers from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeVar import numpy as np +from qcodes.utils.types import NumberType + if TYPE_CHECKING: from collections.abc import Sequence @@ -293,7 +294,7 @@ def __setitem__(self, component: str, value: float) -> None: self.set_component(**{component: value}) def __mul__(self, other: Any) -> FieldVector: - if not isinstance(other, numbers.Real): + if not isinstance(other, NumberType): return NotImplemented return FieldVector( @@ -301,13 +302,13 @@ def __mul__(self, other: Any) -> FieldVector: ) def __rmul__(self, other: Any) -> FieldVector: - if not isinstance(other, numbers.Real): + if not isinstance(other, NumberType): return NotImplemented return self * other def __truediv__(self, other: Any) -> FieldVector: - if not isinstance(other, numbers.Real): + if not isinstance(other, NumberType): return NotImplemented return self * (1.0 / other) diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index adfa5e73ec0..68fd44b44f2 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -2,7 +2,6 @@ import collections.abc import logging -import numbers import time import warnings from contextlib import contextmanager @@ -14,6 +13,7 @@ from qcodes.utils import DelegateAttributes, full_class, qcodes_abstractmethod from qcodes.validators import Enum, Ints, Validator +from ..utils.types import NumberType from .cache import _Cache, _CacheProtocol from .named_repr import named_repr from .permissive_range import permissive_range @@ -810,8 +810,7 @@ def get_ramp_values( self.get() start_value = self.get_latest() if not ( - isinstance(start_value, numbers.Real) - and isinstance(value, numbers.Real) + isinstance(start_value, NumberType) and isinstance(value, NumberType) ): # parameter is numeric but either one of the endpoints # is not or the starting point is unknown. The later @@ -889,7 +888,7 @@ def step(self, step: float | None) -> None: self._step: float | None = step elif not all(getattr(vals, "is_numeric", True) for vals in self._vals): raise TypeError("you can only step numeric parameters") - elif not isinstance(step, numbers.Real): + elif not isinstance(step, NumberType): raise TypeError("step must be a number") elif step == 0: self._step = None @@ -929,7 +928,7 @@ def post_delay(self) -> float: @post_delay.setter def post_delay(self, post_delay: float) -> None: - if not isinstance(post_delay, numbers.Real): + if not isinstance(post_delay, NumberType): raise TypeError(f"post_delay ({post_delay}) must be a number") if post_delay < 0: raise ValueError(f"post_delay ({post_delay}) must not be negative") @@ -958,7 +957,7 @@ def inter_delay(self) -> float: @inter_delay.setter def inter_delay(self, inter_delay: float) -> None: - if not isinstance(inter_delay, numbers.Real): + if not isinstance(inter_delay, NumberType): raise TypeError(f"inter_delay ({inter_delay}) must be a number") if inter_delay < 0: raise ValueError(f"inter_delay ({inter_delay}) must not be negative") diff --git a/src/qcodes/utils/types.py b/src/qcodes/utils/types.py index f15ab97d6e0..bf6a5c96bc8 100644 --- a/src/qcodes/utils/types.py +++ b/src/qcodes/utils/types.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing import TypeAlias + import numpy as np complex_type_union = np.complex64 | np.complex128 | np.complexfloating | complex @@ -82,3 +84,5 @@ concrete_complex_types = (*numpy_concrete_complex, complex) complex_types = (*numpy_concrete_complex, complex) + +NumberType: TypeAlias = int | float | np.integer | np.floating From 4486ff90bae8cba2fee31b783c3fbc44a339157b Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Mon, 27 Jan 2025 18:04:40 +0100 Subject: [PATCH 3/5] Add docstring for NumberType --- src/qcodes/utils/types.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/qcodes/utils/types.py b/src/qcodes/utils/types.py index bf6a5c96bc8..0f0fb0fd5b5 100644 --- a/src/qcodes/utils/types.py +++ b/src/qcodes/utils/types.py @@ -86,3 +86,6 @@ complex_types = (*numpy_concrete_complex, complex) NumberType: TypeAlias = int | float | np.integer | np.floating +""" +Python or NumPy real number. +""" From b6f2b321a5d0fe0888b855babffe25f5b35c5a2e Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Mon, 27 Jan 2025 18:05:24 +0100 Subject: [PATCH 4/5] Cast to float (mypy) --- src/qcodes/math_utils/field_vector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qcodes/math_utils/field_vector.py b/src/qcodes/math_utils/field_vector.py index bdc3d6306e1..593708f95b0 100644 --- a/src/qcodes/math_utils/field_vector.py +++ b/src/qcodes/math_utils/field_vector.py @@ -298,7 +298,7 @@ def __mul__(self, other: Any) -> FieldVector: return NotImplemented return FieldVector( - **{component: self[component] * other for component in "xyz"} + **{component: self[component] * float(other) for component in "xyz"} ) def __rmul__(self, other: Any) -> FieldVector: From 7f3c6ca49e564fb637e9ca3fd09ef0823be6e173 Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Mon, 27 Jan 2025 18:07:16 +0100 Subject: [PATCH 5/5] Allow more general NumberType instead of only float --- src/qcodes/parameters/parameter_base.py | 10 +++++----- src/qcodes/parameters/permissive_range.py | 9 ++++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index 68fd44b44f2..452896f6fb5 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -784,8 +784,8 @@ def set_wrapper(value: ParamDataType, **kwargs: Any) -> None: return set_wrapper def get_ramp_values( - self, value: float | Sized, step: float | None = None - ) -> Sequence[float | Sized]: + self, value: NumberType | Sized, step: NumberType | None = None + ) -> Sequence[NumberType | Sized]: """ Return values to sweep from current value to target value. This method can be overridden to have a custom sweep behaviour. @@ -859,7 +859,7 @@ def validate(self, value: ParamDataType) -> None: validator.validate(value, self._validate_context) @property - def step(self) -> float | None: + def step(self) -> NumberType | None: """ Stepsize that this Parameter uses during set operation. Stepsize must be a positive number or None. @@ -883,9 +883,9 @@ def step(self) -> float | None: return self._step @step.setter - def step(self, step: float | None) -> None: + def step(self, step: NumberType | None) -> None: if step is None: - self._step: float | None = step + self._step: NumberType | None = step elif not all(getattr(vals, "is_numeric", True) for vals in self._vals): raise TypeError("you can only step numeric parameters") elif not isinstance(step, NumberType): diff --git a/src/qcodes/parameters/permissive_range.py b/src/qcodes/parameters/permissive_range.py index 209f4b35dcd..6c102bd9ca6 100644 --- a/src/qcodes/parameters/permissive_range.py +++ b/src/qcodes/parameters/permissive_range.py @@ -1,14 +1,17 @@ from __future__ import annotations import math -from typing import SupportsAbs +from typing import TYPE_CHECKING, SupportsAbs + +if TYPE_CHECKING: + from qcodes.utils.types import NumberType # could use numpy.arange here, but # I'd like to be more flexible with the sign of step def permissive_range( - start: float, stop: float, step: SupportsAbs[float] -) -> list[float]: + start: NumberType, stop: NumberType, step: SupportsAbs[NumberType] +) -> list[NumberType]: """ Returns a range (as a list of values) with floating point steps. Always starts at start and moves toward stop, regardless of the