mathjs
Version:
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser and offers an integrated solution to work with numbers, big numbers, complex numbers, units, and matrices.
1,210 lines (1,116 loc) • 35.3 kB
JavaScript
'use strict';
var util = require('../../util/index');
var DimensionError = require('../../error/DimensionError');
var array = util.array;
var object = util.object;
var string = util.string;
var number = util.number;
var isArray = Array.isArray;
var isNumber = util.number.isNumber;
var isInteger = util.number.isInteger;
var validateIndex = array.validateIndex;
module.exports = function (math) {
var Index = math.type.Index,
BigNumber = math.type.BigNumber,
Matrix = math.type.Matrix;
function CcsMatrix(data) {
if (!(this instanceof CcsMatrix))
throw new SyntaxError('Constructor must be called with the new operator');
if (data instanceof Matrix) {
// check data is a CcsMatrix
if (data.type === 'CcsMatrix') {
// clone arrays
this._values = object.clone(data._values);
this._index = object.clone(data._index);
this._ptr = object.clone(data._ptr);
this._size = object.clone(data._size);
}
else {
// build from matrix data
_createFromArray(this, data.valueOf());
}
}
else if (data && isArray(data.values) && isArray(data.index) && isArray(data.ptr) && isArray(data.size)) {
// initialize fields
this._values = data.values;
this._index = data.index;
this._ptr = data.ptr;
this._size = data.size;
}
else if (isArray(data)) {
// create from array
_createFromArray(this, data);
}
else if (data) {
// unsupported type
throw new TypeError('Unsupported type of data (' + util.types.type(data) + ')');
}
else {
// nothing provided
this._values = [];
this._index = [];
this._ptr = [0];
this._size = [0];
}
}
var _createFromArray = function (matrix, data) {
// initialize fields
matrix._values = [];
matrix._index = [];
matrix._ptr = [];
// discover rows & columns, do not use math.size() to avoid looping array twice
var rows = data.length;
var columns = 0;
// check we have rows (empty array)
if (rows > 0) {
// column index
var j = 0;
do {
// store pointer to values index
matrix._ptr.push(matrix._values.length);
// loop rows
for (var i = 0; i < rows; i++) {
// current row
var row = data[i];
// check row is an array
if (isArray(row)) {
// update columns if needed (only on first column)
if (j ===0 && columns < row.length)
columns = row.length;
// check row has column
if (j < row.length) {
// value
var v = row[j];
// check value != 0
if (!math.equal(v, 0)) {
// store value
matrix._values.push(v);
// index
matrix._index.push(i);
}
}
}
else {
// update columns if needed (only on first column)
if (j === 0 && columns < 1)
columns = 1;
// check value != 0 (row is a scalar)
if (!math.equal(row, 0)) {
// store value
matrix._values.push(row);
// index
matrix._index.push(i);
}
}
}
// increment index
j++;
}
while (j < columns);
}
// store number of values in ptr
matrix._ptr.push(matrix._values.length);
// size
matrix._size = [rows, columns];
};
CcsMatrix.prototype = new math.type.Matrix();
CcsMatrix.prototype.type = 'CcsMatrix';
/**
* Get the storage format used by the matrix.
*
* Usage:
* var format = matrix.storage() // retrieve storage format
*
* @return {string} The storage format.
*/
CcsMatrix.prototype.storage = function () {
return 'ccs';
};
/**
* 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 | Maytrix | *} [replacement]
* @param {*} [defaultValue=0] Default value, filled in on new entries when
* the matrix is resized. If not provided,
* new matrix elements will be filled with zeros.
*/
CcsMatrix.prototype.subset = function (index, replacement, defaultValue) {
// check arguments
switch (arguments.length) {
case 1:
return _getsubset(this, index);
// intentional fall through
case 2:
case 3:
return _setsubset(this, index, replacement, defaultValue);
default:
throw new SyntaxError('Wrong number of arguments');
}
};
var _getsubset = function (matrix, index) {
// check index
if (!(index instanceof Index)) {
throw new TypeError('Invalid index');
}
var isScalar = index.isScalar();
if (isScalar) {
// return a scalar
return matrix.get(index.min());
}
// validate dimensions
var size = index.size();
if (size.length != matrix._size.length) {
throw new DimensionError(size.length, matrix._size.length);
}
// validate if any of the ranges in the index is out of range
var min = index.min();
var max = index.max();
for (var i = 0, ii = matrix._size.length; i < ii; i++) {
validateIndex(min[i], matrix._size[i]);
validateIndex(max[i], matrix._size[i]);
}
// map callback
var callback = function (v) {
// return value
return v;
};
// get sub-matrix
return _map(matrix, min[0], max[0], min[1], max[1], callback, false);
};
var _setsubset = function (matrix, index, submatrix, defaultValue) {
// check index
if (!(index instanceof Index)) {
throw new TypeError('Invalid index');
}
// get index size and check whether the index contains a single value
var iSize = index.size(),
isScalar = index.isScalar();
// calculate the size of the submatrix, and convert it into an Array if needed
var sSize;
if (submatrix instanceof Matrix) {
// submatrix size
sSize = submatrix.size();
// use array representation
submatrix = submatrix.toArray();
}
else {
// get submatrix size (array, scalar)
sSize = array.size(submatrix);
}
// check index is a scalar
if (isScalar) {
// verify submatrix is a scalar
if (sSize.length !== 0) {
throw new TypeError('Scalar expected');
}
// set value
matrix.set(index.min(), submatrix, defaultValue);
}
else {
// validate dimensions, index size must be one or two dimensions
if (iSize.length !== 1 && iSize.length !== 2) {
throw new DimensionError(iSize.length, matrix._size.length, '<');
}
// check submatrix and index have the same dimensions
if (sSize.length < iSize.length) {
// calculate number of missing outer dimensions
var i = 0;
var outer = 0;
while (iSize[i] === 1 && sSize[i] === 1) {
i++;
}
while (iSize[i] === 1) {
outer++;
i++;
}
// unsqueeze both outer and inner dimensions
submatrix = array.unsqueeze(submatrix, iSize.length, outer, sSize);
}
// check whether the size of the submatrix matches the index size
if (!object.deepEqual(iSize, sSize)) {
throw new DimensionError(iSize, sSize, '>');
}
// offsets
var x0 = index.min()[0];
var y0 = index.min()[1];
// submatrix rows and columns
var m = sSize[0];
var n = sSize[1];
// loop submatrix
for (var x = 0; x < m; x++) {
// loop columns
for (var y = 0; y < n; y++) {
// value at i, j
var v = submatrix[x][y];
// invoke set (zero value will remove entry from matrix)
matrix.set([x + x0, y + y0], v, defaultValue);
}
}
}
return matrix;
};
/**
* Get a single element from the matrix.
* @param {Number[]} index Zero-based index
* @return {*} value
*/
CcsMatrix.prototype.get = function (index) {
if (!isArray(index))
throw new TypeError('Array expected');
if (index.length != this._size.length)
throw new DimensionError(index.length, this._size.length);
// row and column
var i = index[0];
var j = index[1];
// check i, j are valid
validateIndex(i, this._size[0]);
validateIndex(j, this._size[1]);
// find value index
var k = _getValueIndex(i, this._ptr[j], this._ptr[j + 1], this._index);
// check k is prior to next column k and it is in the correct row
if (k < this._ptr[j + 1] && this._index[k] === i)
return object.clone(this._values[k]);
return 0;
};
/**
* Replace a single element in the matrix.
* @param {Number[]} index Zero-based index
* @param {*} value
* @param {*} [defaultValue] Default value, filled in on new entries when
* the matrix is resized. If not provided,
* new matrix elements will be set to zero.
* @return {CcsMatrix} self
*/
CcsMatrix.prototype.set = function (index, v, defaultValue) {
if (!isArray(index))
throw new TypeError('Array expected');
if (index.length != this._size.length)
throw new DimensionError(index.length, this._size.length);
// row and column
var i = index[0];
var j = index[1];
// rows & columns
var rows = this._size[0];
var columns = this._size[1];
// check we need to resize matrix
if (i > rows - 1 || j > columns - 1) {
// resize matrix
_resize(this, Math.max(i + 1, rows), Math.max(j + 1, columns), defaultValue);
// update rows & columns
rows = this._size[0];
columns = this._size[1];
}
// check i, j are valid
validateIndex(i, rows);
validateIndex(j, columns);
// find value index
var k = _getValueIndex(i, this._ptr[j], this._ptr[j + 1], this._index);
// check k is prior to next column k and it is in the correct row
if (k < this._ptr[j + 1] && this._index[k] === i) {
// check value != 0
if (!math.equal(v, 0)) {
// update value
this._values[k] = v;
}
else {
// remove value from matrix
_remove(k, j, this._values, this._index, this._ptr);
}
}
else {
// insert value @ (i, j)
_insert(k, i, j, v, this._values, this._index, this._ptr);
}
return this;
};
var _getValueIndex = function(i, top, bottom, index) {
// check row is on the bottom side
if (bottom - top === 0 || i > index[bottom - 1])
return bottom;
// loop until we find row index
while (top < bottom) {
// point in the middle (fast integer division)
var p = ~~((top + bottom) / 2);
// row @ p
var r = index[p];
// check we have to look on the top side, bottom side or we found the row
if (i < r)
bottom = p;
else if (i > r)
top = p + 1;
else
return p;
}
return top;
};
var _remove = function (k, j, values, index, ptr) {
// remove value @ k
values.splice(k, 1);
index.splice(k, 1);
// update pointers
for (var x = j + 1; x < ptr.length; x++)
ptr[x]--;
};
var _insert = function (k, i, j, v, values, index, ptr) {
// insert value
values.splice(k, 0, v);
// update row for k
index.splice(k, 0, i);
// update column pointers
for (var x = j + 1; x < ptr.length; x++)
ptr[x]++;
};
/**
* Resize the matrix to the given size. Returns a copy of the matrix when
* `copy=true`, otherwise return the matrix itself (resize in place).
*
* @param {Number[]} size The new size the matrix should have.
* @param {*} [defaultValue=0] Default value, filled in on new entries.
* If not provided, the matrix elements will
* be filled with zeros.
* @param {boolean} [copy] Return a resized copy of the matrix
*
* @return {Matrix} The resized matrix
*/
CcsMatrix.prototype.resize = function (size, defaultValue, copy) {
// validate arguments
if (!isArray(size))
throw new TypeError('Array expected');
if (size.length !== 2)
throw new Error('Only two dimensions matrix are supported');
// check sizes
size.forEach(function (value) {
if (!number.isNumber(value) || !number.isInteger(value) || value < 0) {
throw new TypeError('Invalid size, must contain positive integers ' +
'(size: ' + string.format(size) + ')');
}
});
// matrix to resize
var m = copy ? this.clone() : this;
// resize matrix
return _resize(m, size[0], size[1], defaultValue);
};
var _resize = function (matrix, rows, columns, defaultValue) {
// value to insert at the time of growing matrix
var value = defaultValue || 0;
// should we insert the value?
var ins = !math.equal(value, 0);
// old columns and rows
var r = matrix._size[0];
var c = matrix._size[1];
var i, j, k;
// check we need to increase columns
if (columns > c) {
// loop new columns
for (j = c; j < columns; j++) {
// update matrix._ptr for current column
matrix._ptr[j] = matrix._values.length;
// check we need to insert matrix._values
if (ins) {
// loop rows
for (i = 0; i < r; i++) {
// add new matrix._values
matrix._values.push(value);
// update matrix._index
matrix._index.push(i);
}
}
}
// store number of matrix._values in matrix._ptr
matrix._ptr[columns] = matrix._values.length;
}
else if (columns < c) {
// truncate matrix._ptr
matrix._ptr.splice(columns + 1, c - columns);
// truncate matrix._values and matrix._index
matrix._values.splice(matrix._ptr[columns], matrix._values.length);
matrix._index.splice(matrix._ptr[columns], matrix._index.length);
}
// update columns
c = columns;
// check we need to increase rows
if (rows > r) {
// check we have to insert values
if (ins) {
// inserts
var n = 0;
// loop columns
for (j = 0; j < c; j++) {
// update matrix._ptr for current column
matrix._ptr[j] = matrix._ptr[j] + n;
// where to insert matrix._values
k = matrix._ptr[j + 1] + n;
// pointer
var p = 0;
// loop new rows, initialize pointer
for (i = r; i < rows; i++, p++) {
// add value
matrix._values.splice(k + p, 0, value);
// update matrix._index
matrix._index.splice(k + p, 0, i);
// increment inserts
n++;
}
}
// store number of matrix._values in matrix._ptr
matrix._ptr[c] = matrix._values.length;
}
}
else if (rows < r) {
// deletes
var d = 0;
// loop columns
for (j = 0; j < c; j++) {
// update matrix._ptr for current column
matrix._ptr[j] = matrix._ptr[j] - d;
// where matrix._values start for next column
var k0 = matrix._ptr[j];
var k1 = matrix._ptr[j + 1] - d;
// loop matrix._index
for (k = k0; k < k1; k++) {
// row
i = matrix._index[k];
// check we need to delete value and matrix._index
if (i > rows - 1) {
// remove value
matrix._values.splice(k, 1);
// remove item from matrix._index
matrix._index.splice(k, 1);
// increase deletes
d++;
}
}
}
// update matrix._ptr for current column
matrix._ptr[j] = matrix._values.length;
}
// update matrix._size
matrix._size[0] = rows;
matrix._size[1] = columns;
// return matrix
return matrix;
};
/**
* Create a clone of the matrix
* @return {CcsMatrix} clone
*/
CcsMatrix.prototype.clone = function () {
var m = new CcsMatrix({
values: object.clone(this._values),
index: object.clone(this._index),
ptr: object.clone(this._ptr),
size: object.clone(this._size)
});
return m;
};
/**
* Retrieve the size of the matrix.
* @returns {Number[]} size
*/
CcsMatrix.prototype.size = function() {
return object.clone(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 function is invoked with three
* parameters: the value of the element, the index
* of the element, and the Matrix being traversed.
* @param {boolean} [skipZeros] Invoke callback function for non-zero values only.
*
* @return {CcsMatrix} matrix
*/
CcsMatrix.prototype.map = function (callback, skipZeros) {
// matrix instance
var me = this;
// rows and columns
var rows = this._size[0];
var columns = this._size[1];
// invoke callback
var invoke = function (v, i, j) {
// invoke callback
return callback(v, [i, j], me);
};
// invoke _map
return _map(this, 0, rows - 1, 0, columns - 1, invoke, skipZeros);
};
/**
* Create a new matrix with the results of the callback function executed on the interval
* [minRow..maxRow, minColumn..maxColumn].
*/
var _map = function (matrix, minRow, maxRow, minColumn, maxColumn, callback, skipZeros) {
// result arrays
var values = [];
var index = [];
var ptr = [];
// invoke callback
var invoke = function (v, x, y) {
// invoke callback
v = callback(v, x, y);
// check value != 0
if (!math.equal(v, 0)) {
// store value
values.push(v);
// index
index.push(x);
}
};
// loop columns
for (var j = minColumn; j <= maxColumn; j++) {
// store pointer to values index
ptr.push(values.length);
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
var k0 = matrix._ptr[j];
var k1 = matrix._ptr[j + 1];
// row pointer
var p = minRow;
// loop k within [k0, k1[
for (var k = k0; k < k1; k++) {
// row index
var i = matrix._index[k];
// check i is in range
if (i >= minRow && i <= maxRow) {
// zero values
if (!skipZeros) {
for (var x = p; x < i; x++)
invoke(0, x - minRow, j - minColumn);
}
// value @ k
invoke(matrix._values[k], i - minRow, j - minColumn);
}
// update pointer
p = i + 1;
}
// zero values
if (!skipZeros) {
for (var y = p; y <= maxRow; y++)
invoke(0, y - minRow, j - minColumn);
}
}
// store number of values in ptr
ptr.push(values.length);
// return ccs
return new CcsMatrix({
values: values,
index: index,
ptr: ptr,
size: [maxRow - minRow + 1, maxColumn - minColumn + 1]
});
};
/**
* Execute a callback function on each entry of the matrix.
* @param {function} callback The callback function is invoked with three
* parameters: the value of the element, the index
* of the element, and the Matrix being traversed.
* @param {boolean} [skipZeros] Invoke callback function for non-zero values only.
*/
CcsMatrix.prototype.forEach = function (callback, skipZeros) {
// matrix instance
var me = this;
// rows and columns
var rows = this._size[0];
var columns = this._size[1];
// loop columns
for (var j = 0; j < columns; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
var k0 = this._ptr[j];
var k1 = this._ptr[j + 1];
// column pointer
var p = 0;
// loop k within [k0, k1[
for (var k = k0; k < k1; k++) {
// row index
var i = this._index[k];
// check we need to process zeros
if (!skipZeros) {
// zero values
for (var x = p; x < i; x++)
callback(0, [x, j], me);
}
// value @ k
callback(this._values[k], [i, j], me);
// update pointer
p = i + 1;
}
// check we need to process zeros
if (!skipZeros) {
// zero values
for (var y = p; y < rows; y++)
callback(0, [y, j], me);
}
}
};
/**
* Create an Array with a copy of the data of the CcsMatrix
* @returns {Array} array
*/
CcsMatrix.prototype.toArray = function () {
return _toArray(this, true);
};
/**
* Get the primitive value of the CcsMatrix: a two dimensions array
* @returns {Array} array
*/
CcsMatrix.prototype.valueOf = function () {
return _toArray(this, false);
};
var _toArray = function (matrix, copy) {
// result
var a = [];
// rows and columns
var rows = matrix._size[0];
var columns = matrix._size[1];
// loop columns
for (var j = 0; j < columns; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
var k0 = matrix._ptr[j];
var k1 = matrix._ptr[j + 1];
// row pointer
var p = 0;
// loop k within [k0, k1[
for (var k = k0; k < k1; k++) {
// row index
var i = matrix._index[k];
// zeros
for (var x = p; x < i; x++)
(a[x] = (a[x] || []))[j] = 0;
// set value
(a[i] = (a[i] || []))[j] = copy ? object.clone(matrix._values[k]) : matrix._values[k];
// update pointer
p = i + 1;
}
// zero values
for (var y = p; y < rows; y++)
(a[y] = (a[y] || []))[j] = 0;
}
return a;
};
/**
* Get a string representation of the matrix, with optional formatting options.
* @param {Object | Number | Function} [options] Formatting options. See
* lib/util/number:format for a
* description of the available
* options.
* @returns {String} str
*/
CcsMatrix.prototype.format = function (options) {
// rows and columns
var rows = this._size[0];
var columns = this._size[1];
// rows & columns
var str = 'CCS [' + string.format(rows, options) + ' x ' + string.format(columns, options) + '] density: ' + string.format(this._values.length / (rows * columns), options) + '\n';
// loop columns
for (var j = 0; j < columns; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
var k0 = this._ptr[j];
var k1 = this._ptr[j + 1];
// loop k within [k0, k1[
for (var k = k0; k < k1; k++) {
// row index
var i = this._index[k];
// append value
str += '\n (' + string.format(i, options) + ', ' + string.format(j, options) + ') ==> ' + string.format(this._values[k], options);
}
}
return str;
};
/**
* Get a string representation of the matrix
* @returns {String} str
*/
CcsMatrix.prototype.toString = function () {
return string.format(this.toArray());
};
/**
* Get a JSON representation of the matrix
* @returns {Object}
*/
CcsMatrix.prototype.toJSON = function () {
return {
mathjs: 'CcsMatrix',
values: this._values,
index: this._index,
ptr: this._ptr,
size: this._size
};
};
/**
* Calculates the transpose of the matrix
* @returns {Matrix}
*/
CcsMatrix.prototype.transpose = function () {
// rows and columns
var rows = this._size[0];
var columns = this._size[1];
// check columns
if (columns === 0) {
// throw exception
throw new RangeError('Cannot transpose a 2D matrix with no columns (size: ' + string.format(this._size) + ')');
}
// ccs transpose is a crs matrix with the same structure
return new math.type.CrsMatrix({
values: object.clone(this._values),
index: object.clone(this._index),
ptr: object.clone(this._ptr),
size: [columns, rows]
});
};
/**
* Get the kth Matrix diagonal.
*
* @param {Number | BigNumber} [k=0] The kth diagonal where the vector will retrieved.
*
* @returns {Array} The array vector with the diagonal values.
*/
CcsMatrix.prototype.diagonal = function(k) {
// validate k if any
if (k) {
// convert BigNumber to a number
if (k instanceof BigNumber)
k = k.toNumber();
// is must be an integer
if (!isNumber(k) || !isInteger(k)) {
throw new TypeError ('The parameter k must be an integer number');
}
}
else {
// default value
k = 0;
}
var kSuper = k > 0 ? k : 0;
var kSub = k < 0 ? -k : 0;
// rows & columns
var rows = this._size[0];
var columns = this._size[1];
// number diagonal values
var n = Math.min(rows - kSub, columns - kSuper);
// diagonal
var values = [];
// loop columns
for (var j = kSuper; j < columns && values.length < n; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
var k0 = this._ptr[j];
var k1 = this._ptr[j + 1];
// column value flag
var cv = false;
// loop x within [k0, k1[
for (var x = k0; x < k1; x++) {
// row index
var i = this._index[x];
// check row
if (i === j - kSuper + kSub) {
// set flag
cv = true;
// value on this column
values.push(object.clone(this._values[x]));
// exit loop
break;
}
else if (i > j - kSuper + kSub) {
// exit loop, no value on the diagonal for column j
break;
}
}
// check this column has a value set
if (!cv && values.length < n) {
// zero on this column
values.push(0);
}
}
return values;
};
/**
* Generate a matrix from a JSON object
* @param {Object} json An object structured like
* `{"mathjs": "CcsMatrix", "values": [], "index": [], "ptr": [], "size": []}`,
* where mathjs is optional
* @returns {CcsMatrix}
*/
CcsMatrix.fromJSON = function (json) {
return new CcsMatrix(json);
};
/**
* Create a diagonal matrix.
*
* @param {Array} size The matrix size.
* @param {Number, Array} value The values for the diagonal.
* @param {Number | BigNumber} [k=0] The kth diagonal where the vector will be filled in.
*
* @returns {CcsMatrix}
*/
CcsMatrix.diagonal = function (size, value, k) {
if (!isArray(size))
throw new TypeError('Array expected, size parameter');
if (size.length !== 2)
throw new Error('Only two dimensions matrix are supported');
// map size & validate
size = size.map(function (s) {
// check it is a big number
if (s instanceof BigNumber) {
// convert it
s = s.toNumber();
}
// validate arguments
if (!isNumber(s) || !isInteger(s) || s < 1) {
throw new Error('Size values must be positive integers');
}
return s;
});
// validate k if any
if (k) {
// convert BigNumber to a number
if (k instanceof BigNumber)
k = k.toNumber();
// is must be an integer
if (!isNumber(k) || !isInteger(k)) {
throw new TypeError ('The parameter k must be an integer number');
}
}
else {
// default value
k = 0;
}
var kSuper = k > 0 ? k : 0;
var kSub = k < 0 ? -k : 0;
// rows and columns
var rows = size[0];
var columns = size[1];
// number of non-zero items
var n = Math.min(rows - kSub, columns - kSuper);
// value extraction function
var _value;
// check value
if (isArray(value)) {
// validate array
if (value.length !== n) {
// number of values in array must be n
throw new Error('Invalid value array length');
}
// define function
_value = function (i) {
// return value @ i
return value[i];
};
}
else {
// define function
_value = function () {
// return value
return value;
};
}
// create arrays
var values = [];
var index = [];
var ptr = [];
// loop items
for (var j = 0; j < columns; j++) {
// number of rows with value
ptr.push(values.length);
// diagonal index
var i = j - kSuper;
// check we need to set diagonal value
if (i >= 0 && i < n) {
// get value @ i
var v = _value(i);
// check for zero
if (!math.equal(v, 0)) {
// column
index.push(i + kSub);
// add value
values.push(v);
}
}
}
// last value should be number of values
ptr.push(values.length);
// create CcsMatrix
return new CcsMatrix({
values: values,
index: index,
ptr: ptr,
size: [rows, columns]
});
};
/**
* Calculate the trace of a matrix: the sum of the elements on the main
* diagonal of a square matrix.
*
* See also:
*
* diagonal
*
* @returns {Number} The matrix trace
*/
CcsMatrix.prototype.trace = function () {
// size
var size = this._size;
// check dimensions
var rows = size[0];
var columns = size[1];
// matrix must be square
if (rows === columns) {
// calulate sum
var sum = 0;
// check we have data (avoid looping columns)
if (this._values.length > 0) {
// loop columns
for (var j = 0; j < columns; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
var k0 = this._ptr[j];
var k1 = this._ptr[j + 1];
// loop k within [k0, k1[
for (var k = k0; k < k1; k++) {
// row index
var i = this._index[k];
// check row
if (i === j) {
// accumulate value
sum = math.add(sum, this._values[k]);
// exit loop
break;
}
if (i > j) {
// exit loop, no value on the diagonal for column j
break;
}
}
}
}
// return trace
return sum;
}
throw new RangeError('Matrix must be square (size: ' + string.format(size) + ')');
};
/**
* Multiply the matrix values times the argument.
*
* @param {Number | BigNumber | Boolean | Complex | Unit | Array | Matrix | null} Value to multiply.
*
* @return {Number | BigNumber | Complex | Unit | Matrix}
*/
CcsMatrix.prototype.multiply = function (value) {
// check dimensions
var rows = this._size[0];
var columns = this._size[1];
// check value is a matrix
if (value instanceof Matrix) {
// matrix size
var z = value.size();
// check value is a vector
if (z.length === 1) {
// mutiply matrix x vector array
return _multiply(this, z[0], 1, function (i) {
// value[i]
return value.get([i]);
});
}
// check two dimensions matrix
if (z.length === 2) {
// mutiply matrix x matrix
return _multiply(this, z[0], z[1], function (i, j) {
// value[i, j]
return value.get([i, j]);
});
}
throw new Error('Can only multiply a 1 or 2 dimensional matrix ' +
'(value has ' + z.length + ' dimensions)');
}
// check value is an array
if (isArray(value)) {
// array size
var s = array.size(value);
// check value is a vector
if (s.length === 1) {
// mutiply matrix x vector array
return _multiply(this, s[0], 1, function (i) {
// value[i]
return value[i];
});
}
if (s.length === 2) {
// mutiply matrix x array
return _multiply(this, s[0], s[1], function (i, j) {
// value[i, j]
return value[i][j];
});
}
throw new Error('Can only multiply a 1 or 2 dimensional matrix ' +
'(value has ' + s.length + ' dimensions)');
}
var callback = function (v) {
return math.multiply(v, value);
};
// map non zero elements
return _map(this, 0, rows - 1, 0, columns - 1, callback, false);
};
var _multiply = function (matrix, r, c, get) {
// matrix dimensions
var rows = matrix._size[0];
var columns = matrix._size[1];
// check dimensions match
if (columns !== r) {
// throw error
throw new RangeError('Dimension mismatch in multiplication. ' +
'Columns of A must match length of B ' +
'(A is ' + rows + 'x' + columns +
', B is ' + r + ', ' +
columns + ' != ' + r + ')');
}
// result arrays
var values = [];
var index = [];
var ptr = [];
// create array with rows entries
var data = [];
for (var x = 0; x < rows; x++)
data[x] = 0;
// loop value columns
for (var z = 0; z < c; z++) {
// update ptr
ptr.push(values.length);
// do not traverse rows in matrix, it is not efficient in CCS
for (var j = 0; j < columns; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
var k0 = matrix._ptr[j];
var k1 = matrix._ptr[j + 1];
// loop k within [k0, k1[
for (var k = k0; k < k1; k++) {
// row
var i = matrix._index[k];
// multiply & aggregate
data[i] = math.add(data[i], math.multiply(matrix._values[k], get(j, z)));
}
}
// finished processing column z, compress results
for (var y = 0; y < rows; y++) {
// check value is different than zero
if (!math.equal(data[y], 0)) {
// push value
values.push(data[y]);
index.push(y);
}
// reset value
data[y] = 0;
}
}
// update ptr
ptr.push(values.length);
// check we need to squeeze the result into a scalar
if (rows === 1 && c === 1)
return values.length === 1 ? values[0] : 0;
// return CCS matrix
return new CcsMatrix({
values: values,
index: index,
ptr: ptr,
size: [rows, c]
});
};
return CcsMatrix;
};