Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Array type validator #342

Merged
merged 3 commits into from
Sep 23, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 47 additions & 5 deletions qcodes/tests/test_validators.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use random in tests - make an explicit array.

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)



62 changes: 59 additions & 3 deletions qcodes/utils/validators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import math
import numpy
import numpy as np

BIGSTRING = 1000000000
BIGINT = int(1e18)
Expand Down Expand Up @@ -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")):

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -294,3 +294,59 @@ def validate(self, value, context=''):
def __repr__(self):
parts = (repr(v)[1:-1] for v in self._validators)
return '<MultiType: {}>'.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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe only check min and/or max if it's not the default (inf) values? This can be expensive for large arrays.

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 '<Arrays{}, shape: {}>'.format(range_str(minv, maxv, 'v'), self._shape)