Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/array_api_compat/numpy/_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,13 @@ def _bound_shape(a: object) -> tuple[int, ...]:
# this covers integer arrays for float and integer bounds
# Also handle cases where the min/max are arrays/lists (replace values below min with iinfo.min and above max with iinfo.max)
if np.issubdtype(dtype, np.integer):
# Bounds with the same dtype are already in range and may be read-only.

if np.issubdtype(type(min), np.integer) and min <= np.iinfo(dtype).min:
min = None
elif np.issubdtype(type(min), np.floating) and min < np.iinfo(dtype).min:
min = np.iinfo(dtype).min
elif isinstance(min, Array):
elif isinstance(min, Array) and min.dtype != dtype:
min[min < np.iinfo(dtype).min] = np.iinfo(dtype).min
elif isinstance(min, (list,tuple)):
min = np.asarray(min)
Expand All @@ -176,7 +177,7 @@ def _bound_shape(a: object) -> tuple[int, ...]:
max = None
elif np.issubdtype(type(max), np.floating) and max > np.iinfo(dtype).max:
max = np.iinfo(dtype).max
elif isinstance(max, Array):
elif isinstance(max, Array) and max.dtype != dtype:
max[max > np.iinfo(dtype).max] = np.iinfo(dtype).max
elif isinstance(max, (list,tuple)):
max = np.asarray(max)
Expand Down
33 changes: 33 additions & 0 deletions tests/test_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,39 @@ def test_numpy_clip_returns_copy_when_unbounded():
np.testing.assert_array_equal(y, x)


@pytest.mark.parametrize("dtype", [np.int8, np.int64, np.uint8, np.uint64])
@pytest.mark.parametrize("bound_name", ["min", "max"])
@pytest.mark.parametrize("broadcast", [False, True])
@pytest.mark.parametrize("use_out", [False, True])
def test_numpy_clip_readonly_integer_bounds(dtype, bound_name, broadcast, use_out):
x = xp.asarray([[0, 3, 9], [4, 8, 1]], dtype=dtype)
if broadcast:
bound = xp.broadcast_to(xp.asarray(2 if bound_name == "min" else 5, dtype=dtype), (3,))
expected = [[2, 3, 9], [4, 8, 2]] if bound_name == "min" else [[0, 3, 5], [4, 5, 1]]
else:
bound = xp.asarray([2, 3, 4] if bound_name == "min" else [5, 6, 7], dtype=dtype)
bound.flags.writeable = False
expected = [[2, 3, 9], [4, 8, 4]] if bound_name == "min" else [[0, 3, 7], [4, 6, 1]]

original_bound = bound.copy()
assert not bound.flags.writeable
kwargs = {bound_name: bound}
if use_out:
kwargs["out"] = xp.empty_like(x)
result = xp.clip(x, **kwargs)

np.testing.assert_array_equal(result, xp.asarray(expected, dtype=dtype))
assert result.dtype == x.dtype
if use_out:
np.testing.assert_array_equal(kwargs["out"], result)
assert np.shares_memory(kwargs["out"], result)
else:
assert not np.shares_memory(x, result)
np.testing.assert_array_equal(x, xp.asarray([[0, 3, 9], [4, 8, 1]], dtype=dtype))
np.testing.assert_array_equal(bound, original_bound)
assert not bound.flags.writeable


def test_matrix_is_not_array_api_obj():
assert is_array_api_obj(np.asarray(3))
assert is_array_api_obj(np.float64(3))
Expand Down
Loading