From 73f153672b86e8af34b25dcfc15268408fdeb233 Mon Sep 17 00:00:00 2001 From: Adriaan Rol Date: Thu, 22 Sep 2016 16:50:50 +0200 Subject: [PATCH 1/2] Array type validator Checks for shape, min val and max val and ensures it is a numpy array --- qcodes/tests/test_validators.py | 52 ++++++++++++++++++++++++--- qcodes/utils/validators.py | 62 +++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 8 deletions(-) diff --git a/qcodes/tests/test_validators.py b/qcodes/tests/test_validators.py index ac3ccd440efb..4aaa55890298 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, MultiType) + Numbers, Ints, Enum, MultiType, + Arrays) 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', @@ -402,3 +403,44 @@ 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.random.rand(2, 2) + m.validate(v) + v = 100*v + with self.assertRaises(ValueError): + m.validate(v) + v = -1*v + with self.assertRaises(ValueError): + m.validate(v) + + v = np.random.rand(3, 2) + with self.assertRaises(ValueError): + m.validate(v) + + def test_shape(self): + m = Arrays(min_value=-5, max_value=50, shape=(2, 2)) + v = np.random.rand(2, 2) + m.validate(v) + v = np.random.rand(3, 2) + with self.assertRaises(ValueError): + m.validate(v) + + # should pass if no shape specified + m = Arrays(min_value=-5, max_value=50) + v = np.random.rand(2, 2) + m.validate(v) + v = np.random.rand(3, 2) + m.validate(v) + + + diff --git a/qcodes/utils/validators.py b/qcodes/utils/validators.py index e17cad51b7e6..e87638108d6d 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): @@ -294,3 +294,59 @@ 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 != None: + if (np.shape(value) != self._shape): + raise ValueError( + '{} does not have expected shape {}; {}'.format( + repr(value), self._shape, context)) + + if not (self._min_value <= np.min(value) and + 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)) + + 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) + From 90406fc8cdc1d3f90d6470aba3ce83ef03b431c0 Mon Sep 17 00:00:00 2001 From: Adriaan Rol Date: Fri, 23 Sep 2016 10:31:20 +0200 Subject: [PATCH 2/2] Changes per review request of Alex No more random values in tests Only checks values if max and min are not infinte as it can be expensive --- qcodes/tests/test_validators.py | 18 ++++++------------ qcodes/utils/validators.py | 29 ++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/qcodes/tests/test_validators.py b/qcodes/tests/test_validators.py index 4aaa55890298..22200ad81e27 100644 --- a/qcodes/tests/test_validators.py +++ b/qcodes/tests/test_validators.py @@ -414,7 +414,7 @@ def test_type(self): def test_min_max(self): m = Arrays(min_value=-5, max_value=50, shape=(2, 2)) - v = np.random.rand(2, 2) + v = np.array([[2, 0], [1, 2]]) m.validate(v) v = 100*v with self.assertRaises(ValueError): @@ -423,24 +423,18 @@ def test_min_max(self): with self.assertRaises(ValueError): m.validate(v) - v = np.random.rand(3, 2) - 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.random.rand(2, 2) - m.validate(v) - v = np.random.rand(3, 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) - v = np.random.rand(2, 2) m.validate(v) - v = np.random.rand(3, 2) + v = np.array([[2, 0], [1, 2]]) m.validate(v) - - - diff --git a/qcodes/utils/validators.py b/qcodes/utils/validators.py index e87638108d6d..731725d27482 100644 --- a/qcodes/utils/validators.py +++ b/qcodes/utils/validators.py @@ -330,23 +330,34 @@ def validate(self, value, context=''): if value.dtype not in self.validtypes: raise TypeError( '{} is not an int or float; {}'.format(repr(value), context)) - if self._shape != None: + 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)) - if not (self._min_value <= np.min(value) and - 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 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) - + return ''.format(range_str(minv, maxv, 'v'), + self._shape)