/*
Chalkboard - Tensor Namespace
Version 3.0.2 Euler
Released April 13th, 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}
*/
export const absolute = (tens: ChalkboardTensor): ChalkboardTensor => {
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}
*/
export const add = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): ChalkboardTensor => {
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}
*/
export const concat = (tens1: ChalkboardTensor, tens2: ChalkboardTensor, rank: number = 1): ChalkboardTensor => {
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}
*/
export const constrain = (tens: ChalkboardTensor, range: [number, number] = [0, 1]): ChalkboardTensor => {
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}
*/
export const contract = (tens: ChalkboardTensor): ChalkboardTensor | number => {
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}
*/
export const copy = (tens: ChalkboardTensor): ChalkboardTensor => {
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}
*/
export const empty = (...size: number[]): ChalkboardTensor => {
size = Array.isArray(size[0]) ? size[0] : size;
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}
*/
export const fill = (element: number, ...size: number[]): ChalkboardTensor => {
size = Array.isArray(size[0]) ? size[0] : size;
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}
*/
export const isApproxEqual = (tens1: ChalkboardTensor, tens2: ChalkboardTensor, precision: number = 0.000001): boolean => {
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}
*/
export const isEqual = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): boolean => {
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}
*/
export const isRankEqual = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): boolean => {
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}
*/
export const isRankOf = (tens: ChalkboardTensor, rank: number): boolean => {
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}
*/
export const isSizeEqual = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): boolean => {
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}
*/
export const isSizeOf = (tens: ChalkboardTensor, ...size: number[]): boolean => {
size = Array.isArray(size[0]) ? size[0] : size;
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}
*/
export const isSizeUniform = (tens: ChalkboardTensor): boolean => {
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}
*/
export const isZero = (tens: ChalkboardTensor): boolean => {
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}
*/
export const mul = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): ChalkboardTensor => {
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}
*/
export const negate = (tens: ChalkboardTensor): ChalkboardTensor => {
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}
*/
export const print = (tens: ChalkboardTensor): void => {
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}
*/
export const pull = (tens: ChalkboardTensor, rank: number, index: number): ChalkboardTensor => {
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}
*/
export const push = (tens: ChalkboardTensor, rank: number, index: number, elements: number[]): ChalkboardTensor => {
tens = tens as ChalkboardTensor[];
if (rank === 0) {
tens.splice(index, 0, elements);
return tens;
} else {
for (let i = 0; i < tens.length; i++) {
Chalkboard.tens.push(tens[i], rank - 1, index, elements[i] as unknown as number[]);
}
return tens;
}
};
/**
* Initializes a random tensor.
* @param {number} inf - The lower bound
* @param {number} sup - The upper bound
* @param {number[]} size - The size
* @returns {ChalkboardTensor}
*/
export const random = (inf: number, sup: number, ...size: number[]): ChalkboardTensor => {
size = Array.isArray(size[0]) ? size[0] : size;
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}
*/
export const rank = (tens: ChalkboardTensor): number => {
return Chalkboard.tens.size(tens).length;
};
/**
* Calculates the reciprocal of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardTensor}
*/
export const reciprocate = (tens: ChalkboardTensor): ChalkboardTensor => {
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}
*/
export const resize = (tens: ChalkboardTensor, ...size: number[]): ChalkboardTensor => {
size = Array.isArray(size[0]) ? size[0] : size;
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}
*/
export const round = (tens: ChalkboardTensor): ChalkboardTensor => {
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}
*/
export const scl = (tens: ChalkboardTensor, num: number): ChalkboardTensor => {
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[]}
*/
export const size = (tens: ChalkboardTensor): number[] => {
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}
*/
export const sub = (tens1: ChalkboardTensor, tens2: ChalkboardTensor): ChalkboardTensor => {
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[]}
*/
export const toArray = (tens: ChalkboardTensor): number[] => {
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}
*/
export const toMatrix = (tens: ChalkboardTensor): ChalkboardMatrix => {
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}
*/
export const toObject = (tens: ChalkboardTensor): object | number => {
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}
*/
export const toSet = (tens: ChalkboardTensor): ChalkboardSet => {
return Chalkboard.abal.set(Chalkboard.tens.toArray(tens));
};
/**
* Converts a tensor to a string.
* @param {ChalkboardTensor} tens - The tensor
* @returns {string}
*/
export const toString = (tens: ChalkboardTensor, indentation: number = 0): string => {
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}
*/
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 TypeError('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}
*/
export const toVector = (tens: ChalkboardTensor, dimension: number, index: number = 0): ChalkboardVector => {
const arr = Chalkboard.tens.toArray(tens);
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 TypeError('Parameter "vect" must be of type "ChalkboardVector" with 2, 3, or 4 dimensions.');
}
};
/**
* Calculates the transpose of a tensor.
* @param {ChalkboardTensor} tens - The tensor
* @returns {ChalkboardTensor}
*/
export const transpose = (tens: ChalkboardTensor): ChalkboardTensor => {
return Chalkboard.tens.resize(tens, ...Chalkboard.tens.size(tens).reverse());
};
/**
* Initializes a zero tensor.
* @param {number[]} size - The size
* @returns {ChalkboardTensor}
*/
export const zero = (...size: number[]): ChalkboardTensor => {
size = Array.isArray(size[0]) ? size[0] : size;
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);
};
}
}