diff --git a/qcodes/tests/test_validators.py b/qcodes/tests/test_validators.py index 19b2e52dfe4e..d8bb90968042 100644 --- a/qcodes/tests/test_validators.py +++ b/qcodes/tests/test_validators.py @@ -1,9 +1,10 @@ from unittest import TestCase import math -import numpy +import numpy as np from qcodes.utils.validators import (Validator, Anything, Bool, Strings, - Numbers, Ints, Enum, Multiples, MultiType) + Numbers, Ints, Enum, MultiType, + Arrays, Multiples) class AClass: @@ -177,7 +178,7 @@ class TestNumbers(TestCase): # warning: +/- inf are allowed if max & min are not specified! -float("inf"), float("inf"), # numpy scalars - numpy.int64(36), numpy.float32(-1.123) + np.int64(36), np.float32(-1.123) ] not_numbers = ['', None, '1', [], {}, [1, 2], {1: 1}, b'good', AClass, AClass(), a_func] @@ -269,8 +270,8 @@ class TestInts(TestCase): # warning: True==1 and False==0 - we *could* prohibit these, using # isinstance(v, bool) True, False, - # numpy scalars - numpy.int64(3)] + # np scalars + np.int64(3)] not_ints = [0.1, -0.1, 1.0, 3.5, -2.3e6, 5.5e15, 1.34e-10, -2.5e-5, math.pi, math.e, '', None, float("nan"), float("inf"), -float("inf"), '1', [], {}, [1, 2], {1: 1}, b'good', @@ -390,7 +391,7 @@ class TestMultiples(TestCase): # isinstance(v, bool) True, False, # numpy scalars - numpy.int64(2)] + np.int64(2)] not_multiples = [0.1, -0.1, 1.0, 3.5, -2.3e6, 5.5e15, 1.34e-10, -2.5e-5, math.pi, math.e, '', None, float("nan"), float("inf"), -float("inf"), '1', [], {}, [1, 2], {1: 1}, b'good', @@ -444,3 +445,38 @@ def test_bad(self): for args in [[], [1], [Strings(), True]]: with self.assertRaises(TypeError): MultiType(*args) + + +class TestArrays(TestCase): + def test_type(self): + m = Arrays(min_value=0.0, max_value=3.2, shape=(2, 2)) + for v in ['somestring', 4, 2, [[2, 0], [1, 2]]]: + with self.assertRaises(TypeError): + m.validate(v) + + def test_min_max(self): + m = Arrays(min_value=-5, max_value=50, shape=(2, 2)) + v = np.array([[2, 0], [1, 2]]) + m.validate(v) + v = 100*v + with self.assertRaises(ValueError): + m.validate(v) + v = -1*v + with self.assertRaises(ValueError): + m.validate(v) + + m = Arrays(min_value=-5, shape=(2, 2)) + v = np.array([[2, 0], [1, 2]]) + m.validate(v*100) + + def test_shape(self): + m = Arrays(min_value=-5, max_value=50, shape=(2, 2)) + v = np.array([[2, 0], [1, 2], [2, 3]]) + with self.assertRaises(ValueError): + m.validate(v) + + # should pass if no shape specified + m = Arrays(min_value=-5, max_value=50) + m.validate(v) + v = np.array([[2, 0], [1, 2]]) + m.validate(v) diff --git a/qcodes/utils/validators.py b/qcodes/utils/validators.py index b0b393c87838..80405a2c06a5 100644 --- a/qcodes/utils/validators.py +++ b/qcodes/utils/validators.py @@ -1,5 +1,5 @@ import math -import numpy +import numpy as np BIGSTRING = 1000000000 BIGINT = int(1e18) @@ -140,7 +140,7 @@ class Numbers(Validator): - fix raises """ - validtypes = (float, int, numpy.integer, numpy.floating) + validtypes = (float, int, np.integer, np.floating) def __init__(self, min_value=-float("inf"), max_value=float("inf")): @@ -180,7 +180,7 @@ class Ints(Validator): min_value <= value <= max_value """ - validtypes = (int, numpy.integer) + validtypes = (int, np.integer) def __init__(self, min_value=-BIGINT, max_value=BIGINT): if isinstance(min_value, self.validtypes): @@ -324,3 +324,70 @@ def validate(self, value, context=''): def __repr__(self): parts = (repr(v)[1:-1] for v in self._validators) return ''.format(', '.join(parts)) + + +class Arrays(Validator): + """ + Validator for numerical numpy arrays + Args: + min_value (Optional[Union[float, int]): Min value allowed, default inf + max_value: (Optional[Union[float, int]): Max value allowed, default inf + shape: (Optional): None + """ + + validtypes = (int, float, np.integer, np.floating) + + def __init__(self, min_value=-float("inf"), max_value=float("inf"), + shape=None): + + if isinstance(min_value, self.validtypes): + self._min_value = min_value + else: + raise TypeError('min_value must be a number') + + if isinstance(max_value, self.validtypes) and max_value > min_value: + self._max_value = max_value + else: + raise TypeError('max_value must be a number bigger than min_value') + self._shape = shape + + def validate(self, value, context=''): + + if not isinstance(value, np.ndarray): + raise TypeError( + '{} is not a numpy array; {}'.format(repr(value), context)) + + if value.dtype not in self.validtypes: + raise TypeError( + '{} is not an int or float; {}'.format(repr(value), context)) + if self._shape is not None: + if (np.shape(value) != self._shape): + raise ValueError( + '{} does not have expected shape {}; {}'.format( + repr(value), self._shape, context)) + + # Only check if max is not inf as it can be expensive for large arrays + if self._max_value != (float("inf")): + if not (np.max(value) <= self._max_value): + raise ValueError( + '{} is invalid: all values must be between ' + '{} and {} inclusive; {}'.format( + repr(value), self._min_value, + self._max_value, context)) + + # Only check if min is not -inf as it can be expensive for large arrays + if self._min_value != (-float("inf")): + if not (self._min_value <= np.min(value)): + raise ValueError( + '{} is invalid: all values must be between ' + '{} and {} inclusive; {}'.format( + repr(value), self._min_value, + self._max_value, context)) + + is_numeric = True + + def __repr__(self): + minv = self._min_value if math.isfinite(self._min_value) else None + maxv = self._max_value if math.isfinite(self._max_value) else None + return ''.format(range_str(minv, maxv, 'v'), + self._shape)