-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprm.py
More file actions
1506 lines (1348 loc) · 56.8 KB
/
Copy pathprm.py
File metadata and controls
1506 lines (1348 loc) · 56.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#! /usr/bin/env python3
"""Find polynomial roots with multiplicities using mpmath.
Running prm.py will solve about 70 test polynomials and roots, multiplicities,
iterations, execution times, figures of merit and method used.
Bob Boehm 2017
raboehm verizon net
2019-Oct-16:
added list of digit precisions, and a loop to cycle through them, to
prm_test to allow automatic increase in precision
added option endearly: if the first algorithm to finish (prm_mult or
prm_sing) is not close enough, end the other and go to the next precision,
if it's not the last. This is disabled by default since testing showed
that the prematurely ended algorithm may produce an acceptable result.
added precdbg to plot or save msf
bug fix in mpr: if itr == 2 or change < dtxv -> if change < dtxv
bug fixes in several early returns
"""
from multiprocessing import Process, cpu_count, Manager
from socket import gethostname
from functools import reduce
from operator import mul
from traceback import extract_stack
import time
import sys
import math as ma
import numpy as np
import mpmath as mp
import matplotlib.pyplot as plt
ZERO = mp.mpf(0)
ONE = mp.mpf(1)
TWO = mp.mpf(2)
THREE = mp.mpf(3)
TEN = mp.mpf(10)
FM01 = "AA2: n=%d"
FM11 = "BB6: n=%d;"
FM18 = "EE: n=%d; itr=%d; jji=%d; kki=%d;"
FM28 = "FF8: val[%d]=%+20.12e %+20.12e j; acc=%+20.12e %+20.12e j;"
FM39 = "LL: n=%4i; delta_method=%2i; itr=%4i; dig=%5i; msft=%9.2f; cst=%8.2f;"
def prm(poly, delta_method=None, full=False, endearly=False):
"""Find roots of a polynomial using two multiple precision methods.
Parameters
----------
poly : list or array like
coefficients of the polynomial -- size n+1
integer, float, or multiple precision, real or complex
delta_method : list
delta methods to be used in first and second algorithms -- see Notes
if None, defaults to [1, 3]
full : boolean
additional output if true
endearly : boolean
True: if the first algorithm to finish (prm_mult or prm_sing) is not
close enough, end the other and go to the next precision, if it's not
the last.
False: if the first to finish is not close enough, wait for the second
y(x) = poly[0]*x^n + poly[1]*x^(n-1) + ... + poly[n-1]*x + poly[n]
Returns
-------
zout : ndarray
multiple precision ndarray containing the roots of the polynomial
mlt : list
multiplicity of zout
and if full:
msf : float
figure of merit of root estimates -- larger negative numbers are better
log10(sum(abs(polyval(zout))) / n)) / 2
num_iter : int
total number of iterations
tstr : string
method that provided root estimate
"sing" = single root algorithm found roots within tolerance first
"(sing)" = both methods finished, single root algorithm was better
"mult" = multiple root algorithm found roots within tolerance first
"(mult)" = both methods finished, multiple root algorithm was better
Notes
-----
The goal is to find the roots of a polynomial with arbitrary complex
coefficients whose roots may be singular or multiple. This function (prm)
starts two separate methods in parallel -- one method converges well when
all roots are singular, and the second converges better than the first when
there are multiple roots.
For polynomials with only singular roots, there are many algorithms that
will find roots simultaneously with varying degrees of rate of convergence.
This method (prm_sing) has options for WeierStrass-Durand-Kerner,
Aberth-Ehrlich, Sakurai-Torii-Sugiura, and Sakurai-Petkovic, but using the
test polynomials included (prm_test and polygen), seems to show that
Sakurai-Torii-Sugiura (delta_sakurai) performed generally the best.
This is the default for this method. It uses an algorithm found in
Krishnan-Foskey-Culver-Keyser-Manocha (rootmax11) to generate initial root
estimates.
For polynomials with multiple roots, the methods for singular roots
converge much more slowly. The method here (prm_mult) uses the fact that,
for multiplicities greater than one, the polynomial's derivative has a root
at the same point as the original polynomial. This second algorithm finds
the roots of the derivative and checks them in the original polynomial. It
is recursive, so all derivatives are checked. The roots of the derivative
are then used as initial root estimates for the polynomial (as this seemed
better than using rootmax11). The Aberth-Ehrlich (delta_aberth) method
seems generally the best here, so it is the default.
If the first algorithm to finish is close enough, the second is stopped.
Otherwise, if endearly is False, the second is allowed to finish and the
better solution is chosen. If endearly is True, the second is ended, and,
with no solution, the precision may be increased.
Running prm.py will solve about 70 test polynomials and roots,
multiplicities, iterations, execution times, figures of merit and method
used. The software still has issues on some of the tests, but it generally
seems to work OK.
delta_method
0 delta_weierstrass WeierStrass-Durand-Kerner
1 delta_aberth Aberth-Ehrlich
3 delta_sakurai Sakurai-Torii-Sugiura
7 delta_petkovic Sakurai-Petkovic
empirical results from prm_test and polygen
prm_mult: 1 is better than 3 & 7, 3 & 7 are equally bad on convergence
1 is better than 0 (iterations & time)
prm_sing: 3 is better than 0 and 1 (iterations & time)
3 is better than 7, mostly on time
References
----------
https://en.wikipedia.org/wiki/Durand%E2%80%93Kerner_method
https://en.wikipedia.org/wiki/Aberth_method
T. Sakurai, M.S. Petkovic 1996-Aug-13
On some simultaneous methods based on Weierstrass correction
Section 2, Sakurai-Torii-Sugiura, Sakurai-Petkovic
Krishnan, Foskey, Culver, Keyser, Manocha 2001-Jun-03
PRECISE: Efficent Multiprecision Evaluation of Algebraic Roots and
Predicates for Reliable Geometric Computation
Section 4.2 Choice of Initial Approximations
Example
-------
>>> mp.mp.dps = 2000
>>> prm([1, 4, 6, 4, 1], full=True)
(array([mpc(real='-1.0', imag='0.0'), mpc(real='0.0', imag='0.0'),
mpc(real='0.0', imag='0.0'), mpc(real='0.0', imag='0.0')],
dtype=object), [4, 0, 0, 0], -4000, 4, 'mult')
prm_test includes code that can be used as an example implementation.
To Do
-----
error handling
prm has a problem in cygwin's python command line (scripts ok, though):
python child info fork abort address space needed by properties
cpython x86 64-cygwin.dll already occupied (cygwin fork)
"""
if delta_method is None:
delta_method = [1, 3]
dbf = 0
thrsh = -mp.mp.dps * 0.80 # originally 0.95
retval = 0
sleeptime = 0.1
maxloop = 1000000 # 100000
poly, rtsat0 = polytrim(poly)
npoly = len(poly) - 1
procs = 2
manager = Manager()
namspc0 = manager.Namespace()
namspc1 = manager.Namespace()
namspcs = [namspc0, namspc1]
ec_("Z1: before queue", dbf > 0)
flgs = [0] * procs # 0=alive; 1=done, > threshold; 2=done, < threshold
ec_("Z2: before process", dbf > 0)
prms = [prm_mult, prm_sing]
proc = [None] * procs
for idx in range(procs):
namspcs[idx].zout = mpmpc([0] * npoly)
namspcs[idx].mlt = [0] * npoly
namspcs[idx].msft = 0
namspcs[idx].itr = 0
proc[idx] = Process(target=prm_worker, args=(poly, delta_method[idx],
prms[idx], namspcs[idx]))
typ = ["mult", "sing"]
mlts = [None] * procs
msfts = [1000000] * procs
itrs = [None] * procs
ec_("Z3: before start", dbf > 0)
for idx in range(procs):
proc[idx].start()
cnt = 0
# if the first to return meets the solution criterion, end the other one
ec_("Z4: before while", dbf > 0)
while prod(flgs) == 0 and cnt < maxloop:
cnt += 1
for idx in range(procs):
if max(flgs) == 2:
break
ec_("Z5: while; cnt=%6d; proc[%d].is_alive()=%d;" %
(cnt, idx, proc[idx].is_alive()), dbf > 1)
if flgs[idx] == 0 and not proc[idx].is_alive():
ec_("A1", dbf > 0)
flgs[idx] = 1
mlts[idx] = namspcs[idx].mlt
msfts[idx] = namspcs[idx].msft
itrs[idx] = namspcs[idx].itr
ec_("A2 msfts[%d]=%9.2f;" % (idx, msfts[idx]), dbf > 0)
if msfts[idx] < thrsh or endearly:
flgs[idx] = 2
ec_("A3 flgs[%d]=%d;" % (idx, flgs[idx]), dbf > 0)
time.sleep(0.1)
zout = namspcs[idx].zout
if rtsat0 > 0:
zout = np.append(zout, ZERO)
mlts[idx] = np.append(mlts[idx], 0)
zout, mlts[idx] = apprtsat0(zout, mlts[idx], rtsat0)
endprocs(proc)
retval = zout, mlts[idx], msfts[idx], itrs[idx], typ[idx]
if max(flgs) == 2 or prod(flgs) == 1:
break
time.sleep(sleeptime)
ec_("Z6: after while; cnt=%6d;" % (cnt), dbf > 0)
if sum(flgs) == 0: # all processes still running
ec_("Processes still running: cnt=%6d/%10d;" % (cnt, maxloop))
endprocs(proc)
elif max(flgs) == 1: # no msfts[idx] < thrsh, return better one
idx = msfts.index(min(msfts))
ec_("Cx idx=%d;" % (idx), dbf > 0)
zout = namspcs[idx].zout
if rtsat0 > 0:
zout = np.append(zout, ZERO)
mlts[idx] = np.append(mlts[idx], 0)
zout, mlts[idx] = apprtsat0(zout, mlts[idx], rtsat0)
endprocs(proc)
retval = zout, mlts[idx], msfts[idx], itrs[idx], "(" + typ[idx] + ")"
ec_("Z7: before return;", dbf > 0)
if full:
return retval
return retval[0], retval[1]
def prm_worker(poly, delta_method, prms, nmsp):
"""Call prm_mult and prm_sing, put mlt, msft, itr, zout on namespace."""
dbf = 0
ec_("ZZ1", dbf > 0)
nmsp.zout, nmsp.mlt, nmsp.msft, nmsp.itr = prms(poly, delta_method)
ec_("ZZ2", dbf > 0)
return
def apprtsat0(zout, mlt, rtsat0):
"""Add root at zero and its multiplicity to non-zero roots found."""
idx = np.where(mlt != 0)
zout[idx[0][-1] + 1] = ZERO
mlt[idx[0][-1] + 1] = rtsat0
return zout, mlt
def endprocs(proc):
"""end all background processes."""
dbf = 0
for j, _ in enumerate(proc):
fmt = "A4 before proc[%d].is_alive()=%d;"
ec_(fmt % (j, proc[j].is_alive()), dbf > 0)
if proc[j].is_alive():
proc[j].terminate()
fmt = "A5 after proc[%d].is_alive()=%d;"
ec_(fmt % (j, proc[j].is_alive()), dbf > 0)
def prm_mult(poly, delta_method):
"""Find roots of a polynomial using method better for multiplicities > 1.
Calculate roots of polynomial poly, finding multiplicities using roots
of the derivatives. Recursive.
Parameters
----------
poly : list or array like
coefficients of the polynomial -- size n+1
integer, float, or multiple precision, real or complex
delta_method : integer
delta method to be used -- see Notes
y(x) = poly[0]*x^n + poly[1]*x^(n-1) + ... + poly[n-1]*x + poly[n]
Returns
-------
zout : ndarray
multiple precision ndarray containing the roots of the polynomial
mlt : list
multiplicity of zout
msf : float
figure of merit of root estimates -- larger negative numbers are better
log10(sum(abs(poly(zout))) / n)) / 2
num_iter : int
total number of iterations
Notes
-----
delta_method
0 delta_weierstrass WeierStrass-Durand-Kerner
1 delta_aberth Aberth-Ehrlich
3 delta_sakurai Sakurai-Torii-Sugiura
7 delta_petkovic Sakurai-Petkovic
Example
-------
>>> mp.mp.dps = 2000
>>> prm_mult([1, 4, 6, 4, 1], 1)
(array([mpc(real='-1.0', imag='0.0'), mpc(real='0.0', imag='0.0'),
mpc(real='0.0', imag='0.0'), mpc(real='0.0', imag='0.0')],
dtype=object), [4, 0, 0, 0], -4000, 4)
"""
cst = time.time()
roottol = mp.mpf(1e-200)
dbf = 0 # -1:4 1
msft = 0
itr = 1
num_iterd = 0
# -------------------------------------------------------------------------
poly, _ = polytrim(poly)
npoly = len(poly) - 1
ec_(FM01 % (npoly), dbf > 2)
if npoly >= 2: # derivatives
polyd = np.polyder(poly)
cst = time.time() - cst
znd, mltd, _, num_iterd = prm_mult(polyd, delta_method) # ---
cst = time.time() - cst
if dbf > 2:
ec_("AA5: n=%d; polyd, znd, mltd:" % (npoly))
for idx in np.where(mltd)[0]:
print(" %d" % mltd[idx], end="")
print(flush=True)
prntall(polyd, znd, mltd, dbf > 4)
if npoly == 0: # only roots at zero
zout = [mp.mpf('0')]
mlt = [0]
ec_("AA3", dbf > 2)
elif npoly == 1: # line 0 = poly[0]*x + poly[1]
zout = [-poly[1]]
mlt = [1]
msft = msemsf(poly, zout, mlt)
ec_("AA4", dbf > 2)
elif npoly == 2: # quadratic 0 = poly[0]*x^2 + poly[1]*s + poly[2]
if mltd[0] == 1 and mp.fabs(mppv(poly, znd[0])) < roottol:
zout = [znd[0], ZERO]
mlt = [2, 0]
else:
sym = poly[1] / TWO
rad = mp.sqrt(sym * sym - poly[2])
zout = [-sym + rad, -sym - rad]
mlt = [1, 1]
msft = msemsf(poly, zout, mlt)
else: # npoly >= 3; cubic and higher
ec_("BB1", dbf > 2)
zeroes = mpmpc([0] * npoly)
znew = np.copy(zeroes)
zout = np.copy(zeroes)
mlt = [0] * npoly
# check znd[:] as roots and update multiplicity
idz = 0
ide = 0
rava = np.copy(zeroes)
mava = np.zeros(npoly, dtype=int)
ec_("BB2", dbf > 2)
for idx in np.where(mltd)[0]:
if mp.fabs(mppv(poly, znd[idx])) < roottol:
znew[idz] = znd[idx]
mlt[idz] = mltd[idx] + 1
idz += 1
else: # collect derivative roots for use in estimate
rava[ide] = znd[idx]
mava[ide] = mltd[idx]
ide += 1
# find roots of poly knowing {znew,mlt}[0:idz]
# starting points are modifications of roots of derivative
nrootsm = sum(mlt) # # roots found from mulitiplicities
nrootsq = npoly - nrootsm # # roots still needed
nrootsd = sum(mava) # # roots avilable from derivative
fmt = "BB2a: n=%d; idz=%d; ide=%d; nrootsm=%d; nrootsq=%d; nrootsd=%d;"
ec_(fmt % (npoly, idz, ide, nrootsm, nrootsq, nrootsd), dbf > 0)
if nrootsq == 0: # all roots found from multiplicity
ec_("BB3: n=%d; nrootsq == 0" % (npoly), dbf > 3) # test 3
return znew, mlt, msemsf(poly, znew, mlt), itr + num_iterd
if nrootsd == 0: # no roots from derivative: poly(x) = x^n +c
# polyn = [2, 3, 4, 8, 9, 81, 82, 83, 84, 85, 86, 89]
mag = mp.exp(mp.log(mp.fabs(poly[npoly])) / nrootsq)
ang = (2 * mp.pi * np.array(mp.arange(nrootsq)) +
mp.arg(ZERO - poly[npoly])) / mp.mpf(nrootsq)
for jjj in range(idz, (idz + nrootsq)):
mlt[jjj] = 1
znew[jjj] = mag * (mp.cos(ang[jjj - idz]) +
mp.sin(ang[jjj - idz]) * 1J)
fmt = "BB4: n=%d; mag=%20.12e; msemsf=%9.2f;"
ec_(fmt % (npoly, mag, msemsf(poly, znew, mlt)), dbf > 0) # test 2
else:
iava = 0
nava = sum(mava > 0)
for idw in range(idz, idz + nrootsq):
mlt[idw] = 1
rndx = (1 + (1 + 1j) / 1000.) ** (idw - idz + 1)
if sum(mava) > 0:
while mava[iava] == 0:
iava = (iava + 1) % nava
znew[idw] = rava[iava] * rndx
mava[iava] -= 1
else:
znew[idw] = rava[iava] * rndx
iava = (iava + 1) % nava
ec_("BB5: znew estimate", dbf > 2)
ec_(FM11 % (npoly), dbf > 0) # test 2
prntz(znew, mlt, dbf > 0) # test 2
zout, msf, itr = mpr(poly, polyd, znew, mlt, dbf, delta_method) # ---
ec_("KK", dbf > 2)
msft = msemsf(poly, zout, mlt)
cst = time.time() - cst
ec_(FM39 % (npoly, delta_method, itr, mp.mp.dps, msft, cst), dbf > 0)
precdbg("mult", npoly, msf, itr)
# npoly >= 3:
prntz(zout, mlt, dbf > 0) # 1
ec_("XX", dbf > 0)
return zout, mlt, msft, itr + num_iterd
def prm_sing(poly, delta_method):
"""Find roots of a polynomial using method better for multiplicities == 1.
Parameters
----------
poly : list or array like
coefficients of the polynomial -- size n+1
integer, float, or multiple precision, real or complex
delta_method : integer
delta method to be used -- see Notes
y(x) = poly[0]*x^n + poly[1]*x^(n-1) + ... + poly[n-1]*x + poly[n]
Returns
-------
zout : ndarray
multiple precision ndarray containing the roots of the polynomial
mlt : list
multiplicity of zout
msf : float
figure of merit of root estimates -- larger negative numbers are better
log10(sum(abs(poly(zout))) / n)) / 2
num_iter : int
total number of iterations
Notes
-----
delta_method
0 delta_weierstrass WeierStrass-Durand-Kerner
1 delta_aberth Aberth-Ehrlich
3 delta_sakurai Sakurai-Torii-Sugiura
7 delta_petkovic Sakurai-Petkovic
Example
-------
>>> mp.mp.dps = 20
>>> prm_sing([1, -10, 35, -50, 24], 3)
(array([mpc(real='2.000000000000000000044', imag='-9.629649721936179e-34'),
mpc(real='1.0000000000000000000017', imag='6.018531076210112e-36'),
mpc(real='2.9999999999999999999492', imag='-3.37037740267766e-33'),
mpc(real='4.000000000000000000061', imag='-3.291384182302405e-36')],
dtype=object), [1, 1, 1, 1], mpf('-18.582708243881810972198'), 7)
"""
cst = time.time()
dbf = 0 # -1:4 1
poly, _ = polytrim(poly)
npoly = len(poly) - 1
ec_(FM01 % (npoly), dbf > 2)
polyd = np.polyder(poly)
znew = rootmax11(poly) # ---
ec_("BB5: znew estimate", dbf > 2)
ec_(FM11 % (npoly), dbf > 0) # test 2
mlt = [1] * npoly
zout, msf, itr = mpr(poly, polyd, znew, mlt, dbf, delta_method) # ---
ec_("KK", dbf > 2)
msft = msemsf(poly, zout, mlt)
cst = time.time() - cst
ec_(FM39 % (npoly, delta_method, itr, mp.mp.dps, msft, cst), dbf > 0)
ec_("WW", dbf > 0)
precdbg("sing", npoly, msf, itr)
return zout, mlt, msft, itr
def precdbg(src, npoly, msf, itr):
"""plot msf or write it to file"""
if True:
return
fil = "prm_%s-%d-%d-" % (src, npoly, mp.mp.dps) + datm()
plt.figure()
plt.plot(msf[1:(itr+1)])
plt.title(fil)
plt.draw()
plt.savefig(fil + ".pdf", bbox_inches="tight")
with open(fil + ".txt", 'a') as fid:
fmt = "mp.mp.dps=%9d;"
for msfi in msf[range(itr + 1)]:
print("%9.2f" % (msfi), file=fid)
return
def polytrim(poly):
"""Remove leading and trailing coefficients of zero."""
dbf = 0
ec_("AA1i", dbf > 0)
polyc = mpmpc(poly)
# Exception: STATUS_ACCESS_VIOLATION: np.where needs comparison n ~ 600
idx = np.where(polyc != ZERO) # where returns indeces of non-zero values
ec_("AA1j", dbf > 0)
polyr = polyc[idx[0][0]:(idx[0][-1] + 1)] # remove leading and trailing
if polyc[0] != ONE:
polyr = polyr / polyr[0] # normalize
ec_("AA1o", dbf > 0)
return polyr, len(polyc) - idx[0][-1] - 1 # return poly and # trailing 0s
def mpr(poly, polyd, znew, mlt, dbf, delta_method):
"""Find roots of a polynomial given starting points and multiplicities.
called by prm_mult, prm_sing
calls delta_weierstrass, delta_aberth, delta_sakurai, delta_petkovic
method agnostic
"""
npoly = len(poly) - 1
zeroes = mpmpc([0] * npoly)
itera = 1000
change_tol = mp.mpf(10) ** (-mp.mp.dps // 2)
msf = np.ones(itera) * 1000000
polydd = np.polyder(polyd)
brkflg = 0
itr = 0
for itr in np.arange(1, itera):
zold = np.copy(znew)
change = ZERO
fmt = "CC: n=%d; itr=%d; digits=%d;"
ec_(fmt % (npoly, itr, mp.mp.dps), dbf > 2)
delta = np.copy(zeroes)
zoabs = np.abs(zold[:])
for jji in np.arange(npoly):
ec_("DD: n=%d; itr=%d; jji=%d;" % (npoly, itr, jji), dbf > 3)
if mlt[jji] != 1:
continue
ec_("CC2: n=%d; itr=%d; jji=%d;" % (npoly, itr, jji), dbf > 3)
val = mppv(poly, zold[jji])
ec_("CC3", dbf > 3)
if delta_method == 0:
ec_("CC4", dbf > 3)
delta[jji] = delta_weierstrass(poly, zold, zoabs, val, mlt,
jji, itr, dbf) # ---
if delta_method == 1:
vald = mppv(polyd, zold[jji])
ec_("CC4", dbf > 3)
delta[jji] = delta_aberth(poly, zold, zoabs, val, vald, mlt,
jji, itr, dbf) # ---
if delta_method == 3:
vald = mppv(polyd, zold[jji])
ec_("CC4", dbf > 3)
valdd = mppv(polydd, zold[jji])
ec_("CC5", dbf > 3)
delta[jji] = delta_sakurai(poly, zold, zoabs, val, vald,
valdd, mlt, jji, itr, dbf) # ---
if delta_method == 7:
vald = mppv(polyd, zold[jji])
ec_("CC4", dbf > 3)
valdd = mppv(polydd, zold[jji])
ec_("CC5", dbf > 3)
delta[jji] = delta_petkovic(poly, zold, zoabs, val, vald,
valdd, mlt, jji, itr, dbf) # ---
fmt = "FF9: delta[%d]=%+20.12e %+20.12e j; "
ec_(fmt % (jji, delta[jji].real, delta[jji].imag), dbf > 3)
ec_("FF10", dbf > 3)
dvt = np.abs(znew[jji]) + zoabs[jji]
if dvt == ZERO:
ec_("np.abs(znew[jji])+np.abs(zold[jji]) is zero")
return zold, msemsf(poly, zold, mlt), itr
dtxv = np.abs(delta[jji]) / dvt
fmt = "FF11: n=%d; itr=%3d; jji=%3d; dvt=%15.9e"
ec_(fmt % (npoly, itr, jji, dvt), dbf > 3)
if change < dtxv:
change = dtxv
ec_("FF12", dbf > 3)
znew[jji] = zold[jji] + delta[jji]
znjj = np.complex(znew[jji])
fmt = "GG2c: znjj=%+15.8e %+15.8e j;"
ec_(fmt % (np.real(znjj), np.imag(znjj)), dbf > 2)
# jji
msf[itr] = msemsf(poly, znew, mlt)
if dbf > 1:
fmt = "HH1: n=%d; change[%2d,%3d]=%9.2e; msf=%9.2f; digits=%d; II"
fmu = "HH2: n=%d; log10(change[%2d,%3d])=%9.2f; " + \
"msf=%9.2f; digits=%d; II"
if np.abs(change) == ZERO:
ec_(fmt % (npoly, delta_method, itr, np.abs(change), msf[itr],
mp.mp.dps))
else:
ec_(fmu % (npoly, delta_method, itr, mp.log10(np.abs(change)),
msf[itr], mp.mp.dps))
if change < change_tol or msf[itr] < -mp.mp.dps * 0.95:
zout = np.copy(znew)
brkflg = 1
break
# itr
if brkflg == 0:
zout = np.copy(znew)
return zout, msf, itr
def delta_weierstrass(poly, zold, zoabs, val, mlt, jji, itr, dbf):
"""Calculate delta using WeierStrass-Durand-Kerner.
Reference:
https://en.wikipedia.org/wiki/Durand%E2%80%93Kerner_method
"""
npoly = len(poly) - 1
acc = ONE
for kki in np.arange(npoly):
if mlt[kki] == 0 or jji == kki:
continue
ec_(FM18 % (npoly, itr, jji, kki), dbf > 3)
if roottoltest(zold, zoabs, jji, kki):
acc *= (zold[jji] - zold[kki]) ** mlt[kki]
ec_("FF1", dbf > 3)
if acc == ZERO:
ec_("FF6: acc is zero")
return ZERO
ec_("FF7: acc is one", (mp.fabs(acc - 1) < mp.mpf(0.01)))
delta = -val / acc
ec_(FM28 % (jji, val.real, val.imag, acc.real, acc.imag), dbf > 3)
return delta
def delta_aberth(poly, zold, zoabs, val, vald, mlt, jji, itr, dbf):
"""Calculate delta using Aberth-Ehrlich.
Reference:
https://en.wikipedia.org/wiki/Aberth_method
"""
npoly = len(poly) - 1
acc = ZERO
for kki in np.arange(npoly):
if mlt[kki] == 0 or jji == kki:
continue
ec_(FM18 % (npoly, itr, jji, kki), dbf > 3)
if roottoltest(zold, zoabs, jji, kki):
dvt = zold[jji] - zold[kki]
if dvt == ZERO:
ec_("zold[delta_method,jji]-zold[delta_method,kki] is zero")
return ZERO
one_dvx = mp.mpf(mlt[kki]) / dvt
acc += one_dvx
ec_("FF1", dbf > 3)
dvt = vald - val * acc
if dvt == ZERO:
ec_("FF2: vald[jji]-val[jji] * acc is zero")
dvt = ONE
delta = -val * ONE / dvt
ec_(FM28 % (jji, val.real, val.imag, acc.real, acc.imag), dbf > 3)
return delta
def delta_sakurai(poly, zold, zoabs, val, vald, valdd, mlt, jji, itr, dbf):
"""Calculate delta using Sakurai-Torii-Sugiura.
Reference:
T. Sakurai, M.S. Petkovic 1996-Aug-13
On some simultaneous methods based on Weierstrass correction
Section 2, Sakurai-Torii-Sugiura
"""
del1, del2, sum1, sum2 = dels_sums(poly, zold, zoabs, val, vald, valdd,
mlt, jji, itr, dbf) # ---
ec_("FF1", dbf > 3)
dvt = del2 + TWO * (sum1 * del1 - del1 * del1) + sum2 - sum1 * sum1
if dvt == ZERO:
delta = ZERO
else:
delta = -TWO * (sum1 - del1) / dvt
ec_(FM28 % (jji, val.real, val.imag, sum1.real, sum1.imag), dbf > 3)
return delta
def delta_petkovic(poly, zold, zoabs, val, vald, valdd, mlt, jji, itr, dbf):
"""Calculate delta using Sakurai-Petkovic.
Reference:
T. Sakurai, M.S. Petkovic 1996-Aug-13
On some simultaneous methods based on Weierstrass correction
Section 2, Sakurai-Petkovic
"""
del1, del2, sum1, sum2 = dels_sums(poly, zold, zoabs, val, vald, valdd,
mlt, jji, itr, dbf) # ---
dvt = TWO * (del1 - sum1) ** THREE
if dvt == ZERO:
delta = ZERO
else:
delta = (-(THREE * (del1 - sum1) ** TWO + del2 - del1 * del1 + sum2) /
dvt)
ec_(FM28 % (jji, val.real, val.imag, sum1.real, sum1.imag), dbf > 3)
return delta
def dels_sums(poly, zold, zoabs, val, vald, valdd, mlt, jji, itr, dbf):
"""Calculate the dels and sums used in the various delta methods."""
npoly = len(poly) - 1
sum1 = ZERO
sum2 = ZERO
del1 = ZERO
del2 = ZERO
if val != ZERO:
del1 = vald / val
del2 = valdd / val
for kki in np.arange(npoly):
if mlt[kki] == 0 or jji == kki:
continue
ec_(FM18 % (npoly, itr, jji, kki), dbf > 3)
if roottoltest(zold, zoabs, jji, kki):
dvt = zold[jji] - zold[kki]
if dvt == ZERO:
ec_("Zo[ab,jj]-Zo[ab,kk] is zero")
return ZERO, ZERO, ZERO, ZERO
# will this work for delta_method == 3?
one_dvx = mp.mpf(mlt[kki]) / dvt
sum1 += one_dvx
sum2 += one_dvx * one_dvx
return del1, del2, sum1, sum2
def rootmax11(poly):
"""Calculate root estimates using Krishnan-Foskey-Culver-Keyser-Manocha.
Reference:
Krishnan, Foskey, Culver, Keyser, Manocha 2001-Jun-03
PRECISE: Efficent Multiprecision Evaluation of Algebraic Roots and
Predicates for Reliable Geometric Computation
Section 4.2 Choice of Initial Approximations
"""
polya = np.abs(poly)
npoly = np.size(poly) - 1
ppolya = polya[1:]
rmax = min(max(ONE, np.sum(ppolya)), ONE + np.max(ppolya))
ppolya = polya[:-1]
rmin = polya[-1] / min(max(polya[-1], np.sum(ppolya)),
(polya[-1] + np.max(ppolya)))
srng = mpmpc([0] * (npoly + 1))
rrng = np.copy(srng)
rrng[0] = rmin
srng[-1] = rmax
kkm = 0
k_list = np.zeros(npoly + 1)
skk = np.copy(srng)
rkk = np.copy(srng)
for kki in [0, npoly] + list(range(1, npoly)):
pak = np.copy(polya)
pak[npoly - kki] = 0 - polya[npoly - kki]
if kki == 0:
srng[kki] = fndlim(pak, rrng[kki], -1, rmax) # ---
rmin = max(rmin, srng[kki])
k_list[kkm] = kki
skk[kkm] = srng[kki]
kkm += 1
elif kki == npoly:
rrng[kki] = fndlim(pak, rmin, 1, rmax) # ---
rmax = min(rmax, rrng[kki])
else:
rrng[kki] = fndlim(pak, skk[kkm - 1], 1, rmax) # ---
srng[kki] = ZERO
if rrng[kki] <= rmax:
srng[kki] = fndlim(pak, rrng[kki], -1, rmax) # ---
if (skk[kkm - 1] <= rrng[kki] and rrng[kki] <= srng[kki] and
srng[kki] <= rmax):
k_list[kkm] = kki
skk[kkm] = srng[kki]
rkk[kkm] = rrng[kki]
kkm += 1
k_list[kkm] = npoly
rkk[kkm] = rrng[npoly]
jjj = 0
znew = mpmpc([0] * npoly)
for mmi in np.arange(kkm):
nnn = k_list[mmi + 1] - k_list[mmi]
phi = mp.pi / (mp.mpf(2) * nnn)
for lli in np.arange(1, nnn + 1):
radius = rkk[mmi + 1]
znew[jjj] = radius * mp.exp(1j * (2 * mp.pi * lli / nnn + phi))
jjj += 1
return znew
def fndlim(pak, rsk, sgn, rmax):
"""Find limits in rootmax11."""
rskk = rsk
fact = TWO
fact_cnt_max = 3
fexp = TWO
fact_cnt = 0
factp = fact
val0 = mppv(pak, rskk)
while val0 * sgn > 0 and rskk <= rmax:
rskk = rskk * factp
val0 = mppv(pak, rskk)
if val0 * sgn < 0 and fact_cnt < fact_cnt_max:
rskk = rskk / factp
val0 = mppv(pak, rskk)
factp = factp ** (1 / fexp)
fact_cnt += 1
if sgn == -1:
rskk = rskk / factp
return rskk
def ec_(msg="", doit=True, timestr=True):
"""Print with timestamp."""
if doit:
fnam = extract_stack(None, 2)[0][2] # function calling ec_
ec_time = ""
if timestr:
ec_time = time.strftime("%Y-%b-%d %H:%M:%S ")
print('[%s%s] %s' % (ec_time, fnam, msg), flush=True)
def mppv(poly, znew):
"""Calculate value of polynomial."""
return mp.polyval(poly.tolist(), znew)
def prod(factors):
"""Calculate the product of the factors."""
return reduce(mul, factors, 1)
def prnt(strng, doit=True):
"""Print if."""
if doit:
print(strng, end="", flush=True)
def hostn(timestr=True):
"""Print the hostname, number of cpus and the version using ec_."""
fmt = "host=%s; cpu=%d; ver=%s"
host = gethostname()
cpus = cpu_count()
vers = sys.version.replace('\n', ' ')
ec_(fmt % (host, cpus, vers), True, timestr)
return
def roottoltest(zold, zoabs, jji, kki):
"""Check difference between roots."""
root_diff_tol = mp.mpf(10) ** (-mp.mp.dps * 10)
return (mp.fabs(zold[jji] - zold[kki]) > root_diff_tol *
(zoabs[jji] + zoabs[kki]))
def msemsf(poly, znew, mlt):
"""Calculate sum of polynomial values at root estimates."""
valasum = ZERO
npoly = np.size(poly) - 1
for idx in np.where(mlt)[0]:
vala = mp.fabs(mppv(poly, znew[idx]))
valasum += vala * vala * mlt[idx]
if valasum == ZERO:
msf = -mp.mp.dps * 2
else:
msf = np.float(mp.log10(valasum / mp.mpf(npoly))) / TWO
return msf
def prntall(poly, znew, mlt, doit=True):
"""Print polynomial coefficients, roots and multiplicities."""
if not doit:
return
fmt = "poly[%d]=%+20.12e %+20.12e j;"
for idx, pidx in enumerate(poly):
ec_(fmt % (idx, pidx.real, pidx.imag))
prntz(znew, mlt)
return
def prntz(znew, mlt, doit=True, timestr=True):
"""Print roots and multiplicities."""
if not doit:
return
fmt = "n=%d; zn[%2d]=%+20.12e %+20.12e j; mlt=%d;"
npoly = len(znew)
idz = np.argsort(np.array(znew, dtype=complex))
for idx in range(npoly):
idy = idz[idx]
if mlt[idy] == 0:
continue
ec_(fmt % (npoly, idy, znew[idy].real, znew[idy].imag, mlt[idy]), True,
timestr)
return
def mpmpc(inlist):
"""Convert list of numbers into array of multiple precision complex."""
outlist = np.array(mp.zeros(len(inlist), 1))
for idx, adx in enumerate(inlist):
adxm = adx
if isinstance(adx, np.int64):
adxm = int(adx)
outlist[idx] = mp.mpc(adxm)
return outlist
def datm():
"""Create a timestamp"""
yr, mx, da, hr, mi, se = time.strftime("%y %m %d %H %M %S").split()
mo = "%x" % (int(mx))
return yr + mo + da + '_' + hr + mi + se
def polysets(ntest=False):
"""generate list of tests"""
x00 = list(range(-1, -15, -1)) # simple ones for prm
y00 = list(range(1, 18))
# y01 = list(range(16, 18))
u00 = list(range(24, 31)) # multiple roots
# u01 = list(range(29, 31)) # multiple roots
q00 = [32, 35, 40] # multiple roots
t00 = [45, 50, 60, 70, 80] # multiple roots
# The v00 set can take long, and 89 and 90 can take even longer.
v00 = [81, 82, 83, 84, 85, 86, 88, 91, 92, 93, 89, 90, 87]
# v01 = list(range(81, 87)) + list(range(88, 94))
# v04 = list(range(81, 87))
# v02 = list(range(90, 94))
# v03 = list(range(92, 94))
w00 = [110, 120, 150, 195, 196, 197, 198, 199] # 197, 198: errors
# Exception: STATUS_ACCESS_VIOLATION at rip=003961FAB36
ct0 = list(range(203, 212)) # Chebyshev T
cu0 = list(range(303, 310)) # Chebyshev U
polyset = [400, 401, 402] * 10
polyset = w00
polyset = [-7, -8, -13, -14] # 2 quintics mult & non
polyset = x00 + y00 + u00 + q00 + t00 + ct0 + cu0 + v00 # standard test
nset = [None]
if ntest: # test at multiple n
nset = [3, 4, 5, 6, 8, 10,
13, 17, 22, 28, 36, 46, 60, 77, 100,
130, 170, 220, 280, 360, 460, 600, 770, 1000,
1300, 1700, 2200, 2800, 3600, 4600, 6000, 7700, 9999]
polyset = [1, 5, 6, 7, 9, 12, 17, 85, 86, 91, 92, 93] # 87
polyset = [17, 9, 85, 1, 86, 5, 12, 92, 6, 93, 91, 7] # sorted (n=100)
polyset = [9, 17, 85, 1, 86, 5, 12, 92, 6, 93, 91, 7] # 17 stuck 1000
return polyset, nset