mirror of
https://github.com/josdejong/mathjs.git
synced 2025-12-08 19:46:04 +00:00
709 lines
18 KiB
JavaScript
709 lines
18 KiB
JavaScript
var util = require('../util/index'),
|
|
Index = require('./Index'),
|
|
|
|
number = util.number,
|
|
string = util.string,
|
|
array = util.array,
|
|
object = util.object,
|
|
|
|
isArray = Array.isArray;
|
|
|
|
/**
|
|
* @constructor Matrix
|
|
*
|
|
* A Matrix is a wrapper around an Array. A matrix can hold a multi dimensional
|
|
* array. A matrix can be constructed as:
|
|
* var matrix = new Matrix(data)
|
|
*
|
|
* Matrix contains the functions to resize, get and set values, get the size,
|
|
* clone the matrix and to convert the matrix to a vector, array, or scalar.
|
|
* Furthermore, one can iterate over the matrix using map and forEach.
|
|
* The internal Array of the Matrix can be accessed using the method valueOf.
|
|
*
|
|
* Example usage:
|
|
* var matrix = new Matrix([[1, 2], [3, 4]);
|
|
* matix.size(); // [2, 2]
|
|
* matrix.resize([3, 2], 5);
|
|
* matrix.valueOf(); // [[1, 2], [3, 4], [5, 5]]
|
|
* matrix.subset([1,2]) // 3 (indexes are zero-based)
|
|
*
|
|
* @param {Array | Matrix} [data] A multi dimensional array
|
|
*/
|
|
function Matrix(data) {
|
|
if (!(this instanceof Matrix)) {
|
|
throw new SyntaxError(
|
|
'Matrix constructor must be called with the new operator');
|
|
}
|
|
|
|
if (data instanceof Matrix) {
|
|
// clone data from a Matrix
|
|
this._data = data.clone()._data;
|
|
}
|
|
else if (Array.isArray(data)) {
|
|
// use array as is
|
|
this._data = data;
|
|
}
|
|
else if (data != null) {
|
|
// unsupported type
|
|
throw new TypeError('Unsupported type of data (' + util.types.type(data) + ')');
|
|
}
|
|
else {
|
|
// nothing provided
|
|
this._data = [];
|
|
}
|
|
|
|
// verify the size of the array
|
|
this._size = array.size(this._data);
|
|
}
|
|
|
|
/**
|
|
* Test whether an object is a Matrix
|
|
* @param {*} object
|
|
* @return {Boolean} isMatrix
|
|
*/
|
|
Matrix.isMatrix = function isMatrix(object) {
|
|
return (object instanceof Matrix);
|
|
};
|
|
|
|
/**
|
|
* Get a subset of the matrix, or replace a subset of the matrix.
|
|
*
|
|
* Usage:
|
|
* var subset = matrix.subset(index) // retrieve subset
|
|
* var value = matrix.subset(index, replacement) // replace subset
|
|
*
|
|
* @param {Index} index
|
|
* @param {Array | Matrix | *} [replacement]
|
|
*/
|
|
Matrix.prototype.subset = function subset(index, replacement) {
|
|
switch (arguments.length) {
|
|
case 1: return _get(this, index);
|
|
case 2: return _set(this, index, replacement);
|
|
default:
|
|
throw new util.error.ArgumentsError('subset', arguments.length, 1, 2);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a value or a submatrix of the matrix.
|
|
* @param {Index} index Zero-based index
|
|
*/
|
|
// TODO: Matrix.get is deprecated since version 0.13.0, remove it some day
|
|
Matrix.prototype.get = function get(index) {
|
|
throw new Error('Matrix.get is removed. Use matrix.subet(index) instead.');
|
|
};
|
|
|
|
|
|
/**
|
|
* Replace a value or a submatrix in the matrix.
|
|
* Indexes are zero-based.
|
|
* @param {Index} index
|
|
* @param {Matrix | Array | *} submatrix
|
|
* @return {Matrix} matrix
|
|
*/
|
|
// TODO: Matrix.set is deprecated since version 0.13.0, remove it some day
|
|
Matrix.prototype.set = function set (index, submatrix) {
|
|
throw new Error('Matrix.set is removed. Use matrix.subet(index, replacement) instead.');
|
|
};
|
|
|
|
/**
|
|
* Get a value or a submatrix of the matrix.
|
|
* @param {Matrix} matrix
|
|
* @param {Index} index Zero-based index
|
|
*/
|
|
function _get (matrix, index) {
|
|
if (!(index instanceof Index)) {
|
|
throw new TypeError('Invalid index');
|
|
}
|
|
|
|
var size = index.size();
|
|
var isScalar = !size.some(function (i) {
|
|
return (i != 1);
|
|
});
|
|
|
|
if (size.length != matrix._size.length) {
|
|
throw new RangeError('Dimension mismatch ' +
|
|
'(' + size.length + ' != ' + matrix._size.length + ')');
|
|
}
|
|
|
|
if (isScalar) {
|
|
// return a single value
|
|
switch (size.length) {
|
|
case 1: return _getScalar1D(matrix._data, index);
|
|
case 2: return _getScalar2D(matrix._data, index);
|
|
default: return _getScalar(matrix._data, index);
|
|
}
|
|
}
|
|
else {
|
|
// return a submatrix
|
|
var submatrix;
|
|
switch (size.length) {
|
|
case 1: submatrix = new Matrix(_getSubmatrix1D(matrix._data, index)); break;
|
|
case 2: submatrix = new Matrix(_getSubmatrix2D(matrix._data, index)); break;
|
|
default: submatrix = new Matrix(_getSubmatrix(matrix._data, index, 0)); break;
|
|
}
|
|
// TODO: more efficient when creating an empty matrix and setting _data and _size manually
|
|
|
|
// squeeze matrix output
|
|
while (isArray(submatrix._data) && submatrix._data.length == 1) {
|
|
submatrix._data = submatrix._data[0];
|
|
submatrix._size.shift();
|
|
}
|
|
|
|
return submatrix;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a single value from an array. The method tests whether:
|
|
* - index is a non-negative integer
|
|
* - index does not exceed the dimensions of array
|
|
* @param {Array} arr
|
|
* @param {Number} index Zero-based index
|
|
* @return {*} value
|
|
* @private
|
|
*/
|
|
function _getElement (arr, index) {
|
|
array.validateIndex(index, arr.length);
|
|
return arr[index]; // zero-based index
|
|
}
|
|
|
|
/**
|
|
* Get a single value from the matrix. The value will be a copy of the original
|
|
* value in the matrix.
|
|
* Index is not checked for correct number of dimensions.
|
|
* @param {Array} data
|
|
* @param {Index} index
|
|
* @return {*} scalar
|
|
* @private
|
|
*/
|
|
function _getScalar (data, index) {
|
|
index.forEach(function (range) {
|
|
data = _getElement(data, range.start);
|
|
});
|
|
return object.clone(data);
|
|
}
|
|
|
|
/**
|
|
* Get a single value from a one dimensional matrix.
|
|
* The value will be a copy of the original value in the matrix.
|
|
* Index is not checked for correct number of dimensions.
|
|
* @param {Array} data
|
|
* @param {Index} index
|
|
* @return {*} scalar
|
|
* @private
|
|
*/
|
|
function _getScalar1D (data, index) {
|
|
return _getElement(data, index.range(0).start);
|
|
}
|
|
|
|
/**
|
|
* Get a single value from a two dimensional matrix.
|
|
* The value will be a copy of the original value in the matrix.
|
|
* Index is not checked for correct number of dimensions.
|
|
* @param {Array} data
|
|
* @param {Index} index
|
|
* @return {*} scalar
|
|
* @private
|
|
*/
|
|
function _getScalar2D (data, index) {
|
|
return _getElement(_getElement(data, index.range(0).start), index.range(1).start);
|
|
}
|
|
|
|
/**
|
|
* Get a submatrix of a zero dimensional matrix.
|
|
* Index is not checked for correct number of dimensions.
|
|
* @param {Array} data
|
|
* @param {Index} index
|
|
* @return {Array} submatrix
|
|
* @private
|
|
*/
|
|
function _getSubmatrix1D (data, index) {
|
|
var range = index.range(0);
|
|
|
|
return range.map(function (i) {
|
|
return _getElement(data, i);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get a submatrix of a 2 dimensional matrix.
|
|
* Index is not checked for correct number of dimensions.
|
|
* @param {Array} data
|
|
* @param {Index} index
|
|
* @return {Array} submatrix
|
|
* @private
|
|
*/
|
|
function _getSubmatrix2D (data, index) {
|
|
var rows = index.range(0);
|
|
var cols = index.range(1);
|
|
|
|
return rows.map(function (row) {
|
|
var child = _getElement(data, row);
|
|
return cols.map(function (col) {
|
|
return _getElement(child, col);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get a submatrix of a multi dimensional matrix.
|
|
* Index is not checked for correct number of dimensions.
|
|
* @param {Array} data
|
|
* @param {Index} index
|
|
* @param {number} dim
|
|
* @return {Array} submatrix
|
|
* @private
|
|
*/
|
|
function _getSubmatrix (data, index, dim) {
|
|
var last = (dim == index.size().length - 1);
|
|
var range = index.range(dim);
|
|
|
|
if (last) {
|
|
return range.map(function (i) {
|
|
return _getElement(data, i);
|
|
});
|
|
}
|
|
else {
|
|
return range.map(function (i) {
|
|
var child = _getElement(data, i);
|
|
return _getSubmatrix(child, index, dim + 1);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replace a value or a submatrix in the matrix.
|
|
* Indexes are zero-based.
|
|
* @param {Matrix} matrix
|
|
* @param {Index} index
|
|
* @param {Matrix | Array | *} submatrix
|
|
* @return {Matrix} matrix
|
|
*/
|
|
function _set (matrix, index, submatrix) {
|
|
if (!(index instanceof Index)) {
|
|
throw new TypeError('Invalid index');
|
|
}
|
|
|
|
var size = index.size();
|
|
var isScalar = !size.some(function (i) {
|
|
return (i != 1);
|
|
});
|
|
|
|
if (size.length < matrix._size.length) {
|
|
throw new RangeError('Dimension mismatch ' +
|
|
'(' + size.length + ' != ' + matrix._size.length + ')');
|
|
}
|
|
|
|
if (submatrix instanceof Matrix) {
|
|
submatrix = submatrix.valueOf();
|
|
}
|
|
|
|
// calculate the size of the submatrix
|
|
var subsize = array.size(submatrix);
|
|
|
|
if (isScalar) {
|
|
// set a scalar
|
|
|
|
// check whether submatrix is a scalar
|
|
if (subsize.length != 0) {
|
|
throw new TypeError('Scalar value expected');
|
|
}
|
|
|
|
switch (index.length) {
|
|
case 1: _setScalar1D(matrix._data, matrix._size, index, submatrix); break;
|
|
case 2: _setScalar2D(matrix._data, matrix._size, index, submatrix); break;
|
|
default: _setScalar(matrix._data, matrix._size, index, submatrix); break;
|
|
}
|
|
}
|
|
else {
|
|
// set a submatrix
|
|
|
|
// unsqueeze the submatrix when needed
|
|
for (var i = 0, ii = size.length - subsize.length; i < ii; i++) {
|
|
submatrix = [submatrix];
|
|
}
|
|
|
|
var newSize = matrix._size.concat();
|
|
_setSubmatrix (matrix._data, newSize, index, 0, submatrix);
|
|
if (!object.deepEqual(matrix._size, newSize)) {
|
|
_init(matrix._data);
|
|
matrix.resize(newSize);
|
|
}
|
|
}
|
|
|
|
return matrix;
|
|
}
|
|
|
|
/**
|
|
* Replace a single value in an array. The method tests whether index is a
|
|
* non-negative integer
|
|
* @param {Array} arr
|
|
* @param {Number} index Zero-based index
|
|
* @param {*} value
|
|
* @private
|
|
*/
|
|
function _setElement (arr, index, value) {
|
|
array.validateIndex(index);
|
|
if (Array.isArray(value)) {
|
|
throw new TypeError('Dimension mismatch, value expected instead of array');
|
|
}
|
|
arr[index] = value; // zero-based index
|
|
}
|
|
|
|
/**
|
|
* Replace a single value in a multi dimensional matrix
|
|
* @param {Array} data
|
|
* @param {Number[]} size
|
|
* @param {Index} index
|
|
* @param {*} value
|
|
* @private
|
|
*/
|
|
function _setScalar (data, size, index, value) {
|
|
var resized = false;
|
|
if (index.size().length > size.length) {
|
|
// dimension added
|
|
resized = true;
|
|
}
|
|
|
|
index.forEach(function (range, i) {
|
|
var v = range.start;
|
|
array.validateIndex(v);
|
|
if ((size[i] == null) || (v + 1 > size[i])) {
|
|
size[i] = v + 1; // size is index + 1 as index is zero-based
|
|
resized = true;
|
|
}
|
|
});
|
|
|
|
if (resized) {
|
|
array.resize(data, size, 0);
|
|
}
|
|
|
|
var len = size.length;
|
|
index.forEach(function (range, dim) {
|
|
var i = range.start;
|
|
if (dim < len - 1) {
|
|
data = data[i]; // zero-based index
|
|
}
|
|
else {
|
|
data[i] = value; // zero-based index
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Replace a single value in a zero dimensional matrix
|
|
* @param {Array} data
|
|
* @param {Number[]} size
|
|
* @param {Index} index
|
|
* @param {*} value
|
|
* @private
|
|
*/
|
|
function _setScalar1D (data, size, index, value) {
|
|
var row = index.range(0).start;
|
|
array.validateIndex(row);
|
|
if (row + 1 > size[0]) {
|
|
array.resize(data, [row + 1], 0); // size is index + 1 as index is zero-based
|
|
size[0] = row + 1;
|
|
}
|
|
data[row] = value; // zero-based index
|
|
}
|
|
|
|
/**
|
|
* Replace a single value in a two dimensional matrix
|
|
* @param {Array} data
|
|
* @param {Number[]} size
|
|
* @param {Index} index zero-based index
|
|
* @param {*} value
|
|
* @private
|
|
*/
|
|
function _setScalar2D (data, size, index, value) {
|
|
var row = index.range(0).start;
|
|
var col = index.range(1).start;
|
|
array.validateIndex(row);
|
|
array.validateIndex(col);
|
|
|
|
var resized = false;
|
|
if (row + 1 > (size[0] || 0)) {
|
|
size[0] = row + 1; // size is index + 1 as index is zero-based
|
|
resized = true;
|
|
}
|
|
if (col + 1 > (size[1] || 0)) {
|
|
size[1] = col + 1; // size is index + 1 as index is zero-based
|
|
resized = true;
|
|
}
|
|
if (resized) {
|
|
array.resize(data, size, 0);
|
|
}
|
|
|
|
data[row][col] = value; // zero-based index
|
|
}
|
|
|
|
/**
|
|
* Replace a submatrix of a multi dimensional matrix.
|
|
* @param {Array} data
|
|
* @param {Array} size
|
|
* @param {Index} index
|
|
* @param {number} dim
|
|
* @param {Array} submatrix
|
|
* @private
|
|
*/
|
|
function _setSubmatrix (data, size, index, dim, submatrix) {
|
|
var last = (dim == index.size().length - 1);
|
|
var range = index.range(dim);
|
|
|
|
var len = (range.size()[0]);
|
|
if (len != submatrix.length) {
|
|
throw new RangeError('Dimensions mismatch ' +
|
|
'(' + len + ' != '+ submatrix.length + ')');
|
|
}
|
|
|
|
if (last) {
|
|
range.forEach(function (dataIndex, subIndex) {
|
|
_setElement(data, dataIndex, submatrix[subIndex]);
|
|
if (dataIndex + 1 > (size[dim] || 0)) {
|
|
size[dim] = dataIndex + 1;
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
range.forEach(function (dataIndex, subIndex) {
|
|
var child = data[dataIndex];
|
|
if (!Array.isArray(child)) {
|
|
data[dataIndex] = child = [child];
|
|
}
|
|
if (dataIndex + 1 > (size[dim] || 0)) {
|
|
size[dim] = dataIndex + 1;
|
|
}
|
|
_setSubmatrix(child, size, index, dim + 1, submatrix[subIndex]);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively initialize all undefined values in the array with zeros
|
|
* @param array
|
|
* @private
|
|
*/
|
|
function _init(array) {
|
|
for (var i = 0, len = array.length; i < len; i++) {
|
|
var value = array[i];
|
|
if (Array.isArray(value)) {
|
|
_init(value);
|
|
}
|
|
else if (value == undefined) {
|
|
array[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resize the matrix
|
|
* @param {Number[]} size
|
|
* @param {*} [defaultValue] Default value, filled in on new entries.
|
|
* If not provided, the matrix will be filled
|
|
* with zeros.
|
|
*/
|
|
Matrix.prototype.resize = function (size, defaultValue) {
|
|
array.resize(this._data, size, defaultValue);
|
|
this._size = object.clone(size);
|
|
};
|
|
|
|
/**
|
|
* Create a clone of the matrix
|
|
* @return {Matrix} clone
|
|
*/
|
|
Matrix.prototype.clone = function () {
|
|
var matrix = new Matrix();
|
|
matrix._data = object.clone(this._data);
|
|
matrix._size = object.clone(this._size);
|
|
return matrix;
|
|
};
|
|
|
|
/**
|
|
* Retrieve the size of the matrix.
|
|
* @returns {Number[]} size
|
|
*/
|
|
Matrix.prototype.size = function () {
|
|
return this._size;
|
|
};
|
|
|
|
/**
|
|
* Create a new matrix with the results of the callback function executed on
|
|
* each entry of the matrix.
|
|
* @param {function} callback The callback method is invoked with three
|
|
* parameters: the value of the element, the index
|
|
* of the element, and the Matrix being traversed.
|
|
* @return {Matrix} matrix
|
|
*/
|
|
Matrix.prototype.map = function (callback) {
|
|
var me = this;
|
|
var matrix = new Matrix();
|
|
var index = [];
|
|
var recurse = function (value, dim) {
|
|
if (Array.isArray(value)) {
|
|
return value.map(function (child, i) {
|
|
index[dim] = i; // zero-based index
|
|
return recurse(child, dim + 1);
|
|
});
|
|
}
|
|
else {
|
|
return callback(value, index, me);
|
|
}
|
|
};
|
|
matrix._data = recurse(this._data, 0);
|
|
matrix._size = object.clone(this._size);
|
|
|
|
return matrix;
|
|
};
|
|
|
|
/**
|
|
* Execute a callback method on each entry of the matrix.
|
|
* @param {function} callback The callback method is invoked with three
|
|
* parameters: the value of the element, the index
|
|
* of the element, and the Matrix being traversed.
|
|
*/
|
|
Matrix.prototype.forEach = function (callback) {
|
|
var me = this;
|
|
var index = [];
|
|
var recurse = function (value, dim) {
|
|
if (Array.isArray(value)) {
|
|
value.forEach(function (child, i) {
|
|
index[dim] = i; // zero-based index
|
|
recurse(child, dim + 1);
|
|
});
|
|
}
|
|
else {
|
|
callback(value, index, me);
|
|
}
|
|
};
|
|
recurse(this._data, 0);
|
|
};
|
|
|
|
/**
|
|
* Create a scalar with a copy of the data of the Matrix
|
|
* Will return null if the matrix does not consist of a scalar value
|
|
* @return {* | null} scalar
|
|
*/
|
|
Matrix.prototype.toScalar = function () {
|
|
var scalar = this._data;
|
|
while (Array.isArray(scalar) && scalar.length == 1) {
|
|
scalar = scalar[0];
|
|
}
|
|
|
|
if (Array.isArray(scalar)) {
|
|
return null;
|
|
}
|
|
else {
|
|
return object.clone(scalar);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Test whether the matrix is a scalar.
|
|
* @return {boolean} isScalar
|
|
*/
|
|
Matrix.prototype.isScalar = function () {
|
|
return this._size.every(function (s) {
|
|
return (s <= 1);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Create a vector with a copy of the data of the Matrix
|
|
* Returns null if the Matrix does not contain a vector
|
|
*
|
|
* A matrix is a vector when it has 0 or 1 dimensions, or has multiple
|
|
* dimensions where maximum one of the dimensions has a size larger than 1.
|
|
* return {Array | null} vector
|
|
*/
|
|
Matrix.prototype.toVector = function () {
|
|
var count = 0;
|
|
var dim = undefined;
|
|
var index = [];
|
|
this._size.forEach(function (length, i) {
|
|
if (length > 1) {
|
|
count++;
|
|
dim = i;
|
|
}
|
|
index[i] = 0;
|
|
});
|
|
|
|
if (count == 0) {
|
|
// scalar or empty
|
|
var scalar = this.toScalar();
|
|
if (scalar) {
|
|
return [scalar];
|
|
}
|
|
else {
|
|
return [];
|
|
}
|
|
}
|
|
else if (count == 1) {
|
|
// valid vector
|
|
var vector = [];
|
|
var recurse = function (data) {
|
|
if (Array.isArray(data)) {
|
|
data.forEach(recurse);
|
|
}
|
|
else {
|
|
vector.push(data);
|
|
}
|
|
};
|
|
recurse(this._data);
|
|
return vector;
|
|
}
|
|
else {
|
|
// count > 1, this is no vector
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Test if the matrix contains a vector.
|
|
* A matrix is a vector when it has 0 or 1 dimensions, or has multiple
|
|
* dimensions where maximum one of the dimensions has a size larger than 1.
|
|
* return {boolean} isVector
|
|
*/
|
|
Matrix.prototype.isVector = function () {
|
|
var count = 0;
|
|
this._size.forEach(function (length) {
|
|
if (length > 1) {
|
|
count++;
|
|
}
|
|
});
|
|
return (count <= 1);
|
|
};
|
|
|
|
/**
|
|
* Create an Array with a copy of the data of the Matrix
|
|
* @returns {Array} array
|
|
*/
|
|
Matrix.prototype.toArray = function () {
|
|
return object.clone(this._data);
|
|
};
|
|
|
|
/**
|
|
* Get the primitive value of the Matrix: a multidimensional array
|
|
* @returns {Array} array
|
|
*/
|
|
Matrix.prototype.valueOf = function () {
|
|
return this._data;
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the matrix
|
|
* @returns {String} str
|
|
*/
|
|
Matrix.prototype.toString = function () {
|
|
return string.format(this._data);
|
|
};
|
|
|
|
// exports
|
|
module.exports = Matrix;
|
|
|
|
// to trick my IDE which doesn't get it
|
|
exports.isMatrix = Matrix.isMatrix;
|
|
|
|
util.types.addType('matrix', Matrix);
|