Skip to content

Complete bcomplex32 real/imag on NumPy 2.5+#383

Open
DeanTMaxim wants to merge 2 commits into
jax-ml:mainfrom
DeanTMaxim:take-over-375
Open

Complete bcomplex32 real/imag on NumPy 2.5+#383
DeanTMaxim wants to merge 2 commits into
jax-ml:mainfrom
DeanTMaxim:take-over-375

Conversation

@DeanTMaxim

@DeanTMaxim DeanTMaxim commented Jun 14, 2026

Copy link
Copy Markdown

Takes over #375 per the discussion in #355.

What this changes

  • Registers real/imag as ufunc loops on np.real/np.imag for bcomplex32 and complex32 via PyUFunc_AddLoopsFromSpecs (NumPy 2.4+ API), gated on PyArray_RUNTIME_VERSION >= 0x16 so NumPy <2.5 is a no-op. On 2.5+, arr.real/arr.imag now return correct results. The loop is a zero-copy view (NPY_NO_CASTING + view_offset 0 / elsize) with a reinterpret-based strided fallback.
  • CI: adds a NumPy 2.5 pre-release job.
  • Docs: bcomplex32.__doc__ lists the other complex-aware builtins that don't recognize custom complex dtypes, with one-line workarounds.

Differences from your draft (#375)

  • Builds as C++17 (not C++20): the PyArrayMethod_Spec uses field assignment instead of designated initializers, so there's no build-standard change.
  • Added static_asserts in the strided loop guarding the [real, imag] no-padding layout the view/extraction relies on.
  • Added the host-side-limitations note to the docstring. (No <2.5 helper warning — per review it only reached the already-correct helper, not arr.real.)

Remaining limitations (documented, out of scope here)

np.iscomplex, np.vdot, np.linalg.norm, np.angle, np.linalg.det/inv still don't recognize these dtypes on any NumPy version. Root cause: NumPy keys "is complex" off the builtin type-number range (PyTypeNum_ISCOMPLEX), not the dtype kind; these are registered with kind='W' (using 'c' would collide bcomplex32 with complex32 under same-kind+same-size equality — verified empirically that even kind='c' leaves issubdtype(complexfloating) False). Documented in bcomplex32.__doc__; a permanent fix is NumPy-side (extending NEP 42 so user DTypes can declare complex-ness) and tracked separately.

Status

  • Tested locally against NumPy 2.5.0rc1 (432 tests pass).
  • Verified the < 0x16 runtime gate: arr.real correct on 2.5.0rc1, silently wrong on 2.4.4.

cc @seberg — happy to adjust. Intended for v0.5.5; unblocks jax-ml/jax#38392.

v0.5.5 release notes (draft)

  • arr.real / arr.imag now work correctly for bcomplex32 / complex32 arrays on NumPy 2.5+.

Takes over jax-ml#375 per issue jax-ml#355.

Registers real/imag as ufunc loops on the np.real/np.imag ufuncs via
PyUFunc_AddLoopsFromSpecs (the NumPy 2.4+ convenience API), gated on
PyArray_RUNTIME_VERSION >= 0x16 (NPY_2_5_API_VERSION) so NumPy <2.5 skips
registration. On NumPy 2.5+ this makes arr.real / arr.imag return correct
results for bcomplex32 and complex32, which NumPy otherwise cannot
recognize as complex (their dtype kind is 'W', not 'c').

The registered loop returns NPY_NO_CASTING with a view_offset (0 for the
real half, elsize for the imaginary half), so the result is a zero-copy
view into the input buffer, with a reinterpret-based strided loop as the
fallback. static_asserts guard the [real, imag] no-padding layout that the
view/extraction relies on. PyUFunc_AddLoopsFromSpecs is backported in
numpy.h (slot 47) so a single wheel runs across NumPy versions.

On NumPy <2.5 the native arr.real/arr.imag are silently wrong, so the
ml_dtypes.real/imag Python helpers (correct on all versions) emit a
RuntimeWarning steering users toward the helpers or an upgrade.

Builds as C++17: the PyArrayMethod_Spec is constructed by field assignment
rather than designated initializers, so no build-standard change is
needed. Adds a NumPy 2.5 pre-release job to the CI matrix.

Several other complex-aware builtins (np.iscomplex, np.vdot,
np.linalg.norm, np.angle, np.linalg.det/inv) do not recognize these custom
complex dtypes on any NumPy version, because NumPy keys complex-ness off
the builtin type-number range rather than the dtype kind; bcomplex32.__doc__
documents these with one-line workarounds.

Tested locally against NumPy 2.5.0rc1.
Comment thread ml_dtypes/__init__.py Outdated
Returns:
The real part of the input array.
"""
_warn_old_numpy("real")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice thought, but this functino is fine. We would basically have to use a module level __getattr__ and warn on first use of any of the complex type.
I am not sure that is actually worth it, though since it is also easy to miss or need to ignore and then does it globally.

(We could if others like it though, or just limit them to NumPy 2.5+ even if a bit of a shame.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped — relying on the docstring now (a module-level getattr would need the types lazily exposed to actually fire).

The warning fired inside ml_dtypes.real/imag, which are already correct on
all NumPy versions, so it only reached users of the safe helpers -- not the
arr.real/arr.imag users who hit the actual silent-wrong on <2.5. Drop it and
rely on the docstring; a module-level __getattr__ guard would be the correct
placement but needs the types exposed lazily to actually fire.

Removes _warn_old_numpy, its calls, the warning test, the now-unneeded
RuntimeWarning suppressions in the other tests, and the CHANGELOG entry.
@DeanTMaxim DeanTMaxim changed the title Complete bcomplex32 real/imag on NumPy 2.5+ and warn on NumPy <2.5 Complete bcomplex32 real/imag on NumPy 2.5+ Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants