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
Validators/permissive multiples #852
Changes from 6 commits
4a0e987
339991e
b2a5364
739623d
004387c
8087cc0
3543dcf
cffd096
27da004
ac6df3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
import math | ||
from typing import Union | ||
|
||
import numpy as np | ||
|
||
BIGSTRING = 1000000000 | ||
|
@@ -217,6 +219,7 @@ def __repr__(self): | |
maxv = self._max_value if self._max_value < BIGINT else None | ||
return '<Ints{}>'.format(range_str(minv, maxv, 'v')) | ||
|
||
|
||
class PermissiveInts(Ints): | ||
""" | ||
requires an integer or a float close to an integer | ||
|
@@ -280,10 +283,11 @@ def validate(self, value, context=''): | |
|
||
class Multiples(Ints): | ||
""" | ||
A validator that checks if a value is an integer multiple of a fixed devisor | ||
This class extends validators.Ints such that the value is also checked for | ||
being integer between an optional min_value and max_value. Furthermore this | ||
validator checks that the value is an integer multiple of an fixed, integer | ||
A validator that checks if a value is an integer multiple of a | ||
fixed divisor. This class extends validators.Ints such that the | ||
value is also checked for being integer between an optional | ||
min_value and max_value. Furthermore this validator checks that | ||
the value is an integer multiple of an fixed, integer | ||
divisor. (i.e. value % divisor == 0) | ||
Args: | ||
divisor (integer), the value need the be a multiple of this divisor | ||
|
@@ -307,6 +311,64 @@ def validate(self, value, context=''): | |
def __repr__(self): | ||
return super().__repr__()[:-1] + ', Multiples of {}>'.format(self._divisor) | ||
|
||
is_numeric = True | ||
|
||
|
||
class PermissiveMultiples(Validator): | ||
""" | ||
A validator that checks whether a value is an integer multiple | ||
of a fixed divisor (to within some precision). If both value and | ||
divisor are integers, the (exact) Multiples validator is used. | ||
|
||
We also allow negative values, meaning that zero by construction is | ||
always a valid value. | ||
|
||
Args: | ||
divisor: The number that the validated value should be an integer | ||
multiple of. | ||
precision: The maximally allowed absolute error between the value and | ||
the nearest true multiple | ||
""" | ||
|
||
def __init__(self, divisor: Union[float, int, np.floating], | ||
precision: float=1e-9) -> None: | ||
if divisor == 0: | ||
raise ValueError('Can not meaningfully check for multiples of' | ||
' zero.') | ||
self.divisor = divisor | ||
self.precision = precision | ||
self._numval = Numbers() | ||
if isinstance(divisor, int): | ||
self._mulval = Multiples(divisor=abs(divisor)) | ||
else: | ||
self._mulval = None | ||
|
||
def validate(self, value: Union[float, int, np.floating], | ||
context: str='') -> None: | ||
""" | ||
Validate the given value. Note that this validator does not use | ||
context for anything. | ||
""" | ||
self._numval.validate(value) | ||
# if zero, it passes by definition | ||
if value == 0: | ||
return | ||
if self._mulval and isinstance(value, int): | ||
self._mulval.validate(abs(value)) | ||
else: | ||
# floating-point division cannot be trusted, so we try to | ||
# multiply our way out of the problem by constructing true | ||
# multiples in the relevant range and see if `value` is one | ||
# of them (within rounding errors) | ||
divs = int(divmod(value, self.divisor)[0]) | ||
true_vals = np.array([n*self.divisor for n in range(divs, divs+2)]) | ||
abs_errs = [abs(tv-value) for tv in true_vals] | ||
if min(abs_errs) > self.precision: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this have a relative tolerance too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mimicking There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes as long as people are not using "strange" units its fine, we can always add later if needed |
||
raise ValueError('{} is not a multiple'.format(value) + | ||
' of {}.'.format(self.divisor)) | ||
|
||
is_numeric = True | ||
|
||
|
||
class MultiType(Validator): | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woops... nice catch.