New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
QZ-decomposition #1067
QZ-decomposition #1067
Changes from 1 commit
7230ae1
6f7ef38
769f20c
b2c6c6b
6a28306
7e9345d
7d8fb3d
748848f
ae35d1c
162bb32
d88337e
55fdd84
4362f00
4038e66
d5069f3
a4de6a8
497a4d8
fb0cb51
b7fe6b9
91b7e05
7510d48
e52f117
5e10ca4
c8a920f
3413ab7
4413a35
adf50a6
2743eef
80a844a
bc31012
ff2d431
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -180,25 +180,44 @@ where | |
} | ||
|
||
/// Calculates the generalized eigenvectors (left and right) associated with the generalized eigenvalues | ||
/// Outputs two matrices, the first one containing the left eigenvectors of the generalized eigenvalues | ||
/// as columns and the second matrix contains the right eigenvectors of the generalized eigenvalues | ||
/// as columns | ||
/// Outputs two matrices. | ||
/// The first output matix contains the left eigenvectors of the generalized eigenvalues | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: "matix" |
||
/// as columns. | ||
/// The second matrix contains the right eigenvectors of the generalized eigenvalues | ||
/// as columns. | ||
/// | ||
/// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) | ||
/// of (A,B) satisfies | ||
/// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) | ||
/// of (A,B) satisfies | ||
/// | ||
/// A * v(j) = lambda(j) * B * v(j). | ||
/// A * v(j) = lambda(j) * B * v(j) | ||
/// | ||
/// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) | ||
/// of (A,B) satisfies | ||
/// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) | ||
/// of (A,B) satisfies | ||
/// | ||
/// u(j)**H * A = lambda(j) * u(j)**H * B . | ||
/// where u(j)**H is the conjugate-transpose of u(j). | ||
/// u(j)**H * A = lambda(j) * u(j)**H * B | ||
/// where u(j)**H is the conjugate-transpose of u(j). | ||
/// | ||
/// How the eigenvectors are build up: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "built"? |
||
/// | ||
/// Since the input entries are all real, the generalized eigenvalues if complex come in pairs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm very confused by this sentence. It says "if complex". But don't we always have to expect complex eigenvalues and eigenvectors? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
LAPACK does it explicitly here
Fixed doc Edit:misunderstood intent |
||
/// as a consequence of <https://en.wikipedia.org/wiki/Complex_conjugate_root_theorem> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure how this link renders - not familiar with angle bracket syntax here, but maybe use "proper" linking to make the text overall more readable (URLs break the flow of reading)? E.g. |
||
/// The Lapack routine output reflects this by expecting the user to unpack the complex eigenvalues associated | ||
/// eigenvectors from the real matrix output via the following procedure | ||
/// | ||
/// (Note: VL stands for the lapack real matrix output containing the left eigenvectors as columns, | ||
/// VR stands for the lapack real matrix output containing the right eigenvectors as columns) | ||
/// | ||
/// If the j-th and (j+1)-th eigenvalues form a complex conjugate pair, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does the user know this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to a comment, so the caller doesn't have to know the details unless they inspect the source code |
||
/// then | ||
/// | ||
/// u(j) = VL(:,j)+i*VL(:,j+1) | ||
/// u(j+1) = VL(:,j)-i*VL(:,j+1) | ||
/// | ||
/// and | ||
/// | ||
/// u(j) = VR(:,j)+i*VR(:,j+1) | ||
/// v(j+1) = VR(:,j)-i*VR(:,j+1). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still very confused about this. Isn't this what's going on internally in the method below? The docs here are written as if the user needs to do this after calling this method, but it looks to me that the method actually takes care of all this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree the user doesn't need to know unless they inspect the source code. Portion moved to comment within function body |
||
/// | ||
/// What is going on below? | ||
/// If the j-th and (j+1)-th eigenvalues form a complex conjugate pair, | ||
/// then u(j) = VSL(:,j)+i*VSL(:,j+1) and u(j+1) = VSL(:,j)-i*VSL(:,j+1). | ||
/// and then v(j) = VSR(:,j)+i*VSR(:,j+1) and v(j+1) = VSR(:,j)-i*VSR(:,j+1). | ||
pub fn eigenvectors(self) -> (OMatrix<Complex<T>, D, D>, OMatrix<Complex<T>, D, D>) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another thing I did not realize before now: Here we take |
||
where | ||
DefaultAllocator: | ||
|
@@ -216,18 +235,14 @@ where | |
.clone() | ||
.map(|x| Complex::new(x, T::RealField::zero())); | ||
|
||
let eigenvalues = &self.eigenvalues(); | ||
let eigenvalues = &self.raw_eigenvalues(); | ||
|
||
let mut c = 0; | ||
|
||
let epsilon = T::RealField::default_epsilon(); | ||
|
||
while c < n { | ||
if eigenvalues[c].im.abs() > epsilon && c + 1 < n && { | ||
let e_conj = eigenvalues[c].conj(); | ||
let e = eigenvalues[c + 1]; | ||
(&e_conj.re).ulps_eq(&e.re, epsilon, 6) && (&e_conj.im).ulps_eq(&e.im, epsilon, 6) | ||
} { | ||
if eigenvalues[c].0.im.abs() > epsilon && c + 1 < n { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still uneasy about this check. Does the LAPACK manual/docs have any recommendations for how to interpret the data? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the LAPACK docs:
alphai is a double precision array as per LAPACK. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you know if LAPACK explicitly sets this to zero, or if it just happens to become "close enough" to zero? From reading that I'd say that the only "safe" thing to do is to use an equality comparison ( Certainly the current epsilon check breaks down if the matrix doesn't happen to have its max eigenvalue scaled to roughly ~1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Andlon Apologies for the radio silence. So I thought the best way to check if LAPACK suggest equality over close enough was to sub the equal operator in and run the tests. It fails the tests You've been driving the scaled eigenvalue point for a while, it was only now the seriousness struck. Apologies for that. I can go ahead and in the method that assembles the eigenvectors by looking at the eigenvalues, bring back the eigenvalue method I deleted, obtain the max eigenvalue and scale the eigenvalue vector so that the max eigenvalue is ~1 Are we in agreement that this is the way forward? Again I think the tests say LAPACK doesn't deal with the problem and if for some reason it(LAPACK) did recommend actual equality, the tests sure say the opposite i.e "close enough" is what we need |
||
// taking care of the left eigenvector matrix | ||
l.column_mut(c).zip_apply(&self.vsl.column(c + 1), |r, i| { | ||
*r = Complex::new(r.re.clone(), i.clone()); | ||
|
@@ -253,32 +268,7 @@ where | |
(l, r) | ||
} | ||
|
||
// only used for internal calculation for assembling eigenvectors based on realness of | ||
// eigenvalues and complex-conjugate checks of subsequent non-real eigenvalues | ||
fn eigenvalues(&self) -> OVector<Complex<T>, D> | ||
where | ||
DefaultAllocator: Allocator<Complex<T>, D>, | ||
{ | ||
let mut out = Matrix::zeros_generic(self.vsl.shape_generic().0, Const::<1>); | ||
|
||
let epsilon = T::RealField::default_epsilon(); | ||
|
||
for i in 0..out.len() { | ||
out[i] = if self.beta[i].clone().abs() < epsilon | ||
|| (self.alphai[i].clone().abs() < epsilon | ||
&& self.alphar[i].clone().abs() < epsilon) | ||
{ | ||
Complex::zero() | ||
} else { | ||
Complex::new(self.alphar[i].clone(), self.alphai[i].clone()) | ||
* (Complex::new(self.beta[i].clone(), T::RealField::zero()).inv()) | ||
} | ||
} | ||
|
||
out | ||
} | ||
|
||
/// outputs the unprocessed (almost) version of generalized eigenvalues ((alphar, alpai), beta) | ||
/// outputs the unprocessed (almost) version of generalized eigenvalues ((alphar, alphai), beta) | ||
/// straight from LAPACK | ||
#[must_use] | ||
pub fn raw_eigenvalues(&self) -> OVector<(Complex<T>, T), D> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't there need to be an extra newline here to make sure only the first sentence ends up in the module overview?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re:module over-view: isn't the module overview here https://github.com/dimforge/nalgebra/pull/1067/files#diff-a056a4c6b4fb629735a73fcb65e0d5223386cb47dedb6ab80d5a1b905ebd9d25R16 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, not module overview, but the docs for the struct. Please render them (
cargo doc
) and see :)