185 lines
5.4 KiB
JavaScript
185 lines
5.4 KiB
JavaScript
|
"use strict";
|
|||
|
|
|||
|
Object.defineProperty(exports, "__esModule", {
|
|||
|
value: true
|
|||
|
});
|
|||
|
exports.createPinv = void 0;
|
|||
|
var _is = require("../../utils/is.js");
|
|||
|
var _array = require("../../utils/array.js");
|
|||
|
var _factory = require("../../utils/factory.js");
|
|||
|
var _string = require("../../utils/string.js");
|
|||
|
var _object = require("../../utils/object.js");
|
|||
|
const name = 'pinv';
|
|||
|
const dependencies = ['typed', 'matrix', 'inv', 'deepEqual', 'equal', 'dotDivide', 'dot', 'ctranspose', 'divideScalar', 'multiply', 'add', 'Complex'];
|
|||
|
const createPinv = exports.createPinv = /* #__PURE__ */(0, _factory.factory)(name, dependencies, _ref => {
|
|||
|
let {
|
|||
|
typed,
|
|||
|
matrix,
|
|||
|
inv,
|
|||
|
deepEqual,
|
|||
|
equal,
|
|||
|
dotDivide,
|
|||
|
dot,
|
|||
|
ctranspose,
|
|||
|
divideScalar,
|
|||
|
multiply,
|
|||
|
add,
|
|||
|
Complex
|
|||
|
} = _ref;
|
|||
|
/**
|
|||
|
* Calculate the Moore–Penrose inverse of a matrix.
|
|||
|
*
|
|||
|
* Syntax:
|
|||
|
*
|
|||
|
* math.pinv(x)
|
|||
|
*
|
|||
|
* Examples:
|
|||
|
*
|
|||
|
* math.pinv([[1, 2], [3, 4]]) // returns [[-2, 1], [1.5, -0.5]]
|
|||
|
* math.pinv([[1, 0], [0, 1], [0, 1]]) // returns [[1, 0, 0], [0, 0.5, 0.5]]
|
|||
|
* math.pinv(4) // returns 0.25
|
|||
|
*
|
|||
|
* See also:
|
|||
|
*
|
|||
|
* inv
|
|||
|
*
|
|||
|
* @param {number | Complex | Array | Matrix} x Matrix to be inversed
|
|||
|
* @return {number | Complex | Array | Matrix} The inverse of `x`.
|
|||
|
*/
|
|||
|
return typed(name, {
|
|||
|
'Array | Matrix': function (x) {
|
|||
|
const size = (0, _is.isMatrix)(x) ? x.size() : (0, _array.arraySize)(x);
|
|||
|
switch (size.length) {
|
|||
|
case 1:
|
|||
|
// vector
|
|||
|
if (_isZeros(x)) return ctranspose(x); // null vector
|
|||
|
if (size[0] === 1) {
|
|||
|
return inv(x); // invertible matrix
|
|||
|
} else {
|
|||
|
return dotDivide(ctranspose(x), dot(x, x));
|
|||
|
}
|
|||
|
case 2:
|
|||
|
// two dimensional array
|
|||
|
{
|
|||
|
if (_isZeros(x)) return ctranspose(x); // zero matrixx
|
|||
|
const rows = size[0];
|
|||
|
const cols = size[1];
|
|||
|
if (rows === cols) {
|
|||
|
try {
|
|||
|
return inv(x); // invertible matrix
|
|||
|
} catch (err) {
|
|||
|
if (err instanceof Error && err.message.match(/Cannot calculate inverse, determinant is zero/)) {
|
|||
|
// Expected
|
|||
|
} else {
|
|||
|
throw err;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if ((0, _is.isMatrix)(x)) {
|
|||
|
return matrix(_pinv(x.valueOf(), rows, cols), x.storage());
|
|||
|
} else {
|
|||
|
// return an Array
|
|||
|
return _pinv(x, rows, cols);
|
|||
|
}
|
|||
|
}
|
|||
|
default:
|
|||
|
// multi dimensional array
|
|||
|
throw new RangeError('Matrix must be two dimensional ' + '(size: ' + (0, _string.format)(size) + ')');
|
|||
|
}
|
|||
|
},
|
|||
|
any: function (x) {
|
|||
|
// scalar
|
|||
|
if (equal(x, 0)) return (0, _object.clone)(x); // zero
|
|||
|
return divideScalar(1, x);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
/**
|
|||
|
* Calculate the Moore–Penrose inverse of a matrix
|
|||
|
* @param {Array[]} mat A matrix
|
|||
|
* @param {number} rows Number of rows
|
|||
|
* @param {number} cols Number of columns
|
|||
|
* @return {Array[]} pinv Pseudoinverse matrix
|
|||
|
* @private
|
|||
|
*/
|
|||
|
function _pinv(mat, rows, cols) {
|
|||
|
const {
|
|||
|
C,
|
|||
|
F
|
|||
|
} = _rankFact(mat, rows, cols); // TODO: Use SVD instead (may improve precision)
|
|||
|
const Cpinv = multiply(inv(multiply(ctranspose(C), C)), ctranspose(C));
|
|||
|
const Fpinv = multiply(ctranspose(F), inv(multiply(F, ctranspose(F))));
|
|||
|
return multiply(Fpinv, Cpinv);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Calculate the reduced row echelon form of a matrix
|
|||
|
*
|
|||
|
* Modified from https://rosettacode.org/wiki/Reduced_row_echelon_form
|
|||
|
*
|
|||
|
* @param {Array[]} mat A matrix
|
|||
|
* @param {number} rows Number of rows
|
|||
|
* @param {number} cols Number of columns
|
|||
|
* @return {Array[]} Reduced row echelon form
|
|||
|
* @private
|
|||
|
*/
|
|||
|
function _rref(mat, rows, cols) {
|
|||
|
const M = (0, _object.clone)(mat);
|
|||
|
let lead = 0;
|
|||
|
for (let r = 0; r < rows; r++) {
|
|||
|
if (cols <= lead) {
|
|||
|
return M;
|
|||
|
}
|
|||
|
let i = r;
|
|||
|
while (_isZero(M[i][lead])) {
|
|||
|
i++;
|
|||
|
if (rows === i) {
|
|||
|
i = r;
|
|||
|
lead++;
|
|||
|
if (cols === lead) {
|
|||
|
return M;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
[M[i], M[r]] = [M[r], M[i]];
|
|||
|
let val = M[r][lead];
|
|||
|
for (let j = 0; j < cols; j++) {
|
|||
|
M[r][j] = dotDivide(M[r][j], val);
|
|||
|
}
|
|||
|
for (let i = 0; i < rows; i++) {
|
|||
|
if (i === r) continue;
|
|||
|
val = M[i][lead];
|
|||
|
for (let j = 0; j < cols; j++) {
|
|||
|
M[i][j] = add(M[i][j], multiply(-1, multiply(val, M[r][j])));
|
|||
|
}
|
|||
|
}
|
|||
|
lead++;
|
|||
|
}
|
|||
|
return M;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Calculate the rank factorization of a matrix
|
|||
|
*
|
|||
|
* @param {Array[]} mat A matrix (M)
|
|||
|
* @param {number} rows Number of rows
|
|||
|
* @param {number} cols Number of columns
|
|||
|
* @return {{C: Array, F: Array}} rank factorization where M = C F
|
|||
|
* @private
|
|||
|
*/
|
|||
|
function _rankFact(mat, rows, cols) {
|
|||
|
const rref = _rref(mat, rows, cols);
|
|||
|
const C = mat.map((_, i) => _.filter((_, j) => j < rows && !_isZero(dot(rref[j], rref[j]))));
|
|||
|
const F = rref.filter((_, i) => !_isZero(dot(rref[i], rref[i])));
|
|||
|
return {
|
|||
|
C,
|
|||
|
F
|
|||
|
};
|
|||
|
}
|
|||
|
function _isZero(x) {
|
|||
|
return equal(add(x, Complex(1, 1)), add(0, Complex(1, 1)));
|
|||
|
}
|
|||
|
function _isZeros(arr) {
|
|||
|
return deepEqual(add(arr, Complex(1, 1)), add(multiply(arr, 0), Complex(1, 1)));
|
|||
|
}
|
|||
|
});
|