Repository: cupy/cupy

Issue:
Difference from SciPy about sparse min/max `sum_duplicates` parameter
I'm not sure its background, but currently, sparse min/max have `sum_duplicates` parameter and its default value is `False`. However, their SciPy counterparts don't have the parameter and always sum duplicate entries as `sum_duplicates=True`.

It would be better to change its default value to `False`.


Discussion:
This features was added in #2711
I would say toggling the default behavior from False to True to be consistent with scipy would be good. The reason I introduced the flag is as a performance enhancement to skip the sum_duplicates operation if the user knows their matrix is canonical. Changing the default flag will make it conform with the default scipy behavior.

For #3583, there's an open pull request on scipy that will implement the explicit nonzero features. I wouldn't remove the functionality from cupy for the sake of standardizing to scipy since it's a useful feature when explicit zeros are included in the sparse matrix. The default behavior for the non_zero flag is the same as standard scipy. The scipy pull request uses the keyword "explicit" rather than "nonzero" which might be a good change to make.

Resolving patch:
diff --git a/cupyx/scipy/sparse/data.py b/cupyx/scipy/sparse/data.py
--- a/cupyx/scipy/sparse/data.py
+++ b/cupyx/scipy/sparse/data.py
@@ -137,15 +137,14 @@ class _minmax_mixin(object):
 
     """
 
-    def _min_or_max_axis(self, axis, min_or_max, sum_duplicates, nonzero):
+    def _min_or_max_axis(self, axis, min_or_max, nonzero):
         N = self.shape[axis]
         if N == 0:
             raise ValueError("zero-size array to reduction operation")
         M = self.shape[1 - axis]
 
         mat = self.tocsc() if axis == 0 else self.tocsr()
-        if sum_duplicates:
-            mat.sum_duplicates()
+        mat.sum_duplicates()
 
         # Do the reudction
         value = mat._minor_reduce(min_or_max, axis, nonzero)
@@ -164,7 +163,7 @@ def _min_or_max_axis(self, axis, min_or_max, sum_duplicates, nonzero):
                 (value, (major_index, cupy.zeros(len(value)))),
                 dtype=self.dtype, shape=(M, 1))
 
-    def _min_or_max(self, axis, out, min_or_max, sum_duplicates, non_zero):
+    def _min_or_max(self, axis, out, min_or_max, non_zero):
         if out is not None:
             raise ValueError(("Sparse matrices do not support "
                               "an 'out' parameter."))
@@ -178,8 +177,7 @@ def _min_or_max(self, axis, out, min_or_max, sum_duplicates, non_zero):
             zero = cupy.zeros((), dtype=self.dtype)
             if self.nnz == 0:
                 return zero
-            if sum_duplicates:
-                self.sum_duplicates()
+            self.sum_duplicates()
             m = min_or_max(self.data)
             if non_zero:
                 return m
@@ -193,20 +191,17 @@ def _min_or_max(self, axis, out, min_or_max, sum_duplicates, non_zero):
             return m
 
         if axis == 0 or axis == 1:
-            return self._min_or_max_axis(axis, min_or_max, sum_duplicates,
-                                         non_zero)
+            return self._min_or_max_axis(axis, min_or_max, non_zero)
         else:
             raise ValueError("axis out of range")
 
-    def _arg_min_or_max_axis(self, axis, op, sum_duplicates):
+    def _arg_min_or_max_axis(self, axis, op):
         if self.shape[axis] == 0:
             raise ValueError("Can't apply the operation along a zero-sized "
                              "dimension.")
 
         mat = self.tocsc() if axis == 0 else self.tocsr()
-
-        if sum_duplicates:
-            mat.sum_duplicates()
+        mat.sum_duplicates()
 
         # Do the reudction
         value = mat._arg_minor_reduce(op, axis)
@@ -216,7 +211,7 @@ def _arg_min_or_max_axis(self, axis, op, sum_duplicates):
         else:
             return value[:, None]
 
-    def _arg_min_or_max(self, axis, out, op, compare, sum_duplicates):
+    def _arg_min_or_max(self, axis, out, op, compare):
         if out is not None:
             raise ValueError("Sparse matrices do not support "
                              "an 'out' parameter.")
@@ -234,8 +229,7 @@ def _arg_min_or_max(self, axis, out, op, compare, sum_duplicates):
                 zero = self.dtype.type(0)
                 mat = self.tocoo()
 
-                if sum_duplicates:
-                    mat.sum_duplicates()
+                mat.sum_duplicates()
 
                 am = op(mat.data)
                 m = mat.data[am]
@@ -254,9 +248,9 @@ def _arg_min_or_max(self, axis, out, op, compare, sum_duplicates):
                         else:
                             return zero_ind
 
-        return self._arg_min_or_max_axis(axis, op, sum_duplicates)
+        return self._arg_min_or_max_axis(axis, op)
 
-    def max(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
+    def max(self, axis=None, out=None, nonzero=False):
         """Returns the maximum of the matrix or maximum along an axis.
 
         Args:
@@ -268,8 +262,6 @@ def max(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
                 This argument is in the signature *solely* for NumPy
                 compatibility reasons. Do not pass in anything except
                 for the default value, as this argument is not used.
-            sum_duplicates (bool): Flag to indicate that duplicate elements
-                should be combined prior to the operation
             nonzero (bool): Return the maximum nonzero value and ignore all
                 zero entries. If the dimension has no nonzero values, a zero is
                 then returned to indicate that it is the only available value.
@@ -287,9 +279,9 @@ def max(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
 
         """
 
-        return self._min_or_max(axis, out, cupy.max, sum_duplicates, nonzero)
+        return self._min_or_max(axis, out, cupy.max, nonzero)
 
-    def min(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
+    def min(self, axis=None, out=None, nonzero=False):
         """Returns the minimum of the matrix or maximum along an axis.
 
         Args:
@@ -301,8 +293,6 @@ def min(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
                 This argument is in the signature *solely* for NumPy
                 compatibility reasons. Do not pass in anything except for
                 the default value, as this argument is not used.
-            sum_duplicates (bool): Flag to indicate that duplicate elements
-                should be combined prior to the operation
             nonzero (bool): Return the minimum nonzero value and ignore all
                 zero entries. If the dimension has no nonzero values, a zero is
                 then returned to indicate that it is the only available value.
@@ -320,9 +310,9 @@ def min(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
 
         """
 
-        return self._min_or_max(axis, out, cupy.min, sum_duplicates, nonzero)
+        return self._min_or_max(axis, out, cupy.min, nonzero)
 
-    def argmax(self, axis=None, out=None, sum_duplicates=False):
+    def argmax(self, axis=None, out=None):
         """Returns indices of maximum elements along an axis.
 
         Implicit zero elements are taken into account. If there are several
@@ -338,8 +328,6 @@ def argmax(self, axis=None, out=None, sum_duplicates=False):
                 This argument is in the signature *solely* for NumPy
                 compatibility reasons. Do not pass in anything except for
                 the default value, as this argument is not used.
-            sum_duplicates (bool): Flag to indicate that duplicate elements
-                should be combined prior to the operation
 
         Returns:
             (cupy.narray or int): Indices of maximum elements. If array,
@@ -347,10 +335,9 @@ def argmax(self, axis=None, out=None, sum_duplicates=False):
 
         """
 
-        return self._arg_min_or_max(axis, out, cupy.argmax, cupy.greater,
-                                    sum_duplicates)
+        return self._arg_min_or_max(axis, out, cupy.argmax, cupy.greater)
 
-    def argmin(self, axis=None, out=None, sum_duplicates=False):
+    def argmin(self, axis=None, out=None):
         """
         Returns indices of minimum elements along an axis.
 
@@ -367,8 +354,6 @@ def argmin(self, axis=None, out=None, sum_duplicates=False):
                 This argument is in the signature *solely* for NumPy
                 compatibility reasons. Do not pass in anything except for
                 the default value, as this argument is not used.
-            sum_duplicates (bool): Flag to indicate that duplicate elements
-                should be combined prior to the operation
 
         Returns:
             (cupy.narray or int): Indices of minimum elements. If matrix,
@@ -376,8 +361,7 @@ def argmin(self, axis=None, out=None, sum_duplicates=False):
 
         """
 
-        return self._arg_min_or_max(axis, out, cupy.argmin, cupy.less,
-                                    sum_duplicates)
+        return self._arg_min_or_max(axis, out, cupy.argmin, cupy.less)
 
 
 def _install_ufunc(func_name):


Test patch: