Skip to content
Draft
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
4 changes: 4 additions & 0 deletions array_api_compat/common/_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class EighResult(NamedTuple):
eigenvalues: Array
eigenvectors: Array

class EigResult(NamedTuple):
eigenvalues: Array
eigenvectors: Array

class QRResult(NamedTuple):
Q: Array
R: Array
Expand Down
80 changes: 80 additions & 0 deletions array_api_compat/numpy/linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
cross = get_xp(np)(_linalg.cross)
outer = get_xp(np)(_linalg.outer)
EighResult = _linalg.EighResult
EigResult = _linalg.EigResult
QRResult = _linalg.QRResult
SlogdetResult = _linalg.SlogdetResult
SVDResult = _linalg.SVDResult
Expand Down Expand Up @@ -97,6 +98,85 @@ def solve(x1: Array, x2: Array, /) -> Array:
return wrap(r.astype(result_t, copy=False))


# Unlike numpy.linalg.eig, Array API version always returns complex results

def eig(x: Array, /) -> tuple[Array, Array]:
try:
from numpy.linalg._linalg import ( # type: ignore[attr-defined]
_assert_stacked_square,
_assert_finite,
_commonType,
_makearray,
_raise_linalgerror_eigenvalues_nonconvergence,
isComplexType,
_complexType,
)
except ImportError:
from numpy.linalg.linalg import ( # type: ignore[attr-defined]
_assert_stacked_square,
_assert_finite,
_commonType,
_makearray,
_raise_linalgerror_eigenvalues_nonconvergence,
isComplexType,
_complexType,
)
from numpy.linalg import _umath_linalg

x, wrap = _makearray(x)
_assert_stacked_square(x)
_assert_finite(x)
t, result_t = _commonType(x)

signature = 'D->DD' if isComplexType(t) else 'd->DD'
with np.errstate(call=_raise_linalgerror_eigenvalues_nonconvergence,
invalid='call', over='ignore', divide='ignore',
under='ignore'):
w, vt = _umath_linalg.eig(x, signature=signature)

result_t = _complexType(result_t)
vt = vt.astype(result_t, copy=False)
return EigResult(w.astype(result_t, copy=False), wrap(vt))
Comment on lines +103 to +139
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

The return type annotation for the eig function indicates it returns a tuple[Array, Array], but the implementation returns an EigResult (a NamedTuple). This inconsistency should be corrected to return EigResult instead of tuple[Array, Array] to match the actual behavior and to be consistent with similar functions like eigh which properly returns EighResult.

Copilot uses AI. Check for mistakes.


def eigvals(x: Array, /) -> Array:
try:
from numpy.linalg._linalg import ( # type: ignore[attr-defined]
_assert_stacked_square,
_assert_finite,
_commonType,
_makearray,
_raise_linalgerror_eigenvalues_nonconvergence,
isComplexType,
_complexType,
)
except ImportError:
from numpy.linalg.linalg import ( # type: ignore[attr-defined]
_assert_stacked_square,
_assert_finite,
_commonType,
_makearray,
_raise_linalgerror_eigenvalues_nonconvergence,
isComplexType,
_complexType,
)
from numpy.linalg import _umath_linalg

x, wrap = _makearray(x)
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

The variable 'wrap' returned from _makearray is not used in the eigvals function, but is assigned. This suggests either it should be used (similar to how it's used in the eig function to wrap the result) or should be replaced with an underscore to indicate it's intentionally unused.

Copilot uses AI. Check for mistakes.
_assert_stacked_square(x)
_assert_finite(x)
t, result_t = _commonType(x)

signature = 'D->D' if isComplexType(t) else 'd->D'
with np.errstate(call=_raise_linalgerror_eigenvalues_nonconvergence,
invalid='call', over='ignore', divide='ignore',
under='ignore'):
w = _umath_linalg.eigvals(x, signature=signature)

result_t = _complexType(result_t)
return w.astype(result_t, copy=False)
Comment on lines +103 to +177
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

There is significant code duplication between the eig and eigvals functions. Both functions import the same set of helper functions from numpy.linalg, perform the same validation steps (_assert_stacked_square, _assert_finite), and use similar dtype handling logic. Consider extracting the common logic into a shared helper function to improve maintainability and reduce duplication.

Copilot uses AI. Check for mistakes.


# These functions are completely new here. If the library already has them
# (i.e., numpy 2.0), use the library version instead of our wrapper.
if hasattr(np.linalg, "vector_norm"):
Expand Down
4 changes: 4 additions & 0 deletions cupy-xfails.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ array_api_tests/test_has_names.py::test_has_names[array_attribute-mT]

array_api_tests/test_linalg.py::test_solve

# 2025.12 support; {eig,eigvals} are new in CuPy 14
array_api_tests/test_linalg.py::test_eig
array_api_tests/test_linalg.py::test_eigvals

# We cannot modify array methods
array_api_tests/test_operators_and_elementwise_functions.py::test_divide[__truediv__(x, s)]
array_api_tests/test_operators_and_elementwise_functions.py::test_floor_divide[__floordiv__(x, s)]
Expand Down
4 changes: 4 additions & 0 deletions dask-xfails.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ array_api_tests/test_linalg.py::test_matrix_norm
array_api_tests/test_linalg.py::test_qr
array_api_tests/test_manipulation_functions.py::test_roll

# 2025.12 support
array_api_tests/test_linalg.py::test_eig
array_api_tests/test_linalg.py::test_eigvals

Comment on lines +132 to +135
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

According to the PR description, CuPy needs xfail entries for eig and eigvals tests until the minimum CuPy version is >=13. However, these xfail entries are missing from cupy-xfails.txt. The dask-xfails.txt file includes the necessary entries (lines 133-134), but cupy-xfails.txt should also have corresponding entries under a "# 2025.12 support" section similar to the pattern used in dask-xfails.txt.

Copilot uses AI. Check for mistakes.
# Stubs have a comment: (**note**: libraries may return ``NaN`` to match Python behavior.)
array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i > 0) -> +infinity]
array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i < 0) -> -infinity]
Expand Down
Loading