From e0548af1a3b6e820d6ec59a41573410fa4f68896 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Thu, 16 Sep 2021 16:41:19 +0100 Subject: [PATCH 01/30] Pin Hypothesis to >=6.21.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f22f1570..ab976664 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ pytest -hypothesis +hypothesis>=6.21.0 regex removestar From 6066c5a1a37c7fd85dd9de1d633cb4165102d083 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Thu, 16 Sep 2021 17:20:12 +0100 Subject: [PATCH 02/30] Rudimentary test_full_like() --- array_api_tests/hypothesis_helpers.py | 5 +++ array_api_tests/test_creation_functions.py | 36 ++++++++++++++++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index f0983b0f..e0726288 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -6,6 +6,7 @@ shared, floats, just, composite, one_of, none, booleans) from hypothesis.extra.numpy import mutually_broadcastable_shapes +from hypothesis.extra.array_api import make_strategies_namespace from hypothesis import assume from .pytest_helpers import nargs @@ -15,10 +16,14 @@ integer_or_boolean_dtype_objects, dtype_objects) from ._array_module import full, float32, float64, bool as bool_dtype, _UndefinedStub from . import _array_module +from ._array_module import mod as xp from .function_stubs import elementwise_functions +xps = make_strategies_namespace(xp) + + # Set this to True to not fail tests just because a dtype isn't implemented. # If no compatible dtype is implemented for a given test, the test will fail # with a hypothesis health check error. Note that this functionality will not diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 5bd2b35b..d84c1519 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -1,13 +1,13 @@ -from ._array_module import (asarray, arange, ceil, empty, eye, full, -equal, all, linspace, ones, zeros, isnan) +from ._array_module import (asarray, arange, ceil, empty, eye, full, full_like, + equal, all, linspace, ones, zeros, isnan) from .array_helpers import (is_integer_dtype, dtype_ranges, assert_exactly_equal, isintegral, is_float_dtype) from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, shapes, sizes, sqrt_sizes, shared_dtypes, - scalars) + scalars, xps) from hypothesis import assume, given -from hypothesis.strategies import integers, floats, one_of, none, booleans, just +from hypothesis.strategies import integers, floats, one_of, none, booleans, just, shared int_range = integers(-MAX_ARRAY_SIZE, MAX_ARRAY_SIZE) float_range = floats(-MAX_ARRAY_SIZE, MAX_ARRAY_SIZE, @@ -131,8 +131,32 @@ def test_full(shape, fill_value, dtype): assert all(equal(a, asarray(fill_value, **kwargs))), "full() array did not equal the fill value" # TODO: implement full_like (requires hypothesis arrays support) -def test_full_like(): - pass +@given( + a=xps.arrays( + dtype=shared(xps.scalar_dtypes(), key='dtypes'), + shape=xps.array_shapes(), + ), + fill_value=shared(xps.scalar_dtypes(), key='dtypes').flatmap(xps.from_dtype), + kwargs=one_of( + just({}), + shared(xps.scalar_dtypes(), key='dtypes').map(lambda d: {'dtype': d}), + ), +) +def test_full_like(a, fill_value, kwargs): + a_like = full_like(a, fill_value, **kwargs) + + if kwargs is None: + pass # TODO: Should it actually match the fill_value? + else: + assert a_like.dtype == a.dtype + + assert a_like.shape == a.shape, "full_like() produced an array with incorrect shape" + + if is_float_dtype(a_like.dtype) and isnan(asarray(fill_value)): + assert all(isnan(a_like)), "full_like() array did not equal the fill value" + else: + assert all(equal(a_like, asarray(fill_value, dtype=a.dtype))), "full_like() array did not equal the fill value" + @given(scalars(shared_dtypes, finite=True), scalars(shared_dtypes, finite=True), From db3370eb692a3b27378b9b937ac4f23c596d0456 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Fri, 17 Sep 2021 10:45:36 +0100 Subject: [PATCH 03/30] Paired nd arrays for test_equal() --- array_api_tests/test_creation_functions.py | 1 - array_api_tests/test_elementwise_functions.py | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index d84c1519..3ca79fa6 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -130,7 +130,6 @@ def test_full(shape, fill_value, dtype): else: assert all(equal(a, asarray(fill_value, **kwargs))), "full() array did not equal the fill value" -# TODO: implement full_like (requires hypothesis arrays support) @given( a=xps.arrays( dtype=shared(xps.scalar_dtypes(), key='dtypes'), diff --git a/array_api_tests/test_elementwise_functions.py b/array_api_tests/test_elementwise_functions.py index a0afc040..00277514 100644 --- a/array_api_tests/test_elementwise_functions.py +++ b/array_api_tests/test_elementwise_functions.py @@ -15,7 +15,7 @@ """ from hypothesis import given, assume -from hypothesis.strategies import composite, just +from hypothesis.strategies import composite, just, shared import math @@ -26,7 +26,7 @@ boolean_dtype_objects, floating_dtypes, numeric_dtypes, integer_or_boolean_dtypes, boolean_dtypes, mutually_promotable_dtypes, - array_scalars) + array_scalars, xps) from .array_helpers import (assert_exactly_equal, negative, positive_mathematical_sign, negative_mathematical_sign, logical_not, @@ -374,9 +374,17 @@ def test_divide(args): # could test that this does implement IEEE 754 division, but we don't yet # have those sorts in general for this module. -@given(two_any_dtypes.flatmap(lambda i: two_array_scalars(*i))) -def test_equal(args): - x1, x2 = args + + +@given( + x1=shared( + xps.arrays(dtype=xps.scalar_dtypes(), shape=xps.array_shapes()), key='arrays' + ), + x2=shared( + xps.arrays(dtype=xps.scalar_dtypes(), shape=xps.array_shapes()), key='arrays' + ), +) +def test_equal(x1, x2): sanity_check(x1, x2) a = _array_module.equal(x1, x2) # NOTE: assert_exactly_equal() itself uses equal(), so we must be careful From f8d784d0510ab0f69b478cb104b1e45084a7ec60 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Fri, 17 Sep 2021 11:43:43 +0100 Subject: [PATCH 04/30] Rudimentary empty/ones/zeros-like tests --- array_api_tests/test_creation_functions.py | 100 ++++++++++++++++++--- 1 file changed, 88 insertions(+), 12 deletions(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 3ca79fa6..1c896a0a 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -1,5 +1,6 @@ -from ._array_module import (asarray, arange, ceil, empty, eye, full, full_like, - equal, all, linspace, ones, zeros, isnan) +from ._array_module import (asarray, arange, ceil, empty, empty_like, eye, full, + full_like, equal, all, linspace, ones, ones_like, + zeros, zeros_like, isnan) from .array_helpers import (is_integer_dtype, dtype_ranges, assert_exactly_equal, isintegral, is_float_dtype) from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, @@ -77,9 +78,29 @@ def test_empty(shape, dtype): shape = (shape,) assert a.shape == shape, "empty() produced an array with an incorrect shape" -# TODO: implement empty_like (requires hypothesis arrays support) -def test_empty_like(): - pass + +@given( + a=xps.arrays( + dtype=shared(xps.scalar_dtypes(), key='dtypes'), + shape=xps.array_shapes(), + ), + kwargs=one_of( + just({}), + shared(xps.scalar_dtypes(), key='dtypes').map(lambda d: {'dtype': d}), + ), +) +def test_empty_like(a, kwargs): + a_like = empty_like(a, **kwargs) + + if kwargs is None: + # TODO: Should it actually match a.dtype? + # assert is_float_dtype(a_like.dtype), "empty_like() should produce an array with the default floating point dtype" + pass + else: + assert a_like.dtype == a.dtype, "empty_like() produced an array with an incorrect dtype" + + assert a_like.shape == a.shape, "empty_like() produced an array with an incorrect shape" + # TODO: Use this method for all optional arguments optional_marker = object() @@ -145,7 +166,8 @@ def test_full_like(a, fill_value, kwargs): a_like = full_like(a, fill_value, **kwargs) if kwargs is None: - pass # TODO: Should it actually match the fill_value? + # TODO: Should it actually match a.dtype? + pass else: assert a_like.dtype == a.dtype @@ -222,9 +244,36 @@ def test_ones(shape, dtype): assert a.shape == shape, "ones() produced an array with incorrect shape" assert all(equal(a, full((), ONE, **kwargs))), "ones() array did not equal 1" -# TODO: implement ones_like (requires hypothesis arrays support) -def test_ones_like(): - pass + +@given( + a=xps.arrays( + dtype=shared(xps.scalar_dtypes(), key='dtypes'), + shape=xps.array_shapes(), + ), + kwargs=one_of( + just({}), + shared(xps.scalar_dtypes(), key='dtypes').map(lambda d: {'dtype': d}), + ), +) +def test_ones_like(a, kwargs): + if kwargs is None or is_float_dtype(a.dtype): + ONE = 1.0 + elif is_integer_dtype(a.dtype): + ONE = 1 + else: + ONE = True + + a_like = ones_like(a, **kwargs) + + if kwargs is None: + # TODO: Should it actually match a.dtype? + pass + else: + assert a_like.dtype == a.dtype, "ones_like() produced an array with an incorrect dtype" + + assert a_like.shape == a.shape, "ones_like() produced an array with an incorrect shape" + assert all(equal(a_like, full((), ONE, dtype=a_like.dtype))), "ones_like() array did not equal 1" + @given(shapes, one_of(none(), dtypes)) def test_zeros(shape, dtype): @@ -248,6 +297,33 @@ def test_zeros(shape, dtype): assert a.shape == shape, "zeros() produced an array with incorrect shape" assert all(equal(a, full((), ZERO, **kwargs))), "zeros() array did not equal 0" -# TODO: implement zeros_like (requires hypothesis arrays support) -def test_zeros_like(): - pass + +@given( + a=xps.arrays( + dtype=shared(xps.scalar_dtypes(), key='dtypes'), + shape=xps.array_shapes(), + ), + kwargs=one_of( + just({}), + shared(xps.scalar_dtypes(), key='dtypes').map(lambda d: {'dtype': d}), + ), +) +def test_zeros_like(a, kwargs): + if kwargs is None or is_float_dtype(a.dtype): + ZERO = 0.0 + elif is_integer_dtype(a.dtype): + ZERO = 0 + else: + ZERO = False + + a_like = zeros_like(a, **kwargs) + + if kwargs is None: + # TODO: Should it actually match a.dtype? + pass + else: + assert a_like.dtype == a.dtype, "zeros_like() produced an array with an incorrect dtype" + + assert a_like.shape == a.shape, "zeros_like() produced an array with an incorrect shape" + assert all(equal(a_like, full((), ZERO, dtype=a_like.dtype))), "zeros_like() array did not equal 0" + From a6e72cf8fd38a7db2f4f9943329c0666157399c4 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Fri, 17 Sep 2021 12:30:27 +0100 Subject: [PATCH 05/30] Make numeric_arrays multi-dimensional --- array_api_tests/hypothesis_helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index e0726288..610685be 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -130,7 +130,10 @@ def two_broadcastable_shapes(draw, shapes=shapes): sqrt_sizes = integers(0, SQRT_MAX_ARRAY_SIZE) # TODO: Generate general arrays here, rather than just scalars. -numeric_arrays = builds(full, just((1,)), floats()) +numeric_arrays = xps.arrays( + dtype=shared(xps.floating_dtypes(), key='dtypes'), + shape=shared(xps.array_shapes(), key='shapes'), +) @composite def scalars(draw, dtypes, finite=False): From 6be73aabf4b79e8f332a16d545bdeaa137a66c32 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Fri, 17 Sep 2021 14:43:08 +0100 Subject: [PATCH 06/30] Make new tests more in-line with established style --- array_api_tests/test_creation_functions.py | 51 ++++++++++------------ 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 1c896a0a..8e52ad4e 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -81,18 +81,17 @@ def test_empty(shape, dtype): @given( a=xps.arrays( - dtype=shared(xps.scalar_dtypes(), key='dtypes'), + dtype=shared_dtypes, shape=xps.array_shapes(), ), - kwargs=one_of( - just({}), - shared(xps.scalar_dtypes(), key='dtypes').map(lambda d: {'dtype': d}), - ), + dtype=one_of(none(), shared_dtypes), ) -def test_empty_like(a, kwargs): +def test_empty_like(a, dtype): + kwargs = {} if dtype is None else {'dtype': dtype} + a_like = empty_like(a, **kwargs) - if kwargs is None: + if dtype is None: # TODO: Should it actually match a.dtype? # assert is_float_dtype(a_like.dtype), "empty_like() should produce an array with the default floating point dtype" pass @@ -153,26 +152,24 @@ def test_full(shape, fill_value, dtype): @given( a=xps.arrays( - dtype=shared(xps.scalar_dtypes(), key='dtypes'), + dtype=shared_dtypes, shape=xps.array_shapes(), ), - fill_value=shared(xps.scalar_dtypes(), key='dtypes').flatmap(xps.from_dtype), - kwargs=one_of( - just({}), - shared(xps.scalar_dtypes(), key='dtypes').map(lambda d: {'dtype': d}), - ), + fill_value=shared_dtypes.flatmap(xps.from_dtype), + dtype=one_of(none(), shared_dtypes), ) -def test_full_like(a, fill_value, kwargs): +def test_full_like(a, fill_value, dtype): + kwargs = {} if dtype is None else {'dtype': dtype} + a_like = full_like(a, fill_value, **kwargs) - if kwargs is None: + if dtype is None: # TODO: Should it actually match a.dtype? pass else: assert a_like.dtype == a.dtype assert a_like.shape == a.shape, "full_like() produced an array with incorrect shape" - if is_float_dtype(a_like.dtype) and isnan(asarray(fill_value)): assert all(isnan(a_like)), "full_like() array did not equal the fill value" else: @@ -247,15 +244,13 @@ def test_ones(shape, dtype): @given( a=xps.arrays( - dtype=shared(xps.scalar_dtypes(), key='dtypes'), + dtype=shared_dtypes, shape=xps.array_shapes(), ), - kwargs=one_of( - just({}), - shared(xps.scalar_dtypes(), key='dtypes').map(lambda d: {'dtype': d}), - ), + dtype=one_of(none(), shared_dtypes), ) -def test_ones_like(a, kwargs): +def test_ones_like(a, dtype): + kwargs = {} if dtype is None else {'dtype': dtype} if kwargs is None or is_float_dtype(a.dtype): ONE = 1.0 elif is_integer_dtype(a.dtype): @@ -300,16 +295,14 @@ def test_zeros(shape, dtype): @given( a=xps.arrays( - dtype=shared(xps.scalar_dtypes(), key='dtypes'), + dtype=shared_dtypes, shape=xps.array_shapes(), ), - kwargs=one_of( - just({}), - shared(xps.scalar_dtypes(), key='dtypes').map(lambda d: {'dtype': d}), - ), + dtype=one_of(none(), shared_dtypes), ) -def test_zeros_like(a, kwargs): - if kwargs is None or is_float_dtype(a.dtype): +def test_zeros_like(a, dtype): + kwargs = {} if dtype is None else {'dtype': dtype} + if dtype is None or is_float_dtype(a.dtype): ZERO = 0.0 elif is_integer_dtype(a.dtype): ZERO = 0 From 6622a81a0b44b87d8dd588c5fc0da3b98eda8eb0 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Fri, 17 Sep 2021 16:21:41 +0100 Subject: [PATCH 07/30] Remove redundant st.shared import --- array_api_tests/test_creation_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 8e52ad4e..ec031628 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -8,7 +8,7 @@ scalars, xps) from hypothesis import assume, given -from hypothesis.strategies import integers, floats, one_of, none, booleans, just, shared +from hypothesis.strategies import integers, floats, one_of, none, booleans, just int_range = integers(-MAX_ARRAY_SIZE, MAX_ARRAY_SIZE) float_range = floats(-MAX_ARRAY_SIZE, MAX_ARRAY_SIZE, From a3470e118aea37e09d9c55e48230df428c8d5f29 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Mon, 20 Sep 2021 11:23:43 +0100 Subject: [PATCH 08/30] promotable_dtypes() strategy * Used in test_*_like() tests * Refactors construction of dtype_pairs table * mutually_promotable_dtypes -> mutually_promotable_dtype_pairs for clarity * Rudimentary meta tests --- array_api_tests/hypothesis_helpers.py | 33 ++++++++++------- .../meta_tests/test_hypothesis_helpers.py | 37 +++++++++++++++++++ array_api_tests/test_creation_functions.py | 26 ++++++------- array_api_tests/test_elementwise_functions.py | 16 ++++---- 4 files changed, 78 insertions(+), 34 deletions(-) create mode 100644 array_api_tests/meta_tests/test_hypothesis_helpers.py diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 610685be..101145dd 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -16,7 +16,7 @@ integer_or_boolean_dtype_objects, dtype_objects) from ._array_module import full, float32, float64, bool as bool_dtype, _UndefinedStub from . import _array_module -from ._array_module import mod as xp +from . import _array_module as xp from .function_stubs import elementwise_functions @@ -49,8 +49,7 @@ shared_dtypes = shared(dtypes) -@composite -def mutually_promotable_dtypes(draw, dtype_objects=dtype_objects): +def make_dtype_pairs(): from .test_type_promotion import dtype_mapping, promotion_table # sort for shrinking (sampled_from shrinks to the earlier elements in the # list). Give pairs of the same dtypes first, then smaller dtypes, @@ -60,17 +59,25 @@ def mutually_promotable_dtypes(draw, dtype_objects=dtype_objects): # pairs (XXX: Can we redesign the strategies so that they can prefer # shrinking dtypes over values?) sorted_table = sorted(promotion_table) - sorted_table = sorted(sorted_table, key=lambda ij: -1 if ij[0] == ij[1] else sorted_table.index(ij)) - dtype_pairs = [(dtype_mapping[i], dtype_mapping[j]) for i, j in - sorted_table] - - filtered_dtype_pairs = [(i, j) for i, j in dtype_pairs if i in - dtype_objects and j in dtype_objects] + sorted_table = sorted( + sorted_table, key=lambda ij: -1 if ij[0] == ij[1] else sorted_table.index(ij) + ) + dtype_pairs = [(dtype_mapping[i], dtype_mapping[j]) for i, j in sorted_table] if FILTER_UNDEFINED_DTYPES: - filtered_dtype_pairs = [(i, j) for i, j in filtered_dtype_pairs - if not isinstance(i, _UndefinedStub) - and not isinstance(j, _UndefinedStub)] - return draw(sampled_from(filtered_dtype_pairs)) + dtype_pairs = [(i, j) for i, j in dtype_pairs + if not isinstance(i, _UndefinedStub) + and not isinstance(j, _UndefinedStub)] + return dtype_pairs + +def promotable_dtypes(dtype): + dtype_pairs = make_dtype_pairs() + dtypes = [j for i, j in dtype_pairs if i == dtype] + return sampled_from(dtypes) + +def mutually_promotable_dtype_pairs(dtype_objects=dtype_objects): + dtype_pairs = make_dtype_pairs() + dtype_pairs = [(i, j) for i, j in dtype_pairs if i in dtype_objects and j in dtype_objects] + return sampled_from(dtype_pairs) # shared() allows us to draw either the function or the function name and they # will both correspond to the same function. diff --git a/array_api_tests/meta_tests/test_hypothesis_helpers.py b/array_api_tests/meta_tests/test_hypothesis_helpers.py new file mode 100644 index 00000000..90527d45 --- /dev/null +++ b/array_api_tests/meta_tests/test_hypothesis_helpers.py @@ -0,0 +1,37 @@ +import pytest +from hypothesis import given + +from .. import _array_module as xp +from .._array_module import _UndefinedStub +from ..array_helpers import dtype_objects +from ..hypothesis_helpers import (mutually_promotable_dtype_pairs, + promotable_dtypes) + +UNDEFINED_DTYPES = any(isinstance(d, _UndefinedStub) for d in dtype_objects) +pytestmark = [pytest.mark.skipif(UNDEFINED_DTYPES, reason="undefined dtypes")] + + +def test_promotable_dtypes(): + dtypes = set() + @given(promotable_dtypes(xp.uint16)) + def run(dtype): + dtypes.add(dtype) + run() + assert dtypes == { + xp.uint8, xp.uint16, xp.uint32, xp.uint64, xp.int8, xp.int16, xp.int32, xp.int64 + } + + +def test_mutually_promotable_dtype_pairs(): + pairs = set() + @given(mutually_promotable_dtype_pairs([xp.float32, xp.float64])) + def run(pair): + pairs.add(pair) + run() + assert pairs == { + (xp.float32, xp.float32), + (xp.float32, xp.float64), + (xp.float64, xp.float32), + (xp.float64, xp.float64), + } + diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index ec031628..faa38b78 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -3,7 +3,7 @@ zeros, zeros_like, isnan) from .array_helpers import (is_integer_dtype, dtype_ranges, assert_exactly_equal, isintegral, is_float_dtype) -from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, +from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, promotable_dtypes, shapes, sizes, sqrt_sizes, shared_dtypes, scalars, xps) @@ -84,7 +84,7 @@ def test_empty(shape, dtype): dtype=shared_dtypes, shape=xps.array_shapes(), ), - dtype=one_of(none(), shared_dtypes), + dtype=one_of(none(), shared_dtypes.flatmap(promotable_dtypes)), ) def test_empty_like(a, dtype): kwargs = {} if dtype is None else {'dtype': dtype} @@ -96,7 +96,7 @@ def test_empty_like(a, dtype): # assert is_float_dtype(a_like.dtype), "empty_like() should produce an array with the default floating point dtype" pass else: - assert a_like.dtype == a.dtype, "empty_like() produced an array with an incorrect dtype" + assert a_like.dtype == dtype, "empty_like() produced an array with an incorrect dtype" assert a_like.shape == a.shape, "empty_like() produced an array with an incorrect shape" @@ -155,8 +155,8 @@ def test_full(shape, fill_value, dtype): dtype=shared_dtypes, shape=xps.array_shapes(), ), - fill_value=shared_dtypes.flatmap(xps.from_dtype), - dtype=one_of(none(), shared_dtypes), + fill_value=shared_dtypes.flatmap(promotable_dtypes).flatmap(xps.from_dtype), + dtype=one_of(none(), shared_dtypes.flatmap(promotable_dtypes)), ) def test_full_like(a, fill_value, dtype): kwargs = {} if dtype is None else {'dtype': dtype} @@ -167,13 +167,13 @@ def test_full_like(a, fill_value, dtype): # TODO: Should it actually match a.dtype? pass else: - assert a_like.dtype == a.dtype + assert a_like.dtype == dtype assert a_like.shape == a.shape, "full_like() produced an array with incorrect shape" if is_float_dtype(a_like.dtype) and isnan(asarray(fill_value)): assert all(isnan(a_like)), "full_like() array did not equal the fill value" else: - assert all(equal(a_like, asarray(fill_value, dtype=a.dtype))), "full_like() array did not equal the fill value" + assert all(equal(a_like, asarray(fill_value, dtype=a_like.dtype))), "full_like() array did not equal the fill value" @given(scalars(shared_dtypes, finite=True), @@ -247,7 +247,7 @@ def test_ones(shape, dtype): dtype=shared_dtypes, shape=xps.array_shapes(), ), - dtype=one_of(none(), shared_dtypes), + dtype=one_of(none(), shared_dtypes.flatmap(promotable_dtypes)), ) def test_ones_like(a, dtype): kwargs = {} if dtype is None else {'dtype': dtype} @@ -260,11 +260,11 @@ def test_ones_like(a, dtype): a_like = ones_like(a, **kwargs) - if kwargs is None: + if dtype is None: # TODO: Should it actually match a.dtype? pass else: - assert a_like.dtype == a.dtype, "ones_like() produced an array with an incorrect dtype" + assert a_like.dtype == dtype, "ones_like() produced an array with an incorrect dtype" assert a_like.shape == a.shape, "ones_like() produced an array with an incorrect shape" assert all(equal(a_like, full((), ONE, dtype=a_like.dtype))), "ones_like() array did not equal 1" @@ -298,7 +298,7 @@ def test_zeros(shape, dtype): dtype=shared_dtypes, shape=xps.array_shapes(), ), - dtype=one_of(none(), shared_dtypes), + dtype=one_of(none(), shared_dtypes.flatmap(promotable_dtypes)), ) def test_zeros_like(a, dtype): kwargs = {} if dtype is None else {'dtype': dtype} @@ -311,11 +311,11 @@ def test_zeros_like(a, dtype): a_like = zeros_like(a, **kwargs) - if kwargs is None: + if dtype is None: # TODO: Should it actually match a.dtype? pass else: - assert a_like.dtype == a.dtype, "zeros_like() produced an array with an incorrect dtype" + assert a_like.dtype == dtype, "zeros_like() produced an array with an incorrect dtype" assert a_like.shape == a.shape, "zeros_like() produced an array with an incorrect shape" assert all(equal(a_like, full((), ZERO, dtype=a_like.dtype))), "zeros_like() array did not equal 0" diff --git a/array_api_tests/test_elementwise_functions.py b/array_api_tests/test_elementwise_functions.py index 00277514..ead01403 100644 --- a/array_api_tests/test_elementwise_functions.py +++ b/array_api_tests/test_elementwise_functions.py @@ -25,7 +25,7 @@ integer_or_boolean_dtype_objects, boolean_dtype_objects, floating_dtypes, numeric_dtypes, integer_or_boolean_dtypes, - boolean_dtypes, mutually_promotable_dtypes, + boolean_dtypes, mutually_promotable_dtype_pairs, array_scalars, xps) from .array_helpers import (assert_exactly_equal, negative, positive_mathematical_sign, @@ -50,17 +50,17 @@ integer_or_boolean_scalars = array_scalars(integer_or_boolean_dtypes) boolean_scalars = array_scalars(boolean_dtypes) -two_integer_dtypes = mutually_promotable_dtypes(integer_dtype_objects) -two_floating_dtypes = mutually_promotable_dtypes(floating_dtype_objects) -two_numeric_dtypes = mutually_promotable_dtypes(numeric_dtype_objects) -two_integer_or_boolean_dtypes = mutually_promotable_dtypes(integer_or_boolean_dtype_objects) -two_boolean_dtypes = mutually_promotable_dtypes(boolean_dtype_objects) -two_any_dtypes = mutually_promotable_dtypes() +two_integer_dtypes = mutually_promotable_dtype_pairs(integer_dtype_objects) +two_floating_dtypes = mutually_promotable_dtype_pairs(floating_dtype_objects) +two_numeric_dtypes = mutually_promotable_dtype_pairs(numeric_dtype_objects) +two_integer_or_boolean_dtypes = mutually_promotable_dtype_pairs(integer_or_boolean_dtype_objects) +two_boolean_dtypes = mutually_promotable_dtype_pairs(boolean_dtype_objects) +two_any_dtypes = mutually_promotable_dtype_pairs() @composite def two_array_scalars(draw, dtype1, dtype2): # two_dtypes should be a strategy that returns two dtypes (like - # mutually_promotable_dtypes()) + # mutually_promotable_dtype_pairs()) return draw(array_scalars(just(dtype1))), draw(array_scalars(just(dtype2))) def sanity_check(x1, x2): From 955cabf13e6afde42170200e8c86d01c24481c1f Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Tue, 21 Sep 2021 09:43:22 +0100 Subject: [PATCH 09/30] Cleaned up hypothesis helper tests --- .../meta_tests/test_hypothesis_helpers.py | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/array_api_tests/meta_tests/test_hypothesis_helpers.py b/array_api_tests/meta_tests/test_hypothesis_helpers.py index 90527d45..ac6d310b 100644 --- a/array_api_tests/meta_tests/test_hypothesis_helpers.py +++ b/array_api_tests/meta_tests/test_hypothesis_helpers.py @@ -11,27 +11,19 @@ pytestmark = [pytest.mark.skipif(UNDEFINED_DTYPES, reason="undefined dtypes")] -def test_promotable_dtypes(): - dtypes = set() - @given(promotable_dtypes(xp.uint16)) - def run(dtype): - dtypes.add(dtype) - run() - assert dtypes == { +@given(promotable_dtypes(xp.uint16)) +def test_promotable_dtypes(dtype): + assert dtype in ( xp.uint8, xp.uint16, xp.uint32, xp.uint64, xp.int8, xp.int16, xp.int32, xp.int64 - } + ) -def test_mutually_promotable_dtype_pairs(): - pairs = set() - @given(mutually_promotable_dtype_pairs([xp.float32, xp.float64])) - def run(pair): - pairs.add(pair) - run() - assert pairs == { +@given(mutually_promotable_dtype_pairs([xp.float32, xp.float64])) +def test_mutually_promotable_dtype_pairs(pairs): + assert pairs in ( (xp.float32, xp.float32), (xp.float32, xp.float64), (xp.float64, xp.float32), (xp.float64, xp.float64), - } + ) From e14c09dbaec97901b5c776f8509dfaff12225a75 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Tue, 21 Sep 2021 09:46:17 +0100 Subject: [PATCH 10/30] Internally flatmap promotable_dtypes --- array_api_tests/hypothesis_helpers.py | 7 +++++-- array_api_tests/test_creation_functions.py | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 101145dd..31bdab7c 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -3,8 +3,9 @@ from math import sqrt from hypothesis.strategies import (lists, integers, builds, sampled_from, - shared, floats, just, composite, one_of, - none, booleans) + shared, tuples as hypotheses_tuples, + floats, just, composite, one_of, none, + booleans, SearchStrategy) from hypothesis.extra.numpy import mutually_broadcastable_shapes from hypothesis.extra.array_api import make_strategies_namespace from hypothesis import assume @@ -70,6 +71,8 @@ def make_dtype_pairs(): return dtype_pairs def promotable_dtypes(dtype): + if isinstance(dtype, SearchStrategy): + return dtype.flatmap(promotable_dtypes) dtype_pairs = make_dtype_pairs() dtypes = [j for i, j in dtype_pairs if i == dtype] return sampled_from(dtypes) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index faa38b78..06224a97 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -84,7 +84,7 @@ def test_empty(shape, dtype): dtype=shared_dtypes, shape=xps.array_shapes(), ), - dtype=one_of(none(), shared_dtypes.flatmap(promotable_dtypes)), + dtype=one_of(none(), promotable_dtypes(shared_dtypes)), ) def test_empty_like(a, dtype): kwargs = {} if dtype is None else {'dtype': dtype} @@ -155,8 +155,8 @@ def test_full(shape, fill_value, dtype): dtype=shared_dtypes, shape=xps.array_shapes(), ), - fill_value=shared_dtypes.flatmap(promotable_dtypes).flatmap(xps.from_dtype), - dtype=one_of(none(), shared_dtypes.flatmap(promotable_dtypes)), + fill_value=promotable_dtypes(shared_dtypes).flatmap(xps.from_dtype), + dtype=one_of(none(), promotable_dtypes(shared_dtypes)), ) def test_full_like(a, fill_value, dtype): kwargs = {} if dtype is None else {'dtype': dtype} @@ -247,7 +247,7 @@ def test_ones(shape, dtype): dtype=shared_dtypes, shape=xps.array_shapes(), ), - dtype=one_of(none(), shared_dtypes.flatmap(promotable_dtypes)), + dtype=one_of(none(), promotable_dtypes(shared_dtypes)), ) def test_ones_like(a, dtype): kwargs = {} if dtype is None else {'dtype': dtype} @@ -298,7 +298,7 @@ def test_zeros(shape, dtype): dtype=shared_dtypes, shape=xps.array_shapes(), ), - dtype=one_of(none(), shared_dtypes.flatmap(promotable_dtypes)), + dtype=one_of(none(), promotable_dtypes(shared_dtypes)), ) def test_zeros_like(a, dtype): kwargs = {} if dtype is None else {'dtype': dtype} From eba6e61027c46ad130abe7c8beef4c1ed7db1e3b Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Tue, 21 Sep 2021 11:08:44 +0100 Subject: [PATCH 11/30] Redefine custom shapes strategy using xps.array_shapes --- array_api_tests/hypothesis_helpers.py | 21 ++++------- .../meta_tests/test_hypothesis_helpers.py | 37 ++++++++++++++++++- array_api_tests/test_creation_functions.py | 8 ++-- 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 31bdab7c..2920bea3 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -6,7 +6,6 @@ shared, tuples as hypotheses_tuples, floats, just, composite, one_of, none, booleans, SearchStrategy) -from hypothesis.extra.numpy import mutually_broadcastable_shapes from hypothesis.extra.array_api import make_strategies_namespace from hypothesis import assume @@ -111,29 +110,25 @@ def tuples(elements, *, min_size=0, max_size=None, unique_by=None, unique=False) return lists(elements, min_size=min_size, max_size=max_size, unique_by=unique_by, unique=unique).map(tuple) -shapes = tuples(integers(0, 10)).filter(lambda shape: prod(shape) < MAX_ARRAY_SIZE) - # Use this to avoid memory errors with NumPy. # See https://github.com/numpy/numpy/issues/15753 -shapes = tuples(integers(0, 10)).filter( - lambda shape: prod([i for i in shape if i]) < MAX_ARRAY_SIZE) +shapes = xps.array_shapes(min_dims=0, min_side=0).filter( + lambda shape: prod(i for i in shape if i) < MAX_ARRAY_SIZE +) -two_mutually_broadcastable_shapes = mutually_broadcastable_shapes(num_shapes=2)\ +two_mutually_broadcastable_shapes = xps.mutually_broadcastable_shapes(num_shapes=2)\ .map(lambda S: S.input_shapes)\ - .filter(lambda S: all(prod([i for i in shape if i]) < MAX_ARRAY_SIZE for shape in S)) + .filter(lambda S: all(prod(i for i in shape if i) < MAX_ARRAY_SIZE for shape in S)) @composite -def two_broadcastable_shapes(draw, shapes=shapes): +def two_broadcastable_shapes(draw): """ This will produce two shapes (shape1, shape2) such that shape2 can be broadcast to shape1. - """ from .test_broadcasting import broadcast_shapes - - shape1, shape2 = draw(two_mutually_broadcastable_shapes) - if broadcast_shapes(shape1, shape2) != shape1: - assume(False) + shape1, shape2 = draw(two_mutually_broadcastable_shapes) + assume(broadcast_shapes(shape1, shape2) == shape1) return (shape1, shape2) sizes = integers(0, MAX_ARRAY_SIZE) diff --git a/array_api_tests/meta_tests/test_hypothesis_helpers.py b/array_api_tests/meta_tests/test_hypothesis_helpers.py index ac6d310b..af968135 100644 --- a/array_api_tests/meta_tests/test_hypothesis_helpers.py +++ b/array_api_tests/meta_tests/test_hypothesis_helpers.py @@ -1,11 +1,16 @@ +from math import prod + import pytest from hypothesis import given from .. import _array_module as xp from .._array_module import _UndefinedStub from ..array_helpers import dtype_objects -from ..hypothesis_helpers import (mutually_promotable_dtype_pairs, - promotable_dtypes) +from ..hypothesis_helpers import (MAX_ARRAY_SIZE, + mutually_promotable_dtype_pairs, + promotable_dtypes, shapes, + two_broadcastable_shapes, + two_mutually_broadcastable_shapes) UNDEFINED_DTYPES = any(isinstance(d, _UndefinedStub) for d in dtype_objects) pytestmark = [pytest.mark.skipif(UNDEFINED_DTYPES, reason="undefined dtypes")] @@ -27,3 +32,31 @@ def test_mutually_promotable_dtype_pairs(pairs): (xp.float64, xp.float64), ) + +def valid_shape(shape) -> bool: + return ( + all(isinstance(side, int) for side in shape) + and all(side >= 0 for side in shape) + and prod(shape) < MAX_ARRAY_SIZE + ) + + +@given(shapes) +def test_shapes(shape): + assert valid_shape(shape) + + +@given(two_mutually_broadcastable_shapes) +def test_two_mutually_broadcastable_shapes(pair): + for shape in pair: + assert valid_shape(shape) + + +@given(two_broadcastable_shapes()) +def test_two_broadcastable_shapes(pair): + for shape in pair: + assert valid_shape(shape) + + from ..test_broadcasting import broadcast_shapes + + assert broadcast_shapes(pair[0], pair[1]) == pair[0] diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 06224a97..26e28b75 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -82,7 +82,7 @@ def test_empty(shape, dtype): @given( a=xps.arrays( dtype=shared_dtypes, - shape=xps.array_shapes(), + shape=shapes, ), dtype=one_of(none(), promotable_dtypes(shared_dtypes)), ) @@ -153,7 +153,7 @@ def test_full(shape, fill_value, dtype): @given( a=xps.arrays( dtype=shared_dtypes, - shape=xps.array_shapes(), + shape=shapes, ), fill_value=promotable_dtypes(shared_dtypes).flatmap(xps.from_dtype), dtype=one_of(none(), promotable_dtypes(shared_dtypes)), @@ -245,7 +245,7 @@ def test_ones(shape, dtype): @given( a=xps.arrays( dtype=shared_dtypes, - shape=xps.array_shapes(), + shape=shapes, ), dtype=one_of(none(), promotable_dtypes(shared_dtypes)), ) @@ -296,7 +296,7 @@ def test_zeros(shape, dtype): @given( a=xps.arrays( dtype=shared_dtypes, - shape=xps.array_shapes(), + shape=shapes, ), dtype=one_of(none(), promotable_dtypes(shared_dtypes)), ) From ae4171f48ddc28f8eb163b33e6c7e167da609635 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Tue, 21 Sep 2021 11:23:02 +0100 Subject: [PATCH 12/30] Defined shared_optional_promotable_dtype --- array_api_tests/hypothesis_helpers.py | 6 ++++++ array_api_tests/test_creation_functions.py | 12 +++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 2920bea3..f62a8105 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -243,3 +243,9 @@ def multiaxis_indices(draw, shapes): extra = draw(lists(one_of(integer_indices(sizes), slices(sizes)), min_size=0, max_size=3)) res += extra return tuple(res) + + +shared_optional_promotable_dtypes = one_of( + none(), + promotable_dtypes(shared_dtypes), +) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 26e28b75..313f9b3a 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -5,11 +5,13 @@ assert_exactly_equal, isintegral, is_float_dtype) from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, promotable_dtypes, shapes, sizes, sqrt_sizes, shared_dtypes, - scalars, xps) + scalars, xps, shared_optional_promotable_dtypes) from hypothesis import assume, given from hypothesis.strategies import integers, floats, one_of, none, booleans, just + + int_range = integers(-MAX_ARRAY_SIZE, MAX_ARRAY_SIZE) float_range = floats(-MAX_ARRAY_SIZE, MAX_ARRAY_SIZE, allow_nan=False) @@ -84,7 +86,7 @@ def test_empty(shape, dtype): dtype=shared_dtypes, shape=shapes, ), - dtype=one_of(none(), promotable_dtypes(shared_dtypes)), + dtype=shared_optional_promotable_dtypes, ) def test_empty_like(a, dtype): kwargs = {} if dtype is None else {'dtype': dtype} @@ -156,7 +158,7 @@ def test_full(shape, fill_value, dtype): shape=shapes, ), fill_value=promotable_dtypes(shared_dtypes).flatmap(xps.from_dtype), - dtype=one_of(none(), promotable_dtypes(shared_dtypes)), + dtype=shared_optional_promotable_dtypes, ) def test_full_like(a, fill_value, dtype): kwargs = {} if dtype is None else {'dtype': dtype} @@ -247,7 +249,7 @@ def test_ones(shape, dtype): dtype=shared_dtypes, shape=shapes, ), - dtype=one_of(none(), promotable_dtypes(shared_dtypes)), + dtype=shared_optional_promotable_dtypes, ) def test_ones_like(a, dtype): kwargs = {} if dtype is None else {'dtype': dtype} @@ -298,7 +300,7 @@ def test_zeros(shape, dtype): dtype=shared_dtypes, shape=shapes, ), - dtype=one_of(none(), promotable_dtypes(shared_dtypes)), + dtype=shared_optional_promotable_dtypes, ) def test_zeros_like(a, dtype): kwargs = {} if dtype is None else {'dtype': dtype} From 78817ef61d414cb2058fda62889badd60fc1e359 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Tue, 21 Sep 2021 11:34:44 +0100 Subject: [PATCH 13/30] Remove redundant imports --- array_api_tests/hypothesis_helpers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index f62a8105..297bf726 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -2,10 +2,9 @@ from operator import mul from math import sqrt -from hypothesis.strategies import (lists, integers, builds, sampled_from, - shared, tuples as hypotheses_tuples, - floats, just, composite, one_of, none, - booleans, SearchStrategy) +from hypothesis.strategies import (lists, integers, sampled_from, + shared, floats, just, composite, one_of, + none, booleans, SearchStrategy) from hypothesis.extra.array_api import make_strategies_namespace from hypothesis import assume From b7089e4e6e79dfe7abce5b65e05238e8486763c6 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Tue, 21 Sep 2021 12:51:16 +0100 Subject: [PATCH 14/30] Test promotable dtype and broadcastable shape in test_equal --- array_api_tests/test_elementwise_functions.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/array_api_tests/test_elementwise_functions.py b/array_api_tests/test_elementwise_functions.py index ead01403..bb2d2148 100644 --- a/array_api_tests/test_elementwise_functions.py +++ b/array_api_tests/test_elementwise_functions.py @@ -26,7 +26,7 @@ boolean_dtype_objects, floating_dtypes, numeric_dtypes, integer_or_boolean_dtypes, boolean_dtypes, mutually_promotable_dtype_pairs, - array_scalars, xps) + array_scalars, two_broadcastable_shapes, xps, shared_dtypes, promotable_dtypes) from .array_helpers import (assert_exactly_equal, negative, positive_mathematical_sign, negative_mathematical_sign, logical_not, @@ -377,11 +377,13 @@ def test_divide(args): @given( - x1=shared( - xps.arrays(dtype=xps.scalar_dtypes(), shape=xps.array_shapes()), key='arrays' + x1=xps.arrays( + dtype=shared_dtypes, + shape=shared(two_broadcastable_shapes(), key="shape_pair").map(lambda pair: pair[0]) ), - x2=shared( - xps.arrays(dtype=xps.scalar_dtypes(), shape=xps.array_shapes()), key='arrays' + x2=xps.arrays( + dtype=promotable_dtypes(shared_dtypes), + shape=shared(two_broadcastable_shapes(), key="shape_pair").map(lambda pair: pair[1]) ), ) def test_equal(x1, x2): From e25c25711cc30690b22913ab8caa210298e1000e Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Wed, 22 Sep 2021 09:35:10 +0100 Subject: [PATCH 15/30] Fixed test_full_like generation to match spec --- array_api_tests/test_creation_functions.py | 36 +++++++++++++--------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 313f9b3a..a350cc9b 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -8,7 +8,7 @@ scalars, xps, shared_optional_promotable_dtypes) from hypothesis import assume, given -from hypothesis.strategies import integers, floats, one_of, none, booleans, just +from hypothesis.strategies import integers, floats, one_of, none, booleans, just, shared, composite @@ -152,30 +152,36 @@ def test_full(shape, fill_value, dtype): else: assert all(equal(a, asarray(fill_value, **kwargs))), "full() array did not equal the fill value" +shared_optional_dtypes = shared(none() | shared_dtypes, key="optional_dtype") + +@composite +def fill_values(draw): + dtype = draw(shared_optional_dtypes) + if dtype is None: + dtype = draw(shared_dtypes) + return draw(xps.from_dtype(dtype)) + @given( - a=xps.arrays( - dtype=shared_dtypes, - shape=shapes, - ), - fill_value=promotable_dtypes(shared_dtypes).flatmap(xps.from_dtype), - dtype=shared_optional_promotable_dtypes, + x=xps.arrays(dtype=shared_dtypes, shape=shapes), + fill_value=fill_values(), + dtype=shared_optional_dtypes, ) -def test_full_like(a, fill_value, dtype): +def test_full_like(x, fill_value, dtype): kwargs = {} if dtype is None else {'dtype': dtype} - a_like = full_like(a, fill_value, **kwargs) + x_like = full_like(x, fill_value, **kwargs) if dtype is None: - # TODO: Should it actually match a.dtype? + # TODO: Should it actually match x.dtype? pass else: - assert a_like.dtype == dtype + assert x_like.dtype == dtype - assert a_like.shape == a.shape, "full_like() produced an array with incorrect shape" - if is_float_dtype(a_like.dtype) and isnan(asarray(fill_value)): - assert all(isnan(a_like)), "full_like() array did not equal the fill value" + assert x_like.shape == x.shape, "full_like() produced an array with incorrect shape" + if is_float_dtype(x_like.dtype) and isnan(asarray(fill_value)): + assert all(isnan(x_like)), "full_like() array did not equal the fill value" else: - assert all(equal(a_like, asarray(fill_value, dtype=a_like.dtype))), "full_like() array did not equal the fill value" + assert all(equal(x_like, asarray(fill_value, dtype=x_like.dtype))), "full_like() array did not equal the fill value" @given(scalars(shared_dtypes, finite=True), From f7f8bdeaeaba54d3b6af576a31e0ea5f31566743 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Wed, 22 Sep 2021 10:07:57 +0100 Subject: [PATCH 16/30] Fixed test_*_like methods to match spec --- array_api_tests/hypothesis_helpers.py | 6 -- array_api_tests/test_creation_functions.py | 67 ++++++++++------------ 2 files changed, 31 insertions(+), 42 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 297bf726..3d36cffe 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -242,9 +242,3 @@ def multiaxis_indices(draw, shapes): extra = draw(lists(one_of(integer_indices(sizes), slices(sizes)), min_size=0, max_size=3)) res += extra return tuple(res) - - -shared_optional_promotable_dtypes = one_of( - none(), - promotable_dtypes(shared_dtypes), -) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index a350cc9b..401a0b73 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -3,14 +3,17 @@ zeros, zeros_like, isnan) from .array_helpers import (is_integer_dtype, dtype_ranges, assert_exactly_equal, isintegral, is_float_dtype) -from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, promotable_dtypes, +from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, shapes, sizes, sqrt_sizes, shared_dtypes, - scalars, xps, shared_optional_promotable_dtypes) + scalars, xps) from hypothesis import assume, given from hypothesis.strategies import integers, floats, one_of, none, booleans, just, shared, composite +optional_dtypes = none() | shared_dtypes +shared_optional_dtypes = shared(optional_dtypes, key="optional_dtype") + int_range = integers(-MAX_ARRAY_SIZE, MAX_ARRAY_SIZE) float_range = floats(-MAX_ARRAY_SIZE, MAX_ARRAY_SIZE, @@ -82,25 +85,25 @@ def test_empty(shape, dtype): @given( - a=xps.arrays( - dtype=shared_dtypes, + x=xps.arrays( + dtype=dtypes, shape=shapes, ), - dtype=shared_optional_promotable_dtypes, + dtype=optional_dtypes, ) -def test_empty_like(a, dtype): +def test_empty_like(x, dtype): kwargs = {} if dtype is None else {'dtype': dtype} - a_like = empty_like(a, **kwargs) + x_like = empty_like(x, **kwargs) if dtype is None: # TODO: Should it actually match a.dtype? - # assert is_float_dtype(a_like.dtype), "empty_like() should produce an array with the default floating point dtype" + # assert is_float_dtype(x_like.dtype), "empty_like() should produce an array with the default floating point dtype" pass else: - assert a_like.dtype == dtype, "empty_like() produced an array with an incorrect dtype" + assert x_like.dtype == dtype, "empty_like() produced an array with an incorrect dtype" - assert a_like.shape == a.shape, "empty_like() produced an array with an incorrect shape" + assert x_like.shape == x.shape, "empty_like() produced an array with an incorrect shape" # TODO: Use this method for all optional arguments @@ -152,8 +155,6 @@ def test_full(shape, fill_value, dtype): else: assert all(equal(a, asarray(fill_value, **kwargs))), "full() array did not equal the fill value" -shared_optional_dtypes = shared(none() | shared_dtypes, key="optional_dtype") - @composite def fill_values(draw): dtype = draw(shared_optional_dtypes) @@ -251,31 +252,28 @@ def test_ones(shape, dtype): @given( - a=xps.arrays( - dtype=shared_dtypes, - shape=shapes, - ), - dtype=shared_optional_promotable_dtypes, + x=xps.arrays(dtype=dtypes, shape=shapes), + dtype=optional_dtypes, ) -def test_ones_like(a, dtype): +def test_ones_like(x, dtype): kwargs = {} if dtype is None else {'dtype': dtype} - if kwargs is None or is_float_dtype(a.dtype): + if kwargs is None or is_float_dtype(x.dtype): ONE = 1.0 - elif is_integer_dtype(a.dtype): + elif is_integer_dtype(x.dtype): ONE = 1 else: ONE = True - a_like = ones_like(a, **kwargs) + x_like = ones_like(x, **kwargs) if dtype is None: # TODO: Should it actually match a.dtype? pass else: - assert a_like.dtype == dtype, "ones_like() produced an array with an incorrect dtype" + assert x_like.dtype == dtype, "ones_like() produced an array with an incorrect dtype" - assert a_like.shape == a.shape, "ones_like() produced an array with an incorrect shape" - assert all(equal(a_like, full((), ONE, dtype=a_like.dtype))), "ones_like() array did not equal 1" + assert x_like.shape == x.shape, "ones_like() produced an array with an incorrect shape" + assert all(equal(x_like, full((), ONE, dtype=x_like.dtype))), "ones_like() array did not equal 1" @given(shapes, one_of(none(), dtypes)) @@ -302,29 +300,26 @@ def test_zeros(shape, dtype): @given( - a=xps.arrays( - dtype=shared_dtypes, - shape=shapes, - ), - dtype=shared_optional_promotable_dtypes, + x=xps.arrays(dtype=dtypes, shape=shapes), + dtype=optional_dtypes, ) -def test_zeros_like(a, dtype): +def test_zeros_like(x, dtype): kwargs = {} if dtype is None else {'dtype': dtype} - if dtype is None or is_float_dtype(a.dtype): + if dtype is None or is_float_dtype(x.dtype): ZERO = 0.0 - elif is_integer_dtype(a.dtype): + elif is_integer_dtype(x.dtype): ZERO = 0 else: ZERO = False - a_like = zeros_like(a, **kwargs) + x_like = zeros_like(x, **kwargs) if dtype is None: # TODO: Should it actually match a.dtype? pass else: - assert a_like.dtype == dtype, "zeros_like() produced an array with an incorrect dtype" + assert x_like.dtype == dtype, "zeros_like() produced an array with an incorrect dtype" - assert a_like.shape == a.shape, "zeros_like() produced an array with an incorrect shape" - assert all(equal(a_like, full((), ZERO, dtype=a_like.dtype))), "zeros_like() array did not equal 0" + assert x_like.shape == x.shape, "zeros_like() produced an array with an incorrect shape" + assert all(equal(x_like, full((), ZERO, dtype=x_like.dtype))), "zeros_like() array did not equal 0" From 68fadcd34e90063f46f9812c4780b944341a1ddf Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Wed, 22 Sep 2021 10:45:53 +0100 Subject: [PATCH 17/30] Refactored strategy used in test_equal --- array_api_tests/hypothesis_helpers.py | 10 ++++++++++ array_api_tests/test_elementwise_functions.py | 16 +++------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 3d36cffe..a3e32604 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -242,3 +242,13 @@ def multiaxis_indices(draw, shapes): extra = draw(lists(one_of(integer_indices(sizes), slices(sizes)), min_size=0, max_size=3)) res += extra return tuple(res) + + +shared_arrays1 = xps.arrays( + dtype=shared_dtypes, + shape=shared(two_mutually_broadcastable_shapes, key="shape_pair").map(lambda pair: pair[0]), +) +shared_arrays2 = xps.arrays( + dtype=promotable_dtypes(shared_dtypes), + shape=shared(two_mutually_broadcastable_shapes, key="shape_pair").map(lambda pair: pair[1]), +) diff --git a/array_api_tests/test_elementwise_functions.py b/array_api_tests/test_elementwise_functions.py index bb2d2148..856643cb 100644 --- a/array_api_tests/test_elementwise_functions.py +++ b/array_api_tests/test_elementwise_functions.py @@ -15,7 +15,7 @@ """ from hypothesis import given, assume -from hypothesis.strategies import composite, just, shared +from hypothesis.strategies import composite, just import math @@ -26,7 +26,7 @@ boolean_dtype_objects, floating_dtypes, numeric_dtypes, integer_or_boolean_dtypes, boolean_dtypes, mutually_promotable_dtype_pairs, - array_scalars, two_broadcastable_shapes, xps, shared_dtypes, promotable_dtypes) + array_scalars, shared_arrays1, shared_arrays2) from .array_helpers import (assert_exactly_equal, negative, positive_mathematical_sign, negative_mathematical_sign, logical_not, @@ -375,17 +375,7 @@ def test_divide(args): # have those sorts in general for this module. - -@given( - x1=xps.arrays( - dtype=shared_dtypes, - shape=shared(two_broadcastable_shapes(), key="shape_pair").map(lambda pair: pair[0]) - ), - x2=xps.arrays( - dtype=promotable_dtypes(shared_dtypes), - shape=shared(two_broadcastable_shapes(), key="shape_pair").map(lambda pair: pair[1]) - ), -) +@given(shared_arrays1, shared_arrays2) def test_equal(x1, x2): sanity_check(x1, x2) a = _array_module.equal(x1, x2) From f2435c9ece86931ff39235eec55ba563fbfe3563 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Wed, 22 Sep 2021 11:31:07 +0100 Subject: [PATCH 18/30] Remove promotable_dtypes and use the original mutual method --- array_api_tests/hypothesis_helpers.py | 33 +++++++++---------- .../meta_tests/test_hypothesis_helpers.py | 10 +----- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index a3e32604..cb298c4a 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -4,7 +4,7 @@ from hypothesis.strategies import (lists, integers, sampled_from, shared, floats, just, composite, one_of, - none, booleans, SearchStrategy) + none, booleans) from hypothesis.extra.array_api import make_strategies_namespace from hypothesis import assume @@ -46,9 +46,15 @@ boolean_dtypes = boolean_dtypes.filter(lambda x: not isinstance(x, _UndefinedStub)) dtypes = dtypes.filter(lambda x: not isinstance(x, _UndefinedStub)) -shared_dtypes = shared(dtypes) +shared_dtypes = shared(dtypes, key="dtype") -def make_dtype_pairs(): +# TODO: Importing things from test_type_promotion should be replaced by +# something that won't cause a circular import. Right now we use @st.composite +# only because it returns a lazy-evaluated strategy - in the future this method +# should remove the composite wrapper, just returning sampled_from(dtype_pairs) +# instead of drawing from it. +@composite +def mutually_promotable_dtype_pairs(draw, dtype_objects=dtype_objects): from .test_type_promotion import dtype_mapping, promotion_table # sort for shrinking (sampled_from shrinks to the earlier elements in the # list). Give pairs of the same dtypes first, then smaller dtypes, @@ -66,19 +72,12 @@ def make_dtype_pairs(): dtype_pairs = [(i, j) for i, j in dtype_pairs if not isinstance(i, _UndefinedStub) and not isinstance(j, _UndefinedStub)] - return dtype_pairs - -def promotable_dtypes(dtype): - if isinstance(dtype, SearchStrategy): - return dtype.flatmap(promotable_dtypes) - dtype_pairs = make_dtype_pairs() - dtypes = [j for i, j in dtype_pairs if i == dtype] - return sampled_from(dtypes) - -def mutually_promotable_dtype_pairs(dtype_objects=dtype_objects): - dtype_pairs = make_dtype_pairs() dtype_pairs = [(i, j) for i, j in dtype_pairs if i in dtype_objects and j in dtype_objects] - return sampled_from(dtype_pairs) + return draw(sampled_from(dtype_pairs)) + +shared_mutually_promotable_dtype_pairs = shared( + mutually_promotable_dtype_pairs(), key="mutually_promotable_dtype_pair" +) # shared() allows us to draw either the function or the function name and they # will both correspond to the same function. @@ -245,10 +244,10 @@ def multiaxis_indices(draw, shapes): shared_arrays1 = xps.arrays( - dtype=shared_dtypes, + dtype=shared_mutually_promotable_dtype_pairs.map(lambda pair: pair[0]), shape=shared(two_mutually_broadcastable_shapes, key="shape_pair").map(lambda pair: pair[0]), ) shared_arrays2 = xps.arrays( - dtype=promotable_dtypes(shared_dtypes), + dtype=shared_mutually_promotable_dtype_pairs.map(lambda pair: pair[1]), shape=shared(two_mutually_broadcastable_shapes, key="shape_pair").map(lambda pair: pair[1]), ) diff --git a/array_api_tests/meta_tests/test_hypothesis_helpers.py b/array_api_tests/meta_tests/test_hypothesis_helpers.py index af968135..6c39c9e6 100644 --- a/array_api_tests/meta_tests/test_hypothesis_helpers.py +++ b/array_api_tests/meta_tests/test_hypothesis_helpers.py @@ -8,21 +8,13 @@ from ..array_helpers import dtype_objects from ..hypothesis_helpers import (MAX_ARRAY_SIZE, mutually_promotable_dtype_pairs, - promotable_dtypes, shapes, - two_broadcastable_shapes, + shapes, two_broadcastable_shapes, two_mutually_broadcastable_shapes) UNDEFINED_DTYPES = any(isinstance(d, _UndefinedStub) for d in dtype_objects) pytestmark = [pytest.mark.skipif(UNDEFINED_DTYPES, reason="undefined dtypes")] -@given(promotable_dtypes(xp.uint16)) -def test_promotable_dtypes(dtype): - assert dtype in ( - xp.uint8, xp.uint16, xp.uint32, xp.uint64, xp.int8, xp.int16, xp.int32, xp.int64 - ) - - @given(mutually_promotable_dtype_pairs([xp.float32, xp.float64])) def test_mutually_promotable_dtype_pairs(pairs): assert pairs in ( From ab6b684092590a137df75cea740a6567a2b1adf8 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Thu, 23 Sep 2021 19:15:58 +0100 Subject: [PATCH 19/30] Correct fill_value values for test_full_like() + relevant assertions --- array_api_tests/test_creation_functions.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 401a0b73..adb8c9c0 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -1,6 +1,8 @@ +from hypothesis.strategies._internal.core import sampled_from from ._array_module import (asarray, arange, ceil, empty, empty_like, eye, full, full_like, equal, all, linspace, ones, ones_like, zeros, zeros_like, isnan) +from . import _array_module as xp from .array_helpers import (is_integer_dtype, dtype_ranges, assert_exactly_equal, isintegral, is_float_dtype) from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, @@ -157,9 +159,11 @@ def test_full(shape, fill_value, dtype): @composite def fill_values(draw): + # If dtype has been specified, fill_value should be inferred from it dtype = draw(shared_optional_dtypes) + # If dtype=None, fill_value can be anything - full_like should infer the dtype if dtype is None: - dtype = draw(shared_dtypes) + dtype = draw(sampled_from([xp.bool, xp.int32, xp.float32])) return draw(xps.from_dtype(dtype)) @given( @@ -168,13 +172,17 @@ def fill_values(draw): dtype=shared_optional_dtypes, ) def test_full_like(x, fill_value, dtype): - kwargs = {} if dtype is None else {'dtype': dtype} - - x_like = full_like(x, fill_value, **kwargs) + x_like = full_like(x, fill_value, dtype=dtype) if dtype is None: - # TODO: Should it actually match x.dtype? - pass + if isinstance(fill_value, bool): + assert x_like.dtype == xp.bool, f"{fill_value=}, but full_like() did not produce a boolean array - instead was {x_like.dtype}" + elif isinstance(fill_value, int): + assert x_like.dtype in (xp.int32, xp.int64), f"{fill_value=}, but full_like() did not produce a int32 or int64 array - instead was {x_like.dtype}" + elif isinstance(fill_value, float): + assert x_like.dtype in (xp.float32, xp.float64), f"{fill_value=}, but full_like() did not produce a float32 or float64 array - instead was {x_like.dtype}" + else: + raise Exception(f"Sanity check failed, indiciating a bug in the test suite. {fill_value=} - should be a bool, int or float") else: assert x_like.dtype == dtype From 2a65bfa6b22e45266ca09d97c02f8859d994499c Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Thu, 23 Sep 2021 19:39:14 +0100 Subject: [PATCH 20/30] For NumPy, xfail test_full_like --- .github/workflows/numpy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/numpy.yml b/.github/workflows/numpy.yml index 20111d49..abcddcda 100644 --- a/.github/workflows/numpy.yml +++ b/.github/workflows/numpy.yml @@ -52,6 +52,8 @@ jobs: "array_api_tests/test_signatures.py::test_function_keyword_only_args[prod]", "array_api_tests/test_signatures.py::test_function_keyword_only_args[sum]", + # https://github.com/data-apis/array-api-tests/pull/18#discussion_r715047213 + "array_api_tests/test_creation_functions.py::test_full_like" ) def pytest_collection_modifyitems(config, items): From 4c2e32ce208b6112cd019e91ea8e1f69f0a602d2 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Wed, 29 Sep 2021 11:25:56 +0100 Subject: [PATCH 21/30] Rename "mutually_promotable_dtype_pairs" back to its original name mutually_promotable_dtypes --- array_api_tests/hypothesis_helpers.py | 4 ++-- .../meta_tests/test_hypothesis_helpers.py | 6 +++--- array_api_tests/test_elementwise_functions.py | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index cb298c4a..2344d684 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -54,7 +54,7 @@ # should remove the composite wrapper, just returning sampled_from(dtype_pairs) # instead of drawing from it. @composite -def mutually_promotable_dtype_pairs(draw, dtype_objects=dtype_objects): +def mutually_promotable_dtypes(draw, dtype_objects=dtype_objects): from .test_type_promotion import dtype_mapping, promotion_table # sort for shrinking (sampled_from shrinks to the earlier elements in the # list). Give pairs of the same dtypes first, then smaller dtypes, @@ -76,7 +76,7 @@ def mutually_promotable_dtype_pairs(draw, dtype_objects=dtype_objects): return draw(sampled_from(dtype_pairs)) shared_mutually_promotable_dtype_pairs = shared( - mutually_promotable_dtype_pairs(), key="mutually_promotable_dtype_pair" + mutually_promotable_dtypes(), key="mutually_promotable_dtype_pair" ) # shared() allows us to draw either the function or the function name and they diff --git a/array_api_tests/meta_tests/test_hypothesis_helpers.py b/array_api_tests/meta_tests/test_hypothesis_helpers.py index 6c39c9e6..7e17cc38 100644 --- a/array_api_tests/meta_tests/test_hypothesis_helpers.py +++ b/array_api_tests/meta_tests/test_hypothesis_helpers.py @@ -7,7 +7,7 @@ from .._array_module import _UndefinedStub from ..array_helpers import dtype_objects from ..hypothesis_helpers import (MAX_ARRAY_SIZE, - mutually_promotable_dtype_pairs, + mutually_promotable_dtypes, shapes, two_broadcastable_shapes, two_mutually_broadcastable_shapes) @@ -15,8 +15,8 @@ pytestmark = [pytest.mark.skipif(UNDEFINED_DTYPES, reason="undefined dtypes")] -@given(mutually_promotable_dtype_pairs([xp.float32, xp.float64])) -def test_mutually_promotable_dtype_pairs(pairs): +@given(mutually_promotable_dtypes([xp.float32, xp.float64])) +def test_mutually_promotable_dtypes(pairs): assert pairs in ( (xp.float32, xp.float32), (xp.float32, xp.float64), diff --git a/array_api_tests/test_elementwise_functions.py b/array_api_tests/test_elementwise_functions.py index 856643cb..01db8882 100644 --- a/array_api_tests/test_elementwise_functions.py +++ b/array_api_tests/test_elementwise_functions.py @@ -25,7 +25,7 @@ integer_or_boolean_dtype_objects, boolean_dtype_objects, floating_dtypes, numeric_dtypes, integer_or_boolean_dtypes, - boolean_dtypes, mutually_promotable_dtype_pairs, + boolean_dtypes, mutually_promotable_dtypes, array_scalars, shared_arrays1, shared_arrays2) from .array_helpers import (assert_exactly_equal, negative, positive_mathematical_sign, @@ -50,17 +50,17 @@ integer_or_boolean_scalars = array_scalars(integer_or_boolean_dtypes) boolean_scalars = array_scalars(boolean_dtypes) -two_integer_dtypes = mutually_promotable_dtype_pairs(integer_dtype_objects) -two_floating_dtypes = mutually_promotable_dtype_pairs(floating_dtype_objects) -two_numeric_dtypes = mutually_promotable_dtype_pairs(numeric_dtype_objects) -two_integer_or_boolean_dtypes = mutually_promotable_dtype_pairs(integer_or_boolean_dtype_objects) -two_boolean_dtypes = mutually_promotable_dtype_pairs(boolean_dtype_objects) -two_any_dtypes = mutually_promotable_dtype_pairs() +two_integer_dtypes = mutually_promotable_dtypes(integer_dtype_objects) +two_floating_dtypes = mutually_promotable_dtypes(floating_dtype_objects) +two_numeric_dtypes = mutually_promotable_dtypes(numeric_dtype_objects) +two_integer_or_boolean_dtypes = mutually_promotable_dtypes(integer_or_boolean_dtype_objects) +two_boolean_dtypes = mutually_promotable_dtypes(boolean_dtype_objects) +two_any_dtypes = mutually_promotable_dtypes() @composite def two_array_scalars(draw, dtype1, dtype2): # two_dtypes should be a strategy that returns two dtypes (like - # mutually_promotable_dtype_pairs()) + # mutually_promotable_dtypes()) return draw(array_scalars(just(dtype1))), draw(array_scalars(just(dtype2))) def sanity_check(x1, x2): From 8c26f5095e8f42c4390f8dabc1157f5958d6d9a3 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Wed, 29 Sep 2021 11:40:35 +0100 Subject: [PATCH 22/30] Update Hypothesis pin 6.21.5 contains a useful patch for dtype stuff --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ab976664..59fa4e81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ pytest -hypothesis>=6.21.0 +hypothesis>=6.21.5 regex removestar From 312db6928fe66205f834ab01c8193a68a3f8c574 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Wed, 29 Sep 2021 11:47:04 +0100 Subject: [PATCH 23/30] Prematurely update test_full_like for proposed spec change See https://github.com/data-apis/array-api/pull/274 --- .github/workflows/numpy.yml | 3 --- array_api_tests/test_creation_functions.py | 25 ++++------------------ 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/.github/workflows/numpy.yml b/.github/workflows/numpy.yml index abcddcda..3a0e0325 100644 --- a/.github/workflows/numpy.yml +++ b/.github/workflows/numpy.yml @@ -51,9 +51,6 @@ jobs: "array_api_tests/test_signatures.py::test_function_positional_args[__index__]", "array_api_tests/test_signatures.py::test_function_keyword_only_args[prod]", "array_api_tests/test_signatures.py::test_function_keyword_only_args[sum]", - - # https://github.com/data-apis/array-api-tests/pull/18#discussion_r715047213 - "array_api_tests/test_creation_functions.py::test_full_like" ) def pytest_collection_modifyitems(config, items): diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index adb8c9c0..055d445e 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -1,8 +1,6 @@ -from hypothesis.strategies._internal.core import sampled_from from ._array_module import (asarray, arange, ceil, empty, empty_like, eye, full, full_like, equal, all, linspace, ones, ones_like, zeros, zeros_like, isnan) -from . import _array_module as xp from .array_helpers import (is_integer_dtype, dtype_ranges, assert_exactly_equal, isintegral, is_float_dtype) from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, @@ -10,7 +8,7 @@ scalars, xps) from hypothesis import assume, given -from hypothesis.strategies import integers, floats, one_of, none, booleans, just, shared, composite +from hypothesis.strategies import integers, floats, one_of, none, booleans, just, shared optional_dtypes = none() | shared_dtypes @@ -157,34 +155,19 @@ def test_full(shape, fill_value, dtype): else: assert all(equal(a, asarray(fill_value, **kwargs))), "full() array did not equal the fill value" -@composite -def fill_values(draw): - # If dtype has been specified, fill_value should be inferred from it - dtype = draw(shared_optional_dtypes) - # If dtype=None, fill_value can be anything - full_like should infer the dtype - if dtype is None: - dtype = draw(sampled_from([xp.bool, xp.int32, xp.float32])) - return draw(xps.from_dtype(dtype)) @given( x=xps.arrays(dtype=shared_dtypes, shape=shapes), - fill_value=fill_values(), + fill_value=shared_dtypes.flatmap(xps.from_dtype), dtype=shared_optional_dtypes, ) def test_full_like(x, fill_value, dtype): x_like = full_like(x, fill_value, dtype=dtype) if dtype is None: - if isinstance(fill_value, bool): - assert x_like.dtype == xp.bool, f"{fill_value=}, but full_like() did not produce a boolean array - instead was {x_like.dtype}" - elif isinstance(fill_value, int): - assert x_like.dtype in (xp.int32, xp.int64), f"{fill_value=}, but full_like() did not produce a int32 or int64 array - instead was {x_like.dtype}" - elif isinstance(fill_value, float): - assert x_like.dtype in (xp.float32, xp.float64), f"{fill_value=}, but full_like() did not produce a float32 or float64 array - instead was {x_like.dtype}" - else: - raise Exception(f"Sanity check failed, indiciating a bug in the test suite. {fill_value=} - should be a bool, int or float") + assert x_like.dtype == x.dtype, f"{x.dtype=}, but full_like() did not produce a {x.dtype} array - instead was {x_like.dtype}" else: - assert x_like.dtype == dtype + assert x_like.dtype == None, f"{dtype=}, but full_like() did not produce a {dtype} array - instead was {x_like.dtype}" assert x_like.shape == x.shape, "full_like() produced an array with incorrect shape" if is_float_dtype(x_like.dtype) and isnan(asarray(fill_value)): From 70099e52983534df62c13d02228fefc0278ae83a Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Wed, 29 Sep 2021 12:54:52 +0100 Subject: [PATCH 24/30] Assert `x_like.dtype == x.dtype` when dtype kwarg is `None` --- array_api_tests/test_creation_functions.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 055d445e..3311e8fe 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -97,11 +97,9 @@ def test_empty_like(x, dtype): x_like = empty_like(x, **kwargs) if dtype is None: - # TODO: Should it actually match a.dtype? - # assert is_float_dtype(x_like.dtype), "empty_like() should produce an array with the default floating point dtype" - pass + assert x_like.dtype == x.dtype, f"{x.dtype=!s}, but empty_like() did not produce a {x.dtype} array - instead was {x_like.dtype}" else: - assert x_like.dtype == dtype, "empty_like() produced an array with an incorrect dtype" + assert x_like.dtype == dtype, f"{dtype=!s}, but empty_like() did not produce a {dtype} array - instead was {x_like.dtype}" assert x_like.shape == x.shape, "empty_like() produced an array with an incorrect shape" @@ -165,9 +163,9 @@ def test_full_like(x, fill_value, dtype): x_like = full_like(x, fill_value, dtype=dtype) if dtype is None: - assert x_like.dtype == x.dtype, f"{x.dtype=}, but full_like() did not produce a {x.dtype} array - instead was {x_like.dtype}" + assert x_like.dtype == x.dtype, f"{x.dtype=!s}, but full_like() did not produce a {x.dtype} array - instead was {x_like.dtype}" else: - assert x_like.dtype == None, f"{dtype=}, but full_like() did not produce a {dtype} array - instead was {x_like.dtype}" + assert x_like.dtype == dtype, f"{dtype=!s}, but full_like() did not produce a {dtype} array - instead was {x_like.dtype}" assert x_like.shape == x.shape, "full_like() produced an array with incorrect shape" if is_float_dtype(x_like.dtype) and isnan(asarray(fill_value)): @@ -258,10 +256,9 @@ def test_ones_like(x, dtype): x_like = ones_like(x, **kwargs) if dtype is None: - # TODO: Should it actually match a.dtype? - pass + assert x_like.dtype == x.dtype, f"{x.dtype=!s}, but ones_like() did not produce a {x.dtype} array - instead was {x_like.dtype}" else: - assert x_like.dtype == dtype, "ones_like() produced an array with an incorrect dtype" + assert x_like.dtype == dtype, f"{dtype=!s}, but ones_like() did not produce a {dtype} array - instead was {x_like.dtype}" assert x_like.shape == x.shape, "ones_like() produced an array with an incorrect shape" assert all(equal(x_like, full((), ONE, dtype=x_like.dtype))), "ones_like() array did not equal 1" @@ -306,10 +303,9 @@ def test_zeros_like(x, dtype): x_like = zeros_like(x, **kwargs) if dtype is None: - # TODO: Should it actually match a.dtype? - pass + assert x_like.dtype == x.dtype, f"{x.dtype=!s}, but zeros_like() did not produce a {x.dtype} array - instead was {x_like.dtype}" else: - assert x_like.dtype == dtype, "zeros_like() produced an array with an incorrect dtype" + assert x_like.dtype == dtype, f"{dtype=!s}, but zeros_like() did not produce a {dtype} array - instead was {x_like.dtype}" assert x_like.shape == x.shape, "zeros_like() produced an array with an incorrect shape" assert all(equal(x_like, full((), ZERO, dtype=x_like.dtype))), "zeros_like() array did not equal 0" From 8b11476f43d83e90d821fb1405ac50cbf4a8d6f8 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Thu, 30 Sep 2021 16:44:55 +0100 Subject: [PATCH 25/30] Implement `kwargs` strategy, and use it in some creation tests --- array_api_tests/hypothesis_helpers.py | 9 + .../meta_tests/test_hypothesis_helpers.py | 43 +++-- array_api_tests/test_creation_functions.py | 176 ++++++++---------- 3 files changed, 114 insertions(+), 114 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 2344d684..6a13b0c0 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -251,3 +251,12 @@ def multiaxis_indices(draw, shapes): dtype=shared_mutually_promotable_dtype_pairs.map(lambda pair: pair[1]), shape=shared(two_mutually_broadcastable_shapes, key="shape_pair").map(lambda pair: pair[1]), ) + + +@composite +def kwargs(draw, **kw): + result = {} + for k, strat in kw.items(): + if draw(booleans()): + result[k] = draw(strat) + return result diff --git a/array_api_tests/meta_tests/test_hypothesis_helpers.py b/array_api_tests/meta_tests/test_hypothesis_helpers.py index 7e17cc38..a3971b3d 100644 --- a/array_api_tests/meta_tests/test_hypothesis_helpers.py +++ b/array_api_tests/meta_tests/test_hypothesis_helpers.py @@ -1,21 +1,18 @@ from math import prod import pytest -from hypothesis import given +from hypothesis import given, strategies as st, assume from .. import _array_module as xp from .._array_module import _UndefinedStub -from ..array_helpers import dtype_objects -from ..hypothesis_helpers import (MAX_ARRAY_SIZE, - mutually_promotable_dtypes, - shapes, two_broadcastable_shapes, - two_mutually_broadcastable_shapes) +from .. import array_helpers as ah +from .. import hypothesis_helpers as hh -UNDEFINED_DTYPES = any(isinstance(d, _UndefinedStub) for d in dtype_objects) +UNDEFINED_DTYPES = any(isinstance(d, _UndefinedStub) for d in ah.dtype_objects) pytestmark = [pytest.mark.skipif(UNDEFINED_DTYPES, reason="undefined dtypes")] -@given(mutually_promotable_dtypes([xp.float32, xp.float64])) +@given(hh.mutually_promotable_dtypes([xp.float32, xp.float64])) def test_mutually_promotable_dtypes(pairs): assert pairs in ( (xp.float32, xp.float32), @@ -29,22 +26,22 @@ def valid_shape(shape) -> bool: return ( all(isinstance(side, int) for side in shape) and all(side >= 0 for side in shape) - and prod(shape) < MAX_ARRAY_SIZE + and prod(shape) < hh.MAX_ARRAY_SIZE ) -@given(shapes) +@given(hh.shapes) def test_shapes(shape): assert valid_shape(shape) -@given(two_mutually_broadcastable_shapes) +@given(hh.two_mutually_broadcastable_shapes) def test_two_mutually_broadcastable_shapes(pair): for shape in pair: assert valid_shape(shape) -@given(two_broadcastable_shapes()) +@given(hh.two_broadcastable_shapes()) def test_two_broadcastable_shapes(pair): for shape in pair: assert valid_shape(shape) @@ -52,3 +49,25 @@ def test_two_broadcastable_shapes(pair): from ..test_broadcasting import broadcast_shapes assert broadcast_shapes(pair[0], pair[1]) == pair[0] + + +def test_kwargs(): + results = [] + + @given(hh.kwargs(n=st.integers(0, 10), c=st.from_regex("[a-f]"))) + def run(kw): + results.append(kw) + + run() + assert all(isinstance(kw, dict) for kw in results) + for size in [0, 1, 2]: + assert any(len(kw) == size for kw in results) + + n_results = [kw for kw in results if "n" in kw] + assert len(n_results) > 0 + assert all(isinstance(kw["n"], int) for kw in n_results) + + c_results = [kw for kw in results if "c" in kw] + assert len(c_results) > 0 + assert all(isinstance(kw["c"], str) for kw in c_results) + diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 3311e8fe..acfd1bba 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -1,11 +1,11 @@ from ._array_module import (asarray, arange, ceil, empty, empty_like, eye, full, full_like, equal, all, linspace, ones, ones_like, - zeros, zeros_like, isnan) + zeros, zeros_like, isnan, float32) from .array_helpers import (is_integer_dtype, dtype_ranges, assert_exactly_equal, isintegral, is_float_dtype) from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, shapes, sizes, sqrt_sizes, shared_dtypes, - scalars, xps) + scalars, xps, kwargs) from hypothesis import assume, given from hypothesis.strategies import integers, floats, one_of, none, booleans, just, shared @@ -74,7 +74,7 @@ def test_arange(start, stop, step, dtype): def test_empty(shape, dtype): if dtype is None: a = empty(shape) - assert is_float_dtype(a.dtype), "empty() should produce an array with the default floating point dtype" + assert is_float_dtype(a.dtype), "empty() should returned an array with the default floating point dtype" else: a = empty(shape, dtype=dtype) assert a.dtype == dtype @@ -85,23 +85,17 @@ def test_empty(shape, dtype): @given( - x=xps.arrays( - dtype=dtypes, - shape=shapes, - ), - dtype=optional_dtypes, + x=xps.arrays(dtype=xps.scalar_dtypes(), shape=shapes), + kw=kwargs(dtype=none() | xps.scalar_dtypes()) ) -def test_empty_like(x, dtype): - kwargs = {} if dtype is None else {'dtype': dtype} - - x_like = empty_like(x, **kwargs) - - if dtype is None: - assert x_like.dtype == x.dtype, f"{x.dtype=!s}, but empty_like() did not produce a {x.dtype} array - instead was {x_like.dtype}" +def test_empty_like(x, kw): + out = empty_like(x, **kw) + dtype = kw.get("dtype", None) or x.dtype + if kw.get("dtype", None) is None: + assert out.dtype == x.dtype, f"{x.dtype=!s}, but empty_like() returned an array with dtype {out.dtype}" else: - assert x_like.dtype == dtype, f"{dtype=!s}, but empty_like() did not produce a {dtype} array - instead was {x_like.dtype}" - - assert x_like.shape == x.shape, "empty_like() produced an array with an incorrect shape" + assert out.dtype == dtype, f"{dtype=!s}, but empty_like() returned an array with dtype {out.dtype}" + assert out.shape == x.shape, "empty_like() produced an array with an incorrect shape" # TODO: Use this method for all optional arguments @@ -117,7 +111,7 @@ def test_eye(n_rows, n_cols, k, dtype): else: a = eye(n_rows, n_cols, **kwargs) if dtype is None: - assert is_float_dtype(a.dtype), "eye() should produce an array with the default floating point dtype" + assert is_float_dtype(a.dtype), "eye() should returned an array with the default floating point dtype" else: assert a.dtype == dtype, "eye() did not produce the correct dtype" @@ -142,7 +136,7 @@ def test_full(shape, fill_value, dtype): if dtype is None: # TODO: Should it actually match the fill_value? - # assert a.dtype in _floating_dtypes, "eye() should produce an array with the default floating point dtype" + # assert a.dtype in _floating_dtypes, "eye() should returned an array with the default floating point dtype" pass else: assert a.dtype == dtype @@ -157,21 +151,20 @@ def test_full(shape, fill_value, dtype): @given( x=xps.arrays(dtype=shared_dtypes, shape=shapes), fill_value=shared_dtypes.flatmap(xps.from_dtype), - dtype=shared_optional_dtypes, + kw=kwargs(dtype=none() | shared_dtypes), ) -def test_full_like(x, fill_value, dtype): - x_like = full_like(x, fill_value, dtype=dtype) - - if dtype is None: - assert x_like.dtype == x.dtype, f"{x.dtype=!s}, but full_like() did not produce a {x.dtype} array - instead was {x_like.dtype}" +def test_full_like(x, fill_value, kw): + out = full_like(x, fill_value, **kw) + dtype = kw.get("dtype", None) or x.dtype + if kw.get("dtype", None) is None: + assert out.dtype == x.dtype, f"{x.dtype=!s}, but full_like() returned an array with dtype {out.dtype}" else: - assert x_like.dtype == dtype, f"{dtype=!s}, but full_like() did not produce a {dtype} array - instead was {x_like.dtype}" - - assert x_like.shape == x.shape, "full_like() produced an array with incorrect shape" - if is_float_dtype(x_like.dtype) and isnan(asarray(fill_value)): - assert all(isnan(x_like)), "full_like() array did not equal the fill value" + assert out.dtype == dtype, f"{dtype=!s}, but full_like() returned an array with dtype {out.dtype}" + assert out.shape == x.shape, "{x.shape=}, but full_like() returned an array with shape {out.shape}" + if is_float_dtype(dtype) and isnan(asarray(fill_value)): + assert all(isnan(out)), "full_like() array did not equal the fill value" else: - assert all(equal(x_like, asarray(fill_value, dtype=x_like.dtype))), "full_like() array did not equal the fill value" + assert all(equal(out, asarray(fill_value, dtype=dtype))), "full_like() array did not equal the fill value" @given(scalars(shared_dtypes, finite=True), @@ -192,11 +185,11 @@ def test_linspace(start, stop, num, dtype, endpoint): a = linspace(start, stop, num, **kwargs) if dtype is None: - assert is_float_dtype(a.dtype), "linspace() should produce an array with the default floating point dtype" + assert is_float_dtype(a.dtype), "linspace() should returned an array with the default floating point dtype" else: assert a.dtype == dtype, "linspace() did not produce the correct dtype" - assert a.shape == (num,), "linspace() did not produce an array with the correct shape" + assert a.shape == (num,), "linspace() did not returned an array with the correct shape" if endpoint in [None, True]: if num > 1: @@ -217,96 +210,75 @@ def test_linspace(start, stop, num, dtype, endpoint): # for i in range(1, num): # assert all(equal(a[i], full((), i*(stop - start)/n + start, dtype=dtype))), f"linspace() produced an array with an incorrect value at index {i}" -@given(shapes, one_of(none(), dtypes)) -def test_ones(shape, dtype): - kwargs = {} if dtype is None else {'dtype': dtype} - if dtype is None or is_float_dtype(dtype): - ONE = 1.0 + +def make_one(dtype): + if kwargs is None or is_float_dtype(dtype): + return 1.0 elif is_integer_dtype(dtype): - ONE = 1 + return 1 else: - ONE = True + return True - a = ones(shape, **kwargs) - if dtype is None: - # TODO: Should it actually match the fill_value? - # assert a.dtype in _floating_dtypes, "eye() should produce an array with the default floating point dtype" - pass +@given(shapes, kwargs(dtype=none() | xps.scalar_dtypes())) +def test_ones(shape, kw): + out = ones(shape, **kw) + dtype = kw.get("dtype", None) or float32 + if kw.get("dtype", None) is None: + assert is_float_dtype(out.dtype), "ones() returned an array with dtype {x.dtype}, but should be the default float dtype" else: - assert a.dtype == dtype - - assert a.shape == shape, "ones() produced an array with incorrect shape" - assert all(equal(a, full((), ONE, **kwargs))), "ones() array did not equal 1" + assert out.dtype == dtype, f"{dtype=!s}, but ones() returned an array with dtype {out.dtype}" + assert out.shape == shape, "ones() produced an array with incorrect shape" + assert all(equal(out, full((), make_one(dtype), dtype=dtype))), "ones() array did not equal 1" @given( x=xps.arrays(dtype=dtypes, shape=shapes), - dtype=optional_dtypes, + kw=kwargs(dtype=none() | xps.scalar_dtypes()), ) -def test_ones_like(x, dtype): - kwargs = {} if dtype is None else {'dtype': dtype} - if kwargs is None or is_float_dtype(x.dtype): - ONE = 1.0 - elif is_integer_dtype(x.dtype): - ONE = 1 +def test_ones_like(x, kw): + out = ones_like(x, **kw) + dtype = kw.get("dtype", None) or x.dtype + if kw.get("dtype", None) is None: + assert out.dtype == x.dtype, f"{x.dtype=!s}, but ones_like() returned an array with dtype {out.dtype}" else: - ONE = True + assert out.dtype == dtype, f"{dtype=!s}, but ones_like() returned an array with dtype {out.dtype}" + assert out.shape == x.shape, "{x.shape=}, but ones_like() returned an array with shape {out.shape}" + assert all(equal(out, full((), make_one(dtype), dtype=dtype))), "ones_like() array elements did not equal 1" - x_like = ones_like(x, **kwargs) - if dtype is None: - assert x_like.dtype == x.dtype, f"{x.dtype=!s}, but ones_like() did not produce a {x.dtype} array - instead was {x_like.dtype}" - else: - assert x_like.dtype == dtype, f"{dtype=!s}, but ones_like() did not produce a {dtype} array - instead was {x_like.dtype}" - - assert x_like.shape == x.shape, "ones_like() produced an array with an incorrect shape" - assert all(equal(x_like, full((), ONE, dtype=x_like.dtype))), "ones_like() array did not equal 1" - - -@given(shapes, one_of(none(), dtypes)) -def test_zeros(shape, dtype): - kwargs = {} if dtype is None else {'dtype': dtype} - if dtype is None or is_float_dtype(dtype): - ZERO = 0.0 +def make_zero(dtype): + if is_float_dtype(dtype): + return 0.0 elif is_integer_dtype(dtype): - ZERO = 0 + return 0 else: - ZERO = False + return False - a = zeros(shape, **kwargs) - if dtype is None: - # TODO: Should it actually match the fill_value? - # assert a.dtype in _floating_dtypes, "eye() should produce an array with the default floating point dtype" - pass +@given(shapes, kwargs(dtype=none() | xps.scalar_dtypes())) +def test_zeros(shape, kw): + out = zeros(shape, **kw) + dtype = kw.get("dtype", None) or float32 + if kw.get("dtype", None) is None: + assert is_float_dtype(out.dtype), "zeros() returned an array with dtype {out.dtype}, but should be the default float dtype" else: - assert a.dtype == dtype - - assert a.shape == shape, "zeros() produced an array with incorrect shape" - assert all(equal(a, full((), ZERO, **kwargs))), "zeros() array did not equal 0" + assert out.dtype == dtype, f"{dtype=!s}, but zeros() returned an array with dtype {out.dtype}" + assert out.shape == shape, "zeros() produced an array with incorrect shape" + assert all(equal(out, full((), make_zero(dtype), dtype=dtype))), "zeros() array did not equal 0" @given( x=xps.arrays(dtype=dtypes, shape=shapes), - dtype=optional_dtypes, + kw=kwargs(dtype=none() | xps.scalar_dtypes()), ) -def test_zeros_like(x, dtype): - kwargs = {} if dtype is None else {'dtype': dtype} - if dtype is None or is_float_dtype(x.dtype): - ZERO = 0.0 - elif is_integer_dtype(x.dtype): - ZERO = 0 - else: - ZERO = False - - x_like = zeros_like(x, **kwargs) - - if dtype is None: - assert x_like.dtype == x.dtype, f"{x.dtype=!s}, but zeros_like() did not produce a {x.dtype} array - instead was {x_like.dtype}" +def test_zeros_like(x, kw): + out = zeros_like(x, **kw) + dtype = kw.get("dtype", None) or x.dtype + if kw.get("dtype", None) is None: + assert out.dtype == x.dtype, f"{x.dtype=!s}, but zeros_like() returned an array with dtype {out.dtype}" else: - assert x_like.dtype == dtype, f"{dtype=!s}, but zeros_like() did not produce a {dtype} array - instead was {x_like.dtype}" - - assert x_like.shape == x.shape, "zeros_like() produced an array with an incorrect shape" - assert all(equal(x_like, full((), ZERO, dtype=x_like.dtype))), "zeros_like() array did not equal 0" + assert out.dtype == dtype, f"{dtype=!s}, but zeros_like() returned an array with dtype {out.dtype}" + assert out.shape == x.shape, "{x.shape=}, but zeros_like() returned an array with shape {out.shape}" + assert all(equal(out, full((), make_zero(dtype), dtype=out.dtype))), "zeros_like() array elements did not all equal 0" From 3f6e3303e6aa4541aa15fcc701d648c68d94be3e Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Thu, 30 Sep 2021 16:56:10 +0100 Subject: [PATCH 26/30] Fixed `test_full_like` --- array_api_tests/hypothesis_helpers.py | 2 +- .../meta_tests/test_hypothesis_helpers.py | 3 +-- array_api_tests/test_creation_functions.py | 13 ++++++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 6a13b0c0..91e9767a 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -2,11 +2,11 @@ from operator import mul from math import sqrt +from hypothesis import assume from hypothesis.strategies import (lists, integers, sampled_from, shared, floats, just, composite, one_of, none, booleans) from hypothesis.extra.array_api import make_strategies_namespace -from hypothesis import assume from .pytest_helpers import nargs from .array_helpers import (dtype_ranges, integer_dtype_objects, diff --git a/array_api_tests/meta_tests/test_hypothesis_helpers.py b/array_api_tests/meta_tests/test_hypothesis_helpers.py index a3971b3d..3a396a59 100644 --- a/array_api_tests/meta_tests/test_hypothesis_helpers.py +++ b/array_api_tests/meta_tests/test_hypothesis_helpers.py @@ -1,7 +1,7 @@ from math import prod import pytest -from hypothesis import given, strategies as st, assume +from hypothesis import given, strategies as st from .. import _array_module as xp from .._array_module import _UndefinedStub @@ -70,4 +70,3 @@ def run(kw): c_results = [kw for kw in results if "c" in kw] assert len(c_results) > 0 assert all(isinstance(kw["c"], str) for kw in c_results) - diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index acfd1bba..5fe8a636 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -8,7 +8,7 @@ scalars, xps, kwargs) from hypothesis import assume, given -from hypothesis.strategies import integers, floats, one_of, none, booleans, just, shared +from hypothesis.strategies import integers, floats, one_of, none, booleans, just, shared, composite optional_dtypes = none() | shared_dtypes @@ -148,10 +148,17 @@ def test_full(shape, fill_value, dtype): assert all(equal(a, asarray(fill_value, **kwargs))), "full() array did not equal the fill value" +@composite +def fill_values(draw): + kw = draw(shared(kwargs(dtype=none() | xps.scalar_dtypes()), key="full_like_kw")) + dtype = kw.get("dtype", None) or draw(shared_dtypes) + return draw(xps.from_dtype(dtype)) + + @given( x=xps.arrays(dtype=shared_dtypes, shape=shapes), - fill_value=shared_dtypes.flatmap(xps.from_dtype), - kw=kwargs(dtype=none() | shared_dtypes), + fill_value=fill_values(), + kw=shared(kwargs(dtype=none() | xps.scalar_dtypes()), key="full_like_kw"), ) def test_full_like(x, fill_value, kw): out = full_like(x, fill_value, **kw) From 0fb851f6a38c9c64879b0388231cb935fe7742e5 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Thu, 30 Sep 2021 18:46:46 +0100 Subject: [PATCH 27/30] Removed redundant optional_dtypes strategy --- array_api_tests/test_creation_functions.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 5fe8a636..c6ad96ea 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -11,10 +11,6 @@ from hypothesis.strategies import integers, floats, one_of, none, booleans, just, shared, composite -optional_dtypes = none() | shared_dtypes -shared_optional_dtypes = shared(optional_dtypes, key="optional_dtype") - - int_range = integers(-MAX_ARRAY_SIZE, MAX_ARRAY_SIZE) float_range = floats(-MAX_ARRAY_SIZE, MAX_ARRAY_SIZE, allow_nan=False) @@ -95,7 +91,7 @@ def test_empty_like(x, kw): assert out.dtype == x.dtype, f"{x.dtype=!s}, but empty_like() returned an array with dtype {out.dtype}" else: assert out.dtype == dtype, f"{dtype=!s}, but empty_like() returned an array with dtype {out.dtype}" - assert out.shape == x.shape, "empty_like() produced an array with an incorrect shape" + assert out.shape == x.shape, f"{x.shape=}, but empty_like() returned an array with shape {out.shape}" # TODO: Use this method for all optional arguments From 4aa49d4103ba8efb26fdeceefaa9144fd2d30cff Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Fri, 1 Oct 2021 10:54:17 +0100 Subject: [PATCH 28/30] Assert inferred dtype correctly in `test_full` --- array_api_tests/test_creation_functions.py | 79 ++++++++++++++-------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index c6ad96ea..9fd39280 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -1,6 +1,7 @@ from ._array_module import (asarray, arange, ceil, empty, empty_like, eye, full, full_like, equal, all, linspace, ones, ones_like, - zeros, zeros_like, isnan, float32) + zeros, zeros_like, isnan) +from . import _array_module as xp from .array_helpers import (is_integer_dtype, dtype_ranges, assert_exactly_equal, isintegral, is_float_dtype) from .hypothesis_helpers import (numeric_dtypes, dtypes, MAX_ARRAY_SIZE, @@ -66,18 +67,17 @@ def test_arange(start, stop, step, dtype): or step < 0 and stop <= start)): assert a.size == ceil(asarray((stop-start)/step)), "arange() produced an array of the incorrect size" -@given(one_of(shapes, sizes), one_of(none(), dtypes)) -def test_empty(shape, dtype): - if dtype is None: - a = empty(shape) - assert is_float_dtype(a.dtype), "empty() should returned an array with the default floating point dtype" +@given(shapes, kwargs(dtype=none() | shared_dtypes)) +def test_empty(shape, kw): + out = empty(shape, **kw) + dtype = kw.get("dtype", None) or xp.float64 + if kw.get("dtype", None) is None: + assert is_float_dtype(out.dtype), f"empty() returned an array with dtype {out.dtype}, but should be the default float dtype" else: - a = empty(shape, dtype=dtype) - assert a.dtype == dtype - + assert out.dtype == dtype, f"{dtype=!s}, but empty() returned an array with dtype {out.dtype}" if isinstance(shape, int): shape = (shape,) - assert a.shape == shape, "empty() produced an array with an incorrect shape" + assert out.shape == shape, f"{shape=}, but empty() returned an array with shape {out.shape}" @given( @@ -124,28 +124,47 @@ def test_eye(n_rows, n_cols, k, dtype): else: assert a[i, j] == 0, "eye() did not produce a 0 off the diagonal" -@given(shapes, scalars(shared_dtypes), one_of(none(), shared_dtypes)) -def test_full(shape, fill_value, dtype): - kwargs = {} if dtype is None else {'dtype': dtype} - a = full(shape, fill_value, **kwargs) +@composite +def full_fill_values(draw): + kw = draw(shared(kwargs(dtype=none() | xps.scalar_dtypes()), key="full_kw")) + dtype = kw.get("dtype", None) or draw(xps.scalar_dtypes()) + return draw(xps.from_dtype(dtype)) - if dtype is None: - # TODO: Should it actually match the fill_value? - # assert a.dtype in _floating_dtypes, "eye() should returned an array with the default floating point dtype" - pass - else: - assert a.dtype == dtype - assert a.shape == shape, "full() produced an array with incorrect shape" - if is_float_dtype(a.dtype) and isnan(asarray(fill_value)): - assert all(isnan(a)), "full() array did not equal the fill value" +@given( + shape=shapes, + fill_value=full_fill_values(), + kw=shared(kwargs(dtype=none() | xps.scalar_dtypes()), key="full_kw"), +) +def test_full(shape, fill_value, kw): + out = full(shape, fill_value, **kw) + if kw.get("dtype", None): + dtype = kw["dtype"] + elif isinstance(fill_value, bool): + dtype = xp.bool + elif isinstance(fill_value, int): + dtype = xp.int64 + else: + dtype = xp.float64 + if kw.get("dtype", None) is None: + if dtype == xp.float64: + assert is_float_dtype(out.dtype), f"full() returned an array with dtype {out.dtype}, but should be the default float dtype" + elif dtype == xp.int64: + assert out.dtype == xp.int32 or out.dtype == xp.int64, f"full() returned an array with dtype {out.dtype}, but should be the default integer dtype" + else: + assert out.dtype == xp.bool, f"full() returned an array with dtype {out.dtype}, but should be the bool dtype" + else: + assert out.dtype == dtype + assert out.shape == shape, f"{shape=}, but full() returned an array with shape {out.shape}" + if is_float_dtype(out.dtype) and isnan(asarray(fill_value)): + assert all(isnan(out)), "full() array did not equal the fill value" else: - assert all(equal(a, asarray(fill_value, **kwargs))), "full() array did not equal the fill value" + assert all(equal(out, asarray(fill_value, dtype=dtype))), "full() array did not equal the fill value" @composite -def fill_values(draw): +def full_like_fill_values(draw): kw = draw(shared(kwargs(dtype=none() | xps.scalar_dtypes()), key="full_like_kw")) dtype = kw.get("dtype", None) or draw(shared_dtypes) return draw(xps.from_dtype(dtype)) @@ -153,7 +172,7 @@ def fill_values(draw): @given( x=xps.arrays(dtype=shared_dtypes, shape=shapes), - fill_value=fill_values(), + fill_value=full_like_fill_values(), kw=shared(kwargs(dtype=none() | xps.scalar_dtypes()), key="full_like_kw"), ) def test_full_like(x, fill_value, kw): @@ -226,12 +245,12 @@ def make_one(dtype): @given(shapes, kwargs(dtype=none() | xps.scalar_dtypes())) def test_ones(shape, kw): out = ones(shape, **kw) - dtype = kw.get("dtype", None) or float32 + dtype = kw.get("dtype", None) or xp.float64 if kw.get("dtype", None) is None: - assert is_float_dtype(out.dtype), "ones() returned an array with dtype {x.dtype}, but should be the default float dtype" + assert is_float_dtype(out.dtype), f"ones() returned an array with dtype {out.dtype}, but should be the default float dtype" else: assert out.dtype == dtype, f"{dtype=!s}, but ones() returned an array with dtype {out.dtype}" - assert out.shape == shape, "ones() produced an array with incorrect shape" + assert out.shape == shape, f"{shape=}, but empty() returned an array with shape {out.shape}" assert all(equal(out, full((), make_one(dtype), dtype=dtype))), "ones() array did not equal 1" @@ -262,7 +281,7 @@ def make_zero(dtype): @given(shapes, kwargs(dtype=none() | xps.scalar_dtypes())) def test_zeros(shape, kw): out = zeros(shape, **kw) - dtype = kw.get("dtype", None) or float32 + dtype = kw.get("dtype", None) or xp.float64 if kw.get("dtype", None) is None: assert is_float_dtype(out.dtype), "zeros() returned an array with dtype {out.dtype}, but should be the default float dtype" else: From afc7822cb864417737c5bd3ed688373df1189053 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Fri, 1 Oct 2021 10:57:35 +0100 Subject: [PATCH 29/30] Use `math.isnan` for checking `fill_value` --- array_api_tests/test_creation_functions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 9fd39280..7edffde6 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -1,3 +1,5 @@ +import math + from ._array_module import (asarray, arange, ceil, empty, empty_like, eye, full, full_like, equal, all, linspace, ones, ones_like, zeros, zeros_like, isnan) @@ -157,7 +159,7 @@ def test_full(shape, fill_value, kw): else: assert out.dtype == dtype assert out.shape == shape, f"{shape=}, but full() returned an array with shape {out.shape}" - if is_float_dtype(out.dtype) and isnan(asarray(fill_value)): + if is_float_dtype(out.dtype) and math.isnan(fill_value): assert all(isnan(out)), "full() array did not equal the fill value" else: assert all(equal(out, asarray(fill_value, dtype=dtype))), "full() array did not equal the fill value" @@ -183,7 +185,7 @@ def test_full_like(x, fill_value, kw): else: assert out.dtype == dtype, f"{dtype=!s}, but full_like() returned an array with dtype {out.dtype}" assert out.shape == x.shape, "{x.shape=}, but full_like() returned an array with shape {out.shape}" - if is_float_dtype(dtype) and isnan(asarray(fill_value)): + if is_float_dtype(dtype) and math.isnan(fill_value): assert all(isnan(out)), "full_like() array did not equal the fill value" else: assert all(equal(out, asarray(fill_value, dtype=dtype))), "full_like() array did not equal the fill value" From 10cb57ac42570f2d9d6f795b8303ab6cde440589 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Fri, 1 Oct 2021 18:34:59 +0100 Subject: [PATCH 30/30] Fix assertion statements --- array_api_tests/test_creation_functions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 7edffde6..b4566902 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -109,7 +109,7 @@ def test_eye(n_rows, n_cols, k, dtype): else: a = eye(n_rows, n_cols, **kwargs) if dtype is None: - assert is_float_dtype(a.dtype), "eye() should returned an array with the default floating point dtype" + assert is_float_dtype(a.dtype), "eye() should return an array with the default floating point dtype" else: assert a.dtype == dtype, "eye() did not produce the correct dtype" @@ -209,11 +209,11 @@ def test_linspace(start, stop, num, dtype, endpoint): a = linspace(start, stop, num, **kwargs) if dtype is None: - assert is_float_dtype(a.dtype), "linspace() should returned an array with the default floating point dtype" + assert is_float_dtype(a.dtype), "linspace() should return an array with the default floating point dtype" else: assert a.dtype == dtype, "linspace() did not produce the correct dtype" - assert a.shape == (num,), "linspace() did not returned an array with the correct shape" + assert a.shape == (num,), "linspace() did not return an array with the correct shape" if endpoint in [None, True]: if num > 1: