From 2782e5b59fb65e083b76785a53c95697a4830f0d Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Sat, 18 May 2024 21:24:38 -0400 Subject: [PATCH 01/31] Include numpy 2 compatibility layer --- src/npy_2_compat.h | 249 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 src/npy_2_compat.h diff --git a/src/npy_2_compat.h b/src/npy_2_compat.h new file mode 100644 index 0000000..50e637f --- /dev/null +++ b/src/npy_2_compat.h @@ -0,0 +1,249 @@ +/* + * This header file defines relevant features which: + * - Require runtime inspection depending on the NumPy version. + * - May be needed when compiling with an older version of NumPy to allow + * a smooth transition. + * + * As such, it is shipped with NumPy 2.0, but designed to be vendored in full + * or parts by downstream projects. + * + * It must be included after any other includes. `import_array()` must have + * been called in the scope or version dependency will misbehave, even when + * only `PyUFunc_` API is used. + * + * If required complicated defs (with inline functions) should be written as: + * + * #if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION + * Simple definition when NumPy 2.0 API is guaranteed. + * #else + * static inline definition of a 1.x compatibility shim + * #if NPY_ABI_VERSION < 0x02000000 + * Make 1.x compatibility shim the public API (1.x only branch) + * #else + * Runtime dispatched version (1.x or 2.x) + * #endif + * #endif + * + * An internal build always passes NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION + */ + +#ifndef NUMPY_CORE_INCLUDE_NUMPY_NPY_2_COMPAT_H_ +#define NUMPY_CORE_INCLUDE_NUMPY_NPY_2_COMPAT_H_ + +/* + * New macros for accessing real and complex part of a complex number can be + * found in "npy_2_complexcompat.h". + */ + + +/* + * This header is meant to be included by downstream directly for 1.x compat. + * In that case we need to ensure that users first included the full headers + * and not just `ndarraytypes.h`. + */ + +#ifndef NPY_FEATURE_VERSION + #error "The NumPy 2 compat header requires `import_array()` for which " \ + "the `ndarraytypes.h` header include is not sufficient. Please " \ + "include it after `numpy/ndarrayobject.h` or similar.\n" \ + "To simplify inclusion, you may use `PyArray_ImportNumPy()` " \ + "which is defined in the compat header and is lightweight (can be)." +#endif + +#if NPY_ABI_VERSION < 0x02000000 + /* + * Define 2.0 feature version as it is needed below to decide whether we + * compile for both 1.x and 2.x (defining it gaurantees 1.x only). + */ + #define NPY_2_0_API_VERSION 0x00000012 + /* + * If we are compiling with NumPy 1.x, PyArray_RUNTIME_VERSION so we + * pretend the `PyArray_RUNTIME_VERSION` is `NPY_FEATURE_VERSION`. + * This allows downstream to use `PyArray_RUNTIME_VERSION` if they need to. + */ + #define PyArray_RUNTIME_VERSION NPY_FEATURE_VERSION + /* Compiling on NumPy 1.x where these are the same: */ + #define PyArray_DescrProto PyArray_Descr +#endif + + +/* + * Define a better way to call `_import_array()` to simplify backporting as + * we now require imports more often (necessary to make ABI flexible). + */ +#ifdef import_array1 + +static inline int +PyArray_ImportNumPyAPI() +{ + if (NPY_UNLIKELY(PyArray_API == NULL)) { + import_array1(-1); + } + return 0; +} + +#endif /* import_array1 */ + + +/* + * NPY_DEFAULT_INT + * + * The default integer has changed, `NPY_DEFAULT_INT` is available at runtime + * for use as type number, e.g. `PyArray_DescrFromType(NPY_DEFAULT_INT)`. + * + * NPY_RAVEL_AXIS + * + * This was introduced in NumPy 2.0 to allow indicating that an axis should be + * raveled in an operation. Before NumPy 2.0, NPY_MAXDIMS was used for this purpose. + * + * NPY_MAXDIMS + * + * A constant indicating the maximum number dimensions allowed when creating + * an ndarray. + * + * NPY_NTYPES_LEGACY + * + * The number of built-in NumPy dtypes. + */ +#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION + #define NPY_DEFAULT_INT NPY_INTP + #define NPY_RAVEL_AXIS NPY_MIN_INT + #define NPY_MAXARGS 64 + +#elif NPY_ABI_VERSION < 0x02000000 + #define NPY_DEFAULT_INT NPY_LONG + #define NPY_RAVEL_AXIS 32 + #define NPY_MAXARGS 32 + + /* Aliases of 2.x names to 1.x only equivalent names */ + #define NPY_NTYPES NPY_NTYPES_LEGACY + #define PyArray_DescrProto PyArray_Descr + #define _PyArray_LegacyDescr PyArray_Descr + /* NumPy 2 definition always works, but add it for 1.x only */ + #define PyDataType_ISLEGACY(dtype) (1) +#else + #define NPY_DEFAULT_INT \ + (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_INTP : NPY_LONG) + #define NPY_RAVEL_AXIS \ + (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? -1 : 32) + #define NPY_MAXARGS \ + (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? 64 : 32) +#endif + + +/* + * Access inline functions for descriptor fields. Except for the first + * few fields, these needed to be moved (elsize, alignment) for + * additional space. Or they are descriptor specific and are not generally + * available anymore (metadata, c_metadata, subarray, names, fields). + * + * Most of these are defined via the `DESCR_ACCESSOR` macro helper. + */ +#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION || NPY_ABI_VERSION < 0x02000000 + /* Compiling for 1.x or 2.x only, direct field access is OK: */ + + static inline void + PyDataType_SET_ELSIZE(PyArray_Descr *dtype, npy_intp size) + { + dtype->elsize = size; + } + + static inline npy_uint64 + PyDataType_FLAGS(const PyArray_Descr *dtype) + { + #if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION + return dtype->flags; + #else + return (unsigned char)dtype->flags; /* Need unsigned cast on 1.x */ + #endif + } + + #define DESCR_ACCESSOR(FIELD, field, type, legacy_only) \ + static inline type \ + PyDataType_##FIELD(const PyArray_Descr *dtype) { \ + if (legacy_only && !PyDataType_ISLEGACY(dtype)) { \ + return (type)0; \ + } \ + return ((_PyArray_LegacyDescr *)dtype)->field; \ + } +#else /* compiling for both 1.x and 2.x */ + + static inline void + PyDataType_SET_ELSIZE(PyArray_Descr *dtype, npy_intp size) + { + if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { + ((_PyArray_DescrNumPy2 *)dtype)->elsize = size; + } + else { + ((PyArray_DescrProto *)dtype)->elsize = (int)size; + } + } + + static inline npy_uint64 + PyDataType_FLAGS(const PyArray_Descr *dtype) + { + if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { + return ((_PyArray_DescrNumPy2 *)dtype)->flags; + } + else { + return (unsigned char)((PyArray_DescrProto *)dtype)->flags; + } + } + + /* Cast to LegacyDescr always fine but needed when `legacy_only` */ + #define DESCR_ACCESSOR(FIELD, field, type, legacy_only) \ + static inline type \ + PyDataType_##FIELD(const PyArray_Descr *dtype) { \ + if (legacy_only && !PyDataType_ISLEGACY(dtype)) { \ + return (type)0; \ + } \ + if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { \ + return ((_PyArray_LegacyDescr *)dtype)->field; \ + } \ + else { \ + return ((PyArray_DescrProto *)dtype)->field; \ + } \ + } +#endif + +DESCR_ACCESSOR(ELSIZE, elsize, npy_intp, 0) +DESCR_ACCESSOR(ALIGNMENT, alignment, npy_intp, 0) +DESCR_ACCESSOR(METADATA, metadata, PyObject *, 1) +DESCR_ACCESSOR(SUBARRAY, subarray, PyArray_ArrayDescr *, 1) +DESCR_ACCESSOR(NAMES, names, PyObject *, 1) +DESCR_ACCESSOR(FIELDS, fields, PyObject *, 1) +DESCR_ACCESSOR(C_METADATA, c_metadata, NpyAuxData *, 1) + +#undef DESCR_ACCESSOR + + +#if !(defined(NPY_INTERNAL_BUILD) && NPY_INTERNAL_BUILD) +#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION + static inline PyArray_ArrFuncs * + PyDataType_GetArrFuncs(const PyArray_Descr *descr) + { + return _PyDataType_GetArrFuncs(descr); + } +#elif NPY_ABI_VERSION < 0x02000000 + static inline PyArray_ArrFuncs * + PyDataType_GetArrFuncs(const PyArray_Descr *descr) + { + return descr->f; + } +#else + static inline PyArray_ArrFuncs * + PyDataType_GetArrFuncs(const PyArray_Descr *descr) + { + if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { + return _PyDataType_GetArrFuncs(descr); + } + else { + return ((PyArray_DescrProto *)descr)->f; + } + } +#endif + + +#endif /* not internal build */ + +#endif /* NUMPY_CORE_INCLUDE_NUMPY_NPY_2_COMPAT_H_ */ From a32bf586f1008b36883e6261e27276ba06f7d3d7 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Sat, 18 May 2024 22:02:47 -0400 Subject: [PATCH 02/31] Automatically update github actions versions --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ff6499d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" \ No newline at end of file From 17e890e3139659abbee6c26619e797a6b03033b8 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Sat, 18 May 2024 22:04:21 -0400 Subject: [PATCH 03/31] Update copyrights --- LICENSE | 2 +- docs/conf.py | 2 +- src/math_msvc_compatibility.h | 2 +- src/quaternion.c | 2 +- src/quaternion.h | 2 +- src/quaternion/__init__.py | 2 +- src/quaternion/calculus.py | 2 +- src/quaternion/means.py | 2 +- src/quaternion/numba_wrapper.py | 2 +- src/quaternion/quaternion_time_series.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/LICENSE b/LICENSE index 6c6d4e7..f691804 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2021 Michael Boyle +Copyright (c) 2024 Michael Boyle Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/conf.py b/docs/conf.py index 6858151..f7bf139 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ # -- Project information ----------------------------------------------------- project = 'quaternion' -copyright = '2020, Michael Boyle' +copyright = '2024, Michael Boyle' author = 'Michael Boyle' # The short X.Y version diff --git a/src/math_msvc_compatibility.h b/src/math_msvc_compatibility.h index 51ee635..536d4f9 100644 --- a/src/math_msvc_compatibility.h +++ b/src/math_msvc_compatibility.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021, Michael Boyle +// Copyright (c) 2024, Michael Boyle // See LICENSE file for details: #ifndef __MATH_MSVC_COMPATIBILITY_H__ diff --git a/src/quaternion.c b/src/quaternion.c index 007d6e0..9b5b23e 100644 --- a/src/quaternion.c +++ b/src/quaternion.c @@ -1,4 +1,4 @@ -// Copyright (c) 2021, Michael Boyle +// Copyright (c) 2024, Michael Boyle // See LICENSE file for details: #ifdef __cplusplus diff --git a/src/quaternion.h b/src/quaternion.h index 3bd6082..21bc770 100644 --- a/src/quaternion.h +++ b/src/quaternion.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021, Michael Boyle +// Copyright (c) 2024, Michael Boyle // See LICENSE file for details: #ifndef __QUATERNION_H__ diff --git a/src/quaternion/__init__.py b/src/quaternion/__init__.py index 654cbab..8c6c77d 100644 --- a/src/quaternion/__init__.py +++ b/src/quaternion/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2021, Michael Boyle +# Copyright (c) 2024, Michael Boyle # See LICENSE file for details: __version__ = "2023.0.4" diff --git a/src/quaternion/calculus.py b/src/quaternion/calculus.py index 7ecb2de..9fdf2f2 100644 --- a/src/quaternion/calculus.py +++ b/src/quaternion/calculus.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2021, Michael Boyle +# Copyright (c) 2024, Michael Boyle # See LICENSE file for details: from __future__ import division, print_function, absolute_import diff --git a/src/quaternion/means.py b/src/quaternion/means.py index 677a26b..8fd6bcb 100644 --- a/src/quaternion/means.py +++ b/src/quaternion/means.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2021, Michael Boyle +# Copyright (c) 2024, Michael Boyle # See LICENSE file for details: from __future__ import division, print_function, absolute_import diff --git a/src/quaternion/numba_wrapper.py b/src/quaternion/numba_wrapper.py index 4a8980d..ebba028 100644 --- a/src/quaternion/numba_wrapper.py +++ b/src/quaternion/numba_wrapper.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2021, Michael Boyle +# Copyright (c) 2024, Michael Boyle # See LICENSE file for details: from __future__ import division, print_function, absolute_import diff --git a/src/quaternion/quaternion_time_series.py b/src/quaternion/quaternion_time_series.py index befc2ac..a473d5f 100644 --- a/src/quaternion/quaternion_time_series.py +++ b/src/quaternion/quaternion_time_series.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2021, Michael Boyle +# Copyright (c) 2024, Michael Boyle # See LICENSE file for details: from __future__ import print_function, division, absolute_import From a4d7997254fe545dd1ba79bb8216fb8969377ac3 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Sat, 18 May 2024 23:11:23 -0400 Subject: [PATCH 04/31] Remove poetry --- .github/workflows/build.yml | 43 +++++++++++++++------------ pyproject.toml | 58 +++++++++++++++++++++++++++++++++---- requirements.txt | 3 -- setup.py | 55 ++++++++++++++++++----------------- 4 files changed, 105 insertions(+), 54 deletions(-) delete mode 100644 requirements.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6966457..ccacfbc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,14 @@ on: tags: '*' pull_request: +concurrency: + group: test-${{ github.head_ref }} + cancel-in-progress: true + +env: + PYTHONUNBUFFERED: "1" + FORCE_COLOR: "1" + jobs: get_new_version: @@ -25,18 +33,10 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.11' + python-version: '3.12' - - name: Install tomli - shell: bash - run: | - python -m pip install --upgrade pip tomli - - - name: Install poetry - shell: bash - run: | - curl -sSL https://install.python-poetry.org | POETRY_HOME="$HOME/.poetry" python - - echo "$HOME/.poetry/bin" >> $GITHUB_PATH + - name: Install Hatch + run: pip install --upgrade hatch - name: Bump version id: get_version @@ -46,11 +46,10 @@ jobs: run: | export version_bump_rule=$(python .github/scripts/parse_bump_rule.py) echo "version_bump_rule: '${version_bump_rule}'" - poetry version "${version_bump_rule}" - export new_version=$(python .github/scripts/parse_version.py pyproject.toml) + hatch version "${version_bump_rule}" + export new_version=$(hatch version) echo "new_version: '${new_version}'" - # echo "::set-output name=version::${new_version}" - echo "version=${new_version}" >> $GITHUB_OUTPUT + echo "new_version=${new_version}" >> $GITHUB_ENV # Save env variable for later steps build_wheels: @@ -190,7 +189,7 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.11' + python-version: '3.12' - name: Update versions shell: bash @@ -222,5 +221,13 @@ jobs: draft: false prerelease: false - - name: Send to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + - name: Publish to PyPI + if: "!contains(github.event.head_commit.message, '[no pypi]')" + env: + # 1) Get key from https://pypi.org/manage/account/token/ + # 2) Copy it to Github > repo > Settings > Secrets + HATCH_INDEX_USER: __token__ + HATCH_INDEX_AUTH: ${{ secrets.PYPI_TOKEN }} + shell: bash + run: | + hatch publish diff --git a/pyproject.toml b/pyproject.toml index 1378fc7..a721691 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,62 @@ -[tool.poetry] +[project] name = "quaternion" -version = "2023.0.4" description = "Add a quaternion dtype to NumPy" readme = "README.md" -license = "MIT" -authors = ["Michael Boyle "] -homepage = "https://github.com/moble/quaternion" +requires-python = ">=3.8" +license = {file = "LICENSE"} +authors = [ + { name = "Michael Boyle", email = "michael.oliver.boyle@gmail.com" } +] +classifiers = [ + "License :: OSI Approved :: MIT License", + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering :: Physics", + "Topic :: Scientific/Engineering :: Astronomy" +] +dependencies = [ + "numpy ==2.0.0.rc2", + "scipy >=1.5", +# "numba >=0.55; implementation_name == 'cpython'", + "importlib-metadata; python_version<'3.10'" +] +dynamic = ["version"] + +[project.urls] +Homepage = "https://github.com/moble/quaternion" +Documentation = "https://quaternionic.readthedocs.io/en/latest" [build-system] -requires = ["setuptools!=50.0", "wheel", "oldest-supported-numpy"] +requires = ["setuptools>=61", "wheel", "oldest-supported-numpy"] build-backend = "setuptools.build_meta" +[tool.hatch.envs.default] +dependencies = [ + "pytest", + "pytest-cov", + "black" +] +[tool.hatch.envs.default.scripts] +# Run these as `hatch run test` +test = "pytest {args:tests}" + +[tool.hatch.envs.docs] +dependencies = [ + "mkdocs", + "mktheapidocs", + "pymdown-extensions" +] +[tool.hatch.envs.docs.scripts] +# Run these as `hatch run docs:build` or `hatch run docs:serve` +build = "mkdocs build --clean" +serve = "mkdocs serve --dev-addr localhost:8000" + + +[tool.setuptools.dynamic] +version = {attr = "quaternion.__version__"} + [tool.pytest.ini_options] minversion = "6.0" norecursedirs = ".* build dist *.egg-info install ENV" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 86b27a1..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -numpy>=1.13, < 2.0 -scipy>=1.5.0 -numba>=0.49.1 diff --git a/setup.py b/setup.py index 9b00b8a..ed95392 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ # Set this first for easier replacement -version = "2023.0.4" +version = "2023.1.0" # read the contents of the README file into the PyPI description this_directory = Path(__file__).parent @@ -30,7 +30,8 @@ depends=[ "src/quaternion.c", "src/quaternion.h", - "src/numpy_quaternion.c" + "src/numpy_quaternion.c", + "src/npy_2_compat.h" ], include_dirs=[ np.get_include(), @@ -51,31 +52,31 @@ long_description=long_description, long_description_content_type="text/markdown", ext_modules=extensions, - install_requires=[ - "numpy>=1.13, < 2.0", - # See also extras and :environment_marker specs below - ], - extras_require={ - "scipy": [ - "scipy", - ], - "numba:python_version < '3.6' and platform_python_implementation != 'PyPy'": [ - "numba<0.49.0", - "llvmlite<0.32.0", - ], - "numba:python_version >= '3.6' and platform_python_implementation != 'PyPy'": [ - "numba", - ], - "docs": [ - "mkdocs", - "mktheapidocs[plugin]", - "pymdown-extensions", - ], - "testing": [ - "pytest", - "pytest-cov", - ] - }, + # install_requires=[ + # "numpy>=1.13", + # # See also extras and :environment_marker specs below + # ], + # extras_require={ + # "scipy": [ + # "scipy", + # ], + # "numba:python_version < '3.6' and platform_python_implementation != 'PyPy'": [ + # "numba<0.49.0", + # "llvmlite<0.32.0", + # ], + # "numba:python_version >= '3.6' and platform_python_implementation != 'PyPy'": [ + # "numba", + # ], + # "docs": [ + # "mkdocs", + # "mktheapidocs[plugin]", + # "pymdown-extensions", + # ], + # "testing": [ + # "pytest", + # "pytest-cov", + # ] + # }, version=version, ) From 38430f79bec4482198f9955af73254ea82c5a76b Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Fri, 14 Jun 2024 09:03:28 +0300 Subject: [PATCH 05/31] First try with numpy 2 --- src/numpy_quaternion.c | 34 ++++++++++++++++++++-------------- src/quaternion/__init__.py | 2 +- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/numpy_quaternion.c b/src/numpy_quaternion.c index 636e3e7..824d7ef 100644 --- a/src/numpy_quaternion.c +++ b/src/numpy_quaternion.c @@ -1,4 +1,4 @@ -// Copyright (c) 2021, Michael Boyle +// Copyright (c) 2024, Michael Boyle // See LICENSE file for details: #define NPY_NO_DEPRECATED_API NPY_API_VERSION @@ -11,6 +11,9 @@ #include "quaternion.h" +// Provide compatibility with numpy 1 and 2 +#include "npy_2_compat.h" + // Numpy 1.19 changed UFuncGenericFunction to use const `dimensions` and `steps` pointers. // Supposedly, this should only generate warnings, but this now causes errors in CI, so // I'm fixing it for real. The API version at which this change was made is set at @@ -57,7 +60,7 @@ static PyTypeObject PyQuaternion_Type; // This is the crucial feature that will make a quaternion into a // built-in numpy data type. We will describe its features below. -PyArray_Descr* quaternion_descr; +PyArray_DescrProto* quaternion_descr; static NPY_INLINE int @@ -260,7 +263,8 @@ pyquaternion_##fake_name##_array_operator(PyObject* a, PyObject* b) { \ } \ iternext = NpyIter_GetIterNext(iter, NULL); \ innerstride = NpyIter_GetInnerStrideArray(iter)[0]; \ - itemsize = NpyIter_GetDescrArray(iter)[1]->elsize; \ + /*itemsize = NpyIter_GetDescrArray(iter)[1]->elsize;*/ \ + itemsize = PyDataType_ELSIZE(NpyIter_GetDescrArray(iter)[1]); \ innersizeptr = NpyIter_GetInnerLoopSizePtr(iter); \ dataptrarray = NpyIter_GetDataPtrArray(iter); \ if(PyArray_EquivTypes(PyArray_DESCR((PyArrayObject*) b), quaternion_descr)) { \ @@ -927,10 +931,10 @@ QUATERNION_nonzero (char *ip, PyArrayObject *ap) else { PyArray_Descr *descr; descr = PyArray_DescrFromType(NPY_DOUBLE); - descr->f->copyswap(&q.w, ip, !PyArray_ISNOTSWAPPED(ap), NULL); - descr->f->copyswap(&q.x, ip+8, !PyArray_ISNOTSWAPPED(ap), NULL); - descr->f->copyswap(&q.y, ip+16, !PyArray_ISNOTSWAPPED(ap), NULL); - descr->f->copyswap(&q.z, ip+24, !PyArray_ISNOTSWAPPED(ap), NULL); + PyDataType_GetArrFuncs(descr)->copyswap(&q.w, ip, !PyArray_ISNOTSWAPPED(ap), NULL); + PyDataType_GetArrFuncs(descr)->copyswap(&q.x, ip+8, !PyArray_ISNOTSWAPPED(ap), NULL); + PyDataType_GetArrFuncs(descr)->copyswap(&q.y, ip+16, !PyArray_ISNOTSWAPPED(ap), NULL); + PyDataType_GetArrFuncs(descr)->copyswap(&q.z, ip+24, !PyArray_ISNOTSWAPPED(ap), NULL); Py_DECREF(descr); } return (npy_bool) !quaternion_equal(q, zero); @@ -942,7 +946,7 @@ QUATERNION_copyswap(quaternion *dst, quaternion *src, { PyArray_Descr *descr; descr = PyArray_DescrFromType(NPY_DOUBLE); - descr->f->copyswapn(dst, sizeof(double), src, sizeof(double), 4, swap, NULL); + PyDataType_GetArrFuncs(descr)->copyswapn(dst, sizeof(double), src, sizeof(double), 4, swap, NULL); Py_DECREF(descr); } @@ -953,10 +957,10 @@ QUATERNION_copyswapn(quaternion *dst, npy_intp dstride, { PyArray_Descr *descr; descr = PyArray_DescrFromType(NPY_DOUBLE); - descr->f->copyswapn(&dst->w, dstride, &src->w, sstride, n, swap, NULL); - descr->f->copyswapn(&dst->x, dstride, &src->x, sstride, n, swap, NULL); - descr->f->copyswapn(&dst->y, dstride, &src->y, sstride, n, swap, NULL); - descr->f->copyswapn(&dst->z, dstride, &src->z, sstride, n, swap, NULL); + PyDataType_GetArrFuncs(descr)->copyswapn(&dst->w, dstride, &src->w, sstride, n, swap, NULL); + PyDataType_GetArrFuncs(descr)->copyswapn(&dst->x, dstride, &src->x, sstride, n, swap, NULL); + PyDataType_GetArrFuncs(descr)->copyswapn(&dst->y, dstride, &src->y, sstride, n, swap, NULL); + PyDataType_GetArrFuncs(descr)->copyswapn(&dst->z, dstride, &src->z, sstride, n, swap, NULL); Py_DECREF(descr); } @@ -1481,18 +1485,20 @@ PyMODINIT_FUNC initnumpy_quaternion(void) { _PyQuaternion_ArrFuncs.fillwithscalar = (PyArray_FillWithScalarFunc*)QUATERNION_fillwithscalar; // The quaternion array descr - quaternion_descr = PyObject_New(PyArray_Descr, &PyArrayDescr_Type); + quaternion_descr = PyObject_New(PyArray_DescrProto, &PyArrayDescr_Type); quaternion_descr->typeobj = &PyQuaternion_Type; quaternion_descr->kind = 'V'; quaternion_descr->type = 'q'; quaternion_descr->byteorder = '='; quaternion_descr->flags = NPY_NEEDS_PYAPI | NPY_USE_GETITEM | NPY_USE_SETITEM; quaternion_descr->type_num = 0; // assigned at registration - quaternion_descr->elsize = quaternion_elsize; + // quaternion_descr->elsize = quaternion_elsize; + PyDataType_SET_ELSIZE(quaternion_descr, quaternion_elsize); quaternion_descr->alignment = quaternion_alignment; quaternion_descr->subarray = NULL; quaternion_descr->fields = NULL; quaternion_descr->names = NULL; + // quaternion_descr->f = &_PyQuaternion_ArrFuncs; quaternion_descr->f = &_PyQuaternion_ArrFuncs; quaternion_descr->metadata = NULL; quaternion_descr->c_metadata = NULL; diff --git a/src/quaternion/__init__.py b/src/quaternion/__init__.py index 8c6c77d..2c49572 100644 --- a/src/quaternion/__init__.py +++ b/src/quaternion/__init__.py @@ -3,7 +3,7 @@ # Copyright (c) 2024, Michael Boyle # See LICENSE file for details: -__version__ = "2023.0.4" +__version__ = "2023.1.0" __doc_title__ = "Quaternion dtype for NumPy" __doc__ = "Adds a quaternion dtype to NumPy." __all__ = ['quaternion', From e4a45a24bf675aec8bd03a7662c68fc711fbffa9 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Sat, 17 Aug 2024 15:19:35 -0400 Subject: [PATCH 06/31] Ignore .vscode directory in git --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4e7d71f..fe1c8cd 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ pip_manylinux1_cache .envs notes poetry.lock +.vscode From 3da57188f16553ee97d5155c6208b7399caddc23 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Sat, 17 Aug 2024 16:56:32 -0400 Subject: [PATCH 07/31] Don't restrict numpy version --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a721691..a25a550 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,9 +17,9 @@ classifiers = [ "Topic :: Scientific/Engineering :: Astronomy" ] dependencies = [ - "numpy ==2.0.0.rc2", + "numpy", "scipy >=1.5", -# "numba >=0.55; implementation_name == 'cpython'", + "numba >=0.55; implementation_name == 'cpython'", "importlib-metadata; python_version<'3.10'" ] dynamic = ["version"] From 039ad396d896581713bf9e525c9927da0f1a1807 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Sat, 17 Aug 2024 16:57:09 -0400 Subject: [PATCH 08/31] Update npy_2_compat as in numpy source --- src/npy_2_compat.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/npy_2_compat.h b/src/npy_2_compat.h index 50e637f..e39e65a 100644 --- a/src/npy_2_compat.h +++ b/src/npy_2_compat.h @@ -53,7 +53,7 @@ #if NPY_ABI_VERSION < 0x02000000 /* * Define 2.0 feature version as it is needed below to decide whether we - * compile for both 1.x and 2.x (defining it gaurantees 1.x only). + * compile for both 1.x and 2.x (defining it guarantees 1.x only). */ #define NPY_2_0_API_VERSION 0x00000012 /* @@ -74,7 +74,7 @@ #ifdef import_array1 static inline int -PyArray_ImportNumPyAPI() +PyArray_ImportNumPyAPI(void) { if (NPY_UNLIKELY(PyArray_API == NULL)) { import_array1(-1); @@ -125,7 +125,7 @@ PyArray_ImportNumPyAPI() #define NPY_DEFAULT_INT \ (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_INTP : NPY_LONG) #define NPY_RAVEL_AXIS \ - (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? -1 : 32) + (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_MIN_INT : 32) #define NPY_MAXARGS \ (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? 64 : 32) #endif From 1a8c05a3e9c24894b00856593184837a50dd63d7 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Sat, 17 Aug 2024 17:02:24 -0400 Subject: [PATCH 09/31] Incorporate some more changes for numpy 2 Co-authored-by: Peter Hawkins --- src/numpy_quaternion.c | 56 +++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/src/numpy_quaternion.c b/src/numpy_quaternion.c index 824d7ef..e7b6077 100644 --- a/src/numpy_quaternion.c +++ b/src/numpy_quaternion.c @@ -1,14 +1,14 @@ // Copyright (c) 2024, Michael Boyle // See LICENSE file for details: -#define NPY_NO_DEPRECATED_API NPY_API_VERSION +#define PY_ARRAY_UNIQUE_SYMBOL NumpyQuaternion +// #define NPY_NO_DEPRECATED_API NPY_API_VERSION +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #include -#include -#include +#include #include #include "structmember.h" - #include "quaternion.h" // Provide compatibility with numpy 1 and 2 @@ -62,7 +62,8 @@ static PyTypeObject PyQuaternion_Type; // built-in numpy data type. We will describe its features below. PyArray_DescrProto* quaternion_descr; - +PyArray_DescrProto quaternion_proto = {PyObject_HEAD_INIT(NULL)}; + static NPY_INLINE int PyQuaternion_Check(PyObject* object) { return PyObject_IsInstance(object,(PyObject*)&PyQuaternion_Type); @@ -263,7 +264,6 @@ pyquaternion_##fake_name##_array_operator(PyObject* a, PyObject* b) { \ } \ iternext = NpyIter_GetIterNext(iter, NULL); \ innerstride = NpyIter_GetInnerStrideArray(iter)[0]; \ - /*itemsize = NpyIter_GetDescrArray(iter)[1]->elsize;*/ \ itemsize = PyDataType_ELSIZE(NpyIter_GetDescrArray(iter)[1]); \ innersizeptr = NpyIter_GetInnerLoopSizePtr(iter); \ dataptrarray = NpyIter_GetDataPtrArray(iter); \ @@ -1445,13 +1445,8 @@ PyMODINIT_FUNC initnumpy_quaternion(void) { } // Initialize numpy - import_array(); - if (PyErr_Occurred()) { - INITERROR; - } - import_umath(); - if (PyErr_Occurred()) { - INITERROR; + if (PyArray_ImportNumPyAPI() < 0) { + return NULL; } numpy = PyImport_ImportModule("numpy"); if (!numpy) { @@ -1485,30 +1480,29 @@ PyMODINIT_FUNC initnumpy_quaternion(void) { _PyQuaternion_ArrFuncs.fillwithscalar = (PyArray_FillWithScalarFunc*)QUATERNION_fillwithscalar; // The quaternion array descr - quaternion_descr = PyObject_New(PyArray_DescrProto, &PyArrayDescr_Type); - quaternion_descr->typeobj = &PyQuaternion_Type; - quaternion_descr->kind = 'V'; - quaternion_descr->type = 'q'; - quaternion_descr->byteorder = '='; - quaternion_descr->flags = NPY_NEEDS_PYAPI | NPY_USE_GETITEM | NPY_USE_SETITEM; - quaternion_descr->type_num = 0; // assigned at registration - // quaternion_descr->elsize = quaternion_elsize; - PyDataType_SET_ELSIZE(quaternion_descr, quaternion_elsize); - quaternion_descr->alignment = quaternion_alignment; - quaternion_descr->subarray = NULL; - quaternion_descr->fields = NULL; - quaternion_descr->names = NULL; - // quaternion_descr->f = &_PyQuaternion_ArrFuncs; - quaternion_descr->f = &_PyQuaternion_ArrFuncs; - quaternion_descr->metadata = NULL; - quaternion_descr->c_metadata = NULL; + Py_SET_TYPE(&quaternion_proto, &PyArrayDescr_Type); + quaternion_proto.typeobj = &PyQuaternion_Type; + quaternion_proto.kind = 'V'; + quaternion_proto.type = 'q'; + quaternion_proto.byteorder = '='; + quaternion_proto.flags = NPY_NEEDS_PYAPI | NPY_USE_GETITEM | NPY_USE_SETITEM; + quaternion_proto.type_num = 0; // assigned at registration + quaternion_proto.elsize = quaternion_elsize; + quaternion_proto.alignment = quaternion_alignment; + quaternion_proto.subarray = NULL; + quaternion_proto.fields = NULL; + quaternion_proto.names = NULL; + quaternion_proto.f = &_PyQuaternion_ArrFuncs; + quaternion_proto.metadata = NULL; + quaternion_proto.c_metadata = NULL; Py_INCREF(&PyQuaternion_Type); - quaternionNum = PyArray_RegisterDataType(quaternion_descr); + quaternionNum = PyArray_RegisterDataType(&quaternion_proto); if (quaternionNum < 0) { INITERROR; } + quaternion_descr = PyArray_DescrFromType(quaternionNum); register_cast_function(NPY_BOOL, quaternionNum, (PyArray_VectorUnaryFunc*)BOOL_to_quaternion); register_cast_function(NPY_BYTE, quaternionNum, (PyArray_VectorUnaryFunc*)BYTE_to_quaternion); From c8900c3c90540e6cdf4c24f92ec1082fb255d448 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Sat, 17 Aug 2024 18:25:02 -0400 Subject: [PATCH 10/31] Drop support for python versions numpy doesn't support --- pyproject.toml | 2 +- src/numpy_quaternion.c | 75 ------------------------------------------ 2 files changed, 1 insertion(+), 76 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a25a550..06569f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "quaternion" description = "Add a quaternion dtype to NumPy" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = {file = "LICENSE"} authors = [ { name = "Michael Boyle", email = "michael.oliver.boyle@gmail.com" } diff --git a/src/numpy_quaternion.c b/src/numpy_quaternion.c index e7b6077..37ccafa 100644 --- a/src/numpy_quaternion.c +++ b/src/numpy_quaternion.c @@ -25,9 +25,6 @@ typedef npy_intp NPY_INTP_CONST; typedef npy_intp const NPY_INTP_CONST; #endif -// The following definitions, along with `#define NPY_PY3K 1`, can -// also be found in the header . -#if PY_MAJOR_VERSION >= 3 #define PyUString_FromString PyUnicode_FromString static NPY_INLINE int PyInt_Check(PyObject *op) { int overflow = 0; @@ -38,16 +35,6 @@ static NPY_INLINE int PyInt_Check(PyObject *op) { return (overflow == 0); } #define PyInt_AsLong PyLong_AsLong -#else -#define PyUString_FromString PyString_FromString -#endif - -// This macro was introduced in python 3.4.2 -#ifndef Py_RETURN_NOTIMPLEMENTED -/* Macro for returning Py_NotImplemented from a function */ -#define Py_RETURN_NOTIMPLEMENTED \ - return Py_INCREF(Py_NotImplemented), Py_NotImplemented -#endif // The basic python object holding a quaternion @@ -547,19 +534,11 @@ static int pyquaternion_num_nonzero(PyObject* a) { } CANNOT_CONVERT(int) CANNOT_CONVERT(float) -#if PY_MAJOR_VERSION < 3 -CANNOT_CONVERT(long) -CANNOT_CONVERT(oct) -CANNOT_CONVERT(hex) -#endif static PyNumberMethods pyquaternion_as_number = { pyquaternion_add, // nb_add pyquaternion_subtract, // nb_subtract pyquaternion_multiply, // nb_multiply - #if PY_MAJOR_VERSION < 3 - pyquaternion_divide, // nb_divide - #endif 0, // nb_remainder 0, // nb_divmod pyquaternion_num_power, // nb_power @@ -573,26 +552,12 @@ static PyNumberMethods pyquaternion_as_number = { 0, // nb_and 0, // nb_xor 0, // nb_or - #if PY_MAJOR_VERSION < 3 - 0, // nb_coerce - #endif pyquaternion_convert_int, // nb_int - #if PY_MAJOR_VERSION >= 3 0, // nb_reserved - #else - pyquaternion_convert_long, // nb_long - #endif pyquaternion_convert_float, // nb_float - #if PY_MAJOR_VERSION < 3 - pyquaternion_convert_oct, // nb_oct - pyquaternion_convert_hex, // nb_hex - #endif 0, // nb_inplace_add 0, // nb_inplace_subtract 0, // nb_inplace_multiply - #if PY_MAJOR_VERSION < 3 - 0, // nb_inplace_divide - #endif 0, // nb_inplace_remainder 0, // nb_inplace_power 0, // nb_inplace_lshift @@ -605,12 +570,8 @@ static PyNumberMethods pyquaternion_as_number = { 0, // nb_inplace_floor_divide 0, // nb_inplace_true_divide 0, // nb_index - #if PY_MAJOR_VERSION >= 3 - #if PY_MINOR_VERSION >= 5 0, // nb_matrix_multiply 0, // nb_inplace_matrix_multiply - #endif - #endif }; @@ -848,12 +809,7 @@ pyquaternion_str(PyObject *o) // Note that many of the slots below will be filled later, after the // corresponding functions are defined. static PyTypeObject PyQuaternion_Type = { -#if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) -#else - PyObject_HEAD_INIT(NULL) - 0, // ob_size -#endif "quaternion.quaternion", // tp_name sizeof(PyQuaternion), // tp_basicsize 0, // tp_itemsize @@ -861,11 +817,7 @@ static PyTypeObject PyQuaternion_Type = { 0, // tp_print 0, // tp_getattr 0, // tp_setattr -#if PY_MAJOR_VERSION >= 3 0, // tp_reserved -#else - 0, // tp_compare -#endif pyquaternion_repr, // tp_repr &pyquaternion_as_number, // tp_as_number 0, // tp_as_sequence @@ -876,11 +828,7 @@ static PyTypeObject PyQuaternion_Type = { 0, // tp_getattro 0, // tp_setattro 0, // tp_as_buffer -#if PY_MAJOR_VERSION >= 3 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags -#else - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES, // tp_flags -#endif "Floating-point quaternion numbers", // tp_doc 0, // tp_traverse 0, // tp_clear @@ -907,12 +855,8 @@ static PyTypeObject PyQuaternion_Type = { 0, // tp_subclasses 0, // tp_weaklist 0, // tp_del -#if PY_VERSION_HEX >= 0x02060000 0, // tp_version_tag -#endif -#if PY_VERSION_HEX >= 0x030400a1 0, // tp_finalize -#endif }; // Functions implementing internal features. Not all of these function @@ -1395,8 +1339,6 @@ int quaternion_alignment = offsetof(align_test, q); ///////////////////////////////////////////////////////////////// -#if PY_MAJOR_VERSION >= 3 - static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "numpy_quaternion", @@ -1414,15 +1356,6 @@ static struct PyModuleDef moduledef = { // This is the initialization function that does the setup PyMODINIT_FUNC PyInit_numpy_quaternion(void) { -#else - -#define INITERROR return - -// This is the initialization function that does the setup -PyMODINIT_FUNC initnumpy_quaternion(void) { - -#endif - PyObject *module; PyObject *tmp_ufunc; PyObject *slerp_evaluate_ufunc; @@ -1434,11 +1367,7 @@ PyMODINIT_FUNC initnumpy_quaternion(void) { PyObject* numpy_dict; // Initialize a (for now, empty) module -#if PY_MAJOR_VERSION >= 3 module = PyModule_Create(&moduledef); -#else - module = Py_InitModule("numpy_quaternion", QuaternionMethods); -#endif if(module==NULL) { INITERROR; @@ -1712,9 +1641,5 @@ PyMODINIT_FUNC initnumpy_quaternion(void) { PyModule_AddObject(module, "quaternion", (PyObject *)&PyQuaternion_Type); -#if PY_MAJOR_VERSION >= 3 return module; -#else - return; -#endif } From 8e86fa61dd6bcf1f9839caf5f85f720e71099a4e Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 08:43:18 -0400 Subject: [PATCH 11/31] Skip builds on python 3.8 --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ccacfbc..bd0c800 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,7 +72,7 @@ jobs: archs: "aarch64" env: - CIBW_SKIP: cp27-* cp35-* cp36-* cp37-* pp* *-musllinux_aarch64 cp312-*_i686 + CIBW_SKIP: cp27-* cp35-* cp36-* cp37-* cp38-* pp* *-musllinux_aarch64 cp312-*_i686 CIBW_ARCHS: ${{matrix.archs}} CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" CIBW_BEFORE_BUILD: python -c "print(('#'*130+'\n')*10)" && python -m pip install oldest-supported-numpy @@ -95,7 +95,7 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.11' + python-version: '3.12' - name: Update versions shell: bash @@ -134,7 +134,7 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.11' + python-version: '3.12' - name: Update versions shell: bash From 5666abe4bebe1e0a825cdaecf3dc58b62398f8b7 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 08:51:11 -0400 Subject: [PATCH 12/31] Try to ensure the right python is being used --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd0c800..1c3bc99 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,8 @@ jobs: python-version: '3.12' - name: Install Hatch - run: pip install --upgrade hatch + shell: bash + run: python -m pip install --upgrade hatch - name: Bump version id: get_version From fe843741e009185ae35de60374ba1b02466cb375 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 09:56:17 -0400 Subject: [PATCH 13/31] Don't insist on non-copying views --- src/quaternion/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/quaternion/__init__.py b/src/quaternion/__init__.py index 2c49572..bdf9d2d 100644 --- a/src/quaternion/__init__.py +++ b/src/quaternion/__init__.py @@ -716,9 +716,9 @@ def isclose(a, b, rtol=4*np.finfo(float).eps, atol=0.0, equal_nan=False): Returns a boolean array where two arrays are element-wise equal within a tolerance. - This function is essentially a copy of the `numpy.isclose` function, - with different default tolerances and one minor changes necessary to - deal correctly with quaternions. + This function is essentially a clone of the `numpy.isclose` function, + with different default tolerances and minor changes necessary to deal + correctly with quaternions. The tolerance values are positive, typically very small numbers. The relative difference (`rtol` * abs(`b`)) and the absolute difference @@ -780,8 +780,8 @@ def within_tol(x, y, atol, rtol): result = np.less_equal(abs(x-y), atol + rtol * abs(y)) return result[()] - x = np.array(a, copy=False, subok=True, ndmin=1) - y = np.array(b, copy=False, subok=True, ndmin=1) + x = np.array(a, subok=True, ndmin=1) + y = np.array(b, subok=True, ndmin=1) # Make sure y is an inexact type to avoid bad behavior on abs(MIN_INT). # This will cause casting of x later. Also, make sure to allow subclasses @@ -790,7 +790,7 @@ def within_tol(x, y, atol, rtol): dt = np.result_type(y, 1.) except TypeError: dt = np.dtype(np.quaternion) - y = np.array(y, dtype=dt, copy=False, subok=True) + y = np.array(y, dtype=dt, subok=True) xfin = np.isfinite(x) yfin = np.isfinite(y) From 9e508cd22073a1d26ec07f9f8834b05d40af4868 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 09:56:51 -0400 Subject: [PATCH 14/31] oldest-supported-numpy is deprecated; Numpy no longer supports python 3.9 --- .github/workflows/build.yml | 4 ++-- pyproject.toml | 11 +++++------ setup.py | 25 ------------------------- 3 files changed, 7 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c3bc99..9b37f8a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,10 +73,10 @@ jobs: archs: "aarch64" env: - CIBW_SKIP: cp27-* cp35-* cp36-* cp37-* cp38-* pp* *-musllinux_aarch64 cp312-*_i686 + CIBW_SKIP: cp36-* cp37-* cp38-* cp39-* pp* *-musllinux_aarch64 cp312-*_i686 CIBW_ARCHS: ${{matrix.archs}} CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" - CIBW_BEFORE_BUILD: python -c "print(('#'*130+'\n')*10)" && python -m pip install oldest-supported-numpy + CIBW_BEFORE_BUILD: python -c "print(('#'*130+'\n')*10)" && python -m pip install "numpy>=2.0,<3" CIBW_TEST_REQUIRES: pytest pytest-cov CIBW_TEST_COMMAND: "pytest {project}/tests" diff --git a/pyproject.toml b/pyproject.toml index 06569f5..0792b54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "quaternion" description = "Add a quaternion dtype to NumPy" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" license = {file = "LICENSE"} authors = [ { name = "Michael Boyle", email = "michael.oliver.boyle@gmail.com" } @@ -17,10 +17,9 @@ classifiers = [ "Topic :: Scientific/Engineering :: Astronomy" ] dependencies = [ - "numpy", - "scipy >=1.5", - "numba >=0.55; implementation_name == 'cpython'", - "importlib-metadata; python_version<'3.10'" + "numpy >=1.25,<3", + "scipy >=1.5<2", + "numba >=0.55; implementation_name == 'cpython'" ] dynamic = ["version"] @@ -29,7 +28,7 @@ Homepage = "https://github.com/moble/quaternion" Documentation = "https://quaternionic.readthedocs.io/en/latest" [build-system] -requires = ["setuptools>=61", "wheel", "oldest-supported-numpy"] +requires = ["setuptools>=61", "wheel", "numpy>=2.0,<3"] build-backend = "setuptools.build_meta" [tool.hatch.envs.default] diff --git a/setup.py b/setup.py index ed95392..67b66e6 100644 --- a/setup.py +++ b/setup.py @@ -52,31 +52,6 @@ long_description=long_description, long_description_content_type="text/markdown", ext_modules=extensions, - # install_requires=[ - # "numpy>=1.13", - # # See also extras and :environment_marker specs below - # ], - # extras_require={ - # "scipy": [ - # "scipy", - # ], - # "numba:python_version < '3.6' and platform_python_implementation != 'PyPy'": [ - # "numba<0.49.0", - # "llvmlite<0.32.0", - # ], - # "numba:python_version >= '3.6' and platform_python_implementation != 'PyPy'": [ - # "numba", - # ], - # "docs": [ - # "mkdocs", - # "mktheapidocs[plugin]", - # "pymdown-extensions", - # ], - # "testing": [ - # "pytest", - # "pytest-cov", - # ] - # }, version=version, ) From ca46e27dfc69dc5496a0146fe5438aea1c1c4da3 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 09:58:01 -0400 Subject: [PATCH 15/31] Soften Euler angle tolerance slightly --- tests/test_quaternion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_quaternion.py b/tests/test_quaternion.py index 9c410dd..f446ad5 100644 --- a/tests/test_quaternion.py +++ b/tests/test_quaternion.py @@ -523,7 +523,7 @@ def test_as_euler_angles(): R1 = quaternion.from_euler_angles(alpha, beta, gamma) R2 = quaternion.from_euler_angles(*list(quaternion.as_euler_angles(R1))) d = quaternion.rotation_intrinsic_distance(R1, R2) - assert d < 6e3*eps, ((alpha, beta, gamma), R1, R2, d) # Can't use allclose here; we don't care about rotor sign + assert d < 5e4*eps, ((alpha, beta, gamma), R1, R2, d) # Can't use allclose here; we don't care about rotor sign q0 = quaternion.quaternion(0, 0.6, 0.8, 0) assert q0.norm() == 1.0 assert abs(q0 - quaternion.from_euler_angles(*list(quaternion.as_euler_angles(q0)))) < 1.e-15 From 9d4d6a58b10082bf7d9101f1cd7524f7c573fa2b Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 11:20:07 -0400 Subject: [PATCH 16/31] Bring hatch version usage in line with `sxs` package --- .github/scripts/parse_bump_rule.py | 15 +++++++-------- .github/workflows/build.yml | 10 ++++++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/scripts/parse_bump_rule.py b/.github/scripts/parse_bump_rule.py index d7e35fe..6a00a10 100644 --- a/.github/scripts/parse_bump_rule.py +++ b/.github/scripts/parse_bump_rule.py @@ -1,18 +1,17 @@ -import sys import os def parse(message): if "#prerelease" in message: - return 'prerelease' + return "prerelease" - for pre in ['pre', '']: - for level in ['patch', 'minor', 'major']: - if f'#{pre}{level}' in message: - return f'{pre}{level}' + for pre in ["pre", ""]: + for level in ["patch", "minor", "major"]: + if f"#{pre}{level}" in message: + return f"{pre}{level}" - return 'patch' + return "patch" -message = os.environ['github_event_head_commit_message'] +message = os.environ["github_event_head_commit_message"] print(parse(message)) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9b37f8a..d58f1e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: - name: Install Hatch shell: bash - run: python -m pip install --upgrade hatch + run: python -m pip install --disable-pip-version-check --upgrade hatch hatchling - name: Bump version id: get_version @@ -45,10 +45,16 @@ jobs: env: github_event_head_commit_message: ${{ github.event.head_commit.message }} run: | + # Note: The following line reads the HEAD commit message to look for an indication of how + # to bump the version number. Specifically, if `#patch`, `#minor`, or `#major` is present + # in the commit message, it bumps the corresponding version number. Those can also be + # prepended as `#premajor`, etc., to add/bump the prerelease modifier. If none of those + # are present, `#patch` is assumed — that is, the lowest-significance number is + # incremented. See the documentation of the `hatch version` command for details. export version_bump_rule=$(python .github/scripts/parse_bump_rule.py) echo "version_bump_rule: '${version_bump_rule}'" hatch version "${version_bump_rule}" - export new_version=$(hatch version) + export new_version=$(TERM="unknown" hatch version) echo "new_version: '${new_version}'" echo "new_version=${new_version}" >> $GITHUB_ENV # Save env variable for later steps From 5bac7884f8054a21c8e07c91ed1e0f6b9a318b02 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 11:26:17 -0400 Subject: [PATCH 17/31] Correct application to docs/conf.py; don't apply to pyproject.toml --- .github/scripts/update_versions.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/scripts/update_versions.py b/.github/scripts/update_versions.py index a5adb2d..77530e7 100644 --- a/.github/scripts/update_versions.py +++ b/.github/scripts/update_versions.py @@ -6,13 +6,24 @@ def update(version): if not version: raise ValueError("Can't replace version with empty string") - files = ("pyproject.toml", "setup.py", "src/quaternion/__init__.py") + short_version = ".".join(version.split(".")[:2]) + + files = ("setup.py", "src/quaternion/__init__.py") pattern = re.compile('^(__version__|version) *= *".*?"') replacement = r'\1 = "' + version + '"' with fileinput.input(files=files, inplace=True) as f: for line in f: print(pattern.sub(replacement, line), end="") + files = ("docs/conf.py") + pattern = re.compile('^release *= *".*?"') + short_pattern = re.compile('^version *= *".*?"') + replacement = r'release = "' + version + '"' + short_replacement = r'version = "' + short_version + '"' + with fileinput.input(files=files, inplace=True) as f: + for line in f: + print(short_pattern.sub(short_replacement, pattern.sub(replacement, line)), end="") + version = os.environ["new_version"] From 8b698f54961af9dacc7891e023e3fcdc97d2c72f Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 11:34:17 -0400 Subject: [PATCH 18/31] Require hatchling for build --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0792b54..328138a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ Homepage = "https://github.com/moble/quaternion" Documentation = "https://quaternionic.readthedocs.io/en/latest" [build-system] -requires = ["setuptools>=61", "wheel", "numpy>=2.0,<3"] +requires = ["hatchling", "setuptools>=61", "wheel", "numpy>=2.0,<3"] build-backend = "setuptools.build_meta" [tool.hatch.envs.default] From 61fc15f1a6ebde5829ac1f353d092b5c9e70b607 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 11:46:09 -0400 Subject: [PATCH 19/31] Tell hatch where to find the current version --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 328138a..20d10a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,8 @@ dependencies = [ build = "mkdocs build --clean" serve = "mkdocs serve --dev-addr localhost:8000" +[tool.hatch.version] +path = "src/quaternion/__init__.py" [tool.setuptools.dynamic] version = {attr = "quaternion.__version__"} From b0751ff969c24025f7ea9edf94a81ee9544262ba Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 12:01:25 -0400 Subject: [PATCH 20/31] Correct sharing of version number across jobs --- .github/workflows/build.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d58f1e1..3140a58 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: name: Get new version number runs-on: ubuntu-latest outputs: - version: ${{ steps.get_version.outputs.version }} + new_version: ${{ steps.get_version.outputs.new_version }} steps: - uses: actions/checkout@v4 @@ -37,7 +37,7 @@ jobs: - name: Install Hatch shell: bash - run: python -m pip install --disable-pip-version-check --upgrade hatch hatchling + run: python -m pip install --disable-pip-version-check --upgrade hatch - name: Bump version id: get_version @@ -56,7 +56,7 @@ jobs: hatch version "${version_bump_rule}" export new_version=$(TERM="unknown" hatch version) echo "new_version: '${new_version}'" - echo "new_version=${new_version}" >> $GITHUB_ENV # Save env variable for later steps + echo "new_version=${new_version}" >> "$GITHUB_OUTPUT" # Save variable for later steps build_wheels: @@ -107,7 +107,7 @@ jobs: - name: Update versions shell: bash run: | - export new_version=${{needs.get_new_version.outputs.version}} + export new_version=${{needs.get_new_version.outputs.new_version}} echo "Updating version to '${new_version}'" python .github/scripts/update_versions.py @@ -146,7 +146,7 @@ jobs: - name: Update versions shell: bash run: | - export new_version=${{needs.get_new_version.outputs.version}} + export new_version=${{needs.get_new_version.outputs.new_version}} echo "Updating version to '${new_version}'" python .github/scripts/update_versions.py @@ -201,14 +201,14 @@ jobs: - name: Update versions shell: bash run: | - export new_version=${{needs.get_new_version.outputs.version}} + export new_version=${{needs.get_new_version.outputs.new_version}} echo "Updating version to '${new_version}'" python .github/scripts/update_versions.py - name: Tag and push new version shell: bash run: | - export new_version=${{needs.get_new_version.outputs.version}} + export new_version=${{needs.get_new_version.outputs.new_version}} git config user.name github-actions git config user.email github-actions@github.com git commit -m "Update version for new release" pyproject.toml setup.py src/quaternion/__init__.py @@ -223,8 +223,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: v${{ needs.get_new_version.outputs.version }} - name: Release v${{ needs.get_new_version.outputs.version }} + tag_name: v${{ needs.get_new_version.outputs.new_version }} + name: Release v${{ needs.get_new_version.outputs.new_version }} draft: false prerelease: false From 34eaf62c0fed029944d95fffb6fa59b0b37e6f90 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 12:04:51 -0400 Subject: [PATCH 21/31] Correct version syntax typo in dependencies --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 20d10a9..a33f71d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ ] dependencies = [ "numpy >=1.25,<3", - "scipy >=1.5<2", + "scipy >=1.5,<2", "numba >=0.55; implementation_name == 'cpython'" ] dynamic = ["version"] From 289647dfaca5f251960b6a45bbfb7923bed923af Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 12:08:13 -0400 Subject: [PATCH 22/31] Update sdist installation --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3140a58..93d9da9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -152,7 +152,8 @@ jobs: - name: Build sdist run: | - python -m pip install oldest-supported-numpy + python -m pip install --upgrade pip + python -m pip install --upgrade "setuptools>=61" wheel "numpy>=2.0,<3" python setup.py sdist - uses: actions/upload-artifact@v3 # Don't upgrade to v4 until each artifact is named From 907cde4f2464ca95c07e7bdf8726b829131a64b2 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 13:41:59 -0400 Subject: [PATCH 23/31] Remove unused script --- .github/scripts/parse_version.py | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .github/scripts/parse_version.py diff --git a/.github/scripts/parse_version.py b/.github/scripts/parse_version.py deleted file mode 100644 index 2f7387e..0000000 --- a/.github/scripts/parse_version.py +++ /dev/null @@ -1,11 +0,0 @@ -import sys - - -def parse(f): - import tomli - with open(f, "rb") as fp: - pyproject = tomli.load(fp) - return pyproject["tool"]["poetry"]["version"] - - -print(parse(sys.argv[1])) From a0e22182d81e9310476213a2786b6653acecfba2 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 13:42:57 -0400 Subject: [PATCH 24/31] Use simpler version of numpy 2 compat Co-authored-by: Peter Hawkins --- src/numpy_quaternion.c | 107 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 95 insertions(+), 12 deletions(-) diff --git a/src/numpy_quaternion.c b/src/numpy_quaternion.c index 37ccafa..ade09ec 100644 --- a/src/numpy_quaternion.c +++ b/src/numpy_quaternion.c @@ -1,18 +1,15 @@ // Copyright (c) 2024, Michael Boyle // See LICENSE file for details: -#define PY_ARRAY_UNIQUE_SYMBOL NumpyQuaternion -// #define NPY_NO_DEPRECATED_API NPY_API_VERSION -#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#define NPY_NO_DEPRECATED_API NPY_API_VERSION #include -#include +#include +#include #include #include "structmember.h" -#include "quaternion.h" -// Provide compatibility with numpy 1 and 2 -#include "npy_2_compat.h" +#include "quaternion.h" // Numpy 1.19 changed UFuncGenericFunction to use const `dimensions` and `steps` pointers. // Supposedly, this should only generate warnings, but this now causes errors in CI, so @@ -25,6 +22,15 @@ typedef npy_intp NPY_INTP_CONST; typedef npy_intp const NPY_INTP_CONST; #endif +#if NPY_ABI_VERSION < 0x02000000 +#define PyArray_DescrProto PyArray_Descr +#define PyDataType_ELSIZE(d) ((d)->elsize) +#define PyDataType_GetArrFuncs(d) ((d)->f) +#endif + +// The following definitions, along with `#define NPY_PY3K 1`, can +// also be found in the header . +#if PY_MAJOR_VERSION >= 3 #define PyUString_FromString PyUnicode_FromString static NPY_INLINE int PyInt_Check(PyObject *op) { int overflow = 0; @@ -35,6 +41,16 @@ static NPY_INLINE int PyInt_Check(PyObject *op) { return (overflow == 0); } #define PyInt_AsLong PyLong_AsLong +#else +#define PyUString_FromString PyString_FromString +#endif + +// This macro was introduced in python 3.4.2 +#ifndef Py_RETURN_NOTIMPLEMENTED +/* Macro for returning Py_NotImplemented from a function */ +#define Py_RETURN_NOTIMPLEMENTED \ + return Py_INCREF(Py_NotImplemented), Py_NotImplemented +#endif // The basic python object holding a quaternion @@ -47,10 +63,10 @@ static PyTypeObject PyQuaternion_Type; // This is the crucial feature that will make a quaternion into a // built-in numpy data type. We will describe its features below. -PyArray_DescrProto* quaternion_descr; +PyArray_Descr* quaternion_descr; PyArray_DescrProto quaternion_proto = {PyObject_HEAD_INIT(NULL)}; - + static NPY_INLINE int PyQuaternion_Check(PyObject* object) { return PyObject_IsInstance(object,(PyObject*)&PyQuaternion_Type); @@ -534,11 +550,19 @@ static int pyquaternion_num_nonzero(PyObject* a) { } CANNOT_CONVERT(int) CANNOT_CONVERT(float) +#if PY_MAJOR_VERSION < 3 +CANNOT_CONVERT(long) +CANNOT_CONVERT(oct) +CANNOT_CONVERT(hex) +#endif static PyNumberMethods pyquaternion_as_number = { pyquaternion_add, // nb_add pyquaternion_subtract, // nb_subtract pyquaternion_multiply, // nb_multiply + #if PY_MAJOR_VERSION < 3 + pyquaternion_divide, // nb_divide + #endif 0, // nb_remainder 0, // nb_divmod pyquaternion_num_power, // nb_power @@ -552,12 +576,26 @@ static PyNumberMethods pyquaternion_as_number = { 0, // nb_and 0, // nb_xor 0, // nb_or + #if PY_MAJOR_VERSION < 3 + 0, // nb_coerce + #endif pyquaternion_convert_int, // nb_int + #if PY_MAJOR_VERSION >= 3 0, // nb_reserved + #else + pyquaternion_convert_long, // nb_long + #endif pyquaternion_convert_float, // nb_float + #if PY_MAJOR_VERSION < 3 + pyquaternion_convert_oct, // nb_oct + pyquaternion_convert_hex, // nb_hex + #endif 0, // nb_inplace_add 0, // nb_inplace_subtract 0, // nb_inplace_multiply + #if PY_MAJOR_VERSION < 3 + 0, // nb_inplace_divide + #endif 0, // nb_inplace_remainder 0, // nb_inplace_power 0, // nb_inplace_lshift @@ -570,8 +608,12 @@ static PyNumberMethods pyquaternion_as_number = { 0, // nb_inplace_floor_divide 0, // nb_inplace_true_divide 0, // nb_index + #if PY_MAJOR_VERSION >= 3 + #if PY_MINOR_VERSION >= 5 0, // nb_matrix_multiply 0, // nb_inplace_matrix_multiply + #endif + #endif }; @@ -809,7 +851,12 @@ pyquaternion_str(PyObject *o) // Note that many of the slots below will be filled later, after the // corresponding functions are defined. static PyTypeObject PyQuaternion_Type = { +#if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) +#else + PyObject_HEAD_INIT(NULL) + 0, // ob_size +#endif "quaternion.quaternion", // tp_name sizeof(PyQuaternion), // tp_basicsize 0, // tp_itemsize @@ -817,7 +864,11 @@ static PyTypeObject PyQuaternion_Type = { 0, // tp_print 0, // tp_getattr 0, // tp_setattr +#if PY_MAJOR_VERSION >= 3 0, // tp_reserved +#else + 0, // tp_compare +#endif pyquaternion_repr, // tp_repr &pyquaternion_as_number, // tp_as_number 0, // tp_as_sequence @@ -828,7 +879,11 @@ static PyTypeObject PyQuaternion_Type = { 0, // tp_getattro 0, // tp_setattro 0, // tp_as_buffer +#if PY_MAJOR_VERSION >= 3 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES, // tp_flags +#endif "Floating-point quaternion numbers", // tp_doc 0, // tp_traverse 0, // tp_clear @@ -855,8 +910,12 @@ static PyTypeObject PyQuaternion_Type = { 0, // tp_subclasses 0, // tp_weaklist 0, // tp_del +#if PY_VERSION_HEX >= 0x02060000 0, // tp_version_tag +#endif +#if PY_VERSION_HEX >= 0x030400a1 0, // tp_finalize +#endif }; // Functions implementing internal features. Not all of these function @@ -1339,6 +1398,8 @@ int quaternion_alignment = offsetof(align_test, q); ///////////////////////////////////////////////////////////////// +#if PY_MAJOR_VERSION >= 3 + static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "numpy_quaternion", @@ -1356,6 +1417,15 @@ static struct PyModuleDef moduledef = { // This is the initialization function that does the setup PyMODINIT_FUNC PyInit_numpy_quaternion(void) { +#else + +#define INITERROR return + +// This is the initialization function that does the setup +PyMODINIT_FUNC initnumpy_quaternion(void) { + +#endif + PyObject *module; PyObject *tmp_ufunc; PyObject *slerp_evaluate_ufunc; @@ -1367,15 +1437,24 @@ PyMODINIT_FUNC PyInit_numpy_quaternion(void) { PyObject* numpy_dict; // Initialize a (for now, empty) module +#if PY_MAJOR_VERSION >= 3 module = PyModule_Create(&moduledef); +#else + module = Py_InitModule("numpy_quaternion", QuaternionMethods); +#endif if(module==NULL) { INITERROR; } // Initialize numpy - if (PyArray_ImportNumPyAPI() < 0) { - return NULL; + import_array(); + if (PyErr_Occurred()) { + INITERROR; + } + import_umath(); + if (PyErr_Occurred()) { + INITERROR; } numpy = PyImport_ImportModule("numpy"); if (!numpy) { @@ -1641,5 +1720,9 @@ PyMODINIT_FUNC PyInit_numpy_quaternion(void) { PyModule_AddObject(module, "quaternion", (PyObject *)&PyQuaternion_Type); +#if PY_MAJOR_VERSION >= 3 return module; -} +#else + return; +#endif +} \ No newline at end of file From c34ac6fa9924ad0b5bdccfae82f4a2d4f996d271 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 13:45:43 -0400 Subject: [PATCH 25/31] Remove unused npy_2_compat.h --- src/npy_2_compat.h | 249 ----------------------------------------- src/numpy_quaternion.c | 2 +- 2 files changed, 1 insertion(+), 250 deletions(-) delete mode 100644 src/npy_2_compat.h diff --git a/src/npy_2_compat.h b/src/npy_2_compat.h deleted file mode 100644 index e39e65a..0000000 --- a/src/npy_2_compat.h +++ /dev/null @@ -1,249 +0,0 @@ -/* - * This header file defines relevant features which: - * - Require runtime inspection depending on the NumPy version. - * - May be needed when compiling with an older version of NumPy to allow - * a smooth transition. - * - * As such, it is shipped with NumPy 2.0, but designed to be vendored in full - * or parts by downstream projects. - * - * It must be included after any other includes. `import_array()` must have - * been called in the scope or version dependency will misbehave, even when - * only `PyUFunc_` API is used. - * - * If required complicated defs (with inline functions) should be written as: - * - * #if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION - * Simple definition when NumPy 2.0 API is guaranteed. - * #else - * static inline definition of a 1.x compatibility shim - * #if NPY_ABI_VERSION < 0x02000000 - * Make 1.x compatibility shim the public API (1.x only branch) - * #else - * Runtime dispatched version (1.x or 2.x) - * #endif - * #endif - * - * An internal build always passes NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION - */ - -#ifndef NUMPY_CORE_INCLUDE_NUMPY_NPY_2_COMPAT_H_ -#define NUMPY_CORE_INCLUDE_NUMPY_NPY_2_COMPAT_H_ - -/* - * New macros for accessing real and complex part of a complex number can be - * found in "npy_2_complexcompat.h". - */ - - -/* - * This header is meant to be included by downstream directly for 1.x compat. - * In that case we need to ensure that users first included the full headers - * and not just `ndarraytypes.h`. - */ - -#ifndef NPY_FEATURE_VERSION - #error "The NumPy 2 compat header requires `import_array()` for which " \ - "the `ndarraytypes.h` header include is not sufficient. Please " \ - "include it after `numpy/ndarrayobject.h` or similar.\n" \ - "To simplify inclusion, you may use `PyArray_ImportNumPy()` " \ - "which is defined in the compat header and is lightweight (can be)." -#endif - -#if NPY_ABI_VERSION < 0x02000000 - /* - * Define 2.0 feature version as it is needed below to decide whether we - * compile for both 1.x and 2.x (defining it guarantees 1.x only). - */ - #define NPY_2_0_API_VERSION 0x00000012 - /* - * If we are compiling with NumPy 1.x, PyArray_RUNTIME_VERSION so we - * pretend the `PyArray_RUNTIME_VERSION` is `NPY_FEATURE_VERSION`. - * This allows downstream to use `PyArray_RUNTIME_VERSION` if they need to. - */ - #define PyArray_RUNTIME_VERSION NPY_FEATURE_VERSION - /* Compiling on NumPy 1.x where these are the same: */ - #define PyArray_DescrProto PyArray_Descr -#endif - - -/* - * Define a better way to call `_import_array()` to simplify backporting as - * we now require imports more often (necessary to make ABI flexible). - */ -#ifdef import_array1 - -static inline int -PyArray_ImportNumPyAPI(void) -{ - if (NPY_UNLIKELY(PyArray_API == NULL)) { - import_array1(-1); - } - return 0; -} - -#endif /* import_array1 */ - - -/* - * NPY_DEFAULT_INT - * - * The default integer has changed, `NPY_DEFAULT_INT` is available at runtime - * for use as type number, e.g. `PyArray_DescrFromType(NPY_DEFAULT_INT)`. - * - * NPY_RAVEL_AXIS - * - * This was introduced in NumPy 2.0 to allow indicating that an axis should be - * raveled in an operation. Before NumPy 2.0, NPY_MAXDIMS was used for this purpose. - * - * NPY_MAXDIMS - * - * A constant indicating the maximum number dimensions allowed when creating - * an ndarray. - * - * NPY_NTYPES_LEGACY - * - * The number of built-in NumPy dtypes. - */ -#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION - #define NPY_DEFAULT_INT NPY_INTP - #define NPY_RAVEL_AXIS NPY_MIN_INT - #define NPY_MAXARGS 64 - -#elif NPY_ABI_VERSION < 0x02000000 - #define NPY_DEFAULT_INT NPY_LONG - #define NPY_RAVEL_AXIS 32 - #define NPY_MAXARGS 32 - - /* Aliases of 2.x names to 1.x only equivalent names */ - #define NPY_NTYPES NPY_NTYPES_LEGACY - #define PyArray_DescrProto PyArray_Descr - #define _PyArray_LegacyDescr PyArray_Descr - /* NumPy 2 definition always works, but add it for 1.x only */ - #define PyDataType_ISLEGACY(dtype) (1) -#else - #define NPY_DEFAULT_INT \ - (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_INTP : NPY_LONG) - #define NPY_RAVEL_AXIS \ - (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_MIN_INT : 32) - #define NPY_MAXARGS \ - (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? 64 : 32) -#endif - - -/* - * Access inline functions for descriptor fields. Except for the first - * few fields, these needed to be moved (elsize, alignment) for - * additional space. Or they are descriptor specific and are not generally - * available anymore (metadata, c_metadata, subarray, names, fields). - * - * Most of these are defined via the `DESCR_ACCESSOR` macro helper. - */ -#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION || NPY_ABI_VERSION < 0x02000000 - /* Compiling for 1.x or 2.x only, direct field access is OK: */ - - static inline void - PyDataType_SET_ELSIZE(PyArray_Descr *dtype, npy_intp size) - { - dtype->elsize = size; - } - - static inline npy_uint64 - PyDataType_FLAGS(const PyArray_Descr *dtype) - { - #if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION - return dtype->flags; - #else - return (unsigned char)dtype->flags; /* Need unsigned cast on 1.x */ - #endif - } - - #define DESCR_ACCESSOR(FIELD, field, type, legacy_only) \ - static inline type \ - PyDataType_##FIELD(const PyArray_Descr *dtype) { \ - if (legacy_only && !PyDataType_ISLEGACY(dtype)) { \ - return (type)0; \ - } \ - return ((_PyArray_LegacyDescr *)dtype)->field; \ - } -#else /* compiling for both 1.x and 2.x */ - - static inline void - PyDataType_SET_ELSIZE(PyArray_Descr *dtype, npy_intp size) - { - if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { - ((_PyArray_DescrNumPy2 *)dtype)->elsize = size; - } - else { - ((PyArray_DescrProto *)dtype)->elsize = (int)size; - } - } - - static inline npy_uint64 - PyDataType_FLAGS(const PyArray_Descr *dtype) - { - if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { - return ((_PyArray_DescrNumPy2 *)dtype)->flags; - } - else { - return (unsigned char)((PyArray_DescrProto *)dtype)->flags; - } - } - - /* Cast to LegacyDescr always fine but needed when `legacy_only` */ - #define DESCR_ACCESSOR(FIELD, field, type, legacy_only) \ - static inline type \ - PyDataType_##FIELD(const PyArray_Descr *dtype) { \ - if (legacy_only && !PyDataType_ISLEGACY(dtype)) { \ - return (type)0; \ - } \ - if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { \ - return ((_PyArray_LegacyDescr *)dtype)->field; \ - } \ - else { \ - return ((PyArray_DescrProto *)dtype)->field; \ - } \ - } -#endif - -DESCR_ACCESSOR(ELSIZE, elsize, npy_intp, 0) -DESCR_ACCESSOR(ALIGNMENT, alignment, npy_intp, 0) -DESCR_ACCESSOR(METADATA, metadata, PyObject *, 1) -DESCR_ACCESSOR(SUBARRAY, subarray, PyArray_ArrayDescr *, 1) -DESCR_ACCESSOR(NAMES, names, PyObject *, 1) -DESCR_ACCESSOR(FIELDS, fields, PyObject *, 1) -DESCR_ACCESSOR(C_METADATA, c_metadata, NpyAuxData *, 1) - -#undef DESCR_ACCESSOR - - -#if !(defined(NPY_INTERNAL_BUILD) && NPY_INTERNAL_BUILD) -#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION - static inline PyArray_ArrFuncs * - PyDataType_GetArrFuncs(const PyArray_Descr *descr) - { - return _PyDataType_GetArrFuncs(descr); - } -#elif NPY_ABI_VERSION < 0x02000000 - static inline PyArray_ArrFuncs * - PyDataType_GetArrFuncs(const PyArray_Descr *descr) - { - return descr->f; - } -#else - static inline PyArray_ArrFuncs * - PyDataType_GetArrFuncs(const PyArray_Descr *descr) - { - if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { - return _PyDataType_GetArrFuncs(descr); - } - else { - return ((PyArray_DescrProto *)descr)->f; - } - } -#endif - - -#endif /* not internal build */ - -#endif /* NUMPY_CORE_INCLUDE_NUMPY_NPY_2_COMPAT_H_ */ diff --git a/src/numpy_quaternion.c b/src/numpy_quaternion.c index ade09ec..5956663 100644 --- a/src/numpy_quaternion.c +++ b/src/numpy_quaternion.c @@ -1725,4 +1725,4 @@ PyMODINIT_FUNC initnumpy_quaternion(void) { #else return; #endif -} \ No newline at end of file +} From 36b312fd7db52cbbaa03e1ebbaa38eed8d3fefcb Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 13:59:34 -0400 Subject: [PATCH 26/31] Skip python 3.13 while numba catches up --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 93d9da9..60337ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,7 +79,7 @@ jobs: archs: "aarch64" env: - CIBW_SKIP: cp36-* cp37-* cp38-* cp39-* pp* *-musllinux_aarch64 cp312-*_i686 + CIBW_SKIP: cp36-* cp37-* cp38-* cp39-* pp* *-musllinux_aarch64 cp312-*_i686 cp313-* CIBW_ARCHS: ${{matrix.archs}} CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" CIBW_BEFORE_BUILD: python -c "print(('#'*130+'\n')*10)" && python -m pip install "numpy>=2.0,<3" From bb7fa1cce27869fab6d6e899ac30d10ecb8339f0 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 15:33:09 -0400 Subject: [PATCH 27/31] Use only versions of scipy,numpy with binaries --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60337ad..2268dff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,6 +79,7 @@ jobs: archs: "aarch64" env: + PIP_ONLY_BINARY: "scipy,numpy" CIBW_SKIP: cp36-* cp37-* cp38-* cp39-* pp* *-musllinux_aarch64 cp312-*_i686 cp313-* CIBW_ARCHS: ${{matrix.archs}} CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" From 62b51e1bf3b122a89ebda8a7a22a1a1672590b9c Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 15:44:38 -0400 Subject: [PATCH 28/31] Skip win32 builds; not supported by numpy --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2268dff..394c0ab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,7 +80,7 @@ jobs: env: PIP_ONLY_BINARY: "scipy,numpy" - CIBW_SKIP: cp36-* cp37-* cp38-* cp39-* pp* *-musllinux_aarch64 cp312-*_i686 cp313-* + CIBW_SKIP: cp36-* cp37-* cp38-* cp39-* pp* *-musllinux_aarch64 *-win32 *-manylinux_i686 cp313-* CIBW_ARCHS: ${{matrix.archs}} CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" CIBW_BEFORE_BUILD: python -c "print(('#'*130+'\n')*10)" && python -m pip install "numpy>=2.0,<3" From 18eb2f2dec37bfdbf6c827a3de2a110781389e40 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 15:55:03 -0400 Subject: [PATCH 29/31] Skip builds on all mulslinux --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 394c0ab..d25d9d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,7 +80,7 @@ jobs: env: PIP_ONLY_BINARY: "scipy,numpy" - CIBW_SKIP: cp36-* cp37-* cp38-* cp39-* pp* *-musllinux_aarch64 *-win32 *-manylinux_i686 cp313-* + CIBW_SKIP: cp36-* cp37-* cp38-* cp39-* pp* *-musllinux* *-win32 *-manylinux_i686 cp313-* CIBW_ARCHS: ${{matrix.archs}} CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" CIBW_BEFORE_BUILD: python -c "print(('#'*130+'\n')*10)" && python -m pip install "numpy>=2.0,<3" From 48ad7b413496e19d32c71270756283c3aa633773 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 16:25:43 -0400 Subject: [PATCH 30/31] Explain CIBW_SKIPs #major --- .github/workflows/build.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d25d9d6..e5cb0ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,8 +79,13 @@ jobs: archs: "aarch64" env: - PIP_ONLY_BINARY: "scipy,numpy" - CIBW_SKIP: cp36-* cp37-* cp38-* cp39-* pp* *-musllinux* *-win32 *-manylinux_i686 cp313-* + # Exclude 32-bit builds: *-win32 *-manylinux_i686 + # Exclude python versions not supported by numpy: cp36-* cp37-* cp38-* cp39-* pp* + # Exclude musllinux builds: *-musllinux* + # Exclude python versions not yet supported by numba: cp313-* + # https://numpy.org/neps/nep-0029-deprecation_policy.html + # https://numba.readthedocs.io/en/stable/user/installing.html#numba-support-info + CIBW_SKIP: *-win32 *-manylinux_i686 cp36-* cp37-* cp38-* cp39-* pp* *-musllinux* cp313-* CIBW_ARCHS: ${{matrix.archs}} CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" CIBW_BEFORE_BUILD: python -c "print(('#'*130+'\n')*10)" && python -m pip install "numpy>=2.0,<3" From 07b8c1f30b8c66c32c96c33adc6f352c29e2be18 Mon Sep 17 00:00:00 2001 From: Mike Boyle Date: Mon, 30 Sep 2024 16:30:51 -0400 Subject: [PATCH 31/31] Format CIBW_SKIPs #major --- .github/workflows/build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5cb0ba..086103f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,18 +79,18 @@ jobs: archs: "aarch64" env: + CIBW_ARCHS: ${{matrix.archs}} + CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" + CIBW_BEFORE_BUILD: python -c "print(('#'*130+'\n')*10)" && python -m pip install "numpy>=2.0,<3" + CIBW_TEST_REQUIRES: pytest pytest-cov + CIBW_TEST_COMMAND: "pytest {project}/tests" + CIBW_SKIP: "*-win32 *-manylinux_i686 cp36-* cp37-* cp38-* cp39-* pp* *-musllinux* cp313-*" # Exclude 32-bit builds: *-win32 *-manylinux_i686 # Exclude python versions not supported by numpy: cp36-* cp37-* cp38-* cp39-* pp* # Exclude musllinux builds: *-musllinux* # Exclude python versions not yet supported by numba: cp313-* # https://numpy.org/neps/nep-0029-deprecation_policy.html # https://numba.readthedocs.io/en/stable/user/installing.html#numba-support-info - CIBW_SKIP: *-win32 *-manylinux_i686 cp36-* cp37-* cp38-* cp39-* pp* *-musllinux* cp313-* - CIBW_ARCHS: ${{matrix.archs}} - CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" - CIBW_BEFORE_BUILD: python -c "print(('#'*130+'\n')*10)" && python -m pip install "numpy>=2.0,<3" - CIBW_TEST_REQUIRES: pytest pytest-cov - CIBW_TEST_COMMAND: "pytest {project}/tests" steps: - name: Set up QEMU