/*
Chalkboard - Tensor Namespace
Version 3.0.4 Euler
Released July 20th, 2026
*/
/*
This Source Code Form is subject to the terms of the
Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
///
namespace Chalkboard {
/**
* The tensor namespace.
* @namespace
*/
export namespace tens {
/**
* Calculates the absolute value of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.absolute([[-1, 2], [-3, 4]]); // Returns [[1, 2], [3, 4]]
*/
export const absolute = (tens: ChalkboardTensor): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.absolute: Parameter "tens" must be a tensor.`);
const result = Chalkboard.tens.init() as ChalkboardTensor[];
if (Array.isArray(tens)) {
for (let i = 0; i < tens.length; i++) {
result[i] = Chalkboard.tens.absolute(tens[i]);
}
return result;
} else {
return Math.abs(tens);
}
};
/**
* Calculates the addition of two tensors.
* @param {ChalkboardTensor} tens1 - The first tensor
* @param {ChalkboardTensor} tens2 - The second tensor
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.add([[1, 2], [3, 4]], [[4, 3], [2, 1]]); // Returns [[5, 5], [5, 5]]
*/
export const add = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): ChalkboardTensor => {
if (tens1 === null || typeof tens1 !== "number" && !Array.isArray(tens1)) throw new Error(`Chalkboard.tens.add: Parameter "tens1" must be a tensor.`);
if (tens2 === null || typeof tens2 !== "number" && !Array.isArray(tens2)) throw new Error(`Chalkboard.tens.add: Parameter "tens2" must be a tensor.`);
const result = Chalkboard.tens.init() as ChalkboardTensor[];
if (Array.isArray(tens1) && Array.isArray(tens2)) {
for (let i = 0; i < Math.max(tens1.length, tens2.length); i++) {
result[i] = Chalkboard.tens.add(tens1[i] !== undefined ? tens1[i] : 0, tens2[i] !== undefined ? tens2[i] : 0);
}
return result;
} else {
return (tens1 as number) + (tens2 as number);
}
};
/**
* Calculates the concatentation of two tensors.
* @param {ChalkboardTensor} tens1 - The first tensor
* @param {ChalkboardTensor} tens2 - The second tensor
* @param {number} [rank=1] - The rank to concatenate
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.concat([[1, 2]], [[3, 4]], 1); // Concatenates the rank-2 tensors
*/
export const concat = (tens1: ChalkboardTensor, tens2: ChalkboardTensor, rank: number = 1): ChalkboardTensor => {
if (tens1 === null || typeof tens1 !== "number" && !Array.isArray(tens1)) throw new Error(`Chalkboard.tens.concat: Parameter "tens1" must be a tensor.`);
if (tens2 === null || typeof tens2 !== "number" && !Array.isArray(tens2)) throw new Error(`Chalkboard.tens.concat: Parameter "tens2" must be a tensor.`);
if (!Number.isInteger(rank) || rank < 1 || rank > Math.min(Chalkboard.tens.rank(tens1), Chalkboard.tens.rank(tens2))) throw new Error(`Chalkboard.tens.concat: Parameter "rank" must be a positive integer less than or equal to both tensor ranks.`);
const concatAtRank = function (arr1: ChalkboardTensor, arr2: ChalkboardTensor, currentRank: number): ChalkboardTensor {
if (currentRank === rank) {
return Chalkboard.tens.init((arr1 as ChalkboardTensor[]).concat(arr2));
}
return (arr1 as ChalkboardTensor[]).map(function (element, index) {
return concatAtRank(element, (arr2 as ChalkboardTensor[])[index], currentRank);
});
};
return concatAtRank(tens1, tens2, 1);
};
/**
* Calculates a tensor constrained within a range.
* @param {ChalkboardTensor} tens - The tensor
* @param {number[]} [range=[0, 1]] - The range
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.constrain([[-1, 0.5], [2, 1]], [0, 1]); // Returns [[0, 0.5], [1, 1]]
*/
export const constrain = (tens: ChalkboardTensor, range: [number, number] = [0, 1]): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.constrain: Parameter "tens" must be a tensor.`);
const result = Chalkboard.tens.init() as ChalkboardTensor[];
if (Array.isArray(tens)) {
for (let i = 0; i < tens.length; i++) {
result[i] = Chalkboard.tens.constrain(tens[i], range);
}
return result;
} else {
return Chalkboard.numb.constrain(tens, range);
}
};
/**
* Calculates the contraction of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardTensor | number}
* @example
* const result = Chalkboard.tens.contract([[1, 2], [3, 4]]); // Contracts the tensor along its outer indices
*/
export const contract = (tens: ChalkboardTensor): ChalkboardTensor | number => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.contract: Parameter "tens" must be a tensor.`);
if (Chalkboard.tens.rank(tens) > 2) {
return Chalkboard.tens.resize(
tens,
Chalkboard.tens.size(tens)[0],
Chalkboard.tens
.size(tens)
.slice(1)
.reduce(function (a: number, b: number): number {
return a * b;
}) / Chalkboard.tens.size(tens)[0]
);
} else if (Chalkboard.tens.rank(tens) === 2) {
return Chalkboard.matr.trace(tens as number[][]);
} else {
return tens;
}
};
/**
* Copies a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.copy([[[1, 2]], [[3, 4]]]); // Returns an independent copy
*/
export const copy = (tens: ChalkboardTensor): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.copy: Parameter "tens" must be a tensor.`);
if (Array.isArray(tens)) {
const result = Chalkboard.tens.init() as ChalkboardTensor[];
for (let i = 0; i < tens.length; i++) {
result[i] = Chalkboard.tens.copy(tens[i]);
}
return result;
} else {
return tens;
}
};
/**
* Initializes an empty tensor.
* @param {number[]} size - The size
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.empty(2, 3, 4); // Returns an empty 2-by-3-by-4 tensor
*/
export const empty = (...size: number[]): ChalkboardTensor => {
size = Array.isArray(size[0]) ? size[0] : size;
if (size.length > 0 && (!Number.isInteger(size[0]) || size[0] < 0) || size.length > 1 && (!Number.isInteger(size[size.length - 1]) || size[size.length - 1] < 0)) throw new Error(`Chalkboard.tens.empty: Parameter "size" must begin and end with non-negative integers.`);
const newNDArray = function (size: number[]): ChalkboardTensor | null {
if (size.length === 0) {
return null;
}
const curr = size[0];
const rest = size.slice(1);
const result: ChalkboardTensor = [];
for (let i = 0; i < curr; i++) {
result[i] = newNDArray(rest) as ChalkboardTensor;
}
return result;
};
return newNDArray(size) as ChalkboardTensor;
};
/**
* Initializes a tensor filled with one number.
* @param {number} element - The number to fill the elements with
* @param {number[]} size - The size
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.fill(1, 2, 2, 2); // Returns a 2-by-2-by-2 tensor of ones
*/
export const fill = (element: number, ...size: number[]): ChalkboardTensor => {
if (!Number.isFinite(element)) throw new Error(`Chalkboard.tens.fill: Parameter "element" must be a finite number.`);
size = Array.isArray(size[0]) ? size[0] : size;
if (size.length > 0 && (!Number.isInteger(size[0]) || size[0] < 0) || size.length > 1 && (!Number.isInteger(size[size.length - 1]) || size[size.length - 1] < 0)) throw new Error(`Chalkboard.tens.fill: Parameter "size" must begin and end with non-negative integers.`);
const newNDArray = function (size: number[]): ChalkboardTensor {
if (size.length === 0) {
return element;
}
const curr = size[0];
const rest = size.slice(1);
const result = [];
for (let i = 0; i < curr; i++) {
result[i] = newNDArray(rest);
}
return result;
};
return newNDArray(size);
};
/**
* Initializes a new tensor.
* @param {ChalkboardTensor[]} tensor - The n-dimensional array either as a sequence of arrays or one array
* @returns {ChalkboardTensor}
* @example
* // Defines a 2x2x2 tensor [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
* let A = Chalkboard.tens.init([[1, 2], [3, 4]],
* [[5, 6], [7, 8]]);
* let B = Chalkboard.tens.init([[[1, 2], [3, 4]],
* [[5, 6], [7, 8]]]);
*/
export const init = (...tensor: ChalkboardTensor[]): ChalkboardTensor => {
if (tensor.length === 0) {
return [];
} else if (tensor.length === 1 && Array.isArray(tensor[0])) {
tensor = tensor[0];
}
const newNDArray = function (arr: ChalkboardTensor[]): ChalkboardTensor[] {
return arr.map(function (subarr) {
if (Array.isArray(subarr)) {
return newNDArray(subarr);
} else {
return subarr;
}
});
};
return newNDArray(tensor);
};
/**
* Checks if two tensors are approximately equal within a particular precision.
* @param {ChalkboardTensor} tens1 - The first tensor
* @param {ChalkboardTensor} tens2 - The second tensor
* @param {number} [precision=0.000001] - The precision to check
* @returns {boolean}
* @example
* const result = Chalkboard.tens.isApproxEqual([[1, 2]], [[1, 2.0000001]]); // Returns true
*/
export const isApproxEqual = (tens1: ChalkboardTensor, tens2: ChalkboardTensor, precision: number = 0.000001): boolean => {
if (tens1 === null || typeof tens1 !== "number" && !Array.isArray(tens1)) throw new Error(`Chalkboard.tens.isApproxEqual: Parameter "tens1" must be a tensor.`);
if (tens2 === null || typeof tens2 !== "number" && !Array.isArray(tens2)) throw new Error(`Chalkboard.tens.isApproxEqual: Parameter "tens2" must be a tensor.`);
if (precision !== undefined && (!Number.isFinite(precision))) throw new Error(`Chalkboard.tens.isApproxEqual: Parameter "precision" must be a finite number.`);
if (Chalkboard.tens.isSizeEqual(tens1, tens2)) {
(tens1 = tens1 as ChalkboardTensor[]), (tens2 = tens2 as ChalkboardTensor[]);
for (let i = 0; i < tens1.length; i++) {
if (Array.isArray(tens1[i]) && Array.isArray(tens2[i])) {
if (!Chalkboard.tens.isApproxEqual(tens1[i], tens2[i], precision)) return false;
} else {
if (!Chalkboard.numb.isApproxEqual(tens1[i] as number, tens2[i] as number, precision)) return false;
}
}
return true;
} else {
return false;
}
};
/**
* Checks if two tensors are equal.
* @param {ChalkboardTensor} tens1 - The first tensor
* @param {ChalkboardTensor} tens2 - The second tensor
* @returns {boolean}
* @example
* const result = Chalkboard.tens.isEqual([[1, 2]], [[1, 2]]); // Returns true
*/
export const isEqual = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): boolean => {
if (tens1 === null || typeof tens1 !== "number" && !Array.isArray(tens1)) throw new Error(`Chalkboard.tens.isEqual: Parameter "tens1" must be a tensor.`);
if (tens2 === null || typeof tens2 !== "number" && !Array.isArray(tens2)) throw new Error(`Chalkboard.tens.isEqual: Parameter "tens2" must be a tensor.`);
if (Chalkboard.tens.isSizeEqual(tens1, tens2)) {
(tens1 = tens1 as ChalkboardTensor[]), (tens2 = tens2 as ChalkboardTensor[]);
for (let i = 0; i < tens1.length; i++) {
if (Array.isArray(tens1[i]) && Array.isArray(tens2[i])) {
if (!Chalkboard.tens.isEqual(tens1[i], tens2[i])) return false;
} else {
if (tens1[i] !== tens2[i]) return false;
}
}
return true;
} else {
return false;
}
};
/**
* Checks if two tensors have equal ranks.
* @param {ChalkboardTensor} tens1 - The first tensor
* @param {ChalkboardTensor} tens2 - The second tensor
* @returns {boolean}
* @example
* const result = Chalkboard.tens.isRankEqual([[1, 2]], [[3, 4], [5, 6]]); // Returns true
*/
export const isRankEqual = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): boolean => {
if (tens1 === null || typeof tens1 !== "number" && !Array.isArray(tens1)) throw new Error(`Chalkboard.tens.isRankEqual: Parameter "tens1" must be a tensor.`);
if (tens2 === null || typeof tens2 !== "number" && !Array.isArray(tens2)) throw new Error(`Chalkboard.tens.isRankEqual: Parameter "tens2" must be a tensor.`);
return Chalkboard.tens.rank(tens1) === Chalkboard.tens.rank(tens2);
};
/**
* Checks if a tensor is of a particular rank.
* @param {ChalkboardTensor} tens - The tensor
* @param {number} rank - The rank
* @returns {boolean}
* @example
* const result = Chalkboard.tens.isRankOf([[[1]]], 3); // Returns true
*/
export const isRankOf = (tens: ChalkboardTensor, rank: number): boolean => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.isRankOf: Parameter "tens" must be a tensor.`);
if (!Number.isInteger(rank) || rank < 0) throw new Error(`Chalkboard.tens.isRankOf: Parameter "rank" must be a non-negative integer.`);
return Chalkboard.tens.rank(tens) === rank;
};
/**
* Checks if two tensors have equal sizes.
* @param {ChalkboardTensor} tens1 - The first tensor
* @param {ChalkboardTensor} tens2 - The second tensor
* @returns {boolean}
* @example
* const result = Chalkboard.tens.isSizeEqual([[1, 2]], [[3, 4]]); // Returns true
*/
export const isSizeEqual = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): boolean => {
if (tens1 === null || typeof tens1 !== "number" && !Array.isArray(tens1)) throw new Error(`Chalkboard.tens.isSizeEqual: Parameter "tens1" must be a tensor.`);
if (tens2 === null || typeof tens2 !== "number" && !Array.isArray(tens2)) throw new Error(`Chalkboard.tens.isSizeEqual: Parameter "tens2" must be a tensor.`);
if (Chalkboard.tens.isRankEqual(tens1, tens2)) {
let score = 0;
for (let i = 0; i < Chalkboard.tens.rank(tens1); i++) {
if (Chalkboard.tens.size(tens1)[i] !== Chalkboard.tens.size(tens2)[i]) score++;
}
return score === 0;
} else {
return false;
}
};
/**
* Checks if a tensor is of a particular size.
* @param {ChalkboardTensor} tens - The tensor
* @param {number[]} size - The size
* @returns {boolean}
* @example
* const result = Chalkboard.tens.isSizeOf([[[1, 2]], [[3, 4]]], 2, 1, 2); // Returns true
*/
export const isSizeOf = (tens: ChalkboardTensor, ...size: number[]): boolean => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.isSizeOf: Parameter "tens" must be a tensor.`);
size = Array.isArray(size[0]) ? size[0] : size;
if (size.length > 0 && (!Number.isInteger(size[0]) || size[0] < 0) || size.length > 1 && (!Number.isInteger(size[size.length - 1]) || size[size.length - 1] < 0)) throw new Error(`Chalkboard.tens.isSizeOf: Parameter "size" must begin and end with non-negative integers.`);
return Chalkboard.tens.isSizeEqual(tens, Chalkboard.tens.empty(...size));
};
/**
* Checks if a tensor has a uniform size (analogous to checking if a matrix is square).
* @param {ChalkboardTensor} tens - The tensor
* @returns {boolean}
* @example
* const result = Chalkboard.tens.isSizeUniform([[1, 2], [3, 4]]); // Returns true
*/
export const isSizeUniform = (tens: ChalkboardTensor): boolean => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.isSizeUniform: Parameter "tens" must be a tensor.`);
let score = 0;
for (let i = 0; i < Chalkboard.tens.rank(tens); i++) {
if (Chalkboard.tens.size(tens)[i] !== Chalkboard.tens.size(tens)[0]) score++;
}
return score === 0;
};
/**
* Checks if a tensor is a zero tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {boolean}
* @example
* const result = Chalkboard.tens.isZero([[0, 0], [0, 0]]); // Returns true
*/
export const isZero = (tens: ChalkboardTensor): boolean => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.isZero: Parameter "tens" must be a tensor.`);
if (Array.isArray(tens)) {
for (let i = 0; i < tens.length; i++) {
if (!Chalkboard.tens.isZero(tens[i])) return false;
}
return true;
} else {
return Chalkboard.numb.isApproxEqual(tens, 0);
}
};
/**
* Calculates the multiplication of two tensors.
* @param {ChalkboardTensor} tens1 - The first tensor
* @param {ChalkboardTensor} tens2 - The second tensor
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.mul([[1, 2], [3, 4]], [[2, 3], [4, 5]]); // Returns the elementwise product
*/
export const mul = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): ChalkboardTensor => {
if (tens1 === null || typeof tens1 !== "number" && !Array.isArray(tens1)) throw new Error(`Chalkboard.tens.mul: Parameter "tens1" must be a tensor.`);
if (tens2 === null || typeof tens2 !== "number" && !Array.isArray(tens2)) throw new Error(`Chalkboard.tens.mul: Parameter "tens2" must be a tensor.`);
const result = Chalkboard.tens.init() as ChalkboardTensor[];
if (Array.isArray(tens1) && Array.isArray(tens2)) {
for (let i = 0; i < tens1.length; i++) {
const subarr = Chalkboard.tens.init() as ChalkboardTensor[];
for (let j = 0; j < tens2.length; j++) {
subarr[j] = Chalkboard.tens.mul(tens1[i], tens2[j]);
}
result.push(subarr);
}
return result;
} else {
return (tens1 as number) * (tens2 as number);
}
};
/**
* Calculates the negation of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.negate([[1, -2], [3, -4]]); // Returns [[-1, 2], [-3, 4]]
*/
export const negate = (tens: ChalkboardTensor): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.negate: Parameter "tens" must be a tensor.`);
const result = Chalkboard.tens.init() as ChalkboardTensor[];
if (Array.isArray(tens)) {
for (let i = 0; i < tens.length; i++) {
result[i] = Chalkboard.tens.negate(tens[i]);
}
return result;
} else {
return -tens;
}
};
/**
* Prints a tensor in the console.
* @param {ChalkboardTensor} tens - The tensor
* @returns {void}
* @example
* Chalkboard.tens.print([[[1, 2]], [[3, 4]]]); // Prints the tensor
*/
export const print = (tens: ChalkboardTensor): void => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.print: Parameter "tens" must be a tensor.`);
console.log(Chalkboard.tens.toString(tens));
};
/**
* Returns a tensor with elements in a rank removed (pulled out).
* @param {ChalkboardTensor} tens - The tensor
* @param {number} rank - The rank
* @param {number} index - The index of the elements to pull
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.pull([[[1, 2]], [[3, 4]]], 1, 0); // Removes the first outer slice
*/
export const pull = (tens: ChalkboardTensor, rank: number, index: number): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.pull: Parameter "tens" must be a tensor.`);
const size = Chalkboard.tens.size(tens);
if (!Number.isInteger(rank) || rank < 0 || rank >= size.length) throw new Error(`Chalkboard.tens.pull: Parameter "rank" must be a non-negative integer less than the tensor rank.`);
if (!Number.isInteger(index) || index < 0 || index >= size[rank]) throw new Error(`Chalkboard.tens.pull: Parameter "index" must be an integer within the selected rank bounds.`);
tens = tens as ChalkboardTensor[];
if (rank === 0) {
tens.splice(index, 1);
return tens;
} else {
for (let i = 0; i < tens.length; i++) {
Chalkboard.tens.pull(tens[i], rank - 1, index);
}
return tens;
}
};
/**
* Returns a tensor with elements in a rank added (pushed in).
* @param {ChalkboardTensor} tens - The tensor
* @param {number} rank - The rank
* @param {number} index - The index of the elements to pull
* @param {number[]} elements - The elements to push
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.push([[[1, 2]]], 1, 1, [3, 4]); // Inserts an outer slice
*/
export const push = (tens: ChalkboardTensor, rank: number, index: number, elements: number[]): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.push: Parameter "tens" must be a tensor.`);
const size = Chalkboard.tens.size(tens);
if (!Number.isInteger(rank) || rank < 0 || rank >= size.length) throw new Error(`Chalkboard.tens.push: Parameter "rank" must be a non-negative integer less than the tensor rank.`);
if (!Number.isInteger(index) || index < 0 || index > size[rank]) throw new Error(`Chalkboard.tens.push: Parameter "index" must be an integer within the selected rank insertion bounds.`);
if (!Array.isArray(elements)) throw new Error(`Chalkboard.tens.push: Parameter "elements" must be an array.`);
tens = tens as ChalkboardTensor[];
const insert = (tensor: ChalkboardTensor[], currentRank: number, currentElements: ChalkboardTensor): ChalkboardTensor[] => {
if (currentRank === 0) {
tensor.splice(index, 0, currentElements);
} else {
for (let i = 0; i < tensor.length; i++) {
insert(tensor[i] as ChalkboardTensor[], currentRank - 1, (currentElements as ChalkboardTensor[])[i]);
}
}
return tensor;
};
return insert(tens, rank, elements);
};
/**
* Initializes a random tensor.
* @param {number} inf - The lower bound
* @param {number} sup - The upper bound
* @param {number[]} size - The size
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.random(-1, 1, 2, 2, 2); // Returns a random 2-by-2-by-2 tensor
*/
export const random = (inf: number, sup: number, ...size: number[]): ChalkboardTensor => {
if (!Number.isFinite(inf)) throw new Error(`Chalkboard.tens.random: Parameter "inf" must be a finite number.`);
if (!Number.isFinite(sup)) throw new Error(`Chalkboard.tens.random: Parameter "sup" must be a finite number.`);
size = Array.isArray(size[0]) ? size[0] : size;
if (size.length > 0 && (!Number.isInteger(size[0]) || size[0] < 0) || size.length > 1 && (!Number.isInteger(size[size.length - 1]) || size[size.length - 1] < 0)) throw new Error(`Chalkboard.tens.random: Parameter "size" must begin and end with non-negative integers.`);
const newNDArray = function (size: number[]): ChalkboardTensor {
if (size.length === 0) {
return Chalkboard.numb.random(inf, sup);
}
const curr = size[0];
const rest = size.slice(1);
const result = [];
for (let i = 0; i < curr; i++) {
result[i] = newNDArray(rest);
}
return result;
};
return newNDArray(size);
};
/**
* Calculates the rank of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {number}
* @example
* const result = Chalkboard.tens.rank([[[1, 2]], [[3, 4]]]); // Returns 3
*/
export const rank = (tens: ChalkboardTensor): number => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.rank: Parameter "tens" must be a tensor.`);
return Chalkboard.tens.size(tens).length;
};
/**
* Calculates the reciprocal of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.reciprocate([[1, 2], [4, 8]]); // Returns [[1, 0.5], [0.25, 0.125]]
*/
export const reciprocate = (tens: ChalkboardTensor): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.reciprocate: Parameter "tens" must be a tensor.`);
const result = Chalkboard.tens.init() as ChalkboardTensor[];
if (Array.isArray(tens)) {
for (let i = 0; i < tens.length; i++) {
result[i] = Chalkboard.tens.reciprocate(tens[i]);
}
return result;
} else {
return 1 / tens;
}
};
/**
* Returns a tensor with its size changed.
* @param {ChalkboardTensor} tens - The tensor
* @param {number[]} size - The size to change to
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.resize([[1, 2], [3, 4]], 4); // Returns [1, 2, 3, 4]
*/
export const resize = (tens: ChalkboardTensor, ...size: number[]): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.resize: Parameter "tens" must be a tensor.`);
size = Array.isArray(size[0]) ? size[0] : size;
if (size.length > 0 && (!Number.isInteger(size[0]) || size[0] < 0) || size.length > 1 && (!Number.isInteger(size[size.length - 1]) || size[size.length - 1] < 0)) throw new Error(`Chalkboard.tens.resize: Parameter "size" must begin and end with non-negative integers.`);
const result = Chalkboard.tens.fill(0, ...size);
const refill = function (arr1: ChalkboardTensor[], arr2: ChalkboardTensor[]): void {
for (let i = 0; i < arr2.length; i++) {
if (Array.isArray(arr2[i])) {
refill(arr1, (arr2 as ChalkboardTensor[][])[i]);
} else {
arr2[i] = arr1.length > 0 ? (arr1.shift() as ChalkboardTensor) : 0;
}
}
};
refill(Chalkboard.tens.toArray(tens), result as ChalkboardTensor[]);
return result;
};
/**
* Calculates the rounding of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.round([[1.2, 2.7], [-3.5, 4.4]]); // Returns [[1, 3], [-3, 4]]
*/
export const round = (tens: ChalkboardTensor): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.round: Parameter "tens" must be a tensor.`);
const result = Chalkboard.tens.init() as ChalkboardTensor[];
if (Array.isArray(tens)) {
for (let i = 0; i < tens.length; i++) {
result[i] = Chalkboard.tens.round(tens[i]);
}
return result;
} else {
return Math.round(tens);
}
};
/**
* Calculates the scalar multiplication of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @param {number} num - The number
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.scl([[1, 2], [3, 4]], 2); // Returns [[2, 4], [6, 8]]
*/
export const scl = (tens: ChalkboardTensor, num: number): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.scl: Parameter "tens" must be a tensor.`);
if (!Number.isFinite(num)) throw new Error(`Chalkboard.tens.scl: Parameter "num" must be a finite number.`);
const result = Chalkboard.tens.init() as ChalkboardTensor[];
if (Array.isArray(tens)) {
for (let i = 0; i < tens.length; i++) {
result[i] = Chalkboard.tens.scl(tens[i], num);
}
return result;
} else {
return tens * num;
}
};
/**
* Returns the size of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {number[]}
* @example
* const result = Chalkboard.tens.size([[[1, 2]], [[3, 4]]]); // Returns [2, 1, 2]
*/
export const size = (tens: ChalkboardTensor): number[] => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.size: Parameter "tens" must be a tensor.`);
if (Array.isArray(tens)) {
let result = [tens.length];
if (Array.isArray(tens[0])) {
result = result.concat(Chalkboard.tens.size(tens[0]));
}
return result;
} else {
return [];
}
};
/**
* Calculates the subtraction of two tensors.
* @param {ChalkboardTensor} tens1 - The first tensor
* @param {ChalkboardTensor} tens2 - The second tensor
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.sub([[5, 8], [13, 21]], [[2, 3], [5, 8]]); // Returns [[3, 5], [8, 13]]
*/
export const sub = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): ChalkboardTensor => {
if (tens1 === null || typeof tens1 !== "number" && !Array.isArray(tens1)) throw new Error(`Chalkboard.tens.sub: Parameter "tens1" must be a tensor.`);
if (tens2 === null || typeof tens2 !== "number" && !Array.isArray(tens2)) throw new Error(`Chalkboard.tens.sub: Parameter "tens2" must be a tensor.`);
const result = Chalkboard.tens.init() as ChalkboardTensor[];
if (Array.isArray(tens1) && Array.isArray(tens2)) {
for (let i = 0; i < Math.max(tens1.length, tens2.length); i++) {
result[i] = Chalkboard.tens.sub(tens1[i] !== undefined ? tens1[i] : 0, tens2[i] !== undefined ? tens2[i] : 0);
}
return result;
} else {
return (tens1 as number) - (tens2 as number);
}
};
/**
* Converts a tensor to an array.
* @param {ChalkboardTensor} tens - The tensor
* @returns {number[]}
* @example
* const result = Chalkboard.tens.toArray([[[1, 2]], [[3, 4]]]); // Returns [1, 2, 3, 4]
*/
export const toArray = (tens: ChalkboardTensor): number[] => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.toArray: Parameter "tens" must be a tensor.`);
const result: number[] = [];
const flatten = function (tens: ChalkboardTensor): void {
for (let i = 0; i < (tens as ChalkboardTensor[]).length; i++) {
if (Array.isArray((tens as ChalkboardTensor[])[i])) {
flatten((tens as ChalkboardTensor[])[i]);
} else {
result.push((tens as number[])[i]);
}
}
};
flatten(tens);
return result;
};
/**
* Converts a tensor to a matrix.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardMatrix}
* @example
* const result = Chalkboard.tens.toMatrix([[[1, 2]], [[3, 4]]]); // Returns a matrix containing the tensor values
*/
export const toMatrix = (tens: ChalkboardTensor): ChalkboardMatrix => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.toMatrix: Parameter "tens" must be a tensor.`);
const result = Chalkboard.matr.init();
const flatten = function (tens: ChalkboardTensor, result: ChalkboardMatrix): void {
for (let i = 0; i < (tens as ChalkboardTensor[]).length; i++) {
if (Array.isArray((tens as ChalkboardTensor[])[i])) {
flatten((tens as ChalkboardTensor[])[i], result);
} else {
result.push((tens as ChalkboardMatrix)[i]);
}
}
};
const matr = Chalkboard.matr.init();
flatten(tens, matr);
const rows = (tens as ChalkboardTensor[]).length || 1;
for (let j = 0; j < rows; j++) {
result.push(matr.slice((j * matr.length) / rows, ((j + 1) * matr.length) / rows) as unknown as number[]);
}
return result;
};
/**
* Converts a tensor to an object.
* @param {ChalkboardTensor} tens - The tensor
* @returns {object}
* @example
* const result = Chalkboard.tens.toObject([[1, 2], [3, 4]]); // Returns a nested object
*/
export const toObject = (tens: ChalkboardTensor): object | number => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.toObject: Parameter "tens" must be a tensor.`);
if (Array.isArray(tens)) {
const result: { [key: string]: number | object } = {};
for (let i = 0; i < tens.length; i++) {
result["_" + (i + 1)] = Chalkboard.tens.toObject(tens[i]);
}
return result;
} else {
return tens;
}
};
/**
* Converts a tensor to a set.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardSet}
* @example
* const result = Chalkboard.tens.toSet([[1, 1], [2, 3]]); // Returns the set {1, 2, 3}
*/
export const toSet = (tens: ChalkboardTensor): ChalkboardSet => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.toSet: Parameter "tens" must be a tensor.`);
return Chalkboard.abal.set(Chalkboard.tens.toArray(tens));
};
/**
* Converts a tensor to a string.
* @param {ChalkboardTensor} tens - The tensor
* @returns {string}
* @example
* const result = Chalkboard.tens.toString([[[1, 2]], [[3, 4]]], 2); // Returns an indented tensor string
*/
export const toString = (tens: ChalkboardTensor, indentation: number = 0): string => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.toString: Parameter "tens" must be a tensor.`);
if (!Number.isInteger(indentation) || indentation < 0) throw new Error(`Chalkboard.tens.toString: Parameter "indentation" must be a non-negative integer.`);
if (Array.isArray((tens as ChalkboardTensor[])[0])) {
let result = "\t".repeat(indentation) + "[\n";
for (let i = 0; i < (tens as ChalkboardTensor[]).length; i++) {
result += Chalkboard.tens.toString((tens as ChalkboardTensor[])[i], indentation + 1);
}
result += "\t".repeat(indentation) + "]\n";
return result;
} else {
let result = "\t".repeat(indentation) + "[ ";
for (let i = 0; i < (tens as ChalkboardTensor[]).length; i++) {
result += (tens as ChalkboardTensor[])[i].toString() + " ";
}
result += "]\n";
return result;
}
};
/**
* Converts a tensor to a typed array.
* @param {ChalkboardTensor} tens - The tensor
* @param {"int8" | "int16" | "int32" | "float32" | "float64" | "bigint64"} [type="float32"] - The type of the typed array, which can be "int8", "int16", "int32", "float32", "float64", or "bigint64" (optional, defaults to "float32")
* @returns {Int8Array | Int16Array | Int32Array | Float32Array | Float64Array | BigInt64Array}
* @example
* const result = Chalkboard.tens.toTypedArray([[1, 2], [3, 4]], "float64"); // Returns a Float64Array
*/
export const toTypedArray = (tens: ChalkboardTensor, type: "int8" | "int16" | "int32" | "float32" | "float64" | "bigint64" = "float32"): Int8Array | Int16Array | Int32Array | Float32Array | Float64Array | BigInt64Array => {
const arr = Chalkboard.tens.toArray(tens);
if (type === "int8") {
return new Int8Array(arr);
} else if (type === "int16") {
return new Int16Array(arr);
} else if (type === "int32") {
return new Int32Array(arr);
} else if (type === "float32") {
return new Float32Array(arr);
} else if (type === "float64") {
return new Float64Array(arr);
} else if (type === "bigint64") {
return new BigInt64Array(arr.map((n) => BigInt(Math.floor(n))));
}
throw new Error(`Chalkboard.tens.toTypedArray: Parameter "type" must be int8, int16, int32, float32, float64, or bigint64.`);
};
/**
* Converts a tensor to a vector.
* @param {ChalkboardTensor} tens - The tensor
* @param {number} dimension - The dimension of the vector, which can be 2, 3, or 4
* @param {number} [index=0] - The index to start from
* @returns {ChalkboardVector}
* @example
* const result = Chalkboard.tens.toVector([[3, 4], [5, 12]], 2, 1); // Returns the second two-dimensional vector
*/
export const toVector = (tens: ChalkboardTensor, dimension: number, index: number = 0): ChalkboardVector => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.toVector: Parameter "tens" must be a tensor.`);
if (dimension !== 2 && dimension !== 3 && dimension !== 4) throw new Error(`Chalkboard.tens.toVector: Parameter "dimension" must be 2, 3, or 4.`);
const arr = Chalkboard.tens.toArray(tens);
if (!Number.isInteger(index) || index < 0 || index + dimension > arr.length) throw new Error(`Chalkboard.tens.toVector: Parameter "index" and parameter "dimension" must fit within the flattened tensor bounds.`);
if (dimension === 2) {
return Chalkboard.vect.init(arr[index], arr[index + 1]);
} else if (dimension === 3) {
return Chalkboard.vect.init(arr[index], arr[index + 1], arr[index + 2]);
} else if (dimension === 4) {
return Chalkboard.vect.init(arr[index], arr[index + 1], arr[index + 2], arr[index + 3]);
} else {
throw new Error(`Chalkboard.tens.toVector: Parameter "dimension" must be 2, 3, or 4.`);
}
};
/**
* Calculates the transpose of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.transpose([[[1, 2]], [[3, 4]]]); // Reverses the tensor axes
*/
export const transpose = (tens: ChalkboardTensor): ChalkboardTensor => {
if (tens === null || typeof tens !== "number" && !Array.isArray(tens)) throw new Error(`Chalkboard.tens.transpose: Parameter "tens" must be a tensor.`);
return Chalkboard.tens.resize(tens, ...Chalkboard.tens.size(tens).reverse());
};
/**
* Initializes a zero tensor.
* @param {number[]} size - The size
* @returns {ChalkboardTensor}
* @example
* const result = Chalkboard.tens.zero(2, 2, 2); // Returns a 2-by-2-by-2 zero tensor
*/
export const zero = (...size: number[]): ChalkboardTensor => {
size = Array.isArray(size[0]) ? size[0] : size;
if (size.length > 0 && (!Number.isInteger(size[0]) || size[0] < 0) || size.length > 1 && (!Number.isInteger(size[size.length - 1]) || size[size.length - 1] < 0)) throw new Error(`Chalkboard.tens.zero: Parameter "size" must begin and end with non-negative integers.`);
const newNDArray = function (size: number[]): ChalkboardTensor {
if (size.length === 0) {
return 0;
}
const curr = size[0];
const rest = size.slice(1);
const result = [];
for (let i = 0; i < curr; i++) {
result[i] = newNDArray(rest);
}
return result;
};
return newNDArray(size);
};
}
}