Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
48 changes: 42 additions & 6 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, Multiples, MultiType)
Numbers, Ints, Enum, MultiType,
Arrays, Multiples)


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 @@ -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',
Expand Down Expand Up @@ -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)
73 changes: 70 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 @@ -324,3 +324,70 @@ 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 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 '<Arrays{}, shape: {}>'.format(range_str(minv, maxv, 'v'),
self._shape)