/* Chalkboard - Complex Numbers 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 complex numbers namespace. * @namespace */ export namespace comp { /** * Calculates the absolute value of a complex number or complex function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns 2 + 3i * const z = Chalkboard.comp.absolute(Chalkboard.comp.init(-2, 3)); */ export const absolute = (comp: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; return Chalkboard.comp.init(Math.abs(z.a), Math.abs(z.b)); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.absolute: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as ((a: number, b: number) => number)[]; const g = [(a: number, b: number) => Math.abs(f[0](a, b)), (a: number, b: number) => Math.abs(f[1](a, b))]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.absolute: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates the addition of two complex numbers or functions. * @param {ChalkboardComplex | number | ChalkboardFunction} comp1 - The first complex number or function * @param {ChalkboardComplex | number | ChalkboardFunction} comp2 - The second complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns 3 + 4i * const sum = Chalkboard.comp.add(Chalkboard.comp.init(2, 3), Chalkboard.comp.init(1, 1)); */ export const add = (comp1: ChalkboardComplex | number | ChalkboardFunction, comp2: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp1 === "number") comp1 = Chalkboard.comp.init(comp1, 0); if (typeof comp2 === "number") comp2 = Chalkboard.comp.init(comp2, 0); if (comp1.hasOwnProperty("a") && comp1.hasOwnProperty("b") && comp2.hasOwnProperty("a") && comp2.hasOwnProperty("b")) { const z1 = comp1 as ChalkboardComplex; const z2 = comp2 as ChalkboardComplex; return Chalkboard.comp.init(z1.a + z2.a, z1.b + z2.b); } else if (comp1.hasOwnProperty("rule") && comp2.hasOwnProperty("rule")) { if ((comp1 as ChalkboardFunction).field !== "comp" || (comp2 as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.add: Properties 'field' of 'comp1' and 'comp2' must be 'comp'."); const f1 = (comp1 as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; const f2 = (comp2 as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; const g = [(a: number, b: number) => f1[0](a, b) + f2[0](a, b), (a: number, b: number) => f1[1](a, b) + f2[1](a, b)]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.add: Parameters 'comp1' and 'comp2' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates the argument of a complex number. * @param {ChalkboardComplex} comp - The complex number * @returns {number} * @example * // Returns 0.9273 (approximately π/3 radians) * const argument = Chalkboard.comp.arg(Chalkboard.comp.init(1, 1.7321)); */ export const arg = (comp: ChalkboardComplex): number => { return Chalkboard.trig.arctan2(comp.b, comp.a); }; /** * Calculates the argument between two complex numbers. * @param {ChalkboardComplex} comp1 - The first complex number * @param {ChalkboardComplex} comp2 - The second complex number * @returns {number} * @example * // Returns 0.7854 (approximately π/4 radians) * const angle = Chalkboard.comp.argBetween(Chalkboard.comp.init(1, 0), Chalkboard.comp.init(1, 1)); */ export const argBetween = (comp1: ChalkboardComplex, comp2: ChalkboardComplex): number => { return Chalkboard.vect.angBetween(Chalkboard.comp.toVector(comp1), Chalkboard.comp.toVector(comp2)); }; /** * Calculates the conjugate of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns 2 - 3i * const conj = Chalkboard.comp.conjugate(Chalkboard.comp.init(2, 3)); */ export const conjugate = (comp: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; return Chalkboard.comp.init(z.a, -z.b); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.conjugate: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as ((a: number, b: number) => number)[]; const g = [(a: number, b: number) => f[0](a, b), (a: number, b: number) => -f[1](a, b)]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.conjugate: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates a complex number constrained within a range. * @param {ChalkboardComplex} comp - The complex number * @param {number[]} [range=[0, 1]] - The range * @returns {ChalkboardComplex} * @example * // Returns 1 + 0.5i * const constrained = Chalkboard.comp.constrain(Chalkboard.comp.init(2, 0.5)); */ export const constrain = (comp: ChalkboardComplex, range: [number, number] = [0, 1]): ChalkboardComplex => { return Chalkboard.comp.init(Chalkboard.numb.constrain(comp.a, range), Chalkboard.numb.constrain(comp.b, range)); }; /** * Copies a complex number. * @param {ChalkboardComplex} comp - The complex number * @returns {ChalkboardComplex} * @example * // Returns 2 + 3i * const copied = Chalkboard.comp.copy(Chalkboard.comp.init(2, 3)); */ export const copy = (comp: ChalkboardComplex): ChalkboardComplex => { return Object.create(Object.getPrototypeOf(comp), Object.getOwnPropertyDescriptors(comp)); }; /** * Calculates the cosine of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns 1 * const z = Chalkboard.comp.random(); * const sin = Chalkboard.comp.sin(z); * const cos = Chalkboard.comp.cos(z); * const w = Chalkboard.comp.add(Chalkboard.comp.sq(sin), Chalkboard.comp.sq(cos)); */ export const cos = (comp: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; return Chalkboard.comp.init( Chalkboard.trig.cos(z.a) * Chalkboard.trig.cosh(z.b), -Chalkboard.trig.sin(z.a) * Chalkboard.trig.sinh(z.b) ); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.cos: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; return Chalkboard.comp.define( (a: number, b: number) => { const re = f[0](a, b); const im = f[1](a, b); return Chalkboard.trig.cos(re) * Chalkboard.trig.cosh(im); }, (a: number, b: number) => { const re = f[0](a, b); const im = f[1](a, b); return -Chalkboard.trig.sin(re) * Chalkboard.trig.sinh(im); } ); } throw new TypeError("Chalkboard.comp.cos: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Defines a mathematical function in the field of complex numbers. * @param {Function | Function[]} rule - The rule of the function, which can be a single function that takes a complex number or an array of two functions that take real and imaginary parts respectively. * @returns {ChalkboardFunction} * @example * // Defines f(z) = z² or f(a+bi) = (a²-b²) + (2ab)i * const f = Chalkboard.comp.define((z) => Chalkboard.comp.sq(z)); * * // Defines g(a+bi) = (a²-b²) + (2ab)i or g(z) = z² * const g = Chalkboard.comp.define([ * (a, b) => a*a - b*b, * (a, b) => 2*a*b * ]); */ export const define = (...rule: (((z: ChalkboardComplex) => ChalkboardComplex) | ((a: number, b: number) => number))[]): ChalkboardFunction => { let f: ((z: ChalkboardComplex) => ChalkboardComplex) | ((a: number, b: number) => number)[] | ((a: number, b: number) => number); if (rule.length === 1 && Array.isArray(rule[0])) { f = rule[0] as ((a: number, b: number) => number)[]; } else if (rule.length > 1) { f = rule as ((a: number, b: number) => number)[]; } else { f = rule[0] as ((z: ChalkboardComplex) => ChalkboardComplex); } if (Array.isArray(f)) { if (f.length !== 2 || f[0].length !== 2 || f[1].length !== 2) throw new TypeError("Chalkboard.comp.define: If 'rule' is an array, it must be an array of two functions of two variables."); if (typeof f[0](0, 0) !== "number" || typeof f[1](0, 0) !== "number") throw new TypeError("Chalkboard.comp.define: If 'rule' is an array, the functions in it must return real numbers."); return { rule: f, field: "comp", type: "vector2d" } as ChalkboardFunction; } else { if (f.length !== 1) throw new TypeError("Chalkboard.comp.define: If 'rule' is a function, it must be a function of one variable."); const F = f as (z: ChalkboardComplex) => ChalkboardComplex; if (!F(Chalkboard.comp.init(0, 0)).hasOwnProperty("a") || !F(Chalkboard.comp.init(0, 0)).hasOwnProperty("b")) throw new TypeError("Chalkboard.comp.define: If 'rule' is a function, it must return a complex number."); return { rule: [(a: number, b: number) => F(Chalkboard.comp.init(a, b)).a, (a: number, b: number) => F(Chalkboard.comp.init(a, b)).b], field: "comp", type: "vector2d" } as ChalkboardFunction; } }; /** * Calculates the distance between two complex numbers. * @param {ChalkboardComplex | number} comp1 - The first complex number * @param {ChalkboardComplex | number} comp2 - The second complex number * @returns {ChalkboardComplex} * @example * // Returns 5 (distance from origin to 3+4i) * const distance = Chalkboard.comp.dist(Chalkboard.comp.init(0, 0), Chalkboard.comp.init(3, 4)); */ export const dist = (comp1: ChalkboardComplex | number, comp2: ChalkboardComplex | number): number => { if (typeof comp1 === "number") comp1 = Chalkboard.comp.init(comp1, 0); if (typeof comp2 === "number") comp2 = Chalkboard.comp.init(comp2, 0); return Chalkboard.real.sqrt((comp2.a - comp1.a) * (comp2.a - comp1.a) + (comp2.b - comp1.b) * (comp2.b - comp1.b)); }; /** * Calculates the distance squared between two complex numbers. * @param {ChalkboardComplex | number} comp1 - The first complex number * @param {ChalkboardComplex | number} comp2 - The second complex number * @returns {ChalkboardComplex} * @example * // Returns 25 (squared distance from origin to 3+4i) * const distanceSquared = Chalkboard.comp.distsq(Chalkboard.comp.init(0, 0), Chalkboard.comp.init(3, 4)); */ export const distsq = (comp1: ChalkboardComplex | number, comp2: ChalkboardComplex | number): number => { if (typeof comp1 === "number") comp1 = Chalkboard.comp.init(comp1, 0); if (typeof comp2 === "number") comp2 = Chalkboard.comp.init(comp2, 0); return (comp2.a - comp1.a) * (comp2.a - comp1.a) + (comp2.b - comp1.b) * (comp2.b - comp1.b); }; /** * Calculates the division of two complex numbers or functions. * @param {ChalkboardComplex | number | ChalkboardFunction} comp1 - The first complex number or function * @param {ChalkboardComplex | number | ChalkboardFunction} comp2 - The second complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns 0.44 + 0.08i (approximate) * const quotient = Chalkboard.comp.div(Chalkboard.comp.init(2, 1), Chalkboard.comp.init(4, 2)); */ export const div = (comp1: ChalkboardComplex | number | ChalkboardFunction, comp2: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp1 === "number") comp1 = Chalkboard.comp.init(comp1, 0); if (typeof comp2 === "number") comp2 = Chalkboard.comp.init(comp2, 0); if (comp1.hasOwnProperty("a") && comp1.hasOwnProperty("b") && comp2.hasOwnProperty("a") && comp2.hasOwnProperty("b")) { const z1 = comp1 as ChalkboardComplex; const z2 = comp2 as ChalkboardComplex; const d = z2.a * z2.a + z2.b * z2.b; return Chalkboard.comp.init((z1.a * z2.a + z1.b * z2.b) / d, (z1.b * z2.a - z1.a * z2.b) / d); } else if (comp1.hasOwnProperty("rule") || comp2.hasOwnProperty("rule")) { if ((comp1 as ChalkboardFunction).field !== "comp" || (comp2 as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.div: Properties 'field' of 'comp1' and 'comp2' must be 'comp'."); const f1 = (comp1 as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; const f2 = (comp2 as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; const g = [ (a: number, b: number) => { const d = f2[0](a, b) * f2[0](a, b) + f2[1](a, b) * f2[1](a, b); return (f1[0](a, b) * f2[0](a, b) + f1[1](a, b) * f2[1](a, b)) / d; }, (a: number, b: number) => { const d = f2[0](a, b) * f2[0](a, b) + f2[1](a, b) * f2[1](a, b); return (f1[1](a, b) * f2[0](a, b) - f1[0](a, b) * f2[1](a, b)) / d; } ]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.div: Parameters 'comp1' and 'comp2' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates Euler's formula (the complex exponential) for the inputted radian. * @param {number} rad * @returns {ChalkboardComplex} * @example * // Returns 0.5403 + 0.8415i (approximate - e^(iπ/4)) * const e = Chalkboard.comp.Euler(Chalkboard.PI(0.25)); */ export const Euler = (rad: number): ChalkboardComplex => { return Chalkboard.comp.init(Chalkboard.trig.cos(rad), Chalkboard.trig.sin(rad)); }; /** * Calculates the exponential of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns approximately -e * const z = Chalkboard.comp.exp(Chalkboard.comp.init(1, Chalkboard.PI())); */ export const exp = (comp: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; const expRe = Math.exp(z.a); return Chalkboard.comp.init(expRe * Math.cos(z.b), expRe * Math.sin(z.b)); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.exp: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; return Chalkboard.comp.define( (a: number, b: number) => { const expRe = Math.exp(f[0](a, b)); return expRe * Math.cos(f[1](a, b)); }, (a: number, b: number) => { const expRe = Math.exp(f[0](a, b)); return expRe * Math.sin(f[1](a, b)); } ); } throw new TypeError("Chalkboard.comp.exp: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Returns the imaginary part of a complex number or complex function. * @param {ChalkboardFunction | ChalkboardComplex} funcORcomp * @returns {Function | ChalkboardComplex} * @example * // Returns 3 * const im = Chalkboard.comp.Im(Chalkboard.comp.init(2, 3)); */ export const Im = (funcORcomp: ChalkboardFunction | ChalkboardComplex): Function | number => { if (funcORcomp.hasOwnProperty("rule")) { return ((funcORcomp as ChalkboardFunction).rule as ([(a: number, b: number) => number, (a: number, b: number) => number]))[1]; } else { return (funcORcomp as ChalkboardComplex).b; } }; /** * Initializes a new complex number * @param {number} a - The real part * @param {number} [b=0] - The imaginary part * @returns {ChalkboardComplex} * @example * const z = Chalkboard.comp.init(2, 3); // Returns 2 + 3i * const w = Chalkboard.comp.init(2); // Returns 2 + 0i also known as 2 * const i = Chalkboard.comp.init(0, 1); // Returns i */ export const init = (a: number, b: number = 0): ChalkboardComplex => { return { a: a, b: b }; }; /** * Calculates the inverse of a complex number. * @param {ChalkboardComplex} comp - The complex number * @returns {ChalkboardComplex} * @example * // Returns 0.2 - 0.1i * const inverse = Chalkboard.comp.invert(Chalkboard.comp.init(4, 2)); */ export const invert = (comp: ChalkboardComplex): ChalkboardComplex => { return Chalkboard.comp.init(comp.a / Chalkboard.comp.magsq(comp), -comp.b / Chalkboard.comp.magsq(comp)); }; /** * Checks if two complex numbers are approximately equal within a particular precision. * @param {ChalkboardComplex | number} comp1 - The first complex number * @param {ChalkboardComplex | number} comp2 - The second complex number * @param {number} [precision=0.000001] - The precision to check * @returns {boolean} * @example * // Returns true * const yes = Chalkboard.comp.isApproxEqual(Chalkboard.comp.init(2, 3), Chalkboard.comp.init(2.0000001, 3.0000001)); * * // Returns false * const no = Chalkboard.comp.isApproxEqual(Chalkboard.comp.init(2, 3), Chalkboard.comp.init(2.1, 3.1)); */ export const isApproxEqual = (comp1: ChalkboardComplex | number, comp2: ChalkboardComplex | number, precision: number = 0.000001): boolean => { if (typeof comp1 === "number") comp1 = Chalkboard.comp.init(comp1, 0); if (typeof comp2 === "number") comp2 = Chalkboard.comp.init(comp2, 0); return Chalkboard.numb.isApproxEqual(comp1.a, comp2.a, precision) && Chalkboard.numb.isApproxEqual(comp1.b, comp2.b, precision); }; /** * Checks if two complex numbers are equal. * @param {ChalkboardComplex | number} comp1 - The first complex number * @param {ChalkboardComplex | number} comp2 - The second complex number * @returns {boolean} * @example * // Returns true * const yes = Chalkboard.comp.isEqual(Chalkboard.comp.init(2, 3), Chalkboard.comp.init(2, 3)); * * // Returns false * const no = Chalkboard.comp.isEqual(Chalkboard.comp.init(2, 3), Chalkboard.comp.init(2.0000001, 3.0000001)); */ export const isEqual = (comp1: ChalkboardComplex | number, comp2: ChalkboardComplex | number): boolean => { if (typeof comp1 === "number") comp1 = Chalkboard.comp.init(comp1, 0); if (typeof comp2 === "number") comp2 = Chalkboard.comp.init(comp2, 0); return comp1.a === comp2.a && comp1.b === comp2.b; }; /** * Checks if two complex numbers are inverses of each other within a particular precision. * @param {ChalkboardComplex | number} comp1 - The first complex number * @param {ChalkboardComplex | number} comp2 - The second complex number * @param {number} [precision=0.000001] - The precision to check * @returns {boolean} * @example * // Returns true * const z = Chalkboard.comp.init(2, 3); * const zi = Chalkboard.comp.invert(z); * const yes = Chalkboard.comp.isInverse(z, zi); */ export const isInverse = (comp1: ChalkboardComplex | number, comp2: ChalkboardComplex | number, precision: number = 0.000001): boolean => { if (typeof comp1 === "number") comp1 = Chalkboard.comp.init(comp1, 0); if (typeof comp2 === "number") comp2 = Chalkboard.comp.init(comp2, 0); return Chalkboard.comp.isApproxEqual(Chalkboard.comp.mul(comp1, comp2) as ChalkboardComplex, Chalkboard.comp.init(1, 0), precision); }; /** * Checks if a complex number is normalized. * @param {ChalkboardComplex} comp - The complex number * @returns {boolean} * @example * // Returns true * const yes = Chalkboard.comp.isNormalized(Chalkboard.comp.init(1, 0)); * * // Returns false * const no = Chalkboard.comp.isNormalized(Chalkboard.comp.init(2, 3)); */ export const isNormalized = (comp: ChalkboardComplex): boolean => { return Chalkboard.numb.isApproxEqual(Chalkboard.comp.magsq(comp), 1); }; /** * Checks if a complex number is zero. * @param {ChalkboardComplex | number} comp - The complex number * @returns {boolean} * @example * // Returns true * const yes = Chalkboard.comp.isZero(Chalkboard.comp.init(0, 0)); * * // Returns false * const no = Chalkboard.comp.isZero(Chalkboard.comp.init(1, 2)); */ export const isZero = (comp: ChalkboardComplex | number): boolean => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); return Chalkboard.comp.isApproxEqual(comp, Chalkboard.comp.init(0, 0)); }; /** * Calculates the complex logarithm of a complex number. * @param {ChalkboardComplex} comp - The complex number * @returns {ChalkboardComplex} * @example * // Returns 1.6094 + 0.9828i (approximate) * const log = Chalkboard.comp.ln(Chalkboard.comp.init(3, 4)); */ export const ln = (comp: ChalkboardComplex): ChalkboardComplex => { return Chalkboard.comp.init(Chalkboard.real.ln(Chalkboard.comp.mag(comp)), Chalkboard.trig.arctan2(comp.b, comp.a)); }; /** * Calculates the magnitude (or modulus) of a complex number. * @param {ChalkboardComplex} comp - The complex number * @returns {number} * @example * // Returns 5 * const r = Chalkboard.comp.mag(Chalkboard.comp.init(3, 4)); */ export const mag = (comp: ChalkboardComplex): number => { return Chalkboard.real.sqrt(comp.a * comp.a + comp.b * comp.b); }; /** * Calculates a complex number with the inputted magnitude. * @param {ChalkboardComplex} comp - The complex number * @param {number} num - The magnitude to set to * @returns {ChalkboardComplex} * @example * // Returns 6 + 8i (scaled to magnitude 10) * const normscl = Chalkboard.comp.magset(Chalkboard.comp.init(3, 4), 10); */ export const magset = (comp: ChalkboardComplex, num: number): ChalkboardComplex => { return Chalkboard.comp.scl(Chalkboard.comp.normalize(comp), num) as ChalkboardComplex; }; /** * Calculates the magnitude (or modulus) squared of a complex number. * @param {ChalkboardComplex} comp - The complex number * @returns {number} * @example * // Returns 25 * const r2 = Chalkboard.comp.magsq(Chalkboard.comp.init(3, 4)); */ export const magsq = (comp: ChalkboardComplex): number => { return comp.a * comp.a + comp.b * comp.b; }; /** * Calculates the multiplication of two complex numbers or functions. * @param {ChalkboardComplex | number | ChalkboardFunction} comp1 - The first complex number or function * @param {ChalkboardComplex | number | ChalkboardFunction} comp2 - The second complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns -5 + 10i * const product = Chalkboard.comp.mul(Chalkboard.comp.init(2, 3), Chalkboard.comp.init(1, 2)); */ export const mul = (comp1: ChalkboardComplex | number | ChalkboardFunction, comp2: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp1 === "number") comp1 = Chalkboard.comp.init(comp1, 0); if (typeof comp2 === "number") comp2 = Chalkboard.comp.init(comp2, 0); if (comp1.hasOwnProperty("a") && comp1.hasOwnProperty("b") && comp2.hasOwnProperty("a") && comp2.hasOwnProperty("b")) { const z1 = comp1 as ChalkboardComplex; const z2 = comp2 as ChalkboardComplex; return Chalkboard.comp.init(z1.a * z2.a - z1.b * z2.b, z1.a * z2.b + z1.b * z2.a); } else if (comp1.hasOwnProperty("rule") || comp2.hasOwnProperty("rule")) { if ((comp1 as ChalkboardFunction).field !== "comp" || (comp2 as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.mul: Properties 'field' of 'comp1' and 'comp2' must be 'comp'."); const f1 = (comp1 as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; const f2 = (comp2 as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; const g = [(a: number, b: number) => f1[0](a, b) * f2[0](a, b) - f1[1](a, b) * f2[1](a, b), (a: number, b: number) => f1[0](a, b) * f2[1](a, b) + f1[1](a, b) * f2[0](a, b)]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.mul: Parameters 'comp1' and 'comp2' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates the negation of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns -2 - 3i * const negated = Chalkboard.comp.negate(Chalkboard.comp.init(2, 3)); */ export const negate = (comp: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; return Chalkboard.comp.init(-z.a, -z.b); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.negate: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as ((a: number, b: number) => number)[]; const g = [(a: number, b: number) => -f[0](a, b), (a: number, b: number) => -f[1](a, b)]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.negate: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates the normalization of a complex number. * @param {ChalkboardComplex} comp - The complex number * @returns {ChalkboardComplex} * @example * // Returns 0.6 + 0.8i * const unit = Chalkboard.comp.normalize(Chalkboard.comp.init(3, 4)); */ export const normalize = (comp: ChalkboardComplex): ChalkboardComplex => { return Chalkboard.comp.init(comp.a / Chalkboard.comp.mag(comp), comp.b / Chalkboard.comp.mag(comp)); }; /** * Parses, simplifies, and optionally evaluates a complex number expression. * @param {string} expr - The complex number expression to parse * @param {Record} [config.values] - Optional object mapping variable names to values * @param {number} [config.roundTo] - Optional number of decimal places to round the result to * @param {boolean} [config.returnAST=false] - If true, returns an abstract syntax tree (AST) instead of a string * @param {boolean} [config.returnJSON=false] - If true, returns an AST in JSON instead of a string * @param {boolean} [config.returnLaTeX=false] - If true, returns LaTeX code instead of a string * @returns {string | ChalkboardComplex | { type: string, [key: string]: any }} * @example * // Returns -2 + 4i * const expr1 = Chalkboard.comp.parse("z^2 + 1", { values: { z: Chalkboard.comp.init(1, 2) } }); * * // Returns 16x^4 + 81y^4 + 96x^3y + 216x^2y^2 + 216y^3x * const expr2 = Chalkboard.comp.parse("(2x + 3y)^4"); * * // Returns -23.0631 + 18.6612i * const expr3 = Chalkboard.comp.parse("(1 + exp(2i))(3 + sin(4i))"); * * // Returns w\mathrm{exp}\left(z\right) + \mathrm{exp}\left(z\right) * const expr4 = Chalkboard.comp.parse("exp(z)(w + 1)", { returnLaTeX: true }); * * // Returns {"type":"add","left":{"type":"mul","left":{"type":"var","name":"w"},"right":{"type":"func","name":"exp","args":[{"type":"var","name":"z"}]}},"right":{"type":"func","name":"exp","args":[{"type":"var","name":"z"}]}} * const expr5 = Chalkboard.comp.parse("exp(z)(w + 1)", { returnJSON: true }); */ export const parse = ( expr: string, config: { values?: Record, roundTo?: number, returnAST?: boolean, returnJSON?: boolean, returnLaTeX?: boolean } = { returnAST: false, returnJSON: false, returnLaTeX: false } ): string | ChalkboardComplex | { type: string, [key: string]: any } => { const tokenize = (input: string): string[] => { const tokens: string[] = []; let i = 0; const registered = ["sin", "cos", "tan", "abs", "sq", "sqrt", "root", "ln", "exp", "conj", "conjugate", "invert", "mag", "arg", "re", "im"]; const isFunction = (name: string): boolean => registered.includes(name) || Chalkboard.REGISTRY[name] !== undefined; while (i < input.length) { const ch = input[i]; if (/\s/.test(ch)) { i++; continue; } if ("+-*/(),^".indexOf(ch) !== -1) { tokens.push(ch); i++; if (ch === ")" && i < input.length && (/[a-zA-Z0-9_i(]/.test(input[i]))) { if (tokens[tokens.length - 1] !== "*") tokens.push("*"); } } else if (ch === "i" && (i === 0 || !/[a-zA-Z0-9_]/.test(input[i - 1]))) { tokens.push("i"); i++; if (i < input.length && (/[a-zA-Z0-9_(]/.test(input[i]))) { if (tokens[tokens.length - 1] !== "*") tokens.push("*"); } } else if (/[0-9]/.test(ch) || (ch === "." && /[0-9]/.test(input[i + 1]))) { let num = ""; let hasDecimal = false; while (i < input.length && ((/[0-9]/.test(input[i])) || (input[i] === "." && !hasDecimal))) { if (input[i] === ".") hasDecimal = true; num += input[i++]; } tokens.push(num); if (i < input.length && input[i] === "i") { if (tokens[tokens.length - 1] !== "*") tokens.push("*"); tokens.push("i"); i++; } if (i < input.length && (/[a-zA-Z_]/.test(input[i]) || input[i] === "(")) { if (tokens[tokens.length - 1] !== "*") tokens.push("*"); } } else if (/[a-zA-Z_]/.test(ch)) { let name = ""; while (i < input.length && /[a-zA-Z0-9_]/.test(input[i])) { name += input[i++]; } if (/^[a-zA-Z]+$/.test(name) && name.length > 1 && !isFunction(name)) { for (let j = 0; j < name.length; j++) { tokens.push(name[j]); if (j < name.length - 1) tokens.push("*"); } } else { tokens.push(name); } if (i < input.length && input[i] === "(") { if (!isFunction(name)) { if (tokens[tokens.length - 1] !== "*") tokens.push("*"); } } else if (i < input.length && (/[a-zA-Z_]/.test(input[i]))) { if (tokens[tokens.length - 1] !== "*") tokens.push("*"); } } else { throw new Error(`Chalkboard.comp.parse: Unexpected character ${ch}`); } } return tokens; }; const parseTokens = (tokens: string[]): { type: string, [key: string]: any } => { let pos = 0; const peek = (): string => tokens[pos] || ""; const consume = (token?: string): string => { if (token && tokens[pos] !== token) throw new Error(`Chalkboard.comp.parse: Expected token '${token}' but found '${tokens[pos]}'`); return tokens[pos++]; }; const parseExpression = (): { type: string, [key: string]: any } => parseAdditive(); const parseAdditive = (): { type: string, [key: string]: any } => { let node = parseMultiplicative(); while (peek() === "+" || peek() === "-") { const op = consume(); const right = parseMultiplicative(); node = { type: op === "+" ? "add" : "sub", left: node, right }; } return node; }; const parseMultiplicative = (): { type: string, [key: string]: any } => { let node = parseUnary(); while (peek() === "*" || peek() === "/") { const op = consume(); const right = parseUnary(); node = { type: op === "*" ? "mul" : "div", left: node, right }; } return node; }; const parseUnary = (): { type: string, [key: string]: any } => { if (peek() === "-") { consume("-"); return { type: "neg", expr: parseExponent() }; } else if (peek() === "+") { consume("+"); return parseExponent(); } return parseExponent(); }; const parseExponent = (): { type: string, [key: string]: any } => { let node = parsePrimary(); if (peek() === "^") { consume("^"); const right = parseExponentUnary(); node = { type: "pow", base: node, exponent: right }; } return node; }; const parseExponentUnary = (): { type: string, [key: string]: any } => { if (peek() === "-") { consume("-"); return { type:"neg", expr: parseExponentUnary() }; } if (peek() === "+") { consume("+"); return parseExponentUnary(); } return parseExponent(); }; const parsePrimary = (): { type: string, [key: string]: any } => { const token = peek(); if (/^-?[0-9]/.test(token) || /^-?\.[0-9]/.test(token)) { consume(); return { type: "num", value: parseFloat(token) }; } if (token === "i") { consume(); return { type: "complex", a: 0, b: 1 }; } if (/^[a-zA-Z_]/.test(token)) { const name = consume(); if (peek() === "(") { consume("("); const args: { type: string, [key: string]: any }[] = []; if (peek() !== ")") { args.push(parseExpression()); while (peek() === ",") { consume(","); args.push(parseExpression()); } } consume(")"); return { type: "func", name, args }; } return { type: "var", name }; } if (token === "(") { consume("("); const node = parseExpression(); consume(")"); return node; } throw new Error(`Chalkboard.comp.parse: Unexpected token ${token}`); }; const ast = parseExpression(); if (pos < tokens.length) throw new Error(`Chalkboard.comp.parse: Unexpected token ${tokens[pos]}`); return ast; }; const evaluateNode = (node: { type: string, [key: string]: any }, values: Record): ChalkboardComplex => { switch (node.type) { case "num": { return Chalkboard.comp.init(node.value, 0); } case "complex": { return Chalkboard.comp.init(node.a, node.b); } case "var": { const varname = node.name; if (varname in values) return values[varname]; throw new Error(`Chalkboard.comp.parse: Variable '${varname}' not defined in values`); } case "add": { return Chalkboard.comp.add(evaluateNode(node.left, values), evaluateNode(node.right, values)) as ChalkboardComplex; } case "sub": { return Chalkboard.comp.sub(evaluateNode(node.left, values), evaluateNode(node.right, values)) as ChalkboardComplex; } case "mul": { return Chalkboard.comp.mul(evaluateNode(node.left, values), evaluateNode(node.right, values)) as ChalkboardComplex; } case "div": { return Chalkboard.comp.div(evaluateNode(node.left, values), evaluateNode(node.right, values)) as ChalkboardComplex; } case "pow": { const base = evaluateNode(node.base, values); const exponent = evaluateNode(node.exponent, values); if (exponent.b === 0) { return Chalkboard.comp.pow(base, exponent.a) as ChalkboardComplex; } else { throw new Error("Chalkboard.comp.parse: Complex exponentiation with complex exponent not supported"); } } case "neg": { return Chalkboard.comp.negate(evaluateNode(node.expr, values)) as ChalkboardComplex; } case "func": { const funcName = node.name.toLowerCase(); const args = node.args.map((arg: { type: string, [key: string]: any }) => evaluateNode(arg, values)); if (Chalkboard.REGISTRY && Chalkboard.REGISTRY[funcName]) { try { const realArgs = args.map((arg: { type: string, [key: string]: any }) => { if (arg.b !== 0) throw new Error("Complex argument in real function"); return arg.a; }); const result = Chalkboard.REGISTRY[funcName](...realArgs); return Chalkboard.comp.init(result, 0); } catch (e) {} } switch (funcName) { case "abs": { return Chalkboard.comp.absolute(args[0]) as ChalkboardComplex; } case "conj": case "conjugate": { return Chalkboard.comp.conjugate(args[0]) as ChalkboardComplex; } case "mag": { return Chalkboard.comp.init(Chalkboard.comp.mag(args[0]), 0); } case "arg": { return Chalkboard.comp.init(Chalkboard.comp.arg(args[0]), 0); } case "re": { return Chalkboard.comp.init(Chalkboard.comp.Re(args[0]) as number, 0); } case "im": { return Chalkboard.comp.init(Chalkboard.comp.Im(args[0]) as number, 0); } case "ln": { return Chalkboard.comp.ln(args[0]); } case "sin": { return Chalkboard.comp.sin(args[0]) as ChalkboardComplex; } case "cos": { return Chalkboard.comp.cos(args[0]) as ChalkboardComplex; } case "tan": { return Chalkboard.comp.tan(args[0]) as ChalkboardComplex; } case "exp": { return Chalkboard.comp.exp(args[0]) as ChalkboardComplex; } case "invert": { return Chalkboard.comp.invert(args[0]); } case "sq": { return Chalkboard.comp.sq(args[0]) as ChalkboardComplex; } case "sqrt": { return Chalkboard.comp.sqrt(args[0]) as ChalkboardComplex; } case "pow": { if (args.length < 2) throw new Error("Chalkboard.comp.parse: Function pow requires two arguments"); return Chalkboard.comp.pow(args[0], args[1].a) as ChalkboardComplex; } case "root": { if (args.length < 2) throw new Error("Chalkboard.comp.parse: Function root requires two arguments"); const index = args[1].a; if (!Number.isInteger(index) || index <= 0) throw new Error("Chalkboard.comp.parse: Root index must be a positive integer"); return Chalkboard.comp.root(args[0], index)[0]; } default: { throw new Error(`Chalkboard.comp.parse: Unknown function ${node.name}`); } } } } throw new Error(`Chalkboard.comp.parse: Unknown node type ${node.type}`); }; const needsParensInPow = (z: { a: number, b: number }): boolean => { if (z.b === 0) return false; if (z.a === 0 && (z.b === 1 || z.b === -1)) return false; return true; }; const nodeToString = (node: { type: string, [key: string]: any }): string => { switch (node.type) { case "num": { return node.value.toString(); } case "complex": { if (node.a === 0 && node.b === 1) return "i"; if (node.a === 0 && node.b === -1) return "-i"; if (node.a === 0) return `${node.b}i`; if (node.b === 0) return node.a.toString(); if (node.b === 1) return `${node.a} + i`; if (node.b === -1) return `${node.a} - i`; return node.b > 0 ? `${node.a} + ${node.b}i` : `${node.a} - ${-node.b}i`; } case "var": { return node.name; } case "add": { const rightStr = nodeToString(node.right); if (rightStr.startsWith("-")) return `${nodeToString(node.left)} - ${rightStr.slice(1)}`; return `${nodeToString(node.left)} + ${rightStr}`; } case "sub": { const rightStr = node.right.type === "add" || node.right.type === "sub" ? `(${nodeToString(node.right)})` : nodeToString(node.right); return `${nodeToString(node.left)} - ${rightStr}`; } case "mul": { if (node.left.type === "num" && node.left.value === 1) return nodeToString(node.right); if (node.right.type === "num" && node.right.value === 1) return nodeToString(node.left); const leftMul = (node.left.type === "add" || node.left.type === "sub") ? `(${nodeToString(node.left)})` : nodeToString(node.left); const rightMul = (node.right.type === "add" || node.right.type === "sub") ? `(${nodeToString(node.right)})` : nodeToString(node.right); if (node.left.type === "num" && node.left.value === -1 && node.right.type === "var") return `-${nodeToString(node.right)}`; if (node.left.type === "num" && node.left.value === -1 && node.right.type === "pow") return `-${nodeToString(node.right)}`; if ((node.left.type === "num" || node.left.type === "complex") && (node.right.type === "var" || (node.right.type === "complex" && node.right.a === 0 && node.right.b === 1))) { return `${leftMul}${rightMul}`; } else { return `${leftMul} * ${rightMul}`; } } case "div": { const powNode = { type: "pow", base: node.right, exponent: { type: "num", value: -1 } }; const mulNode = { type: "mul", left: node.left, right: powNode }; return nodeToString(mulNode); } case "pow": { const baseIsComplex = node.base?.type === "complex"; const baseStrRaw = nodeToString(node.base); const baseStr = baseIsComplex && needsParensInPow(node.base) ? `(${baseStrRaw})` : (node.base.type !== "num" && node.base.type !== "var" && node.base.type !== "complex") ? `(${baseStrRaw})` : baseStrRaw; const expStr = (node.exponent.type !== "num" && node.exponent.type !== "var" && node.exponent.type !== "complex") ? `(${nodeToString(node.exponent)})` : nodeToString(node.exponent); return `${baseStr}^${expStr}`; } case "neg": { const exprStr = (node.expr.type !== "num" && node.expr.type !== "var" && node.expr.type !== "complex") ? `(${nodeToString(node.expr)})` : nodeToString(node.expr); return `-${exprStr}`; } case "func": { return `${node.name}(${node.args.map((arg: { type: string, [key: string]: any }) => nodeToString(arg)).join(", ")})`; } } return ""; }; const nodeToLaTeX = (node: { type: string, [key: string]: any }): string => { switch (node.type) { case "num": { return node.value.toString(); } case "complex": { const re = node.a !== 0 ? node.a.toString() : ""; const im = node.b !== 0 ? (node.b === 1 ? "i" : node.b === -1 ? "-i" : `${node.b}i`) : ""; if (re && im) { return node.b > 0 ? `${re} + ${im}` : `${re} - ${im.slice(1)}`; } return re || im || "0"; } case "var": { return node.name; } case "add": { const right = nodeToLaTeX(node.right); if (right.startsWith("-")) return `${nodeToLaTeX(node.left)} - ${right.slice(1)}`; return `${nodeToLaTeX(node.left)} + ${right}`; } case "sub": { const right = nodeToLaTeX(node.right); if (right.startsWith("-")) return `${nodeToLaTeX(node.left)} + ${right.slice(1)}`; return `${nodeToLaTeX(node.left)} - ${right}`; } case "mul": { const isAtomicLaTeX = (n: any): boolean => n.type === "num" || n.type === "var" || n.type === "complex" || n.type === "pow" || n.type === "func"; const wrapIfNeeded = (n: any): string => { const s = nodeToLaTeX(n); if (n.type === "add" || n.type === "sub") return `\\left(${s}\\right)`; return s; }; const left = wrapIfNeeded(node.left); const right = wrapIfNeeded(node.right); if (isAtomicLaTeX(node.left) && isAtomicLaTeX(node.right)) return `${left}${right}`; return `${left} \\cdot ${right}`; } case "div": { return `\\frac{${nodeToLaTeX(node.left)}}{${nodeToLaTeX(node.right)}}`; } case "pow": { return `${nodeToLaTeX(node.base)}^{${nodeToLaTeX(node.exponent)}}`; } case "neg": { return `-${nodeToLaTeX(node.expr)}`; } case "func": { return `\\mathrm{${node.name}}\\left(${node.args.map(nodeToLaTeX).join(", ")}\\right)`; } default: { throw new Error(`Chalkboard.comp.parse: Unknown node type ${node.type}`); } } }; const areEqualVars = (a: { type: string, [key: string]: any }, b: { type: string, [key: string]: any }): boolean => { if (a.type === "var" && b.type === "var") return a.name === b.name; if (a.type === "complex" && b.type === "complex") return a.a === b.a && a.b === b.b; return JSON.stringify(a) === JSON.stringify(b); }; const simplifyNode = (node: { type: string, [key: string]: any }): { type: string, [key: string]: any } => { switch (node.type) { case "num": { return { type: "complex", a: node.value, b: 0 }; } case "complex": { return node; } case "var": { return node; } case "add": { const leftAdd = simplifyNode(node.left); const rightAdd = simplifyNode(node.right); if (leftAdd.type === "complex" && rightAdd.type === "complex") return { type: "complex", a: leftAdd.a + rightAdd.a, b: leftAdd.b + rightAdd.b }; if (leftAdd.type === "complex" && leftAdd.a === 0 && leftAdd.b === 0) return rightAdd; if (rightAdd.type === "complex" && rightAdd.a === 0 && rightAdd.b === 0) return leftAdd; if (areEqualVars(leftAdd, rightAdd)) return { type: "mul", left: { type: "num", value: 2 }, right: leftAdd }; return { type: "add", left: leftAdd, right: rightAdd }; } case "sub": { const leftSub = simplifyNode(node.left); const rightSub = simplifyNode(node.right); if (leftSub.type === "complex" && rightSub.type === "complex") return { type: "complex", a: leftSub.a - rightSub.a, b: leftSub.b - rightSub.b }; if (rightSub.type === "complex" && rightSub.a === 0 && rightSub.b === 0) return leftSub; if (leftSub.type === "complex" && leftSub.a === 0 && leftSub.b === 0) return { type: "neg", expr: rightSub }; if (areEqualVars(leftSub, rightSub)) return { type: "complex", a: 0, b: 0 }; return { type: "sub", left: leftSub, right: rightSub }; } case "mul": { const leftMul = simplifyNode(node.left); const rightMul = simplifyNode(node.right); if ((leftMul.type === "add" || leftMul.type === "sub") && (rightMul.type === "add" || rightMul.type === "sub")) { const extractTerms = (node: any): any[] => { if (node.type === "add") { return [...extractTerms(node.left), ...extractTerms(node.right)]; } else if (node.type === "sub") { const rightTerms = extractTerms(node.right).map(term => ({ type: "neg", expr: term })); return [...extractTerms(node.left), ...rightTerms]; } else { return [node]; } }; const leftTerms = extractTerms(leftMul); const rightTerms = extractTerms(rightMul); const products = []; for (const leftTerm of leftTerms) { for (const rightTerm of rightTerms) { if (leftTerm.type === "neg" && rightTerm.type === "neg") { products.push(simplifyNode({ type: "mul", left: leftTerm.expr, right: rightTerm.expr })); } else if (leftTerm.type === "neg") { products.push(simplifyNode({ type: "neg", expr: { type: "mul", left: leftTerm.expr, right: rightTerm } })); } else if (rightTerm.type === "neg") { products.push(simplifyNode({ type: "neg", expr: { type: "mul", left: leftTerm, right: rightTerm.expr } })); } else { products.push(simplifyNode({ type: "mul", left: leftTerm, right: rightTerm })); } } } let result = products[0]; for (let i = 1; i < products.length; i++) { result = { type: "add", left: result, right: products[i] }; } return simplifyNode(result); } if (leftMul.type === "complex" && rightMul.type === "complex") return { type: "complex", a: leftMul.a * rightMul.a - leftMul.b * rightMul.b, b: leftMul.a * rightMul.b + leftMul.b * rightMul.a }; if ((leftMul.type === "complex" && leftMul.a === 0 && leftMul.b === 0) || (rightMul.type === "complex" && rightMul.a === 0 && rightMul.b === 0)) return { type: "complex", a: 0, b: 0 }; if (leftMul.type === "complex" && leftMul.a === 1 && leftMul.b === 0) return rightMul; if (rightMul.type === "complex" && rightMul.a === 1 && rightMul.b === 0) return leftMul; if (leftMul.type === "complex" && leftMul.a === 0 && leftMul.b === 1 && rightMul.type === "complex") return { type: "complex", a: -rightMul.b, b: rightMul.a }; return { type: "mul", left: leftMul, right: rightMul }; } case "div": { const leftDiv = simplifyNode(node.left); const rightDiv = simplifyNode(node.right); if (leftDiv.type === "add" || leftDiv.type === "sub") { const left = { type: "div", left: leftDiv.left, right: JSON.parse(JSON.stringify(rightDiv)) }; const right = { type: "div", left: leftDiv.right, right: JSON.parse(JSON.stringify(rightDiv)) }; return { type: leftDiv.type, left: simplifyNode(left), right: simplifyNode(right) }; } if (leftDiv.type === "complex" && rightDiv.type === "complex") { const denominator = rightDiv.a * rightDiv.a + rightDiv.b * rightDiv.b; if (denominator === 0) throw new Error("Chalkboard.comp.parse: Division by zero."); return { type: "complex", a: (leftDiv.a * rightDiv.a + leftDiv.b * rightDiv.b) / denominator, b: (leftDiv.b * rightDiv.a - leftDiv.a * rightDiv.b) / denominator }; } if (rightDiv.type === "complex" && rightDiv.a === 1 && rightDiv.b === 0) return leftDiv; if (leftDiv.type === "complex" && leftDiv.a === 0 && leftDiv.b === 0) return { type: "complex", a: 0, b: 0 }; return { type: "div", left: leftDiv, right: rightDiv }; } case "pow": { const base = simplifyNode(node.base); const exponent = simplifyNode(node.exponent); return { type: "pow", base, exponent }; } case "neg": { const expr = simplifyNode(node.expr); if (expr.type === "complex") return { type: "complex", a: -expr.a, b: -expr.b }; if (expr.type === "neg") return expr.expr; return { type: "neg", expr }; } case "func": { const args = node.args.map((arg: { type: string, [key: string]: any }) => simplifyNode(arg)); return { type: "func", name: node.name, args }; } } return node; }; const isRealOnly = (node: { type: string, [key: string]: any }): boolean => { switch (node.type) { case "num": return true; case "var": return true; case "complex": return node.b === 0; case "neg": return isRealOnly(node.expr); case "add": case "sub": case "mul": case "div": return isRealOnly(node.left) && isRealOnly(node.right); case "pow": return isRealOnly(node.base) && isRealOnly(node.exponent); case "func": return node.args.every(isRealOnly); default: return false; } }; const realNum = (n: number): any => ({ type: "num", value: n }); const realAdd = (l: any, r: any): any => ({ type: "add", left: l, right: r }); const realSub = (l: any, r: any): any => ({ type: "sub", left: l, right: r }); const realMul = (l: any, r: any): any => ({ type: "mul", left: l, right: r }); const realDiv = (l: any, r: any): any => ({ type: "div", left: l, right: r }); const realPow = (b: any, e: any): any => ({ type: "pow", base: b, exponent: e }); const realNeg = (x: any): any => ({ type: "neg", expr: x }); const realNodeToString = (node: any): string => { switch (node.type) { case "num": return node.value.toString(); case "var": return node.name; case "add": return `${realNodeToString(node.left)} + ${realNodeToString(node.right)}`; case "sub": return `${realNodeToString(node.left)} - ${realNodeToString(node.right)}`; case "mul": { const L = (node.left.type === "add" || node.left.type === "sub") ? `(${realNodeToString(node.left)})` : realNodeToString(node.left); const R = (node.right.type === "add" || node.right.type === "sub") ? `(${realNodeToString(node.right)})` : realNodeToString(node.right); return `${L} * ${R}`; } case "div": { const L = (node.left.type === "add" || node.left.type === "sub") ? `(${realNodeToString(node.left)})` : realNodeToString(node.left); const R = (node.right.type === "add" || node.right.type === "sub") ? `(${realNodeToString(node.right)})` : realNodeToString(node.right); return `${L} / ${R}`; } case "pow": { const B = (node.base.type === "num" || node.base.type === "var") ? realNodeToString(node.base) : `(${realNodeToString(node.base)})`; const E = (node.exponent.type === "num" || node.exponent.type === "var") ? realNodeToString(node.exponent) : `(${realNodeToString(node.exponent)})`; return `${B}^${E}`; } case "neg": { const inner = (node.expr.type === "num" || node.expr.type === "var") ? realNodeToString(node.expr) : `(${realNodeToString(node.expr)})`; return `-${inner}`; } default: { throw new Error(`Chalkboard.comp.parse: Unsupported real-node type ${node.type}`); } } }; const simplifyRealString = (s: string): string => { return Chalkboard.real.parse(s, { roundTo: config.roundTo, returnAST: false, returnJSON: false, returnLaTeX: false }) as string; }; const toReIm = (node: any): { re: any, im: any } => { switch (node.type) { case "num": { return { re: realNum(node.value), im: realNum(0) }; } case "var": { if (node.name === "i") return { re: realNum(0), im: realNum(1) }; return { re: { type: "var", name: node.name }, im: realNum(0) }; } case "complex": { return { re: realNum(node.a), im: realNum(node.b) }; } case "neg": { const p = toReIm(node.expr); return { re: realNeg(p.re), im: realNeg(p.im) }; } case "add": { const L = toReIm(node.left); const R = toReIm(node.right); return { re: realAdd(L.re, R.re), im: realAdd(L.im, R.im) }; } case "sub": { const L = toReIm(node.left); const R = toReIm(node.right); return { re: realSub(L.re, R.re), im: realSub(L.im, R.im) }; } case "mul": { const L = toReIm(node.left); const R = toReIm(node.right); const ac = realMul(L.re, R.re); const bd = realMul(L.im, R.im); const ad = realMul(L.re, R.im); const bc = realMul(L.im, R.re); return { re: realSub(ac, bd), im: realAdd(ad, bc) }; } case "div": { const L = toReIm(node.left); const R = toReIm(node.right); const c2 = realPow(R.re, realNum(2)); const d2 = realPow(R.im, realNum(2)); const denom = realAdd(c2, d2); const ac = realMul(L.re, R.re); const bd = realMul(L.im, R.im); const bc = realMul(L.im, R.re); const ad = realMul(L.re, R.im); const reNum = realAdd(ac, bd); const imNum = realSub(bc, ad); return { re: realDiv(reNum, denom), im: realDiv(imNum, denom) }; } case "pow": { const expParts = toReIm(node.exponent); const expImStr = simplifyRealString(realNodeToString(expParts.im)); if (expImStr !== "0") throw new Error("Chalkboard.comp.parse: Complex exponent not supported in symbolic splitting."); const expReStr = simplifyRealString(realNodeToString(expParts.re)); const n = Number(expReStr); if (!Number.isInteger(n)) throw new Error("Chalkboard.comp.parse: Non-integer exponent not supported in symbolic splitting."); const baseParts = toReIm(node.base); let re = realNum(1); let im = realNum(0); const steps = Math.abs(n); for (let i = 0; i < steps; i++) { const a = re; const b = im; const c = baseParts.re; const d = baseParts.im; const newRe = realSub(realMul(a, c), realMul(b, d)); const newIm = realAdd(realMul(a, d), realMul(b, c)); re = newRe; im = newIm; } if (n < 0) { const denom = realAdd(realPow(re, realNum(2)), realPow(im, realNum(2))); const reInv = realDiv(re, denom); const imInv = realNeg(realDiv(im, denom)); return { re: reInv, im: imInv }; } return { re, im }; } case "func": { throw new Error(`Chalkboard.comp.parse: Symbolic splitting for function '${node.name}' not supported.`); } } throw new Error(`Chalkboard.comp.parse: Unsupported node type '${node.type}' in symbolic splitting.`); }; const combineReImStrings = (reStr: string, imStr: string): string => { const reS = reStr.trim(); const imS = imStr.trim(); const isZero = (s: string): boolean => s === "0" || s === "0.0"; const needsParens = (s: string): boolean => { return s.includes(" + ") || s.includes(" - "); }; if (isZero(imS)) return reS; if (isZero(reS)) { if (imS === "1") return "i"; if (imS === "-1") return "-i"; return needsParens(imS) ? `(${imS})i` : `${imS}i`; } const imWithI = imS === "1" ? "i" : imS === "-1" ? "-i" : needsParens(imS) ? `(${imS})i` : `${imS}i`; if (!needsParens(imS) && imS.startsWith("-")) { const mag = imS.slice(1); if (mag === "1") return `${reS} - i`; return `${reS} - ${mag}i`; } else { if (imWithI.startsWith("-")) return `${reS} - ${imWithI.slice(1)}`; return `${reS} + ${imWithI}`; } }; try { const tokens = tokenize(expr); const ast = parseTokens(tokens); const hasVars = (node: { type: string, [key: string]: any }): boolean => { switch (node.type) { case "var": return true; case "num": return false; case "complex": return false; case "neg": return hasVars(node.expr); case "add": case "sub": case "mul": case "div": return hasVars(node.left) || hasVars(node.right); case "pow": return hasVars(node.base) || hasVars(node.exponent); case "func": return node.args.some(hasVars); default: return false; } }; if (!config.returnAST && !config.returnJSON) { const values = config.values || {}; if ((config.values && Object.keys(config.values).length > 0) || !hasVars(ast)) { try { let result = evaluateNode(ast, values); if (config.roundTo !== undefined) { result = Chalkboard.comp.init( Chalkboard.numb.roundTo(result.a, config.roundTo), Chalkboard.numb.roundTo(result.b, config.roundTo) ); } if (config.returnLaTeX) return nodeToLaTeX({ type: "complex", a: result.a, b: result.b }); return result; } catch (e) { } } } if (isRealOnly(ast)) { return Chalkboard.real.parse(expr, { roundTo: config.roundTo, returnAST: config.returnAST, returnJSON: config.returnJSON, returnLaTeX: config.returnLaTeX }) as any; } if (!config.returnAST && !config.returnJSON) { try { const parts = toReIm(ast); const reExprStr = realNodeToString(parts.re); const imExprStr = realNodeToString(parts.im); if (reExprStr.includes("i") || imExprStr.includes("i")) { throw new Error("Chalkboard.comp.parse: Internal error: 'i' leaked into real split."); } const reSimpl = simplifyRealString(reExprStr); const imSimpl = simplifyRealString(imExprStr); if (config.returnLaTeX) { const reTex = Chalkboard.real.parse(reExprStr, { returnLaTeX: true, roundTo: config.roundTo }) as string; const imTex = Chalkboard.real.parse(imExprStr, { returnLaTeX: true, roundTo: config.roundTo }) as string; if (imTex.trim() === "0") return reTex; if (reTex.trim() === "0") return `${imTex}i`; return `${reTex} + ${String.raw`\left(${imTex}\right)`}i`; } return combineReImStrings(reSimpl, imSimpl); } catch (e) { } } let simplified = simplifyNode(ast); simplified = simplifyNode(simplified); if (config.roundTo !== undefined) { const roundNodes = (node: { type: string, [key: string]: any }): { type: string, [key: string]: any } => { if (node.type === "num") return { ...node, value: Chalkboard.numb.roundTo(node.value, config.roundTo!) }; if (node.type === "complex") return { ...node, a: Chalkboard.numb.roundTo(node.a, config.roundTo!), b: Chalkboard.numb.roundTo(node.b, config.roundTo!) }; const n = Object.keys(node).length; for (let i = 0; i < n; i++) { const key = Object.keys(node)[i]; if (key !== "type" && node[key] && typeof node[key] === "object" && "type" in node[key]) node[key] = roundNodes(node[key]); } return node; }; simplified = roundNodes(simplified); } if (config.returnAST) return simplified; if (config.returnJSON) return JSON.stringify(simplified); if (config.returnLaTeX) { return nodeToLaTeX(simplified); } return nodeToString(simplified); } catch (err) { if (err instanceof Error) { throw new Error(`Chalkboard.comp.parse: Error parsing complex expression ${err.message}`); } else { throw new Error(`Chalkboard.comp.parse: Error parsing complex expression ${String(err)}`); } } }; /** * Calculates the exponentiation of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @param {number} num - The exponent * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns -11 + 2i * const pow = Chalkboard.comp.pow(Chalkboard.comp.init(2, 1), 3); */ export const pow = (comp: ChalkboardComplex | number | ChalkboardFunction, num: number): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; const mag = Chalkboard.comp.mag(z); const arg = Chalkboard.comp.arg(z); return Chalkboard.comp.init( (Chalkboard.real.pow(mag, num) as number) * Chalkboard.trig.cos(num * arg), (Chalkboard.real.pow(mag, num) as number) * Chalkboard.trig.sin(num * arg) ); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.pow: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as ((a: number, b: number) => number)[]; const g = [ (a: number, b: number) => { const mag = Chalkboard.real.sqrt(f[0](a, b) * f[0](a, b) + f[1](a, b) * f[1](a, b)); const arg = Chalkboard.trig.arctan2(f[1](a, b), f[0](a, b)); return (Chalkboard.real.pow(mag, num) as number) * Chalkboard.trig.cos(num * arg); }, (a: number, b: number) => { const mag = Chalkboard.real.sqrt(f[0](a, b) * f[0](a, b) + f[1](a, b) * f[1](a, b)); const arg = Chalkboard.trig.arctan2(f[1](a, b), f[0](a, b)); return (Chalkboard.real.pow(mag, num) as number) * Chalkboard.trig.sin(num * arg); } ]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.pow: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Prints a complex number in the console. * @param {ChalkboardComplex} comp - The complex number * @returns {void} * @example * // Prints "2 + 3i" in the console * Chalkboard.comp.print(Chalkboard.comp.init(2, 3)); */ export const print = (comp: ChalkboardComplex): void => { console.log(Chalkboard.comp.toString(comp)); }; /** * Initializes a random complex number. * @param {number} [inf=0] - The lower bound * @param {number} [sup=1] - The upper bound * @returns {ChalkboardComplex} * @example * // Returns a random complex number with real and imaginary parts between 0 and 1 * const z = Chalkboard.comp.random(); */ export const random = (inf: number = 0, sup: number = 1): ChalkboardComplex => { return Chalkboard.comp.init(Chalkboard.numb.random(inf, sup), Chalkboard.numb.random(inf, sup)); }; /** * Returns the real part of a complex function or a complex number. * @param {ChalkboardFunction | ChalkboardComplex} funcORcomp - The complex function or complex number * @returns {Function | ChalkboardComplex} * @example * // Returns 2 * const re = Chalkboard.comp.Re(Chalkboard.comp.init(2, 3)); */ export const Re = (funcORcomp: ChalkboardFunction | ChalkboardComplex): Function | number => { if (funcORcomp.hasOwnProperty("rule")) { return ((funcORcomp as ChalkboardFunction).rule as ([(a: number, b: number) => number, (a: number, b: number) => number]))[0]; } else { return (funcORcomp as ChalkboardComplex).a; } }; /** * Calculates the reciprocal of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns 0.5 + 0.3333i * const reciprocated = Chalkboard.comp.reciprocate(Chalkboard.comp.init(2, 3)); */ export const reciprocate = (comp: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; return Chalkboard.comp.init(1 / z.a, 1 / z.b); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.reciprocate: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as ((a: number, b: number) => number)[]; const g = [(a: number, b: number) => 1 / f[0](a, b), (a: number, b: number) => 1 / f[1](a, b)]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.reciprocate: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates the nth-root of a complex number. * @param {ChalkboardComplex} comp - The complex number * @param {number} [index=3] - The index * @returns {ChalkboardComplex} * @example * // Returns an array of cube roots of 8 * const roots = Chalkboard.comp.root(Chalkboard.comp.init(8), 3); */ export const root = (comp: ChalkboardComplex, index: number = 3): ChalkboardComplex[] => { const result = []; const r = Chalkboard.comp.mag(comp); const t = Chalkboard.comp.arg(comp); for (let i = 0; i < index; i++) { result.push( Chalkboard.comp.init( Chalkboard.real.root(r, index) * Chalkboard.trig.cos((t + Chalkboard.PI(2 * i)) / index), Chalkboard.real.root(r, index) * Chalkboard.trig.sin((t + Chalkboard.PI(2 * i)) / index) ) ); } return result; }; /** * Calculates the rotation of a complex number. * @param {ChalkboardComplex} comp - The complex number * @param {number} rad - The radians to rotate by * @returns {ChalkboardComplex} * @example * // Returns 0 + 1i (1 rotated by π/2 radians) * const rotated = Chalkboard.comp.rotate(Chalkboard.comp.init(1, 0), Chalkboard.PI(0.5)); */ export const rotate = (comp: ChalkboardComplex, rad: number): ChalkboardComplex => { return Chalkboard.comp.init( Chalkboard.comp.mag(comp) * Chalkboard.trig.cos(Chalkboard.comp.arg(comp) + rad), Chalkboard.comp.mag(comp) * Chalkboard.trig.sin(Chalkboard.comp.arg(comp) + rad) ); }; /** * Calculates the rounding of a complex number. * @param {ChalkboardComplex} comp - The complex number * @returns {ChalkboardComplex} * @example * // Returns 2 + 3i * const rounded = Chalkboard.comp.round(Chalkboard.comp.init(1.9, 3.4)); */ export const round = (comp: ChalkboardComplex): ChalkboardComplex => { return Chalkboard.comp.init(Math.round(comp.a), Math.round(comp.b)); }; /** * Calculates the scalar multiplication of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @param {number} num - The scalar * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns 6 + 9i * const scaled = Chalkboard.comp.scl(Chalkboard.comp.init(2, 3), 3); */ export const scl = (comp: ChalkboardComplex | number | ChalkboardFunction, num: number): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; return Chalkboard.comp.init(z.a * num, z.b * num); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.scl: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as ((a: number, b: number) => number)[]; const g = [(a: number, b: number) => f[0](a, b) * num, (a: number, b: number) => f[1](a, b) * num]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.scl: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates the sine of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns 1 * const z = Chalkboard.comp.random(); * const sin = Chalkboard.comp.sin(z); * const cos = Chalkboard.comp.cos(z); * const w = Chalkboard.comp.add(Chalkboard.comp.sq(sin), Chalkboard.comp.sq(cos)); */ export const sin = (comp: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; return Chalkboard.comp.init( Chalkboard.trig.sin(z.a) * Chalkboard.trig.cosh(z.b), Chalkboard.trig.cos(z.a) * Chalkboard.trig.sinh(z.b) ); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.sin: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; return Chalkboard.comp.define( (a: number, b: number) => { const re = f[0](a, b); const im = f[1](a, b); return Chalkboard.trig.sin(re) * Chalkboard.trig.cosh(im); }, (a: number, b: number) => { const re = f[0](a, b); const im = f[1](a, b); return Chalkboard.trig.cos(re) * Chalkboard.trig.sinh(im); } ); } throw new TypeError("Chalkboard.comp.sin: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates the slope of a complex number. * @param {ChalkboardComplex} comp - The complex number * @returns {number} * @example * // Returns 1.5 * const m = Chalkboard.comp.slope(Chalkboard.comp.init(2, 3)); */ export const slope = (comp: ChalkboardComplex): number => { return comp.b / comp.a; }; /** * Calculates the square of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns -5 + 12i * const sq = Chalkboard.comp.sq(Chalkboard.comp.init(3, 2)); */ export const sq = (comp: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; return Chalkboard.comp.init(z.a * z.a - z.b * z.b, 2 * z.a * z.b); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.sq: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as ((a: number, b: number) => number)[]; const g = [(a: number, b: number) => f[0](a, b) * f[0](a, b) - f[1](a, b) * f[1](a, b), (a: number, b: number) => 2 * f[0](a, b) * f[1](a, b)]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.sq: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates the square root of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns 2 + 0.5i * const sqrt = Chalkboard.comp.sqrt(Chalkboard.comp.init(4, 2)); */ export const sqrt = (comp: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp === "number") comp = Chalkboard.comp.init(comp, 0); if (comp.hasOwnProperty("a") && comp.hasOwnProperty("b")) { const z = comp as ChalkboardComplex; return Chalkboard.comp.init( Chalkboard.real.sqrt((z.a + Chalkboard.real.sqrt(z.a * z.a + z.b * z.b)) / 2), (Chalkboard.numb.sgn(z.b) as 0 | 1 | -1) * Chalkboard.real.sqrt((-z.a + Chalkboard.real.sqrt(z.a * z.a + z.b * z.b)) / 2) ); } else if (comp.hasOwnProperty("rule")) { if ((comp as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.sqrt: Property 'field' of 'comp' must be 'comp'."); const f = (comp as ChalkboardFunction).rule as ((a: number, b: number) => number)[]; const g = [ (a: number, b: number) => { const re = f[0](a, b); const im = f[1](a, b); return Chalkboard.real.sqrt((re + Chalkboard.real.sqrt(re * re + im * im)) / 2); }, (a: number, b: number) => { const re = f[0](a, b); const im = f[1](a, b); return (Chalkboard.numb.sgn(im) as 0 | 1 | -1) * Chalkboard.real.sqrt((-re + Chalkboard.real.sqrt(re * re + im * im)) / 2); } ]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.sqrt: Parameter 'comp' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates the subtraction of two complex numbers or functions. * @param {ChalkboardComplex | number | ChalkboardFunction} comp1 - The first complex number or function * @param {ChalkboardComplex | number | ChalkboardFunction} comp2 - The second complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns 1 - 1i * const difference = Chalkboard.comp.sub(Chalkboard.comp.init(3, 2), Chalkboard.comp.init(2, 3)); */ export const sub = (comp1: ChalkboardComplex | number | ChalkboardFunction, comp2: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { if (typeof comp1 === "number") comp1 = Chalkboard.comp.init(comp1, 0); if (typeof comp2 === "number") comp2 = Chalkboard.comp.init(comp2, 0); if (comp1.hasOwnProperty("a") && comp1.hasOwnProperty("b") && comp2.hasOwnProperty("a") && comp2.hasOwnProperty("b")) { const z1 = comp1 as ChalkboardComplex; const z2 = comp2 as ChalkboardComplex; return Chalkboard.comp.init(z1.a - z2.a, z1.b - z2.b); } else if (comp1.hasOwnProperty("rule") || comp2.hasOwnProperty("rule")) { if ((comp1 as ChalkboardFunction).field !== "comp" || (comp2 as ChalkboardFunction).field !== "comp") throw new TypeError("Chalkboard.comp.sub: Properties 'field' of 'comp1' and 'comp2' must be 'comp'."); const f1 = (comp1 as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; const f2 = (comp2 as ChalkboardFunction).rule as [(a: number, b: number) => number, (a: number, b: number) => number]; const g = [(a: number, b: number) => f1[0](a, b) - f2[0](a, b), (a: number, b: number) => f1[1](a, b) - f2[1](a, b)]; return Chalkboard.comp.define(...g); } throw new TypeError("Chalkboard.comp.sub: Parameters 'comp1' and 'comp2' must be of type ChalkboardComplex, number, or ChalkboardFunction."); }; /** * Calculates the tangent of a complex number or function. * @param {ChalkboardComplex | number | ChalkboardFunction} comp - The complex number or function * @returns {ChalkboardComplex | ChalkboardFunction} * @example * // Returns ((e² - 1) / (e² + 1))i * const z = Chalkboard.comp.tan(Chalkboard.I()); */ export const tan = (comp: ChalkboardComplex | number | ChalkboardFunction): ChalkboardComplex | ChalkboardFunction => { return Chalkboard.comp.div(Chalkboard.comp.sin(comp), Chalkboard.comp.cos(comp)); }; /** * Converts a complex number to an array. * @param {ChalkboardComplex} comp - The complex number * @returns {number[]} * @example * // Returns [3, 4] * const arr = Chalkboard.comp.toArray(Chalkboard.comp.init(3, 4)); */ export const toArray = (comp: ChalkboardComplex): [number, number] => { return [comp.a, comp.b]; }; /** * Converts a complex number to a matrix. * @param {ChalkboardComplex} comp - The complex number * @returns {ChalkboardMatrix} * @example * // Returns the 2×2 matrix: * // [ * // [3, -4], * // [4, 3] * // ] * const matr = Chalkboard.comp.toMatrix(Chalkboard.comp.init(3, 4)); */ export const toMatrix = (comp: ChalkboardComplex): ChalkboardMatrix => { return Chalkboard.matr.init([comp.a, -comp.b], [comp.b, comp.a]); }; /** * Converts a complex number to a string * @param {ChalkboardComplex} comp - The complex number * @returns {string} * @example * // Returns "2 + 3i" * const str = Chalkboard.comp.toString(Chalkboard.comp.init(2, 3)); */ export const toString = (comp: ChalkboardComplex): string => { if (comp.a === 1 && comp.b === 0) { return "1"; } else if (comp.a === 0 && comp.b === 1) { return "i"; } else if (comp.a === -1 && comp.b === 0) { return "-1"; } else if (comp.a === 0 && comp.b === -1) { return "-i"; } else if (comp.b >= 0) { return comp.a.toString() + " + " + (comp.b === 1 ? "i" : comp.b.toString() + "i"); } else { return comp.a.toString() + " - " + (comp.b === -1 ? "i" : Math.abs(comp.b).toString() + "i"); } }; /** * Converts a complex number to a typed array. * @param {ChalkboardComplex} comp - The complex number * @param {"int8" | "int16" | "int32" | "float32" | "float64" | "bigint64"} [type="float32"] - The type of the typed array, which can be "int8", "int16", "int32", "float32", "float64", or "bigint64" (optional, defaults to "float32") * @returns {Int8Array | Int16Array | Int32Array | Float32Array | Float64Array | BigInt64Array} * @example * // Returns a Float32Array [3, 4] * const z = Chalkboard.comp.init(3, 4); * const zf32 = Chalkboard.comp.toTypedArray(z); */ export const toTypedArray = (comp: ChalkboardComplex, type: "int8" | "int16" | "int32" | "float32" | "float64" | "bigint64" = "float32"): Int8Array | Int16Array | Int32Array | Float32Array | Float64Array | BigInt64Array => { const arr = Chalkboard.comp.toArray(comp); 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 complex number to a vector. * @param {ChalkboardComplex} comp - The complex number * @returns {ChalkboadVector} * @example * // Returns the 2D vector (3, 4) * const v = Chalkboard.comp.toVector(Chalkboard.comp.init(3, 4)); */ export const toVector = (comp: ChalkboardComplex): ChalkboardVector => { return Chalkboard.vect.init(comp.a, comp.b); }; /** * Evaluates a complex function at a complex number * @param {ChalkboardFunction} func - The function * @param {ChalkboardComplex} comp - The complex number * @returns {ChalkboardComplex} * @example * // Returns 3 + 4i * const f = Chalkboard.comp.define((z) => Chalkboard.comp.sq(z)); * const z = Chalkboard.comp.val(f, Chalkboard.comp.init(2, 1)); */ export const val = (func: ChalkboardFunction, comp: ChalkboardComplex): ChalkboardComplex => { if (func.field !== "comp") throw new TypeError("Chalkboard.comp.val: Property 'field' of 'func' must be 'comp'."); const f = func.rule as [(a: number, b: number) => number, (a: number, b: number) => number]; return Chalkboard.comp.init(f[0](comp.a, comp.b), f[1](comp.a, comp.b)); }; } }