CCS and Dense matrix updates

This commit is contained in:
rjbaucells 2015-03-07 22:24:41 -05:00
parent a072041fac
commit 9e214d030d
5 changed files with 680 additions and 72 deletions

View File

@ -6,6 +6,7 @@ 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 validateIndex = array.validateIndex;
@ -47,21 +48,36 @@ module.exports = function (math) {
for (var i = 0; i < rows; i++) {
// current row
var row = data[i];
// update columns
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)) {
// 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
this._values.push(v);
// index
this._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
this._values.push(v);
this._values.push(row);
// index
this._index.push(i);
}
}
}
}
// increment index
j++;
@ -286,7 +302,7 @@ module.exports = function (math) {
* var value = matrix.subset(index, replacement) // replace subset
*
* @param {Index} index
* @param {Array | DenseFormat | *} [replacement]
* @param {Array | CcsFormat | *} [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.
@ -304,9 +320,9 @@ module.exports = function (math) {
* 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 DenseFormat being traversed.
* of the element, and the Matrix being traversed.
* @param {Matrix} matrix The Matrix instance
* @return {DenseFormat} matrix
* @return {CcsFormat} matrix
*/
CcsFormat.prototype.map = function (callback, matrix) {
// result arrays
@ -327,11 +343,13 @@ module.exports = function (math) {
// store value
values.push(v);
// index
index.push(i);
index.push(x);
}
};
// loop columns
for (var j = 0; j < columns; j++) {
// store pointer to values index
ptr.push(values.length);
// k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1]
var k0 = this._ptr[j];
var k1 = this._ptr[j + 1];
@ -349,7 +367,7 @@ module.exports = function (math) {
// increment k
k++;
// update pointer
p = j + 1;
p = i + 1;
}
// zero values
for (var y = p; y < rows; y++)
@ -366,6 +384,178 @@ module.exports = function (math) {
});
};
/**
* 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 {Matrix} matrix The Matrix instance
*/
CcsFormat.prototype.forEach = function (callback, matrix) {
// values index
var k = 0;
// 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;
// check k is within [k0, k1[
while (k >= k0 && k < k1) {
// row index
var i = this._index[k];
// zero values
for (var x = p; x < i; x++)
callback(0, [x, j], matrix);
// value @ k
callback(this._values[k], [i, j], matrix);
// increment k
k++;
// update pointer
p = i + 1;
}
// zero values
for (var y = p; y < rows; y++)
callback(0, [y, j], matrix);
}
};
/**
* Resize the matrix
* @param {Number[]} size
* @param {*} [defaultValue=0] Default value, filled in on new entries.
* If not provided, the matrix elements will
* be filled with zeros.
* @return {CcsFormat} self The matrix itself is returned
*/
CcsFormat.prototype.resize = function (size, defaultValue) {
// 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) + ')');
}
});
// value to inser at the time of growing matrix
var value = (defaultValue !== undefined) ? defaultValue : 0;
// should we insert the value?
var ins = !math.equal(value, 0);
// old columns and rows
var r = this._size[0];
var c = this._size[1];
// rows & columns
var rows = size[0];
var columns = 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 ptr for current column
this._ptr[j] = this._values.length;
// check we need to insert values
if (ins) {
// loop rows
for (i = 0; i < r; i++) {
// add new values
this._values.push(value);
// update index
this._index.push(i);
}
}
}
// store number of values in ptr
this._ptr[columns] = this._values.length;
}
else if (columns < c) {
// truncate ptr
this._ptr.splice(columns + 1, c - columns);
// truncate values and index
this._values.splice(this._ptr[columns], this._values.length);
this._index.splice(this._ptr[columns], this._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 ptr for current column
this._ptr[j] = this._ptr[j] + n;
// where to insert values
k = this._ptr[j + 1] + n;
// pointer
var p = 0;
// loop new rows, initialize pointer
for (i = r; i < rows; i++, p++) {
// add value
this._values.splice(k + p, 0, value);
// update index
this._index.splice(k + p, 0, i);
// increment inserts
n++;
}
}
// store number of values in ptr
this._ptr[c] = this._values.length;
}
}
else if (rows < r) {
// deletes
var d = 0;
// loop columns
for (j = 0; j < c; j++) {
// update ptr for current column
this._ptr[j] = this._ptr[j] - d;
// where values start for next column
var k0 = this._ptr[j];
var k1 = this._ptr[j + 1] - d;
// loop index
for (k = k0; k < k1; k++) {
// row
i = this._index[k];
// check we need to delete value and index
if (i > rows - 1) {
// remove value
this._values.splice(k, 1);
// remove item from index
this._index.splice(k, 1);
// increase deletes
d++;
}
}
}
// update ptr for current column
this._ptr[j] = this._values.length;
}
// update size
this._size = [rows, columns];
// return the matrix itself
return this;
};
CcsFormat.diagonal = function (rows, columns, value) {
// create arrays
var values = [];

View File

@ -416,7 +416,7 @@ module.exports = function (math) {
* 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 DenseFormat being traversed.
* of the element, and the Matrix being traversed.
* @param {Matrix} matrix The Matrix instance
* @return {DenseFormat} matrix
*/
@ -442,7 +442,7 @@ module.exports = function (math) {
* 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 DenseFormat being traversed.
* of the element, and the Matrix being traversed.
* @param {Matrix} matrix The Matrix instance
*/
DenseFormat.prototype.forEach = function (callback, matrix) {

View File

@ -255,7 +255,7 @@ describe('matrix', function() {
it('should set a value in a matrix', function() {
var m = new Matrix([[0, 0], [0, 0]]);
m.set([1,0], 5);
m.set([1, 0], 5);
assert.deepEqual(m, new Matrix([
[0, 0],
[5, 0]
@ -512,26 +512,18 @@ describe('matrix', function() {
var m, m2;
m = new Matrix([
[[1,2],[3,4]],
[[5,6],[7,8]],
[[9,10],[11,12]],
[[13,14],[15,16]]
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11,12],
[13, 14, 15,16]
]);
m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(m2.valueOf(), [
[[2,4],[6,8]],
[[10,12],[14,16]],
[[18,20],[22,24]],
[[26,28],[30,32]]
assert.deepEqual(m2.toArray(), [
[2, 4, 6, 8],
[10, 12, 14, 16],
[18, 20, 22, 24],
[26, 28, 30, 32]
]);
m = new Matrix([1]);
m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(m2.valueOf(), [2]);
m = new Matrix([1,2,3]);
m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(m2.valueOf(), [2,4,6]);
});
it('should work on empty matrices', function() {
@ -541,43 +533,38 @@ describe('matrix', function() {
});
it('should invoke callback with parameters value, index, obj', function() {
var m = new Matrix([[1,2,3], [4,5,6]]);
var m = new Matrix([[1, 2, 3], [4, 5, 6]]);
var m2 = m.map(
function (value, index, obj) {
return value + index[0] * 100 + index[1] * 10 + (obj === m ? 1000 : 0);
}
);
var m2 = m.map(function (value, index, obj) {
return math.clone([value, index, obj === m]);
});
assert.deepEqual(
m2.toArray(),
[
[
[1, [0, 0], true ],
[2, [0, 1], true ],
[3, [0, 2], true ]
],
[
[4, [1, 0], true ],
[5, [1, 1], true ],
[6, [1, 2], true ]
]
[1001, 1012, 1023],
[1104, 1115, 1126]
]);
});
});
describe('forEach', function() {
it('should run on all elements of the matrix, last dimension first', function() {
it('should run on all elements of the matrix', function() {
var m, output;
m = new Matrix([
[[1,2],[3,4]],
[[5,6],[7,8]],
[[9,10],[11,12]],
[[13,14],[15,16]]
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
]);
output = [];
m.forEach(function (value) { output.push(value); });
assert.deepEqual(output, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);
// assert all values were visited, order is Matrix storage format specific
assert.deepEqual(output.sort(), [1, 10, 11,12, 13, 14, 15, 16, 2, 3, 4, 5, 6, 7, 8, 9]);
m = new Matrix([1]);
output = [];
@ -587,7 +574,8 @@ describe('matrix', function() {
m = new Matrix([1,2,3]);
output = [];
m.forEach(function (value) { output.push(value); });
assert.deepEqual(output, [1,2,3]);
// assert all values were visited, order is Matrix storage format specific
assert.deepEqual(output.sort(), [1, 2, 3]);
});
it('should work on empty matrices', function() {
@ -598,22 +586,21 @@ describe('matrix', function() {
});
it('should invoke callback with parameters value, index, obj', function() {
var m = new Matrix([[1,2,3], [4,5,6]]);
var m = new Matrix(
[
[1, 2, 3],
[4, 5, 6]
]);
var o = {};
var output = [];
m.forEach(function (value, index, obj) {
output.push(math.clone([value, index, obj === m]));
});
assert.deepEqual(output, [
[1, [0, 0], true ],
[2, [0, 1], true ],
[3, [0, 2], true ],
[4, [1, 0], true ],
[5, [1, 1], true ],
[6, [1, 2], true ]
]);
m.forEach(
function (value, index, obj) {
output.push(value + index[0] * 100 + index[1] * 10 + (obj === m ? 1000 : 0));
}
);
// assert all values were visited, order is Matrix storage format specific
assert.deepEqual(output.sort(), [1001, 1012, 1023, 1104, 1115, 1126]);
});
});
describe('clone', function() {

View File

@ -64,6 +64,15 @@ describe('CcsFormat', function() {
assert.deepEqual(m._ptr, [0]);
});
it('should create a CCS from a vector', function () {
var m = new CcsFormat([1, 2, 3]);
assert.equal(m._format, 'ccs');
assert.deepEqual(m._size, [3, 1]);
assert.deepEqual(m._values, [1, 2, 3]);
assert.deepEqual(m._index, [0, 1, 2]);
assert.deepEqual(m._ptr, [0, 3]);
});
it('should throw an error when called without new keyword', function () {
assert.throws(function () { CcsFormat(); }, /Constructor must be called with the new operator/);
});
@ -341,6 +350,212 @@ describe('CcsFormat', function() {
});
});
describe('resize', function() {
it('should increase columns as needed, zero value', function() {
var m = new CcsFormat(
[
[1, 2, 3],
[4, 5, 6]
]);
m.resize([2, 4]);
assert.deepEqual(m._size, [2, 4]);
assert.deepEqual(m._values, [1, 4, 2, 5, 3, 6]);
assert.deepEqual(m._index, [0, 1, 0, 1, 0, 1]);
assert.deepEqual(m._ptr, [0, 2, 4, 6, 6]);
assert.deepEqual(
m.toArray(),
[
[1, 2, 3, 0],
[4, 5, 6, 0]
]);
});
it('should increase columns as needed, non zero value', function() {
var m = new CcsFormat(
[
[1, 2, 3],
[4, 5, 6]
]);
m.resize([2, 4], 100);
assert.deepEqual(m._size, [2, 4]);
assert.deepEqual(m._values, [1, 4, 2, 5, 3, 6, 100, 100]);
assert.deepEqual(m._index, [0, 1, 0, 1, 0, 1, 0, 1]);
assert.deepEqual(m._ptr, [0, 2, 4, 6, 8]);
assert.deepEqual(
m.toArray(),
[
[1, 2, 3, 100],
[4, 5, 6, 100]
]);
});
it('should increase rows as needed, zero value', function() {
var m = new CcsFormat(
[
[1, 2, 3],
[4, 5, 6]
]);
m.resize([3, 3]);
assert.deepEqual(m._size, [3, 3]);
assert.deepEqual(m._values, [1, 4, 2, 5, 3, 6]);
assert.deepEqual(m._index, [0, 1, 0, 1, 0, 1]);
assert.deepEqual(m._ptr, [0, 2, 4, 6]);
assert.deepEqual(
m.toArray(),
[
[1, 2, 3],
[4, 5, 6],
[0, 0, 0]
]);
});
it('should increase rows as needed, non zero value', function() {
var m = new CcsFormat(
[
[1, 2, 3],
[4, 5, 6]
]);
m.resize([3, 3], 100);
assert.deepEqual(m._size, [3, 3]);
assert.deepEqual(m._values, [1, 4, 100, 2, 5, 100, 3, 6, 100]);
assert.deepEqual(m._index, [0, 1, 2, 0, 1, 2, 0, 1, 2]);
assert.deepEqual(m._ptr, [0, 3, 6, 9]);
assert.deepEqual(
m.toArray(),
[
[1, 2, 3],
[4, 5, 6],
[100, 100, 100]
]);
});
it('should increase rows & columns as needed, zero value, empty CCS', function() {
var m = new CcsFormat([]);
m.resize([2, 2]);
assert.deepEqual(m._size, [2, 2]);
assert.deepEqual(m._values, []);
assert.deepEqual(m._index, []);
assert.deepEqual(m._ptr, [0, 0, 0]);
assert.deepEqual(
m.toArray(),
[
[0, 0],
[0, 0]
]);
});
it('should increase rows & columns as needed, non zero value, empty CCS', function() {
var m = new CcsFormat([]);
m.resize([2, 2], 100);
assert.deepEqual(m._size, [2, 2]);
assert.deepEqual(m._values, [100, 100, 100, 100]);
assert.deepEqual(m._index, [0, 1, 0, 1]);
assert.deepEqual(m._ptr, [0, 2, 4]);
assert.deepEqual(
m.toArray(),
[
[100, 100],
[100, 100]
]);
});
it('should decrease columns as needed', function() {
var m = new CcsFormat(
[
[1, 2, 3],
[4, 5, 6]
]);
m.resize([2, 2]);
assert.deepEqual(m._size, [2, 2]);
assert.deepEqual(m._values, [1, 4, 2, 5]);
assert.deepEqual(m._index, [0, 1, 0, 1]);
assert.deepEqual(m._ptr, [0, 2, 4]);
assert.deepEqual(
m.toArray(),
[
[1, 2],
[4, 5]
]);
});
it('should decrease columns as needed, zero matrix', function() {
var m = new CcsFormat(
[
[0, 0, 0],
[0, 0, 0]
]);
m.resize([2, 2]);
assert.deepEqual(m._size, [2, 2]);
assert.deepEqual(m._values, []);
assert.deepEqual(m._index, []);
assert.deepEqual(m._ptr, [0, 0, 0]);
assert.deepEqual(
m.toArray(),
[
[0, 0],
[0, 0]
]);
});
it('should decrease rows as needed', function() {
var m = new CcsFormat(
[
[1, 2],
[3, 4]
]);
m.resize([1, 2]);
assert.deepEqual(m._size, [1, 2]);
assert.deepEqual(m._values, [1, 2]);
assert.deepEqual(m._index, [0, 0]);
assert.deepEqual(m._ptr, [0, 1, 2]);
assert.deepEqual(
m.toArray(),
[
[1, 2]
]);
});
it('should decrease rows as needed, zero CCS', function() {
var m = new CcsFormat(
[
[0, 0],
[0, 0]
]);
m.resize([1, 2]);
assert.deepEqual(m._size, [1, 2]);
assert.deepEqual(m._values, []);
assert.deepEqual(m._index, []);
assert.deepEqual(m._ptr, [0, 0, 0]);
assert.deepEqual(
m.toArray(),
[
[0, 0]
]);
});
it('should decrease rows & columns as needed, zero CCS', function() {
var m = new CcsFormat(
[
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
]);
m.resize([2, 2]);
assert.deepEqual(m._size, [2, 2]);
assert.deepEqual(m._values, []);
assert.deepEqual(m._index, []);
assert.deepEqual(m._ptr, [0, 0, 0]);
assert.deepEqual(
m.toArray(),
[
[0, 0],
[0, 0]
]);
});
});
describe('clone', function() {
it('should clone the matrix properly', function() {
@ -356,6 +571,107 @@ describe('CcsFormat', function() {
});
});
describe('map', function() {
it('should apply the given function to all elements in the matrix', function() {
var m, m2;
m = new CcsFormat([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11,12],
[13, 14, 15,16]
]);
m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(m2.toArray(), [
[2, 4, 6, 8],
[10, 12, 14, 16],
[18, 20, 22, 24],
[26, 28, 30, 32]
]);
m = new CcsFormat([1]);
m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(m2.toArray(), [[2]]);
m = new CcsFormat([1,2,3]);
m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(m2.toArray(), [[2],[4],[6]]);
});
it('should work on empty matrices', function() {
var m = new CcsFormat([]);
var m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(m2.toArray(), []);
});
it('should invoke callback with parameters value, index, obj', function() {
var m = new CcsFormat([[1, 2, 3], [4, 5, 6]]);
var o = {};
var m2 = m.map(
function (value, index, obj) {
return value + index[0] * 100 + index[1] * 10 + (obj === o ? 1000 : 0);
},
o
);
assert.deepEqual(
m2.toArray(),
[
[1001, 1012, 1023],
[1104, 1115, 1126]
]);
});
});
describe('forEach', function() {
it('should run on all elements of the matrix', function() {
var m, output;
m = new CcsFormat([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11,12],
[13, 14, 15,16]
]);
output = [];
m.forEach(function (value) { output.push(value); });
assert.deepEqual(output, [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16]);
m = new CcsFormat([1]);
output = [];
m.forEach(function (value) { output.push(value); });
assert.deepEqual(output, [1]);
m = new CcsFormat([1,2,3]);
output = [];
m.forEach(function (value) { output.push(value); });
assert.deepEqual(output, [1,2,3]);
});
it('should work on empty matrices', function() {
m = new CcsFormat([]);
output = [];
m.forEach(function (value) { output.push(value); });
assert.deepEqual(output, []);
});
it('should invoke callback with parameters value, index, obj', function() {
var m = new CcsFormat([[1,2,3], [4,5,6]]);
var o = {};
var output = [];
m.forEach(
function (value, index, obj) {
output.push(value + index[0] * 100 + index[1] * 10 + (obj === o ? 1000 : 0));
},
o
);
assert.deepEqual(output, [1001, 1104, 1012, 1115, 1023, 1126]);
});
});
describe('toString', function() {
it('should return string representation of matrix', function() {

View File

@ -314,6 +314,121 @@ describe('DenseFormat', function() {
assert.deepEqual(m1._data, m2._data);
});
});
describe('map', function() {
it('should apply the given function to all elements in the matrix', function() {
var m = new DenseFormat([
[[1,2],[3,4]],
[[5,6],[7,8]],
[[9,10],[11,12]],
[[13,14],[15,16]]
]);
var m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(
m2.valueOf(),
[
[[2,4],[6,8]],
[[10,12],[14,16]],
[[18,20],[22,24]],
[[26,28],[30,32]]
]);
m = new DenseFormat([1]);
m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(m2.valueOf(), [2]);
m = new DenseFormat([1,2,3]);
m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(m2.valueOf(), [2,4,6]);
});
it('should work on empty matrices', function() {
var m = new DenseFormat([]);
var m2 = m.map(function (value) { return value * 2; });
assert.deepEqual(m2.toArray(), []);
});
it('should invoke callback with parameters value, index, obj', function() {
var m = new DenseFormat([[1,2,3], [4,5,6]]);
var o = {};
var m2 = m.map(
function (value, index, obj) {
return math.clone([value, index, obj === o]);
},
o
);
assert.deepEqual(
m2.toArray(),
[
[
[1, [0, 0], true ],
[2, [0, 1], true ],
[3, [0, 2], true ]
],
[
[4, [1, 0], true ],
[5, [1, 1], true ],
[6, [1, 2], true ]
]
]);
});
});
describe('forEach', function() {
it('should run on all elements of the matrix, last dimension first', function() {
var m, output;
m = new DenseFormat([
[[1,2],[3,4]],
[[5,6],[7,8]],
[[9,10],[11,12]],
[[13,14],[15,16]]
]);
output = [];
m.forEach(function (value) { output.push(value); });
assert.deepEqual(output, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);
m = new DenseFormat([1]);
output = [];
m.forEach(function (value) { output.push(value); });
assert.deepEqual(output, [1]);
m = new DenseFormat([1,2,3]);
output = [];
m.forEach(function (value) { output.push(value); });
assert.deepEqual(output, [1,2,3]);
});
it('should work on empty matrices', function() {
m = new DenseFormat([]);
output = [];
m.forEach(function (value) { output.push(value); });
assert.deepEqual(output, []);
});
it('should invoke callback with parameters value, index, obj', function() {
var m = new DenseFormat([[1,2,3], [4,5,6]]);
var o = {};
var output = [];
m.forEach(
function (value, index, obj) {
output.push(math.clone([value, index, obj === o]));
},
o
);
assert.deepEqual(output, [
[1, [0, 0], true ],
[2, [0, 1], true ],
[3, [0, 2], true ],
[4, [1, 0], true ],
[5, [1, 1], true ],
[6, [1, 2], true ]
]);
});
});
describe('toString', function() {