Repository: dask/dask

Issue:
`dask.array.reductions.cumreduction` no longer works with `numpy.maximum.accumulate`
<!-- Please include a self-contained copy-pastable example that generates the issue if possible.

Please be concise with code posted. See guidelines below on how to provide a good bug report:

- Craft Minimal Bug Reports http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports
- Minimal Complete Verifiable Examples https://stackoverflow.com/help/mcve

Bug reports that follow these guidelines are easier to diagnose, and so are often handled much more quickly.
-->

**Describe the issue**:

Since version 2025.10.0, `dask.array.reductions.cumreduction` errors out when used with  `numpy.maximum.accumulate`.

**Minimal Complete Verifiable Example**:

```python
import dask.array as da
import numpy as np

x = da.from_array([5, 1, 3, 7, 12, 6])

result = da.reductions.cumreduction(np.maximum.accumulate, np.maximum, -np.inf, x)

print(result.compute())
```
With previous dask versions, this gives
```
[ 5  5  5  7 12 12]
```
With 2025.10.0, it gives
```
Traceback (most recent call last):
  File "[script-path]/dask_cumreduction_maximum_accumulate.py", line 6, in <module>
    result = da.reductions.cumreduction(np.maximum.accumulate, np.maximum, -np.inf, x)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[site-packages-path]/dask/array/reductions.py", line 1220, in cumreduction
    if "dtype" in inspect.signature(func).parameters:
                  ^^^^^^^^^^^^^^^^^^^^^^^
  File "[python-path]/lib/python3.12/inspect.py", line 3348, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[python-path]/lib/python3.12/inspect.py", line 3085, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[python-path]/lib/python3.12/inspect.py", line 2602, in _signature_from_callable
    return _signature_from_builtin(sigcls, obj,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[python-path]/lib/python3.12/inspect.py", line 2392, in _signature_from_builtin
    raise ValueError("no signature found for builtin {!r}".format(func))
ValueError: no signature found for builtin <built-in method accumulate of numpy.ufunc object at 0x7fa751e24e40>
```

**Anything else we need to know?**:

The `inspect` call came in at #12097.

**Environment**:

- Dask version: 2025.10.0
- Python version: 3.11, 3.12, 3.13 (see failures at https://github.com/SciTools/iris/pull/6752)
- Operating System: Ubuntu, RHEL9
- Install method (conda, pip, source): conda

Discussion:

Fix (patch):
diff --git a/dask/array/reductions.py b/dask/array/reductions.py
index 6a1e9adaa0c..de91dfdccf6 100644
--- a/dask/array/reductions.py
+++ b/dask/array/reductions.py
@@ -1217,7 +1217,20 @@ def cumreduction(
     assert isinstance(axis, Integral)
     axis = validate_axis(axis, x.ndim)
 
-    if "dtype" in inspect.signature(func).parameters:
+    use_dtype = False
+    try:
+        func_params = inspect.signature(func).parameters
+        use_dtype = "dtype" in func_params
+    except ValueError:
+        try:
+            # Workaround for https://github.com/numpy/numpy/issues/30095
+            # np.ufunc.accumulate doesn't have a signature, but it does accept dtype
+            if isinstance(func.__self__, np.ufunc) and func.__name__ == "accumulate":
+                use_dtype = True
+        except AttributeError:
+            pass
+
+    if use_dtype:
         m = x.map_blocks(partial(func, dtype=dtype), axis=axis, dtype=dtype)
     else:
         m = x.map_blocks(func, axis=axis, dtype=dtype)


Test changes:
diff --git a/dask/array/tests/test_reductions.py b/dask/array/tests/test_reductions.py
index 6eeb2ec37b8..3c8f714fb71 100644
--- a/dask/array/tests/test_reductions.py
+++ b/dask/array/tests/test_reductions.py
@@ -664,6 +664,24 @@ def test_array_cumreduction_dtype(func, method, target_dtype):
     assert_eq(a_r, d_r)
 
 
+@pytest.mark.parametrize("ufunc", ["add", "multiply", "maximum"])
+@pytest.mark.parametrize("target_dtype", [None, int, float])
+def test_array_cumreduction_ufunc(ufunc, target_dtype):
+    ufunc_obj = getattr(np, ufunc)
+    accumulate = ufunc_obj.accumulate
+    identity = ufunc_obj.identity
+
+    a = np.linspace(0, 1, num=10, dtype=float)
+    d = da.from_array(a)
+
+    cumreduction = da.reductions.cumreduction
+
+    a_r = accumulate(a, dtype=target_dtype)
+    d_r = cumreduction(accumulate, ufunc_obj, identity, d, dtype=target_dtype)
+
+    assert_eq(a_r, d_r)
+
+
 @pytest.mark.parametrize("func", [np.cumsum, np.cumprod])
 def test_array_cumreduction_out(func):
     x = da.ones((10, 10), chunks=(4, 4))
@@ -1002,7 +1020,6 @@ def test_quantile(rechunk, q, axis):
 
 @pytest.mark.parametrize("func", [da.quantile, da.nanquantile, da.nanpercentile])
 def test_quantile_func_family_with_axis_none(func):
-
     # Check that these functions raise a NotImplementedError
     # when axis=None and more than one chunk is present
     # along at least one dimension