QRDecomposition.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
  3. use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
  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. *
  17. * @version 1.1
  18. */
  19. class QRDecomposition
  20. {
  21. const MATRIX_RANK_EXCEPTION = 'Can only perform operation on full-rank matrix.';
  22. /**
  23. * Array for internal storage of decomposition.
  24. *
  25. * @var array
  26. */
  27. private $QR = [];
  28. /**
  29. * Row dimension.
  30. *
  31. * @var int
  32. */
  33. private $m;
  34. /**
  35. * Column dimension.
  36. *
  37. * @var int
  38. */
  39. private $n;
  40. /**
  41. * Array for internal storage of diagonal of R.
  42. *
  43. * @var array
  44. */
  45. private $Rdiag = [];
  46. /**
  47. * QR Decomposition computed by Householder reflections.
  48. *
  49. * @param matrix $A Rectangular matrix
  50. */
  51. public function __construct($A)
  52. {
  53. if ($A instanceof Matrix) {
  54. // Initialize.
  55. $this->QR = $A->getArrayCopy();
  56. $this->m = $A->getRowDimension();
  57. $this->n = $A->getColumnDimension();
  58. // Main loop.
  59. for ($k = 0; $k < $this->n; ++$k) {
  60. // Compute 2-norm of k-th column without under/overflow.
  61. $nrm = 0.0;
  62. for ($i = $k; $i < $this->m; ++$i) {
  63. $nrm = hypo($nrm, $this->QR[$i][$k]);
  64. }
  65. if ($nrm != 0.0) {
  66. // Form k-th Householder vector.
  67. if ($this->QR[$k][$k] < 0) {
  68. $nrm = -$nrm;
  69. }
  70. for ($i = $k; $i < $this->m; ++$i) {
  71. $this->QR[$i][$k] /= $nrm;
  72. }
  73. $this->QR[$k][$k] += 1.0;
  74. // Apply transformation to remaining columns.
  75. for ($j = $k + 1; $j < $this->n; ++$j) {
  76. $s = 0.0;
  77. for ($i = $k; $i < $this->m; ++$i) {
  78. $s += $this->QR[$i][$k] * $this->QR[$i][$j];
  79. }
  80. $s = -$s / $this->QR[$k][$k];
  81. for ($i = $k; $i < $this->m; ++$i) {
  82. $this->QR[$i][$j] += $s * $this->QR[$i][$k];
  83. }
  84. }
  85. }
  86. $this->Rdiag[$k] = -$nrm;
  87. }
  88. } else {
  89. throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION);
  90. }
  91. }
  92. // function __construct()
  93. /**
  94. * Is the matrix full rank?
  95. *
  96. * @return bool true if R, and hence A, has full rank, else false
  97. */
  98. public function isFullRank()
  99. {
  100. for ($j = 0; $j < $this->n; ++$j) {
  101. if ($this->Rdiag[$j] == 0) {
  102. return false;
  103. }
  104. }
  105. return true;
  106. }
  107. // function isFullRank()
  108. /**
  109. * Return the Householder vectors.
  110. *
  111. * @return Matrix Lower trapezoidal matrix whose columns define the reflections
  112. */
  113. public function getH()
  114. {
  115. for ($i = 0; $i < $this->m; ++$i) {
  116. for ($j = 0; $j < $this->n; ++$j) {
  117. if ($i >= $j) {
  118. $H[$i][$j] = $this->QR[$i][$j];
  119. } else {
  120. $H[$i][$j] = 0.0;
  121. }
  122. }
  123. }
  124. return new Matrix($H);
  125. }
  126. // function getH()
  127. /**
  128. * Return the upper triangular factor.
  129. *
  130. * @return Matrix upper triangular factor
  131. */
  132. public function getR()
  133. {
  134. for ($i = 0; $i < $this->n; ++$i) {
  135. for ($j = 0; $j < $this->n; ++$j) {
  136. if ($i < $j) {
  137. $R[$i][$j] = $this->QR[$i][$j];
  138. } elseif ($i == $j) {
  139. $R[$i][$j] = $this->Rdiag[$i];
  140. } else {
  141. $R[$i][$j] = 0.0;
  142. }
  143. }
  144. }
  145. return new Matrix($R);
  146. }
  147. // function getR()
  148. /**
  149. * Generate and return the (economy-sized) orthogonal factor.
  150. *
  151. * @return Matrix orthogonal factor
  152. */
  153. public function getQ()
  154. {
  155. for ($k = $this->n - 1; $k >= 0; --$k) {
  156. for ($i = 0; $i < $this->m; ++$i) {
  157. $Q[$i][$k] = 0.0;
  158. }
  159. $Q[$k][$k] = 1.0;
  160. for ($j = $k; $j < $this->n; ++$j) {
  161. if ($this->QR[$k][$k] != 0) {
  162. $s = 0.0;
  163. for ($i = $k; $i < $this->m; ++$i) {
  164. $s += $this->QR[$i][$k] * $Q[$i][$j];
  165. }
  166. $s = -$s / $this->QR[$k][$k];
  167. for ($i = $k; $i < $this->m; ++$i) {
  168. $Q[$i][$j] += $s * $this->QR[$i][$k];
  169. }
  170. }
  171. }
  172. }
  173. return new Matrix($Q);
  174. }
  175. // function getQ()
  176. /**
  177. * Least squares solution of A*X = B.
  178. *
  179. * @param Matrix $B a Matrix with as many rows as A and any number of columns
  180. *
  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 Matrix($X);
  215. return $X->getMatrix(0, $this->n - 1, 0, $nx);
  216. }
  217. throw new CalculationException(self::MATRIX_RANK_EXCEPTION);
  218. }
  219. throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
  220. }
  221. }