diff --git a/.github/workflows/array-api-tests.yml b/.github/workflows/array-api-tests.yml index 4386cde..9f168cb 100644 --- a/.github/workflows/array-api-tests.yml +++ b/.github/workflows/array-api-tests.yml @@ -11,8 +11,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.10', '3.11', '3.12'] - numpy-version: ['2.1', 'dev'] + python-version: ['3.9', '3.10', '3.11', '3.12'] + numpy-version: ['1.26', 'dev'] + exclude: + - python-version: '3.8' + numpy-version: 'dev' steps: - name: Checkout array-api-strict @@ -35,7 +38,7 @@ jobs: if [[ "${{ matrix.numpy-version }}" == "dev" ]]; then python -m pip install --pre --extra-index https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy; else - python -m pip install 'numpy>=${{ matrix.numpy-version }},<${{ matrix.numpy-version }}.99'; + python -m pip install 'numpy>=1.26,<2.0'; fi python -m pip install ${GITHUB_WORKSPACE}/array-api-strict python -m pip install -r ${GITHUB_WORKSPACE}/array-api-tests/requirements.txt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a03c045..d8124d4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,8 +5,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.10', '3.11', '3.12'] - numpy-version: ['2.1', 'dev'] + python-version: ['3.9', '3.10', '3.11', '3.12'] + numpy-version: ['1.26', 'dev'] + exclude: + - python-version: '3.8' + numpy-version: 'dev' fail-fast: true steps: - uses: actions/checkout@v4 @@ -19,7 +22,7 @@ jobs: if [[ "${{ matrix.numpy-version }}" == "dev" ]]; then python -m pip install --pre --extra-index https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy; else - python -m pip install 'numpy>=${{ matrix.numpy-version }},<${{ matrix.numpy-version }}.99'; + python -m pip install 'numpy>=1.26,<2.0'; fi python -m pip install -r requirements-dev.txt - name: Run Tests diff --git a/array_api_strict/__init__.py b/array_api_strict/__init__.py index cbda499..ff43660 100644 --- a/array_api_strict/__init__.py +++ b/array_api_strict/__init__.py @@ -16,12 +16,6 @@ """ -import numpy as np -from numpy.lib import NumpyVersion - -if NumpyVersion(np.__version__) < NumpyVersion('2.1.0'): - raise ImportError("array-api-strict requires NumPy >= 2.1.0") - __all__ = [] # Warning: __array_api_version__ could change globally with diff --git a/array_api_strict/_array_object.py b/array_api_strict/_array_object.py index 76cdfac..c57d6ed 100644 --- a/array_api_strict/_array_object.py +++ b/array_api_strict/_array_object.py @@ -162,7 +162,19 @@ def __array__(self, dtype: None | np.dtype[Any] = None, copy: None | bool = None if _allow_array: if self._device != CPU_DEVICE: raise RuntimeError(f"Can not convert array on the '{self._device}' device to a Numpy array.") - return np.asarray(self._array, dtype=dtype, copy=copy) + # copy keyword is new in 2.0.0; for older versions don't use it + # retry without that keyword. + if np.__version__[0] < '2': + return np.asarray(self._array, dtype=dtype) + elif np.__version__.startswith('2.0.0-dev0'): + # Handle dev version for which we can't know based on version + # number whether or not the copy keyword is supported. + try: + return np.asarray(self._array, dtype=dtype, copy=copy) + except TypeError: + return np.asarray(self._array, dtype=dtype) + else: + return np.asarray(self._array, dtype=dtype, copy=copy) raise ValueError("Conversion from an array_api_strict array to a NumPy ndarray is not supported") # These are various helper functions to make the array behavior match the @@ -574,14 +586,24 @@ def __dlpack__( if copy is not _default: raise ValueError("The copy argument to __dlpack__ requires at least version 2023.12 of the array API") - kwargs = {'stream': stream} - if max_version is not _default: - kwargs['max_version'] = max_version - if dl_device is not _default: - kwargs['dl_device'] = dl_device - if copy is not _default: - kwargs['copy'] = copy - return self._array.__dlpack__(**kwargs) + if np.lib.NumpyVersion(np.__version__) < '2.1.0': + if max_version not in [_default, None]: + raise NotImplementedError("The max_version argument to __dlpack__ is not yet implemented") + if dl_device not in [_default, None]: + raise NotImplementedError("The device argument to __dlpack__ is not yet implemented") + if copy not in [_default, None]: + raise NotImplementedError("The copy argument to __dlpack__ is not yet implemented") + + return self._array.__dlpack__(stream=stream) + else: + kwargs = {'stream': stream} + if max_version is not _default: + kwargs['max_version'] = max_version + if dl_device is not _default: + kwargs['dl_device'] = dl_device + if copy is not _default: + kwargs['copy'] = copy + return self._array.__dlpack__(**kwargs) def __dlpack_device__(self: Array, /) -> Tuple[IntEnum, int]: """ diff --git a/array_api_strict/_creation_functions.py b/array_api_strict/_creation_functions.py index 8d7705b..e506bcc 100644 --- a/array_api_strict/_creation_functions.py +++ b/array_api_strict/_creation_functions.py @@ -83,6 +83,29 @@ def asarray( if isinstance(obj, Array) and device is None: device = obj.device + if np.lib.NumpyVersion(np.__version__) < '2.0.0': + if copy is False: + # Note: copy=False is not yet implemented in np.asarray for + # NumPy 1 + + # Work around it by creating the new array and seeing if NumPy + # copies it. + if isinstance(obj, Array): + new_array = np.array(obj._array, copy=copy, dtype=_np_dtype) + if new_array is not obj._array: + raise ValueError("Unable to avoid copy while creating an array from given array.") + return Array._new(new_array, device=device) + elif _supports_buffer_protocol(obj): + # Buffer protocol will always support no-copy + return Array._new(np.array(obj, copy=copy, dtype=_np_dtype), device=device) + else: + # No-copy is unsupported for Python built-in types. + raise ValueError("Unable to avoid copy while creating an array from given object.") + + if copy is None: + # NumPy 1 treats copy=False the same as the standard copy=None + copy = False + if isinstance(obj, Array): return Array._new(np.array(obj._array, copy=copy, dtype=_np_dtype), device=device) if dtype is None and isinstance(obj, int) and (obj > 2 ** 64 or obj < -(2 ** 63)): diff --git a/array_api_strict/tests/test_array_object.py b/array_api_strict/tests/test_array_object.py index 0480f00..4f843ba 100644 --- a/array_api_strict/tests/test_array_object.py +++ b/array_api_strict/tests/test_array_object.py @@ -448,7 +448,7 @@ def test_iter(): pytest.raises(TypeError, lambda: iter(ones((3, 3)))) @pytest.mark.parametrize("api_version", ['2021.12', '2022.12', '2023.12']) -def dlpack_2023_12(api_version): +def test_dlpack_2023_12(api_version): if api_version == '2021.12': with pytest.warns(UserWarning): set_array_api_strict_flags(api_version=api_version) @@ -456,19 +456,41 @@ def dlpack_2023_12(api_version): set_array_api_strict_flags(api_version=api_version) a = asarray([1, 2, 3], dtype=int8) - - # Do not error + # Never an error a.__dlpack__() - a.__dlpack__(dl_device=CPU_DEVICE) - a.__dlpack__(dl_device=None) - a.__dlpack__(max_version=(1, 0)) - a.__dlpack__(max_version=None) - a.__dlpack__(copy=False) - a.__dlpack__(copy=True) - a.__dlpack__(copy=None) - - x = np.from_dlpack(a) - assert isinstance(x, np.ndarray) - assert x.dtype == np.int8 - assert x.shape == (3,) - assert np.all(x == np.asarray([1, 2, 3])) + + if api_version < '2023.12': + pytest.raises(ValueError, lambda: + a.__dlpack__(dl_device=a.__dlpack_device__())) + pytest.raises(ValueError, lambda: + a.__dlpack__(dl_device=None)) + pytest.raises(ValueError, lambda: + a.__dlpack__(max_version=(1, 0))) + pytest.raises(ValueError, lambda: + a.__dlpack__(max_version=None)) + pytest.raises(ValueError, lambda: + a.__dlpack__(copy=False)) + pytest.raises(ValueError, lambda: + a.__dlpack__(copy=True)) + pytest.raises(ValueError, lambda: + a.__dlpack__(copy=None)) + elif np.lib.NumpyVersion(np.__version__) < '2.1.0': + pytest.raises(NotImplementedError, lambda: + a.__dlpack__(dl_device=CPU_DEVICE)) + a.__dlpack__(dl_device=None) + pytest.raises(NotImplementedError, lambda: + a.__dlpack__(max_version=(1, 0))) + a.__dlpack__(max_version=None) + pytest.raises(NotImplementedError, lambda: + a.__dlpack__(copy=False)) + pytest.raises(NotImplementedError, lambda: + a.__dlpack__(copy=True)) + a.__dlpack__(copy=None) + else: + a.__dlpack__(dl_device=a.__dlpack_device__()) + a.__dlpack__(dl_device=None) + a.__dlpack__(max_version=(1, 0)) + a.__dlpack__(max_version=None) + a.__dlpack__(copy=False) + a.__dlpack__(copy=True) + a.__dlpack__(copy=None) diff --git a/requirements-dev.txt b/requirements-dev.txt index 62673a8..137e973 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,3 @@ pytest hypothesis -numpy>=2.1 +numpy diff --git a/requirements.txt b/requirements.txt index 92ce2ba..24ce15a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -numpy>=2.1 +numpy diff --git a/setup.py b/setup.py index 89e85e5..29a94df 100644 --- a/setup.py +++ b/setup.py @@ -15,15 +15,14 @@ long_description_content_type="text/markdown", url="https://data-apis.org/array-api-strict/", license="MIT", - python_requires=">=3.10", - install_requires=["numpy>=2.1"], + python_requires=">=3.9", + install_requires=["numpy"], classifiers=[ "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", ],