SingularValueDecomposition.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. <?php
  2. /**
  3. * 重庆赤晓店信息科技有限公司
  4. * https://www.chixiaodian.com
  5. * Copyright (c) 2023 赤店商城 All rights reserved.
  6. */
  7. class SingularValueDecomposition
  8. {
  9. /**
  10. * Internal storage of U.
  11. * @var array
  12. */
  13. private $U = array();
  14. /**
  15. * Internal storage of V.
  16. * @var array
  17. */
  18. private $V = array();
  19. /**
  20. * Internal storage of singular values.
  21. * @var array
  22. */
  23. private $s = array();
  24. /**
  25. * Row dimension.
  26. * @var int
  27. */
  28. private $m;
  29. /**
  30. * Column dimension.
  31. * @var int
  32. */
  33. private $n;
  34. /**
  35. * Construct the singular value decomposition
  36. *
  37. * Derived from LINPACK code.
  38. *
  39. * @param $A Rectangular matrix
  40. * @return Structure to access U, S and V.
  41. */
  42. public function __construct($Arg)
  43. {
  44. // Initialize.
  45. $A = $Arg->getArrayCopy();
  46. $this->m = $Arg->getRowDimension();
  47. $this->n = $Arg->getColumnDimension();
  48. $nu = min($this->m, $this->n);
  49. $e = array();
  50. $work = array();
  51. $wantu = true;
  52. $wantv = true;
  53. $nct = min($this->m - 1, $this->n);
  54. $nrt = max(0, min($this->n - 2, $this->m));
  55. // Reduce A to bidiagonal form, storing the diagonal elements
  56. // in s and the super-diagonal elements in e.
  57. for ($k = 0; $k < max($nct, $nrt); ++$k) {
  58. if ($k < $nct) {
  59. // Compute the transformation for the k-th column and
  60. // place the k-th diagonal in s[$k].
  61. // Compute 2-norm of k-th column without under/overflow.
  62. $this->s[$k] = 0;
  63. for ($i = $k; $i < $this->m; ++$i) {
  64. $this->s[$k] = hypo($this->s[$k], $A[$i][$k]);
  65. }
  66. if ($this->s[$k] != 0.0) {
  67. if ($A[$k][$k] < 0.0) {
  68. $this->s[$k] = -$this->s[$k];
  69. }
  70. for ($i = $k; $i < $this->m; ++$i) {
  71. $A[$i][$k] /= $this->s[$k];
  72. }
  73. $A[$k][$k] += 1.0;
  74. }
  75. $this->s[$k] = -$this->s[$k];
  76. }
  77. for ($j = $k + 1; $j < $this->n; ++$j) {
  78. if (($k < $nct) & ($this->s[$k] != 0.0)) {
  79. // Apply the transformation.
  80. $t = 0;
  81. for ($i = $k; $i < $this->m; ++$i) {
  82. $t += $A[$i][$k] * $A[$i][$j];
  83. }
  84. $t = -$t / $A[$k][$k];
  85. for ($i = $k; $i < $this->m; ++$i) {
  86. $A[$i][$j] += $t * $A[$i][$k];
  87. }
  88. // Place the k-th row of A into e for the
  89. // subsequent calculation of the row transformation.
  90. $e[$j] = $A[$k][$j];
  91. }
  92. }
  93. if ($wantu and ($k < $nct)) {
  94. // Place the transformation in U for subsequent back
  95. // multiplication.
  96. for ($i = $k; $i < $this->m; ++$i) {
  97. $this->U[$i][$k] = $A[$i][$k];
  98. }
  99. }
  100. if ($k < $nrt) {
  101. // Compute the k-th row transformation and place the
  102. // k-th super-diagonal in e[$k].
  103. // Compute 2-norm without under/overflow.
  104. $e[$k] = 0;
  105. for ($i = $k + 1; $i < $this->n; ++$i) {
  106. $e[$k] = hypo($e[$k], $e[$i]);
  107. }
  108. if ($e[$k] != 0.0) {
  109. if ($e[$k+1] < 0.0) {
  110. $e[$k] = -$e[$k];
  111. }
  112. for ($i = $k + 1; $i < $this->n; ++$i) {
  113. $e[$i] /= $e[$k];
  114. }
  115. $e[$k+1] += 1.0;
  116. }
  117. $e[$k] = -$e[$k];
  118. if (($k+1 < $this->m) and ($e[$k] != 0.0)) {
  119. // Apply the transformation.
  120. for ($i = $k+1; $i < $this->m; ++$i) {
  121. $work[$i] = 0.0;
  122. }
  123. for ($j = $k+1; $j < $this->n; ++$j) {
  124. for ($i = $k+1; $i < $this->m; ++$i) {
  125. $work[$i] += $e[$j] * $A[$i][$j];
  126. }
  127. }
  128. for ($j = $k + 1; $j < $this->n; ++$j) {
  129. $t = -$e[$j] / $e[$k+1];
  130. for ($i = $k + 1; $i < $this->m; ++$i) {
  131. $A[$i][$j] += $t * $work[$i];
  132. }
  133. }
  134. }
  135. if ($wantv) {
  136. // Place the transformation in V for subsequent
  137. // back multiplication.
  138. for ($i = $k + 1; $i < $this->n; ++$i) {
  139. $this->V[$i][$k] = $e[$i];
  140. }
  141. }
  142. }
  143. }
  144. // Set up the final bidiagonal matrix or order p.
  145. $p = min($this->n, $this->m + 1);
  146. if ($nct < $this->n) {
  147. $this->s[$nct] = $A[$nct][$nct];
  148. }
  149. if ($this->m < $p) {
  150. $this->s[$p-1] = 0.0;
  151. }
  152. if ($nrt + 1 < $p) {
  153. $e[$nrt] = $A[$nrt][$p-1];
  154. }
  155. $e[$p-1] = 0.0;
  156. // If required, generate U.
  157. if ($wantu) {
  158. for ($j = $nct; $j < $nu; ++$j) {
  159. for ($i = 0; $i < $this->m; ++$i) {
  160. $this->U[$i][$j] = 0.0;
  161. }
  162. $this->U[$j][$j] = 1.0;
  163. }
  164. for ($k = $nct - 1; $k >= 0; --$k) {
  165. if ($this->s[$k] != 0.0) {
  166. for ($j = $k + 1; $j < $nu; ++$j) {
  167. $t = 0;
  168. for ($i = $k; $i < $this->m; ++$i) {
  169. $t += $this->U[$i][$k] * $this->U[$i][$j];
  170. }
  171. $t = -$t / $this->U[$k][$k];
  172. for ($i = $k; $i < $this->m; ++$i) {
  173. $this->U[$i][$j] += $t * $this->U[$i][$k];
  174. }
  175. }
  176. for ($i = $k; $i < $this->m; ++$i) {
  177. $this->U[$i][$k] = -$this->U[$i][$k];
  178. }
  179. $this->U[$k][$k] = 1.0 + $this->U[$k][$k];
  180. for ($i = 0; $i < $k - 1; ++$i) {
  181. $this->U[$i][$k] = 0.0;
  182. }
  183. } else {
  184. for ($i = 0; $i < $this->m; ++$i) {
  185. $this->U[$i][$k] = 0.0;
  186. }
  187. $this->U[$k][$k] = 1.0;
  188. }
  189. }
  190. }
  191. // If required, generate V.
  192. if ($wantv) {
  193. for ($k = $this->n - 1; $k >= 0; --$k) {
  194. if (($k < $nrt) and ($e[$k] != 0.0)) {
  195. for ($j = $k + 1; $j < $nu; ++$j) {
  196. $t = 0;
  197. for ($i = $k + 1; $i < $this->n; ++$i) {
  198. $t += $this->V[$i][$k]* $this->V[$i][$j];
  199. }
  200. $t = -$t / $this->V[$k+1][$k];
  201. for ($i = $k + 1; $i < $this->n; ++$i) {
  202. $this->V[$i][$j] += $t * $this->V[$i][$k];
  203. }
  204. }
  205. }
  206. for ($i = 0; $i < $this->n; ++$i) {
  207. $this->V[$i][$k] = 0.0;
  208. }
  209. $this->V[$k][$k] = 1.0;
  210. }
  211. }
  212. // Main iteration loop for the singular values.
  213. $pp = $p - 1;
  214. $iter = 0;
  215. $eps = pow(2.0, -52.0);
  216. while ($p > 0) {
  217. // Here is where a test for too many iterations would go.
  218. // This section of the program inspects for negligible
  219. // elements in the s and e arrays. On completion the
  220. // variables kase and k are set as follows:
  221. // kase = 1 if s(p) and e[k-1] are negligible and k<p
  222. // kase = 2 if s(k) is negligible and k<p
  223. // kase = 3 if e[k-1] is negligible, k<p, and
  224. // s(k), ..., s(p) are not negligible (qr step).
  225. // kase = 4 if e(p-1) is negligible (convergence).
  226. for ($k = $p - 2; $k >= -1; --$k) {
  227. if ($k == -1) {
  228. break;
  229. }
  230. if (abs($e[$k]) <= $eps * (abs($this->s[$k]) + abs($this->s[$k+1]))) {
  231. $e[$k] = 0.0;
  232. break;
  233. }
  234. }
  235. if ($k == $p - 2) {
  236. $kase = 4;
  237. } else {
  238. for ($ks = $p - 1; $ks >= $k; --$ks) {
  239. if ($ks == $k) {
  240. break;
  241. }
  242. $t = ($ks != $p ? abs($e[$ks]) : 0.) + ($ks != $k + 1 ? abs($e[$ks-1]) : 0.);
  243. if (abs($this->s[$ks]) <= $eps * $t) {
  244. $this->s[$ks] = 0.0;
  245. break;
  246. }
  247. }
  248. if ($ks == $k) {
  249. $kase = 3;
  250. } elseif ($ks == $p-1) {
  251. $kase = 1;
  252. } else {
  253. $kase = 2;
  254. $k = $ks;
  255. }
  256. }
  257. ++$k;
  258. // Perform the task indicated by kase.
  259. switch ($kase) {
  260. // Deflate negligible s(p).
  261. case 1:
  262. $f = $e[$p-2];
  263. $e[$p-2] = 0.0;
  264. for ($j = $p - 2; $j >= $k; --$j) {
  265. $t = hypo($this->s[$j], $f);
  266. $cs = $this->s[$j] / $t;
  267. $sn = $f / $t;
  268. $this->s[$j] = $t;
  269. if ($j != $k) {
  270. $f = -$sn * $e[$j-1];
  271. $e[$j-1] = $cs * $e[$j-1];
  272. }
  273. if ($wantv) {
  274. for ($i = 0; $i < $this->n; ++$i) {
  275. $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$p-1];
  276. $this->V[$i][$p-1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$p-1];
  277. $this->V[$i][$j] = $t;
  278. }
  279. }
  280. }
  281. break;
  282. // Split at negligible s(k).
  283. case 2:
  284. $f = $e[$k-1];
  285. $e[$k-1] = 0.0;
  286. for ($j = $k; $j < $p; ++$j) {
  287. $t = hypo($this->s[$j], $f);
  288. $cs = $this->s[$j] / $t;
  289. $sn = $f / $t;
  290. $this->s[$j] = $t;
  291. $f = -$sn * $e[$j];
  292. $e[$j] = $cs * $e[$j];
  293. if ($wantu) {
  294. for ($i = 0; $i < $this->m; ++$i) {
  295. $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$k-1];
  296. $this->U[$i][$k-1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$k-1];
  297. $this->U[$i][$j] = $t;
  298. }
  299. }
  300. }
  301. break;
  302. // Perform one qr step.
  303. case 3:
  304. // Calculate the shift.
  305. $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]));
  306. $sp = $this->s[$p-1] / $scale;
  307. $spm1 = $this->s[$p-2] / $scale;
  308. $epm1 = $e[$p-2] / $scale;
  309. $sk = $this->s[$k] / $scale;
  310. $ek = $e[$k] / $scale;
  311. $b = (($spm1 + $sp) * ($spm1 - $sp) + $epm1 * $epm1) / 2.0;
  312. $c = ($sp * $epm1) * ($sp * $epm1);
  313. $shift = 0.0;
  314. if (($b != 0.0) || ($c != 0.0)) {
  315. $shift = sqrt($b * $b + $c);
  316. if ($b < 0.0) {
  317. $shift = -$shift;
  318. }
  319. $shift = $c / ($b + $shift);
  320. }
  321. $f = ($sk + $sp) * ($sk - $sp) + $shift;
  322. $g = $sk * $ek;
  323. // Chase zeros.
  324. for ($j = $k; $j < $p-1; ++$j) {
  325. $t = hypo($f, $g);
  326. $cs = $f/$t;
  327. $sn = $g/$t;
  328. if ($j != $k) {
  329. $e[$j-1] = $t;
  330. }
  331. $f = $cs * $this->s[$j] + $sn * $e[$j];
  332. $e[$j] = $cs * $e[$j] - $sn * $this->s[$j];
  333. $g = $sn * $this->s[$j+1];
  334. $this->s[$j+1] = $cs * $this->s[$j+1];
  335. if ($wantv) {
  336. for ($i = 0; $i < $this->n; ++$i) {
  337. $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$j+1];
  338. $this->V[$i][$j+1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$j+1];
  339. $this->V[$i][$j] = $t;
  340. }
  341. }
  342. $t = hypo($f, $g);
  343. $cs = $f/$t;
  344. $sn = $g/$t;
  345. $this->s[$j] = $t;
  346. $f = $cs * $e[$j] + $sn * $this->s[$j+1];
  347. $this->s[$j+1] = -$sn * $e[$j] + $cs * $this->s[$j+1];
  348. $g = $sn * $e[$j+1];
  349. $e[$j+1] = $cs * $e[$j+1];
  350. if ($wantu && ($j < $this->m - 1)) {
  351. for ($i = 0; $i < $this->m; ++$i) {
  352. $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$j+1];
  353. $this->U[$i][$j+1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$j+1];
  354. $this->U[$i][$j] = $t;
  355. }
  356. }
  357. }
  358. $e[$p-2] = $f;
  359. $iter = $iter + 1;
  360. break;
  361. // Convergence.
  362. case 4:
  363. // Make the singular values positive.
  364. if ($this->s[$k] <= 0.0) {
  365. $this->s[$k] = ($this->s[$k] < 0.0 ? -$this->s[$k] : 0.0);
  366. if ($wantv) {
  367. for ($i = 0; $i <= $pp; ++$i) {
  368. $this->V[$i][$k] = -$this->V[$i][$k];
  369. }
  370. }
  371. }
  372. // Order the singular values.
  373. while ($k < $pp) {
  374. if ($this->s[$k] >= $this->s[$k+1]) {
  375. break;
  376. }
  377. $t = $this->s[$k];
  378. $this->s[$k] = $this->s[$k+1];
  379. $this->s[$k+1] = $t;
  380. if ($wantv and ($k < $this->n - 1)) {
  381. for ($i = 0; $i < $this->n; ++$i) {
  382. $t = $this->V[$i][$k+1];
  383. $this->V[$i][$k+1] = $this->V[$i][$k];
  384. $this->V[$i][$k] = $t;
  385. }
  386. }
  387. if ($wantu and ($k < $this->m-1)) {
  388. for ($i = 0; $i < $this->m; ++$i) {
  389. $t = $this->U[$i][$k+1];
  390. $this->U[$i][$k+1] = $this->U[$i][$k];
  391. $this->U[$i][$k] = $t;
  392. }
  393. }
  394. ++$k;
  395. }
  396. $iter = 0;
  397. --$p;
  398. break;
  399. } // end switch
  400. } // end while
  401. } // end constructor
  402. /**
  403. * Return the left singular vectors
  404. *
  405. * @access public
  406. * @return U
  407. */
  408. public function getU()
  409. {
  410. return new Matrix($this->U, $this->m, min($this->m + 1, $this->n));
  411. }
  412. /**
  413. * Return the right singular vectors
  414. *
  415. * @access public
  416. * @return V
  417. */
  418. public function getV()
  419. {
  420. return new Matrix($this->V);
  421. }
  422. /**
  423. * Return the one-dimensional array of singular values
  424. *
  425. * @access public
  426. * @return diagonal of S.
  427. */
  428. public function getSingularValues()
  429. {
  430. return $this->s;
  431. }
  432. /**
  433. * Return the diagonal matrix of singular values
  434. *
  435. * @access public
  436. * @return S
  437. */
  438. public function getS()
  439. {
  440. for ($i = 0; $i < $this->n; ++$i) {
  441. for ($j = 0; $j < $this->n; ++$j) {
  442. $S[$i][$j] = 0.0;
  443. }
  444. $S[$i][$i] = $this->s[$i];
  445. }
  446. return new Matrix($S);
  447. }
  448. /**
  449. * Two norm
  450. *
  451. * @access public
  452. * @return max(S)
  453. */
  454. public function norm2()
  455. {
  456. return $this->s[0];
  457. }
  458. /**
  459. * Two norm condition number
  460. *
  461. * @access public
  462. * @return max(S)/min(S)
  463. */
  464. public function cond()
  465. {
  466. return $this->s[0] / $this->s[min($this->m, $this->n) - 1];
  467. }
  468. /**
  469. * Effective numerical matrix rank
  470. *
  471. * @access public
  472. * @return Number of nonnegligible singular values.
  473. */
  474. public function rank()
  475. {
  476. $eps = pow(2.0, -52.0);
  477. $tol = max($this->m, $this->n) * $this->s[0] * $eps;
  478. $r = 0;
  479. for ($i = 0; $i < count($this->s); ++$i) {
  480. if ($this->s[$i] > $tol) {
  481. ++$r;
  482. }
  483. }
  484. return $r;
  485. }
  486. }