/** ** ============================== ** O O O OOOO ** O O O O O O ** O O O O O O ** OOOO OOOO O OOO OOOO ** O O O O O O O ** O O O O O O O ** OOOO OOOO O O OOOO ** ============================== ** Dr. Stefan Bosse http://www.sblab.de ** ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED ** BY THE AUTHOR(S). ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, ** MODIFIED, OR OTHERWISE USED IN A CONTEXT ** OUTSIDE OF THE SOFTWARE SYSTEM. ** ** $AUTHORS: Stefan Bosse ** $INITIAL: (C) 2006-2019 bLAB ** $CREATED: 1-1-19 by sbosse. ** $VERSION: 1.3.3 ** ** $INFO: ** ** Numerical Matrix Module associated with typed arrays, but with genery array compatibility. ** A matrix provides a wrapper and multi-dimensional array view for one-dimensional byte arrays (typed arrays using buffers). ** ** $ENDOFINFO */ Require('numerics/polyfill') var sprintf = Require('com/sprintf'); var Vector = Require('numerics/vector'); var ALL = [], FORALL = '*', FOREACH = 'x'; isRange = function (v) { return isArray(v) && v.length==2 } isAll = function (v) { return v=='*' || (isArray(v) && v.length==0) } isForEach = function (v) { return v == FOREACH } isArrayArray = function (v) { return isArray(v) && isArray(v[0]) } isArrayArrayArray = function (v) { return isArray(v) && isArray(v[0]) && isArray(v[0][0]) } integer = function (v) { return Math.floor(v) } divide = function (a,b) { return Math.floor(a/b) } function todo (what) { throw ("Not implemented: Matrix."+what) } function checkNumber(name, value) { if (typeof value !== 'number') { throw new TypeError(name+' must be a number'); } } function transpose (layout) { switch (layout) { case 12: return 21; case 21: return 12; } } /********** TYPEDARRY/ARRAY Extension for Matrix/Vector compatibility *************/ // Most generic versions - always overwrite (polyfill/vector definitions) Object.defineProperty(Array.prototype, 'get', {value: function (i,j,k) { if (k!=undefined) return this[i][j][k]; else if (j!=undefined) return this[i][j]; else return this[i]; }, configurable: true}) Object.defineProperty(Array.prototype, 'getRow', {value: function (i) { return this[i]; }, configurable: true}) Object.defineProperty(Array.prototype, 'mapRow', {value: function (f) { return this[i].map(f); }, configurable: true}) Object.defineProperty(Array.prototype, 'set', {value: function (a,b,c,d) { if (d!=undefined) return this[a][b][c]=d; else if (c!=undefined) return this[a][b]=c; else return this[a]=b; }, configurable: true}) Object.defineProperty(Array.prototype, 'setRow', {value: function (i,row) { return this[i]=row; }, configurable: true}) Object.defineProperty(Array.prototype, 'print', {value: function (format) { var i,j,k,s='',sep='', info=this.info(); if (!format) format = '%4.2f'; switch (info.dims) { case 1: for(j=0;j max) this.errorRange('Column index out of range'); } /** * @private * Check that a row index is not out of bounds * @param {Matrix} matrix * @param {number} index * @param {boolean} [outer] */ Matrix.prototype.checkRowIndex = function(index, outer) { var max = outer ? this.rows : this.rows - 1; if (index < 0 || index > max) this.errorRange('Row index out of range'); } /** * @private * Check that the provided vector is an array with the right length * @param {Matrix} matrix * @param {Array|Matrix} vector * @return {Array} * @throws {RangeError} */ Matrix.prototype.checkRowVector = function(vector) { if (vector.to1DArray) { vector = vector.to1DArray(); } if (vector.length !== this.columns) this.errorRange( 'vector size must be the same as the number of columns' ); return vector; } /** * @private * Check that the provided vector is an array with the right length * @param {Matrix} matrix * @param {Array|Matrix} vector * @return {Array} * @throws {RangeError} */ Matrix.prototype.checkColumnVector = function(vector) { if (vector.to1DArray) { vector = vector.to1DArray(); } if (vector.length !== this.rows) this.errorRange('vector size must be the same as the number of rows'); return vector; } Matrix.prototype.checkIndices = function(rowIndices, columnIndices) { return { row: this.checkRowIndices(rowIndices), column: this.checkColumnIndices(columnIndices) }; } Matrix.prototype.checkRowIndices = function(rowIndices) { var self=this; if (typeof rowIndices !== 'object') { throw new TypeError('unexpected type for row indices'); } var rowOut = rowIndices.some(function (r) { return r < 0 || r >= self.rows; }); if (rowOut) { throw new RangeError('row indices are out of range'); } if (!Array.isArray(rowIndices)) rowIndices = Array.from(rowIndices); return rowIndices; } Matrix.prototype.checkColumnIndices = function(columnIndices) { var self=this; if (typeof columnIndices !== 'object') { throw new TypeError('unexpected type for column indices'); } var columnOut = columnIndices.some(function (c) { return c < 0 || c >= self.columns; }); if (columnOut) { throw new RangeError('column indices are out of range'); } if (!Array.isArray(columnIndices)) columnIndices = Array.from(columnIndices); return columnIndices; } Matrix.prototype.checkRange = function(startRow, endRow, startColumn, endColumn) { if (arguments.length !== 4) { throw new RangeError('expected 4 arguments'); } checkNumber('startRow', startRow); checkNumber('endRow', endRow); checkNumber('startColumn', startColumn); checkNumber('endColumn', endColumn); if ( startRow > endRow || startColumn > endColumn || startRow < 0 || startRow >= this.rows || endRow < 0 || endRow >= this.rows || startColumn < 0 || startColumn >= this.columns || endColumn < 0 || endColumn >= this.columns ) { throw new RangeError('Submatrix indices are out of range'); } } Matrix.prototype.clone = function () { return Matrix(this); } /** Copy (1) a sorurce array (vector) into this matrix (row/column w or w/o subrange), or (2) create a copy of this matrix (empty argument list) * * copy() * copy([a,b]|[],[v1,v2,...]) * copy(number,[a,b]|[],[v1,v2,...]) * copy([a,b]|[],number,[v1,v2,...]) * copy(number,number,[a,b]|[],[v1,v2,...]) * .. */ Matrix.prototype.copy = function (a,b,c,d) { var x,y,z,rx,ry,rz,i,j,k,src; if (isNumber(a)) i=a; if (isNumber(b)) j=b; if (isNumber(c)) k=c; if (isArray(a)) rx=a; if (isArray(b)) ry=b; if (isArray(c)) rz=c; if (isArray(d)) src=d; if (isVector(d)) src=d; if (!src && !d && (isArray(c) || isVector(c))) src=c,rz=undefined; if (!src && !c && (isArray(b) || isVector(b))) src=b,ry=undefined; if (!src && !a && (isArray(a) || isVector(a))) src=a,rx=[0,this.columns-1]; // 1-dim only if (isVector(src)) src=src.data; if (!src) return Matrix({ rows:this.rows, columns:this.columns, levels:this.levels, dtn:this.dtn, layout:this.layout, data:this.data.slice() }) if (!src) throw "Matrix.copy: no source array provided"; if (rx && rx.length==0) rx=[0,this.rows-1]; if (ry && ry.length==0) ry=[0,this.columns-1]; if (rz && rz.length==0) rz=[0,this.levels-1]; if (rx && (rx[1]-rx[0]+1)!=src.length) throw "Matrix.copy: range mismatch (src)" if (ry && (ry[1]-ry[0]+1)!=src.length) throw "Matrix.copy: range mismatch (src)" if (rz && (rz[1]-rz[0]+1)!=src.length) throw "Matrix.copy: range mismatch (src)" switch (this.dims) { case 1: for(x=rx[0];x number */ Matrix.prototype.convert = function (a,b,c,d) { var i,j,k,d,v,m,ni,nj,nk,filter; if (isNumber(a)) i=a; if (isNumber(b)) j=b; if (isNumber(c)) k=c; if (isString(b)) filter=b; if (isString(c)) filter=c; if (isString(d)) filter=d; if (!filter) filter='mean'; if (!i) throw "Matrix.convert: no target size (number)"; m = Matrix(i,j,k); switch (filter) { case 'mean': filter=function (a,b,i,n) { if (i==n-1) return (a+b)/n; else return a+b }; break; case 'exp': filter=function (a,b,i,n) { return (a+b)/2 }; break; case 'exp-peak': filter=function (a,b,i,n) { return (Math.abs(a)+Math.abs(b))/2 }; break; case 'peak': filter=function (a,b,i,n) { if (Math.abs(a)>Math.abs(b)) return Math.abs(a); else return Math.abs(a); }; break; case 'min': filter=function (a,b,i,n) { return ab?a:b }; break; default: filter = function () { return 0 } } switch (this.dims) { case 1: ni=Math.floor(this.i/m.i); for(i=0;i previousColumn)) { checked = true; previousColumn = j; } else { isEchelonForm = false; checked = true; } } i++; } return isEchelonForm; } Matrix.prototype.isReducedEchelonForm = function () { this.checkMatrixDims(2); var i = 0; var j = 0; var previousColumn = -1; var isReducedEchelonForm = true; var checked = false; while ((i < this.rows) && (isReducedEchelonForm)) { j = 0; checked = false; while ((j < this.columns) && (checked === false)) { if (this.get(i, j) === 0) { j++; } else if ((this.get(i, j) === 1) && (j > previousColumn)) { checked = true; previousColumn = j; } else { isReducedEchelonForm = false; checked = true; } } for (var k = j + 1; k < this.rows; k++) { if (this.get(i, k) !== 0) { isReducedEchelonForm = false; } } i++; } return isReducedEchelonForm; } Matrix.prototype.isRowVector = function () { return this.rows === 1; } Matrix.prototype.isSquare = function () { return this.rows==this.columns } Matrix.prototype.isSymmetric = function () { if (this.isSquare()) { for (var i = 0; i < this.rows; i++) { for (var j = 0; j <= i; j++) { if (this.get(i, j) !== this.get(j, i)) { return false; } } } return true; } return false; } /** Iterate over matrix elements * Parameter arrays specify iteration ranges, FORALL='*' specifies a target vector range * iter(function (@elem,@index,@array)) * iter(number [],function) * iter(number [],number [],function) * iter(number [],number [],number [],function) * Examples: * m.iter(FORALL,[],[],f) <=> for all x-vectors with y in [0,j-1], z in [0,k-1] do .. * m.iter([], FORALL,[],f) <=> for all y-vectors with x in [0,j-1], z in [0,k-1] do .. * m.iter([],[],[],f) <=> for all values with x in [0,i-1], y in [0,j-1], z in [0,k-1] do .. * m.iter(f) <=> for all values with x in [0,i-1], y in [0,j-1], z in [0,k-1] do .. * * */ Matrix.prototype.iter = function (a,b,c,d) { var func,rx,ry,rz,x,y,z, self=this; if (isFunction(a)) func=a; else if (isFunction(b)) func=b; else if (isFunction(c)) func=c; else if (isFunction(d)) func=d; if (isArray(a)) rx=a; if (isArray(b)) ry=b; if (isArray(c)) rz=c; if (isString(a)) rx=a; if (isString(b)) ry=b; if (isString(c)) rz=c; if (!func) throw "Matrx.iter: no function supplied"; if (!rx && !ry && !rz) // linear iteration over all elements return this.data.forEach(func); switch (this.dims) { case 1: break; // TODO todo('iter 1') case 2: break; // TODO todo('iter 2') case 3: if (isArray(rx) && rx.length==0) rx=[0,this.rows]; if (isArray(ry) && ry.length==0) ry=[0,this.columns]; if (isArray(rz) && rz.length==0) rz=[0,this.levels]; if (rz == FORALL) { for(x=rx[0];x c to all elements. * Returns a scalar value or any other object accumulated by the supplied function */ Matrix.prototype.reduce = function (f) { return this.data.reduce(f); } /** resize matrix (only modifying meta data - not buffer data) * */ Matrix.prototype.resize = function (options) { for(var p in options) { switch (p) { case 'rows': case 'columns': case 'levels': this[p]=options[p]; break; } } this.size=this.columns*(this.rows?this.rows:1)*(this.levels?this.levels:1); this.length=this.rows?this.rows:this.columns; return this } Matrix.prototype.reverseRow = function (row) { var t,len=this.columns; for(var i=0;i<(len/2)|0;i++) { t=this.get(row,i); this.set(row,i,this.get(row,len-i-1)); this.set(row,len-i-1,t); } return this; } /** Scale (and/or adjust offset optionally of) all matrix elements -= offset *= k * scale(k) * scale(k,inplace:boolean) * scale(k,offset) * scale(k,offset,inplace:boolean) */ Matrix.prototype.scale = function (a,b,c) { var m,k=1,offset,inplace=false; if (isNumber(a)) k=a; if (isBoolean(b)) inplace=b; else if (isBoolean(c)) inplace=c; if (isNumber(b)) offset=b; else if (isNumber(c)) offset=c; m = inplace?this:this.copy(); if (k!=1) { if (offset) for(var i=0;i, columnIndices: Array): Matrix Parameters rowIndices (Array) The row indices to select. Order matters and an index can be more than once. columnIndices (Array) The column indices to select. Order matters and an index can be use more than once. Returns Matrix: The new matrix */ Matrix.prototype.selection = function (rowIndices,columnIndices) { this.checkMatrixDims(2); var newMatrix = Matrix(rowIndices.length,columnIndices.length,{dtn:this.dtn}); for (var i = 0; i < rowIndices.length; i++) { var rowIndex = rowIndices[i]; for (var j = 0; j < columnIndices.length; j++) { var columnIndex = columnIndices[j]; newMatrix.set(i,j, this.get(rowIndex, columnIndex)); } } return newMatrix; } // Set a row of the matrix Matrix.prototype.setRow = function (row,data) { data=Matrix.checkArray(data); for(var i=0;i endColumn) || (startColumn < 0) || (startColumn >= this.columns) || (endColumn < 0) || (endColumn >= this.columns)) { throw new RangeError('Argument out of range'); } var newMatrix = Matrix(indices.length, endColumn - startColumn + 1, {dtn:this.dtn}); for (var i = 0; i < indices.length; i++) { for (var j = startColumn; j <= endColumn; j++) { if (indices[i] < 0 || indices[i] >= this.rows) { throw new RangeError('Row index out of range: '+indices[i]); } newMatrix.set(i, j - startColumn, this.get(indices[i], j)); } } return newMatrix; } Matrix.prototype.subMatrixColumn = function (indices, startRow, endRow) { this.checkMatrixDims(2); if (startRow === undefined) startRow = 0; if (endRow === undefined) endRow = this.rows - 1; if ((startRow > endRow) || (startRow < 0) || (startRow >= this.rows) || (endRow < 0) || (endRow >= this.rows)) { throw new RangeError('Argument out of range'); } var newMatrix = Matrix(endRow - startRow + 1, indices.length, {dtn:this.dtn}); for (var i = 0; i < indices.length; i++) { for (var j = startRow; j <= endRow; j++) { if (indices[i] < 0 || indices[i] >= this.columns) { throw new RangeError('Column index out of range: '+indices[i]); } newMatrix.set(j - startRow, i, this.get(j, indices[i])); } } return newMatrix; } Matrix.prototype.subRowVector = function (vector) { this.checkMatrixDims(2); vector = this.checkRowVector(vector); for (var i = 0; i < this.rows; i++) { for (var j = 0; j < this.columns; j++) { this.set(i, j, this.get(i, j) - vector[j]); } } return this; } Matrix.prototype.setSubMatrix = function (matrix, startRow, startColumn) { matrix = this.checkMatrix(matrix); this.checkMatrixDims(2); matrix.checkMatrixDims(2); var endRow = startRow + matrix.rows - 1; var endColumn = startColumn + matrix.columns - 1; this.checkRange(startRow, endRow, startColumn, endColumn); for (var i = 0; i < matrix.rows; i++) { for (var j = 0; j < matrix.columns; j++) { this.set(startRow + i,startColumn + j) = matrix.get(i, j); } } return this; } Matrix.prototype.sum = function (by) { var i,j,k,v=0; switch (by) { case 'row': return this.sumByRow(); case 'column': return this.sumByColumn(); default: switch (this.dtn+this.dims) { case 'Array1': for (i = 0; i < this.columns; i++) { v += this.data[i]; } break; case 'Array2': for (i = 0; i < this.rows; i++) { for (j = 0; j < this.columns; j++) { v += this.data[i][j]; } } break; case 'Array3': for (i = 0; i < this.rows; i++) { for (j = 0; j < this.columns; j++) { for (k = 0; k < this.levels; k++) { v += this.data[i][j][k]; } } } break; default: for (i = 0; i < this.size; i++) v += this.data[i]; } return v; } } Matrix.prototype.sumByRow = function () { var sum = Matrix.zeros(this.rows, 1); for (var i = 0; i < this.rows; ++i) { for (var j = 0; j < this.columns; ++j) { sum.set(i, 0, sum.get(i, 0) + this.get(i, j)); } } return sum; } Matrix.prototype.sumByColumn = function() { var sum = Matrix.zeros(1, this.columns); for (var i = 0; i < this.rows; ++i) { for (var j = 0; j < this.columns; ++j) { sum.set(0, j, sum.get(0, j) + this.get(i, j)); } } return sum; } Matrix.prototype.toArray = function (rx,ry,rz) { switch (this.dims) { case 1: return Array.from(this.data); case 2: todo('toArray 2') case 3: todo('toArray 3') } } Matrix.ALL=ALL; Matrix.FOREACH=FOREACH; Matrix.FORALL=FORALL; module.exports = Matrix