mathjs
Version:
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with dif
1,493 lines (1,390 loc) • 47 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createSparseMatrixClass = void 0;
var _is = require("../../utils/is.js");
var _number = require("../../utils/number.js");
var _string = require("../../utils/string.js");
var _object = require("../../utils/object.js");
var _array = require("../../utils/array.js");
var _factory = require("../../utils/factory.js");
var _DimensionError = require("../../error/DimensionError.js");
var _optimizeCallback = require("../../utils/optimizeCallback.js");
const name = 'SparseMatrix';
const dependencies = ['typed', 'equalScalar', 'Matrix'];
const createSparseMatrixClass = exports.createSparseMatrixClass = /* #__PURE__ */(0, _factory.factory)(name, dependencies, _ref => {
let {
typed,
equalScalar,
Matrix
} = _ref;
/**
* Sparse Matrix implementation. This type implements
* a [Compressed Column Storage](https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_column_(CSC_or_CCS))
* format for two-dimensional sparse matrices.
* @class SparseMatrix
*/
function SparseMatrix(data, datatype) {
if (!(this instanceof SparseMatrix)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
if (datatype && !(0, _is.isString)(datatype)) {
throw new Error('Invalid datatype: ' + datatype);
}
if ((0, _is.isMatrix)(data)) {
// create from matrix
_createFromMatrix(this, data, datatype);
} else if (data && (0, _is.isArray)(data.index) && (0, _is.isArray)(data.ptr) && (0, _is.isArray)(data.size)) {
// initialize fields
this._values = data.values;
this._index = data.index;
this._ptr = data.ptr;
this._size = data.size;
this._datatype = datatype || data.datatype;
} else if ((0, _is.isArray)(data)) {
// create from array
_createFromArray(this, data, datatype);
} else if (data) {
// unsupported type
throw new TypeError('Unsupported type of data (' + (0, _is.typeOf)(data) + ')');
} else {
// nothing provided
this._values = [];
this._index = [];
this._ptr = [0];
this._size = [0, 0];
this._datatype = datatype;
}
}
function _createFromMatrix(matrix, source, datatype) {
// check matrix type
if (source.type === 'SparseMatrix') {
// clone arrays
matrix._values = source._values ? (0, _object.clone)(source._values) : undefined;
matrix._index = (0, _object.clone)(source._index);
matrix._ptr = (0, _object.clone)(source._ptr);
matrix._size = (0, _object.clone)(source._size);
matrix._datatype = datatype || source._datatype;
} else {
// build from matrix data
_createFromArray(matrix, source.valueOf(), datatype || source._datatype);
}
}
function _createFromArray(matrix, data, datatype) {
// initialize fields
matrix._values = [];
matrix._index = [];
matrix._ptr = [];
matrix._datatype = datatype;
// discover rows & columns, do not use math.size() to avoid looping array twice
const rows = data.length;
let columns = 0;
// equal signature to use
let eq = equalScalar;
// zero value
let zero = 0;
if ((0, _is.isString)(datatype)) {
// find signature that matches (datatype, datatype)
eq = typed.find(equalScalar, [datatype, datatype]) || equalScalar;
// convert 0 to the same datatype
zero = typed.convert(0, datatype);
}
// check we have rows (empty array)
if (rows > 0) {
// column index
let j = 0;
do {
// store pointer to values index
matrix._ptr.push(matrix._index.length);
// loop rows
for (let i = 0; i < rows; i++) {
// current row
const row = data[i];
// check row is an array
if ((0, _is.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
const v = row[j];
// check value != 0
if (!eq(v, zero)) {
// 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 (!eq(row, zero)) {
// 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._index.length);
// size
matrix._size = [rows, columns];
}
SparseMatrix.prototype = new Matrix();
/**
* Create a new SparseMatrix
*/
SparseMatrix.prototype.createSparseMatrix = function (data, datatype) {
return new SparseMatrix(data, datatype);
};
/**
* Attach type information
*/
Object.defineProperty(SparseMatrix, 'name', {
value: 'SparseMatrix'
});
SparseMatrix.prototype.constructor = SparseMatrix;
SparseMatrix.prototype.type = 'SparseMatrix';
SparseMatrix.prototype.isSparseMatrix = true;
/**
* Get the matrix type
*
* Usage:
* const matrixType = matrix.getDataType() // retrieves the matrix type
*
* @memberOf SparseMatrix
* @return {string} type information; if multiple types are found from the Matrix, it will return "mixed"
*/
SparseMatrix.prototype.getDataType = function () {
return (0, _array.getArrayDataType)(this._values, _is.typeOf);
};
/**
* Get the storage format used by the matrix.
*
* Usage:
* const format = matrix.storage() // retrieve storage format
*
* @memberof SparseMatrix
* @return {string} The storage format.
*/
SparseMatrix.prototype.storage = function () {
return 'sparse';
};
/**
* Get the datatype of the data stored in the matrix.
*
* Usage:
* const format = matrix.datatype() // retrieve matrix datatype
*
* @memberof SparseMatrix
* @return {string} The datatype.
*/
SparseMatrix.prototype.datatype = function () {
return this._datatype;
};
/**
* Create a new SparseMatrix
* @memberof SparseMatrix
* @param {Array} data
* @param {string} [datatype]
*/
SparseMatrix.prototype.create = function (data, datatype) {
return new SparseMatrix(data, datatype);
};
/**
* Get the matrix density.
*
* Usage:
* const density = matrix.density() // retrieve matrix density
*
* @memberof SparseMatrix
* @return {number} The matrix density.
*/
SparseMatrix.prototype.density = function () {
// rows & columns
const rows = this._size[0];
const columns = this._size[1];
// calculate density
return rows !== 0 && columns !== 0 ? this._index.length / (rows * columns) : 0;
};
/**
* Get a subset of the matrix, or replace a subset of the matrix.
*
* Usage:
* const subset = matrix.subset(index) // retrieve subset
* const value = matrix.subset(index, replacement) // replace subset
*
* @memberof SparseMatrix
* @param {Index} index
* @param {Array | Matrix | *} [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.
*/
SparseMatrix.prototype.subset = function (index, replacement, defaultValue) {
// check it is a pattern matrix
if (!this._values) {
throw new Error('Cannot invoke subset on a Pattern only matrix');
}
// 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');
}
};
function _getsubset(matrix, idx) {
// check idx
if (!(0, _is.isIndex)(idx)) {
throw new TypeError('Invalid index');
}
const isScalar = idx.isScalar();
if (isScalar) {
// return a scalar
return matrix.get(idx.min());
}
// validate dimensions
const size = idx.size();
if (size.length !== matrix._size.length) {
throw new _DimensionError.DimensionError(size.length, matrix._size.length);
}
// vars
let i, ii, k, kk;
// validate if any of the ranges in the index is out of range
const min = idx.min();
const max = idx.max();
for (i = 0, ii = matrix._size.length; i < ii; i++) {
(0, _array.validateIndex)(min[i], matrix._size[i]);
(0, _array.validateIndex)(max[i], matrix._size[i]);
}
// matrix arrays
const mvalues = matrix._values;
const mindex = matrix._index;
const mptr = matrix._ptr;
// rows & columns dimensions for result matrix
const rows = idx.dimension(0);
const columns = idx.dimension(1);
// workspace & permutation vector
const w = [];
const pv = [];
// loop rows in resulting matrix
rows.forEach(function (i, r) {
// update permutation vector
pv[i] = r[0];
// mark i in workspace
w[i] = true;
});
// result matrix arrays
const values = mvalues ? [] : undefined;
const index = [];
const ptr = [];
// loop columns in result matrix
columns.forEach(function (j) {
// update ptr
ptr.push(index.length);
// loop values in column j
for (k = mptr[j], kk = mptr[j + 1]; k < kk; k++) {
// row
i = mindex[k];
// check row is in result matrix
if (w[i] === true) {
// push index
index.push(pv[i]);
// check we need to process values
if (values) {
values.push(mvalues[k]);
}
}
}
});
// update ptr
ptr.push(index.length);
// return matrix
return new SparseMatrix({
values,
index,
ptr,
size,
datatype: matrix._datatype
});
}
function _setsubset(matrix, index, submatrix, defaultValue) {
// check index
if (!index || index.isIndex !== true) {
throw new TypeError('Invalid index');
}
// get index size and check whether the index contains a single value
const iSize = index.size();
const isScalar = index.isScalar();
// calculate the size of the submatrix, and convert it into an Array if needed
let sSize;
if ((0, _is.isMatrix)(submatrix)) {
// submatrix size
sSize = submatrix.size();
// use array representation
submatrix = submatrix.toArray();
} else {
// get submatrix size (array, scalar)
sSize = (0, _array.arraySize)(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.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
let i = 0;
let outer = 0;
while (iSize[i] === 1 && sSize[i] === 1) {
i++;
}
while (iSize[i] === 1) {
outer++;
i++;
}
// unsqueeze both outer and inner dimensions
submatrix = (0, _array.unsqueeze)(submatrix, iSize.length, outer, sSize);
}
// check whether the size of the submatrix matches the index size
if (!(0, _object.deepStrictEqual)(iSize, sSize)) {
throw new _DimensionError.DimensionError(iSize, sSize, '>');
}
// insert the sub matrix
if (iSize.length === 1) {
// if the replacement index only has 1 dimension, go trough each one and set its value
const range = index.dimension(0);
range.forEach(function (dataIndex, subIndex) {
(0, _array.validateIndex)(dataIndex);
matrix.set([dataIndex, 0], submatrix[subIndex[0]], defaultValue);
});
} else {
// if the replacement index has 2 dimensions, go through each one and set the value in the correct index
const firstDimensionRange = index.dimension(0);
const secondDimensionRange = index.dimension(1);
firstDimensionRange.forEach(function (firstDataIndex, firstSubIndex) {
(0, _array.validateIndex)(firstDataIndex);
secondDimensionRange.forEach(function (secondDataIndex, secondSubIndex) {
(0, _array.validateIndex)(secondDataIndex);
matrix.set([firstDataIndex, secondDataIndex], submatrix[firstSubIndex[0]][secondSubIndex[0]], defaultValue);
});
});
}
}
return matrix;
}
/**
* Get a single element from the matrix.
* @memberof SparseMatrix
* @param {number[]} index Zero-based index
* @return {*} value
*/
SparseMatrix.prototype.get = function (index) {
if (!(0, _is.isArray)(index)) {
throw new TypeError('Array expected');
}
if (index.length !== this._size.length) {
throw new _DimensionError.DimensionError(index.length, this._size.length);
}
// check it is a pattern matrix
if (!this._values) {
throw new Error('Cannot invoke get on a Pattern only matrix');
}
// row and column
const i = index[0];
const j = index[1];
// check i, j are valid
(0, _array.validateIndex)(i, this._size[0]);
(0, _array.validateIndex)(j, this._size[1]);
// find value index
const 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 this._values[k];
}
return 0;
};
/**
* Replace a single element in the matrix.
* @memberof SparseMatrix
* @param {number[]} index Zero-based index
* @param {*} v
* @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 {SparseMatrix} self
*/
SparseMatrix.prototype.set = function (index, v, defaultValue) {
if (!(0, _is.isArray)(index)) {
throw new TypeError('Array expected');
}
if (index.length !== this._size.length) {
throw new _DimensionError.DimensionError(index.length, this._size.length);
}
// check it is a pattern matrix
if (!this._values) {
throw new Error('Cannot invoke set on a Pattern only matrix');
}
// row and column
const i = index[0];
const j = index[1];
// rows & columns
let rows = this._size[0];
let columns = this._size[1];
// equal signature to use
let eq = equalScalar;
// zero value
let zero = 0;
if ((0, _is.isString)(this._datatype)) {
// find signature that matches (datatype, datatype)
eq = typed.find(equalScalar, [this._datatype, this._datatype]) || equalScalar;
// convert 0 to the same datatype
zero = typed.convert(0, this._datatype);
}
// 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
(0, _array.validateIndex)(i, rows);
(0, _array.validateIndex)(j, columns);
// find value index
const 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 (!eq(v, zero)) {
// update value
this._values[k] = v;
} else {
// remove value from matrix
_remove(k, j, this._values, this._index, this._ptr);
}
} else {
if (!eq(v, zero)) {
// insert value @ (i, j)
_insert(k, i, j, v, this._values, this._index, this._ptr);
}
}
return this;
};
function _getValueIndex(i, top, bottom, index) {
// check row is on the bottom side
if (bottom - top === 0) {
return bottom;
}
// loop rows [top, bottom[
for (let r = top; r < bottom; r++) {
// check we found value index
if (index[r] === i) {
return r;
}
}
// we did not find row
return top;
}
function _remove(k, j, values, index, ptr) {
// remove value @ k
values.splice(k, 1);
index.splice(k, 1);
// update pointers
for (let x = j + 1; x < ptr.length; x++) {
ptr[x]--;
}
}
function _insert(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 (let 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).
*
* @memberof SparseMatrix
* @param {number[] | Matrix} size The new size the matrix should have.
* Since sparse matrices are always two-dimensional,
* size must be two numbers in either an array or a matrix
* @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
*/
SparseMatrix.prototype.resize = function (size, defaultValue, copy) {
// validate arguments
if (!(0, _is.isCollection)(size)) {
throw new TypeError('Array or Matrix expected');
}
// SparseMatrix input is always 2d, flatten this into 1d if it's indeed a vector
const sizeArray = size.valueOf().map(value => {
return Array.isArray(value) && value.length === 1 ? value[0] : value;
});
if (sizeArray.length !== 2) {
throw new Error('Only two dimensions matrix are supported');
}
// check sizes
sizeArray.forEach(function (value) {
if (!(0, _is.isNumber)(value) || !(0, _number.isInteger)(value) || value < 0) {
throw new TypeError('Invalid size, must contain positive integers ' + '(size: ' + (0, _string.format)(sizeArray) + ')');
}
});
// matrix to resize
const m = copy ? this.clone() : this;
// resize matrix
return _resize(m, sizeArray[0], sizeArray[1], defaultValue);
};
function _resize(matrix, rows, columns, defaultValue) {
// value to insert at the time of growing matrix
let value = defaultValue || 0;
// equal signature to use
let eq = equalScalar;
// zero value
let zero = 0;
if ((0, _is.isString)(matrix._datatype)) {
// find signature that matches (datatype, datatype)
eq = typed.find(equalScalar, [matrix._datatype, matrix._datatype]) || equalScalar;
// convert 0 to the same datatype
zero = typed.convert(0, matrix._datatype);
// convert value to the same datatype
value = typed.convert(value, matrix._datatype);
}
// should we insert the value?
const ins = !eq(value, zero);
// old columns and rows
const r = matrix._size[0];
let c = matrix._size[1];
let 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
let 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
let 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
let 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
const k0 = matrix._ptr[j];
const 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;
}
/**
* Reshape the matrix to the given size. Returns a copy of the matrix when
* `copy=true`, otherwise return the matrix itself (reshape in place).
*
* NOTE: This might be better suited to copy by default, instead of modifying
* in place. For now, it operates in place to remain consistent with
* resize().
*
* @memberof SparseMatrix
* @param {number[]} sizes The new size the matrix should have.
* Since sparse matrices are always two-dimensional,
* size must be two numbers in either an array or a matrix
* @param {boolean} [copy] Return a reshaped copy of the matrix
*
* @return {Matrix} The reshaped matrix
*/
SparseMatrix.prototype.reshape = function (sizes, copy) {
// validate arguments
if (!(0, _is.isArray)(sizes)) {
throw new TypeError('Array expected');
}
if (sizes.length !== 2) {
throw new Error('Sparse matrices can only be reshaped in two dimensions');
}
// check sizes
sizes.forEach(function (value) {
if (!(0, _is.isNumber)(value) || !(0, _number.isInteger)(value) || value <= -2 || value === 0) {
throw new TypeError('Invalid size, must contain positive integers or -1 ' + '(size: ' + (0, _string.format)(sizes) + ')');
}
});
const currentLength = this._size[0] * this._size[1];
sizes = (0, _array.processSizesWildcard)(sizes, currentLength);
const newLength = sizes[0] * sizes[1];
// m * n must not change
if (currentLength !== newLength) {
throw new Error('Reshaping sparse matrix will result in the wrong number of elements');
}
// matrix to reshape
const m = copy ? this.clone() : this;
// return unchanged if the same shape
if (this._size[0] === sizes[0] && this._size[1] === sizes[1]) {
return m;
}
// Convert to COO format (generate a column index)
const colIndex = [];
for (let i = 0; i < m._ptr.length; i++) {
for (let j = 0; j < m._ptr[i + 1] - m._ptr[i]; j++) {
colIndex.push(i);
}
}
// Clone the values array
const values = m._values.slice();
// Clone the row index array
const rowIndex = m._index.slice();
// Transform the (row, column) indices
for (let i = 0; i < m._index.length; i++) {
const r1 = rowIndex[i];
const c1 = colIndex[i];
const flat = r1 * m._size[1] + c1;
colIndex[i] = flat % sizes[1];
rowIndex[i] = Math.floor(flat / sizes[1]);
}
// Now reshaping is supposed to preserve the row-major order, BUT these sparse matrices are stored
// in column-major order, so we have to reorder the value array now. One option is to use a multisort,
// sorting several arrays based on some other array.
// OR, we could easily just:
// 1. Remove all values from the matrix
m._values.length = 0;
m._index.length = 0;
m._ptr.length = sizes[1] + 1;
m._size = sizes.slice();
for (let i = 0; i < m._ptr.length; i++) {
m._ptr[i] = 0;
}
// 2. Re-insert all elements in the proper order (simplified code from SparseMatrix.prototype.set)
// This step is probably the most time-consuming
for (let h = 0; h < values.length; h++) {
const i = rowIndex[h];
const j = colIndex[h];
const v = values[h];
const k = _getValueIndex(i, m._ptr[j], m._ptr[j + 1], m._index);
_insert(k, i, j, v, m._values, m._index, m._ptr);
}
// The value indices are inserted out of order, but apparently that's... still OK?
return m;
};
/**
* Create a clone of the matrix
* @memberof SparseMatrix
* @return {SparseMatrix} clone
*/
SparseMatrix.prototype.clone = function () {
const m = new SparseMatrix({
values: this._values ? (0, _object.clone)(this._values) : undefined,
index: (0, _object.clone)(this._index),
ptr: (0, _object.clone)(this._ptr),
size: (0, _object.clone)(this._size),
datatype: this._datatype
});
return m;
};
/**
* Retrieve the size of the matrix.
* @memberof SparseMatrix
* @returns {number[]} size
*/
SparseMatrix.prototype.size = function () {
return this._size.slice(0); // copy the Array
};
/**
* Create a new matrix with the results of the callback function executed on
* each entry of the matrix.
* @memberof SparseMatrix
* @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 {SparseMatrix} matrix
*/
SparseMatrix.prototype.map = function (callback, skipZeros) {
// check it is a pattern matrix
if (!this._values) {
throw new Error('Cannot invoke map on a Pattern only matrix');
}
// matrix instance
const me = this;
// rows and columns
const rows = this._size[0];
const columns = this._size[1];
const fastCallback = (0, _optimizeCallback.optimizeCallback)(callback, me, 'map');
// invoke callback
const invoke = function (v, i, j) {
// invoke callback
return fastCallback.fn(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].
*/
function _map(matrix, minRow, maxRow, minColumn, maxColumn, callback, skipZeros) {
// result arrays
const values = [];
const index = [];
const ptr = [];
// equal signature to use
let eq = equalScalar;
// zero value
let zero = 0;
if ((0, _is.isString)(matrix._datatype)) {
// find signature that matches (datatype, datatype)
eq = typed.find(equalScalar, [matrix._datatype, matrix._datatype]) || equalScalar;
// convert 0 to the same datatype
zero = typed.convert(0, matrix._datatype);
}
// invoke callback
const invoke = function (v, x, y) {
// invoke callback
const value = callback(v, x, y);
// check value != 0
if (!eq(value, zero)) {
// store value
values.push(value);
// index
index.push(x);
}
};
// loop columns
for (let 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]
const k0 = matrix._ptr[j];
const k1 = matrix._ptr[j + 1];
if (skipZeros) {
// loop k within [k0, k1[
for (let k = k0; k < k1; k++) {
// row index
const i = matrix._index[k];
// check i is in range
if (i >= minRow && i <= maxRow) {
// value @ k
invoke(matrix._values[k], i - minRow, j - minColumn);
}
}
} else {
// create a cache holding all defined values
const values = {};
for (let k = k0; k < k1; k++) {
const i = matrix._index[k];
values[i] = matrix._values[k];
}
// loop over all rows (indexes can be unordered so we can't use that),
// and either read the value or zero
for (let i = minRow; i <= maxRow; i++) {
const value = i in values ? values[i] : 0;
invoke(value, i - minRow, j - minColumn);
}
}
}
// store number of values in ptr
ptr.push(values.length);
// return sparse matrix
return new SparseMatrix({
values,
index,
ptr,
size: [maxRow - minRow + 1, maxColumn - minColumn + 1]
});
}
/**
* Execute a callback function on each entry of the matrix.
* @memberof SparseMatrix
* @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.
* If false, the indices are guaranteed to be in order,
* if true, the indices can be unordered.
*/
SparseMatrix.prototype.forEach = function (callback, skipZeros) {
// check it is a pattern matrix
if (!this._values) {
throw new Error('Cannot invoke forEach on a Pattern only matrix');
}
// matrix instance
const me = this;
// rows and columns
const rows = this._size[0];
const columns = this._size[1];
const fastCallback = (0, _optimizeCallback.optimizeCallback)(callback, me, 'forEach');
// loop columns
for (let j = 0; j < columns; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
const k0 = this._ptr[j];
const k1 = this._ptr[j + 1];
if (skipZeros) {
// loop k within [k0, k1[
for (let k = k0; k < k1; k++) {
// row index
const i = this._index[k];
// value @ k
// TODO apply a non indexed version of algorithm in case fastCallback is not optimized
fastCallback.fn(this._values[k], [i, j], me);
}
} else {
// create a cache holding all defined values
const values = {};
for (let k = k0; k < k1; k++) {
const i = this._index[k];
values[i] = this._values[k];
}
// loop over all rows (indexes can be unordered so we can't use that),
// and either read the value or zero
for (let i = 0; i < rows; i++) {
const value = i in values ? values[i] : 0;
fastCallback.fn(value, [i, j], me);
}
}
}
};
/**
* Iterate over the matrix elements, skipping zeros
* @return {Iterable<{ value, index: number[] }>}
*/
SparseMatrix.prototype[Symbol.iterator] = function* () {
if (!this._values) {
throw new Error('Cannot iterate a Pattern only matrix');
}
const columns = this._size[1];
for (let j = 0; j < columns; j++) {
const k0 = this._ptr[j];
const k1 = this._ptr[j + 1];
for (let k = k0; k < k1; k++) {
// row index
const i = this._index[k];
yield {
value: this._values[k],
index: [i, j]
};
}
}
};
/**
* Create an Array with a copy of the data of the SparseMatrix
* @memberof SparseMatrix
* @returns {Array} array
*/
SparseMatrix.prototype.toArray = function () {
return _toArray(this._values, this._index, this._ptr, this._size, true);
};
/**
* Get the primitive value of the SparseMatrix: a two dimensions array
* @memberof SparseMatrix
* @returns {Array} array
*/
SparseMatrix.prototype.valueOf = function () {
return _toArray(this._values, this._index, this._ptr, this._size, false);
};
function _toArray(values, index, ptr, size, copy) {
// rows and columns
const rows = size[0];
const columns = size[1];
// result
const a = [];
// vars
let i, j;
// initialize array
for (i = 0; i < rows; i++) {
a[i] = [];
for (j = 0; j < columns; j++) {
a[i][j] = 0;
}
}
// loop columns
for (j = 0; j < columns; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
const k0 = ptr[j];
const k1 = ptr[j + 1];
// loop k within [k0, k1[
for (let k = k0; k < k1; k++) {
// row index
i = index[k];
// set value (use one for pattern matrix)
a[i][j] = values ? copy ? (0, _object.clone)(values[k]) : values[k] : 1;
}
}
return a;
}
/**
* Get a string representation of the matrix, with optional formatting options.
* @memberof SparseMatrix
* @param {Object | number | Function} [options] Formatting options. See
* lib/utils/number:format for a
* description of the available
* options.
* @returns {string} str
*/
SparseMatrix.prototype.format = function (options) {
// rows and columns
const rows = this._size[0];
const columns = this._size[1];
// density
const density = this.density();
// rows & columns
let str = 'Sparse Matrix [' + (0, _string.format)(rows, options) + ' x ' + (0, _string.format)(columns, options) + '] density: ' + (0, _string.format)(density, options) + '\n';
// loop columns
for (let j = 0; j < columns; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
const k0 = this._ptr[j];
const k1 = this._ptr[j + 1];
// loop k within [k0, k1[
for (let k = k0; k < k1; k++) {
// row index
const i = this._index[k];
// append value
str += '\n (' + (0, _string.format)(i, options) + ', ' + (0, _string.format)(j, options) + ') ==> ' + (this._values ? (0, _string.format)(this._values[k], options) : 'X');
}
}
return str;
};
/**
* Get a string representation of the matrix
* @memberof SparseMatrix
* @returns {string} str
*/
SparseMatrix.prototype.toString = function () {
return (0, _string.format)(this.toArray());
};
/**
* Get a JSON representation of the matrix
* @memberof SparseMatrix
* @returns {Object}
*/
SparseMatrix.prototype.toJSON = function () {
return {
mathjs: 'SparseMatrix',
values: this._values,
index: this._index,
ptr: this._ptr,
size: this._size,
datatype: this._datatype
};
};
/**
* Get the kth Matrix diagonal.
*
* @memberof SparseMatrix
* @param {number | BigNumber} [k=0] The kth diagonal where the vector will retrieved.
*
* @returns {Matrix} The matrix vector with the diagonal values.
*/
SparseMatrix.prototype.diagonal = function (k) {
// validate k if any
if (k) {
// convert BigNumber to a number
if ((0, _is.isBigNumber)(k)) {
k = k.toNumber();
}
// is must be an integer
if (!(0, _is.isNumber)(k) || !(0, _number.isInteger)(k)) {
throw new TypeError('The parameter k must be an integer number');
}
} else {
// default value
k = 0;
}
const kSuper = k > 0 ? k : 0;
const kSub = k < 0 ? -k : 0;
// rows & columns
const rows = this._size[0];
const columns = this._size[1];
// number diagonal values
const n = Math.min(rows - kSub, columns - kSuper);
// diagonal arrays
const values = [];
const index = [];
const ptr = [];
// initial ptr value
ptr[0] = 0;
// loop columns
for (let j = kSuper; j < columns && values.length < n; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
const k0 = this._ptr[j];
const k1 = this._ptr[j + 1];
// loop x within [k0, k1[
for (let x = k0; x < k1; x++) {
// row index
const i = this._index[x];
// check row
if (i === j - kSuper + kSub) {
// value on this column
values.push(this._values[x]);
// store row
index[values.length - 1] = i - kSub;
// exit loop
break;
}
}
}
// close ptr
ptr.push(values.length);
// return matrix
return new SparseMatrix({
values,
index,
ptr,
size: [n, 1]
});
};
/**
* Generate a matrix from a JSON object
* @memberof SparseMatrix
* @param {Object} json An object structured like
* `{"mathjs": "SparseMatrix", "values": [], "index": [], "ptr": [], "size": []}`,
* where mathjs is optional
* @returns {SparseMatrix}
*/
SparseMatrix.fromJSON = function (json) {
return new SparseMatrix(json);
};
/**
* Create a diagonal matrix.
*
* @memberof SparseMatrix
* @param {Array} size The matrix size.
* @param {number | Array | Matrix } value The values for the diagonal.
* @param {number | BigNumber} [k=0] The kth diagonal where the vector will be filled in.
* @param {number} [defaultValue] The default value for non-diagonal
* @param {string} [datatype] The Matrix datatype, values must be of this datatype.
*
* @returns {SparseMatrix}
*/
SparseMatrix.diagonal = function (size, value, k, defaultValue, datatype) {
if (!(0, _is.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 ((0, _is.isBigNumber)(s)) {
// convert it
s = s.toNumber();
}
// validate arguments
if (!(0, _is.isNumber)(s) || !(0, _number.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 ((0, _is.isBigNumber)(k)) {
k = k.toNumber();
}
// is must be an integer
if (!(0, _is.isNumber)(k) || !(0, _number.isInteger)(k)) {
throw new TypeError('The parameter k must be an integer number');
}
} else {
// default value
k = 0;
}
// equal signature to use
let eq = equalScalar;
// zero value
let zero = 0;
if ((0, _is.isString)(datatype)) {
// find signature that matches (datatype, datatype)
eq = typed.find(equalScalar, [datatype, datatype]) || equalScalar;
// convert 0 to the same datatype
zero = typed.convert(0, datatype);
}
const kSuper = k > 0 ? k : 0;
const kSub = k < 0 ? -k : 0;
// rows and columns
const rows = size[0];
const columns = size[1];
// number of non-zero items
const n = Math.min(rows - kSub, columns - kSuper);
// value extraction function
let _value;
// check value
if ((0, _is.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 if ((0, _is.isMatrix)(value)) {
// matrix size
const ms = value.size();
// validate matrix
if (ms.length !== 1 || ms[0] !== n) {
// number of values in array must be n
throw new Error('Invalid matrix length');
}
// define function
_value = function (i) {
// return value @ i
return value.get([i]);
};
} else {
// define function
_value = function () {
// return value
return value;
};
}
// create arrays
const values = [];
const index = [];
const ptr = [];
// loop items
for (let j = 0; j < columns; j++) {
// number of rows with value
ptr.push(values.length);
// diagonal index
const i = j - kSuper;
// check we need to set diagonal value
if (i >= 0 && i < n) {
// get value @ i
const v = _value(i);
// check for zero
if (!eq(v, zero)) {
// column
index.push(i + kSub);
// add value
values.push(v);
}
}
}
// last value should be number of values
ptr.push(values.length);
// create SparseMatrix
return new SparseMatrix({
values,
index,
ptr,
size: [rows, columns]
});
};
/**
* Swap rows i and j in Matrix.
*
* @memberof SparseMatrix
* @param {number} i Matrix row index 1
* @param {number} j Matrix row index 2
*
* @return {Matrix} The matrix reference
*/
SparseMatrix.prototype.swapRows = function (i, j) {
// check index
if (!(0, _is.isNumber)(i) || !(0, _number.isInteger)(i) || !(0, _is.isNumber)(j) || !(0, _number.isInteger)(j)) {
throw new Error('Row index must be positive integers');
}
// check dimensions
if (this._size.length !== 2) {
throw new Error('Only two dimensional matrix is supported');
}
// validate index
(0, _array.validateIndex)(i, this._size[0]);
(0, _array.validateIndex)(j, this._size[0]);
// swap rows
SparseMatrix._swapRows(i, j, this._size[1], this._values, this._index, this._ptr);
// return current instance
return this;
};
/**
* Loop rows with data in column j.
*
* @param {number} j Column
* @param {Array} values Matrix values
* @param {Array} index Matrix row indeces
* @param {Array} ptr Matrix column pointers
* @param {Function} callback Callback function invoked for every row in column j
*/
SparseMatrix._forEachRow = function (j, values, index, ptr, callback) {
// indeces for column j
const k0 = ptr[j];
const k1 = ptr[j + 1];
// loop
for (let k = k0; k < k1; k++) {
// invoke callback
callback(index[k], values[k]);
}
};
/**
* Swap rows x and y in Sparse Matrix data structures.
*
* @param {number} x Matrix row index 1
* @param {number} y Matrix row index 2
* @param {number} columns Number of columns in matrix
* @param {Array} values Matrix values
* @param {Array} index Matrix row indeces
* @param {Array} ptr Matrix column pointers
*/
SparseMatrix._swapRows = function (x, y, columns, values, index, ptr) {
// loop columns
for (let j = 0; j < columns; j++) {
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
const k0 = ptr[j];
const k1 = ptr[j + 1];
// find value index @ x
const kx = _getValueIndex(x, k0, k1, index);
// find value index @ x
const ky = _getValueIndex(y, k0, k1, index);
// check both rows exist in matrix
if (kx < k1 && ky < k1 && index[kx] === x && index[ky] === y) {
// swap values (check for pattern matrix)
if (values) {
const v = values[kx];
values[kx] = values[ky];
values[ky] = v;
}
// next column
continue;
}
// check x row exist & no y row
if (kx < k1 && index[kx] === x && (ky >= k1 || index[ky] !== y)) {
// value @ x (check for pattern matrix)
const vx = values ? values[kx] : undefined;
// insert value @ y
index.splice(ky, 0, y);
if (values) {
values.splice(ky, 0, vx);
}
// remove value @ x (adjust array index if needed)
index.splice(ky <= kx ? kx + 1 : kx, 1);
if (values) {
values.splice(ky <= kx ? kx + 1 : kx, 1);
}
// next column
continue;
}
// check y row exist & no x row
if (ky < k1 && index[ky] === y && (kx >= k1 || index[kx] !== x)) {
// value @ y (check for pattern matrix)
const vy = values ? values[ky] : undefined;
// insert value @ x
index.splice(kx, 0, x);
if (values) {
values.splice(kx, 0, vy);
}
// remove value @ y (adjust array index if needed)
index.splice(kx <= ky ? ky + 1 : ky, 1);
if (values) {
values.splice(kx <= ky ? ky + 1 : ky, 1);
}
}
}
};
return SparseMatrix;
}, {
isClass: true
});