np = {
function shape(ndarray) {
if (isValue(ndarray)) {
return [];
}
return [ndarray.length].concat(shape(ndarray[0]));
}
function isBroadcastable(x, y) {
let shapeXReversed = (isValue(x) ? [1] : shape(x)).reverse();
let shapeYReversed = (isValue(y) ? [1] : shape(y)).reverse();
for (
let i = 0;
i < Math.min(shapeXReversed.length, shapeYReversed.length);
i++
) {
const oneDimIsOne = shapeXReversed[i] == 1 || shapeYReversed[i] == 1;
const dimsMatch = shapeXReversed[i] == shapeYReversed[i];
const broadcastableDim = dimsMatch || oneDimIsOne;
if (!broadcastableDim) {
return false;
}
}
return true;
}
function broadcast(x, y) {
}
function fill(shape, fillFn = (idx) => null, idx = null) {
if (idx === null) {
idx = [];
}
let ndarray = fillFn(idx);
if (shape.length == 0) {
return ndarray;
}
ndarray = Array(shape[0])
.fill(0)
.map((v, i) => fill(shape.slice(1), fillFn, idx.concat([i])));
return ndarray;
}
function index(ndarray, idx) {
if (idx.length == 0) {
return ndarray;
}
return index(ndarray[idx[0]], idx.slice(1));
}
function clone(ndarray) {
if (!Array.isArray(ndarray)) {
// Will create mutable copy if non array value is object which is returned as a reference
return ndarray;
}
return ndarray.map((x) => clone(x));
}
function* axisIter(ndarray, axis) {
const arr_shape = shape(ndarray);
const return_shape = arr_shape.map((x, i) => (i == axis ? 1 : x));
for (let i = 0; i < arr_shape[axis]; i++) {
const fillFn = (idx) =>
index(
ndarray,
idx.map((x, j) => (j == axis ? i : x))
);
yield fill(return_shape, fillFn);
}
}
function isNdArray(ndarray) {
return Array.isArray(ndarray);
}
function isValue(ndarray) {
return !isNdArray(ndarray);
}
function vectorizeWrapper(fn) {
function vecFn() {
const args = [...arguments];
if (isValue(args[0])) {
return fn(...args);
}
return args[0].map((_, i) => vecFn(...args.map((x) => x[i])));
}
return vecFn;
}
function axisReduce(ndarray, axis, reduceFn, initVal = null) {
const arr_shape = shape(ndarray);
const return_shape = arr_shape.map((x, i) => (i == axis ? 1 : x));
// let reduceVal = empty(return_shape, 0);
let reduceVal = null;
const axisValGen = axisIter(ndarray, axis);
if (initVal !== null) {
reduceVal = clone(initVal);
} else {
reduceVal = axisValGen.next().value;
}
reduceFn = vectorizeWrapper(reduceFn);
for (let x of axisValGen) {
reduceVal = reduceFn(reduceVal, x);
}
return reduceVal;
}
function amax(ndarray, axis = null) {
if (axis === null) {
if (!Array.isArray(ndarray)) {
return ndarray;
}
return d3.max(ndarray.map((row) => amax(row)));
}
}
function empty(shape, fillValue = null) {
let ndarray = fillValue;
if (shape.length == 0) {
return ndarray;
}
ndarray = Array(shape[0]).fill(empty(shape.slice(1), fillValue));
return ndarray;
}
return {
shape,
fill,
amax,
empty,
index,
clone,
axisIter,
axisReduce,
vectorizeWrapper,
isBroadcastable
};
}