From 6011f96d7a32f8ba1f7f2cbde8b22ddecd1c1c5a Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Mon, 13 Oct 2025 13:24:59 +0200 Subject: [PATCH 1/2] ENH: add `eig` and `eigvals` to the `linalg` extension --- src/array_api_stubs/_draft/linalg.py | 132 +++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 8 deletions(-) diff --git a/src/array_api_stubs/_draft/linalg.py b/src/array_api_stubs/_draft/linalg.py index fa69ef971..3076e73f2 100644 --- a/src/array_api_stubs/_draft/linalg.py +++ b/src/array_api_stubs/_draft/linalg.py @@ -187,9 +187,6 @@ def eigh(x: array, /) -> Tuple[array, array]: .. note:: Whether an array library explicitly checks whether an input array is Hermitian or a symmetric matrix (or a stack of matrices) is implementation-defined. - .. note:: - The function ``eig`` will be added in a future version of the specification. - Parameters ---------- x: array @@ -201,7 +198,7 @@ def eigh(x: array, /) -> Tuple[array, array]: a namedtuple (``eigenvalues``, ``eigenvectors``) whose - first element must have the field name ``eigenvalues`` (corresponding to :math:`\operatorname{diag}\Lambda` above) and must be an array consisting of computed eigenvalues. The array containing the eigenvalues must have shape ``(..., M)`` and must have a real-valued floating-point data type whose precision matches the precision of ``x`` (e.g., if ``x`` is ``complex128``, then ``eigenvalues`` must be ``float64``). - - second element have have the field name ``eigenvectors`` (corresponding to :math:`Q` above) and must be an array where the columns of the inner most matrices contain the computed eigenvectors. These matrices must be orthogonal. The array containing the eigenvectors must have shape ``(..., M, M)`` and must have the same data type as ``x``. + - second element have the field name ``eigenvectors`` (corresponding to :math:`Q` above) and must be an array where the columns of the inner most matrices contain the computed eigenvectors. These matrices must be orthogonal. The array containing the eigenvectors must have shape ``(..., M, M)`` and must have the same data type as ``x``. Notes ----- @@ -227,15 +224,12 @@ def eigvalsh(x: array, /) -> array: where :math:`\lambda \in \mathbb{R}` and where :math:`I_n` is the *n*-dimensional identity matrix. - .. note:; + .. note:: The eigenvalues of a complex Hermitian or real symmetric matrix are always real. .. note:: Whether an array library explicitly checks whether an input array is Hermitian or a symmetric matrix (or a stack of matrices) is implementation-defined. - .. note:: - The function ``eigvals`` will be added in a future version of the specification. - Parameters ---------- x: array @@ -257,6 +251,128 @@ def eigvalsh(x: array, /) -> array: """ +def eig(x: array, /) -> Tuple[array, array]: + r""" + Returns eigenvalues and eigenvectors of a real or complex matrix (or a stack of matrices) ``x``. + + If ``x`` is real-valued, let :math:`\mathbb{K}` be the union of the set of real numbers :math:`\mathbb{R}` + and the set of complex numbers, :math:`\mathbb{C}`; if ``x`` is complex-valued, let :math:`\mathbb{K}` + be the set of complex numbers :math:`\mathbb{C}`. + + A real or complex value :math:`lambda \in \mathbb{K}` is an **eigenvalue** of a real + or complex matrix :math:`x \in \mathbb{K}^{n \times n}` if there exist a real or complex vector + :math:`v \in \mathbb{K}^{n}`, such that + + .. math:: + + x v = \lambda v + + Then, :math:`v` is referred to as an **eigenvector** of :math:`x`, corresponding to + the eigenvalue :math:`\lambda`. + + A general matrix :math:`x \in \mathbb{K}^{n \times n}` + + - has :math:`n` eigenvectors, which are defined as the roots (counted with multiplicity) of the + polynomial :math:`p` of degree :math:`n` given by + + .. math:: + + p(\lambda) = \operatorname{det}(x - \lambda I_n) + + - does not in general have :math:`n` linearly independent eigenvectors if it has + eigenvalues with multiplicity larger than one. + + .. note:: + The eigenvalues of a non-symmetric real matrix in general are complex: for + :math:x \in \mathbb{R}^{n \times n}`, the eigenvalues, :math:`\lambda \in \mathbb{C}`, + and may or may not reside on the real axis of the complex plane. + + .. warning:: + The eigenvectors of a general matrix are not unique and are not continuous with respect to ``x``. Because eigenvectors are not unique, different hardware and software may compute different eigenvectors. + + For eigenvalues of multiplity :math:`s=1`, the non-uniqueness stems from the fact that multiplying an eigenvector by :math:`-1` when ``x`` is real-valued and by :math:`e^{\phi j}` (:math:`\phi \in \mathbb{R}`) when ``x`` is complex produces another set of valid eigenvectors. + + For eigenvalues of multiplity :math:`s > 1`, the :math:`s` computed eigenvectors may be repeated or + have entries differ by an order of machine epsilon for the data type of :math:`x`. + + Parameters + ---------- + x: array + input array having shape ``(..., M, M)`` and whose innermost two dimensions form square matrices. Should have a floating-point data type. + + Returns + ------- + out: Tuple[array, array] + a namedtuple (``eigenvalues``, ``eigenvectors``) whose + + - first element must have the field name ``eigenvalues`` (corresponding to :math:`\lambda` above) and must be an array consisting of computed eigenvalues. The array containing the eigenvalues must have shape ``(..., M)`` and must have a real-valued or complex-valued floating-point data type whose precision matches the precision of ``x`` (e.g., if ``x`` is ``float64``, then ``eigenvalues`` must be either ``float64`` or `complex128`). + + - second element have the field name ``eigenvectors`` (corresponding to :math:`v` above) and must be an array where the columns of the inner most matrices contain the computed eigenvectors. These matrices must be orthogonal. The array containing the eigenvectors must have shape ``(..., M, M)`` and must have the same data type as ``x``. + + Notes + ----- + + .. note:: + Eigenvalue sort order is left unspecified and is thus implementation-dependent. + + .. note:: + If all eigenvectors have zero imaginary parts, a conforming implementation may + return either a complex-valued array or a real-valued array containing the real + parts of eigenvalues. + + .. note:: + For real symmetric or complex Hermitian matrices, prefer using the ``eigh`` routine. + + .. versionadded:: 2025.12 + """ + + +def eigvals(x: array, /) -> array: + r""" + Returns the eigenvalues of a real or complex (or a stack of matrices) ``x``. + + If ``x`` is real-valued, let :math:`\mathbb{K}` be the union of the set of real numbers :math:`\mathbb{R}` + and the set of complex numbers, :math:`\mathbb{C}`; if ``x`` is complex-valued, let :math:`\mathbb{K}` be the set of complex numbers :math:`\mathbb{C}`. + + The **eigenvalues** of a real or complex matrix :math:`x \in\ \mathbb{K}^{n \times n}` are defined as the roots (counted with multiplicity) of the polynomial :math:`p` of degree :math:`n` given by + + .. math:: + p(\lambda) = \operatorname{det}(x - \lambda I_n) + + where :math:`\lambda \in \mathbb{K}` and where :math:`I_n` is the *n*-dimensional identity matrix. + + .. note:: + The eigenvalues of a non-symmetric real matrix in general are complex: for + :math:x \in \mathbb{R}^{n \times n}`, the eigenvalues :math:`\lambda \in \mathbb{C}`, + and may or may not reside on the real axis of the complex plane. + + + Parameters + ---------- + x: array + input array having shape ``(..., M, M)`` and whose innermost two dimensions form square matrices. Should have a floating-point data type. + + Returns + ------- + out: array + an array containing the computed eigenvalues. The returned array must have shape ``(..., M)`` and have a real-valued or complex-valued floating-point data type whose precision matches the precision of ``x`` (e.g., if ``x`` is ``float64``, then must have a ``float64`` or a ``complex128`` data type). + + Notes + ----- + + .. note:: + Eigenvalue sort order is left unspecified and is thus implementation-dependent. + + .. note:: + If all eigenvectors have zero imaginary parts, a conforming implementation may + return either a complex-valued array or a real-valued array containing the real + parts of eigenvalues. + + .. versionadded:: 2025.12 + """ + + + def inv(x: array, /) -> array: r""" Returns the multiplicative inverse of a square matrix (or a stack of square matrices) ``x``. From 9c37de1c9144e0d1ea67c2e35d7671d3eabcf299 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 17 Oct 2025 11:56:26 +0200 Subject: [PATCH 2/2] eig: require complex-valued returns --- src/array_api_stubs/_draft/linalg.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/array_api_stubs/_draft/linalg.py b/src/array_api_stubs/_draft/linalg.py index 3076e73f2..cf85e6863 100644 --- a/src/array_api_stubs/_draft/linalg.py +++ b/src/array_api_stubs/_draft/linalg.py @@ -255,9 +255,8 @@ def eig(x: array, /) -> Tuple[array, array]: r""" Returns eigenvalues and eigenvectors of a real or complex matrix (or a stack of matrices) ``x``. - If ``x`` is real-valued, let :math:`\mathbb{K}` be the union of the set of real numbers :math:`\mathbb{R}` - and the set of complex numbers, :math:`\mathbb{C}`; if ``x`` is complex-valued, let :math:`\mathbb{K}` - be the set of complex numbers :math:`\mathbb{C}`. + Let :math:`\mathbb{K}` be the union of the set of real numbers :math:`\mathbb{R}` + and the set of complex numbers, :math:`\mathbb{C}`. A real or complex value :math:`lambda \in \mathbb{K}` is an **eigenvalue** of a real or complex matrix :math:`x \in \mathbb{K}^{n \times n}` if there exist a real or complex vector @@ -305,9 +304,9 @@ def eig(x: array, /) -> Tuple[array, array]: out: Tuple[array, array] a namedtuple (``eigenvalues``, ``eigenvectors``) whose - - first element must have the field name ``eigenvalues`` (corresponding to :math:`\lambda` above) and must be an array consisting of computed eigenvalues. The array containing the eigenvalues must have shape ``(..., M)`` and must have a real-valued or complex-valued floating-point data type whose precision matches the precision of ``x`` (e.g., if ``x`` is ``float64``, then ``eigenvalues`` must be either ``float64`` or `complex128`). + - first element must have the field name ``eigenvalues`` (corresponding to :math:`\lambda` above) and must be an array consisting of computed eigenvalues. The array containing the eigenvalues must have shape ``(..., M)`` and must have a complex floating-point array data type having the same precision as that of ``x`` (e.g., if ``x`` has a ``float32`` data type, ``eigenvalues`` must have the ``complex64`` data type; if ``x`` has a ``float64`` data type, ``eigenvalues`` have the ``complex128`` data type). - - second element have the field name ``eigenvectors`` (corresponding to :math:`v` above) and must be an array where the columns of the inner most matrices contain the computed eigenvectors. These matrices must be orthogonal. The array containing the eigenvectors must have shape ``(..., M, M)`` and must have the same data type as ``x``. + - second element have the field name ``eigenvectors`` (corresponding to :math:`v` above) and must be an array where the columns of the inner most matrices contain the computed eigenvectors. These matrices must be orthogonal. The array containing the eigenvectors must have shape ``(..., M, M)`` and must have the same data type as ``eigenvalues``. Notes ----- @@ -315,11 +314,6 @@ def eig(x: array, /) -> Tuple[array, array]: .. note:: Eigenvalue sort order is left unspecified and is thus implementation-dependent. - .. note:: - If all eigenvectors have zero imaginary parts, a conforming implementation may - return either a complex-valued array or a real-valued array containing the real - parts of eigenvalues. - .. note:: For real symmetric or complex Hermitian matrices, prefer using the ``eigh`` routine. @@ -355,7 +349,7 @@ def eigvals(x: array, /) -> array: Returns ------- out: array - an array containing the computed eigenvalues. The returned array must have shape ``(..., M)`` and have a real-valued or complex-valued floating-point data type whose precision matches the precision of ``x`` (e.g., if ``x`` is ``float64``, then must have a ``float64`` or a ``complex128`` data type). + an array containing the computed eigenvalues. The returned array must have shape ``(..., M)`` and have a complex floating-point array data type having the same precision as that of ``x`` (e.g., if ``x`` has a ``float32`` data type, the array must have the ``complex64`` data type; if ``x`` has a ``float64`` data type, the array has the ``complex128`` data type). Notes ----- @@ -363,11 +357,6 @@ def eigvals(x: array, /) -> array: .. note:: Eigenvalue sort order is left unspecified and is thus implementation-dependent. - .. note:: - If all eigenvectors have zero imaginary parts, a conforming implementation may - return either a complex-valued array or a real-valued array containing the real - parts of eigenvalues. - .. versionadded:: 2025.12 """