'use strict';
var ndarray = require('ndarray');
var cwise = require('cwise');
var ops = require('ndarray-ops');
var ndFFT = require('ndarray-fft');
var CONF = require('./config');
var DTYPES = require('./dtypes');
var NdArray = require('./ndarray');
var _ = require('./utils');
var errors = require('./errors');
function broadcast (shape1, shape2) {
if (shape1.length === 0 || shape2.length === 0) {
return;
}
var reversed1 = shape1.slice().reverse();
var reversed2 = shape2.slice().reverse();
var maxLength = Math.max(shape1.length, shape2.length);
var outShape = new Array(maxLength);
for (var i = 0; i < maxLength; i++) {
if (!reversed1[i] || reversed1[i] === 1) {
outShape[i] = reversed2[i];
} else if (!reversed2[i] || reversed2[i] === 1) {
outShape[i] = reversed1[i];
} else if (reversed1[i] === reversed2[i]) {
outShape[i] = reversed1[i];
} else {
return;
}
}
return outShape.reverse();
}
/**
* Add arguments, element-wise.
*
* @param {(NdArray|Array|number)} a
* @param {(NdArray|Array|number)} b
* @returns {NdArray}
*/
function add (a, b) {
return NdArray.new(a).add(b);
}
/**
* Multiply arguments, element-wise.
*
* @param {(Array|NdArray)} a
* @param {(Array|NdArray|number)} b
* @returns {NdArray}
*/
function multiply (a, b) {
return NdArray.new(a).multiply(b);
}
/**
* Divide `a` by `b`, element-wise.
*
* @param {(Array|NdArray)} a
* @param {(Array|NdArray|number)} b
* @returns {NdArray}
*/
function divide (a, b) {
return NdArray.new(a).divide(b);
}
/**
* Subtract second argument from the first, element-wise.
*
* @param {(NdArray|Array|number)} a
* @param {(NdArray|Array|number)} b
* @returns {NdArray}
*/
function subtract (a, b) {
return NdArray.new(a).subtract(b);
}
/**
* Return true if two arrays have the same shape and elements, false otherwise.
* @param {(Array|NdArray)} array1
* @param {(Array|NdArray)} array2
* @returns {boolean}
*/
function equal (array1, array2) {
return NdArray.new(array1).equal(array2);
}
/**
* Return a copy of the array collapsed into one dimension using row-major order (C-style)
* @param {(Array|NdArray)} array
* @returns {NdArray}
*/
function flatten (array) {
return NdArray.new(array).flatten();
}
/**
* Gives a new shape to an array without changing its data.
* @param {(Array|NdArray)} array
* @param {Array} shape - The new shape should be compatible with the original shape. If an integer, then the result will be a 1-D array of that length
* @returns {NdArray}
*/
function reshape (array, shape) {
return NdArray.new(array).reshape(shape);
}
/**
* Calculate the exponential of all elements in the input array, element-wise.
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function exp (x) {
return NdArray.new(x).exp();
}
/**
* Calculate the natural logarithm of all elements in the input array, element-wise.
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function log (x) {
return NdArray.new(x).log();
}
/**
* Calculate the positive square-root of all elements in the input array, element-wise.
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function sqrt (x) {
return NdArray.new(x).sqrt();
}
/**
* Raise first array elements to powers from second array, element-wise.
*
* @param {(Array|NdArray|number)} x1
* @param {(Array|NdArray|number)} x2
* @returns {NdArray}
*/
function power (x1, x2) {
return NdArray.new(x1).pow(x2);
}
/**
* Return the sum of input array elements.
*
* @param {(Array|NdArray|number)} x
* @returns {number}
*/
function sum (x) {
return NdArray.new(x).sum();
}
/**
* Return the arithmetic mean of input array elements.
*
* @param {(Array|NdArray|number)} x
* @returns {number}
*/
function mean (x) {
return NdArray.new(x).mean();
}
/**
* Returns the standard deviation, a measure of the spread of a distribution, of the input array elements.
*
* @param {(Array|NdArray|number)} x
* @returns {number}
*/
function std (x, options) {
return NdArray.new(x).std(options);
}
/**
* Return the minimum value of the array
*
* @param {(Array|NdArray|number)} x
* @returns {Number}
*/
function min (x) {
return NdArray.new(x).min();
}
/**
* Return the maximum value of the array
*
* @param {(Array|NdArray|number)} x
* @returns {Number}
*/
function max (x) {
return NdArray.new(x).max();
}
/**
* Return element-wise remainder of division.
* Computes the remainder complementary to the `floor` function. It is equivalent to the Javascript modulus operator``x1 % x2`` and has the same sign as the divisor x2.
*
* @param {(NdArray|Array|number)} x1
* @param {(NdArray|Array|number)} x2
* @returns {NdArray}
*/
function mod (x1, x2) {
return NdArray.new(x1).mod(x2);
}
/**
* Permute the dimensions of the input array according to the given axes.
*
* @param {(Array|NdArray|number)} x
* @param {(number|...number)} [axes]
* @returns {NdArray}
* @example
*
arr = nj.arange(6).reshape(1,2,3)
// array([[[ 0, 1, 2],
// [ 3, 4, 5]]])
arr.T
// array([[[ 0],
// [ 3]],
// [[ 1],
// [ 4]],
// [[ 2],
// [ 5]]])
arr.transpose(1,0,2)
// array([[[ 0, 1, 2]],
// [[ 3, 4, 5]]])
*/
function transpose (x, axes) {
return NdArray.new(x).transpose(axes);
}
/**
* Return the inverse of the input array, element-wise.
*
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function negative (x) {
return NdArray.new(x).negative();
}
/**
* Return evenly spaced values within a given interval.
*
* @param {int} [start=0] - Start of interval. The interval includes this value.
* @param {int} stop - End of interval. The interval does not include this value.
* @param {int} [step=1] - Spacing between values. The default step size is 1. If step is specified, start must also be given.
* @param {(String|Object)} [dtype=Array] The type of the output array.
*
* @return {NdArray} Array of evenly spaced values.
*/
function arange (start, stop, step, dtype) {
if (arguments.length === 1) {
return arange(0, start, 1, undefined);
} else if (arguments.length === 2 && _.isNumber(stop)) {
return arange(start, stop, 1, undefined);
} else if (arguments.length === 2) {
return arange(0, start, 1, stop);
} else if (arguments.length === 3 && !_.isNumber(step)) {
return arange(start, stop, 1, step);
}
var result = [];
var i = 0;
while (start < stop) {
result[i++] = start;
start += step;
}
return NdArray.new(result, dtype);
}
/**
* Return a new array of given shape and type, filled with zeros.
*
* @param {(Array|int)} shape - Shape of the new array, e.g., [2, 3] or 2.
* @param {(String|Object)} [dtype=Array] The type of the output array.
*
* @return {NdArray} Array of zeros with the given shape and dtype
*/
function zeros (shape, dtype) {
if (_.isNumber(shape) && shape >= 0) {
shape = [shape];
}
var s = _.shapeSize(shape);
var T = _.getType(dtype);
var arr = new NdArray(new T(s), shape);
if (arr.dtype === 'array') {
ops.assigns(arr.selection, 0);
}
return arr;
}
/**
* Return a new array of given shape and type, filled with ones.
*
* @param {(Array|int)} shape - Shape of the new array, e.g., [2, 3] or 2.
* @param {(String|Object)} [dtype=Array] - The type of the output array.
*
* @return {NdArray} Array of ones with the given shape and dtype
*/
function ones (shape, dtype) {
if (_.isNumber(shape) && shape >= 0) {
shape = [shape];
}
var s = _.shapeSize(shape);
var T = _.getType(dtype);
var arr = new NdArray(new T(s), shape);
ops.assigns(arr.selection, 1);
return arr;
}
/**
* Return a new array of given shape and type, filled with `undefined` values.
*
* @param {(Array|int)} shape - Shape of the new array, e.g., [2, 3] or 2.
* @param {(String|Object)} [dtype=Array] - The type of the output array.
*
* @return {NdArray} Array of `undefined` values with the given shape and dtype
*/
function empty (shape, dtype) {
if (_.isNumber(shape) && shape >= 0) {
shape = [shape];
}
var s = _.shapeSize(shape);
var T = _.getType(dtype);
return new NdArray(new T(s), shape);
}
/**
* Create an array of the given shape and propagate it with random samples from a uniform distribution over [0, 1].
* @param {number|Array|...number} shape - The dimensions of the returned array, should all be positive integers
* @returns {NdArray}
*/
function random (shape) {
if (arguments.length === 0) {
return NdArray.new(Math.random());
} else if (arguments.length === 1) {
shape = _.isNumber(shape) ? [shape | 0] : shape;
} else {
shape = [].slice.call(arguments);
}
var s = _.shapeSize(shape);
var arr = new NdArray(new Float64Array(s), shape);
ops.random(arr.selection);
return arr;
}
/**
* Return the softmax, or normalized exponential, of the input array, element-wise.
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function softmax (x) {
var e = NdArray.new(x).exp();
var se = e.sum(); // scalar
ops.divseq(e.selection, se);
return e;
}
var doSigmoid = cwise({
args: ['array', 'scalar'],
body: function sigmoidCwise (a, t) {
a = a < -30 ? 0 : a > 30 ? 1 : 1 / (1 + Math.exp(-1 * t * a));
}
});
/**
* Return the sigmoid of the input array, element-wise.
* @param {(Array|NdArray|number)} x
* @param {number} [t=1] - stifness parameter
* @returns {NdArray}
*/
function sigmoid (x, t) {
x = NdArray.new(x).clone();
t = t || 1;
doSigmoid(x.selection, t);
return x;
}
var doClip = cwise({
args: ['array', 'scalar', 'scalar'],
body: function clipCwise (a, min, max) {
a = Math.min(Math.max(min, a), max);
}
});
/**
* Clip (limit) the values in an array between min and max, element-wise.
*
* @param {(Array|NdArray|number)} x
* @param {number} [min=0]
* @param {number} [max=1]
* @returns {NdArray}
*/
function clip (x, min, max) {
if (arguments.length === 1) {
min = 0;
max = 1;
} else if (arguments.length === 2) {
max = 1;
}
var s = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
doClip(s.selection, min, max);
return s;
}
var doLeakyRelu = cwise({
args: ['array', 'scalar'],
body: function leakyReluCwise (xi, alpha) {
xi = Math.max(alpha * xi, xi);
}
});
function leakyRelu (x, alpha) {
alpha = alpha || 1e-3;
var s = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
doLeakyRelu(s.selection, alpha);
return s;
}
var doTanh = cwise({
args: ['array'],
body: function tanhCwise (xi) {
xi = (Math.exp(2 * xi) - 1) / (Math.exp(2 * xi) + 1);
}
});
/**
* Return hyperbolic tangent of the input array, element-wise.
*
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function tanh (x) {
var s = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
doTanh(s.selection);
return s;
}
/**
* Return absolute value of the input array, element-wise.
*
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function abs (x) {
var s = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
ops.abseq(s.selection);
return s;
}
/**
* Return trigonometric cosine of the input array, element-wise.
*
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function cos (x) {
var s = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
ops.coseq(s.selection);
return s;
}
/**
* Return trigonometric inverse cosine of the input array, element-wise.
*
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function arccos (x) {
var s = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
ops.acoseq(s.selection);
return s;
}
/**
* Return trigonometric sine of the input array, element-wise.
*
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function sin (x) {
var s = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
ops.sineq(s.selection);
return s;
}
/**
* Return trigonometric inverse sine of the input array, element-wise.
*
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function arcsin (x) {
var s = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
ops.asineq(s.selection);
return s;
}
/**
* Return trigonometric tangent of the input array, element-wise.
*
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function tan (x) {
var s = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
ops.taneq(s.selection);
return s;
}
/**
* Return trigonometric inverse tangent of the input array, element-wise.
*
* @param {(Array|NdArray|number)} x
* @returns {NdArray}
*/
function arctan (x) {
var s = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
ops.ataneq(s.selection);
return s;
}
/**
* Dot product of two arrays.
*
* WARNING: supported products are:
* - matrix dot matrix
* - vector dot vector
* - matrix dot vector
* - vector dot matrix
* @param {(Array|NdArray)} a
* @param {(Array|NdArray)} b
* @returns {NdArray}
*/
function dot (a, b) {
return NdArray.new(a).dot(b);
}
/**
* Join given arrays along the last axis.
*
* @param {...(Array|NdArray)} arrays
* @returns {NdArray}
*/
function concatenate (arrays) {
if (arguments.length > 1) {
arrays = [].slice.call(arguments);
}
var i, a;
for (i = 0; i < arrays.length; i++) {
a = arrays[i];
arrays[i] = (a instanceof NdArray) ? a.tolist() : _.isNumber(a) ? [a] : a;
}
var m = arrays[0];
for (i = 1; i < arrays.length; i++) {
a = arrays[i];
var mShape = _.getShape(m);
var aShape = _.getShape(a);
if (mShape.length !== aShape.length) {
throw new errors.ValueError('all the input arrays must have same number of dimensions');
} else if (mShape.length === 1 && aShape.length === 1) {
m = m.concat(a);
} else if ((mShape.length === 2 && aShape.length === 2 && mShape[0] === aShape[0]) ||
(mShape.length === 1 && aShape.length === 2 && mShape[0] === aShape[0]) ||
(mShape.length === 2 && aShape.length === 1 && mShape[0] === aShape[0])) {
for (var row = 0; row < mShape[0]; row++) {
m[row] = m[row].concat(a[row]);
}
} else if ((mShape.length === 3 && aShape.length === 3 && mShape[0] === aShape[0] && mShape[1] === aShape[1]) ||
(mShape.length === 2 && aShape.length === 3 && mShape[0] === aShape[0] && mShape[1] === aShape[1]) ||
(mShape.length === 3 && aShape.length === 2 && mShape[0] === aShape[0] && mShape[1] === aShape[1])) {
for (var rowI = 0; rowI < mShape[0]; rowI++) {
var rowV = new Array(mShape[1]);
for (var colI = 0; colI < mShape[1]; colI++) {
rowV[colI] = m[rowI][colI].concat(a[rowI][colI]);
}
m[rowI] = rowV;
}
} else {
throw new errors.ValueError('cannot concatenate "' + mShape + '" with "' + aShape + '"');
}
}
return NdArray.new(m, arrays[0].dtype);
}
/**
* Round an array to the to the nearest integer.
*
* @param {(Array|NdArray)} x
* @returns {NdArray}
*/
function round (x) {
return NdArray.new(x).round();
}
/**
* Convolve 2 N-dimensionnal arrays
*
* @note: Arrays must have the same dimensions and a must be greater than b.
* @note: The convolution product is only given for points where the signals overlap completely. Values outside the signal boundary have no effect. This behaviour is known as the 'valid' mode.
*
* @param {Array|NdArray} a
* @param {Array|NdArray} b
*/
function convolve (a, b) {
return NdArray.new(a).convolve(b);
}
/**
* Convolve 2 N-dimensionnal arrays using Fast Fourier Transform (FFT)
*
* @note: Arrays must have the same dimensions and a must be greater than b.
* @note: The convolution product is only given for points where the signals overlap completely. Values outside the signal boundary have no effect. This behaviour is known as the 'valid' mode.
*
* @param {Array|NdArray} a
* @param {Array|NdArray} b
*/
function fftconvolve (a, b) {
return NdArray.new(a).fftconvolve(b);
}
function fft (x) {
x = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
var xShape = x.shape;
var d = xShape.length;
if (xShape[d - 1] !== 2) {
throw new errors.ValueError('expect last dimension of the array to have 2 values (for both real and imaginary part)');
}
var rPicker = new Array(d);
var iPicker = new Array(d);
rPicker[d - 1] = 0;
iPicker[d - 1] = 1;
ndFFT(1, x.selection.pick.apply(x.selection, rPicker), x.selection.pick.apply(x.selection, iPicker));
return x;
}
function ifft (x) {
x = (x instanceof NdArray) ? x.clone() : NdArray.new(x);
var xShape = x.shape;
var d = xShape.length;
if (xShape[d - 1] !== 2) {
throw new errors.ValueError('expect last dimension of the array to have 2 values (for both real and imaginary part)');
}
var rPicker = new Array(d);
var iPicker = new Array(d);
rPicker[d - 1] = 0;
iPicker[d - 1] = 1;
ndFFT(-1, x.selection.pick.apply(x.selection, rPicker), x.selection.pick.apply(x.selection, iPicker));
return x;
}
/**
* Extract a diagonal or construct a diagonal array.
*
* @param {Array|NdArray} x
* @returns {NdArray} a view a of the original array when possible, a new array otherwise
*/
function diag (x) {
return NdArray.new(x).diag();
}
/**
* The identity array is a square array with ones on the main diagonal.
* @param {number} Number of rows (and columns) in n x n output.
* @param {(String|Object)} [dtype=Array] The type of the output array.
* @return {Array} n x n array with its main diagonal set to one, and all other elements 0
*/
function identity (n, dtype) {
var arr = zeros([n, n], dtype);
for (var i = 0; i < n; i++) arr.set(i, i, 1);
return arr;
}
/**
* Join a sequence of arrays along a new axis.
* The axis parameter specifies the index of the new axis in the dimensions of the result.
* For example, if axis=0 it will be the first dimension and if axis=-1 it will be the last dimension.
* @param {Array} sequence of array_like
* @param {number} [axis=0] The axis in the result array along which the input arrays are stacked.
* @return {Array} The stacked array has one more dimension than the input arrays.
*/
function stack (arrays, axis) {
axis = axis || 0;
if (!arrays || arrays.length === 0) {
throw new errors.ValueError('need at least one array to stack');
}
arrays = arrays.map(function (a) { return _.isNumber(a) ? a : NdArray.new(a); });
var expectedShape = arrays[0].shape || []; // for numbers
for (var i=1; i<arrays.length; i++){
var shape = arrays[i].shape || []; // for numbers
var len = Math.max(expectedShape.length, shape.length);
for (var j = 0; j < len; j++){
if (expectedShape[j] !== shape[j]) throw new errors.ValueError('all input arrays must have the same shape');
}
}
var stacked;
if (expectedShape.length === 0) { // stacking numbers
stacked = concatenate(arrays);
} else {
stacked = zeros([arrays.length].concat(expectedShape));
for (var i=0; i<arrays.length; i++) {
stacked.pick(i).assign(arrays[i], false);
}
}
if (axis) {
// recompute neg axis
if (axis < 0) axis = stacked.ndim + axis;
var d = stacked.ndim;
var axes = new Array(d);
for (var i = 0; i < d; i++){
axes[i] = i < axis ? i + 1 : i === axis ? 0 : i;
}
return stacked.transpose(axes);
}
return stacked;
}
/**
* Reverse the order of elements in an array along the given axis.
* The shape of the array is preserved, but the elements are reordered.
* New in version 0.15.0.
* @param {Array|NdArray} m Input array.
* @param {number} axis Axis in array, which entries are reversed.
* @return {NdArray} A view of `m` with the entries of axis reversed. Since a view is returned, this operation is done in constant time.
*/
function flip(m, axis) {
m = NdArray.new(m);
var indexer = ones(m.ndim).tolist();
var cleanaxis = axis;
while (cleanaxis < 0) {
cleanaxis += m.ndim;
}
if (indexer[cleanaxis] === undefined) {
throw new errors.ValueError('axis=' + axis + 'invalid for the ' + m.ndim + '-dimensional input array');
}
indexer[cleanaxis] = -1;
return m.step.apply(m, indexer);
}
/**
* Rotate an array by 90 degrees in the plane specified by axes.
* Rotation direction is from the first towards the second axis.
* New in version 0.15.0.
* @param {Array|NdArray} m array_like
* @param {number} [k=1] Number of times the array is rotated by 90 degrees.
* @param {Array|NdArray} [axes=(0,1)] The array is rotated in the plane defined by the axes. Axes must be different.
* @return {NdArray} A rotated view of m.
*/
function rot90 (m, k, axes) {
k = k || 1;
while (k < 0) {
k += 4;
}
k = k % 4;
m = NdArray.new(m);
axes = NdArray.new(axes || [0, 1]);
if (axes.shape.length !== 1 || axes.shape[0] !== 2) {
throw new errors.ValueError('len(axes) must be 2');
}
axes = axes.tolist();
if (axes[0] === axes[1] || abs(axes[0] - axes[1]) === m.ndim) {
throw new errors.ValueError("Axes must be different.")
}
if (k === 0) {
return m;
}
if (k === 2) {
return flip(flip(m, axes[0]), axes[1]);
}
var axesList = arange(m.ndim).tolist();
var keep = axesList[axes[0]];
axesList[axes[0]] = axesList[axes[1]];
axesList[axes[1]] = keep;
if (k === 1) {
return transpose(flip(m, axes[1]), axesList);
} else {
return flip(transpose(m, axesList), axes[1]);
}
}
module.exports = {
config: CONF,
dtypes: DTYPES,
NdArray: NdArray,
ndarray: ndarray,
array: NdArray.new,
arange: arange,
reshape: reshape,
zeros: zeros,
ones: ones,
empty: empty,
flatten: flatten,
flip: flip,
random: random,
softmax: softmax,
sigmoid: sigmoid,
leakyRelu: leakyRelu,
abs: abs,
arccos: arccos,
arcsin: arcsin,
arctan: arctan,
cos: cos,
sin: sin,
tan: tan,
tanh: tanh,
clip: clip,
exp: exp,
log: log,
sqrt: sqrt,
power: power,
sum: sum,
mean: mean,
std: std,
dot: dot,
add: add,
subtract: subtract,
multiply: multiply,
divide: divide,
negative: negative,
equal: equal,
max: max,
min: min,
mod: mod,
remainder: mod,
concatenate: concatenate,
transpose: transpose,
errors: errors,
broadcast: broadcast,
round: round,
convolve: convolve,
fftconvolve: fftconvolve,
fft: fft,
ifft: ifft,
diag: diag,
identity: identity,
stack: stack,
rot90: rot90,
int8: function (array) { return NdArray.new(array, 'int8'); },
uint8: function (array) { return NdArray.new(array, 'uint8'); },
int16: function (array) { return NdArray.new(array, 'int16'); },
uint16: function (array) { return NdArray.new(array, 'uint16'); },
int32: function (array) { return NdArray.new(array, 'int32'); },
uint32: function (array) { return NdArray.new(array, 'uint32'); },
float32: function (array) { return NdArray.new(array, 'float32'); },
float64: function (array) { return NdArray.new(array, 'float64'); },
images: require('./images')
};