Skip to content

Commit 926050d

Browse files
author
Release Manager
committed
Trac #29243: the generalized eigenvalue problem over RDF/CDF
This ticket adds support for solving the [https://en.wikipedia.org/wiki/ Eigendecomposition_of_a_matrix#Generalized_eigenvalue_problem generalized eigenvalue problem] `A v = λ B v` for two matrices `A`,`B` over `RDF` or `CDF`. Example: {{{ sage: A = matrix.identity(RDF, 2) sage: B = matrix(RDF, [[3, 5], [6, 10]]) sage: D, V = A.eigenmatrix_right(B); D # tol 1e-14 [0.07692307692307694 0.0] [ 0.0 +infinity] sage: λ = D[0, 0] sage: v = V[:, 0] sage: (A * v - B * v * λ).norm() < 1e-14 True }}} This is implemented using [https://docs.scipy.org/doc/scipy/reference/ge nerated/scipy.linalg.eig.html scipy.linalg.eig]. The changes include: - an optional argument `other` is added to `eigenmatrix_right/left` in `matrix2.pyx` as well as related functions in `matrix_double_dense.pyx` - an optional keyword `homogeneous` is added to obtain the generalized eigenvalues in terms of homogeneous coordinates - improvements to the documentation URL: https://trac.sagemath.org/29243 Reported by: gh-mwageringel Ticket author(s): Markus Wageringel Reviewer(s): Sébastien Labbé
2 parents 43eb974 + 254c6e9 commit 926050d

File tree

3 files changed

+560
-86
lines changed

3 files changed

+560
-86
lines changed

src/sage/matrix/matrix2.pyx

+220-25
Original file line numberDiff line numberDiff line change
@@ -6390,10 +6390,21 @@ cdef class Matrix(Matrix1):
63906390
self.cache('eigenvalues', eigenvalues)
63916391
return eigenvalues
63926392

6393-
def eigenvectors_left(self,extend=True):
6393+
def eigenvectors_left(self, other=None, *, extend=True):
63946394
r"""
63956395
Compute the left eigenvectors of a matrix.
63966396

6397+
INPUT:
6398+
6399+
- ``other`` -- a square matrix `B` (default: ``None``) in a generalized
6400+
eigenvalue problem; if ``None``, an ordinary eigenvalue problem is
6401+
solved (currently supported only if the base ring of ``self`` is
6402+
``RDF`` or ``CDF``)
6403+
6404+
- ``extend`` -- boolean (default: ``True``)
6405+
6406+
OUTPUT:
6407+
63976408
For each distinct eigenvalue, returns a list of the form (e,V,n)
63986409
where e is the eigenvalue, V is a list of eigenvectors forming a
63996410
basis for the corresponding left eigenspace, and n is the algebraic
@@ -6402,8 +6413,9 @@ cdef class Matrix(Matrix1):
64026413
If the option extend is set to False, then only the eigenvalues that
64036414
live in the base ring are considered.
64046415

6405-
EXAMPLES: We compute the left eigenvectors of a `3\times 3`
6406-
rational matrix.
6416+
EXAMPLES:
6417+
6418+
We compute the left eigenvectors of a `3\times 3` rational matrix.
64076419

64086420
::
64096421

@@ -6436,7 +6448,37 @@ cdef class Matrix(Matrix1):
64366448
(0, 0, 1)
64376449
], 1)]
64386450

6451+
TESTS::
6452+
6453+
sage: A = matrix(QQ, [[1, 2], [3, 4]])
6454+
sage: B = matrix(QQ, [[1, 1], [0, 1]])
6455+
sage: A.eigenvectors_left(B)
6456+
Traceback (most recent call last):
6457+
...
6458+
NotImplementedError: generalized eigenvector decomposition is
6459+
implemented for RDF and CDF, but not for Rational Field
6460+
6461+
Check the deprecation::
6462+
6463+
sage: matrix(QQ, [[1, 2], [3, 4]]).eigenvectors_left(False)
6464+
doctest:...: DeprecationWarning: "extend" should be used as keyword argument
6465+
See https://trac.sagemath.org/29243 for details.
6466+
[]
64396467
"""
6468+
if other is not None:
6469+
if isinstance(other, bool):
6470+
# for backward compatibility
6471+
from sage.misc.superseded import deprecation
6472+
deprecation(29243,
6473+
'"extend" should be used as keyword argument')
6474+
extend = other
6475+
other = None
6476+
else:
6477+
raise NotImplementedError('generalized eigenvector '
6478+
'decomposition is implemented '
6479+
'for RDF and CDF, but not for %s'
6480+
% self.base_ring())
6481+
64406482
x = self.fetch('eigenvectors_left')
64416483
if not x is None:
64426484
return x
@@ -6476,10 +6518,21 @@ cdef class Matrix(Matrix1):
64766518

64776519
left_eigenvectors = eigenvectors_left
64786520

6479-
def eigenvectors_right(self, extend=True):
6521+
def eigenvectors_right(self, other=None, *, extend=True):
64806522
r"""
64816523
Compute the right eigenvectors of a matrix.
64826524

6525+
INPUT:
6526+
6527+
- ``other`` -- a square matrix `B` (default: ``None``) in a generalized
6528+
eigenvalue problem; if ``None``, an ordinary eigenvalue problem is
6529+
solved (currently supported only if the base ring of ``self`` is
6530+
``RDF`` or ``CDF``)
6531+
6532+
- ``extend`` -- boolean (default: ``True``)
6533+
6534+
OUTPUT:
6535+
64836536
For each distinct eigenvalue, returns a list of the form (e,V,n)
64846537
where e is the eigenvalue, V is a list of eigenvectors forming a
64856538
basis for the corresponding right eigenspace, and n is the
@@ -6488,8 +6541,9 @@ cdef class Matrix(Matrix1):
64886541
closure of the base field where this is implemented; otherwise
64896542
it will restrict to eigenvalues in the base field.
64906543

6491-
EXAMPLES: We compute the right eigenvectors of a
6492-
`3\times 3` rational matrix.
6544+
EXAMPLES:
6545+
6546+
We compute the right eigenvectors of a `3\times 3` rational matrix.
64936547

64946548
::
64956549

@@ -6511,17 +6565,57 @@ cdef class Matrix(Matrix1):
65116565
sage: delta = eval*evec - A*evec
65126566
sage: abs(abs(delta)) < 1e-10
65136567
True
6568+
6569+
TESTS::
6570+
6571+
sage: A = matrix(QQ, [[1, 2], [3, 4]])
6572+
sage: B = matrix(QQ, [[1, 1], [0, 1]])
6573+
sage: A.eigenvectors_right(B)
6574+
Traceback (most recent call last):
6575+
...
6576+
NotImplementedError: generalized eigenvector decomposition is
6577+
implemented for RDF and CDF, but not for Rational Field
65146578
"""
6515-
return self.transpose().eigenvectors_left(extend=extend)
6579+
return self.transpose().eigenvectors_left(other=other, extend=extend)
65166580

65176581
right_eigenvectors = eigenvectors_right
65186582

6519-
def eigenmatrix_left(self):
6583+
def eigenmatrix_left(self, other=None):
65206584
r"""
6521-
Return matrices D and P, where D is a diagonal matrix of
6522-
eigenvalues and P is the corresponding matrix where the rows are
6523-
corresponding eigenvectors (or zero vectors) so that P\*self =
6524-
D\*P.
6585+
Return matrices `D` and `P`, where `D` is a diagonal matrix of
6586+
eigenvalues and the rows of `P` are corresponding eigenvectors
6587+
(or zero vectors).
6588+
6589+
INPUT:
6590+
6591+
- ``other`` -- a square matrix `B` (default: ``None``) in a generalized
6592+
eigenvalue problem; if ``None``, an ordinary eigenvalue problem is
6593+
solved
6594+
6595+
OUTPUT:
6596+
6597+
If ``self`` is a square matrix `A`, then the output is a diagonal
6598+
matrix `D` and a matrix `P` such that
6599+
6600+
.. MATH::
6601+
6602+
P A = D P,
6603+
6604+
where the rows of `P` are eigenvectors of `A` and the diagonal entries
6605+
of `D` are the corresponding eigenvalues.
6606+
6607+
If a matrix `B` is passed as optional argument, the output is a
6608+
solution to the generalized eigenvalue problem such that
6609+
6610+
.. MATH::
6611+
6612+
P A = D P B.
6613+
6614+
The ordinary eigenvalue problem is equivalent to the generalized one if
6615+
`B` is the identity matrix.
6616+
6617+
The generalized eigenvector decomposition is currently only implemented
6618+
for matrices over ``RDF`` and ``CDF``.
65256619

65266620
EXAMPLES::
65276621

@@ -6541,14 +6635,14 @@ cdef class Matrix(Matrix1):
65416635
sage: P*A == D*P
65426636
True
65436637

6544-
Because P is invertible, A is diagonalizable.
6638+
Because `P` is invertible, `A` is diagonalizable.
65456639

65466640
::
65476641

65486642
sage: A == (~P)*D*P
65496643
True
65506644

6551-
The matrix P may contain zero rows corresponding to eigenvalues for
6645+
The matrix `P` may contain zero rows corresponding to eigenvalues for
65526646
which the algebraic multiplicity is greater than the geometric
65536647
multiplicity. In these cases, the matrix is not diagonalizable.
65546648

@@ -6558,7 +6652,6 @@ cdef class Matrix(Matrix1):
65586652
[2 1 0]
65596653
[0 2 1]
65606654
[0 0 2]
6561-
sage: A = jordan_block(2,3)
65626655
sage: D, P = A.eigenmatrix_left()
65636656
sage: D
65646657
[2 0 0]
@@ -6571,6 +6664,42 @@ cdef class Matrix(Matrix1):
65716664
sage: P*A == D*P
65726665
True
65736666

6667+
A generalized eigenvector decomposition::
6668+
6669+
sage: A = matrix(RDF, [[1, -2], [3, 4]])
6670+
sage: B = matrix(RDF, [[0, 7], [2, -3]])
6671+
sage: D, P = A.eigenmatrix_left(B)
6672+
sage: (P * A - D * P * B).norm() < 1e-14
6673+
True
6674+
6675+
The matrix `B` in a generalized eigenvalue problem may be singular::
6676+
6677+
sage: A = matrix.identity(CDF, 2)
6678+
sage: B = matrix(CDF, [[2, 1+I], [4, 2+2*I]])
6679+
sage: D, P = A.eigenmatrix_left(B)
6680+
sage: D.diagonal() # tol 1e-14
6681+
[0.2 - 0.1*I, +infinity]
6682+
6683+
In this case, we can still verify the eigenvector equation for the
6684+
first eigenvalue and first eigenvector::
6685+
6686+
sage: l = D[0, 0]
6687+
sage: v = P[0, :]
6688+
sage: (v * A - l * v * B).norm() < 1e-14
6689+
True
6690+
6691+
The second eigenvector is contained in the left kernel of `B`::
6692+
6693+
sage: (P[1, :] * B).norm() < 1e-14
6694+
True
6695+
6696+
.. SEEALSO::
6697+
6698+
:meth:`eigenvalues`,
6699+
:meth:`eigenvectors_left`,
6700+
:meth:`.Matrix_double_dense.eigenvectors_left`,
6701+
:meth:`eigenmatrix_right`.
6702+
65746703
TESTS:
65756704

65766705
For matrices with floating point entries, some platforms will
@@ -6630,7 +6759,7 @@ cdef class Matrix(Matrix1):
66306759
"""
66316760
from sage.misc.flatten import flatten
66326761
from sage.matrix.constructor import diagonal_matrix, matrix
6633-
evecs = self.eigenvectors_left()
6762+
evecs = self.eigenvectors_left(other=other)
66346763
D = diagonal_matrix(flatten([[e[0]]*e[2] for e in evecs]))
66356764
rows = []
66366765
for e in evecs:
@@ -6647,12 +6776,42 @@ cdef class Matrix(Matrix1):
66476776

66486777
left_eigenmatrix = eigenmatrix_left
66496778

6650-
def eigenmatrix_right(self):
6779+
def eigenmatrix_right(self, other=None):
66516780
r"""
6652-
Return matrices D and P, where D is a diagonal matrix of
6653-
eigenvalues and P is the corresponding matrix where the columns are
6654-
corresponding eigenvectors (or zero vectors) so that self\*P =
6655-
P\*D.
6781+
Return matrices `D` and `P`, where `D` is a diagonal matrix of
6782+
eigenvalues and the columns of `P` are corresponding eigenvectors
6783+
(or zero vectors).
6784+
6785+
INPUT:
6786+
6787+
- ``other`` -- a square matrix `B` (default: ``None``) in a generalized
6788+
eigenvalue problem; if ``None``, an ordinary eigenvalue problem is
6789+
solved
6790+
6791+
OUTPUT:
6792+
6793+
If ``self`` is a square matrix `A`, then the output is a diagonal
6794+
matrix `D` and a matrix `P` such that
6795+
6796+
.. MATH::
6797+
6798+
A P = P D,
6799+
6800+
where the columns of `P` are eigenvectors of `A` and the diagonal
6801+
entries of `D` are the corresponding eigenvalues.
6802+
6803+
If a matrix `B` is passed as optional argument, the output is a
6804+
solution to the generalized eigenvalue problem such that
6805+
6806+
.. MATH::
6807+
6808+
A P = B P D.
6809+
6810+
The ordinary eigenvalue problem is equivalent to the generalized one if
6811+
`B` is the identity matrix.
6812+
6813+
The generalized eigenvector decomposition is currently only implemented
6814+
for matrices over ``RDF`` and ``CDF``.
66566815

66576816
EXAMPLES::
66586817

@@ -6672,14 +6831,14 @@ cdef class Matrix(Matrix1):
66726831
sage: A*P == P*D
66736832
True
66746833

6675-
Because P is invertible, A is diagonalizable.
6834+
Because `P` is invertible, `A` is diagonalizable.
66766835

66776836
::
66786837

66796838
sage: A == P*D*(~P)
66806839
True
66816840

6682-
The matrix P may contain zero columns corresponding to eigenvalues
6841+
The matrix `P` may contain zero columns corresponding to eigenvalues
66836842
for which the algebraic multiplicity is greater than the geometric
66846843
multiplicity. In these cases, the matrix is not diagonalizable.
66856844

@@ -6689,7 +6848,6 @@ cdef class Matrix(Matrix1):
66896848
[2 1 0]
66906849
[0 2 1]
66916850
[0 0 2]
6692-
sage: A = jordan_block(2,3)
66936851
sage: D, P = A.eigenmatrix_right()
66946852
sage: D
66956853
[2 0 0]
@@ -6702,6 +6860,42 @@ cdef class Matrix(Matrix1):
67026860
sage: A*P == P*D
67036861
True
67046862

6863+
A generalized eigenvector decomposition::
6864+
6865+
sage: A = matrix(RDF, [[1, -2], [3, 4]])
6866+
sage: B = matrix(RDF, [[0, 7], [2, -3]])
6867+
sage: D, P = A.eigenmatrix_right(B)
6868+
sage: (A * P - B * P * D).norm() < 1e-14
6869+
True
6870+
6871+
The matrix `B` in a generalized eigenvalue problem may be singular::
6872+
6873+
sage: A = matrix.identity(RDF, 2)
6874+
sage: B = matrix(RDF, [[3, 5], [6, 10]])
6875+
sage: D, P = A.eigenmatrix_right(B); D # tol 1e-14
6876+
[0.07692307692307694 0.0]
6877+
[ 0.0 +infinity]
6878+
6879+
In this case, we can still verify the eigenvector equation for the
6880+
first eigenvalue and first eigenvector::
6881+
6882+
sage: l = D[0, 0]
6883+
sage: v = P[:, 0]
6884+
sage: (A * v - B * v * l).norm() < 1e-14
6885+
True
6886+
6887+
The second eigenvector is contained in the right kernel of `B`::
6888+
6889+
sage: (B * P[:, 1]).norm() < 1e-14
6890+
True
6891+
6892+
.. SEEALSO::
6893+
6894+
:meth:`eigenvalues`,
6895+
:meth:`eigenvectors_right`,
6896+
:meth:`.Matrix_double_dense.eigenvectors_right`,
6897+
:meth:`eigenmatrix_left`.
6898+
67056899
TESTS:
67066900

67076901
For matrices with floating point entries, some platforms will
@@ -6744,7 +6938,8 @@ cdef class Matrix(Matrix1):
67446938
True
67456939

67466940
"""
6747-
D,P = self.transpose().eigenmatrix_left()
6941+
D,P = self.transpose().eigenmatrix_left(None if other is None
6942+
else other.transpose())
67486943
return D,P.transpose()
67496944

67506945
right_eigenmatrix = eigenmatrix_right

0 commit comments

Comments
 (0)