From 7ae4b736f4fa332963cfda7324d859a99bf76a40 Mon Sep 17 00:00:00 2001 From: Matthew Barber Date: Mon, 18 Oct 2021 16:09:01 +0100 Subject: [PATCH 01/24] Rudimentary where type promotion test --- array_api_tests/dtype_helpers.py | 6 ++++++ array_api_tests/test_type_promotion.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/array_api_tests/dtype_helpers.py b/array_api_tests/dtype_helpers.py index a60abef1..f92f0cd7 100644 --- a/array_api_tests/dtype_helpers.py +++ b/array_api_tests/dtype_helpers.py @@ -163,6 +163,7 @@ class MinMax(NamedTuple): func_in_dtypes = { + # elementwise 'abs': numeric_dtypes, 'acos': float_dtypes, 'acosh': float_dtypes, @@ -219,10 +220,13 @@ class MinMax(NamedTuple): 'tan': float_dtypes, 'tanh': float_dtypes, 'trunc': numeric_dtypes, + # searching + 'where': all_dtypes, } func_returns_bool = { + # elementwise 'abs': False, 'acos': False, 'acosh': False, @@ -279,6 +283,8 @@ class MinMax(NamedTuple): 'tan': False, 'tanh': False, 'trunc': False, + # searching + 'where': False, } diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index ea4e6eb8..a81d1e29 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -112,6 +112,26 @@ def test_func_promotion(func_name, in_dtypes, out_dtype, data): assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' +promotion_table_params: List[Tuple[Tuple[DT, DT], DT]] = [] +for (dtype1, dtype2), promoted_dtype in dh.promotion_table.items(): + p = pytest.param( + (dtype1, dtype2), + promoted_dtype, + id=make_id('', (dtype1, dtype2), promoted_dtype), + ) + promotion_table_params.append(p) + + +@pytest.mark.parametrize('in_dtypes, out_dtype', promotion_table_params) +@given(shapes=hh.mutually_broadcastable_shapes(3), data=st.data()) +def test_where(in_dtypes, out_dtype, shapes, data): + x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') + x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') + cond = data.draw(xps.arrays(dtype=xp.bool, shape=shapes[2]), label='condition') + out = xp.where(cond, x1, x2) + assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + + op_params: List[Tuple[str, str, Tuple[DT, ...], DT]] = [] op_to_symbol = {**dh.unary_op_to_symbol, **dh.binary_op_to_symbol} for op, symbol in op_to_symbol.items(): From cbc2f26b66b93fb43aa28239a042134066dc7be3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 19 Oct 2021 12:04:11 +0100 Subject: [PATCH 02/24] Rudimentary `test_result_type`, library-agnostic `dh.result_type()` --- array_api_tests/dtype_helpers.py | 9 +++++++++ array_api_tests/test_type_promotion.py | 22 ++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/array_api_tests/dtype_helpers.py b/array_api_tests/dtype_helpers.py index f92f0cd7..ce6608a8 100644 --- a/array_api_tests/dtype_helpers.py +++ b/array_api_tests/dtype_helpers.py @@ -148,6 +148,15 @@ class MinMax(NamedTuple): } +def result_type(*dtypes): + if len(dtypes) < 2: + raise ValueError() + result = promotion_table[dtypes[0], dtypes[1]] + for i in range(2, len(dtypes)): + result = promotion_table[result, dtypes[i]] + return result + + dtype_nbits = { **{d: 8 for d in [xp.int8, xp.uint8]}, **{d: 16 for d in [xp.int16, xp.uint16]}, diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index a81d1e29..26ae52e1 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -17,6 +17,24 @@ from .pytest_helpers import nargs +DT = Type +ScalarType = Union[Type[bool], Type[int], Type[float]] + + +multi_promotable_dtypes: st.SearchStrategy[Tuple[DT, ...]] = st.one_of( + st.lists(st.just(xp.bool), min_size=2), + st.lists(st.sampled_from(dh.all_int_dtypes), min_size=2).filter( + lambda l: not (xp.uint64 in l and any(d in dh.int_dtypes for d in l)) + ), + st.lists(st.sampled_from(dh.float_dtypes), min_size=2), +).map(tuple) + + +@given(multi_promotable_dtypes) +def test_result_type(dtypes): + assert xp.result_type(*dtypes) == dh.result_type(*dtypes) + + bitwise_shift_funcs = [ 'bitwise_left_shift', 'bitwise_right_shift', @@ -27,10 +45,6 @@ ] -DT = Type -ScalarType = Union[Type[bool], Type[int], Type[float]] - - # We apply filters to xps.arrays() so we don't generate array elements that # are erroneous or undefined for a function/operator. filters = defaultdict( From 8ce99e3bba6f0c99c5df1866696bf1a52dc76ede Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 19 Oct 2021 12:56:52 +0100 Subject: [PATCH 03/24] Rudimentary `test_meshgrid` --- array_api_tests/test_type_promotion.py | 46 ++++++++++++++++++++------ 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 26ae52e1..b9f9928d 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -21,18 +21,44 @@ ScalarType = Union[Type[bool], Type[int], Type[float]] -multi_promotable_dtypes: st.SearchStrategy[Tuple[DT, ...]] = st.one_of( - st.lists(st.just(xp.bool), min_size=2), - st.lists(st.sampled_from(dh.all_int_dtypes), min_size=2).filter( - lambda l: not (xp.uint64 in l and any(d in dh.int_dtypes for d in l)) - ), - st.lists(st.sampled_from(dh.float_dtypes), min_size=2), -).map(tuple) +def multi_promotable_dtypes( + allow_bool: bool = True, +) -> st.SearchStrategy[Tuple[DT, ...]]: + strats = [ + st.lists(st.sampled_from(dh.all_int_dtypes), min_size=2).filter( + lambda l: not (xp.uint64 in l and any(d in dh.int_dtypes for d in l)) + ), + st.lists(st.sampled_from(dh.float_dtypes), min_size=2), + ] + if allow_bool: + strats.append(st.lists(st.just(xp.bool), min_size=2)) + return st.one_of(strats).map(tuple) + + +@given(multi_promotable_dtypes()) +def test_result_type(dtypes): + out = xp.result_type(*dtypes) + expected = dh.result_type(*dtypes) + assert out == expected, f'{out=!s}, but should be {expected}' -@given(multi_promotable_dtypes) -def test_result_type(dtypes): - assert xp.result_type(*dtypes) == dh.result_type(*dtypes) +@given( + dtypes=multi_promotable_dtypes(allow_bool=False), + kw=hh.kwargs(indexing=st.sampled_from(['xy', 'ij'])), + data=st.data(), +) +def test_meshgrid(dtypes, kw, data): + arrays = [] + shapes = data.draw(hh.mutually_broadcastable_shapes(len(dtypes)), label='shapes') + for i, (dtype, shape) in enumerate(zip(dtypes, shapes), 1): + x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') + arrays.append(x) + out = xp.meshgrid(*arrays, **kw) + expected = dh.result_type(*dtypes) + for i in range(len(out)): + assert ( + out[i].dtype == expected + ), f'out[{i}]={out[i].dtype}, but should be {expected}' bitwise_shift_funcs = [ From 705bfd1685cb3a08410c0aefac379d92368ac386 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 19 Oct 2021 13:28:24 +0100 Subject: [PATCH 04/24] Make `hh.shapes` a wrapper function, rudimentary `test_concat` --- array_api_tests/hypothesis_helpers.py | 18 ++++--- .../meta/test_hypothesis_helpers.py | 2 +- array_api_tests/test_broadcasting.py | 2 +- array_api_tests/test_creation_functions.py | 16 +++--- array_api_tests/test_elementwise_functions.py | 52 +++++++++---------- array_api_tests/test_indexing.py | 4 +- array_api_tests/test_linalg.py | 22 ++++---- array_api_tests/test_type_promotion.py | 28 ++++++++-- 8 files changed, 85 insertions(+), 59 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 96328720..6f2d8d72 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -113,15 +113,21 @@ def tuples(elements, *, min_size=0, max_size=None, unique_by=None, unique=False) # Use this to avoid memory errors with NumPy. # See https://github.com/numpy/numpy/issues/15753 -shapes = xps.array_shapes(min_dims=0, min_side=0).filter( - lambda shape: prod(i for i in shape if i) < MAX_ARRAY_SIZE -) +def shapes(**kw): + if 'min_dims' not in kw.keys(): + kw['min_dims'] = 0 + if 'min_side' not in kw.keys(): + kw['min_side'] = 0 + return xps.array_shapes(**kw).filter( + lambda shape: prod(i for i in shape if i) < MAX_ARRAY_SIZE + ) + one_d_shapes = xps.array_shapes(min_dims=1, max_dims=1, min_side=0, max_side=SQRT_MAX_ARRAY_SIZE) # Matrix shapes assume stacks of matrices @composite -def matrix_shapes(draw, stack_shapes=shapes): +def matrix_shapes(draw, stack_shapes=shapes()): stack_shape = draw(stack_shapes) mat_shape = draw(xps.array_shapes(max_dims=2, min_dims=2)) shape = stack_shape + mat_shape @@ -164,13 +170,13 @@ def positive_definite_matrices(draw, dtypes=xps.floating_dtypes()): # using something like # https://github.com/scikit-learn/scikit-learn/blob/844b4be24/sklearn/datasets/_samples_generator.py#L1351. n = draw(integers(0)) - shape = draw(shapes) + (n, n) + shape = draw(shapes()) + (n, n) assume(prod(i for i in shape if i) < MAX_ARRAY_SIZE) dtype = draw(dtypes) return broadcast_to(eye(n, dtype=dtype), shape) @composite -def invertible_matrices(draw, dtypes=xps.floating_dtypes(), stack_shapes=shapes): +def invertible_matrices(draw, dtypes=xps.floating_dtypes(), stack_shapes=shapes()): # For now, just generate stacks of diagonal matrices. n = draw(integers(0, SQRT_MAX_ARRAY_SIZE),) stack_shape = draw(stack_shapes) diff --git a/array_api_tests/meta/test_hypothesis_helpers.py b/array_api_tests/meta/test_hypothesis_helpers.py index 93a63f8e..0ba43409 100644 --- a/array_api_tests/meta/test_hypothesis_helpers.py +++ b/array_api_tests/meta/test_hypothesis_helpers.py @@ -32,7 +32,7 @@ def valid_shape(shape) -> bool: ) -@given(hh.shapes) +@given(hh.shapes()) def test_shapes(shape): assert valid_shape(shape) diff --git a/array_api_tests/test_broadcasting.py b/array_api_tests/test_broadcasting.py index 1a1c9c47..d951d48f 100644 --- a/array_api_tests/test_broadcasting.py +++ b/array_api_tests/test_broadcasting.py @@ -110,7 +110,7 @@ def test_broadcast_shapes_explicit_spec(): @pytest.mark.parametrize('func_name', [i for i in elementwise_functions.__all__ if nargs(i) > 1]) -@given(shape1=shapes, shape2=shapes, data=data()) +@given(shape1=shapes(), shape2=shapes(), data=data()) def test_broadcasting_hypothesis(func_name, shape1, shape2, data): # Internal consistency checks assert nargs(func_name) == 2 diff --git a/array_api_tests/test_creation_functions.py b/array_api_tests/test_creation_functions.py index 58575a8a..46587d68 100644 --- a/array_api_tests/test_creation_functions.py +++ b/array_api_tests/test_creation_functions.py @@ -70,7 +70,7 @@ 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(shapes, kwargs(dtype=none() | shared_dtypes)) +@given(shapes(), kwargs(dtype=none() | shared_dtypes)) def test_empty(shape, kw): out = empty(shape, **kw) dtype = kw.get("dtype", None) or xp.float64 @@ -84,7 +84,7 @@ def test_empty(shape, kw): @given( - x=xps.arrays(dtype=xps.scalar_dtypes(), shape=shapes), + x=xps.arrays(dtype=xps.scalar_dtypes(), shape=shapes()), kw=kwargs(dtype=none() | xps.scalar_dtypes()) ) def test_empty_like(x, kw): @@ -146,7 +146,7 @@ def full_fill_values(draw): @given( - shape=shapes, + shape=shapes(), fill_value=full_fill_values(), kw=shared(kwargs(dtype=none() | xps.scalar_dtypes()), key="full_kw"), ) @@ -184,7 +184,7 @@ def full_like_fill_values(draw): @given( - x=xps.arrays(dtype=shared_dtypes, shape=shapes), + x=xps.arrays(dtype=shared_dtypes, shape=shapes()), fill_value=full_like_fill_values(), kw=shared(kwargs(dtype=none() | xps.scalar_dtypes()), key="full_like_kw"), ) @@ -255,7 +255,7 @@ def make_one(dtype): return True -@given(shapes, kwargs(dtype=none() | xps.scalar_dtypes())) +@given(shapes(), kwargs(dtype=none() | xps.scalar_dtypes())) def test_ones(shape, kw): out = ones(shape, **kw) dtype = kw.get("dtype", None) or xp.float64 @@ -268,7 +268,7 @@ def test_ones(shape, kw): @given( - x=xps.arrays(dtype=dtypes, shape=shapes), + x=xps.arrays(dtype=dtypes, shape=shapes()), kw=kwargs(dtype=none() | xps.scalar_dtypes()), ) def test_ones_like(x, kw): @@ -291,7 +291,7 @@ def make_zero(dtype): return False -@given(shapes, kwargs(dtype=none() | xps.scalar_dtypes())) +@given(shapes(), kwargs(dtype=none() | xps.scalar_dtypes())) def test_zeros(shape, kw): out = zeros(shape, **kw) dtype = kw.get("dtype", None) or xp.float64 @@ -304,7 +304,7 @@ def test_zeros(shape, kw): @given( - x=xps.arrays(dtype=dtypes, shape=shapes), + x=xps.arrays(dtype=dtypes, shape=shapes()), kw=kwargs(dtype=none() | xps.scalar_dtypes()), ) def test_zeros_like(x, kw): diff --git a/array_api_tests/test_elementwise_functions.py b/array_api_tests/test_elementwise_functions.py index ee714458..d4a52ac4 100644 --- a/array_api_tests/test_elementwise_functions.py +++ b/array_api_tests/test_elementwise_functions.py @@ -49,7 +49,7 @@ def sanity_check(x1, x2): except ValueError: raise RuntimeError("Error in test generation (probably a bug in the test suite") -@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes())) def test_abs(x): if dh.is_int_dtype(x.dtype): minval = dh.dtype_ranges[x.dtype][0] @@ -66,7 +66,7 @@ def test_abs(x): # abs(x) = x for x >= 0 ah.assert_exactly_equal(a[ah.logical_not(less_zero)], x[ah.logical_not(less_zero)]) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_acos(x): a = xp.acos(x) ONE = ah.one(x.shape, x.dtype) @@ -80,7 +80,7 @@ def test_acos(x): # nan, which is already tested in the special cases. ah.assert_exactly_equal(domain, codomain) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_acosh(x): a = xp.acosh(x) ONE = ah.one(x.shape, x.dtype) @@ -102,7 +102,7 @@ def test_add(x1, x2): ah.assert_exactly_equal(a, b) # TODO: Test that add is actually addition -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_asin(x): a = xp.asin(x) ONE = ah.one(x.shape, x.dtype) @@ -113,7 +113,7 @@ def test_asin(x): # mapped to nan, which is already tested in the special cases. ah.assert_exactly_equal(domain, codomain) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_asinh(x): a = xp.asinh(x) INFINITY = ah.infinity(x.shape, x.dtype) @@ -123,7 +123,7 @@ def test_asinh(x): # mapped to nan, which is already tested in the special cases. ah.assert_exactly_equal(domain, codomain) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_atan(x): a = xp.atan(x) INFINITY = ah.infinity(x.shape, x.dtype) @@ -170,7 +170,7 @@ def test_atan2(x1, x2): ah.assert_exactly_equal(ah.logical_or(ah.logical_and(negx1, posx2), ah.logical_and(negx1, negx2)), nega) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_atanh(x): a = xp.atanh(x) ONE = ah.one(x.shape, x.dtype) @@ -230,7 +230,7 @@ def test_bitwise_left_shift(x1, x2): vals_shift = ah.int_to_dtype(vals_shift, dh.dtype_nbits[out.dtype], dh.dtype_signed[out.dtype]) assert vals_shift == res -@given(xps.arrays(dtype=hh.integer_or_boolean_dtypes, shape=hh.shapes)) +@given(xps.arrays(dtype=hh.integer_or_boolean_dtypes, shape=hh.shapes())) def test_bitwise_invert(x): out = xp.bitwise_invert(x) # Compare against the Python ~ operator. @@ -322,7 +322,7 @@ def test_bitwise_xor(x1, x2): vals_xor = ah.int_to_dtype(vals_xor, dh.dtype_nbits[out.dtype], dh.dtype_signed[out.dtype]) assert vals_xor == res -@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes())) def test_ceil(x): # This test is almost identical to test_floor() a = xp.ceil(x) @@ -333,7 +333,7 @@ def test_ceil(x): integers = ah.isintegral(x) ah.assert_exactly_equal(a[integers], x[integers]) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_cos(x): a = xp.cos(x) ONE = ah.one(x.shape, x.dtype) @@ -344,7 +344,7 @@ def test_cos(x): # to nan, which is already tested in the special cases. ah.assert_exactly_equal(domain, codomain) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_cosh(x): a = xp.cosh(x) INFINITY = ah.infinity(x.shape, x.dtype) @@ -379,7 +379,7 @@ def test_equal(x1, x2): # First we broadcast the arrays so that they can be indexed uniformly. # TODO: it should be possible to skip this step if we instead generate - # indices to x1 and x2 that correspond to the broadcasted hh.shapes. This + # indices to x1 and x2 that correspond to the broadcasted shapes. This # would avoid the dependence in this test on broadcast_to(). shape = broadcast_shapes(x1.shape, x2.shape) _x1 = xp.broadcast_to(x1, shape) @@ -414,7 +414,7 @@ def test_equal(x1, x2): assert aidx.shape == x1idx.shape == x2idx.shape assert bool(aidx) == (scalar_func(x1idx) == scalar_func(x2idx)) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_exp(x): a = xp.exp(x) INFINITY = ah.infinity(x.shape, x.dtype) @@ -425,7 +425,7 @@ def test_exp(x): # mapped to nan, which is already tested in the special cases. ah.assert_exactly_equal(domain, codomain) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_expm1(x): a = xp.expm1(x) INFINITY = ah.infinity(x.shape, x.dtype) @@ -436,7 +436,7 @@ def test_expm1(x): # mapped to nan, which is already tested in the special cases. ah.assert_exactly_equal(domain, codomain) -@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes())) def test_floor(x): # This test is almost identical to test_ceil a = xp.floor(x) @@ -529,7 +529,7 @@ def test_greater_equal(x1, x2): assert aidx.shape == x1idx.shape == x2idx.shape assert bool(aidx) == (scalar_func(x1idx) >= scalar_func(x2idx)) -@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes())) def test_isfinite(x): a = ah.isfinite(x) TRUE = ah.true(x.shape) @@ -545,7 +545,7 @@ def test_isfinite(x): s = float(x[idx]) assert bool(a[idx]) == math.isfinite(s) -@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes())) def test_isinf(x): a = xp.isinf(x) FALSE = ah.false(x.shape) @@ -560,7 +560,7 @@ def test_isinf(x): s = float(x[idx]) assert bool(a[idx]) == math.isinf(s) -@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes())) def test_isnan(x): a = ah.isnan(x) FALSE = ah.false(x.shape) @@ -633,7 +633,7 @@ def test_less_equal(x1, x2): assert aidx.shape == x1idx.shape == x2idx.shape assert bool(aidx) == (scalar_func(x1idx) <= scalar_func(x2idx)) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_log(x): a = xp.log(x) INFINITY = ah.infinity(x.shape, x.dtype) @@ -644,7 +644,7 @@ def test_log(x): # mapped to nan, which is already tested in the special cases. ah.assert_exactly_equal(domain, codomain) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_log1p(x): a = xp.log1p(x) INFINITY = ah.infinity(x.shape, x.dtype) @@ -655,7 +655,7 @@ def test_log1p(x): # mapped to nan, which is already tested in the special cases. ah.assert_exactly_equal(domain, codomain) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_log2(x): a = xp.log2(x) INFINITY = ah.infinity(x.shape, x.dtype) @@ -666,7 +666,7 @@ def test_log2(x): # mapped to nan, which is already tested in the special cases. ah.assert_exactly_equal(domain, codomain) -@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.floating_dtypes(), shape=hh.shapes())) def test_log10(x): a = xp.log10(x) INFINITY = ah.infinity(x.shape, x.dtype) @@ -698,7 +698,7 @@ def test_logical_and(x1, x2): for idx in ah.ndindex(shape): assert a[idx] == (bool(_x1[idx]) and bool(_x2[idx])) -@given(xps.arrays(dtype=xp.bool, shape=hh.shapes)) +@given(xps.arrays(dtype=xp.bool, shape=hh.shapes())) def test_logical_not(x): a = ah.logical_not(x) @@ -740,7 +740,7 @@ def test_multiply(x1, x2): # multiply is commutative ah.assert_exactly_equal(a, b) -@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes())) def test_negative(x): out = ah.negative(x) @@ -790,7 +790,7 @@ def test_not_equal(x1, x2): assert bool(aidx) == (scalar_func(x1idx) != scalar_func(x2idx)) -@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes())) def test_positive(x): out = xp.positive(x) # Positive does nothing @@ -817,7 +817,7 @@ def test_remainder(x1, x2): not_nan = ah.logical_not(ah.logical_or(ah.isnan(out), ah.isnan(x2))) ah.assert_same_sign(out[not_nan], x2[not_nan]) -@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes)) +@given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes())) def test_round(x): a = xp.round(x) diff --git a/array_api_tests/test_indexing.py b/array_api_tests/test_indexing.py index 54467633..9d3a4a88 100644 --- a/array_api_tests/test_indexing.py +++ b/array_api_tests/test_indexing.py @@ -58,8 +58,8 @@ def test_slicing(size, s): for i in range(len(sliced_list)): assert sliced_array[i] == sliced_list[i], "Slice index did not give the same elements as slicing an equivalent Python list" -@given(shared(shapes, key='array_shapes'), - multiaxis_indices(shapes=shared(shapes, key='array_shapes'))) +@given(shared(shapes(), key='array_shapes'), + multiaxis_indices(shapes=shared(shapes(), key='array_shapes'))) def test_multiaxis_indexing(shape, idx): # NOTE: Out of bounds indices (both integer and slices) are out of scope # for the spec. If you get a (valid) out of bounds error, it indicates a diff --git a/array_api_tests/test_linalg.py b/array_api_tests/test_linalg.py index 421eeacd..c13dd909 100644 --- a/array_api_tests/test_linalg.py +++ b/array_api_tests/test_linalg.py @@ -105,7 +105,7 @@ def cross_args(draw, dtype_objects=dh.numeric_dtypes): in the drawn axis. """ - shape = list(draw(shapes)) + shape = list(draw(shapes())) size = len(shape) assume(size > 0) @@ -297,7 +297,7 @@ def test_matmul(x1, x2): @pytest.mark.xp_extension('linalg') @given( - x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes), + x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), kw=kwargs(axis=todo, keepdims=todo, ord=todo) ) def test_matrix_norm(x, kw): @@ -329,7 +329,7 @@ def test_matrix_power(x, n): @pytest.mark.xp_extension('linalg') @given( - x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes), + x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), kw=kwargs(rtol=todo) ) def test_matrix_rank(x, kw): @@ -379,7 +379,7 @@ def test_outer(x1, x2): @pytest.mark.xp_extension('linalg') @given( - x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes), + x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), kw=kwargs(rtol=todo) ) def test_pinv(x, kw): @@ -523,15 +523,15 @@ def test_svd(x, kw): @pytest.mark.xp_extension('linalg') @given( - x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes), + x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), ) def test_svdvals(x): # res = linalg.svdvals(x) pass @given( - x1=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes), - x2=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes), + x1=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), + x2=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), kw=kwargs(axes=todo) ) def test_tensordot(x1, x2, kw): @@ -540,7 +540,7 @@ def test_tensordot(x1, x2, kw): @pytest.mark.xp_extension('linalg') @given( - x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes), + x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), kw=kwargs(offset=todo) ) def test_trace(x, kw): @@ -548,8 +548,8 @@ def test_trace(x, kw): pass @given( - x1=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes), - x2=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes), + x1=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), + x2=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), kw=kwargs(axis=todo) ) def test_vecdot(x1, x2, kw): @@ -558,7 +558,7 @@ def test_vecdot(x1, x2, kw): @pytest.mark.xp_extension('linalg') @given( - x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes), + x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), kw=kwargs(axis=todo, keepdims=todo, ord=todo) ) def test_vector_norm(x, kw): diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index b9f9928d..27b4dd55 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -61,6 +61,22 @@ def test_meshgrid(dtypes, kw, data): ), f'out[{i}]={out[i].dtype}, but should be {expected}' +@given( + shape=hh.shapes(min_dims=1), + dtypes=multi_promotable_dtypes(allow_bool=False), + kw=hh.kwargs(axis=st.none() | st.just(0)), + data=st.data(), +) +def test_concat(shape, dtypes, kw, data): + arrays = [] + for i, dtype in enumerate(dtypes, 1): + x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') + arrays.append(x) + out = xp.concat(arrays, **kw) + expected = dh.result_type(*dtypes) + assert out.dtype == expected, f'{out.dtype=!s}, but should be {expected}' + + bitwise_shift_funcs = [ 'bitwise_left_shift', 'bitwise_right_shift', @@ -132,7 +148,8 @@ def test_func_promotion(func_name, in_dtypes, out_dtype, data): x_filter = filters[func_name] if len(in_dtypes) == 1: x = data.draw( - xps.arrays(dtype=in_dtypes[0], shape=hh.shapes).filter(x_filter), label='x' + xps.arrays(dtype=in_dtypes[0], shape=hh.shapes()).filter(x_filter), + label='x', ) out = func(x) else: @@ -220,7 +237,8 @@ def test_op_promotion(op, expr, in_dtypes, out_dtype, data): x_filter = filters[op] if len(in_dtypes) == 1: x = data.draw( - xps.arrays(dtype=in_dtypes[0], shape=hh.shapes).filter(x_filter), label='x' + xps.arrays(dtype=in_dtypes[0], shape=hh.shapes()).filter(x_filter), + label='x', ) out = eval(expr, {'x': x}) else: @@ -305,7 +323,7 @@ def test_op_scalar_promotion(op, expr, in_dtype, in_stype, out_dtype, data): kw = {k: in_stype is float for k in ('allow_nan', 'allow_infinity')} s = data.draw(xps.from_dtype(in_dtype, **kw).map(in_stype), label='scalar') x = data.draw( - xps.arrays(dtype=in_dtype, shape=hh.shapes).filter(x_filter), label='x' + xps.arrays(dtype=in_dtype, shape=hh.shapes()).filter(x_filter), label='x' ) try: out = eval(expr, {'x': x, 's': s}) @@ -336,7 +354,9 @@ def test_inplace_op_scalar_promotion(op, expr, dtype, in_stype, data): x_filter = filters[op] kw = {k: in_stype is float for k in ('allow_nan', 'allow_infinity')} s = data.draw(xps.from_dtype(dtype, **kw).map(in_stype), label='scalar') - x = data.draw(xps.arrays(dtype=dtype, shape=hh.shapes).filter(x_filter), label='x') + x = data.draw( + xps.arrays(dtype=dtype, shape=hh.shapes()).filter(x_filter), label='x' + ) locals_ = {'x': x, 's': s} try: exec(expr, locals_) From 59364aa66cbffbedcee48db85faa004d8a390747 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 19 Oct 2021 16:05:20 +0100 Subject: [PATCH 05/24] Rudimentary `test_stack` --- array_api_tests/test_type_promotion.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 27b4dd55..51d32410 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -77,6 +77,22 @@ def test_concat(shape, dtypes, kw, data): assert out.dtype == expected, f'{out.dtype=!s}, but should be {expected}' +@given( + shape=hh.shapes(), + dtypes=multi_promotable_dtypes(), + kw=hh.kwargs(axis=st.just(0)), + data=st.data(), +) +def test_stack(shape, dtypes, kw, data): + arrays = [] + for i, dtype in enumerate(dtypes, 1): + x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') + arrays.append(x) + out = xp.stack(arrays, **kw) + expected = dh.result_type(*dtypes) + assert out.dtype == expected, f'{out.dtype=!s}, but should be {expected}' + + bitwise_shift_funcs = [ 'bitwise_left_shift', 'bitwise_right_shift', From 3324f444b8aebb95282a09b89bf106e10030fdcd Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 19 Oct 2021 16:27:48 +0100 Subject: [PATCH 06/24] Rudimentary `test_matmul` `numpy.array_api.matmul` currently doesn't support broadcastable shapes --- array_api_tests/hypothesis_helpers.py | 6 ++++-- array_api_tests/test_type_promotion.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 6f2d8d72..149156a4 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -141,9 +141,11 @@ def matrix_shapes(draw, stack_shapes=shapes()): elements=dict(allow_nan=False, allow_infinity=False)) -def mutually_broadcastable_shapes(num_shapes: int) -> SearchStrategy[Tuple[Tuple]]: +def mutually_broadcastable_shapes( + num_shapes: int, **kw +) -> SearchStrategy[Tuple[Tuple[int, ...], ...]]: return ( - xps.mutually_broadcastable_shapes(num_shapes) + xps.mutually_broadcastable_shapes(num_shapes, **kw) .map(lambda BS: BS.input_shapes) .filter(lambda shapes: all( prod(i for i in s if i > 0) < MAX_ARRAY_SIZE for s in shapes diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 51d32410..3fed5182 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -205,6 +205,18 @@ def test_where(in_dtypes, out_dtype, shapes, data): assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' +numeric_promotion_table_params = promotion_table_params[1:] + + +@pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_table_params) +@given(shapes=hh.mutually_broadcastable_shapes(2, min_dims=1), data=st.data()) +def test_matmul(in_dtypes, out_dtype, shapes, data): + x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') + x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') + out = xp.matmul(x1, x2) + assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + + op_params: List[Tuple[str, str, Tuple[DT, ...], DT]] = [] op_to_symbol = {**dh.unary_op_to_symbol, **dh.binary_op_to_symbol} for op, symbol in op_to_symbol.items(): From a9d5d20a4823981d65e2e8dd8609193a6e10a9f9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 19 Oct 2021 16:53:25 +0100 Subject: [PATCH 07/24] Rudimentary tensor/vec dot tests --- array_api_tests/test_type_promotion.py | 28 +++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 3fed5182..1840df15 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -185,17 +185,17 @@ def test_func_promotion(func_name, in_dtypes, out_dtype, data): assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' -promotion_table_params: List[Tuple[Tuple[DT, DT], DT]] = [] +promotion_params: List[Tuple[Tuple[DT, DT], DT]] = [] for (dtype1, dtype2), promoted_dtype in dh.promotion_table.items(): p = pytest.param( (dtype1, dtype2), promoted_dtype, id=make_id('', (dtype1, dtype2), promoted_dtype), ) - promotion_table_params.append(p) + promotion_params.append(p) -@pytest.mark.parametrize('in_dtypes, out_dtype', promotion_table_params) +@pytest.mark.parametrize('in_dtypes, out_dtype', promotion_params) @given(shapes=hh.mutually_broadcastable_shapes(3), data=st.data()) def test_where(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') @@ -205,10 +205,10 @@ def test_where(in_dtypes, out_dtype, shapes, data): assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' -numeric_promotion_table_params = promotion_table_params[1:] +numeric_promotion_params = promotion_params[1:] -@pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_table_params) +@pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_params) @given(shapes=hh.mutually_broadcastable_shapes(2, min_dims=1), data=st.data()) def test_matmul(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') @@ -217,6 +217,24 @@ def test_matmul(in_dtypes, out_dtype, shapes, data): assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' +@pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_params) +@given(shapes=hh.mutually_broadcastable_shapes(2, min_dims=2), data=st.data()) +def test_tensordot(in_dtypes, out_dtype, shapes, data): + x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') + x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') + out = xp.tensordot(x1, x2) + assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + + +@pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_params) +@given(shapes=hh.mutually_broadcastable_shapes(2, min_dims=1), data=st.data()) +def test_vecdot(in_dtypes, out_dtype, shapes, data): + x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') + x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') + out = xp.vecdot(x1, x2) + assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + + op_params: List[Tuple[str, str, Tuple[DT, ...], DT]] = [] op_to_symbol = {**dh.unary_op_to_symbol, **dh.binary_op_to_symbol} for op, symbol in op_to_symbol.items(): From 508abcc126f50d4b59846b34eab2c5776a69a69c Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 20 Oct 2021 14:28:01 +0100 Subject: [PATCH 08/24] Alias `Param` type hint as `Tuple` --- array_api_tests/test_type_promotion.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 1840df15..151f4b12 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -19,6 +19,7 @@ DT = Type ScalarType = Union[Type[bool], Type[int], Type[float]] +Param = Tuple def multi_promotable_dtypes( @@ -126,7 +127,7 @@ def make_id( return f'{func_name}({f_args}) -> {f_out_dtype}' -func_params: List[Tuple[str, Tuple[DT, ...], DT]] = [] +func_params: List[Param[str, Tuple[DT, ...], DT]] = [] for func_name in elementwise_functions.__all__: valid_in_dtypes = dh.func_in_dtypes[func_name] ndtypes = nargs(func_name) @@ -185,7 +186,7 @@ def test_func_promotion(func_name, in_dtypes, out_dtype, data): assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' -promotion_params: List[Tuple[Tuple[DT, DT], DT]] = [] +promotion_params: List[Param[Tuple[DT, DT], DT]] = [] for (dtype1, dtype2), promoted_dtype in dh.promotion_table.items(): p = pytest.param( (dtype1, dtype2), @@ -235,7 +236,7 @@ def test_vecdot(in_dtypes, out_dtype, shapes, data): assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' -op_params: List[Tuple[str, str, Tuple[DT, ...], DT]] = [] +op_params: List[Param[str, str, Tuple[DT, ...], DT]] = [] op_to_symbol = {**dh.unary_op_to_symbol, **dh.binary_op_to_symbol} for op, symbol in op_to_symbol.items(): if op == '__matmul__': @@ -303,7 +304,7 @@ def test_op_promotion(op, expr, in_dtypes, out_dtype, data): assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' -inplace_params: List[Tuple[str, str, Tuple[DT, ...], DT]] = [] +inplace_params: List[Param[str, str, Tuple[DT, ...], DT]] = [] for op, symbol in dh.inplace_op_to_symbol.items(): if op == '__imatmul__': continue @@ -344,7 +345,7 @@ def test_inplace_op_promotion(op, expr, in_dtypes, out_dtype, shapes, data): assert x1.dtype == out_dtype, f'{x1.dtype=!s}, but should be {out_dtype}' -op_scalar_params: List[Tuple[str, str, DT, ScalarType, DT]] = [] +op_scalar_params: List[Param[str, str, DT, ScalarType, DT]] = [] for op, symbol in dh.binary_op_to_symbol.items(): if op == '__matmul__': continue @@ -378,7 +379,7 @@ def test_op_scalar_promotion(op, expr, in_dtype, in_stype, out_dtype, data): assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' -inplace_scalar_params: List[Tuple[str, str, DT, ScalarType]] = [] +inplace_scalar_params: List[Param[str, str, DT, ScalarType]] = [] for op, symbol in dh.inplace_op_to_symbol.items(): if op == '__imatmul__': continue From ee9ab198efc88c2d93bcb4bb150df5e9f5c3c898 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 20 Oct 2021 16:01:07 +0100 Subject: [PATCH 09/24] Include func/op and param dtypes in type promotion error messages --- array_api_tests/test_type_promotion.py | 75 +++++++++++++++++--------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 151f4b12..8d618a2a 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -2,6 +2,7 @@ https://data-apis.github.io/array-api/latest/API_specification/type_promotion.html """ from collections import defaultdict +from functools import lru_cache from typing import Tuple, Type, Union, List import pytest @@ -22,6 +23,26 @@ Param = Tuple +@lru_cache +def fmt_types(types: Tuple[Union[DT, ScalarType], ...]) -> str: + f_types = [] + for type_ in types: + try: + f_types.append(dh.dtype_to_name[type_]) + except KeyError: + # i.e. dtype is bool, int, or float + f_types.append(type_.__name__) + return ', '.join(f_types) + + +def assert_dtype(test_case: str, result_name: str, dtype: DT, expected: DT): + msg = ( + f'{result_name}={dh.dtype_to_name[dtype]}, ' + f'but should be {dh.dtype_to_name[expected]} [{test_case}]' + ) + assert dtype == expected, msg + + def multi_promotable_dtypes( allow_bool: bool = True, ) -> st.SearchStrategy[Tuple[DT, ...]]: @@ -39,8 +60,9 @@ def multi_promotable_dtypes( @given(multi_promotable_dtypes()) def test_result_type(dtypes): out = xp.result_type(*dtypes) - expected = dh.result_type(*dtypes) - assert out == expected, f'{out=!s}, but should be {expected}' + assert_dtype( + f'result_type({fmt_types(dtypes)})', 'out', out, dh.result_type(*dtypes) + ) @given( @@ -56,10 +78,9 @@ def test_meshgrid(dtypes, kw, data): arrays.append(x) out = xp.meshgrid(*arrays, **kw) expected = dh.result_type(*dtypes) - for i in range(len(out)): - assert ( - out[i].dtype == expected - ), f'out[{i}]={out[i].dtype}, but should be {expected}' + test_case = f'meshgrid({fmt_types(dtypes)})' + for i, x in enumerate(out): + assert_dtype(test_case, f'out[{i}].dtype', x.dtype, expected) @given( @@ -74,8 +95,9 @@ def test_concat(shape, dtypes, kw, data): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) out = xp.concat(arrays, **kw) - expected = dh.result_type(*dtypes) - assert out.dtype == expected, f'{out.dtype=!s}, but should be {expected}' + assert_dtype( + f'concat({fmt_types(dtypes)})', 'out.dtype', out.dtype, dh.result_type(*dtypes) + ) @given( @@ -90,8 +112,9 @@ def test_stack(shape, dtypes, kw, data): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) out = xp.stack(arrays, **kw) - expected = dh.result_type(*dtypes) - assert out.dtype == expected, f'{out.dtype=!s}, but should be {expected}' + assert_dtype( + f'stack({fmt_types(dtypes)})', 'out.dtype', out.dtype, dh.result_type(*dtypes) + ) bitwise_shift_funcs = [ @@ -115,14 +138,7 @@ def test_stack(shape, dtypes, kw, data): def make_id( func_name: str, in_dtypes: Tuple[Union[DT, ScalarType], ...], out_dtype: DT ) -> str: - f_in_dtypes = [] - for dtype in in_dtypes: - try: - f_in_dtypes.append(dh.dtype_to_name[dtype]) - except KeyError: - # i.e. dtype is bool, int, or float - f_in_dtypes.append(dtype.__name__) - f_args = ', '.join(f_in_dtypes) + f_args = fmt_types(in_dtypes) f_out_dtype = dh.dtype_to_name[out_dtype] return f'{func_name}({f_args}) -> {f_out_dtype}' @@ -183,7 +199,9 @@ def test_func_promotion(func_name, in_dtypes, out_dtype, data): out = func(*arrays) except OverflowError: reject() - assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + assert_dtype( + f'{func_name}({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype + ) promotion_params: List[Param[Tuple[DT, DT], DT]] = [] @@ -203,7 +221,7 @@ def test_where(in_dtypes, out_dtype, shapes, data): x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') cond = data.draw(xps.arrays(dtype=xp.bool, shape=shapes[2]), label='condition') out = xp.where(cond, x1, x2) - assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + assert_dtype(f'where({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype) numeric_promotion_params = promotion_params[1:] @@ -215,7 +233,7 @@ def test_matmul(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') out = xp.matmul(x1, x2) - assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + assert_dtype(f'matmul({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype) @pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_params) @@ -224,7 +242,9 @@ def test_tensordot(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') out = xp.tensordot(x1, x2) - assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + assert_dtype( + f'tensordot({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype + ) @pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_params) @@ -233,7 +253,7 @@ def test_vecdot(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') out = xp.vecdot(x1, x2) - assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + assert_dtype(f'vecdot({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype) op_params: List[Param[str, str, Tuple[DT, ...], DT]] = [] @@ -301,7 +321,7 @@ def test_op_promotion(op, expr, in_dtypes, out_dtype, data): out = eval(expr, locals_) except OverflowError: reject() - assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + assert_dtype(f'{op}({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype) inplace_params: List[Param[str, str, Tuple[DT, ...], DT]] = [] @@ -342,7 +362,7 @@ def test_inplace_op_promotion(op, expr, in_dtypes, out_dtype, shapes, data): except OverflowError: reject() x1 = locals_['x1'] - assert x1.dtype == out_dtype, f'{x1.dtype=!s}, but should be {out_dtype}' + assert_dtype(f'{op}({fmt_types(in_dtypes)})', 'x1.dtype', x1.dtype, out_dtype) op_scalar_params: List[Param[str, str, DT, ScalarType, DT]] = [] @@ -376,7 +396,9 @@ def test_op_scalar_promotion(op, expr, in_dtype, in_stype, out_dtype, data): out = eval(expr, {'x': x, 's': s}) except OverflowError: reject() - assert out.dtype == out_dtype, f'{out.dtype=!s}, but should be {out_dtype}' + assert_dtype( + f'{op}({fmt_types((in_dtype, in_stype))})', 'out.dtype', out.dtype, out_dtype + ) inplace_scalar_params: List[Param[str, str, DT, ScalarType]] = [] @@ -411,6 +433,7 @@ def test_inplace_op_scalar_promotion(op, expr, dtype, in_stype, data): reject() x = locals_['x'] assert x.dtype == dtype, f'{x.dtype=!s}, but should be {dtype}' + assert_dtype(f'{op}({fmt_types((dtype, in_stype))})', 'x.dtype', x.dtype, dtype) if __name__ == '__main__': From 65db28a10b303078c11a434ce7d867ddb8b1b253 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 20 Oct 2021 16:42:40 +0100 Subject: [PATCH 10/24] Replace array filtering with `xps.from_dtype()` kwargs --- array_api_tests/test_type_promotion.py | 36 ++++++++++++-------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 8d618a2a..67bfdb2c 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -10,7 +10,6 @@ from hypothesis import strategies as st from . import _array_module as xp -from . import array_helpers as ah from . import dtype_helpers as dh from . import hypothesis_helpers as hh from . import xps @@ -127,11 +126,10 @@ def test_stack(shape, dtypes, kw, data): ] -# We apply filters to xps.arrays() so we don't generate array elements that -# are erroneous or undefined for a function/operator. -filters = defaultdict( - lambda: lambda _: True, - {func: lambda x: ah.all(x > 0) for func in bitwise_shift_funcs}, +# We pass kwargs to the elements strategy used by xps.arrays() so that we don't +# generate array elements that are erroneous or undefined for a function. +func_elements = defaultdict( + lambda: None, {func: {'min_value': 1} for func in bitwise_shift_funcs} ) @@ -178,10 +176,10 @@ def make_id( @given(data=st.data()) def test_func_promotion(func_name, in_dtypes, out_dtype, data): func = getattr(xp, func_name) - x_filter = filters[func_name] + elements = func_elements[func_name] if len(in_dtypes) == 1: x = data.draw( - xps.arrays(dtype=in_dtypes[0], shape=hh.shapes()).filter(x_filter), + xps.arrays(dtype=in_dtypes[0], shape=hh.shapes(), elements=elements), label='x', ) out = func(x) @@ -192,7 +190,7 @@ def test_func_promotion(func_name, in_dtypes, out_dtype, data): ) for i, (dtype, shape) in enumerate(zip(in_dtypes, shapes), 1): x = data.draw( - xps.arrays(dtype=dtype, shape=shape).filter(x_filter), label=f'x{i}' + xps.arrays(dtype=dtype, shape=shape, elements=elements), label=f'x{i}' ) arrays.append(x) try: @@ -301,10 +299,10 @@ def test_vecdot(in_dtypes, out_dtype, shapes, data): @pytest.mark.parametrize('op, expr, in_dtypes, out_dtype', op_params) @given(data=st.data()) def test_op_promotion(op, expr, in_dtypes, out_dtype, data): - x_filter = filters[op] + elements = func_elements[func_name] if len(in_dtypes) == 1: x = data.draw( - xps.arrays(dtype=in_dtypes[0], shape=hh.shapes()).filter(x_filter), + xps.arrays(dtype=in_dtypes[0], shape=hh.shapes(), elements=elements), label='x', ) out = eval(expr, {'x': x}) @@ -315,7 +313,7 @@ def test_op_promotion(op, expr, in_dtypes, out_dtype, data): ) for i, (dtype, shape) in enumerate(zip(in_dtypes, shapes), 1): locals_[f'x{i}'] = data.draw( - xps.arrays(dtype=dtype, shape=shape).filter(x_filter), label=f'x{i}' + xps.arrays(dtype=dtype, shape=shape, elements=elements), label=f'x{i}' ) try: out = eval(expr, locals_) @@ -349,12 +347,12 @@ def test_op_promotion(op, expr, in_dtypes, out_dtype, data): @given(shapes=hh.mutually_broadcastable_shapes(2), data=st.data()) def test_inplace_op_promotion(op, expr, in_dtypes, out_dtype, shapes, data): assume(len(shapes[0]) >= len(shapes[1])) - x_filter = filters[op] + elements = func_elements[func_name] x1 = data.draw( - xps.arrays(dtype=in_dtypes[0], shape=shapes[0]).filter(x_filter), label='x1' + xps.arrays(dtype=in_dtypes[0], shape=shapes[0], elements=elements), label='x1' ) x2 = data.draw( - xps.arrays(dtype=in_dtypes[1], shape=shapes[1]).filter(x_filter), label='x2' + xps.arrays(dtype=in_dtypes[1], shape=shapes[1], elements=elements), label='x2' ) locals_ = {'x1': x1, 'x2': x2} try: @@ -386,11 +384,11 @@ def test_inplace_op_promotion(op, expr, in_dtypes, out_dtype, shapes, data): @pytest.mark.parametrize('op, expr, in_dtype, in_stype, out_dtype', op_scalar_params) @given(data=st.data()) def test_op_scalar_promotion(op, expr, in_dtype, in_stype, out_dtype, data): - x_filter = filters[op] + elements = func_elements[func_name] kw = {k: in_stype is float for k in ('allow_nan', 'allow_infinity')} s = data.draw(xps.from_dtype(in_dtype, **kw).map(in_stype), label='scalar') x = data.draw( - xps.arrays(dtype=in_dtype, shape=hh.shapes()).filter(x_filter), label='x' + xps.arrays(dtype=in_dtype, shape=hh.shapes(), elements=elements), label='x' ) try: out = eval(expr, {'x': x, 's': s}) @@ -420,11 +418,11 @@ def test_op_scalar_promotion(op, expr, in_dtype, in_stype, out_dtype, data): @pytest.mark.parametrize('op, expr, dtype, in_stype', inplace_scalar_params) @given(data=st.data()) def test_inplace_op_scalar_promotion(op, expr, dtype, in_stype, data): - x_filter = filters[op] + elements = func_elements[func_name] kw = {k: in_stype is float for k in ('allow_nan', 'allow_infinity')} s = data.draw(xps.from_dtype(dtype, **kw).map(in_stype), label='scalar') x = data.draw( - xps.arrays(dtype=dtype, shape=hh.shapes()).filter(x_filter), label='x' + xps.arrays(dtype=dtype, shape=hh.shapes(), elements=elements), label='x' ) locals_ = {'x': x, 's': s} try: From b82358e5ff130e56fe4928c571ae903ed3a35311 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 20 Oct 2021 17:12:33 +0100 Subject: [PATCH 11/24] Scrap generating kwargs for promotion tests --- array_api_tests/test_type_promotion.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 67bfdb2c..6c3954d4 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -66,16 +66,15 @@ def test_result_type(dtypes): @given( dtypes=multi_promotable_dtypes(allow_bool=False), - kw=hh.kwargs(indexing=st.sampled_from(['xy', 'ij'])), data=st.data(), ) -def test_meshgrid(dtypes, kw, data): +def test_meshgrid(dtypes, data): arrays = [] shapes = data.draw(hh.mutually_broadcastable_shapes(len(dtypes)), label='shapes') for i, (dtype, shape) in enumerate(zip(dtypes, shapes), 1): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) - out = xp.meshgrid(*arrays, **kw) + out = xp.meshgrid(*arrays) expected = dh.result_type(*dtypes) test_case = f'meshgrid({fmt_types(dtypes)})' for i, x in enumerate(out): @@ -85,15 +84,14 @@ def test_meshgrid(dtypes, kw, data): @given( shape=hh.shapes(min_dims=1), dtypes=multi_promotable_dtypes(allow_bool=False), - kw=hh.kwargs(axis=st.none() | st.just(0)), data=st.data(), ) -def test_concat(shape, dtypes, kw, data): +def test_concat(shape, dtypes, data): arrays = [] for i, dtype in enumerate(dtypes, 1): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) - out = xp.concat(arrays, **kw) + out = xp.concat(arrays) assert_dtype( f'concat({fmt_types(dtypes)})', 'out.dtype', out.dtype, dh.result_type(*dtypes) ) @@ -102,15 +100,14 @@ def test_concat(shape, dtypes, kw, data): @given( shape=hh.shapes(), dtypes=multi_promotable_dtypes(), - kw=hh.kwargs(axis=st.just(0)), data=st.data(), ) -def test_stack(shape, dtypes, kw, data): +def test_stack(shape, dtypes, data): arrays = [] for i, dtype in enumerate(dtypes, 1): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) - out = xp.stack(arrays, **kw) + out = xp.stack(arrays) assert_dtype( f'stack({fmt_types(dtypes)})', 'out.dtype', out.dtype, dh.result_type(*dtypes) ) From deebaef7494c28585e580a20119b6bb92f274c76 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 20 Oct 2021 17:23:40 +0100 Subject: [PATCH 12/24] Better minimisation behaviour for `multi_promotable_dtypes()` --- array_api_tests/dtype_helpers.py | 8 ++++---- array_api_tests/test_type_promotion.py | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/array_api_tests/dtype_helpers.py b/array_api_tests/dtype_helpers.py index ce6608a8..ddb2a204 100644 --- a/array_api_tests/dtype_helpers.py +++ b/array_api_tests/dtype_helpers.py @@ -31,16 +31,16 @@ ] -_int_names = ('int8', 'int16', 'int32', 'int64') _uint_names = ('uint8', 'uint16', 'uint32', 'uint64') +_int_names = ('int8', 'int16', 'int32', 'int64') _float_names = ('float32', 'float64') -_dtype_names = ('bool',) + _int_names + _uint_names + _float_names +_dtype_names = ('bool',) + _uint_names + _int_names + _float_names -int_dtypes = tuple(getattr(xp, name) for name in _int_names) uint_dtypes = tuple(getattr(xp, name) for name in _uint_names) +int_dtypes = tuple(getattr(xp, name) for name in _int_names) float_dtypes = tuple(getattr(xp, name) for name in _float_names) -all_int_dtypes = int_dtypes + uint_dtypes +all_int_dtypes = uint_dtypes + int_dtypes numeric_dtypes = all_int_dtypes + float_dtypes all_dtypes = (xp.bool,) + numeric_dtypes bool_and_all_int_dtypes = (xp.bool,) + all_int_dtypes diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 6c3954d4..af7c53c7 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -46,13 +46,15 @@ def multi_promotable_dtypes( allow_bool: bool = True, ) -> st.SearchStrategy[Tuple[DT, ...]]: strats = [ + st.lists(st.sampled_from(dh.uint_dtypes), min_size=2), + st.lists(st.sampled_from(dh.int_dtypes), min_size=2), + st.lists(st.sampled_from(dh.float_dtypes), min_size=2), st.lists(st.sampled_from(dh.all_int_dtypes), min_size=2).filter( lambda l: not (xp.uint64 in l and any(d in dh.int_dtypes for d in l)) ), - st.lists(st.sampled_from(dh.float_dtypes), min_size=2), ] if allow_bool: - strats.append(st.lists(st.just(xp.bool), min_size=2)) + strats.insert(0, st.lists(st.just(xp.bool), min_size=2)) return st.one_of(strats).map(tuple) From c0412280628bb989c1672ef5212ea14e4ce53f2f Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 21 Oct 2021 10:49:23 +0100 Subject: [PATCH 13/24] Use setdefault instead of manualy keys check in `hh.shapes()` --- array_api_tests/hypothesis_helpers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 149156a4..9b528f07 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -114,10 +114,8 @@ def tuples(elements, *, min_size=0, max_size=None, unique_by=None, unique=False) # Use this to avoid memory errors with NumPy. # See https://github.com/numpy/numpy/issues/15753 def shapes(**kw): - if 'min_dims' not in kw.keys(): - kw['min_dims'] = 0 - if 'min_side' not in kw.keys(): - kw['min_side'] = 0 + kw.setdefault('min_dims', 0) + kw.setdefault('min_side', 0) return xps.array_shapes(**kw).filter( lambda shape: prod(i for i in shape if i) < MAX_ARRAY_SIZE ) From bbce5809a9c795e2875adf6bc31002b6e279cba6 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 21 Oct 2021 11:11:06 +0100 Subject: [PATCH 14/24] Remove faulty matmul type promotion test --- array_api_tests/test_type_promotion.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index af7c53c7..3c9def18 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -224,15 +224,6 @@ def test_where(in_dtypes, out_dtype, shapes, data): numeric_promotion_params = promotion_params[1:] -@pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_params) -@given(shapes=hh.mutually_broadcastable_shapes(2, min_dims=1), data=st.data()) -def test_matmul(in_dtypes, out_dtype, shapes, data): - x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') - x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') - out = xp.matmul(x1, x2) - assert_dtype(f'matmul({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype) - - @pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_params) @given(shapes=hh.mutually_broadcastable_shapes(2, min_dims=2), data=st.data()) def test_tensordot(in_dtypes, out_dtype, shapes, data): From 0c71410a3a87acf59968f57380d0c846ca7df52a Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 21 Oct 2021 12:05:49 +0100 Subject: [PATCH 15/24] `mutually_promotable_dtypes()` can generate 2-or-more dtypes --- array_api_tests/hypothesis_helpers.py | 41 +++++++++++++++---- .../meta/test_hypothesis_helpers.py | 2 +- array_api_tests/test_elementwise_functions.py | 10 ++--- array_api_tests/test_linalg.py | 4 +- array_api_tests/test_type_promotion.py | 24 ++--------- 5 files changed, 44 insertions(+), 37 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index 9b528f07..a7e96ee4 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -2,13 +2,12 @@ from operator import mul from math import sqrt import itertools -from typing import Tuple +from typing import Tuple, Optional from hypothesis import assume from hypothesis.strategies import (lists, integers, sampled_from, shared, floats, just, composite, one_of, - none, booleans) -from hypothesis.strategies._internal.strategies import SearchStrategy + none, booleans, SearchStrategy) from .pytest_helpers import nargs from .array_helpers import ndindex @@ -77,10 +76,34 @@ def _dtypes_sorter(dtype_pair): ] -def mutually_promotable_dtypes(dtype_objs=dh.all_dtypes): - return sampled_from( - [(i, j) for i, j in promotable_dtypes if i in dtype_objs and j in dtype_objs] - ) +def mutually_promotable_dtypes( + max_size: Optional[int] = 2, + *, + dtypes=dh.all_dtypes, +) -> SearchStrategy[Tuple]: + if max_size == 2: + return sampled_from( + [(i, j) for i, j in promotable_dtypes if i in dtypes and j in dtypes] + ) + if isinstance(max_size, int) and max_size < 2: + raise ValueError(f'{max_size=} should be >=2') + strats = [] + category_samples = { + category: [d for d in dtypes if d in category] for category in _dtype_categories + } + for samples in category_samples.values(): + if len(samples) > 0: + strat = lists(sampled_from(samples), min_size=2, max_size=max_size) + strats.append(strat) + if len(category_samples[dh.uint_dtypes]) > 0 and len(category_samples[dh.int_dtypes]) > 0: + mixed_samples = category_samples[dh.uint_dtypes] + category_samples[dh.int_dtypes] + strat = lists(sampled_from(mixed_samples), min_size=2, max_size=max_size) + if xp.uint64 in mixed_samples: + strat = strat.filter( + lambda l: not (xp.uint64 in l and any(d in dh.int_dtypes for d in l)) + ) + return one_of(strats).map(tuple) + # shared() allows us to draw either the function or the function name and they # will both correspond to the same function. @@ -324,9 +347,9 @@ def multiaxis_indices(draw, shapes): def two_mutual_arrays( - dtype_objs=dh.all_dtypes, two_shapes=two_mutually_broadcastable_shapes + dtypes=dh.all_dtypes, two_shapes=two_mutually_broadcastable_shapes ): - mutual_dtypes = shared(mutually_promotable_dtypes(dtype_objs)) + mutual_dtypes = shared(mutually_promotable_dtypes(dtypes=dtypes)) mutual_shapes = shared(two_shapes) arrays1 = xps.arrays( dtype=mutual_dtypes.map(lambda pair: pair[0]), diff --git a/array_api_tests/meta/test_hypothesis_helpers.py b/array_api_tests/meta/test_hypothesis_helpers.py index 0ba43409..2edf072f 100644 --- a/array_api_tests/meta/test_hypothesis_helpers.py +++ b/array_api_tests/meta/test_hypothesis_helpers.py @@ -14,7 +14,7 @@ UNDEFINED_DTYPES = any(isinstance(d, _UndefinedStub) for d in dh.all_dtypes) pytestmark = [pytest.mark.skipif(UNDEFINED_DTYPES, reason="undefined dtypes")] -@given(hh.mutually_promotable_dtypes([xp.float32, xp.float64])) +@given(hh.mutually_promotable_dtypes(dtypes=dh.float_dtypes)) def test_mutually_promotable_dtypes(pairs): assert pairs in ( (xp.float32, xp.float32), diff --git a/array_api_tests/test_elementwise_functions.py b/array_api_tests/test_elementwise_functions.py index d4a52ac4..4e6f9f44 100644 --- a/array_api_tests/test_elementwise_functions.py +++ b/array_api_tests/test_elementwise_functions.py @@ -29,11 +29,11 @@ integer_or_boolean_scalars = hh.array_scalars(hh.integer_or_boolean_dtypes) boolean_scalars = hh.array_scalars(hh.boolean_dtypes) -two_integer_dtypes = hh.mutually_promotable_dtypes(dh.all_int_dtypes) -two_floating_dtypes = hh.mutually_promotable_dtypes(dh.float_dtypes) -two_numeric_dtypes = hh.mutually_promotable_dtypes(dh.numeric_dtypes) -two_integer_or_boolean_dtypes = hh.mutually_promotable_dtypes(dh.bool_and_all_int_dtypes) -two_boolean_dtypes = hh.mutually_promotable_dtypes((xp.bool,)) +two_integer_dtypes = hh.mutually_promotable_dtypes(dtypes=dh.all_int_dtypes) +two_floating_dtypes = hh.mutually_promotable_dtypes(dtypes=dh.float_dtypes) +two_numeric_dtypes = hh.mutually_promotable_dtypes(dtypes=dh.numeric_dtypes) +two_integer_or_boolean_dtypes = hh.mutually_promotable_dtypes(dtypes=dh.bool_and_all_int_dtypes) +two_boolean_dtypes = hh.mutually_promotable_dtypes(dtypes=(xp.bool,)) two_any_dtypes = hh.mutually_promotable_dtypes() @st.composite diff --git a/array_api_tests/test_linalg.py b/array_api_tests/test_linalg.py index c13dd909..09d251ea 100644 --- a/array_api_tests/test_linalg.py +++ b/array_api_tests/test_linalg.py @@ -113,7 +113,7 @@ def cross_args(draw, dtype_objects=dh.numeric_dtypes): axis = kw.get('axis', -1) shape[axis] = 3 - mutual_dtypes = shared(mutually_promotable_dtypes(dtype_objects)) + mutual_dtypes = shared(mutually_promotable_dtypes(dtypes=dtype_objects)) arrays1 = xps.arrays( dtype=mutual_dtypes.map(lambda pair: pair[0]), shape=shape, @@ -355,7 +355,7 @@ def test_matrix_transpose(x): @pytest.mark.xp_extension('linalg') @given( - *two_mutual_arrays(dtype_objs=dh.numeric_dtypes, + *two_mutual_arrays(dtypes=dh.numeric_dtypes, two_shapes=tuples(one_d_shapes, one_d_shapes)) ) def test_outer(x1, x2): diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 3c9def18..72f70923 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -42,23 +42,7 @@ def assert_dtype(test_case: str, result_name: str, dtype: DT, expected: DT): assert dtype == expected, msg -def multi_promotable_dtypes( - allow_bool: bool = True, -) -> st.SearchStrategy[Tuple[DT, ...]]: - strats = [ - st.lists(st.sampled_from(dh.uint_dtypes), min_size=2), - st.lists(st.sampled_from(dh.int_dtypes), min_size=2), - st.lists(st.sampled_from(dh.float_dtypes), min_size=2), - st.lists(st.sampled_from(dh.all_int_dtypes), min_size=2).filter( - lambda l: not (xp.uint64 in l and any(d in dh.int_dtypes for d in l)) - ), - ] - if allow_bool: - strats.insert(0, st.lists(st.just(xp.bool), min_size=2)) - return st.one_of(strats).map(tuple) - - -@given(multi_promotable_dtypes()) +@given(hh.mutually_promotable_dtypes(None)) def test_result_type(dtypes): out = xp.result_type(*dtypes) assert_dtype( @@ -67,7 +51,7 @@ def test_result_type(dtypes): @given( - dtypes=multi_promotable_dtypes(allow_bool=False), + dtypes=hh.mutually_promotable_dtypes(None, dtypes=dh.numeric_dtypes), data=st.data(), ) def test_meshgrid(dtypes, data): @@ -85,7 +69,7 @@ def test_meshgrid(dtypes, data): @given( shape=hh.shapes(min_dims=1), - dtypes=multi_promotable_dtypes(allow_bool=False), + dtypes=hh.mutually_promotable_dtypes(None, dtypes=dh.numeric_dtypes), data=st.data(), ) def test_concat(shape, dtypes, data): @@ -101,7 +85,7 @@ def test_concat(shape, dtypes, data): @given( shape=hh.shapes(), - dtypes=multi_promotable_dtypes(), + dtypes=hh.mutually_promotable_dtypes(None), data=st.data(), ) def test_stack(shape, dtypes, data): From f43829887f7d81b36089651f543d9fdd9edc69ef Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 21 Oct 2021 16:50:08 +0100 Subject: [PATCH 16/24] Factor out `assert_dtype` and `fmt_types`, add `typing.py` --- array_api_tests/dtype_helpers.py | 17 +++- array_api_tests/pytest_helpers.py | 13 +++ array_api_tests/test_type_promotion.py | 105 +++++++++++-------------- array_api_tests/typing.py | 11 +++ 4 files changed, 88 insertions(+), 58 deletions(-) create mode 100644 array_api_tests/typing.py diff --git a/array_api_tests/dtype_helpers.py b/array_api_tests/dtype_helpers.py index ddb2a204..56790cde 100644 --- a/array_api_tests/dtype_helpers.py +++ b/array_api_tests/dtype_helpers.py @@ -1,8 +1,10 @@ from warnings import warn -from typing import NamedTuple +from functools import lru_cache +from typing import NamedTuple, Tuple, Union from . import _array_module as xp from ._array_module import _UndefinedStub +from .typing import DataType, ScalarType __all__ = [ @@ -28,6 +30,7 @@ 'binary_op_to_symbol', 'unary_op_to_symbol', 'inplace_op_to_symbol', + 'fmt_types', ] @@ -367,3 +370,15 @@ def result_type(*dtypes): inplace_op_to_symbol[iop] = f'{symbol}=' func_in_dtypes[iop] = func_in_dtypes[op] func_returns_bool[iop] = func_returns_bool[op] + + +@lru_cache +def fmt_types(types: Tuple[Union[DataType, ScalarType], ...]) -> str: + f_types = [] + for type_ in types: + try: + f_types.append(dtype_to_name[type_]) + except KeyError: + # i.e. dtype is bool, int, or float + f_types.append(type_.__name__) + return ', '.join(f_types) diff --git a/array_api_tests/pytest_helpers.py b/array_api_tests/pytest_helpers.py index 354ad50e..c6b9d8fc 100644 --- a/array_api_tests/pytest_helpers.py +++ b/array_api_tests/pytest_helpers.py @@ -1,5 +1,9 @@ from inspect import getfullargspec + +from . import dtype_helpers as dh from . import function_stubs +from .typing import DataType + def raises(exceptions, function, message=''): """ @@ -33,3 +37,12 @@ def doesnt_raise(function, message=''): def nargs(func_name): return len(getfullargspec(getattr(function_stubs, func_name)).args) + +def assert_dtype(test_case: str, result_name: str, dtype: DataType, expected: DataType): + msg = ( + f'{result_name}={dh.dtype_to_name[dtype]}, ' + f'but should be {dh.dtype_to_name[expected]} [{test_case}]' + ) + assert dtype == expected, msg + + diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 72f70923..6e306160 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -2,8 +2,7 @@ https://data-apis.github.io/array-api/latest/API_specification/type_promotion.html """ from collections import defaultdict -from functools import lru_cache -from typing import Tuple, Type, Union, List +from typing import Tuple, Union, List import pytest from hypothesis import assume, given, reject @@ -12,41 +11,17 @@ from . import _array_module as xp from . import dtype_helpers as dh from . import hypothesis_helpers as hh +from . import pytest_helpers as ph from . import xps +from .typing import DataType, ScalarType, Param from .function_stubs import elementwise_functions -from .pytest_helpers import nargs - - -DT = Type -ScalarType = Union[Type[bool], Type[int], Type[float]] -Param = Tuple - - -@lru_cache -def fmt_types(types: Tuple[Union[DT, ScalarType], ...]) -> str: - f_types = [] - for type_ in types: - try: - f_types.append(dh.dtype_to_name[type_]) - except KeyError: - # i.e. dtype is bool, int, or float - f_types.append(type_.__name__) - return ', '.join(f_types) - - -def assert_dtype(test_case: str, result_name: str, dtype: DT, expected: DT): - msg = ( - f'{result_name}={dh.dtype_to_name[dtype]}, ' - f'but should be {dh.dtype_to_name[expected]} [{test_case}]' - ) - assert dtype == expected, msg @given(hh.mutually_promotable_dtypes(None)) def test_result_type(dtypes): out = xp.result_type(*dtypes) - assert_dtype( - f'result_type({fmt_types(dtypes)})', 'out', out, dh.result_type(*dtypes) + ph.assert_dtype( + f'result_type({dh.fmt_types(dtypes)})', 'out', out, dh.result_type(*dtypes) ) @@ -62,9 +37,9 @@ def test_meshgrid(dtypes, data): arrays.append(x) out = xp.meshgrid(*arrays) expected = dh.result_type(*dtypes) - test_case = f'meshgrid({fmt_types(dtypes)})' + test_case = f'meshgrid({dh.fmt_types(dtypes)})' for i, x in enumerate(out): - assert_dtype(test_case, f'out[{i}].dtype', x.dtype, expected) + ph.assert_dtype(test_case, f'out[{i}].dtype', x.dtype, expected) @given( @@ -78,8 +53,11 @@ def test_concat(shape, dtypes, data): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) out = xp.concat(arrays) - assert_dtype( - f'concat({fmt_types(dtypes)})', 'out.dtype', out.dtype, dh.result_type(*dtypes) + ph.assert_dtype( + f'concat({dh.fmt_types(dtypes)})', + 'out.dtype', + out.dtype, + dh.result_type(*dtypes), ) @@ -94,8 +72,11 @@ def test_stack(shape, dtypes, data): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) out = xp.stack(arrays) - assert_dtype( - f'stack({fmt_types(dtypes)})', 'out.dtype', out.dtype, dh.result_type(*dtypes) + ph.assert_dtype( + f'stack({dh.fmt_types(dtypes)})', + 'out.dtype', + out.dtype, + dh.result_type(*dtypes), ) @@ -117,17 +98,19 @@ def test_stack(shape, dtypes, data): def make_id( - func_name: str, in_dtypes: Tuple[Union[DT, ScalarType], ...], out_dtype: DT + func_name: str, + in_dtypes: Tuple[Union[DataType, ScalarType], ...], + out_dtype: DataType, ) -> str: - f_args = fmt_types(in_dtypes) + f_args = dh.fmt_types(in_dtypes) f_out_dtype = dh.dtype_to_name[out_dtype] return f'{func_name}({f_args}) -> {f_out_dtype}' -func_params: List[Param[str, Tuple[DT, ...], DT]] = [] +func_params: List[Param[str, Tuple[DataType, ...], DataType]] = [] for func_name in elementwise_functions.__all__: valid_in_dtypes = dh.func_in_dtypes[func_name] - ndtypes = nargs(func_name) + ndtypes = ph.nargs(func_name) if ndtypes == 1: for in_dtype in valid_in_dtypes: out_dtype = xp.bool if dh.func_returns_bool[func_name] else in_dtype @@ -180,12 +163,12 @@ def test_func_promotion(func_name, in_dtypes, out_dtype, data): out = func(*arrays) except OverflowError: reject() - assert_dtype( - f'{func_name}({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype + ph.assert_dtype( + f'{func_name}({dh.fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype ) -promotion_params: List[Param[Tuple[DT, DT], DT]] = [] +promotion_params: List[Param[Tuple[DataType, DataType], DataType]] = [] for (dtype1, dtype2), promoted_dtype in dh.promotion_table.items(): p = pytest.param( (dtype1, dtype2), @@ -202,7 +185,9 @@ def test_where(in_dtypes, out_dtype, shapes, data): x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') cond = data.draw(xps.arrays(dtype=xp.bool, shape=shapes[2]), label='condition') out = xp.where(cond, x1, x2) - assert_dtype(f'where({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype) + ph.assert_dtype( + f'where({dh.fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype + ) numeric_promotion_params = promotion_params[1:] @@ -214,8 +199,8 @@ def test_tensordot(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') out = xp.tensordot(x1, x2) - assert_dtype( - f'tensordot({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype + ph.assert_dtype( + f'tensordot({dh.fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype ) @@ -225,16 +210,18 @@ def test_vecdot(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') out = xp.vecdot(x1, x2) - assert_dtype(f'vecdot({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype) + ph.assert_dtype( + f'vecdot({dh.fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype + ) -op_params: List[Param[str, str, Tuple[DT, ...], DT]] = [] +op_params: List[Param[str, str, Tuple[DataType, ...], DataType]] = [] op_to_symbol = {**dh.unary_op_to_symbol, **dh.binary_op_to_symbol} for op, symbol in op_to_symbol.items(): if op == '__matmul__': continue valid_in_dtypes = dh.func_in_dtypes[op] - ndtypes = nargs(op) + ndtypes = ph.nargs(op) if ndtypes == 1: for in_dtype in valid_in_dtypes: out_dtype = xp.bool if dh.func_returns_bool[op] else in_dtype @@ -293,10 +280,12 @@ def test_op_promotion(op, expr, in_dtypes, out_dtype, data): out = eval(expr, locals_) except OverflowError: reject() - assert_dtype(f'{op}({fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype) + ph.assert_dtype( + f'{op}({dh.fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype + ) -inplace_params: List[Param[str, str, Tuple[DT, ...], DT]] = [] +inplace_params: List[Param[str, str, Tuple[DataType, ...], DataType]] = [] for op, symbol in dh.inplace_op_to_symbol.items(): if op == '__imatmul__': continue @@ -334,10 +323,10 @@ def test_inplace_op_promotion(op, expr, in_dtypes, out_dtype, shapes, data): except OverflowError: reject() x1 = locals_['x1'] - assert_dtype(f'{op}({fmt_types(in_dtypes)})', 'x1.dtype', x1.dtype, out_dtype) + ph.assert_dtype(f'{op}({dh.fmt_types(in_dtypes)})', 'x1.dtype', x1.dtype, out_dtype) -op_scalar_params: List[Param[str, str, DT, ScalarType, DT]] = [] +op_scalar_params: List[Param[str, str, DataType, ScalarType, DataType]] = [] for op, symbol in dh.binary_op_to_symbol.items(): if op == '__matmul__': continue @@ -368,12 +357,12 @@ def test_op_scalar_promotion(op, expr, in_dtype, in_stype, out_dtype, data): out = eval(expr, {'x': x, 's': s}) except OverflowError: reject() - assert_dtype( - f'{op}({fmt_types((in_dtype, in_stype))})', 'out.dtype', out.dtype, out_dtype + ph.assert_dtype( + f'{op}({dh.fmt_types((in_dtype, in_stype))})', 'out.dtype', out.dtype, out_dtype ) -inplace_scalar_params: List[Param[str, str, DT, ScalarType]] = [] +inplace_scalar_params: List[Param[str, str, DataType, ScalarType]] = [] for op, symbol in dh.inplace_op_to_symbol.items(): if op == '__imatmul__': continue @@ -405,7 +394,9 @@ def test_inplace_op_scalar_promotion(op, expr, dtype, in_stype, data): reject() x = locals_['x'] assert x.dtype == dtype, f'{x.dtype=!s}, but should be {dtype}' - assert_dtype(f'{op}({fmt_types((dtype, in_stype))})', 'x.dtype', x.dtype, dtype) + ph.assert_dtype( + f'{op}({dh.fmt_types((dtype, in_stype))})', 'x.dtype', x.dtype, dtype + ) if __name__ == '__main__': diff --git a/array_api_tests/typing.py b/array_api_tests/typing.py new file mode 100644 index 00000000..d61d0baf --- /dev/null +++ b/array_api_tests/typing.py @@ -0,0 +1,11 @@ +from typing import Tuple, Type, Union, Any + +__all__ = [ + "DataType", + "ScalarType", + "Param", +] + +DataType = Type[Any] +ScalarType = Union[Type[bool], Type[int], Type[float]] +Param = Tuple From ed76b321d1c19b58220bd5221816f16349670fbb Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 21 Oct 2021 17:16:23 +0100 Subject: [PATCH 17/24] Construct test case name in `ph.assert_dtype()` --- array_api_tests/pytest_helpers.py | 19 ++++++++-- array_api_tests/test_type_promotion.py | 51 ++++++-------------------- 2 files changed, 27 insertions(+), 43 deletions(-) diff --git a/array_api_tests/pytest_helpers.py b/array_api_tests/pytest_helpers.py index c6b9d8fc..1d93b868 100644 --- a/array_api_tests/pytest_helpers.py +++ b/array_api_tests/pytest_helpers.py @@ -1,4 +1,5 @@ from inspect import getfullargspec +from typing import Tuple from . import dtype_helpers as dh from . import function_stubs @@ -38,11 +39,21 @@ def doesnt_raise(function, message=''): def nargs(func_name): return len(getfullargspec(getattr(function_stubs, func_name)).args) -def assert_dtype(test_case: str, result_name: str, dtype: DataType, expected: DataType): + +def assert_dtype( + func_name: str, + in_dtypes: Tuple[DataType, ...], + out_name: str, + out_dtype: DataType, + expected: DataType +): + f_in_dtypes = dh.fmt_types(in_dtypes) + f_out_dtype = dh.dtype_to_name[out_dtype] + f_expected = dh.dtype_to_name[expected] msg = ( - f'{result_name}={dh.dtype_to_name[dtype]}, ' - f'but should be {dh.dtype_to_name[expected]} [{test_case}]' + f"{out_name}={f_out_dtype}, but should be {f_expected} " + f"[{func_name}({f_in_dtypes})]" ) - assert dtype == expected, msg + assert out_dtype == expected, msg diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 6e306160..07992b69 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -20,9 +20,7 @@ @given(hh.mutually_promotable_dtypes(None)) def test_result_type(dtypes): out = xp.result_type(*dtypes) - ph.assert_dtype( - f'result_type({dh.fmt_types(dtypes)})', 'out', out, dh.result_type(*dtypes) - ) + ph.assert_dtype('result_type', dtypes, 'out', out, dh.result_type(*dtypes)) @given( @@ -37,9 +35,8 @@ def test_meshgrid(dtypes, data): arrays.append(x) out = xp.meshgrid(*arrays) expected = dh.result_type(*dtypes) - test_case = f'meshgrid({dh.fmt_types(dtypes)})' for i, x in enumerate(out): - ph.assert_dtype(test_case, f'out[{i}].dtype', x.dtype, expected) + ph.assert_dtype('meshgrid', dtypes, f'out[{i}].dtype', x.dtype, expected) @given( @@ -53,12 +50,7 @@ def test_concat(shape, dtypes, data): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) out = xp.concat(arrays) - ph.assert_dtype( - f'concat({dh.fmt_types(dtypes)})', - 'out.dtype', - out.dtype, - dh.result_type(*dtypes), - ) + ph.assert_dtype('concat', dtypes, 'out.dtype', out.dtype, dh.result_type(*dtypes)) @given( @@ -72,12 +64,7 @@ def test_stack(shape, dtypes, data): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) out = xp.stack(arrays) - ph.assert_dtype( - f'stack({dh.fmt_types(dtypes)})', - 'out.dtype', - out.dtype, - dh.result_type(*dtypes), - ) + ph.assert_dtype('stack', dtypes, 'out.dtype', out.dtype, dh.result_type(*dtypes)) bitwise_shift_funcs = [ @@ -163,9 +150,7 @@ def test_func_promotion(func_name, in_dtypes, out_dtype, data): out = func(*arrays) except OverflowError: reject() - ph.assert_dtype( - f'{func_name}({dh.fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype - ) + ph.assert_dtype(func_name, in_dtypes, 'out.dtype', out.dtype, out_dtype) promotion_params: List[Param[Tuple[DataType, DataType], DataType]] = [] @@ -185,9 +170,7 @@ def test_where(in_dtypes, out_dtype, shapes, data): x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') cond = data.draw(xps.arrays(dtype=xp.bool, shape=shapes[2]), label='condition') out = xp.where(cond, x1, x2) - ph.assert_dtype( - f'where({dh.fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype - ) + ph.assert_dtype('where', in_dtypes, 'out.dtype', out.dtype, out_dtype) numeric_promotion_params = promotion_params[1:] @@ -199,9 +182,7 @@ def test_tensordot(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') out = xp.tensordot(x1, x2) - ph.assert_dtype( - f'tensordot({dh.fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype - ) + ph.assert_dtype('tensordot', in_dtypes, 'out.dtype', out.dtype, out_dtype) @pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_params) @@ -210,9 +191,7 @@ def test_vecdot(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') out = xp.vecdot(x1, x2) - ph.assert_dtype( - f'vecdot({dh.fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype - ) + ph.assert_dtype('vecdot', in_dtypes, 'out.dtype', out.dtype, out_dtype) op_params: List[Param[str, str, Tuple[DataType, ...], DataType]] = [] @@ -280,9 +259,7 @@ def test_op_promotion(op, expr, in_dtypes, out_dtype, data): out = eval(expr, locals_) except OverflowError: reject() - ph.assert_dtype( - f'{op}({dh.fmt_types(in_dtypes)})', 'out.dtype', out.dtype, out_dtype - ) + ph.assert_dtype(op, in_dtypes, 'out.dtype', out.dtype, out_dtype) inplace_params: List[Param[str, str, Tuple[DataType, ...], DataType]] = [] @@ -323,7 +300,7 @@ def test_inplace_op_promotion(op, expr, in_dtypes, out_dtype, shapes, data): except OverflowError: reject() x1 = locals_['x1'] - ph.assert_dtype(f'{op}({dh.fmt_types(in_dtypes)})', 'x1.dtype', x1.dtype, out_dtype) + ph.assert_dtype(op, in_dtypes, 'x1.dtype', x1.dtype, out_dtype) op_scalar_params: List[Param[str, str, DataType, ScalarType, DataType]] = [] @@ -357,9 +334,7 @@ def test_op_scalar_promotion(op, expr, in_dtype, in_stype, out_dtype, data): out = eval(expr, {'x': x, 's': s}) except OverflowError: reject() - ph.assert_dtype( - f'{op}({dh.fmt_types((in_dtype, in_stype))})', 'out.dtype', out.dtype, out_dtype - ) + ph.assert_dtype(op, (in_dtype, in_stype), 'out.dtype', out.dtype, out_dtype) inplace_scalar_params: List[Param[str, str, DataType, ScalarType]] = [] @@ -394,9 +369,7 @@ def test_inplace_op_scalar_promotion(op, expr, dtype, in_stype, data): reject() x = locals_['x'] assert x.dtype == dtype, f'{x.dtype=!s}, but should be {dtype}' - ph.assert_dtype( - f'{op}({dh.fmt_types((dtype, in_stype))})', 'x.dtype', x.dtype, dtype - ) + ph.assert_dtype(op, (dtype, in_stype), 'x.dtype', x.dtype, dtype) if __name__ == '__main__': From 6206a55950e3e20d1fdda9ee0ae82cf20cb1f243 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 21 Oct 2021 17:27:21 +0100 Subject: [PATCH 18/24] Use `ph.assert_dtype` in `test_matmul` (proof of concept) --- array_api_tests/test_linalg.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/array_api_tests/test_linalg.py b/array_api_tests/test_linalg.py index 09d251ea..6684e253 100644 --- a/array_api_tests/test_linalg.py +++ b/array_api_tests/test_linalg.py @@ -26,8 +26,8 @@ mutually_promotable_dtypes, one_d_shapes, two_mutually_broadcastable_shapes, SQRT_MAX_ARRAY_SIZE, finite_matrices) -from .pytest_helpers import raises from . import dtype_helpers as dh +from . import pytest_helpers as ph from .test_broadcasting import broadcast_shapes @@ -274,13 +274,19 @@ def test_matmul(x1, x2): or len(x1.shape) >= 2 and len(x2.shape) >= 2 and x1.shape[-1] != x2.shape[-2]): # The spec doesn't specify what kind of exception is used here. Most # libraries will use a custom exception class. - raises(Exception, lambda: _array_module.matmul(x1, x2), + ph.raises(Exception, lambda: _array_module.matmul(x1, x2), "matmul did not raise an exception for invalid shapes") return else: res = _array_module.matmul(x1, x2) - assert res.dtype == dh.promotion_table[x1.dtype, x2.dtype], "matmul() did not return the correct dtype" + ph.assert_dtype( + "matmul", + (x1.dtype, x2.dtype), + "out.dtype", + res.dtype, + dh.promotion_table[x1.dtype, x2.dtype], + ) if len(x1.shape) == len(x2.shape) == 1: assert res.shape == () From d6665c0e8fcf0199d6c829e4e9945533cb45dd74 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 22 Oct 2021 09:35:15 +0100 Subject: [PATCH 19/24] Type hint some hypothesis helpers --- array_api_tests/hypothesis_helpers.py | 18 ++++++++++-------- array_api_tests/typing.py | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/array_api_tests/hypothesis_helpers.py b/array_api_tests/hypothesis_helpers.py index a7e96ee4..f6f0a8e2 100644 --- a/array_api_tests/hypothesis_helpers.py +++ b/array_api_tests/hypothesis_helpers.py @@ -2,7 +2,7 @@ from operator import mul from math import sqrt import itertools -from typing import Tuple, Optional +from typing import Tuple, Optional, List from hypothesis import assume from hypothesis.strategies import (lists, integers, sampled_from, @@ -11,6 +11,7 @@ from .pytest_helpers import nargs from .array_helpers import ndindex +from .typing import DataType, Shape from . import dtype_helpers as dh from ._array_module import (full, float32, float64, bool as bool_dtype, _UndefinedStub, eye, broadcast_to) @@ -49,7 +50,7 @@ _dtype_categories = [(xp.bool,), dh.uint_dtypes, dh.int_dtypes, dh.float_dtypes] _sorted_dtypes = [d for category in _dtype_categories for d in category] -def _dtypes_sorter(dtype_pair): +def _dtypes_sorter(dtype_pair: Tuple[DataType, DataType]): dtype1, dtype2 = dtype_pair if dtype1 == dtype2: return _sorted_dtypes.index(dtype1) @@ -66,7 +67,7 @@ def _dtypes_sorter(dtype_pair): key += 1 return key -promotable_dtypes = sorted(dh.promotion_table.keys(), key=_dtypes_sorter) +promotable_dtypes: List[Tuple[DataType, DataType]] = sorted(dh.promotion_table.keys(), key=_dtypes_sorter) if FILTER_UNDEFINED_DTYPES: promotable_dtypes = [ @@ -79,8 +80,8 @@ def _dtypes_sorter(dtype_pair): def mutually_promotable_dtypes( max_size: Optional[int] = 2, *, - dtypes=dh.all_dtypes, -) -> SearchStrategy[Tuple]: + dtypes: Tuple[DataType, ...] = dh.all_dtypes, +) -> SearchStrategy[Tuple[DataType, ...]]: if max_size == 2: return sampled_from( [(i, j) for i, j in promotable_dtypes if i in dtypes and j in dtypes] @@ -164,7 +165,7 @@ def matrix_shapes(draw, stack_shapes=shapes()): def mutually_broadcastable_shapes( num_shapes: int, **kw -) -> SearchStrategy[Tuple[Tuple[int, ...], ...]]: +) -> SearchStrategy[Tuple[Shape, ...]]: return ( xps.mutually_broadcastable_shapes(num_shapes, **kw) .map(lambda BS: BS.input_shapes) @@ -347,8 +348,9 @@ def multiaxis_indices(draw, shapes): def two_mutual_arrays( - dtypes=dh.all_dtypes, two_shapes=two_mutually_broadcastable_shapes -): + dtypes: Tuple[DataType, ...] = dh.all_dtypes, + two_shapes: SearchStrategy[Tuple[Shape, Shape]] = two_mutually_broadcastable_shapes, +) -> SearchStrategy: mutual_dtypes = shared(mutually_promotable_dtypes(dtypes=dtypes)) mutual_shapes = shared(two_shapes) arrays1 = xps.arrays( diff --git a/array_api_tests/typing.py b/array_api_tests/typing.py index d61d0baf..93165c72 100644 --- a/array_api_tests/typing.py +++ b/array_api_tests/typing.py @@ -3,9 +3,11 @@ __all__ = [ "DataType", "ScalarType", + "Shape", "Param", ] DataType = Type[Any] ScalarType = Union[Type[bool], Type[int], Type[float]] +Shape = Tuple[int, ...] Param = Tuple From dc211d4db2a642ea0093db96193e97f54be1ddd4 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 22 Oct 2021 10:07:19 +0100 Subject: [PATCH 20/24] Default `expected` and `out_name` in `ph.assert_dtype()` --- array_api_tests/pytest_helpers.py | 9 ++++++--- array_api_tests/test_linalg.py | 8 +------- array_api_tests/test_type_promotion.py | 25 ++++++++++++------------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/array_api_tests/pytest_helpers.py b/array_api_tests/pytest_helpers.py index 1d93b868..129b6fc3 100644 --- a/array_api_tests/pytest_helpers.py +++ b/array_api_tests/pytest_helpers.py @@ -1,5 +1,5 @@ from inspect import getfullargspec -from typing import Tuple +from typing import Optional, Tuple from . import dtype_helpers as dh from . import function_stubs @@ -43,12 +43,15 @@ def nargs(func_name): def assert_dtype( func_name: str, in_dtypes: Tuple[DataType, ...], - out_name: str, out_dtype: DataType, - expected: DataType + expected: Optional[DataType] = None, + *, + out_name: str = "out.dtype", ): f_in_dtypes = dh.fmt_types(in_dtypes) f_out_dtype = dh.dtype_to_name[out_dtype] + if expected is None: + expected = dh.result_type(*in_dtypes) f_expected = dh.dtype_to_name[expected] msg = ( f"{out_name}={f_out_dtype}, but should be {f_expected} " diff --git a/array_api_tests/test_linalg.py b/array_api_tests/test_linalg.py index 6684e253..a180a516 100644 --- a/array_api_tests/test_linalg.py +++ b/array_api_tests/test_linalg.py @@ -280,13 +280,7 @@ def test_matmul(x1, x2): else: res = _array_module.matmul(x1, x2) - ph.assert_dtype( - "matmul", - (x1.dtype, x2.dtype), - "out.dtype", - res.dtype, - dh.promotion_table[x1.dtype, x2.dtype], - ) + ph.assert_dtype("matmul", (x1.dtype, x2.dtype), res.dtype) if len(x1.shape) == len(x2.shape) == 1: assert res.shape == () diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 07992b69..68fb060f 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -20,7 +20,7 @@ @given(hh.mutually_promotable_dtypes(None)) def test_result_type(dtypes): out = xp.result_type(*dtypes) - ph.assert_dtype('result_type', dtypes, 'out', out, dh.result_type(*dtypes)) + ph.assert_dtype('result_type', dtypes, out, out_name='out') @given( @@ -34,9 +34,8 @@ def test_meshgrid(dtypes, data): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) out = xp.meshgrid(*arrays) - expected = dh.result_type(*dtypes) for i, x in enumerate(out): - ph.assert_dtype('meshgrid', dtypes, f'out[{i}].dtype', x.dtype, expected) + ph.assert_dtype('meshgrid', dtypes, x.dtype, out_name=f'out[{i}].dtype') @given( @@ -50,7 +49,7 @@ def test_concat(shape, dtypes, data): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) out = xp.concat(arrays) - ph.assert_dtype('concat', dtypes, 'out.dtype', out.dtype, dh.result_type(*dtypes)) + ph.assert_dtype('concat', dtypes, out.dtype) @given( @@ -64,7 +63,7 @@ def test_stack(shape, dtypes, data): x = data.draw(xps.arrays(dtype=dtype, shape=shape), label=f'x{i}') arrays.append(x) out = xp.stack(arrays) - ph.assert_dtype('stack', dtypes, 'out.dtype', out.dtype, dh.result_type(*dtypes)) + ph.assert_dtype('stack', dtypes, out.dtype) bitwise_shift_funcs = [ @@ -150,7 +149,7 @@ def test_func_promotion(func_name, in_dtypes, out_dtype, data): out = func(*arrays) except OverflowError: reject() - ph.assert_dtype(func_name, in_dtypes, 'out.dtype', out.dtype, out_dtype) + ph.assert_dtype(func_name, in_dtypes, out.dtype, out_dtype) promotion_params: List[Param[Tuple[DataType, DataType], DataType]] = [] @@ -170,7 +169,7 @@ def test_where(in_dtypes, out_dtype, shapes, data): x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') cond = data.draw(xps.arrays(dtype=xp.bool, shape=shapes[2]), label='condition') out = xp.where(cond, x1, x2) - ph.assert_dtype('where', in_dtypes, 'out.dtype', out.dtype, out_dtype) + ph.assert_dtype('where', in_dtypes, out.dtype, out_dtype) numeric_promotion_params = promotion_params[1:] @@ -182,7 +181,7 @@ def test_tensordot(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') out = xp.tensordot(x1, x2) - ph.assert_dtype('tensordot', in_dtypes, 'out.dtype', out.dtype, out_dtype) + ph.assert_dtype('tensordot', in_dtypes, out.dtype, out_dtype) @pytest.mark.parametrize('in_dtypes, out_dtype', numeric_promotion_params) @@ -191,7 +190,7 @@ def test_vecdot(in_dtypes, out_dtype, shapes, data): x1 = data.draw(xps.arrays(dtype=in_dtypes[0], shape=shapes[0]), label='x1') x2 = data.draw(xps.arrays(dtype=in_dtypes[1], shape=shapes[1]), label='x2') out = xp.vecdot(x1, x2) - ph.assert_dtype('vecdot', in_dtypes, 'out.dtype', out.dtype, out_dtype) + ph.assert_dtype('vecdot', in_dtypes, out.dtype, out_dtype) op_params: List[Param[str, str, Tuple[DataType, ...], DataType]] = [] @@ -259,7 +258,7 @@ def test_op_promotion(op, expr, in_dtypes, out_dtype, data): out = eval(expr, locals_) except OverflowError: reject() - ph.assert_dtype(op, in_dtypes, 'out.dtype', out.dtype, out_dtype) + ph.assert_dtype(op, in_dtypes, out.dtype, out_dtype) inplace_params: List[Param[str, str, Tuple[DataType, ...], DataType]] = [] @@ -300,7 +299,7 @@ def test_inplace_op_promotion(op, expr, in_dtypes, out_dtype, shapes, data): except OverflowError: reject() x1 = locals_['x1'] - ph.assert_dtype(op, in_dtypes, 'x1.dtype', x1.dtype, out_dtype) + ph.assert_dtype(op, in_dtypes, x1.dtype, out_dtype, out_name='x1.dtype') op_scalar_params: List[Param[str, str, DataType, ScalarType, DataType]] = [] @@ -334,7 +333,7 @@ def test_op_scalar_promotion(op, expr, in_dtype, in_stype, out_dtype, data): out = eval(expr, {'x': x, 's': s}) except OverflowError: reject() - ph.assert_dtype(op, (in_dtype, in_stype), 'out.dtype', out.dtype, out_dtype) + ph.assert_dtype(op, (in_dtype, in_stype), out.dtype, out_dtype) inplace_scalar_params: List[Param[str, str, DataType, ScalarType]] = [] @@ -369,7 +368,7 @@ def test_inplace_op_scalar_promotion(op, expr, dtype, in_stype, data): reject() x = locals_['x'] assert x.dtype == dtype, f'{x.dtype=!s}, but should be {dtype}' - ph.assert_dtype(op, (dtype, in_stype), 'x.dtype', x.dtype, dtype) + ph.assert_dtype(op, (dtype, in_stype), x.dtype, dtype, out_name='x.dtype') if __name__ == '__main__': From 27ded9b00382636b9c1a5feb5fa2f167d9ffc277 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 22 Oct 2021 10:18:03 +0100 Subject: [PATCH 21/24] Accept single dtype in `dh.resut_type()` and thus `ph.assert_dtype()` --- array_api_tests/dtype_helpers.py | 6 ++++-- array_api_tests/meta/test_pytest_helpers.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 array_api_tests/meta/test_pytest_helpers.py diff --git a/array_api_tests/dtype_helpers.py b/array_api_tests/dtype_helpers.py index 56790cde..28844c87 100644 --- a/array_api_tests/dtype_helpers.py +++ b/array_api_tests/dtype_helpers.py @@ -151,9 +151,11 @@ class MinMax(NamedTuple): } -def result_type(*dtypes): - if len(dtypes) < 2: +def result_type(*dtypes: DataType): + if len(dtypes) == 0: raise ValueError() + elif len(dtypes) == 1: + return dtypes[0] result = promotion_table[dtypes[0], dtypes[1]] for i in range(2, len(dtypes)): result = promotion_table[result, dtypes[i]] diff --git a/array_api_tests/meta/test_pytest_helpers.py b/array_api_tests/meta/test_pytest_helpers.py new file mode 100644 index 00000000..9b0f4fad --- /dev/null +++ b/array_api_tests/meta/test_pytest_helpers.py @@ -0,0 +1,13 @@ +from pytest import raises + +from .. import pytest_helpers as ph +from .. import _array_module as xp + + +def test_assert_dtype(): + ph.assert_dtype("promoted_func", (xp.uint8, xp.int8), xp.int16) + with raises(AssertionError): + ph.assert_dtype("bad_func", (xp.uint8, xp.int8), xp.float32) + ph.assert_dtype("bool_func", (xp.uint8, xp.int8), xp.bool, xp.bool) + ph.assert_dtype("single_promoted_func", (xp.uint8,), xp.uint8) + ph.assert_dtype("single_bool_func", (xp.uint8,), xp.bool, xp.bool) From 8acad7b5b806ed9a93301dd1895e3cf833b4f3e5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 22 Oct 2021 11:11:25 +0100 Subject: [PATCH 22/24] Remove `sanity_check()` in elementwise We now test that `two_mutual_arrays()` generates mutually promotable dtypes --- .../meta/test_hypothesis_helpers.py | 3 +- array_api_tests/test_elementwise_functions.py | 30 ------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/array_api_tests/meta/test_hypothesis_helpers.py b/array_api_tests/meta/test_hypothesis_helpers.py index 2edf072f..f583e711 100644 --- a/array_api_tests/meta/test_hypothesis_helpers.py +++ b/array_api_tests/meta/test_hypothesis_helpers.py @@ -9,7 +9,6 @@ from .. import dtype_helpers as dh from .. import hypothesis_helpers as hh from ..test_broadcasting import broadcast_shapes -from ..test_elementwise_functions import sanity_check UNDEFINED_DTYPES = any(isinstance(d, _UndefinedStub) for d in dh.all_dtypes) pytestmark = [pytest.mark.skipif(UNDEFINED_DTYPES, reason="undefined dtypes")] @@ -52,7 +51,7 @@ def test_two_broadcastable_shapes(pair): @given(*hh.two_mutual_arrays()) def test_two_mutual_arrays(x1, x2): - sanity_check(x1, x2) + assert (x1.dtype, x2.dtype) in dh.promotion_table.keys() assert broadcast_shapes(x1.shape, x2.shape) in (x1.shape, x2.shape) diff --git a/array_api_tests/test_elementwise_functions.py b/array_api_tests/test_elementwise_functions.py index 4e6f9f44..5aee5c2d 100644 --- a/array_api_tests/test_elementwise_functions.py +++ b/array_api_tests/test_elementwise_functions.py @@ -42,13 +42,6 @@ def two_array_scalars(draw, dtype1, dtype2): # hh.mutually_promotable_dtypes()) return draw(hh.array_scalars(st.just(dtype1))), draw(hh.array_scalars(st.just(dtype2))) -# TODO: refactor this into dtype_helpers.py, see https://github.com/data-apis/array-api-tests/pull/26 -def sanity_check(x1, x2): - try: - dh.promotion_table[x1.dtype, x2.dtype] - except ValueError: - raise RuntimeError("Error in test generation (probably a bug in the test suite") - @given(xps.arrays(dtype=xps.numeric_dtypes(), shape=hh.shapes())) def test_abs(x): if dh.is_int_dtype(x.dtype): @@ -94,7 +87,6 @@ def test_acosh(x): @given(*hh.two_mutual_arrays(dh.numeric_dtypes)) def test_add(x1, x2): - sanity_check(x1, x2) a = xp.add(x1, x2) b = xp.add(x2, x1) @@ -136,7 +128,6 @@ def test_atan(x): @given(*hh.two_mutual_arrays(dh.float_dtypes)) def test_atan2(x1, x2): - sanity_check(x1, x2) a = xp.atan2(x1, x2) INFINITY1 = ah.infinity(x1.shape, x1.dtype) INFINITY2 = ah.infinity(x2.shape, x2.dtype) @@ -183,7 +174,6 @@ def test_atanh(x): @given(*hh.two_mutual_arrays(dh.bool_and_all_int_dtypes)) def test_bitwise_and(x1, x2): - sanity_check(x1, x2) out = xp.bitwise_and(x1, x2) # TODO: generate indices without broadcasting arrays (see test_equal comment) @@ -210,7 +200,6 @@ def test_bitwise_and(x1, x2): @given(*hh.two_mutual_arrays(dh.all_int_dtypes)) def test_bitwise_left_shift(x1, x2): - sanity_check(x1, x2) assume(not ah.any(ah.isnegative(x2))) out = xp.bitwise_left_shift(x1, x2) @@ -249,7 +238,6 @@ def test_bitwise_invert(x): @given(*hh.two_mutual_arrays(dh.bool_and_all_int_dtypes)) def test_bitwise_or(x1, x2): - sanity_check(x1, x2) out = xp.bitwise_or(x1, x2) # TODO: generate indices without broadcasting arrays (see test_equal comment) @@ -276,7 +264,6 @@ def test_bitwise_or(x1, x2): @given(*hh.two_mutual_arrays(dh.all_int_dtypes)) def test_bitwise_right_shift(x1, x2): - sanity_check(x1, x2) assume(not ah.any(ah.isnegative(x2))) out = xp.bitwise_right_shift(x1, x2) @@ -297,7 +284,6 @@ def test_bitwise_right_shift(x1, x2): @given(*hh.two_mutual_arrays(dh.bool_and_all_int_dtypes)) def test_bitwise_xor(x1, x2): - sanity_check(x1, x2) out = xp.bitwise_xor(x1, x2) # TODO: generate indices without broadcasting arrays (see test_equal comment) @@ -356,7 +342,6 @@ def test_cosh(x): @given(*hh.two_mutual_arrays(dh.float_dtypes)) def test_divide(x1, x2): - sanity_check(x1, x2) xp.divide(x1, x2) # There isn't much we can test here. The spec doesn't require any behavior # beyond the special cases, and indeed, there aren't many mathematical @@ -367,7 +352,6 @@ def test_divide(x1, x2): @given(*hh.two_mutual_arrays()) def test_equal(x1, x2): - sanity_check(x1, x2) a = ah.equal(x1, x2) # NOTE: ah.assert_exactly_equal() itself uses ah.equal(), so we must be careful # not to use it here. Otherwise, the test would be circular and @@ -449,7 +433,6 @@ def test_floor(x): @given(*hh.two_mutual_arrays(dh.numeric_dtypes)) def test_floor_divide(x1, x2): - sanity_check(x1, x2) if dh.is_int_dtype(x1.dtype): # The spec does not specify the behavior for division by 0 for integer # dtypes. A library may choose to raise an exception in this case, so @@ -473,7 +456,6 @@ def test_floor_divide(x1, x2): @given(*hh.two_mutual_arrays(dh.numeric_dtypes)) def test_greater(x1, x2): - sanity_check(x1, x2) a = xp.greater(x1, x2) # See the comments in test_equal() for a description of how this test @@ -502,7 +484,6 @@ def test_greater(x1, x2): @given(*hh.two_mutual_arrays(dh.numeric_dtypes)) def test_greater_equal(x1, x2): - sanity_check(x1, x2) a = xp.greater_equal(x1, x2) # See the comments in test_equal() for a description of how this test @@ -577,7 +558,6 @@ def test_isnan(x): @given(*hh.two_mutual_arrays(dh.numeric_dtypes)) def test_less(x1, x2): - sanity_check(x1, x2) a = ah.less(x1, x2) # See the comments in test_equal() for a description of how this test @@ -606,7 +586,6 @@ def test_less(x1, x2): @given(*hh.two_mutual_arrays(dh.numeric_dtypes)) def test_less_equal(x1, x2): - sanity_check(x1, x2) a = ah.less_equal(x1, x2) # See the comments in test_equal() for a description of how this test @@ -679,7 +658,6 @@ def test_log10(x): @given(*hh.two_mutual_arrays(dh.float_dtypes)) def test_logaddexp(x1, x2): - sanity_check(x1, x2) xp.logaddexp(x1, x2) # The spec doesn't require any behavior for this function. We could test # that this is indeed an approximation of log(exp(x1) + exp(x2)), but we @@ -687,7 +665,6 @@ def test_logaddexp(x1, x2): @given(*hh.two_mutual_arrays([xp.bool])) def test_logical_and(x1, x2): - sanity_check(x1, x2) a = ah.logical_and(x1, x2) # See the comments in test_equal @@ -707,7 +684,6 @@ def test_logical_not(x): @given(*hh.two_mutual_arrays([xp.bool])) def test_logical_or(x1, x2): - sanity_check(x1, x2) a = ah.logical_or(x1, x2) # See the comments in test_equal @@ -720,7 +696,6 @@ def test_logical_or(x1, x2): @given(*hh.two_mutual_arrays([xp.bool])) def test_logical_xor(x1, x2): - sanity_check(x1, x2) a = xp.logical_xor(x1, x2) # See the comments in test_equal @@ -733,7 +708,6 @@ def test_logical_xor(x1, x2): @given(*hh.two_mutual_arrays(dh.numeric_dtypes)) def test_multiply(x1, x2): - sanity_check(x1, x2) a = xp.multiply(x1, x2) b = xp.multiply(x2, x1) @@ -762,7 +736,6 @@ def test_negative(x): @given(*hh.two_mutual_arrays()) def test_not_equal(x1, x2): - sanity_check(x1, x2) a = xp.not_equal(x1, x2) # See the comments in test_equal() for a description of how this test @@ -798,7 +771,6 @@ def test_positive(x): @given(*hh.two_mutual_arrays(dh.float_dtypes)) def test_pow(x1, x2): - sanity_check(x1, x2) xp.pow(x1, x2) # There isn't much we can test here. The spec doesn't require any behavior # beyond the special cases, and indeed, there aren't many mathematical @@ -809,7 +781,6 @@ def test_pow(x1, x2): @given(*hh.two_mutual_arrays(dh.numeric_dtypes)) def test_remainder(x1, x2): assume(len(x1.shape) <= len(x2.shape)) # TODO: rework same sign testing below to remove this - sanity_check(x1, x2) out = xp.remainder(x1, x2) # out and x2 should have the same sign. @@ -868,7 +839,6 @@ def test_sqrt(x): @given(two_numeric_dtypes.flatmap(lambda i: two_array_scalars(*i))) def test_subtract(args): x1, x2 = args - sanity_check(x1, x2) # a = xp.subtract(x1, x2) @given(floating_scalars) From 2d913404f2fcd58328972cc35fe1b4e8c0ed310e Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 25 Oct 2021 09:41:33 +0100 Subject: [PATCH 23/24] Comment that non-elementwise promotion tests are temporary --- array_api_tests/test_type_promotion.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/array_api_tests/test_type_promotion.py b/array_api_tests/test_type_promotion.py index 68fb060f..e3c37c11 100644 --- a/array_api_tests/test_type_promotion.py +++ b/array_api_tests/test_type_promotion.py @@ -17,6 +17,10 @@ from .function_stubs import elementwise_functions +# TODO: move tests not covering elementwise funcs/ops into standalone tests +# result_type, meshgrid, concat, stack, where, tensordor, vecdot + + @given(hh.mutually_promotable_dtypes(None)) def test_result_type(dtypes): out = xp.result_type(*dtypes) From 866dcd87e4a9c530fc18afc9cb3cf383f9ce9ab3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 25 Oct 2021 13:04:01 +0100 Subject: [PATCH 24/24] Support incomplete case names for `xfails.txt` And update NumPy workflow to xfail the failing new test cases --- .github/workflows/numpy.yml | 6 ++++++ conftest.py | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/numpy.yml b/.github/workflows/numpy.yml index e4694fb0..262874e7 100644 --- a/.github/workflows/numpy.yml +++ b/.github/workflows/numpy.yml @@ -41,6 +41,12 @@ jobs: array_api_tests/test_signatures.py::test_function_keyword_only_args[__dlpack__] # floor_divide has an issue related to https://github.com/data-apis/array-api/issues/264 array_api_tests/test_elementwise_functions.py::test_floor_divide + # mesgrid doesn't return all arrays as the promoted dtype + array_api_tests/test_type_promotion.py::test_meshgrid + # https://github.com/numpy/numpy/pull/20066#issuecomment-947056094 + array_api_tests/test_type_promotion.py::test_where + # shape mismatches are not handled + array_api_tests/test_type_promotion.py::test_tensordot EOF diff --git a/conftest.py b/conftest.py index 804bab29..64c46df7 100644 --- a/conftest.py +++ b/conftest.py @@ -102,5 +102,6 @@ def pytest_collection_modifyitems(config, items): except StopIteration: pass # workflow xfail_ids - if item.nodeid in xfail_ids: - item.add_marker(mark.xfail(reason='xfails.txt')) + for id_ in xfail_ids: + if item.nodeid.startswith(id_): + item.add_marker(mark.xfail(reason='xfails.txt'))