QRDecomposition.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <?php
  2. /**
  3. * @package JAMA
  4. *
  5. * For an m-by-n matrix A with m >= n, the QR decomposition is an m-by-n
  6. * orthogonal matrix Q and an n-by-n upper triangular matrix R so that
  7. * A = Q*R.
  8. *
  9. * The QR decompostion always exists, even if the matrix does not have
  10. * full rank, so the constructor will never fail. The primary use of the
  11. * QR decomposition is in the least squares solution of nonsquare systems
  12. * of simultaneous linear equations. This will fail if isFullRank()
  13. * returns false.
  14. *
  15. * @author Paul Meagher
  16. * @license PHP v3.0
  17. * @version 1.1
  18. */
  19. class PHPExcel_Shared_JAMA_QRDecomposition {
  20. const MatrixRankException = "Can only perform operation on full-rank matrix.";
  21. /**
  22. * Array for internal storage of decomposition.
  23. * @var array
  24. */
  25. private $QR = array();
  26. /**
  27. * Row dimension.
  28. * @var integer
  29. */
  30. private $m;
  31. /**
  32. * Column dimension.
  33. * @var integer
  34. */
  35. private $n;
  36. /**
  37. * Array for internal storage of diagonal of R.
  38. * @var array
  39. */
  40. private $Rdiag = array();
  41. /**
  42. * QR Decomposition computed by Householder reflections.
  43. *
  44. * @param matrix $A Rectangular matrix
  45. * @return Structure to access R and the Householder vectors and compute Q.
  46. */
  47. public function __construct($A) {
  48. if($A instanceof PHPExcel_Shared_JAMA_Matrix) {
  49. // Initialize.
  50. $this->QR = $A->getArrayCopy();
  51. $this->m = $A->getRowDimension();
  52. $this->n = $A->getColumnDimension();
  53. // Main loop.
  54. for ($k = 0; $k < $this->n; ++$k) {
  55. // Compute 2-norm of k-th column without under/overflow.
  56. $nrm = 0.0;
  57. for ($i = $k; $i < $this->m; ++$i) {
  58. $nrm = hypo($nrm, $this->QR[$i][$k]);
  59. }
  60. if ($nrm != 0.0) {
  61. // Form k-th Householder vector.
  62. if ($this->QR[$k][$k] < 0) {
  63. $nrm = -$nrm;
  64. }
  65. for ($i = $k; $i < $this->m; ++$i) {
  66. $this->QR[$i][$k] /= $nrm;
  67. }
  68. $this->QR[$k][$k] += 1.0;
  69. // Apply transformation to remaining columns.
  70. for ($j = $k+1; $j < $this->n; ++$j) {
  71. $s = 0.0;
  72. for ($i = $k; $i < $this->m; ++$i) {
  73. $s += $this->QR[$i][$k] * $this->QR[$i][$j];
  74. }
  75. $s = -$s/$this->QR[$k][$k];
  76. for ($i = $k; $i < $this->m; ++$i) {
  77. $this->QR[$i][$j] += $s * $this->QR[$i][$k];
  78. }
  79. }
  80. }
  81. $this->Rdiag[$k] = -$nrm;
  82. }
  83. } else {
  84. throw new PHPExcel_Calculation_Exception(PHPExcel_Shared_JAMA_Matrix::ArgumentTypeException);
  85. }
  86. } // function __construct()
  87. /**
  88. * Is the matrix full rank?
  89. *
  90. * @return boolean true if R, and hence A, has full rank, else false.
  91. */
  92. public function isFullRank() {
  93. for ($j = 0; $j < $this->n; ++$j) {
  94. if ($this->Rdiag[$j] == 0) {
  95. return false;
  96. }
  97. }
  98. return true;
  99. } // function isFullRank()
  100. /**
  101. * Return the Householder vectors
  102. *
  103. * @return Matrix Lower trapezoidal matrix whose columns define the reflections
  104. */
  105. public function getH() {
  106. for ($i = 0; $i < $this->m; ++$i) {
  107. for ($j = 0; $j < $this->n; ++$j) {
  108. if ($i >= $j) {
  109. $H[$i][$j] = $this->QR[$i][$j];
  110. } else {
  111. $H[$i][$j] = 0.0;
  112. }
  113. }
  114. }
  115. return new PHPExcel_Shared_JAMA_Matrix($H);
  116. } // function getH()
  117. /**
  118. * Return the upper triangular factor
  119. *
  120. * @return Matrix upper triangular factor
  121. */
  122. public function getR() {
  123. for ($i = 0; $i < $this->n; ++$i) {
  124. for ($j = 0; $j < $this->n; ++$j) {
  125. if ($i < $j) {
  126. $R[$i][$j] = $this->QR[$i][$j];
  127. } elseif ($i == $j) {
  128. $R[$i][$j] = $this->Rdiag[$i];
  129. } else {
  130. $R[$i][$j] = 0.0;
  131. }
  132. }
  133. }
  134. return new PHPExcel_Shared_JAMA_Matrix($R);
  135. } // function getR()
  136. /**
  137. * Generate and return the (economy-sized) orthogonal factor
  138. *
  139. * @return Matrix orthogonal factor
  140. */
  141. public function getQ() {
  142. for ($k = $this->n-1; $k >= 0; --$k) {
  143. for ($i = 0; $i < $this->m; ++$i) {
  144. $Q[$i][$k] = 0.0;
  145. }
  146. $Q[$k][$k] = 1.0;
  147. for ($j = $k; $j < $this->n; ++$j) {
  148. if ($this->QR[$k][$k] != 0) {
  149. $s = 0.0;
  150. for ($i = $k; $i < $this->m; ++$i) {
  151. $s += $this->QR[$i][$k] * $Q[$i][$j];
  152. }
  153. $s = -$s/$this->QR[$k][$k];
  154. for ($i = $k; $i < $this->m; ++$i) {
  155. $Q[$i][$j] += $s * $this->QR[$i][$k];
  156. }
  157. }
  158. }
  159. }
  160. /*
  161. for($i = 0; $i < count($Q); ++$i) {
  162. for($j = 0; $j < count($Q); ++$j) {
  163. if(! isset($Q[$i][$j]) ) {
  164. $Q[$i][$j] = 0;
  165. }
  166. }
  167. }
  168. */
  169. return new PHPExcel_Shared_JAMA_Matrix($Q);
  170. } // function getQ()
  171. /**
  172. * Least squares solution of A*X = B
  173. *
  174. * @param Matrix $B A Matrix with as many rows as A and any number of columns.
  175. * @return Matrix Matrix that minimizes the two norm of Q*R*X-B.
  176. */
  177. public function solve($B) {
  178. if ($B->getRowDimension() == $this->m) {
  179. if ($this->isFullRank()) {
  180. // Copy right hand side
  181. $nx = $B->getColumnDimension();
  182. $X = $B->getArrayCopy();
  183. // Compute Y = transpose(Q)*B
  184. for ($k = 0; $k < $this->n; ++$k) {
  185. for ($j = 0; $j < $nx; ++$j) {
  186. $s = 0.0;
  187. for ($i = $k; $i < $this->m; ++$i) {
  188. $s += $this->QR[$i][$k] * $X[$i][$j];
  189. }
  190. $s = -$s/$this->QR[$k][$k];
  191. for ($i = $k; $i < $this->m; ++$i) {
  192. $X[$i][$j] += $s * $this->QR[$i][$k];
  193. }
  194. }
  195. }
  196. // Solve R*X = Y;
  197. for ($k = $this->n-1; $k >= 0; --$k) {
  198. for ($j = 0; $j < $nx; ++$j) {
  199. $X[$k][$j] /= $this->Rdiag[$k];
  200. }
  201. for ($i = 0; $i < $k; ++$i) {
  202. for ($j = 0; $j < $nx; ++$j) {
  203. $X[$i][$j] -= $X[$k][$j]* $this->QR[$i][$k];
  204. }
  205. }
  206. }
  207. $X = new PHPExcel_Shared_JAMA_Matrix($X);
  208. return ($X->getMatrix(0, $this->n-1, 0, $nx));
  209. } else {
  210. throw new PHPExcel_Calculation_Exception(self::MatrixRankException);
  211. }
  212. } else {
  213. throw new PHPExcel_Calculation_Exception(PHPExcel_Shared_JAMA_Matrix::MatrixDimensionException);
  214. }
  215. } // function solve()
  216. } // PHPExcel_Shared_JAMA_class QRDecomposition