/*
Chalkboard - Real 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 real numbers namespace.
* @namespace
*/
export namespace real {
/**
* Calculates the absolute value of a function.
* @param {ChalkboardFunction} func - The function
* @returns {ChalkboardFunction}
*/
export const absolute = (func: ChalkboardFunction): ChalkboardFunction => {
if (func.field !== "real") throw new TypeError("Chalkboard.real.absolute: Property 'field' of 'func' must be 'real'.");
if (func.type.startsWith("scalar")) {
const f = func.rule as ((...x: number[]) => number);
const g = (...x: number[]) => Math.abs(f(...x));
return Chalkboard.real.define(g);
} else if (func.type.startsWith("vector")) {
const f = func.rule as ((...x: number[]) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((...x: number[]) => Math.abs(f[i](...x)));
}
return Chalkboard.real.define(g);
} else if (func.type.startsWith("curve")) {
const f = func.rule as ((t: number) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((t: number) => Math.abs(f[i](t)));
}
return Chalkboard.real.define(g);
} else if (func.type.startsWith("surface")) {
const f = func.rule as ((s: number, t: number) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((s: number, t: number) => Math.abs(f[i](s, t)));
}
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.absolute: Property 'type' of 'func' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
};
/**
* Calculates the addition of two functions.
* @param {ChalkboardFunction} func1 - The first function
* @param {ChalkboardFunction} func2 - The second function
* @returns {ChalkboardFunction}
*/
export const add = (func1: ChalkboardFunction, func2: ChalkboardFunction): ChalkboardFunction => {
if (func1.field !== "real" || func2.field !== "real") throw new TypeError("Chalkboard.real.add: Properties 'field' of 'func1' and 'func2' must be 'real'.");
if (func1.type !== func2.type) throw new TypeError("Chalkboard.real.add: Properties 'type' of 'func1' and 'func2' must be the same.");
if (func1.type.startsWith("scalar")) {
const f1 = func1.rule as ((...x: number[]) => number);
const f2 = func2.rule as ((...x: number[]) => number);
const g = (...x: number[]) => f1(...x) + f2(...x);
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("vector")) {
const f1 = func1.rule as ((...x: number[]) => number)[];
const f2 = func2.rule as ((...x: number[]) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((...x: number[]) => f1[i](...x) + f2[i](...x));
}
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("curve")) {
const f1 = func1.rule as ((t: number) => number)[];
const f2 = func2.rule as ((t: number) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((t: number) => f1[i](t) + f2[i](t));
}
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("surface")) {
const f1 = func1.rule as ((s: number, t: number) => number)[];
const f2 = func2.rule as ((s: number, t: number) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((s: number, t: number) => f1[i](s, t) + f2[i](s, t));
}
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.add: Properties 'type' of 'func1' and 'func2' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
};
/**
* Calculates the composition of two functions.
* @param {ChalkboardFunction} func1 - The outer function
* @param {ChalkboardFunction} func2 - The inner function
* @returns {ChalkboardFunction}
*/
export const compose = (func1: ChalkboardFunction, func2: ChalkboardFunction): ChalkboardFunction => {
if (func1.field !== "real" || func2.field !== "real") throw new TypeError("Chalkboard.real.compose: Properties 'field' of 'func1' and 'func2' must be 'real'.");
if (func1.type !== func2.type) throw new TypeError("Chalkboard.real.compose: Properties 'type' of 'func1' and 'func2' must be the same.");
if (func1.type.startsWith("scalar")) {
const f1 = func1.rule as ((...x: number[]) => number);
const f2 = func2.rule as ((...x: number[]) => number);
const g = (...x: number[]) => f1(f2(...x));
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("vector")) {
const f1 = func1.rule as ((...x: number[]) => number)[];
const f2 = func2.rule as ((...x: number[]) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((...x: number[]) => f1[i](f2[i](...x)));
}
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.compose: Properties 'type' of 'func1' and 'func2' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', or 'vector4d'.");
};
/**
* Defines a mathematical function in the field of real numbers.
* @param {Function | Function[]} rule - The rule(s) of the function
* @returns {ChalkboardFunction}
*/
export const define = (...rule: (((...x: number[]) => number) | ((...x: number[]) => number)[])[]): ChalkboardFunction => {
let f: ((...x: number[]) => number) | ((...x: number[]) => number)[];
let type: "scalar2d" | "scalar3d" | "scalar4d" | "vector2d" | "vector3d" | "vector4d" | "curve2d" | "curve3d" | "curve4d" | "surface3d" = "scalar2d";
if (rule.length === 1 && Array.isArray(rule[0])) {
f = rule[0] as ((...x: number[]) => number)[];
} else if (rule.length > 1) {
f = rule as ((...x: number[]) => number)[];
} else {
f = rule[0] as ((...x: number[]) => number);
}
if (Array.isArray(f)) {
if (f.length === 2) {
if (f[0].length === 1) {
type = "curve2d";
} else if (f[0].length === 2) {
type = "vector2d";
} else {
throw new TypeError("Chalkboard.real.define: Functions in array 'rule' must have one variable to define a parametric curve or two variables to define a vector field.");
}
} else if (f.length === 3) {
if (f[0].length === 1) {
type = "curve3d";
} else if (f[0].length === 2) {
type = "surface3d";
} else if (f[0].length === 3) {
type = "vector3d";
} else {
throw new TypeError("Chalkboard.real.define: Functions in array 'rule' must have one variable to define a parametric curve, two variables to define a parametric surface, or three variables to define a vector field.");
}
} else if (f.length === 4) {
if (f[0].length === 1) {
type = "curve4d";
} else if (f[0].length === 4) {
type = "vector4d";
} else {
throw new TypeError("Chalkboard.real.define: Functions in array 'rule' must have one variable to define a parametric curve or four variables to define a vector field.");
}
}
} else {
if (f.length === 1) {
type = "scalar2d";
} else if (f.length === 2) {
type = "scalar3d";
} else if (f.length === 3) {
type = "scalar4d";
} else {
throw new TypeError("Chalkboard.real.define: Function 'rule' must have one, two, or three variables to define a scalar function.");
}
}
return { rule: f, field: "real", type } as ChalkboardFunction;
};
/**
* Evaluates the Dirac delta function on a number.
* @param {number} num - The number
* @param {number} [edge=0] - The edge of the function
* @param {number} [scl=1] - The scale of the function
* @returns {number}
*/
export const Dirac = (num: number, edge: number = 0, scl: number = 1): number => {
if (num === edge) {
return scl;
} else {
return 0;
}
};
/**
* Calculates the discriminant of a quadratic polynomial given its coefficients and form.
* @param {number} a - The leading coefficient
* @param {number} b - The middle coefficient
* @param {number} c - The last coefficient (the constant)
* @param {"standard" | "vertex"} [form="standard"] - The form of the polynomial, which can be "standard" for standard form or "vertex" for vertex form
* @returns {number}
*/
export const discriminant = (a: number, b: number, c: number, form: "standard" | "vertex" = "standard"): number => {
if (form === "standard") {
return b * b - 4 * a * c;
} else if (form === "vertex") {
return 2 * a * b * (2 * a * b) - 4 * a * c;
} else {
throw new TypeError("Chalkboard.real.discriminant: String 'form' must be 'standard' or 'vertex'.");
}
};
/**
* Calculates the division of two functions
* @param {ChalkboardFunction} func1 - The numerator function
* @param {ChalkboardFunction} func2 - The denominator function
* @returns {ChalkboardFunction}
*/
export const div = (func1: ChalkboardFunction, func2: ChalkboardFunction): ChalkboardFunction => {
if (func1.field !== "real" || func2.field !== "real") throw new TypeError("Chalkboard.real.div: Properties 'field' of 'func1' and 'func2' must be 'real'.");
if (func1.type !== func2.type) throw new TypeError("Chalkboard.real.div: Properties 'type' of 'func1' and 'func2' must be the same.");
if (func1.type.startsWith("scalar")) {
const f1 = func1.rule as ((...x: number[]) => number);
const f2 = func2.rule as ((...x: number[]) => number);
const g = (...x: number[]) => f1(...x) / f2(...x);
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("vector")) {
const f1 = func1.rule as ((...x: number[]) => number)[];
const f2 = func2.rule as ((...x: number[]) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((...x: number[]) => f1[i](...x) / f2[i](...x));
}
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("curve")) {
const f1 = func1.rule as ((t: number) => number)[];
const f2 = func2.rule as ((t: number) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((t: number) => f1[i](t) / f2[i](t));
}
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("surface")) {
const f1 = func1.rule as ((s: number, t: number) => number)[];
const f2 = func2.rule as ((s: number, t: number) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((s: number, t: number) => f1[i](s, t) / f2[i](s, t));
}
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.div: Properties 'type' of 'func1' and 'func2' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
};
/**
* Evaluates the error function erf(x) on a number.
* @param {number} num - The number
* @returns {number}
*/
export const erf = (num: number): number => {
if (typeof num !== "number" || !Number.isFinite(num)) throw new TypeError("Chalkboard.real.erf: Parameter 'num' must be a finite number.");
const sign = num < 0 ? -1 : 1;
const x = Math.abs(num);
const p = 0.3275911;
const a1 = 0.254829592;
const a2 = -0.284496736;
const a3 = 1.421413741;
const a4 = -1.453152027;
const a5 = 1.061405429;
const t = 1 / (1 + p * x);
const y = 1 - (((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t) * Chalkboard.E(-x * x);
return sign * y;
};
/**
* Evaluates the Gamma function Γ(x) on a number.
* @param {number} num - The number
* @returns {number}
*/
export const Gamma = (num: number): number => {
if (typeof num !== "number" || !Number.isFinite(num)) throw new TypeError("Chalkboard.real.Gamma: Parameter 'num' must be a finite number.");
if (Number.isInteger(num) && num <= 0) return NaN;
const p0 = 0.99999999999980993;
const p1 = 676.5203681218851;
const p2 = -1259.1392167224028;
const p3 = 771.32342877765313;
const p4 = -176.61502916214059;
const p5 = 12.507343278686905;
const p6 = -0.13857109526572012;
const p7 = 9.9843695780195716e-6;
const p8 = 1.5056327351493116e-7;
if (num < 0.5) return Chalkboard.PI() / (Chalkboard.trig.sin(Chalkboard.PI(num)) * Chalkboard.real.Gamma(1 - num));
const g = 7;
let x = num - 1;
let a = p0;
a += p1 / (x + 1);
a += p2 / (x + 2);
a += p3 / (x + 3);
a += p4 / (x + 4);
a += p5 / (x + 5);
a += p6 / (x + 6);
a += p7 / (x + 7);
a += p8 / (x + 8);
const t = x + g + 0.5;
return Chalkboard.real.sqrt(Chalkboard.PI(2)) * (Chalkboard.real.pow(t, x + 0.5) as number) * Chalkboard.E(-t) * a;
};
/**
* Evaluates the Heaviside step function on a number.
* @param {number} num - The number
* @param {number} edge - The edge of the function
* @param {number} scl - The scale of the function
* @returns {number}
*/
export const Heaviside = (num: number, edge: number = 0, scl: number = 1): number => {
if (num >= edge) {
return scl;
} else {
return 0;
}
};
/**
* Calculates the linear interpolation between a point and a variable.
* @param {number} p - The point
* @param {number} t - The variable
* @returns {number}
*/
export const lerp = (p: [number, number], t: number): number => {
return (p[1] - p[0]) * t + p[0];
};
/**
* Defines a linear function with two points.
* @param {number} x1 - The x-coordinate of the first point
* @param {number} y1 - The y-coordinate of the first point
* @param {number} x2 - The x-coordinate of the second point
* @param {number} y2 - The y-coordinate of the second point
* @returns {ChalkboardFunction}
*/
export const linear = (x1: number, y1: number, x2: number, y2: number): ChalkboardFunction => {
return Chalkboard.real.define((x: number) => Chalkboard.real.slope(x1, y1, x2, y2) * (x - x2) + y2);
};
/**
* Solves a linear equation.
* @param {number} a - The slope (in slope-intercept form)
* @param {number} b - The y-intercept (in slope-intercept form)
* @param {number} [c] - The y-intercept (in standard form)
* @param {number} [d] - The y-coordinate of the second point (in point-slope form)
* @returns {number}
*/
export const linearFormula = (a: number, b: number, c?: number, d?: number): number => {
if (typeof c === "undefined" && typeof d === "undefined") {
return -b / a;
} else if (typeof c === "number" && typeof d === "undefined") {
return c / a;
} else {
return -b / Chalkboard.real.slope(a, b, c as number, d as number) + a;
}
};
/**
* Calculates the natural logarithm of a number.
* @param {number} num - The number
* @returns {number}
*/
export const ln = (num: number): number => {
if (num <= 0) return NaN;
if (num === 1) return 0;
if (num === Infinity) return Infinity;
const LN2 = 0.6931471805599453;
let E = 0, m = num;
while (m > 1.414213562373095) {
m *= 0.5;
E++;
}
while (m < 0.7071067811865475) {
m *= 2.0;
E--;
}
const y = (m - 1) / (m + 1), y2 = y * y, y3 = y2 * y, y5 = y3 * y2, y7 = y5 * y2, y9 = y7 * y2, y11 = y9 * y2, y13 = y11 * y2, y15 = y13 * y2, y17 = y15 * y2, y19 = y17 * y2;
const series = y + y3 / 3 + y5 / 5 + y7 / 7 + y9 / 9 + y11 / 11 + y13 / 13 + y15 / 15 + y17 / 17 + y19 / 19;
return 2 * series + E * LN2;
};
/**
* Calculates the logarithm of a number with a particular base.
* @param {number} base - The base
* @param {number} num - The number
* @returns {number}
*/
export const log = (base: number, num: number): number => {
return Chalkboard.real.ln(num) / Chalkboard.real.ln(base);
};
/**
* Calculates the logarithm of a number with base 10.
* @param {number} num - The number
* @returns {number}
*/
export const log10 = (num: number): number => {
return Chalkboard.real.log(10, num);
};
/**
* Calculates the multiplication of two functions.
* @param {ChalkboardFunction} func1 - The first function
* @param {ChalkboardFunction} func2 - The second function
* @returns {ChalkboardFunction}
*/
export const mul = (func1: ChalkboardFunction, func2: ChalkboardFunction): ChalkboardFunction => {
if (func1.field !== "real" || func2.field !== "real") throw new TypeError("Chalkboard.real.mul: Properties 'field' of 'func1' and 'func2' must be 'real'.");
if (func1.type !== func2.type) throw new TypeError("Chalkboard.real.mul: Properties 'type' of 'func1' and 'func2' must be the same.");
if (func1.type.startsWith("scalar")) {
const f1 = func1.rule as ((...x: number[]) => number);
const f2 = func2.rule as ((...x: number[]) => number);
const g = (...x: number[]) => f1(...x) * f2(...x);
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("vector")) {
const f1 = func1.rule as ((...x: number[]) => number)[];
const f2 = func2.rule as ((...x: number[]) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((...x: number[]) => f1[i](...x) * f2[i](...x));
}
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("curve")) {
const f1 = func1.rule as ((t: number) => number)[];
const f2 = func2.rule as ((t: number) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((t: number) => f1[i](t) * f2[i](t));
}
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("surface")) {
const f1 = func1.rule as ((s: number, t: number) => number)[];
const f2 = func2.rule as ((s: number, t: number) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((s: number, t: number) => f1[i](s, t) * f2[i](s, t));
}
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.mul: Properties 'type' of 'func1' and 'func2' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
};
/**
* Calculates the negation of a function.
* @param {ChalkboardFunction} func - The function
* @returns {ChalkboardFunction}
*/
export const negate = (func: ChalkboardFunction): ChalkboardFunction => {
if (func.field !== "real") throw new TypeError("Chalkboard.real.negate: Property 'field' of 'func' must be 'real'.");
if (func.type.startsWith("scalar")) {
const f = func.rule as ((...x: number[]) => number);
const g = (...x: number[]) => -f(...x);
return Chalkboard.real.define(g);
} else if (func.type.startsWith("vector")) {
const f = func.rule as ((...x: number[]) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((...x: number[]) => -f[i](...x));
}
return Chalkboard.real.define(g);
} else if (func.type.startsWith("curve")) {
const f = func.rule as ((t: number) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((t: number) => -f[i](t));
}
return Chalkboard.real.define(g);
} else if (func.type.startsWith("surface")) {
const f = func.rule as ((s: number, t: number) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((s: number, t: number) => -f[i](s, t));
}
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.negate: Property 'type' of 'func' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
};
/**
* Parses, simplifies, and optionally evaluates a real number expression.
* @param {string} expr - The real 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 | number | { type: string, [key: string]: any }}
* @example
* // Returns 5
* const expr1 = Chalkboard.real.parse("x^2 + 1", { values: { x: 2 } });
*
* // Returns 16x^4 + 81y^4 + 96x^3y + 216x^2y^2 + 216y^3x
* const expr2 = Chalkboard.real.parse("(2x + 3y)^4");
*
* // Returns 25.1672 + 8.3891sin(4x)
* const expr3 = Chalkboard.real.parse("(1 + exp(2))(3 + sin(4x))");
*
* // Returns y^{2}\mathrm{tan}\left(x\right) + 2y \cdot \mathrm{tan}\left(x\right) + \mathrm{tan}\left(x\right)
* const expr4 = Chalkboard.real.parse("tan(x)(y + 1)^2", { returnLaTeX: true });
*
* // Returns {"type":"mul","left":{"type":"func","name":"tan","args":[{"type":"var","name":"x"}]},"right":{"type":"pow","base":{"type":"add","left":{"type":"var","name":"y"},"right":{"type":"num","value":1}},"exponent":{"type":"num","value":2}}}
* const expr5 = Chalkboard.real.parse("tan(x)(y + 1)^2", { 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 | number | { type: string, [key: string]: any } => {
if (expr === "") return "";
const tokenize = (input: string): string[] => {
const tokens: string[] = [];
let i = 0;
const registered = ["sin", "cos", "tan", "abs", "sqrt", "log", "ln", "exp", "min", "max"];
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_(]/.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 && (/[a-zA-Z_(]/.test(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.real.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.real.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 (/^[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.real.parse: Unexpected token ${token}`);
};
const ast = parseExpression();
if (pos < tokens.length) throw new Error(`Chalkboard.real.parse: Unexpected token ${tokens[pos]}`);
return ast;
};
const evaluateNode = (node: { type: string, [key: string]: any }, values: Record): number => {
switch (node.type) {
case "num": {
return node.value;
}
case "var": {
const varname = node.name;
if (varname in values) return values[varname];
throw new Error(`Chalkboard.real.parse: Variable '${varname}' not defined in values`);
}
case "add": {
return evaluateNode(node.left, values) + evaluateNode(node.right, values);
}
case "sub": {
return evaluateNode(node.left, values) - evaluateNode(node.right, values);
}
case "mul": {
return evaluateNode(node.left, values) * evaluateNode(node.right, values);
}
case "div": {
const numerator = evaluateNode(node.left, values);
const denominator = evaluateNode(node.right, values);
if (denominator === 0) throw new Error(`Chalkboard.real.parse: Division by zero`);
return numerator / denominator;
}
case "pow": {
return Math.pow(evaluateNode(node.base, values), evaluateNode(node.exponent, values));
}
case "neg": {
return -evaluateNode(node.expr, values);
}
case "func": {
const funcName = node.name.toLowerCase();
const args = node.args.map((arg: { type: string, [key: string]: any }) => evaluateNode(arg, values));
if (Chalkboard.REGISTRY[funcName] !== undefined) return Chalkboard.REGISTRY[funcName](...args);
switch (funcName) {
case "sin": return Math.sin(args[0]);
case "cos": return Math.cos(args[0]);
case "tan": return Math.tan(args[0]);
case "abs": return Math.abs(args[0]);
case "sqrt": return Math.sqrt(args[0]);
case "log": return args.length > 1 ? Math.log(args[0]) / Math.log(args[1]) : Math.log(args[0]);
case "ln": return Math.log(args[0]);
case "exp": return Math.exp(args[0]);
case "min": return Math.min(...args);
case "max": return Math.max(...args);
default: throw new Error(`Chalkboard.real.parse: Unknown function ${node.name}`);
}
}
}
throw new Error(`Chalkboard.real.parse: Unknown node type ${node.type}`);
};
const nodeToString = (node: { type: string, [key: string]: any }): string => {
switch (node.type) {
case "num": {
return node.value.toString();
}
case "var": {
return node.name;
}
case "add": {
if (node.right.type === "mul" && node.right.left?.type === "num" && node.right.left.value === -1 && node.right.right?.type === "neg") return `${nodeToString(node.left)} + ${nodeToString(node.right.right.expr)}`;
if (node.right.type === "num" && node.right.value < 0) return `${nodeToString(node.left)} - ${nodeToString({ type: "num", value: -node.right.value })}`;
if (node.right.type === "neg") return `${nodeToString(node.left)} - ${nodeToString(node.right.expr)}`;
if (node.right.type === "mul" && node.right.left.type === "num" && node.right.left.value < 0) return `${nodeToString(node.left)} - ${nodeToString({ type: "mul", left: { type: "num", value: -node.right.left.value }, right: node.right.right })}`;
if (node.right.type === "mul" && node.right.right.type === "num" && node.right.right.value < 0) return `${nodeToString(node.left)} - ${nodeToString({ type: "mul", left: node.right.left, right: { type: "num", value: -node.right.right.value }})}`;
if (nodeToString(node.right).startsWith("-")) return `${nodeToString(node.left)} - ${nodeToString(node.right).slice(1)}`;
return `${nodeToString(node.left)} + ${nodeToString(node.right)}`;
}
case "sub": {
if (node.right.type === "neg") return `${nodeToString(node.left)} + ${nodeToString(node.right.expr)}`;
if (node.right.type === "mul" && node.right.left?.type === "num" && node.right.left.value === 1 && node.right.right?.type === "neg") return `${nodeToString(node.left)} + ${nodeToString(node.right.right.expr)}`;
if (node.right.type === "mul" && node.right.left?.type === "num" && node.right.left.value === -1) return `${nodeToString(node.left)} + ${nodeToString(node.right.right)}`;
if (node.right.type === "mul" && node.right.left?.type === "num" && node.right.left.value === 1 && node.right.right?.type === "mul" && node.right.right.left?.type === "num" && node.right.right.left.value === -1) return `${nodeToString(node.left)} + ${nodeToString(node.right.right.right)}`;
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);
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.right.type === "var" || node.right.type === "pow")) return `${nodeToString(node.left)}${nodeToString(node.right)}`;
if (node.left.type === "pow" && node.right.type === "pow") return `${nodeToString(node.left)}${nodeToString(node.right)}`;
if (node.left.type === "pow" && node.right.type === "var") return `${nodeToString(node.left)}${nodeToString(node.right)}`;
if (node.left.type === "var" && node.right.type === "pow") return `${nodeToString(node.left)}${nodeToString(node.right)}`;
if (node.left.type === "var" && node.right.type === "var") return `${nodeToString(node.left)}${nodeToString(node.right)}`;
return `${nodeToString(node.left)} * ${nodeToString(node.right)}`;
}
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 baseStr = (node.base.type !== "num" && node.base.type !== "var") ? `(${nodeToString(node.base)})` : nodeToString(node.base);
const expStr = (node.exponent.type !== "num" && node.exponent.type !== "var") ? `(${nodeToString(node.exponent)})` : nodeToString(node.exponent);
return `${baseStr}^${expStr}`;
}
case "neg": {
if (node.expr.type === "add" || node.expr.type === "sub") return `-(${ nodeToString(node.expr) })`;
if (node.expr.type === "pow") return `-${nodeToString(node.expr)}`;
const exprStr = (node.expr.type !== "num" && node.expr.type !== "var") ? `(${ 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 "var": {
return node.name;
}
case "add": {
if (nodeToLaTeX(node.right).startsWith("-")) return `${nodeToLaTeX(node.left)} - ${nodeToLaTeX(node.right).slice(1)}`;
return `${nodeToLaTeX(node.left)} + ${nodeToLaTeX(node.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 === "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.real.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;
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 node;
}
case "var": {
return node;
}
case "add": {
const left = simplifyNode(node.left);
const right = simplifyNode(node.right);
const flatten = (n: any): any[] => n.type === "add" ? [...flatten(n.left), ...flatten(n.right)] : [n];
const terms = flatten({ type: "add", left, right });
const coeffs: Record = {};
let constants = 0;
const others: any[] = [];
const powers: { pow: number, name: string }[] = [];
for (let i = 0; i < terms.length; i++) {
const t = terms[i];
if (t.type === "num") {
constants += t.value;
} else if (t.type === "mul" && t.left.type === "num" && t.right.type === "var") {
const k = t.right.name;
coeffs[k] = (coeffs[k] || 0) + t.left.value;
} else if (t.type === "var") {
const k = t.name;
coeffs[k] = (coeffs[k] || 0) + 1;
} else if (t.type === "pow" && t.base.type === "var" && t.exponent.type === "num") {
const k = t.base.name + "^" + t.exponent.value;
coeffs[k] = (coeffs[k] || 0) + 1;
powers.push({ pow: t.exponent.value, name: k });
} else if (t.type === "mul" && t.left.type === "num" && t.right.type === "pow" && t.right.base.type === "var" && t.right.exponent.type === "num") {
const k = t.right.base.name + "^" + t.right.exponent.value;
coeffs[k] = (coeffs[k] || 0) + t.left.value;
powers.push({ pow: t.right.exponent.value, name: k });
} else {
others.push(t);
}
}
const powerKeys: string[] = [];
for (let i = 0; i < powers.length; i++) {
let exists = false;
for (let j = 0; j < powerKeys.length; j++) {
if (powerKeys[j] === powers[i].name) {
exists = true;
break;
}
}
if (!exists) powerKeys.push(powers[i].name);
}
for (let i = 0; i < powerKeys.length - 1; i++) {
for (let j = i + 1; j < powerKeys.length; j++) {
const pa = powers.find((p) => p.name === powerKeys[i])?.pow ?? 1;
const pb = powers.find((p) => p.name === powerKeys[j])?.pow ?? 1;
if (pb > pa) {
const tmp = powerKeys[i];
powerKeys[i] = powerKeys[j];
powerKeys[j] = tmp;
}
}
}
const coeffKeys = Object.keys(coeffs);
const varKeys = coeffKeys.filter((k) => k.indexOf("^") === -1);
let result: any = null;
for (let i = 0; i < powerKeys.length; i++) {
const k = powerKeys[i];
if (coeffs[k] === 0) continue;
const split = k.split("^");
const name = split[0];
const exp = split[1];
const pownode = { type: "pow", base: { type: "var", name }, exponent: { type: "num", value: Number(exp) } };
const term = coeffs[k] === 1 ? pownode : { type: "mul", left: { type: "num", value: coeffs[k] }, right: pownode };
result = result ? { type: "add", left: result, right: term } : term;
}
for (let i = 0; i < varKeys.length; i++) {
const k = varKeys[i];
if (coeffs[k] === 0) continue;
const term = coeffs[k] === 1 ? { type: "var", name: k } : { type: "mul", left: { type: "num", value: coeffs[k] }, right: { type: "var", name: k } };
result = result ? { type: "add", left: result, right: term } : term;
}
if (constants !== 0) result = result ? { type: "add", left: result, right: { type: "num", value: constants } } : { type: "num", value: constants };
for (let i = 0; i < others.length; i++) result = result ? { type: "add", left: result, right: others[i] } : others[i];
return result || { type: "num", value: 0 };
}
case "sub": {
const leftSub = simplifyNode(node.left);
const rightSub = simplifyNode(node.right);
return simplifyNode({ type: "add", left: leftSub, right: { type: "mul", left: { type: "num", value: -1 }, right: rightSub } });
}
case "mul": {
const leftMul = simplifyNode(node.left);
const rightMul = simplifyNode(node.right);
if (rightMul.type === "add" || rightMul.type === "sub") return simplifyNode({ type: rightMul.type, left: { type: "mul", left: leftMul, right: rightMul.left }, right: { type: "mul", left: leftMul, right: rightMul.right } });
if (leftMul.type === "add" || leftMul.type === "sub") return simplifyNode({ type: leftMul.type, left: { type: "mul", left: rightMul, right: leftMul.left }, right: { type: "mul", left: rightMul, right: leftMul.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);
}
const flattenMul = (n: any): any[] => n.type === "mul" ? [...flattenMul(n.left), ...flattenMul(n.right)] : [n];
const factors = flattenMul({ type: "mul", left: leftMul, right: rightMul });
let coeffMul = 1;
const powersMul: Record = {};
const othersMul: any[] = [];
for (let i = 0; i < factors.length; i++) {
const f = factors[i];
if (f.type === "num") {
coeffMul *= f.value;
} else if (f.type === "var") {
powersMul[f.name] = (powersMul[f.name] || 0) + 1;
} else if (f.type === "pow" && f.base.type === "var" && f.exponent.type === "num") {
powersMul[f.base.name] = (powersMul[f.base.name] || 0) + f.exponent.value;
} else {
othersMul.push(f);
}
}
const powerKeysMul: string[] = [];
const powerValsMul: number[] = [];
for (const k in powersMul) {
if (powersMul.hasOwnProperty(k)) {
powerKeysMul.push(k);
powerValsMul.push(powersMul[k]);
}
}
for (let i = 0; i < powerKeysMul.length - 1; i++) {
for (let j = i + 1; j < powerKeysMul.length; j++) {
if (powerValsMul[j] > powerValsMul[i]) {
const tmpK = powerKeysMul[i];
const tmpV = powerValsMul[i];
powerKeysMul[i] = powerKeysMul[j];
powerValsMul[i] = powerValsMul[j];
powerKeysMul[j] = tmpK;
powerValsMul[j] = tmpV;
}
}
}
let resultMul: any = null;
if (coeffMul !== 1 || (powerKeysMul.length === 0 && othersMul.length === 0)) resultMul = { type: "num", value: coeffMul };
for (let i = 0; i < powerKeysMul.length; i++) {
const k = powerKeysMul[i];
const v = powerValsMul[i];
if (v === 0) continue;
if (v === 1) {
resultMul = resultMul ? { type: "mul", left: resultMul, right: { type: "var", name: k } } : { type: "var", name: k };
} else {
resultMul = resultMul ? { type: "mul", left: resultMul, right: { type: "pow", base: { type: "var", name: k }, exponent: { type: "num", value: v } } } : { type: "pow", base: { type: "var", name: k }, exponent: { type: "num", value: v } };
}
}
for (let i = 0; i < othersMul.length; i++) resultMul = resultMul ? { type: "mul", left: resultMul, right: othersMul[i] } : othersMul[i];
return resultMul;
}
case "div": {
const leftDiv = simplifyNode(node.left);
const rightDiv = simplifyNode(node.right);
if (leftDiv.type === "num" && leftDiv.value === 1 && (rightDiv.type === "add" || rightDiv.type === "sub")) return { type: "pow", base: rightDiv, exponent: { type: "num", value: -1 } };
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 === "num" && leftDiv.value === 0) return { type: "num", value: 0 };
if (rightDiv.type === "num" && rightDiv.value === 1) return leftDiv;
if (leftDiv.type === "num" && rightDiv.type === "num") return { type: "num", value: leftDiv.value / rightDiv.value };
if (areEqualVars(leftDiv, rightDiv)) return { type: "num", value: 1 };
if (leftDiv.type === "num" && leftDiv.value === 1 && (rightDiv.type === "add" || rightDiv.type === "sub")) return { type: "pow", base: rightDiv, exponent: { type: "num", value: -1 } };
if (leftDiv.type === "mul" && areEqualVars(leftDiv.right, rightDiv)) return simplifyNode(leftDiv.left);
if (leftDiv.type === "mul" && areEqualVars(leftDiv.left, rightDiv)) return simplifyNode(leftDiv.right);
if (leftDiv.type === "pow" && rightDiv.type === "pow" && areEqualVars(leftDiv.base, rightDiv.base)) return { type: "pow", base: leftDiv.base, exponent: { type: "sub", left: simplifyNode(leftDiv.exponent), right: simplifyNode(rightDiv.exponent) } };
if (leftDiv.type === "pow" && areEqualVars(leftDiv.base, rightDiv)) return { type: "pow", base: rightDiv, exponent: { type: "sub", left: simplifyNode(leftDiv.exponent), right: { type: "num", value: 1 } } };
if (rightDiv.type === "pow" && areEqualVars(leftDiv, rightDiv.base)) return { type: "pow", base: leftDiv, exponent: { type: "sub", left: { type: "num", value: 1 }, right: simplifyNode(rightDiv.exponent) } };
const flattenDiv = (n: any, type: string): any[] => {
if (!n) return [];
return n.type === type ? [...flattenDiv(n.left, type), ...flattenDiv(n.right, type)] : [n];
};
const numFactors = flattenDiv(leftDiv, "mul");
const denFactors = flattenDiv(rightDiv, "mul");
let coeffNum = 1, coeffDen = 1;
const powersNum: Record = {}, powersDen: Record = {};
const othersNum: any[] = [], othersDen: any[] = [];
for (let i = 0; i < numFactors.length; i++) {
const f = numFactors[i];
if (f.type === "num") {
coeffNum *= f.value;
} else if (f.type === "var") {
powersNum[f.name] = (powersNum[f.name] || 0) + 1;
} else if (f.type === "pow" && f.base.type === "var" && f.exponent.type === "num") {
powersNum[f.base.name] = (powersNum[f.base.name] || 0) + f.exponent.value;
} else {
othersNum.push(f);
}
}
for (let i = 0; i < denFactors.length; i++) {
const f = denFactors[i];
if (f.type === "num") {
coeffDen *= f.value;
} else if (f.type === "var") {
powersDen[f.name] = (powersDen[f.name] || 0) + 1;
} else if (f.type === "pow" && f.base.type === "var" && f.exponent.type === "num") {
powersDen[f.base.name] = (powersDen[f.base.name] || 0) + f.exponent.value;
} else {
othersDen.push(f);
}
}
let resultDiv: any = null;
const coeffQuot = coeffNum / coeffDen;
if (coeffQuot !== 1 || (Object.keys(powersNum).length === 0 && Object.keys(powersDen).length === 0 && othersNum.length === 0 && othersDen.length === 0)) resultDiv = { type: "num", value: coeffQuot };
const allPowerKeys: string[] = [];
for (const k in powersNum) if (allPowerKeys.indexOf(k) === -1) allPowerKeys.push(k);
for (const k in powersDen) if (allPowerKeys.indexOf(k) === -1) allPowerKeys.push(k);
for (let i = 0; i < allPowerKeys.length - 1; i++) {
for (let j = i + 1; j < allPowerKeys.length; j++) {
const pa = (powersNum[allPowerKeys[i]] || 0) - (powersDen[allPowerKeys[i]] || 0);
const pb = (powersNum[allPowerKeys[j]] || 0) - (powersDen[allPowerKeys[j]] || 0);
if (pb > pa) {
const tmp = allPowerKeys[i];
allPowerKeys[i] = allPowerKeys[j];
allPowerKeys[j] = tmp;
}
}
}
for (let i = 0; i < allPowerKeys.length; i++) {
const k = allPowerKeys[i];
const exp = (powersNum[k] || 0) - (powersDen[k] || 0);
if (exp === 0) continue;
if (exp === 1) {
resultDiv = resultDiv ? { type: "mul", left: resultDiv, right: { type: "var", name: k } } : { type: "var", name: k };
} else {
resultDiv = resultDiv ? { type: "mul", left: resultDiv, right: { type: "pow", base: { type: "var", name: k }, exponent: { type: "num", value: exp } } } : { type: "pow", base: { type: "var", name: k }, exponent: { type: "num", value: exp } };
}
}
for (let i = 0; i < othersNum.length; i++) resultDiv = resultDiv ? { type: "mul", left: resultDiv, right: othersNum[i] } : othersNum[i];
for (let i = 0; i < othersDen.length; i++) resultDiv = { type: "div", left: resultDiv, right: othersDen[i] };
return resultDiv;
}
case "pow": {
const base = simplifyNode(node.base);
const exponent = simplifyNode(node.exponent);
if ((base.type === "add" || base.type === "sub") && exponent.type === "num" && Number.isInteger(exponent.value)) {
if (exponent.value < 0) {
const absExpr = Math.abs(exponent.value);
if (absExpr === 1) {
return { type: "pow", base: base, exponent: { type: "num", value: -1 } };
} else {
const positiveExpr = { type: "pow", base, exponent: { type: "num", value: absExpr } };
const expanded = simplifyNode(positiveExpr);
return { type: "pow", base: expanded, exponent: { type: "num", value: -1 } };
}
} else if (exponent.value > 0) {
const n = exponent.value;
const a = base.left;
const b = base.right;
const sign = base.type === "add" ? 1 : -1;
let result = null;
for (let k = 0; k <= n; k++) {
const c = Chalkboard.numb.binomial(n, k);
const leftPower = n - k === 0 ? { type: "num", value: 1 } : (n - k === 1 ? a : simplifyNode({ type: "pow", base: a, exponent: { type: "num", value: n - k } }));
const rightPower = k === 0 ? { type: "num", value: 1 } : (k === 1 ? (sign === 1 ? b : { type: "neg", expr: b }) : simplifyNode({ type: "pow", base: b, exponent: { type: "num", value: k } }));
const termSign = (sign === -1 && k % 2 === 1) ? -1 : 1;
let term;
if (k === 0) {
term = leftPower;
} else if (n - k === 0) {
term = rightPower;
} else {
term = simplifyNode({ type: "mul", left: leftPower, right: rightPower });
}
if (c !== 1) {
term = simplifyNode({ type: "mul", left: { type: "num", value: termSign * c }, right: term });
} else if (termSign === -1) {
term = { type: "neg", expr: term };
}
if (result === null) {
result = term;
} else {
result = simplifyNode({ type: "add", left: result, right: term });
}
}
return result;
}
}
if (exponent.type === "num" && exponent.value === 0) return { type: "num", value: 1 };
if (exponent.type === "num" && exponent.value === 1) return base;
if (base.type === "num" && base.value === 0 && exponent.type === "num" && exponent.value > 0) return { type: "num", value: 0 };
if (base.type === "num" && base.value === 1) return { type: "num", value: 1 };
if (base.type === "num" && exponent.type === "num") return { type: "num", value: Math.pow(base.value, exponent.value) };
if (base.type === "pow") return { type: "pow", base: base.base, exponent: { type: "mul", left: simplifyNode(base.exponent), right: exponent } };
if (base.type === "mul" && exponent.type === "num") return simplifyNode({ type: "mul", left: { type: "pow", base: base.left, exponent }, right: { type: "pow", base: base.right, exponent } });
return { type: "pow", base, exponent };
}
case "neg": {
const expr = simplifyNode(node.expr);
if (expr.type === "neg") return expr.expr;
if (expr.type === "num") return { type: "num", value: -expr.value };
if (expr.type === "add") return simplifyNode({ type: "add", left: simplifyNode({ type: "neg", expr: expr.left }), right: simplifyNode({ type: "neg", expr: expr.right }) });
if (expr.type === "sub") return simplifyNode({ type: "add", left: simplifyNode({ type: "neg", expr: expr.left }), right: expr.right });
return { type: "neg", expr };
}
case "func": {
const args = node.args.map((arg: { type: string, [key: string]: any }) => simplifyNode(arg));
if (args.every((arg: { type: string, [key: string]: any }) => arg.type === "num")) {
try {
const funcName = node.name.toLowerCase();
if (Chalkboard.REGISTRY[funcName] !== undefined) {
const argValues = args.map((arg: { type: string, [key: string]: any }) => arg.value);
return { type: "num", value: Chalkboard.REGISTRY[funcName](...argValues) };
}
const result = evaluateNode({ type: "func", name: node.name, args }, {});
return { type: "num", value: result };
} catch (e) {
return { type: "func", name: node.name, args };
}
}
return { type: "func", name: node.name, args };
}
}
return node;
};
try {
const tokens = tokenize(expr);
const ast = parseTokens(tokens);
if (config.returnAST) return ast;
if (config.returnJSON) return JSON.stringify(ast);
if (config.values && Object.keys(config.values).length > 0) {
const result = evaluateNode(ast, config.values);
if (config.roundTo !== undefined) return Chalkboard.numb.roundTo(result, config.roundTo);
return result;
}
let simplified = simplifyNode(ast);
let normalizedast = parseTokens(tokenize(nodeToString(simplified)));
simplified = simplifyNode(normalizedast);
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!) };
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.returnLaTeX) return nodeToLaTeX(simplified);
return nodeToString(simplified);
} catch (err) {
if (err instanceof Error) {
throw new Error(`Chalkboard.real.parse: Error parsing real expression ${err.message}`);
} else {
throw new Error(`Chalkboard.real.parse: Error parsing real expression ${String(err)}`);
}
}
};
/**
* Evaluates the ping-pong function at a number.
* @param {number} num - The number
* @param {number} edge - The edge of the function
* @param {number} scl - The scale of the function
* @returns {number}
*/
export const pingpong = (num: number, edge: number = 0, scl: number = 1): number => {
if ((num + edge) % (2 * scl) < scl) {
return (num + edge) % scl;
} else {
return scl - ((num + edge) % scl);
}
};
/**
* Defines a polynomial function by its coefficients.
* @param {...number[]} coeffs - The coefficients of the polynomial, starting with the leading coefficient and ending with the constant term, which can be written either in an array or as separate arguments
* @returns {ChalkboardFunction}
*/
export const polynomial = (...coeffs: number[]): ChalkboardFunction => {
let arr: number[];
if (coeffs.length === 1 && Array.isArray(coeffs[0])) {
arr = coeffs[0];
} else {
arr = coeffs;
}
while (arr.length > 1 && arr[0] === 0) {
arr.shift();
}
const f = (x: number): number => {
if (arr.length === 0) return 0;
let result = arr[0];
for (let i = 1; i < arr.length; i++) {
result = result * x + arr[i];
}
return result;
};
return Chalkboard.real.define(f);
};
/**
* Calculates the exponentiation of a number or a function to the power of a number.
* @param {number | ChalkboardFunction} base - The number or function
* @param {number} num - The power
* @returns {number | ChalkboardFunction}
*/
export const pow = (base: number | ChalkboardFunction, num: number): number | ChalkboardFunction => {
if (typeof base === "number") {
if (base === 0 && num === 0) return 1;
if (base === 0) return 0;
if (num === 0) return 1;
if (num === 1) return base;
if (Number.isInteger(num)) {
let res = 1;
let b = base;
let n = Math.abs(num);
while (n > 0) {
if (n % 2 === 1) res *= b;
b *= b;
n = Math.floor(n / 2);
}
return num < 0 ? 1 / res : res;
} else {
if (base < 0) return NaN;
return Chalkboard.E(num * Chalkboard.real.ln(base));
}
} else {
const func = base;
if (func.field !== "real") throw new TypeError("Chalkboard.real.pow: Property 'field' of 'func' must be 'real'.");
if (func.type.startsWith("scalar")) {
const f = func.rule as ((...x: number[]) => number);
const g = (...x: number[]) => f(...x) ** num;
return Chalkboard.real.define(g);
} else if (func.type.startsWith("vector")) {
const f = func.rule as ((...x: number[]) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((...x: number[]) => f[i](...x) ** num);
}
return Chalkboard.real.define(g);
} else if (func.type.startsWith("curve")) {
const f = func.rule as ((t: number) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((t: number) => f[i](t) ** num);
}
return Chalkboard.real.define(g);
} else if (func.type.startsWith("surface")) {
const f = func.rule as ((s: number, t: number) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((s: number, t: number) => f[i](s, t) ** num);
}
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.pow: Property 'type' of 'func' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
}
};
/**
* Calculates the quadratic interpolation between three points.
* @param {number[]} p1 - The first point
* @param {number[]} p2 - The second point
* @param {number[]} p3 - The third point
* @param {number} t - The variable
* @returns {number}
*/
export const qerp = (p1: [number, number], p2: [number, number], p3: [number, number], t: number): number => {
const a = p1[1] / ((p1[0] - p2[0]) * (p1[0] - p3[0])) + p2[1] / ((p2[0] - p1[0]) * (p2[0] - p3[0])) + p3[1] / ((p3[0] - p1[0]) * (p3[0] - p2[0]));
const b =
(-p1[1] * (p2[0] + p3[0])) / ((p1[0] - p2[0]) * (p1[0] - p3[0])) -
(p2[1] * (p1[0] + p3[0])) / ((p2[0] - p1[0]) * (p2[0] - p3[0])) -
(p3[1] * (p1[0] + p2[0])) / ((p3[0] - p1[0]) * (p3[0] - p2[0]));
const c =
(p1[1] * p2[0] * p3[0]) / ((p1[0] - p2[0]) * (p1[0] - p3[0])) +
(p2[1] * p1[0] * p3[0]) / ((p2[0] - p1[0]) * (p2[0] - p3[0])) +
(p3[1] * p1[0] * p2[0]) / ((p3[0] - p1[0]) * (p3[0] - p2[0]));
return a * t * t + b * t + c;
};
/**
* Defines a quadratic function.
* @param {number} a - The leading coefficient
* @param {number} b - The middle coefficient
* @param {number} c - The last coefficient (the constant)
* @param {"standard" | "vertex"} [form="standard"] - The form of the polynomial, which can be "standard" for standard form or "vertex" for vertex form
* @returns {ChalkboardFunction}
*/
export const quadratic = (a: number, b: number, c: number, form: "standard" | "vertex" = "standard"): ChalkboardFunction => {
if (form === "standard") {
return Chalkboard.real.define((x: number) => a * x * x + b * x + c);
} else if (form === "vertex") {
return Chalkboard.real.define((x: number) => a * (x - b) * (x - b) + c);
} else {
throw new TypeError("Chalkboard.real.quadratic: String 'form' must be 'standard' or 'vertex'.");
}
};
/**
* Solves a quadratic equation.
* @param {number} a - The leading coefficient
* @param {number} b - The middle coefficient
* @param {number} c - The last coefficient (the constant)
* @param {"standard" | "vertex"} [form="standard"] - The form of the polynomial, which can be "standard" for standard form or "vertex" for vertex form
* @returns {number[]}
*/
export const quadraticFormula = (a: number, b: number, c: number, form: "standard" | "vertex" = "standard"): [number, number] => {
if (form === "standard") {
return [(-b + Chalkboard.real.sqrt(Chalkboard.real.discriminant(a, b, c, "standard"))) / (2 * a), (-b - Chalkboard.real.sqrt(Chalkboard.real.discriminant(a, b, c, "standard"))) / (2 * a)];
} else if (form === "vertex") {
return [b + Chalkboard.real.sqrt(-c / a), b - Chalkboard.real.sqrt(-c / a)];
} else {
throw new TypeError("Chalkboard.real.quadraticFormula: String 'form' must be 'standard' or 'vertex'.");
}
};
/**
* Evaluates the ramp function at a number.
* @param {number} num - The number
* @param {number} edge - The edge of the function
* @param {number} scl - The scale of the function
* @returns {number}
*/
export const ramp = (num: number, edge: number = 0, scl: number = 1): number => {
if (num >= edge) {
return num * scl;
} else {
return 0;
}
};
/**
* Defines a polynomial with random coefficients.
* @param {number} degree - The degree of the polynomial
* @param {number} [inf=0] - The lower bound of the coefficients (optional, defaults to 0)
* @param {number} [sup=1] - The upper bound of the coefficients (optional, defaults to 1)
* @returns {ChalkboardFunction}
*/
export const randomPolynomial = (degree: number, inf: number = 0, sup: number = 1): ChalkboardFunction => {
return Chalkboard.real.polynomial(...Chalkboard.stat.random(degree + 1, inf, sup));
};
/**
* Calculates the reciprocation of a function.
* @param {ChalkboardFunction} func - The function
* @returns {ChalkboardFunction}
*/
export const reciprocate = (func: ChalkboardFunction): ChalkboardFunction => {
if (func.field !== "real") throw new TypeError("Chalkboard.real.reciprocate: Property 'field' of 'func' must be 'real'.");
if (func.type.startsWith("scalar")) {
const f = func.rule as ((...x: number[]) => number);
const g = (...x: number[]) => 1 / f(...x);
return Chalkboard.real.define(g);
} else if (func.type.startsWith("vector")) {
const f = func.rule as ((...x: number[]) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((...x: number[]) => 1 / f[i](...x));
}
return Chalkboard.real.define(g);
} else if (func.type.startsWith("curve")) {
const f = func.rule as ((t: number) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((t: number) => 1 / f[i](t));
}
return Chalkboard.real.define(g);
} else if (func.type.startsWith("surface")) {
const f = func.rule as ((s: number, t: number) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((s: number, t: number) => 1 / f[i](s, t));
}
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.reciprocate: Property 'type' of 'func' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
};
/**
* Evaluates the rect function at a number.
* @param {number} num the number
* @param {number} center - The position of the function
* @param {number} width - The width of the function
* @param {number} scl - The scale of the function
* @returns {number}
*/
export const rect = (num: number, center: number = 0, width: number = 2, scl: number = 1): number => {
if (num > center + width / 2 || num < center - width / 2) {
return 0;
} else {
return scl;
}
};
/**
* Calculates the nth-root of a number.
* @param {number} num - The number
* @param {number} [index=3] - The nth-root to take
* @returns {number}
*/
export const root = (num: number, index: number = 3): number => {
if (num === 0) return 0;
if (num < 0) {
if (Number.isInteger(index) && Math.abs(index) % 2 === 1) return -Chalkboard.E(Chalkboard.real.ln(-num) / index);
return NaN;
}
return Chalkboard.E(Chalkboard.real.ln(num) / index);
};
/**
* Calculates the scalar multiplication of a function.
* @param {ChalkboardFunction} func - The function
* @param {number} num - The scalar
* @returns {ChalkboardFunction}
*/
export const scl = (func: ChalkboardFunction, num: number): ChalkboardFunction => {
if (func.field !== "real") throw new TypeError("Chalkboard.real.scl: Property 'field' of 'func' must be 'real'.");
if (func.type.startsWith("scalar")) {
const f = func.rule as ((...x: number[]) => number);
const g = (...x: number[]) => f(...x) * num;
return Chalkboard.real.define(g);
} else if (func.type.startsWith("vector")) {
const f = func.rule as ((...x: number[]) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((...x: number[]) => f[i](...x) * num);
}
return Chalkboard.real.define(g);
} else if (func.type.startsWith("curve")) {
const f = func.rule as ((t: number) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((t: number) => f[i](t) * num);
}
return Chalkboard.real.define(g);
} else if (func.type.startsWith("surface")) {
const f = func.rule as ((s: number, t: number) => number)[];
const g = [];
for (let i = 0; i < f.length; i++) {
g.push((s: number, t: number) => f[i](s, t) * num);
}
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.scl: Property 'type' of 'func' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
};
/**
* Calculates the slope of two points.
* @param {number} x1 - The x-coordinate of the first point
* @param {number} y1 - The y-coordinate of the first point
* @param {number} x2 - The x-coordinate of the first point
* @param {number} y2 - The y-coordinate of the second point
* @returns {number}
*/
export const slope = (x1: number, y1: number, x2: number, y2: number): number => {
return (y2 - y1) / (x2 - x1);
};
/**
* Calculates the square root of a number.
* @param {number} num - The number
* @returns {number}
*/
export const sqrt = (num: number): number => {
if (num < 0) return NaN;
if (num === 0 || num === 1 || num === Infinity) return num;
let S = num, E = 0;
while (S >= 1.0) {
S *= 0.25;
E++;
}
while (S < 0.25) {
S *= 4.0;
E--;
}
let x = (S + 1.0) * 0.5;
x = 0.5 * (x + S / x);
x = 0.5 * (x + S / x);
x = 0.5 * (x + S / x);
x = 0.5 * (x + S / x);
x = 0.5 * (x + S / x);
return x * (2 ** E);
};
/**
* Calculates the subtraction of two functions.
* @param {ChalkboardFunction} func1 - The first function
* @param {ChalkboardFunction} func2 - The second function
* @returns {ChalkboardFunction}
*/
export const sub = (func1: ChalkboardFunction, func2: ChalkboardFunction): ChalkboardFunction => {
if (func1.field !== "real" || func2.field !== "real") throw new TypeError("Chalkboard.real.sub: Properties 'field' of 'func1' and 'func2' must be 'real'.");
if (func1.type !== func2.type) throw new TypeError("Chalkboard.real.sub: Properties 'type' of 'func1' and 'func2' must be the same.");
if (func1.type.startsWith("scalar")) {
const f1 = func1.rule as ((...x: number[]) => number);
const f2 = func2.rule as ((...x: number[]) => number);
const g = (...x: number[]) => f1(...x) - f2(...x);
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("vector")) {
const f1 = func1.rule as ((...x: number[]) => number)[];
const f2 = func2.rule as ((...x: number[]) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((...x: number[]) => f1[i](...x) - f2[i](...x));
}
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("curve")) {
const f1 = func1.rule as ((t: number) => number)[];
const f2 = func2.rule as ((t: number) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((t: number) => f1[i](t) - f2[i](t));
}
return Chalkboard.real.define(g);
} else if (func1.type.startsWith("surface")) {
const f1 = func1.rule as ((s: number, t: number) => number)[];
const f2 = func2.rule as ((s: number, t: number) => number)[];
const g = [];
for (let i = 0; i < f1.length; i++) {
g.push((s: number, t: number) => f1[i](s, t) - f2[i](s, t));
}
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.sub: Properties 'type' of 'func1' and 'func2' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
};
/**
* Calculates the tetration of a number.
* @param {number} base - The number
* @param {number} num - The tetratant, or the height of the power tower
* @returns {number}
*/
export const tetration = (base: number, num: number): number | undefined => {
if (!Number.isInteger(num) || num < 0) return NaN;
if (num === 0) return 1;
if (num === 1) return base;
let result = base;
for (let i = 1; i < num; i++) {
result = Chalkboard.real.pow(base, result) as number;
if (result === Infinity) return Infinity;
}
return result;
};
/**
* Calculates the translation of a function, which can be horizontally, vertically, or both.
* @param {ChalkboardFunction} func - The function
* @param {number} h - Horizontal translation (positive moves right)
* @param {number} v - Vertical translation (positive moves up)
* @returns {ChalkboardFunction}
*/
export const translate = (func: ChalkboardFunction, h: number = 0, v: number = 0): ChalkboardFunction => {
if (func.field !== "real") throw new TypeError("Chalkboard.real.translate: Property 'field' of 'func' must be 'real'.");
if (func.type === "scalar2d") {
const f = func.rule as (x: number) => number;
const g = (x: number) => f(x - h) + v;
return Chalkboard.real.define(g);
}
throw new TypeError("Chalkboard.real.translate: Property 'type' of 'func' must be 'scalar2d'.");
};
/**
* Calculates the value of a function at a value.
* @param {ChalkboardFunction} func - The function
* @param {number} val - The value
* @returns {number | ChalkboardVector}
*/
export const val = (func: ChalkboardFunction, val: number | ChalkboardVector): number | ChalkboardVector => {
if (func.field !== "real") throw new TypeError("Chalkboard.real.val: Property 'field' of 'func' must be 'real'.");
if (func.type === "scalar2d") {
const f = func.rule as (x: number) => number;
const x = val as number;
return f(x);
} else if (func.type === "scalar3d") {
const f = func.rule as (x: number, y: number) => number;
const v = val as ChalkboardVector as { x: number, y: number };
return f(v.x, v.y);
} else if (func.type === "scalar4d") {
const f = func.rule as (x: number, y: number, z: number) => number;
const v = val as ChalkboardVector as { x: number, y: number, z: number };
return f(v.x, v.y, v.z);
} else if (func.type === "vector2d") {
const f = func.rule as [(x: number, y: number) => number, (x: number, y: number) => number];
const v = val as ChalkboardVector as { x: number, y: number };
return Chalkboard.vect.init(f[0](v.x, v.y), f[1](v.x, v.y));
} else if (func.type === "vector3d") {
const f = func.rule as [(x: number, y: number, z: number) => number, (x: number, y: number, z: number) => number, (x: number, y: number, z: number) => number];
const v = val as ChalkboardVector as { x: number, y: number, z: number };
return Chalkboard.vect.init(f[0](v.x, v.y, v.z), f[1](v.x, v.y, v.z), f[2](v.x, v.y, v.z));
} else if (func.type === "vector4d") {
const f = func.rule as [(x: number, y: number, z: number, w: number) => number, (x: number, y: number, z: number, w: number) => number, (x: number, y: number, z: number, w: number) => number, (x: number, y: number, z: number, w: number) => number];
const v = val as ChalkboardVector as { x: number, y: number, z: number, w: number };
return Chalkboard.vect.init(f[0](v.x, v.y, v.z, v.w), f[1](v.x, v.y, v.z, v.w), f[2](v.x, v.y, v.z, v.w), f[3](v.x, v.y, v.z, v.w));
} else if (func.type === "curve2d") {
const f = func.rule as [(t: number) => number, (t: number) => number];
const t = val as number;
return Chalkboard.vect.init(f[0](t), f[1](t));
} else if (func.type === "curve3d") {
const f = func.rule as [(t: number) => number, (t: number) => number, (t: number) => number];
const t = val as number;
return Chalkboard.vect.init(f[0](t), f[1](t), f[2](t));
} else if (func.type === "curve4d") {
const f = func.rule as [(t: number) => number, (t: number) => number, (t: number) => number, (t: number) => number];
const t = val as number;
return Chalkboard.vect.init(f[0](t), f[1](t), f[2](t), f[3](t));
} else if (func.type === "surface3d") {
const f = func.rule as [(s: number, t: number) => number, (s: number, t: number) => number, (s: number, t: number) => number];
const v = val as ChalkboardVector as { x: number, y: number };
return Chalkboard.vect.init(f[0](v.x, v.y), f[1](v.x, v.y), f[2](v.x, v.y));
}
throw new TypeError("Chalkboard.real.val: Property 'type' of 'func' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'");
};
/**
* Defines a zero function of a particular type.
* @param {"scalar2d" | "scalar3d" | "scalar4d" | "vector2d" | "vector3d" | "vector4d" | "curve2d" | "curve3d" | "curve4d" | "surface3d"} [type="scalar2d"] - The type of the function
* @returns {ChalkboardFunction}
*/
export const zero = (type: "scalar2d" | "scalar3d" | "scalar4d" | "vector2d" | "vector3d" | "vector4d" | "curve2d" | "curve3d" | "curve4d" | "surface3d" = "scalar2d"): ChalkboardFunction => {
if (type === "scalar2d") {
return Chalkboard.real.define((x) => 0);
} else if (type === "scalar3d") {
return Chalkboard.real.define((x, y) => 0);
} else if (type === "scalar4d") {
return Chalkboard.real.define((x, y, z) => 0);
} else if (type === "vector2d") {
return Chalkboard.real.define((x, y) => 0, (x, y) => 0);
} else if (type === "vector3d") {
return Chalkboard.real.define((x, y, z) => 0, (x, y, z) => 0, (x, y, z) => 0);
} else if (type === "vector4d") {
return Chalkboard.real.define((x, y, z, w) => 0, (x, y, z, w) => 0, (x, y, z, w) => 0, (x, y, z, w) => 0);
} else if (type === "curve2d") {
return Chalkboard.real.define((t) => 0, (t) => 0);
} else if (type === "curve3d") {
return Chalkboard.real.define((t) => 0, (t) => 0, (t) => 0);
} else if (type === "curve4d") {
return Chalkboard.real.define((t) => 0, (t) => 0, (t) => 0, (t) => 0);
} else if (type === "surface3d") {
return Chalkboard.real.define((s, t) => 0, (s, t) => 0, (s, t) => 0);
}
throw new TypeError("Chalkboard.real.zero: String 'type' must be 'scalar2d', 'scalar3d', 'scalar4d', 'vector2d', 'vector3d', 'vector4d', 'curve2d', 'curve3d', 'curve4d', or 'surface3d'.");
};
}
}