UNPKG

nerdamer-prime

Version:
1,483 lines (1,440 loc) 270 kB
/* * Author : Martin Donk * Website : http://www.nerdamer.com * Email : martin.r.donk@gmail.com * License : MIT * Source : https://github.com/jiggzson/nerdamer */ // Type imports for JSDoc ====================================================== // These typedefs provide type aliases for the interfaces defined in index.d.ts. // They enable proper type checking when working with the classes defined in this file. // // Usage patterns: // - For return types: @returns {NerdamerSymbolType} // - For parameters: @param {NerdamerSymbolType} symbol // - For variable declarations: /** @type {NerdamerSymbolType} */ // // Note: When casting local class instances to interface types, use the pattern: // /** @type {InterfaceType} */ (/** @type {unknown} */ (localInstance)) // This is needed because TypeScript sees local classes and interfaces as separate types. /** * Core type aliases from index.d.ts * * @typedef {import('./index').NerdamerCore.NerdamerSymbol} NerdamerSymbolType * * @typedef {import('./index').NerdamerCore.Frac} FracType * * @typedef {import('./index').NerdamerCore.Vector} VectorType * * @typedef {import('./index').NerdamerCore.Matrix} MatrixType * * @typedef {NerdamerSymbolType | VectorType | MatrixType} ParseResultType Union type for parse results * * @typedef {import('./index').NerdamerCore.Parser} ParserType * * @typedef {import('./index').NerdamerCore.Collection} CollectionType * * @typedef {import('./index').NerdamerCore.Settings} SettingsType * * @typedef {import('./index').NerdamerExpression} ExpressionType * * @typedef {typeof import('./index')} NerdamerType * * @typedef {import('./index').NerdamerCore.Utils} UtilsInterface * * @typedef {import('./index').NerdamerCore.Math2} Math2Interface * * @typedef {import('./index').NerdamerCore.Core} CoreType * * @typedef {import('./index').ExpressionParam} ExpressionParam * * @typedef {import('./index').ArithmeticOperand} ArithmeticOperand * * @typedef {import('./index').ExpandOptions} ExpandOptions * * @typedef {import('./index').NerdamerCore.DecomposeResultObject} DecomposeResultType Constructor types * * @typedef {import('./index').NerdamerCore.FracConstructor} FracConstructor * * @typedef {import('./index').NerdamerCore.SymbolConstructor} SymbolConstructor * * @typedef {import('./index').NerdamerCore.VectorConstructor} VectorConstructor * * @typedef {import('./index').NerdamerCore.AlgebraModule} AlgebraModuleType * * @typedef {import('./index').NerdamerCore.Polynomial} Polynomial * * @typedef {import('./index').NerdamerCore.Factors} Factors * * @typedef {import('./index').NerdamerCore.FactorsLike} FactorsLike * * @typedef {import('./index').NerdamerCore.MVTerm} MVTerm * * @typedef {import('./index').NerdamerCore.FactorSubModule} FactorInterface * * @typedef {import('./index').NerdamerCore.SimplifySubModule} SimplifyInterface * * @typedef {import('./index').NerdamerCore.PartFracSubModule} PartFracInterface * * @typedef {new () => Factors} FactorsConstructor */ // Check if nerdamer exists globally (browser) or needs to be required (Node.js) let nerdamer = typeof globalThis !== 'undefined' && globalThis.nerdamer ? globalThis.nerdamer : undefined; if (typeof module !== 'undefined' && nerdamer === undefined) { nerdamer = require('./nerdamer.core.js'); require('./Calculus.js'); } (function initAlgebraModule() { /* Shortcuts*/ /** @type {CoreType} */ const core = nerdamer.getCore(); /** @type {ParserType} */ const _ = core.PARSER; const { N, P, S, EX, FN, PL, CP, CB } = core.groups; const { keys, even, variables, format, round, isInt } = core.Utils; const { Frac, NerdamerSymbol, Vector: _Vector, Expression: _Expression } = core; const { CONST_HASH } = core.Settings; /** @type {Record<string, Function>} */ const math = core.Utils.importFunctions(); const _evaluate = core.Utils.evaluate; //* ************** CLASSES ***************// /** * Converts a symbol into an equivalent polynomial arrays of the form [[coefficient_1, power_1],[coefficient_2, * power_2], ... ] Univariate polymials only. * * @class * @this {Polynomial} * @param {NerdamerSymbolType | number | string} [symbol] * @param {string} [variable] The variable name of the polynomial * @param {number} [order] */ function Polynomial(symbol, variable, order) { /** @type {FracType[]} */ this.coeffs = []; /** @type {string} */ this.variable = ''; if (core.Utils.isSymbol(symbol)) { this.parse(/** @type {NerdamerSymbolType} */ (symbol)); this.variable ||= variable || ''; } else if (typeof symbol === 'number' && !isNaN(symbol)) { order ||= 0; if (variable === undefined) { throw new core.exceptions.InvalidVariableNameError( 'Polynomial expects a variable name when creating using order' ); } this.coeffs = []; this.coeffs[order] = new Frac(symbol); this.fill(symbol); } else if (typeof symbol === 'string') { this.parse(_.parse(symbol)); } } /** * Creates a Polynomial given an array of coefficients * * @param {FracType[]} arr * @param {string} variable * @returns {Polynomial} */ Polynomial.fromArray = function fromArray(arr, variable) { if (typeof variable === 'undefined') { throw new core.exceptions.InvalidVariableNameError( 'A variable name must be specified when creating polynomial from array' ); } /** @type {Polynomial} */ const p = new Polynomial(); p.coeffs = arr; p.variable = variable; return p; }; /** * @param {number} c1 * @param {number} c2 * @param {number} n * @param {number} base * @param {number} p * @param {string} variable * @returns {Polynomial | null} */ Polynomial.fit = function fit(c1, c2, n, base, p, variable) { // After having looped through and mod 10 the number to get the matching factor const terms = new Array(p + 1); let t = n - c2; terms[0] = c2; // The constants is assumed to be correct // constant for x^p is also assumed know so add terms[p] = c1; t -= c1 * base ** p; // Start fitting for (let i = p - 1; i > 0; i--) { const b = base ** i; // We want as many wholes as possible const q = t / b; const sign = Math.sign(q); const c = sign * Math.floor(Math.abs(q)); t -= c * b; terms[i] = c; } if (t !== 0) { return null; } for (let i = 0; i < terms.length; i++) { terms[i] = new Frac(terms[i]); } return Polynomial.fromArray(terms, variable); }; Polynomial.prototype = { /** * Converts NerdamerSymbol to Polynomial * * @this {Polynomial} * @param {NerdamerSymbolType} symbol * @param {FracType[]} [c] - A collector array * @returns {void} */ parse(symbol, c) { this.variable = variables(symbol)[0]; if (!symbol.isPoly()) { throw new core.exceptions.NerdamerTypeError(`Polynomial Expected! Received ${core.Utils.text(symbol)}`); } c ||= []; if (!(/** @type {FracType} */ (symbol.power).absEquals(1))) { symbol = /** @type {NerdamerSymbolType} */ (_.expand(symbol)); } if (symbol.group === core.groups.N) { c[0] = symbol.multiplier; } else if (symbol.group === core.groups.S) { c[Number(/** @type {FracType} */ (symbol.power).toDecimal())] = symbol.multiplier; } else { for (const x in symbol.symbols) { if (!Object.hasOwn(symbol.symbols, x)) { continue; } const sub = symbol.symbols[x]; const p = sub.power; if (core.Utils.isSymbol(p)) { throw new core.exceptions.NerdamerTypeError('power cannot be a NerdamerSymbol'); } const pNum = sub.group === N ? 0 : Number(/** @type {FracType} */ (p).toDecimal()); if (sub.symbols) { this.parse(sub, c); } else { c[pNum] = sub.multiplier; } } } this.coeffs = c; this.fill(); }, /** * Fills in the holes in a polynomial with zeroes * * @this {Polynomial} * @param {number} [x] - The number to fill the holes with * @returns {Polynomial} */ fill(x) { x = Number(x) || 0; const l = this.coeffs.length; for (let i = 0; i < l; i++) { if (this.coeffs[i] === undefined) { this.coeffs[i] = new Frac(x); } } return this; }, /** * Removes higher order zeros or a specific coefficient * * @this {Polynomial} * @returns {Polynomial} */ trim() { let l = this.coeffs.length; while (l--) { const c = this.coeffs[l]; const equalsZero = c.equals(0); if (c && equalsZero) { if (l === 0) { break; } this.coeffs.pop(); } else { break; } } return this; }, /** * Returns polynomial mod p **currently fails** * * @this {Polynomial} * @param {number} p * @returns {Polynomial} */ modP(p) { const l = this.coeffs.length; for (let i = 0; i < l; i++) { let c = this.coeffs[i]; let j; if (c.lessThan(0)) { // Go borrow /** @type {FracType | undefined} */ let b; // A coefficient > 0 for (j = i; j < l; j++) { // Starting from where we left off if (this.coeffs[j].greaterThan(0)) { b = this.coeffs[j]; break; } } if (b) { // If such a coefficient exists for (; j > i; j--) { // Go down the line and adjust using p this.coeffs[j] = this.coeffs[j].subtract(new Frac(1)); this.coeffs[j - 1] = this.coeffs[j - 1].add(new Frac(p)); } c = this.coeffs[i]; // Reset c } } const d = c.mod(new Frac(p)); const w = c.subtract(d).divide(new Frac(p)); if (!w.equals(0)) { const upOne = i + 1; let next = this.coeffs[upOne] || new Frac(0); next = next.add(w); this.coeffs[upOne] = next; this.coeffs[i] = d; } } return this; }, /** * Adds together 2 polynomials * * @this {Polynomial} * @param {Polynomial} poly * @returns {Polynomial} */ add(poly) { const l = Math.max(this.coeffs.length, poly.coeffs.length); for (let i = 0; i < l; i++) { const a = this.coeffs[i] || new Frac(0); const b = poly.coeffs[i] || new Frac(0); this.coeffs[i] = a.add(b); } return this; }, /** * Subtracts 2 polynomials * * @this {Polynomial} * @param {Polynomial} poly * @returns {Polynomial} */ subtract(poly) { const l = Math.max(this.coeffs.length, poly.coeffs.length); for (let i = 0; i < l; i++) { const a = this.coeffs[i] || new Frac(0); const b = poly.coeffs[i] || new Frac(0); this.coeffs[i] = a.subtract(b); } return this; }, /** * Divides two polynomials * * @this {Polynomial} * @param {Polynomial} poly * @returns {[Polynomial, Polynomial]} */ divide(poly) { const { variable } = this; /** @type {FracType[]} */ const dividend = /** @type {FracType[]} */ (core.Utils.arrayClone(this.coeffs)); /** @type {FracType[]} */ const divisor = /** @type {FracType[]} */ (core.Utils.arrayClone(poly.coeffs)); const n = dividend.length; const mp = divisor.length - 1; /** @type {FracType[]} */ const quotient = []; // Loop through the dividend for (let i = 0; i < n; i++) { const p = n - (i + 1); // Get the difference of the powers const d = p - mp; // Get the quotient of the coefficients const q = dividend[p].divide(divisor[mp]); if (d < 0) { break; } // The divisor is not greater than the dividend // place it in the quotient quotient[d] = q; for (let j = 0; j <= mp; j++) { // Reduce the dividend dividend[j + d] = dividend[j + d].subtract(divisor[j].multiply(q)); } } // Clean up const p1 = Polynomial.fromArray(dividend, variable || 'x').trim(); // Pass in x for safety const p2 = Polynomial.fromArray(quotient, variable || 'x'); return [p2, p1]; }, /** * Multiplies two polynomials * * @this {Polynomial} * @param {Polynomial} poly * @returns {Polynomial} */ multiply(poly) { const l1 = this.coeffs.length; const l2 = poly.coeffs.length; /** @type {FracType[]} */ const c = []; // Array to be returned for (let i = 0; i < l1; i++) { const x1 = this.coeffs[i]; for (let j = 0; j < l2; j++) { const k = i + j; // Add the powers together const x2 = poly.coeffs[j]; const e = c[k] || new Frac(0); // Get the existing term from the new array c[k] = e.add(x1.multiply(x2)); // Multiply the coefficients and add to new polynomial array } } this.coeffs = c; return this; }, /** * Checks if a polynomial is zero * * @this {Polynomial} * @returns {boolean} */ isZero() { const l = this.coeffs.length; for (let i = 0; i < l; i++) { const e = this.coeffs[i]; if (!e.equals(0)) { return false; } } return true; }, /** * Substitutes in a number n into the polynomial p(n) * * @this {Polynomial} * @param {number} n * @returns {FracType} */ sub(n) { let sum = new Frac(0); const l = this.coeffs.length; for (let i = 0; i < l; i++) { const t = this.coeffs[i]; if (!t.equals(0)) { sum = sum.add(t.multiply(new Frac(n ** i))); } } return sum; }, /** * Returns a clone of the polynomial * * @this {Polynomial} * @returns {Polynomial} */ clone() { /** @type {Polynomial} */ const p = new Polynomial(); p.coeffs = this.coeffs.slice(); p.variable = this.variable; return p; }, /** * Gets the degree of the polynomial * * @this {Polynomial} * @returns {number} */ deg() { this.trim(); return this.coeffs.length - 1; }, /** * Returns a lead coefficient * * @this {Polynomial} * @returns {FracType} */ lc() { return this.coeffs[this.deg()].clone(); }, /** * Converts polynomial into a monic polynomial * * @this {Polynomial} * @returns {Polynomial} */ monic() { const lc = this.lc(); const l = this.coeffs.length; for (let i = 0; i < l; i++) { this.coeffs[i] = this.coeffs[i].divide(lc); } return this; }, /** * Returns the GCD of two polynomials * * @this {Polynomial} * @param {Polynomial} poly * @returns {Polynomial} */ gcd(poly) { // Get the maximum power of each const mp1 = this.coeffs.length - 1; const mp2 = poly.coeffs.length - 1; /** @type {[Polynomial, Polynomial]} */ let T; // Swap so we always have the greater power first if (mp1 < mp2) { return poly.gcd(this); } /** @type {Polynomial} */ let a = this; while (!poly.isZero()) { const t = poly.clone(); a = a.clone(); T = a.divide(t); poly = T[1]; a = t; } const gcd = core.Math2.QGCD.apply(null, a.coeffs); if (!gcd.equals(1)) { const l = a.coeffs.length; for (let i = 0; i < l; i++) { a.coeffs[i] = a.coeffs[i].divide(gcd); } } return a; }, /** * Differentiates the polynomial * * @this {Polynomial} * @returns {Polynomial} */ diff() { /** @type {FracType[]} */ const newArray = []; const l = this.coeffs.length; for (let i = 1; i < l; i++) { newArray.push(this.coeffs[i].multiply(new Frac(i))); } this.coeffs = newArray; return this; }, /** * Integrates the polynomial * * @this {Polynomial} * @returns {Polynomial} */ integrate() { /** @type {FracType[]} */ const newArray = [new Frac(0)]; const l = this.coeffs.length; for (let i = 0; i < l; i++) { const c = new Frac(i + 1); newArray[i + 1] = this.coeffs[i].divide(c); } this.coeffs = newArray; return this; }, /** * Returns the Greatest common factor of the polynomial * * @this {Polynomial} * @param {boolean} [toPolynomial] - True if a polynomial is wanted * @returns {[FracType, number] | Polynomial} */ gcf(toPolynomial) { // Get the first nozero coefficient and returns its power /** * @param {FracType[]} a * @returns {number | undefined} */ const fnz = function (a) { for (let i = 0; i < a.length; i++) { if (!a[i].equals(0)) { return i; } } return undefined; }; /** @type {FracType[]} */ const ca = []; for (let i = 0; i < this.coeffs.length; i++) { const c = this.coeffs[i]; if (!c.equals(0) && ca.indexOf(c) === -1) { ca.push(c); } } /** @type {[FracType, number] | Polynomial} */ let p = [core.Math2.QGCD.apply(undefined, ca), fnz(this.coeffs) || 0]; if (toPolynomial) { const parr = []; parr[p[1] - 1] = p[0]; p = Polynomial.fromArray(parr, this.variable).fill(); } return p; }, /** * Raises a polynomial P to a power p -> P^p. e.g. (x+1)^2 * * @this {Polynomial} * @param {boolean} [inclImg] - Include imaginary numbers * @returns {number[]} */ quad(inclImg) { /** @type {number[]} */ const roots = []; if (this.coeffs.length > 3) { throw new Error(`Cannot calculate quadratic order of ${this.coeffs.length - 1}`); } if (this.coeffs.length === 0) { throw new Error('Polynomial array has no terms'); } const a = this.coeffs[2] ? Number(this.coeffs[2].toDecimal()) : 0; const b = this.coeffs[1] ? Number(this.coeffs[1].toDecimal()) : 0; const c = Number(this.coeffs[0].toDecimal()); const dsc = b * b - 4 * a * c; if (dsc < 0 && !inclImg) { return roots; } roots[0] = (-b + Math.sqrt(dsc)) / (2 * a); roots[1] = (-b - Math.sqrt(dsc)) / (2 * a); return roots; }, /** * Makes polynomial square free * * @this {Polynomial} * @returns {[Polynomial, Polynomial, number]} */ squareFree() { const a = this.clone(); let i = 1; const b = a.clone().diff(); let c = a.clone().gcd(b); let w = a.divide(c)[0]; let output = Polynomial.fromArray([new Frac(1)], a.variable); while (!c.equalsNumber(1)) { const y = w.gcd(c); let z = w.divide(y)[0]; // One of the factors may have shown up since it's square but smaller than the // one where finding if (!z.equalsNumber(1) && i > 1) { const t = z.clone(); for (let j = 1; j < i; j++) { t.multiply(z.clone()); } z = t; } output = output.multiply(z); i++; w = y; c = c.divide(y)[0]; } return [output, w, i]; }, /** * Converts polynomial to NerdamerSymbol * * @this {Polynomial} * @returns {NerdamerSymbolType} */ toSymbol() { const l = this.coeffs.length; const { variable } = this; if (l === 0) { return new NerdamerSymbol(0); } // Polynomials must have a variable if (!variable) { throw new core.exceptions.NerdamerTypeError( 'Polynomial.toSymbol requires a variable. Constants should not be converted to Polynomial.' ); } const terms = []; for (let i = 0; i < l; i++) { const e = this.coeffs[i]; if (!e.equals(0)) { terms.push(`${e}*${variable}^${i}`); } } if (terms.length === 0) { return new NerdamerSymbol(0); } return _.parse(terms.join('+')); }, /** * Checks if polynomial is equal to a number * * @this {Polynomial} * @param {number} x * @returns {boolean} */ equalsNumber(x) { this.trim(); return this.coeffs.length === 1 && this.coeffs[0].toDecimal() === String(x); }, /** * @this {Polynomial} * @returns {string} */ toString() { return this.toSymbol().toString(); }, }; /** * # TODO * * # THIS METHOD HAS A NASTY HIDDEN BUG. IT HAS INCONSISTENT RETURN TYPES PRIMARILY DUE TO * * WRONG ASSUMPTIONS AT THE BEGINNING. THE ASSUMPTION WAS THAT COEFFS WERE ALWAYS GOING BE NUMBERS NOT TAKING INTO * ACCOUNT THAT IMAGINARY NUMBERS. FIXING THIS BREAKS WAY TOO MANY TESTS AT THEM MOMENT WHICH I DON'T HAVE TO FIX * * If the symbols is of group PL or CP it will return the multipliers of each symbol as these are polynomial * coefficients. CB symbols are glued together by multiplication so the symbol multiplier carries the coefficients * for all contained symbols. For S it just returns it's own multiplier. This function doesn't care if it's a * polynomial or not * * @this {NerdamerSymbolType} * @param {Array} [c] The coefficient array * @param {boolean} [withOrder] * @returns {Array} */ NerdamerSymbol.prototype.coeffs = function coeffs(c, withOrder) { if (withOrder && !this.isPoly(true)) { _.error('Polynomial expected when requesting coefficients with order'); } c ||= []; const s = this.clone().distributeMultiplier(); if (s.isComposite()) { for (const x in s.symbols) { if (!Object.hasOwn(s.symbols, x)) { continue; } const sub = s.symbols[x]; if (sub.isComposite()) { sub.clone().distributeMultiplier().coeffs(c, withOrder); } else if (withOrder) { c[sub.isConstant() ? 0 : Number(/** @type {FracType} */ (sub.power).toDecimal())] = sub.multiplier; } else { c.push(sub.multiplier); } } } else if (withOrder) { c[s.isConstant(true) ? 0 : Number(/** @type {FracType} */ (s.power).toDecimal())] = s.multiplier; } else if (s.group === CB && s.isImaginary()) { let m = new NerdamerSymbol(s.multiplier); s.each(x => { // Add the imaginary part if (x.isConstant(true) || x.imaginary) { m = /** @type {NerdamerSymbolType} */ (_.multiply(m, x)); } }); c.push(m); } else { c.push(s.multiplier); } // Fill the holes if (withOrder) { for (let i = 0; i < c.length; i++) { if (c[i] === undefined) { c[i] = new NerdamerSymbol(0); } } } return c; }; /** * @this {NerdamerSymbolType} * @param {Record<string, number> & { length: number }} map * @returns {MVTerm[]} */ NerdamerSymbol.prototype.tBase = function tBase(map) { if (typeof map === 'undefined') { throw new Error('NerdamerSymbol.tBase requires a map object!'); } /** @type {MVTerm[]} */ const terms = []; const symbols = /** @type {NerdamerSymbolType[]} */ (this.collectSymbols(null, null, null, true)); const l = symbols.length; for (let i = 0; i < l; i++) { const symbol = symbols[i]; const g = symbol.group; /** @type {MVTerm} */ const nterm = new MVTerm(symbol.multiplier, [], map); if (g === CB) { for (const x in symbol.symbols) { if (!Object.hasOwn(symbol.symbols, x)) { continue; } const sym = symbol.symbols[x]; nterm.terms[map[x]] = /** @type {FracType} */ (sym.power); } } else { nterm.terms[map[symbol.value]] = /** @type {FracType} */ (symbol.power); } terms.push(nterm.fill()); nterm.updateCount(); } return terms; }; /** * @this {NerdamerSymbolType} * @param {string} x * @returns {string} */ NerdamerSymbol.prototype.altVar = function altVar(x) { const m = this.multiplier.toString(); const p = this.power.toString(); return (m === '1' ? '' : `${m}*`) + x + (p === '1' ? '' : `^${p}`); }; /** * Checks to see if the symbols contain the same variables * * @this {NerdamerSymbolType} * @param {NerdamerSymbolType} symbol * @returns {boolean} */ NerdamerSymbol.prototype.sameVars = function sameVars(symbol) { if (!(this.symbols || this.group === symbol.group)) { return false; } for (const x in this.symbols) { if (!Object.hasOwn(this.symbols, x)) { continue; } const a = this.symbols[x]; const b = symbol.symbols[x]; if (!b) { return false; } if (a.value !== b.value) { return false; } } return true; }; /** * Groups the terms in a symbol with respect to a variable For instance the symbol {a_b^2_x^2+a_b_x^2+x+6} returns * [6,1,a_b+a_b^2] * * @this {NerdamerSymbolType} * @param {string} x * @returns {NerdamerSymbolType[]} */ NerdamerSymbol.prototype.groupTerms = function groupTerms(x) { x = String(x); /** @type {DecomposeResultType | undefined} */ let f; /** @type {number} */ let p; /** @type {NerdamerSymbolType[] | undefined} */ let egrouped; /** @type {NerdamerSymbolType[]} */ const grouped = []; this.each(e => { if (e.group === PL) { egrouped = e.groupTerms(x); for (let i = 0; i < egrouped.length; i++) { const el = egrouped[i]; if (el) { grouped[i] = el; } } } else { f = /** @type {DecomposeResultType} */ (core.Utils.decompose_fn(e, x, true)); p = /** @type {NerdamerSymbolType} */ (f.x).value === x ? Number(/** @type {NerdamerSymbolType} */ (f.x).power) : 0; // Check if there's an existing value grouped[p] = /** @type {NerdamerSymbolType} */ (_.add(grouped[p] || new NerdamerSymbol(0), f.a)); } }); return grouped; }; /** * Use this to collect Factors * * @this {NerdamerSymbolType} * @returns {NerdamerSymbolType[]} */ NerdamerSymbol.prototype.collectFactors = function collectFactors() { /** @type {NerdamerSymbolType[]} */ const factors = []; if (this.group === CB) { this.each(x => { factors.push(x.clone()); }); } else { factors.push(this.clone()); } return factors; }; /** * A container class for factors * * @class * @this {Factors} */ function Factors() { /** @type {Record<string, NerdamerSymbolType>} */ this.factors = {}; /** @type {number} */ this.length = 0; /** @type {((s: NerdamerSymbolType) => NerdamerSymbolType) | undefined} */ this.preAdd = undefined; /** @type {number | string | undefined} */ this.pFactor = undefined; } /** * @this {Factors} * @returns {number} */ Factors.prototype.getNumberSymbolics = function getNumberSymbolics() { let n = 0; this.each(x => { if (!x.isConstant(true)) { n++; } }); return n; }; /** * Adds the factors to the factor object * * @this {Factors} * @param {NerdamerSymbolType} s * @returns {Factors} */ Factors.prototype.add = function add(s) { if (s.equals(0)) { return this; } // Nothing to add // we don't want to carry -1 as a factor. If a factor already exists, // then add the minus one to that factor and return. if (s.equals(-1) && this.length > 0) { const fo = core.Utils.firstObject(this.factors, null, true); const newObj = /** @type {NerdamerSymbolType} */ ( _.symfunction(core.Settings.PARENTHESIS, [fo.obj]).negate() ); delete this.factors[fo.key]; this.add(newObj); this.length--; return this; } if (s.group === CB) { const factors = this; if (!s.multiplier.equals(1)) { factors.add(new NerdamerSymbol(s.multiplier)); } s.each(x => { factors.add(x); }); } else { if (this.preAdd) // If a preAdd function was defined call it to do prep { s = this.preAdd(s); } if (this.pFactor) // If the symbol isn't linear add back the power { s = /** @type {NerdamerSymbolType} */ (_.pow(s, new NerdamerSymbol(this.pFactor))); } const isConstant = s.isConstant(); if (isConstant && s.equals(1)) { return this; } // Don't add 1 const v = isConstant ? s.value : s.text(); if (v in this.factors) { this.factors[v] = /** @type {NerdamerSymbolType} */ (_.multiply(this.factors[v], s)); // Did the addition cancel out the existing factor? If so remove it and decrement the length if (this.factors[v].equals(1)) { delete this.factors[v]; this.length--; } } else { this.factors[v] = s; this.length++; } } return this; }; /** * Converts the factor object to a NerdamerSymbol * * @this {Factors} * @returns {NerdamerSymbolType} */ Factors.prototype.toSymbol = function toSymbol() { /** @type {NerdamerSymbolType} */ let factored = new NerdamerSymbol(1); const factors = Object.values(this.factors).sort((a, b) => (a.group > b.group ? 1 : -1)); for (let i = 0, l = factors.length; i < l; i++) { const f = factors[i]; // Don't wrap group S or FN const factor = f.power.equals(1) && f.fname !== '' /* Don't wrap it twice */ ? _.symfunction(core.Settings.PARENTHESIS, [f]) : f; factored = /** @type {NerdamerSymbolType} */ (_.multiply(factored, factor)); } if (factored.fname === '') { factored = NerdamerSymbol.unwrapPARENS(factored); } return factored; }; /** * Merges 2 factor objects into one * * @this {Factors} * @param {Record<string, NerdamerSymbolType>} o * @returns {Factors} */ Factors.prototype.merge = function merge(o) { for (const x in o) { if (x in this.factors) { this.factors[x] = /** @type {NerdamerSymbolType} */ (_.multiply(this.factors[x], o[x])); } else { this.factors[x] = o[x]; } } return this; }; /** * The iterator for the factor object * * @this {Factors} * @param {(factor: NerdamerSymbolType, key: string) => void} f - Callback * @returns {Factors} */ Factors.prototype.each = function each(f) { for (const x in this.factors) { if (!Object.hasOwn(this.factors, x)) { continue; } let factor = this.factors[x]; if (factor.fname === core.Settings.PARENTHESIS && factor.isLinear()) { factor = factor.args[0]; } f.call(this, factor, x); } return this; }; /** * Return the number of factors contained in the factor object * * @this {Factors} * @returns {number} */ Factors.prototype.count = function count() { return keys(this.factors).length; }; /** * Cleans up factors from -1 * * @this {Factors} * @returns {void} */ Factors.prototype.clean = function clean() { try { const h = core.Settings.CONST_HASH; if (this.factors[h].lessThan(0)) { if (this.factors[h].equals(-1)) { delete this.factors[h]; } else { this.factors[h].negate(); } this.each(x => { x.negate(); }); } } catch (e) { if (/** @type {Error} */ (e).message === 'timeout') { throw e; } } }; /** * @this {Factors} * @returns {string} */ Factors.prototype.toString = function toString() { return this.toSymbol().toString(); }; /** * A wrapper for performing multivariate division * * @class * @this {MVTerm} * @param {FracType} coeff * @param {FracType[]} [terms] * @param {Record<string, number> & { length: number }} [map] */ function MVTerm(coeff, terms, map) { /** @type {FracType[]} */ this.terms = terms || []; /** @type {FracType} */ this.coeff = coeff; /** @type {(Record<string, number> & { length: number }) | undefined} */ this.map = map; // Careful! all maps are the same object /** @type {FracType} */ this.sum = new Frac(0); /** @type {string | undefined} */ this.image = undefined; /** @type {Record<number, string> | undefined} */ this.revMap = undefined; /** @type {number | undefined} */ this.count = undefined; } /** * @this {MVTerm} * @returns {MVTerm} */ MVTerm.prototype.updateCount = function updateCount() { this.count ||= 0; for (let i = 0; i < this.terms.length; i++) { if (!this.terms[i].equals(0)) { this.count++; } } return this; }; /** * @this {MVTerm} * @returns {string} */ MVTerm.prototype.getVars = function getVars() { /** @type {string[]} */ const vars = []; for (let i = 0; i < this.terms.length; i++) { const term = this.terms[i]; this.getRevMap(); if (!term.equals(0) && this.revMap) { vars.push(this.revMap[i]); } } return vars.join(' '); }; /** * @this {MVTerm} * @returns {number} */ MVTerm.prototype.len = function len() { if (typeof this.count === 'undefined') { this.updateCount(); } return this.count || 0; }; /** * @this {MVTerm} * @param {Record<number, string>} [revMap] * @returns {NerdamerSymbolType} */ MVTerm.prototype.toSymbol = function toSymbol(revMap) { revMap ||= this.getRevMap(); /** @type {NerdamerSymbolType} */ let symbol = new NerdamerSymbol(this.coeff); for (let i = 0; i < this.terms.length; i++) { const v = revMap[i]; const t = this.terms[i]; if (t.equals(0) || v === CONST_HASH) { continue; } const mapped = new NerdamerSymbol(v); mapped.power = t; symbol = /** @type {NerdamerSymbolType} */ (_.multiply(symbol, mapped)); } return symbol; }; /** * @this {MVTerm} * @returns {Record<number, string>} */ MVTerm.prototype.getRevMap = function getRevMap() { if (this.revMap) { return this.revMap; } /** @type {Record<number, string>} */ const o = {}; if (this.map) { for (const x in this.map) { if (!Object.hasOwn(this.map, x)) { continue; } o[this.map[x]] = x; } } this.revMap = o; return o; }; /** * @this {MVTerm} * @returns {MVTerm} */ MVTerm.prototype.generateImage = function generateImage() { this.image = this.terms.join(' '); return this; }; /** * @this {MVTerm} * @returns {string} */ MVTerm.prototype.getImg = function getImg() { if (!this.image) { this.generateImage(); } return this.image || ''; }; /** * @this {MVTerm} * @returns {MVTerm} */ MVTerm.prototype.fill = function fill() { const l = this.map ? this.map.length : 0; for (let i = 0; i < l; i++) { if (typeof this.terms[i] === 'undefined') { this.terms[i] = new Frac(0); } else { this.sum = this.sum.add(this.terms[i]); } } return this; }; /** * @this {MVTerm} * @param {MVTerm} mvterm * @returns {MVTerm} */ MVTerm.prototype.divide = function divide(mvterm) { const c = this.coeff.divide(mvterm.coeff); const l = this.terms.length; /** @type {MVTerm} */ const newMvterm = new MVTerm(c, [], this.map); for (let i = 0; i < l; i++) { newMvterm.terms[i] = this.terms[i].subtract(mvterm.terms[i]); newMvterm.sum = newMvterm.sum.add(newMvterm.terms[i]); } return newMvterm; }; /** * @this {MVTerm} * @param {MVTerm} mvterm * @returns {MVTerm} */ MVTerm.prototype.multiply = function multiply(mvterm) { const c = this.coeff.multiply(mvterm.coeff); const l = this.terms.length; /** @type {MVTerm} */ const newMvterm = new MVTerm(c, [], this.map); for (let i = 0; i < l; i++) { newMvterm.terms[i] = this.terms[i].add(mvterm.terms[i]); newMvterm.sum = newMvterm.sum.add(newMvterm.terms[i]); } return newMvterm; }; /** * @this {MVTerm} * @returns {boolean} */ MVTerm.prototype.isZero = function isZero() { return this.coeff.equals(0); }; /** * @this {MVTerm} * @returns {string} */ MVTerm.prototype.toString = function toString() { return `{ coeff: ${this.coeff.toString()}, terms: [${this.terms.join( ',' )}]: sum: ${this.sum.toString()}, count: ${this.count}}`; }; /** * @param {string[]} arr * @returns {Record<string, number> & { length: number }} */ core.Utils.toMapObj = function toMapObj(arr) { let c = 0; /** @type {Record<string, number> & { length: number }} */ const o = /** @type {Record<string, number> & { length: number }} */ ({ length: 0 }); for (let i = 0; i < arr.length; i++) { const v = arr[i]; if (typeof o[v] === 'undefined') { o[v] = c; c++; } } o.length = c; return o; }; /** * @template T * @param {T} v * @param {number} n * @param {new (v: T) => T} [Clss] * @returns {T[]} */ core.Utils.filledArray = function filledArray(v, n, Clss) { const a = []; while (n--) { a[n] = Clss ? new Clss(v) : v; } return a; }; /** * @param {number[]} arr * @returns {number} */ core.Utils.arrSum = function arrSum(arr) { let sum = 0; const l = arr.length; for (let i = 0; i < l; i++) { sum += arr[i]; } return sum; }; /** * Determines if 2 arrays have intersecting elements. * * @template T * @param {T[]} a * @param {T[]} b * @returns {boolean} True if a and b have intersecting elements. */ core.Utils.haveIntersection = function haveIntersection(a, b) { if (b.length > a.length) { [a, b] = [b, a]; // IndexOf to loop over shorter } return a.some(e => b.indexOf(e) > -1); }; /** * Substitutes out functions as variables so they can be used in regular algorithms * * @param {NerdamerSymbolType} symbol * @param {Record<string, string>} [map] * @returns {string} The expression string */ core.Utils.subFunctions = function subFunctions(symbol, map) { map ||= {}; /** @type {string[]} */ const subbed = []; const vars = new Set(variables(symbol)); symbol.each(x => { if (x.group === FN || x.previousGroup === FN) { // We need a new variable name so why not use one of the existing const val = core.Utils.text(x, 'hash'); const tvar = map[val]; if (tvar) { subbed.push(x.altVar(tvar)); } else { // Generate a unique enough name // GM make sure it's not the name of an existing variable let i = 0; let t; do { t = x.fname + keys(map).length + (i > 0 ? String(i) : ''); i++; } while (vars.has(t)); map[val] = t; subbed.push(x.altVar(t)); } } else if (x.group === CB || x.group === PL || x.group === CP) { subbed.push(core.Utils.subFunctions(x, map)); } else { subbed.push(x.text()); } }); if (symbol.group === CP || symbol.group === PL) { return symbol.altVar(core.Utils.inBrackets(subbed.join('+'))); } if (symbol.group === CB) { return symbol.altVar(core.Utils.inBrackets(subbed.join('*'))); } return symbol.text(); }; /** * @param {Record<string, string>} map * @returns {Record<string, NerdamerSymbolType>} */ core.Utils.getFunctionsSubs = function getFunctionsSubs(map) { /** @type {Record<string, NerdamerSymbolType>} */ const subs = {}; // Prepare substitutions for (const x in map) { if (!Object.hasOwn(map, x)) { continue; } subs[map[x]] = _.parse(x); } return subs; }; /** @type {AlgebraModuleType} */ const __ = (core.Algebra = { version: '1.4.6', /** * @param {NerdamerSymbolType | Array} symbol * @param {number} [decp] * @returns {(string | number)[]} */ proots(symbol, decp) { // The roots will be rounded up to 7 decimal places. // if this causes trouble you can explicitly pass in a different number of places // rarr for polynomial of power n is of format [n, coeff x^n, coeff x^(n-1), ..., coeff x^0] decp ||= 7; const zeros = 0; /** @type {(string | number)[]} */ const knownRoots = []; /** * @param {FracType[]} rarr * @param {(string | number)[]} powers * @param {number} max * @returns {(string | number)[]} */ const getRoots = function (rarr, powers, max) { const roots = calcroots(rarr, powers, max).concat(knownRoots); for (let i = 0; i < zeros; i++) { roots.unshift(0); } return /** @type {string[]} */ (roots); }; if (core.Utils.isSymbol(symbol) && /** @type {NerdamerSymbolType} */ (symbol).isPoly()) { let sym = /** @type {NerdamerSymbolType} */ (symbol); sym.distributeMultiplier(); // Make it so the symbol has a constants as the lowest term if (sym.group === PL) { const lowestPow = core.Utils.arrayMin( /** @type {number[]} */ (/** @type {unknown} */ (keys(sym.symbols))) ); const lowestSymbol = sym.symbols[lowestPow].clone().toUnitMultiplier(); sym = /** @type {NerdamerSymbolType} */ (_.expand(_.divide(sym, lowestSymbol))); knownRoots.push(0); // Add zero since this is a known root } if (sym.group === core.groups.S) { return [/** @type {string} */ ('0')]; } if (sym.group === core.groups.PL) { const powers = keys(sym.symbols); const minpower = core.Utils.arrayMin(/** @type {number[]} */ (/** @type {unknown} */ (powers))); sym = /** @type {NerdamerSymbolType} */ ( core.PARSER.divide(sym, core.PARSER.parse(`${sym.value}^${minpower}`)) ); } const variable = keys(sym.symbols).sort().pop(); const subSym = sym.group === core.groups.PL ? sym.symbols : sym.symbols[variable || '']; const g = subSym.group;