static/js/dncp/util/ndarray.js
/**
* Numpy like n-dimensional array proccessing class
* http://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html
*
* @author pissang (https://github.com/pissang/)
*/
define(function (require) {
'use strict';
var kwargs = require('./kwargs');
var ArraySlice = Array.prototype.slice;
// Polyfill of Typed Array
this.Int32Array = window.Int32Array || Array;
this.Int16Array = window.Int16Array || Array;
this.Int8Array = window.Int8Array || Array;
this.Uint32Array = window.Uint32Array || Array;
this.Uint16Array = window.Uint16Array || Array;
this.Uint8Array = window.Uint8Array || Array;
this.Float32Array = window.Float32Array || Array;
this.Float64Array = window.Float64Array || Array;
// Map of numpy dtype and typed array
// http://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html#arrays-dtypes
// http://www.khronos.org/registry/typedarray/specs/latest/
var ArrayConstructor = {
'int32' : this.Int32Array,
'int16' : this.Int16Array,
'int8' : this.Int8Array,
'uint32' : this.Uint32Array,
'uint16' : this.Uint16Array,
'uint8' : this.Uint8Array,
// 'uint8c' is not existed in numpy
'uint8c' : this.Uint8ClampedArray,
'float32' : this.Float32Array,
'float64' : this.Float64Array,
'number' : Array
};
var dTypeStrideMap = {
'int32' : 4,
'int16' : 2,
'int8' : 1,
'uint32' : 4,
'uint16' : 2,
'uint8' : 1,
'uint8c' : 1,
'float32' : 4,
'float64' : 8,
// Consider array stride is 1
'number' : 1
};
var E_ADD = 0;
var E_SUB = 1;
var E_MUL = 2;
var E_DIV = 3;
var E_MOD = 4;
var E_AND = 5;
var E_OR = 6;
var E_XOR = 7;
var E_EQL = 8;
function guessDataType(arr) {
if (typeof(arr) === 'undefined') {
return 'number';
}
switch(Object.prototype.toString.call(arr)) {
case '[object Int32Array]':
return 'int32';
case '[object Int16Array]':
return 'int16';
case '[object Int8Array]':
return 'int8';
case '[object Uint32Array]':
return 'uint32';
case '[object Uint16Array]':
return 'uint16';
case '[object Uint8Array]':
return 'uint8';
case '[object Uint8ClampedArray]':
return 'uint8c';
case '[object Float32Array]':
return 'float32';
case '[object Float64Array]':
return 'float64';
default:
return 'number';
}
}
/**
* NDArray
* @param {Array|NDArray} array
* @param {String} dtype
*/
var NDArray = function (array) {
// Last argument describe the data type of ndarray
var dtype = arguments[arguments.length-1];
if (typeof(dtype) == 'string') {
this._dtype = dtype;
} else {
// Normal array
this._dtype = guessDataType(array);
}
if (array && typeof(array) !== 'string') {
if (array instanceof NDArray) {
array._dtype = this._dtype;
return array;
} else if (typeof(array.length) !== 'undefined') {
// Init from array
this.initFromArray(array);
} else if(typeof(array) === 'number') {
// Init from shape
this.initFromShape.apply(this, arguments);
}
} else {
/**
* _array
* Initialized with an empty array
* Data is continuous one-dimensional array, row-major
* A [2, 2] dim empty array is stored like
* [0,0, 0,0]
* TODO : Consider column majors ?
* @type {ArrayConstructor}
*/
this._array = new ArrayConstructor[this._dtype]();
/**
* _shape
* a tuple array describe the dimension and size of each dimension
* [10, 10] means a 10x10 array
* @type {Array}
*/
this._shape = [0];
/**
* _size
* size of the storage array length
* @type {Number}
*/
this._size = 0;
}
};
NDArray.prototype = {
/**
* Initialize from a normal js array.
*
* @param {Array} input
* @return {NDArray} this
*/
initFromArray : function (input) {
var dim = getDimension(input);
var cursor = 0;
function flatten(axis, _out, _in) {
var len = _in.length;
for (var i = 0; i < len; i++) {
if (axis < dim-1) {
flatten(axis+1, _out, _in[i]);
} else {
_out[cursor++] = _in[i];
}
}
}
var shape = getShape(input);
var size = getSize(shape);
this._array = new ArrayConstructor[this._dtype](size);
flatten(0, this._array, input);
this._shape = shape;
this._size = size;
return this;
},
/**
* Initialize from the given shape description.
* @param {Array} shape
* @return {NDArray} this
*/
initFromShape : function (shape) {
if (typeof(shape) == 'number') {
shape = Array.prototype.slice.call(arguments);
}
if(shape) {
var size = getSize(shape);
if (this._dtype === 'number') {
this._array = [];
var data = this._array;
for (var i = 0; i < size; i++) {
data[i] = 0;
}
} else {
this._array = new ArrayConstructor[this._dtype](size);
}
}
this._shape = shape;
this._size = getSize(shape);
return this;
},
/**
* Fill the array with the given value.
* @param {Number} value
* @return {NDArray} this
*/
fill : function (value) {
var data = this._array;
for (var i = 0; i < data.length; i++) {
data[i] = value;
}
return this;
},
/**
* Get ndarray shape copy.
* @return {Array}
*/
shape : function () {
// Create a copy
return this._shape.slice();
},
/**
* Get array size
* @return {Number}
*/
size : function () {
return this._size;
},
/**
* Get array data type.
* 'int32'
* 'int16'
* 'int8'
* 'uint32'
* 'uint16'
* 'uint8'
* 'float32'
* 'float64'
* @return {String}
*/
dtype : function () {
return this._dtype;
},
/**
* Get array dimension.
* @return {number} [description]
*/
dimension : function () {
return this._shape.length;
},
/**
* Tuple of bytes to step in each dimension when traversing an array.
* @return {Array}
*/
strides : function () {
var strides = calculateDimStrides(this._shape);
var dTypeStride = dTypeStrideMap[this._dtype];
for (var i = 0; i < strides.length; i++) {
strides[i] *= dTypeStride;
}
return strides;
},
/**
* Gives a new shape to an array without changing its data.
* @param {Array} shape
* @return {NDArray}
*/
reshape : function (shape) {
if (typeof(shape) == 'number') {
shape = Array.prototype.slice.call(arguments);
}
if (this._isShapeValid(shape)) {
this._shape = shape;
} else {
throw new Error('Total size of new array must be unchanged');
}
return this;
},
_isShapeValid : function (shape) {
return getSize(shape) === this._size;
},
/**
* Change shape and size of array in-place.
* @param {Array} shape
* @return {NDArray}
*/
resize : function (shape) {
if (typeof(shape) == 'number') {
shape = Array.prototype.slice.call(arguments);
}
var len = getSize(shape);
if (len < this._size) {
if (this._dtype === 'number') {
this._array.length = len;
}
} else {
if (this._dtype === 'number') {
for (var i = this._array.length; i < len; i++) {
// Fill the rest with zero
this._array[i] = 0;
}
} else {
// Reallocate new buffer
var newArr = new ArrayConstructor[this._dtype](len);
var originArr = this._array;
// Copy data
for (var i = 0; i < originArr.length; i++) {
newArr[i] = originArr[i];
}
this._array = newArr;
}
}
this._shape = shape;
this._size = len;
return this;
},
/**
* Returns a new array with axes transposed.
* @param {Array} [axes]
* @param {NDArray} [out]
* @return {NDArray}
*/
transpose : kwargs(function (axes, out) {
var originAxes = [];
for (var i = 0; i < this._shape.length; i++) {
originAxes.push(i);
}
if (typeof(axes) === 'undefined') {
axes = originAxes.slice();
}
// Check if any axis is out of bounds
for (var i = 0; i < axes.length; i++) {
if (axes[i] >= this._shape.length) {
throw new Error(axisOutofBoundsErrorMsg(axes[i]));
}
}
// Has no effect on 1-D transpose
if (axes.length <= 1) {
return this;
}
var targetAxes = originAxes.slice();
for (var i = 0; i < Math.floor(axes.length / 2); i++) {
for (var j = axes.length-1; j >= Math.ceil(axes.length / 2) ; j--) {
// Swap axes
targetAxes[axes[i]] = axes[j];
targetAxes[axes[j]] = axes[i];
}
}
return this._transposelike(targetAxes, out);
}),
/**
* Return a new array with axis1 and axis2 interchanged.
* @param {Number} axis1
* @param {Number} axis2
* @param {NDArray} out
* @return {NDArray}
*/
swapaxes : kwargs(function (axis1, axis2, out) {
return this.transpose([axis1, axis2], out);
}),
/**
* Roll the specified axis backwards, until it lies in a given position.
* @param {Number} axis
* @param {Number} [start=0]
* @param {NDArray} out
* @return {NDArray}
*/
rollaxis : kwargs(function (axis, start, out) {
if (axis >= this._shape.length) {
throw new Error(axisOutofBoundsErrorMsg(axis));
}
var axes = [];
for (var i = 0; i < this._shape.length; i++) {
axes.push(i);
}
axes.splice(axis, 1);
axes.splice(start, 0, axis);
return this._transposelike(axes, out);
}, { start : 0}),
// Base function for transpose-like operations
_transposelike : function (axes, out) {
var source = this._array;
var shape = this._shape.slice();
var strides = calculateDimStrides(this._shape);
var dim = shape.length;
// Swap
var tmpStrides = [];
var tmpShape = [];
for (var i = 0; i < axes.length; i++) {
var axis = axes[i];
// swap to target axis
tmpShape[i] = shape[axis];
tmpStrides[i] = strides[axis];
}
strides = tmpStrides;
shape = tmpShape;
this._shape = shape;
var transposedStrides = calculateDimStrides(this._shape);
if (!out) {
out = new NDArray();
out._shape = this._shape.slice();
out._dtype = this._dtype;
out._size = this._size;
}
// FIXME in-place transpose?
var transposedData = new ArrayConstructor[this._dtype](this._size);
out._array = transposedData;
// @param Item offset in current axis offset of the original array
// @param Item offset in current axis offset of the transposed array
function transpose(axis, offset, transposedOffset) {
var size = shape[axis];
// strides in orginal array
var stride = strides[axis];
// strides in transposed array
var transposedStride = transposedStrides[axis];
if (axis < dim-1) {
for (var i = 0; i < size; i++) {
transpose(
axis+1,
offset + stride * i,
transposedOffset + transposedStride * i
);
}
} else {
for (var i = 0; i < size; i++) {
// offset + stride * i is the index of the original array
// transposedOffset + i is the index of the transposed array
transposedData[transposedOffset + i]
= source[offset + stride * i];
}
}
}
transpose(0, 0, 0);
return out;
},
/**
* Repeat elements of an array along axis
* @param {Number} repeats
* The number of repetitions for each element.
* repeats is broadcasted to fit the shape of the given axis.
* @param {Number} [axis]
* The axis along which to repeat values.
* By default, use the flattened input array,
* and return a flat output array.
* @param {NDArray} [out]
* @return {NDArray}
*/
repeat : kwargs(function (repeats, axis, out) {
var shape;
// flattened input array
if (typeof(axis) === 'undefined') {
shape = [this._size];
axis = 0;
} else {
shape = this._shape.slice();
}
var originShape = shape.slice();
shape[axis] *= repeats;
if (!out) {
out = new NDArray(this._dtype);
out.initFromShape(shape);
} else {
if (!arrayEqual(shape, out._shape)) {
throw new Error(broadcastErrorMsg(shape, out._shape));
}
}
var data = out._array;
var stride = calculateDimStride(originShape, axis);
var axisSize = originShape[axis];
var source = this._array;
var offsetStride = stride * axisSize;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var k = 0; k < stride; k++) {
var idx = offset + k;
var idxRepeated = offset * repeats + k;
for (var i = 0; i < axisSize; i++) {
for (var j = 0; j < repeats; j++) {
data[idxRepeated] = source[idx];
idxRepeated += stride;
}
idx += stride;
}
}
}
return out;
}),
choose : function () {
console.warn('TODO');
},
take : function () {
console.warn('TODO');
},
tile : function () {
console.warn('TODO');
},
/**
* Preprocess for array calculation
* max, min, argmax, argmin, sum, ptp, val, mean
* Which will reduce one axis if the axis is given
*
* @param {Number} axis
* @param {NDArray} out
* @param {Function} funcWithAxis
* @param {Function} funcFlatten
* @return {Number|NDArray}
*/
_withPreprocess1 : function (axis, out, funcWithAxis, funcFlatten) {
var source = this._array;
if (!this._size) {
return;
}
if (typeof(axis)!=='undefined') {
if (axis < 0) {
axis = this._shape.length + axis;
}
if (axis >= this._shape.length || axis < 0) {
throw new Error(axisOutofBoundsErrorMsg(axis));
}
var shape = this._shape.slice();
shape.splice(axis, 1);
if (out && !arrayEqual(shape, out._shape)) {
throw new Error(broadcastErrorMsg(shape, out._shape));
}
if (!out) {
out = new NDArray(this._dtype);
out.initFromShape(shape);
}
var data = out._array;
var stride = calculateDimStride(this._shape, axis);
var axisSize = this._shape[axis];
var offsetStride = stride * axisSize;
funcWithAxis.call(
this, data, source, offsetStride, axisSize, stride
);
return out;
} else {
return funcFlatten.call(this, source);
}
},
/**
* Preprocess for array calculation cumsum, cumprod
* Which will keep the shape if axis is given
* and flatten if axis is undefined
* @param {Number} axis
* @param {NDArray} out
* @param {Function} funcWithAxis
* @param {Function} funcFlatten
* @return {NDArray}
*/
_withPreprocess2 : function (axis, out, funcWithAxis, funcFlatten) {
var source = this._array;
if (!this._size) {
return;
}
if (out && !arrayEqual(this._shape, out._shape)) {
throw new Error(broadcastErrorMsg(this._shape, out._shape));
}
if (!out) {
out = new NDArray(this._dtype);
out.initFromShape(this._shape);
}
var data = out._array;
if (typeof(axis)!=='undefined') {
if (axis < 0) {
axis = this._shape.length + axis;
}
if (axis >= this._shape.length || axis < 0) {
throw new Error(axisOutofBoundsErrorMsg(axis));
}
if (axis >= this._shape.length) {
throw new Error(axisOutofBoundsErrorMsg(axis));
}
var stride = calculateDimStride(this._shape, axis);
var axisSize = this._shape[axis];
var offsetStride = stride * axisSize;
funcWithAxis.call(
this, data, source, offsetStride, axisSize, stride
);
} else {
out.reshape([this._size]);
funcFlatten.call(this, data, source);
}
return out;
},
/**
* Get the max value of ndarray
* If the axis is given, the max is only calculate in this dimension
* Example, for the given ndarray
* [[3, 9],
* [4, 8]]
* >>> max(0)
* [4, 9]
* >>> max(1)
* [9, 8]
*
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
max : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
var cursor = 0;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var idx = i + offset;
var max = source[idx];
for (var j = 0; j < axisSize; j++) {
var d = source[idx];
if (d > max) {
max = d;
}
idx += stride;
}
data[cursor++] = max;
}
}
}
function withFlatten(source) {
var max = source[0];
for (var i = 1; i < this._size; i++) {
if (source[i] > max) {
max = source[i];
}
}
return max;
}
return function (axis, out) {
return this._withPreprocess1(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Return the minimum of an array or minimum along an axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
min : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
var cursor = 0;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var idx = i + offset;
var min = source[idx];
for (var j = 0; j < axisSize; j++) {
var d = source[idx];
if (d < min) {
min = d;
}
idx += stride;
}
data[cursor++] = min;
}
}
}
function withFlatten(source) {
var min = source[0];
for (var i = 1; i < this._size; i++) {
if (source[i] < min) {
min = source[i];
}
}
return min;
}
return function (axis, out) {
return this._withPreprocess1(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Return indices of the maximum values along an axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
argmax : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
var cursor = 0;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var dataIdx = 0;
var idx = i + offset;
var max = source[idx];
for (var j = 0; j < axisSize; j++) {
var d = source[idx];
if (d > max) {
max = d;
dataIdx = j;
}
idx += stride;
}
data[cursor++] = dataIdx;
}
}
}
function withFlatten(source) {
var max = source[0];
var idx = 0;
for (var i = 1; i < this._size; i++) {
if (source[i] > max) {
idx = i;
max = source[i];
}
}
return idx;
}
return function (axis, out) {
return this._withPreprocess1(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Indices of the minimum values along an axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
argmin : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
var cursor = 0;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var dataIdx = 0;
var idx = i + offset;
var min = source[idx];
for (var j = 0; j < axisSize; j++) {
var d = source[idx];
if (d < min) {
min = d;
dataIdx = j;
}
idx += stride;
}
data[cursor++] = dataIdx;
}
}
}
function withFlatten(source) {
var min = source[0];
var idx = 0;
for (var i = 1; i < this._size; i++) {
if (source[i] < min) {
idx = i;
min = source[i];
}
}
return idx;
}
return function (axis, out) {
return this._withPreprocess1(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Return the sum of the array elements over the given axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
sum : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
var cursor = 0;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var sum = 0;
var idx = i + offset;
for (var j = 0; j < axisSize; j++) {
sum += source[idx];
idx += stride;
}
data[cursor++] = sum;
}
}
}
function withFlatten(source) {
var sum = 0;
for (var i = 0; i < this._size; i++) {
sum += source[i];
}
return sum;
}
return function (axis, out) {
return this._withPreprocess1(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Return the product of the array elements over the given axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
prod : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
var cursor = 0;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var prod = 1;
var idx = i + offset;
for (var j = 0; j < axisSize; j++) {
prod *= source[idx];
idx += stride;
}
data[cursor++] = prod;
}
}
}
function withFlatten(source) {
var prod = 1;
for (var i = 0; i < this._size; i++) {
prod *= source[i];
}
return prod;
}
return function (axis, out) {
return this._withPreprocess1(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Returns the average of the array elements along given axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
mean : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
var cursor = 0;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var sum = 0;
var idx = i + offset;
for (var j = 0; j < axisSize; j++) {
sum += source[idx];
idx += stride;
}
var mean = sum / axisSize;
data[cursor++] = mean;
}
}
}
function withFlatten(source) {
var sum = 0;
var len = source.length;
for (var i = 0; i < len; i++) {
sum += source[i];
}
var mean = sum / len;
return mean;
}
return function (axis, out) {
return this._withPreprocess1(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Return the variance of the array elements over the given axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
'var' : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
var cursor = 0;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var sum = 0;
var idx = i + offset;
for (var j = 0; j < axisSize; j++) {
sum += source[idx];
idx += stride;
}
var mean = sum / axisSize;
var moments = 0;
idx = i + offset;
for (var j = 0; j < axisSize; j++) {
var diff = source[idx] - mean;
moments += diff * diff;
idx += stride;
}
data[cursor++] = moments / axisSize;
}
}
}
function withFlatten(source) {
var sum = 0;
var len = source.length;
for (var i = 0; i < len; i++) {
sum += source[i];
}
var mean = sum / len;
var moments = 0;
for (var i = 0; i < len; i++) {
var diff = source[i] - mean;
moments += diff * diff;
}
return moments / len;
}
return function (axis, out) {
return this._withPreprocess1(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Return the standard derivatione of the array elements
* over the given axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
std : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
var cursor = 0;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var sum = 0;
var idx = i + offset;
for (var j = 0; j < axisSize; j++) {
sum += source[idx];
idx += stride;
}
var mean = sum / axisSize;
var moments = 0;
idx = i + offset;
for (var j = 0; j < axisSize; j++) {
var diff = source[idx] - mean;
moments += diff * diff;
idx += stride;
}
data[cursor++] = Math.sqrt(moments / axisSize);
}
}
}
function withFlatten(source) {
var sum = 0;
var len = source.length;
for (var i = 0; i < len; i++) {
sum += source[i];
}
var mean = sum / len;
var moments = 0;
for (var i = 0; i < len; i++) {
var diff = source[i] - mean;
moments += diff * diff;
}
return Math.sqrt(moments / len);
}
return function (axis, out) {
return this._withPreprocess1(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Peak to peak (maximum - minimum) value along a given axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
ptp : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
var cursor = 0;
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var idx = offset + i;
var min = source[idx];
var max = source[idx];
for (var j = 0; j < axisSize; j++) {
var d = source[idx];
if (d < min) {
min = d;
}
if (d > max) {
max = d;
}
idx += stride;
}
data[cursor++] = max - min;
}
}
}
function withFlatten(source) {
var min = source[0];
var max = source[0];
for (var i = 1; i < this._size; i++) {
if (source[i] < min) {
min = source[i];
}
if (source[i] > max) {
max = source[i];
}
}
return max - min;
}
return function (axis, out) {
return this._withPreprocess1(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
*
* @param {Number} [axis=-1]
* @param {string} [order='ascending']
* 'ascending' | 'descending'
* @return {NDArray}
*/
// FIXME : V8 is quick sort, firefox and safari is merge sort
// order : ascending or desc
sort : kwargs(function (axis, order) {
if (axis < 0) {
axis = this._shape.length + axis;
}
var compareFunc;
if (order === 'ascending') {
compareFunc = function (a, b) {
return a - b;
};
} else if( order === 'descending') {
compareFunc = function (a, b) {
return b - a;
};
}
var source = this._array;
var stride = calculateDimStride(this._shape, axis);
var axisSize = this._shape[axis];
var offsetStride = stride * axisSize;
var tmp = new Array(axisSize);
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var idx = offset + i;
for (var j = 0; j < axisSize; j++) {
tmp[j] = source[idx];
idx += stride;
}
tmp.sort(compareFunc);
var idx = offset + i;
// Copy back
for (var j = 0; j < axisSize; j++) {
source[idx] = tmp[j];
idx += stride;
}
}
}
return this;
}, {axis : -1, order : 'ascending'}),
/**
*
* @param {Number} [axis=-1]
* @param {string} [order='ascending']
* 'ascending' | 'descending'
* @param {NDArray} [out]
* @return {NDArray}
*/
argsort : kwargs(function (axis, order, out) {
if (axis < 0) {
axis = this._shape.length + axis;
}
if (!this._size) {
return;
}
if (out && !arrayEqual(this._shape, out._shape)) {
throw new Error(broadcastErrorMsg(this._shape, out._shape));
}
if (!out) {
out = new NDArray(this._dtype);
out.initFromShape(this._shape);
}
var data = out._array;
var compareFunc;
if (order === 'ascending') {
compareFunc = function (a, b) {
return tmp[a] - tmp[b];
};
} else if( order === 'descending') {
compareFunc = function (a, b) {
return tmp[b] - tmp[a];
};
}
var source = this._array;
var stride = calculateDimStride(this._shape, axis);
var axisSize = this._shape[axis];
var offsetStride = stride * axisSize;
var tmp = new Array(axisSize);
var indexList = new Array(axisSize);
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var idx = offset + i;
for (var j = 0; j < axisSize; j++) {
tmp[j] = source[idx];
indexList[j] = j;
idx += stride;
}
indexList.sort(compareFunc);
// Copy back
var idx = offset + i;
for (var j = 0; j < axisSize; j++) {
data[idx] = indexList[j];
idx += stride;
}
}
}
return out;
}, {axis : -1, order : 'ascending'}),
/**
* Return the cumulative sum of the elements along the given axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
cumsum : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var idx = offset + i;
var prevIdx = idx;
data[idx] = source[idx];
for (var j = 1; j < axisSize; j++) {
prevIdx = idx;
idx += stride;
data[idx] = data[prevIdx] + source[idx];
}
}
}
}
function withFlatten(data, source) {
data[0] = source[0];
for (var i = 1; i < data.length; i++) {
data[i] = data[i-1] + source[i];
}
}
return function (axis, out) {
return this._withPreprocess2(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Return the cumulative product of the elements along the given axis.
* @param {Number} [axis]
* @param {NDArray} out
* @return {NDArray}
*/
cumprod : kwargs((function () {
function withAxis(data, source, offsetStride, axisSize, stride) {
for (var offset = 0; offset < this._size; offset+=offsetStride) {
for (var i = 0; i < stride; i++) {
var idx = offset + i;
var prevIdx = idx;
data[idx] = source[idx];
for (var j = 1; j < axisSize; j++) {
prevIdx = idx;
idx += stride;
data[idx] = data[prevIdx] * source[idx];
}
}
}
}
function withFlatten(data, source) {
data[0] = source[0];
for (var i = 1; i < data.length; i++) {
data[i] = data[i-1] * source[i];
}
}
return function (axis, out) {
return this._withPreprocess2(
axis, out,
withAxis, withFlatten
);
};
})()),
/**
* Dot product of two arrays.
*
* @param {NDArray|Number} b
* @param {NDArray} [out]
* @return {NDArray|Number}
*/
dot : function () {
console.warn('TODO');
},
/**
* Mapped to region [min, max]
* @param {Number} mappedMin
* @param {Number} mappedMax
*/
map : function (mappedMin, mappedMax) {
var input = this._array;
var output = this._array;
var min = input[0];
var max = input[0];
var l = this._size;
for (var i = 1; i < l; i++) {
var val = input[i];
if (val < min) {
min = val;
}
if (val > max) {
max = val;
}
}
var range = max - min;
var mappedRange = mappedMax - mappedMin;
for (var i = 0; i < l; i++) {
if (range === 0) {
output[i] = mappedMin;
} else {
var val = input[i];
var percent = (val - min) / range;
output[i] = mappedRange * percent + mappedMin;
}
}
return this;
},
/**
* Add
*/
add : function (rightOperand, out) {
return this.binaryOperation(
this, rightOperand, E_ADD, out
);
},
/**
* Substract
*/
sub : function (rightOperand, out) {
return this.binaryOperation(
this, rightOperand, E_SUB, out
);
},
/**
* Multiply
*/
mul : function (rightOperand, out) {
return this.binaryOperation(
this, rightOperand, E_MUL, out
);
},
/**
* Divide
*/
div : function (rightOperand, out) {
return this.binaryOperation(
this, rightOperand, E_DIV, out
);
},
/**
* mod
*/
mod : function (rightOperand, out) {
return this.binaryOperation(
this, rightOperand, E_MOD, out
);
},
/**
* and
*/
and : function (rightOperand, out) {
return this.binaryOperation(
this, rightOperand, E_AND, out
);
},
/**
* or
*/
or : function (rightOperand, out) {
return this.binaryOperation(
this, rightOperand, E_OR, out
);
},
/**
* xor
*/
xor : function (rightOperand, out) {
return this.binaryOperation(
this, rightOperand, E_XOR, out
);
},
/**
* equal
*/
equal : function (rightOperand, out) {
return this.binaryOperation(
this, rightOperand, E_EQL, out
);
},
binaryOperation : function (lo, ro, op, out) {
// Broadcasting
// http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html
var shape = [];
var isLoScalar = typeof(lo) === 'number';
var isRoScalar = typeof(ro) === 'number';
if (isLoScalar) {
shape = ro._shape.slice();
} else if (isRoScalar) {
shape = lo._shape.slice();
} else {
// Starts with the trailing dimensions
var cl = lo._shape.length-1;
var cr = ro._shape.length-1;
var loBroadCasted = lo;
var roBroadCasted = ro;
while (cl >= 0 && cr >= 0) {
if (lo._shape[cl] == 1) {
shape.unshift(ro._shape[cr]);
loBroadCasted = lo.repeat(ro._shape[cr], cl);
} else if(ro._shape[cr] == 1) {
shape.unshift(lo._shape[cl]);
roBroadCasted = ro.repeat(lo._shape[cl], cr);
} else if(ro._shape[cr] == lo._shape[cl]) {
shape.unshift(lo._shape[cl]);
} else {
throw new Error(broadcastErrorMsg(lo._shape, ro._shape));
}
cl --;
cr --;
}
for (var i = cl; i >= 0; i--) {
shape.unshift(lo._shape[i]);
}
for (var i = cr; i >= 0; i--) {
shape.unshift(ro._shape[i]);
}
lo = loBroadCasted;
ro = roBroadCasted;
}
if (!out) {
out = new NDArray(this._dtype);
out.initFromShape(shape);
} else {
if (! arrayEqual(shape, out._shape)) {
throw new Error(broadcastErrorMsg(shape, out._shape));
}
}
var outData = out._array;
var diffAxis;
var isLoLarger;
var loData;
var roData;
if (isLoScalar) {
diffAxis = ro._shape.length-1;
isLoLarger = false;
loData = lo;
roData = ro._array;
} else if(isRoScalar) {
diffAxis = lo._shape.length-1;
isLoLarger = true;
roData = ro;
loData = lo._array;
} else {
diffAxis = Math.abs(lo._shape.length - ro._shape.length);
isLoLarger = lo._shape.length >= ro._shape.length;
loData = lo._array;
roData = ro._array;
}
var stride = calculateDimStride(shape, diffAxis);
var axisSize = shape[diffAxis];
var offsetStride = stride * axisSize;
var offsetRepeats = out._size / offsetStride;
var _a, _b, res;
var idx = 0;
if (isLoLarger) {
if(isRoScalar) {
for (var c = 0; c < offsetRepeats; c++) {
for (var i = 0; i < offsetStride; i++) {
_a = loData[idx]; _b = roData;
switch (op) {
case E_ADD: res = _a + _b; break;
case E_SUB: res = _a - _b; break;
case E_MUL: res = _a * _b; break;
case E_DIV: res = _a / _b; break;
case E_MOD: res = _a % _b; break;
case E_AND: res = _a & _b; break;
case E_OR: res = _a | _b; break;
case E_XOR: res = _a ^ _b; break;
case E_EQL: res = _a == _b; break;
default: throw new Error('Unkown operation ' + op);
}
outData[idx] = res;
idx ++;
}
}
} else {
for (var c = 0; c < offsetRepeats; c++) {
for (var i = 0; i < offsetStride; i++) {
_a = loData[idx]; _b = roData[i];
switch (op) {
case E_ADD: res = _a + _b; break;
case E_SUB: res = _a - _b; break;
case E_MUL: res = _a * _b; break;
case E_DIV: res = _a / _b; break;
case E_MOD: res = _a % _b; break;
case E_AND: res = _a & _b; break;
case E_OR: res = _a | _b; break;
case E_XOR: res = _a ^ _b; break;
case E_EQL: res = _a == _b; break;
default: throw new Error('Unkown operation ' + op);
}
outData[idx] = res;
idx ++;
}
}
}
} else {
if (isLoScalar) {
for (var c = 0; c < offsetRepeats; c++) {
for (var i = 0; i < offsetStride; i++) {
_a = loData; _b = roData[idx];
switch (op) {
case E_ADD: res = _a + _b; break;
case E_SUB: res = _a - _b; break;
case E_MUL: res = _a * _b; break;
case E_DIV: res = _a / _b; break;
case E_MOD: res = _a % _b; break;
case E_AND: res = _a & _b; break;
case E_OR: res = _a | _b; break;
case E_XOR: res = _a ^ _b; break;
case E_EQL: res = _a == _b; break;
default: throw new Error('Unkown operation ' + op);
}
outData[idx] = res;
idx ++;
}
}
} else {
for (var c = 0; c < offsetRepeats; c++) {
for (var i = 0; i < offsetStride; i++) {
_a = loData[idx]; _b = roData[i];
switch (op) {
case E_ADD: res = _a + _b; break;
case E_SUB: res = _a - _b; break;
case E_MUL: res = _a * _b; break;
case E_DIV: res = _a / _b; break;
case E_MOD: res = _a % _b; break;
case E_AND: res = _a & _b; break;
case E_OR: res = _a | _b; break;
case E_XOR: res = _a ^ _b; break;
case E_EQL: res = _a == _b; break;
default: throw new Error('Unkown operation ' + op);
}
outData[idx] = res;
idx ++;
}
}
}
}
return out;
},
/**
* negtive
*/
neg : function () {
var data = this._array;
for (var i = 0; i < this._size; i++) {
data[i] = -data[i];
}
return this;
},
/**
* @return {NDArray} this
*/
sin : function () {
return this._mathAdapter(Math.sin);
},
/**
* @return {NDArray} this
*/
cos : function () {
return this._mathAdapter(Math.cos);
},
/**
* @return {NDArray} this
*/
tan : function () {
return this._mathAdapter(Math.tan);
},
/**
* @return {NDArray} this
*/
abs : function () {
return this._mathAdapter(Math.abs);
},
/**
* @return {NDArray} this
*/
log : function () {
return this._mathAdapter(Math.log);
},
/**
* @return {NDArray} this
*/
sqrt : function () {
return this._mathAdapter(Math.sqrt);
},
/**
* @return {NDArray} this
*/
ceil : function () {
return this._mathAdapter(Math.ceil);
},
/**
* @return {NDArray} this
*/
floor : function () {
return this._mathAdapter(Math.floor);
},
/**
* @return {NDArray} this
*/
pow : function (exp) {
var data = this._array;
for (var i = 0; i < this._size; i++) {
data[i] = Math.pow(data[i], exp);
}
return this;
},
_mathAdapter : function (mathFunc) {
var data = this._array;
for (var i = 0; i < this._size; i++) {
data[i] = mathFunc(data[i]);
}
return this;
},
/**
* @param {Number} decimals
* @return {NDArray} this
*/
round : function (decimals) {
decimals = Math.floor(decimals || 0);
var offset = Math.pow(10, decimals);
var data = this._array;
if (decimals === 0) {
for (var i = 0; i < this._size; i++) {
data[i] = Math.round(data[i]);
}
} else {
for (var i = 0; i < this._size; i++) {
data[i] = Math.round(data[i] * offset) / offset;
}
}
return this;
},
/**
* @param {Number} min
* @param {Number} max
* Clip to [min, max]
*/
clip : function (min, max) {
// TODO : Support array_like param
var data = this._array;
for (var i = 0; i < this._size; i++) {
data[i] = Math.max(Math.min(data[i], max), min);
}
return this;
},
/**
* Indexing array, support range indexing
* @param {string} index
* Index syntax can be an integer 1, 2, 3
* Or more complex range indexing
* '1:2'
* '1:2, 1:2'
* '1:2, :'
* More about the indexing syntax can check the doc of numpy ndarray
* @param {NDArray} [out]
* @return {NDArray} New created sub array, or out if given
*/
get : function (index, out) {
if (typeof(index) == 'number') {
index = index.toString();
}
var strides = calculateDimStrides(this._shape);
var res = this._parseRanges(index);
var ranges = res[0];
var shape = res[1];
if (ranges.length > this._shape.length) {
throw new Error('Too many indices');
}
// Get data
var len = ranges.length;
var data;
if (shape.length) {
out = new NDArray(this._dtype);
out.initFromShape(shape);
data = out._array;
} else {
data = [];
}
var source = this._array;
var cursor = 0;
function getPiece(axis, offset) {
var range = ranges[axis];
var stride = strides[axis];
if (axis < len-1) {
if (range[2] > 0) {
for (var i = range[0]; i < range[1]; i += range[2]) {
getPiece(axis+1, offset + stride * i);
}
} else {
for (var i = range[0]; i > range[1]; i += range[2]) {
getPiece(axis+1, offset + stride * i);
}
}
} else {
if (range[2] > 0) {
for (var i = range[0]; i < range[1]; i += range[2]) {
for (var j = 0; j < stride; j++) {
data[cursor++] = source[i*stride + j + offset];
}
}
} else {
for (var i = range[0]; i > range[1]; i += range[2]) {
for (var j = 0; j < stride; j++) {
data[cursor++] = source[i*stride + j + offset];
}
}
}
}
}
getPiece(0, 0);
if (shape.length) {
// Return scalar
return out;
} else {
return data[0];
}
},
/**
*
* @param {string} index
* index syntax can be an integer 1, 2, 3
* Or more complex range indexing
* '1:2'
* '1:2, 1:2'
* '1:2, :'
* More about the indexing syntax can check the doc of numpy ndarray
* @param {NDArray} ndarray Ndarray data source
* @return {NDArray} this
*/
set : function (index, narray) {
if (typeof(index) == 'number') {
index = index.toString();
}
var strides = calculateDimStrides(this._shape);
var res = this._parseRanges(index);
var ranges = res[0];
var shape = res[1];
if (ranges.length > this._shape.length) {
throw new Error('Too many indices');
}
var isScalar = typeof(narray) == 'number';
var len = ranges.length;
var data = this._array;
if (isScalar) {
// Set with a single scalar
var source = narray;
} else {
if (!arrayEqual(shape, narray.shape())) {
throw new Error(broadcastErrorMsg(shape, narray.shape()));
}
var source = narray._array;
}
var cursor = 0;
var setPiece = function (axis, offset) {
var range = ranges[axis];
var stride = strides[axis];
if (axis < len-1) {
if (range[2] > 0) {
for (var i = range[0]; i < range[1]; i += range[2]) {
setPiece(axis+1, offset + stride * i);
}
} else {
for (var i = range[0]; i > range[1]; i += range[2]) {
setPiece(axis+1, offset + stride * i);
}
}
} else {
if (range[2] > 0) {
for (var i = range[0]; i < range[1]; i += range[2]) {
for (var j = 0; j < stride; j++) {
if (isScalar) {
data[i*stride + j + offset] = source;
} else {
data[i*stride + j + offset] = source[cursor++];
}
}
}
} else {
for (var i = range[0]; i > range[1]; i += range[2]) {
for (var j = 0; j < stride; j++) {
if (isScalar) {
data[i*stride + j + offset] = source;
} else {
data[i*stride + j + offset] = source[cursor++];
}
}
}
}
}
};
setPiece(0, 0);
return this;
},
/**
* Insert values along the given axis before the given indices.
* @param {Number|Array} obj
* Object that defines the index or indices before
* which values is inserted.
* @param {Number|Array|NDArray} values
* Values to insert
* @param {Number} [axis]
* @return {NDArray} this
*/
insert : kwargs(function (obj, values, axis) {
var data = this._array;
var isObjScalar = false;
if (typeof(obj) === 'number') {
obj = [obj];
isObjScalar = true;
}
if (typeof(values) === 'number') {
values = new NDArray([values]);
} else if (values instanceof Array) {
values = new NDArray(values);
}
if (typeof(axis) === 'undefined') {
this._shape = [this._size];
axis = 0;
}
// Checking if indices is valid
var prev = obj[0];
var axisSize = this._shape[axis];
for (var i = 0; i < obj.length; i++) {
if (obj[i] < 0) {
obj[i] = axisSize + obj[i];
}
if (obj[i] > axisSize) {
throw new Error(indexOutofBoundsErrorMsg(obj[i]));
}
if (obj[i] < prev) {
throw new Error('Index must be in ascending order');
}
prev = obj[i];
}
// Broadcasting
var targetShape = this._shape.slice();
if (isObjScalar) {
targetShape.splice(axis, 1);
} else {
targetShape[axis] = obj.length;
}
var sourceShape = values._shape;
var cs = sourceShape.length - 1;
var ct = targetShape.length - 1;
var valueBroadcasted = values;
while (cs >= 0 && ct >= 0) {
if (sourceShape[cs] === 1) {
valueBroadcasted = values.repeat(targetShape[ct], cs);
} else if(sourceShape[cs] !== targetShape[ct]) {
throw new Error(broadcastErrorMsg(sourceShape, targetShape));
}
cs --;
ct --;
}
values = valueBroadcasted;
// Calculate indices to insert
var stride = calculateDimStride(this._shape, axis);
var axisSize = this._shape[axis];
var offsetStride = axisSize * stride;
var offsetRepeats = this._size / offsetStride;
var objLen = obj.length;
var indices = new Uint32Array(offsetRepeats * objLen);
var cursor = 0;
for (var offset = 0; offset < this._size; offset += offsetStride) {
for (var i = 0; i < objLen; i++) {
var objIdx = obj[i];
indices[cursor++] = offset + objIdx * stride;
}
}
var resShape = this._shape.slice();
resShape[axis] += obj.length;
var resSize = getSize(resShape);
if (this._array.length < resSize) {
var data = new ArrayConstructor[this._dtype](resSize);
} else {
var data = this._array;
}
var source = this._array;
var valuesArr = values._array;
var idxCursor = indices.length - 1;
var end = this._size;
var start = indices[idxCursor];
var dataCursor = resSize - 1;
var valueCursor = values._size - 1;
while (idxCursor >= 0) {
// Copy source data;
for (var i = end - 1; i >= start; i--) {
data[dataCursor--] = source[i];
}
end = start;
start = indices[--idxCursor];
// Copy inserted data;
for (var i = 0; i < stride; i++) {
if (valueCursor < 0) {
valueCursor = values._size - 1;
}
data[dataCursor--] = valuesArr[valueCursor--];
}
}
// Copy the rest
for (var i = end - 1; i >= 0; i--) {
data[dataCursor--] = source[i];
}
this._array = data;
this._shape = resShape;
this._size = resSize;
return this;
}),
append : function () {
console.warn('TODO');
},
/**
* Delete values along the axis
* @param {Array|Number} obj
* @param {Number} [axis]
* @return {NDArray} this
*/
'delete' : kwargs(function (obj, axis) {
var data = this._array;
if (typeof(obj) === 'number') {
obj = [obj];
}
var size = this._size;
if (typeof(axis) === 'undefined') {
this._shape = [size];
axis = 0;
}
var stride = calculateDimStride(this._shape, axis);
var axisSize = this._shape[axis];
var offsetStride = stride * axisSize;
var cursor = 0;
for (var offset = 0; offset < size; offset += offsetStride) {
var start = 0;
var end = obj[0];
var objCursor = 0;
while(objCursor < obj.length) {
if (end < 0) {
end = end + axisSize;
}
if (end > axisSize) {
throw new Error(indexOutofBoundsErrorMsg(end));
}
if (end < start) {
throw new Error('Index must be in ascending order');
}
for (var i = start; i < end; i++) {
for (var j = 0; j < stride; j++) {
data[cursor++] = data[i * stride + j + offset];
}
}
start = end + 1;
end = obj[++objCursor];
}
// Copy the rest
for (var i = start; i < axisSize; i++) {
for (var j = 0; j < stride; j++) {
data[cursor++] = data[i * stride + j + offset];
}
}
}
this._shape[axis] -= obj.length;
this._size = getSize(this._shape);
return this;
}),
_parseRanges : function (index) {
var rangesStr = index.split(/\s*,\s*/);
// Parse range of each axis
var ranges = [];
var shape = [];
var j = 0;
for (var i = 0; i < rangesStr.length; i++) {
if (rangesStr[i] === '...') {
var end = this._shape.length - (rangesStr.length - i);
while (j <= end) {
ranges.push([0, this._shape[j], 1]);
shape.push(this._shape[j]);
j++;
}
} else {
var range = parseRange(rangesStr[i], this._shape[j]);
ranges.push(range);
if(rangesStr[i].indexOf(':') >= 0) {
var size = Math.floor((range[1] - range[0]) / range[2]);
size = size < 0 ? 0 : size;
// Get a range not a item
shape.push(size);
}
j++;
}
}
// Copy the lower dimension size
for (; j < this._shape.length; j++) {
shape.push(this._shape[j]);
}
return [ranges, shape];
},
/**
* Export normal js array
* @return {Array}
*/
toArray : function () {
var data = this._array;
var cursor = 0;
var shape = this._shape;
var dim = shape.length;
function create(axis, out) {
var len = shape[axis];
for (var i = 0; i < len; i++) {
if (axis < dim-1) {
create(axis+1, out[i] = []);
} else {
out[i] = data[cursor++];
}
}
}
var output = [];
create(0, output);
return output;
},
/**
* Create a copy of self
* @return {NDArray}
*/
copy : function () {
var numArr = new NDArray();
numArr._array = ArraySlice.call(this._array);
numArr._shape = this._shape.slice();
numArr._dtype = this._dtype;
numArr._size = this._size;
return numArr;
},
constructor : NDArray
};
/**
*
* @param {Number} [min=0]
* @param {Number} max
* @param {Number} [step=1]
* @param {string} [dtype]
* @return {NDArray}
*/
NDArray.range = kwargs(function (min, max, step, dtype) {
var args = ArraySlice.call(arguments);
// Last argument describe the data type of ndarray
var lastArg = args[args.length-1];
if (typeof(lastArg) == 'string') {
var dtype = lastArg;
args.pop();
}
if (args.length === 1) {
max = args[0];
step = 1;
min = 0;
} else if(args.length == 2) {
step = 1;
}
dtype = dtype || 'number';
var array = new ArrayConstructor[dtype](Math.ceil((max - min)/step));
var cursor = 0;
for (var i = min; i < max; i+=step) {
array[cursor++] = i;
}
var ndarray = new NDArray();
ndarray._array = array;
ndarray._shape = [array.length];
ndarray._dtype = dtype;
ndarray._size = array.length;
return ndarray;
});
/**
*
* @param {Array} shape
* @param {String} [dtype]
* @return {NDArray}
*/
NDArray.zeros = kwargs(function (shape, dtype) {
var ret = new NDArray(dtype);
ret.initFromShape(shape);
return ret;
});
/**
* Python like array indexing
* http://www.python.org/dev/peps/pep-0204/
*
* @param {string} index
* index can be a simple integer 1,2,3,
* or a range 2:10, 2:10:1
* example :
* 2:10 => [2, 10, 1],
* 10:2:-2 => [10, 2, -2],
* : => [0, dimSize, 1],
* ::-1 => [dimSize-1, -1, -1],
* @param {number} dimSize
* @return {Array} a tuple array [startOffset, endOffset, sliceStep]
*/
function parseRange(index, dimSize) {
if (index.indexOf(':') >= 0) {
// Range indexing;
var res = index.split(/\s*:\s*/);
var step = parseInt(res[2] || 1, 10);
var start, end;
if (step === 0) {
throw new Error('Slice step cannot be zero');
}
else if (step > 0) {
start = parseInt(res[0] || 0, 10);
end = parseInt(res[1] || dimSize, 10);
}
else {
start = parseInt(res[0] || dimSize - 1, 10);
end = parseInt(res[1] || -1, 10);
}
// Negtive offset
if (start < 0) {
start = dimSize + start;
}
// Negtive offset
if (end < 0 && res[1]) {
end = dimSize + end;
}
if (step > 0) {
// Clamp to [0-dimSize]
start = Math.max(Math.min(dimSize, start), 0);
// Clamp to [0-dimSize]
end = Math.max(Math.min(dimSize, end), 0);
} else {
// Clamp to [0-dimSize)
start = Math.max(Math.min(dimSize-1, start), -1);
// Clamp to [0-dimSize)
end = Math.max(Math.min(dimSize-1, end), -1);
}
return [start, end, step];
} else {
var start = parseInt(index, 10);
// Negtive offset
if (start < 0) {
start = dimSize + start;
}
if (start < 0 || start > dimSize) {
throw new Error(indexOutofBoundsErrorMsg(index));
}
// Clamp to [0-dimSize)
start = Math.max(Math.min(dimSize-1, start), 0);
return [start, start+1, 1];
}
}
function getSize(shape) {
var size = shape[0];
for (var i = 1; i < shape.length; i++) {
size *= shape[i];
}
return size;
}
function getDimension(array) {
var dim = 1;
var el = array[0];
while (el instanceof Array) {
el = el[0];
dim ++;
}
return dim;
}
function getShape(array) {
var shape = [array.length];
var el = array[0];
while (el instanceof Array) {
shape.push(el.length);
el = el[0];
}
return shape;
}
function calculateDimStride(shape, axis) {
if (axis == shape.length-1) {
return 1;
}
var stride = shape[axis+1];
for (var i = axis+2; i < shape.length; i++) {
stride *= shape[i];
}
return stride;
}
function calculateDimStrides(shape) {
// Calculate stride of each axis
var strides = [];
var tmp = 1;
var len = getSize(shape);
for (var i = 0; i < shape.length; i++) {
tmp *= shape[i];
strides.push(len / tmp);
}
return strides;
}
function arrayEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
for (var i = 0; i <arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
function broadcastErrorMsg(shape1, shape2) {
return 'Shape ('
+ shape1.toString() + ') (' + shape2.toString()
+') could not be broadcast together';
}
function axisOutofBoundsErrorMsg(axis) {
return 'Axis ' + axis + ' out of bounds';
}
function indexOutofBoundsErrorMsg(idx) {
return 'Index ' + idx + ' out of bounds';
}
return NDArray;
});