numpy.unwrap changes targeting 2.6.0 (new _unwrap gufunc)

Iason Krommydas via NumPy-Discussion <[email protected]> Tue, 21 Jul 2026 20:56:13 -0500
Newsgroups gmane.comp.python.numeric.general
Message-ID <CAMnQhRFRtr742Gmm8LzXKLYqKBr7X6WJTSu0R2VGxq1ZvC-P-g@mail.gmail.com>
--===============2604038207089080382==
Content-Type: multipart/alternative; boundary="00000000000069a796065729724c"

--00000000000069a796065729724c
Content-Type: text/plain; charset="UTF-8"

Hi all,

In gh-31848 <https://github.com/numpy/numpy/pull/31848>, numpy.unwrap has
been reimplemented as a generalized ufunc, targeting the 2.6.0 release.
Below is a summary of what's changing.

The core of unwrap is now a generalized ufunc written in C++. That is a new
private ufunc _unwrap with signature (n),(),()->(n), covering the  floating
point and signed integer dtypes, computed in a single pass without the
intermediate arrays the previous python implementation had to allocate.
We've seen that this offers a 2x-3x speedup and lower memory usage.

As a result, unwrap now preserves ndarray subclasses instead of always
returning a base ndarray. The one exception is masked arrays. A mask-aware
unwrap is genuinely ill-defined for this algorithm, so masked arrays (and
their subclasses) get unmasked and the operation runs on a plain ndarray
instead, same as before. I will open a separate issue and have put a TODO
in the codebase to perhaps get a proper mask-aware unwrap in the np.ma
module.

Two edge-case behavior changes:
- An explicitly typed discont argument wider than the result dtype used to
get promoted. It's now compared at the result dtype instead. This can
change the result by up to ~1 ULP in that specific case.
- Calling unwrap with an unsigned integer period that can't represent the
values needed internally used to raise OverflowError (from an internal mod
call). it now raises TypeError (a "no loop found" ufunc error reporting the
mismatched dtypes) instead.

If you maintain an array library that implements __array_ufunc__ and tries
to stay compatible with all of  numpy, you'll now see a call to _unwrap
where unwrap previously dispatched through a sequence of familiar public
ufuncs (subtract, remainder, cumsum,   etc.). If your override doesn't
recognize _unwrap but still handles those underlying ops, unwrap falls back
to a pure Python path built from them, so things should keep working. If it
declines everything unconditionally, unwrap will now raise instead of
silently doing the wrong thing. Astropy is the library we know of that
might care here, flagging in case others maintain something similar.

Kind regards,
Iason.

--00000000000069a796065729724c
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr"><div>Hi all,</div><div><br></div><div>In=C2=A0<a href=3D"h=
ttps://github.com/numpy/numpy/pull/31848">gh-31848</a>,=C2=A0numpy.unwrap h=
as been reimplemented as a generalized ufunc, targeting=C2=A0the 2.6.0 rele=
ase. Below is a summary of what&#39;s changing.</div><div><br></div><div>Th=
e core of unwrap is now a generalized ufunc written in C++. That is a new p=
rivate ufunc _unwrap with signature (n),(),()-&gt;(n), covering the=C2=A0 f=
loating point and signed integer dtypes, computed in a single pass without =
the intermediate arrays the previous python implementation had to allocate.=
 We&#39;ve seen that this offers a 2x-3x speedup and lower memory usage.</d=
iv><div><br></div><div>As a result, unwrap=C2=A0now preserves ndarray subcl=
asses instead of always returning a base ndarray. The one exception is mask=
ed arrays. A mask-aware unwrap is genuinely ill-defined for this algorithm,=
 so masked arrays (and their subclasses) get unmasked and the operation run=
s on a plain ndarray instead, same as before. I will open a separate issue =
and have put a TODO in the codebase to perhaps get a proper mask-aware unwr=
ap in the <a href=3D"http://np.ma">np.ma</a> module.</div><div><br></div><d=
iv>Two edge-case behavior changes:<br>- An explicitly typed discont argumen=
t wider than the result dtype used to get promoted. It&#39;s now compared a=
t the result dtype instead. This can change the result by up to ~1 ULP in t=
hat specific case.<br>- Calling unwrap with an unsigned integer period that=
 can&#39;t represent the values needed internally used to raise OverflowErr=
or (from an internal mod call). it now raises TypeError (a &quot;no loop fo=
und&quot; ufunc error reporting the mismatched dtypes) instead.<br><br></di=
v><div>If you maintain an array library that implements __array_ufunc__ and=
 tries to stay compatible with all of=C2=A0 numpy, you&#39;ll now see a cal=
l to _unwrap where unwrap previously dispatched through a sequence of famil=
iar public ufuncs (subtract, remainder, cumsum, =C2=A0 etc.). If your overr=
ide doesn&#39;t recognize _unwrap but still handles those underlying ops, u=
nwrap falls back to a pure Python path built from them, so things should ke=
ep working. If it declines everything unconditionally, unwrap will now rais=
e instead of silently doing the wrong thing. Astropy is the library we know=
 of that might care here, flagging in case others maintain something simila=
r.</div><div><br></div><div>Kind regards,</div><div>Iason.</div></div>

--00000000000069a796065729724c--

--===============2604038207089080382==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
NumPy-Discussion mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/numpy-discussion.python.org
Member address: [email protected]

--===============2604038207089080382==--