Test Failed
Push — main ( c8394f...8477f1 )
by Rafael
66:21
created

EigenvalueDecomposition::hqr2()   F

Complexity

Conditions 71
Paths > 20000

Size

Total Lines 377
Code Lines 272

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 71
eloc 272
nc 1766255
nop 0
dl 0
loc 377
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
4
5
/**
6
 *    Class to obtain eigenvalues and eigenvectors of a real matrix.
7
 *
8
 *    If A is symmetric, then A = V*D*V' where the eigenvalue matrix D
9
 *    is diagonal and the eigenvector matrix V is orthogonal (i.e.
10
 *    A = V.times(D.times(V.transpose())) and V.times(V.transpose())
11
 *    equals the identity matrix).
12
 *
13
 *    If A is not symmetric, then the eigenvalue matrix D is block diagonal
14
 *    with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues,
15
 *    lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda].  The
16
 *    columns of V represent the eigenvectors in the sense that A*V = V*D,
17
 *    i.e. A.times(V) equals V.times(D).  The matrix V may be badly
18
 *    conditioned, or even singular, so the validity of the equation
19
 *    A = V*D*inverse(V) depends upon V.cond().
20
 *
21
 *    @author  Paul Meagher
22
 *
23
 *    @version 1.1
24
 */
25
class EigenvalueDecomposition
26
{
27
    /**
28
     * Row and column dimension (square matrix).
29
     *
30
     * @var int
31
     */
32
    private $n;
33
34
    /**
35
     * Arrays for internal storage of eigenvalues.
36
     *
37
     * @var array
38
     */
39
    private $d = [];
40
41
    private $e = [];
42
43
    /**
44
     * Array for internal storage of eigenvectors.
45
     *
46
     * @var array
47
     */
48
    private $V = [];
49
50
    /**
51
     * Array for internal storage of nonsymmetric Hessenberg form.
52
     *
53
     * @var array
54
     */
55
    private $H = [];
56
57
    /**
58
     * Working storage for nonsymmetric algorithm.
59
     *
60
     * @var array
61
     */
62
    private $ort;
63
64
    /**
65
     * Used for complex scalar division.
66
     *
67
     * @var float
68
     */
69
    private $cdivr;
70
71
    private $cdivi;
72
73
    /**
74
     * Symmetric Householder reduction to tridiagonal form.
75
     */
76
    private function tred2()
77
    {
78
        //  This is derived from the Algol procedures tred2 by
79
        //  Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
80
        //  Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
81
        //  Fortran subroutine in EISPACK.
82
        $this->d = $this->V[$this->n - 1];
83
        // Householder reduction to tridiagonal form.
84
        for ($i = $this->n - 1; $i > 0; --$i) {
85
            $i_ = $i - 1;
86
            // Scale to avoid under/overflow.
87
            $h = $scale = 0.0;
88
            $scale += array_sum(array_map('abs', $this->d));
89
            if ($scale == 0.0) {
90
                $this->e[$i] = $this->d[$i_];
91
                $this->d = array_slice($this->V[$i_], 0, $i_);
92
                for ($j = 0; $j < $i; ++$j) {
93
                    $this->V[$j][$i] = $this->V[$i][$j] = 0.0;
94
                }
95
            } else {
96
                // Generate Householder vector.
97
                for ($k = 0; $k < $i; ++$k) {
98
                    $this->d[$k] /= $scale;
99
                    $h += pow($this->d[$k], 2);
100
                }
101
                $f = $this->d[$i_];
102
                $g = sqrt($h);
103
                if ($f > 0) {
104
                    $g = -$g;
105
                }
106
                $this->e[$i] = $scale * $g;
107
                $h = $h - $f * $g;
108
                $this->d[$i_] = $f - $g;
109
                for ($j = 0; $j < $i; ++$j) {
110
                    $this->e[$j] = 0.0;
111
                }
112
                // Apply similarity transformation to remaining columns.
113
                for ($j = 0; $j < $i; ++$j) {
114
                    $f = $this->d[$j];
115
                    $this->V[$j][$i] = $f;
116
                    $g = $this->e[$j] + $this->V[$j][$j] * $f;
117
                    for ($k = $j + 1; $k <= $i_; ++$k) {
118
                        $g += $this->V[$k][$j] * $this->d[$k];
119
                        $this->e[$k] += $this->V[$k][$j] * $f;
120
                    }
121
                    $this->e[$j] = $g;
122
                }
123
                $f = 0.0;
124
                for ($j = 0; $j < $i; ++$j) {
125
                    $this->e[$j] /= $h;
126
                    $f += $this->e[$j] * $this->d[$j];
127
                }
128
                $hh = $f / (2 * $h);
129
                for ($j = 0; $j < $i; ++$j) {
130
                    $this->e[$j] -= $hh * $this->d[$j];
131
                }
132
                for ($j = 0; $j < $i; ++$j) {
133
                    $f = $this->d[$j];
134
                    $g = $this->e[$j];
135
                    for ($k = $j; $k <= $i_; ++$k) {
136
                        $this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]);
137
                    }
138
                    $this->d[$j] = $this->V[$i - 1][$j];
139
                    $this->V[$i][$j] = 0.0;
140
                }
141
            }
142
            $this->d[$i] = $h;
143
        }
144
145
        // Accumulate transformations.
146
        for ($i = 0; $i < $this->n - 1; ++$i) {
147
            $this->V[$this->n - 1][$i] = $this->V[$i][$i];
148
            $this->V[$i][$i] = 1.0;
149
            $h = $this->d[$i + 1];
150
            if ($h != 0.0) {
151
                for ($k = 0; $k <= $i; ++$k) {
152
                    $this->d[$k] = $this->V[$k][$i + 1] / $h;
153
                }
154
                for ($j = 0; $j <= $i; ++$j) {
155
                    $g = 0.0;
156
                    for ($k = 0; $k <= $i; ++$k) {
157
                        $g += $this->V[$k][$i + 1] * $this->V[$k][$j];
158
                    }
159
                    for ($k = 0; $k <= $i; ++$k) {
160
                        $this->V[$k][$j] -= $g * $this->d[$k];
161
                    }
162
                }
163
            }
164
            for ($k = 0; $k <= $i; ++$k) {
165
                $this->V[$k][$i + 1] = 0.0;
166
            }
167
        }
168
169
        $this->d = $this->V[$this->n - 1];
170
        $this->V[$this->n - 1] = array_fill(0, $j, 0.0);
171
        $this->V[$this->n - 1][$this->n - 1] = 1.0;
172
        $this->e[0] = 0.0;
173
    }
174
175
    /**
176
     * Symmetric tridiagonal QL algorithm.
177
     *
178
     *    This is derived from the Algol procedures tql2, by
179
     *    Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
180
     *    Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
181
     * Fortran subroutine in EISPACK.
182
     */
183
    private function tql2()
184
    {
185
        for ($i = 1; $i < $this->n; ++$i) {
186
            $this->e[$i - 1] = $this->e[$i];
187
        }
188
        $this->e[$this->n - 1] = 0.0;
189
        $f = 0.0;
190
        $tst1 = 0.0;
191
        $eps = pow(2.0, -52.0);
192
193
        for ($l = 0; $l < $this->n; ++$l) {
194
            // Find small subdiagonal element
195
            $tst1 = max($tst1, abs($this->d[$l]) + abs($this->e[$l]));
196
            $m = $l;
197
            while ($m < $this->n) {
198
                if (abs($this->e[$m]) <= $eps * $tst1) {
199
                    break;
200
                }
201
                ++$m;
202
            }
203
            // If m == l, $this->d[l] is an eigenvalue,
204
            // otherwise, iterate.
205
            if ($m > $l) {
206
                $iter = 0;
207
                do {
208
                    // Could check iteration count here.
209
                    $iter += 1;
210
                    // Compute implicit shift
211
                    $g = $this->d[$l];
212
                    $p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]);
213
                    $r = hypo($p, 1.0);
214
                    if ($p < 0) {
215
                        $r *= -1;
216
                    }
217
                    $this->d[$l] = $this->e[$l] / ($p + $r);
218
                    $this->d[$l + 1] = $this->e[$l] * ($p + $r);
219
                    $dl1 = $this->d[$l + 1];
220
                    $h = $g - $this->d[$l];
221
                    for ($i = $l + 2; $i < $this->n; ++$i) {
222
                        $this->d[$i] -= $h;
223
                    }
224
                    $f += $h;
225
                    // Implicit QL transformation.
226
                    $p = $this->d[$m];
227
                    $c = 1.0;
228
                    $c2 = $c3 = $c;
229
                    $el1 = $this->e[$l + 1];
230
                    $s = $s2 = 0.0;
231
                    for ($i = $m - 1; $i >= $l; --$i) {
232
                        $c3 = $c2;
233
                        $c2 = $c;
234
                        $s2 = $s;
235
                        $g = $c * $this->e[$i];
236
                        $h = $c * $p;
237
                        $r = hypo($p, $this->e[$i]);
238
                        $this->e[$i + 1] = $s * $r;
239
                        $s = $this->e[$i] / $r;
240
                        $c = $p / $r;
241
                        $p = $c * $this->d[$i] - $s * $g;
242
                        $this->d[$i + 1] = $h + $s * ($c * $g + $s * $this->d[$i]);
243
                        // Accumulate transformation.
244
                        for ($k = 0; $k < $this->n; ++$k) {
245
                            $h = $this->V[$k][$i + 1];
246
                            $this->V[$k][$i + 1] = $s * $this->V[$k][$i] + $c * $h;
247
                            $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h;
248
                        }
249
                    }
250
                    $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1;
251
                    $this->e[$l] = $s * $p;
252
                    $this->d[$l] = $c * $p;
253
                    // Check for convergence.
254
                } while (abs($this->e[$l]) > $eps * $tst1);
255
            }
256
            $this->d[$l] = $this->d[$l] + $f;
257
            $this->e[$l] = 0.0;
258
        }
259
260
        // Sort eigenvalues and corresponding vectors.
261
        for ($i = 0; $i < $this->n - 1; ++$i) {
262
            $k = $i;
263
            $p = $this->d[$i];
264
            for ($j = $i + 1; $j < $this->n; ++$j) {
265
                if ($this->d[$j] < $p) {
266
                    $k = $j;
267
                    $p = $this->d[$j];
268
                }
269
            }
270
            if ($k != $i) {
271
                $this->d[$k] = $this->d[$i];
272
                $this->d[$i] = $p;
273
                for ($j = 0; $j < $this->n; ++$j) {
274
                    $p = $this->V[$j][$i];
275
                    $this->V[$j][$i] = $this->V[$j][$k];
276
                    $this->V[$j][$k] = $p;
277
                }
278
            }
279
        }
280
    }
281
282
    /**
283
     * Nonsymmetric reduction to Hessenberg form.
284
     *
285
     *    This is derived from the Algol procedures orthes and ortran,
286
     *    by Martin and Wilkinson, Handbook for Auto. Comp.,
287
     *    Vol.ii-Linear Algebra, and the corresponding
288
     * Fortran subroutines in EISPACK.
289
     */
290
    private function orthes()
291
    {
292
        $low = 0;
293
        $high = $this->n - 1;
294
295
        for ($m = $low + 1; $m <= $high - 1; ++$m) {
296
            // Scale column.
297
            $scale = 0.0;
298
            for ($i = $m; $i <= $high; ++$i) {
299
                $scale = $scale + abs($this->H[$i][$m - 1]);
300
            }
301
            if ($scale != 0.0) {
302
                // Compute Householder transformation.
303
                $h = 0.0;
304
                for ($i = $high; $i >= $m; --$i) {
305
                    $this->ort[$i] = $this->H[$i][$m - 1] / $scale;
306
                    $h += $this->ort[$i] * $this->ort[$i];
307
                }
308
                $g = sqrt($h);
309
                if ($this->ort[$m] > 0) {
310
                    $g *= -1;
311
                }
312
                $h -= $this->ort[$m] * $g;
313
                $this->ort[$m] -= $g;
314
                // Apply Householder similarity transformation
315
                // H = (I -u * u' / h) * H * (I -u * u') / h)
316
                for ($j = $m; $j < $this->n; ++$j) {
317
                    $f = 0.0;
318
                    for ($i = $high; $i >= $m; --$i) {
319
                        $f += $this->ort[$i] * $this->H[$i][$j];
320
                    }
321
                    $f /= $h;
322
                    for ($i = $m; $i <= $high; ++$i) {
323
                        $this->H[$i][$j] -= $f * $this->ort[$i];
324
                    }
325
                }
326
                for ($i = 0; $i <= $high; ++$i) {
327
                    $f = 0.0;
328
                    for ($j = $high; $j >= $m; --$j) {
329
                        $f += $this->ort[$j] * $this->H[$i][$j];
330
                    }
331
                    $f = $f / $h;
332
                    for ($j = $m; $j <= $high; ++$j) {
333
                        $this->H[$i][$j] -= $f * $this->ort[$j];
334
                    }
335
                }
336
                $this->ort[$m] = $scale * $this->ort[$m];
337
                $this->H[$m][$m - 1] = $scale * $g;
338
            }
339
        }
340
341
        // Accumulate transformations (Algol's ortran).
342
        for ($i = 0; $i < $this->n; ++$i) {
343
            for ($j = 0; $j < $this->n; ++$j) {
344
                $this->V[$i][$j] = ($i == $j ? 1.0 : 0.0);
345
            }
346
        }
347
        for ($m = $high - 1; $m >= $low + 1; --$m) {
348
            if ($this->H[$m][$m - 1] != 0.0) {
349
                for ($i = $m + 1; $i <= $high; ++$i) {
350
                    $this->ort[$i] = $this->H[$i][$m - 1];
351
                }
352
                for ($j = $m; $j <= $high; ++$j) {
353
                    $g = 0.0;
354
                    for ($i = $m; $i <= $high; ++$i) {
355
                        $g += $this->ort[$i] * $this->V[$i][$j];
356
                    }
357
                    // Double division avoids possible underflow
358
                    $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1];
359
                    for ($i = $m; $i <= $high; ++$i) {
360
                        $this->V[$i][$j] += $g * $this->ort[$i];
361
                    }
362
                }
363
            }
364
        }
365
    }
366
367
    /**
368
     * Performs complex division.
369
     *
370
     * @param mixed $xr
371
     * @param mixed $xi
372
     * @param mixed $yr
373
     * @param mixed $yi
374
     */
375
    private function cdiv($xr, $xi, $yr, $yi)
376
    {
377
        if (abs($yr) > abs($yi)) {
378
            $r = $yi / $yr;
379
            $d = $yr + $r * $yi;
380
            $this->cdivr = ($xr + $r * $xi) / $d;
381
            $this->cdivi = ($xi - $r * $xr) / $d;
382
        } else {
383
            $r = $yr / $yi;
384
            $d = $yi + $r * $yr;
385
            $this->cdivr = ($r * $xr + $xi) / $d;
386
            $this->cdivi = ($r * $xi - $xr) / $d;
387
        }
388
    }
389
390
    /**
391
     * Nonsymmetric reduction from Hessenberg to real Schur form.
392
     *
393
     *    Code is derived from the Algol procedure hqr2,
394
     *    by Martin and Wilkinson, Handbook for Auto. Comp.,
395
     *    Vol.ii-Linear Algebra, and the corresponding
396
     * Fortran subroutine in EISPACK.
397
     */
398
    private function hqr2()
399
    {
400
        //  Initialize
401
        $nn = $this->n;
402
        $n = $nn - 1;
403
        $low = 0;
404
        $high = $nn - 1;
405
        $eps = pow(2.0, -52.0);
406
        $exshift = 0.0;
407
        $p = $q = $r = $s = $z = 0;
408
        // Store roots isolated by balanc and compute matrix norm
409
        $norm = 0.0;
410
411
        for ($i = 0; $i < $nn; ++$i) {
412
            if (($i < $low) or ($i > $high)) {
413
                $this->d[$i] = $this->H[$i][$i];
414
                $this->e[$i] = 0.0;
415
            }
416
            for ($j = max($i - 1, 0); $j < $nn; ++$j) {
417
                $norm = $norm + abs($this->H[$i][$j]);
418
            }
419
        }
420
421
        // Outer loop over eigenvalue index
422
        $iter = 0;
423
        while ($n >= $low) {
424
            // Look for single small sub-diagonal element
425
            $l = $n;
426
            while ($l > $low) {
427
                $s = abs($this->H[$l - 1][$l - 1]) + abs($this->H[$l][$l]);
428
                if ($s == 0.0) {
429
                    $s = $norm;
430
                }
431
                if (abs($this->H[$l][$l - 1]) < $eps * $s) {
432
                    break;
433
                }
434
                --$l;
435
            }
436
            // Check for convergence
437
            // One root found
438
            if ($l == $n) {
439
                $this->H[$n][$n] = $this->H[$n][$n] + $exshift;
440
                $this->d[$n] = $this->H[$n][$n];
441
                $this->e[$n] = 0.0;
442
                --$n;
443
                $iter = 0;
444
            // Two roots found
445
            } elseif ($l == $n - 1) {
446
                $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
447
                $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0;
448
                $q = $p * $p + $w;
449
                $z = sqrt(abs($q));
450
                $this->H[$n][$n] = $this->H[$n][$n] + $exshift;
451
                $this->H[$n - 1][$n - 1] = $this->H[$n - 1][$n - 1] + $exshift;
452
                $x = $this->H[$n][$n];
453
                // Real pair
454
                if ($q >= 0) {
455
                    if ($p >= 0) {
456
                        $z = $p + $z;
457
                    } else {
458
                        $z = $p - $z;
459
                    }
460
                    $this->d[$n - 1] = $x + $z;
461
                    $this->d[$n] = $this->d[$n - 1];
462
                    if ($z != 0.0) {
463
                        $this->d[$n] = $x - $w / $z;
464
                    }
465
                    $this->e[$n - 1] = 0.0;
466
                    $this->e[$n] = 0.0;
467
                    $x = $this->H[$n][$n - 1];
468
                    $s = abs($x) + abs($z);
469
                    $p = $x / $s;
470
                    $q = $z / $s;
471
                    $r = sqrt($p * $p + $q * $q);
472
                    $p = $p / $r;
473
                    $q = $q / $r;
474
                    // Row modification
475
                    for ($j = $n - 1; $j < $nn; ++$j) {
476
                        $z = $this->H[$n - 1][$j];
477
                        $this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j];
478
                        $this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z;
479
                    }
480
                    // Column modification
481
                    for ($i = 0; $i <= $n; ++$i) {
482
                        $z = $this->H[$i][$n - 1];
483
                        $this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n];
484
                        $this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z;
485
                    }
486
                    // Accumulate transformations
487
                    for ($i = $low; $i <= $high; ++$i) {
488
                        $z = $this->V[$i][$n - 1];
489
                        $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n];
490
                        $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z;
491
                    }
492
                    // Complex pair
493
                } else {
494
                    $this->d[$n - 1] = $x + $p;
495
                    $this->d[$n] = $x + $p;
496
                    $this->e[$n - 1] = $z;
497
                    $this->e[$n] = -$z;
498
                }
499
                $n = $n - 2;
500
                $iter = 0;
501
            // No convergence yet
502
            } else {
503
                // Form shift
504
                $x = $this->H[$n][$n];
505
                $y = 0.0;
506
                $w = 0.0;
507
                if ($l < $n) {
508
                    $y = $this->H[$n - 1][$n - 1];
509
                    $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
510
                }
511
                // Wilkinson's original ad hoc shift
512
                if ($iter == 10) {
513
                    $exshift += $x;
514
                    for ($i = $low; $i <= $n; ++$i) {
515
                        $this->H[$i][$i] -= $x;
516
                    }
517
                    $s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]);
518
                    $x = $y = 0.75 * $s;
519
                    $w = -0.4375 * $s * $s;
520
                }
521
                // MATLAB's new ad hoc shift
522
                if ($iter == 30) {
523
                    $s = ($y - $x) / 2.0;
524
                    $s = $s * $s + $w;
525
                    if ($s > 0) {
526
                        $s = sqrt($s);
527
                        if ($y < $x) {
528
                            $s = -$s;
529
                        }
530
                        $s = $x - $w / (($y - $x) / 2.0 + $s);
531
                        for ($i = $low; $i <= $n; ++$i) {
532
                            $this->H[$i][$i] -= $s;
533
                        }
534
                        $exshift += $s;
535
                        $x = $y = $w = 0.964;
536
                    }
537
                }
538
                // Could check iteration count here.
539
                $iter = $iter + 1;
540
                // Look for two consecutive small sub-diagonal elements
541
                $m = $n - 2;
542
                while ($m >= $l) {
543
                    $z = $this->H[$m][$m];
544
                    $r = $x - $z;
545
                    $s = $y - $z;
546
                    $p = ($r * $s - $w) / $this->H[$m + 1][$m] + $this->H[$m][$m + 1];
547
                    $q = $this->H[$m + 1][$m + 1] - $z - $r - $s;
548
                    $r = $this->H[$m + 2][$m + 1];
549
                    $s = abs($p) + abs($q) + abs($r);
550
                    $p = $p / $s;
551
                    $q = $q / $s;
552
                    $r = $r / $s;
553
                    if ($m == $l) {
554
                        break;
555
                    }
556
                    if (
557
                        abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) <
558
                        $eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1])))
559
                    ) {
560
                        break;
561
                    }
562
                    --$m;
563
                }
564
                for ($i = $m + 2; $i <= $n; ++$i) {
565
                    $this->H[$i][$i - 2] = 0.0;
566
                    if ($i > $m + 2) {
567
                        $this->H[$i][$i - 3] = 0.0;
568
                    }
569
                }
570
                // Double QR step involving rows l:n and columns m:n
571
                for ($k = $m; $k <= $n - 1; ++$k) {
572
                    $notlast = ($k != $n - 1);
573
                    if ($k != $m) {
574
                        $p = $this->H[$k][$k - 1];
575
                        $q = $this->H[$k + 1][$k - 1];
576
                        $r = ($notlast ? $this->H[$k + 2][$k - 1] : 0.0);
577
                        $x = abs($p) + abs($q) + abs($r);
578
                        if ($x != 0.0) {
579
                            $p = $p / $x;
580
                            $q = $q / $x;
581
                            $r = $r / $x;
582
                        }
583
                    }
584
                    if ($x == 0.0) {
585
                        break;
586
                    }
587
                    $s = sqrt($p * $p + $q * $q + $r * $r);
588
                    if ($p < 0) {
589
                        $s = -$s;
590
                    }
591
                    if ($s != 0) {
592
                        if ($k != $m) {
593
                            $this->H[$k][$k - 1] = -$s * $x;
594
                        } elseif ($l != $m) {
595
                            $this->H[$k][$k - 1] = -$this->H[$k][$k - 1];
596
                        }
597
                        $p = $p + $s;
598
                        $x = $p / $s;
599
                        $y = $q / $s;
600
                        $z = $r / $s;
601
                        $q = $q / $p;
602
                        $r = $r / $p;
603
                        // Row modification
604
                        for ($j = $k; $j < $nn; ++$j) {
605
                            $p = $this->H[$k][$j] + $q * $this->H[$k + 1][$j];
606
                            if ($notlast) {
607
                                $p = $p + $r * $this->H[$k + 2][$j];
608
                                $this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z;
609
                            }
610
                            $this->H[$k][$j] = $this->H[$k][$j] - $p * $x;
611
                            $this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y;
612
                        }
613
                        // Column modification
614
                        $iMax = min($n, $k + 3);
615
                        for ($i = 0; $i <= $iMax; ++$i) {
616
                            $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1];
617
                            if ($notlast) {
618
                                $p = $p + $z * $this->H[$i][$k + 2];
619
                                $this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r;
620
                            }
621
                            $this->H[$i][$k] = $this->H[$i][$k] - $p;
622
                            $this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q;
623
                        }
624
                        // Accumulate transformations
625
                        for ($i = $low; $i <= $high; ++$i) {
626
                            $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1];
627
                            if ($notlast) {
628
                                $p = $p + $z * $this->V[$i][$k + 2];
629
                                $this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r;
630
                            }
631
                            $this->V[$i][$k] = $this->V[$i][$k] - $p;
632
                            $this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q;
633
                        }
634
                    }  // ($s != 0)
635
                }  // k loop
636
            }  // check convergence
637
        }  // while ($n >= $low)
638
639
        // Backsubstitute to find vectors of upper triangular form
640
        if ($norm == 0.0) {
641
            return;
642
        }
643
644
        for ($n = $nn - 1; $n >= 0; --$n) {
645
            $p = $this->d[$n];
646
            $q = $this->e[$n];
647
            // Real vector
648
            if ($q == 0) {
649
                $l = $n;
650
                $this->H[$n][$n] = 1.0;
651
                for ($i = $n - 1; $i >= 0; --$i) {
652
                    $w = $this->H[$i][$i] - $p;
653
                    $r = 0.0;
654
                    for ($j = $l; $j <= $n; ++$j) {
655
                        $r = $r + $this->H[$i][$j] * $this->H[$j][$n];
656
                    }
657
                    if ($this->e[$i] < 0.0) {
658
                        $z = $w;
659
                        $s = $r;
660
                    } else {
661
                        $l = $i;
662
                        if ($this->e[$i] == 0.0) {
663
                            if ($w != 0.0) {
664
                                $this->H[$i][$n] = -$r / $w;
665
                            } else {
666
                                $this->H[$i][$n] = -$r / ($eps * $norm);
667
                            }
668
                            // Solve real equations
669
                        } else {
670
                            $x = $this->H[$i][$i + 1];
671
                            $y = $this->H[$i + 1][$i];
672
                            $q = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i];
673
                            $t = ($x * $s - $z * $r) / $q;
674
                            $this->H[$i][$n] = $t;
675
                            if (abs($x) > abs($z)) {
676
                                $this->H[$i + 1][$n] = (-$r - $w * $t) / $x;
677
                            } else {
678
                                $this->H[$i + 1][$n] = (-$s - $y * $t) / $z;
679
                            }
680
                        }
681
                        // Overflow control
682
                        $t = abs($this->H[$i][$n]);
683
                        if (($eps * $t) * $t > 1) {
684
                            for ($j = $i; $j <= $n; ++$j) {
685
                                $this->H[$j][$n] = $this->H[$j][$n] / $t;
686
                            }
687
                        }
688
                    }
689
                }
690
                // Complex vector
691
            } elseif ($q < 0) {
692
                $l = $n - 1;
693
                // Last vector component imaginary so matrix is triangular
694
                if (abs($this->H[$n][$n - 1]) > abs($this->H[$n - 1][$n])) {
695
                    $this->H[$n - 1][$n - 1] = $q / $this->H[$n][$n - 1];
696
                    $this->H[$n - 1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n - 1];
697
                } else {
698
                    $this->cdiv(0.0, -$this->H[$n - 1][$n], $this->H[$n - 1][$n - 1] - $p, $q);
699
                    $this->H[$n - 1][$n - 1] = $this->cdivr;
700
                    $this->H[$n - 1][$n] = $this->cdivi;
701
                }
702
                $this->H[$n][$n - 1] = 0.0;
703
                $this->H[$n][$n] = 1.0;
704
                for ($i = $n - 2; $i >= 0; --$i) {
705
                    // double ra,sa,vr,vi;
706
                    $ra = 0.0;
707
                    $sa = 0.0;
708
                    for ($j = $l; $j <= $n; ++$j) {
709
                        $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1];
710
                        $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n];
711
                    }
712
                    $w = $this->H[$i][$i] - $p;
713
                    if ($this->e[$i] < 0.0) {
714
                        $z = $w;
715
                        $r = $ra;
716
                        $s = $sa;
717
                    } else {
718
                        $l = $i;
719
                        if ($this->e[$i] == 0) {
720
                            $this->cdiv(-$ra, -$sa, $w, $q);
721
                            $this->H[$i][$n - 1] = $this->cdivr;
722
                            $this->H[$i][$n] = $this->cdivi;
723
                        } else {
724
                            // Solve complex equations
725
                            $x = $this->H[$i][$i + 1];
726
                            $y = $this->H[$i + 1][$i];
727
                            $vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q;
728
                            $vi = ($this->d[$i] - $p) * 2.0 * $q;
729
                            if ($vr == 0.0 & $vi == 0.0) {
730
                                $vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z));
731
                            }
732
                            $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi);
733
                            $this->H[$i][$n - 1] = $this->cdivr;
734
                            $this->H[$i][$n] = $this->cdivi;
735
                            if (abs($x) > (abs($z) + abs($q))) {
736
                                $this->H[$i + 1][$n - 1] = (-$ra - $w * $this->H[$i][$n - 1] + $q * $this->H[$i][$n]) / $x;
737
                                $this->H[$i + 1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x;
738
                            } else {
739
                                $this->cdiv(-$r - $y * $this->H[$i][$n - 1], -$s - $y * $this->H[$i][$n], $z, $q);
740
                                $this->H[$i + 1][$n - 1] = $this->cdivr;
741
                                $this->H[$i + 1][$n] = $this->cdivi;
742
                            }
743
                        }
744
                        // Overflow control
745
                        $t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n]));
746
                        if (($eps * $t) * $t > 1) {
747
                            for ($j = $i; $j <= $n; ++$j) {
748
                                $this->H[$j][$n - 1] = $this->H[$j][$n - 1] / $t;
749
                                $this->H[$j][$n] = $this->H[$j][$n] / $t;
750
                            }
751
                        }
752
                    } // end else
753
                } // end for
754
            } // end else for complex case
755
        } // end for
756
757
        // Vectors of isolated roots
758
        for ($i = 0; $i < $nn; ++$i) {
759
            if ($i < $low | $i > $high) {
760
                for ($j = $i; $j < $nn; ++$j) {
761
                    $this->V[$i][$j] = $this->H[$i][$j];
762
                }
763
            }
764
        }
765
766
        // Back transformation to get eigenvectors of original matrix
767
        for ($j = $nn - 1; $j >= $low; --$j) {
768
            for ($i = $low; $i <= $high; ++$i) {
769
                $z = 0.0;
770
                $kMax = min($j, $high);
771
                for ($k = $low; $k <= $kMax; ++$k) {
772
                    $z = $z + $this->V[$i][$k] * $this->H[$k][$j];
773
                }
774
                $this->V[$i][$j] = $z;
775
            }
776
        }
777
    }
778
779
    // end hqr2
780
781
    /**
782
     * Constructor: Check for symmetry, then construct the eigenvalue decomposition.
783
     *
784
     * @param mixed $Arg A Square matrix
785
     */
786
    public function __construct($Arg)
787
    {
788
        $this->A = $Arg->getArray();
789
        $this->n = $Arg->getColumnDimension();
790
791
        $issymmetric = true;
792
        for ($j = 0; ($j < $this->n) & $issymmetric; ++$j) {
793
            for ($i = 0; ($i < $this->n) & $issymmetric; ++$i) {
794
                $issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]);
795
            }
796
        }
797
798
        if ($issymmetric) {
799
            $this->V = $this->A;
800
            // Tridiagonalize.
801
            $this->tred2();
802
            // Diagonalize.
803
            $this->tql2();
804
        } else {
805
            $this->H = $this->A;
806
            $this->ort = [];
807
            // Reduce to Hessenberg form.
808
            $this->orthes();
809
            // Reduce Hessenberg to real Schur form.
810
            $this->hqr2();
811
        }
812
    }
813
814
    /**
815
     * Return the eigenvector matrix.
816
     *
817
     * @return Matrix V
818
     */
819
    public function getV()
820
    {
821
        return new Matrix($this->V, $this->n, $this->n);
822
    }
823
824
    /**
825
     * Return the real parts of the eigenvalues.
826
     *
827
     * @return array real(diag(D))
828
     */
829
    public function getRealEigenvalues()
830
    {
831
        return $this->d;
832
    }
833
834
    /**
835
     * Return the imaginary parts of the eigenvalues.
836
     *
837
     * @return array imag(diag(D))
838
     */
839
    public function getImagEigenvalues()
840
    {
841
        return $this->e;
842
    }
843
844
    /**
845
     * Return the block diagonal eigenvalue matrix.
846
     *
847
     * @return Matrix D
848
     */
849
    public function getD()
850
    {
851
        for ($i = 0; $i < $this->n; ++$i) {
852
            $D[$i] = array_fill(0, $this->n, 0.0);
853
            $D[$i][$i] = $this->d[$i];
854
            if ($this->e[$i] == 0) {
855
                continue;
856
            }
857
            $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1;
858
            $D[$i][$o] = $this->e[$i];
859
        }
860
861
        return new Matrix($D);
862
    }
863
}
864