Source for file EigenvalueDecomposition.php
Documentation is available at EigenvalueDecomposition.php
* Class to obtain eigenvalues and eigenvectors of a real matrix.
* If A is symmetric, then A = V*D*V' where the eigenvalue matrix D
* is diagonal and the eigenvector matrix V is orthogonal (i.e.
* A = V.times(D.times(V.transpose())) and V.times(V.transpose())
* equals the identity matrix).
* If A is not symmetric, then the eigenvalue matrix D is block diagonal
* with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues,
* lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The
* columns of V represent the eigenvectors in the sense that A*V = V*D,
* i.e. A.times(V) equals V.times(D). The matrix V may be badly
* conditioned, or even singular, so the validity of the equation
* A = V*D*inverse(V) depends upon V.cond().
* Row and column dimension (square matrix).
* Internal symmetry flag.
* Arrays for internal storage of eigenvalues.
* Array for internal storage of eigenvectors.
* Array for internal storage of nonsymmetric Hessenberg form.
* Working storage for nonsymmetric algorithm.
* Used for complex scalar division.
* Symmetric Householder reduction to tridiagonal form.
private function tred2 () {
// This is derived from the Algol procedures tred2 by
// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
// Fortran subroutine in EISPACK.
$this->d = $this->V[$this->n- 1];
// Householder reduction to tridiagonal form.
for ($i = $this->n- 1; $i > 0; -- $i) {
// Scale to avoid under/overflow.
$this->e[$i] = $this->d[$i_];
for ($j = 0; $j < $i; ++ $j) {
$this->V[$j][$i] = $this->V[$i][$j] = 0.0;
// Generate Householder vector.
for ($k = 0; $k < $i; ++ $k) {
$h += pow($this->d[$k], 2);
$this->e[$i] = $scale * $g;
for ($j = 0; $j < $i; ++ $j) {
// Apply similarity transformation to remaining columns.
for ($j = 0; $j < $i; ++ $j) {
$g = $this->e[$j] + $this->V[$j][$j] * $f;
for ($k = $j+ 1; $k <= $i_; ++ $k) {
$g += $this->V[$k][$j] * $this->d[$k];
$this->e[$k] += $this->V[$k][$j] * $f;
for ($j = 0; $j < $i; ++ $j) {
$f += $this->e[$j] * $this->d[$j];
for ($j= 0; $j < $i; ++ $j) {
$this->e[$j] -= $hh * $this->d[$j];
for ($j = 0; $j < $i; ++ $j) {
for ($k = $j; $k <= $i_; ++ $k) {
$this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]);
$this->d[$j] = $this->V[$i- 1][$j];
// Accumulate transformations.
for ($i = 0; $i < $this->n- 1; ++ $i) {
$this->V[$this->n- 1][$i] = $this->V[$i][$i];
for ($k = 0; $k <= $i; ++ $k) {
$this->d[$k] = $this->V[$k][$i+ 1] / $h;
for ($j = 0; $j <= $i; ++ $j) {
for ($k = 0; $k <= $i; ++ $k) {
$g += $this->V[$k][$i+ 1] * $this->V[$k][$j];
for ($k = 0; $k <= $i; ++ $k) {
$this->V[$k][$j] -= $g * $this->d[$k];
for ($k = 0; $k <= $i; ++ $k) {
$this->V[$k][$i+ 1] = 0.0;
$this->d = $this->V[$this->n- 1];
$this->V[$this->n- 1][$this->n- 1] = 1.0;
* Symmetric tridiagonal QL algorithm.
* This is derived from the Algol procedures tql2, by
* Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
* Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
* Fortran subroutine in EISPACK.
private function tql2() {
for ($i = 1; $i < $this->n; ++ $i) {
$this->e[$i- 1] = $this->e[$i];
$this->e[$this->n- 1] = 0.0;
for ($l = 0; $l < $this->n; ++ $l) {
// Find small subdiagonal element
$tst1 = max($tst1, abs($this->d[$l]) + abs($this->e[$l]));
if (abs($this->e[$m]) <= $eps * $tst1)
// If m == l, $this->d[l] is an eigenvalue,
// Could check iteration count here.
// Compute implicit shift
$p = ($this->d[$l+ 1] - $g) / (2.0 * $this->e[$l]);
$this->d[$l] = $this->e[$l] / ($p + $r);
$this->d[$l+ 1] = $this->e[$l] * ($p + $r);
for ($i = $l + 2; $i < $this->n; ++ $i)
// Implicit QL transformation.
for ($i = $m- 1; $i >= $l; -- $i) {
$r = hypo($p, $this->e[$i]);
$this->e[$i+ 1] = $s * $r;
$p = $c * $this->d[$i] - $s * $g;
$this->d[$i+ 1] = $h + $s * ($c * $g + $s * $this->d[$i]);
// Accumulate transformation.
for ($k = 0; $k < $this->n; ++ $k) {
$this->V[$k][$i+ 1] = $s * $this->V[$k][$i] + $c * $h;
$this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h;
$p = - $s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1;
// Check for convergence.
} while (abs($this->e[$l]) > $eps * $tst1);
$this->d[$l] = $this->d[$l] + $f;
// Sort eigenvalues and corresponding vectors.
for ($i = 0; $i < $this->n - 1; ++ $i) {
for ($j = $i+ 1; $j < $this->n; ++ $j) {
$this->d[$k] = $this->d[$i];
for ($j = 0; $j < $this->n; ++ $j) {
$this->V[$j][$i] = $this->V[$j][$k];
* Nonsymmetric reduction to Hessenberg form.
* This is derived from the Algol procedures orthes and ortran,
* by Martin and Wilkinson, Handbook for Auto. Comp.,
* Vol.ii-Linear Algebra, and the corresponding
* Fortran subroutines in EISPACK.
private function orthes () {
for ($m = $low+ 1; $m <= $high- 1; ++ $m) {
for ($i = $m; $i <= $high; ++ $i) {
$scale = $scale + abs($this->H[$i][$m- 1]);
// Compute Householder transformation.
for ($i = $high; $i >= $m; -- $i) {
$this->ort[$i] = $this->H[$i][$m- 1] / $scale;
$h += $this->ort[$i] * $this->ort[$i];
if ($this->ort[$m] > 0) {
$h -= $this->ort[$m] * $g;
// Apply Householder similarity transformation
// H = (I -u * u' / h) * H * (I -u * u') / h)
for ($j = $m; $j < $this->n; ++ $j) {
for ($i = $high; $i >= $m; -- $i) {
$f += $this->ort[$i] * $this->H[$i][$j];
for ($i = $m; $i <= $high; ++ $i) {
$this->H[$i][$j] -= $f * $this->ort[$i];
for ($i = 0; $i <= $high; ++ $i) {
for ($j = $high; $j >= $m; -- $j) {
$f += $this->ort[$j] * $this->H[$i][$j];
for ($j = $m; $j <= $high; ++ $j) {
$this->H[$i][$j] -= $f * $this->ort[$j];
$this->ort[$m] = $scale * $this->ort[$m];
$this->H[$m][$m- 1] = $scale * $g;
// Accumulate transformations (Algol's ortran).
for ($i = 0; $i < $this->n; ++ $i) {
for ($j = 0; $j < $this->n; ++ $j) {
$this->V[$i][$j] = ($i == $j ? 1.0 : 0.0);
for ($m = $high- 1; $m >= $low+ 1; -- $m) {
if ($this->H[$m][$m- 1] != 0.0) {
for ($i = $m+ 1; $i <= $high; ++ $i) {
$this->ort[$i] = $this->H[$i][$m- 1];
for ($j = $m; $j <= $high; ++ $j) {
for ($i = $m; $i <= $high; ++ $i) {
$g += $this->ort[$i] * $this->V[$i][$j];
// Double division avoids possible underflow
$g = ($g / $this->ort[$m]) / $this->H[$m][$m- 1];
for ($i = $m; $i <= $high; ++ $i) {
$this->V[$i][$j] += $g * $this->ort[$i];
* Performs complex division.
private function cdiv($xr, $xi, $yr, $yi) {
$this->cdivr = ($xr + $r * $xi) / $d;
$this->cdivi = ($xi - $r * $xr) / $d;
$this->cdivr = ($r * $xr + $xi) / $d;
$this->cdivi = ($r * $xi - $xr) / $d;
* Nonsymmetric reduction from Hessenberg to real Schur form.
* Code is derived from the Algol procedure hqr2,
* by Martin and Wilkinson, Handbook for Auto. Comp.,
* Vol.ii-Linear Algebra, and the corresponding
* Fortran subroutine in EISPACK.
private function hqr2 () {
$p = $q = $r = $s = $z = 0;
// Store roots isolated by balanc and compute matrix norm
for ($i = 0; $i < $nn; ++ $i) {
if (($i < $low) OR ($i > $high)) {
$this->d[$i] = $this->H[$i][$i];
for ($j = max($i- 1, 0); $j < $nn; ++ $j) {
$norm = $norm + abs($this->H[$i][$j]);
// Outer loop over eigenvalue index
// Look for single small sub-diagonal element
$s = abs($this->H[$l- 1][$l- 1]) + abs($this->H[$l][$l]);
if (abs($this->H[$l][$l- 1]) < $eps * $s) {
$this->H[$n][$n] = $this->H[$n][$n] + $exshift;
$this->d[$n] = $this->H[$n][$n];
$w = $this->H[$n][$n- 1] * $this->H[$n- 1][$n];
$p = ($this->H[$n- 1][$n- 1] - $this->H[$n][$n]) / 2.0;
$this->H[$n][$n] = $this->H[$n][$n] + $exshift;
$this->H[$n- 1][$n- 1] = $this->H[$n- 1][$n- 1] + $exshift;
$this->d[$n- 1] = $x + $z;
$this->d[$n] = $this->d[$n- 1];
$this->d[$n] = $x - $w / $z;
$r = sqrt($p * $p + $q * $q);
for ($j = $n- 1; $j < $nn; ++ $j) {
$this->H[$n- 1][$j] = $q * $z + $p * $this->H[$n][$j];
$this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z;
for ($i = 0; $i <= n; ++ $i) {
$this->H[$i][$n- 1] = $q * $z + $p * $this->H[$i][$n];
$this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z;
// Accumulate transformations
for ($i = $low; $i <= $high; ++ $i) {
$this->V[$i][$n- 1] = $q * $z + $p * $this->V[$i][$n];
$this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z;
$this->d[$n- 1] = $x + $p;
$y = $this->H[$n- 1][$n- 1];
$w = $this->H[$n][$n- 1] * $this->H[$n- 1][$n];
// Wilkinson's original ad hoc shift
for ($i = $low; $i <= $n; ++ $i) {
$s = abs($this->H[$n][$n- 1]) + abs($this->H[$n- 1][$n- 2]);
// MATLAB's new ad hoc shift
$s = $x - $w / (($y - $x) / 2.0 + $s);
for ($i = $low; $i <= $n; ++ $i) {
// Could check iteration count here.
// Look for two consecutive small sub-diagonal elements
$p = ($r * $s - $w) / $this->H[$m+ 1][$m] + $this->H[$m][$m+ 1];
$q = $this->H[$m+ 1][$m+ 1] - $z - $r - $s;
$r = $this->H[$m+ 2][$m+ 1];
if (abs($this->H[$m][$m- 1]) * (abs($q) + abs($r)) <
$eps * (abs($p) * (abs($this->H[$m- 1][$m- 1]) + abs($z) + abs($this->H[$m+ 1][$m+ 1])))) {
for ($i = $m + 2; $i <= $n; ++ $i) {
$this->H[$i][$i- 2] = 0.0;
$this->H[$i][$i- 3] = 0.0;
// Double QR step involving rows l:n and columns m:n
for ($k = $m; $k <= $n- 1; ++ $k) {
$q = $this->H[$k+ 1][$k- 1];
$r = ($notlast ? $this->H[$k+ 2][$k- 1] : 0.0);
$s = sqrt($p * $p + $q * $q + $r * $r);
$this->H[$k][$k- 1] = - $s * $x;
$this->H[$k][$k- 1] = - $this->H[$k][$k- 1];
for ($j = $k; $j < $nn; ++ $j) {
$p = $this->H[$k][$j] + $q * $this->H[$k+ 1][$j];
$p = $p + $r * $this->H[$k+ 2][$j];
$this->H[$k+ 2][$j] = $this->H[$k+ 2][$j] - $p * $z;
$this->H[$k][$j] = $this->H[$k][$j] - $p * $x;
$this->H[$k+ 1][$j] = $this->H[$k+ 1][$j] - $p * $y;
for ($i = 0; $i <= min($n, $k+ 3); ++ $i) {
$p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k+ 1];
$p = $p + $z * $this->H[$i][$k+ 2];
$this->H[$i][$k+ 2] = $this->H[$i][$k+ 2] - $p * $r;
$this->H[$i][$k] = $this->H[$i][$k] - $p;
$this->H[$i][$k+ 1] = $this->H[$i][$k+ 1] - $p * $q;
// Accumulate transformations
for ($i = $low; $i <= $high; ++ $i) {
$p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k+ 1];
$p = $p + $z * $this->V[$i][$k+ 2];
$this->V[$i][$k+ 2] = $this->V[$i][$k+ 2] - $p * $r;
$this->V[$i][$k] = $this->V[$i][$k] - $p;
$this->V[$i][$k+ 1] = $this->V[$i][$k+ 1] - $p * $q;
// Backsubstitute to find vectors of upper triangular form
for ($n = $nn- 1; $n >= 0; -- $n) {
for ($i = $n- 1; $i >= 0; -- $i) {
$w = $this->H[$i][$i] - $p;
for ($j = $l; $j <= $n; ++ $j) {
$r = $r + $this->H[$i][$j] * $this->H[$j][$n];
if ($this->e[$i] < 0.0) {
if ($this->e[$i] == 0.0) {
$this->H[$i][$n] = - $r / $w;
$this->H[$i][$n] = - $r / ($eps * $norm);
$q = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i];
$t = ($x * $s - $z * $r) / $q;
$this->H[$i+ 1][$n] = (- $r - $w * $t) / $x;
$this->H[$i+ 1][$n] = (- $s - $y * $t) / $z;
$t = abs($this->H[$i][$n]);
if (($eps * $t) * $t > 1) {
for ($j = $i; $j <= $n; ++ $j) {
$this->H[$j][$n] = $this->H[$j][$n] / $t;
// Last vector component imaginary so matrix is triangular
if (abs($this->H[$n][$n- 1]) > abs($this->H[$n- 1][$n])) {
$this->H[$n- 1][$n- 1] = $q / $this->H[$n][$n- 1];
$this->H[$n- 1][$n] = - ($this->H[$n][$n] - $p) / $this->H[$n][$n- 1];
$this->cdiv(0.0, - $this->H[$n- 1][$n], $this->H[$n- 1][$n- 1] - $p, $q);
$this->H[$n- 1][$n- 1] = $this->cdivr;
$this->H[$n- 1][$n] = $this->cdivi;
$this->H[$n][$n- 1] = 0.0;
for ($i = $n- 2; $i >= 0; -- $i) {
for ($j = $l; $j <= $n; ++ $j) {
$ra = $ra + $this->H[$i][$j] * $this->H[$j][$n- 1];
$sa = $sa + $this->H[$i][$j] * $this->H[$j][$n];
$w = $this->H[$i][$i] - $p;
if ($this->e[$i] < 0.0) {
$this->cdiv(- $ra, - $sa, $w, $q);
$this->H[$i][$n- 1] = $this->cdivr;
$this->H[$i][$n] = $this->cdivi;
// Solve complex equations
$vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q;
$vi = ($this->d[$i] - $p) * 2.0 * $q;
if ($vr == 0.0 & $vi == 0.0) {
$this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi);
$this->H[$i][$n- 1] = $this->cdivr;
$this->H[$i][$n] = $this->cdivi;
$this->H[$i+ 1][$n- 1] = (- $ra - $w * $this->H[$i][$n- 1] + $q * $this->H[$i][$n]) / $x;
$this->H[$i+ 1][$n] = (- $sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n- 1]) / $x;
$this->cdiv(- $r - $y * $this->H[$i][$n- 1], - $s - $y * $this->H[$i][$n], $z, $q);
$this->H[$i+ 1][$n- 1] = $this->cdivr;
$this->H[$i+ 1][$n] = $this->cdivi;
$t = max(abs($this->H[$i][$n- 1]),abs($this->H[$i][$n]));
if (($eps * $t) * $t > 1) {
for ($j = $i; $j <= $n; ++ $j) {
$this->H[$j][$n- 1] = $this->H[$j][$n- 1] / $t;
$this->H[$j][$n] = $this->H[$j][$n] / $t;
} // end else for complex case
// Vectors of isolated roots
for ($i = 0; $i < $nn; ++ $i) {
if ($i < $low | $i > $high) {
for ($j = $i; $j < $nn; ++ $j) {
$this->V[$i][$j] = $this->H[$i][$j];
// Back transformation to get eigenvectors of original matrix
for ($j = $nn- 1; $j >= $low; -- $j) {
for ($i = $low; $i <= $high; ++ $i) {
for ($k = $low; $k <= min($j,$high); ++ $k) {
$z = $z + $this->V[$i][$k] * $this->H[$k][$j];
* Constructor: Check for symmetry, then construct the eigenvalue decomposition
* @return Structure to access D and V.
$this->A = $Arg->getArray();
$this->n = $Arg->getColumnDimension();
for ($j = 0; ($j < $this->n) & $issymmetric; ++ $j) {
for ($i = 0; ($i < $this->n) & $issymmetric; ++ $i) {
$issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]);
// Reduce to Hessenberg form.
// Reduce Hessenberg to real Schur form.
* Return the eigenvector matrix
return new Matrix($this->V, $this->n, $this->n);
* Return the real parts of the eigenvalues
* Return the imaginary parts of the eigenvalues
* Return the block diagonal eigenvalue matrix
for ($i = 0; $i < $this->n; ++ $i) {
$D[$i][$i] = $this->d[$i];
$o = ($this->e[$i] > 0) ? $i + 1 : $i - 1;
$D[$i][$o] = $this->e[$i];
} // class EigenvalueDecomposition
|