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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ extend-ignore =

per-file-ignores =
mkl_umath/__init__.py: F401
mkl_umath/ufunc_docstrings_numpy1.py: E501
mkl_umath/ufunc_docstrings_numpy2.py: E501

exclude =
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ mkl_umath.restore_numpy_umath() # Restore original NumPy loops
- **Compatibility:** Must work with upstream NumPy APIs (NEP-36 compliance)
- **Testing:** Add tests to `mkl_umath/tests/test_basic.py`
- **Build hygiene:** `meson.build` is the source of truth for build config — verify Linux + Windows
- **Docs:** Update docstrings via `ufunc_docstrings_numpy{1,2}.py`
- **Docs:** Update docstrings via `ufunc_docstrings_numpy2.py`

## Code structure
- **Generated code:** `*.src` files are templates (conv_template.py processes them)
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [dev] - YYYY-MM-DD

### Changed
* Raised the minimum NumPy version to 2.0 at build time and at runtime in `pyproject.toml` and the conda recipe. `mkl_umath` built against NumPy 2 kept NumPy 2 behavior when run with NumPy 1.x, which the previous `numpy >=1.26.4` requirement allowed: complex `sign` returned NumPy 2 results and, on Windows, `patch_numpy_umath()` could not find NumPy loops matching the `int64` `ldexp` loops [gh-289](https://github.com/IntelPython/mkl_umath/pull/289)

### Removed
* Removed the NumPy 1 code paths: the `USE_NUMPY_2` switch with the NumPy 1 complex `sign` and `ldexp_long` loops, and `ufunc_docstrings_numpy1.py`. Building against NumPy 1 headers now fails with an explicit error [gh-289](https://github.com/IntelPython/mkl_umath/pull/289)

## [0.6.0] - 2026-10-15

### Added
Expand Down
2 changes: 1 addition & 1 deletion conda-recipe/conda_build_config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
numpy:
- '1.26.4'
- '2'
c_compiler: # [linux]
- gcc # [linux]
cxx_compiler: # [linux]
Expand Down
2 changes: 1 addition & 1 deletion conda-recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ requirements:
- {{ pin_compatible('mkl') }}
- {{ pin_compatible('intel-openmp') }}
- {{ pin_compatible('intel-cmplr-lib-rt') }}
- numpy >=1.26.4
- numpy >=2

test:
requires:
Expand Down
4 changes: 2 additions & 2 deletions mkl_umath/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Core MKL-backed ufunc implementation: Python interface, Cython patching, and C/M
- `tests/` — basic functionality and patching tests
- `generate_umath.py` — code generation for ufunc loops
- `generate_umath_doc.py` — docstring generation
- `ufunc_docstrings_numpy{1,2}.py` — NumPy version-specific docstrings
- `ufunc_docstrings_numpy2.py` — ufunc docstrings (adapted from NumPy 2)

## Patching API
```python
Expand All @@ -28,7 +28,7 @@ mkl_umath.is_patched() # Check patch status
## Code generation
- `*.src` files are templates processed by `_vendored/conv_template.py`
- Generated files: `src/__umath_generated.c`, loop implementations
- Docstrings: dual NumPy 1.x/2.x support via separate docstring modules
- Docstrings: `ufunc_docstrings_numpy2.py` (NumPy 1 is not supported)

## Notes
- `_patch_numpy.pyx` is Cython; changes require Cython rebuild
Expand Down
22 changes: 6 additions & 16 deletions mkl_umath/generate_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@
import string
import textwrap

import numpy as np

# identity objects
Zero = "PyLong_FromLong(0)"
One = "PyLong_FromLong(1)"
Expand Down Expand Up @@ -425,20 +423,12 @@ def english_upper(s):
# an object which expands a list of character codes into an array of
# TypeDescriptions.

if np.lib.NumpyVersion(np.__version__) < "2.0.0":
ldexp_signature = [
TypeDescription("f", None, "fi", "f"),
TypeDescription("f", FuncNameSuffix("long"), "fl", "f"),
TypeDescription("d", None, "di", "d"),
TypeDescription("d", FuncNameSuffix("long"), "dl", "d"),
]
else:
ldexp_signature = [
TypeDescription("f", None, "fi", "f"),
TypeDescription("f", FuncNameSuffix("int64"), "f" + int64, "f"),
TypeDescription("d", None, "di", "d"),
TypeDescription("d", FuncNameSuffix("int64"), "d" + int64, "d"),
]
ldexp_signature = [
TypeDescription("f", None, "fi", "f"),
TypeDescription("f", FuncNameSuffix("int64"), "f" + int64, "f"),
TypeDescription("d", None, "di", "d"),
TypeDescription("d", FuncNameSuffix("int64"), "d" + int64, "d"),
]

defdict = {
"add": Ufunc(
Expand Down
8 changes: 2 additions & 6 deletions mkl_umath/generate_umath_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,9 @@
import sys
import textwrap

import numpy as np

sys.path.insert(0, os.path.dirname(__file__))
if np.lib.NumpyVersion(np.__version__) < "2.0.0":
import ufunc_docstrings_numpy1 as docstrings
else:
import ufunc_docstrings_numpy2 as docstrings
import ufunc_docstrings_numpy2 as docstrings # noqa: E402

sys.path.pop(0)


Expand Down
103 changes: 26 additions & 77 deletions mkl_umath/src/mkl_umath_loops.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,6 @@
#include "blocking_utils.h"
#include "mkl_umath_loops.h"

// NPY_2_0_API_VERSION 0x00000012 is defined in numpy-2
// inside numpy/_core/include/numpy/numpyconfig.h
#if NPY_API_VERSION >= 0x00000012
#define USE_NUMPY_2
#endif

/* Adapted from NumPy's source code.
* https://github.com/numpy/numpy/blob/main/LICENSE.txt */

Expand Down Expand Up @@ -1104,7 +1098,6 @@ mkl_umath_@TYPE@_ldexp(char **args, const npy_intp *dimensions, const npy_intp *
}
}

#ifdef USE_NUMPY_2
void
mkl_umath_@TYPE@_ldexp_int64(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func))
{
Expand Down Expand Up @@ -1134,37 +1127,6 @@ mkl_umath_@TYPE@_ldexp_int64(char **args, const npy_intp *dimensions, const npy_
}
}
}
#else
void
mkl_umath_@TYPE@_ldexp_long(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func))
{
/*
* Additional loop to handle npy_long integer inputs (cf. #866, #1633).
* npy_long != npy_int on many 64-bit platforms, so we need this second loop
* to handle the default integer type.
*/
BINARY_LOOP {
const @type@ in1 = *(@type@ *)ip1;
const long in2 = *(long *)ip2;
if (((int)in2) == in2) {
/* Range OK */
*((@type@ *)op1) = ldexp@c@(in1, ((int)in2));
}
else {
/*
* Outside npy_int range -- also ldexp will overflow in this case,
* given that exponent has less bits than npy_int.
*/
if (in2 > 0) {
*((@type@ *)op1) = ldexp@c@(in1, NPY_MAX_INT);
}
else {
*((@type@ *)op1) = ldexp@c@(in1, NPY_MIN_INT);
}
}
}
}
#endif
/**end repeat**/

/*
Expand Down Expand Up @@ -1552,52 +1514,39 @@ mkl_umath_@TYPE@_absolute(char **args, const npy_intp *dimensions, const npy_int
void
mkl_umath_@TYPE@_sign(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func))
{
#ifdef USE_NUMPY_2
UNARY_LOOP {
const @ftype@ in_r = ((@ftype@ *)ip1)[0];
const @ftype@ in_i = ((@ftype@ *)ip1)[1];
const @ftype@ in_abs = hypot@c@(in_r, in_i);
if (NPY_UNLIKELY(npy_isnan(in_abs))) {
((@ftype@ *)op1)[0] = NPY_NAN@C@;
((@ftype@ *)op1)[1] = NPY_NAN@C@;
}
else if (NPY_UNLIKELY(npy_isinf(in_abs))) {
if (npy_isinf(in_r)) {
if (npy_isinf(in_i)) {
((@ftype@ *)op1)[0] = NPY_NAN@C@;
((@ftype@ *)op1)[1] = NPY_NAN@C@;
}
else {
((@ftype@ *)op1)[0] = in_r > 0 ? 1.: -1.;
((@ftype@ *)op1)[1] = 0.;
}
UNARY_LOOP {
const @ftype@ in_r = ((@ftype@ *)ip1)[0];
const @ftype@ in_i = ((@ftype@ *)ip1)[1];
const @ftype@ in_abs = hypot@c@(in_r, in_i);
if (NPY_UNLIKELY(npy_isnan(in_abs))) {
((@ftype@ *)op1)[0] = NPY_NAN@C@;
((@ftype@ *)op1)[1] = NPY_NAN@C@;
}
else if (NPY_UNLIKELY(npy_isinf(in_abs))) {
if (npy_isinf(in_r)) {
if (npy_isinf(in_i)) {
((@ftype@ *)op1)[0] = NPY_NAN@C@;
((@ftype@ *)op1)[1] = NPY_NAN@C@;
}
else {
((@ftype@ *)op1)[0] = 0.;
((@ftype@ *)op1)[1] = in_i > 0 ? 1.: -1.;
((@ftype@ *)op1)[0] = in_r > 0 ? 1.: -1.;
((@ftype@ *)op1)[1] = 0.;
}
}
else if (NPY_UNLIKELY(in_abs == 0)) {
((@ftype@ *)op1)[0] = 0.@C@;
((@ftype@ *)op1)[1] = 0.@C@;
}
else{
((@ftype@ *)op1)[0] = in_r / in_abs;
((@ftype@ *)op1)[1] = in_i / in_abs;
else {
((@ftype@ *)op1)[0] = 0.;
((@ftype@ *)op1)[1] = in_i > 0 ? 1.: -1.;
}
}
#else
/* fixme: sign of nan is currently 0 */
UNARY_LOOP {
const @ftype@ in1r = ((@ftype@ *)ip1)[0];
const @ftype@ in1i = ((@ftype@ *)ip1)[1];
((@ftype@ *)op1)[0] = CGT(in1r, in1i, 0.0, 0.0) ? 1 :
(CLT(in1r, in1i, 0.0, 0.0) ? -1 :
(CEQ(in1r, in1i, 0.0, 0.0) ? 0 : NPY_NAN@C@));
((@ftype@ *)op1)[1] = 0;
else if (NPY_UNLIKELY(in_abs == 0)) {
((@ftype@ *)op1)[0] = 0.@C@;
((@ftype@ *)op1)[1] = 0.@C@;
}
feclearexcept(FE_INVALID);
#endif
else{
((@ftype@ *)op1)[0] = in_r / in_abs;
((@ftype@ *)op1)[1] = in_i / in_abs;
}
}
}

/**begin repeat1
Expand Down
10 changes: 2 additions & 8 deletions mkl_umath/src/mkl_umath_loops.h.src
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@

// NPY_2_0_API_VERSION 0x00000012 is defined in numpy-2
// inside numpy/_core/include/numpy/numpyconfig.h
#if NPY_API_VERSION >= 0x00000012
#define USE_NUMPY_2
#if NPY_API_VERSION < 0x00000012
#error "mkl_umath must be built against NumPy >= 2.0"
#endif

/**begin repeat
Expand All @@ -66,15 +66,9 @@ void
mkl_umath_@TYPE@_@kind@(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func));
/**end repeat1**/

#ifdef USE_NUMPY_2
MKL_UMATH_API
void
mkl_umath_@TYPE@_ldexp_int64(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func));
#else
MKL_UMATH_API
void
mkl_umath_@TYPE@_ldexp_long(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func));
#endif

/**end repeat**/

Expand Down
Loading
Loading