/* Chalkboard - Abstract Algebra 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 abstract algebra namespace. * @namespace */ export namespace abal { /** @ignore */ const $ = JSON.stringify; /** * The set of all even permutations of n elements, denoted as An. * @param {number} n - The number of elements * @returns {ChalkboardSet} */ export const A = (n: number): ChalkboardSet => { if (!Number.isInteger(n) || n <= 0) { throw new Error('The parameter "n" must be a positive integer.'); } const Sn = Chalkboard.abal.S(n); const isEvenPermutation = (perm: number[]): boolean => { let inversions = 0; for (let i = 0; i < perm.length; i++) { for (let j = i + 1; j < perm.length; j++) { if (perm[i] > perm[j]) inversions++; } } return inversions % 2 === 0; }; const elements = (Sn.elements || []).filter(isEvenPermutation); return { contains: (element: number[]) => elements.some((perm) => $(perm) === $(element)), elements: elements, id: `A${n}` }; }; /** * Defines an automorphism of an algebraic structure. * @template T * @param {ChalkboardStructure} struc - The algebraic structure which is both the domain and codomain of the morphism * @param {(element: T) => T} mapping - The bijective function that takes an element from the structure and maps it to another element in the same structure * @returns {ChalkboardMorphism} */ export const automorphism = (struc: ChalkboardStructure, mapping: (element: T) => T): ChalkboardMorphism => { const morphism = Chalkboard.abal.homomorphism(struc, struc, mapping); if (!Chalkboard.abal.isHomomorphism(morphism)) { throw new Error("The mapping is not a homomorphism, so it cannot be an automorphism."); } if (!Chalkboard.abal.isBijective(morphism)) { throw new Error("The mapping is not bijective, so it cannot be an automorphism."); } return morphism; }; /** * The set of all complex numbers, denoted as C, or the set of nth roots of unity when n is provided. * @param {number} [n] - The degree of the roots of unity (optional, returns the set of all complex numbers if omitted) * @returns {ChalkboardSet} */ export const C = (n?: number): ChalkboardSet => { if (n === undefined) { return { contains: (element: ChalkboardComplex) => { return typeof element.a === "number" && typeof element.b === "number"; }, id: "C" }; } else { if (!Number.isInteger(n) || n <= 0) { throw new Error('The parameter "n" must be a positive integer.'); } const elements: ChalkboardComplex[] = []; for (let k = 0; k < n; k++) { const t = (2 * Math.PI * k) / n; elements.push(Chalkboard.comp.init(Chalkboard.numb.roundTo(Math.cos(t), 0.0001), Chalkboard.numb.roundTo(Math.sin(t), 0.0001))); } return { contains: (element: ChalkboardComplex) => elements.some((e) => { return e.a === element.a && e.b === element.b; }), elements: elements, id: `C${n}` }; } }; /** * Calculates the cardinality of a set or algebraic structure. * @template T * @param {ChalkboardSet | ChalkboardStructure} struc - The set or structure * @returns {number} */ export const cardinality = (struc: ChalkboardSet | ChalkboardStructure): number => { const id = "set" in struc && struc.set ? struc.set.id : ("id" in struc ? struc.id : undefined); if (id?.startsWith("M(") || id?.startsWith("GL") || ["Z", "Q", "R", "C", "P"].includes(id || "")) { return Infinity; } if ("elements" in struc && struc.elements) { return struc.elements.length; } if ("set" in struc && struc.set.elements) { return struc.set.elements.length; } throw new Error("The inputted structure does not have a finite cardinality or is missing elements."); }; /** * Calculates the Cartesian product of two sets. * @template T, U * @param {ChalkboardSet} set1 - The first set * @param {ChalkboardSet} set2 - The second set * @returns {ChalkboardSet<[T, U]>} */ export const Cartesian = (set1: ChalkboardSet, set2: ChalkboardSet): ChalkboardSet<[T, U]> => { const result: [T, U][] = []; for (const a of set1.elements || []) { for (const b of set2.elements || []) { result.push([a, b]); } } return Chalkboard.abal.set(result); }; /** * Calculates the Cayley table for an algebraic structure. * @param {ChalkboardStructure} struc - The algebraic structure * @param {"add" | "mul"} [type="add"] - The type of operation to calculate the Cayley table for ("add" for additive operations, "mul" for multiplicative operations, defaults to "add") */ export const Cayley = (struc: ChalkboardStructure, type: "add" | "mul" = "add"): ChalkboardMatrix => { if (!struc.set.elements) { throw new Error("The structure must have a finite set of elements."); } const elements = struc.set.elements; if ("operation" in struc && struc.operation) { if (type === "add") { let result = Chalkboard.matr.fill(0, elements.length); for (let i = 0; i < elements.length; i++) { for (let j = 0; j < elements.length; j++) { result[i][j] = struc.operation(elements[i], elements[j]); } } return result; } throw new Error('The "type" parameter for groups should remain as the default "add" since there is no distinction between their additive and multiplicative Cayley tables.'); } if ("add" in struc && struc.add && "mul" in struc && struc.mul) { if (type === "add") { let result = Chalkboard.matr.fill(0, elements.length); for (let i = 0; i < elements.length; i++) { for (let j = 0; j < elements.length; j++) { result[i][j] = struc.add(elements[i], elements[j]); } } return result; } let result = Chalkboard.matr.fill(0, elements.length); for (let i = 0; i < elements.length; i++) { for (let j = 0; j < elements.length; j++) { result[i][j] = struc.mul(elements[i], elements[j]); } } return result; } throw new Error("Invalid algebraic structure for Cayley table."); }; /** * Calculates the center of a group. * @template T * @param {ChalkboardStructure} group - The group * @returns {ChalkboardSet} */ export const center = (group: ChalkboardStructure): ChalkboardSet => { const { set, operation } = group; if (!set.elements || !operation) { return Chalkboard.abal.set([]); } const result = set.elements.filter((z) => (set.elements ?? []).every((g) => operation(z, g) === operation(g, z)) ); return Chalkboard.abal.set(result); }; /** * Calculates the complement of a set relative to a superset. * @template T * @param {ChalkboardSet} set - The set * @param {ChalkboardSet} superset - The superset * @returns {ChalkboardSet} */ export const complement = (set: ChalkboardSet, superset: ChalkboardSet): ChalkboardSet => { return Chalkboard.abal.set((superset.elements || []).filter((element) => !set.contains(element))); }; /** * Calculates the composition of two morphisms. * @param {ChalkboardMorphism} morph1 - The first morphism * @param {ChalkboardMorphism} morph2 - The second morphism * @returns {ChalkboardMorphism} */ export const compose = (morph1: ChalkboardMorphism, morph2: ChalkboardMorphism): ChalkboardMorphism => { if (!Chalkboard.abal.isHomomorphism(morph1) || !Chalkboard.abal.isHomomorphism(morph2)) { throw new Error("Both morphisms of the morphism composition must be homomorphisms."); } if (!Chalkboard.abal.isEqual(morph1.struc2, morph2.struc1)) { throw new Error("The codomain of the first morphism and the domain of the second morphism must be equal to calculate the composition morphism."); } return Chalkboard.abal.homomorphism(morph1.struc1, morph2.struc2, (x) => morph2.mapping(morph1.mapping(x))); }; /** * Copies a set, structure, structure extension, or morphism. * @template T, U * @param {ChalkboardSet | ChalkboardStructure | ChalkboardStructureExtension | ChalkboardMorphism} struc - The set, structure, structure extension, or morphism * @returns {ChalkboardSet | ChalkboardStructure | ChalkboardStructureExtension | ChalkboardMorphism} */ export const copy = (struc: ChalkboardSet | ChalkboardStructure | ChalkboardStructureExtension | ChalkboardMorphism): ChalkboardSet | ChalkboardStructure | ChalkboardStructureExtension | ChalkboardMorphism => { const isSet = (obj: any): obj is ChalkboardSet => obj && typeof obj.contains === "function" && (!obj.set && !obj.struc1 && !obj.base); const isStructure = (obj: any): obj is ChalkboardStructure => obj && obj.set && (obj.operation || obj.add || obj.mul); const isExtension = (obj: any): obj is ChalkboardStructureExtension => obj && obj.base && obj.extension && typeof obj.degree === "number"; const isMorphism = (obj: any): obj is ChalkboardMorphism => obj && obj.struc1 && obj.struc2 && typeof obj.mapping === "function"; if (isSet(struc)) { const copiedSet: ChalkboardSet = { contains: struc.contains, ...(struc.id && { id: struc.id }), ...(struc.elements && { elements: [...struc.elements] }) }; return copiedSet; } if (isStructure(struc)) { const copiedSet = Chalkboard.abal.copy(struc.set) as ChalkboardSet; const copiedStructure: ChalkboardStructure = { set: copiedSet, ...(struc.operation && { operation: struc.operation }), ...(struc.identity !== undefined && { identity: struc.identity }), ...(struc.inverter && { inverter: struc.inverter }), ...(struc.add && { add: struc.add }), ...(struc.mul && { mul: struc.mul }), ...(struc.addIdentity !== undefined && { addIdentity: struc.addIdentity }), ...(struc.mulIdentity !== undefined && { mulIdentity: struc.mulIdentity }), ...(struc.addInverter && { addInverter: struc.addInverter }), ...(struc.mulInverter && { mulInverter: struc.mulInverter }) }; return copiedStructure; } if (isExtension(struc)) { const copiedBase = Chalkboard.abal.copy(struc.base) as ChalkboardStructure; const copiedExtension = Chalkboard.abal.copy(struc.extension) as ChalkboardStructure; const copiedExtensionStructure: ChalkboardStructureExtension = { base: copiedBase, extension: copiedExtension, degree: struc.degree, basis: struc.basis ? [...struc.basis] : [], isFinite: struc.isFinite, isSimple: struc.isSimple, isAlgebraic: struc.isAlgebraic }; return copiedExtensionStructure; } if (isMorphism(struc)) { const copiedStruc1 = Chalkboard.abal.copy(struc.struc1) as ChalkboardStructure; const copiedStruc2 = Chalkboard.abal.copy(struc.struc2) as ChalkboardStructure; const copiedMorphism: ChalkboardMorphism = { struc1: copiedStruc1, struc2: copiedStruc2, mapping: struc.mapping }; return copiedMorphism; } throw new Error('The "struc" must be a set, structure, structure extension, or morphism.'); }; /** * Calculates the cosets of a subgroup or ideal with respect to a group or ring. * @template T * @param {ChalkboardStructure} struc - The group or ring * @param {ChalkboardStructure} substruc - The subgroup or ideal * @returns {ChalkboardSet>} */ export const coset = (struc: ChalkboardStructure, substruc: ChalkboardStructure): ChalkboardSet> => { if ("operation" in struc && !Chalkboard.abal.isSubgroup(struc, substruc.set)) { throw new Error('The "substruc" must be a subgroup of the "struc".'); } else if ("add" in struc && !Chalkboard.abal.isIdeal(struc, substruc.set)) { throw new Error('The "substruc" must be an ideal of the "struc".'); } const elements = Chalkboard.abal.toArray(struc.set); const subElements = Chalkboard.abal.toArray(substruc.set); const cosets = new Map>(); elements.forEach((g) => { const cosetElements = subElements.map(h => "operation" in struc ? struc.operation!(g, h) : struc.add!(g, h) ); const sortedElements = [...cosetElements].sort((a, b) => { if (typeof a === "number" && typeof b === "number") { return a - b; } return $(a).localeCompare($(b)); }); const key = $(sortedElements); if (!cosets.has(key)) { const coset = Chalkboard.abal.set(cosetElements); cosets.set(key, coset); } }); return Chalkboard.abal.set(Array.from(cosets.values())); }; /** * Generates the cyclic subgroup of an element in a group. * @template T * @param {ChalkboardStructure} group - The group * @param {T} element - The generator of the cyclic subgroup * @returns {ChalkboardSet} */ export const cyclicSubgroup = (group: ChalkboardStructure, element: T): ChalkboardSet => { if (group.set.id && ["Z", "Q", "R", "C"].includes(group.set.id)) { throw new Error('The "group" must be finite.'); } const result: T[] = []; let current = element; if (!group.operation) { return Chalkboard.abal.set([]); } do { result.push(current); current = group.operation(current, element); } while (!result.includes(current)); return Chalkboard.abal.set(result); }; /** * The set of all rotations and reflections of a polygon with n sides, denoted as Dn. * @param {number} n - The number of sides of the polygon * @returns {ChalkboardSet} */ export const D = (n: number): ChalkboardSet => { if (!Number.isInteger(n) || n <= 0) { throw new Error('The parameter "n" must be a positive integer.'); } const elements: string[] = []; for (let i = 0; i < n; i++) { elements.push(`r${i}`); } for (let i = 0; i < n; i++) { elements.push(`s${i}`); } return { contains: (element: string) => elements.includes(element), elements: elements, id: `D${n}` }; }; /** * Calculates the difference of two sets. * @template T * @param {ChalkboardSet} set1 - The first set * @param {ChalkboardSet} set2 - The second set * @returns {ChalkboardSet} */ export const difference = (set1: ChalkboardSet, set2: ChalkboardSet): ChalkboardSet => { const result = (set1.elements || []).filter((element) => !set2.contains(element)); return Chalkboard.abal.set(result); }; /** * Calculates the direct product or direct sum of two algebraic structures. * @template T, U * @param {ChalkboardStructure} struc1 - The first structure * @param {ChalkboardStructure} struc2 - The second structure * @param {"product" | "sum"} [type="product"] - The type of direct operation ("product" or "sum", defaults to "product"). * @returns {ChalkboardStructure<[T, U]>} */ export const direct = (struc1: ChalkboardStructure, struc2: ChalkboardStructure, type: "product" | "sum" = "product"): ChalkboardStructure<[T, U]> => { const set = Chalkboard.abal.Cartesian(struc1.set, struc2.set); const add = (a: [T, U], b: [T, U]): [T, U] => [ (struc1 as any).add(a[0], b[0]), (struc2 as any).add(a[1], b[1]) ]; const mul = (a: [T, U], b: [T, U]): [T, U] => [ (struc1 as any).mul(a[0], b[0]), (struc2 as any).mul(a[1], b[1]) ]; const addIdentity: [T, U] = [ (struc1 as any).addIdentity, (struc2 as any).addIdentity ]; const mulIdentity: [T, U] = [ (struc1 as any).mulIdentity, (struc2 as any).mulIdentity ]; const addInverter = (a: [T, U]): [T, U] => [ (struc1 as any).addInverter(a[0]), (struc2 as any).addInverter(a[1]) ]; const mulInverter = (a: [T, U]): [T, U] => [ (struc1 as any).mulInverter(a[0]), (struc2 as any).mulInverter(a[1]) ]; if ("operation" in struc1 && "operation" in struc2) { const operation = (a: [T, U], b: [T, U]): [T, U] => [ (struc1.operation as (x: T, y: T) => T)(a[0], b[0]), (struc2.operation as (x: U, y: U) => U)(a[1], b[1]) ]; const identity: [T, U] = [struc1.identity as T, struc2.identity as U]; if ("inverter" in struc1 && "inverter" in struc2) { const inverter = (a: [T, U]): [T, U] => [ (struc1.inverter as (x: T) => T)(a[0]), (struc2.inverter as (x: U) => U)(a[1]) ]; if (type === "sum") { if (!struc1.set.elements || !struc2.set.elements) { throw new Error("Direct sum is only defined for finite groups."); } } return Chalkboard.abal.group(set, operation, identity, inverter); } if (type === "sum") { if (!struc1.set.elements || !struc2.set.elements) { throw new Error("Direct sum is only defined for finite structures."); } } return Chalkboard.abal.monoid(set, operation, identity); } if ("add" in struc1 && "add" in struc2 && "mul" in struc1 && "mul" in struc2) { if (type === "sum") { if (!struc1.set.elements || !struc2.set.elements) { throw new Error("Direct sum is only defined for finite rings."); } } return Chalkboard.abal.ring(set, add, mul, addIdentity, mulIdentity, addInverter); } if ("add" in struc1 && "add" in struc2 && "mul" in struc1 && "mul" in struc2 && "mulInverter" in struc1 && "mulInverter" in struc2) { if (type === "sum") { if (!struc1.set.elements || !struc2.set.elements) { throw new Error("Direct sum is only defined for finite fields."); } } return Chalkboard.abal.field(set, add, mul, addIdentity, mulIdentity, addInverter, mulInverter); } throw new Error("Invalid algebraic structures for direct product or sum."); }; /** * Defines an endomorphism of an algebraic structure. * @template T * @param {ChalkboardStructure} struc - The algebraic structure which is both the domain and codomain of the morphism * @param {(element: T) => T} mapping - The function that takes an element from the structure and maps it to another element in the same structure * @returns {ChalkboardMorphism} */ export const endomorphism = (struc: ChalkboardStructure, mapping: (element: T) => T): ChalkboardMorphism => { const morphism = Chalkboard.abal.homomorphism(struc, struc, mapping); if (!Chalkboard.abal.isHomomorphism(morphism)) { throw new Error("The mapping is not a homomorphism, so it cannot be an endomorphism."); } return morphism; }; /** * Defines an algebraic structure known as a field. * @template T * @param {ChalkboardSet} set - The set of the field * @param {(a: T, b: T) => T} add - The additive operation of the field * @param {(a: T, b: T) => T} mul - The multiplicative operation of the field * @param {T} [addIdentity] - The additive identity element of the field * @param {T} [mulIdentity] - The multiplicative identity element of the field * @param {(a: T) => T} [addInverter] - The function to calculate the additive inverse of an element of the field (optional if the set is Z, Q, R, C, or M) * @param {(a: T) => T} [mulInverter] - The function to calculate the multiplicative inverse of an element of the field (optional if the set is Z, Q, R, C, or M) * @returns {ChalkboardStructure} */ export const field = (set: ChalkboardSet, add: (a: T, b: T) => T, mul: (a: T, b: T) => T, addIdentity?: T, mulIdentity?: T, addInverter?: (a: T) => T, mulInverter?: (a: T) => T): ChalkboardStructure => { const autoconfig = (): { addIdentity: T; mulIdentity: T; addInverter: (a: T) => T; mulInverter: (a: T) => T } => { if (!set.id) { throw new Error('The "set" must have a valid "id" property, or you must input "addIdentity", "mulIdentity", "addInverter", and "mulInverter" explicitly.'); } if (set.id === "Q" || set.id === "R") { return { addIdentity: 0 as T, mulIdentity: 1 as T, addInverter: (a: T) => (-a as unknown) as T, mulInverter: (a: T) => (1 / (a as unknown as number)) as T }; } else if (set.id === "C") { return { addIdentity: Chalkboard.comp.init(0, 0) as T, mulIdentity: Chalkboard.comp.init(1, 0) as T, addInverter: (a: T) => Chalkboard.comp.negate(a as unknown as ChalkboardComplex) as T, mulInverter: (a: T) => Chalkboard.comp.invert(a as unknown as ChalkboardComplex) as T }; } throw new Error('Automatic configuration of the "addIdentity", "mulIdentity", "addInverter", and "mulInverter" properties is not available for the inputted "set".'); }; const configured = typeof addIdentity === "undefined" || typeof mulIdentity === "undefined" || typeof addInverter === "undefined" || typeof mulInverter === "undefined" ? autoconfig() : { addIdentity, mulIdentity, addInverter, mulInverter }; const field: ChalkboardStructure = { set, add, mul, addIdentity: configured.addIdentity, mulIdentity: configured.mulIdentity, addInverter: configured.addInverter, mulInverter: configured.mulInverter }; if (!Chalkboard.abal.isField(field)) { throw new Error('The inputted "set", "add", "mul", "addIdentity", "mulIdentity", "addInverter", and "mulInverter" do not form a field.'); } return field; }; /** * Defines an algebraic structure extension known as a field extension. * @template T, U * @property {ChalkboardStructure} base - The algebraic structure which is a substructure of the extension structure * @property {ChalkboardStructure} extension - The algebraic structure which is an extension of the base structure * @property {number} [degree] - The dimension of the extension structure as a vector space over the base structure (optional for extensions of Q, R, or C) * @property {ChalkboardVector[]} [basis] - The basis vectors of the extension structure (optional for extensions of Q, R, or C) * @property {boolean} [isFinite] - Whether the extension structure is finite or not (optional for extensions of Q, R, or C) * @property {boolean} [isSimple] - Whether the extension structure is simple or not (optional for extensions of Q, R, or C) * @property {boolean} [isAlgebraic] - Whether the extension structure is algebraic or not (optional for extensions of Q, R, or C) * @returns {ChalkboardStructureExtension} */ export const fieldExtension = (base: ChalkboardStructure, extension: ChalkboardStructure, degree: number, basis: ChalkboardVector[], isFinite: boolean, isSimple: boolean, isAlgebraic: boolean): ChalkboardStructureExtension => { if (!Chalkboard.abal.isSubfield(base as ChalkboardStructure, extension.set as ChalkboardSet)) { throw new Error('The "base" must be a subfield of the "extension".'); } const autoconfig = (): { degree: number; basis: ChalkboardVector[]; isFinite: boolean; isSimple: boolean; isAlgebraic: boolean } => { if (!base.set.id) { throw new Error('The "set" property of the "base" must have a valid "id" property, or you must input "degree", "basis", "isFinite", "isSimple", and "isAlgebraic" explicitly.'); } if (base.set.id === "Q" && extension.set.id === "R") { return { degree: Infinity, basis: [], isFinite: false, isSimple: false, isAlgebraic: false }; } else if (base.set.id === "R" && extension.set.id === "C") { return { degree: 2, basis: [Chalkboard.vect.init(1, 0), Chalkboard.vect.init(0, 1)], isFinite: true, isSimple: true, isAlgebraic: true }; } throw new Error('Automatic configuration of the "degree", "basis", "isFinite", "isSimple", and "isAlgebraic" properties is not available for the inputted "base".'); }; const configured = typeof degree === "undefined" || typeof basis === "undefined" || typeof isFinite === "undefined" || typeof isSimple === "undefined" || typeof isAlgebraic === "undefined" ? autoconfig() : { degree, basis, isFinite, isSimple, isAlgebraic }; return { base, extension, degree: configured.degree, basis: configured.basis, isFinite: configured.isFinite, isSimple: configured.isSimple, isAlgebraic: configured.isAlgebraic }; }; /** * The set of all real-valued invertible matrices of size "n" x "n", denoted as GLn. * @param {number} n - The number of rows/columns * @returns {ChalkboardSet} */ export const GL = (n: number): ChalkboardSet => ({ contains: (element: ChalkboardMatrix) => { return Array.isArray(element) && Chalkboard.matr.isSizeOf(element, n) && Chalkboard.matr.isInvertible(element); }, id: `GL${n}` }); /** * Defines an algebraic structure known as a group. * @template T * @param {ChalkboardSet} set - The set of the group * @param {(a: T, b: T) => T} operation - The operation of the group * @param {T} [identity] - The identity element of the group (optional if the set is Z, Q, R, C, A, S, M, or GL) * @param {(a: T) => T} [inverter] - The function to calculate the inverse of an element of the group (optional if the set is Z, Q, R, C, M, A, S, or GL) * @returns {ChalkboardStructure} */ export const group = (set: ChalkboardSet, operation: (a: T, b: T) => T, identity?: T, inverter?: (a: T) => T): ChalkboardStructure => { const autoconfig = (): { identity: T; inverter: (a: T) => T } => { if (!set.id) { throw new Error('The "set" must have a valid "id" property, or you must input "identity" and "inverter" explicitly.'); } if (set.id === "Z" || set.id === "Q" || set.id === "R") { return { identity: 0 as T, inverter: (a: T) => (-a as unknown) as T }; } else if (set.id === "C") { return { identity: Chalkboard.comp.init(0, 0) as T, inverter: (a: T) => Chalkboard.comp.negate(a as unknown as ChalkboardComplex) as T }; } else if (set.id.startsWith("Z") && set.id.length > 1) { const n = parseInt(set.id.slice(1), 10); return { identity: 0 as T, inverter: (a: T) => ((n - (a as unknown as number) % n) % n) as T }; } else if (set.id.startsWith("C") && set.id.length > 1) { return { identity: Chalkboard.comp.init(1, 0) as T, inverter: (a: T) => Chalkboard.comp.conjugate(a as unknown as ChalkboardComplex) as T }; } else if (set.id.startsWith("M(")) { const rows = (set as any).rows; const cols = (set as any).cols; return { identity: Chalkboard.matr.fill(0, rows, cols) as T, inverter: (a: T) => Chalkboard.matr.negate(a as unknown as ChalkboardMatrix) as T }; } else if (set.id.startsWith("GL")) { const n = parseInt(set.id.slice(2), 10); return { identity: Chalkboard.matr.identity(n) as T, inverter: (a: T) => Chalkboard.matr.invert(a as unknown as ChalkboardMatrix) as T }; } else if (set.id.match(/^[SA]\d+$/)) { const n = parseInt(set.id.slice(1), 10); return { identity: Array.from({length: n}, (_, i) => i) as T, inverter: (a: T) => { const perm = a as unknown as number[]; const inverse = new Array(perm.length); for (let i = 0; i < perm.length; i++) inverse[perm[i]] = i; return inverse as T; } }; } throw new Error('Automatic configuration of the "identity" and "inverter" properties is not available for the inputted "set".'); }; const configured = typeof identity === "undefined" || typeof inverter === "undefined" ? autoconfig() : { identity, inverter: inverter }; const group: ChalkboardStructure = { set, operation, identity: configured.identity, inverter: configured.inverter }; if (!Chalkboard.abal.isGroup(group)) { throw new Error('The inputted "set", "operation", "identity", and "inverter" do not form a group.'); } return group; }; /** * Defines a homomorphism between two algebraic structures. * @template T, U * @param {ChalkboardStructure} struc1 - The first algebraic structure which is the domain of the morphism * @param {ChalkboardStructure} struc2 - The second algebraic structure which is the codomain of the morphism * @param {(element: T) => U} mapping - The function that takes an element from the first structure and maps it to the second structure * @returns {ChalkboardMorphism} */ export const homomorphism = (struc1: ChalkboardStructure, struc2: ChalkboardStructure, mapping: (element: T) => U): ChalkboardMorphism => { const morphism: ChalkboardMorphism = { struc1, struc2, mapping }; if (!Chalkboard.abal.isHomomorphism(morphism)) { throw new Error('The inputted "struc1", "struc2", and "mapping" do not form a homomorphism.'); } return morphism; }; /** * Defines the identity morphism for an algebraic structure. * @template T * @param {ChalkboardStructure} struc - The algebraic structure which is the domain and codomain of the morphism * @returns {ChalkboardMorphism} */ export const idmorphism = (struc: ChalkboardStructure): ChalkboardMorphism => { return Chalkboard.abal.automorphism(struc, (x) => x); }; /** * Calculates the image of a morphism, either for its domain or for a subset of its domain. * @template T, U * @param {ChalkboardMorphism} morph - The morphism * @param {ChalkboardSet} [subset] - The subset of the domain (optional, defaults to the domain) * @returns {ChalkboardSet} */ export const image = (morph: ChalkboardMorphism, subset?: ChalkboardSet): ChalkboardSet => { const { struc1, mapping } = morph; if (!struc1.set.elements) { throw new Error('The domain of the "morph" must have a finite set of elements to calculate the image.'); } const _subset = subset || struc1.set; if (!_subset.elements) { throw new Error('The domain of the "morph" or the subset of it must have a finite set of elements to calculate the image.'); } const mapped = _subset.elements.map(mapping); const result = Array.from(new Set(mapped.map((e) => $(e)))).map((e) => JSON.parse(e)); return Chalkboard.abal.set(result); }; /** * Calculates the intersection of two sets. * @template T * @param {ChalkboardSet} set1 - The first set * @param {ChalkboardSet} set2 - The second set * @returns {ChalkboardSet} */ export const intersection = (set1: ChalkboardSet, set2: ChalkboardSet): ChalkboardSet => { const result = (set1.elements || []).filter((element) => set2.contains(element)); return Chalkboard.abal.set(result); }; /** * Defines the inverse morphism for an isomorphism (which is the only type of morphism that is invertible). * @template T, U * @param {ChalkboardMorphism} morph - The isomorphism * @returns {ChalkboardMorphism} */ export const invmorphism = (morph: ChalkboardMorphism): ChalkboardMorphism => { if (morph.struc1.set.id && ["Z", "Q", "R", "C"].includes(morph.struc1.set.id)) { throw new Error('Inverse morphisms cannot be defined for morphisms with infinite domains.'); } if (!Chalkboard.abal.isIsomorphism(morph)) { throw new Error("The morphism is not an isomorphism, so it does not have an inverse."); } return Chalkboard.abal.homomorphism(morph.struc2, morph.struc1, (y: U) => { const domain = morph.struc1.set.elements || []; for (const x of domain) { if ($(morph.mapping(x)) === $(y)) { return x; } } throw new Error(`The inverse morphism failed to be defined because no element in the domain maps to the element "${$(y)}" in the codomain.`); }); }; /** * Checks if a morphism is an automorphism. * @template T * @param {ChalkboardMorphism} morph - The morphism * @returns {boolean} */ export const isAutomorphism = (morph: ChalkboardMorphism): boolean => { return Chalkboard.abal.isHomomorphism(morph) && Chalkboard.abal.isEndomorphism(morph) && Chalkboard.abal.isIsomorphism(morph); }; /** * Checks if a morphism is bijective (i.e. both injective and surjective). * @template T, U * @param {ChalkboardMorphism} morph - The morphism * @returns {boolean} */ export const isBijective = (morph: ChalkboardMorphism): boolean => { if (["Z", "Q", "R", "C"].includes(morph.struc1.set.id || "") || ["Z", "Q", "R", "C"].includes(morph.struc2.set.id || "")) { return morph.struc1.set.id === morph.struc2.set.id; } return Chalkboard.abal.isInjective(morph) && Chalkboard.abal.isSurjective(morph); }; /** * Checks if a set is closed under an operation. * @template T * @param {ChalkboardSet} set - The set * @param {(a: T, b: T) => T} operation - The operation * @returns {boolean} */ export const isClosed = (set: ChalkboardSet, operation: (a: T, b: T) => T): boolean => { if (set.id && ["Z", "Q", "R", "C"].includes(set.id)) { return true; } if (set.id?.startsWith("M(")) { if (operation === Chalkboard.matr.add as unknown as (a: T, b: T) => T) { return true; } if (operation === Chalkboard.matr.mul as unknown as (a: T, b: T) => T) { const dimensions = set.id.match(/\d+/g)?.map(Number); if (dimensions && dimensions.length >= 2) { return dimensions[0] === dimensions[1]; } } return false; } if (set.id === "C") { if (operation === Chalkboard.comp.add as unknown as (a: T, b: T) => T || operation === Chalkboard.comp.mul as unknown as (a: T, b: T) => T) { return true; } return false; } if (typeof set === "object" && "elements" in set && set.elements) { for (const a of set.elements) { for (const b of set.elements) { const result = operation(a, b); if (!set.contains(result)) { return false; } } } return true; } return true; }; /** * Checks if an algebraic structure is commutative. * @template T * @param {ChalkboardStructure} struc - The algebraic structure * @returns {boolean} */ export const isCommutative = (struc: ChalkboardStructure): boolean => { const { set } = struc; if (set.id && ["Z", "Q", "R", "C"].includes(set.id)) { return true; } if (!set.elements) { return false; } if ("operation" in struc && struc.operation) { const { operation } = struc; for (const a of set.elements) { for (const b of set.elements) { if ($(operation(a, b)) !== $(operation(b, a))) { return false; } } } return true; } if ("add" in struc && "mul" in struc && struc.add && struc.mul) { const { add, mul } = struc; for (const a of set.elements) { for (const b of set.elements) { if ($(add(a, b)) !== $(add(b, a))) { return false; } } } if ("mulIdentity" in struc) { for (const a of set.elements) { for (const b of set.elements) { if ($(mul(a, b)) !== $(mul(b, a))) { return false; } } } } return true; } return false; }; /** * Checks if a subgroup of a group is a cyclic subgroup. * @template T * @param {ChalkboardStructure} group - The group * @param {ChalkboardSet} subgroup - The subgroup * @returns {boolean} */ export const isCyclicSubgroup = (group: ChalkboardStructure, subgroup: ChalkboardSet): boolean => { if (!Chalkboard.abal.isSubgroup(group, subgroup) || !group.operation) { return false; } const { operation } = group; for (const generator of subgroup.elements || []) { const generatedElements: T[] = []; let current = generator; do { generatedElements.push(current); current = operation(current, generator); } while (!generatedElements.includes(current)); const generatedSet = Chalkboard.abal.set(generatedElements); if (Chalkboard.abal.isSubset(subgroup, generatedSet)) { return true; } } return false; }; /** * Checks if a set or algebraic structure is empty. * @template T * @param {ChalkboardSet | ChalkboardStructure} struc - The structure to check * @returns {boolean} */ export const isEmpty = (struc: ChalkboardSet | ChalkboardStructure): boolean => { const id = "set" in struc && struc.set ? struc.set.id : ("id" in struc ? struc.id : undefined); if (id === "Z" || id === "Q" || id === "R" || id === "C" || id?.startsWith("M(")) { return false; } if ("elements" in struc && struc.elements) { return struc.elements.length === 0; } if ("set" in struc && struc.set.elements) { return struc.set.elements.length === 0; } return true; }; /** * Checks if a morphism is an endomorphism. * @template T * @param {ChalkboardMorphism} morph - The morphism * @returns {boolean} */ export const isEndomorphism = (morph: ChalkboardMorphism): boolean => { return Chalkboard.abal.isHomomorphism(morph) && Chalkboard.abal.isEqual(morph.struc1, morph.struc2); }; /** * Checks if two sets, algebraic structures, or morphisms are equal. * @template T, U * @param {ChalkboardSet | ChalkboardStructure | ChalkboardMorphism} struc1 - The first set, structure, or morphism * @param {ChalkboardSet | ChalkboardStructure | ChalkboardMorphism} struc2 - The second set, structure, or morphism * @returns {boolean} */ export const isEqual = (struc1: ChalkboardSet | ChalkboardStructure | ChalkboardMorphism, struc2: ChalkboardSet | ChalkboardStructure | ChalkboardMorphism): boolean => { if (struc1.constructor !== struc2.constructor) { return false; } if ("elements" in struc1 && "elements" in struc2) { if ("id" in struc1 && "id" in struc2 && struc1.id === struc2.id) { return true; } const set1 = struc1.elements || []; const set2 = struc2.elements || []; if (set1.length !== set2.length) { return false; } return set1.every((x) => struc2.contains(x)) && set2.every((x) => struc1.contains(x)); } if ("operation" in struc1 && "operation" in struc2) { const monoiroup1 = struc1 as ChalkboardStructure; const monoiroup2 = struc2 as ChalkboardStructure; const monoidEqual = Chalkboard.abal.isEqual(monoiroup1.set, monoiroup2.set) && monoiroup1.identity === monoiroup2.identity && ( monoiroup1.operation === monoiroup2.operation || (monoiroup1.operation as (x: T, y: T) => T).toString() === (monoiroup2.operation as (x: T, y: T) => T).toString() ); if ("inverter" in monoiroup1 && "inverter" in monoiroup2) { return monoidEqual && ( monoiroup1.inverter === monoiroup2.inverter || (monoiroup1.inverter as (x: T) => T).toString() === (monoiroup2.inverter as (x: T) => T).toString() ); } if (("inverter" in monoiroup1) !== ("inverter" in monoiroup2)) { return false; } return monoidEqual; } if ("add" in struc1 && "add" in struc2 && "mul" in struc1 && "mul" in struc2) { const ring1 = struc1 as ChalkboardStructure; const ring2 = struc2 as ChalkboardStructure; return ( Chalkboard.abal.isEqual(ring1.set, ring2.set) && ring1.addIdentity === ring2.addIdentity && ring1.mulIdentity === ring2.mulIdentity && ( ring1.add === ring2.add || (ring1.add as (x: T, y: T) => T).toString() === (ring2.add as (x: T, y: T) => T).toString() ) && ( ring1.mul === ring2.mul || (ring1.mul as (x: T, y: T) => T).toString() === (ring2.mul as (x: T, y: T) => T).toString() ) && ( ring1.addInverter === ring2.addInverter || (ring1.addInverter as (x: T, y: T) => T).toString() === (ring2.addInverter as (x: T, y: T) => T).toString() ) ); } if ("mulInverter" in struc1 && "mulInverter" in struc2) { const field1 = struc1 as ChalkboardStructure; const field2 = struc2 as ChalkboardStructure; return ( Chalkboard.abal.isEqual(field1.set, field2.set) && field1.addIdentity === field2.addIdentity && field1.mulIdentity === field2.mulIdentity && ( field1.add === field2.add || (field1.add as (x: T, y: T) => T).toString() === (field2.add as (x: T, y: T) => T).toString() ) && ( field1.mul === field2.mul || (field1.mul as (x: T, y: T) => T).toString() === (field2.mul as (x: T, y: T) => T).toString() ) && ( field1.addInverter === field2.addInverter || (field1.addInverter as (x: T, y: T) => T).toString() === (field2.addInverter as (x: T, y: T) => T).toString() ) && ( field1.mulInverter === field2.mulInverter || (field1.mulInverter as (x: T, y: T) => T).toString() === (field2.mulInverter as (x: T, y: T) => T).toString() ) ); } if ("mapping" in struc1 && "mapping" in struc2) { const morph1 = struc1 as ChalkboardMorphism; const morph2 = struc2 as ChalkboardMorphism; return ( Chalkboard.abal.isEqual(morph1.struc1, morph2.struc1) && Chalkboard.abal.isEqual(morph1.struc2, morph2.struc2) && ( morph1.mapping === morph2.mapping || morph1.mapping.toString() === morph2.mapping.toString() || ( Chalkboard.abal.isEqual(Chalkboard.abal.image(morph1), Chalkboard.abal.image(morph2)) && Chalkboard.abal.isEqual(Chalkboard.abal.preimage(morph1), Chalkboard.abal.preimage(morph2)) && Chalkboard.abal.isEqual(Chalkboard.abal.kernel(morph1), Chalkboard.abal.kernel(morph2)) ) ) ); } return false; }; /** * Checks if two morphisms are exact (i.e. the image of the first morphism is equal to the kernel of the second morphism). * @template T, U, V * @param {ChalkboardMorphism} morph1 - The first morphism * @param {ChalkboardMorphism} morph2 - The second morphism * @returns {boolean} */ export const isExact = (morph1: ChalkboardMorphism, morph2: ChalkboardMorphism): boolean => { return Chalkboard.abal.isEqual(Chalkboard.abal.image(morph1), Chalkboard.abal.kernel(morph2)); }; /** * Checks if an algebraic structure is a field. * @template T * @param {ChalkboardStructure} field - The field * @returns {boolean} */ export const isField = (field: ChalkboardStructure): boolean => { const { set, add, mul, addIdentity, mulIdentity, addInverter, mulInverter } = field; if (set.id === "Q" || set.id === "R" || set.id === "C") { return true; } if (typeof add === "undefined" || typeof mul === "undefined" || typeof addIdentity === "undefined" || typeof mulIdentity === "undefined" || typeof addInverter === "undefined" || typeof mulInverter === "undefined") { return false; } const additiveGroup: ChalkboardStructure = { set, operation: add, identity: addIdentity, inverter: addInverter }; if (!Chalkboard.abal.isGroup(additiveGroup) || !Chalkboard.abal.isCommutative(additiveGroup)) { return false; } if (!Chalkboard.abal.isClosed(set, mul)) { return false; } for (const a of set.elements || []) { for (const b of set.elements || []) { for (const c of set.elements || []) { if ($(mul(mul(a, b), c)) !== $(mul(a, mul(b, c)))) { return false; } } } } for (const a of set.elements || []) { if (a !== addIdentity && (!set.contains(mulInverter(a)) || $(mul(a, mulInverter(a))) !== $(mulIdentity))) { return false; } } if (!Chalkboard.abal.isCommutative(field)) { return false; } for (const a of field.set.elements || []) { for (const b of field.set.elements || []) { for (const c of field.set.elements || []) { if ($(field.mul!(a, field.add!(b, c))) !== $(field.add!(field.mul!(a, b), field.mul!(a, c)))) { return false; } } } } return true; }; /** * Checks if an algebraic structure is a group. * @template T * @param {ChalkboardStructure} group - The group * @returns {boolean} */ export const isGroup = (group: ChalkboardStructure): boolean => { const { set, operation, identity, inverter } = group; if (set.id === "Z" || set.id === "Q" || set.id === "R" || set.id === "C" || set.id === "GL") { return true; } if (typeof set.elements === "undefined") { return false; } if (typeof operation === "undefined" || typeof identity === "undefined" || typeof inverter === "undefined") { return false; } if (!Chalkboard.abal.isClosed(set, operation)) { return false; } for (const a of set.elements) { if ($(operation(a, identity)) !== $(a) || $(operation(identity, a)) !== $(a)) { return false; } } for (const a of set.elements) { if (!set.contains(inverter(a)) || $(operation(a, inverter!(a))) !== $(identity)) { return false; } } for (const a of set.elements) { for (const b of set.elements) { for (const c of set.elements) { if ($(operation(operation(a, b), c)) !== $(operation(a, operation(b, c)))) { return false; } } } } return true; }; /** * Checks if a morphism is a homomorphism. * @template T, U * @param {ChalkboardMorphism} morph - The homomorphism * @returns {boolean} */ export const isHomomorphism = (morph: ChalkboardMorphism): boolean => { const { struc1, struc2, mapping } = morph; if ("operation" in struc1 && "operation" in struc2 && struc1.operation && struc2.operation) { const { operation: op1 } = struc1; const { operation: op2 } = struc2; for (const a of struc1.set.elements || []) { for (const b of struc1.set.elements || []) { if ($(op2(mapping(a), mapping(b))) !== $(mapping(op1(a, b)))) { return false; } } } return true; } if ("add" in struc1 && "add" in struc2 && "mul" in struc1 && "mul" in struc2 && struc1.add && struc2.add && struc1.mul && struc2.mul) { const { add: add1, mul: mul1 } = struc1; const { add: add2, mul: mul2 } = struc2; for (const a of struc1.set.elements || []) { for (const b of struc1.set.elements || []) { if ($(add2(mapping(a), mapping(b))) !== $(mapping(add1(a, b)))) { return false; } if ($(mul2(mapping(a), mapping(b))) !== $(mapping(mul1(a, b)))) { return false; } } } return true; } throw new Error("The algebraic structures of the homomorphism may have missing operations or incompatible types."); }; /** * Checks if a subset is an ideal of a ring. * @template T * @param {ChalkboardStructure} ring - The ring * @param {ChalkboardSet} subset - The subset * @returns {boolean} */ export const isIdeal = (ring: ChalkboardStructure, subset: ChalkboardSet): boolean => { const { add, mul, addIdentity, addInverter } = ring; if (typeof add === "undefined" || typeof mul === "undefined" || typeof addIdentity === "undefined" || typeof addInverter === "undefined") { return false; } if (!Chalkboard.abal.isClosed(subset, add)) { return false; } if (!subset.contains(addIdentity)) { return false; } for (const a of subset.elements || []) { if (!subset.contains(addInverter(a))) { return false; } } for (const r of ring.set.elements || []) { for (const a of subset.elements || []) { if (!subset.contains(mul(r, a)) || !subset.contains(mul(a, r))) { return false; } } } return true; }; /** * Checks if an element is the identity of an algebraic structure. * @template T * @param {ChalkboardStructure} struc - The algebraic structure * @param {T} element - The element * @param {"add" | "mul"} [type="add"] - The type of identity to check ("add" for additive identity, "mul" for multiplicative identity, defaults to "add") * @returns {boolean} */ export const isIdentity = (struc: ChalkboardStructure, element: T, type: "add" | "mul" = "add"): boolean => { if (type === "add" && struc.add && struc.addIdentity) { return ( "add" in struc && struc.add(element, struc.addIdentity) === element && struc.add(struc.addIdentity, element) === element ); } else if (type === "mul" && struc.mul && struc.mulIdentity) { return ( "mul" in struc && "mulIdentity" in struc && struc.mul?.(element, struc.mulIdentity) === element && struc.mul?.(struc.mulIdentity, element) === element ); } return false; }; /** * Checks if a morphism is injective. * @template T, U * @param {ChalkboardMorphism} morph - The morphism * @returns {boolean} */ export const isInjective = (morph: ChalkboardMorphism): boolean => { if (["Z", "Q", "R", "C"].includes(morph.struc1.set.id || "") || ["Z", "Q", "R", "C"].includes(morph.struc2.set.id || "")) { return morph.struc1.set.id === morph.struc2.set.id; } const { struc1, mapping } = morph; const domain = struc1.set.elements || []; const mapped = domain.map(mapping); return new Set(mapped.map((e) => $(e))).size === domain.length; }; /** * Checks if two elements are inverses of each other in an algebraic structure. * @template T * @param {ChalkboardStructure} struc - The algebraic structure * @param {T} element1 - The first element * @param {T} element2 - The second element * @param {"add" | "mul"} [type="add"] - The type of inverse to check ("add" for additive inverse, "mul" for multiplicative inverse, defaults to "add") * @returns {boolean} */ export const isInverse = (struc: ChalkboardStructure, element1: T, element2: T, type: "add" | "mul" = "add"): boolean => { if (type === "add") { return ( "add" in struc && struc.add?.(element1, element2) === struc.addIdentity && struc.add?.(element2, element1) === struc.addIdentity ); } else if (type === "mul" && "mul" in struc && "mulIdentity" in struc) { return ( struc.mul?.(element1, element2) === struc.mulIdentity && struc.mul?.(element2, element1) === struc.mulIdentity ); } return false; }; /** * Checks if a morphism is an isomorphism. * @template T, U * @param {ChalkboardMorphism} morph - The morphism * @returns {boolean} */ export const isIsomorphism = (morph: ChalkboardMorphism): boolean => { return Chalkboard.abal.isHomomorphism(morph) && Chalkboard.abal.isBijective(morph); }; /** * Checks if an algebraic structure is a monoid. * @template T * @param {ChalkboardStructure} monoid - The monoid * @returns {boolean} */ export const isMonoid = (monoid: ChalkboardStructure): boolean => { const { set, operation, identity } = monoid; if (set.id === "Z" || set.id === "Q" || set.id === "R" || set.id === "C" || set.id === "GL") { return true; } if (typeof set.elements === "undefined") { return false; } if (typeof operation === "undefined" || typeof identity === "undefined") { return false; } if (!Chalkboard.abal.isClosed(set, operation)) { return false; } for (const a of set.elements) { if ($(operation(a, identity)) !== $(a) || $(operation(identity, a)) !== $(a)) { return false; } } for (const a of set.elements) { for (const b of set.elements) { for (const c of set.elements) { if ($(operation(operation(a, b), c)) !== $(operation(a, operation(b, c)))) { return false; } } } } return true; }; /** * Checks if a subgroup of a group is a normal subgroup. * @template T * @param {ChalkboardStructure} group - The group * @param {ChalkboardSet} subgroup - The subgroup * @returns {boolean} */ export const isNormalSubgroup = (group: ChalkboardStructure, subgroup: ChalkboardSet): boolean => { const { set, operation, inverter } = group; if (!operation || !inverter) { return false; } if (!Chalkboard.abal.isSubgroup(group, subgroup)) { return false; } for (const g of set.elements || []) { for (const h of subgroup.elements || []) { const conjugate = operation(operation(g, h), inverter(g)); if (!subgroup.contains(conjugate)) { return false; } } } return true; }; /** * Defines an isomorphism between two algebraic structures. * @template T, U * @param {ChalkboardStructure} struc1 - The first algebraic structure which is the domain of the morphism * @param {ChalkboardStructure} struc2 - The second algebraic structure which is the codomain of the morphism * @param {(element: T) => U} mapping - The bijective function that takes an element from the first structure and maps it to the second structure * @returns {ChalkboardMorphism} */ export const isomorphism = (struc1: ChalkboardStructure, struc2: ChalkboardStructure, mapping: (element: T) => U): ChalkboardMorphism => { const morphism = Chalkboard.abal.homomorphism(struc1, struc2, mapping); if (!Chalkboard.abal.isHomomorphism(morphism)) { throw new Error("The mapping is not a homomorphism, so it cannot be an isomorphism."); } if (!Chalkboard.abal.isBijective(morphism)) { throw new Error("The mapping is not bijective, so it cannot be an isomorphism."); } return morphism; }; /** * Checks if an ideal is a principal ideal of a ring. * @template T * @param {ChalkboardStructure} ring - The ring * @param {ChalkboardSet} ideal - The ideal * @returns {boolean} */ export const isPrincipalIdeal = (ring: ChalkboardStructure, ideal: ChalkboardSet): boolean => { for (const generator of ideal.elements || []) { const principalIdeal = Chalkboard.abal.principalIdeal(ring, generator); if (Chalkboard.abal.isSubset(ideal, principalIdeal) && Chalkboard.abal.isSubset(principalIdeal, ideal)) { return true; } } return false; }; /** * Checks if an algebraic structure is a ring. * @template T * @param {ChalkboardStructure} ring - The ring * @returns {boolean} */ export const isRing = (ring: ChalkboardStructure): boolean => { const { set, add, mul, addIdentity, addInverter } = ring; if (set.id === "Z" || set.id === "Q" || set.id === "R" || set.id === "C") { return true; } if (typeof add === "undefined" || typeof mul === "undefined" || typeof addIdentity === "undefined" || typeof addInverter === "undefined") { return false; } const additiveGroup: ChalkboardStructure = { set, operation: add, identity: addIdentity, inverter: addInverter }; if (!Chalkboard.abal.isGroup(additiveGroup) || !Chalkboard.abal.isCommutative(additiveGroup)) { return false; } if (!Chalkboard.abal.isClosed(set, mul!)) { return false; } for (const a of set.elements || []) { for (const b of set.elements || []) { for (const c of set.elements || []) { if ($(mul(mul(a, b), c)) !== $(mul(a, mul(b, c)))) { return false; } } } } return true; }; /** * Checks if a subset is a subfield of a field. * @template T * @param {ChalkboardStructure} field - The field * @param {ChalkboardSet} subset - The subset * @returns {boolean} */ export const isSubfield = (field: ChalkboardStructure, subset: ChalkboardSet): boolean => { const { add, mul, addIdentity, mulIdentity, addInverter, mulInverter } = field; if (field.set.id && subset.id) { if (subset.id === field.set.id && ["Q", "R", "C"].includes(subset.id)) { return true; } if (subset.id === "Q" && ["R", "C"].includes(field.set.id)) { return true; } if (subset.id === "R" && field.set.id === "C") { return true; } if (subset.id === "Z") { return false; } } if (typeof add === "undefined" || typeof mul === "undefined" || typeof addIdentity === "undefined" || typeof mulIdentity === "undefined" || typeof addInverter === "undefined" || typeof mulInverter === "undefined") { return false; } if (!subset.contains(addIdentity) || !subset.contains(mulIdentity)) { return false; } if (!Chalkboard.abal.isClosed(subset, add) || !Chalkboard.abal.isClosed(subset, mul)) { return false; } for (const a of subset.elements || []) { if (!subset.contains(addInverter(a))) { return false; } } for (const a of subset.elements || []) { if ($(a) !== $(addIdentity) && !subset.contains(mulInverter(a))) { return false; } } return true; }; /** * Checks if a subset is a subgroup of a group. * @template T * @param {ChalkboardStructure} group - The group * @param {ChalkboardSet} subset - The subset * @returns {boolean} */ export const isSubgroup = (group: ChalkboardStructure, subset: ChalkboardSet): boolean => { const { operation, identity, inverter } = group; if (group.set.id && subset.id) { if (subset.id === "Z" && ["Z", "Q", "R", "C"].includes(group.set.id)) { return true; } if (subset.id === "Q" && ["Q", "R", "C"].includes(group.set.id)) { return true; } if (subset.id === "R" && ["R", "C"].includes(group.set.id)) { return true; } if (subset.id === "C" && group.set.id === "C") { return true; } if (subset.id.startsWith("Z") && group.set.id.startsWith("Z")) { const nSubset = parseInt(subset.id.slice(1), 10); const nGroup = parseInt(group.set.id.slice(1), 10); if (!isNaN(nSubset) && !isNaN(nGroup)) { return nGroup % nSubset === 0; } } if (subset.id?.startsWith("GL") && subset.id === group.set.id) { return true; } } if (typeof operation === "undefined" || typeof identity === "undefined" || typeof inverter === "undefined") { return false; } if (!subset.contains(identity)) { return false; } if (!Chalkboard.abal.isClosed(subset, operation)) { return false; } for (const a of subset.elements || []) { if (!subset.contains(inverter(a))) { return false; } } return true; }; /** * Checks if a subset is a submonoid of a monoid. * @template T * @param {ChalkboardStructure} monoid - The monoid * @param {ChalkboardSet} subset - The subset * @returns {boolean} */ export const isSubmonoid = (monoid: ChalkboardStructure, subset: ChalkboardSet): boolean => { const { operation, identity } = monoid; if (monoid.set.id && subset.id) { if (subset.id === monoid.set.id) { return true; } if (subset.id === "Z" && ["Z", "Q", "R", "C"].includes(monoid.set.id)) { return true; } if (subset.id === "Q" && ["Q", "R", "C"].includes(monoid.set.id)) { return true; } if (subset.id === "R" && ["R", "C"].includes(monoid.set.id)) { return true; } } if (typeof operation === "undefined" || typeof identity === "undefined") { return false; } if (!subset.contains(identity)) { return false; } if (!Chalkboard.abal.isClosed(subset, operation)) { return false; } return true; }; /** * Checks if a subset is a subring of a ring. * @template T * @param {ChalkboardStructure} ring - The ring * @param {ChalkboardSet} subset - The subset * @returns {boolean} */ export const isSubring = (ring: ChalkboardStructure, subset: ChalkboardSet): boolean => { const { add, mul, addIdentity, addInverter } = ring; if (ring.set.id && subset.id) { if (subset.id === ring.set.id) { return true; } if (subset.id === "Z" && ["Z", "Q", "R", "C"].includes(ring.set.id)) { return true; } if (subset.id === "Q" && ["Q", "R", "C"].includes(ring.set.id)) { return true; } if (subset.id === "R" && ["R", "C"].includes(ring.set.id)) { return true; } if (subset.id.startsWith("Z") && ring.set.id.startsWith("Z")) { const nSubset = parseInt(subset.id.slice(1), 10); const nRing = parseInt(ring.set.id.slice(1), 10); if (!isNaN(nSubset) && !isNaN(nRing)) { return nRing % nSubset === 0; } } } if (typeof add === "undefined" || typeof mul === "undefined" || typeof addIdentity === "undefined" || typeof addInverter === "undefined") { return false; } if (!subset.contains(addIdentity)) { return false; } if (!Chalkboard.abal.isClosed(subset, add) || !Chalkboard.abal.isClosed(subset, mul)) { return false; } for (const a of subset.elements || []) { if (!subset.contains(addInverter(a))) { return false; } } return true; }; /** * Checks if a set is a subset of another set. * @template T * @param {ChalkboardSet} set - The set * @param {ChalkboardSet} superset - The potential superset * @returns {boolean} */ export const isSubset = (set: ChalkboardSet, superset: ChalkboardSet): boolean => { if (set.id && superset.id) { if (set.id === superset.id) { return true; } if (set.id === "Z") { return ["Z", "Q", "R", "C"].includes(superset.id); } if (set.id === "Q") { return ["Q", "R", "C"].includes(superset.id); } if (set.id === "R") { return ["R", "C"].includes(superset.id); } if (set.id === "N") { return ["N", "Z", "Q", "R", "C"].includes(superset.id); } if (set.id.startsWith("Z") && superset.id.startsWith("Z")) { const nSet = parseInt(set.id.slice(1), 10); const nSuper = parseInt(superset.id.slice(1), 10); if (!isNaN(nSet) && !isNaN(nSuper)) { return nSuper % nSet === 0; } } } return (set.elements || []).every((element) => superset.contains(element)); }; /** * Checks if a set is a superset of another set. * @template T * @param {ChalkboardSet} set - The set * @param {ChalkboardSet} subset - The potential subset * @returns {boolean} */ export const isSuperset = (set: ChalkboardSet, subset: ChalkboardSet): boolean => { return Chalkboard.abal.isSubset(subset, set); }; /** * Checks if a morphism is surjective. * @template T, U * @param {ChalkboardMorphism} morph - The morphism * @returns {boolean} */ export const isSurjective = (morph: ChalkboardMorphism): boolean => { const { struc1, struc2, mapping } = morph; if (["Z", "Q", "R", "C", "P"].includes(struc2.set.id || "")) { if (struc2.set.id === "C" && ["R", "C"].includes(struc1.set.id || "")) return true; if (struc2.set.id === "R" && struc1.set.id === "Q") return false; if (struc1.set.id === struc2.set.id) return true; return false; } const domain = struc1.set.elements || []; const codomain = struc2.set.elements || []; const mapped = domain.map(mapping); return codomain.every((e) => mapped.some((m) => $(m) === $(e))); }; /** * Calculates the kernel of a morphism, either for its domain or for a subset of its domain. * @template T, U * @param {ChalkboardMorphism} morph - The morphism * @param {ChalkboardSet} [subset] - The subset of the domain (optional, defaults to the domain) * @returns {ChalkboardSet} */ export const kernel = (morph: ChalkboardMorphism, subset?: ChalkboardSet): ChalkboardSet => { const { struc1, struc2, mapping } = morph; if (!struc1.set.elements) { throw new Error('The domain of the "morph" must have a finite set of elements to calculate the kernel.'); } const _subset = subset?.elements || struc1.set.elements; let identity: U | undefined; if ("identity" in struc2) { identity = (struc2 as ChalkboardStructure).identity; } else if ("addIdentity" in struc2) { identity = (struc2 as ChalkboardStructure | ChalkboardStructure).addIdentity; } else { throw new Error('The codomain of the "morph" must have an identity element to calculate the kernel.'); } const result = _subset.filter((element) => $(mapping(element)) === $(identity)); return Chalkboard.abal.set(result); }; /** * Checks if a group and a subgroup satisfy Lagrange's Theorem, i.e. the order of a subgroup divides the order of its group. * @template T * @param {ChalkboardStructure} group - The group * @param {ChalkboardSet} subgroup - The subgroup * @returns {boolean} */ export const Lagrange = (group: ChalkboardStructure, subgroup: ChalkboardSet): boolean => { if (group.set.id && ["Z", "Q", "R", "C"].includes(group.set.id)) { throw new Error("Lagrange's Theorem only applies to finite groups"); } return Chalkboard.abal.cardinality(group) % Chalkboard.abal.cardinality(subgroup) === 0; }; /** * The set of all real-valued matrices of size "rows" x "cols", denoted as M(rows, cols). * @param {number} rows - The number of rows * @param {number} [cols=rows] - The number of columns (optional, defaults to the number of rows) * @returns {ChalkboardSet} */ export const M = (rows: number, cols: number = rows): ChalkboardSet => ({ contains: (element: ChalkboardMatrix) => { return Array.isArray(element) && Chalkboard.matr.isSizeOf(element, rows, cols); }, id: `M(${rows}, ${cols})` }); /** * Defines an algebraic structure known as a monoid. * @template T * @param {ChalkboardSet} set - The set of the monoid * @param {(a: T, b: T) => T} operation - The operation of the monoid * @param {T} [identity] - The identity element of the monoid (optional if the set is Z, Q, R, C, A, S, M, or GL) * @returns {ChalkboardStructure} */ export const monoid = (set: ChalkboardSet, operation: (a: T, b: T) => T, identity?: T): ChalkboardStructure => { const autoconfig = (): { identity: T } => { if (!set.id) { throw new Error('The "set" must have a valid "id" property, or you must input "identity" explicitly.'); } if (set.id === "Z" || set.id === "Q" || set.id === "R") { return { identity: 0 as T }; } else if (set.id === "C") { return { identity: Chalkboard.comp.init(0, 0) as T }; } else if (set.id.startsWith("Z") && set.id.length > 1) { return { identity: 0 as T }; } else if (set.id.startsWith("C") && set.id.length > 1) { return { identity: Chalkboard.comp.init(1, 0) as T }; } else if (set.id.startsWith("M(")) { const rows = (set as any).rows; const cols = (set as any).cols; return { identity: Chalkboard.matr.fill(0, rows, cols) as T }; } else if (set.id.startsWith("GL")) { const n = parseInt(set.id.slice(2), 10); return { identity: Chalkboard.matr.identity(n) as T }; } else if (set.id.match(/^[SA]\d+$/)) { const n = parseInt(set.id.slice(1), 10); return { identity: Array.from({length: n}, (_, i) => i) as T }; } throw new Error('Automatic configuration of the "identity" property is not available for the inputted "set".'); }; const configured = typeof identity === "undefined" ? autoconfig() : { identity }; const monoid: ChalkboardStructure = { set, operation, identity: configured.identity }; if (!Chalkboard.abal.isMonoid(monoid)) { throw new Error('The inputted "set", "operation", and "identity" do not form a monoid.'); } return monoid; }; /** * The set of all natural numbers, denoted as N. * @returns {ChalkboardSet} */ export const N = (): ChalkboardSet => ({ contains: (element: number) => Number.isInteger(element) && element > 0, id: "N" }); /** * Calculates the order of an element in a group. * @template T * @param {ChalkboardStructure} group - The group * @param {T} element - The element * @returns {number} */ export const order = (group: ChalkboardStructure, element: T): number => { if (!group.operation) { throw new Error('The "group" must have an "operation" property to calculate the order of an element.'); } let result = 1; let current = element; while ($(current) !== $(group.identity)) { current = group.operation(current, element); result++; if (result > (group.set.elements?.length || Infinity)) { throw new Error('The "group" might not be finite because an infinite loop was detected.'); } } return result; }; /** * The set of all prime numbers, denoted as P. * @returns {ChalkboardSet} */ export const P = (): ChalkboardSet => ({ contains: (element: number) => Chalkboard.numb.isPrime(element), id: "P" }); /** * Calculates the power set of a set. * @template T * @param {ChalkboardSet} set - The set * @returns {ChalkboardSet>} */ export const powerSet = (set: ChalkboardSet): ChalkboardSet> => { const result: ChalkboardSet[] = []; const elements = set.elements || []; const totalSubsets = 1 << elements.length; for (let i = 0; i < totalSubsets; i++) { const subset: T[] = []; for (let j = 0; j < elements.length; j++) { if (i & (1 << j)) { subset.push(elements[j]); } } result.push(Chalkboard.abal.set(subset)); } return Chalkboard.abal.set(result); }; /** * Calculates the preimage of a morphism, either for its codomain or for a subset of its codomain. * @template T, U * @param {ChalkboardMorphism} morph - The morphism * @param {ChalkboardSet} [subset] - The subset of the codomain (optional, defaults to the codomain) * @returns {ChalkboardSet} */ export const preimage = (morph: ChalkboardMorphism, subset?: ChalkboardSet): ChalkboardSet => { const { struc1, struc2, mapping } = morph; if (!struc1.set.elements) { throw new Error('The domain of the "morph" must have a finite set of elements to calculate the preimage.'); } const _subset = subset || struc2.set; if (!_subset.elements) { throw new Error('The codomain of the "morph" or the subset of it must have a finite set of elements to calculate the preimage.'); } const result = struc1.set.elements.filter((element) => _subset.contains(mapping(element))); return Chalkboard.abal.set(result); }; /** * Generates the principal ideal of an element in a ring. * @template T * @param {ChalkboardStructure} ring - The ring * @param {T} element - The generator of the principal ideal * @returns {ChalkboardSet} */ export const principalIdeal = (ring: ChalkboardStructure, element: T): ChalkboardSet => { if (ring.set.id && ["Z", "Q", "R", "C"].includes(ring.set.id)) { throw new Error('The "ring" must be finite.'); } const result: T[] = []; const { mul, add } = ring; if (!add || !mul) { throw new Error('The "ring" must have "mul" and "add" properties to generate a principal ideal.'); } for (const r of ring.set.elements || []) { const leftProduct = mul(element, r); const rightProduct = mul(r, element); if (!result.includes(leftProduct)) { result.push(leftProduct); } if (!result.includes(rightProduct)) { result.push(rightProduct); } } for (let i = 0; i < result.length; i++) { for (let j = 0; j < result.length; j++) { const sum = add(result[i], result[j]); if (!result.includes(sum)) { result.push(sum); } } } return Chalkboard.abal.set(result); }; /** * Prints a set or structure in the console. * @param {ChalkboardSet | ChalkboardStructure} struc - The set or structure * @returns {void} */ export const print = (struc: ChalkboardSet | ChalkboardStructure): void => { console.log(Chalkboard.abal.toString(struc)); }; /** * The set of all rational numbers, denoted as Q. * @returns {ChalkboardSet} */ export const Q = (): ChalkboardSet => ({ contains: (element: number) => Number.isFinite(element) && Chalkboard.numb.isRational(element), id: "Q" }); /** * Defines a quotient group or ring, modulo a normal subgroup or ideal. * @template T * @param {ChalkboardStructure} struc - The group or ring * @param {ChalkboardStructure} substruc - The normal subgroup or ideal * @returns {ChalkboardStructure>} */ export const quotient = (struc: ChalkboardStructure, substruc: ChalkboardStructure): ChalkboardStructure> => { if ("operation" in struc && !Chalkboard.abal.isNormalSubgroup(struc, substruc.set)) { throw new Error('The "substruc" must be a normal subgroup of the "struc".'); } if ("add" in struc && !Chalkboard.abal.isIdeal(struc, substruc.set)) { throw new Error('The "substruc" must be an ideal of the "struc".'); } const cosets = Chalkboard.abal.coset(struc, substruc); const operationConfig = (a: ChalkboardSet, b: ChalkboardSet, operation: Function) => { const repA = a.elements![0]; const repB = b.elements![0]; const result = operation(repA, repB); return cosets.elements!.find((c) => c.contains(result))!; }; return { set: cosets, ...("operation" in struc ? { operation: (a, b) => operationConfig(a, b, struc.operation!), identity: cosets.elements!.find(c => c.contains(struc.identity!))!, inverter: a => operationConfig(a, a, (x: T) => struc.inverter!(x)) } : { add: (a, b) => operationConfig(a, b, struc.add!), mul: (a, b) => operationConfig(a, b, struc.mul!), addIdentity: cosets.elements!.find(c => c.contains(struc.addIdentity!))!, addInverter: a => operationConfig(a, a, (x: T) => struc.addInverter!(x)) }) } as ChalkboardStructure>; }; /** * The set of all real numbers, denoted as R. * @returns {ChalkboardSet} */ export const R = (): ChalkboardSet => ({ contains: (element: number) => Number.isFinite(element), id: "R" }); /** * Defines an algebraic structure known as a ring. * @template T * @param {ChalkboardSet} set - The set of the ring * @param {(a: T, b: T) => T} add - The additive operation of the ring * @param {(a: T, b: T) => T} mul - The multiplicative operation of the ring * @param {T} [addIdentity] - The additive identity element of the ring (optional if the set is Z, Q, R, C, or M) * @param {T} [mulIdentity] - The multiplicative identity element of the ring (optional if the set is Z, Q, R, C, or M) * @param {(a: T) => T} [addInverter] - The function to calculate the additive inverse of an element of the ring (optional if the set is Z, Q, R, C, or M) * @returns {ChalkboardStructure} */ export const ring = (set: ChalkboardSet, add: (a: T, b: T) => T, mul: (a: T, b: T) => T, addIdentity?: T, mulIdentity?: T, addInverter?: (a: T) => T): ChalkboardStructure => { const autoconfig = (): { addIdentity: T; mulIdentity: T; addInverter: (a: T) => T } => { if (!set.id) { throw new Error('The "set" must have a valid "id" property, or you must input "addIdentity", "mulIdentity", and "addInverter" explicitly.'); } if (set.id === "Z" || set.id === "Q" || set.id === "R") { return { addIdentity: 0 as T, mulIdentity: 1 as T, addInverter: (a: T) => (-a as unknown) as T }; } else if (set.id === "C") { return { addIdentity: Chalkboard.comp.init(0, 0) as T, mulIdentity: Chalkboard.comp.init(1, 0) as T, addInverter: (a: T) => Chalkboard.comp.negate(a as unknown as ChalkboardComplex) as T }; } else if (set.id.startsWith("Z") && set.id.length > 1) { const n = parseInt(set.id.slice(1), 10); if (isNaN(n) || n <= 0) { throw new Error(`Invalid modulus in set "${set.id}".`); } return { addIdentity: 0 as T, mulIdentity: 1 as T, addInverter: (a: T) => ((n - (a as unknown as number) % n) % n) as T }; } else if (set.id.startsWith("M(")) { const rows = (set as any).rows; const cols = (set as any).cols; if (rows !== cols) { throw new Error("Only square matrices can form a ring."); } return { addIdentity: Chalkboard.matr.fill(0, rows, cols) as T, mulIdentity: Chalkboard.matr.identity(rows) as T, addInverter: (a: T) => Chalkboard.matr.negate(a as unknown as ChalkboardMatrix) as T }; } throw new Error('Automatic configuration of the "addIdentity", "mulIdentity", and "addInverter" properties is not available for the inputted "set".'); }; const configured = typeof addIdentity === "undefined" || typeof mulIdentity === "undefined" || typeof addInverter === "undefined" ? autoconfig() : { addIdentity, mulIdentity, addInverter }; const ring: ChalkboardStructure = { set, add, mul, addIdentity: configured.addIdentity, mulIdentity: configured.mulIdentity, addInverter: configured.addInverter}; if (!Chalkboard.abal.isRing(ring)) { throw new Error('The inputted "set", "add", "mul", "addIdentity", "mulIdentity", and "addInverter" do not form a ring.'); } return ring; }; /** * Defines an algebraic structure extension known as a ring extension. * @template T, U * @property {ChalkboardStructure} base - The algebraic structure which is a substructure of the extension structure * @property {ChalkboardStructure} extension - The algebraic structure which is an extension of the base structure * @property {number} [degree] - The rank of the extension structure as a module over the base structure (optional for extensions of Z, Q, R, or C) * @property {ChalkboardVector[]} [basis] - The basis vectors of the extension structure (optional for extensions of Z, Q, R, or C) * @property {boolean} [isFinite] - Whether the extension structure is finite or not (optional for extensions of Z, Q, R, or C) * @property {boolean} [isSimple] - Whether the extension structure is simple or not (optional for extensions of Z, Q, R, or C) * @property {boolean} [isAlgebraic] - Whether the extension structure is algebraic or not (optional for extensions of Z, Q, R, or C) * @returns {ChalkboardStructureExtension} */ export const ringExtension = (base: ChalkboardStructure, extension: ChalkboardStructure, degree: number, basis: ChalkboardVector[], isFinite: boolean, isSimple: boolean, isAlgebraic: boolean): ChalkboardStructureExtension => { if (!Chalkboard.abal.isSubring(base as ChalkboardStructure, extension.set as ChalkboardSet)) { throw new Error('The "base" must be a subring of the "extension".'); } const autoconfig = (): { degree: number; basis: ChalkboardVector[]; isFinite: boolean; isSimple: boolean; isAlgebraic: boolean } => { if (!base.set.id) { throw new Error('The "set" property of the "base" must have a valid "id" property, or you must input "degree", "basis", "isFinite", "isSimple", and "isAlgebraic" explicitly.'); } if (base.set.id === "Z" && extension.set.id === "Q") { return { degree: Infinity, basis: [], isFinite: false, isSimple: false, isAlgebraic: false }; } else if (base.set.id === "Q" && extension.set.id === "R") { return { degree: Infinity, basis: [], isFinite: false, isSimple: false, isAlgebraic: false }; } else if (base.set.id === "R" && extension.set.id === "C") { return { degree: 2, basis: [Chalkboard.vect.init(1, 0), Chalkboard.vect.init(0, 1)], isFinite: true, isSimple: true, isAlgebraic: true }; } throw new Error('Automatic configuration of the "degree", "basis", "isFinite", "isSimple", and "isAlgebraic" properties is not available for the inputted "base".'); }; const configured = typeof degree === "undefined" || typeof basis === "undefined" || typeof isFinite === "undefined" || typeof isSimple === "undefined" || typeof isAlgebraic === "undefined" ? autoconfig() : { degree, basis, isFinite, isSimple, isAlgebraic }; return { base, extension, degree: configured.degree, basis: configured.basis, isFinite: configured.isFinite, isSimple: configured.isSimple, isAlgebraic: configured.isAlgebraic }; }; /** * The set of all permutations of n elements, denoted as Sn. * @param {number} n - The number of elements * @returns {ChalkboardSet} */ export const S = (n: number): ChalkboardSet => { if (!Number.isInteger(n) || n <= 0) { throw new Error('The parameter "n" must be a positive integer.'); } const generatePermutations = (arr: number[]): number[][] => { if (arr.length === 0) return [[]]; const result: number[][] = []; for (let i = 0; i < arr.length; i++) { const rest = [...arr.slice(0, i), ...arr.slice(i + 1)]; const perms = generatePermutations(rest); for (const perm of perms) { result.push([arr[i], ...perm]); } } return result; }; const elements = generatePermutations(Array.from({ length: n }, (_, i) => i)); return { contains: (element: number[]) => elements.some((perm) => $(perm) === $(element)), elements: elements, id: `S${n}` }; }; /** * Defines a finite set of elements. * @param {T[]} set - The elements of the set * @returns {ChalkboardSet} */ export const set = (set: T[]): ChalkboardSet => { const elements = Chalkboard.stat.unique(set); return { contains: (element: T) => elements.some((x) => $(x) === $(element)), elements: elements }; }; /** * Calculates the symmetric difference of two sets. * @template T * @param {ChalkboardSet} set1 - The first set * @param {ChalkboardSet} set2 - The second set * @returns {ChalkboardSet} */ export const symmetricDifference = (set1: ChalkboardSet, set2: ChalkboardSet): ChalkboardSet => { const diffA = Chalkboard.abal.difference(set1, set2).elements || []; const diffB = Chalkboard.abal.difference(set2, set1).elements || []; return Chalkboard.abal.set([...diffA, ...diffB]); }; /** * Converts a set or algebraic structure to an array. * @template T * @param {ChalkboardSet | ChalkboardStructure} struc - The set or structure * @returns {T[]} */ export const toArray = (struc: ChalkboardSet | ChalkboardStructure): T[] => { const result = "set" in struc ? struc.set : struc; if (!result.elements) { throw new Error("Cannot convert infinite set to array."); } return [...result.elements]; }; /** * Converts a set or algebraic structure to a matrix. * @param {ChalkboardSet | ChalkboardStructure} struc - The set or structure * @param {number} rows - The number of rows of the matrix * @param {number} [cols=rows] - The number of columns of the matrix (optional, defaults to the number of rows to make a square matrix) * @returns {ChalkboardMatrix} */ export const toMatrix = (struc: ChalkboardSet | ChalkboardStructure, rows: number, cols: number = rows): ChalkboardMatrix => { const result = "set" in struc ? struc.set : struc; if (!result.elements) { throw new Error("Cannot convert infinite set to matrix."); } return Chalkboard.stat.toMatrix(result.elements, rows, cols); }; /** * Converts a set or algebraic structure to an object. * @param struc - The set or structure * @returns {object} */ export const toObject = (struc: ChalkboardSet | ChalkboardStructure): object => { const result = "set" in struc ? struc.set : struc; if (!result.elements) { throw new Error("Cannot convert infinite set to object."); } return Chalkboard.stat.toObject(result.elements); }; /** * Converts a set or algebraic structure to a string. * @param {ChalkboardSet | ChalkboardStructure} struc - The set or structure * @returns {string} */ export const toString = (struc: ChalkboardSet | ChalkboardStructure): string => { const result = "set" in struc ? struc.set : struc; if (!result.elements) { throw new Error("Cannot convert infinite set to string."); } return Chalkboard.stat.toString(result.elements); }; /** * Converts a set or algebraic structure to a tensor. * @param {ChalkboardSet | ChalkboardStructure} struc - The set or structure * @param {number[]} size - The size of the tensor * @returns {ChalkboardTensor} */ export const toTensor = (struc: ChalkboardSet | ChalkboardStructure, ...size: number[]): ChalkboardTensor => { const result = "set" in struc ? struc.set : struc; if (!result.elements) { throw new Error("Cannot convert infinite set to tensor."); } if (Array.isArray(size[0])) size = size[0]; return Chalkboard.tens.resize(result.elements, ...size); }; /** * Converts a set or algebraic structure to a typed array. * @param {ChalkboardSet | ChalkboardStructure} struc - The set or structure * @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 = (struc: ChalkboardSet | ChalkboardStructure, type: "int8" | "int16" | "int32" | "float32" | "float64" | "bigint64" = "float32"): Int8Array | Int16Array | Int32Array | Float32Array | Float64Array | BigInt64Array => { const result = "set" in struc ? struc.set : struc; if (!result.elements) { throw new Error("Cannot convert infinite set to typed array."); } const arr = Chalkboard.abal.toArray(result); 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 set or algebraic structure to a vector. * @param {ChalkboardSet | ChalkboardStructure} struc - The set or structure * @param {number} dimension - The dimension of the vector, which can be 2, 3, or 4 * @param {number} [index=0] - The index of the array to start the vector * @returns {ChalkboardVector} */ export const toVector = (struc: ChalkboardSet | ChalkboardStructure, dimension: 2 | 3 | 4, index: number = 0): ChalkboardVector => { const elements = "set" in struc ? struc.set.elements : struc.elements; if (!elements) { throw new Error("Cannot convert infinite set to vector."); } if (dimension === 2) { return Chalkboard.vect.init(elements[index], elements[index + 1]); } else if (dimension === 3) { return Chalkboard.vect.init(elements[index], elements[index + 1], elements[index + 2]); } else if (dimension === 4) { return Chalkboard.vect.init(elements[index], elements[index + 1], elements[index + 2], elements[index + 3]); } else { throw new RangeError('Parameter "dimension" must be 2, 3, or 4.'); } }; /** * Calculates the union of two sets. * @template T * @param {ChalkboardSet} set1 - The first set * @param {ChalkboardSet} set2 - The second set * @returns {ChalkboardSet} */ export const union = (set1: ChalkboardSet, set2: ChalkboardSet): ChalkboardSet => { const result = Array.from(new Set([...(set1.elements || []), ...(set2.elements || [])])); return Chalkboard.abal.set(result); }; /** * The set of integers, denoted as Z, or the set of integers modulo n, denoted as Zn. * @param {number} [n] - The modulus (optional, returns the set of all integers, i.e. Z, if omitted) * @returns {ChalkboardSet} */ export const Z = (n?: number): ChalkboardSet => { if (n === undefined) { return { contains: (element: number) => Number.isInteger(element), id: "Z" }; } else { if (!Number.isInteger(n) || n <= 0) { throw new Error('The modulus "n" must be a positive integer.'); } return { contains: (element: number) => Number.isInteger(element) && element >= 0 && element < n, elements: Array.from({ length: n }, (_, i) => i), id: `Z${n}` }; } }; } }