- Notifications
You must be signed in to change notification settings - Fork 46
More improvements to test_linalg #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 18 commits
Commits
Show all changes
61 commits Select commit Hold shift + click to select a range
360df9f Add shape testing for vector_norm()
asmeurer e34f492 Test the dtype and stacks in the vector_norm() test
asmeurer 111c237 Remove an ununsed variable
asmeurer 4f3aa54 Use a simpler strategy for ord in test_vector_norm
asmeurer 979b81b Skip the test_vector_norm test on the NumPy CI
asmeurer 8df237a Fix syntax error
asmeurer d11a685 Fix the input strategies for test_tensordot()
asmeurer a776cd4 Add a test for the tensordot result shape
asmeurer 45b36d6 Test stacking for tensordot
asmeurer 414b322 Add allclose() and assert_allclose() helper functions
asmeurer 9bb8c7a Use assert_allclose() in the linalg tests for float inputs
asmeurer b3fb4ec Remove skip from test_eigh
asmeurer 241220e Disable eigenvectors stack test
asmeurer ca70fbe Reduce the relative tolerance in assert_allclose
asmeurer 720b309 Sort the eigenvalues when testing stacks
asmeurer 17d93bf Merge branch 'more-linalg2' of github.com:asmeurer/array-api-tests in…
asmeurer f439259 Sort the results in eigvalsh before comparing
asmeurer 75ca73a Remove the allclose testing in linalg
asmeurer d86a0a1 Add (commented out) stacking tests for solve()
asmeurer 9bccfa5 Remove unused none standin in the linalg tests
asmeurer f494b45 Don't compare float elements in test_tensordot
asmeurer 74add08 Fix test_vecdot
asmeurer f12be47 Fix typo in test_vecdot
asmeurer d41d0bd Expand vecdot tests
asmeurer 1220d6e Merge branch 'master' into more-linalg2
asmeurer a96a5df Merge branch 'master' into more-linalg2
asmeurer 48a8442 Check specially that the result of linalg functions is not a unnamed …
asmeurer fd6367f Use a more robust fallback helper for matrix_transpose
asmeurer 7017797 Be more constrained about constructing symmetric matrices
asmeurer 335574e Merge branch 'more-linalg2' of github.com:asmeurer/array-api-tests in…
asmeurer 246e38a Don't require the arguments to assert_keepdimable_shape to be positio…
asmeurer 02542ff Show the arrays in the error message for assert_exactly_equal
asmeurer 72974e0 Allow passing an extra assertion message to assert_equal in linalg an…
asmeurer 1daba5d Fix the true_value check for test_vecdot
asmeurer bbfe50f Fix the test_diagonal true value check
asmeurer 64b0342 Use a function instead of operation
asmeurer 9cb58a1 Add a comment
asmeurer 0b3e170 Merge branch 'master' into more-linalg2
asmeurer c51216b Remove flaky skips from linalg tests
asmeurer cffd076 Fix some issues in linalg tests from recent merge
asmeurer 3501116 Fix vector_norm to not use our custom arrays strategy
asmeurer 5c1aa45 Update _test_stacks to use updated ndindex behavior
asmeurer 7a46e6b Further limit the size of n in test_matrix_power
asmeurer 6d154f2 Fix test_trace
asmeurer 257aa13 Fix test_vecdot to only generate axis in [-min(x1.ndim, x2.ndim), -1]
asmeurer afc8a25 Update test_cross to test broadcastable shapes
asmeurer 3cb9912 Fix test_cross to use assert_dtype and assert_shape helpers
asmeurer 012ca19 Remove some completed TODO comments
asmeurer 5ceb81d Update linalg tests to test complex dtypes
asmeurer a4d419f Update linalg tests to use assert_dtype and assert_shape helpers
asmeurer 6f9db94 Factor out dtype logic from test_sum() and test_prod() and apply it t…
asmeurer 5aa9083 Remove unused allclose and assert_allclose helpers
asmeurer 938f086 Update ndindex version requirement
asmeurer 3856b8f Fix linting issue
asmeurer ccc6ca3 Skip `test_cross` in CI
honno 3092422 Test matmul, matrix_transpose, tensordot, and vecdot for the main and…
asmeurer 2d918e4 Merge branch 'more-linalg2' of github.com:asmeurer/array-api-tests in…
asmeurer 3fefd20 Remove need for filtering in `invertible_matrices()`
honno a76e051 Merge branch 'master' into more-linalg2
honno 268682d Skip flaky `test_reshape`
honno 0ddb0cd Less filtering in `positive_definitive_matrices`
honno File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -15,19 +15,22 @@ | |
| | ||
| import pytest | ||
| from hypothesis import assume, given | ||
| from hypothesis.strategies import (booleans, composite, none, tuples, integers, | ||
| shared, sampled_from, one_of, data, just) | ||
| from hypothesis.strategies import (booleans, composite, none, tuples, floats, | ||
| integers, shared, sampled_from, one_of, | ||
| data, just) | ||
| from ndindex import iter_indices | ||
| | ||
| from .array_helpers import assert_exactly_equal, asarray | ||
| import itertools | ||
| | ||
| from .array_helpers import assert_exactly_equal, asarray, assert_allclose | ||
| from .hypothesis_helpers import (xps, dtypes, shapes, kwargs, matrix_shapes, | ||
| square_matrix_shapes, symmetric_matrices, | ||
| positive_definite_matrices, MAX_ARRAY_SIZE, | ||
| invertible_matrices, two_mutual_arrays, | ||
| mutually_promotable_dtypes, one_d_shapes, | ||
| two_mutually_broadcastable_shapes, | ||
| SQRT_MAX_ARRAY_SIZE, finite_matrices, | ||
| rtol_shared_matrix_shapes, rtols) | ||
| rtol_shared_matrix_shapes, rtols, axes) | ||
| from . import dtype_helpers as dh | ||
| from . import pytest_helpers as ph | ||
| from . import shape_helpers as sh | ||
| | @@ -41,9 +44,23 @@ | |
| # Standin strategy for not yet implemented tests | ||
| todo = none() | ||
| | ||
| def assert_equal(x, y): | ||
| if x.dtype in dh.float_dtypes: | ||
| # It's too difficult to do an approximately equal test here because | ||
| # different routines can give completely different answers, and even | ||
| # when it does work, the elementwise comparisons are too slow. So for | ||
| # floating-point dtypes only test the shape and dtypes. | ||
| | ||
| # assert_allclose(x, y) | ||
| | ||
| assert x.shape == y.shape, f"The input arrays do not have the same shapes ({x.shape} != {y.shape})" | ||
| assert x.dtype == y.dtype, f"The input arrays do not have the same dtype ({x.dtype} != {y.dtype})" | ||
| ||
| else: | ||
| assert_exactly_equal(x, y) | ||
| | ||
| def _test_stacks(f, *args, res=None, dims=2, true_val=None, | ||
| matrix_axes=(-2, -1), | ||
| assert_equal=assert_exactly_equal, **kw): | ||
| assert_equal=assert_equal, **kw): | ||
| """ | ||
| Test that f(*args, **kw) maps across stacks of matrices | ||
| | ||
| | @@ -225,7 +242,6 @@ def true_diag(x_stack): | |
| | ||
| _test_stacks(linalg.diagonal, x, **kw, res=res, dims=1, true_val=true_diag) | ||
| | ||
| @pytest.mark.skip(reason="Inputs need to be restricted") # TODO | ||
| @pytest.mark.xp_extension('linalg') | ||
| @given(x=symmetric_matrices(finite=True)) | ||
| def test_eigh(x): | ||
| | @@ -242,8 +258,15 @@ def test_eigh(x): | |
| assert eigenvectors.dtype == x.dtype, "eigh().eigenvectors did not return the correct dtype" | ||
| assert eigenvectors.shape == x.shape, "eigh().eigenvectors did not return the correct shape" | ||
| | ||
| # Note: _test_stacks here is only testing the shape and dtype. The actual | ||
| # eigenvalues and eigenvectors may not be equal at all, since there is not | ||
| # requirements about how eigh computes an eigenbasis, or about the order | ||
| # of the eigenvalues | ||
| _test_stacks(lambda x: linalg.eigh(x).eigenvalues, x, | ||
| res=eigenvalues, dims=1) | ||
| | ||
| # TODO: Test that eigenvectors are orthonormal. | ||
| | ||
| _test_stacks(lambda x: linalg.eigh(x).eigenvectors, x, | ||
| res=eigenvectors, dims=2) | ||
| | ||
| | @@ -258,9 +281,14 @@ def test_eigvalsh(x): | |
| assert res.dtype == x.dtype, "eigvalsh() did not return the correct dtype" | ||
| assert res.shape == x.shape[:-1], "eigvalsh() did not return the correct shape" | ||
| | ||
| # Note: _test_stacks here is only testing the shape and dtype. The actual | ||
| # eigenvalues may not be equal at all, since there is not requirements or | ||
| # about the order of the eigenvalues, and the stacking code may use a | ||
| # different code path. | ||
| _test_stacks(linalg.eigvalsh, x, res=res, dims=1) | ||
| | ||
| # TODO: Should we test that the result is the same as eigh(x).eigenvalues? | ||
| # (probably no because the spec doesn't actually require that) | ||
| | ||
| # TODO: Test that res actually corresponds to the eigenvalues of x | ||
| | ||
| | @@ -309,8 +337,6 @@ def test_matmul(x1, x2): | |
| assert res.shape == stack_shape + (x1.shape[-2], x2.shape[-1]) | ||
| _test_stacks(_array_module.matmul, x1, x2, res=res) | ||
| | ||
| matrix_norm_shapes = shared(matrix_shapes()) | ||
| | ||
| @pytest.mark.xp_extension('linalg') | ||
| @given( | ||
| x=finite_matrices(), | ||
| | @@ -571,22 +597,118 @@ def test_svdvals(x): | |
| | ||
| # TODO: Check that svdvals() is the same as svd().s. | ||
| | ||
| _tensordot_pre_shapes = shared(two_mutually_broadcastable_shapes) | ||
| | ||
| @composite | ||
| def _tensordot_axes(draw): | ||
| shape1, shape2 = draw(_tensordot_pre_shapes) | ||
| ndim1, ndim2 = len(shape1), len(shape2) | ||
| isint = draw(booleans()) | ||
| | ||
| if isint: | ||
| N = min(ndim1, ndim2) | ||
| return draw(integers(0, N)) | ||
| else: | ||
| if ndim1 < ndim2: | ||
| first = draw(xps.valid_tuple_axes(ndim1)) | ||
| second = draw(xps.valid_tuple_axes(ndim2, min_size=len(first), | ||
| max_size=len(first))) | ||
| else: | ||
| second = draw(xps.valid_tuple_axes(ndim2)) | ||
| first = draw(xps.valid_tuple_axes(ndim1, min_size=len(second), | ||
| max_size=len(second))) | ||
| return (tuple(first), tuple(second)) | ||
| | ||
| tensordot_kw = shared(kwargs(axes=_tensordot_axes())) | ||
| | ||
| @composite | ||
| def tensordot_shapes(draw): | ||
| _shape1, _shape2 = map(list, draw(_tensordot_pre_shapes)) | ||
| ndim1, ndim2 = len(_shape1), len(_shape2) | ||
| kw = draw(tensordot_kw) | ||
| if 'axes' not in kw: | ||
| assume(ndim1 >= 2 and ndim2 >= 2) | ||
| axes = kw.get('axes', 2) | ||
| | ||
| if isinstance(axes, int): | ||
| axes = [list(range(-axes, 0)), list(range(0, axes))] | ||
| | ||
| first, second = axes | ||
| for i, j in zip(first, second): | ||
| try: | ||
| if -ndim2 <= j < ndim2 and _shape2[j] != 1: | ||
| _shape1[i] = _shape2[j] | ||
| if -ndim1 <= i < ndim1 and _shape1[i] != 1: | ||
| _shape2[j] = _shape1[i] | ||
| except: | ||
| raise | ||
| | ||
| shape1, shape2 = map(tuple, [_shape1, _shape2]) | ||
| return (shape1, shape2) | ||
| | ||
| def _test_tensordot_stacks(x1, x2, kw, res): | ||
| """ | ||
| Variant of _test_stacks for tensordot | ||
| | ||
| tensordot doesn't stack directly along the non-contracted dimensions like | ||
| the other linalg functions. Rather, it is stacked along the product of | ||
| each non-contracted dimension. These dimensions are independent of one | ||
| another and do not broadcast. | ||
| """ | ||
| shape1, shape2 = x1.shape, x2.shape | ||
| | ||
| axes = kw.get('axes', 2) | ||
| | ||
| if isinstance(axes, int): | ||
| res_axes = axes | ||
| axes = [list(range(-axes, 0)), list(range(0, axes))] | ||
| else: | ||
| # Convert something like (0, 4, 2) into (0, 2, 1) | ||
| res_axes = [] | ||
| for a, s in zip(axes, [shape1, shape2]): | ||
| indices = [range(len(s))[i] for i in a] | ||
| repl = dict(zip(sorted(indices), range(len(indices)))) | ||
| res_axes.append(tuple(repl[i] for i in indices)) | ||
| | ||
| for ((i,), (j,)), (res_idx,) in zip( | ||
| itertools.product( | ||
| iter_indices(shape1, skip_axes=axes[0]), | ||
| iter_indices(shape2, skip_axes=axes[1])), | ||
| iter_indices(res.shape)): | ||
| i, j, res_idx = i.raw, j.raw, res_idx.raw | ||
| | ||
| res_stack = res[res_idx] | ||
| x1_stack = x1[i] | ||
| x2_stack = x2[j] | ||
| decomp_res_stack = xp.tensordot(x1_stack, x2_stack, axes=res_axes) | ||
| assert_exactly_equal(res_stack, decomp_res_stack) | ||
| | ||
| @given( | ||
| dtypes=mutually_promotable_dtypes(dtypes=dh.numeric_dtypes), | ||
| shape=shapes(), | ||
| data=data(), | ||
| *two_mutual_arrays(dh.numeric_dtypes, two_shapes=tensordot_shapes()), | ||
| tensordot_kw, | ||
| ) | ||
| def test_tensordot(dtypes, shape, data): | ||
| def test_tensordot(x1, x2, kw): | ||
| # TODO: vary shapes, vary contracted axes, test different axes arguments | ||
| x1 = data.draw(xps.arrays(dtype=dtypes[0], shape=shape), label="x1") | ||
| x2 = data.draw(xps.arrays(dtype=dtypes[1], shape=shape), label="x2") | ||
| res = xp.tensordot(x1, x2, **kw) | ||
| | ||
| out = xp.tensordot(x1, x2, axes=len(shape)) | ||
| ph.assert_dtype("tensordot", [x1.dtype, x2.dtype], res.dtype) | ||
| | ||
| ph.assert_dtype("tensordot", dtypes, out.dtype) | ||
| # TODO: assert shape and elements | ||
| axes = _axes = kw.get('axes', 2) | ||
| | ||
| if isinstance(axes, int): | ||
| _axes = [list(range(-axes, 0)), list(range(0, axes))] | ||
| | ||
| _shape1 = list(x1.shape) | ||
| _shape2 = list(x2.shape) | ||
| for i, j in zip(*_axes): | ||
| _shape1[i] = _shape2[j] = None | ||
| _shape1 = tuple([i for i in _shape1 if i is not None]) | ||
| _shape2 = tuple([i for i in _shape2 if i is not None]) | ||
| result_shape = _shape1 + _shape2 | ||
| ph.assert_result_shape('tensordot', [x1.shape, x2.shape], res.shape, | ||
| expected=result_shape) | ||
| # TODO: assert stacking and elements | ||
| _test_tensordot_stacks(x1, x2, kw, res) | ||
| | ||
| @pytest.mark.xp_extension('linalg') | ||
| @given( | ||
| | @@ -645,11 +767,42 @@ def test_vecdot(dtypes, shape, data): | |
| # TODO: assert shape and elements | ||
| | ||
| | ||
| # Insanely large orders might not work. There isn't a limit specified in the | ||
| # spec, so we just limit to reasonable values here. | ||
| max_ord = 100 | ||
| | ||
| @pytest.mark.xp_extension('linalg') | ||
| @given( | ||
| x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes()), | ||
| kw=kwargs(axis=todo, keepdims=todo, ord=todo) | ||
| x=xps.arrays(dtype=xps.floating_dtypes(), shape=shapes(min_side=1)), | ||
| data=data(), | ||
| ) | ||
| def test_vector_norm(x, kw): | ||
| # res = linalg.vector_norm(x, **kw) | ||
| pass | ||
| def test_vector_norm(x, data): | ||
| kw = data.draw( | ||
| # We use data because axes is parameterized on x.ndim | ||
| kwargs(axis=axes(x.ndim), | ||
| keepdims=booleans(), | ||
| ord=one_of( | ||
| sampled_from([2, 1, 0, -1, -2, float("inf"), float("-inf")]), | ||
| integers(-max_ord, max_ord), | ||
| floats(-max_ord, max_ord), | ||
| )), label="kw") | ||
| | ||
| | ||
| res = linalg.vector_norm(x, **kw) | ||
| axis = kw.get('axis', None) | ||
| keepdims = kw.get('keepdims', False) | ||
| # TODO: Check that the ord values give the correct norms. | ||
| # ord = kw.get('ord', 2) | ||
| | ||
| _axes = sh.normalise_axis(axis, x.ndim) | ||
| | ||
| ph.assert_keepdimable_shape('linalg.vector_norm', res.shape, x.shape, | ||
| _axes, keepdims, **kw) | ||
| ph.assert_dtype('linalg.vector_norm', x.dtype, res.dtype) | ||
| | ||
| _kw = kw.copy() | ||
| _kw.pop('axis', None) | ||
| _test_stacks(linalg.vector_norm, x, res=res, | ||
| dims=x.ndim if keepdims else 0, | ||
| matrix_axes=_axes, **_kw | ||
| ) | ||
Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep looks good, this is really how we have to do it if we can't use masking.
Maybe move this and
assert_allclosetotest_linalg.pyfor now, as ideally what we'd be doing is standardising these utils across function groups then, and scoping these utils for each function group makes it easier to distinguish where they're used and their subtle differences. (standardising seems quite doable, I just need to get round to it)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One problem here is represents a pretty significant performance hit. Before this commit (with the exact equality test), the linalg tests take 15 seconds on my computer. After, they take 44 seconds. Performance isn't our top priority, but maybe we should try array operations and only fallback to this when there are nonfinite values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think I'd still like this for now, esp as I'm generally rethinking the pytest helpers for #200/general look at vectorisation. Not blocking tho.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I'm just going to delete them. I'm not using them anymore, because testing float arrays from linalg functions like this turned out to be too difficult.