LUTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. <?php
  2. namespace MathPHP\Tests\LinearAlgebra\Decomposition;
  3. use MathPHP\LinearAlgebra\MatrixFactory;
  4. use MathPHP\Exception;
  5. use MathPHP\LinearAlgebra\Vector;
  6. use MathPHP\Tests\LinearAlgebra\Fixture\MatrixDataProvider;
  7. class LUTest extends \PHPUnit\Framework\TestCase
  8. {
  9. use MatrixDataProvider;
  10. /**
  11. * @test LU decomposition - expected values for L, U, and P
  12. * @dataProvider dataProviderForLUDecomposition
  13. * @param array $A
  14. * @param array $L
  15. * @param array $U
  16. * @param array $P
  17. * @throws \Exception
  18. */
  19. public function testLUDecomposition(array $A, array $L, array $U, array $P)
  20. {
  21. // Given
  22. $A = MatrixFactory::create($A);
  23. $L = MatrixFactory::create($L);
  24. $U = MatrixFactory::create($U);
  25. $P = MatrixFactory::create($P);
  26. // When
  27. $LU = $A->luDecomposition();
  28. // Then
  29. $this->assertEqualsWithDelta($L, $LU->L, 0.001);
  30. $this->assertEqualsWithDelta($U, $LU->U, 0.001);
  31. $this->assertEqualsWithDelta($P, $LU->P, 0.001);
  32. }
  33. /**
  34. * @test LU decomposition - PA = LU
  35. * @dataProvider dataProviderForLUDecomposition
  36. * @param array $A
  37. * @throws \Exception
  38. */
  39. public function testLUDecompositionPaEqualsLu(array $A)
  40. {
  41. // Given
  42. $A = MatrixFactory::create($A);
  43. // When
  44. $LU = $A->luDecomposition();
  45. // Then PA = LU;
  46. $PA = $LU->P->multiply($A);
  47. $LU = $LU->L->multiply($LU->U);
  48. $this->assertEqualsWithDelta($PA->getMatrix(), $LU->getMatrix(), 0.01);
  49. }
  50. /**
  51. * @test LU decomposition - L and U properties
  52. * @dataProvider dataProviderForLUDecomposition
  53. * @param array $A
  54. * @throws \Exception
  55. */
  56. public function testLUDecompositionLAndUProperties(array $A)
  57. {
  58. // Given
  59. $A = MatrixFactory::create($A);
  60. // When
  61. $LU = $A->luDecomposition();
  62. // Then
  63. $this->assertTrue($LU->L->isLowerTriangular());
  64. $this->assertTrue($LU->U->isUpperTriangular());
  65. }
  66. /**
  67. * @test Solve
  68. * @dataProvider dataProviderForSolve
  69. * @param array $A
  70. * @param array $b
  71. * @param array $expected
  72. * @throws \Exception
  73. */
  74. public function testSolve(array $A, array $b, array $expected)
  75. {
  76. // Given
  77. $A = MatrixFactory::create($A);
  78. $LU = $A->luDecomposition();
  79. // And
  80. $expected = new Vector($expected);
  81. // When
  82. $x = $LU->solve($b);
  83. // Then
  84. $this->assertEqualsWithDelta($expected, $x, 0.00001);
  85. }
  86. /**
  87. * @test LU decomposition with small pivots
  88. * (http://buzzard.ups.edu/courses/2014spring/420projects/math420-UPS-spring-2014-reid-LU-pivoting.pdf)
  89. * Results computed with SciPy scipy.linalg.lu(A)
  90. * @throws \Exception
  91. */
  92. public function testLuDecompositionSmallPivots()
  93. {
  94. // Given
  95. $A = MatrixFactory::create([
  96. [10e-20, 1],
  97. [1, 2],
  98. ]);
  99. // And
  100. $L = MatrixFactory::create([
  101. [1, 0],
  102. [1e-19, 1],
  103. ]);
  104. $U = MatrixFactory::create([
  105. [1, 2],
  106. [0, 1],
  107. ]);
  108. $P = MatrixFactory::create([
  109. [0, 1],
  110. [1, 0],
  111. ]);
  112. // When
  113. $LU = $A->luDecomposition();
  114. // Then
  115. $this->assertEqualsWithDelta($L, $LU->L, 1e-20);
  116. $this->assertEqualsWithDelta($U, $LU->U, 1e-20);
  117. $this->assertEqualsWithDelta($P, $LU->P, 1e-20);
  118. // And
  119. $this->assertTrue($LU->L->isLowerTriangular());
  120. $this->assertTrue($LU->U->isUpperTriangular());
  121. // And PA = LU;
  122. $PA = $LU->P->multiply($A);
  123. $LU = $LU->L->multiply($LU->U);
  124. $this->assertEqualsWithDelta($PA->getMatrix(), $LU->getMatrix(), 0.01);
  125. }
  126. /**
  127. * Test data from various sources:
  128. * SciPy scipy.linalg.lu(A)
  129. * Online calculator: https://www.easycalculation.com/matrix/lu-decomposition-matrix.php
  130. * Various other sources.
  131. * @return array (A, L, U, P)
  132. */
  133. public function dataProviderForLuDecomposition(): array
  134. {
  135. return [
  136. [
  137. [
  138. [4, 3],
  139. [6, 3],
  140. ],
  141. [
  142. [1, 0],
  143. [0.667, 1],
  144. ],
  145. [
  146. [6, 3],
  147. [0, 1],
  148. ],
  149. [
  150. [0, 1],
  151. [1, 0],
  152. ],
  153. ],
  154. // Matrix Computations 3.4 Pivoting example - pivoting prevents large entries in the triangular factors L and U
  155. [
  156. [
  157. [.0001, 1],
  158. [1, 1],
  159. ],
  160. [
  161. [1, 0],
  162. [0.0001, 1],
  163. ],
  164. [
  165. [1, 1],
  166. [0, 0.999],
  167. ],
  168. [
  169. [0, 1],
  170. [1, 0],
  171. ],
  172. ],
  173. // Zero at first pivot element would cause a divide by zero error without pivoting (http://buzzard.ups.edu/courses/2014spring/420projects/math420-UPS-spring-2014-reid-LU-pivoting.pdf)
  174. [
  175. [
  176. [0, 1],
  177. [1, 2],
  178. ],
  179. [
  180. [1, 0],
  181. [0, 1],
  182. ],
  183. [
  184. [1, 2],
  185. [0, 1],
  186. ],
  187. [
  188. [0, 1],
  189. [1, 0],
  190. ],
  191. ],
  192. // Small pivots
  193. [
  194. [
  195. [10e-20, 1],
  196. [1, 2],
  197. ],
  198. [
  199. [1, 0],
  200. [1e-19, 1],
  201. ],
  202. [
  203. [1, 2],
  204. [0, 1],
  205. ],
  206. [
  207. [0, 1],
  208. [1, 0],
  209. ],
  210. ],
  211. [
  212. [
  213. [1, 3, 5],
  214. [2, 4, 7],
  215. [1, 1, 0],
  216. ],
  217. [
  218. [1, 0, 0],
  219. [.5, 1, 0],
  220. [.5, -1, 1],
  221. ],
  222. [
  223. [2, 4, 7],
  224. [0, 1, 1.5],
  225. [0, 0, -2],
  226. ],
  227. [
  228. [0, 1, 0],
  229. [1, 0, 0],
  230. [0, 0, 1],
  231. ]
  232. ],
  233. [
  234. [
  235. [1, -2, 3],
  236. [2, -5, 12],
  237. [0, 2, -10],
  238. ],
  239. [
  240. [1, 0, 0],
  241. [0, 1, 0],
  242. [0.5, 0.25, 1],
  243. ],
  244. [
  245. [2, -5, 12],
  246. [0, 2, -10],
  247. [0, 0, -0.5],
  248. ],
  249. [
  250. [0, 1, 0],
  251. [0, 0, 1],
  252. [1, 0, 0],
  253. ],
  254. ],
  255. [
  256. [
  257. [4, 2, 3],
  258. [-3, 1, 4],
  259. [2, 4, 5],
  260. ],
  261. [
  262. [1, 0, 0],
  263. [0.5, 1, 0],
  264. [-0.75, 0.833, 1],
  265. ],
  266. [
  267. [4, 2, 3],
  268. [0, 3, 3.5],
  269. [0, 0, 3.333]
  270. ],
  271. [
  272. [1, 0, 0],
  273. [0, 0, 1],
  274. [0, 1, 0],
  275. ],
  276. ],
  277. // Partial pivoting example - (http://buzzard.ups.edu/courses/2014spring/420projects/math420-UPS-spring-2014-reid-LU-pivoting.pdf)
  278. [
  279. [
  280. [1, 2, 4],
  281. [2, 1, 3],
  282. [3, 2, 4],
  283. ],
  284. [
  285. [1, 0, 0],
  286. [1 / 3, 1, 0],
  287. [2 / 3, -1 / 4, 1],
  288. ],
  289. [
  290. [3, 2, 4],
  291. [0, 4 / 3, 8 / 3],
  292. [0, 0, 1]
  293. ],
  294. [
  295. [0, 0, 1],
  296. [1, 0, 0],
  297. [0, 1, 0],
  298. ],
  299. ],
  300. [
  301. [
  302. [2, 3, 4],
  303. [4, 7, 5],
  304. [4, 9, 5],
  305. ],
  306. [
  307. [1, 0, 0],
  308. [1, 1, 0],
  309. [0.5, -0.25, 1],
  310. ],
  311. [
  312. [4, 7, 5],
  313. [0, 2, 0],
  314. [0, 0, 1.5]
  315. ],
  316. [
  317. [0, 1, 0],
  318. [0, 0, 1],
  319. [1, 0, 0],
  320. ],
  321. ],
  322. [
  323. [
  324. [5, 4, 8, 9],
  325. [9, 9, 9, 9],
  326. [4, 5, 5, 7],
  327. [1, 9, 8, 7],
  328. ],
  329. [
  330. [1, 0, 0, 0],
  331. [.556, 1, 0, 0],
  332. [.111, -8, 1, 0],
  333. [.444, -1, .129, 1],
  334. ],
  335. [
  336. [9, 9, 9, 9],
  337. [0, -1, 3, 4],
  338. [0, 0, 31, 38],
  339. [0, 0, 0, 2.097],
  340. ],
  341. [
  342. [0, 1, 0, 0],
  343. [1, 0, 0, 0],
  344. [0, 0, 0, 1],
  345. [0, 0, 1, 0],
  346. ],
  347. ],
  348. [
  349. [
  350. [2, 1, 1, 0],
  351. [4, 3, 3, 1],
  352. [8, 7, 9, 5],
  353. [6, 7, 9, 8],
  354. ],
  355. [
  356. [1, 0, 0, 0],
  357. [0.25, 1, 0, 0],
  358. [0.5, 0.667, 1, 0],
  359. [0.75, -2.333, 1, 1],
  360. ],
  361. [
  362. [8, 7, 9, 5],
  363. [0, -0.75, -1.25, -1.25],
  364. [0, 0, -0.667, -0.667],
  365. [0, 0, 0, 2],
  366. ],
  367. [
  368. [0, 0, 1, 0],
  369. [1, 0, 0, 0],
  370. [0, 1, 0, 0],
  371. [0, 0, 0, 1],
  372. ],
  373. ],
  374. [
  375. [
  376. [11, 9, 24, 2],
  377. [1, 5, 2, 6],
  378. [3, 17, 18, 1],
  379. [2, 5, 7, 1],
  380. ],
  381. [
  382. [1, 0, 0, 0],
  383. [.27273, 1, 0, 0],
  384. [.09091, .28750, 1, 0],
  385. [.18182, .23125, .00360, 1],
  386. ],
  387. [
  388. [11, 9, 24, 2],
  389. [0, 14.54545, 11.45455, 0.45455],
  390. [0, 0, -3.47500, 5.68750],
  391. [0, 0, 0, 0.51079],
  392. ],
  393. [
  394. [1, 0, 0, 0],
  395. [0, 0, 1, 0],
  396. [0, 1, 0, 0],
  397. [0, 0, 0, 1],
  398. ],
  399. ],
  400. [
  401. [
  402. [5, 3, 8],
  403. [6, 4, 5],
  404. [1, 8, 9],
  405. ],
  406. [
  407. [1, 0, 0],
  408. [0.167, 1, 0],
  409. [.833, -0.045, 1],
  410. ],
  411. [
  412. [6, 4, 5],
  413. [0, 7.333, 8.167],
  414. [0, 0, 4.205]
  415. ],
  416. [
  417. [0, 1, 0],
  418. [0, 0, 1],
  419. [1, 0, 0],
  420. ],
  421. ],
  422. [
  423. [
  424. [3, 2, 6, 7],
  425. [4, 3, -6, 2],
  426. [12, 14, 14, -6],
  427. [4, 6, 4, -42],
  428. ],
  429. [
  430. [1, 0, 0, 0],
  431. [0.25, 1, 0, 0],
  432. [0.333, 1.111, 1, 0],
  433. [0.333, -0.889, -0.116, 1],
  434. ],
  435. [
  436. [12, 14, 14, -6],
  437. [0, -1.5, 2.5, 8.5],
  438. [0, 0, -13.444, -5.444],
  439. [0, 0, 0, -33.074],
  440. ],
  441. [
  442. [0, 0, 1, 0],
  443. [1, 0, 0, 0],
  444. [0, 1, 0, 0],
  445. [0, 0, 0, 1],
  446. ],
  447. ],
  448. [
  449. [
  450. [5, 3, 4, 1],
  451. [5, 6, 4, 3],
  452. [7, 6, 5, 3],
  453. [2, 7, 4, 7],
  454. ],
  455. [
  456. [1, 0, 0, 0],
  457. [0.286, 1, 0, 0],
  458. [0.714, -0.243, 1, 0],
  459. [0.714, 0.324, -0.385, 1],
  460. ],
  461. [
  462. [7, 6, 5, 3],
  463. [0, 5.286, 2.571, 6.143],
  464. [0, 0, 1.054, 0.351],
  465. [0, 0, 0, -1],
  466. ],
  467. [
  468. [0, 0, 1, 0],
  469. [0, 0, 0, 1],
  470. [1, 0, 0, 0],
  471. [0, 1, 0, 0],
  472. ],
  473. ],
  474. ];
  475. }
  476. /**
  477. * @test LU decomposition exception for matrix not being square
  478. * @throws \Exception
  479. */
  480. public function testLUDecompositionExceptionNotSquare()
  481. {
  482. // Given
  483. $A = MatrixFactory::create([
  484. [1, 2, 3],
  485. [2, 3, 4],
  486. ]);
  487. // Then
  488. $this->expectException(Exception\MatrixException::class);
  489. // When
  490. $A->luDecomposition();
  491. }
  492. /**
  493. * @test LU Decomposition invalid property
  494. * @throws \Exception
  495. */
  496. public function testLUDecompositionInvalidProperty()
  497. {
  498. // Given
  499. $A = MatrixFactory::create([
  500. [5, 3, 4, 1],
  501. [5, 6, 4, 3],
  502. [7, 6, 5, 3],
  503. [2, 7, 4, 7],
  504. ]);
  505. $LU = $A->luDecomposition();
  506. // Then
  507. $this->expectException(Exception\MathException::class);
  508. // When
  509. $doesNotExist = $LU->doesNotExist;
  510. }
  511. /**
  512. * @test LU Decomposition solve incorrect type on b
  513. * @throws \Exception
  514. */
  515. public function testLUDecompositionSolveIncorrectTypeError()
  516. {
  517. // Given
  518. $A = MatrixFactory::create([
  519. [5, 3, 4, 1],
  520. [5, 6, 4, 3],
  521. [7, 6, 5, 3],
  522. [2, 7, 4, 7],
  523. ]);
  524. $LU = $A->luDecomposition();
  525. // And
  526. $b = new \stdClass();
  527. // Then
  528. $this->expectException(Exception\IncorrectTypeException::class);
  529. // When
  530. $LU->solve($b);
  531. }
  532. }