77from tests .third_party .cupy import testing
88
99_all_methods = (
10- # " inverted_cdf" , # TODO(takagi) Not implemented
11- # " averaged_inverted_cdf" , # TODO(takagi) Not implemented
12- # " closest_observation" , # TODO(takagi) Not implemented
13- # " interpolated_inverted_cdf" , # TODO(takagi) Not implemented
14- # " hazen" , # TODO(takagi) Not implemented
15- # " weibull" , # TODO(takagi) Not implemented
10+ # ' inverted_cdf' , # TODO(takagi) Not implemented
11+ # ' averaged_inverted_cdf' , # TODO(takagi) Not implemented
12+ # ' closest_observation' , # TODO(takagi) Not implemented
13+ # ' interpolated_inverted_cdf' , # TODO(takagi) Not implemented
14+ # ' hazen' , # TODO(takagi) Not implemented
15+ # ' weibull' , # TODO(takagi) Not implemented
1616 "linear" ,
17- # " median_unbiased" , # TODO(takagi) Not implemented
18- # " normal_unbiased" , # TODO(takagi) Not implemented
17+ # ' median_unbiased' , # TODO(takagi) Not implemented
18+ # ' normal_unbiased' , # TODO(takagi) Not implemented
1919 "lower" ,
2020 "higher" ,
2121 "midpoint" ,
22- # "nearest", # TODO(hvy): Not implemented
22+ "nearest" ,
2323)
2424
2525
2626def for_all_methods (name = "method" ):
2727 return pytest .mark .parametrize (name , _all_methods )
2828
2929
30+ @pytest .mark .skip ("dpnp.quantile() is not implemented yet" )
3031@testing .with_requires ("numpy>=1.22.0rc1" )
31- class TestOrder :
32- @for_all_methods ()
32+ class TestQuantile :
33+
34+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
35+ def test_percentile_unexpected_method (self , dtype ):
36+ for xp in (numpy , cupy ):
37+ a = testing .shaped_random ((4 , 2 , 3 , 2 ), xp , dtype )
38+ q = testing .shaped_random ((5 ,), xp , dtype = dtype , scale = 100 )
39+ with pytest .raises (ValueError ):
40+ xp .percentile (a , q , axis = - 1 , method = "deadbeef" )
41+
42+ # See gh-4453
43+ @testing .for_float_dtypes ()
44+ def test_percentile_memory_access (self , dtype ):
45+ # Create an allocator that guarantees array allocated in
46+ # cupy.percentile call will be followed by a NaN
47+ original_allocator = cuda .get_allocator ()
48+
49+ def controlled_allocator (size ):
50+ memptr = original_allocator (size )
51+ base_size = memptr .mem .size
52+ assert base_size % 512 == 0
53+ item_size = dtype ().itemsize
54+ shape = (base_size // item_size ,)
55+ x = cupy .ndarray (memptr = memptr , shape = shape , dtype = dtype )
56+ x .fill (cupy .nan )
57+ return memptr
58+
59+ # Check that percentile still returns non-NaN results
60+ a = testing .shaped_random ((5 ,), cupy , dtype )
61+ q = cupy .array ((0 , 100 ), dtype = dtype )
62+
63+ cuda .set_allocator (controlled_allocator )
64+ try :
65+ percentiles = cupy .percentile (a , q , axis = None , method = "linear" )
66+ finally :
67+ cuda .set_allocator (original_allocator )
68+
69+ assert not cupy .any (cupy .isnan (percentiles ))
70+
71+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
72+ def test_quantile_unexpected_method (self , dtype ):
73+ for xp in (numpy , cupy ):
74+ a = testing .shaped_random ((4 , 2 , 3 , 2 ), xp , dtype )
75+ q = testing .shaped_random ((5 ,), xp , dtype = dtype , scale = 1 )
76+ with pytest .raises (ValueError ):
77+ xp .quantile (a , q , axis = - 1 , method = "deadbeef" )
78+
79+
80+ @pytest .mark .skip ("dpnp.quantile() is not implemented yet" )
81+ @testing .with_requires ("numpy>=1.22.0rc1" )
82+ @for_all_methods ()
83+ class TestQuantileMethods :
84+
3385 @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
3486 @testing .numpy_cupy_allclose ()
3587 def test_percentile_defaults (self , xp , dtype , method ):
3688 a = testing .shaped_random ((2 , 3 , 8 ), xp , dtype )
3789 q = testing .shaped_random ((3 ,), xp , dtype = dtype , scale = 100 )
3890 return xp .percentile (a , q , method = method )
3991
40- @for_all_methods ()
4192 @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
4293 @testing .numpy_cupy_allclose ()
4394 def test_percentile_q_list (self , xp , dtype , method ):
4495 a = testing .shaped_arange ((1001 ,), xp , dtype )
4596 q = [99 , 99.9 ]
4697 return xp .percentile (a , q , method = method )
4798
48- @for_all_methods ()
4999 @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
50100 @testing .numpy_cupy_allclose (rtol = 1e-6 )
51101 def test_percentile_no_axis (self , xp , dtype , method ):
52102 a = testing .shaped_random ((10 , 2 , 4 , 8 ), xp , dtype )
53103 q = testing .shaped_random ((5 ,), xp , dtype = dtype , scale = 100 )
54104 return xp .percentile (a , q , axis = None , method = method )
55105
56- @for_all_methods ()
57106 @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
58107 @testing .numpy_cupy_allclose (rtol = 1e-6 )
59108 def test_percentile_neg_axis (self , xp , dtype , method ):
60109 a = testing .shaped_random ((4 , 3 , 10 , 2 , 8 ), xp , dtype )
61110 q = testing .shaped_random ((5 ,), xp , dtype = dtype , scale = 100 )
62111 return xp .percentile (a , q , axis = - 1 , method = method )
63112
64- @for_all_methods ()
65113 @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
66114 @testing .numpy_cupy_allclose (rtol = 1e-6 )
67115 def test_percentile_tuple_axis (self , xp , dtype , method ):
68116 a = testing .shaped_random ((1 , 6 , 3 , 2 ), xp , dtype )
69117 q = testing .shaped_random ((5 ,), xp , dtype = dtype , scale = 100 )
70118 return xp .percentile (a , q , axis = (0 , 1 , 2 ), method = method )
71119
72- @for_all_methods ()
73120 @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
74121 @testing .numpy_cupy_allclose ()
75122 def test_percentile_scalar_q (self , xp , dtype , method ):
76123 a = testing .shaped_random ((2 , 3 , 8 ), xp , dtype )
77124 q = 13.37
78125 return xp .percentile (a , q , method = method )
79126
80- @for_all_methods ()
81127 @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
82128 @testing .numpy_cupy_allclose (rtol = 1e-5 )
83129 def test_percentile_keepdims (self , xp , dtype , method ):
84130 a = testing .shaped_random ((7 , 2 , 9 , 2 ), xp , dtype )
85131 q = testing .shaped_random ((5 ,), xp , dtype = dtype , scale = 100 )
86132 return xp .percentile (a , q , axis = None , keepdims = True , method = method )
87133
88- @for_all_methods ()
89134 @testing .for_float_dtypes (no_float16 = True ) # NumPy raises error on int8
90135 @testing .numpy_cupy_allclose (rtol = 1e-6 )
91136 def test_percentile_out (self , xp , dtype , method ):
@@ -94,7 +139,17 @@ def test_percentile_out(self, xp, dtype, method):
94139 out = testing .shaped_random ((5 , 10 , 2 , 3 ), xp , dtype )
95140 return xp .percentile (a , q , axis = - 1 , method = method , out = out )
96141
97- @for_all_methods ()
142+ @testing .for_float_dtypes (no_float16 = True )
143+ @testing .numpy_cupy_allclose (rtol = 1e-6 )
144+ def test_percentile_overwrite (self , xp , dtype , method ):
145+ a = testing .shaped_random ((10 , 2 , 3 , 2 ), xp , dtype )
146+ ap = a .copy ()
147+ q = testing .shaped_random ((5 ,), xp , dtype = dtype , scale = 100 )
148+ res = xp .percentile (ap , q , axis = - 1 , method = method , overwrite_input = True )
149+
150+ assert not xp .all (ap == a )
151+ return res
152+
98153 @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
99154 def test_percentile_bad_q (self , dtype , method ):
100155 for xp in (numpy , cupy ):
@@ -104,38 +159,127 @@ def test_percentile_bad_q(self, dtype, method):
104159 xp .percentile (a , q , axis = - 1 , method = method )
105160
106161 @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
107- def test_percentile_unxpected_method (self , dtype ):
162+ def test_percentile_out_of_range_q (self , dtype , method ):
108163 for xp in (numpy , cupy ):
109164 a = testing .shaped_random ((4 , 2 , 3 , 2 ), xp , dtype )
110- q = testing .shaped_random ((5 ,), xp , dtype = dtype , scale = 100 )
165+ for q in [[- 0.1 ], [100.1 ]]:
166+ with pytest .raises (ValueError ):
167+ xp .percentile (a , q , axis = - 1 , method = method )
168+
169+ @testing .for_all_dtypes ()
170+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
171+ @testing .numpy_cupy_allclose ()
172+ def test_quantile_defaults (self , xp , dtype , method ):
173+ a = testing .shaped_random ((2 , 3 , 8 ), xp , dtype )
174+ q = testing .shaped_random ((3 ,), xp , scale = 1 )
175+ return xp .quantile (a , q , method = method )
176+
177+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
178+ @testing .numpy_cupy_allclose ()
179+ def test_quantile_q_list (self , xp , dtype , method ):
180+ a = testing .shaped_arange ((1001 ,), xp , dtype )
181+ q = [0.99 , 0.999 ]
182+ return xp .quantile (a , q , method = method )
183+
184+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
185+ @testing .numpy_cupy_allclose (rtol = 1e-5 )
186+ def test_quantile_no_axis (self , xp , dtype , method ):
187+ a = testing .shaped_random ((10 , 2 , 4 , 8 ), xp , dtype )
188+ q = testing .shaped_random ((5 ,), xp , scale = 1 )
189+ return xp .quantile (a , q , axis = None , method = method )
190+
191+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
192+ @testing .numpy_cupy_allclose (rtol = 1e-6 )
193+ def test_quantile_neg_axis (self , xp , dtype , method ):
194+ a = testing .shaped_random ((4 , 3 , 10 , 2 , 8 ), xp , dtype )
195+ q = testing .shaped_random ((5 ,), xp , scale = 1 )
196+ return xp .quantile (a , q , axis = - 1 , method = method )
197+
198+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
199+ @testing .numpy_cupy_allclose (rtol = 1e-6 )
200+ def test_quantile_tuple_axis (self , xp , dtype , method ):
201+ a = testing .shaped_random ((1 , 6 , 3 , 2 ), xp , dtype )
202+ q = testing .shaped_random ((5 ,), xp , scale = 1 )
203+ return xp .quantile (a , q , axis = (0 , 1 , 2 ), method = method )
204+
205+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
206+ @testing .numpy_cupy_allclose ()
207+ def test_quantile_scalar_q (self , xp , dtype , method ):
208+ a = testing .shaped_random ((2 , 3 , 8 ), xp , dtype )
209+ q = 0.1337
210+ return xp .quantile (a , q , method = method )
211+
212+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
213+ @testing .numpy_cupy_allclose (rtol = 1e-5 )
214+ def test_quantile_keepdims (self , xp , dtype , method ):
215+ a = testing .shaped_random ((7 , 2 , 9 , 2 ), xp , dtype )
216+ q = testing .shaped_random ((5 ,), xp , scale = 1 )
217+ return xp .quantile (a , q , axis = None , keepdims = True , method = method )
218+
219+ @testing .for_float_dtypes (no_float16 = True ) # NumPy raises error on int8
220+ @testing .numpy_cupy_allclose (rtol = 1e-6 )
221+ def test_quantile_out (self , xp , dtype , method ):
222+ a = testing .shaped_random ((10 , 2 , 3 , 2 ), xp , dtype )
223+ q = testing .shaped_random ((5 ,), xp , dtype = dtype , scale = 1 )
224+ out = testing .shaped_random ((5 , 10 , 2 , 3 ), xp , dtype )
225+ return xp .quantile (a , q , axis = - 1 , method = method , out = out )
226+
227+ @testing .for_float_dtypes (no_float16 = True )
228+ @testing .numpy_cupy_allclose (rtol = 1e-6 )
229+ def test_quantile_overwrite (self , xp , dtype , method ):
230+ a = testing .shaped_random ((10 , 2 , 3 , 2 ), xp , dtype )
231+ ap = a .copy ()
232+ q = testing .shaped_random ((5 ,), xp , dtype = dtype , scale = 1 )
233+
234+ res = xp .quantile (a , q , axis = - 1 , method = method , overwrite_input = True )
235+
236+ assert not xp .all (ap == a )
237+ return res
238+
239+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
240+ def test_quantile_bad_q (self , dtype , method ):
241+ for xp in (numpy , cupy ):
242+ a = testing .shaped_random ((4 , 2 , 3 , 2 ), xp , dtype )
243+ q = testing .shaped_random ((1 , 2 , 3 ), xp , dtype = dtype , scale = 1 )
111244 with pytest .raises (ValueError ):
112- xp .percentile (a , q , axis = - 1 , method = "deadbeef" )
245+ xp .quantile (a , q , axis = - 1 , method = method )
246+
247+ @testing .for_all_dtypes (no_float16 = True , no_bool = True , no_complex = True )
248+ def test_quantile_out_of_range_q (self , dtype , method ):
249+ for xp in (numpy , cupy ):
250+ a = testing .shaped_random ((4 , 2 , 3 , 2 ), xp , dtype )
251+ for q in [[- 0.1 ], [1.1 ]]:
252+ with pytest .raises (ValueError ):
253+ xp .quantile (a , q , axis = - 1 , method = method )
254+
255+
256+ class TestOrder :
113257
114258 @testing .for_all_dtypes (no_complex = True )
115259 @testing .numpy_cupy_allclose ()
116260 def test_nanmax_all (self , xp , dtype ):
117261 a = testing .shaped_random ((2 , 3 ), xp , dtype )
118262 return xp .nanmax (a )
119263
120- @testing .for_all_dtypes (no_complex = True )
264+ @testing .for_all_dtypes ()
121265 @testing .numpy_cupy_allclose ()
122266 def test_nanmax_axis_large (self , xp , dtype ):
123267 a = testing .shaped_random ((3 , 1000 ), xp , dtype )
124268 return xp .nanmax (a , axis = 0 )
125269
126- @testing .for_all_dtypes (no_complex = True )
270+ @testing .for_all_dtypes ()
127271 @testing .numpy_cupy_allclose ()
128272 def test_nanmax_axis0 (self , xp , dtype ):
129273 a = testing .shaped_random ((2 , 3 , 4 ), xp , dtype )
130274 return xp .nanmax (a , axis = 0 )
131275
132- @testing .for_all_dtypes (no_complex = True )
276+ @testing .for_all_dtypes ()
133277 @testing .numpy_cupy_allclose ()
134278 def test_nanmax_axis1 (self , xp , dtype ):
135279 a = testing .shaped_random ((2 , 3 , 4 ), xp , dtype )
136280 return xp .nanmax (a , axis = 1 )
137281
138- @testing .for_all_dtypes (no_complex = True )
282+ @testing .for_all_dtypes ()
139283 @testing .numpy_cupy_allclose ()
140284 def test_nanmax_axis2 (self , xp , dtype ):
141285 a = testing .shaped_random ((2 , 3 , 4 ), xp , dtype )
@@ -159,31 +303,31 @@ def test_nanmax_all_nan(self, xp, dtype):
159303 assert w [0 ].category is RuntimeWarning
160304 return m
161305
162- @testing .for_all_dtypes (no_complex = True )
306+ @testing .for_all_dtypes ()
163307 @testing .numpy_cupy_allclose ()
164308 def test_nanmin_all (self , xp , dtype ):
165309 a = testing .shaped_random ((2 , 3 ), xp , dtype )
166310 return xp .nanmin (a )
167311
168- @testing .for_all_dtypes (no_complex = True )
312+ @testing .for_all_dtypes ()
169313 @testing .numpy_cupy_allclose ()
170314 def test_nanmin_axis_large (self , xp , dtype ):
171315 a = testing .shaped_random ((3 , 1000 ), xp , dtype )
172316 return xp .nanmin (a , axis = 0 )
173317
174- @testing .for_all_dtypes (no_complex = True )
318+ @testing .for_all_dtypes ()
175319 @testing .numpy_cupy_allclose ()
176320 def test_nanmin_axis0 (self , xp , dtype ):
177321 a = testing .shaped_random ((2 , 3 , 4 ), xp , dtype )
178322 return xp .nanmin (a , axis = 0 )
179323
180- @testing .for_all_dtypes (no_complex = True )
324+ @testing .for_all_dtypes ()
181325 @testing .numpy_cupy_allclose ()
182326 def test_nanmin_axis1 (self , xp , dtype ):
183327 a = testing .shaped_random ((2 , 3 , 4 ), xp , dtype )
184328 return xp .nanmin (a , axis = 1 )
185329
186- @testing .for_all_dtypes (no_complex = True )
330+ @testing .for_all_dtypes ()
187331 @testing .numpy_cupy_allclose ()
188332 def test_nanmin_axis2 (self , xp , dtype ):
189333 a = testing .shaped_random ((2 , 3 , 4 ), xp , dtype )
@@ -248,3 +392,39 @@ def test_ptp_nan(self, xp, dtype):
248392 def test_ptp_all_nan (self , xp , dtype ):
249393 a = xp .array ([float ("nan" ), float ("nan" )], dtype )
250394 return xp .ptp (a )
395+
396+
397+ # See gh-4607
398+ # "Magic" values used in this test were empirically found to result in
399+ # non-monotonicity for less accurate linear interpolation formulas
400+ @pytest .mark .skip ("dpnp.percentile() is not implemented yet" )
401+ @testing .parameterize (
402+ * testing .product (
403+ {
404+ "magic_value" : (
405+ - 29 ,
406+ - 53 ,
407+ - 207 ,
408+ - 16373 ,
409+ - 99999 ,
410+ )
411+ }
412+ )
413+ )
414+ class TestPercentileMonotonic :
415+
416+ @testing .with_requires ("numpy>=1.22.0rc1" )
417+ @testing .for_float_dtypes (no_float16 = True )
418+ @testing .numpy_cupy_allclose ()
419+ def test_percentile_monotonic (self , dtype , xp ):
420+ a = testing .shaped_random ((5 ,), xp , dtype )
421+
422+ a [0 ] = self .magic_value
423+ a [1 ] = self .magic_value
424+ q = xp .linspace (0 , 100 , 21 )
425+ percentiles = xp .percentile (a , q , method = "linear" )
426+
427+ # Assert that percentile output increases monotonically
428+ assert xp .all (xp .diff (percentiles ) >= 0 )
429+
430+ return percentiles
0 commit comments