/** * Symmetric tri-diagonal 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. * * @access private */ private function tql2() { for ($i = 1; $i < $this->n; ++$i) { $this->e[$i - 1] = $this->e[$i]; } $this->e[$this->n - 1] = 0.0; $f = 0.0; $tst1 = 0.0; $eps = pow(2.0, -52.0); for ($l = 0; $l < $this->n; ++$l) { // Find small sub-diagonal element $tst1 = max($tst1, abs($this->d[$l]) + abs($this->e[$l])); $m = $l; while ($m < $this->n) { if (abs($this->e[$m]) <= $eps * $tst1) { break; } ++$m; } // If m == l, $this->d[l] is an eigenvalue, // otherwise, iterate. if ($m > $l) { $iterator = 0; do { // Could check iteration count here. $iterator += 1; // Compute implicit shift $g = $this->d[$l]; $p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]); $r = Matrix::hypo($p, 1.0); if ($p < 0) { $r *= -1; } $this->d[$l] = $this->e[$l] / ($p + $r); $this->d[$l + 1] = $this->e[$l] * ($p + $r); $dl1 = $this->d[$l + 1]; $h = $g - $this->d[$l]; for ($i = $l + 2; $i < $this->n; ++$i) { $this->d[$i] -= $h; } $f += $h; // Implicit QL transformation. $p = $this->d[$m]; $c = 1.0; $c2 = $c3 = $c; $el1 = $this->e[$l + 1]; $s = $s2 = 0.0; for ($i = $m - 1; $i >= $l; --$i) { $c3 = $c2; $c2 = $c; $s2 = $s; $g = $c * $this->e[$i]; $h = $c * $p; $r = Matrix::hypo($p, $this->e[$i]); $this->e[$i + 1] = $s * $r; $s = $this->e[$i] / $r; $c = $p / $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) { $h = $this->V[$k][$i + 1]; $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; $this->e[$l] = $s * $p; $this->d[$l] = $c * $p; // Check for convergence. } while (abs($this->e[$l]) > $eps * $tst1); } $this->d[$l] = $this->d[$l] + $f; $this->e[$l] = 0.0; } // Sort eigenvalues and corresponding vectors. for ($i = 0; $i < $this->n - 1; ++$i) { $k = $i; $p = $this->d[$i]; for ($j = $i + 1; $j < $this->n; ++$j) { if ($this->d[$j] < $p) { $k = $j; $p = $this->d[$j]; } } if ($k != $i) { $this->d[$k] = $this->d[$i]; $this->d[$i] = $p; for ($j = 0; $j < $this->n; ++$j) { $p = $this->V[$j][$i]; $this->V[$j][$i] = $this->V[$j][$k]; $this->V[$j][$k] = $p; } } } }
/** * @param $Matrix Matrix */ public function __construct($Matrix) { $this->m = $Matrix->getRowDimension(); $this->n = $Matrix->getColumnDimension(); $this->U = $Matrix->matrix; $this->V = $Matrix->getMatrix($this->n)->matrix; $eps = 2.22045E-16; // Decompose Phase // Householder reduction to bi-diagonal form. $g = $scale = $aNorm = 0.0; $l = 0; $rv1 = []; for ($i = 0; $i < $this->n; $i++) { $l = $i + 2; $rv1[$i] = $scale * $g; $g = $s = $scale = 0.0; if ($i < $this->m) { for ($k = $i; $k < $this->m; $k++) { $scale += abs($this->U[$k][$i]); } if ($scale != 0.0) { for ($k = $i; $k < $this->m; $k++) { $this->U[$k][$i] /= $scale; $s += $this->U[$k][$i] * $this->U[$k][$i]; } $f = $this->U[$i][$i]; $g = -Matrix::sameSign(sqrt($s), $f); $h = $f * $g - $s; $this->U[$i][$i] = $f - $g; for ($j = $l - 1; $j < $this->n; $j++) { for ($s = 0.0, $k = $i; $k < $this->m; $k++) { $s += $this->U[$k][$i] * $this->U[$k][$j]; } $f = $s / $h; for ($k = $i; $k < $this->m; $k++) { $this->U[$k][$j] += $f * $this->U[$k][$i]; } } for ($k = $i; $k < $this->m; $k++) { $this->U[$k][$i] *= $scale; } } } $this->W[$i] = $scale * $g; $g = $s = $scale = 0.0; if ($i + 1 <= $this->m && $i + 1 != $this->n) { for ($k = $l - 1; $k < $this->n; $k++) { $scale += abs($this->U[$i][$k]); } if ($scale != 0.0) { for ($k = $l - 1; $k < $this->n; $k++) { $this->U[$i][$k] /= $scale; $s += $this->U[$i][$k] * $this->U[$i][$k]; } $f = $this->U[$i][$l - 1]; $g = -Matrix::sameSign(sqrt($s), $f); $h = $f * $g - $s; $this->U[$i][$l - 1] = $f - $g; for ($k = $l - 1; $k < $this->n; $k++) { $rv1[$k] = $this->U[$i][$k] / $h; } for ($j = $l - 1; $j < $this->m; $j++) { for ($s = 0.0, $k = $l - 1; $k < $this->n; $k++) { $s += $this->U[$j][$k] * $this->U[$i][$k]; } for ($k = $l - 1; $k < $this->n; $k++) { $this->U[$j][$k] += $s * $rv1[$k]; } } for ($k = $l - 1; $k < $this->n; $k++) { $this->U[$i][$k] *= $scale; } } } $aNorm = max($aNorm, abs($this->W[$i]) + abs($rv1[$i])); } // Accumulation of right-hand transformations. for ($i = $this->n - 1; $i >= 0; $i--) { if ($i < $this->n - 1) { if ($g != 0.0) { for ($j = $l; $j < $this->n; $j++) { // Double division to avoid possible underflow. $this->V[$j][$i] = $this->U[$i][$j] / $this->U[$i][$l] / $g; } for ($j = $l; $j < $this->n; $j++) { for ($s = 0.0, $k = $l; $k < $this->n; $k++) { $s += $this->U[$i][$k] * $this->V[$k][$j]; } for ($k = $l; $k < $this->n; $k++) { $this->V[$k][$j] += $s * $this->V[$k][$i]; } } } for ($j = $l; $j < $this->n; $j++) { $this->V[$i][$j] = $this->V[$j][$i] = 0.0; } } $this->V[$i][$i] = 1.0; $g = $rv1[$i]; $l = $i; } // Accumulation of left-hand transformations. for ($i = min($this->m, $this->n) - 1; $i >= 0; $i--) { $l = $i + 1; $g = $this->W[$i]; for ($j = $l; $j < $this->n; $j++) { $this->U[$i][$j] = 0.0; } if ($g != 0.0) { $g = 1.0 / $g; for ($j = $l; $j < $this->n; $j++) { for ($s = 0.0, $k = $l; $k < $this->m; $k++) { $s += $this->U[$k][$i] * $this->U[$k][$j]; } $f = $s / $this->U[$i][$i] * $g; for ($k = $i; $k < $this->m; $k++) { $this->U[$k][$j] += $f * $this->U[$k][$i]; } } for ($j = $i; $j < $this->m; $j++) { $this->U[$j][$i] *= $g; } } else { for ($j = $i; $j < $this->m; $j++) { $this->U[$j][$i] = 0.0; } } ++$this->U[$i][$i]; } // Diagonalization of the bi-diagonal form // Loop over singular values, and over allowed iterations. $nm = 0; for ($k = $this->n - 1; $k >= 0; $k--) { for ($its = 0; $its < 30; $its++) { $flag = true; for ($l = $k; $l >= 0; $l--) { $nm = $l - 1; if ($l == 0 || abs($rv1[$l]) <= $eps * $aNorm) { $flag = false; break; } if (abs($this->W[$nm]) <= $eps * $aNorm) { break; } } if ($flag) { $c = 0.0; // Cancellation of rv1[l], if l > 0. $s = 1.0; for ($i = $l; $i < $k + 1; $i++) { $f = $s * $rv1[$i]; $rv1[$i] = $c * $rv1[$i]; if (abs($f) <= $eps * $aNorm) { break; } $g = $this->W[$i]; $h = Matrix::hypo($f, $g); $this->W[$i] = $h; $h = 1.0 / $h; $c = $g * $h; $s = -$f * $h; for ($j = 0; $j < $this->m; $j++) { $y = $this->U[$j][$nm]; $z = $this->U[$j][$i]; $this->U[$j][$nm] = $y * $c + $z * $s; $this->U[$j][$i] = $z * $c - $y * $s; } } } $z = $this->W[$k]; if ($l == $k) { if ($z < 0.0) { $this->W[$k] = -$z; // Singular value is made nonnegative. for ($j = 0; $j < $this->n; $j++) { $this->V[$j][$k] = -$this->V[$j][$k]; } } break; } if ($its == 29) { print "no convergence in 30 svd iterations"; } $x = $this->W[$l]; // Shift from bottom 2-by-2 minor. $nm = $k - 1; $y = $this->W[$nm]; $g = $rv1[$nm]; $h = $rv1[$k]; $f = (($y - $z) * ($y + $z) + ($g - $h) * ($g + $h)) / (2.0 * $h * $y); $g = Matrix::hypo($f, 1.0); $f = (($x - $z) * ($x + $z) + $h * ($y / ($f + Matrix::sameSign($g, $f)) - $h)) / $x; $c = $s = 1.0; for ($j = $l; $j <= $nm; $j++) { $i = $j + 1; $g = $rv1[$i]; $y = $this->W[$i]; $h = $s * $g; $g = $c * $g; $z = Matrix::hypo($f, $h); $rv1[$j] = $z; $c = $f / $z; $s = $h / $z; $f = $x * $c + $g * $s; $g = $g * $c - $x * $s; $h = $y * $s; $y *= $c; for ($jj = 0; $jj < $this->n; $jj++) { $x = $this->V[$jj][$j]; $z = $this->V[$jj][$i]; $this->V[$jj][$j] = $x * $c + $z * $s; $this->V[$jj][$i] = $z * $c - $x * $s; } $z = Matrix::hypo($f, $h); $this->W[$j] = $z; // Rotation can be arbitrary if z = 0. if ($z) { $z = 1.0 / $z; $c = $f * $z; $s = $h * $z; } $f = $c * $g + $s * $y; $x = $c * $y - $s * $g; for ($jj = 0; $jj < $this->m; $jj++) { $y = $this->U[$jj][$j]; $z = $this->U[$jj][$i]; $this->U[$jj][$j] = $y * $c + $z * $s; $this->U[$jj][$i] = $z * $c - $y * $s; } } $rv1[$l] = 0.0; $rv1[$k] = $f; $this->W[$k] = $x; } } // Reorder Phase // Sort. The method is Shell's sort. // (The work is negligible as compared to that already done in decompose phase.) $inc = 1; do { $inc *= 3; $inc++; } while ($inc <= $this->n); $su = []; $sv = []; do { $inc /= 3; for ($i = $inc; $i < $this->n; $i++) { $sw = $this->W[$i]; for ($k = 0; $k < $this->m; $k++) { $su[$k] = $this->U[$k][$i]; } for ($k = 0; $k < $this->n; $k++) { $sv[$k] = $this->V[$k][$i]; } $j = $i; while ($this->W[$j - $inc] < $sw) { $this->W[$j] = $this->W[$j - $inc]; for ($k = 0; $k < $this->m; $k++) { $this->U[$k][$j] = $this->U[$k][$j - $inc]; } for ($k = 0; $k < $this->n; $k++) { $this->V[$k][$j] = $this->V[$k][$j - $inc]; } $j -= $inc; if ($j < $inc) { break; } } $this->W[$j] = $sw; for ($k = 0; $k < $this->m; $k++) { $this->U[$k][$j] = $su[$k]; } for ($k = 0; $k < $this->n; $k++) { $this->V[$k][$j] = $sv[$k]; } } } while ($inc > 1); for ($k = 0; $k < $this->n; $k++) { $s = 0; for ($i = 0; $i < $this->m; $i++) { if ($this->U[$i][$k] < 0.0) { $s++; } } for ($j = 0; $j < $this->n; $j++) { if ($this->V[$j][$k] < 0.0) { $s++; } } if ($s > ($this->m + $this->n) / 2) { for ($i = 0; $i < $this->m; $i++) { $this->U[$i][$k] = -$this->U[$i][$k]; } for ($j = 0; $j < $this->n; $j++) { $this->V[$j][$k] = -$this->V[$j][$k]; } } } // calculate the rank $rank = 0; for ($i = 0; $i < count($this->W); $i++) { if (round($this->W[$i], 4) > 0) { $rank += 1; } } // Low-Rank Approximation $q = 0.9; $k = 0; $fRobA = 0; $fRobAk = 0; for ($i = 0; $i < $rank; $i++) { $fRobA += $this->W[$i]; } do { for ($i = 0; $i <= $k; $i++) { $fRobAk += $this->W[$i]; } $clt = $fRobAk / $fRobA; $k++; } while ($clt < $q); // prepare S matrix as n*n daigonal matrix of singular values for ($i = 0; $i < $this->n; $i++) { // for ($j = 0; $j < $n; $j++) { // $this->s[$i][$j] = 0; $this->s[$i] = $this->W[$i]; // } } $this->K = $k; $this->Rank = $rank; }