Solving a large (150 variable) system of linear, ordinary differential equations; running into floating point rounding and/or stiffness problems

Question:

EDIT: Original post too vague. I am looking for an algorithm to solve a large-system, solvable, linear IVP that can handle very small floating point values. Solving for the eigenvectors and eigenvalues is impossible with numpy.linalg.eig() as the returned values are complex and should not be, it does not support numpy.float128 either, and the matrix is not symmetric so numpy.linalg.eigh() won’t work. Sympy could do it given an infinite amount of time, but after running it for 5 hours I gave up. scipy.integrate.solve_ivp() works with implicit methods (have tried Radau and BDF), but the output is wildly wrong. Are there any libraries, methods, algorithms, or solutions for working with this many, very small numbers?

Feel free to ignore the rest of this.

I have a 150×150 sparse (~500 nonzero entries of 22500) matrix representing a system of first order, linear differential equations. I’m attempting to find the eigenvalues and eigenvectors of this matrix to construct a function that serves as the analytical solution to the system so that I can just give it a time and it will give me values for each variable. I’ve used this method in the past for similar 40×40 matrices, and it’s much (tens, in some cases hundreds of times) faster than scipy.integrate.solve_ivp() and also makes post model analysis much easier as I can find maximum values and maximum rates of change using scipy.optimize.fmin() or evaluate my function at inf to see where things settle if left long enough.

This time around, however, numpy.linalg.eig() doesn’t seem to like my matrix and is giving me complex values, which I know are wrong because I’m modeling a physical system that can’t have complex rates of growth or decay (or sinusoidal solutions), much less complex values for its variables. I believe this to be a stiffness or floating point rounding problem where the underlying LAPACK algorithm is unable to handle either the very small values (smallest is ~3e-14, and most nonzero values are of similar scale) or disparity between some values (largest is ~4000, but values greater than 1 only show up a handful of times).

I have seen suggestions for similar users’ problems to use sympy to solve for the eigenvalues, but when it hadn’t solved my matrix after 5 hours I figured it wasn’t a viable solution for my large system. I’ve also seen suggestions to use numpy.real_if_close() to remove the imaginary portions of the complex values, but I’m not sure this is a good solution either; several eigenvalues from numpy.linalg.eig() are 0, which is a sign of error to me, but additionally almost all the real portions are of the same scale as the imaginary portions (exceedingly small), which makes me question their validity as well. My matrix is real, but unfortunately not symmetric, so numpy.linalg.eigh() is not viable either.

I’m at a point where I may just run scipy.integrate.solve_ivp() for an arbitrarily long time (a few thousand hours) which will probably take a long time to compute, and then use scipy.optimize.curve_fit() to approximate the analytical solutions I want, since I have a good idea of their forms. This isn’t ideal as it makes my program much slower, and I’m also not even sure it will work with the stiffness and rounding problems I’ve encountered with numpy.linalg.eig(); I suspect Radau or BDF would be able to navigate the stiffness, but not the rounding.

Anybody have any ideas? Any other algorithms for finding eigenvalues that could handle this? Can numpy.linalg.eig() work with numpy.float128 instead of numpy.float64 or would even that extra precision not help?

I’m happy to provide additional details upon request. I’m open to changing languages if needed.

Asked By: Kibeth

||

Answers:

As mentioned in the comment chain above the best solution for this is to use a Matrix Exponential, which is a lot simpler (and apparently less error prone) than diagonalizing your system with eigenvectors and eigenvalues.

For my case I used scipy.sparse.linalg.expm() since my system is sparse. It’s fast, accurate, and simple. My only complaint is the loss of evaluation at infinity, but it’s easy enough to work around.

Answered By: Kibeth