pinv.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import { isMatrix } from '../../utils/is.js';
  2. import { arraySize } from '../../utils/array.js';
  3. import { factory } from '../../utils/factory.js';
  4. import { format } from '../../utils/string.js';
  5. import { clone } from '../../utils/object.js';
  6. var name = 'pinv';
  7. var dependencies = ['typed', 'matrix', 'inv', 'deepEqual', 'equal', 'dotDivide', 'dot', 'ctranspose', 'divideScalar', 'multiply', 'add', 'Complex'];
  8. export var createPinv = /* #__PURE__ */factory(name, dependencies, _ref => {
  9. var {
  10. typed,
  11. matrix,
  12. inv,
  13. deepEqual,
  14. equal,
  15. dotDivide,
  16. dot,
  17. ctranspose,
  18. divideScalar,
  19. multiply,
  20. add,
  21. Complex
  22. } = _ref;
  23. /**
  24. * Calculate the Moore–Penrose inverse of a matrix.
  25. *
  26. * Syntax:
  27. *
  28. * math.pinv(x)
  29. *
  30. * Examples:
  31. *
  32. * math.pinv([[1, 2], [3, 4]]) // returns [[-2, 1], [1.5, -0.5]]
  33. * math.pinv([[1, 0], [0, 1], [0, 1]]) // returns [[1, 0, 0], [0, 0.5, 0.5]]
  34. * math.pinv(4) // returns 0.25
  35. *
  36. * See also:
  37. *
  38. * inv
  39. *
  40. * @param {number | Complex | Array | Matrix} x Matrix to be inversed
  41. * @return {number | Complex | Array | Matrix} The inverse of `x`.
  42. */
  43. return typed(name, {
  44. 'Array | Matrix': function ArrayMatrix(x) {
  45. var size = isMatrix(x) ? x.size() : arraySize(x);
  46. switch (size.length) {
  47. case 1:
  48. // vector
  49. if (_isZeros(x)) return ctranspose(x); // null vector
  50. if (size[0] === 1) {
  51. return inv(x); // invertible matrix
  52. } else {
  53. return dotDivide(ctranspose(x), dot(x, x));
  54. }
  55. case 2:
  56. // two dimensional array
  57. {
  58. if (_isZeros(x)) return ctranspose(x); // zero matrixx
  59. var rows = size[0];
  60. var cols = size[1];
  61. if (rows === cols) {
  62. try {
  63. return inv(x); // invertible matrix
  64. } catch (err) {
  65. if (err instanceof Error && err.message.match(/Cannot calculate inverse, determinant is zero/)) {
  66. // Expected
  67. } else {
  68. throw err;
  69. }
  70. }
  71. }
  72. if (isMatrix(x)) {
  73. return matrix(_pinv(x.valueOf(), rows, cols), x.storage());
  74. } else {
  75. // return an Array
  76. return _pinv(x, rows, cols);
  77. }
  78. }
  79. default:
  80. // multi dimensional array
  81. throw new RangeError('Matrix must be two dimensional ' + '(size: ' + format(size) + ')');
  82. }
  83. },
  84. any: function any(x) {
  85. // scalar
  86. if (equal(x, 0)) return clone(x); // zero
  87. return divideScalar(1, x);
  88. }
  89. });
  90. /**
  91. * Calculate the Moore–Penrose inverse of a matrix
  92. * @param {Array[]} mat A matrix
  93. * @param {number} rows Number of rows
  94. * @param {number} cols Number of columns
  95. * @return {Array[]} pinv Pseudoinverse matrix
  96. * @private
  97. */
  98. function _pinv(mat, rows, cols) {
  99. var {
  100. C,
  101. F
  102. } = _rankFact(mat, rows, cols); // TODO: Use SVD instead (may improve precision)
  103. var Cpinv = multiply(inv(multiply(ctranspose(C), C)), ctranspose(C));
  104. var Fpinv = multiply(ctranspose(F), inv(multiply(F, ctranspose(F))));
  105. return multiply(Fpinv, Cpinv);
  106. }
  107. /**
  108. * Calculate the reduced row echelon form of a matrix
  109. *
  110. * Modified from https://rosettacode.org/wiki/Reduced_row_echelon_form
  111. *
  112. * @param {Array[]} mat A matrix
  113. * @param {number} rows Number of rows
  114. * @param {number} cols Number of columns
  115. * @return {Array[]} Reduced row echelon form
  116. * @private
  117. */
  118. function _rref(mat, rows, cols) {
  119. var M = clone(mat);
  120. var lead = 0;
  121. for (var r = 0; r < rows; r++) {
  122. if (cols <= lead) {
  123. return M;
  124. }
  125. var i = r;
  126. while (_isZero(M[i][lead])) {
  127. i++;
  128. if (rows === i) {
  129. i = r;
  130. lead++;
  131. if (cols === lead) {
  132. return M;
  133. }
  134. }
  135. }
  136. [M[i], M[r]] = [M[r], M[i]];
  137. var val = M[r][lead];
  138. for (var j = 0; j < cols; j++) {
  139. M[r][j] = dotDivide(M[r][j], val);
  140. }
  141. for (var _i = 0; _i < rows; _i++) {
  142. if (_i === r) continue;
  143. val = M[_i][lead];
  144. for (var _j = 0; _j < cols; _j++) {
  145. M[_i][_j] = add(M[_i][_j], multiply(-1, multiply(val, M[r][_j])));
  146. }
  147. }
  148. lead++;
  149. }
  150. return M;
  151. }
  152. /**
  153. * Calculate the rank factorization of a matrix
  154. *
  155. * @param {Array[]} mat A matrix (M)
  156. * @param {number} rows Number of rows
  157. * @param {number} cols Number of columns
  158. * @return {{C: Array, F: Array}} rank factorization where M = C F
  159. * @private
  160. */
  161. function _rankFact(mat, rows, cols) {
  162. var rref = _rref(mat, rows, cols);
  163. var C = mat.map((_, i) => _.filter((_, j) => j < rows && !_isZero(dot(rref[j], rref[j]))));
  164. var F = rref.filter((_, i) => !_isZero(dot(rref[i], rref[i])));
  165. return {
  166. C,
  167. F
  168. };
  169. }
  170. function _isZero(x) {
  171. return equal(add(x, Complex(1, 1)), add(0, Complex(1, 1)));
  172. }
  173. function _isZeros(arr) {
  174. return deepEqual(add(arr, Complex(1, 1)), add(multiply(arr, 0), Complex(1, 1)));
  175. }
  176. });