From 8e1a7b1f1877c9d47dc798d51c9ff4f9f82f3e92 Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:09:19 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- js/numerics/matrix.js | 1707 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1707 insertions(+) create mode 100644 js/numerics/matrix.js diff --git a/js/numerics/matrix.js b/js/numerics/matrix.js new file mode 100644 index 0000000..da3e961 --- /dev/null +++ b/js/numerics/matrix.js @@ -0,0 +1,1707 @@ +/** + ** ============================== + ** 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