1
1
from functools import reduce
2
2
from operator import mul
3
3
from math import sqrt
4
+ import itertools
4
5
5
6
from hypothesis import assume
6
7
from hypothesis .strategies import (lists , integers , sampled_from ,
7
8
shared , floats , just , composite , one_of ,
8
9
none , booleans )
9
- from hypothesis .extra .array_api import make_strategies_namespace
10
10
11
11
from .pytest_helpers import nargs
12
12
from .array_helpers import (dtype_ranges , integer_dtype_objects ,
13
13
floating_dtype_objects , numeric_dtype_objects ,
14
14
boolean_dtype_objects ,
15
- integer_or_boolean_dtype_objects , dtype_objects )
16
- from . _array_module import full , float32 , float64 , bool as bool_dtype , _UndefinedStub
15
+ integer_or_boolean_dtype_objects , dtype_objects ,
16
+ ndindex )
17
17
from .dtype_helpers import promotion_table
18
- from . import _array_module
18
+ from ._array_module import (full , float32 , float64 , bool as bool_dtype ,
19
+ _UndefinedStub , eye , broadcast_to )
19
20
from . import _array_module as xp
21
+ from . import xps
20
22
21
23
from .function_stubs import elementwise_functions
22
24
23
25
24
- xps = make_strategies_namespace (xp )
25
-
26
-
27
26
# Set this to True to not fail tests just because a dtype isn't implemented.
28
27
# If no compatible dtype is implemented for a given test, the test will fail
29
28
# with a hypothesis health check error. Note that this functionality will not
48
47
dtypes = dtypes .filter (lambda x : not isinstance (x , _UndefinedStub ))
49
48
50
49
shared_dtypes = shared (dtypes , key = "dtype" )
50
+ shared_floating_dtypes = shared (floating_dtypes , key = "dtype" )
51
51
52
52
53
53
sorted_table = sorted (promotion_table )
@@ -72,10 +72,6 @@ def mutually_promotable_dtypes(dtype_objects=dtype_objects):
72
72
[(i , j ) for i , j in sorted_table if i in dtype_objects and j in dtype_objects ]
73
73
)
74
74
75
- shared_mutually_promotable_dtype_pairs = shared (
76
- mutually_promotable_dtypes (), key = "mutually_promotable_dtype_pair"
77
- )
78
-
79
75
# shared() allows us to draw either the function or the function name and they
80
76
# will both correspond to the same function.
81
77
@@ -86,10 +82,10 @@ def mutually_promotable_dtypes(dtype_objects=dtype_objects):
86
82
lambda func_name : nargs (func_name ) > 1 )
87
83
88
84
elementwise_function_objects = elementwise_functions_names .map (
89
- lambda i : getattr (_array_module , i ))
85
+ lambda i : getattr (xp , i ))
90
86
array_functions = elementwise_function_objects
91
87
multiarg_array_functions = multiarg_array_functions_names .map (
92
- lambda i : getattr (_array_module , i ))
88
+ lambda i : getattr (xp , i ))
93
89
94
90
# Limit the total size of an array shape
95
91
MAX_ARRAY_SIZE = 10000
@@ -111,25 +107,72 @@ def tuples(elements, *, min_size=0, max_size=None, unique_by=None, unique=False)
111
107
lambda shape : prod (i for i in shape if i ) < MAX_ARRAY_SIZE
112
108
)
113
109
110
+ # Matrix shapes assume stacks of matrices
111
+ matrix_shapes = xps .array_shapes (min_dims = 2 , min_side = 1 ).filter (
112
+ lambda shape : prod (i for i in shape if i ) < MAX_ARRAY_SIZE
113
+ )
114
+
115
+ square_matrix_shapes = matrix_shapes .filter (lambda shape : shape [- 1 ] == shape [- 2 ])
116
+
114
117
two_mutually_broadcastable_shapes = xps .mutually_broadcastable_shapes (num_shapes = 2 )\
115
118
.map (lambda S : S .input_shapes )\
116
119
.filter (lambda S : all (prod (i for i in shape if i ) < MAX_ARRAY_SIZE for shape in S ))
117
120
121
+ # Note: This should become hermitian_matrices when complex dtypes are added
122
+ @composite
123
+ def symmetric_matrices (draw , dtypes = xps .floating_dtypes (), finite = True ):
124
+ shape = draw (square_matrix_shapes )
125
+ dtype = draw (dtypes )
126
+ elements = {'allow_nan' : False , 'allow_infinity' : False } if finite else None
127
+ a = draw (xps .arrays (dtype = dtype , shape = shape , elements = elements ))
128
+ upper = xp .triu (a )
129
+ lower = xp .triu (a , k = 1 ).mT
130
+ return upper + lower
131
+
132
+ @composite
133
+ def positive_definite_matrices (draw , dtypes = xps .floating_dtypes ()):
134
+ # For now just generate stacks of identity matrices
135
+ # TODO: Generate arbitrary positive definite matrices, for instance, by
136
+ # using something like
137
+ # https://github.com/scikit-learn/scikit-learn/blob/844b4be24/sklearn/datasets/_samples_generator.py#L1351.
138
+ n = draw (integers (0 ))
139
+ shape = draw (shapes ) + (n , n )
140
+ assume (prod (i for i in shape if i ) < MAX_ARRAY_SIZE )
141
+ dtype = draw (dtypes )
142
+ return broadcast_to (eye (n , dtype = dtype ), shape )
143
+
144
+ @composite
145
+ def invertible_matrices (draw , dtypes = xps .floating_dtypes ()):
146
+ # For now, just generate stacks of diagonal matrices.
147
+ n = draw (integers (0 , SQRT_MAX_ARRAY_SIZE ),)
148
+ stack_shape = draw (shapes )
149
+ shape = stack_shape + (n , n )
150
+ d = draw (xps .arrays (dtypes , shape = n * prod (stack_shape ),
151
+ elements = dict (allow_nan = False , allow_infinity = False )))
152
+ # Functions that require invertible matrices may do anything when it is
153
+ # singular, including raising an exception, so we make sure the diagonals
154
+ # are sufficiently nonzero to avoid any numerical issues.
155
+ assume (xp .all (xp .abs (d ) > 0.5 ))
156
+
157
+ a = xp .zeros (shape )
158
+ for j , (idx , i ) in enumerate (itertools .product (ndindex (stack_shape ), range (n ))):
159
+ a [idx + (i , i )] = d [j ]
160
+ return a
161
+
118
162
@composite
119
163
def two_broadcastable_shapes (draw ):
120
164
"""
121
165
This will produce two shapes (shape1, shape2) such that shape2 can be
122
166
broadcast to shape1.
123
167
"""
124
168
from .test_broadcasting import broadcast_shapes
125
- shape1 , shape2 = draw (two_mutually_broadcastable_shapes )
169
+ shape1 , shape2 = draw (two_mutually_broadcastable_shapes )
126
170
assume (broadcast_shapes (shape1 , shape2 ) == shape1 )
127
171
return (shape1 , shape2 )
128
172
129
173
sizes = integers (0 , MAX_ARRAY_SIZE )
130
174
sqrt_sizes = integers (0 , SQRT_MAX_ARRAY_SIZE )
131
175
132
- # TODO: Generate general arrays here, rather than just scalars.
133
176
numeric_arrays = xps .arrays (
134
177
dtype = shared (xps .floating_dtypes (), key = 'dtypes' ),
135
178
shape = shared (xps .array_shapes (), key = 'shapes' ),
@@ -233,25 +276,46 @@ def multiaxis_indices(draw, shapes):
233
276
234
277
# Avoid using 'in', which might do == on an array.
235
278
res_has_ellipsis = any (i is ... for i in res )
236
- if n_entries == len (shape ) and not res_has_ellipsis :
237
- # note("Adding extra")
238
- extra = draw (lists (one_of (integer_indices (sizes ), slices (sizes )), min_size = 0 , max_size = 3 ))
239
- res += extra
279
+ if not res_has_ellipsis :
280
+ if n_entries < len (shape ):
281
+ # The spec requires either an ellipsis or exactly as many indices
282
+ # as dimensions.
283
+ assume (False )
284
+ elif n_entries == len (shape ):
285
+ # note("Adding extra")
286
+ extra = draw (lists (one_of (integer_indices (sizes ), slices (sizes )), min_size = 0 , max_size = 3 ))
287
+ res += extra
240
288
return tuple (res )
241
289
242
290
243
- shared_arrays1 = xps .arrays (
244
- dtype = shared_mutually_promotable_dtype_pairs .map (lambda pair : pair [0 ]),
245
- shape = shared (two_mutually_broadcastable_shapes , key = "shape_pair" ).map (lambda pair : pair [0 ]),
246
- )
247
- shared_arrays2 = xps .arrays (
248
- dtype = shared_mutually_promotable_dtype_pairs .map (lambda pair : pair [1 ]),
249
- shape = shared (two_mutually_broadcastable_shapes , key = "shape_pair" ).map (lambda pair : pair [1 ]),
250
- )
291
+ def two_mutual_arrays (dtype_objects = dtype_objects ):
292
+ mutual_dtypes = shared (mutually_promotable_dtypes (dtype_objects ))
293
+ mutual_shapes = shared (two_mutually_broadcastable_shapes )
294
+ arrays1 = xps .arrays (
295
+ dtype = mutual_dtypes .map (lambda pair : pair [0 ]),
296
+ shape = mutual_shapes .map (lambda pair : pair [0 ]),
297
+ )
298
+ arrays2 = xps .arrays (
299
+ dtype = mutual_dtypes .map (lambda pair : pair [1 ]),
300
+ shape = mutual_shapes .map (lambda pair : pair [1 ]),
301
+ )
302
+ return arrays1 , arrays2
251
303
252
304
253
305
@composite
254
306
def kwargs (draw , ** kw ):
307
+ """
308
+ Strategy for keyword arguments
309
+
310
+ For a signature like f(x, /, dtype=None, val=1) use
311
+
312
+ @given(x=arrays(), kw=kwargs(a=none() | dtypes, val=integers()))
313
+ def test_f(x, kw):
314
+ res = f(x, **kw)
315
+
316
+ kw may omit the keyword argument, meaning the default for f will be used.
317
+
318
+ """
255
319
result = {}
256
320
for k , strat in kw .items ():
257
321
if draw (booleans ()):
0 commit comments