Testing
Utility functions for evotorch-related unit testing.
TestingError (Exception)
¶
assert_allclose(actual, desired, *, rtol=None, atol=None, equal_nan=True)
¶
This function is similar to numpy.testing.assert_allclose(...)
except
that atol
and rtol
are keyword-only arguments (which encourages
one to be more explicit when writing tests) and that the default dtype
is "float32" when the provided arguments are neither numpy arrays nor
torch tensors. Having "float32" as the default target dtype is a behavior
that is compatible with PyTorch.
This function first casts actual
into the dtype of desired
, then
uses numpy's assert_allclose(...)
for testing the closeness of the
values.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
actual |
Iterable |
An iterable of numbers. |
required |
desired |
Iterable |
An iterable of numbers. These numbers represent the values
that we expect the |
required |
rtol |
Optional[float] |
Relative tolerance.
Can be left as None if only |
None |
atol |
Optional[float] |
Absolute tolerance.
Can be left as None if only |
None |
equal_nan |
bool |
If True, |
True |
Exceptions:
Type | Description |
---|---|
AssertionError |
if the numerical difference between |
TestingError |
If both |
Source code in evotorch/testing.py
def assert_allclose(
actual: Iterable,
desired: Iterable,
*,
rtol: Optional[float] = None,
atol: Optional[float] = None,
equal_nan: bool = True,
):
"""
This function is similar to `numpy.testing.assert_allclose(...)` except
that `atol` and `rtol` are keyword-only arguments (which encourages
one to be more explicit when writing tests) and that the default dtype
is "float32" when the provided arguments are neither numpy arrays nor
torch tensors. Having "float32" as the default target dtype is a behavior
that is compatible with PyTorch.
This function first casts `actual` into the dtype of `desired`, then
uses numpy's `assert_allclose(...)` for testing the closeness of the
values.
Args:
actual: An iterable of numbers.
desired: An iterable of numbers. These numbers represent the values
that we expect the `actual` to contain. If the numbers contained
by `actual` are significantly different than `desired`, the
assertion will fail.
rtol: Relative tolerance.
Can be left as None if only `atol` is to be used.
See the documentation of `numpy.testing.assert_allclose(...)`
for details about how `rtol` affects the tolerance.
atol: Absolute tolerance.
Can be left as None if only `rtol` is to be used.
See the documentation of `numpy.testing.assert_allclose(...)`
for details about how `atol` affects the tolerance.
equal_nan: If True, `nan` values will be counted as equal.
Raises:
AssertionError: if the numerical difference between `actual`
and `desired` are beyond the tolerance expressed by `atol`
and `rtol`.
TestingError: If both `rtol` and `atol` are given as None.
"""
if rtol is None and atol is None:
raise TestingError(
"Both `rtol` and `atol` were found to be None. Please either specify `rtol`, `atol`, or both."
)
elif rtol is None:
rtol = 0.0
elif atol is None:
atol = 0.0
desired = _to_numpy(desired)
actual = _to_numpy(actual, dtype=desired.dtype)
np.testing.assert_allclose(actual, desired, rtol=rtol, atol=atol, equal_nan=bool(equal_nan))
assert_almost_between(x, lb, ub, *, atol=None)
¶
Assert that the given Iterable has its values between the desired bounds.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
Iterable |
An Iterable containing numeric (float) values. |
required |
lb |
Union[float, Iterable] |
Lower bound for the desired interval. Can be a scalar or an iterable of values. |
required |
ub |
Union[float, Iterable] |
Upper bound for the desired interval. Can be a scalar or an iterable of values. |
required |
atol |
Optional[float] |
Absolute tolerance. If given, then the effective interval will
be |
None |
Exceptions:
Type | Description |
---|---|
AssertionError |
if any element of |
Source code in evotorch/testing.py
def assert_almost_between(
x: Iterable, lb: Union[float, Iterable], ub: Union[float, Iterable], *, atol: Optional[float] = None
):
"""
Assert that the given Iterable has its values between the desired bounds.
Args:
x: An Iterable containing numeric (float) values.
lb: Lower bound for the desired interval.
Can be a scalar or an iterable of values.
ub: Upper bound for the desired interval.
Can be a scalar or an iterable of values.
atol: Absolute tolerance. If given, then the effective interval will
be `[lb-atol; ub+atol]` instead of `[lb; ub]`.
Raises:
AssertionError: if any element of `x` violates the boundaries.
"""
x = _to_numpy(x)
lb = _to_numpy(lb)
ub = _to_numpy(ub)
if lb.shape != x.shape:
lb = np.broadcast_to(lb, x.shape)
if ub.shape != x.shape:
ub = np.broadcast_to(ub, x.shape)
lb = np.asarray(lb, dtype=x.dtype)
ub = np.asarray(ub, dtype=x.dtype)
if atol is not None:
atol = float(atol)
tolerant_lb = lb - atol
tolerant_ub = ub + atol
else:
tolerant_lb = lb
tolerant_ub = ub
assert np.all((x >= tolerant_lb) & (x <= tolerant_ub)), (
f"The provided array is not within the desired boundaries."
f"Provided array: {x}. Lower bound: {lb}. Upper bound: {ub}. Absolute tolerance: {atol}."
)
assert_dtype_matches(x, dtype)
¶
Assert that the dtype of x
is compatible with the given dtype
.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
Iterable |
An object with |
required |
dtype |
Union[str, Type, numpy.dtype, torch.dtype] |
The dtype which |
required |
Exceptions:
Type | Description |
---|---|
AssertionError |
if |
Source code in evotorch/testing.py
def assert_dtype_matches(x: Iterable, dtype: Union[str, Type, np.dtype, torch.dtype]):
"""
Assert that the dtype of `x` is compatible with the given `dtype`.
Args:
x: An object with `dtype` attribute (e.g. can be numpy array,
a torch tensor, an ObjectArray, a Solution, etc.)
dtype: The dtype which `x` is expected to have.
Can be given as a string, as a numpy dtype, as a torch dtype,
or as a native type (e.g. int, float, bool, object).
Raises:
AssertionError: if `x` has a different dtype.
"""
actual_dtype = x.dtype
if isinstance(actual_dtype, torch.dtype):
actual_dtype = torch.tensor([], dtype=actual_dtype).numpy().dtype
else:
actual_dtype = np.dtype(actual_dtype)
if dtype == "Any" or dtype is Any:
dtype = np.dtype(object)
elif isinstance(dtype, torch.dtype):
dtype = torch.tensor([], dtype=dtype).numpy().dtype
else:
dtype = np.dtype(dtype)
assert dtype == actual_dtype, f"dtype mismatch. Encountered dtype: {actual_dtype}, expected dtype: {dtype}"
assert_eachclose(x, value, *, rtol=None, atol=None)
¶
Assert that the given tensor or array consists of a single value.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
Iterable |
The tensor in which each value will be compared against |
required |
value |
Any |
A scalar |
required |
Exceptions:
Type | Description |
---|---|
AssertionError |
if at least one value is different enough |
Source code in evotorch/testing.py
def assert_eachclose(x: Iterable, value: Any, *, rtol: Optional[float] = None, atol: Optional[float] = None):
"""
Assert that the given tensor or array consists of a single value.
Args:
x: The tensor in which each value will be compared against `value`
value: A scalar
Raises:
AssertionError: if at least one value is different enough
"""
# If the given scalar is not a Real, then try to cast it to float
if not isinstance(value, Real):
value = float(value)
x = _to_numpy(x)
desired = np.empty_like(x)
desired[:] = value
assert_allclose(x, desired, rtol=rtol, atol=atol)
assert_shape_matches(x, shape)
¶
Assert that the dtype of x
matches the given shape
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
Iterable |
An object which can be converted to a PyTorch tensor. |
required |
shape |
Union[tuple, int] |
A tuple, or a torch.Size, or an integer. |
required |
Exceptions:
Type | Description |
---|---|
AssertionError |
if there is a shape mismatch. |
Source code in evotorch/testing.py
def assert_shape_matches(x: Iterable, shape: Union[tuple, int]):
"""
Assert that the dtype of `x` matches the given shape
Args:
x: An object which can be converted to a PyTorch tensor.
shape: A tuple, or a torch.Size, or an integer.
Raises:
AssertionError: if there is a shape mismatch.
"""
if isinstance(x, torch.Tensor):
pass # nothing to do
elif isinstance(x, np.ndarray):
x = torch.from_numpy(x)
else:
x = torch.tensor(x)
if not isinstance(shape, Iterable):
shape = (int(shape),)
assert x.shape == shape, f"Encountered a shape mismatch. Shape of the tensor: {x.shape}. Expected shape: {shape}"