"""Numerical utilities functions."""
from numbers import Number
import lena.core
[docs]def clip(a, interval):
"""Clip (limit) the value.
Given an interval *(a_min, a_max)*,
values of *a* outside the interval are clipped to the interval edges.
For example, if an interval of *[0, 1]* is specified,
values smaller than 0 become 0, and values larger than 1 become 1.
>>> clip(-1, (0, 1))
0
>>> # tuple looks better, but list can be used too
>>> clip(2, [0, 1])
1
>>> clip(0.5, (0, 1))
0.5
If *a_min* > *a_max* or if *interval* has length more than 2,
:exc:`.LenaValueError` is raised.
If *interval* is not a container,
:exc:`.LenaTypeError` is raised.
"""
# Inspired by
# https://docs.scipy.org/doc/numpy/reference/generated/numpy.clip.html
# https://stackoverflow.com/a/9775761/952234
try:
l = len(interval)
except TypeError:
raise lena.core.LenaTypeError("interval must be a container of size 2.")
if l != 2:
raise lena.core.LenaValueError("interval must be a container of size 2.")
a_min = interval[0]
a_max = interval[1]
if a_min > a_max:
raise lena.core.LenaValueError(
"interval must be increasing, "
"({}, {}) provided.".format(a_min, a_max)
)
return max(min(a_max, a), a_min)
def _isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
# https://docs.python.org/3/whatsnew/3.5.html#pep-485-a-function-for-testing-approximate-equality
# https://stackoverflow.com/a/33024979/952234
return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
[docs]def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
"""Return ``True`` if *a* and *b* are approximately equal,
and ``False`` otherwise.
*rel_tol* is the relative tolerance.
It is multiplied by the greater of the magnitudes
of the two arguments;
as the values get larger,
so does the allowed difference between them
while still considering them close.
*abs_tol* is the absolute tolerance.
If the difference is less than either of those tolerances,
the values are considered equal.
*a* and *b* must be either numbers
or lists/tuples of same dimensions (may be nested),
or have a method *isclose*.
Otherwise :exc:`.LenaTypeError` is raised.
For containers, *isclose* is called elementwise.
If every corresponding element is close, the containers are close.
Dimensions are not checked to be equal.
First, *a* and *b* are checked if any of them has *isclose* method.
If *a* and *b* both have *isclose* method,
then they must both return ``True`` to be close.
Otherwise, if only one of *a* or *b* has *isclose* method,
it is called.
Special values of ``NaN``, ``inf``, and ``-inf`` are not supported.
>>> isclose(1, 2)
False
>>> isclose([1, 2, 3], (1, 2., 3))
True
This function for scalar numbers
appeared in ``math`` module in *Python 3.5*.
"""
if hasattr(a, 'isclose') and hasattr(b, 'isclose'):
# isclose should be reflexive
return a.isclose(b, rel_tol, abs_tol) \
and b.isclose(a, rel_tol, abs_tol)
elif hasattr(a, 'isclose'):
return a.isclose(b, rel_tol, abs_tol)
elif hasattr(b, 'isclose'):
return b.isclose(a, rel_tol, abs_tol)
elif isinstance(a, Number):
return _isclose(a, b, rel_tol, abs_tol)
elif isinstance(a, (list, tuple)):
for ind, el in enumerate(a):
if not isclose(el, b[ind], rel_tol, abs_tol):
return False
return True
else:
raise lena.core.LenaTypeError("isclose doesn't support {} and {}".format(a, b))