-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
General eigenproblem algorithm #1743
Conversation
Thanks Michal, I will have a look into your work soon! |
The previous implementation of function eigs(m: number[][]): {
values: number[],
vectors: number[][]
};
function eigs(m: Matrix): {
values: Vector,
vectors: Matrix
}; (By To be honest, I consider the convention of matrix functions returning arrays for array input and matrices for matrix input a little weird. But the In my opinion, a much more sensible type signature would be: function eigs(m: number[][]): {
values: number[],
vectors: number[][]
};
function eigs(m: Matrix): {
values: number[],
vectors: Vector[]
}; Of course, if you think that the original signature is more reasonable, or that we shouldn't change the API, I'll use the original signature. But I think it's worth at least reconsidering. What's your opinion on this, Jos? EDIT: Numerical recipes in Fortran 77 doesn't have a good description of QR of complex Hessenberg matrices, so I'll have to take more time figuring it out. Sorry for the delay. EDIT 2: Oh, we already have a QR algorithm. If it performs well on Hessenberg matrices, my PR is almost done. I still need to add tests though. |
The algorithm now successfully computes the eigenvalues for general real matrices. Complex matrices don't work only because You can test that const math = require('.');
m = math.matrix([[1,2,3,4,5,6],[1,2,3,4,5,5],[1,2,3,4,4,6],[1,2,3,3,5,6],[1,2,2,4,5,6],[1,1,3,4,5,6]]);
math.eigs(m) Next I'll write the tests and then I'll add |
Sounds good! This weekend I hope to review your PR. |
src/function/matrix/eigs/complex.js
Outdated
// this isn't the only time we loop thru the matrix... | ||
let last = false | ||
|
||
while (!last) |
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.
Is there a risk of getting into an infinite loop in this while
loop?
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.
I'm quite certain that no. The loop iterates through every row-column pair only once, then they are balanced for good. The only reason for using while
instead of for (let i = 0; i < N; i++)
is that the rows and columns get switched, so there's no clear ordering.
I've had a look at your PR @m93a , looks like a great start! Thanks for all your effort, this is quite some work. I've placed a few comments in the code. Most important I think is that currently the functions are sort of a hybrid containing many switches to handle either the One other thing I'm missing in the PR yet is unit tests. I guess you're planning on implementing that later? |
Thanks for the review, Jos! I found out that finding the eigenvectors is much harder than I expected, but I hope I'll finish it during this weekend. |
Now the only things missing are tests, fixing But I need to take a break for a while, this PR took much more time than I estimated. |
if (hasBig) { | ||
for (let i = 0; i < N; i++) { | ||
for (let j = 0; j < N; j++) { | ||
arr[i][j] = bignumber(arr[i][j]) |
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.
The function bignumber
also accepts array or matrix input, so you can replace the two for loops simply with
arr = bignumber(arr)
?
Thanks Michal, sounds good. Very nice that you even solved these limitations in About the const big = type === 'BigNumber'
// ...
colNorm = big ? colNorm.add(c) : colNorm + c
rowNorm = big ? rowNorm.add(c) : rowNorm + r
// ... with: const big = type === 'BigNumber'
const add = big
? (a, b) => a.add(b)
: (a, b) => a + b
// ...
colNorm = add(colNorm, c)
rowNorm = add(rowNorm, c)
// ... This will make the code much better readable I expect, which is really important to me. As a side effect it would also make it very easy to try out the performance penalty of simply using the generic
Yeah I can imagine that! Take it easy 😃 |
Since |
@m93a I see you made some new commits 👍 . Is the PR ready to review? |
Sadly not yet, I have the bad habit of consistently overestimating my free time and underestimating the difficulty of tasks. I'll let you know once it is ready for review 😅️✌️ |
👍 😂 no problem at all, I'll await your message. |
Hey Jos, I'm finally back! I hope to finish this PR in a reasonable time. However I wanted to ask you something – the const { eigs, matrix } = math;
// The way eigs works now:
eigs( matrix([[0,1],[1,0]]) )
→ {
values: matrix([-1, 1]),
vectors: matrix([ [0.7, 0.7], [0.7, -0.7] ]) //clearly vectors.data[0] has eigenvalue values.data[1]
}
// The way I would like it:
eigs( matrix([[0,1],[1,0]]) )
→ {
values: [-1, 1],
vectors: [ matrix([0.7, -0.7]), matrix([0.7, 0.7]) ]
} Does this kind of change look good to you? |
More specifically: if the change I proposed does look good to you, there are two minor inconveniences I should solve before we merge this PR. Until now, diagonalization of a matrix could be performed like this: const { eigs, matrix, multiply, transpose } = math
let M = matrix([[1,2,3],[2,4,5],[3,5,6]])
let V = eigs(M).vectors
multiply( transpose(V), M, V ) // returns a diagonal matrix However now this fails, because
Do you like the solutions to these two problems? Should I open a PR for them? If we implement these two solutions, the new diagonalization code would look something like this: const { eigs, matrix, matrixFromColumns, multiply, transpose } = math
let M = matrix([[1,2,3],[2,4,5],[3,5,6]])
let V = matrixFromColumns( eigs(M).vectors )
multiply( transpose(V), M, V ) // returns a diagonal matrix |
Good to hear back from you 😄 👍
Yes that makes sense to me too. I'm not sure though whether there where specific reasons to not do that in the first place. I understand the issue you explain with vectors being an array with matrices. mathjs in general expects either (nested) arrays or matrices, but not an array with matrices. A helper function like |
Hey Jos!
Yes, that's exactly the current behavior 😁️ I found it unintuitive, because to find the eigenvector of |
Ahh, ok. Yes indeed with I'm a bit afraid it will get hairy very quickly when we start having functions which return arrays with nested arrays, not being the same as a nested array representing a 2d array. So far the concept basically is that the returned results are a single "thing", the thing typically being either a 2d matrix or nested array representing a 2d matrix. I think for consistency and simplicity of the library it will be good to keep thinking from this high level "Matrix" concept, and see usage of Array only as a Matrix implementation and not use Array for other usage like returning multiple vectors. I understand that the latter would be convenient in practice in this case, but think it doesn't outweigh introducing this inconsistency in the library. What do you think? |
Okay, I'm keeping the old behavior, |
The current implementation is still somewhat pathetic. Eigenvalues converge very slowly for matrices as small as 4x4, the search for eigenvectors fails maybe 50% of the time. There are two specific things that can be done to remedy this (see lines 287 and 418). But the vast majority of the algorithm is there and I'm a little burnt out about this specific PR, I want to work on different things in math.js to get motivated again :D I propose we do this:
|
Thanks for your honesty of being fed up with the PR 😄 I know the feeling. You're right that the PR provides functional improvements. Most important feedback was about refactoring the code such that we get rid of all the switches for number vs BigNumber, see #1743 (comment). I agree with you that we can be pragmatic here and address this refactoring in a later stage. The PR is already a step in the right direction :). Can you add a clear comment on top of the relevant functions in this PR what we would like to refactor/improve in them: address the number/bignumber switches, and address the slow convergence for small matrices with the pointers you mention above? Let's merge your work after that 👍 |
And: thanks again for all your effort 👍 |
This should already be fixed, I use
All the things that are imo suboptimal are marked with a descriptive |
O wow, I hadn't realized you already addressed all that refactoring 😎 . Thanks again, will merge your PR now! |
* split the eigs function into multiple algorithms * moved checks and coersions to eigs.js, made them more robust * fix little bugs, make im and re more robust * Implemented matrix balancing algorithm * fix typos * a draft of reduction to Hessenberg matrix * finished implementation of reduction to Hessenberg * fix Hessenberg elimination for complex numbers * implemented non-shifted explicit QR algorithm for real matrices * implemented vector computation, won't work untill usolve is fixed * refactored to match yarn lint * some minor changes * solve merge conflicts * refactored and re-fixed josdejong#1789 * some old uncommited changes * fix small problems introduced by merging * done some polishing * improved jsdoc description of eigs * little changes in jsdoc Co-authored-by: Jos de Jong <wjosdejong@gmail.com>
Fixes #1741.
Introduces #2178, #2179.