linalg.js - Linear Algebra for the web.
Linear Algebra Expressions and operator overloading on the web.. or can you ?
Since every post should start with a nice image, here’s a rank 15,10,5 and 2 approximation of an image calculated in javascript :
And here’s the script that generates it :
var img = new Image();
img.src = '../res/testimage.jpg';
img.onload = Matrix(function() {
var U=Matrix(this), S, V, R;
"[U,S,V]=U.SVD"
[15,10,5,2].forEach(j=>{
for (var i=j; i < S.rows; i++) S[i]=0;
"R=U*S~*V'"
Matrix.show(R,document.body);
});
});
Hey - wait a minute .. what’s that string line inside the function ? And why is the function wrapped in a Matrix call ?
Lets look at another example to see how linalg.js will make your world easier. and more readable.
// Find the rigid transform that transforms pointset A to pointset B.
// A and B are matrices with nr_of_points rows and nr_of_dimensions columns.
// It returns a square nr_of_dimensions dimensional matrix T so that A*T=B
var findTransform = Matrix(function(A,B){
var mA, mB, U, S, V, COV;
// Center dataset by subtracting average row..
"mA = A-A.avg"
"mB = B-B.avg"
// Calculate covariance matrix.
"COV = mA'*mB"
// do SVD factorisation on covariance matrix.
"[U,S,V] = COV.SVD"
// Find transformation that brings A to B
"return U*V'"
});
That’s right .. full matrix math, factorisation and operator overloading .. just a string quote away from being javascript..
enter linalg.js
12861 bytes - https://enkimute.github.io/res/linalg.min.js
All features of linalg.js are accessed through a single global function call, appropriately named ‘Matrix’.
you use it to create matrices ..
// a skinny matrix.
var A = Matrix([[1,2],[3,4],[5,6],[7,8]]);
// an empty matrix.
var B = Matrix(4,2);
// a matrix from an image, video or canvas :
var C = Matrix(document.getElementById('myImage'));
Or solve some simple expressions ..
// Solve (x=0,x=1,y=0)
var solution = Matrix("[[1,0],[1,0],[0,1]]\\[0,1,0]");
// add the transpose of a to b then multiply with c
var D = Matrix("(A'+B)*C");
// Multiply B with the inverse of A :
var E = Matrix("B*A^-1");
// you can store these expressions as functions.
// simpy add the arguments list as second parameter.
var solve = Matrix("(A'*A)^-1*A'*Y","A,Y");
var solution2 = solve([[1,0],[1,0],[0,1]],[0,1,0]);
or wrap your functions in it ..
.. so that you can put linear algebra expressions between string quotes on their own line ..
var sample = Matrix(function(){
var A=[[1,0],[1,0],[0,1]];
var Y=[0,1,0];
"return A\Y"
});
var solve = Matrix(function(A,Y){
var Ai = Matrix(A);
"Ai = (A'*A)^-1*A'"
"return Ai*Y"
});
solve([[1,0],[1,0],[0,1]],[0,1,0]);
Together with modern javascript features ..
you can write some pretty readable algebra code.
var fitCubic = Matrix(function (data) {
var Vandermonde = data.map(x=>[x[0]*x[0]*x[0],x[0]*x[0],x[0],1]);
var Rhs = data.map(x=>x[1]);
"return Vandermonde\Rhs"
});
In just a few lines we can implement various popular least squares methods ..
// Linear Least squares, regularised linear least squares, weighted linear least squares.
var lls = Matrix("A\\Y","A,Y");
var rlls = Matrix("(A'*A + l*I)^-1*A'*Y","A,Y,I,l");
var wlls = Matrix("(A'*W~*A)^-1*A'*W~*Y","A,Y,W");
// Find intersection of the system ( y=0, x=1, y=2, x=3 )
var A=[[0,1],[1,0],[0,1],[1,0]];
var Y=[0,1,2,3];
// Using linear least squares
lls(A,Y).log();
// Regularised .. importance of arg size is 0.1
rlls(A,Y,Matrix.identity(2),0.1).log();
// Weighted .. first two more important.
wlls(A,Y,[1,1,0.5,0.5]).log();
List of supported operators and functions.
All operators work with Matrix,Vector and Scalar types as appropriate. All functions will upcast their arguments to Matrix objects if needed.
Operator | Description | Example |
---|---|---|
+ | Overloaded add | C=A+B |
- | Overloaded minus | C=A-B |
* | Overloaded multiply | C=A*B |
’ | Overloaded transpose | B=A’ |
^ | Overloaded pow | B=A^-1 |
~ | Diagonalise vector into matrix. | A=V~ |
\ | Overloaded solve | X=A\Y |
() | Parentheses | D=(A+B)*C |
Decomposition | Expression |
---|---|
Matrix.factorCholesky(A); | C=A.Cholesky |
Matrix.factorLU(A); | LU=A.LU |
Matrix.factorLUP(A); | [LU,P]=A.LUP |
Matrix.factorSVD(A,S,V); | [U,S,V]=A.SVD |
Inversion | Expression |
---|---|
Matrix.invertCholesky(A); | |
Matrix.invertLU(A); | |
Matrix.invertLUP(A); | B=A^-1 |
Matrix.invertSVD(A); |
Solver | Expression |
---|---|
Matrix.solveCholesky(C,Y); | |
Matrix.solveLU(LU,Y); | |
Matrix.solveLUP(LU,P,Y); | |
Matrix.solveSVD(U,S,V,Y) | |
Matrix.solveSeidel(A,Y,iter); | |
Matrix.solve(A,Y); | X=A\Y |
Special Matrices | Description |
---|---|
Matrix.identity(x); | Identity matrix of size x |
Matrix.hankel(V,c); | Hankel matrix of column V and count c |
Matrix.toHankel(A); | Convert to Hankel form |
Matrix.toeplitz(V,c); | Toeplitz matrix of vector V and count c |
Matrix.augment(A,B,AB); | Augment A with B |
Metrics/Norms | Description |
---|---|
Matrix.equals(A,B); | compare |
Matrix.len(A); | length matrix or vector |
Matrix.normalize(A); | matrix or vector |
Matrix.avg(A); | return average row. |
Utility | description |
---|---|
Matrix.cmul(A,B); | component wise multiply |
Matrix.print(A); | pretty console log |
Matrix.show(A,htmlElement) | Append a matrix as image. |
Matrix.row(A,x,c); | extract rows from A |
Matrix.repeat(r,x); | repeat row r x times. |
Filters | Description |
---|---|
Matrix.blackman(n); | Generate blackman vector of size n |
Matrix.hamming(n); | Generate hamming vector of size n |
Matrix.sinc(n,sampling,cutoff); | Generate a sinc vector of size n and given sampling and cutoff |