Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.56% |
452 / 473 |
|
81.82% |
9 / 11 |
CRAP | |
0.00% |
0 / 1 |
EigenvalueDecomposition | |
95.56% |
452 / 473 |
|
81.82% |
9 / 11 |
145 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
4 | |||
tred2 | |
92.42% |
61 / 66 |
|
0.00% |
0 / 1 |
23.23 | |||
tql2 | |
100.00% |
68 / 68 |
|
100.00% |
1 / 1 |
15 | |||
orthes | |
100.00% |
44 / 44 |
|
100.00% |
1 / 1 |
21 | |||
cdiv | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
hqr2 | |
93.60% |
234 / 250 |
|
0.00% |
0 / 1 |
72.32 | |||
isSymmetric | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getV | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getRealEigenvalues | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getImagEigenvalues | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getD | |
100.00% |
12 / 12 |
|
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 | */ |
14 | declare(strict_types=1); |
15 | |
16 | namespace phpOMS\Math\Matrix; |
17 | |
18 | use 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 | */ |
31 | final 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 | } |