QRDecomposition.php 7.7 KB

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