<?php

/**

 *	@package JAMA

 *

 *	For an m-by-n matrix A with m >= n, the singular value decomposition is

 *	an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and

 *	an n-by-n orthogonal matrix V so that A = U*S*V'.

 *

 *	The singular values, sigma[$k] = S[$k][$k], are ordered so that

 *	sigma[0] >= sigma[1] >= ... >= sigma[n-1].

 *

 *	The singular value decompostion always exists, so the constructor will

 *	never fail.  The matrix condition number and the effective numerical

 *	rank can be computed from this decomposition.

 *

 *	@author  Paul Meagher

 *	@license PHP v3.0

 *	@version 1.1

 */

class SingularValueDecomposition  {



	/**

	 *	Internal storage of U.

	 *	@var array

	 */

	private $U = array();



	/**

	 *	Internal storage of V.

	 *	@var array

	 */

	private $V = array();



	/**

	 *	Internal storage of singular values.

	 *	@var array

	 */

	private $s = array();



	/**

	 *	Row dimension.

	 *	@var int

	 */

	private $m;



	/**

	 *	Column dimension.

	 *	@var int

	 */

	private $n;





	/**

	 *	Construct the singular value decomposition

	 *

	 *	Derived from LINPACK code.

	 *

	 *	@param $A Rectangular matrix

	 *	@return Structure to access U, S and V.

	 */

	public function __construct($Arg) {



		// Initialize.

		$A = $Arg->getArrayCopy();

		$this->m = $Arg->getRowDimension();

		$this->n = $Arg->getColumnDimension();

		$nu      = min($this->m, $this->n);

		$e       = array();

		$work    = array();

		$wantu   = true;

		$wantv   = true;

		$nct = min($this->m - 1, $this->n);

		$nrt = max(0, min($this->n - 2, $this->m));



		// Reduce A to bidiagonal form, storing the diagonal elements

		// in s and the super-diagonal elements in e.

		for ($k = 0; $k < max($nct,$nrt); ++$k) {



			if ($k < $nct) {

				// Compute the transformation for the k-th column and

				// place the k-th diagonal in s[$k].

				// Compute 2-norm of k-th column without under/overflow.

				$this->s[$k] = 0;

				for ($i = $k; $i < $this->m; ++$i) {

					$this->s[$k] = hypo($this->s[$k], $A[$i][$k]);

				}

				if ($this->s[$k] != 0.0) {

					if ($A[$k][$k] < 0.0) {

						$this->s[$k] = -$this->s[$k];

					}

					for ($i = $k; $i < $this->m; ++$i) {

						$A[$i][$k] /= $this->s[$k];

					}

					$A[$k][$k] += 1.0;

				}

				$this->s[$k] = -$this->s[$k];

			}



			for ($j = $k + 1; $j < $this->n; ++$j) {

				if (($k < $nct) & ($this->s[$k] != 0.0)) {

					// Apply the transformation.

					$t = 0;

					for ($i = $k; $i < $this->m; ++$i) {

						$t += $A[$i][$k] * $A[$i][$j];

					}

					$t = -$t / $A[$k][$k];

					for ($i = $k; $i < $this->m; ++$i) {

						$A[$i][$j] += $t * $A[$i][$k];

					}

					// Place the k-th row of A into e for the

					// subsequent calculation of the row transformation.

					$e[$j] = $A[$k][$j];

				}

			}



			if ($wantu AND ($k < $nct)) {

				// Place the transformation in U for subsequent back

				// multiplication.

				for ($i = $k; $i < $this->m; ++$i) {

					$this->U[$i][$k] = $A[$i][$k];

				}

			}



			if ($k < $nrt) {

				// Compute the k-th row transformation and place the

				// k-th super-diagonal in e[$k].

				// Compute 2-norm without under/overflow.

				$e[$k] = 0;

				for ($i = $k + 1; $i < $this->n; ++$i) {

					$e[$k] = hypo($e[$k], $e[$i]);

				}

				if ($e[$k] != 0.0) {

					if ($e[$k+1] < 0.0) {

						$e[$k] = -$e[$k];

					}

					for ($i = $k + 1; $i < $this->n; ++$i) {

						$e[$i] /= $e[$k];

					}

					$e[$k+1] += 1.0;

				}

				$e[$k] = -$e[$k];

				if (($k+1 < $this->m) AND ($e[$k] != 0.0)) {

					// Apply the transformation.

					for ($i = $k+1; $i < $this->m; ++$i) {

						$work[$i] = 0.0;

					}

					for ($j = $k+1; $j < $this->n; ++$j) {

						for ($i = $k+1; $i < $this->m; ++$i) {

							$work[$i] += $e[$j] * $A[$i][$j];

						}

					}

					for ($j = $k + 1; $j < $this->n; ++$j) {

						$t = -$e[$j] / $e[$k+1];

						for ($i = $k + 1; $i < $this->m; ++$i) {

							$A[$i][$j] += $t * $work[$i];

						}

					}

				}

				if ($wantv) {

					// Place the transformation in V for subsequent

					// back multiplication.

					for ($i = $k + 1; $i < $this->n; ++$i) {

						$this->V[$i][$k] = $e[$i];

					}

				}

			}

		}



		// Set up the final bidiagonal matrix or order p.

		$p = min($this->n, $this->m + 1);

		if ($nct < $this->n) {

			$this->s[$nct] = $A[$nct][$nct];

		}

		if ($this->m < $p) {

			$this->s[$p-1] = 0.0;

		}

		if ($nrt + 1 < $p) {

			$e[$nrt] = $A[$nrt][$p-1];

		}

		$e[$p-1] = 0.0;

		// If required, generate U.

		if ($wantu) {

			for ($j = $nct; $j < $nu; ++$j) {

				for ($i = 0; $i < $this->m; ++$i) {

					$this->U[$i][$j] = 0.0;

				}

				$this->U[$j][$j] = 1.0;

			}

			for ($k = $nct - 1; $k >= 0; --$k) {

				if ($this->s[$k] != 0.0) {

					for ($j = $k + 1; $j < $nu; ++$j) {

						$t = 0;

						for ($i = $k; $i < $this->m; ++$i) {

							$t += $this->U[$i][$k] * $this->U[$i][$j];

						}

						$t = -$t / $this->U[$k][$k];

						for ($i = $k; $i < $this->m; ++$i) {

							$this->U[$i][$j] += $t * $this->U[$i][$k];

						}

					}

					for ($i = $k; $i < $this->m; ++$i ) {

						$this->U[$i][$k] = -$this->U[$i][$k];

					}

					$this->U[$k][$k] = 1.0 + $this->U[$k][$k];

					for ($i = 0; $i < $k - 1; ++$i) {

						$this->U[$i][$k] = 0.0;

					}

				} else {

					for ($i = 0; $i < $this->m; ++$i) {

						$this->U[$i][$k] = 0.0;

					}

					$this->U[$k][$k] = 1.0;

				}

			}

		}



		// If required, generate V.

		if ($wantv) {

			for ($k = $this->n - 1; $k >= 0; --$k) {

				if (($k < $nrt) AND ($e[$k] != 0.0)) {

					for ($j = $k + 1; $j < $nu; ++$j) {

						$t = 0;

						for ($i = $k + 1; $i < $this->n; ++$i) {

							$t += $this->V[$i][$k]* $this->V[$i][$j];

						}

						$t = -$t / $this->V[$k+1][$k];

						for ($i = $k + 1; $i < $this->n; ++$i) {

							$this->V[$i][$j] += $t * $this->V[$i][$k];

						}

					}

				}

				for ($i = 0; $i < $this->n; ++$i) {

					$this->V[$i][$k] = 0.0;

				}

				$this->V[$k][$k] = 1.0;

			}

		}



		// Main iteration loop for the singular values.

		$pp   = $p - 1;

		$iter = 0;

		$eps  = pow(2.0, -52.0);



		while ($p > 0) {

			// Here is where a test for too many iterations would go.

			// This section of the program inspects for negligible

			// elements in the s and e arrays.  On completion the

			// variables kase and k are set as follows:

			// kase = 1  if s(p) and e[k-1] are negligible and k<p

			// kase = 2  if s(k) is negligible and k<p

			// kase = 3  if e[k-1] is negligible, k<p, and

			//           s(k), ..., s(p) are not negligible (qr step).

			// kase = 4  if e(p-1) is negligible (convergence).

			for ($k = $p - 2; $k >= -1; --$k) {

				if ($k == -1) {

					break;

				}

				if (abs($e[$k]) <= $eps * (abs($this->s[$k]) + abs($this->s[$k+1]))) {

					$e[$k] = 0.0;

					break;

				}

			}

			if ($k == $p - 2) {

				$kase = 4;

			} else {

				for ($ks = $p - 1; $ks >= $k; --$ks) {

					if ($ks == $k) {

						break;

					}

					$t = ($ks != $p ? abs($e[$ks]) : 0.) + ($ks != $k + 1 ? abs($e[$ks-1]) : 0.);

					if (abs($this->s[$ks]) <= $eps * $t)  {

						$this->s[$ks] = 0.0;

						break;

					}

				}

				if ($ks == $k) {

					$kase = 3;

				} else if ($ks == $p-1) {

					$kase = 1;

				} else {

					$kase = 2;

					$k = $ks;

				}

			}

			++$k;



			// Perform the task indicated by kase.

			switch ($kase) {

				// Deflate negligible s(p).

				case 1:

						$f = $e[$p-2];

						$e[$p-2] = 0.0;

						for ($j = $p - 2; $j >= $k; --$j) {

							$t  = hypo($this->s[$j],$f);

							$cs = $this->s[$j] / $t;

							$sn = $f / $t;

							$this->s[$j] = $t;

							if ($j != $k) {

								$f = -$sn * $e[$j-1];

								$e[$j-1] = $cs * $e[$j-1];

							}

							if ($wantv) {

								for ($i = 0; $i < $this->n; ++$i) {

									$t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$p-1];

									$this->V[$i][$p-1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$p-1];

									$this->V[$i][$j] = $t;

								}

							}

						}

						break;

				// Split at negligible s(k).

				case 2:

						$f = $e[$k-1];

						$e[$k-1] = 0.0;

						for ($j = $k; $j < $p; ++$j) {

							$t = hypo($this->s[$j], $f);

							$cs = $this->s[$j] / $t;

							$sn = $f / $t;

							$this->s[$j] = $t;

							$f = -$sn * $e[$j];

							$e[$j] = $cs * $e[$j];

							if ($wantu) {

								for ($i = 0; $i < $this->m; ++$i) {

									$t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$k-1];

									$this->U[$i][$k-1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$k-1];

									$this->U[$i][$j] = $t;

								}

							}

						}

						break;

				// Perform one qr step.

				case 3:

						// Calculate the shift.

						$scale = max(max(max(max(

									abs($this->s[$p-1]),abs($this->s[$p-2])),abs($e[$p-2])),

									abs($this->s[$k])), abs($e[$k]));

						$sp   = $this->s[$p-1] / $scale;

						$spm1 = $this->s[$p-2] / $scale;

						$epm1 = $e[$p-2] / $scale;

						$sk   = $this->s[$k] / $scale;

						$ek   = $e[$k] / $scale;

						$b    = (($spm1 + $sp) * ($spm1 - $sp) + $epm1 * $epm1) / 2.0;

						$c    = ($sp * $epm1) * ($sp * $epm1);

						$shift = 0.0;

						if (($b != 0.0) || ($c != 0.0)) {

							$shift = sqrt($b * $b + $c);

							if ($b < 0.0) {

								$shift = -$shift;

							}

							$shift = $c / ($b + $shift);

						}

						$f = ($sk + $sp) * ($sk - $sp) + $shift;

						$g = $sk * $ek;

						// Chase zeros.

						for ($j = $k; $j < $p-1; ++$j) {

							$t  = hypo($f,$g);

							$cs = $f/$t;

							$sn = $g/$t;

							if ($j != $k) {

								$e[$j-1] = $t;

							}

							$f = $cs * $this->s[$j] + $sn * $e[$j];

							$e[$j] = $cs * $e[$j] - $sn * $this->s[$j];

							$g = $sn * $this->s[$j+1];

							$this->s[$j+1] = $cs * $this->s[$j+1];

							if ($wantv) {

								for ($i = 0; $i < $this->n; ++$i) {

									$t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$j+1];

									$this->V[$i][$j+1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$j+1];

									$this->V[$i][$j] = $t;

								}

							}

							$t = hypo($f,$g);

							$cs = $f/$t;

							$sn = $g/$t;

							$this->s[$j] = $t;

							$f = $cs * $e[$j] + $sn * $this->s[$j+1];

							$this->s[$j+1] = -$sn * $e[$j] + $cs * $this->s[$j+1];

							$g = $sn * $e[$j+1];

							$e[$j+1] = $cs * $e[$j+1];

							if ($wantu && ($j < $this->m - 1)) {

								for ($i = 0; $i < $this->m; ++$i) {

									$t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$j+1];

									$this->U[$i][$j+1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$j+1];

									$this->U[$i][$j] = $t;

								}

							}

						}

						$e[$p-2] = $f;

						$iter = $iter + 1;

						break;

				// Convergence.

				case 4:

						// Make the singular values positive.

						if ($this->s[$k] <= 0.0) {

							$this->s[$k] = ($this->s[$k] < 0.0 ? -$this->s[$k] : 0.0);

							if ($wantv) {

								for ($i = 0; $i <= $pp; ++$i) {

									$this->V[$i][$k] = -$this->V[$i][$k];

								}

							}

						}

						// Order the singular values.

						while ($k < $pp) {

							if ($this->s[$k] >= $this->s[$k+1]) {

								break;

							}

							$t = $this->s[$k];

							$this->s[$k] = $this->s[$k+1];

							$this->s[$k+1] = $t;

							if ($wantv AND ($k < $this->n - 1)) {

								for ($i = 0; $i < $this->n; ++$i) {

									$t = $this->V[$i][$k+1];

									$this->V[$i][$k+1] = $this->V[$i][$k];

									$this->V[$i][$k] = $t;

								}

							}

							if ($wantu AND ($k < $this->m-1)) {

								for ($i = 0; $i < $this->m; ++$i) {

									$t = $this->U[$i][$k+1];

									$this->U[$i][$k+1] = $this->U[$i][$k];

									$this->U[$i][$k] = $t;

								}

							}

							++$k;

						}

						$iter = 0;

						--$p;

						break;

			} // end switch

		} // end while



	} // end constructor





	/**

	 *	Return the left singular vectors

	 *

	 *	@access public

	 *	@return U

	 */

	public function getU() {

		return new Matrix($this->U, $this->m, min($this->m + 1, $this->n));

	}





	/**

	 *	Return the right singular vectors

	 *

	 *	@access public

	 *	@return V

	 */

	public function getV() {

		return new Matrix($this->V);

	}





	/**

	 *	Return the one-dimensional array of singular values

	 *

	 *	@access public

	 *	@return diagonal of S.

	 */

	public function getSingularValues() {

		return $this->s;

	}





	/**

	 *	Return the diagonal matrix of singular values

	 *

	 *	@access public

	 *	@return S

	 */

	public function getS() {

		for ($i = 0; $i < $this->n; ++$i) {

			for ($j = 0; $j < $this->n; ++$j) {

				$S[$i][$j] = 0.0;

			}

			$S[$i][$i] = $this->s[$i];

		}

		return new Matrix($S);

	}





	/**

	 *	Two norm

	 *

	 *	@access public

	 *	@return max(S)

	 */

	public function norm2() {

		return $this->s[0];

	}





	/**

	 *	Two norm condition number

	 *

	 *	@access public

	 *	@return max(S)/min(S)

	 */

	public function cond() {

		return $this->s[0] / $this->s[min($this->m, $this->n) - 1];

	}





	/**

	 *	Effective numerical matrix rank

	 *

	 *	@access public

	 *	@return Number of nonnegligible singular values.

	 */

	public function rank() {

		$eps = pow(2.0, -52.0);

		$tol = max($this->m, $this->n) * $this->s[0] * $eps;

		$r = 0;

		for ($i = 0; $i < count($this->s); ++$i) {

			if ($this->s[$i] > $tol) {

				++$r;

			}

		}

		return $r;

	}



}	//	class SingularValueDecomposition

