/src/LinearAlgebra/MatrixFactory.php

https://github.com/markrogoyski/math-php · PHP · 588 lines · 218 code · 52 blank · 318 comment · 50 complexity · 0297099926bf76af2a2b8f7648d226fb MD5 · raw file

  1. <?php
  2. namespace MathPHP\LinearAlgebra;
  3. use MathPHP\Exception;
  4. /**
  5. * Matrix factory to create matrices of all types.
  6. * Use factory instead of instantiating individual Matrix classes.
  7. */
  8. class MatrixFactory
  9. {
  10. /**
  11. * Factory method
  12. *
  13. * @param array[] $A 2-dimensional array of Matrix data
  14. *
  15. * @return Matrix
  16. *
  17. * @throws Exception\BadDataException
  18. * @throws Exception\IncorrectTypeException
  19. * @throws Exception\MathException
  20. * @throws Exception\MatrixException
  21. */
  22. public static function create(array $A): Matrix
  23. {
  24. self::checkParams($A);
  25. $matrix_type = self::determineMatrixType($A);
  26. switch ($matrix_type) {
  27. case 'matrix':
  28. return new Matrix($A);
  29. case 'square':
  30. return new SquareMatrix($A);
  31. case 'function':
  32. return new FunctionMatrix($A);
  33. case 'function_square':
  34. return new FunctionSquareMatrix($A);
  35. case 'object_square':
  36. return new ObjectSquareMatrix($A);
  37. }
  38. throw new Exception\IncorrectTypeException('Unknown matrix type');
  39. }
  40. /**
  41. * Factory method to create a matrix from an array of Vectors
  42. *
  43. * Example:
  44. * [1] [4] [7] [8]
  45. * X₁ = [2] X₂ = [2] X₃ = [8] X₄ = [4]
  46. * [1] [13] [1] [5]
  47. *
  48. * [1 4 7 8]
  49. * R = [2 2 8 4]
  50. * [1 13 1 5]
  51. *
  52. * @param Vector[] $A array of Vectors
  53. *
  54. * @return Matrix
  55. *
  56. * @throws Exception\MatrixException if the Vectors are not all the same length
  57. * @throws Exception\IncorrectTypeException
  58. * @throws Exception\BadDataException
  59. */
  60. public static function createFromVectors(array $A): Matrix
  61. {
  62. // Check that all vectors are the same length
  63. $m = $A[0]->getN();
  64. $n = count($A);
  65. for ($j = 1; $j < $n; $j++) {
  66. if ($A[$j]->getN() !== $m) {
  67. throw new Exception\MatrixException('Vectors being combined into matrix have different lengths');
  68. }
  69. }
  70. // Concatenate all the vectors
  71. $R = [];
  72. foreach ($A as $V) {
  73. $R[] = $V->getVector();
  74. }
  75. // Transpose to create matrix from the vector columns
  76. return (new Matrix($R))->transpose();
  77. }
  78. /**************************************************************************
  79. * SPECIAL MATRICES - Not created from an array of arrays
  80. * - identity
  81. * - exchange
  82. * - downshiftPermutation
  83. * - upshiftPermutation
  84. * - zero
  85. * - one
  86. * - eye
  87. * - diagonal
  88. * - hilbert
  89. * - vandermonde
  90. * - givens
  91. **************************************************************************/
  92. /**
  93. * Identity matrix - n x n matrix with ones in the diagonal
  94. *
  95. * Example:
  96. * n = 3;
  97. *
  98. * [1 0 0]
  99. * A = [0 1 0]
  100. * [0 0 1]
  101. *
  102. * @param int $n size of matrix
  103. *
  104. * @return Matrix
  105. *
  106. * @throws Exception\BadDataException
  107. * @throws Exception\IncorrectTypeException
  108. * @throws Exception\MathException
  109. * @throws Exception\MatrixException
  110. * @throws Exception\OutOfBoundsException if n < 0
  111. */
  112. public static function identity(int $n): Matrix
  113. {
  114. if ($n < 0) {
  115. throw new Exception\OutOfBoundsException("n must be ≥ 0. n = $n");
  116. }
  117. $R = [];
  118. for ($i = 0; $i < $n; $i++) {
  119. for ($j = 0; $j < $n; $j++) {
  120. $R[$i][$j] = $i == $j ? 1 : 0;
  121. }
  122. }
  123. return self::create($R);
  124. }
  125. /**
  126. * Exchange matrix - n x n matrix with ones in the reverse diagonal
  127. * Row-reversed, or column-reversed version of the identity matrix.
  128. * https://en.wikipedia.org/wiki/Exchange_matrix
  129. *
  130. * Example:
  131. * n = 3;
  132. *
  133. * [0 0 1]
  134. * A = [0 1 0]
  135. * [1 0 0]
  136. *
  137. * @param int $n size of matrix
  138. *
  139. * @return Matrix
  140. *
  141. * @throws Exception\BadDataException
  142. * @throws Exception\IncorrectTypeException
  143. * @throws Exception\MathException
  144. * @throws Exception\MatrixException
  145. * @throws Exception\OutOfBoundsException if n < 0
  146. */
  147. public static function exchange(int $n): Matrix
  148. {
  149. if ($n < 0) {
  150. throw new Exception\OutOfBoundsException("n must be ≥ 0. n = $n");
  151. }
  152. $R = [];
  153. $one = $n - 1;
  154. for ($i = 0; $i < $n; $i++) {
  155. for ($j = 0; $j < $n; $j++) {
  156. $R[$i][$j] = $j == $one ? 1 : 0;
  157. }
  158. $one--;
  159. }
  160. return self::create($R);
  161. }
  162. /**
  163. * Downshift permutation matrix
  164. * Pushes the components of a vector down one notch with wraparound
  165. *
  166. * [0, 0, 0, 1] [x₁] [x₄]
  167. * [1, 0, 0, 0] [x₂] [x₁]
  168. * D₄x = [0, 1, 0, 0] [x₃] = [x₂]
  169. * [0, 0, 1, 0] [x₄] [x₃]
  170. *
  171. * @param int $n
  172. *
  173. * @return Matrix
  174. *
  175. * @throws Exception\BadDataException
  176. * @throws Exception\IncorrectTypeException
  177. * @throws Exception\MathException
  178. * @throws Exception\MatrixException
  179. * @throws Exception\OutOfBoundsException if n < 0
  180. */
  181. public static function downshiftPermutation(int $n): Matrix
  182. {
  183. $I = self::identity($n)->getMatrix();
  184. $bottom_row = array_pop($I);
  185. array_unshift($I, $bottom_row);
  186. return self::create($I);
  187. }
  188. /**
  189. * Upshift permutation matrix - Dᵀ
  190. * Pushes the components of a vector up one notch with wraparound
  191. *
  192. * @param int $n
  193. *
  194. * @return Matrix
  195. *
  196. * @throws Exception\BadDataException
  197. * @throws Exception\IncorrectTypeException
  198. * @throws Exception\MathException
  199. * @throws Exception\MatrixException
  200. * @throws Exception\OutOfBoundsException
  201. */
  202. public static function upshiftPermutation(int $n): Matrix
  203. {
  204. return self::downshiftPermutation($n)->transpose();
  205. }
  206. /**
  207. * Zero matrix - m x n matrix with all elements being zeros
  208. *
  209. * Example:
  210. * m = 3; n = 3
  211. *
  212. * [0 0 0]
  213. * A = [0 0 0]
  214. * [0 0 0]
  215. *
  216. * @param int $m rows
  217. * @param int $n columns
  218. *
  219. * @return Matrix
  220. *
  221. * @throws Exception\BadDataException
  222. * @throws Exception\IncorrectTypeException
  223. * @throws Exception\MathException
  224. * @throws Exception\MatrixException
  225. * @throws Exception\OutOfBoundsException if m < 1 or n < 1
  226. */
  227. public static function zero(int $m, int $n): Matrix
  228. {
  229. if ($m < 1 || $n < 1) {
  230. throw new Exception\OutOfBoundsException("m and n must be > 0. m = $m, n = $n");
  231. }
  232. $R = [];
  233. for ($i = 0; $i < $m; $i++) {
  234. for ($j = 0; $j < $n; $j++) {
  235. $R[$i][$j] = 0;
  236. }
  237. }
  238. return self::create($R);
  239. }
  240. /**
  241. * Ones matrix - m x n matrix with all elements being ones
  242. *
  243. * Example:
  244. * m = 3; n = 3
  245. *
  246. * [1 1 1]
  247. * A = [1 1 1]
  248. * [1 1 1]
  249. *
  250. * @param int $m rows
  251. * @param int $n columns
  252. *
  253. * @return Matrix
  254. *
  255. * @throws Exception\BadDataException
  256. * @throws Exception\IncorrectTypeException
  257. * @throws Exception\MathException
  258. * @throws Exception\MatrixException
  259. * @throws Exception\OutOfBoundsException if m or n < 1
  260. */
  261. public static function one(int $m, int $n): Matrix
  262. {
  263. if ($m < 1 || $n < 1) {
  264. throw new Exception\OutOfBoundsException("m and n must be > 0. m = $m, n = $n");
  265. }
  266. $R = [];
  267. for ($i = 0; $i < $m; $i++) {
  268. for ($j = 0; $j < $n; $j++) {
  269. $R[$i][$j] = 1;
  270. }
  271. }
  272. return self::create($R);
  273. }
  274. /**
  275. * Eye matrix - ones on the k diagonal and zeros everywhere else.
  276. * Diagonal can start at any column.
  277. * Option to set the diagonal to any number.
  278. *
  279. * Example:
  280. * m = 3; n = 3; k = 1; x = 1 (3x3 matrix with 1s on the kth (1) diagonal)
  281. *
  282. * [0 1 0]
  283. * A = [0 0 1]
  284. * [0 0 0]
  285. *
  286. * @param int $m number of rows
  287. * @param int $n number of columns
  288. * @param int $k Diagonal to fill with xs
  289. * @param float $x (optional; default 1)
  290. *
  291. * @return Matrix
  292. *
  293. * @throws Exception\BadDataException
  294. * @throws Exception\IncorrectTypeException
  295. * @throws Exception\MathException
  296. * @throws Exception\MatrixException
  297. * @throws Exception\OutOfBoundsException if m, n, or k are < 0; if k >= n
  298. */
  299. public static function eye(int $m, int $n, int $k, float $x = null): Matrix
  300. {
  301. if ($n < 0 || $m < 0 || $k < 0) {
  302. throw new Exception\OutOfBoundsException("m, n and k must be ≥ 0. m = $m, n = $n, k = $k");
  303. }
  304. if ($k >= $n) {
  305. throw new Exception\OutOfBoundsException("k must be < n. k = $k, n = $n");
  306. }
  307. $x = $x ?? 1;
  308. $R = (self::zero($m, $n))->getMatrix();
  309. for ($i = 0; $i < $m; $i++) {
  310. if (($k + $i) < $n) {
  311. $R[$i][$k + $i] = $x;
  312. }
  313. }
  314. return self::create($R);
  315. }
  316. /**
  317. * A Diagonal Matrix is constructed from a single-row array.
  318. * The elements of this array are placed on the diagonal of a square matrix.
  319. *
  320. * Example:
  321. * D = [1, 2, 3]
  322. *
  323. * [1 0 0]
  324. * A = [0 2 0]
  325. * [0 0 3]
  326. *
  327. * @param array $D elements of the diagonal
  328. *
  329. * @return DiagonalMatrix
  330. *
  331. * @throws Exception\MatrixException
  332. */
  333. public static function diagonal(array $D): DiagonalMatrix
  334. {
  335. $m = count($D);
  336. $A = [];
  337. for ($i = 0; $i < $m; $i++) {
  338. for ($j = 0; $j < $m; $j++) {
  339. if ($i == $j) {
  340. $A[$i][$j] = $D[$i];
  341. } else {
  342. $A[$i][$j] = 0;
  343. }
  344. }
  345. }
  346. return new DiagonalMatrix($A);
  347. }
  348. /**
  349. * Hilbert matrix - a square matrix with entries being the unit fractions
  350. * https://en.wikipedia.org/wiki/Hilbert_matrix
  351. *
  352. * 1
  353. * Hij = ---------
  354. * i + j - 1
  355. *
  356. * Example: n = 5
  357. *
  358. * [1 ½ ⅓ ¼ ⅕]
  359. * [½ ⅓ ¼ ⅕ ⅙]
  360. * H = [⅓ ¼ ⅕ ⅙ ⅐]
  361. * [¼ ⅕ ⅙ ⅐ ⅛]
  362. * [⅕ ⅙ ⅐ ⅛ ⅑]
  363. *
  364. * @param int $n
  365. *
  366. * @return Matrix
  367. *
  368. * @throws Exception\BadDataException
  369. * @throws Exception\IncorrectTypeException
  370. * @throws Exception\MathException
  371. * @throws Exception\MatrixException
  372. * @throws Exception\OutOfBoundsException
  373. */
  374. public static function hilbert(int $n): Matrix
  375. {
  376. if ($n < 1) {
  377. throw new Exception\OutOfBoundsException("n must be > 0. m = $n");
  378. }
  379. $H = [];
  380. for ($i = 1; $i <= $n; $i++) {
  381. for ($j = 1; $j <= $n; $j++) {
  382. $H[$i - 1][$j - 1] = 1 / ($i + $j - 1);
  383. }
  384. }
  385. return self::create($H);
  386. }
  387. /**
  388. * Create the Vandermonde Matrix from a simple array.
  389. *
  390. * @param array $M (α₁, α₂, α₃ ⋯ αm)
  391. * @param int $n
  392. *
  393. * @return Matrix
  394. *
  395. * @throws Exception\BadDataException
  396. * @throws Exception\IncorrectTypeException
  397. * @throws Exception\MathException
  398. * @throws Exception\MatrixException
  399. */
  400. public static function vandermonde(array $M, int $n): Matrix
  401. {
  402. $A = [];
  403. foreach ($M as $row => $α) {
  404. for ($i = 0; $i < $n; $i++) {
  405. $A[$row][$i] = $α ** $i;
  406. }
  407. }
  408. return self::create($A);
  409. }
  410. /**
  411. * Construct a Givens rotation matrix
  412. *
  413. * [ 1 ⋯ 0 ⋯ 0 ⋯ 0 ]
  414. * [ ⋮ ⋱ ⋮ ⋮ ⋮ ]
  415. * [ 0 ⋯ c ⋯-s ⋯ 0 ]
  416. * G (𝒾,𝒿,θ) = [ ⋮ ⋮ ⋱ ⋮ ⋮ ]
  417. * [ 0 ⋯ s ⋯ c ⋯ 0 ]
  418. * [ ⋮ ⋮ ⋮ ⋱ ⋮ ]
  419. * [ 0 ⋯ 0 ⋯ 0 ⋯ 1 ]
  420. *
  421. * https://en.wikipedia.org/wiki/Givens_rotation
  422. *
  423. * @param int $m The row in G in which the top of the rotation lies
  424. * @param int $n The column in G in which the left of the rotation lies
  425. * @param float $angle The angle to use in the trigonometric functions
  426. * @param int $size The total number of rows in G
  427. *
  428. * @return Matrix
  429. *
  430. * @throws Exception\BadDataException
  431. * @throws Exception\IncorrectTypeException
  432. * @throws Exception\MathException
  433. * @throws Exception\MatrixException
  434. * @throws Exception\OutOfBoundsException
  435. */
  436. public static function givens(int $m, int $n, float $angle, int $size): Matrix
  437. {
  438. if ($m >= $size || $n >= $size || $m < 0 || $n < 0) {
  439. throw new Exception\OutOfBoundsException("m and n must be within the matrix");
  440. }
  441. $G = MatrixFactory::identity($size)->getMatrix();
  442. $G[$m][$m] = cos($angle);
  443. $G[$n][$n] = cos($angle);
  444. $G[$m][$n] = -1 * sin($angle);
  445. $G[$n][$m] = sin($angle);
  446. return MatrixFactory::create($G);
  447. }
  448. /**
  449. * Create a Matrix of random numbers
  450. *
  451. * @param int $m number of rows
  452. * @param int $n number of columns
  453. * @param int $min lower bound for the random number (optional - default: 0)
  454. * @param int $max upper bound for the random number (optional - default: 20)
  455. *
  456. * @return Matrix
  457. *
  458. * @throws Exception\BadDataException
  459. * @throws Exception\IncorrectTypeException
  460. * @throws Exception\MathException
  461. * @throws Exception\MatrixException
  462. */
  463. public static function random(int $m, int $n, int $min = 0, int $max = 20): Matrix
  464. {
  465. $A = [];
  466. for ($i = 0; $i < $m; $i++) {
  467. for ($j = 0; $j < $n; $j++) {
  468. $A[$i][$j] = rand($min, $max);
  469. }
  470. }
  471. return self::create($A);
  472. }
  473. /* ************************************************************************
  474. * PRIVATE HELPER METHODS
  475. * ***********************************************************************/
  476. /**
  477. * Check input parameters
  478. *
  479. * @param array $A
  480. *
  481. * @return bool
  482. *
  483. * @throws Exception\BadDataException if array data not provided for matrix creation
  484. * @throws Exception\MatrixException if any row has a different column count
  485. */
  486. private static function checkParams(array $A): bool
  487. {
  488. if (empty($A)) {
  489. throw new Exception\BadDataException('Array data not provided for Matrix creation');
  490. }
  491. if (isset($A[0]) && is_array($A[0])) {
  492. $column_count = count($A[0]);
  493. foreach ($A as $i => $row) {
  494. if (count($row) !== $column_count) {
  495. throw new Exception\MatrixException("Row $i has a different column count: " . count($row) . "; was expecting $column_count.");
  496. }
  497. }
  498. }
  499. return true;
  500. }
  501. /**
  502. * Determine what type of matrix to create
  503. *
  504. * @param array[] $A 2-dimensional array of Matrix data
  505. *
  506. * @return string indicating what matrix type to create
  507. */
  508. private static function determineMatrixType(array $A): string
  509. {
  510. $m = count($A);
  511. $n = count($A[0]);
  512. // Square Matrices have the same number of rows (m) and columns (n)
  513. if ($m === $n) {
  514. // closures are objects, so we need to separate them out.
  515. if (is_object($A[0][0])) {
  516. if ($A[0][0] instanceof \Closure) {
  517. return 'function_square';
  518. } else {
  519. return 'object_square';
  520. }
  521. }
  522. return 'square';
  523. }
  524. // Non square Matrices
  525. // First check to make sure it isn't something strange
  526. if (is_array($A[0][0])) {
  527. return 'unknown';
  528. }
  529. // Then check remaining matrix types
  530. if (is_callable($A[0][0])) {
  531. return 'function';
  532. }
  533. // Numeric matrix
  534. return 'matrix';
  535. }
  536. }