Sign In

numpy

Package Overview
Dependencies
Maintainers
2
Versions
164
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

numpy - pypi Package Compare versions

Comparing version
2.5.1
to
2.5.2
+57
doc/changelog/2.5.2-changelog.rst
Contributors
============
A total of 16 people contributed to this release. People with a "+" by their
names contributed a patch for the first time.
* Abhijeetsingh Meena +
* Charalampos Stratakis
* Charles Harris
* Chris Ninham +
* David Woods
* Geonho +
* Gopu Yeshwanth Reddy +
* Iason Krommydas
* Ijtihed Kilani
* Jelle Zijlstra +
* Joren Hammudoglu
* Kumar Aditya
* Mike Boyle
* Nathan Goldbaum
* Raghuveer Devulapalli
* Sebastian Berg
Pull requests merged
====================
A total of 28 pull requests were merged for this release.
* `#31864 <https://github.com/numpy/numpy/pull/31864>`__: MAINT: Prepare 2.5.x for further development
* `#31889 <https://github.com/numpy/numpy/pull/31889>`__: TYP: Backport multiple static typing fixes 1.
* `#31900 <https://github.com/numpy/numpy/pull/31900>`__: TST: add tests for stable ABI numpy extensions (#31822)
* `#31901 <https://github.com/numpy/numpy/pull/31901>`__: BUG: fix ``StringDType`` coerce flag in binary ufunc promotion...
* `#31902 <https://github.com/numpy/numpy/pull/31902>`__: BLD: fix meson deprecation warnings (#31892)
* `#31921 <https://github.com/numpy/numpy/pull/31921>`__: TYP: Backport multiple typing fixes 2.
* `#31947 <https://github.com/numpy/numpy/pull/31947>`__: MAINT: Update x86-simd-sort subproject (5adb334 → fa944ef) (#31908)
* `#31949 <https://github.com/numpy/numpy/pull/31949>`__: BUG: fix crash on 32 bit systems using abi3t (#31771)
* `#31950 <https://github.com/numpy/numpy/pull/31950>`__: MNT: remove some obsolete string to bool workarounds (#31859)
* `#31952 <https://github.com/numpy/numpy/pull/31952>`__: BUG: centralized helper for output coerce and na_object in stringdtype...
* `#31953 <https://github.com/numpy/numpy/pull/31953>`__: BUG: fix CPU feature env diagnostic buffer overruns (#31905)
* `#31954 <https://github.com/numpy/numpy/pull/31954>`__: BUG: restore ndarray.conjugate() for legacy user-defined dtypes...
* `#31955 <https://github.com/numpy/numpy/pull/31955>`__: TYP: Avoid shadowed dtype annotations
* `#32077 <https://github.com/numpy/numpy/pull/32077>`__: MAINT: Update verdored-meson/meson to match main.
* `#32114 <https://github.com/numpy/numpy/pull/32114>`__: BUG: fix refcount leak on overlapping copyto with where=False
* `#32115 <https://github.com/numpy/numpy/pull/32115>`__: BUG: fix swallowed cast error in fancy indexing assignment (#31975)
* `#32116 <https://github.com/numpy/numpy/pull/32116>`__: BUG: Fix buffered iterator stride after removing multi-index
* `#32117 <https://github.com/numpy/numpy/pull/32117>`__: BUG: fix ``np.fromiter`` corruption when reusing a ``StringDType``...
* `#32119 <https://github.com/numpy/numpy/pull/32119>`__: BUG: add a special case for StringDType in np.isdtype (#32030)
* `#32121 <https://github.com/numpy/numpy/pull/32121>`__: BUG: reference leak in ``simd_sequence_from_iterable`` (#32038)
* `#32122 <https://github.com/numpy/numpy/pull/32122>`__: BUG: ensure lock is held when accessing or writing to RNG state...
* `#32123 <https://github.com/numpy/numpy/pull/32123>`__: BUG: fully reset cached RNG state for non-MT19937 RNGs (#32062)
* `#32135 <https://github.com/numpy/numpy/pull/32135>`__: TYP: type capabilities max dimensions
* `#32158 <https://github.com/numpy/numpy/pull/32158>`__: BUG: avoid possible stack overflow in arraydescr_dealloc (#32133)
* `#32206 <https://github.com/numpy/numpy/pull/32206>`__: MAINT: Update cibuildwheel to v4.2.0
* `#32214 <https://github.com/numpy/numpy/pull/32214>`__: MAINT: Skip limited_api tests on some platforms.
* `#32220 <https://github.com/numpy/numpy/pull/32220>`__: TYP: ``isclose`` shape-typing fix for 2d array-likes (#32205)
* `#32221 <https://github.com/numpy/numpy/pull/32221>`__: BUG: avoid segfaults when legacy copyswap slot is not defined...
.. currentmodule:: numpy
=========================
NumPy 2.5.2 Release Notes
=========================
The NumPy 2.5.2 is a patch release that fixes bugs discovered after the 2.5.1
release. The big news is that it includes wheels for the newly released
Python 3.15.0rc1.
This release supports Python versions 3.12-3.15
C API changes
=============
``PyArray_StringDTypeObject`` is opaque under the abi3t stable ABI
------------------------------------------------------------------
The ``PyArray_StringDTypeObject`` was accidentally exposed in NumPy
2.5 when targeting the free-threading-compatible stable ABI
(``Py_TARGET_ABI3T``). ``PyArray_StringDTypeObject`` is now an opaque
struct: extensions compiled that way cannot access its fields, since
the struct layout depends on the size of the object header. Any code
that accessed ``PyArray_StringDTypeObject`` fields in an abi3t build
would have crashed, so we are making this API change in a bugfix
release.
The ``NpyString`` allocator API remains usable by passing the
descriptor object pointer, e.g.
``NpyString_acquire_allocator((PyArray_StringDTypeObject *)descr)``.
(`gh-31771 <https://github.com/numpy/numpy/pull/31771>`__)
Contributors
============
A total of 16 people contributed to this release. People with a "+" by their
names contributed a patch for the first time.
* Abhijeetsingh Meena +
* Charalampos Stratakis
* Charles Harris
* Chris Ninham +
* David Woods
* Geonho +
* Gopu Yeshwanth Reddy +
* Iason Krommydas
* Ijtihed Kilani
* Jelle Zijlstra +
* Joren Hammudoglu
* Kumar Aditya
* Mike Boyle
* Nathan Goldbaum
* Raghuveer Devulapalli
* Sebastian Berg
Pull requests merged
====================
A total of 28 pull requests were merged for this release.
* `#31864 <https://github.com/numpy/numpy/pull/31864>`__: MAINT: Prepare 2.5.x for further development
* `#31889 <https://github.com/numpy/numpy/pull/31889>`__: TYP: Backport multiple static typing fixes 1.
* `#31900 <https://github.com/numpy/numpy/pull/31900>`__: TST: add tests for stable ABI numpy extensions (#31822)
* `#31901 <https://github.com/numpy/numpy/pull/31901>`__: BUG: fix ``StringDType`` coerce flag in binary ufunc promotion...
* `#31902 <https://github.com/numpy/numpy/pull/31902>`__: BLD: fix meson deprecation warnings (#31892)
* `#31921 <https://github.com/numpy/numpy/pull/31921>`__: TYP: Backport multiple typing fixes 2.
* `#31947 <https://github.com/numpy/numpy/pull/31947>`__: MAINT: Update x86-simd-sort subproject (5adb334 → fa944ef) (#31908)
* `#31949 <https://github.com/numpy/numpy/pull/31949>`__: BUG: fix crash on 32 bit systems using abi3t (#31771)
* `#31950 <https://github.com/numpy/numpy/pull/31950>`__: MNT: remove some obsolete string to bool workarounds (#31859)
* `#31952 <https://github.com/numpy/numpy/pull/31952>`__: BUG: centralized helper for output coerce and na_object in stringdtype...
* `#31953 <https://github.com/numpy/numpy/pull/31953>`__: BUG: fix CPU feature env diagnostic buffer overruns (#31905)
* `#31954 <https://github.com/numpy/numpy/pull/31954>`__: BUG: restore ndarray.conjugate() for legacy user-defined dtypes...
* `#31955 <https://github.com/numpy/numpy/pull/31955>`__: TYP: Avoid shadowed dtype annotations
* `#32077 <https://github.com/numpy/numpy/pull/32077>`__: MAINT: Update verdored-meson/meson to match main.
* `#32114 <https://github.com/numpy/numpy/pull/32114>`__: BUG: fix refcount leak on overlapping copyto with where=False
* `#32115 <https://github.com/numpy/numpy/pull/32115>`__: BUG: fix swallowed cast error in fancy indexing assignment (#31975)
* `#32116 <https://github.com/numpy/numpy/pull/32116>`__: BUG: Fix buffered iterator stride after removing multi-index
* `#32117 <https://github.com/numpy/numpy/pull/32117>`__: BUG: fix ``np.fromiter`` corruption when reusing a ``StringDType``...
* `#32119 <https://github.com/numpy/numpy/pull/32119>`__: BUG: add a special case for StringDType in np.isdtype (#32030)
* `#32121 <https://github.com/numpy/numpy/pull/32121>`__: BUG: reference leak in ``simd_sequence_from_iterable`` (#32038)
* `#32122 <https://github.com/numpy/numpy/pull/32122>`__: BUG: ensure lock is held when accessing or writing to RNG state...
* `#32123 <https://github.com/numpy/numpy/pull/32123>`__: BUG: fully reset cached RNG state for non-MT19937 RNGs (#32062)
* `#32135 <https://github.com/numpy/numpy/pull/32135>`__: TYP: type capabilities max dimensions
* `#32158 <https://github.com/numpy/numpy/pull/32158>`__: BUG: avoid possible stack overflow in arraydescr_dealloc (#32133)
* `#32206 <https://github.com/numpy/numpy/pull/32206>`__: MAINT: Update cibuildwheel to v4.2.0
* `#32214 <https://github.com/numpy/numpy/pull/32214>`__: MAINT: Skip limited_api tests on some platforms.
* `#32220 <https://github.com/numpy/numpy/pull/32220>`__: TYP: ``isclose`` shape-typing fix for 2d array-likes (#32205)
* `#32221 <https://github.com/numpy/numpy/pull/32221>`__: BUG: avoid segfaults when legacy copyswap slot is not defined...
name: 'Meson Build'
description: 'Build project using Meson and Ninja'
inputs:
compiler:
description: 'C++ compiler to use (e.g., g++-9, clang++-18, icpx)'
required: true
meson-options:
description: 'Additional Meson options'
required: false
default: '-Dbuild_tests=true --warnlevel 2 --werror --buildtype release'
cxxflags:
description: 'Additional CXXFLAGS'
required: false
default: ''
build-examples:
description: 'Whether to build examples first'
required: false
default: 'false'
pre-build-commands:
description: 'Commands to run before build (e.g., source environment)'
required: false
default: ''
runs:
using: 'composite'
steps:
- name: Build examples
if: inputs.build-examples == 'true'
shell: bash
env:
CXX: ${{ inputs.compiler }}
CXXFLAGS: ${{ inputs.cxxflags }}
run: |
${{ inputs.pre-build-commands }}
cd examples
make all
- name: Build with Meson
shell: bash
env:
CXX: ${{ inputs.compiler }}
CXXFLAGS: ${{ inputs.cxxflags }}
run: |
${{ inputs.pre-build-commands }}
make clean
meson setup ${{ inputs.meson-options }} builddir
cd builddir
ninja
name: 'Setup Build Environment'
description: 'Common setup for building and testing x86-simd-sort'
inputs:
compiler:
description: 'Compiler to install (e.g., g++-9, clang-18)'
required: true
extra-packages:
description: 'Additional apt packages to install'
required: false
default: ''
setup-sde:
description: 'Whether to setup Intel SDE'
required: false
default: 'true'
sde-version:
description: 'Intel SDE version to install'
required: false
default: '9.58.0'
runs:
using: 'composite'
steps:
- name: Install dependencies
shell: bash
run: |
sudo apt update
sudo apt -y install ${{ inputs.compiler }} libgtest-dev meson wget git ${{ inputs.extra-packages }}
- name: Setup Intel SDE
if: inputs.setup-sde == 'true'
uses: petarpetrovt/setup-sde@31aa4a8e85e109bef00f1d838613fcc6ec421271 # v5.0
with:
environmentVariableName: SDE_PATH
sdeVersion: ${{ inputs.sde-version }}
- name: Add Intel SDE to PATH
if: inputs.setup-sde == 'true'
shell: bash
run: echo "$SDE_PATH" >> "$GITHUB_PATH"
#ifndef X86SIMDSORT_CPUID_H
#define X86SIMDSORT_CPUID_H
#ifdef _MSC_VER
#include <intrin.h>
#include <string>
#include <unordered_map>
static std::unordered_map<std::string, bool> xss_cpu_features;
static bool os_supports_avx()
{
int cpuInfo[4];
__cpuid(cpuInfo, 1);
bool osxsaveSupported = (cpuInfo[2] & (1 << 27)) != 0; // OSXSAVE bit
bool avxSupported = (cpuInfo[2] & (1 << 28)) != 0; // AVX bit
if (!(avxSupported && osxsaveSupported)) return false;
// Check XCR0[2:1] (XMM and YMM state)
unsigned long long xcr0 = _xgetbv(0);
return (xcr0 & 0x6) == 0x6;
}
static bool os_supports_avx512()
{
if (!os_supports_avx()) return false;
// Need XCR0[7:5] = opmask/ZMM/YMM state enabled
unsigned long long xcr0 = _xgetbv(0);
return (xcr0 & 0xE0) == 0xE0;
}
static void xss_cpu_init()
{
int cpuInfo[4];
__cpuid(cpuInfo, 0);
int maxLeaf = cpuInfo[0];
bool hasAVX2 = false;
bool hasAVX512F = false, hasAVX512DQ = false, hasAVX512BW = false,
hasAVX512VL = false;
bool hasAVX512VBMI2 = false, hasAVX512FP16 = false;
if (maxLeaf >= 7) {
__cpuidex(cpuInfo, 7, 0);
// EBX bits
hasAVX2 = os_supports_avx() && (cpuInfo[1] & (1 << 5));
hasAVX512F = os_supports_avx512() && (cpuInfo[1] & (1 << 16));
hasAVX512DQ = os_supports_avx512() && (cpuInfo[1] & (1 << 17));
hasAVX512BW = os_supports_avx512() && (cpuInfo[1] & (1 << 30));
hasAVX512VL = os_supports_avx512() && (cpuInfo[1] & (1 << 31));
// ECX bits
hasAVX512VBMI2 = os_supports_avx512() && (cpuInfo[2] & (1 << 6));
// EDX bits
hasAVX512FP16 = os_supports_avx512() && (cpuInfo[3] & (1 << 23));
}
xss_cpu_features["avx2"] = hasAVX2;
xss_cpu_features["avx512f"] = hasAVX512F;
xss_cpu_features["avx512dq"] = hasAVX512DQ;
xss_cpu_features["avx512bw"] = hasAVX512BW;
xss_cpu_features["avx512vl"] = hasAVX512VL;
xss_cpu_features["avx512vbmi2"] = hasAVX512VBMI2;
xss_cpu_features["avx512fp16"] = hasAVX512FP16;
}
inline bool xss_cpu_supports(const char *feature)
{
auto it = xss_cpu_features.find(feature);
return it != xss_cpu_features.end() && it->second;
}
#else
#define xss_cpu_init() __builtin_cpu_init()
#define xss_cpu_supports(feature) __builtin_cpu_supports(feature)
#endif // _MSC_VER
#endif // X86SIMDSORT_CPUID_H
#cython: language_level=3
"""
Make sure cython can compile using the NumPy C-API in limited API mode
(see meson.build).
"""
cimport numpy as cnp
cnp.import_array()
def nonzero(cnp.ndarray arr):
"""Count non-zero elements of a float64 array using the NpyIter API."""
cdef cnp.NpyIter *it
cdef cnp.NpyIter_IterNextFunc iternext
cdef char **dataptr
cdef cnp.npy_intp *strideptr
cdef cnp.npy_intp *innersizeptr
cdef cnp.npy_intp nonzero_count = 0
cdef cnp.npy_intp stride, count
cdef char *data
if cnp.PyArray_SIZE(arr) == 0:
return 0
it = cnp.NpyIter_New(
arr,
cnp.NPY_ITER_READONLY | cnp.NPY_ITER_EXTERNAL_LOOP | cnp.NPY_ITER_REFS_OK,
cnp.NPY_KEEPORDER, cnp.NPY_NO_CASTING, <cnp.dtype>NULL)
if it == NULL:
raise RuntimeError("NpyIter_New failed")
iternext = cnp.NpyIter_GetIterNext(it, NULL)
if iternext == NULL:
cnp.NpyIter_Deallocate(it)
raise RuntimeError("NpyIter_GetIterNext failed")
dataptr = cnp.NpyIter_GetDataPtrArray(it)
strideptr = cnp.NpyIter_GetInnerStrideArray(it)
innersizeptr = cnp.NpyIter_GetInnerLoopSizePtr(it)
while True:
data = dataptr[0]
stride = strideptr[0]
count = innersizeptr[0]
while count > 0:
if (<double *>data)[0] != 0.0:
nonzero_count += 1
data += stride
count -= 1
if not iternext(it):
break
cnp.NpyIter_Deallocate(it)
return nonzero_count
def iter_next(cnp.ndarray arr):
"""Sum float64 array elements using PyArray_ITER_NEXT / _DATA / _NOTDONE."""
cdef cnp.flatiter it = cnp.PyArray_IterNew(arr)
cdef double total = 0.0
while cnp.PyArray_ITER_NOTDONE(it):
total += (<double *>cnp.PyArray_ITER_DATA(it))[0]
cnp.PyArray_ITER_NEXT(it)
return total
def iter_goto1d(cnp.ndarray arr, cnp.npy_intp index):
"""Get element at a flat index using PyArray_ITER_GOTO1D."""
cdef cnp.flatiter it = cnp.PyArray_IterNew(arr)
cnp.PyArray_ITER_GOTO1D(it, index)
return (<double *>cnp.PyArray_ITER_DATA(it))[0]
def iter_reset(cnp.ndarray arr):
"""Sum float64 array elements after PyArray_ITER_RESET."""
cdef cnp.flatiter it = cnp.PyArray_IterNew(arr)
cdef double total = 0.0
while cnp.PyArray_ITER_NOTDONE(it):
cnp.PyArray_ITER_NEXT(it)
cnp.PyArray_ITER_RESET(it)
while cnp.PyArray_ITER_NOTDONE(it):
total += (<double *>cnp.PyArray_ITER_DATA(it))[0]
cnp.PyArray_ITER_NEXT(it)
return total
def iter_goto(cnp.ndarray arr, tuple coord):
"""Get element at a coordinate using PyArray_ITER_GOTO."""
cdef int nd = cnp.PyArray_NDIM(arr)
cdef cnp.npy_intp destination[32]
cdef cnp.flatiter it
cdef int i
if len(coord) != nd:
raise ValueError("coordinate length mismatch")
for i in range(nd):
destination[i] = coord[i]
it = cnp.PyArray_IterNew(arr)
cnp.PyArray_ITER_GOTO(it, destination)
return (<double *>cnp.PyArray_ITER_DATA(it))[0]
def multi_iter_next(cnp.ndarray a, cnp.ndarray b):
"""Sum broadcast (a + b) using PyArray_MultiIter_NEXT / _DATA."""
cdef cnp.broadcast multi = cnp.PyArray_MultiIterNew2(a, b)
cdef double total = 0.0
while cnp.PyArray_MultiIter_NOTDONE(multi):
total += (<double *>cnp.PyArray_MultiIter_DATA(multi, 0))[0]
total += (<double *>cnp.PyArray_MultiIter_DATA(multi, 1))[0]
cnp.PyArray_MultiIter_NEXT(multi)
return total
def multi_iter_goto1d(cnp.ndarray a, cnp.ndarray b, cnp.npy_intp index):
"""Get (a, b) at a flat index using PyArray_MultiIter_GOTO1D."""
cdef cnp.broadcast multi = cnp.PyArray_MultiIterNew2(a, b)
cdef double va, vb
cnp.PyArray_MultiIter_GOTO1D(multi, index)
va = (<double *>cnp.PyArray_MultiIter_DATA(multi, 0))[0]
vb = (<double *>cnp.PyArray_MultiIter_DATA(multi, 1))[0]
return va, vb
def multi_iter_nexti(cnp.ndarray a, cnp.ndarray b, int steps):
"""Advance only iterator 0 by N steps using PyArray_MultiIter_NEXTi."""
cdef cnp.broadcast multi = cnp.PyArray_MultiIterNew2(a, b)
cdef int _i
for _i in range(steps):
cnp.PyArray_MultiIter_NEXTi(multi, 0)
return (<double *>cnp.PyArray_MultiIter_DATA(multi, 0))[0]
def get_datetime_value(obj):
"""Underlying int64 of a datetime64 scalar via get_datetime64_value."""
return cnp.get_datetime64_value(obj)
def get_timedelta_value(obj):
"""Underlying int64 of a timedelta64 scalar via get_timedelta64_value."""
return cnp.get_timedelta64_value(obj)
def get_datetime_unit(obj):
"""Unit (NPY_DATETIMEUNIT base) of a datetime64 scalar."""
return <int>cnp.get_datetime64_unit(obj)
def is_datetime64(obj):
"""Cython is_datetime64_object (isinstance-like check)."""
return cnp.is_datetime64_object(obj)
def is_timedelta64(obj):
"""Cython is_timedelta64_object (isinstance-like check)."""
return cnp.is_timedelta64_object(obj)
#ifndef MODULE_NAME
#error "MODULE_NAME must be defined before including this file"
#endif
#ifndef Py_LIMITED_API
#error "Py_LIMITED_API must be defined before including this file"
#endif
#include <Python.h>
#include <numpy/ndarraytypes.h>
#include <numpy/arrayobject.h>
#include <numpy/ufuncobject.h>
#include <stddef.h>
#include <string.h>
#define MODULE_NAME_STR NPY_TOSTRING(MODULE_NAME)
#ifdef Py_TARGET_ABI3T
#define ENTRY_FUNC_NAME NPY_CAT(PyModExport_, MODULE_NAME)
#else
#define ENTRY_FUNC_NAME NPY_CAT(PyInit_, MODULE_NAME)
#endif
static PyObject *limited_api_nonzero(PyObject *mod, PyArrayObject *self)
{
PyArray_NonzeroFunc* nonzero = PyDataType_GetArrFuncs(PyArray_DESCR(self))->nonzero;
NpyIter* iter;
NpyIter_IterNextFunc *iternext;
char** dataptr;
npy_intp nonzero_count;
npy_intp* strideptr,* innersizeptr;
/* Handle zero-sized arrays specially */
if (PyArray_SIZE(self) == 0) {
return PyLong_FromLong(0);
}
/*
* Create and use an iterator to count the nonzeros.
* flag NPY_ITER_READONLY
* - The array is never written to.
* flag NPY_ITER_EXTERNAL_LOOP
* - Inner loop is done outside the iterator for efficiency.
* flag NPY_ITER_NPY_ITER_REFS_OK
* - Reference types are acceptable.
* order NPY_KEEPORDER
* - Visit elements in memory order, regardless of strides.
* This is good for performance when the specific order
* elements are visited is unimportant.
* casting NPY_NO_CASTING
* - No casting is required for this operation.
*/
iter = NpyIter_New(self, NPY_ITER_READONLY|
NPY_ITER_EXTERNAL_LOOP|
NPY_ITER_REFS_OK,
NPY_KEEPORDER, NPY_NO_CASTING,
NULL);
if (iter == NULL) {
return NULL;
}
/*
* The iternext function gets stored in a local variable
* so it can be called repeatedly in an efficient manner.
*/
iternext = NpyIter_GetIterNext(iter, NULL);
if (iternext == NULL) {
NpyIter_Deallocate(iter);
return NULL;
}
/* The location of the data pointer which the iterator may update */
dataptr = NpyIter_GetDataPtrArray(iter);
/* The location of the stride which the iterator may update */
strideptr = NpyIter_GetInnerStrideArray(iter);
/* The location of the inner loop size which the iterator may update */
innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
nonzero_count = 0;
do {
/* Get the inner loop data/stride/count values */
char* data = *dataptr;
npy_intp stride = *strideptr;
npy_intp count = *innersizeptr;
/* This is a typical inner loop for NPY_ITER_EXTERNAL_LOOP */
while (count--) {
if (nonzero(data, self)) {
++nonzero_count;
}
data += stride;
}
/* Increment the iterator to the next inner loop */
} while(iternext(iter));
NpyIter_Deallocate(iter);
return PyLong_FromLong(nonzero_count);
}
/*
* Test PyArray_ITER_NEXT, PyArray_ITER_RESET, PyArray_ITER_DATA,
* and PyArray_ITER_NOTDONE by summing all elements using the
* legacy iterator macros.
*/
static PyObject *
limited_api_iter_next(PyObject *mod, PyArrayObject *self)
{
PyObject *iter_obj = PyArray_IterNew((PyObject *)self);
if (iter_obj == NULL) {
return NULL;
}
double sum = 0.0;
while (PyArray_ITER_NOTDONE(iter_obj)) {
sum += *(double *)PyArray_ITER_DATA(iter_obj);
PyArray_ITER_NEXT(iter_obj);
}
Py_DECREF(iter_obj);
return PyFloat_FromDouble(sum);
}
/*
* Test PyArray_ITER_GOTO1D by accessing a specific flat index.
*/
static PyObject *
limited_api_iter_goto1d(PyObject *mod, PyObject *args)
{
PyArrayObject *arr;
npy_intp index;
if (!PyArg_ParseTuple(args, "O!n", &PyArray_Type, &arr, &index)) {
return NULL;
}
PyObject *iter_obj = PyArray_IterNew((PyObject *)arr);
if (iter_obj == NULL) {
return NULL;
}
PyArray_ITER_GOTO1D(iter_obj, index);
double val = *(double *)PyArray_ITER_DATA(iter_obj);
Py_DECREF(iter_obj);
return PyFloat_FromDouble(val);
}
/*
* Test PyArray_ITER_RESET by iterating, resetting, and iterating again.
* Returns the sum from the second pass (should equal the first).
*/
static PyObject *
limited_api_iter_reset(PyObject *mod, PyArrayObject *self)
{
PyObject *iter_obj = PyArray_IterNew((PyObject *)self);
if (iter_obj == NULL) {
return NULL;
}
/* First pass: skip through */
while (PyArray_ITER_NOTDONE(iter_obj)) {
PyArray_ITER_NEXT(iter_obj);
}
/* Reset and sum */
PyArray_ITER_RESET(iter_obj);
double sum = 0.0;
while (PyArray_ITER_NOTDONE(iter_obj)) {
sum += *(double *)PyArray_ITER_DATA(iter_obj);
PyArray_ITER_NEXT(iter_obj);
}
Py_DECREF(iter_obj);
return PyFloat_FromDouble(sum);
}
/*
* Test PyArray_MultiIter_NEXT, PyArray_MultiIter_RESET,
* PyArray_MultiIter_DATA, and PyArray_MultiIter_NOTDONE
* by computing the element-wise sum of two broadcastable arrays.
* Returns the total sum of (a + b) for all broadcast elements.
*/
static PyObject *
limited_api_multi_iter_next(PyObject *mod, PyObject *args)
{
PyArrayObject *a, *b;
if (!PyArg_ParseTuple(args, "O!O!", &PyArray_Type, &a,
&PyArray_Type, &b)) {
return NULL;
}
PyObject *multi = PyArray_MultiIterNew(2, a, b);
if (multi == NULL) {
return NULL;
}
double sum = 0.0;
while (PyArray_MultiIter_NOTDONE(multi)) {
double va = *(double *)PyArray_MultiIter_DATA(multi, 0);
double vb = *(double *)PyArray_MultiIter_DATA(multi, 1);
sum += va + vb;
PyArray_MultiIter_NEXT(multi);
}
/* Test reset: iterate again and verify same sum */
PyArray_MultiIter_RESET(multi);
double sum2 = 0.0;
while (PyArray_MultiIter_NOTDONE(multi)) {
double va = *(double *)PyArray_MultiIter_DATA(multi, 0);
double vb = *(double *)PyArray_MultiIter_DATA(multi, 1);
sum2 += va + vb;
PyArray_MultiIter_NEXT(multi);
}
Py_DECREF(multi);
if (sum != sum2) {
PyErr_SetString(PyExc_RuntimeError,
"MultiIter reset produced different sum");
return NULL;
}
return PyFloat_FromDouble(sum);
}
/*
* Test PyArray_ITER_GOTO by jumping to a coordinate and reading the value.
*/
static PyObject *
limited_api_iter_goto(PyObject *mod, PyObject *args)
{
PyArrayObject *arr;
PyObject *coord_tuple;
if (!PyArg_ParseTuple(args, "O!O!", &PyArray_Type, &arr,
&PyTuple_Type, &coord_tuple)) {
return NULL;
}
int nd = PyArray_NDIM(arr);
if (PyTuple_Size(coord_tuple) != nd) {
PyErr_SetString(PyExc_ValueError, "coordinate length mismatch");
return NULL;
}
npy_intp destination[NPY_MAXDIMS_LEGACY_ITERS];
for (int i = 0; i < nd; i++) {
destination[i] = PyLong_AsLong(PyTuple_GetItem(coord_tuple, i));
if (destination[i] == -1 && PyErr_Occurred()) {
return NULL;
}
}
PyObject *iter_obj = PyArray_IterNew((PyObject *)arr);
if (iter_obj == NULL) {
return NULL;
}
PyArray_ITER_GOTO(iter_obj, destination);
double val = *(double *)PyArray_ITER_DATA(iter_obj);
Py_DECREF(iter_obj);
return PyFloat_FromDouble(val);
}
/*
* Test PyArray_MultiIter_GOTO by jumping to a coordinate
* and returning (a_val, b_val) at that position.
*/
static PyObject *
limited_api_multi_iter_goto(PyObject *mod, PyObject *args)
{
PyArrayObject *a, *b;
PyObject *coord_tuple;
if (!PyArg_ParseTuple(args, "O!O!O!", &PyArray_Type, &a,
&PyArray_Type, &b,
&PyTuple_Type, &coord_tuple)) {
return NULL;
}
PyObject *multi = PyArray_MultiIterNew(2, a, b);
if (multi == NULL) {
return NULL;
}
int nd = _PyMIT(multi)->nd;
if (PyTuple_Size(coord_tuple) != nd) {
Py_DECREF(multi);
PyErr_SetString(PyExc_ValueError, "coordinate length mismatch");
return NULL;
}
npy_intp destination[NPY_MAXDIMS_LEGACY_ITERS];
for (int i = 0; i < nd; i++) {
destination[i] = PyLong_AsLong(PyTuple_GetItem(coord_tuple, i));
if (destination[i] == -1 && PyErr_Occurred()) {
Py_DECREF(multi);
return NULL;
}
}
PyArray_MultiIter_GOTO(multi, destination);
double va = *(double *)PyArray_MultiIter_DATA(multi, 0);
double vb = *(double *)PyArray_MultiIter_DATA(multi, 1);
Py_DECREF(multi);
return Py_BuildValue("dd", va, vb);
}
/*
* Test PyArray_MultiIter_GOTO1D by jumping to a flat index
* and returning (a_val, b_val) at that position.
*/
static PyObject *
limited_api_multi_iter_goto1d(PyObject *mod, PyObject *args)
{
PyArrayObject *a, *b;
npy_intp index;
if (!PyArg_ParseTuple(args, "O!O!n", &PyArray_Type, &a,
&PyArray_Type, &b, &index)) {
return NULL;
}
PyObject *multi = PyArray_MultiIterNew(2, a, b);
if (multi == NULL) {
return NULL;
}
PyArray_MultiIter_GOTO1D(multi, index);
double va = *(double *)PyArray_MultiIter_DATA(multi, 0);
double vb = *(double *)PyArray_MultiIter_DATA(multi, 1);
Py_DECREF(multi);
return Py_BuildValue("dd", va, vb);
}
/*
* Test PyArray_MultiIter_NEXTi by advancing only the first iterator
* and returning its data pointer value after N steps.
*/
static PyObject *
limited_api_multi_iter_nexti(PyObject *mod, PyObject *args)
{
PyArrayObject *a, *b;
int steps;
if (!PyArg_ParseTuple(args, "O!O!i", &PyArray_Type, &a,
&PyArray_Type, &b, &steps)) {
return NULL;
}
PyObject *multi = PyArray_MultiIterNew(2, a, b);
if (multi == NULL) {
return NULL;
}
for (int i = 0; i < steps; i++) {
PyArray_MultiIter_NEXTi(multi, 0);
}
double val = *(double *)PyArray_MultiIter_DATA(multi, 0);
Py_DECREF(multi);
return PyFloat_FromDouble(val);
}
static PyObject *
limited_api_datetime_metadata(PyObject *mod, PyArrayObject *arr)
{
PyArray_Descr *descr = PyArray_DESCR(arr);
npy_uint64 flags = PyDataType_FLAGS(descr);
PyArray_DatetimeDTypeMetaData *dt_meta =
(PyArray_DatetimeDTypeMetaData *)PyDataType_C_METADATA(descr);
if (dt_meta == NULL) {
PyErr_SetString(
PyExc_RuntimeError,
"PyDataType_C_METADATA() returned NULL for a datetime descriptor");
return NULL;
}
return Py_BuildValue("Kii", (unsigned long long)flags,
(int)dt_meta->meta.base, dt_meta->meta.num);
}
/*
* Test the NpyString allocator API. Under the abi3t stable ABI
* PyArray_StringDTypeObject is an opaque struct; the descriptor object
* pointer is passed to NpyString_acquire_allocator without accessing any
* struct fields. The API only exists when targeting NumPy 2.0+.
*/
#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION
static PyObject *
limited_api_stringdtype_load(PyObject *mod, PyArrayObject *arr)
{
npy_string_allocator *allocator = NpyString_acquire_allocator(
(PyArray_StringDTypeObject *)PyArray_DESCR(arr));
npy_packed_static_string *packed =
(npy_packed_static_string *)PyArray_DATA(arr);
npy_static_string s = {0, NULL};
PyObject *res = NULL;
int is_null = NpyString_load(allocator, packed, &s);
if (is_null == -1) {
PyErr_SetString(PyExc_RuntimeError, "NpyString_load failed");
}
else if (is_null) {
res = Py_None;
Py_INCREF(res);
}
else {
res = PyUnicode_FromStringAndSize(s.buf, (Py_ssize_t)s.size);
}
NpyString_release_allocator(allocator);
return res;
}
#endif /* NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION */
static PyMethodDef limited_api_methods[] = {
{"nonzero", (PyCFunction)limited_api_nonzero, METH_O,
"Count the number of non-zero elements in the array."},
{"iter_next", (PyCFunction)limited_api_iter_next, METH_O,
"Sum array elements using PyArray_ITER_NEXT."},
{"iter_goto1d", (PyCFunction)limited_api_iter_goto1d, METH_VARARGS,
"Get element at flat index using PyArray_ITER_GOTO1D."},
{"iter_reset", (PyCFunction)limited_api_iter_reset, METH_O,
"Sum array elements after reset using PyArray_ITER_RESET."},
{"multi_iter_next", (PyCFunction)limited_api_multi_iter_next,
METH_VARARGS,
"Sum broadcast (a+b) using PyArray_MultiIter_NEXT."},
{"iter_goto", (PyCFunction)limited_api_iter_goto, METH_VARARGS,
"Get element at coordinate using PyArray_ITER_GOTO."},
{"multi_iter_goto", (PyCFunction)limited_api_multi_iter_goto,
METH_VARARGS,
"Get (a, b) at coordinate using PyArray_MultiIter_GOTO."},
{"multi_iter_goto1d", (PyCFunction)limited_api_multi_iter_goto1d,
METH_VARARGS,
"Get (a, b) at flat index using PyArray_MultiIter_GOTO1D."},
{"multi_iter_nexti", (PyCFunction)limited_api_multi_iter_nexti,
METH_VARARGS,
"Advance only iter 0 N steps using PyArray_MultiIter_NEXTi."},
{"datetime_metadata", (PyCFunction)limited_api_datetime_metadata,
METH_O,
"Get (flags, unit base, unit num) from a datetime64/timedelta64 array."},
#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION
{"stringdtype_load", (PyCFunction)limited_api_stringdtype_load, METH_O,
"Load the first element of a StringDType array via the NpyString API."},
#endif
{NULL, NULL, 0, NULL} /* Sentinel */
};
#ifdef Py_TARGET_ABI3T
PyABIInfo_VAR(abi_info);
static PySlot limited_api_slots[] = {
PySlot_STATIC_DATA(Py_mod_abi, &abi_info),
PySlot_STATIC_DATA(Py_mod_name, MODULE_NAME_STR),
PySlot_STATIC_DATA(Py_mod_methods, limited_api_methods),
PySlot_STATIC_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED),
PySlot_END,
};
PyMODEXPORT_FUNC
ENTRY_FUNC_NAME(void)
{
import_array();
import_umath();
return limited_api_slots;
}
#else
static PyModuleDef moduledef = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = MODULE_NAME_STR,
.m_size = -1,
.m_methods = limited_api_methods,
};
PyMODINIT_FUNC
ENTRY_FUNC_NAME(void)
{
import_array();
import_umath();
return PyModule_Create(&moduledef);
}
#endif
+1
-1

@@ -38,3 +38,3 @@ name: Test Emscripten/Pyodide build

persist-credentials: false
- uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0
- uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
env:

@@ -41,0 +41,0 @@ CIBW_PLATFORM: pyodide

@@ -105,5 +105,5 @@ # Workflow to build and test wheels, similarly to numpy/numpy-release.

- name: Build wheels
uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0
env:
CIBW_BUILD: ${{ matrix.python }}-${{ matrix.buildplat[1] }}
uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
with:
only: ${{ matrix.python }}-${{ matrix.buildplat[1] }}

@@ -110,0 +110,0 @@ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1

@@ -103,2 +103,5 @@ # Prevent git from showing duplicate names with commands like "git shortlog"

Abdul Muneer <abdulmuneer@gmail.com>
Abhijeetsingh Meena <abhijeet040403@gmail.com>
Abhijeetsingh Meena <abhijeet040403@gmail.com> <amethhac@gmail.com>
Abhijeetsingh Meena <abhijeet040403@gmail.com> NumPy Bugfix <bugfix@localhost>
Abhilash Barigidad <abhilash.ub@gmail.com>

@@ -341,2 +344,3 @@ Abhilash Barigidad <abhilash.ub@gmail.com> <64172584+abhilash42@users.noreply.github.com>

Gavin Zhang <zhanggan@cn.ibm.com>
Geonho <mnb0968@naver.com>
Gerrit Holl <gerrit.holl@gmail.com> <gerrit.holl@utoronto.ca>

@@ -347,2 +351,4 @@ Gerrit Holl <gerrit.holl@gmail.com> <g.holl@reading.ac.uk>

Gopal Singh Meena <gopalmeena94@gmail.com>
Gopu Yeshwanth Reddy <Gopu.Reddy@deshaw.com>
Gopu Yeshwanth Reddy <Gopu.Reddy@deshaw.com> <87797634+Yeshwanth-G@users.noreply.github.com>
Greg Knoll <gregory@bccn-berlin.de>

@@ -349,0 +355,0 @@ Greg Yang <sorcererofdm@gmail.com>

@@ -8,2 +8,3 @@ *************

2.5.2 <release/2.5.2-notes>
2.5.1 <release/2.5.1-notes>

@@ -10,0 +11,0 @@ 2.5.0 <release/2.5.0-notes>

@@ -71,3 +71,3 @@ # This file is generated by numpy's build process

},
"cross-compiled": bool("@CROSS_COMPILED@".lower().replace("false", "")),
"cross-compiled": @CROSS_COMPILED@,
},

@@ -77,3 +77,3 @@ "Build Dependencies": {

"name": "@BLAS_NAME@",
"found": bool("@BLAS_FOUND@".lower().replace("false", "")),
"found": @BLAS_FOUND@,
"version": "@BLAS_VERSION@",

@@ -88,3 +88,3 @@ "detection method": "@BLAS_TYPE_NAME@",

"name": "@LAPACK_NAME@",
"found": bool("@LAPACK_FOUND@".lower().replace("false", "")),
"found": @LAPACK_FOUND@,
"version": "@LAPACK_VERSION@",

@@ -91,0 +91,0 @@ "detection method": "@LAPACK_TYPE_NAME@",

@@ -76,2 +76,5 @@ """

- **"max dimensions"**: integer indicating maximum number of supported
dimensions. Always ``64`` for NumPy.
See

@@ -78,0 +81,0 @@ https://data-apis.org/array-api/latest/API_specification/generated/array_api.info.capabilities.html

@@ -13,2 +13,3 @@ from typing import Literal, Never, TypedDict, final, overload, type_check_only

"data-dependent shapes": Literal[True],
"max dimensions": Literal[64],
},

@@ -15,0 +16,0 @@ )

@@ -6,2 +6,3 @@ #ifndef NUMPY_CORE_INCLUDE_NUMPY_UFUNCOBJECT_H_

#include <numpy/npy_common.h>
#include <numpy/utils.h>

@@ -112,3 +113,4 @@ #ifdef __cplusplus

*/
int nin, nout, nargs;
_NPY_OPAQUE_FIRST_FIELD int nin;
int nout, nargs;

@@ -115,0 +117,0 @@ /*

@@ -16,2 +16,6 @@ #ifndef NUMPY_CORE_INCLUDE_NUMPY_UTILS_H_

// Older compilers may name `_Alignas` differently; to allow compilation on such
// unsupported platforms, we don't redefine NPY_DECL_ALIGNED if it's already
// defined similar to CPython's _Py_ALIGNED_DEF.
#ifndef NPY_DECL_ALIGNED
#if defined(__GNUC__) || defined(__ICC) || defined(__clang__)

@@ -21,5 +25,63 @@ #define NPY_DECL_ALIGNED(x) __attribute__ ((aligned (x)))

#define NPY_DECL_ALIGNED(x) __declspec(align(x))
#elif defined(__cplusplus)
#define NPY_DECL_ALIGNED(x) alignas(x)
#else
#define NPY_DECL_ALIGNED(x)
#define NPY_DECL_ALIGNED(x) _Alignas(x)
#endif
#endif
/*
* Force the first field of a struct to be 8-byte aligned on Python 3.15+ to achieve
* ABI compatibility of `_fields` structs with and without `PyObject_HEAD`.
*
* When we expose (or may want to) expose an object structs contents to the Python
* stable ABI we define it as:
*
* struct _PyArray_Descr {
* #if !defined(Py_TARGET_ABI3T)
* PyObject_HEAD;
* #endif
* _NPY_OPAQUE_FIRST_FIELD T first_field;
* ...
* } fields;
*
* Effectively creating two versions, one `fields_obj` and `fields_no_obj` depending
* on the build target. These builds must be ABI compatible and for that:
*
* offsetof(fields_obj, first_field) % alignof(fields_no_obj) == 0
*
* must hold. If it does not hold, then `fields_obj` (minus the PyObject_HEAD) will
* be padded differently from `fields_no_obj`. Concretely, this happened for 32bit
* free-threaded builds (PyObject_HEAD having a size of 20 bytes) where the uint64
* flags required padding to achieve 8 byte alignment, but with the `PyObject_HEAD`.
*
* The only way to ensure the above holds always is to pad the first field so that
* it's offset is a multiple of the alignment. The simplest path (e.g. what PEP 697
* also does) may be to always pad to `alignof(max_align_t)`.
* We do _not_ do this, because we have to remain ABI compatible to abi3 builds and
* Python GIL enabled builds have a PyObject_HEAD size that is a multiple of
* 8/16 (32bit/64bit systems) alignment.
* So, instead we use 8 bytes. We could choose 16 for 64bit systems but it currently
* makes no difference (there is a static_assert to notify us if it might matter).
*
* Because the ABI3T target was added in Python 3.15 we _can_ freely add padding for
* Python 3.15+ builds for them. And this is where it matters, because 32bit
* free-threaded builds have a `sizeof(PyObject) == 20` which gives 4 byte max
* alignment which breaks the requirement above.
*
* As an actual implementation we add the padding by explicitly aligning the first
* field to 8 bytes.
* In theory this might guarantee an alignment larger than `alignof(max_align_t)`,
* we assume that we would notice this first and in practice malloc alignment is >=8.
*
* One may be able to expose >8 byte aligned fields, but this requires thoughts on
* how it affects abi3, abi3t builds and compatibility with older Python/NumPy builds.
*
* Note that assertions concerning these assumptions are paired with the
* `*_GET_ITEM_DATA` function definitions (in `arrayobject.c`).
*/
#if PY_VERSION_HEX >= 0x030f0000
#define _NPY_OPAQUE_FIRST_FIELD NPY_DECL_ALIGNED(8)
#else
#define _NPY_OPAQUE_FIRST_FIELD
#endif

@@ -26,0 +88,0 @@ /* Use this to tag a variable as not used. It will remove unused variable

@@ -810,3 +810,6 @@ import datetime as dt

#
def bincount(x: _ArrayLikeInt_co, /, weights: ArrayLike | None = None, minlength: SupportsIndex = 0) -> _Array1D[intp]: ...
@overload
def bincount(x: _ArrayLikeInt_co, /, weights: None = None, minlength: SupportsIndex = 0) -> _Array1D[intp]: ...
@overload
def bincount(x: _ArrayLikeInt_co, /, weights: _ArrayLikeFloat_co, minlength: SupportsIndex = 0) -> _Array1D[np.float64]: ...

@@ -1793,3 +1796,3 @@ #

flags: Sequence[_NDIterFlagsKind] | None = None,
op_flags: Sequence[_NDIterFlagsOp] | None = None,
op_flags: Sequence[_NDIterFlagsOp] | Sequence[Sequence[_NDIterFlagsOp]] | None = None,
op_dtypes: DTypeLike | None = None,

@@ -1796,0 +1799,0 @@ order: _OrderKACF = "K",

@@ -1283,4 +1283,4 @@ from _typeshed import Incomplete

b: _NumberLike_co,
rtol: ArrayLike = 1e-5,
atol: ArrayLike = 1e-8,
rtol: _ScalarLike_co = 1e-5,
atol: _ScalarLike_co = 1e-8,
equal_nan: py_bool = False,

@@ -1292,4 +1292,4 @@ ) -> np.bool: ...

b: np.ndarray[ShapeT] | _NumberLike_co,
rtol: ArrayLike = 1e-5,
atol: ArrayLike = 1e-8,
rtol: _ScalarLike_co = 1e-5,
atol: _ScalarLike_co = 1e-8,
equal_nan: py_bool = False,

@@ -1301,4 +1301,4 @@ ) -> np.ndarray[ShapeT, np.dtype[np.bool]]: ...

b: np.ndarray[ShapeT],
rtol: ArrayLike = 1e-5,
atol: ArrayLike = 1e-8,
rtol: _ScalarLike_co = 1e-5,
atol: _ScalarLike_co = 1e-8,
equal_nan: py_bool = False,

@@ -1310,4 +1310,4 @@ ) -> np.ndarray[ShapeT, np.dtype[np.bool]]: ...

b: Sequence[_NumberLike_co] | _NumberLike_co | np.ndarray[tuple[int]],
rtol: ArrayLike = 1e-5,
atol: ArrayLike = 1e-8,
rtol: _ScalarLike_co = 1e-5,
atol: _ScalarLike_co = 1e-8,
equal_nan: py_bool = False,

@@ -1319,4 +1319,4 @@ ) -> np.ndarray[tuple[int], np.dtype[np.bool]]: ...

b: Sequence[_NumberLike_co],
rtol: ArrayLike = 1e-5,
atol: ArrayLike = 1e-8,
rtol: _ScalarLike_co = 1e-5,
atol: _ScalarLike_co = 1e-8,
equal_nan: py_bool = False,

@@ -1328,6 +1328,6 @@ ) -> np.ndarray[tuple[int], np.dtype[np.bool]]: ...

b: Sequence[Sequence[_NumberLike_co]] | Sequence[_NumberLike_co] | _NumberLike_co | np.ndarray[tuple[int] | tuple[int, int]],
rtol: ArrayLike = 1e-5,
atol: ArrayLike = 1e-8,
rtol: _ScalarLike_co = 1e-5,
atol: _ScalarLike_co = 1e-8,
equal_nan: py_bool = False,
) -> np.ndarray[tuple[int], np.dtype[np.bool]]: ...
) -> np.ndarray[tuple[int, int], np.dtype[np.bool]]: ...
@overload # <=2d array-like, 2d sequence

@@ -1337,6 +1337,6 @@ def isclose(

a: Sequence[Sequence[_NumberLike_co]],
rtol: ArrayLike = 1e-5,
atol: ArrayLike = 1e-8,
rtol: _ScalarLike_co = 1e-5,
atol: _ScalarLike_co = 1e-8,
equal_nan: py_bool = False,
) -> np.ndarray[tuple[int], np.dtype[np.bool]]: ...
) -> np.ndarray[tuple[int, int], np.dtype[np.bool]]: ...
@overload # unknown shape, unknown shape

@@ -1343,0 +1343,0 @@ def isclose(

@@ -84,2 +84,3 @@ """

from . import multiarray as ma
from ._multiarray_umath import StringDType
from .multiarray import (

@@ -315,5 +316,11 @@ busday_count,

"""
orig_dtype = dtype
if isinstance(dtype, ma.dtype):
dtype = dtype.type
if isinstance(dtype, ndarray) or dtype not in allTypes.values():
if isinstance(orig_dtype, StringDType) or orig_dtype is StringDType:
# StringDType's scalar type is the builtin `str`, which is not
# a NumPy scalar type, so use the DType class to identify it.
# if we ever fix gh-28165, this should be deleted
return StringDType
raise _PreprocessDTypeError

@@ -320,0 +327,0 @@ return dtype

@@ -1,2 +0,1 @@

# pyright: reportSelfClsParameterName=false
from _typeshed import Incomplete, StrOrBytesPath

@@ -148,4 +147,3 @@ from collections.abc import Buffer, Iterable, Sequence

shape: _ShapeLike | None = None,
*,
formats: DTypeLike | None,
formats: DTypeLike | None = None,
names: str | Sequence[str] | None = None,

@@ -157,2 +155,3 @@ titles: str | Sequence[str] | None = None,

# exported in `numpy.rec`
@overload

@@ -174,4 +173,3 @@ def fromrecords(

shape: _ShapeLike | None = None,
*,
formats: DTypeLike | None,
formats: DTypeLike | None = None,
names: str | Sequence[str] | None = None,

@@ -178,0 +176,0 @@ titles: str | Sequence[str] | None = None,

@@ -108,2 +108,3 @@ /**

if (dst == NULL) {
Py_DECREF(seq_obj);
return NULL;

@@ -110,0 +111,0 @@ }

@@ -277,4 +277,4 @@ #include "npy_cpu_features.h"

PyErr_Format(PyExc_RuntimeError,
"Length of environment variable '%s' is %d, only %d accepted",
env_name, var_len, NPY__MAX_VAR_LEN
"Length of environment variable '%s' is %zu, only %zu accepted",
env_name, var_len, (size_t)NPY__MAX_VAR_LEN
);

@@ -289,3 +289,3 @@ return -1;

char notsupp[sizeof(NPY_WITH_CPU_DISPATCH) + 1];
char notsupp[NPY__MAX_VAR_LEN];
char *notsupp_cur = &notsupp[0];

@@ -313,4 +313,7 @@

int flen = strlen(feature);
if (nexist_cur != nexist) {
*nexist_cur++ = ' ';
}
memcpy(nexist_cur, feature, flen);
nexist_cur[flen] = ' '; nexist_cur += flen + 1;
nexist_cur += flen;
goto next;

@@ -321,4 +324,7 @@ }

int flen = strlen(feature);
if (notsupp_cur != notsupp) {
*notsupp_cur++ = ' ';
}
memcpy(notsupp_cur, feature, flen);
notsupp_cur[flen] = ' '; notsupp_cur += flen + 1;
notsupp_cur += flen;
goto next;

@@ -342,3 +348,2 @@ }

if (nexist[0] != '\0') {
*(nexist_cur-1) = '\0'; // trim the last space
if (PyErr_WarnFormat(PyExc_ImportWarning, 1,

@@ -362,3 +367,2 @@ "%sYou cannot %s CPU features (%s), since "

if (notsupp[0] != '\0') {
*(notsupp_cur-1) = '\0'; // trim the last space
if (!disable){

@@ -365,0 +369,0 @@ PyErr_Format(PyExc_RuntimeError, NOTSUPP_BODY);

@@ -444,2 +444,5 @@ /*

/* where=False copies nothing */
if (copied_src) {
Py_DECREF(src);
}
return 0;

@@ -446,0 +449,0 @@ }

@@ -468,4 +468,4 @@ /*

}
memset((char *)(res->method) + sizeof(PyObject), 0,
sizeof(PyArrayMethodObject) - sizeof(PyObject));
memset((char *)(res->method) + offsetof(PyArrayMethodObject, name), 0,
sizeof(PyArrayMethodObject) - offsetof(PyArrayMethodObject, name));

@@ -472,0 +472,0 @@ res->method->nin = spec->nin;

@@ -1280,8 +1280,37 @@ /*

/*
The following *_GET_ITEM_DATA functions are used to get the pointer to the fields of the
corresponding struct from the given object. It is technically undefined behaviour
to access the fields of the struct through a pointer that is not of the same type,
but in our case it is not a problem in practice because this is used only in stable ABI
extensions where the original object layout is opaque.
*/
* Python stable ABI compatible object field accessor functions.
*
* The following *_GET_ITEM_DATA functions are used to get the pointer to the fields of the
* corresponding struct from the given object. It is technically undefined behaviour
* to access the fields of the struct through a pointer that is not of the same type,
* but in our case it is not a problem in practice because this is used only in stable ABI
* extensions where the original object layout is opaque.
*
* To expose the struct this way alignment guarantees must be met, see `utils.h` and the
* definition of `_NPY_OPAQUE_FIRST_FIELD`.
*/
#if SIZEOF_VOID_P != 4 // not a 32bit build
/*
* If this assert fails then Python changed the sizeof(PyObject). If we simply remove the
* assert we lose flexibility to add 16byte aligned fields to the stable ABI fields.
* We can choose that this is fine or increase the padding to 16/max_align_t when it happens.
* (See comments in `ndarraytypes.h` for more details.)
*/
static_assert(sizeof(PyObject) % 16 == 0,
"Expected sizeof(PyObject) to be multiple of 16 on 64bit builds.");
#endif
static_assert(NPY_ALIGNOF(PyArray_Descr_fields) <= 8,
"PyArray_Descr must not require more than 8-byte alignment");
static_assert(NPY_ALIGNOF(_PyArray_LegacyDescr_fields) <= 8,
"_PyArray_LegacyDescr must not require more than 8-byte alignment");
static_assert(NPY_ALIGNOF(PyArrayObject_fields) <= 8,
"PyArrayObject must not require more than 8-byte alignment");
static_assert(NPY_ALIGNOF(PyArrayMultiIterObject_fields) <= 8,
"PyArrayMultiIterObject must not require more than 8-byte alignment");
static_assert(NPY_ALIGNOF(PyArrayIterObject_fields) <= 8,
"PyArrayIterObject must not require more than 8-byte alignment");
static_assert(NPY_ALIGNOF(PyArrayNeighborhoodIterObject_fields) <= 8,
"PyArrayNeighborhoodIterObject must not require more than 8-byte alignment");
#undef _PyDataType_GET_ITEM_DATA

@@ -1292,3 +1321,3 @@ /*NUMPY_API*/

{
return (PyArray_Descr_fields *)(((char *)dtype) + sizeof(PyObject));
return (PyArray_Descr_fields *)(((char *)dtype) + offsetof(PyArray_Descr, typeobj));
}

@@ -1300,3 +1329,3 @@ #undef _PyArray_LegacyDescr_GET_ITEM_DATA

{
return (_PyArray_LegacyDescr_fields *)(((char *)dtype) + sizeof(PyObject));
return (_PyArray_LegacyDescr_fields *)(((char *)dtype) + offsetof(_PyArray_LegacyDescr, typeobj));
}

@@ -1308,3 +1337,3 @@ #undef _PyArray_GET_ITEM_DATA

{
return (PyArrayObject_fields *)(((char *)arr) + sizeof(PyObject));
return (PyArrayObject_fields *)(((char *)arr) + offsetof(PyArrayObject_fields, data));
}

@@ -1316,3 +1345,3 @@ #undef _PyArrayMultiIter_GET_ITEM_DATA

{
return (PyArrayMultiIterObject_fields *)(((char *)multi) + sizeof(PyObject));
return (PyArrayMultiIterObject_fields *)(((char *)multi) + offsetof(PyArrayMultiIterObject_fields, numiter));
}

@@ -1324,3 +1353,3 @@ #undef _PyArrayIter_GET_ITEM_DATA

{
return (PyArrayIterObject_fields *)(((char *)iter) + sizeof(PyObject));
return (PyArrayIterObject_fields *)(((char *)iter) + offsetof(PyArrayIterObject_fields, nd_m1));
}

@@ -1332,3 +1361,3 @@ #undef _PyArrayNeighborhoodIter_GET_ITEM_DATA

{
return (PyArrayNeighborhoodIterObject_fields *)(((char *)iter) + sizeof(PyObject));
return (PyArrayNeighborhoodIterObject_fields *)(((char *)iter) + offsetof(PyArrayNeighborhoodIterObject_fields, nd_m1));
}

@@ -1335,0 +1364,0 @@ #undef _PyDatetimeScalarObject_GetMetadata

@@ -831,4 +831,12 @@ #define NPY_NO_DEPRECATED_API NPY_API_VERSION

{
if (NPY_DT_SLOTS(NPY_DTYPE(PyArray_DTYPE(self)))->imag_meth != NULL) {
/* The dtype has `arr.imag` so `conjugate` must exist (or error) */
PyArray_DTypeMeta *dtype = NPY_DTYPE(PyArray_DESCR(self));
/*
* If a dtype doesn't define `imag_meth` and is numeric, we assume it isn't
* a complex dtype (`conjugate()` does nothing).
* For user defined legacy dtypes we always try the ufunc for backwards
* compatibility (could be deprecated). Unless they flag "numeric" because if
* they do they live in a future where they could set `imag_meth` as well.
*/
if (NPY_DT_SLOTS(dtype)->imag_meth != NULL
|| (PyArray_ISUSERDEF(self) && !NPY_DT_is_numeric(dtype))) {
if (out == NULL) {

@@ -845,3 +853,3 @@ return PyArray_GenericUnaryFunction(self,

else {
if (!NPY_DT_is_numeric(NPY_DTYPE(PyArray_DTYPE(self)))) {
if (!NPY_DT_is_numeric(dtype)) {
PyErr_SetString(PyExc_TypeError,

@@ -848,0 +856,0 @@ "cannot conjugate non-numeric dtype");

@@ -62,2 +62,26 @@ #ifndef NUMPY_CORE_SRC_MULTIARRAY_COMMON_H_

/*
* Raise a TypeError for a dtype whose legacy copyswap slot is missing
* (e.g. a dtype written using the new DType API). This never succeeds:
* it always returns -1, so that it can be chained onto the NULL test at
* the call site. `inplace_swap` marks a request that only swaps bytes
* in place (no data copy); it is unused for now, but is passed so that
* such requests can become a no-op (returning 0) when byte order does
* not apply to the dtype (see gh-32150).
*
* WARNING: every call site is written as
*
* if (copyswap == NULL && raise_missing_copyswap(...) < 0) {
* <error return>;
* }
* copyswap(...);
*
* which calls through the NULL slot if this function returns 0. This is
* safe only while it unconditionally returns -1: before changing it to
* return 0, every call site must first be restructured to skip the
* copyswap call on success.
*/
NPY_NO_EXPORT int
raise_missing_copyswap(PyArray_Descr *dtype, int inplace_swap);
NPY_NO_EXPORT PyObject *

@@ -64,0 +88,0 @@ convert_shape_to_string(npy_intp n, npy_intp const *vals, char *ending);

@@ -716,2 +716,7 @@ /* Array Descr Object */

copyswap(selfit->dataptr, arrit->dataptr, swap, self);
if (PyErr_Occurred()) {
/* e.g. a structured dtype field that does not support copyswap;
stop writing as soon as the error is visible */
goto exit;
}
PyArray_ITER_NEXT(selfit);

@@ -718,0 +723,0 @@ PyArray_ITER_NEXT(arrit);

@@ -214,2 +214,58 @@ /* The implementation of the StringDType class */

// Combine the na_object and coerce attributes of the string descriptors in
// descrs, ignoring descriptors of other dtypes. The na_objects must be
// compatible, and coercion is only enabled if it is enabled for all string
// descriptors. Returns -1 with an error set if two descriptors have
// incompatible na_objects. out_na_object (a borrowed reference) and
// out_coerce may be NULL if only the compatibility check is needed.
NPY_NO_EXPORT int
stringdtype_common_na_coerce(int ndescrs, PyArray_Descr *const descrs[],
PyObject **out_na_object, int *out_coerce)
{
PyObject *na_object = NULL;
int coerce = 1;
for (int i = 0; i < ndescrs; i++) {
if (NPY_DTYPE(descrs[i]) != &PyArray_StringDType) {
continue;
}
PyArray_StringDTypeObject *descr = (PyArray_StringDTypeObject *)descrs[i];
if (stringdtype_compatible_na(na_object, descr->na_object, &na_object) == -1) {
return -1;
}
coerce = coerce && descr->coerce;
}
if (out_na_object != NULL) {
*out_na_object = na_object;
}
if (out_coerce != NULL) {
*out_coerce = coerce;
}
return 0;
}
// Select the descriptor that determines how nulls are handled in an
// operation accepting several string operands whose descriptors may differ,
// ignoring descriptors of other dtypes. The caller has already checked that
// the na_objects are compatible, so the first string descriptor with a set
// na_object determines the null-handling behavior for the whole operation.
// Returns the first string descriptor if none has an na_object set.
NPY_NO_EXPORT PyArray_StringDTypeObject *
stringdtype_effective_na_descr(int ndescrs, PyArray_Descr *const descrs[])
{
PyArray_StringDTypeObject *first = NULL;
for (int i = 0; i < ndescrs; i++) {
if (NPY_DTYPE(descrs[i]) != &PyArray_StringDType) {
continue;
}
PyArray_StringDTypeObject *descr = (PyArray_StringDTypeObject *)descrs[i];
if (descr->na_object != NULL) {
return descr;
}
if (first == NULL) {
first = descr;
}
}
return first;
}
/*

@@ -223,6 +279,7 @@ * This is used to determine the correct dtype to return when dealing

{
PyArray_Descr *descrs[2] = {(PyArray_Descr *)dtype1, (PyArray_Descr *)dtype2};
PyObject *out_na_object = NULL;
int out_coerce = 1;
if (stringdtype_compatible_na(
dtype1->na_object, dtype2->na_object, &out_na_object) == -1) {
if (stringdtype_common_na_coerce(2, descrs, &out_na_object, &out_coerce) == -1) {
PyErr_Format(PyExc_TypeError,

@@ -235,3 +292,3 @@ "Cannot find common instance for incompatible dtypes "

return (PyArray_StringDTypeObject *)new_stringdtype_instance(
out_na_object, dtype1->coerce && dtype2->coerce);
out_na_object, out_coerce);
}

@@ -467,13 +524,8 @@

npy_string_allocator *allocator_b = descr_b->allocator;
// descr_a and descr_b are either the same object or objects
// that are equal, so we can safely refer only to descr_a.
// This is enforced in the resolve_descriptors for comparisons
//
// Note that even though the default_string isn't checked in comparisons,
// it will still be the same for both descrs because the value of
// default_string is always the empty string unless na_object is a string.
int has_null = descr_a->na_object != NULL;
int has_string_na = descr_a->has_string_na;
int has_nan_na = descr_a->has_nan_na;
npy_static_string *default_string = &descr_a->default_string;
PyArray_Descr *descrs[2] = {(PyArray_Descr *)descr_a, (PyArray_Descr *)descr_b};
PyArray_StringDTypeObject *nadescr = stringdtype_effective_na_descr(2, descrs);
int has_null = nadescr->na_object != NULL;
int has_string_na = nadescr->has_string_na;
int has_nan_na = nadescr->has_nan_na;
npy_static_string *default_string = &nadescr->default_string;
const npy_packed_static_string *ps_a = (npy_packed_static_string *)a;

@@ -480,0 +532,0 @@ npy_static_string s_a = {0, NULL};

@@ -60,2 +60,9 @@ #ifndef _NPY_CORE_SRC_MULTIARRAY_STRINGDTYPE_DTYPE_H_

NPY_NO_EXPORT int
stringdtype_common_na_coerce(int ndescrs, PyArray_Descr *const descrs[],
PyObject **out_na_object, int *out_coerce);
NPY_NO_EXPORT PyArray_StringDTypeObject *
stringdtype_effective_na_descr(int ndescrs, PyArray_Descr *const descrs[]);
NPY_NO_EXPORT int
na_eq_cmp(PyObject *a, PyObject *b);

@@ -62,0 +69,0 @@

@@ -48,8 +48,11 @@ name: NumPy

- name: Install Intel SDE
run: |
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Setup Intel SDE
uses: petarpetrovt/setup-sde@31aa4a8e85e109bef00f1d838613fcc6ec421271 # v5.0
with:
environmentVariableName: SDE_PATH
sdeVersion: 9.58.0
- name: Add Intel SDE to PATH
run: echo "$SDE_PATH" >> "$GITHUB_PATH"
- name: Install NumPy dependencies

@@ -102,8 +105,11 @@ working-directory: ${{ github.workspace }}/numpy

- name: Install Intel SDE
run: |
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Setup Intel SDE
uses: petarpetrovt/setup-sde@31aa4a8e85e109bef00f1d838613fcc6ec421271 # v5.0
with:
environmentVariableName: SDE_PATH
sdeVersion: 9.58.0
- name: Add Intel SDE to PATH
run: echo "$SDE_PATH" >> "$GITHUB_PATH"
- name: Checkout NumPy main

@@ -110,0 +116,0 @@ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

@@ -18,3 +18,3 @@ #!/bin/bash

## Install Intel SDE
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
wget -O /tmp/sde.tar.xz https://downloadmirror.intel.com/915934/sde-external-10.8.0-2026-03-15-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/

@@ -21,0 +21,0 @@ mv /tmp/sde/* /opt/sde && ln -s /opt/sde/sde /usr/bin/sde

@@ -12,2 +12,50 @@ name: Build and run tests

jobs:
build-windows-msvc:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
# Set up MSVC environment
- name: Set up MSVC Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
# Install Python (Meson requires it)
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
# Install Meson and Ninja
- name: Install Meson + Ninja
run: |
python -m pip install --upgrade pip
pip install meson ninja
- name: Setup environment
run: |
echo "PKG_CONFIG=${{ github.workspace }}/vcpkg/installed/x64-windows/tools/pkgconf/pkgconf.exe" >> $env:GITHUB_ENV
echo "PKG_CONFIG_PATH=${{ github.workspace }}/vcpkg/installed/x64-windows/lib/pkgconfig" >> $env:GITHUB_ENV
echo "LIB=${{ github.workspace }}/vcpkg/installed/x64-windows/lib;$env:LIB" >> $env:GITHUB_ENV
echo "INCLUDE=${{ github.workspace }}/vcpkg/installed/x64-windows/include;$env:INCLUDE" >> $env:GITHUB_ENV
shell: pwsh
- name: Setup vcpkg and install pkg-config and gtest
run: |
git clone https://github.com/Microsoft/vcpkg.git
.\vcpkg\bootstrap-vcpkg.bat
.\vcpkg\vcpkg install gtest:x64-windows pkgconf:x64-windows
# Configure and build with Meson (MSVC will be used automatically)
- name: Configure (Meson)
run: meson setup -Dbuild_tests=true --warnlevel 2 --buildtype release builddir --backend=ninja
- name: Build (Ninja)
run: ninja -C builddir
- name: Run tests
run: meson test -C builddir --test-args "\-\-gtest_filter=*qsort*" -v
SKL-gcc9:

@@ -20,21 +68,11 @@

- name: Install dependencies
run: |
sudo apt update
sudo apt -y install g++-9 libgtest-dev meson curl git
- name: Setup build environment
uses: ./.github/actions/setup-build-env
with:
compiler: g++-9
- name: Install Intel SDE
run: |
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Build
env:
CXX: g++-9
run: |
make clean
meson setup -Dbuild_tests=true --warnlevel 2 --werror --buildtype release builddir
cd builddir
ninja
uses: ./.github/actions/meson-build
with:
compiler: g++-9

@@ -51,21 +89,11 @@ - name: Run test suite on SKL

- name: Install dependencies
run: |
sudo apt update
sudo apt -y install g++-10 libgtest-dev meson curl git
- name: Setup build environment
uses: ./.github/actions/setup-build-env
with:
compiler: g++-10
- name: Install Intel SDE
run: |
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Build
env:
CXX: g++-10
run: |
make clean
meson setup -Dbuild_tests=true --warnlevel 2 --werror --buildtype release builddir
cd builddir
ninja
uses: ./.github/actions/meson-build
with:
compiler: g++-10

@@ -82,21 +110,11 @@ - name: Run test suite on SKX

- name: Install dependencies
run: |
sudo apt update
sudo apt -y install g++-11 libgtest-dev meson curl git
- name: Setup build environment
uses: ./.github/actions/setup-build-env
with:
compiler: g++-11
- name: Install Intel SDE
run: |
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Build
env:
CXX: g++-11
run: |
make clean
meson setup -Dbuild_tests=true --warnlevel 2 --werror --buildtype release builddir
cd builddir
ninja
uses: ./.github/actions/meson-build
with:
compiler: g++-11
- name: Run test suite on TGL

@@ -112,28 +130,12 @@ run: sde -tgl -- ./builddir/testexe

- name: Install dependencies
run: |
sudo apt update
sudo apt -y install g++-13 libgtest-dev meson curl git
- name: Setup build environment
uses: ./.github/actions/setup-build-env
with:
compiler: g++-13
- name: Install Intel SDE
run: |
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Build examples
env:
CXX: g++-13
run: |
cd examples
make all
- name: Build
env:
CXX: g++-13
run: |
make clean
meson setup -Dbuild_tests=true --warnlevel 2 --werror --buildtype release builddir
cd builddir
ninja
uses: ./.github/actions/meson-build
with:
compiler: g++-13
build-examples: 'true'

@@ -143,3 +145,3 @@ - name: Run test suite on SPR

ADL-ASAN-clang18:
ASAN-clang18:

@@ -151,72 +153,19 @@ runs-on: ubuntu-24.04

- name: Install dependencies
run: |
sudo apt update
sudo apt -y install clang-18 libomp-18-dev libgtest-dev meson curl git
- name: Setup build environment
uses: ./.github/actions/setup-build-env
with:
compiler: clang-18
extra-packages: libomp-18-dev
- name: Install Intel SDE
run: |
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Build examples
env:
CXX: clang++-18
run: |
cd examples
make all
- name: Build
env:
CXX: clang++-18
run: |
make clean
meson setup -Dbuild_tests=true -Duse_openmp=true -Db_sanitize=address,undefined -Dfatal_sanitizers=true -Dasan_ci_dont_validate=true -Db_lundef=false --warnlevel 0 --buildtype release builddir
cd builddir
ninja
uses: ./.github/actions/meson-build
with:
compiler: clang++-18
meson-options: '-Dbuild_tests=true -Duse_openmp=true -Db_sanitize=address,undefined -Dfatal_sanitizers=true -Dasan_ci_dont_validate=true -Db_lundef=false --warnlevel 0 --buildtype release'
build-examples: 'true'
- name: Run test suite on ADL
run: sde -adl -- ./builddir/testexe --gtest_filter="*simdsort*"
- name: Run test suite
run: ./builddir/testexe --gtest_filter="*simdsort*"
SPR-ASAN-clang18:
runs-on: intel-ubuntu-24.04
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Install dependencies
run: |
sudo apt update
sudo apt -y install clang-18 libomp-18-dev libgtest-dev meson curl git
- name: Install Intel SDE
run: |
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Build examples
env:
CXX: clang++-18
run: |
cd examples
make all
- name: Build
env:
CXX: clang++-18
run: |
make clean
meson setup -Dbuild_tests=true -Duse_openmp=true -Db_sanitize=address,undefined -Dfatal_sanitizers=true -Dasan_ci_dont_validate=true -Db_lundef=false --warnlevel 0 --buildtype release builddir
cd builddir
ninja
- name: Run test suite on SPR
run: sde -spr -- ./builddir/testexe
- name: Run ICL fp16 tests
# Note: This filters for the _Float16 tests based on the number assigned to it, which could change in the future
run: sde -icx -- ./builddir/testexe --gtest_filter="*/simdsort/2*"
SKX-SKL-openmp:

@@ -229,21 +178,12 @@

- name: Install dependencies
run: |
sudo apt update
sudo apt -y install g++-10 libgtest-dev meson curl git
- name: Setup build environment
uses: ./.github/actions/setup-build-env
with:
compiler: g++-10
- name: Install Intel SDE
run: |
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Build
env:
CXX: g++-10
run: |
make clean
meson setup -Dbuild_tests=true -Duse_openmp=true --warnlevel 2 --werror --buildtype release builddir
cd builddir
ninja
uses: ./.github/actions/meson-build
with:
compiler: g++-10
meson-options: '-Dbuild_tests=true -Duse_openmp=true --warnlevel 2 --werror --buildtype release'

@@ -262,22 +202,12 @@ - name: Run test suite on SKX and SKL

- name: Install dependencies
run: |
sudo apt update
sudo apt -y install g++-13 libgtest-dev meson curl git
- name: Setup build environment
uses: ./.github/actions/setup-build-env
with:
compiler: g++-13
- name: Install Intel SDE
run: |
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Build
env:
CXX: g++-13
CXXFLAGS: "-DXSS_MINIMAL_NETWORK_SORT -DXSS_TEST_KEYVALUE_BASE_CASE"
run: |
make clean
meson setup -Dbuild_tests=true --warnlevel 2 --werror --buildtype release builddir
cd builddir
ninja
uses: ./.github/actions/meson-build
with:
compiler: g++-13
cxxflags: '-DXSS_MINIMAL_NETWORK_SORT -DXSS_TEST_KEYVALUE_BASE_CASE'

@@ -310,3 +240,3 @@ - name: List exported symbols

- name: Install dependencies
- name: Install Intel oneAPI repository and dependencies
run: |

@@ -316,32 +246,20 @@ echo "deb https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list

sudo apt update --allow-insecure-repositories
sudo apt --allow-unauthenticated -y install intel-oneapi-compiler-dpcpp-cpp libgtest-dev curl git python3-pip meson
sudo apt --allow-unauthenticated -y install intel-oneapi-compiler-dpcpp-cpp libgtest-dev wget git python3-pip meson
- name: Install Intel SDE
run: |
#INTEL_SDE_URL=$(curl -s https://www.intel.com/content/www/us/en/download/684897/813591/intel-software-development-emulator.html | grep -Po 'https://downloadmirror.intel.com/.*lin.tar.xz(?=")')
#curl -o /tmp/sde.tar.xz $INTEL_SDE_URL
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
- name: Setup Intel SDE
uses: petarpetrovt/setup-sde@31aa4a8e85e109bef00f1d838613fcc6ec421271 # v5.0
with:
environmentVariableName: SDE_PATH
sdeVersion: 9.58.0
- name: Build examples
env:
CXX: icpx
CXXFLAGS: -fp-model=precise
run: |
source /opt/intel/oneapi/setvars.sh
cd examples
make all
- name: Add Intel SDE to PATH
run: echo "$SDE_PATH" >> "$GITHUB_PATH"
- name: Build
env:
CXX: icpx
CXXFLAGS: -fp-model=precise
run: |
make clean
source /opt/intel/oneapi/setvars.sh
icpx --version
meson setup -Dbuild_tests=true --warnlevel 2 --werror --buildtype release builddir
cd builddir
ninja
uses: ./.github/actions/meson-build
with:
compiler: icpx
cxxflags: '-fp-model=precise'
build-examples: 'true'
pre-build-commands: 'source /opt/intel/oneapi/setvars.sh && icpx --version'

@@ -348,0 +266,0 @@ - name: Run test suite on SPR

@@ -8,29 +8,53 @@ // AVX2 specific routines:

template <> \
void qsort(type *arr, size_t arrsize, bool hasnan, bool descending) \
void qsort(type *arr, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
x86simdsortStatic::qsort(arr, arrsize, hasnan, descending); \
x86simdsortStatic::qsort( \
arr, arrsize, hasnan, descending, nans_last); \
} \
template <> \
void qselect( \
type *arr, size_t k, size_t arrsize, bool hasnan, bool descending) \
void qselect(type *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
x86simdsortStatic::qselect(arr, k, arrsize, hasnan, descending); \
x86simdsortStatic::qselect( \
arr, k, arrsize, hasnan, descending, nans_last); \
} \
template <> \
void partial_qsort( \
type *arr, size_t k, size_t arrsize, bool hasnan, bool descending) \
void partial_qsort(type *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
x86simdsortStatic::partial_qsort(arr, k, arrsize, hasnan, descending); \
x86simdsortStatic::partial_qsort( \
arr, k, arrsize, hasnan, descending, nans_last); \
} \
template <> \
std::vector<size_t> argsort( \
type *arr, size_t arrsize, bool hasnan, bool descending) \
std::vector<size_t> argsort(const type *arr, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
return x86simdsortStatic::argsort(arr, arrsize, hasnan, descending); \
return x86simdsortStatic::argsort( \
arr, arrsize, hasnan, descending, nans_last); \
} \
template <> \
std::vector<size_t> argselect( \
type *arr, size_t k, size_t arrsize, bool hasnan) \
std::vector<size_t> argselect(const type *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
return x86simdsortStatic::argselect(arr, k, arrsize, hasnan); \
return x86simdsortStatic::argselect( \
arr, k, arrsize, hasnan, descending, nans_last); \
}

@@ -37,0 +61,0 @@

// ICL specific routines:
#include "x86simdsort-static-incl.h"
#include "x86simdsort-internal.h"
#ifdef _MSC_VER
#include "avx512-16bit-qsort.hpp"
#endif

@@ -8,5 +11,9 @@ namespace xss {

template <>
void qsort(uint16_t *arr, size_t size, bool hasnan, bool descending)
void qsort(uint16_t *arr,
size_t size,
bool hasnan,
bool descending,
bool nans_last)
{
x86simdsortStatic::qsort(arr, size, hasnan, descending);
x86simdsortStatic::qsort(arr, size, hasnan, descending, nans_last);
}

@@ -18,5 +25,7 @@ template <>

bool hasnan,
bool descending)
bool descending,
bool nans_last)
{
x86simdsortStatic::qselect(arr, k, arrsize, hasnan, descending);
x86simdsortStatic::qselect(
arr, k, arrsize, hasnan, descending, nans_last);
}

@@ -28,10 +37,16 @@ template <>

bool hasnan,
bool descending)
bool descending,
bool nans_last)
{
x86simdsortStatic::partial_qsort(arr, k, arrsize, hasnan, descending);
x86simdsortStatic::partial_qsort(
arr, k, arrsize, hasnan, descending, nans_last);
}
template <>
void qsort(int16_t *arr, size_t size, bool hasnan, bool descending)
void qsort(int16_t *arr,
size_t size,
bool hasnan,
bool descending,
bool nans_last)
{
x86simdsortStatic::qsort(arr, size, hasnan, descending);
x86simdsortStatic::qsort(arr, size, hasnan, descending, nans_last);
}

@@ -43,5 +58,7 @@ template <>

bool hasnan,
bool descending)
bool descending,
bool nans_last)
{
x86simdsortStatic::qselect(arr, k, arrsize, hasnan, descending);
x86simdsortStatic::qselect(
arr, k, arrsize, hasnan, descending, nans_last);
}

@@ -53,5 +70,7 @@ template <>

bool hasnan,
bool descending)
bool descending,
bool nans_last)
{
x86simdsortStatic::partial_qsort(arr, k, arrsize, hasnan, descending);
x86simdsortStatic::partial_qsort(
arr, k, arrsize, hasnan, descending, nans_last);
}

@@ -62,5 +81,9 @@ } // namespace avx512

template <>
void qsort(_Float16 *arr, size_t size, bool hasnan, bool descending)
void qsort(_Float16 *arr,
size_t size,
bool hasnan,
bool descending,
bool nans_last)
{
x86simdsortStatic::qsort(arr, size, hasnan, descending);
x86simdsortStatic::qsort(arr, size, hasnan, descending, nans_last);
}

@@ -72,5 +95,7 @@ template <>

bool hasnan,
bool descending)
bool descending,
bool nans_last)
{
x86simdsortStatic::qselect(arr, k, arrsize, hasnan, descending);
x86simdsortStatic::qselect(
arr, k, arrsize, hasnan, descending, nans_last);
}

@@ -82,5 +107,7 @@ template <>

bool hasnan,
bool descending)
bool descending,
bool nans_last)
{
x86simdsortStatic::partial_qsort(arr, k, arrsize, hasnan, descending);
x86simdsortStatic::partial_qsort(
arr, k, arrsize, hasnan, descending, nans_last);
}

@@ -87,0 +114,0 @@ #endif

@@ -13,3 +13,4 @@ #ifndef XSS_INTERNAL_METHODS

bool hasnan = false, \
bool descending = false); \
bool descending = false, \
bool nans_last = true); \
template <typename T1, typename T2> \

@@ -26,3 +27,4 @@ XSS_HIDE_SYMBOL void keyvalue_qsort(T1 *key, \

bool hasnan = false, \
bool descending = false); \
bool descending = false, \
bool nans_last = true); \
template <typename T1, typename T2> \

@@ -40,3 +42,4 @@ XSS_HIDE_SYMBOL void keyvalue_select(T1 *key, \

bool hasnan = false, \
bool descending = false); \
bool descending = false, \
bool nans_last = true); \
template <typename T1, typename T2> \

@@ -50,9 +53,14 @@ XSS_HIDE_SYMBOL void keyvalue_partial_sort(T1 *key, \

template <typename T> \
XSS_HIDE_SYMBOL std::vector<size_t> argsort(T *arr, \
XSS_HIDE_SYMBOL std::vector<size_t> argsort(const T *arr, \
size_t arrsize, \
bool hasnan = false, \
bool descending = false); \
bool descending = false, \
bool nans_last = true); \
template <typename T> \
XSS_HIDE_SYMBOL std::vector<size_t> \
argselect(T *arr, size_t k, size_t arrsize, bool hasnan = false); \
XSS_HIDE_SYMBOL std::vector<size_t> argselect(const T *arr, \
size_t k, \
size_t arrsize, \
bool hasnan = false, \
bool descending = false, \
bool nans_last = true); \
}

@@ -59,0 +67,0 @@

@@ -27,9 +27,20 @@ #include "custom-compare.h"

template <typename T>
decltype(auto) get_cmp_func(bool hasnan, bool reverse)
decltype(auto)
get_cmp_func(bool hasnan, bool reverse, bool nans_last = true)
{
std::function<bool(T, T)> cmp;
if (hasnan) {
if (reverse == true) { cmp = compare<T, std::greater<T>>(); }
if (nans_last) {
if (reverse == true) {
cmp = compare_nan_end<T, std::greater<T>>();
}
else {
cmp = compare<T, std::less<T>>();
}
}
else {
cmp = compare<T, std::less<T>>();
if (reverse == true) { cmp = compare<T, std::greater<T>>(); }
else {
cmp = compare_nan_begin<T, std::less<T>>();
}
}

@@ -49,29 +60,47 @@ }

template <typename T>
void qsort(T *arr, size_t arrsize, bool hasnan, bool reversed)
void qsort(T *arr,
size_t arrsize,
bool hasnan,
bool reversed,
bool nans_last)
{
std::sort(arr,
arr + arrsize,
xss::utils::get_cmp_func<T>(hasnan, reversed));
xss::utils::get_cmp_func<T>(hasnan, reversed, nans_last));
}
template <typename T>
void qselect(T *arr, size_t k, size_t arrsize, bool hasnan, bool reversed)
void qselect(T *arr,
size_t k,
size_t arrsize,
bool hasnan,
bool reversed,
bool nans_last)
{
std::nth_element(arr,
arr + k,
arr + arrsize,
xss::utils::get_cmp_func<T>(hasnan, reversed));
std::nth_element(
arr,
arr + k,
arr + arrsize,
xss::utils::get_cmp_func<T>(hasnan, reversed, nans_last));
}
template <typename T>
void
partial_qsort(T *arr, size_t k, size_t arrsize, bool hasnan, bool reversed)
void partial_qsort(T *arr,
size_t k,
size_t arrsize,
bool hasnan,
bool reversed,
bool nans_last)
{
std::partial_sort(arr,
arr + k,
arr + arrsize,
xss::utils::get_cmp_func<T>(hasnan, reversed));
std::partial_sort(
arr,
arr + k,
arr + arrsize,
xss::utils::get_cmp_func<T>(hasnan, reversed, nans_last));
}
template <typename T>
std::vector<size_t>
argsort(T *arr, size_t arrsize, bool hasnan, bool reversed)
std::vector<size_t> argsort(const T *arr,
size_t arrsize,
bool hasnan,
bool reversed,
bool nans_last)
{

@@ -81,10 +110,25 @@ UNUSED(hasnan);

std::iota(arg.begin(), arg.end(), 0);
if (reversed) {
std::sort(arg.begin(),
arg.end(),
compare_arg<T, std::greater<T>>(arr));
if (nans_last) {
if (reversed) {
std::sort(arg.begin(),
arg.end(),
compare_arg_nan_end<T, std::greater<T>>(arr));
}
else {
std::sort(arg.begin(),
arg.end(),
compare_arg<T, std::less<T>>(arr));
}
}
else {
std::sort(
arg.begin(), arg.end(), compare_arg<T, std::less<T>>(arr));
if (reversed) {
std::sort(arg.begin(),
arg.end(),
compare_arg<T, std::greater<T>>(arr));
}
else {
std::sort(arg.begin(),
arg.end(),
compare_arg_nan_begin<T, std::less<T>>(arr));
}
}

@@ -94,3 +138,8 @@ return arg;

template <typename T>
std::vector<size_t> argselect(T *arr, size_t k, size_t arrsize, bool hasnan)
std::vector<size_t> argselect(const T *arr,
size_t k,
size_t arrsize,
bool hasnan,
bool descending,
bool nans_last)
{

@@ -100,6 +149,18 @@ UNUSED(hasnan);

std::iota(arg.begin(), arg.end(), 0);
std::nth_element(arg.begin(),
arg.begin() + k,
arg.end(),
compare_arg<T, std::less<T>>(arr));
std::function<bool(size_t, size_t)> cmp;
if (nans_last) {
if (descending) {
cmp = compare_arg_nan_end<T, std::greater<T>>(arr);
}
else {
cmp = compare_arg<T, std::less<T>>(arr);
}
}
else {
if (descending) { cmp = compare_arg<T, std::greater<T>>(arr); }
else {
cmp = compare_arg_nan_begin<T, std::less<T>>(arr);
}
}
std::nth_element(arg.begin(), arg.begin() + k, arg.end(), cmp);
return arg;

@@ -111,3 +172,4 @@ }

{
std::vector<size_t> arg = argsort(key, arrsize, hasnan, descending);
std::vector<size_t> arg
= argsort(key, arrsize, hasnan, descending, true);
utils::apply_permutation_in_place(key, arg);

@@ -114,0 +176,0 @@ utils::apply_permutation_in_place(val, arg);

@@ -8,29 +8,53 @@ // SKX specific routines:

template <> \
void qsort(type *arr, size_t arrsize, bool hasnan, bool descending) \
void qsort(type *arr, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
x86simdsortStatic::qsort(arr, arrsize, hasnan, descending); \
x86simdsortStatic::qsort( \
arr, arrsize, hasnan, descending, nans_last); \
} \
template <> \
void qselect( \
type *arr, size_t k, size_t arrsize, bool hasnan, bool descending) \
void qselect(type *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
x86simdsortStatic::qselect(arr, k, arrsize, hasnan, descending); \
x86simdsortStatic::qselect( \
arr, k, arrsize, hasnan, descending, nans_last); \
} \
template <> \
void partial_qsort( \
type *arr, size_t k, size_t arrsize, bool hasnan, bool descending) \
void partial_qsort(type *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
x86simdsortStatic::partial_qsort(arr, k, arrsize, hasnan, descending); \
x86simdsortStatic::partial_qsort( \
arr, k, arrsize, hasnan, descending, nans_last); \
} \
template <> \
std::vector<size_t> argsort( \
type *arr, size_t arrsize, bool hasnan, bool descending) \
std::vector<size_t> argsort(const type *arr, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
return x86simdsortStatic::argsort(arr, arrsize, hasnan, descending); \
return x86simdsortStatic::argsort( \
arr, arrsize, hasnan, descending, nans_last); \
} \
template <> \
std::vector<size_t> argselect( \
type *arr, size_t k, size_t arrsize, bool hasnan) \
std::vector<size_t> argselect(const type *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
return x86simdsortStatic::argselect(arr, k, arrsize, hasnan); \
return x86simdsortStatic::argselect( \
arr, k, arrsize, hasnan, descending, nans_last); \
}

@@ -37,0 +61,0 @@

@@ -8,5 +8,9 @@ // SPR specific routines:

template <>
void qsort(_Float16 *arr, size_t size, bool hasnan, bool descending)
void qsort(_Float16 *arr,
size_t size,
bool hasnan,
bool descending,
bool nans_last)
{
x86simdsortStatic::qsort(arr, size, hasnan, descending);
x86simdsortStatic::qsort(arr, size, hasnan, descending, nans_last);
}

@@ -18,5 +22,7 @@ template <>

bool hasnan,
bool descending)
bool descending,
bool nans_last)
{
x86simdsortStatic::qselect(arr, k, arrsize, hasnan, descending);
x86simdsortStatic::qselect(
arr, k, arrsize, hasnan, descending, nans_last);
}

@@ -28,7 +34,9 @@ template <>

bool hasnan,
bool descending)
bool descending,
bool nans_last)
{
x86simdsortStatic::partial_qsort(arr, k, arrsize, hasnan, descending);
x86simdsortStatic::partial_qsort(
arr, k, arrsize, hasnan, descending, nans_last);
}
} // namespace fp16_spr
} // namespace xss

@@ -0,4 +1,10 @@

#if defined(_MSC_VER)
#define XSS_ATTRIBUTE_CONSTRUCTOR
#else
#define XSS_ATTRIBUTE_CONSTRUCTOR __attribute__((constructor))
#endif
#include "x86simdsort.h"
#include "x86simdsort-internal.h"
#include "x86simdsort-scalar.h"
#include "x86simdsortcpuid.h"
#include <algorithm>

@@ -15,5 +21,4 @@ #include <iostream>

&& (!defined(__clang_major__) || __clang_major__ >= 18)
return __builtin_cpu_supports("avx512f")
&& __builtin_cpu_supports("avx512fp16")
&& __builtin_cpu_supports("avx512vbmi2");
return xss_cpu_supports("avx512f") && xss_cpu_supports("avx512fp16")
&& xss_cpu_supports("avx512vbmi2");
#else

@@ -23,12 +28,9 @@ return 0;

else if ((cpufeature == "avx512_icl") && (!disable_avx512))
return __builtin_cpu_supports("avx512f")
&& __builtin_cpu_supports("avx512vbmi2")
&& __builtin_cpu_supports("avx512bw")
&& __builtin_cpu_supports("avx512vl");
return xss_cpu_supports("avx512f") && xss_cpu_supports("avx512vbmi2")
&& xss_cpu_supports("avx512bw") && xss_cpu_supports("avx512vl");
else if ((cpufeature == "avx512_skx") && (!disable_avx512))
return __builtin_cpu_supports("avx512f")
&& __builtin_cpu_supports("avx512dq")
&& __builtin_cpu_supports("avx512vl");
return xss_cpu_supports("avx512f") && xss_cpu_supports("avx512dq")
&& xss_cpu_supports("avx512vl");
else if (cpufeature == "avx2")
return __builtin_cpu_supports("avx2");
return xss_cpu_supports("avx2");

@@ -62,53 +64,214 @@ return 0;

/* runtime dispatch mechanism */
#define DISPATCH(func, TYPE, ISA) \
DECLARE_INTERNAL_##func(TYPE) static XSS_ATTRIBUTE_CONSTRUCTOR void CAT( \
CAT(resolve_, func), TYPE)(void) \
{ \
CAT(CAT(internal_, func), TYPE) = &xss::scalar::func<TYPE>; \
xss_cpu_init(); \
std::string_view preferred_cpu = find_preferred_cpu(ISA); \
if constexpr (dispatch_requested("avx512", ISA)) { \
if (preferred_cpu.find("avx512") != std::string_view::npos) { \
if constexpr (IS_TYPE_FLOAT16<TYPE>()) { \
if (preferred_cpu.find("avx512_spr") \
!= std::string_view::npos) { \
CAT(CAT(internal_, func), TYPE) \
= &xss::fp16_spr::func<TYPE>; \
return; \
} \
if (preferred_cpu.find("avx512_icl") \
!= std::string_view::npos) { \
CAT(CAT(internal_, func), TYPE) \
= &xss::fp16_icl::func<TYPE>; \
return; \
} \
} \
else { \
CAT(CAT(internal_, func), TYPE) \
= &xss::avx512::func<TYPE>; \
} \
return; \
} \
} \
if constexpr (dispatch_requested("avx2", ISA)) { \
if (preferred_cpu.find("avx2") != std::string_view::npos) { \
CAT(CAT(internal_, func), TYPE) = &xss::avx2::func<TYPE>; \
return; \
} \
} \
}
#ifdef _MSC_VER
#define DECLARE_INTERNAL_qsort(TYPE) \
static void (*internal_qsort##TYPE)(TYPE *, size_t, bool, bool) = NULL; \
static void CAT(resolve_qsort, TYPE)(void); \
static void (*internal_qsort##TYPE)(TYPE *, size_t, bool, bool, bool) \
= NULL; \
template <> \
void qsort(TYPE *arr, size_t arrsize, bool hasnan, bool descending) \
void XSS_EXPORT_SYMBOL qsort(TYPE *arr, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
(*internal_qsort##TYPE)(arr, arrsize, hasnan, descending); \
if (internal_qsort##TYPE == NULL) { CAT(resolve_qsort, TYPE)(); } \
(*internal_qsort##TYPE)( \
arr, arrsize, hasnan, descending, nans_last); \
}
#define DECLARE_INTERNAL_qselect(TYPE) \
static void (*internal_qselect##TYPE)(TYPE *, size_t, size_t, bool, bool) \
static void CAT(resolve_qselect, TYPE)(void); \
static void (*internal_qselect##TYPE)( \
TYPE *, size_t, size_t, bool, bool, bool) \
= NULL; \
template <> \
void qselect( \
TYPE *arr, size_t k, size_t arrsize, bool hasnan, bool descending) \
void XSS_EXPORT_SYMBOL qselect(TYPE *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
(*internal_qselect##TYPE)(arr, k, arrsize, hasnan, descending); \
if (internal_qselect##TYPE == NULL) { CAT(resolve_qselect, TYPE)(); } \
(*internal_qselect##TYPE)( \
arr, k, arrsize, hasnan, descending, nans_last); \
}
#define DECLARE_INTERNAL_partial_qsort(TYPE) \
static void CAT(resolve_partial_qsort, TYPE)(void); \
static void (*internal_partial_qsort##TYPE)( \
TYPE *, size_t, size_t, bool, bool) \
TYPE *, size_t, size_t, bool, bool, bool) \
= NULL; \
template <> \
void partial_qsort( \
TYPE *arr, size_t k, size_t arrsize, bool hasnan, bool descending) \
void XSS_EXPORT_SYMBOL partial_qsort(TYPE *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
(*internal_partial_qsort##TYPE)(arr, k, arrsize, hasnan, descending); \
if (internal_partial_qsort##TYPE == NULL) { \
CAT(resolve_partial_qsort, TYPE)(); \
} \
(*internal_partial_qsort##TYPE)( \
arr, k, arrsize, hasnan, descending, nans_last); \
}
#define DECLARE_INTERNAL_argsort(TYPE) \
static void CAT(resolve_argsort, TYPE)(void); \
static std::vector<size_t> (*internal_argsort##TYPE)( \
TYPE *, size_t, bool, bool) \
const TYPE *, size_t, bool, bool, bool) \
= NULL; \
template <> \
std::vector<size_t> argsort( \
TYPE *arr, size_t arrsize, bool hasnan, bool descending) \
std::vector<size_t> XSS_EXPORT_SYMBOL argsort(const TYPE *arr, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
return (*internal_argsort##TYPE)(arr, arrsize, hasnan, descending); \
if (internal_argsort##TYPE == NULL) { CAT(resolve_argsort, TYPE)(); } \
return (*internal_argsort##TYPE)( \
arr, arrsize, hasnan, descending, nans_last); \
}
#define DECLARE_INTERNAL_argselect(TYPE) \
static void CAT(resolve_argselect, TYPE)(void); \
static std::vector<size_t> (*internal_argselect##TYPE)( \
TYPE *, size_t, size_t, bool) \
const TYPE *, size_t, size_t, bool, bool, bool) \
= NULL; \
template <> \
std::vector<size_t> argselect( \
TYPE *arr, size_t k, size_t arrsize, bool hasnan) \
std::vector<size_t> XSS_EXPORT_SYMBOL argselect(const TYPE *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
return (*internal_argselect##TYPE)(arr, k, arrsize, hasnan); \
if (internal_argselect##TYPE == NULL) { \
CAT(resolve_argselect, TYPE)(); \
} \
return (*internal_argselect##TYPE)( \
arr, k, arrsize, hasnan, descending, nans_last); \
}
#else
#define DECLARE_INTERNAL_qsort(TYPE) \
static void (*internal_qsort##TYPE)(TYPE *, size_t, bool, bool, bool) \
= NULL; \
template <> \
void XSS_EXPORT_SYMBOL qsort(TYPE *arr, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
(*internal_qsort##TYPE)( \
arr, arrsize, hasnan, descending, nans_last); \
}
#define DECLARE_INTERNAL_qselect(TYPE) \
static void (*internal_qselect##TYPE)( \
TYPE *, size_t, size_t, bool, bool, bool) \
= NULL; \
template <> \
void XSS_EXPORT_SYMBOL qselect(TYPE *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
(*internal_qselect##TYPE)( \
arr, k, arrsize, hasnan, descending, nans_last); \
}
#define DECLARE_INTERNAL_partial_qsort(TYPE) \
static void (*internal_partial_qsort##TYPE)( \
TYPE *, size_t, size_t, bool, bool, bool) \
= NULL; \
template <> \
void XSS_EXPORT_SYMBOL partial_qsort(TYPE *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
(*internal_partial_qsort##TYPE)( \
arr, k, arrsize, hasnan, descending, nans_last); \
}
#define DECLARE_INTERNAL_argsort(TYPE) \
static std::vector<size_t> (*internal_argsort##TYPE)( \
const TYPE *, size_t, bool, bool, bool) \
= NULL; \
template <> \
std::vector<size_t> XSS_EXPORT_SYMBOL argsort(const TYPE *arr, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
return (*internal_argsort##TYPE)( \
arr, arrsize, hasnan, descending, nans_last); \
}
#define DECLARE_INTERNAL_argselect(TYPE) \
static std::vector<size_t> (*internal_argselect##TYPE)( \
const TYPE *, size_t, size_t, bool, bool, bool) \
= NULL; \
template <> \
std::vector<size_t> XSS_EXPORT_SYMBOL argselect(const TYPE *arr, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
return (*internal_argselect##TYPE)( \
arr, k, arrsize, hasnan, descending, nans_last); \
}
#endif // _MSC_VER
/* simple constexpr function as a way around having #ifdef __FLT16_MAX__ block

@@ -125,41 +288,2 @@ * within the DISPATCH macro */

/* runtime dispatch mechanism */
#define DISPATCH(func, TYPE, ISA) \
DECLARE_INTERNAL_##func(TYPE) static __attribute__((constructor)) void \
CAT(CAT(resolve_, func), TYPE)(void) \
{ \
CAT(CAT(internal_, func), TYPE) = &xss::scalar::func<TYPE>; \
__builtin_cpu_init(); \
std::string_view preferred_cpu = find_preferred_cpu(ISA); \
if constexpr (dispatch_requested("avx512", ISA)) { \
if (preferred_cpu.find("avx512") != std::string_view::npos) { \
if constexpr (IS_TYPE_FLOAT16<TYPE>()) { \
if (preferred_cpu.find("avx512_spr") \
!= std::string_view::npos) { \
CAT(CAT(internal_, func), TYPE) \
= &xss::fp16_spr::func<TYPE>; \
return; \
} \
if (preferred_cpu.find("avx512_icl") \
!= std::string_view::npos) { \
CAT(CAT(internal_, func), TYPE) \
= &xss::fp16_icl::func<TYPE>; \
return; \
} \
} \
else { \
CAT(CAT(internal_, func), TYPE) \
= &xss::avx512::func<TYPE>; \
} \
return; \
} \
} \
if constexpr (dispatch_requested("avx2", ISA)) { \
if (preferred_cpu.find("avx2") != std::string_view::npos) { \
CAT(CAT(internal_, func), TYPE) = &xss::avx2::func<TYPE>; \
return; \
} \
} \
}
#define ISA_LIST(...) \

@@ -211,3 +335,32 @@ std::initializer_list<std::string_view> \

/* Key-Value methods */
#define DISPATCH_KV_FUNC(func, TYPE1, TYPE2, ISA) \
static XSS_ATTRIBUTE_CONSTRUCTOR void CAT( \
CAT(CAT(CAT(resolve_, func), _), TYPE1), TYPE2)(void) \
{ \
CAT(CAT(CAT(CAT(internal_, func), _), TYPE1), TYPE2) \
= &xss::scalar::func<TYPE1, TYPE2>; \
xss_cpu_init(); \
std::string_view preferred_cpu = find_preferred_cpu(ISA); \
if constexpr (dispatch_requested("avx512", ISA)) { \
if (preferred_cpu.find("avx512") != std::string_view::npos) { \
CAT(CAT(CAT(CAT(internal_, func), _), TYPE1), TYPE2) \
= &xss::avx512::func<TYPE1, TYPE2>; \
return; \
} \
} \
if constexpr (dispatch_requested("avx2", ISA)) { \
if (preferred_cpu.find("avx2") != std::string_view::npos) { \
CAT(CAT(CAT(CAT(internal_, func), _), TYPE1), TYPE2) \
= &xss::avx2::func<TYPE1, TYPE2>; \
return; \
} \
} \
}
#ifdef _MSC_VER
#define DECLARE_ALL_KEYVALUE_METHODS(TYPE1, TYPE2) \
static void CAT(CAT(resolve_keyvalue_select_, TYPE1), TYPE2)(void); \
static void CAT(CAT(resolve_keyvalue_partial_sort_, TYPE1), TYPE2)(void); \
static void CAT(CAT(resolve_keyvalue_qsort_, TYPE1), TYPE2)(void); \
static void(CAT(CAT(*internal_keyvalue_qsort_, TYPE1), TYPE2))( \

@@ -223,8 +376,11 @@ TYPE1 *, TYPE2 *, size_t, bool, bool) \

template <> \
void keyvalue_qsort(TYPE1 *key, \
TYPE2 *val, \
size_t arrsize, \
bool hasnan, \
bool descending) \
void XSS_EXPORT_SYMBOL keyvalue_qsort(TYPE1 *key, \
TYPE2 *val, \
size_t arrsize, \
bool hasnan, \
bool descending) \
{ \
if ((CAT(CAT(*internal_keyvalue_qsort_, TYPE1), TYPE2)) == NULL) { \
CAT(CAT(resolve_keyvalue_qsort_, TYPE1), TYPE2)(); \
} \
(CAT(CAT(*internal_keyvalue_qsort_, TYPE1), TYPE2))( \

@@ -234,9 +390,12 @@ key, val, arrsize, hasnan, descending); \

template <> \
void keyvalue_select(TYPE1 *key, \
TYPE2 *val, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending) \
void XSS_EXPORT_SYMBOL keyvalue_select(TYPE1 *key, \
TYPE2 *val, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending) \
{ \
if ((CAT(CAT(*internal_keyvalue_select_, TYPE1), TYPE2)) == NULL) { \
CAT(CAT(resolve_keyvalue_select_, TYPE1), TYPE2)(); \
} \
(CAT(CAT(*internal_keyvalue_select_, TYPE1), TYPE2))( \

@@ -246,36 +405,60 @@ key, val, k, arrsize, hasnan, descending); \

template <> \
void keyvalue_partial_sort(TYPE1 *key, \
TYPE2 *val, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending) \
void XSS_EXPORT_SYMBOL keyvalue_partial_sort(TYPE1 *key, \
TYPE2 *val, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending) \
{ \
if ((CAT(CAT(*internal_keyvalue_partial_sort_, TYPE1), TYPE2)) \
== NULL) { \
CAT(CAT(resolve_keyvalue_partial_sort_, TYPE1), TYPE2)(); \
} \
(CAT(CAT(*internal_keyvalue_partial_sort_, TYPE1), TYPE2))( \
key, val, k, arrsize, hasnan, descending); \
}
#define DISPATCH_KV_FUNC(func, TYPE1, TYPE2, ISA) \
static __attribute__((constructor)) void CAT( \
CAT(CAT(CAT(resolve_, func), _), TYPE1), TYPE2)(void) \
#else
#define DECLARE_ALL_KEYVALUE_METHODS(TYPE1, TYPE2) \
static void(CAT(CAT(*internal_keyvalue_qsort_, TYPE1), TYPE2))( \
TYPE1 *, TYPE2 *, size_t, bool, bool) \
= NULL; \
static void(CAT(CAT(*internal_keyvalue_select_, TYPE1), TYPE2))( \
TYPE1 *, TYPE2 *, size_t, size_t, bool, bool) \
= NULL; \
static void(CAT(CAT(*internal_keyvalue_partial_sort_, TYPE1), TYPE2))( \
TYPE1 *, TYPE2 *, size_t, size_t, bool, bool) \
= NULL; \
template <> \
void XSS_EXPORT_SYMBOL keyvalue_qsort(TYPE1 *key, \
TYPE2 *val, \
size_t arrsize, \
bool hasnan, \
bool descending) \
{ \
CAT(CAT(CAT(CAT(internal_, func), _), TYPE1), TYPE2) \
= &xss::scalar::func<TYPE1, TYPE2>; \
__builtin_cpu_init(); \
std::string_view preferred_cpu = find_preferred_cpu(ISA); \
if constexpr (dispatch_requested("avx512", ISA)) { \
if (preferred_cpu.find("avx512") != std::string_view::npos) { \
CAT(CAT(CAT(CAT(internal_, func), _), TYPE1), TYPE2) \
= &xss::avx512::func<TYPE1, TYPE2>; \
return; \
} \
} \
if constexpr (dispatch_requested("avx2", ISA)) { \
if (preferred_cpu.find("avx2") != std::string_view::npos) { \
CAT(CAT(CAT(CAT(internal_, func), _), TYPE1), TYPE2) \
= &xss::avx2::func<TYPE1, TYPE2>; \
return; \
} \
} \
(CAT(CAT(*internal_keyvalue_qsort_, TYPE1), TYPE2))( \
key, val, arrsize, hasnan, descending); \
} \
template <> \
void XSS_EXPORT_SYMBOL keyvalue_select(TYPE1 *key, \
TYPE2 *val, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending) \
{ \
(CAT(CAT(*internal_keyvalue_select_, TYPE1), TYPE2))( \
key, val, k, arrsize, hasnan, descending); \
} \
template <> \
void XSS_EXPORT_SYMBOL keyvalue_partial_sort(TYPE1 *key, \
TYPE2 *val, \
size_t k, \
size_t arrsize, \
bool hasnan, \
bool descending) \
{ \
(CAT(CAT(*internal_keyvalue_partial_sort_, TYPE1), TYPE2))( \
key, val, k, arrsize, hasnan, descending); \
}
#endif // _MSC_VER

@@ -282,0 +465,0 @@ #define DISPATCH_KEYVALUE_SORT(TYPE1, TYPE2, ISA) \

@@ -9,4 +9,9 @@ #ifndef X86_SIMD_SORT

#if defined(_MSC_VER)
#define XSS_EXPORT_SYMBOL __declspec(dllexport)
#define XSS_HIDE_SYMBOL
#else
#define XSS_EXPORT_SYMBOL __attribute__((visibility("default")))
#define XSS_HIDE_SYMBOL __attribute__((visibility("hidden")))
#endif
#define UNUSED(x) (void)(x)

@@ -18,4 +23,7 @@

template <typename T>
XSS_EXPORT_SYMBOL void
qsort(T *arr, size_t arrsize, bool hasnan = false, bool descending = false);
XSS_EXPORT_SYMBOL void qsort(T *arr,
size_t arrsize,
bool hasnan = false,
bool descending = false,
bool nans_last = true);

@@ -28,3 +36,4 @@ // quickselect

bool hasnan = false,
bool descending = false);
bool descending = false,
bool nans_last = true);

@@ -37,13 +46,21 @@ // partial sort

bool hasnan = false,
bool descending = false);
bool descending = false,
bool nans_last = true);
// argsort
template <typename T>
XSS_EXPORT_SYMBOL std::vector<size_t>
argsort(T *arr, size_t arrsize, bool hasnan = false, bool descending = false);
XSS_EXPORT_SYMBOL std::vector<size_t> argsort(const T *arr,
size_t arrsize,
bool hasnan = false,
bool descending = false,
bool nans_last = true);
// argselect
template <typename T>
XSS_EXPORT_SYMBOL std::vector<size_t>
argselect(T *arr, size_t k, size_t arrsize, bool hasnan = false);
XSS_EXPORT_SYMBOL std::vector<size_t> argselect(const T *arr,
size_t k,
size_t arrsize,
bool hasnan = false,
bool descending = false,
bool nans_last = true);

@@ -50,0 +67,0 @@ // keyvalue sort

@@ -1,2 +0,2 @@

# x86-simd-sort
# x86-simd-sort [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/numpy/x86-simd-sort)

@@ -40,5 +40,5 @@ C++ template library for high performance SIMD based sorting routines for

```cpp
void x86simdsort::qsort(T* arr, size_t size, bool hasnan, bool descending);
void x86simdsort::qselect(T* arr, size_t k, size_t size, bool hasnan, bool descending);
void x86simdsort::partial_qsort(T* arr, size_t k, size_t size, bool hasnan, bool descending);
void x86simdsort::qsort(T* arr, size_t size, bool hasnan, bool descending, bool trailing_nans);
void x86simdsort::qselect(T* arr, size_t k, size_t size, bool hasnan, bool descending, bool trailing_nans);
void x86simdsort::partial_qsort(T* arr, size_t k, size_t size, bool hasnan, bool descending, bool trailing_nans);
```

@@ -60,4 +60,4 @@ Supported datatypes: `T` $\in$ `[_Float16, uint16_t, int16_t, float, uint32_t,

```cpp
std::vector<size_t> arg = x86simdsort::argsort(T* arr, size_t size, bool hasnan, bool descending);
std::vector<size_t> arg = x86simdsort::argselect(T* arr, size_t k, size_t size, bool hasnan);
std::vector<size_t> arg = x86simdsort::argsort(const T* arr, size_t size, bool hasnan, bool descending, bool trailing_nans);
std::vector<size_t> arg = x86simdsort::argselect(const T* arr, size_t k, size_t size, bool hasnan, bool descending, bool trailing_nans);
```

@@ -87,2 +87,12 @@ Supported datatypes: `T` $\in$ `[_Float16, uint16_t, int16_t, float, uint32_t, int32_t, double,

## Supported Compilers
This library requires a **C++17** compatible compiler. While most modern compilers work, specific versions are required to support advanced SIMD instruction sets like AVX-512 and AVX-512FP16:
* **GCC**: Version **8.x** or higher is required for general builds.
* *Note: To support `_Float16` (AVX-512FP16), **GCC 12** or higher is mandatory.*
* **Clang**: Version **8.0** or higher is generally recommended for robust AVX-512 support.
* **MSVC**: **Visual Studio 2019 (version 16.8)** or newer is required for C++17 and initial AVX-512 intrinsic support.
* **Intel C++ Compiler (icx)**: All modern versions of the LLVM-based Intel compiler are fully supported and often provide optimal performance for these routines.
## Build using OpenMP

@@ -170,9 +180,18 @@

hasnan` set to `false` by default (these are relevant to floating point data
types only). If your array has NAN's, the the behaviour of the sorting routine
is undefined. If `hasnan` is set to true, NAN's are always sorted to the end of
the array. In addition to that, qsort will replace all your NAN's with
`std::numeric_limits<T>::quiet_NaN`. The original bit-exact NaNs in
the input are not preserved. Also note that the arg methods (argsort and
argselect) will not use the SIMD based algorithms if they detect NAN's in the
array. You can read details of all the implementations
types only). If your array has NaN values, the behaviour of the sorting routine
is undefined unless `hasnan` is set to `true`. When `hasnan=true`, NaN placement
is controlled by the optional `bool trailing_nans` parameter (default `true`):
- `trailing_nans=true` (default): NaN values are placed at the **end** of the
result, regardless of sort direction.
- `trailing_nans=false`: NaN values are placed at the **beginning** of the
result, regardless of sort direction.
All routines accept an optional `bool descending` parameter (default `false`).
When `descending=true`, results are in descending order. For `argselect`, the
k-th element becomes the k-th **largest**, with all elements before index k
being greater than or equal to it.
Note that the arg methods (argsort and argselect) will not use the SIMD based
algorithms if they detect NaN values in the array. You can read details of all the implementations
[here](https://github.com/intel/x86-simd-sort/blob/main/src/README.md).

@@ -179,0 +198,0 @@

@@ -87,3 +87,3 @@ /*******************************************************************

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -241,3 +241,3 @@ return set(arr[ind[3]], arr[ind[2]], arr[ind[1]], arr[ind[0]]);

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -426,3 +426,3 @@ return set(arr[ind[3]], arr[ind[2]], arr[ind[1]], arr[ind[0]]);

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -429,0 +429,0 @@ return set(arr[ind[3]], arr[ind[2]], arr[ind[1]], arr[ind[0]]);

@@ -102,3 +102,3 @@ /*******************************************************************

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -273,3 +273,3 @@ return set(arr[ind[3]], arr[ind[2]], arr[ind[1]], arr[ind[0]]);

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -504,3 +504,3 @@ return set(arr[ind[3]], arr[ind[2]], arr[ind[1]], arr[ind[0]]);

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -507,0 +507,0 @@ return set(arr[ind[3]], arr[ind[2]], arr[ind[1]], arr[ind[0]]);

@@ -522,27 +522,8 @@ /*******************************************************************

template <>
X86_SIMD_SORT_INLINE_ONLY arrsize_t
replace_nan_with_inf<zmm_vector<float16>>(uint16_t *arr, arrsize_t arrsize)
{
arrsize_t nan_count = 0;
__mmask16 loadmask = 0xFFFF;
for (arrsize_t ii = 0; ii < arrsize;
ii = ii + zmm_vector<float16>::numlanes / 2) {
if (arrsize - ii < 16) {
loadmask = (0x0001 << (arrsize - ii)) - 0x0001;
}
__m256i in_zmm = _mm256_maskz_loadu_epi16(loadmask, arr);
__m512 in_zmm_asfloat = _mm512_cvtph_ps(in_zmm);
__mmask16 nanmask = _mm512_cmp_ps_mask(
in_zmm_asfloat, in_zmm_asfloat, _CMP_NEQ_UQ);
nan_count += _mm_popcnt_u32((int32_t)nanmask);
_mm256_mask_storeu_epi16(arr, nanmask, YMM_MAX_HALF);
arr += 16;
}
return nan_count;
}
template <typename comparator>
[[maybe_unused]] X86_SIMD_SORT_INLINE void
avx512_qsort_fp16_helper(uint16_t *arr, arrsize_t arrsize)
avx512_qsort_fp16_helper(uint16_t *arr,
arrsize_t arrsize,
arrsize_t index_first_elem,
arrsize_t index_last_elem)
{

@@ -553,7 +534,9 @@ using T = uint16_t;

#ifdef XSS_COMPILE_OPENMP
bool use_parallel = arrsize > 100000;
bool use_parallel = (index_last_elem - index_first_elem + 1) > 100000;
if (use_parallel) {
int thread_count = xss_get_num_threads();
arrsize_t task_threshold = std::max((arrsize_t)100000, arrsize / 100);
arrsize_t task_threshold
= std::max((arrsize_t)100000,
(index_last_elem - index_first_elem + 1) / 100);

@@ -566,4 +549,4 @@ // We use omp parallel and then omp single to setup the threads that will run the omp task calls in qsort_

qsort_<vtype, comparator, T>(arr,
0,
arrsize - 1,
index_first_elem,
index_last_elem,
2 * (arrsize_t)log2(arrsize),

@@ -574,4 +557,4 @@ task_threshold);

qsort_<vtype, comparator, T>(arr,
0,
arrsize - 1,
index_first_elem,
index_last_elem,
2 * (arrsize_t)log2(arrsize),

@@ -582,4 +565,7 @@ std::numeric_limits<arrsize_t>::max());

#else
qsort_<vtype, comparator, T>(
arr, 0, arrsize - 1, 2 * (arrsize_t)log2(arrsize), 0);
qsort_<vtype, comparator, T>(arr,
index_first_elem,
index_last_elem,
2 * (arrsize_t)log2(arrsize),
0);
#endif

@@ -592,3 +578,4 @@ }

bool hasnan = false,
bool descending = false)
bool descending = false,
bool nans_last = true)
{

@@ -598,13 +585,22 @@ using vtype = zmm_vector<float16>;

if (arrsize > 1) {
arrsize_t nan_count = 0;
arrsize_t index_first_elem = 0;
arrsize_t index_last_elem = arrsize - 1;
if (UNLIKELY(hasnan)) {
nan_count = replace_nan_with_inf<vtype, uint16_t>(arr, arrsize);
if (!nans_last) {
index_first_elem = move_nans_to_start_of_array(arr, arrsize);
}
else {
index_last_elem = move_nans_to_end_of_array(arr, arrsize);
}
}
if (descending) {
avx512_qsort_fp16_helper<Comparator<vtype, true>>(arr, arrsize);
if (index_first_elem <= index_last_elem && index_last_elem < arrsize) {
if (descending) {
avx512_qsort_fp16_helper<Comparator<vtype, true>>(
arr, arrsize, index_first_elem, index_last_elem);
}
else {
avx512_qsort_fp16_helper<Comparator<vtype, false>>(
arr, arrsize, index_first_elem, index_last_elem);
}
}
else {
avx512_qsort_fp16_helper<Comparator<vtype, false>>(arr, arrsize);
}
replace_inf_with_nan(arr, arrsize, nan_count, descending);
}

@@ -623,3 +619,4 @@

bool hasnan = false,
bool descending = false)
bool descending = false,
bool nans_last = true)
{

@@ -635,3 +632,3 @@ using vtype = zmm_vector<float16>;

if (UNLIKELY(hasnan)) {
if (descending) {
if (!nans_last) {
index_first_elem = move_nans_to_start_of_array(arr, arrsize);

@@ -674,8 +671,9 @@ }

bool hasnan = false,
bool descending = false)
bool descending = false,
bool nans_last = true)
{
if (k == 0) return;
avx512_qselect_fp16(arr, k - 1, arrsize, hasnan, descending);
avx512_qsort_fp16(arr, k - 1, hasnan, descending);
avx512_qselect_fp16(arr, k - 1, arrsize, hasnan, descending, nans_last);
avx512_qsort_fp16(arr, k - 1, hasnan, descending, nans_last);
}
#endif // AVX512_QSORT_16BIT

@@ -102,3 +102,3 @@ /*******************************************************************

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -297,3 +297,3 @@ return set(arr[ind[7]],

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -486,3 +486,3 @@ return set(arr[ind[7]],

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -686,3 +686,3 @@ return set(arr[ind[7]],

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -850,3 +850,3 @@ return set(arr[ind[7]],

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -1070,3 +1070,3 @@ return set(arr[ind[7]],

}
static reg_t i64gather(type_t *arr, arrsize_t *ind)
static reg_t i64gather(const type_t *arr, arrsize_t *ind)
{

@@ -1073,0 +1073,0 @@ return set(arr[ind[7]],

@@ -178,24 +178,2 @@ /*******************************************************************

template <>
X86_SIMD_SORT_INLINE_ONLY void replace_inf_with_nan(_Float16 *arr,
arrsize_t size,
arrsize_t nan_count,
bool descending)
{
Fp16Bits val;
val.i_ = 0x7c01;
if (descending) {
for (arrsize_t ii = 0; nan_count > 0; ++ii) {
arr[ii] = val.f_;
nan_count -= 1;
}
}
else {
for (arrsize_t ii = size - 1; nan_count > 0; --ii) {
arr[ii] = val.f_;
nan_count -= 1;
}
}
}
#endif // AVX512FP16_QSORT_16BIT

@@ -21,3 +21,3 @@ # x86-simd-sort

```cpp
void x86simdsortStatic::qsort<T>(T* arr, size_t arrsize, bool hasnan = false, bool descending = false);
void x86simdsortStatic::qsort<T>(T* arr, size_t arrsize, bool hasnan = false, bool descending = false, bool nans_last = true);
```

@@ -27,4 +27,5 @@ Supported datatypes: `uint16_t`, `int16_t`, `_Float16`, `uint32_t`, `int32_t`,

32-bit and 64-bit dtypes only. For floating-point types, if `arr` contains
NaNs, they are moved to the end and replaced with a quiet NaN. That is, the
original, bit-exact NaNs in the input are not preserved.
NaNs, their placement is controlled by `nans_last`: `true` (default) places
NaNs at the end; `false` places them at the beginning. Bit-exact NaN payloads
are preserved.

@@ -39,3 +40,3 @@ #### Quickselect

```cpp
void x86simdsortStatic::qselect<T>(T* arr, size_t k, size_t arrsize, bool hasnan = false, bool descending = false);
void x86simdsortStatic::qselect<T>(T* arr, size_t k, size_t arrsize, bool hasnan = false, bool descending = false, bool nans_last = true);
```

@@ -45,4 +46,5 @@ Supported datatypes: `uint16_t`, `int16_t`, `_Float16`, `uint32_t`, `int32_t`,

32-bit and 64-bit dtypes only. For floating-point types, if `bool hasnan` is
set, NaNs are moved to the end of the array, preserving the bit-exact NaNs in
the input. If NaNs are present but `hasnan` is `false`, the behavior is
set, NaN placement is controlled by `nans_last`: `true` (default) places
NaNs at the end; `false` places them at the beginning. Bit-exact NaN payloads
are preserved. If NaNs are present but `hasnan` is `false`, the behavior is
undefined.

@@ -56,3 +58,3 @@

```cpp
void x86simdsortStatic::partial_qsort<T>(T* arr, size_t k, size_t arrsize, bool hasnan = false, bool descending = false)
void x86simdsortStatic::partial_qsort<T>(T* arr, size_t k, size_t arrsize, bool hasnan = false, bool descending = false, bool nans_last = true);
```

@@ -62,4 +64,5 @@ Supported datatypes: `uint16_t`, `int16_t`, `_Float16`, `uint32_t`, `int32_t`,

32-bit and 64-bit dtypes only. For floating-point types, if `bool hasnan` is
set, NaNs are moved to the end of the array, preserving the bit-exact NaNs in
the input. If NaNs are present but `hasnan` is `false`, the behavior is
set, NaN placement is controlled by `nans_last`: `true` (default) places
NaNs at the end; `false` places them at the beginning. Bit-exact NaN payloads
are preserved. If NaNs are present but `hasnan` is `false`, the behavior is
undefined.

@@ -72,3 +75,3 @@

```cpp
void x86simdsortStatic::argsort<T>(T* arr, size_t *arg, size_t arrsize, bool hasnan = false, bool descending = false);
void x86simdsortStatic::argsort<T>(const T* arr, size_t *arg, size_t arrsize, bool hasnan = false, bool descending = false, bool nans_last = true);
```

@@ -85,3 +88,3 @@ Supported datatypes: `uint32_t`, `int32_t`, `float`, `uint64_t`, `int64_t` and

```cpp
void x86simdsortStatic::argselect<T>(T* arr, size_t *arg, size_t k, size_t arrsize, bool hasnan = false);
void x86simdsortStatic::argselect<T>(const T* arr, size_t *arg, size_t k, size_t arrsize, bool hasnan = false, bool descending = false, bool nans_last = true);
```

@@ -91,4 +94,9 @@ Supported datatypes: `uint32_t`, `int32_t`, `float`, `uint64_t`, `int64_t` and

The algorithm resorts to scalar `std::sort` if the array contains NaNs.
When `descending=true`, the k-th element is the k-th **largest** and elements
before index k are all greater than or equal to it. For floating-point types,
if `bool hasnan` is set, NaN placement is controlled by `nans_last`:
`true` (default) places NaNs at the end; `false` places them at the beginning.
The algorithm resorts to scalar `std::nth_element` if the array contains NaNs.
#### Key-value sort

@@ -95,0 +103,0 @@ ```cpp

@@ -10,4 +10,7 @@ #ifndef X86_SIMD_SORT_STATIC_METHODS

template <typename T>
X86_SIMD_SORT_FINLINE void
qsort(T *arr, size_t size, bool hasnan = false, bool descending = false);
X86_SIMD_SORT_FINLINE void qsort(T *arr,
size_t size,
bool hasnan = false,
bool descending = false,
bool nans_last = true);

@@ -19,3 +22,4 @@ template <typename T>

bool hasnan = false,
bool descending = false);
bool descending = false,
bool nans_last = true);

@@ -27,24 +31,38 @@ template <typename T>

bool hasnan = false,
bool descending = false);
bool descending = false,
bool nans_last = true);
template <typename T>
X86_SIMD_SORT_FINLINE std::vector<size_t>
argsort(T *arr, size_t size, bool hasnan = false, bool descending = false);
X86_SIMD_SORT_FINLINE std::vector<size_t> argsort(const T *arr,
size_t size,
bool hasnan = false,
bool descending = false,
bool nans_last = true);
/* argsort API required by NumPy: */
template <typename T>
X86_SIMD_SORT_FINLINE void argsort(T *arr,
X86_SIMD_SORT_FINLINE void argsort(const T *arr,
size_t *arg,
size_t size,
bool hasnan = false,
bool descending = false);
bool descending = false,
bool nans_last = true);
template <typename T>
X86_SIMD_SORT_FINLINE std::vector<size_t>
argselect(T *arr, size_t k, size_t size, bool hasnan = false);
X86_SIMD_SORT_FINLINE std::vector<size_t> argselect(const T *arr,
size_t k,
size_t size,
bool hasnan = false,
bool descending = false,
bool nans_last = true);
/* argselect API required by NumPy: */
template <typename T>
void X86_SIMD_SORT_FINLINE
argselect(T *arr, size_t *arg, size_t k, size_t size, bool hasnan = false);
void X86_SIMD_SORT_FINLINE argselect(const T *arr,
size_t *arg,
size_t k,
size_t size,
bool hasnan = false,
bool descending = false,
bool nans_last = true);

@@ -78,28 +96,48 @@ template <typename T1, typename T2>

template <typename T> \
X86_SIMD_SORT_FINLINE void x86simdsortStatic::qsort( \
T *arr, size_t size, bool hasnan, bool descending) \
X86_SIMD_SORT_FINLINE void x86simdsortStatic::qsort(T *arr, \
size_t size, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
ISA##_qsort(arr, size, hasnan, descending); \
ISA##_qsort(arr, size, hasnan, descending, nans_last); \
} \
template <typename T> \
X86_SIMD_SORT_FINLINE void x86simdsortStatic::qselect( \
T *arr, size_t k, size_t size, bool hasnan, bool descending) \
X86_SIMD_SORT_FINLINE void x86simdsortStatic::qselect(T *arr, \
size_t k, \
size_t size, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
ISA##_qselect(arr, k, size, hasnan, descending); \
ISA##_qselect(arr, k, size, hasnan, descending, nans_last); \
} \
template <typename T> \
X86_SIMD_SORT_FINLINE void x86simdsortStatic::partial_qsort( \
T *arr, size_t k, size_t size, bool hasnan, bool descending) \
T *arr, \
size_t k, \
size_t size, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
ISA##_partial_qsort(arr, k, size, hasnan, descending); \
ISA##_partial_qsort(arr, k, size, hasnan, descending, nans_last); \
} \
template <typename T> \
X86_SIMD_SORT_FINLINE void x86simdsortStatic::argsort( \
T *arr, size_t *arg, size_t size, bool hasnan, bool descending) \
X86_SIMD_SORT_FINLINE void x86simdsortStatic::argsort(const T *arr, \
size_t *arg, \
size_t size, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
ISA##_argsort(arr, arg, size, hasnan, descending); \
ISA##_argsort(arr, arg, size, hasnan, descending, nans_last); \
} \
template <typename T> \
X86_SIMD_SORT_FINLINE std::vector<size_t> x86simdsortStatic::argsort( \
T *arr, size_t size, bool hasnan, bool descending) \
const T *arr, \
size_t size, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \

@@ -109,3 +147,3 @@ std::vector<size_t> indices(size); \

x86simdsortStatic::argsort( \
arr, indices.data(), size, hasnan, descending); \
arr, indices.data(), size, hasnan, descending, nans_last); \
return indices; \

@@ -115,13 +153,30 @@ } \

X86_SIMD_SORT_FINLINE void x86simdsortStatic::argselect( \
T *arr, size_t *arg, size_t k, size_t size, bool hasnan) \
const T *arr, \
size_t *arg, \
size_t k, \
size_t size, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
ISA##_argselect(arr, arg, k, size, hasnan); \
ISA##_argselect(arr, arg, k, size, hasnan, descending, nans_last); \
} \
template <typename T> \
X86_SIMD_SORT_FINLINE std::vector<size_t> x86simdsortStatic::argselect( \
T *arr, size_t k, size_t size, bool hasnan) \
const T *arr, \
size_t k, \
size_t size, \
bool hasnan, \
bool descending, \
bool nans_last) \
{ \
std::vector<size_t> indices(size); \
std::iota(indices.begin(), indices.end(), 0); \
x86simdsortStatic::argselect(arr, indices.data(), k, size, hasnan); \
x86simdsortStatic::argselect(arr, \
indices.data(), \
k, \
size, \
hasnan, \
descending, \
nans_last); \
return indices; \

@@ -189,19 +244,30 @@ } \

bool hasnan,
bool descending)
bool descending,
bool nans_last)
{
avx512_qsort_fp16((uint16_t *)arr, size, hasnan, descending);
avx512_qsort_fp16((uint16_t *)arr, size, hasnan, descending, nans_last);
}
template <>
[[maybe_unused]]
void x86simdsortStatic::qselect<_Float16>(
_Float16 *arr, size_t k, size_t size, bool hasnan, bool descending)
void x86simdsortStatic::qselect<_Float16>(_Float16 *arr,
size_t k,
size_t size,
bool hasnan,
bool descending,
bool nans_last)
{
avx512_qselect_fp16((uint16_t *)arr, k, size, hasnan, descending);
avx512_qselect_fp16(
(uint16_t *)arr, k, size, hasnan, descending, nans_last);
}
template <>
[[maybe_unused]]
void x86simdsortStatic::partial_qsort<_Float16>(
_Float16 *arr, size_t k, size_t size, bool hasnan, bool descending)
void x86simdsortStatic::partial_qsort<_Float16>(_Float16 *arr,
size_t k,
size_t size,
bool hasnan,
bool descending,
bool nans_last)
{
avx512_partial_qsort_fp16((uint16_t *)arr, k, size, hasnan, descending);
avx512_partial_qsort_fp16(
(uint16_t *)arr, k, size, hasnan, descending, nans_last);
}

@@ -221,2 +287,2 @@ #endif

#endif // X86_SIMD_SORT_STATIC_METHODS
#endif // X86_SIMD_SORT_STATIC_METHODS

@@ -14,39 +14,46 @@ /*******************************************************************

template <typename T>
X86_SIMD_SORT_INLINE void std_argselect_withnan(
T *arr, arrsize_t *arg, arrsize_t k, arrsize_t left, arrsize_t right)
X86_SIMD_SORT_INLINE void std_argselect_withnan(const T *arr,
arrsize_t *arg,
arrsize_t k,
arrsize_t left,
arrsize_t right,
bool nans_last = true,
bool descending = false)
{
std::nth_element(arg + left,
arg + k,
arg + right,
[arr](arrsize_t a, arrsize_t b) -> bool {
if ((!std::isnan(arr[a])) && (!std::isnan(arr[b]))) {
return arr[a] < arr[b];
}
else if (std::isnan(arr[a])) {
return false;
}
else {
return true;
}
});
std::nth_element(
arg + left,
arg + k,
arg + right,
[arr, nans_last, descending](arrsize_t a, arrsize_t b) -> bool {
bool a_nan = std::isnan(arr[a]);
bool b_nan = std::isnan(arr[b]);
if (!a_nan && !b_nan) {
return descending ? arr[a] > arr[b] : arr[a] < arr[b];
}
if (a_nan && b_nan) { return false; }
return nans_last ? !a_nan : a_nan;
});
}
/* argsort using std::sort */
/* argsort using std::sort, handles NaN placement and descending order */
template <typename T>
X86_SIMD_SORT_INLINE void
std_argsort_withnan(T *arr, arrsize_t *arg, arrsize_t left, arrsize_t right)
X86_SIMD_SORT_INLINE void std_argsort_withnan(const T *arr,
arrsize_t *arg,
arrsize_t left,
arrsize_t right,
bool nans_last = true,
bool descending = false)
{
std::sort(arg + left,
arg + right,
[arr](arrsize_t left, arrsize_t right) -> bool {
if ((!std::isnan(arr[left])) && (!std::isnan(arr[right]))) {
return arr[left] < arr[right];
}
else if (std::isnan(arr[left])) {
return false;
}
else {
return true;
}
});
std::sort(
arg + left,
arg + right,
[arr, nans_last, descending](arrsize_t a, arrsize_t b) -> bool {
bool a_nan = std::isnan(arr[a]);
bool b_nan = std::isnan(arr[b]);
if (!a_nan && !b_nan) {
return descending ? arr[a] > arr[b] : arr[a] < arr[b];
}
if (a_nan && b_nan) { return false; }
return nans_last ? !a_nan : a_nan;
});
}

@@ -57,3 +64,3 @@

X86_SIMD_SORT_INLINE void
std_argsort(T *arr, arrsize_t *arg, arrsize_t left, arrsize_t right)
std_argsort(const T *arr, arrsize_t *arg, arrsize_t left, arrsize_t right)
{

@@ -177,3 +184,3 @@ std::sort(arg + left,

template <typename vtype, typename argtype, typename type_t>
X86_SIMD_SORT_INLINE arrsize_t argpartition(type_t *arr,
X86_SIMD_SORT_INLINE arrsize_t argpartition(const type_t *arr,
arrsize_t *arg,

@@ -297,3 +304,3 @@ arrsize_t left,

typename type_t = typename vtype::type_t>
X86_SIMD_SORT_INLINE arrsize_t argpartition_unrolled(type_t *arr,
X86_SIMD_SORT_INLINE arrsize_t argpartition_unrolled(const type_t *arr,
arrsize_t *arg,

@@ -429,3 +436,3 @@ arrsize_t left,

template <typename vtype, typename type_t>
X86_SIMD_SORT_INLINE type_t get_pivot_64bit(type_t *arr,
X86_SIMD_SORT_INLINE type_t get_pivot_64bit(const type_t *arr,
arrsize_t *arg,

@@ -476,3 +483,3 @@ const arrsize_t left,

template <typename vtype, typename argtype, typename type_t>
X86_SIMD_SORT_INLINE void argsort_(type_t *arr,
X86_SIMD_SORT_INLINE void argsort_(const type_t *arr,
arrsize_t *arg,

@@ -558,3 +565,3 @@ arrsize_t left,

template <typename vtype, typename argtype, typename type_t>
X86_SIMD_SORT_INLINE void argselect_(type_t *arr,
X86_SIMD_SORT_INLINE void argselect_(const type_t *arr,
arrsize_t *arg,

@@ -600,7 +607,8 @@ arrsize_t pos,

typename half_vector>
X86_SIMD_SORT_INLINE void xss_argsort(T *arr,
X86_SIMD_SORT_INLINE void xss_argsort(const T *arr,
arrsize_t *arg,
arrsize_t arrsize,
bool hasnan = false,
bool descending = false)
bool descending = false,
bool nans_last = true)
{

@@ -621,6 +629,4 @@

if ((hasnan) && (array_has_nan<vectype>(arr, arrsize))) {
std_argsort_withnan(arr, arg, 0, arrsize);
if (descending) { std::reverse(arg, arg + arrsize); }
std_argsort_withnan(
arr, arg, 0, arrsize, nans_last, descending);
return;

@@ -630,2 +636,3 @@ }

UNUSED(hasnan);
UNUSED(nans_last);

@@ -682,21 +689,23 @@ /* early exit for already sorted arrays: float/double with nan never reach here*/

template <typename T>
X86_SIMD_SORT_INLINE void avx512_argsort(T *arr,
X86_SIMD_SORT_INLINE void avx512_argsort(const T *arr,
arrsize_t *arg,
arrsize_t arrsize,
bool hasnan = false,
bool descending = false)
bool descending = false,
bool nans_last = true)
{
xss_argsort<T, zmm_vector, ymm_vector>(
arr, arg, arrsize, hasnan, descending);
arr, arg, arrsize, hasnan, descending, nans_last);
}
template <typename T>
X86_SIMD_SORT_INLINE void avx2_argsort(T *arr,
X86_SIMD_SORT_INLINE void avx2_argsort(const T *arr,
arrsize_t *arg,
arrsize_t arrsize,
bool hasnan = false,
bool descending = false)
bool descending = false,
bool nans_last = true)
{
xss_argsort<T, avx2_vector, avx2_half_vector>(
arr, arg, arrsize, hasnan, descending);
arr, arg, arrsize, hasnan, descending, nans_last);
}

@@ -710,7 +719,9 @@

typename half_vector>
X86_SIMD_SORT_INLINE void xss_argselect(T *arr,
X86_SIMD_SORT_INLINE void xss_argselect(const T *arr,
arrsize_t *arg,
arrsize_t k,
arrsize_t arrsize,
bool hasnan = false)
bool hasnan = false,
bool descending = false,
bool nans_last = true)
{

@@ -730,3 +741,4 @@ /* TODO optimization: on 32-bit, use full_vector for 32-bit dtype */

if ((hasnan) && (array_has_nan<vectype>(arr, arrsize))) {
std_argselect_withnan(arr, arg, k, 0, arrsize);
std_argselect_withnan(
arr, arg, k, 0, arrsize, nans_last, descending);
return;

@@ -736,4 +748,10 @@ }

UNUSED(hasnan);
UNUSED(nans_last);
/* For descending, partition at the mirror position so the k-th
* largest lands at arrsize-1-k; reversal then moves it to k. */
arrsize_t pos = descending ? arrsize - 1 - k : k;
argselect_<vectype, argtype>(
arr, arg, k, 0, arrsize - 1, 2 * (arrsize_t)log2(arrsize));
arr, arg, pos, 0, arrsize - 1, 2 * (arrsize_t)log2(arrsize));
if (descending) { std::reverse(arg, arg + arrsize); }
}

@@ -748,22 +766,27 @@

template <typename T>
X86_SIMD_SORT_INLINE void avx512_argselect(T *arr,
X86_SIMD_SORT_INLINE void avx512_argselect(const T *arr,
arrsize_t *arg,
arrsize_t k,
arrsize_t arrsize,
bool hasnan = false)
bool hasnan = false,
bool descending = false,
bool nans_last = true)
{
xss_argselect<T, zmm_vector, ymm_vector>(arr, arg, k, arrsize, hasnan);
xss_argselect<T, zmm_vector, ymm_vector>(
arr, arg, k, arrsize, hasnan, descending, nans_last);
}
template <typename T>
X86_SIMD_SORT_INLINE void avx2_argselect(T *arr,
X86_SIMD_SORT_INLINE void avx2_argselect(const T *arr,
arrsize_t *arg,
arrsize_t k,
arrsize_t arrsize,
bool hasnan = false)
bool hasnan = false,
bool descending = false,
bool nans_last = true)
{
xss_argselect<T, avx2_vector, avx2_half_vector>(
arr, arg, k, arrsize, hasnan);
arr, arg, k, arrsize, hasnan, descending, nans_last);
}
#endif // XSS_COMMON_ARGSORT

@@ -644,4 +644,5 @@ /*******************************************************************

if (descending) {
std::reverse(keys, keys + arrsize);
std::reverse(indexes, indexes + arrsize);
// Only reverse the real portion; NaN at the end stays in place
std::reverse(keys, keys + index_last_elem + 1);
std::reverse(indexes, indexes + index_last_elem + 1);
}

@@ -692,4 +693,2 @@ }

if (minarrsize) {
if (descending) { k = arrsize - 1 - k; }
arrsize_t index_last_elem = arrsize - 1;

@@ -704,2 +703,5 @@ if constexpr (xss::fp::is_floating_point_v<T1>) {

// For descending: map k to ascending position within real portion
if (descending) { k = index_last_elem - k; }
UNUSED(hasnan);

@@ -712,4 +714,5 @@ if (index_last_elem >= k) {

if (descending) {
std::reverse(keys, keys + arrsize);
std::reverse(indexes, indexes + arrsize);
// Only reverse the real portion; NaN at the end stays in place
std::reverse(keys, keys + index_last_elem + 1);
std::reverse(indexes, indexes + index_last_elem + 1);
}

@@ -716,0 +719,0 @@ }

@@ -54,31 +54,4 @@ /*******************************************************************

template <typename vtype, typename T>
X86_SIMD_SORT_INLINE arrsize_t replace_nan_with_inf(T *arr, arrsize_t size)
{
arrsize_t nan_count = 0;
using opmask_t = typename vtype::opmask_t;
using reg_t = typename vtype::reg_t;
opmask_t loadmask;
reg_t in;
/*
* (ii + numlanes) can never overflow: max val of size is 2**63 on 64-bit
* and 2**31 on 32-bit systems
*/
for (arrsize_t ii = 0; ii < size; ii = ii + vtype::numlanes) {
if (size - ii < vtype::numlanes) {
loadmask = vtype::get_partial_loadmask(size - ii);
in = vtype::maskz_loadu(loadmask, arr + ii);
}
else {
in = vtype::loadu(arr + ii);
}
opmask_t nanmask = vtype::template fpclass<0x01 | 0x80>(in);
nan_count += _mm_popcnt_u32(vtype::convert_mask_to_int(nanmask));
vtype::mask_storeu(arr + ii, nanmask, vtype::zmm_max());
}
return nan_count;
}
template <typename vtype, typename type_t>
X86_SIMD_SORT_INLINE bool array_has_nan(type_t *arr, arrsize_t size)
X86_SIMD_SORT_INLINE bool array_has_nan(const type_t *arr, arrsize_t size)
{

@@ -108,32 +81,2 @@ using opmask_t = typename vtype::opmask_t;

template <typename type_t>
X86_SIMD_SORT_INLINE void replace_inf_with_nan(type_t *arr,
arrsize_t size,
arrsize_t nan_count,
bool descending = false)
{
if (descending) {
for (arrsize_t ii = 0; nan_count > 0; ++ii) {
if constexpr (xss::fp::is_floating_point_v<type_t>) {
arr[ii] = xss::fp::quiet_NaN<type_t>();
}
else {
arr[ii] = 0x7c01; // std::quiet_nan
}
nan_count -= 1;
}
}
else {
for (arrsize_t ii = size - 1; nan_count > 0; --ii) {
if constexpr (xss::fp::is_floating_point_v<type_t>) {
arr[ii] = xss::fp::quiet_NaN<type_t>();
}
else {
arr[ii] = 0x7c01; // std::quiet_nan
}
nan_count -= 1;
}
}
}
/*

@@ -655,3 +598,4 @@ * Sort all the NAN's to end of the array and return the index of the last elem

template <typename vtype, typename T, bool descending = false>
X86_SIMD_SORT_INLINE void xss_qsort(T *arr, arrsize_t arrsize, bool hasnan)
X86_SIMD_SORT_INLINE void
xss_qsort(T *arr, arrsize_t arrsize, bool hasnan, bool nans_last = true)
{

@@ -664,6 +608,13 @@ using comparator =

if (arrsize > 1) {
arrsize_t nan_count = 0;
arrsize_t index_first_elem = 0;
arrsize_t index_last_elem = arrsize - 1;
if constexpr (xss::fp::is_floating_point_v<T>) {
if (UNLIKELY(hasnan)) {
nan_count = replace_nan_with_inf<vtype>(arr, arrsize);
if (!nans_last) {
index_first_elem
= move_nans_to_start_of_array(arr, arrsize);
}
else {
index_last_elem = move_nans_to_end_of_array(arr, arrsize);
}
}

@@ -674,36 +625,42 @@ }

if (index_first_elem <= index_last_elem && index_last_elem < arrsize) {
#ifdef XSS_COMPILE_OPENMP
bool use_parallel = arrsize > 100000;
bool use_parallel
= (index_last_elem - index_first_elem + 1) > 100000;
if (use_parallel) {
int thread_count = xss_get_num_threads();
arrsize_t task_threshold
= std::max((arrsize_t)100000, arrsize / 100);
if (use_parallel) {
int thread_count = xss_get_num_threads();
arrsize_t task_threshold = std::max(
(arrsize_t)100000,
(index_last_elem - index_first_elem + 1) / 100);
// We use omp parallel and then omp single to setup the threads that will run the omp task calls in qsort_
// The omp single prevents multiple threads from running the initial qsort_ simultaneously and causing problems
// Note that we do not use the if(...) clause built into OpenMP, because it causes a performance regression for small arrays
// We use omp parallel and then omp single to setup the threads that will run the omp task calls in qsort_
// The omp single prevents multiple threads from running the initial qsort_ simultaneously and causing problems
// Note that we do not use the if(...) clause built into OpenMP, because it causes a performance regression for small arrays
#pragma omp parallel num_threads(thread_count)
#pragma omp single
qsort_<vtype, comparator, T>(arr,
0,
arrsize - 1,
2 * (arrsize_t)log2(arrsize),
task_threshold);
qsort_<vtype, comparator, T>(arr,
index_first_elem,
index_last_elem,
2 * (arrsize_t)log2(arrsize),
task_threshold);
#pragma omp taskwait
}
else {
}
else {
qsort_<vtype, comparator, T>(
arr,
index_first_elem,
index_last_elem,
2 * (arrsize_t)log2(arrsize),
std::numeric_limits<arrsize_t>::max());
}
#else
qsort_<vtype, comparator, T>(arr,
0,
arrsize - 1,
index_first_elem,
index_last_elem,
2 * (arrsize_t)log2(arrsize),
std::numeric_limits<arrsize_t>::max());
0);
#endif
}
#else
qsort_<vtype, comparator, T>(
arr, 0, arrsize - 1, 2 * (arrsize_t)log2(arrsize), 0);
#endif
replace_inf_with_nan(arr, arrsize, nan_count, descending);
}

@@ -719,4 +676,7 @@

template <typename vtype, typename T, bool descending = false>
X86_SIMD_SORT_INLINE void
xss_qselect(T *arr, arrsize_t k, arrsize_t arrsize, bool hasnan)
X86_SIMD_SORT_INLINE void xss_qselect(T *arr,
arrsize_t k,
arrsize_t arrsize,
bool hasnan,
bool nans_last = true)
{

@@ -736,3 +696,3 @@ using comparator =

if (UNLIKELY(hasnan)) {
if constexpr (descending) {
if (!nans_last) {
index_first_elem = move_nans_to_start_of_array(arr, arrsize);

@@ -763,8 +723,12 @@ }

template <typename vtype, typename T, bool descending = false>
X86_SIMD_SORT_INLINE void
xss_partial_qsort(T *arr, arrsize_t k, arrsize_t arrsize, bool hasnan)
X86_SIMD_SORT_INLINE void xss_partial_qsort(T *arr,
arrsize_t k,
arrsize_t arrsize,
bool hasnan,
bool nans_last = true)
{
if (k == 0) return;
xss_qselect<vtype, T, descending>(arr, k - 1, arrsize, hasnan);
xss_qsort<vtype, T, descending>(arr, k - 1, hasnan);
xss_qselect<vtype, T, descending>(
arr, k - 1, arrsize, hasnan, nans_last);
xss_qsort<vtype, T, descending>(arr, k - 1, hasnan, nans_last);
}

@@ -777,7 +741,10 @@

bool hasnan = false, \
bool descending = false) \
bool descending = false, \
bool nans_last = true) \
{ \
if (descending) { xss_qsort<VTYPE, T, true>(arr, size, hasnan); } \
if (descending) { \
xss_qsort<VTYPE, T, true>(arr, size, hasnan, nans_last); \
} \
else { \
xss_qsort<VTYPE, T, false>(arr, size, hasnan); \
xss_qsort<VTYPE, T, false>(arr, size, hasnan, nans_last); \
} \

@@ -790,7 +757,10 @@ } \

bool hasnan = false, \
bool descending = false) \
bool descending = false, \
bool nans_last = true) \
{ \
if (descending) { xss_qselect<VTYPE, T, true>(arr, k, size, hasnan); } \
if (descending) { \
xss_qselect<VTYPE, T, true>(arr, k, size, hasnan, nans_last); \
} \
else { \
xss_qselect<VTYPE, T, false>(arr, k, size, hasnan); \
xss_qselect<VTYPE, T, false>(arr, k, size, hasnan, nans_last); \
} \

@@ -803,9 +773,12 @@ } \

bool hasnan = false, \
bool descending = false) \
bool descending = false, \
bool nans_last = true) \
{ \
if (descending) { \
xss_partial_qsort<VTYPE, T, true>(arr, k, size, hasnan); \
xss_partial_qsort<VTYPE, T, true>( \
arr, k, size, hasnan, nans_last); \
} \
else { \
xss_partial_qsort<VTYPE, T, false>(arr, k, size, hasnan); \
xss_partial_qsort<VTYPE, T, false>( \
arr, k, size, hasnan, nans_last); \
} \

@@ -812,0 +785,0 @@ }

@@ -211,3 +211,3 @@ #ifndef XSS_KEYVALUE_NETWORKS

X86_SIMD_SORT_INLINE void
argsort_n_vec(typename keyType::type_t *keys, arrsize_t *indices, int N)
argsort_n_vec(const typename keyType::type_t *keys, arrsize_t *indices, int N)
{

@@ -358,3 +358,3 @@ using kreg_t = typename keyType::reg_t;

X86_SIMD_SORT_INLINE void
argsort_n(typename keyType::type_t *keys, arrsize_t *indices, int N)
argsort_n(const typename keyType::type_t *keys, arrsize_t *indices, int N)
{

@@ -361,0 +361,0 @@ static_assert(keyType::numlanes == indexType::numlanes,

@@ -63,10 +63,10 @@ #ifndef AVX512_TEST_COMMON

if (!descending) {
cmp_less = compare<T, std::less<T>>();
cmp_leq = compare<T, std::less_equal<T>>();
cmp_geq = compare<T, std::greater_equal<T>>();
cmp_less = compare_nan_end<T, std::less<T>>();
cmp_leq = compare_nan_end<T, std::less_equal<T>>();
cmp_geq = compare_nan_end<T, std::greater_equal<T>>();
}
else {
cmp_less = compare<T, std::greater<T>>();
cmp_leq = compare<T, std::greater_equal<T>>();
cmp_geq = compare<T, std::less_equal<T>>();
cmp_less = compare_nan_end<T, std::greater<T>>();
cmp_leq = compare_nan_end<T, std::greater_equal<T>>();
cmp_geq = compare_nan_end<T, std::less_equal<T>>();
}

@@ -78,2 +78,7 @@

}
// If arr[k] is NaN, k is in the trailing NaN block; value comparisons are
// not meaningful, so skip the left/right partition checks.
if constexpr (xss::fp::is_floating_point_v<T>) {
if (xss::fp::isnan(arr[k])) return;
}
// ( 2) Elements to the left of k should be atmost arr[k]

@@ -113,3 +118,4 @@ if (k >= 1) {

size_t k,
std::string type)
std::string type,
bool descending = false)
{

@@ -121,4 +127,4 @@ EXPECT_UNIQUE(arg)

}
IS_ARR_PARTITIONED(part_arr, k, true_kth, type);
IS_ARR_PARTITIONED(part_arr, k, true_kth, type, descending);
}
#endif

@@ -79,3 +79,3 @@ /*******************************************

sortedarr.end(),
compare<TypeParam, std::greater<TypeParam>>());
compare_nan_end<TypeParam, std::greater<TypeParam>>());
IS_SORTED(sortedarr, arr, type);

@@ -123,3 +123,3 @@ #endif

sortedarr.end(),
compare<TypeParam, std::greater<TypeParam>>());
compare_nan_end<TypeParam, std::greater<TypeParam>>());
IS_ARG_SORTED(sortedarr, arr, arg, type);

@@ -133,2 +133,88 @@ #endif

TYPED_TEST_P(simdsort, test_argsort_nans_last)
{
if constexpr (xss::fp::is_floating_point_v<TypeParam>) {
std::vector<std::string> nan_types
= {"rand_with_nan", "rand_with_max_and_nan"};
for (auto type : nan_types) {
for (auto size : this->arrsize_long) {
std::vector<TypeParam> base = get_array<TypeParam>(type, size);
// ascending, NaNs at end
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
auto arg = x86simdsort::argsort(
arr.data(), arr.size(), true, false, true);
#ifndef XSS_ASAN_CI_NOCHECK
std::sort(
sortedarr.begin(),
sortedarr.end(),
compare_nan_end<TypeParam, std::less<TypeParam>>());
IS_ARG_SORTED(sortedarr, arr, arg, type);
#endif
}
// descending, NaNs at end
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
auto arg = x86simdsort::argsort(
arr.data(), arr.size(), true, true, true);
#ifndef XSS_ASAN_CI_NOCHECK
std::sort(sortedarr.begin(),
sortedarr.end(),
compare_nan_end<TypeParam,
std::greater<TypeParam>>());
IS_ARG_SORTED(sortedarr, arr, arg, type);
#endif
}
}
}
}
}
TYPED_TEST_P(simdsort, test_argsort_leading_nans)
{
if constexpr (xss::fp::is_floating_point_v<TypeParam>) {
std::vector<std::string> nan_types
= {"rand_with_nan", "rand_with_max_and_nan"};
for (auto type : nan_types) {
for (auto size : this->arrsize_long) {
std::vector<TypeParam> base = get_array<TypeParam>(type, size);
// ascending, NaNs at start
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
auto arg = x86simdsort::argsort(
arr.data(), arr.size(), true, false, false);
#ifndef XSS_ASAN_CI_NOCHECK
std::sort(sortedarr.begin(),
sortedarr.end(),
compare_nan_begin<TypeParam,
std::less<TypeParam>>());
IS_ARG_SORTED(sortedarr, arr, arg, type);
#endif
}
// descending, NaNs at start
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
auto arg = x86simdsort::argsort(
arr.data(), arr.size(), true, true, false);
#ifndef XSS_ASAN_CI_NOCHECK
std::sort(sortedarr.begin(),
sortedarr.end(),
compare_nan_begin<TypeParam,
std::greater<TypeParam>>());
IS_ARG_SORTED(sortedarr, arr, arg, type);
#endif
}
}
}
}
}
TYPED_TEST_P(simdsort, test_qselect_ascending)

@@ -175,6 +261,7 @@ {

#ifndef XSS_ASAN_CI_NOCHECK
std::nth_element(sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare<TypeParam, std::greater<TypeParam>>());
std::nth_element(
sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare_nan_end<TypeParam, std::greater<TypeParam>>());
if (size == 0) continue;

@@ -189,4 +276,102 @@ IS_ARR_PARTITIONED(arr, k, sortedarr[k], type, true);

TYPED_TEST_P(simdsort, test_argselect)
TYPED_TEST_P(simdsort, test_qselect_nans_last)
{
if constexpr (xss::fp::is_floating_point_v<TypeParam>) {
std::vector<std::string> nan_types
= {"rand_with_nan", "rand_with_max_and_nan"};
for (auto type : nan_types) {
for (auto size : this->arrsize) {
size_t k = size != 0 ? rand() % size : 0;
std::vector<TypeParam> base = get_array<TypeParam>(type, size);
// ascending, NaNs at end
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
x86simdsort::qselect(
arr.data(), k, arr.size(), true, false, true);
#ifndef XSS_ASAN_CI_NOCHECK
std::nth_element(
sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare_nan_end<TypeParam, std::less<TypeParam>>());
if (size == 0) continue;
IS_ARR_PARTITIONED(arr, k, sortedarr[k], type);
#endif
}
// descending, NaNs at end
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
x86simdsort::qselect(
arr.data(), k, arr.size(), true, true, true);
#ifndef XSS_ASAN_CI_NOCHECK
std::nth_element(
sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare_nan_end<TypeParam,
std::greater<TypeParam>>());
if (size == 0) continue;
IS_ARR_PARTITIONED(arr, k, sortedarr[k], type, true);
#endif
}
}
}
}
}
TYPED_TEST_P(simdsort, test_qselect_leading_nans)
{
if constexpr (xss::fp::is_floating_point_v<TypeParam>) {
std::vector<std::string> nan_types
= {"rand_with_nan", "rand_with_max_and_nan"};
for (auto type : nan_types) {
for (auto size : this->arrsize) {
size_t k = size != 0 ? rand() % size : 0;
std::vector<TypeParam> base = get_array<TypeParam>(type, size);
// ascending, NaNs at start
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
x86simdsort::qselect(
arr.data(), k, arr.size(), true, false, false);
#ifndef XSS_ASAN_CI_NOCHECK
std::nth_element(sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare_nan_begin<TypeParam,
std::less<TypeParam>>());
if (size == 0) continue;
IS_ARR_PARTITIONED(arr, k, sortedarr[k], type);
#endif
}
// descending, NaNs at start
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
x86simdsort::qselect(
arr.data(), k, arr.size(), true, true, false);
#ifndef XSS_ASAN_CI_NOCHECK
std::nth_element(
sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare_nan_begin<TypeParam,
std::greater<TypeParam>>());
if (size == 0) continue;
IS_ARR_PARTITIONED(arr, k, sortedarr[k], type, true);
#endif
}
}
}
}
}
TYPED_TEST_P(simdsort, test_argselect_ascending)
{
for (auto type : this->arrtype) {

@@ -214,2 +399,126 @@ bool hasnan = is_nan_test(type);

TYPED_TEST_P(simdsort, test_argselect_descending)
{
for (auto type : this->arrtype) {
bool hasnan = is_nan_test(type);
for (auto size : this->arrsize) {
size_t k = size != 0 ? rand() % size : 0;
std::vector<TypeParam> arr = get_array<TypeParam>(type, size);
std::vector<TypeParam> sortedarr = arr;
auto arg = x86simdsort::argselect(
arr.data(), k, arr.size(), hasnan, true);
#ifndef XSS_ASAN_CI_NOCHECK
std::nth_element(
sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare_nan_end<TypeParam, std::greater<TypeParam>>());
if (size == 0) continue;
IS_ARG_PARTITIONED(arr, arg, sortedarr[k], k, type, true);
#endif
arr.clear();
sortedarr.clear();
}
}
}
TYPED_TEST_P(simdsort, test_argselect_nans_last)
{
if constexpr (xss::fp::is_floating_point_v<TypeParam>) {
std::vector<std::string> nan_types
= {"rand_with_nan", "rand_with_max_and_nan"};
for (auto type : nan_types) {
for (auto size : this->arrsize) {
size_t k = size != 0 ? rand() % size : 0;
std::vector<TypeParam> base = get_array<TypeParam>(type, size);
// ascending, NaNs at end
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
auto arg = x86simdsort::argselect(
arr.data(), k, arr.size(), true, false, true);
#ifndef XSS_ASAN_CI_NOCHECK
std::nth_element(
sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare_nan_end<TypeParam, std::less<TypeParam>>());
if (size == 0) continue;
IS_ARG_PARTITIONED(arr, arg, sortedarr[k], k, type);
#endif
}
// descending, NaNs at end
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
auto arg = x86simdsort::argselect(
arr.data(), k, arr.size(), true, true, true);
#ifndef XSS_ASAN_CI_NOCHECK
std::nth_element(
sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare_nan_end<TypeParam,
std::greater<TypeParam>>());
if (size == 0) continue;
IS_ARG_PARTITIONED(arr, arg, sortedarr[k], k, type, true);
#endif
}
}
}
}
}
TYPED_TEST_P(simdsort, test_argselect_leading_nans)
{
if constexpr (xss::fp::is_floating_point_v<TypeParam>) {
std::vector<std::string> nan_types
= {"rand_with_nan", "rand_with_max_and_nan"};
for (auto type : nan_types) {
for (auto size : this->arrsize) {
size_t k = size != 0 ? rand() % size : 0;
std::vector<TypeParam> base = get_array<TypeParam>(type, size);
// ascending, NaNs at start
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
auto arg = x86simdsort::argselect(
arr.data(), k, arr.size(), true, false, false);
#ifndef XSS_ASAN_CI_NOCHECK
std::nth_element(sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare_nan_begin<TypeParam,
std::less<TypeParam>>());
if (size == 0) continue;
IS_ARG_PARTITIONED(arr, arg, sortedarr[k], k, type);
#endif
}
// descending, NaNs at start
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
auto arg = x86simdsort::argselect(
arr.data(), k, arr.size(), true, true, false);
#ifndef XSS_ASAN_CI_NOCHECK
std::nth_element(
sortedarr.begin(),
sortedarr.begin() + k,
sortedarr.end(),
compare_nan_begin<TypeParam,
std::greater<TypeParam>>());
if (size == 0) continue;
IS_ARG_PARTITIONED(arr, arg, sortedarr[k], k, type, true);
#endif
}
}
}
}
}
TYPED_TEST_P(simdsort, test_partial_qsort_ascending)

@@ -257,3 +566,3 @@ {

sortedarr.end(),
compare<TypeParam, std::greater<TypeParam>>());
compare_nan_end<TypeParam, std::greater<TypeParam>>());
if (size == 0) continue;

@@ -301,10 +610,105 @@ IS_ARR_PARTIALSORTED(arr, k, sortedarr, type);

TYPED_TEST_P(simdsort, test_qsort_nans_last)
{
if constexpr (xss::fp::is_floating_point_v<TypeParam>) {
std::vector<std::string> nan_types
= {"rand_with_nan", "rand_with_max_and_nan"};
for (auto type : nan_types) {
for (auto size : this->arrsize_long) {
std::vector<TypeParam> base = get_array<TypeParam>(type, size);
// ascending, NaNs at end
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
x86simdsort::qsort(
arr.data(), arr.size(), true, false, true);
#ifndef XSS_ASAN_CI_NOCHECK
std::sort(
sortedarr.begin(),
sortedarr.end(),
compare_nan_end<TypeParam, std::less<TypeParam>>());
IS_SORTED(sortedarr, arr, type);
#endif
}
// descending, NaNs at end
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
x86simdsort::qsort(
arr.data(), arr.size(), true, true, true);
#ifndef XSS_ASAN_CI_NOCHECK
std::sort(sortedarr.begin(),
sortedarr.end(),
compare_nan_end<TypeParam,
std::greater<TypeParam>>());
IS_SORTED(sortedarr, arr, type);
#endif
}
}
}
}
}
TYPED_TEST_P(simdsort, test_qsort_leading_nans)
{
if constexpr (xss::fp::is_floating_point_v<TypeParam>) {
std::vector<std::string> nan_types
= {"rand_with_nan", "rand_with_max_and_nan"};
for (auto type : nan_types) {
for (auto size : this->arrsize_long) {
std::vector<TypeParam> base = get_array<TypeParam>(type, size);
// ascending, NaNs at start
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
x86simdsort::qsort(
arr.data(), arr.size(), true, false, false);
#ifndef XSS_ASAN_CI_NOCHECK
std::sort(sortedarr.begin(),
sortedarr.end(),
compare_nan_begin<TypeParam,
std::less<TypeParam>>());
IS_SORTED(sortedarr, arr, type);
#endif
}
// descending, NaNs at start
{
std::vector<TypeParam> arr = base;
std::vector<TypeParam> sortedarr = base;
x86simdsort::qsort(
arr.data(), arr.size(), true, true, false);
#ifndef XSS_ASAN_CI_NOCHECK
std::sort(sortedarr.begin(),
sortedarr.end(),
compare_nan_begin<TypeParam,
std::greater<TypeParam>>());
IS_SORTED(sortedarr, arr, type);
#endif
}
}
}
}
}
REGISTER_TYPED_TEST_SUITE_P(simdsort,
test_qsort_ascending,
test_qsort_descending,
test_qsort_nans_last,
test_qsort_leading_nans,
test_argsort_ascending,
test_argsort_descending,
test_argselect,
test_argsort_nans_last,
test_argsort_leading_nans,
test_argselect_ascending,
test_argselect_descending,
test_argselect_nans_last,
test_argselect_leading_nans,
test_qselect_ascending,
test_qselect_descending,
test_qselect_nans_last,
test_qselect_leading_nans,
test_partial_qsort_ascending,

@@ -311,0 +715,0 @@ test_partial_qsort_descending,

@@ -49,2 +49,66 @@ #ifndef UTILS_CUSTOM_COMPARE

/*
* Comparator that always places NaN at the end of the sorted array,
* regardless of whether Comparator is ascending or descending.
*/
template <typename T, typename Comparator>
struct compare_nan_end {
static constexpr auto op = Comparator {};
bool operator()(const T a, const T b) const
{
if constexpr (xss::fp::is_floating_point_v<T>) {
bool a_nan = xss::fp::isnan(a);
bool b_nan = xss::fp::isnan(b);
if (!a_nan && !b_nan) { return op(a, b); }
if (a_nan && b_nan) { return false; }
return !a_nan; // b is NaN → a before b → NaN at end
}
else {
return op(a, b);
}
}
};
/*
* Comparator that always places NaN at the beginning of the sorted array,
* regardless of whether Comparator is ascending or descending.
*/
template <typename T, typename Comparator>
struct compare_nan_begin {
static constexpr auto op = Comparator {};
bool operator()(const T a, const T b) const
{
if constexpr (xss::fp::is_floating_point_v<T>) {
bool a_nan = xss::fp::isnan(a);
bool b_nan = xss::fp::isnan(b);
if (!a_nan && !b_nan) { return op(a, b); }
if (a_nan && b_nan) { return false; }
return a_nan; // a is NaN → a before b → NaN at beginning
}
else {
return op(a, b);
}
}
};
template <typename T, typename Comparator>
struct compare_arg_nan_end {
compare_arg_nan_end(const T *arr) : arr(arr) {}
bool operator()(const int64_t a, const int64_t b) const
{
return compare_nan_end<T, Comparator>()(arr[a], arr[b]);
}
const T *arr;
};
template <typename T, typename Comparator>
struct compare_arg_nan_begin {
compare_arg_nan_begin(const T *arr) : arr(arr) {}
bool operator()(const int64_t a, const int64_t b) const
{
return compare_nan_begin<T, Comparator>()(arr[a], arr[b]);
}
const T *arr;
};
#endif // UTILS_CUSTOM_COMPARE

@@ -161,5 +161,5 @@ /*

/* Don't copy PyObject_HEAD part */
memcpy((char *)new + sizeof(PyObject),
(char *)self + sizeof(PyObject),
sizeof(PyArray_SFloatDescr) - sizeof(PyObject));
memcpy((char *)new + offsetof(PyArray_Descr, typeobj),
(char *)self + offsetof(PyArray_Descr, typeobj),
sizeof(PyArray_SFloatDescr) - offsetof(PyArray_Descr, typeobj));

@@ -166,0 +166,0 @@ new->scaling = new->scaling * factor;

@@ -404,19 +404,21 @@ from typing import overload

type _tuple3[T] = tuple[T, T, T]
@overload
def partition(a: U_co, sep: U_co) -> NDArray[np.str_]: ...
def partition(a: U_co, sep: U_co) -> _tuple3[NDArray[np.str_]]: ...
@overload
def partition(a: S_co, sep: S_co) -> NDArray[np.bytes_]: ...
def partition(a: S_co, sep: S_co) -> _tuple3[NDArray[np.bytes_]]: ...
@overload
def partition(a: _StringDTypeSupportsArray, sep: _StringDTypeSupportsArray) -> _StringDTypeArray: ...
def partition(a: _StringDTypeSupportsArray, sep: _StringDTypeSupportsArray) -> _tuple3[_StringDTypeArray]: ...
@overload
def partition(a: T_co, sep: T_co) -> _StringDTypeOrUnicodeArray: ...
def partition(a: T_co, sep: T_co) -> _tuple3[_StringDTypeOrUnicodeArray]: ...
@overload
def rpartition(a: U_co, sep: U_co) -> NDArray[np.str_]: ...
def rpartition(a: U_co, sep: U_co) -> _tuple3[NDArray[np.str_]]: ...
@overload
def rpartition(a: S_co, sep: S_co) -> NDArray[np.bytes_]: ...
def rpartition(a: S_co, sep: S_co) -> _tuple3[NDArray[np.bytes_]]: ...
@overload
def rpartition(a: _StringDTypeSupportsArray, sep: _StringDTypeSupportsArray) -> _StringDTypeArray: ...
def rpartition(a: _StringDTypeSupportsArray, sep: _StringDTypeSupportsArray) -> _tuple3[_StringDTypeArray]: ...
@overload
def rpartition(a: T_co, sep: T_co) -> _StringDTypeOrUnicodeArray: ...
def rpartition(a: T_co, sep: T_co) -> _tuple3[_StringDTypeOrUnicodeArray]: ...

@@ -423,0 +425,0 @@ @overload

@@ -449,2 +449,14 @@ import sys

def test_copyto_overlapping_where_false_no_leak():
# gh-31968: overlapping copyto with scalar where=False used to leak the
# overlap temp (missing Py_DECREF on the early-return path). No refcount
# assertion is made here; a leak sanitizer build is expected to catch
# the regression.
a = np.arange(36, dtype=object).reshape(6, 6)
original = a.copy()
np.copyto(a, a[::-1, :], where=False)
# where=False must write nothing
assert_array_equal(a, original)
def test_copyto_cast_safety():

@@ -451,0 +463,0 @@ with pytest.raises(TypeError):

@@ -98,5 +98,3 @@ """

# Bool:
# XFAIL: Bool should be added, but has some bad properties when it
# comes to strings, see also gh-9875
# yield param(np.bool(0), id="bool")
yield param(np.bool(0), id="bool")

@@ -103,0 +101,0 @@ # Integers:

@@ -284,2 +284,19 @@ import os

)
@pytest.mark.parametrize("action", ["ENABLE", "DISABLE"])
def test_max_length_nonexistent_feature(self, action):
"""
Test that the maximum accepted environment variable length can be
processed even if the whole value is an invalid feature name.
"""
MAX_VAR_LENGTH = 1024
self.env[f'NPY_{action}_CPU_FEATURES'] = "t" * (MAX_VAR_LENGTH - 1)
self._run()
@pytest.mark.skipif(
not __cpu_dispatch__,
reason=(
"NPY_*_CPU_FEATURES only parsed if "
"`__cpu_dispatch__` is non-empty"
)
)
def test_impossible_feature_disable(self):

@@ -339,3 +356,25 @@ """

@pytest.mark.parametrize("action", ["ENABLE", "DISABLE"])
def test_repeated_unavailable_feature(self, action):
"""
Test that repeated unavailable features can be processed without
overflowing the diagnostic buffer.
"""
if self.UNAVAILABLE_FEAT is None:
pytest.skip("There are no unavailable features to test with")
bad_feature = self.UNAVAILABLE_FEAT
feature_repeats = 1024 // (len(bad_feature) + 1)
self.env[f'NPY_{action}_CPU_FEATURES'] = ",".join(
[bad_feature] * feature_repeats
)
if action == "ENABLE":
msg = "You cannot enable CPU features"
err_type = "RuntimeError"
self._expect_error(msg, err_type)
else:
self._run()
is_linux = sys.platform.startswith('linux')

@@ -342,0 +381,0 @@ is_cygwin = sys.platform.startswith('cygwin')

@@ -47,2 +47,94 @@ from tempfile import NamedTemporaryFile

def test_structured_field_byteswap_raises(self):
# a field (or subarray field) whose dtype lacks the legacy copyswap
# slot cannot be byteswapped; previously this segfaulted via the
# unguarded field copyswap calls in VOID_copyswapn
arr = np.zeros(2, dtype=[("a", "i4"), ("v", SF(1.))])
with pytest.raises(TypeError, match="does not implement copyswap"):
arr.byteswap()
subarr = np.zeros(2, dtype=[("v", SF(1.), (2,))])
with pytest.raises(TypeError, match="does not implement copyswap"):
subarr.byteswap()
def test_byteswap_raises(self):
# a top-level DType without the legacy copyswap slot does not
# support byteswapping
arr = np.zeros(2, dtype=SF(1.))
with pytest.raises(TypeError, match="does not implement copyswap"):
arr.byteswap()
# the in-place writeability check fires before the missing-slot
# check, matching the behavior for dtypes that fill the slot
arr.flags.writeable = False
with pytest.raises(ValueError, match="array to be byte-swapped"):
arr.byteswap(inplace=True)
def test_structured_field_place_and_flat_raise(self):
# requests that need to copy through the missing legacy copyswap
# slot raise instead of crashing
arr = np.zeros(2, dtype=[("v", SF(1.))])
with pytest.raises(TypeError, match="does not implement copyswap"):
np.place(arr, [True, False], arr[:1])
with pytest.raises(TypeError, match="does not implement copyswap"):
arr.flat = arr[:1]
# both stop copying as soon as the error is detected, so elements
# after the first (failing) one keep their values, even for fields
# that could have been copied
marr = np.zeros(3, dtype=[("a", "i4"), ("v", SF(1.))])
marr["a"] = [5, 6, 7]
src = marr[:1].copy()
src["a"] = 1
with pytest.raises(TypeError, match="does not implement copyswap"):
marr.flat = src
assert marr["a"].tolist()[1:] == [6, 7]
with pytest.raises(TypeError, match="does not implement copyswap"):
np.place(marr, [True, True, True], src)
assert marr["a"].tolist()[1:] == [6, 7]
def test_structured_field_sort_raises(self):
# VOID_compare called the field's legacy compare slot, which
# new-style DTypes do not fill
arr = np.zeros(3, dtype=[("v", SF(1.))])
with pytest.raises(TypeError, match="does not support comparison"):
np.sort(arr, order="v")
# packed fields are misaligned; the compare == NULL check fires
# before VOID_compare's alignment handling, so this exits through
# the same guard as the aligned case above and never reaches the
# misaligned buffer path (which would call the missing copyswap
# slot); it checks that the guard keeps covering that layout
packed_dt = np.dtype({"names": ["a", "v"],
"formats": ["u1", SF(1.)],
"offsets": [0, 1], "itemsize": 17})
with pytest.raises(TypeError, match="does not support comparison"):
np.sort(np.zeros(3, dtype=packed_dt), order="v")
def test_structured_setitem_uses_cast_path(self):
# scalar assignment between equivalent structured dtypes used to
# segfault in the copyswap fast path; it now falls back to casting
arr = np.zeros(2, dtype=[("v", SF(1.))])
arr["v"] = np.array([1., 2.]).view(SF(1.))
arr[0] = arr[1]
assert arr["v"].view(np.float64).tolist() == [2., 2.]
def test_structured_setitem_nested_uses_cast_path(self):
# for a nested field the copyswap slot is VOID_copyswap, which
# fails on the inner sfloat field only after it is called; the
# error must not leak out of a successful-looking assignment and
# the copy falls back to the casting path like the flat case
arr = np.zeros(2, dtype=[("a", "i4"), ("nested", [("v", SF(1.))])])
arr["a"] = [1, 2]
arr["nested"]["v"] = np.array([1., 2.]).view(SF(1.))
arr[0] = arr[1]
assert arr["a"].tolist() == [2, 2]
assert arr["nested"]["v"].view(np.float64).tolist() == [2., 2.]
# same for a subarray field of an sfloat dtype
arr = np.zeros(2, dtype=[("sub", SF(1.), (2,))])
arr["sub"] = np.array([[1., 2.], [3., 4.]]).view(SF(1.))
arr[0] = arr[1]
assert arr["sub"].view(np.float64).tolist() == [[3., 4.], [3., 4.]]
def test_repr(self):

@@ -49,0 +141,0 @@ # Check the repr, mainly to cover the code paths:

@@ -24,3 +24,3 @@ import os

# Note: keep in sync with the one in pyproject.toml
required_version = "3.0.6"
required_version = "3.1.0"
if _pep440.parse(cython_version) < _pep440.Version(required_version):

@@ -27,0 +27,0 @@ # too old or wrong cython, skip the test

@@ -0,1 +1,2 @@

import importlib
import os

@@ -9,2 +10,3 @@ import shutil

import numpy as np
from numpy.testing import IS_EDITABLE, IS_WASM, NOGIL_BUILD

@@ -23,3 +25,3 @@ from numpy.testing._private.utils import run_subprocess

# Note: keep in sync with the one in pyproject.toml
required_version = "3.0.6"
required_version = "3.1.0"
if _pep440.parse(cython_version) < _pep440.Version(required_version):

@@ -87,4 +89,122 @@ # too old or wrong cython, skip the test

@pytest.mark.skipif(IS_WASM, reason="Can't start subprocess")
@pytest.mark.xfail(
def _check_api_module(mod, cython=False):
arr = np.ones((200, 200))
assert mod.nonzero(arr) == 200 * 200
# Legacy single-array iterator: PyArray_ITER_NEXT / _DATA / _NOTDONE.
arr = np.arange(12.0).reshape(3, 4)
assert mod.iter_next(arr) == 66.0
assert mod.iter_goto1d(arr, 5) == 5.0
assert mod.iter_goto1d(arr, -1) == 11.0
assert mod.iter_reset(arr) == 66.0
assert mod.iter_goto(arr, (1, 2)) == 6.0
assert mod.iter_goto(arr, (2, 3)) == 11.0
# Broadcasting multi-iterator.
a = np.arange(3.0).reshape(3, 1)
b = np.arange(4.0).reshape(1, 4)
assert mod.multi_iter_next(a, b) == float(np.sum(a + b))
if not cython:
va, vb = mod.multi_iter_goto(a, b, (1, 2))
assert va == 1.0 and vb == 2.0
va, vb = mod.multi_iter_goto1d(a, b, 6)
assert va == 1.0 and vb == 2.0
a = np.arange(6.0).reshape(2, 3)
b = np.zeros((2, 3))
assert mod.multi_iter_nexti(a, b, 3) == 3.0
if cython:
# Datetime / timedelta scalar accessors (.pxd helpers).
dt = np.datetime64("2021-01-01", "D")
assert mod.get_datetime_value(dt) == dt.astype("int64")
assert mod.get_datetime_unit(dt) == 4
assert mod.is_datetime64(dt)
assert not mod.is_timedelta64(dt)
# A plain seconds timedelta: base NPY_FR_s, unit multiplier 1.
td = np.timedelta64(5, "s")
assert mod.get_timedelta_value(td) == 5
assert mod.is_timedelta64(td)
assert not mod.is_datetime64(td)
# A non-unit multiplier exercises the metadata `num` field.
td = np.timedelta64(1000, "ms").astype("timedelta64[10ms]")
assert mod.get_timedelta_value(td) == td.astype("int64")
else:
# PyDataType_FLAGS / PyDataType_C_METADATA on datetime descriptors.
dt = np.array(["2021-01-01"], dtype="datetime64[D]")
flags, base, num = mod.datetime_metadata(dt)
assert flags == dt.dtype.flags
assert base == 4 # NPY_FR_D
assert num == 1
# A plain seconds timedelta: base NPY_FR_s, unit multiplier 1.
td = np.array([5], dtype="timedelta64[s]")
flags, base, num = mod.datetime_metadata(td)
assert flags == td.dtype.flags
assert base == 7 # NPY_FR_s
assert num == 1
# A non-unit multiplier exercises the metadata `num` field.
td = np.array([1000], dtype="timedelta64[ms]").astype(
"timedelta64[10ms]")
flags, base, num = mod.datetime_metadata(td)
assert flags == td.dtype.flags
assert base == 8 # NPY_FR_ms
assert num == 10
# Non-datetime descriptors have no c_metadata and are rejected.
with pytest.raises(RuntimeError):
mod.datetime_metadata(np.arange(3))
# NpyString allocator API; under abi3t PyArray_StringDTypeObject is
# opaque and only the descriptor object pointer is passed. Absent
# when the module targets NumPy < 2.0 (the "default" target).
if hasattr(mod, "stringdtype_load"):
arr = np.array(["hello", "world"], dtype=np.dtypes.StringDType())
assert mod.stringdtype_load(arr) == "hello"
# A long string is stored on the heap, so loading it
# dereferences the allocator acquired from the descriptor.
text = "numpy" * 20
arr = np.array([text, "world"], dtype=np.dtypes.StringDType())
assert mod.stringdtype_load(arr) == text
arr = np.array([None, "world"],
dtype=np.dtypes.StringDType(na_object=None))
assert mod.stringdtype_load(arr) is None
# Test limited API extension modules for all supported Python and NumPy versions
# The _PY_ABI3_VERSIONS and _NPY_TARGET_VERSIONS lists should be kept in sync
# with the lists defined in meson.build, and the test should be updated
# if new versions are added here.
# The special "default" entry builds without defining NPY_TARGET_VERSION at all,
# which exercises the path where numpyconfig.h picks the current API version.
_PY_ABI3_VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
_NPY_TARGET_VERSIONS = ["2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "default"]
def _module_names(prefix, abi3_versions):
names = []
for py_ver in abi3_versions:
if sys.version_info < tuple(map(int, py_ver.split('.'))):
continue
py = py_ver.replace('.', '_')
for npy_ver in _NPY_TARGET_VERSIONS:
npy = npy_ver.replace('.', '_')
names.append(f"{prefix}_{py}_npy{npy}")
return names
def limited_api_module_names():
return _module_names("limited_api", _PY_ABI3_VERSIONS)
def limited_api_cython_module_names():
return _module_names("limited_api_cython", _PY_ABI3_VERSIONS)
@pytest.mark.skipif(
IS_WASM, reason="platform cannot start subprocesses"
)
@pytest.mark.skipif(
sysconfig.get_config_var("Py_DEBUG"),

@@ -96,63 +216,43 @@ reason=(

)
@pytest.mark.xfail(
@pytest.mark.skipif(
NOGIL_BUILD,
reason="Py_GIL_DISABLED builds do not currently support the limited API",
reason="Py_GIL_DISABLED builds do not support abi3",
)
def test_limited_api(install_temp):
"""Test building a third-party C extension with the limited API
and building a cython extension with the limited API
"""
@pytest.mark.parametrize("module_name", limited_api_module_names())
def test_limited_api_abi3(install_temp, module_name):
mod = importlib.import_module(module_name)
_check_api_module(mod)
import limited_api1 # Earliest (3.6) # noqa: F401
import limited_api2 # cython # noqa: F401
import limited_api_latest # Latest version (current Python) # noqa: F401
@pytest.mark.skipif(
IS_WASM, reason="platform cannot start subprocesses"
)
@pytest.mark.skipif(
sysconfig.get_config_var("Py_DEBUG"),
reason=(
"Py_LIMITED_API is incompatible with Py_DEBUG, Py_TRACE_REFS, "
"and Py_REF_DEBUG"
),
)
@pytest.mark.skipif(
NOGIL_BUILD,
reason="Py_GIL_DISABLED builds do not support abi3",
)
@pytest.mark.parametrize("module_name", limited_api_cython_module_names())
def test_limited_api_cython(install_temp, module_name):
mod = importlib.import_module(module_name)
_check_api_module(mod, cython=True)
@pytest.mark.skipif(
sys.version_info < (3, 15), reason="opaque PyObject requires Python 3.15+"
)
@pytest.mark.skipif(
sys.platform == "win32" and not sysconfig.get_config_var('Py_GIL_DISABLED'),
reason=("Meson does not yet support building abi3t extensions on the "
"GIL-enabled build")
)
def test_limited_opaque(install_temp):
import limited_api_opaque
import numpy as np
arr = np.ones((200, 200))
assert limited_api_opaque.nonzero(arr) == 200 * 200
# Test PyArray_ITER_NEXT / PyArray_ITER_DATA / PyArray_ITER_NOTDONE
arr = np.arange(12.0).reshape(3, 4)
assert limited_api_opaque.iter_next(arr) == 66.0
# Test PyArray_ITER_GOTO1D
assert limited_api_opaque.iter_goto1d(arr, 5) == 5.0
assert limited_api_opaque.iter_goto1d(arr, -1) == 11.0
# Test PyArray_ITER_RESET
assert limited_api_opaque.iter_reset(arr) == 66.0
# Test PyArray_MultiIter_NEXT / RESET / DATA with broadcasting
a = np.arange(3.0).reshape(3, 1) # shape (3, 1)
b = np.arange(4.0).reshape(1, 4) # shape (1, 4)
# Each broadcast element is a[i] + b[j], total sum:
expected = float(np.sum(a + b))
assert limited_api_opaque.multi_iter_next(a, b) == expected
# Test PyArray_ITER_GOTO
arr = np.arange(12.0).reshape(3, 4)
assert limited_api_opaque.iter_goto(arr, (1, 2)) == 6.0
assert limited_api_opaque.iter_goto(arr, (2, 3)) == 11.0
# Test PyArray_MultiIter_GOTO
a = np.arange(3.0).reshape(3, 1)
b = np.arange(4.0).reshape(1, 4)
va, vb = limited_api_opaque.multi_iter_goto(a, b, (1, 2))
assert va == 1.0 and vb == 2.0
# Test PyArray_MultiIter_GOTO1D
# flat index 6 in (3,4) broadcast → row 1, col 2
va, vb = limited_api_opaque.multi_iter_goto1d(a, b, 6)
assert va == 1.0 and vb == 2.0
# Test PyArray_MultiIter_NEXTi
a = np.arange(6.0).reshape(2, 3)
b = np.zeros((2, 3))
# Advance iter 0 by 3 steps → flat index 3 → value 3.0
assert limited_api_opaque.multi_iter_nexti(a, b, 3) == 3.0
_check_api_module(limited_api_opaque)
import concurrent.futures
import inspect
import random
import subprocess

@@ -43,2 +44,44 @@ import sys

def test_parallel_seed_get_state():
seeds = [12345, 67890]
rng = np.random.RandomState()
expected = []
for seed in seeds:
rng.seed(seed)
expected.append(rng.get_state()[1])
def func(i, barrier):
rnd = random.Random(i)
barrier.wait()
for _ in range(100):
if rnd.randrange(2):
rng.seed(rnd.choice(seeds))
else:
key = rng.get_state()[1]
assert any((key == exp).all() for exp in expected)
run_threaded(func, pass_count=True, pass_barrier=True)
@pytest.mark.parametrize(
"bitgen_name", ["MT19937", "PCG64", "PCG64DXSM", "Philox", "SFC64"])
def test_parallel_bit_generator_state_access(bitgen_name):
bg = getattr(np.random, bitgen_name)(12345)
gen = np.random.Generator(bg)
def func(i, barrier):
rnd = random.Random(i)
barrier.wait()
for _ in range(100):
branch = rnd.randrange(3)
if branch == 0:
bg.state = bg.state
elif branch == 1:
gen.random(10)
elif hasattr(bg, "advance"):
bg.advance(10)
else:
_ = bg.state
run_threaded(func, pass_count=True, pass_barrier=True)
def test_parallel_ufunc_execution():

@@ -45,0 +88,0 @@ # if the loop data cache or dispatch cache are not thread-safe

@@ -473,2 +473,28 @@ import itertools

def test_isdtype_string_dtype(self):
# gh-27545: StringDType has no NumPy scalar type, but it is a
# built-in dtype, so `isdtype` must accept it rather than raise
dt = np.dtypes.StringDType()
assert not np.isdtype(dt, "bool")
for kind in self.dtype_group_dict:
assert not np.isdtype(dt, kind)
# matches itself and the StringDType class, which stands in for
# the scalar type that identifies the other dtypes
assert np.isdtype(dt, dt)
assert np.isdtype(dt, np.dtypes.StringDType)
assert np.isdtype(np.dtypes.StringDType, dt)
assert np.isdtype(dt, ("numeric", np.dtypes.StringDType))
assert not np.isdtype(np.int64, dt)
assert not np.isdtype(dt, ("integral", np.int64))
# dtype parameters are ignored, like datetime64 units
assert np.isdtype(np.dtypes.StringDType(na_object=None), dt)
assert np.isdtype(dt, np.dtypes.StringDType(na_object=np.nan,
coerce=False))
# fixed-width unicode strings are a different kind
assert not np.isdtype(dt, np.str_)
assert not np.isdtype(np.dtype("U8"), dt)
def test_sctypes_complete(self):

@@ -475,0 +501,0 @@ # issue 26439: int32/intc were masking each other on 32-bit builds

@@ -249,3 +249,3 @@ from _typeshed import Incomplete

weights: _ArrayLikeFloat64_co | None = None,
) -> tuple[NDArray[np.float64], tuple[_Array1D[np.float64], ...]]: ...
) -> tuple[NDArray[np.float64], list[_Array1D[np.float64]]]: ...
@overload # dtype ~complex

@@ -258,3 +258,3 @@ def histogramdd(

weights: _ArrayLikeFloat64_co | None = None,
) -> tuple[NDArray[np.float64], tuple[_Array1D[np.complex128], ...]]: ...
) -> tuple[NDArray[np.float64], list[_Array1D[np.complex128]]]: ...
@overload # dtype known

@@ -267,3 +267,3 @@ def histogramdd[ScalarT: np.inexact](

weights: _ArrayLikeFloat64_co | None = None,
) -> tuple[NDArray[np.float64], tuple[_Array1D[ScalarT], ...]]: ...
) -> tuple[NDArray[np.float64], list[_Array1D[ScalarT]]]: ...
@overload # dtype unknown

@@ -276,2 +276,2 @@ def histogramdd(

weights: _ArrayLikeFloat64_co | None = None,
) -> tuple[NDArray[np.float64], tuple[_Array1D[Any], ...]]: ...
) -> tuple[NDArray[np.float64], list[_Array1D[Any]]]: ...

@@ -43,2 +43,4 @@ from _typeshed import Incomplete

type _AtLeast2D = tuple[int, int, *tuple[int, ...]] # input only
type _Array1D[ScalarT: np.generic] = np.ndarray[tuple[int], np.dtype[ScalarT]]

@@ -196,4 +198,6 @@ type _Array2D[ScalarT: np.generic] = np.ndarray[tuple[int, int], np.dtype[ScalarT]]

@overload
def tril[ArrayT: np.ndarray](m: ArrayT, k: int = 0) -> ArrayT: ...
def tril[ArrayT: np.ndarray[_AtLeast2D]](m: ArrayT, k: int = 0) -> ArrayT: ...
@overload
def tril[DTypeT: np.dtype](m: np.ndarray[tuple[int], DTypeT], k: int = 0) -> np.ndarray[tuple[int, int], DTypeT]: ...
@overload
def tril[ScalarT: np.generic](m: _ArrayLike[ScalarT], k: int = 0) -> NDArray[ScalarT]: ...

@@ -205,4 +209,6 @@ @overload

@overload
def triu[ArrayT: np.ndarray](m: ArrayT, k: int = 0) -> ArrayT: ...
def triu[ArrayT: np.ndarray[_AtLeast2D]](m: ArrayT, k: int = 0) -> ArrayT: ...
@overload
def triu[DTypeT: np.dtype](m: np.ndarray[tuple[int], DTypeT], k: int = 0) -> np.ndarray[tuple[int, int], DTypeT]: ...
@overload
def triu[ScalarT: np.generic](m: _ArrayLike[ScalarT], k: int = 0) -> NDArray[ScalarT]: ...

@@ -209,0 +215,0 @@ @overload

@@ -207,2 +207,12 @@ import pytest

def test_unravel_index_buffer_boundary(self):
indices = np.arange(10_000, dtype=np.intp)[:, None]
coords = np.unravel_index(indices, (100, 100))
assert_array_equal(
np.ravel_multi_index(coords, (100, 100)),
indices,
)
class TestGrid:

@@ -209,0 +219,0 @@ def test_basic(self):

@@ -199,4 +199,4 @@ from collections.abc import Iterable, Sequence

class SlogdetResult(NamedTuple, Generic[_FloatingOrArrayT_co, _InexactOrArrayT_co]):
sign: _FloatingOrArrayT_co
logabsdet: _InexactOrArrayT_co
sign: _InexactOrArrayT_co
logabsdet: _FloatingOrArrayT_co

@@ -966,39 +966,45 @@ # keep in sync with `solve`

) -> Any: ...
@overload # 2d known dtype, dtype=None
def trace[ScalarT: _to_complex](x: _ArrayLike2D[ScalarT], /, *, offset: SupportsIndex = 0, dtype: None = None) -> ScalarT: ...
@overload # 2d known dtype
def trace[ScalarT: np.inexact](x: _ArrayLike2D[ScalarT], /, *, offset: SupportsIndex = 0, dtype: None = None) -> ScalarT: ...
@overload # 2d, dtype=<given>
def trace[ScalarT: _to_complex](
x: _ToArrayComplex_2d, /, *, offset: SupportsIndex = 0, dtype: _DTypeLike[ScalarT]
) -> ScalarT: ...
@overload # 2d bool
def trace(x: _Sequence2D[bool], /, *, offset: SupportsIndex = 0, dtype: None = None) -> np.bool: ...
@overload # 2d int
def trace(x: Sequence[list[int]], /, *, offset: SupportsIndex = 0, dtype: None = None) -> np.int_: ...
@overload # 2d float
def trace[ScalarT: np.number](x: _ToArrayComplex_2d, /, *, offset: SupportsIndex = 0, dtype: _DTypeLike[ScalarT]) -> ScalarT: ...
@overload # 2d +int
def trace(
x: _ArrayLike2D[np.integer | np.bool] | Sequence[Sequence[int]], /, *, offset: SupportsIndex = 0, dtype: None = None
) -> np.int_: ...
@overload # 2d ~float
def trace(x: Sequence[list[float]], /, *, offset: SupportsIndex = 0, dtype: None = None) -> np.float64: ...
@overload # 2d complex
@overload # 2d ~complex
def trace(x: Sequence[list[complex]], /, *, offset: SupportsIndex = 0, dtype: None = None) -> np.complex128: ...
@overload # 3d known dtype, dtype=None
def trace[DTypeT: np.dtype[_to_complex]](
@overload # 3d known dtype
def trace[DTypeT: np.dtype[np.inexact]](
x: _SupportsArray[tuple[int, int, int], DTypeT], /, *, offset: SupportsIndex = 0, dtype: None = None
) -> np.ndarray[tuple[int], DTypeT]: ...
@overload # 3d +int
def trace(
x: _SupportsArray[tuple[int, int, int], np.dtype[np.integer | np.bool]], /, *, offset: SupportsIndex = 0, dtype: None = None
) -> np.ndarray[tuple[int], np.dtype[np.int_]]: ...
@overload # 3d, dtype=<given>
def trace[ScalarT: _to_complex](
def trace[ScalarT: np.number](
x: _ToArrayComplex_3d, /, *, offset: SupportsIndex = 0, dtype: _DTypeLike[ScalarT]
) -> _Array1D[ScalarT]: ...
@overload # 3d+ known dtype, dtype=None
def trace[DTypeT: np.dtype[_to_complex]](
@overload # 3d+ known dtype
def trace[DTypeT: np.dtype[np.inexact]](
x: _SupportsArray[_AtLeast3D, DTypeT], /, *, offset: SupportsIndex = 0, dtype: None = None
) -> np.ndarray[tuple[int, *tuple[Any, ...]], DTypeT]: ...
@overload # 3d+, dtype=<given>
def trace[ScalarT: _to_complex](
def trace[ScalarT: np.inexact](
x: _ArrayLike3ND[_to_complex] | _Sequence3ND[complex], /, *, offset: SupportsIndex = 0, dtype: _DTypeLike[ScalarT]
) -> np.ndarray[tuple[int, *tuple[Any, ...]], np.dtype[ScalarT]]: ...
@overload # 3d+ bool
def trace(x: _Sequence3ND[bool], /, *, offset: SupportsIndex = 0, dtype: None = None) -> NDArray[np.bool]: ...
@overload # 3d+ int
def trace(x: _Sequence2ND[list[int]], /, *, offset: SupportsIndex = 0, dtype: None = None) -> NDArray[np.int_]: ...
@overload # 3d+ float
@overload # 3d+ +integer
def trace(
x: _SupportsArray[_AtLeast3D, np.dtype[np.integer | np.bool]] | _Sequence2ND[Sequence[int]],
/,
*,
offset: SupportsIndex = 0,
dtype: None = None,
) -> NDArray[np.int_]: ...
@overload # 3d+ ~float
def trace(x: _Sequence2ND[list[float]], /, *, offset: SupportsIndex = 0, dtype: None = None) -> NDArray[np.float64]: ...
@overload # 3d+ complex
@overload # 3d+ ~complex
def trace(x: _Sequence2ND[list[complex]], /, *, offset: SupportsIndex = 0, dtype: None = None) -> NDArray[np.complex128]: ...

@@ -1005,0 +1011,0 @@ @overload # fallback

@@ -295,3 +295,6 @@ from _typeshed import Incomplete, SupportsLenAndGetItem

#
def count_masked(arr: ArrayLike, axis: SupportsIndex | None = None) -> NDArray[np.intp]: ...
@overload
def count_masked(arr: ArrayLike, axis: None = None) -> np.intp: ...
@overload
def count_masked(arr: ArrayLike, axis: SupportsIndex) -> NDArray[np.intp]: ...

@@ -298,0 +301,0 @@ #

@@ -691,7 +691,7 @@ # Aliases for builtins shadowed by classes to avoid annotations resolving to class members by ty

#
@overload # >=0d int, size=None (default)
@overload # 0d int, size=None (default)
def choice(
self,
/,
a: int | _NestedSequence[int],
a: int,
size: None = None,

@@ -703,2 +703,13 @@ replace: bool = True,

) -> int: ...
@overload # >0d int, size=None (default)
def choice(
self,
/,
a: _NestedSequence[int],
size: None = None,
replace: bool = True,
p: _ArrayLikeFloat_co | None = None,
axis: int = 0,
shuffle: bool = True,
) -> np.int_: ...
@overload # >=0d known, size=None (default)

@@ -705,0 +716,0 @@ def choice[ScalarT: np.generic](

@@ -195,3 +195,3 @@ #cython: binding=True

mt19937_init_by_array(&self.rng_state, <uint32_t*> obj.data, np.PyArray_DIM(obj, 0))
self._seed_seq = None
self._seed_seq = None

@@ -270,7 +270,8 @@ cdef jump_inplace(self, iter):

key = np.zeros(624, dtype=np.uint32)
for i in range(624):
key[i] = self.rng_state.key[i]
with self.lock:
for i in range(624):
key[i] = self.rng_state.key[i]
return {'bit_generator': self.__class__.__name__,
'state': {'key': key, 'pos': self.rng_state.pos}}
return {'bit_generator': self.__class__.__name__,
'state': {'key': key, 'pos': self.rng_state.pos}}

@@ -291,4 +292,5 @@ @state.setter

key = value['state']['key']
for i in range(624):
self.rng_state.key[i] = key[i]
self.rng_state.pos = value['state']['pos']
with self.lock:
for i in range(624):
self.rng_state.key[i] = key[i]
self.rng_state.pos = value['state']['pos']

@@ -209,5 +209,6 @@ #cython: binding=True

state_vec = <np.ndarray>np.empty(4, dtype=np.uint64)
pcg64_get_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
&has_uint32, &uinteger)
with self.lock:
pcg64_get_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
&has_uint32, &uinteger)
state = int(state_vec[0]) * 2**64 + int(state_vec[1])

@@ -237,5 +238,6 @@ inc = int(state_vec[2]) * 2**64 + int(state_vec[3])

uinteger = value['uinteger']
pcg64_set_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
has_uint32, uinteger)
with self.lock:
pcg64_set_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
has_uint32, uinteger)

@@ -283,4 +285,5 @@ def advance(self, delta):

d[1] = delta % 2**64
pcg64_advance(&self.rng_state, <uint64_t *>np.PyArray_DATA(d))
self._reset_state_variables()
with self.lock:
pcg64_advance(&self.rng_state, <uint64_t *>np.PyArray_DATA(d))
self._reset_state_variables()
return self

@@ -446,5 +449,6 @@

state_vec = <np.ndarray>np.empty(4, dtype=np.uint64)
pcg64_get_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
&has_uint32, &uinteger)
with self.lock:
pcg64_get_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
&has_uint32, &uinteger)
state = int(state_vec[0]) * 2**64 + int(state_vec[1])

@@ -474,5 +478,6 @@ inc = int(state_vec[2]) * 2**64 + int(state_vec[3])

uinteger = value['uinteger']
pcg64_set_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
has_uint32, uinteger)
with self.lock:
pcg64_set_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
has_uint32, uinteger)

@@ -520,4 +525,5 @@ def advance(self, delta):

d[1] = delta % 2**64
pcg64_cm_advance(&self.rng_state, <uint64_t *>np.PyArray_DATA(d))
self._reset_state_variables()
with self.lock:
pcg64_cm_advance(&self.rng_state, <uint64_t *>np.PyArray_DATA(d))
self._reset_state_variables()
return self

@@ -218,16 +218,17 @@ #cython: binding=True

buffer = np.empty(PHILOX_BUFFER_SIZE, dtype=np.uint64)
for i in range(4):
ctr[i] = self.rng_state.ctr.v[i]
if i < 2:
key[i] = self.rng_state.key.v[i]
for i in range(PHILOX_BUFFER_SIZE):
buffer[i] = self.rng_state.buffer[i]
with self.lock:
for i in range(4):
ctr[i] = self.rng_state.ctr.v[i]
if i < 2:
key[i] = self.rng_state.key.v[i]
for i in range(PHILOX_BUFFER_SIZE):
buffer[i] = self.rng_state.buffer[i]
state = {'counter': ctr, 'key': key}
return {'bit_generator': self.__class__.__name__,
'state': state,
'buffer': buffer,
'buffer_pos': self.rng_state.buffer_pos,
'has_uint32': self.rng_state.has_uint32,
'uinteger': self.rng_state.uinteger}
state = {'counter': ctr, 'key': key}
return {'bit_generator': self.__class__.__name__,
'state': state,
'buffer': buffer,
'buffer_pos': self.rng_state.buffer_pos,
'has_uint32': self.rng_state.has_uint32,
'uinteger': self.rng_state.uinteger}

@@ -241,12 +242,13 @@ @state.setter

raise ValueError(f'state must be for a {self.__class__.__name__} PRNG')
for i in range(4):
self.rng_state.ctr.v[i] = <uint64_t> value['state']['counter'][i]
if i < 2:
self.rng_state.key.v[i] = <uint64_t> value['state']['key'][i]
for i in range(PHILOX_BUFFER_SIZE):
self.rng_state.buffer[i] = <uint64_t> value['buffer'][i]
with self.lock:
for i in range(4):
self.rng_state.ctr.v[i] = <uint64_t> value['state']['counter'][i]
if i < 2:
self.rng_state.key.v[i] = <uint64_t> value['state']['key'][i]
for i in range(PHILOX_BUFFER_SIZE):
self.rng_state.buffer[i] = <uint64_t> value['buffer'][i]
self.rng_state.has_uint32 = value['has_uint32']
self.rng_state.uinteger = value['uinteger']
self.rng_state.buffer_pos = value['buffer_pos']
self.rng_state.has_uint32 = value['has_uint32']
self.rng_state.uinteger = value['uinteger']
self.rng_state.buffer_pos = value['buffer_pos']

@@ -333,4 +335,5 @@ cdef jump_inplace(self, iter):

delta_a = int_to_array(delta, 'step', 256, 64)
philox_advance(<uint64_t *> delta_a.data, &self.rng_state)
self._reset_state_variables()
with self.lock:
philox_advance(<uint64_t *> delta_a.data, &self.rng_state)
self._reset_state_variables()
return self

@@ -121,5 +121,6 @@ #cython: binding=True

state_vec = <np.ndarray>np.empty(4, dtype=np.uint64)
sfc64_get_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
&has_uint32, &uinteger)
with self.lock:
sfc64_get_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
&has_uint32, &uinteger)
return {'bit_generator': self.__class__.__name__,

@@ -144,4 +145,5 @@ 'state': {'state': state_vec},

uinteger = value['uinteger']
sfc64_set_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
has_uint32, uinteger)
with self.lock:
sfc64_set_state(&self.rng_state,
<uint64_t *>np.PyArray_DATA(state_vec),
has_uint32, uinteger)

@@ -100,5 +100,5 @@ from builtins import bytes as py_bytes

@overload
def get_state(self, legacy: Literal[False] = False) -> dict[str, Any]: ...
def get_state(self, legacy: Literal[False]) -> dict[str, Any]: ...
@overload
def get_state(self, legacy: Literal[True] = True) -> dict[str, Any] | tuple[str, NDArray[np.uint32], int, int, float]: ...
def get_state(self, legacy: Literal[True] = True) -> tuple[str, NDArray[np.uint32], int, int, float]: ...

@@ -105,0 +105,0 @@ #

@@ -41,3 +41,3 @@ import os

# Note: keep in sync with the one in pyproject.toml
required_version = '3.0.6'
required_version = '3.1.0'
if _pep440.parse(cython_version) < _pep440.Version(required_version):

@@ -44,0 +44,0 @@ # too old or wrong cython, skip the test

@@ -27,4 +27,4 @@ from typing import Any

np.piecewise(AR_f8, True, [fn_ar_i], "wrong") # type: ignore[call-overload]
np.piecewise(AR_f8, AR_b_list, [fn_none_i]) # type: ignore[call-overload]
np.piecewise(AR_f8, AR_b_list, [fn_ar_i]) # type: ignore[call-overload]
np.piecewise(AR_f8, AR_b_list, [fn_none_i]) # type: ignore[list-item]
np.piecewise(AR_f8, AR_b_list, [fn_ar_i]) # type: ignore[list-item]
np.piecewise(AR_f8, AR_b_list, [fn_ar_i], 3.14) # type: ignore[call-overload]

@@ -31,0 +31,0 @@ np.piecewise(AR_f8, AR_b_list, [fn_ar_i], 42, None) # type: ignore[call-overload]

@@ -6,4 +6,3 @@ import numpy as np

np.nditer([0, 1], flags=["test"]) # type: ignore[list-item]
np.nditer([0, 1], op_flags=[["test"]]) # type: ignore[list-item]
np.nditer([0, 1], itershape=(1.0,)) # type: ignore[arg-type]
np.nditer([0, 1], buffersize=1.0) # type: ignore[call-overload]

@@ -16,3 +16,5 @@ """Tests for :mod:`_core.fromnumeric`."""

AR_f4_3d: np.ndarray[tuple[int, int, int], np.dtype[np.float32]]
AR_c8: npt.NDArray[np.complex64]
AR_c16: npt.NDArray[np.complex128]
AR_i1: npt.NDArray[np.int8]
AR_u8: npt.NDArray[np.uint64]

@@ -246,7 +248,9 @@ AR_i8: npt.NDArray[np.int64]

assert_type(np.cumsum(b), np.ndarray[tuple[int], np.dtype[np.bool]])
assert_type(np.cumsum(b), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumsum(f4), np.ndarray[tuple[int], np.dtype[np.float32]])
assert_type(np.cumsum(f), np.ndarray[tuple[int]])
assert_type(np.cumsum(AR_b), np.ndarray[tuple[int], np.dtype[np.bool]])
assert_type(np.cumsum(AR_b, axis=0), npt.NDArray[np.bool])
assert_type(np.cumsum(AR_b), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumsum(AR_b, axis=0), npt.NDArray[np.int_])
assert_type(np.cumsum(AR_i1), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumsum(AR_i1, axis=0), npt.NDArray[np.int_])
assert_type(np.cumsum(AR_f4), np.ndarray[tuple[int], np.dtype[np.float32]])

@@ -258,7 +262,9 @@ assert_type(np.cumsum(AR_f4, axis=0), npt.NDArray[np.float32])

assert_type(np.cumulative_sum(b), np.ndarray[tuple[int], np.dtype[np.bool]])
assert_type(np.cumulative_sum(b), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumulative_sum(f4), np.ndarray[tuple[int], np.dtype[np.float32]])
assert_type(np.cumulative_sum(f), np.ndarray[tuple[int]])
assert_type(np.cumulative_sum(AR_b), np.ndarray[tuple[int], np.dtype[np.bool]])
assert_type(np.cumulative_sum(AR_b, axis=0), npt.NDArray[np.bool])
assert_type(np.cumulative_sum(AR_b), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumulative_sum(AR_b, axis=0), npt.NDArray[np.int_])
assert_type(np.cumulative_sum(AR_i1), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumulative_sum(AR_i1, axis=0), npt.NDArray[np.int_])
assert_type(np.cumulative_sum(AR_f4), np.ndarray[tuple[int], np.dtype[np.float32]])

@@ -348,7 +354,9 @@ assert_type(np.cumulative_sum(AR_f4, axis=0), npt.NDArray[np.float32])

assert_type(np.cumprod(b), np.ndarray[tuple[int], np.dtype[np.bool]])
assert_type(np.cumprod(b), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumprod(f4), np.ndarray[tuple[int], np.dtype[np.float32]])
assert_type(np.cumprod(f), np.ndarray[tuple[int]])
assert_type(np.cumprod(AR_b), np.ndarray[tuple[int], np.dtype[np.bool]])
assert_type(np.cumprod(AR_b, axis=0), npt.NDArray[np.bool])
assert_type(np.cumprod(AR_b), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumprod(AR_b, axis=0), npt.NDArray[np.int_])
assert_type(np.cumprod(AR_i1), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumprod(AR_i1, axis=0), npt.NDArray[np.int_])
assert_type(np.cumprod(AR_f4), np.ndarray[tuple[int], np.dtype[np.float32]])

@@ -360,7 +368,9 @@ assert_type(np.cumprod(AR_f4, axis=0), npt.NDArray[np.float32])

assert_type(np.cumulative_prod(b), np.ndarray[tuple[int], np.dtype[np.bool]])
assert_type(np.cumulative_prod(b), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumulative_prod(f4), np.ndarray[tuple[int], np.dtype[np.float32]])
assert_type(np.cumulative_prod(f), np.ndarray[tuple[int]])
assert_type(np.cumulative_prod(AR_b), np.ndarray[tuple[int], np.dtype[np.bool]])
assert_type(np.cumulative_prod(AR_b, axis=0), npt.NDArray[np.bool])
assert_type(np.cumulative_prod(AR_b), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumulative_prod(AR_b, axis=0), npt.NDArray[np.int_])
assert_type(np.cumulative_prod(AR_i1), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(np.cumulative_prod(AR_i1, axis=0), npt.NDArray[np.int_])
assert_type(np.cumulative_prod(AR_f4), np.ndarray[tuple[int], np.dtype[np.float32]])

@@ -397,6 +407,10 @@ assert_type(np.cumulative_prod(AR_f4, axis=0), npt.NDArray[np.float32])

assert_type(np.prod(AR_b), np.int_)
assert_type(np.prod(AR_i8), np.int64)
assert_type(np.prod(AR_i8, axis=0), npt.NDArray[np.int64])
assert_type(np.prod(AR_i8, keepdims=True), npt.NDArray[np.int64])
assert_type(np.prod(AR_i8, axis=0, keepdims=True), npt.NDArray[np.int64])
assert_type(np.prod(AR_i1), np.int_)
assert_type(np.prod(AR_i1, axis=0), npt.NDArray[np.int_])
assert_type(np.prod(AR_i1, keepdims=True), npt.NDArray[np.int_])
assert_type(np.prod(AR_i1, axis=0, keepdims=True), npt.NDArray[np.int_])
assert_type(np.prod(AR_i8), np.int_)
assert_type(np.prod(AR_i8, axis=0), npt.NDArray[np.int_])
assert_type(np.prod(AR_i8, keepdims=True), npt.NDArray[np.int_])
assert_type(np.prod(AR_i8, axis=0, keepdims=True), npt.NDArray[np.int_])
assert_type(np.prod(AR_f4), np.float32)

@@ -427,6 +441,10 @@ assert_type(np.prod(AR_c16), np.complex128)

assert_type(np.sum(AR_b), np.int_)
assert_type(np.sum(AR_i8), np.int64)
assert_type(np.sum(AR_i8, axis=0), npt.NDArray[np.int64])
assert_type(np.sum(AR_i8, keepdims=True), npt.NDArray[np.int64])
assert_type(np.sum(AR_i8, axis=0, keepdims=True), npt.NDArray[np.int64])
assert_type(np.sum(AR_i1), np.int_)
assert_type(np.sum(AR_i1, axis=0), npt.NDArray[np.int_])
assert_type(np.sum(AR_i1, keepdims=True), npt.NDArray[np.int_])
assert_type(np.sum(AR_i1, axis=0, keepdims=True), npt.NDArray[np.int_])
assert_type(np.sum(AR_i8), np.int_)
assert_type(np.sum(AR_i8, axis=0), npt.NDArray[np.int_])
assert_type(np.sum(AR_i8, keepdims=True), npt.NDArray[np.int_])
assert_type(np.sum(AR_i8, axis=0, keepdims=True), npt.NDArray[np.int_])
assert_type(np.sum(AR_f4), np.float32)

@@ -495,3 +513,8 @@ assert_type(np.sum(AR_c16), np.complex128)

assert_type(np.std(AR_f4), np.float32)
assert_type(np.std(AR_c16), np.complex128)
assert_type(np.std(AR_c8), Any)
assert_type(np.std(AR_c8, axis=0), npt.NDArray[Any])
assert_type(np.std(AR_c8, keepdims=True), npt.NDArray[Any])
assert_type(np.std(AR_c16), np.float64)
assert_type(np.std(AR_c16, axis=0), npt.NDArray[np.float64])
assert_type(np.std(AR_c16, keepdims=True), npt.NDArray[np.float64])
assert_type(np.std(AR_O), np.float64)

@@ -527,3 +550,8 @@ assert_type(np.std(AR_O, axis=0), npt.NDArray[np.object_])

assert_type(np.var(AR_f4), np.float32)
assert_type(np.var(AR_c16), np.complex128)
assert_type(np.var(AR_c8), Any)
assert_type(np.var(AR_c8, axis=0), npt.NDArray[Any])
assert_type(np.var(AR_c8, keepdims=True), npt.NDArray[Any])
assert_type(np.var(AR_c16), np.float64)
assert_type(np.var(AR_c16, axis=0), npt.NDArray[np.float64])
assert_type(np.var(AR_c16, keepdims=True), npt.NDArray[np.float64])
assert_type(np.var(AR_O), np.float64)

@@ -530,0 +558,0 @@ assert_type(np.var(AR_O, axis=0), npt.NDArray[np.object_])

@@ -61,10 +61,10 @@ from typing import Any, assert_type

assert_type(np.histogramdd(AR_i8, bins=[1]), tuple[npt.NDArray[np.float64], tuple[_Array1D[np.float64], ...]])
assert_type(np.histogramdd(AR_i8, range=[(0, 3)]), tuple[npt.NDArray[np.float64], tuple[_Array1D[np.float64], ...]])
assert_type(np.histogramdd(AR_i8, weights=AR_f8), tuple[npt.NDArray[np.float64], tuple[_Array1D[np.float64], ...]])
assert_type(np.histogramdd(AR_f8, density=True), tuple[npt.NDArray[np.float64], tuple[_Array1D[np.float64], ...]])
assert_type(np.histogramdd(AR_i4), tuple[npt.NDArray[np.float64], tuple[_Array1D[np.float64], ...]])
assert_type(np.histogramdd(AR_i8), tuple[npt.NDArray[np.float64], tuple[_Array1D[np.float64], ...]])
assert_type(np.histogramdd(AR_f4), tuple[npt.NDArray[np.float64], tuple[_Array1D[np.float32], ...]])
assert_type(np.histogramdd(AR_c8), tuple[npt.NDArray[np.float64], tuple[_Array1D[np.complex64], ...]])
assert_type(np.histogramdd(AR_c16), tuple[npt.NDArray[np.float64], tuple[_Array1D[np.complex128], ...]])
assert_type(np.histogramdd(AR_i8, bins=[1]), tuple[npt.NDArray[np.float64], list[_Array1D[np.float64]]])
assert_type(np.histogramdd(AR_i8, range=[(0, 3)]), tuple[npt.NDArray[np.float64], list[_Array1D[np.float64]]])
assert_type(np.histogramdd(AR_i8, weights=AR_f8), tuple[npt.NDArray[np.float64], list[_Array1D[np.float64]]])
assert_type(np.histogramdd(AR_f8, density=True), tuple[npt.NDArray[np.float64], list[_Array1D[np.float64]]])
assert_type(np.histogramdd(AR_i4), tuple[npt.NDArray[np.float64], list[_Array1D[np.float64]]])
assert_type(np.histogramdd(AR_i8), tuple[npt.NDArray[np.float64], list[_Array1D[np.float64]]])
assert_type(np.histogramdd(AR_f4), tuple[npt.NDArray[np.float64], list[_Array1D[np.float32]]])
assert_type(np.histogramdd(AR_c8), tuple[npt.NDArray[np.float64], list[_Array1D[np.complex64]]])
assert_type(np.histogramdd(AR_c16), tuple[npt.NDArray[np.float64], list[_Array1D[np.complex128]]])

@@ -106,2 +106,3 @@ from collections.abc import Callable

# piecewise
assert_type(np.piecewise(AR_f8_1d, AR_b, [-1.0, 1.0]), np.ndarray[tuple[int], np.dtype[np.float64]])
assert_type(np.piecewise(AR_f8_1d, AR_b, [func]), np.ndarray[tuple[int], np.dtype[np.float64]])

@@ -220,2 +221,3 @@ assert_type(np.piecewise(AR_f8, AR_b, [func]), npt.NDArray[np.float64])

assert_type(np.sort_complex(AR_c16_1d), np.ndarray[tuple[int], np.dtype[np.complex128]])
assert_type(np.sort_complex(AR_LIKE_f8), npt.NDArray[np.complex128])

@@ -222,0 +224,0 @@ # trim_zeros

@@ -19,4 +19,6 @@ from typing import Any, Literal, assert_type

bool_list_2d: list[list[bool]]
bool_list_3d: list[list[list[bool]]]
int_list_1d: list[int]
int_list_2d: list[list[int]]
int_list_3d: list[list[list[int]]]
float_list_1d: list[float]

@@ -63,2 +65,4 @@ float_list_2d: list[list[float]]

AR_i1_2d: _Array2D[np.int8]
AR_i1_3d: _Array3D[np.int8]
AR_f2_2d: _Array2D[np.float16]

@@ -393,2 +397,4 @@ AR_f4_1d: _Array1D[np.float32]

assert_type(np.linalg.trace(AR_f4), Any)
assert_type(np.linalg.trace(AR_i1_2d), np.int_)
assert_type(np.linalg.trace(AR_i1_3d), _Array1D[np.int_])
assert_type(np.linalg.trace(AR_f4_2d), np.float32)

@@ -399,6 +405,8 @@ assert_type(np.linalg.trace(AR_f8_2d), np.float64)

assert_type(np.linalg.trace(AR_f8_4d), np.ndarray[tuple[int, *tuple[Any, ...]], np.dtype[np.float64]])
assert_type(np.linalg.trace(bool_list_2d), np.bool)
assert_type(np.linalg.trace(bool_list_2d), np.int_)
assert_type(np.linalg.trace(int_list_2d), np.int_)
assert_type(np.linalg.trace(float_list_2d), np.float64)
assert_type(np.linalg.trace(complex_list_2d), np.complex128)
assert_type(np.linalg.trace(bool_list_3d), npt.NDArray[np.int_])
assert_type(np.linalg.trace(int_list_3d), npt.NDArray[np.int_])
assert_type(np.linalg.trace(float_list_3d), npt.NDArray[np.float64])

@@ -405,0 +413,0 @@

@@ -241,2 +241,5 @@ from typing import Any, Literal, NoReturn, assert_type

assert_type(np.ma.where(MAR_b), tuple[np.ndarray[tuple[int], np.dtype[np.intp]], ...])
assert_type(np.ma.where(MAR_b, 0, 1), MaskedArray[Any])
assert_type(MAR_f4 >= 3, MaskedArray[np.bool])

@@ -243,0 +246,0 @@ assert_type(MAR_i8 >= AR_td64, MaskedArray[np.bool])

@@ -224,2 +224,3 @@ import datetime as dt

assert_type(np.bincount(AR_i8), np.ndarray[tuple[int], np.dtype[np.intp]])
assert_type(np.bincount(AR_i8, weights=AR_i8), np.ndarray[tuple[int], np.dtype[np.float64]])

@@ -226,0 +227,0 @@ assert_type(np.copyto(AR_f8, [1., 1.5, 1.6]), None)

@@ -35,2 +35,3 @@ """

AR_c8: npt.NDArray[np.complex64]
AR_c16: npt.NDArray[np.complex128]
AR_m: npt.NDArray[np.timedelta64]

@@ -123,4 +124,6 @@ AR_U: npt.NDArray[np.str_]

assert_type(AR_i8.cumprod(), np.ndarray[tuple[int], np.dtype[np.int64]])
assert_type(AR_i8.cumprod(axis=0), npt.NDArray[np.int64])
assert_type(AR_u1.cumprod(), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(AR_u1.cumprod(axis=0), npt.NDArray[np.int_])
assert_type(AR_i8.cumprod(), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(AR_i8.cumprod(axis=0), npt.NDArray[np.int_])
assert_type(AR_f8.cumprod(), np.ndarray[tuple[int], np.dtype[np.float64]])

@@ -138,4 +141,6 @@ assert_type(AR_f8.cumprod(axis=0), npt.NDArray[np.float64])

assert_type(AR_i8.cumsum(), np.ndarray[tuple[int], np.dtype[np.int64]])
assert_type(AR_i8.cumsum(axis=0), npt.NDArray[np.int64])
assert_type(AR_u1.cumsum(), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(AR_u1.cumsum(axis=0), npt.NDArray[np.int_])
assert_type(AR_i8.cumsum(), np.ndarray[tuple[int], np.dtype[np.int_]])
assert_type(AR_i8.cumsum(axis=0), npt.NDArray[np.int_])
assert_type(AR_f8.cumsum(), np.ndarray[tuple[int], np.dtype[np.float64]])

@@ -157,6 +162,10 @@ assert_type(AR_f8.cumsum(axis=0), npt.NDArray[np.float64])

assert_type(f8.prod(), np.float64)
assert_type(AR_i8.prod(), np.int64)
assert_type(AR_i8.prod(keepdims=True), npt.NDArray[np.int64])
assert_type(AR_i8.prod(axis=0), npt.NDArray[np.int64])
assert_type(AR_i8.prod(axis=0, keepdims=True), npt.NDArray[np.int64])
assert_type(AR_u1.prod(), np.int_)
assert_type(AR_u1.prod(keepdims=True), npt.NDArray[np.int_])
assert_type(AR_u1.prod(axis=0), npt.NDArray[np.int_])
assert_type(AR_u1.prod(axis=0, keepdims=True), npt.NDArray[np.int_])
assert_type(AR_i8.prod(), np.int_)
assert_type(AR_i8.prod(keepdims=True), npt.NDArray[np.int_])
assert_type(AR_i8.prod(axis=0), npt.NDArray[np.int_])
assert_type(AR_i8.prod(axis=0, keepdims=True), npt.NDArray[np.int_])
assert_type(AR_f8.prod(), np.float64)

@@ -188,6 +197,10 @@ assert_type(AR_f8.prod(keepdims=True), npt.NDArray[np.float64])

assert_type(m8_na.sum(), np.timedelta64[None])
assert_type(AR_i8.sum(), np.int64)
assert_type(AR_i8.sum(keepdims=True), npt.NDArray[np.int64])
assert_type(AR_i8.sum(axis=0), npt.NDArray[np.int64])
assert_type(AR_i8.sum(axis=0, keepdims=True), npt.NDArray[np.int64])
assert_type(AR_u1.sum(), np.int_)
assert_type(AR_u1.sum(keepdims=True), npt.NDArray[np.int_])
assert_type(AR_u1.sum(axis=0), npt.NDArray[np.int_])
assert_type(AR_u1.sum(axis=0, keepdims=True), npt.NDArray[np.int_])
assert_type(AR_i8.sum(), np.int_)
assert_type(AR_i8.sum(keepdims=True), npt.NDArray[np.int_])
assert_type(AR_i8.sum(axis=0), npt.NDArray[np.int_])
assert_type(AR_i8.sum(axis=0, keepdims=True), npt.NDArray[np.int_])
assert_type(AR_f8.sum(), np.float64)

@@ -232,3 +245,3 @@ assert_type(AR_f8.sum(keepdims=True), npt.NDArray[np.float64])

# same as above
# as above, but returns floating for complexfloating input
assert_type(f8.std(), Any)

@@ -239,2 +252,6 @@ assert_type(AR_f8.std(), np.float64)

assert_type(AR_f8.std(axis=0, keepdims=True), npt.NDArray[np.float64])
assert_type(AR_c16.std(), np.float64)
assert_type(AR_c16.std(keepdims=True), npt.NDArray[np.float64])
assert_type(AR_c16.std(axis=0), npt.NDArray[np.float64])
assert_type(AR_c16.std(axis=0, keepdims=True), npt.NDArray[np.float64])
assert_type(AR_f8.std(dtype=np.float32), np.float32)

@@ -260,2 +277,6 @@ assert_type(AR_f8.std(dtype=np.float32, axis=0), npt.NDArray[np.float32])

assert_type(AR_f8.var(axis=0, keepdims=True), npt.NDArray[np.float64])
assert_type(AR_c16.var(), np.float64)
assert_type(AR_c16.var(keepdims=True), npt.NDArray[np.float64])
assert_type(AR_c16.var(axis=0), npt.NDArray[np.float64])
assert_type(AR_c16.var(axis=0, keepdims=True), npt.NDArray[np.float64])
assert_type(AR_f8.var(dtype=np.float32), np.float32)

@@ -262,0 +283,0 @@ assert_type(AR_f8.var(dtype=np.float32, axis=0), npt.NDArray[np.float32])

@@ -30,2 +30,3 @@ """

_to_1d_float: list[float]
_to_2d_float: list[list[float]]
_to_1d_complex: list[complex]

@@ -165,2 +166,5 @@

assert_type(np.isclose(_to_1d_int, _to_1d_int), np.ndarray[tuple[int], np.dtype[np.bool]])
assert_type(np.isclose(_to_1d_int, _to_2d_float), np.ndarray[tuple[int, int], np.dtype[np.bool]])
assert_type(np.isclose(_to_2d_float, _to_1d_int), np.ndarray[tuple[int, int], np.dtype[np.bool]])
assert_type(np.isclose(_to_2d_float, _to_2d_float), np.ndarray[tuple[int, int], np.dtype[np.bool]])
assert_type(np.isclose(AR_i8, AR_i8), npt.NDArray[np.bool])

@@ -167,0 +171,0 @@

@@ -64,2 +64,6 @@ import io

assert_type(
np.rec.fromarrays(AR_LIST, names=["i8", "f8"]),
_RecArray,
)
assert_type(
np.rec.fromarrays(

@@ -77,3 +81,2 @@ AR_LIST,

)
assert_type(

@@ -86,3 +89,2 @@ np.rec.fromrecords(

)
assert_type(

@@ -96,2 +98,6 @@ np.rec.fromrecords(

)
assert_type(
np.rec.fromrecords(REC_AR_V, names=["i8", "f8"]),
_RecArray,
)

@@ -98,0 +104,0 @@ assert_type(

@@ -0,1 +1,2 @@

import datetime as dt
from typing import Any, Literal, assert_type

@@ -52,4 +53,2 @@

assert_type(c16[()], np.complex128)
assert_type(U[()], np.str_)
assert_type(S[()], np.bytes_)
assert_type(V[()], np.void)

@@ -69,6 +68,2 @@

assert_type(c16[(...,)], np.ndarray[tuple[()], np.dtype[np.complex128]])
assert_type(U[...], np.ndarray[tuple[()], np.dtype[np.str_]])
assert_type(U[(...,)], np.ndarray[tuple[()], np.dtype[np.str_]])
assert_type(S[...], np.ndarray[tuple[()], np.dtype[np.bytes_]])
assert_type(S[(...,)], np.ndarray[tuple[()], np.dtype[np.bytes_]])
assert_type(V[...], np.ndarray[tuple[()], np.dtype[np.void]])

@@ -118,13 +113,11 @@ assert_type(V[(...,)], np.ndarray[tuple[()], np.dtype[np.void]])

assert_type(U[None], np.ndarray[tuple[int], np.dtype[np.str_]])
assert_type(U[None1], np.ndarray[tuple[int], np.dtype[np.str_]])
assert_type(U[None2], np.ndarray[tuple[int, int], np.dtype[np.str_]])
assert_type(U[None3], np.ndarray[tuple[int, int, int], np.dtype[np.str_]])
assert_type(U[None4], np.ndarray[tuple[Any, ...], np.dtype[np.str_]])
assert_type(U[0], str)
assert_type(U[:1], str)
assert_type(U[1:], str)
assert_type(U[:], str)
assert_type(S[None], np.ndarray[tuple[int], np.dtype[np.bytes_]])
assert_type(S[None1], np.ndarray[tuple[int], np.dtype[np.bytes_]])
assert_type(S[None2], np.ndarray[tuple[int, int], np.dtype[np.bytes_]])
assert_type(S[None3], np.ndarray[tuple[int, int, int], np.dtype[np.bytes_]])
assert_type(S[None4], np.ndarray[tuple[Any, ...], np.dtype[np.bytes_]])
assert_type(S[0], int)
assert_type(S[:1], bytes)
assert_type(S[1:], bytes)
assert_type(S[:], bytes)

@@ -264,2 +257,20 @@ assert_type(V[None], np.ndarray[tuple[int], np.dtype[np.void]])

assert_type(np.datetime64(), np.datetime64[None])
assert_type(np.datetime64("now", "ns"), np.datetime64[int])
assert_type(np.datetime64("now", "s"), np.datetime64[dt.datetime])
assert_type(np.datetime64("now", "Y"), np.datetime64[dt.date])
# unit-swapping
assert_type(np.datetime64(np.datetime64(), "ns"), np.datetime64[int])
assert_type(np.datetime64(np.datetime64("now", "ns"), "ns"), np.datetime64[int])
assert_type(np.datetime64(np.datetime64("now", "s"), "ns"), np.datetime64[int])
assert_type(np.datetime64(np.datetime64("now", "Y"), "ns"), np.datetime64[int])
assert_type(np.datetime64(np.datetime64(), "s"), np.datetime64[dt.datetime])
assert_type(np.datetime64(np.datetime64("now", "ns"), "s"), np.datetime64[dt.datetime])
assert_type(np.datetime64(np.datetime64("now", "s"), "s"), np.datetime64[dt.datetime])
assert_type(np.datetime64(np.datetime64("now", "Y"), "s"), np.datetime64[dt.datetime])
assert_type(np.datetime64(np.datetime64(), "Y"), np.datetime64[dt.date])
assert_type(np.datetime64(np.datetime64("now", "ns"), "Y"), np.datetime64[dt.date])
assert_type(np.datetime64(np.datetime64("now", "s"), "Y"), np.datetime64[dt.date])
assert_type(np.datetime64(np.datetime64("now", "Y"), "Y"), np.datetime64[dt.date])
# These fail fail because of a mypy __new__ bug:

@@ -266,0 +277,0 @@ # https://github.com/python/mypy/issues/15182

@@ -96,10 +96,12 @@ from typing import assert_type

assert_type(np.strings.partition(AR_U, "\n"), npt.NDArray[np.str_])
assert_type(np.strings.partition(AR_S, [b"a", b"b", b"c"]), npt.NDArray[np.bytes_])
assert_type(np.strings.partition(AR_T, "\n"), AR_TU_alias)
type _tuple3[T] = tuple[T, T, T]
assert_type(np.strings.rpartition(AR_U, "\n"), npt.NDArray[np.str_])
assert_type(np.strings.rpartition(AR_S, [b"a", b"b", b"c"]), npt.NDArray[np.bytes_])
assert_type(np.strings.rpartition(AR_T, "\n"), AR_TU_alias)
assert_type(np.strings.partition(AR_U, "\n"), _tuple3[npt.NDArray[np.str_]])
assert_type(np.strings.partition(AR_S, [b"a", b"b", b"c"]), _tuple3[npt.NDArray[np.bytes_]])
assert_type(np.strings.partition(AR_T, "\n"), _tuple3[AR_TU_alias])
assert_type(np.strings.rpartition(AR_U, "\n"), _tuple3[npt.NDArray[np.str_]])
assert_type(np.strings.rpartition(AR_S, [b"a", b"b", b"c"]), _tuple3[npt.NDArray[np.bytes_]])
assert_type(np.strings.rpartition(AR_T, "\n"), _tuple3[AR_TU_alias])
assert_type(np.strings.replace(AR_U, "_", "-"), npt.NDArray[np.str_])

@@ -106,0 +108,0 @@ assert_type(np.strings.replace(AR_S, [b"_", b""], [b"a", b"b"]), npt.NDArray[np.bytes_])

@@ -8,2 +8,3 @@ from typing import Any, assert_type, type_check_only

type _2D = tuple[int, int]
type _3D = tuple[int, int, int]
type _ND = tuple[Any, ...]

@@ -21,2 +22,3 @@

_2d_bool: np.ndarray[_2D, np.dtype[np.bool]]
_3d_bool: np.ndarray[_3D, np.dtype[np.bool]]
_nd_u64: np.ndarray[_ND, np.dtype[np.uint64]]

@@ -94,2 +96,5 @@ _nd_i64: np.ndarray[_ND, np.dtype[np.int64]]

assert_type(np.tril(_to_2d_bool, k=0), np.ndarray)
assert_type(np.tril(_1d_bool, k=0), np.ndarray[_2D, np.dtype[np.bool]])
assert_type(np.tril(_2d_bool, k=0), np.ndarray[_2D, np.dtype[np.bool]])
assert_type(np.tril(_3d_bool, k=0), np.ndarray[_3D, np.dtype[np.bool]])

@@ -101,2 +106,5 @@ # triu

assert_type(np.triu(_to_2d_bool, k=0), np.ndarray)
assert_type(np.triu(_1d_bool, k=0), np.ndarray[_2D, np.dtype[np.bool]])
assert_type(np.triu(_2d_bool, k=0), np.ndarray[_2D, np.dtype[np.bool]])
assert_type(np.triu(_3d_bool, k=0), np.ndarray[_3D, np.dtype[np.bool]])

@@ -103,0 +111,0 @@ # vander

@@ -5,8 +5,8 @@

"""
version = "2.5.1"
version = "2.5.2"
__version__ = version
full_version = version
git_revision = "5e1d03ffac5f2c0a9c39bfcaa9fc853b2b83151e"
git_revision = "48fecee5453aa1d31e6b79dcb3969dc1a6d1a891"
release = 'dev' not in version and '+' not in version
short_version = version.split("+")[0]
Metadata-Version: 2.4
Name: numpy
Version: 2.5.1
Version: 2.5.2
Summary: Fundamental package for array computing in Python

@@ -34,2 +34,3 @@ Author: Travis E. Oliphant et al.

Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Programming Language :: Python :: 3 :: Only

@@ -36,0 +37,0 @@ Classifier: Programming Language :: Python :: Implementation :: CPython

@@ -5,3 +5,3 @@ [build-system]

"meson-python>=0.18.0",
"Cython>=3.0.6", # keep in sync with version check in meson.build
"Cython>=3.1.0", # keep in sync with version check in meson.build
]

@@ -11,3 +11,3 @@

name = "numpy"
version = "2.5.1"
version = "2.5.2"
description = "Fundamental package for array computing in Python"

@@ -30,2 +30,3 @@ authors = [{name = "Travis E. Oliphant et al."}]

'Programming Language :: Python :: 3.14',
'Programming Language :: Python :: 3.15',
'Programming Language :: Python :: 3 :: Only',

@@ -32,0 +33,0 @@ 'Programming Language :: Python :: Implementation :: CPython',

@@ -1,5 +0,5 @@

meson-python>=0.19.0
Cython>=3.2.4
meson-python>=0.18.0
Cython>=3.1.0
ninja
spin
build

@@ -346,3 +346,8 @@ # SPDX-License-Identifier: Apache-2.0

def language_stdlib_only_link_flags(self) -> T.List[str]:
# No need to add search paths here, because LCC ships everything
# (C, C++, Fortran) and always knows where to look for its stuff
return ['-lgfortran', '-lm']
class G95FortranCompiler(FortranCompiler):

@@ -349,0 +354,0 @@

@@ -99,1 +99,5 @@ # SPDX-License-Identifier: Apache-2.0

return ['-fopenmp']
@classmethod
def use_linker_args(cls, linker: str, version: str) -> T.List[str]:
return []

@@ -774,2 +774,6 @@ # Copyright 2013-2020 The Meson development team

self.parse_modules(kwargs)
# In addition to checking the LP64/ILP64 interface, we need to
# differentiate between the original Accelerate, and the
# ACCELERATE_NEW_LAPACK variant.
self.use_new_lapack = True

@@ -795,2 +799,9 @@ for_machine = MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST

def check_iOS_recent_enough(self) -> bool:
# The ILP64 interface is only available as part of
# ACCELERATE_NEW_LAPACK, which requires iOS 16.4 and later.
if self.interface == "ilp64":
required_version = ">=16.4"
else:
required_version = ">=13.0"
# platform.ios_ver() is only available since Python 3.13; use getattr

@@ -801,12 +812,17 @@ # so this type-checks across Python versions.

return False
ios_version = ios_ver().system
deploy_target = os.environ.get('IPHONEOS_DEPLOYMENT_TARGET', ios_version)
if not mesonlib.version_compare(deploy_target, '>=16.4'):
deploy_target = os.environ.get('IPHONEOS_DEPLOYMENT_TARGET', ios_ver().release)
if not mesonlib.version_compare(deploy_target, required_version):
return False
# We also need the SDK to be >=16.4
# If we're targeting iOS < 16.4, we can't use ACCELERATE_NEW_LAPACK
if mesonlib.version_compare(deploy_target, "<16.4"):
self.use_new_lapack = False
# We also need the SDK to be the right version
sdk = "iphonesimulator" if ios_ver().is_simulator else "iphoneos"
cmd = ['xcrun', '-sdk', sdk, '--show-sdk-version']
sdk_version = subprocess.run(cmd, capture_output=True, check=True, encoding='utf-8').stdout.strip()
return mesonlib.version_compare(sdk_version, '>=16.4')
return mesonlib.version_compare(sdk_version, required_version)

@@ -820,3 +836,4 @@ def detect(self, kwargs: 'DependencyObjectKWs') -> None:

self.link_args = dep.link_args
self.compile_args += ['-DACCELERATE_NEW_LAPACK']
if self.use_new_lapack:
self.compile_args += ['-DACCELERATE_NEW_LAPACK']
if self.interface == 'ilp64':

@@ -829,3 +846,6 @@ self.compile_args += ['-DACCELERATE_LAPACK_ILP64']

def get_symbol_suffix(self) -> str:
return '$NEWLAPACK' if self.interface == 'lp64' else '$NEWLAPACK$ILP64'
if self.use_new_lapack:
return '$NEWLAPACK' if self.interface == 'lp64' else '$NEWLAPACK$ILP64'
else:
return ''

@@ -832,0 +852,0 @@

#include <Python.h>
#include <numpy/arrayobject.h>
#include <numpy/ufuncobject.h>
#if Py_LIMITED_API != PY_VERSION_HEX & 0xffff0000
# error "Py_LIMITED_API not defined to Python major+minor version"
#endif
static PyModuleDef moduledef = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "limited_api_latest"
};
PyMODINIT_FUNC PyInit_limited_api_latest(void)
{
import_array();
import_umath();
return PyModule_Create(&moduledef);
}
#ifndef Py_TARGET_ABI3T
#error "This file must be compiled with -DPy_TARGET_ABI3T"
#endif
#include <Python.h>
#include <numpy/ndarraytypes.h>
#include <numpy/arrayobject.h>
#include <numpy/ufuncobject.h>
static PyObject *limited_api_opaque_nonzero(PyObject *mod, PyArrayObject *self)
{
PyArray_NonzeroFunc* nonzero = PyDataType_GetArrFuncs(PyArray_DESCR(self))->nonzero;
NpyIter* iter;
NpyIter_IterNextFunc *iternext;
char** dataptr;
npy_intp nonzero_count;
npy_intp* strideptr,* innersizeptr;
/* Handle zero-sized arrays specially */
if (PyArray_SIZE(self) == 0) {
return PyLong_FromLong(0);
}
/*
* Create and use an iterator to count the nonzeros.
* flag NPY_ITER_READONLY
* - The array is never written to.
* flag NPY_ITER_EXTERNAL_LOOP
* - Inner loop is done outside the iterator for efficiency.
* flag NPY_ITER_NPY_ITER_REFS_OK
* - Reference types are acceptable.
* order NPY_KEEPORDER
* - Visit elements in memory order, regardless of strides.
* This is good for performance when the specific order
* elements are visited is unimportant.
* casting NPY_NO_CASTING
* - No casting is required for this operation.
*/
iter = NpyIter_New(self, NPY_ITER_READONLY|
NPY_ITER_EXTERNAL_LOOP|
NPY_ITER_REFS_OK,
NPY_KEEPORDER, NPY_NO_CASTING,
NULL);
if (iter == NULL) {
return NULL;
}
/*
* The iternext function gets stored in a local variable
* so it can be called repeatedly in an efficient manner.
*/
iternext = NpyIter_GetIterNext(iter, NULL);
if (iternext == NULL) {
NpyIter_Deallocate(iter);
return NULL;
}
/* The location of the data pointer which the iterator may update */
dataptr = NpyIter_GetDataPtrArray(iter);
/* The location of the stride which the iterator may update */
strideptr = NpyIter_GetInnerStrideArray(iter);
/* The location of the inner loop size which the iterator may update */
innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
nonzero_count = 0;
do {
/* Get the inner loop data/stride/count values */
char* data = *dataptr;
npy_intp stride = *strideptr;
npy_intp count = *innersizeptr;
/* This is a typical inner loop for NPY_ITER_EXTERNAL_LOOP */
while (count--) {
if (nonzero(data, self)) {
++nonzero_count;
}
data += stride;
}
/* Increment the iterator to the next inner loop */
} while(iternext(iter));
NpyIter_Deallocate(iter);
return PyLong_FromLong(nonzero_count);
}
/*
* Test PyArray_ITER_NEXT, PyArray_ITER_RESET, PyArray_ITER_DATA,
* and PyArray_ITER_NOTDONE by summing all elements using the
* legacy iterator macros.
*/
static PyObject *
limited_api_opaque_iter_next(PyObject *mod, PyArrayObject *self)
{
PyObject *iter_obj = PyArray_IterNew((PyObject *)self);
if (iter_obj == NULL) {
return NULL;
}
double sum = 0.0;
while (PyArray_ITER_NOTDONE(iter_obj)) {
sum += *(double *)PyArray_ITER_DATA(iter_obj);
PyArray_ITER_NEXT(iter_obj);
}
Py_DECREF(iter_obj);
return PyFloat_FromDouble(sum);
}
/*
* Test PyArray_ITER_GOTO1D by accessing a specific flat index.
*/
static PyObject *
limited_api_opaque_iter_goto1d(PyObject *mod, PyObject *args)
{
PyArrayObject *arr;
npy_intp index;
if (!PyArg_ParseTuple(args, "O!n", &PyArray_Type, &arr, &index)) {
return NULL;
}
PyObject *iter_obj = PyArray_IterNew((PyObject *)arr);
if (iter_obj == NULL) {
return NULL;
}
PyArray_ITER_GOTO1D(iter_obj, index);
double val = *(double *)PyArray_ITER_DATA(iter_obj);
Py_DECREF(iter_obj);
return PyFloat_FromDouble(val);
}
/*
* Test PyArray_ITER_RESET by iterating, resetting, and iterating again.
* Returns the sum from the second pass (should equal the first).
*/
static PyObject *
limited_api_opaque_iter_reset(PyObject *mod, PyArrayObject *self)
{
PyObject *iter_obj = PyArray_IterNew((PyObject *)self);
if (iter_obj == NULL) {
return NULL;
}
/* First pass: skip through */
while (PyArray_ITER_NOTDONE(iter_obj)) {
PyArray_ITER_NEXT(iter_obj);
}
/* Reset and sum */
PyArray_ITER_RESET(iter_obj);
double sum = 0.0;
while (PyArray_ITER_NOTDONE(iter_obj)) {
sum += *(double *)PyArray_ITER_DATA(iter_obj);
PyArray_ITER_NEXT(iter_obj);
}
Py_DECREF(iter_obj);
return PyFloat_FromDouble(sum);
}
/*
* Test PyArray_MultiIter_NEXT, PyArray_MultiIter_RESET,
* PyArray_MultiIter_DATA, and PyArray_MultiIter_NOTDONE
* by computing the element-wise sum of two broadcastable arrays.
* Returns the total sum of (a + b) for all broadcast elements.
*/
static PyObject *
limited_api_opaque_multi_iter_next(PyObject *mod, PyObject *args)
{
PyArrayObject *a, *b;
if (!PyArg_ParseTuple(args, "O!O!", &PyArray_Type, &a,
&PyArray_Type, &b)) {
return NULL;
}
PyObject *multi = PyArray_MultiIterNew(2, a, b);
if (multi == NULL) {
return NULL;
}
double sum = 0.0;
while (PyArray_MultiIter_NOTDONE(multi)) {
double va = *(double *)PyArray_MultiIter_DATA(multi, 0);
double vb = *(double *)PyArray_MultiIter_DATA(multi, 1);
sum += va + vb;
PyArray_MultiIter_NEXT(multi);
}
/* Test reset: iterate again and verify same sum */
PyArray_MultiIter_RESET(multi);
double sum2 = 0.0;
while (PyArray_MultiIter_NOTDONE(multi)) {
double va = *(double *)PyArray_MultiIter_DATA(multi, 0);
double vb = *(double *)PyArray_MultiIter_DATA(multi, 1);
sum2 += va + vb;
PyArray_MultiIter_NEXT(multi);
}
Py_DECREF(multi);
if (sum != sum2) {
PyErr_SetString(PyExc_RuntimeError,
"MultiIter reset produced different sum");
return NULL;
}
return PyFloat_FromDouble(sum);
}
/*
* Test PyArray_ITER_GOTO by jumping to a coordinate and reading the value.
*/
static PyObject *
limited_api_opaque_iter_goto(PyObject *mod, PyObject *args)
{
PyArrayObject *arr;
PyObject *coord_tuple;
if (!PyArg_ParseTuple(args, "O!O!", &PyArray_Type, &arr,
&PyTuple_Type, &coord_tuple)) {
return NULL;
}
int nd = PyArray_NDIM(arr);
if (PyTuple_Size(coord_tuple) != nd) {
PyErr_SetString(PyExc_ValueError, "coordinate length mismatch");
return NULL;
}
npy_intp destination[NPY_MAXDIMS_LEGACY_ITERS];
for (int i = 0; i < nd; i++) {
destination[i] = PyLong_AsLong(PyTuple_GetItem(coord_tuple, i));
if (destination[i] == -1 && PyErr_Occurred()) {
return NULL;
}
}
PyObject *iter_obj = PyArray_IterNew((PyObject *)arr);
if (iter_obj == NULL) {
return NULL;
}
PyArray_ITER_GOTO(iter_obj, destination);
double val = *(double *)PyArray_ITER_DATA(iter_obj);
Py_DECREF(iter_obj);
return PyFloat_FromDouble(val);
}
/*
* Test PyArray_MultiIter_GOTO by jumping to a coordinate
* and returning (a_val, b_val) at that position.
*/
static PyObject *
limited_api_opaque_multi_iter_goto(PyObject *mod, PyObject *args)
{
PyArrayObject *a, *b;
PyObject *coord_tuple;
if (!PyArg_ParseTuple(args, "O!O!O!", &PyArray_Type, &a,
&PyArray_Type, &b,
&PyTuple_Type, &coord_tuple)) {
return NULL;
}
PyObject *multi = PyArray_MultiIterNew(2, a, b);
if (multi == NULL) {
return NULL;
}
int nd = _PyMIT(multi)->nd;
if (PyTuple_Size(coord_tuple) != nd) {
Py_DECREF(multi);
PyErr_SetString(PyExc_ValueError, "coordinate length mismatch");
return NULL;
}
npy_intp destination[NPY_MAXDIMS_LEGACY_ITERS];
for (int i = 0; i < nd; i++) {
destination[i] = PyLong_AsLong(PyTuple_GetItem(coord_tuple, i));
if (destination[i] == -1 && PyErr_Occurred()) {
Py_DECREF(multi);
return NULL;
}
}
PyArray_MultiIter_GOTO(multi, destination);
double va = *(double *)PyArray_MultiIter_DATA(multi, 0);
double vb = *(double *)PyArray_MultiIter_DATA(multi, 1);
Py_DECREF(multi);
return Py_BuildValue("dd", va, vb);
}
/*
* Test PyArray_MultiIter_GOTO1D by jumping to a flat index
* and returning (a_val, b_val) at that position.
*/
static PyObject *
limited_api_opaque_multi_iter_goto1d(PyObject *mod, PyObject *args)
{
PyArrayObject *a, *b;
npy_intp index;
if (!PyArg_ParseTuple(args, "O!O!n", &PyArray_Type, &a,
&PyArray_Type, &b, &index)) {
return NULL;
}
PyObject *multi = PyArray_MultiIterNew(2, a, b);
if (multi == NULL) {
return NULL;
}
PyArray_MultiIter_GOTO1D(multi, index);
double va = *(double *)PyArray_MultiIter_DATA(multi, 0);
double vb = *(double *)PyArray_MultiIter_DATA(multi, 1);
Py_DECREF(multi);
return Py_BuildValue("dd", va, vb);
}
/*
* Test PyArray_MultiIter_NEXTi by advancing only the first iterator
* and returning its data pointer value after N steps.
*/
static PyObject *
limited_api_opaque_multi_iter_nexti(PyObject *mod, PyObject *args)
{
PyArrayObject *a, *b;
int steps;
if (!PyArg_ParseTuple(args, "O!O!i", &PyArray_Type, &a,
&PyArray_Type, &b, &steps)) {
return NULL;
}
PyObject *multi = PyArray_MultiIterNew(2, a, b);
if (multi == NULL) {
return NULL;
}
for (int i = 0; i < steps; i++) {
PyArray_MultiIter_NEXTi(multi, 0);
}
double val = *(double *)PyArray_MultiIter_DATA(multi, 0);
Py_DECREF(multi);
return PyFloat_FromDouble(val);
}
static PyMethodDef limited_api_opaque_methods[] = {
{"nonzero", (PyCFunction)limited_api_opaque_nonzero, METH_O,
"Count the number of non-zero elements in the array."},
{"iter_next", (PyCFunction)limited_api_opaque_iter_next, METH_O,
"Sum array elements using PyArray_ITER_NEXT."},
{"iter_goto1d", (PyCFunction)limited_api_opaque_iter_goto1d, METH_VARARGS,
"Get element at flat index using PyArray_ITER_GOTO1D."},
{"iter_reset", (PyCFunction)limited_api_opaque_iter_reset, METH_O,
"Sum array elements after reset using PyArray_ITER_RESET."},
{"multi_iter_next", (PyCFunction)limited_api_opaque_multi_iter_next,
METH_VARARGS,
"Sum broadcast (a+b) using PyArray_MultiIter_NEXT."},
{"iter_goto", (PyCFunction)limited_api_opaque_iter_goto, METH_VARARGS,
"Get element at coordinate using PyArray_ITER_GOTO."},
{"multi_iter_goto", (PyCFunction)limited_api_opaque_multi_iter_goto,
METH_VARARGS,
"Get (a, b) at coordinate using PyArray_MultiIter_GOTO."},
{"multi_iter_goto1d", (PyCFunction)limited_api_opaque_multi_iter_goto1d,
METH_VARARGS,
"Get (a, b) at flat index using PyArray_MultiIter_GOTO1D."},
{"multi_iter_nexti", (PyCFunction)limited_api_opaque_multi_iter_nexti,
METH_VARARGS,
"Advance only iter 0 N steps using PyArray_MultiIter_NEXTi."},
{NULL, NULL, 0, NULL} /* Sentinel */
};
PyABIInfo_VAR(abi_info);
static PySlot limited_api_opaque_slots[] = {
PySlot_STATIC_DATA(Py_mod_abi, &abi_info),
PySlot_STATIC_DATA(Py_mod_name, "limited_api_opaque"),
PySlot_STATIC_DATA(Py_mod_methods, limited_api_opaque_methods),
PySlot_STATIC_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED),
PySlot_END,
};
PyMODEXPORT_FUNC
PyModExport_limited_api_opaque(void)
{
import_array();
import_umath();
return limited_api_opaque_slots;
}
#include <Python.h>
#include <numpy/arrayobject.h>
#include <numpy/ufuncobject.h>
static PyModuleDef moduledef = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "limited_api1"
};
PyMODINIT_FUNC PyInit_limited_api1(void)
{
import_array();
import_umath();
return PyModule_Create(&moduledef);
}
#cython: language_level=3
"""
Make sure cython can compile in limited API mode (see meson.build)
"""
cdef extern from "numpy/arrayobject.h":
pass
cdef extern from "numpy/arrayscalars.h":
pass

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display