Repository: dask/dask

Issue:
arange crashes for parameters near 2**63
python 3.11
numpy 2.2.2
dask 2025.1.0

```python
>>> start, stop, step = 0., -9_131_138_316_486_228_481, -92_233_720_368_547_759
>>> da.arange(start, stop, step)

NotImplementedError: An error occurred while calling the arange method registered to the numpy backend.
Original Message: Can not use auto rechunking with object dtype. We are unable to estimate the size in bytes of object data
```

The problem is that da.arange internally multiplies step by 100, which sends it beyond the limit of np.int64, which in turn triggers edge case behaviour in numpy:

```python
>>> np.arange(start, stop, step * 100)
array([0.0], dtype=object)
```

The last person to ever touch this was... myself, a whopping 7 years ago: https://github.com/dask/dask/pull/3722


Discussion:
github-actions[bot]: ## Unit Test Results
_See [test report](https://dask.github.io/dask/test_report.html) for an extended history of previous test failures. This is useful for diagnosing flaky tests._

     15 files  ±     0       15 suites  ±0   4h 24m 36s ⏱️ - 5m 14s
 16 072 tests  -  1 070   15 280 ✅  -   682     791 💤  -   389  0 ❌ ±0   1 🔥 + 1 
197 355 runs   - 13 871  185 343 ✅  - 8 849  11 960 💤  - 5 074  0 ❌ ±0  52 🔥 +52 

For more details on these errors, see [this check](https://github.com/dask/dask/runs/36441700098).

Results for commit 11804cef. ± Comparison against base commit 7f490b91.

<details>
  <summary>This pull request <b>removes</b> 1071 and <b>adds</b> 1 tests. <i>Note that renamed tests count towards both.</i></summary>

```
dask.array.tests.test_creation ‑ test_arange
dask.array.tests.test_creation ‑ test_arange_cast_float_int_step
dask.array.tests.test_creation ‑ test_arange_dtypes[0-1-1-None]
dask.array.tests.test_creation ‑ test_arange_dtypes[1-2-0.5-None]
dask.array.tests.test_creation ‑ test_arange_dtypes[1-2.5-1-None]
dask.array.tests.test_creation ‑ test_arange_dtypes[1.5-2-1-None]
dask.array.tests.test_creation ‑ test_arange_dtypes[start4-stop4-step4-None]
dask.array.tests.test_creation ‑ test_arange_dtypes[start5-stop5-step5-None]
dask.array.tests.test_creation ‑ test_arange_dtypes[start6-stop6-step6-None]
dask.array.tests.test_creation ‑ test_arange_dtypes[start7-stop7-step7-None]
…
```

```
dask.array.tests.test_creation
```
</details>

[test-results]:data:application/gzip;base64,H4sIAIncm2cC/02OSQ7DIAwAvxJx7gGHmKWfqRwCEmqWisAp6t9LaJP0OGN75I35MLqV3RvAW8PWHNJFQ46UwjJX1koWVaapziVX7cGPNVtbl1rNL/kMryKVgVN5CmNR146LcYn7ZTExz7VslED88VnWKDpxym8ZwEh+uL905aOM+5d2maaQCjEAzTvrPPeWCMgaEMJ7wp6EckMPvAeH0nH2/gDBIKSxGgEAAA==

crusaderky: CI failures are unrelated; ready for review and merge

Patch:
diff --git a/dask/array/creation.py b/dask/array/creation.py
index ec7e96f8e..0f69d165c 100644
--- a/dask/array/creation.py
+++ b/dask/array/creation.py
@@ -436,7 +436,7 @@ def arange(
     meta = meta_from_array(like) if like is not None else None
 
     if dtype is None:
-        dtype = np.arange(start, stop, step * num if num else step).dtype
+        dtype = np.arange(type(start)(0), type(stop)(0), step).dtype
 
     chunks = normalize_chunks(chunks, (num,), dtype=dtype)
 
diff --git a/dask/array/slicing.py b/dask/array/slicing.py
index 473c42646..aebbfbf0f 100644
--- a/dask/array/slicing.py
+++ b/dask/array/slicing.py
@@ -2020,9 +2020,9 @@ def setitem(x, v, indices):
 
     Parameters
     ----------
-    x : numpy array
+    x : numpy/cupy/etc. array
         The array to be assigned to.
-    v : numpy array
+    v : numpy/cupy/etc. array
         The values which will be assigned.
     indices : list of `slice`, `int`, or numpy array
         The indices describing the elements of x to be assigned from
@@ -2038,7 +2038,7 @@ def setitem(x, v, indices):
 
     Returns
     -------
-    numpy array
+    numpy/cupy/etc. array
         A new independent array with assigned elements, unless v is
         empty (i.e. has zero size) in which case then the input array
         is returned and the indices are ignored.
@@ -2086,8 +2086,11 @@ def setitem(x, v, indices):
     if not np.ma.isMA(x) and np.ma.isMA(v):
         x = x.view(np.ma.MaskedArray)
 
-    # Copy the array to guarantee no other objects are corrupted
-    x = x.copy()
+    # Copy the array to guarantee no other objects are corrupted.
+    # When x is the output of a scalar __getitem__ call, it is a
+    # np.generic, which is read-only. Convert it to a (writeable)
+    # 0-d array. x could also be a cupy array etc.
+    x = np.asarray(x) if isinstance(x, np.generic) else x.copy()
 
     # Do the assignment
     try:


Test patch:
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
index f6873bf41..b136b5302 100644
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -5771,3 +5771,17 @@ def test_load_store_chunk():
     )
     expected = np.array([])
     assert all(actual == expected)
+
+
+def test_scalar_setitem():
+    """After a da.Array.__getitem__ call that returns a scalar, the chunk contains a
+    read-only np.generic instead of a writeable np.ndarray. This is a specific quirk of
+    numpy; cupy and other backends always return a 0-dimensional array.
+    Make sure that __setitem__ still works.
+    """
+    x = da.zeros(1)
+    y = x[0]
+    assert isinstance(y.compute(), np.generic)
+    y[()] = 2
+    assert_eq(y, 2.0)
+    assert isinstance(y.compute(), np.ndarray)
diff --git a/dask/array/tests/test_creation.py b/dask/array/tests/test_creation.py
index e8f51479f..fef3272e8 100644
--- a/dask/array/tests/test_creation.py
+++ b/dask/array/tests/test_creation.py
@@ -254,35 +254,58 @@ def test_arange():
     assert da.arange(10).chunks == ((10,),)
 
 
+arange_dtypes = [
+    np.uint8,
+    np.uint64,
+    np.int8,
+    np.int64,
+    np.float32,
+    np.float64,
+]
+
+
+# FIXME hypothesis would be much better suited for this
+@pytest.mark.parametrize("start_type", arange_dtypes + [int, float])
+@pytest.mark.parametrize("stop_type", arange_dtypes + [int, float])
+@pytest.mark.parametrize("step_type", arange_dtypes + [int, float])
+def test_arange_dtype_infer(start_type, stop_type, step_type):
+    start = start_type(3)
+    stop = stop_type(13)
+    step = step_type(2)
+    a_np = np.arange(start, stop, step)
+    a_da = da.arange(start, stop, step)
+    assert_eq(a_np, a_da)
+
+
+@pytest.mark.parametrize("dtype", arange_dtypes)
+def test_arange_dtype_force(dtype):
+    assert da.arange(10, dtype=dtype).dtype == dtype
+    assert da.arange(np.float32(10), dtype=dtype).dtype == dtype
+    assert da.arange(np.int64(10), dtype=dtype).dtype == dtype
+
+
+@pytest.mark.skipif(np.array(0).dtype == np.int32, reason="64-bit platforms only")
 @pytest.mark.parametrize(
-    "start,stop,step,dtype",
+    "start,stop,step",
     [
-        (0, 1, 1, None),  # int64
-        (1.5, 2, 1, None),  # float64
-        (1, 2.5, 1, None),  # float64
-        (1, 2, 0.5, None),  # float64
-        (np.float32(1), np.float32(2), np.float32(1), None),  # promoted to float64
-        (np.int32(1), np.int32(2), np.int32(1), None),  # promoted to int64
-        (np.uint32(1), np.uint32(2), np.uint32(1), None),  # promoted to int64
-        (np.uint64(1), np.uint64(2), np.uint64(1), None),  # promoted to float64
-        (np.uint32(1), np.uint32(2), np.uint32(1), np.uint32),
-        (np.uint64(1), np.uint64(2), np.uint64(1), np.uint64),
-        # numpy.arange gives unexpected results
-        # https://github.com/numpy/numpy/issues/11505
-        # (1j, 2, 1, None),
-        # (1, 2j, 1, None),
-        # (1, 2, 1j, None),
-        # (1+2j, 2+3j, 1+.1j, None),
+        (2**63 - 10_000, 2**63 - 1, 1),
+        (2**63 - 1, 2**63 - 10_000, -1),
+        (0, 2**63 - 1, 2**63 - 10_000),
+        (0.0, 2**63 - 1, 2**63 - 10_000),
+        (0.0, -9_131_138_316_486_228_481, -92_233_720_368_547_759),
     ],
 )
-def test_arange_dtypes(start, stop, step, dtype):
-    a_np = np.arange(start, stop, step, dtype=dtype)
-    a_da = da.arange(start, stop, step, dtype=dtype, chunks=-1)
+def test_arange_very_large_args(start, stop, step):
+    """Test args that are very close to 2**63
+    https://github.com/dask/dask/issues/11706
+    """
+    a_np = np.arange(start, stop, step)
+    a_da = da.arange(start, stop, step)
     assert_eq(a_np, a_da)
 
 
 @pytest.mark.xfail(
-    reason="Casting floats to ints is not supported since edge"
+    reason="Casting floats to ints is not supported since edge "
     "behavior is not specified or guaranteed by NumPy."
 )
 def test_arange_cast_float_int_step():