scijs / poly-roots

Find all roots of a polynomial using the Jenkins-Traub method

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Comparison to Durand-Kerner method

mikolalysenko opened this issue · comments

Out of some curiosity would like to see how the Durand-Kerner method compares to this technique.

https://www.npmjs.com/package/durand-kerner

Haha, oh man, I'd like to see that too. I'm not sure I'd have implemented this if I'd seen that module. Huh. It'd be nice if Github's organization search feature searched the descriptions as well as the titles.

Hmm… Very interesting… Looks like it's Newton's method-based. I wonder how robust it is in different corner cases like clusters of roots. I was intrigued by the fact that Jenkins-Traub is basically designed for numerical robustness and efficiency, but Durand-Kerner looks pretty ridiculously simple. Reading up, sounds like maybe convergence for D-K is sub-quadratic around roots with multiplicity since it's Newton iteration based…

The application I'm going for basically sweeps over a whole parameter space and requires tons and tons of roots of moderate-order polynomials with—in one place or another—closely spaced roots, but I'm pretty sure implementing this was a moderately silly exercise. Frankly, got halfway through and just wasn't willing to give up. Informative though.

Hmm… wait, does D-K also require a guess, or does it always default to powers of 0.4 + 0.9i? That was one of the plusses of Jenkins-Traub since if you sweep over enough cases, bad guesses tend to get exposed.

Edit: Oh. Guess optional. I didn't scroll over far enough.

Sorry, benchmarked but there was a bit more subtlety to it… still working on straightening things out… The short version:

D-K: fast. struggles with closely spaced roots, e.g. (z-1) * (z+1) * (z + 1 + 1e-4i) * (z + 1 - 1e-4i)
J-T: slow. good precision overall but currently failing on a 20th order polynomial. convergence criteria needs debugging.
Companion: Good overall, but about three decimal places of precision on (z-1)(z-2)(z-3)(z-4)(z-5)(z-6)(z-7)(z-8)(z-9)(z-10).

The companion matrix method is probably not performing well due to the poor implementation of QR factorization in numeric.js. I think if we had a better eigen decomposition routine it might be more competitive/accurate.

Long term though, always nice to have more modules to solve a given problem. I think having some options here is good since it allows users to trade accuracy for performance in various cases.

Agreed! (Nitpick: QR factorization != QR algorithm, and it's the QR algo that's suffering maybe?)

Yeah, the results are actually turning out to be rather interesting. Getting error estimates and will post results/repo.

I got a little sidetracked on a couple issues because there appears to be a case in which the companion matrix method loses all digits of precision, and a case in which it's pretty precise but (perhaps) totally inaccurate (see: scijs/companion-roots#1 ). This might be a case of that geometric vs. algebraic multiplicity stuff I can never keep straight with eigenvalues, or it might be something else entirely. Or I could be totally wrong.

The Durand-Kerner method is blazing fast. Except it struggles with closely spaced roots as in case 6. Otherwise it's pretty outstanding, but I suspect maybe there are robustness issues including sensitivity to initial guesses (perhaps a red flag for black box solvers) (and maybe scaling issues — like how do you choose good guesses for roots that span orders of magnitude? does it lose lots of precision in these cases?) that prevent it from being the go-to method for these things.

Jenkins-Traub brings up the rear on timing, at least until you get to large problems. It chugs along pretty respectably and doesn't fall over in these cases, although I'm sure there are plenty of corner cases where it could struggle. Its strength is in needing no input and proceeding from the smallest roots so that precision isn't lost.

An interesting note:

D-K chokes pretty badly on case 6, returning:

1 -7.981039600486516e-18
-0.9999887619012027 0.00038412524677635475
-0.9999887619012027 0.00038412524677635475
-0.9999887619012027 0.00038412524677635475

for the four roots. Technically it achieves four digits of precision, but it'd be interesting to see what the threshold is for roots snapping to numerically identical.

Okay, here are some numbers:

                        Method                Time               Error

1)  z - 6:
                 Jenkins-Traub          0.18021 ms         0.00000e+00
                 Durand-Kerner          0.01288 ms         2.22045e-16
              Companion Matrix          0.06486 ms         0.00000e+00

2)  z^2 + 1:
                 Jenkins-Traub          0.25117 ms         1.07203e-18
                 Durand-Kerner          0.04905 ms         0.00000e+00
              Companion Matrix          0.84850 ms         3.14208e-16

3)  z^2 + 2z - 3:
                 Jenkins-Traub          0.17595 ms         1.08792e-22
                 Durand-Kerner          0.04448 ms         5.88876e-17
              Companion Matrix          0.15792 ms         1.35064e-15

4)  z^3 - 4z^2 + z + 6:
                 Jenkins-Traub          0.29488 ms         6.43045e-21
                 Durand-Kerner          0.02057 ms         2.31972e-16
              Companion Matrix          0.50635 ms         3.51259e-15

5)  z^3 - (4+i)z^2 + (1+i)z + (6+2i):
                 Jenkins-Traub          0.21368 ms         2.65217e-16
                 Durand-Kerner          0.00407 ms         1.24919e-16
              Companion Matrix          0.38835 ms         1.41421e+00

6)  (z-1) * (z+1) * (z + 1 + 1e-4i) * (z + 1 - 1e-4i):
                 Jenkins-Traub          0.35790 ms         3.05511e-08
                 Durand-Kerner          0.02325 ms         6.90018e-04
              Companion Matrix         29.76010 ms         6.79238e-08

7)  (x-1000000)*(x+%i)*(x-%i):
                 Jenkins-Traub          0.19570 ms         7.58889e-19
                 Durand-Kerner          0.00867 ms         3.83997e-11
              Companion Matrix          0.53090 ms         7.01365e-10

8)  (z-1)(z-2)(z-3)(z-4)(z-5)(z-6)(z-7)(z-8)(z-9)(z-10):
                 Jenkins-Traub          0.83523 ms         4.45072e-10
                 Durand-Kerner          0.17730 ms         3.32534e-10
              Companion Matrix         18.65999 ms         1.41236e+00

9)  z^20 + i * z^10 + 1:
                 Jenkins-Traub          2.23841 ms
                 Durand-Kerner          0.13878 ms
              Companion Matrix         64.20926 ms

Marking closed since I think that's a reasonable comparison. Solving problems is a different matter. 😄