/*
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 =