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
20 changes: 20 additions & 0 deletions doc/release/upcoming_changes/30411.compatibility.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
* `linalg.eig` and `linalg.eigvals` always return complex arrays. Previously, the return
values were depening on whether the eigenvalues happen to lie on the real line (which, for
a general, non-symmetric matrix, is not guaranteed).

The user change depends on your usage:

- to retain the previous behavior, do

```
w = eigvals(a)
if w.imag == 0: # this is what NumPy used to do
w = w.real
```

- if your matrix is symmetrix/hermitian, use `eigh` and `eigvalsh` instead of `eig` and `eigvals`:
these are guaranteed to return real values. (A common case is covariance matrices, which are
symmetric and positive definite by construction)



4 changes: 4 additions & 0 deletions doc/release/upcoming_changes/30738.deprecation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
``numpy.ma.round_`` is deprecated
---------------------------------
``numpy.ma.round_`` is deprecated.
``numpy.ma.round`` can be used as a replacement.
1 change: 1 addition & 0 deletions doc/release/upcoming_changes/30802.deprecation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* The ``numpy.char.[as]array`` functions are deprecated. Use an ``numpy.[as]array`` with a string or bytes dtype instead.
8 changes: 8 additions & 0 deletions numpy/_core/defchararray.py
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,10 @@ def array(obj, itemsize=None, copy=True, unicode=None, order=None):
"""
Create a `~numpy.char.chararray`.

.. deprecated:: 2.5
``chararray`` is deprecated. Use an ``ndarray`` with a string or
bytes dtype instead.

.. note::
This class is provided for numarray backward-compatibility.
New code (not concerned with numarray compatibility) should use
Expand Down Expand Up @@ -1363,6 +1367,10 @@ def asarray(obj, itemsize=None, unicode=None, order=None):
Convert the input to a `~numpy.char.chararray`, copying the data only if
necessary.

.. deprecated:: 2.5
``chararray`` is deprecated. Use an ``ndarray`` with a string or
bytes dtype instead.

Versus a NumPy array of dtype `bytes_` or `str_`, this
class adds the following functionality:

Expand Down
14 changes: 14 additions & 0 deletions numpy/_core/defchararray.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ def str_len(A: UST_co) -> NDArray[int_]: ...
# overload 5 and 6: arbitrary object with unicode=True (-> str_)
# overload 7: arbitrary object with unicode=None (default) (-> str_ | bytes_)
@overload
@deprecated("numpy.char.array is deprecated and will be removed in a future release.")
def array(
obj: U_co,
itemsize: int | None = None,
Expand All @@ -669,6 +670,7 @@ def array(
order: _OrderKACF = None,
) -> _CharArray[str_]: ...
@overload
@deprecated("numpy.char.array is deprecated and will be removed in a future release.")
def array(
obj: S_co,
itemsize: int | None = None,
Expand All @@ -677,6 +679,7 @@ def array(
order: _OrderKACF = None,
) -> _CharArray[bytes_]: ...
@overload
@deprecated("numpy.char.array is deprecated and will be removed in a future release.")
def array(
obj: object,
itemsize: int | None,
Expand All @@ -685,6 +688,7 @@ def array(
order: _OrderKACF = None,
) -> _CharArray[bytes_]: ...
@overload
@deprecated("numpy.char.array is deprecated and will be removed in a future release.")
def array(
obj: object,
itemsize: int | None = None,
Expand All @@ -694,6 +698,7 @@ def array(
order: _OrderKACF = None,
) -> _CharArray[bytes_]: ...
@overload
@deprecated("numpy.char.array is deprecated and will be removed in a future release.")
def array(
obj: object,
itemsize: int | None,
Expand All @@ -702,6 +707,7 @@ def array(
order: _OrderKACF = None,
) -> _CharArray[str_]: ...
@overload
@deprecated("numpy.char.array is deprecated and will be removed in a future release.")
def array(
obj: object,
itemsize: int | None = None,
Expand All @@ -711,6 +717,7 @@ def array(
order: _OrderKACF = None,
) -> _CharArray[str_]: ...
@overload
@deprecated("numpy.char.array is deprecated and will be removed in a future release.")
def array(
obj: object,
itemsize: int | None = None,
Expand All @@ -720,27 +727,31 @@ def array(
) -> _CharArray[str_] | _CharArray[bytes_]: ...

@overload
@deprecated("numpy.char.asarray is deprecated and will be removed in a future release.")
def asarray(
obj: U_co,
itemsize: int | None = None,
unicode: L[True] | None = None,
order: _OrderKACF = None,
) -> _CharArray[str_]: ...
@overload
@deprecated("numpy.char.asarray is deprecated and will be removed in a future release.")
def asarray(
obj: S_co,
itemsize: int | None = None,
unicode: L[False] | None = None,
order: _OrderKACF = None,
) -> _CharArray[bytes_]: ...
@overload
@deprecated("numpy.char.asarray is deprecated and will be removed in a future release.")
def asarray(
obj: object,
itemsize: int | None,
unicode: L[False],
order: _OrderKACF = None,
) -> _CharArray[bytes_]: ...
@overload
@deprecated("numpy.char.asarray is deprecated and will be removed in a future release.")
def asarray(
obj: object,
itemsize: int | None = None,
Expand All @@ -749,13 +760,15 @@ def asarray(
order: _OrderKACF = None,
) -> _CharArray[bytes_]: ...
@overload
@deprecated("numpy.char.asarray is deprecated and will be removed in a future release.")
def asarray(
obj: object,
itemsize: int | None,
unicode: L[True],
order: _OrderKACF = None,
) -> _CharArray[str_]: ...
@overload
@deprecated("numpy.char.asarray is deprecated and will be removed in a future release.")
def asarray(
obj: object,
itemsize: int | None = None,
Expand All @@ -764,6 +777,7 @@ def asarray(
order: _OrderKACF = None,
) -> _CharArray[str_]: ...
@overload
@deprecated("numpy.char.asarray is deprecated and will be removed in a future release.")
def asarray(
obj: object,
itemsize: int | None = None,
Expand Down
3 changes: 2 additions & 1 deletion numpy/_core/tests/test_defchararray.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
kw_unicode_false = {'unicode': False}

ignore_charray_deprecation = pytest.mark.filterwarnings(
r"ignore:\w+ chararray \w+:DeprecationWarning"
r"ignore:\w+ (chararray|array|asarray) \w+:DeprecationWarning"
)


@ignore_charray_deprecation
class TestBasic:
def test_from_object_array(self):
A = np.array([['abc', 2],
Expand Down
10 changes: 10 additions & 0 deletions numpy/_core/tests/test_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,13 @@ class TestTypenameDeprecation(_DeprecationTestCase):
def test_typename_emits_deprecation_warning(self):
self.assert_deprecated(lambda: np.typename("S1"))
self.assert_deprecated(lambda: np.typename("h"))

class TestRoundDeprecation(_DeprecationTestCase):
# Deprecation in NumPy 2.5, 2026-02

def test_round_emits_deprecation_warning_array(self):
a = np.array([1.5, 2.7, -1.5, -2.7])
self.assert_deprecated(lambda: np.ma.round_(a))

def test_round_emits_deprecation_warning_scalar(self):
self.assert_deprecated(lambda: np.ma.round_(3.14))
8 changes: 3 additions & 5 deletions numpy/char/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from numpy._core.defchararray import __all__, __doc__

__DEPRECATED = frozenset({"chararray", "array", "asarray"})


def __getattr__(name: str):
if name == "chararray":
if name in __DEPRECATED:
# Deprecated in NumPy 2.5, 2026-01-07
import warnings

Expand All @@ -15,10 +17,6 @@ def __getattr__(name: str):
stacklevel=2,
)

from numpy._core.defchararray import chararray

return chararray

import numpy._core.defchararray as char

if (export := getattr(char, name, None)) is not None:
Expand Down
1 change: 1 addition & 0 deletions numpy/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ def warnings_errors_and_rng(test=None):
"numpy.fix is deprecated", # fix -> trunc
"The chararray class is deprecated", # char.chararray
"numpy.typename is deprecated", # typename -> dtype.name
"numpy.ma.round_ is deprecated", # ma.round_ -> ma.round
]
msg = "|".join(msgs)

Expand Down
4 changes: 4 additions & 0 deletions numpy/lib/_polynomial_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ def roots(p):
A = diag(NX.ones((N - 2,), p.dtype), -1)
A[0, :] = -p[1:] / p[0]
roots = eigvals(A)

# backwards compat: return real values if possible
from numpy.linalg._linalg import _to_real_if_imag_zero
roots = _to_real_if_imag_zero(roots, A)
else:
roots = NX.array([])

Expand Down
8 changes: 8 additions & 0 deletions numpy/lib/tests/test_polynomial.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ def test_roots(self):
# to take into account numerical calculation error
assert_almost_equal(res, tgt, 14 - int(np.log10(i)))

@pytest.mark.parametrize("dtyp", [int, np.float32, np.float64])
def test_roots_dtype(self, dtyp):
coef = np.asarray([1, 0, -1], dtype=dtyp) # x**2 - 1
r = np.roots(coef)
r.sort()
assert_allclose(r, np.asarray([-1, 1]))
assert r.dtype == {int: np.float64}.get(dtyp, dtyp)

def test_str_leading_zeros(self):
p = np.poly1d([4, 3, 2, 1])
p[3] = 0
Expand Down
46 changes: 23 additions & 23 deletions numpy/linalg/_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,19 @@ def _realType(t, default=double):
def _complexType(t, default=cdouble):
return _complex_types_map.get(t, default)


def _to_real_if_imag_zero(w, t):
"""Backwards compat helper: force w to be real if t.dtype is real and w.imag == 0
"""
result_t = t.dtype.type
if not isComplexType(result_t) and all(w.imag == 0.0):
w = w.real
result_t = _realType(result_t)
else:
result_t = _complexType(result_t)
return w.astype(result_t, copy=False)


def _commonType(*arrays):
# in lite version, use higher precision (always double or cdouble)
result_type = single
Expand Down Expand Up @@ -1229,11 +1242,11 @@ def eigvals(a):

>>> D = np.diag((-1,1))
>>> LA.eigvals(D)
array([-1., 1.])
array([-1. + 0.j, 1. + 0.j])
>>> A = np.dot(Q, D)
>>> A = np.dot(A, Q.T)
>>> LA.eigvals(A)
array([ 1., -1.]) # random
array([ 1., -1.]) # random

"""
a, wrap = _makearray(a)
Expand All @@ -1247,14 +1260,7 @@ def eigvals(a):
under='ignore'):
w = _umath_linalg.eigvals(a, signature=signature)

if not isComplexType(t):
if all(w.imag == 0):
w = w.real
result_t = _realType(result_t)
else:
result_t = _complexType(result_t)

return w.astype(result_t, copy=False)
return w.astype(_complexType(result_t), copy=False)


def _eigvalsh_dispatcher(a, UPLO=None):
Expand Down Expand Up @@ -1450,8 +1456,8 @@ def eig(a):

>>> eigenvalues, eigenvectors = LA.eig(np.diag((1, 2, 3)))
>>> eigenvalues
array([1., 2., 3.])
>>> eigenvectors
array([1. + 0j, 2. + 0j, 3. + 0j])
>>> eigenvectors.real
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
Expand Down Expand Up @@ -1483,8 +1489,8 @@ def eig(a):
>>> # Theor. eigenvalues are 1 +/- 1e-9
>>> eigenvalues, eigenvectors = LA.eig(a)
>>> eigenvalues
array([1., 1.])
>>> eigenvectors
array([1.+0j, 1.+0j])
>>> eigenvectors.real
array([[1., 0.],
[0., 1.]])

Expand All @@ -1500,15 +1506,9 @@ def eig(a):
under='ignore'):
w, vt = _umath_linalg.eig(a, signature=signature)

if not isComplexType(t) and all(w.imag == 0.0):
w = w.real
vt = vt.real
result_t = _realType(result_t)
else:
result_t = _complexType(result_t)

vt = vt.astype(result_t, copy=False)
return EigResult(w.astype(result_t, copy=False), wrap(vt))
w = w.astype(_complexType(result_t), copy=False)
vt = vt.astype(_complexType(result_t), copy=False)
return EigResult(w, wrap(vt))


@array_function_dispatch(_eigvalsh_dispatcher)
Expand Down
4 changes: 2 additions & 2 deletions numpy/linalg/_linalg.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,11 @@ def eig(a: NDArray[np.inexact[Never]]) -> EigResult: ...
@overload # ~complex128
def eig(a: _AsArrayC128) -> EigResult[np.complex128]: ...
@overload # +float64
def eig(a: _ToArrayF64) -> EigResult[np.complex128] | EigResult[np.float64]: ...
def eig(a: _ToArrayF64) -> EigResult[np.complex128]: ...
@overload # ~complex64
def eig(a: _ArrayLike[np.complex64]) -> EigResult[np.complex64]: ...
@overload # ~float32
def eig(a: _ArrayLike[np.float32]) -> EigResult[np.complex64] | EigResult[np.float32]: ...
def eig(a: _ArrayLike[np.float32]) -> EigResult[np.complex64]: ...
@overload # fallback
def eig(a: _ArrayLikeComplex_co) -> EigResult: ...

Expand Down
12 changes: 6 additions & 6 deletions numpy/linalg/tests/test_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ class TestEigvals(EigvalsCases):
@pytest.mark.parametrize('dtype', [single, double, csingle, cdouble])
def test_types(self, dtype):
x = np.array([[1, 0.5], [0.5, 1]], dtype=dtype)
assert_equal(linalg.eigvals(x).dtype, dtype)
assert_equal(linalg.eigvals(x).dtype, get_complex_dtype(dtype))
x = np.array([[1, 0.5], [-1, 1]], dtype=dtype)
assert_equal(linalg.eigvals(x).dtype, get_complex_dtype(dtype))

Expand All @@ -614,7 +614,7 @@ class ArraySubclass(np.ndarray):
pass
a = np.zeros((0, 1, 1), dtype=np.int_).view(ArraySubclass)
res = linalg.eigvals(a)
assert_(res.dtype.type is np.float64)
assert_(res.dtype.type is np.complex128)
assert_equal((0, 1), res.shape)
# This is just for documentation, it might make sense to change:
assert_(isinstance(res, np.ndarray))
Expand Down Expand Up @@ -643,8 +643,8 @@ class TestEig(EigCases):
def test_types(self, dtype):
x = np.array([[1, 0.5], [0.5, 1]], dtype=dtype)
w, v = np.linalg.eig(x)
assert_equal(w.dtype, dtype)
assert_equal(v.dtype, dtype)
assert_equal(w.dtype, get_complex_dtype(dtype))
assert_equal(v.dtype, get_complex_dtype(dtype))

x = np.array([[1, 0.5], [-1, 1]], dtype=dtype)
w, v = np.linalg.eig(x)
Expand All @@ -657,8 +657,8 @@ class ArraySubclass(np.ndarray):
pass
a = np.zeros((0, 1, 1), dtype=np.int_).view(ArraySubclass)
res, res_v = linalg.eig(a)
assert_(res_v.dtype.type is np.float64)
assert_(res.dtype.type is np.float64)
assert_(res_v.dtype.type is np.complex128)
assert_(res.dtype.type is np.complex128)
assert_equal(a.shape, res_v.shape)
assert_equal((0, 1), res.shape)
# This is just for documentation, it might make sense to change:
Expand Down
Loading
Loading