Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.56% covered (success)
95.56%
452 / 473
81.82% covered (warning)
81.82%
9 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
EigenvalueDecomposition
95.56% covered (success)
95.56%
452 / 473
81.82% covered (warning)
81.82%
9 / 11
145
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 tred2
92.42% covered (success)
92.42%
61 / 66
0.00% covered (danger)
0.00%
0 / 1
23.23
 tql2
100.00% covered (success)
100.00%
68 / 68
100.00% covered (success)
100.00%
1 / 1
15
 orthes
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
1 / 1
21
 cdiv
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 hqr2
93.60% covered (success)
93.60%
234 / 250
0.00% covered (danger)
0.00%
0 / 1
72.32
 isSymmetric
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getV
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getRealEigenvalues
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getImagEigenvalues
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getD
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Math\Matrix
8 * @copyright Dennis Eichhorn
9 * @copyright JAMA - https://math.nist.gov/javanumerics/jama/
10 * @license   OMS License 2.0
11 * @version   1.0.0
12 * @link      https://jingga.app
13 */
14declare(strict_types=1);
15
16namespace phpOMS\Math\Matrix;
17
18use phpOMS\Math\Geometry\Shape\D2\Triangle;
19
20/**
21 * Eigenvalue decomposition
22 *
23 * A symmetric then A = V*D*V'
24 * A not symmetric then (potentially) A = V*D*inverse(V)
25 *
26 * @package phpOMS\Math\Matrix
27 * @license OMS License 2.0
28 * @link    https://jingga.app
29 * @since   1.0.0
30 */
31final class EigenvalueDecomposition
32{
33    /**
34     * Epsilon for float comparison.
35     *
36     * @var float
37     * @since 1.0.0
38     */
39    public const EPSILON = 4.88e-04;
40
41    /**
42     * Dimension m
43     *
44     * @var int
45     * @since 1.0.0
46     */
47    private int $m = 0;
48
49    /**
50     * Is symmetric
51     *
52     * @var bool
53     * @since 1.0.0
54     */
55    private bool $isSymmetric = true;
56
57    /**
58     * A square matrix.
59     *
60     * @var array
61     * @since 1.0.0
62     */
63    private array $A = [];
64
65    /**
66     * Eigenvectors
67     *
68     * @var array
69     * @since 1.0.0
70     */
71    private array $V = [];
72
73    /**
74     * Eigenvalues
75     *
76     * @var array
77     * @since 1.0.0
78     */
79    private array $D = [];
80
81    /**
82     * Eigenvalues
83     *
84     * @var array
85     * @since 1.0.0
86     */
87    private array $E = [];
88
89    /**
90     * Hessenberg form
91     *
92     * @var array
93     * @since 1.0.0
94     */
95    private array $H = [];
96
97    /**
98     * Non-symmetric storage
99     *
100     * @var array
101     * @since 1.0.0
102     */
103    private array $ort = [];
104
105    /**
106     * Complex scalar division
107     *
108     * @var float
109     * @since 1.0.0
110     */
111    private float $cdivr = 0.0;
112
113    /**
114     * Complex scalar division
115     *
116     * @var float
117     * @since 1.0.0
118     */
119    private float $cdivi = 0.0;
120
121    /**
122     * Constructor.
123     *
124     * @param Matrix $M Matrix
125     *
126     * @since 1.0.0
127     */
128    public function __construct(Matrix $M)
129    {
130        $this->m = $M->getM();
131        $this->A = $M->toArray();
132
133        for ($j = 0; ($j < $this->m) & $this->isSymmetric; ++$j) {
134            for ($i = 0; ($i < $this->m) & $this->isSymmetric; ++$i) {
135                $this->isSymmetric = ($this->A[$i][$j] === $this->A[$j][$i]);
136            }
137        }
138
139        if ($this->isSymmetric) {
140            $this->V = $this->A;
141
142            $this->tred2();
143            $this->tql2();
144        } else {
145            $this->H = $this->A;
146
147            $this->orthes();
148            $this->hqr2();
149        }
150    }
151
152    /**
153     * Housholder tridiagonal form reduction.
154     *
155     * @return void
156     *
157     * @since 1.0.0
158     */
159    private function tred2() : void
160    {
161        for ($j = 0; $j < $this->m; ++$j) {
162            $this->D[$j] = $this->V[$this->m - 1][$j];
163        }
164
165        for ($i = $this->m - 1; $i > 0; --$i) {
166            $scale = 0.0;
167            $h     = 0.0;
168
169            for ($k = 0; $k < $i; ++$k) {
170                $scale += \abs($this->D[$k]);
171            }
172
173            if ($scale == 0) {
174                $this->E[$i] = $this->D[$i - 1];
175
176                /* @phpstan-ignore-next-line */
177                for ($j = 0; $j > $i; ++$j) {
178                    $this->D[$j]     = $this->V[$i - 1][$j];
179                    $this->V[$i][$j] = 0.0;
180                    $this->V[$j][$i] = 0.0;
181                }
182            } else {
183                for ($k = 0; $k < $i; ++$k) {
184                    $this->D[$k] /= $scale;
185                    $h           += $this->D[$k] * $this->D[$k];
186                }
187
188                $f = $this->D[$i - 1];
189                $g = $f > 0 ? -\sqrt($h) : \sqrt($h);
190
191                $this->E[$i]     = $scale * $g;
192                $h              -= $f * $g;
193                $this->D[$i - 1] = $f - $g;
194
195                for ($j = 0; $j < $i; ++$j) {
196                    $this->E[$j] = 0.0;
197                }
198
199                for ($j = 0; $j < $i; ++$j) {
200                    $f               = $this->D[$j];
201                    $this->V[$j][$i] = $f;
202                    $g               = $this->E[$j] + $this->V[$j][$j] * $f;
203
204                    for ($k = $j + 1; $k < $i; ++$k) {
205                        $g           += $this->V[$k][$j] * $this->D[$k];
206                        $this->E[$k] += $this->V[$k][$j] * $f;
207                    }
208
209                    $this->E[$j] = $g;
210                }
211
212                $f = 0.0;
213                for ($j = 0; $j < $i; ++$j) {
214                    $this->E[$j] /= $h;
215                    $f           += $this->E[$j] * $this->D[$j];
216                }
217
218                $hh = $f / ($h + $h);
219                for ($j = 0; $j < $i; ++$j) {
220                    $this->E[$j] -= $hh * $this->D[$j];
221                }
222
223                for ($j = 0; $j < $i; ++$j) {
224                    $f = $this->D[$j];
225                    $g = $this->E[$j];
226
227                    for ($k = $j; $k < $i; ++$k) {
228                        $this->V[$k][$j] -= ($f * $this->E[$k] + $g * $this->D[$k]);
229                    }
230
231                    $this->D[$j]     = $this->V[$i - 1][$j];
232                    $this->V[$i][$j] = 0.0;
233                }
234            }
235
236            $this->D[$i] = $h;
237        }
238
239        for ($i = 0; $i < $this->m - 1; ++$i) {
240            $this->V[$this->m - 1][$i] = $this->V[$i][$i];
241            $this->V[$i][$i]           = 1.0;
242            $h                         = $this->D[$i + 1];
243
244            if ($h != 0) {
245                for ($k = 0; $k <= $i; ++$k) {
246                    $this->D[$k] = $this->V[$k][$i + 1] / $h;
247                }
248
249                for ($j = 0; $j <= $i; ++$j) {
250                    $g = 0.0;
251                    for ($k = 0; $k <= $i; ++$k) {
252                        $g += $this->V[$k][$i + 1] * $this->V[$k][$j];
253                    }
254
255                    for ($k = 0; $k <= $i; ++$k) {
256                        $this->V[$k][$j] -= $g * $this->D[$k];
257                    }
258                }
259            }
260
261            for ($k = 0; $k <= $i; ++$k) {
262                $this->V[$k][$i + 1] = 0.0;
263            }
264        }
265
266        for ($j = 0; $j < $this->m; ++$j) {
267            $this->D[$j]               = $this->V[$this->m - 1][$j];
268            $this->V[$this->m - 1][$j] = 0.0;
269        }
270
271        $this->V[$this->m - 1][$this->m - 1] = 1.0;
272        $this->E[0]                          = 0.0;
273    }
274
275    /**
276     * Symmetric tridiagonal QL algorithm
277     *
278     * @return void
279     *
280     * @since 1.0.0
281     */
282    private function tql2() : void
283    {
284        for ($i = 1; $i < $this->m; ++$i) {
285            $this->E[$i - 1] = $this->E[$i];
286        }
287
288        $this->E[$this->m - 1] = 0.0;
289
290        $f    = 0.0;
291        $tst1 = 0.0;
292
293        for ($l = 0; $l < $this->m; ++$l) {
294            $tst1 = \max($tst1, \abs($this->D[$l]) + \abs($this->E[$l]));
295            $m    = $l;
296
297            while ($m < $this->m) {
298                if (\abs($this->E[$m]) <= self::EPSILON * $tst1) {
299                    break;
300                }
301
302                ++$m;
303            }
304
305            if ($m > $l) {
306                $iter = 0;
307
308                do {
309                    ++$iter;
310
311                    $g = $this->D[$l];
312                    $p = ($this->D[$l + 1] - $g) / (2.0 * $this->E[$l]);
313                    $r = $p < 0 ? -Triangle::getHypot($p, 1) : Triangle::getHypot($p, 1);
314
315                    $this->D[$l]     = $this->E[$l] / ($p + $r);
316                    $this->D[$l + 1] = $this->E[$l] * ($p + $r);
317                    $dl1             = $this->D[$l + 1];
318                    $h               = $g - $this->D[$l];
319
320                    for ($i = $l + 2; $i < $this->m; ++$i) {
321                        $this->D[$i] -= $h;
322                    }
323
324                    $f  += $h;
325                    $p   = $this->D[$m];
326                    $c   = 1.0;
327                    $c2  = 1.0;
328                    $c3  = 1.0;
329                    $el1 = $this->E[$l + 1];
330                    $s   = 0.0;
331                    $s2  = 0.0;
332
333                    for ($i = $m - 1; $i >= $l; --$i) {
334                        $c3              = $c2;
335                        $c2              = $c;
336                        $s2              = $s;
337                        $g               = $c * $this->E[$i];
338                        $h               = $c * $p;
339                        $r               = Triangle::getHypot($p, $this->E[$i]);
340                        $this->E[$i + 1] = $s * $r;
341                        $s               = $this->E[$i] / $r;
342                        $c               = $p / $r;
343                        $p               = $c * $this->D[$i] - $s * $g;
344                        $this->D[$i + 1] = $h + $s * ($c * $g + $s * $this->D[$i]);
345
346                        for ($k = 0; $k < $this->m; ++$k) {
347                            $h                   = $this->V[$k][$i + 1];
348                            $this->V[$k][$i + 1] = $s * $this->V[$k][$i] + $c * $h;
349                            $this->V[$k][$i]     = $c * $this->V[$k][$i] - $s * $h;
350                        }
351                    }
352
353                    $p           = -$s * $s2 * $c3 * $el1 * $this->E[$l] / $dl1;
354                    $this->E[$l] = $s * $p;
355                    $this->D[$l] = $c * $p;
356                } while (\abs($this->E[$l]) > self::EPSILON * $tst1);
357            }
358
359            $this->D[$l] += $f;
360            $this->E[$l]  = 0.0;
361        }
362
363        for ($i = 0; $i < $this->m - 1; ++$i) {
364            $k = $i;
365            $p = $this->D[$i];
366
367            for ($j = $i + 1; $j < $this->m; ++$j) {
368                if ($this->D[$j] < $p) {
369                    $k = $j;
370                    $p = $this->D[$j];
371                }
372            }
373
374            if ($k !== $i) {
375                $this->D[$k] = $this->D[$i];
376                $this->D[$i] = $p;
377
378                for ($j = 0; $j < $this->m; ++$j) {
379                    $p               = $this->V[$j][$i];
380                    $this->V[$j][$i] = $this->V[$j][$k];
381                    $this->V[$j][$k] = $p;
382                }
383            }
384        }
385    }
386
387    /**
388     * Create the orthogonal eigenvectors
389     *
390     * @return void
391     *
392     * @since 1.0.0
393     */
394    private function orthes() : void
395    {
396        $low  = 0;
397        $high = $this->m - 1;
398
399        for ($m = $low + 1; $m < $high; ++$m) {
400            $scale = 0.0;
401
402            for ($i = $m; $i <= $high; ++$i) {
403                $scale += \abs($this->H[$i][$m - 1]);
404            }
405
406            if ($scale != 0) {
407                $h = 0.0;
408                for ($i = $high; $i >= $m; --$i) {
409                    $this->ort[$i] = $this->H[$i][$m - 1] / $scale;
410                    $h            += $this->ort[$i] * $this->ort[$i];
411                }
412
413                $g              = $this->ort[$m] > 0 ? -\sqrt($h) : \sqrt($h);
414                $h             -= $this->ort[$m] * $g;
415                $this->ort[$m] -= $g;
416
417                for ($j = $m; $j < $this->m; ++$j) {
418                    $f = 0.0;
419                    for ($i = $high; $i >= $m; --$i) {
420                        $f += $this->ort[$i] * $this->H[$i][$j];
421                    }
422
423                    $f /= $h;
424                    for ($i = $m; $i <= $high; ++$i) {
425                        $this->H[$i][$j] -= $f * $this->ort[$i];
426                    }
427                }
428
429                for ($i = 0; $i <= $high; ++$i) {
430                    $f = 0.0;
431                    for ($j = $high; $j >= $m; --$j) {
432                        $f += $this->ort[$j] * $this->H[$i][$j];
433                    }
434
435                    $f /= $h;
436                    for ($j = $m; $j <= $high; ++$j) {
437                        $this->H[$i][$j] -= $f * $this->ort[$j];
438                    }
439                }
440
441                $this->ort[$m]      *= $scale;
442                $this->H[$m][$m - 1] = $scale * $g;
443            }
444        }
445
446        for ($i = 0; $i < $this->m; ++$i) {
447            for ($j = 0; $j < $this->m; ++$j) {
448                $this->V[$i][$j] = $i === $j ? 1.0 : 0.0;
449            }
450        }
451
452        for ($m = $high - 1; $m > $low; --$m) {
453            if ($this->H[$m][$m - 1] != 0) {
454                for ($i = $m + 1; $i <= $high; ++$i) {
455                    $this->ort[$i] = $this->H[$i][$m - 1];
456                }
457
458                for ($j = $m; $j <= $high; ++$j) {
459                    $g = 0.0;
460                    for ($i = $m; $i <= $high; ++$i) {
461                        $g += $this->ort[$i] * $this->V[$i][$j];
462                    }
463
464                    $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1];
465                    for ($i = $m; $i <= $high; ++$i) {
466                        $this->V[$i][$j] += $g * $this->ort[$i];
467                    }
468                }
469            }
470        }
471    }
472
473    /**
474     * Perform complex division
475     *
476     * @param float $xr Real value
477     * @param float $xi Imaginary value
478     * @param float $yr Real value
479     * @param float $yi Imaginary value
480     *
481     * @return void
482     *
483     * @since 1.0.0
484     */
485    private function cdiv(float $xr, float $xi, float $yr, float $yi) : void
486    {
487        $r = 0.0;
488        $d = 0.0;
489
490        if (\abs($yr) > \abs($yi)) {
491            $r = $yi / $yr;
492            $d = $yr + $r * $yi;
493
494            $this->cdivr = ($xr + $r * $xi) / $d;
495            $this->cdivi = ($xi - $r * $xr) / $d;
496        } else {
497            $r = $yr / $yi;
498            $d = $yi + $r * $yr;
499
500            $this->cdivr = ($r * $xr + $xi) / $d;
501            $this->cdivi = ($r * $xi - $xr) / $d;
502        }
503    }
504
505    /**
506     * QR algorithm
507     *
508     * @return void
509     *
510     * @since 1.0.0
511     */
512    private function hqr2() : void
513    {
514        $nn      = $this->m;
515        $n       = $nn - 1;
516        $low     = 0;
517        $high    = $nn - 1;
518        $exshift = 0.0;
519        $p       = 0;
520        $q       = 0;
521        $r       = 0;
522        $s       = 0;
523        $z       = 0;
524        $norm    = 0.0;
525
526        for ($i = 0; $i < $nn; ++$i) {
527            /* @phpstan-ignore-next-line */
528            if ($i < $low || $i > $high) {
529                $this->D[$i] = $this->H[$i][$i];
530                $this->E[$i] = 0.0;
531            }
532
533            for ($j = \max($i - 1, 0); $j < $nn; ++$j) {
534                $norm += \abs($this->H[$i][$j]);
535            }
536        }
537
538        $iter = 0;
539        while ($n >= $low) {
540            $l = $n;
541            while ($l > $low) {
542                $s = \abs($this->H[$l - 1][$l - 1]) + \abs($this->H[$l][$l]);
543                if ($s == 0) {
544                    $s = $norm;
545                }
546
547                if (\abs($this->H[$l][$l - 1]) < self::EPSILON * $s) {
548                    break;
549                }
550
551                --$l;
552            }
553
554            if ($l === $n) {
555                $this->H[$n][$n] += $exshift;
556                $this->D[$n]      = $this->H[$n][$n];
557                $this->E[$n]      = 0.0;
558                $iter             = 0;
559
560                --$n;
561            } elseif ($l === $n - 1) {
562                $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
563                $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0;
564                $q = $p * $p + $w;
565                $z = \sqrt(\abs($q));
566
567                $this->H[$n][$n]         += $exshift;
568                $this->H[$n - 1][$n - 1] += $exshift;
569
570                $x = $this->H[$n][$n];
571
572                if ($q >= 0) {
573                    $z               = $p >= 0 ? $p + $z : $p - $z;
574                    $this->D[$n - 1] = $x + $z;
575                    $this->D[$n]     = $z != 0 ? $x - $w / $z : $this->D[$n - 1];
576                    $this->E[$n - 1] = 0.0;
577                    $this->E[$n]     = 0.0;
578
579                    $x  = $this->H[$n][$n - 1];
580                    $s  = \abs($x) + \abs($z);
581                    $p  = $x / $s;
582                    $q  = $z / $s;
583                    $r  = \sqrt($p * $p + $q * $q);
584                    $p /= $r;
585                    $q /= $r;
586
587                    for ($j = $n - 1; $j < $nn; ++$j) {
588                        $z                   = $this->H[$n - 1][$j];
589                        $this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j];
590                        $this->H[$n][$j]     = $q * $this->H[$n][$j] - $p * $z;
591                    }
592
593                    for ($i = 0; $i <= $n; ++$i) {
594                        $z                   = $this->H[$i][$n - 1];
595                        $this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n];
596                        $this->H[$i][$n]     = $q * $this->H[$i][$n] - $p * $z;
597                    }
598
599                    for ($i = $low; $i <= $high; ++$i) {
600                        $z                   = $this->V[$i][$n - 1];
601                        $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n];
602                        $this->V[$i][$n]     = $q * $this->V[$i][$n] - $p * $z;
603                    }
604                } else {
605                    $this->D[$n - 1] = $x + $p;
606                    $this->D[$n]     = $x + $p;
607                    $this->E[$n - 1] = $z;
608                    $this->E[$n]     = -$z;
609                }
610
611                $n   -= 2;
612                $iter = 0;
613            } else {
614                $x = $this->H[$n][$n];
615                $y = 0.0;
616                $w = 0.0;
617
618                if ($l < $n) {
619                    $y = $this->H[$n - 1][$n - 1];
620                    $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
621                }
622
623                if ($iter === 10) {
624                    $exshift += $x;
625                    for ($i = $low; $i <= $n; ++$i) {
626                        $this->H[$i][$i] -= $x;
627                    }
628
629                    $s = \abs($this->H[$n][$n - 1]) + \abs($this->H[$n - 1][$n - 2]);
630                    $x = 0.75 * $s;
631                    $y = $x;
632                    $w = -0.4375 * $s * $s;
633                }
634
635                if ($iter === 30) {
636                    $s = ($y - $x) / 2.0;
637                    $s = $s * $s + $w;
638
639                    if ($s > 0) {
640                        $s = $y < $x ? -\sqrt($s) : \sqrt($s);
641                        $s = $x - $w / (($y - $x) / 2.0 + $s);
642
643                        for ($i = $low; $i <= $n; ++$i) {
644                            $this->H[$i][$i] -= $s;
645                        }
646
647                        $exshift += $s;
648                        $x        = $y = $w = 0.964;
649                    }
650                }
651
652                ++$iter;
653                $m = $n - 2;
654
655                while ($m >= $l) {
656                    $z  = $this->H[$m][$m];
657                    $r  = $x - $z;
658                    $s  = $y - $z;
659                    $p  = ($r * $s - $w) / $this->H[$m + 1][$m] + $this->H[$m][$m + 1];
660                    $q  = $this->H[$m + 1][$m + 1] - $z - $r - $s;
661                    $r  = $this->H[$m + 2][$m + 1];
662                    $s  = \abs($p) + \abs($q) + \abs($r);
663                    $p /= $s;
664                    $q /= $s;
665                    $r /= $s;
666
667                    if ($m === $l
668                        || \abs($this->H[$m][$m - 1]) * (\abs($q) + \abs($r)) < self::EPSILON * (\abs($p) * (\abs($this->H[$m - 1][$m - 1]) + \abs($z) + \abs($this->H[$m + 1][$m + 1])))
669                    ) {
670                        break;
671                    }
672
673                    --$m;
674                }
675
676                for ($i = $m + 2; $i <= $n; ++$i) {
677                    $this->H[$i][$i - 2] = 0.0;
678
679                    if ($i > $m + 2) {
680                        $this->H[$i][$i - 3] = 0.0;
681                    }
682                }
683
684                for ($k = $m; $k < $n; ++$k) {
685                    $notlast = ($k !== $n - 1);
686
687                    if ($k !== $m) {
688                        $p = $this->H[$k][$k - 1];
689                        $q = $this->H[$k + 1][$k - 1];
690                        $r = ($notlast ? $this->H[$k + 2][$k - 1] : 0.0);
691                        $x = \abs($p) + \abs($q) + \abs($r);
692
693                        if ($x == 0) {
694                            continue;
695                        }
696
697                        $p /= $x;
698                        $q /= $x;
699                        $r /= $x;
700                    }
701
702                    $s = $p < 0 ? -\sqrt($p * $p + $q * $q + $r * $r) : \sqrt($p * $p + $q * $q + $r * $r);
703
704                    if ($s == 0) {
705                        continue;
706                    }
707
708                    if ($k !== $m) {
709                        $this->H[$k][$k - 1] = -$s * $x;
710                    } elseif ($l !== $m) {
711                        $this->H[$k][$k - 1] = -$this->H[$k][$k - 1];
712                    }
713
714                    $p += $s;
715                    $x  = $p / $s;
716                    $y  = $q / $s;
717                    $z  = $r / $s;
718                    $q /= $p;
719                    $r /= $p;
720
721                    for ($j = $k; $j < $nn; ++$j) {
722                        $p = $this->H[$k][$j] + $q * $this->H[$k + 1][$j];
723                        if ($notlast) {
724                            $p                   += $r * $this->H[$k + 2][$j];
725                            $this->H[$k + 2][$j] -= $p * $z;
726                        }
727
728                        $this->H[$k][$j]     -= $p * $x;
729                        $this->H[$k + 1][$j] -= $p * $y;
730                    }
731
732                    $min = \min($n, $k + 3);
733                    for ($i = 0; $i <= $min; ++$i) {
734                        $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1];
735
736                        if ($notlast) {
737                            $p                   += $z * $this->H[$i][$k + 2];
738                            $this->H[$i][$k + 2] -= $p * $r;
739                        }
740
741                        $this->H[$i][$k]     -= $p;
742                        $this->H[$i][$k + 1] -= $p * $q;
743                    }
744
745                    for ($i = $low; $i <= $high; ++$i) {
746                        $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1];
747
748                        if ($notlast) {
749                            $p                   += $z * $this->V[$i][$k + 2];
750                            $this->V[$i][$k + 2] -= $p * $r;
751                        }
752                        $this->V[$i][$k]     -= $p;
753                        $this->V[$i][$k + 1] -= $p * $q;
754                    }
755                }
756            }
757        }
758
759        if ($norm == 0) {
760            return;
761        }
762
763        for ($n = $nn - 1; $n >= 0; --$n) {
764            $p = $this->D[$n];
765            $q = $this->E[$n];
766
767            if ($q == 0) {
768                $l               = $n;
769                $this->H[$n][$n] = 1.0;
770
771                for ($i = $n - 1; $i >= 0; --$i) {
772                    $w = $this->H[$i][$i] - $p;
773                    $r = 0.0;
774
775                    for ($j = $l; $j <= $n; ++$j) {
776                        $r += $this->H[$i][$j] * $this->H[$j][$n];
777                    }
778
779                    if ($this->E[$i] < 0.0) {
780                        $z = $w;
781                        $s = $r;
782                    } else {
783                        $l = $i;
784
785                        if ($this->E[$i] == 0) {
786                            $this->H[$i][$n] = $w != 0 ? -$r / $w : -$r / (self::EPSILON * $norm);
787                        } else {
788                            $x                   = $this->H[$i][$i + 1];
789                            $y                   = $this->H[$i + 1][$i];
790                            $q                   = ($this->D[$i] - $p) * ($this->D[$i] - $p) + $this->E[$i] * $this->E[$i];
791                            $t                   = ($x * $s - $z * $r) / $q;
792                            $this->H[$i][$n]     = $t;
793                            $this->H[$i + 1][$n] = \abs($x) > \abs($z) ? (-$r - $w * $t) / $x : (-$s - $y * $t) / $z;
794                        }
795
796                        $t = \abs($this->H[$i][$n]);
797                        if ((self::EPSILON * $t) * $t > 1) {
798                            for ($j = $i; $j <= $n; ++$j) {
799                                $this->H[$j][$n] /= $t;
800                            }
801                        }
802                    }
803                }
804            } elseif ($q < 0) {
805                $l = $n - 1;
806
807                if (\abs($this->H[$n][$n - 1]) > \abs($this->H[$n - 1][$n])) {
808                    $this->H[$n - 1][$n - 1] = $q / $this->H[$n][$n - 1];
809                    $this->H[$n - 1][$n]     = -($this->H[$n][$n] - $p) / $this->H[$n][$n - 1];
810                } else {
811                    $this->cdiv(0.0, -$this->H[$n - 1][$n], $this->H[$n - 1][$n - 1] - $p, $q);
812                    $this->H[$n - 1][$n - 1] = $this->cdivr;
813                    $this->H[$n - 1][$n]     = $this->cdivi;
814                }
815
816                $this->H[$n][$n - 1] = 0.0;
817                $this->H[$n][$n]     = 1.0;
818
819                for ($i = $n - 2; $i >= 0; --$i) {
820                    $ra = 0.0;
821                    $sa = 0.0;
822
823                    for ($j = $l; $j <= $n; ++$j) {
824                        $ra += $this->H[$i][$j] * $this->H[$j][$n - 1];
825                        $sa += $this->H[$i][$j] * $this->H[$j][$n];
826                    }
827
828                    $w = $this->H[$i][$i] - $p;
829                    if ($this->E[$i] < 0.0) {
830                        $z = $w;
831                        $r = $ra;
832                        $s = $sa;
833                    } else {
834                        $l = $i;
835
836                        if ($this->E[$i] == 0) {
837                            $this->cdiv(-$ra, -$sa, $w, $q);
838
839                            $this->H[$i][$n - 1] = $this->cdivr;
840                            $this->H[$i][$n]     = $this->cdivi;
841                        } else {
842                            $x  = $this->H[$i][$i + 1];
843                            $y  = $this->H[$i + 1][$i];
844                            $vr = ($this->D[$i] - $p) * ($this->D[$i] - $p) + $this->E[$i] * $this->E[$i] - $q * $q;
845                            $vi = ($this->D[$i] - $p) * 2.0 * $q;
846
847                            if (($vr == 0 & $vi == 0) !== 0) {
848                                $vr = self::EPSILON * $norm * (\abs($w) + \abs($q) + \abs($x) + \abs($y) + \abs($z));
849                            }
850
851                            $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi);
852
853                            $this->H[$i][$n - 1] = $this->cdivr;
854                            $this->H[$i][$n]     = $this->cdivi;
855
856                            if (\abs($x) > (\abs($z) + \abs($q))) {
857                                $this->H[$i + 1][$n - 1] = (-$ra - $w * $this->H[$i][$n - 1] + $q * $this->H[$i][$n]) / $x;
858                                $this->H[$i + 1][$n]     = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x;
859                            } else {
860                                $this->cdiv(-$r - $y * $this->H[$i][$n - 1], -$s - $y * $this->H[$i][$n], $z, $q);
861                                $this->H[$i + 1][$n - 1] = $this->cdivr;
862                                $this->H[$i + 1][$n]     = $this->cdivi;
863                            }
864                        }
865
866                        $t = \max(\abs($this->H[$i][$n - 1]), \abs($this->H[$i][$n]));
867                        if ((self::EPSILON * $t) * $t > 1) {
868                            for ($j = $i; $j <= $n; ++$j) {
869                                $this->H[$j][$n - 1] /= $t;
870                                $this->H[$j][$n]     /= $t;
871                            }
872                        }
873                    }
874                }
875            }
876        }
877
878        for ($i = 0; $i < $nn; ++$i) {
879            /* @phpstan-ignore-next-line */
880            if ($i < $low || $i > $high) {
881                for ($j = $i; $j < $nn; ++$j) {
882                    $this->V[$i][$j] = $this->H[$i][$j];
883                }
884            }
885        }
886
887        for ($j = $nn - 1; $j >= $low; --$j) {
888            for ($i = $low; $i <= $high; ++$i) {
889                $z = 0.0;
890
891                $min = \min($j, $high);
892                for ($k = $low; $k <= $min; ++$k) {
893                    $z += $this->V[$i][$k] * $this->H[$k][$j];
894                }
895
896                $this->V[$i][$j] = $z;
897            }
898        }
899    }
900
901    /**
902     * Is matrix symmetric?
903     *
904     * @return bool
905     *
906     * @since 1.0.0
907     */
908    public function isSymmetric() : bool
909    {
910        return $this->isSymmetric;
911    }
912
913    /**
914     * Get V matrix
915     *
916     * @return Matrix
917     *
918     * @since 1.0.0
919     */
920    public function getV() : Matrix
921    {
922        $matrix = new Matrix();
923        $matrix->setMatrix($this->V);
924
925        return $matrix;
926    }
927
928    /**
929     * Get real eigenvalues
930     *
931     * @return Vector
932     *
933     * @since 1.0.0
934     */
935    public function getRealEigenvalues() : Vector
936    {
937        $vector = new Vector();
938        $vector->setMatrix($this->D);
939
940        return $vector;
941    }
942
943    /**
944     * Get imaginary eigenvalues
945     *
946     * @return Vector
947     *
948     * @since 1.0.0
949     */
950    public function getImagEigenvalues() : Vector
951    {
952        $vector = new Vector();
953        $vector->setMatrix($this->E);
954
955        return $vector;
956    }
957
958    /**
959     * Get D matrix
960     *
961     * @return Matrix
962     *
963     * @since 1.0.0
964     */
965    public function getD() : Matrix
966    {
967        $matrix = new Matrix();
968
969        $D = [[]];
970        for ($i = 0; $i < $this->m; ++$i) {
971            for ($j = 0; $j < $this->m; ++$j) {
972                $D[$i][$j] = 0.0;
973            }
974
975            $D[$i][$i] = $this->D[$i];
976            if ($this->E[$i] > 0) {
977                $D[$i][$i + 1] = $this->E[$i];
978            } elseif ($this->E[$i] < 0) {
979                $D[$i][$i - 1] = $this->E[$i];
980            }
981        }
982
983        $matrix->setMatrix($D);
984
985        return $matrix;
986    }
987}