Skip to content

Commit 3279a15

Browse files
zafaralitargos
authored andcommitted
feat: add pseudoinverse function based on SVD
1 parent 29ad468 commit 3279a15

File tree

3 files changed

+86
-12
lines changed

3 files changed

+86
-12
lines changed

src/abstractMatrix.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module.exports = abstractMatrix;
44

55
var LuDecomposition = require('./dc/lu');
6+
var SvDecomposition = require('./dc/svd');
67
var arrayUtils = require('ml-array-utils');
78
var util = require('./util');
89
var MatrixTransposeView = require('./views/transpose');
@@ -1545,6 +1546,32 @@ function abstractMatrix(superCtor) {
15451546
throw Error('Determinant can only be calculated for a square matrix.');
15461547
}
15471548
}
1549+
1550+
/**
1551+
* Returns inverse of a matrix if it exists or the pseudoinverse
1552+
* @param {number} threshold - threshold for taking inverse of singular values (default = 1e-15)
1553+
* @return {Matrix} the (pseudo)inverted matrix.
1554+
*/
1555+
pseudoInverse(threshold) {
1556+
if (threshold === undefined) threshold = Number.EPSILON;
1557+
var svdSolution = new SvDecomposition(this, {autoTranspose: true});
1558+
1559+
var U = svdSolution.leftSingularVectors;
1560+
var V = svdSolution.rightSingularVectors;
1561+
var s = svdSolution.diagonal;
1562+
1563+
for (var i = 0; i < s.length; i++) {
1564+
if (Math.abs(s[i]) > threshold) {
1565+
s[i] = 1.0 / s[i];
1566+
} else {
1567+
s[i] = 0.0;
1568+
}
1569+
}
1570+
1571+
// convert list to diagonal
1572+
s = this.constructor[Symbol.species].diag(s);
1573+
return V.mmul(s.mmul(U.transposeView()));
1574+
}
15481575
}
15491576

15501577
Matrix.prototype.klass = 'Matrix';

src/dc/svd.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
var Matrix = require('../matrix').Matrix;
3+
var Matrix = require('../matrix');
44
var util = require('./util');
55
var hypotenuse = util.hypotenuse;
66
var getFilled2DArray = util.getFilled2DArray;
@@ -10,7 +10,7 @@ function SingularValueDecomposition(value, options) {
1010
if (!(this instanceof SingularValueDecomposition)) {
1111
return new SingularValueDecomposition(value, options);
1212
}
13-
value = Matrix.checkMatrix(value);
13+
value = Matrix.Matrix.checkMatrix(value);
1414

1515
options = options || {};
1616

@@ -418,26 +418,26 @@ SingularValueDecomposition.prototype = {
418418
return (Math.pow(2, -52) / 2) * Math.max(this.m, this.n) * this.s[0];
419419
},
420420
get leftSingularVectors() {
421-
if (!Matrix.isMatrix(this.U)) {
422-
this.U = new Matrix(this.U);
421+
if (!Matrix.Matrix.isMatrix(this.U)) {
422+
this.U = new Matrix.Matrix(this.U);
423423
}
424424
return this.U;
425425
},
426426
get rightSingularVectors() {
427-
if (!Matrix.isMatrix(this.V)) {
428-
this.V = new Matrix(this.V);
427+
if (!Matrix.Matrix.isMatrix(this.V)) {
428+
this.V = new Matrix.Matrix(this.V);
429429
}
430430
return this.V;
431431
},
432432
get diagonalMatrix() {
433-
return Matrix.diag(this.s);
433+
return Matrix.Matrix.diag(this.s);
434434
},
435435
solve: function (value) {
436436

437437
var Y = value,
438438
e = this.threshold,
439439
scols = this.s.length,
440-
Ls = Matrix.zeros(scols, scols),
440+
Ls = Matrix.Matrix.zeros(scols, scols),
441441
i;
442442

443443
for (i = 0; i < scols; i++) {
@@ -454,7 +454,7 @@ SingularValueDecomposition.prototype = {
454454
var VL = V.mmul(Ls),
455455
vrows = V.rows,
456456
urows = U.length,
457-
VLU = Matrix.zeros(vrows, urows),
457+
VLU = Matrix.Matrix.zeros(vrows, urows),
458458
j, k, sum;
459459

460460
for (i = 0; i < vrows; i++) {
@@ -470,14 +470,14 @@ SingularValueDecomposition.prototype = {
470470
return VLU.mmul(Y);
471471
},
472472
solveForDiagonal: function (value) {
473-
return this.solve(Matrix.diag(value));
473+
return this.solve(Matrix.Matrix.diag(value));
474474
},
475475
inverse: function () {
476476
var V = this.V;
477477
var e = this.threshold,
478478
vrows = V.length,
479479
vcols = V[0].length,
480-
X = new Matrix(vrows, this.s.length),
480+
X = new Matrix.Matrix(vrows, this.s.length),
481481
i, j;
482482

483483
for (i = 0; i < vrows; i++) {
@@ -494,7 +494,7 @@ SingularValueDecomposition.prototype = {
494494

495495
var urows = U.length,
496496
ucols = U[0].length,
497-
Y = new Matrix(vrows, urows),
497+
Y = new Matrix.Matrix(vrows, urows),
498498
k, sum;
499499

500500
for (i = 0; i < vrows; i++) {

test/matrix/utility.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,51 @@ describe('utility methods', function () {
176176
matrix2 = new Matrix([[2, 1, 3], [7, 1, 1], [6, 2, 7]]);
177177
matrix.strassen3x3(matrix2).to2DArray().should.eql([[38, 8, 17], [33, 12, 36], [35, 12, 37]]);
178178
});
179+
180+
it('pseudoinverse', function () {
181+
// Actual values calculated by the Numpy library
182+
183+
var matrix = new Matrix([[2, 4], [7, 1]]);
184+
var result = matrix.pseudoInverse().to2DArray();
185+
186+
result[0][0].should.approximately(-0.03846153846153843, 1e-5);
187+
result[0][1].should.approximately(0.15384615384615374, 1e-5);
188+
result[1][0].should.approximately(0.2692307692307691, 1e-5);
189+
result[1][1].should.approximately(-0.076923076923077, 1e-5);
190+
191+
matrix = new Matrix([[4, 7], [2, 6]]);
192+
result = matrix.pseudoInverse().to2DArray();
193+
result[0][0].should.approximately(0.6, 1e-5);
194+
result[0][1].should.approximately(-0.7, 1e-5);
195+
result[1][0].should.approximately(-0.2, 1e-5);
196+
result[1][1].should.approximately(0.4, 1e-5);
197+
198+
// Example from http://reference.wolfram.com/language/ref/PseudoInverse.html
199+
matrix = new Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
200+
result = matrix.pseudoInverse().to2DArray();
201+
202+
result[0][0].should.approximately(-6.38888889e-01, 1e-5);
203+
result[0][1].should.approximately(-1.66666667e-01, 1e-5);
204+
result[0][2].should.approximately(3.05555556e-01, 1e-5);
205+
206+
result[1][0].should.approximately(-5.55555556e-02, 1e-5);
207+
result[1][1].should.approximately(1.34337961e-16, 1e-5);
208+
result[1][2].should.approximately(5.55555556e-02, 1e-5);
209+
210+
result[2][0].should.approximately(5.27777778e-01, 1e-5);
211+
result[2][1].should.approximately(1.66666667e-01, 1e-5);
212+
result[2][2].should.approximately(-1.94444444e-01, 1e-5);
213+
214+
// non-square matrix.
215+
matrix = new Matrix([[1, 0, 1], [2, 4, 5]]);
216+
result = matrix.pseudoInverse().to2DArray();
217+
218+
result[0][0].should.approximately(0.75609756, 1e-5);
219+
result[0][1].should.approximately(-0.07317073, 1e-5);
220+
result[1][0].should.approximately(-0.68292683, 1e-5);
221+
result[1][1].should.approximately(0.19512195, 1e-5);
222+
result[2][0].should.approximately(0.24390244, 1e-5);
223+
result[2][1].should.approximately(0.07317073, 1e-5);
224+
225+
});
179226
});

0 commit comments

Comments
 (0)