@@ -136,3 +136,69 @@ def test_asarray(self, input, dtype, format):
136136
137137 if isinstance (input , SparseArray ):
138138 assert sparse .asarray (input ).__class__ is input .__class__
139+
140+
141+ class TestArrayAPIReductions :
142+ """
143+ Array API standard compliance: reductions over the entire array must return
144+ a zero-dimensional array, not a NumPy scalar.
145+
146+ See: https://github.com/pydata/sparse/issues/921
147+ """
148+
149+ @pytest .mark .parametrize ("format" , ["coo" , "gcxs" ])
150+ @pytest .mark .parametrize (
151+ "fn, expected" ,
152+ [
153+ (sparse .sum , 2.0 ),
154+ (sparse .max , 1.0 ),
155+ (sparse .min , 0.0 ),
156+ (sparse .prod , 0.0 ),
157+ (sparse .mean , 0.5 ),
158+ ],
159+ )
160+ def test_full_reduction_returns_0d_array (self , fn , expected , format ):
161+ x = sparse .asarray (np .eye (2 ), format = format )
162+ result = fn (x )
163+ assert result .ndim == 0 , f"{ fn .__name__ } () over entire array returned ndim={ result .ndim } , expected 0-D array"
164+ assert isinstance (result , SparseArray ), (
165+ f"{ fn .__name__ } () returned { type (result ).__name__ } , expected a SparseArray"
166+ )
167+ assert abs (float (result ) - expected ) < 1e-9 , f"{ fn .__name__ } () returned { float (result )} , expected { expected } "
168+
169+ @pytest .mark .parametrize ("fn" , [sparse .any , sparse .all ])
170+ def test_boolean_reduction_returns_0d_array (self , fn ):
171+ x = sparse .asarray (np .eye (2 ), format = "coo" )
172+ result = fn (x )
173+ assert result .ndim == 0 , f"{ fn .__name__ } () returned ndim={ result .ndim } , expected 0-D array"
174+ assert isinstance (result , SparseArray ), (
175+ f"{ fn .__name__ } () returned { type (result ).__name__ } , expected a SparseArray"
176+ )
177+
178+ def test_partial_reduction_still_returns_nd_array (self ):
179+ """Axis-specific reductions must still return N-D sparse arrays."""
180+ x = sparse .asarray (np .eye (2 ), format = "coo" )
181+
182+ result_ax0 = sparse .sum (x , axis = 0 )
183+ assert result_ax0 .shape == (2 ,), f"Expected shape (2,), got { result_ax0 .shape } "
184+ assert isinstance (result_ax0 , SparseArray )
185+
186+ result_ax1 = sparse .sum (x , axis = 1 )
187+ assert result_ax1 .shape == (2 ,), f"Expected shape (2,), got { result_ax1 .shape } "
188+ assert isinstance (result_ax1 , SparseArray )
189+
190+ def test_keepdims_full_reduction (self ):
191+ """keepdims=True must preserve all dimensions as size-1."""
192+ x = sparse .asarray (np .eye (2 ), format = "coo" )
193+ result = sparse .sum (x , keepdims = True )
194+ assert result .shape == (1 , 1 ), f"Expected shape (1, 1), got { result .shape } "
195+ assert isinstance (result , SparseArray )
196+
197+ @pytest .mark .parametrize ("format" , ["coo" , "gcxs" ])
198+ def test_1d_full_reduction_returns_0d_array (self , format ):
199+ """1-D input fully reduced must also give a 0-D array."""
200+ x = sparse .asarray (np .array ([1.0 , 2.0 , 3.0 ]), format = format )
201+ result = sparse .sum (x )
202+ assert result .ndim == 0 , f"Expected 0-D array, got ndim={ result .ndim } "
203+ assert isinstance (result , SparseArray )
204+ assert abs (float (result ) - 6.0 ) < 1e-9
0 commit comments