Public
Edited
Mar 26
Fork of Super map
Insert cell
Insert cell
Insert cell
The main idea is to use a broadcasted index and then convert it to an index for each part.

```
A = [[1],[2]]
B = [[3, 4]]
```
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// experiments are an array of objects with a name and a function to run.
experiments = [
{ name: "math.map", run: () => math.map(X, Y, Z, fn) },
{ name: "mapMultipleNaive", run: () => mapMultipleNaive([X, Y, Z], fn)},
{ name: "mapMultipleNoBroadcast", run: () => mapMultipleNoBroadcast([X, Y, Z], fn)},
{ name: "mapMultipleNoDummy", run: () => mapMultipleNoDummy([X, Y, Z], fn)},
{ name: "mapMultipleMemoize", run: () => mapMultipleMemoize([X, Y, Z], fn)},
{ name: "mapMultipleMemoize1", run: () => mapMultipleMemoize1([X, Y, Z], fn)},
{ name: "mapMultipleIdeal", run: () => mapMultipleIdeal([X, Y, Z], fn)}
]
Insert cell
function mapMultipleNoBroadcast(arrays, multiCallback) {
const newSize = broadcastedSizes;
const sizes = arrays.map((array) => math.size(array));
const firstBroadcastedArray = broadcastTo(arrays[0], newSize);
const dummy = math.zeros(newSize);
return math.map(dummy, (_, index) =>
multiCallback(
...arrays.map((v, i) => get(v, unbroadcastIndex(index, sizes[i])))
)
);
}
Insert cell
function unbroadcastIndex(broadcastedIndex, size) {
// this function gets an array of indices from a broadcasted index according to the availables sizes
// if the broadcasted index is [2, 2] and the sizes are [3], [1, 3] and [3, 1]
// it will return [2], [0, 2] and [2, 0]
let index = [];
let offset = broadcastedIndex.length - size.length;
for (let i = 0; i < size.length; i++) {
if (size[i] === 1) {
index.push(0);
} else {
index.push(broadcastedIndex[i + offset]);
}
}
return index;
}
Insert cell
Insert cell
function mapMultipleMemoize(arrays, multiCallback) {
// in each array in each dimension save the previous index and value
// otherwise use get
const newSize = broadcastedSizes;
const maxDepth = newSize.length - 1;
const sizes = arrays.map(array => math.size(array));
const buffers = arrays.map(()=>new Map())

const index = [];
return recurse();

// maintain an array of buffers and offsets, then for each depth
// find if the depth-offest has a matching index.toString()
// and if so use it's value
// start by using only one
// use new Map get and set methods

function getFromBuffer(arrayIndex, index){
const indexStr = index.toString()
let result = buffers[arrayIndex].get(indexStr)
if( result === undefined){
result = get(arrays[arrayIndex], index)
buffers[arrayIndex].set(indexStr, result)
}
return result
};
function recurse(depth = 0) {
const result = [];
if (depth < maxDepth) {
for (let i = 0; i < newSize[depth]; i++) {
index[depth] = i;
result.push(recurse(depth + 1));
}
} else {
for (let i = 0; i < newSize[depth]; i++) {
index[depth] = i;
result.push(
multiCallback(
...arrays.map((arr, idx) =>
getFromBuffer(idx, unbroadcastIndex(index, sizes[idx]))
)
)
);
}
}
return result;
}
}
Insert cell
parser = {
const parser = math.parser();
parser.set("X", X);
parser.set("Y", Y);
parser.set("Z", Z);
return parser;
}
Insert cell
broadcastedSizes = math.size(math.add(math.add(X, math.dotMultiply(0, Y)), math.dotMultiply(0, Z)))
Insert cell
sizes = [X, Y, Z].map (array => math.size(array))
Insert cell
function get (array, index) {
if(array.length === 0) throw new Error('empty')
return index.reduce((acc, curr) => acc[curr], array)
}
Insert cell
function broadcastTo(array, size){
return math.add(array, math.zeros(size))
}
Insert cell
function mapMultipleNaive(arrays, multiCallback) {
const newSize = broadcastedSizes;
const maxDepth = newSize - 1;
const sizes = arrays.map((array) => math.size(array));

const broadcastedArrays = arrays.map((array, index) => broadcastTo(array, newSize))
const restArrays = broadcastedArrays.slice(1)
return math.map(broadcastedArrays[0], (valueOfFirstArray, index) => multiCallback(valueOfFirstArray, ...restArrays.map(v => get(v, index))))
}
Insert cell
mapMultipleNoDummy([X, Y, Z], fn)
Insert cell
function mapMultipleNoDummy(arrays, multiCallback) {
const newSize = broadcastedSizes;
const maxDepth = newSize.length - 1;
const sizes = arrays.map(array => math.size(array));

const index = [];
return recurse();

function recurse(depth = 0) {
const result = [];
if (depth < maxDepth) {
for (let i = 0; i < newSize[depth]; i++) {
index[depth] = i;
result.push(recurse(depth + 1));
}
} else {
for (let i = 0; i < newSize[depth]; i++) {
index[depth] = i;
result.push(
multiCallback(
...arrays.map((arr, idx) =>
get(arr, unbroadcastIndex(index, sizes[idx]))
)
)
);
}
}
return result;
}
}
Insert cell
function mapMultipleIdeal(arrays, callback) {
// const sizes = arrays.map(array => math.size(array));
const broadcastedSize = broadcastedSizes //broadcastSizes(...sizes);
const maxDepth = broadcastedSize.length - 1;
const offsets = sizes.map(size => maxDepth - size.length + 1);

return recurse(arrays, 0);

function recurse(values, depth) {
const N = broadcastedSize[depth];
const relativeDepth = offsets.map(offset => depth - offset);
const lengths = sizes.map((size, id) => relativeDepth[id] < 0 ? null : size[relativeDepth[id]]);
const result = new Array(N);
if (depth < maxDepth) {
for (let i = 0; i < N; i++) {
result[i] = recurse(values.map((value, id) => (relativeDepth[id] < 0 ? value : value[i])), depth + 1);
}
} else {
for (let i = 0; i < N; i++) {
result[i] = callback(...values.map((value, id) => (lengths[id] > 1 ? value[i] : value[0])));
}
}
return result;
}
}

Insert cell
function mapMultipleMemoize1(arrays, multiCallback) {
// in each array in each dimension save the previous index and value
// otherwise use get
const newSize = broadcastedSizes;
const maxDepth = newSize.length - 1;
const sizes = arrays.map(array => math.size(array));
const buffers = arrays.map(()=>({}))

const index = [];
return recurse();

// maintain an array of buffers and offsets, then for each depth
// find if the depth-offest has a matching index.toString()
// only keep a simple buffer

function getFromBuffer(arrayIndex, index){
const indexStr = index.toString()
const previous = buffers[arrayIndex]
let result
if (previous.value === undefined || previous.index !== indexStr){
result = get(arrays[arrayIndex], index)
buffers[arrayIndex] = {index:indexStr, value: result}
} else {
result = previous.value
}
return result
};
function recurse(depth = 0) {
const result = [];
if (depth < maxDepth) {
for (let i = 0; i < newSize[depth]; i++) {
index[depth] = i;
result.push(recurse(depth + 1));
}
} else {
for (let i = 0; i < newSize[depth]; i++) {
index[depth] = i;
result.push(
multiCallback(
...arrays.map((arr, idx) =>
getFromBuffer(idx, unbroadcastIndex(index, sizes[idx]))
)
)
);
}
}
return result;
}
}
Insert cell
math.deepEqual(
math.map(X, Y, Z, fn),
mapMultipleNaive([X, Y, Z], fn)
)
Insert cell
math.deepEqual(
math.map(X, Y, Z, fn),
mapMultipleNoBroadcast([X, Y, Z], fn)
)
Insert cell
math.deepEqual(
math.map(X, Y, Z, fn),
mapMultipleNoDummy([X, Y, Z], fn)
)
Insert cell
math.deepEqual(
math.map(X, Y, Z, fn),
mapMultipleMemoize([X, Y, Z], fn)
)
Insert cell
math.deepEqual(
math.map(X, Y, Z, fn),
mapMultipleMemoize1([X, Y, Z], fn)
)
Insert cell
math.deepEqual(
math.map(X, Y, Z, fn),
mapMultipleIdeal([X, Y, Z], fn)
)
Insert cell
function mapMultipleNoGet(arrays, multiCallback) {
// this will attempt to only pass section of each array without broadcasting them according to the needs

const newSize = broadcastedSizes; // Determine the broadcasted size
const maxDepth = newSize.length - 1;

const index = Array(maxDepth + 1).fill(0); // Initialize the index array

return recurse(arrays);

function recurse(values, depth = 0) {
const result = [];
const bcSize = broadcastedSizes.slice(depth);
const sizes = values.map((value) => math.size(value));
const offsets = sizes.map((size) => bcSize.length - size.length);
if (depth < maxDepth) {
for (let i = 0; i < newSize[depth]; i++) {
index[depth] = i;
result.push(
recurse(
values.map((value, idx) => {
const thisOffset = offsets[idx];
return thisOffset > 0 ? value : value[i]
}),
depth + 1
)
);
}
} else {
for (let i = 0; i < newSize[depth]; i++) {
index[depth] = i;
result.push(multiCallback(...values));
}
}
console.log(result);
return result;
}
}
Insert cell
function fn(x, y, z){ return math.norm([x,y,z])}
Insert cell
Insert cell
Insert cell
Z = math.random([N])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
math = import("https://cdn.jsdelivr.net/npm/mathjs@latest/+esm")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more