UNPKG

nerdamer-prime

Version:
1,194 lines (1,134 loc) 198 kB
/* * Author : Martin Donk * Website : http://www.nerdamer.com * Email : martin.r.donk@gmail.com * 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 {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.FactorSubModule} FactorSubModuleType * * @typedef {import('./index').NerdamerCore.PartFracSubModule} PartFracSubModuleType * * @typedef {import('./index').NerdamerCore.AlgebraClassesSubModule} AlgebraClassesSubModuleType * * @typedef {import('./index').NerdamerCore.Factors} FactorsType * * @typedef {import('./index').NerdamerCore.SimplifySubModule} SimplifySubModuleType * * @typedef {import('./index').NerdamerCore.CalculusModule} CalculusModuleType * * Constructor types * * @typedef {import('./index').NerdamerCore.FracConstructor} FracConstructor * * @typedef {import('./index').NerdamerCore.SymbolConstructor} SymbolConstructor * * @typedef {import('./index').NerdamerCore.VectorConstructor} VectorConstructor * * @typedef {import('./index').NerdamerCore.MatrixConstructor} MatrixConstructor * * @typedef {import('./index').NerdamerCore.DecomposeResultObject} DecomposeResultType * * @typedef {import('./index').NerdamerCore.IntegrationOptions} IntegrationOptions */ // 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('./Algebra.js'); } /** @returns {CalculusModuleType} */ (function initCalculusModule() { const core = nerdamer.getCore(); const _ = core.PARSER; const { Frac } = core; const { Settings } = core; const { isSymbol } = core.Utils; const { FN } = core.groups; const { NerdamerSymbol } = core; const { text } = core.Utils; const { inBrackets } = core.Utils; const { isInt } = core.Utils; const { format } = core.Utils; const { even } = core.Utils; const { evaluate } = core.Utils; const { N } = core.groups; const { S } = core.groups; const { PL } = core.groups; const { CP } = core.groups; const { CB } = core.groups; const { EX } = core.groups; const { P } = core.groups; const { LOG } = Settings; const EXP = 'exp'; const ABS = 'abs'; const SQRT = 'sqrt'; const SIN = 'sin'; const COS = 'cos'; const TAN = 'tan'; const SEC = 'sec'; const CSC = 'csc'; const COT = 'cot'; const ASIN = 'asin'; const ACOS = 'acos'; const ATAN = 'atan'; const ASEC = 'asec'; const ACSC = 'acsc'; const ACOT = 'acot'; const SINH = 'sinh'; const COSH = 'cosh'; const TANH = 'tanh'; const CSCH = 'csch'; const SECH = 'sech'; const COTH = 'coth'; const ASECH = 'asech'; const ACSCH = 'acsch'; const ACOTH = 'acoth'; /** * Check if a symbol's power is itself a symbol with group S or CB * * @param {NerdamerSymbolType} sym * @returns {boolean} */ function hasPowerGroupSOrCB(sym) { return isSymbol(sym.power) && (sym.power.group === S || sym.power.group === CB); } // Custom errors function NoIntegralFound(msg) { this.message = msg || ''; } NoIntegralFound.prototype = new Error(); // Preparations NerdamerSymbol.prototype.hasIntegral = function hasIntegral() { return this.containsFunction('integrate'); }; // Transforms a function NerdamerSymbol.prototype.fnTransform = function fnTransform() { if (this.group !== FN) { return this; } let retval; const a = this.args[0]; const m = new NerdamerSymbol(this.multiplier); const sym = this.clone().toUnitMultiplier(); if (this.isLinear()) { switch (this.fname) { case SINH: retval = _.parse(format('(e^({0})-e^(-({0})))/2', a)); break; case COSH: retval = _.parse(format('(e^({0})+e^(-({0})))/2', a)); break; case TANH: retval = _.parse(format('(e^({0})-e^(-({0})))/(e^({0})+e^(-({0})))', a)); break; case TAN: retval = _.parse(format('sin({0})/cos({0})', a)); break; case CSC: retval = _.parse(format('1/sin({0})', a)); break; case SEC: retval = _.parse(format('1/cos({0})', a)); break; default: retval = sym; } } else if (this.power.equals(2)) { switch (this.fname) { case SIN: retval = _.parse(format('1/2-cos(2*({0}))/2', a)); break; case COS: retval = _.parse(format('1/2+cos(2*({0}))/2', a)); break; case TAN: // Retval = _.parse(format('(1-cos(2*({0})))/(1+cos(2*({0})))', a)); retval = _.parse(format('sin({0})^2/cos({0})^2', a)); break; case COSH: retval = _.parse(format('1/2+cosh(2*({0}))/2', a)); break; case SINH: retval = _.parse(format('-1/2+cosh(2*({0}))/2', a)); break; case TANH: retval = _.parse(format('(1+cosh(2*({0})))/(-1+cosh(2*({0})))', a)); break; case SEC: retval = _.parse(format('(1-cos(2*({0})))/(1+cos(2*({0})))+1', a)); break; default: retval = sym; } } else if (this.fname === SEC) { retval = _.parse(format('1/cos({0})^({1})', this.args[0], this.power)); } else if (this.fname === CSC) { retval = _.parse(format('1/sin({0})^({1})', this.args[0], this.power)); } else if (this.fname === TAN) { if (this.power.lessThan(0)) { retval = _.parse(format('cos({0})^(-({1}))/sin({0})^({1})', this.args[0], this.power.negate())); } else { retval = _.parse(format('sin({0})^({1})/cos({0})^({1})', this.args[0], this.power)); } } else if (this.fname === SIN && this.power.lessThan(0)) { retval = _.parse(format('csc({0})^(-({1}))', this.args[0], this.power.negate())); } else if (this.fname === COS && this.power.lessThan(0)) { retval = _.parse(format('sec({0})^(-({1}))', this.args[0], this.power.negate())); } else if (this.fname === SIN && this.power.equals(3)) { retval = _.parse(format('(3*sin({0})-sin(3*({0})))/4', this.args[0])); } else if (this.fname === COS && this.power.equals(3)) { retval = _.parse(format('(cos(3*({0}))+3*cos({0}))/4', this.args[0])); } // Cos(a*x)^(2*n) or sin(a*x)^(2*n) else if ((this.fname === COS || this.fname === SIN) && even(this.power)) { const n = this.power / 2; // Convert to a double angle const cloned = /** @type {NerdamerSymbolType} */ (this.clone().toLinear()); const doubleAngle = /** @type {NerdamerSymbolType} */ (_.pow(cloned, _.parse(2))); const transformed = /** @type {NerdamerSymbolType} */ ( _.expand(_.pow(doubleAngle.fnTransform(), _.parse(n))) ); retval = new NerdamerSymbol(0); transformed.each(s => { const t = s.fnTransform(); retval = /** @type {NerdamerSymbolType} */ (_.add(retval, t)); }, true); } else { retval = sym; } return _.multiply(retval, m); }; NerdamerSymbol.prototype.hasTrig = function hasTrig() { if (this.isConstant(true) || this.group === S) { return false; } if (this.fname && (core.Utils.inTrig(this.fname) || core.Utils.inInverseTrig(this.fname))) { return true; } if (this.symbols) { for (const x in this.symbols) { if (this.symbols[x].hasTrig()) { return true; } } } return false; }; core.Expression.prototype.hasIntegral = function hasIntegral() { return this.symbol.hasIntegral(); }; /** * Attempts to rewrite a symbol under one common denominator * * @param {NerdamerSymbolType} symbol * @returns {NerdamerSymbolType} */ core.Utils.toCommonDenominator = function toCommonDenominator(symbol) { // Transform x/a+x -> (ax+x)/a if (symbol.isComposite() && symbol.isLinear()) { const m = new NerdamerSymbol(symbol.multiplier); let denominator = new NerdamerSymbol(1); let numerator = new NerdamerSymbol(0); symbol.each(x => { denominator = /** @type {NerdamerSymbolType} */ (_.multiply(denominator, x.getDenom())); }, true); // Remove the denomitor in each term symbol.each(x => { const num = x.getNum(); const den = x.getDenom(); const factor = /** @type {NerdamerSymbolType} */ (_.multiply(num, _.divide(denominator.clone(), den))); numerator = /** @type {NerdamerSymbolType} */ (_.add(numerator, factor)); }); const retval = /** @type {NerdamerSymbolType} */ ( _.multiply( m, core.Algebra.divide( /** @type {NerdamerSymbolType} */ (_.expand(numerator)), /** @type {NerdamerSymbolType} */ (_.expand(denominator)) ) ) ); return retval; } return symbol; }; // A function to check if a function name is an inverse trig function core.Utils.inInverseTrig = function inInverseTrig(x) { const invTrigFns = [ASIN, ACOS, ATAN, ACSC, ASEC, ACOT]; return invTrigFns.indexOf(x) !== -1; }; // A function to check if a function name is a trig function core.Utils.inTrig = function inTrig(x) { const trigFns = [COS, SIN, TAN, SEC, CSC, COT]; return trigFns.indexOf(x) !== -1; }; core.Utils.inHtrig = function inHtrig(x) { const trigFns = [SINH, COSH, TANH, ACSCH, ASECH, ACOTH]; return trigFns.indexOf(x) !== -1; }; // Matrix functions core.Matrix.jacobian = function jacobian(eqns, vars) { const result = new core.Matrix(); // Get the variables if not supplied vars ||= core.Utils.arrayGetVariables(eqns); vars.forEach((v, i) => { eqns.forEach((eq, j) => { const e = core.Calculus.diff(eq.clone(), v); result.set(j, i, e); }); }); return result; }; core.Matrix.prototype.max = function max() { let maxValue = new NerdamerSymbol(0); this.each(x => { const e = x.abs(); if (e.gt(maxValue)) { maxValue = e; } }); return maxValue; }; core.Matrix.cMatrix = function cMatrix(value, vars) { const m = new core.Matrix(); // Make an initial guess vars.forEach((v, i) => { m.set(i, 0, _.parse(value)); }); return m; }; /** * Checks if all elements in an array are function symbols * * @param {NerdamerSymbolType[]} arr * @returns {boolean} */ const allFunctions = (core.Utils.allFunctions = function allFunctions(arr) { for (let i = 0, l = arr.length; i < l; i++) { if (arr[i].group !== FN) { return false; } } return true; }); /** * Transforms cos(a)*sin(b) into (sin(a+b)-sin(a-b))/2 * * @param {NerdamerSymbolType} symbol1 * @param {NerdamerSymbolType} symbol2 * @returns {NerdamerSymbolType} */ const cosAsinBtransform = (core.Utils.cosAsinBtranform = function cosAsinBtranform(symbol1, symbol2) { const a = symbol1.args[0]; const b = symbol2.args[0]; return /** @type {NerdamerSymbolType} */ (_.parse(format('(sin(({0})+({1}))-sin(({0})-({1})))/2', a, b))); }); /** * Transforms cos(a)*sin(a) into sin(2a)/2 * * @param {NerdamerSymbolType} symbol1 * @param {NerdamerSymbolType} symbol2 * @returns {NerdamerSymbolType} */ const cosAsinAtransform = (core.Utils.cosAsinAtranform = function cosAsinAtranform(symbol1, symbol2) { // TODO: temporary fix for integrate(e^x*sin(x)*cos(x)^2). // we technically know how to do this transform but more is needed for correct output if (Number(symbol2.power) !== 1) { return /** @type {NerdamerSymbolType} */ (_.multiply(symbol1, symbol2)); } const a = symbol1.args[0]; return /** @type {NerdamerSymbolType} */ (_.parse(format('(sin(2*({0})))/2', a))); }); /** * Transforms sin(a)*sin(b) into (cos(a+b)-cos(a-b))/2 * * @param {NerdamerSymbolType} symbol1 * @param {NerdamerSymbolType} symbol2 * @returns {NerdamerSymbolType} */ const sinAsinBtransform = (core.Utils.cosAsinBtranform = function cosAsinBtranform(symbol1, symbol2) { const a = symbol1.args[0]; const b = symbol2.args[0]; return /** @type {NerdamerSymbolType} */ (_.parse(format('(cos(({0})+({1}))-cos(({0})-({1})))/2', a, b))); }); /** * Transforms an array of trig functions into simplified form * * @param {NerdamerSymbolType[]} arr * @returns {NerdamerSymbolType} */ const trigTransform = (core.Utils.trigTransform = function trigTransform(arr) { /** @type {Record<string, NerdamerSymbolType>} */ const map = {}; let symbol; let t; let retval = new NerdamerSymbol(1); for (let i = 0, l = arr.length; i < l; i++) { symbol = arr[i]; if (symbol.group === FN) { const { fname } = symbol; if (fname === COS && map[SIN]) { if (map[SIN].args[0].toString() === symbol.args[0].toString()) { t = cosAsinAtransform(symbol, map[SIN]); } else { t = cosAsinBtransform(symbol, map[SIN]); } delete map[SIN]; retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, t)); } else if (fname === SIN && map[COS]) { if (map[COS].args[0].toString() === symbol.args[0].toString()) { t = cosAsinAtransform(symbol, map[COS]); } else { t = cosAsinBtransform(symbol, map[COS]); } delete map[COS]; retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, t)); } else if (fname === SIN && map[SIN]) { if (map[SIN].args[0].toString() === symbol.args[0].toString()) { // This should actually be redundant code but let's put just in case t = /** @type {NerdamerSymbolType} */ (_.multiply(symbol, map[SIN])); delete map[SIN]; } else { t = sinAsinBtransform(symbol, map[SIN]); delete map[SIN]; } retval = t; } else { map[fname] = symbol; } } else { retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, symbol)); } } // Put back the remaining functions for (const x in map) { if (!Object.hasOwn(map, x)) { continue; } retval = /** @type {NerdamerSymbolType} */ (_.multiply(retval, map[x])); } return retval; }); core.Settings.integration_depth = 10; core.Settings.max_lim_depth = 10; /** @type {CalculusModuleType} */ const __ = (core.Calculus = { version: '1.4.6', /** * Computes the sum of a function over an index range * * @param {NerdamerSymbolType} fn * @param {NerdamerSymbolType} index * @param {NerdamerSymbolType} start * @param {NerdamerSymbolType} end * @returns {NerdamerSymbolType} */ sum(fn, index, start, end) { if (!(index.group === core.groups.S)) { throw new core.exceptions.NerdamerTypeError(`Index must be symbol. ${text(index)} provided`); } const indexName = index.value; let retval; if (core.Utils.isNumericSymbol(start) && core.Utils.isNumericSymbol(end)) { const modifier = /** @type {'' | 'PARSE2NUMBER'} */ ( Number(end) - Number(start) < 200 ? '' : 'PARSE2NUMBER' ); const startNum = Number(start); const endNum = Number(end); retval = core.Utils.block(modifier, () => { const f = fn.text(); /** @type {Record<string, NerdamerSymbolType | boolean>} */ const subs = { '~': true }; // Lock subs. Is this even being used? let result = new core.NerdamerSymbol(0); for (let i = startNum; i <= endNum; i++) { subs[indexName] = new NerdamerSymbol(i); const ans = _.parse(f, /** @type {Record<string, ExpressionParam>} */ (subs)); result = /** @type {NerdamerSymbolType} */ (_.add(result, ans)); } return result; }); } else { retval = _.symfunction('sum', [fn, new NerdamerSymbol(indexName), start, end]); } return retval; }, /** * Computes the product of a function over an index range * * @param {NerdamerSymbolType} fn * @param {NerdamerSymbolType} index * @param {NerdamerSymbolType} start * @param {NerdamerSymbolType} end * @returns {NerdamerSymbolType} */ product(fn, index, start, end) { if (!(index.group === core.groups.S)) { throw new core.exceptions.NerdamerTypeError(`Index must be symbol. ${text(index)} provided`); } const indexName = index.value; let retval; if (core.Utils.isNumericSymbol(start) && core.Utils.isNumericSymbol(end)) { const modifier = /** @type {'' | 'PARSE2NUMBER'} */ ( Number(end) - Number(start) < 200 ? '' : 'PARSE2NUMBER' ); retval = core.Utils.block(modifier, () => { const startNum = Number(start); const endNum = Number(end.multiplier); const f = fn.text(); /** @type {Record<string, NerdamerSymbolType>} */ const subs = {}; let result = new core.NerdamerSymbol(1); for (let i = startNum; i <= endNum; i++) { subs[indexName] = new NerdamerSymbol(i); result = /** @type {NerdamerSymbolType} */ ( _.multiply(result, _.parse(f, /** @type {Record<string, ExpressionParam>} */ (subs))) ); } return result; }); } else { retval = _.symfunction('product', [fn, new NerdamerSymbol(indexName), start, end]); } return retval; }, /** * Computes the derivative of a symbol * * @param {NerdamerSymbolType | VectorType | MatrixType} symbol * @param {NerdamerSymbolType | string} [wrt] * @param {NerdamerSymbolType | number} [nth] * @returns {NerdamerSymbolType | VectorType | MatrixType} */ diff(symbol, wrt, nth) { if (core.Utils.isVector(symbol)) { const vector = new core.Vector([]); symbol.each(x => { vector.elements.push(__.diff(/** @type {NerdamerSymbolType} */ (x), wrt, nth)); }); return vector; } if (core.Utils.isMatrix(symbol)) { const matrix = new core.Matrix(); symbol.each((x, i, j) => { matrix.set(i, j, __.diff(/** @type {NerdamerSymbolType} */ (x), wrt, nth)); }); return matrix; } const sym = /** @type {NerdamerSymbolType & { LHS?: NerdamerSymbolType; RHS?: NerdamerSymbolType }} */ ( symbol ); if (sym.LHS && sym.RHS) { // Equation, diff both sides const result = new core.Equation( /** @type {NerdamerSymbolType} */ (__.diff(sym.LHS.clone(), wrt, nth)), /** @type {NerdamerSymbolType} */ (__.diff(sym.RHS.clone(), wrt, nth)) ); return /** @type {NerdamerSymbolType} */ (/** @type {unknown} */ (result)); } let d = isSymbol(wrt) ? wrt.text() : wrt; // The nth derivative nth = /** @type {number} */ (isSymbol(nth) ? nth.multiplier.toDecimal() : nth || 1); if (d === undefined) { d = core.Utils.variables(/** @type {NerdamerSymbolType} */ (symbol))[0]; } // Unwrap sqrt if (sym.group === FN && sym.fname === SQRT) { const s = sym.args[0]; const sp = /** @type {FracType} */ (sym.power).clone(); // These groups go to zero anyway so why waste time? if (s.group !== N || s.group !== P) { s.power = isSymbol(s.power) ? /** @type {NerdamerSymbolType | FracType} */ ( _.multiply(_.multiply(s.power, new NerdamerSymbol(1 / 2)), new NerdamerSymbol(sp)) ) : s.power.multiply(new Frac(0.5)).multiply(sp); s.multiplier = s.multiplier.multiply(sym.multiplier); } symbol = s; } if (symbol.group === FN && !isSymbol(symbol.power)) { const a = derive(_.parse(symbol)); const b = __.diff(symbol.args[0].clone(), d); symbol = _.multiply(a, b); // Chain rule } else { symbol = derive(symbol); } if (nth > 1) { nth--; symbol = __.diff(symbol, wrt, nth); } return symbol; // Equivalent to "derivative of the outside". function polydiff(s) { if (s.value === d || s.contains(d, true)) { s.multiplier = s.multiplier.multiply(s.power); s.power = s.power.subtract(new Frac(1)); if (s.power.equals(0)) { s = new NerdamerSymbol(s.multiplier); } } return s; } function derive(s) { const g = s.group; let _a; let b; let cp; if (g === N || (g === S && s.value !== d) || g === P) { s = new NerdamerSymbol(0); } else if (g === S) { s = polydiff(s); } else if (g === CB) { const m = s.multiplier.clone(); s.toUnitMultiplier(); const retval = _.multiply(productRule(s), polydiff(s)); retval.multiplier = retval.multiplier.multiply(m); return retval; } else if (g === FN && s.power.equals(1)) { // Table of known derivatives const m = s.multiplier.clone(); s.toUnitMultiplier(); switch (s.fname) { case LOG: cp = s.clone(); s = s.args[0].clone(); // Get the arguments s.power = s.power.negate(); s.multiplier = cp.multiplier.divide(s.multiplier); break; case COS: // Cos -> -sin s.fname = SIN; s.multiplier.negate(); break; case SIN: // Sin -> cos s.fname = COS; break; case TAN: // Tan -> sec^2 s.fname = SEC; s.power = new Frac(2); break; case SEC: // Use a clone if this gives errors s = qdiff(s, TAN); break; case CSC: s = qdiff(s, '-cot'); break; case COT: s.fname = CSC; s.multiplier.negate(); s.power = new Frac(2); break; case ASIN: s = _.parse(`(sqrt(1-(${text(s.args[0])})^2))^(-1)`); break; case ACOS: s = _.parse(`-(sqrt(1-(${text(s.args[0])})^2))^(-1)`); break; case ATAN: s = _.parse(`(1+(${text(s.args[0])})^2)^(-1)`); break; case ABS: // Depending on the complexity of the symbol it's easier to just parse it into a new symbol // this should really be readdressed soon b = s.args[0].clone(); b.toUnitMultiplier(); s = _.parse(`${inBrackets(text(s.args[0]))}/abs${inBrackets(text(b))}`); break; case 'parens': // See product rule: f'.g goes to zero since f' will return zero. This way we only get back // 1*g' s = new NerdamerSymbol(1); break; case 'cosh': // Cosh -> -sinh s.fname = 'sinh'; break; case 'sinh': // Sinh -> cosh s.fname = 'cosh'; break; case TANH: // Tanh -> sech^2 s.fname = SECH; s.power = new Frac(2); break; case SECH: // Use a clone if this gives errors s = qdiff(s, '-tanh'); break; case CSCH: { const cschArg = String(s.args[0]); s = _.parse(`-coth(${cschArg})*csch(${cschArg})`); break; } case COTH: { const cothArg = String(s.args[0]); s = _.parse(`-csch(${cothArg})^2`); break; } case 'asinh': s = _.parse(`(sqrt(1+(${text(s.args[0])})^2))^(-1)`); break; case 'acosh': s = _.parse(`(sqrt(-1+(${text(s.args[0])})^2))^(-1)`); break; case 'atanh': s = _.parse(`(1-(${text(s.args[0])})^2)^(-1)`); break; case ASECH: { const asechArg = String(s.args[0]); s = _.parse(`-1/(sqrt(1/(${asechArg})^2-1)*(${asechArg})^2)`); break; } case ACOTH: s = _.parse(`-1/((${s.args[0]})^2-1)`); break; case ACSCH: { const arg = String(s.args[0]); s = _.parse(`-1/(sqrt(1/(${arg})^2+1)*(${arg})^2)`); break; } case ASEC: { const arg = String(s.args[0]); s = _.parse(`1/(sqrt(1-1/(${arg})^2)*(${arg})^2)`); break; } case ACSC: { const arg = String(s.args[0]); s = _.parse(`-1/(sqrt(1-1/(${arg})^2)*(${arg})^2)`); break; } case ACOT: s = _.parse(`-1/((${s.args[0]})^2+1)`); break; case 'S': { const arg = String(s.args[0]); s = _.parse(`sin((pi*(${arg})^2)/2)`); break; } case 'C': { const arg = String(s.args[0]); s = _.parse(`cos((pi*(${arg})^2)/2)`); break; } case 'Si': { const arg = s.args[0]; s = _.parse(`sin(${arg})/(${arg})`); break; } case 'Shi': { const arg = s.args[0]; s = _.parse(`sinh(${arg})/(${arg})`); break; } case 'Ci': { const arg = s.args[0]; s = _.parse(`cos(${arg})/(${arg})`); break; } case 'Chi': { const arg = s.args[0]; s = _.parse(`cosh(${arg})/(${arg})`); break; } case 'Ei': { const arg = s.args[0]; s = _.parse(`e^(${arg})/(${arg})`); break; } case 'Li': { const arg = s.args[0]; s = _.parse(`1/${Settings.LOG}(${arg})`); break; } case 'erf': s = _.parse(`(2*e^(-(${s.args[0]})^2))/sqrt(pi)`); break; case 'atan2': { const x_ = String(s.args[0]); const y_ = String(s.args[1]); s = _.parse(`(${y_})/((${y_})^2+(${x_})^2)`); break; } case 'sign': s = new NerdamerSymbol(0); break; case 'sinc': s = _.parse(format('(({0})*cos({0})-sin({0}))*({0})^(-2)', s.args[0])); break; case Settings.LOG10: s = _.parse(`1/((${s.args[0]})*${Settings.LOG}(10))`); break; default: s = _.symfunction('diff', [s, wrt]); } s.multiplier = s.multiplier.multiply(m); } else if (g === EX || (g === FN && isSymbol(s.power))) { let value; if (g === EX) { value = s.value; } else if (g === FN && s.contains(d)) { value = s.fname + inBrackets(text(s.args[0])); } else { value = s.value + inBrackets(text(s.args[0])); } b = __.diff(_.multiply(_.parse(LOG + inBrackets(value)), s.power.clone()), d); s = _.multiply(s, b); } else if (g === FN && !s.power.equals(1)) { b = s.clone(); b.toLinear(); b.toUnitMultiplier(); s = _.multiply(polydiff(s.clone()), derive(b)); } else if (g === CP || g === PL) { // Note: Do not use `parse` since this puts back the sqrt and causes a bug as in #610. Use clone. const c = s.clone(); let result = new NerdamerSymbol(0); for (const x in s.symbols) { if (!Object.hasOwn(s.symbols, x)) { continue; } result = /** @type {NerdamerSymbolType} */ (_.add(result, __.diff(s.symbols[x].clone(), d))); } s = _.multiply(polydiff(c), result); } s.updateHash(); return s; } function qdiff(s, val, altVal) { return _.multiply(s, _.parse(val + inBrackets(altVal || text(s.args[0])))); } function productRule(s) { // Grab all the symbols within the CB symbol const symbols = s.collectSymbols(); let result = new NerdamerSymbol(0); const l = symbols.length; // Loop over all the symbols for (let i = 0; i < l; i++) { let df = __.diff(symbols[i].clone(), d); for (let j = 0; j < l; j++) { // Skip the symbol of which we just pulled the derivative if (i !== j) { // Multiply out the remaining symbols df = /** @type {NerdamerSymbolType} */ (_.multiply(df, symbols[j].clone())); } } // Add the derivative to the result result = /** @type {NerdamerSymbolType} */ (_.add(result, df)); } return result; // Done } }, integration: { /** * Performs u-substitution for integration. * * @param {NerdamerSymbolType[]} symbols - Array of symbols to work with * @param {string} dx - Variable of integration * @returns {NerdamerSymbolType | VectorType | MatrixType | undefined} */ u_substitution(symbols, dx) { // May cause problems if person is using this already. Will need // to find algorithm for detecting conflict const u = '__u__'; function tryCombo(a, b, f) { const d = __.diff(b, dx); const q = f ? f(a, b) : _.divide(a.clone(), d); if (!q.contains(dx, true)) { return q; } return null; } function doFnSub(fname, arg) { let subbed = /** @type {NerdamerSymbolType} */ ( __.integrate(_.symfunction(fname, [new NerdamerSymbol(u)]), u, 0) ); subbed = subbed.sub(new NerdamerSymbol(u), arg); subbed.updateHash(); return subbed; } const a = symbols[0].clone(); const b = symbols[1].clone(); const g1 = a.group; const g2 = b.group; let Q; if (g1 === FN && g2 !== FN) { // E.g. 2*x*cos(x^2) const arg = a.args[0]; Q = tryCombo(b, arg.clone()); if (Q) { return _.multiply(Q, doFnSub(a.fname, arg)); } Q = tryCombo(b, a); if (Q) { return __.integration.poly_integrate(a); } } else if (g2 === FN && g1 !== FN) { // E.g. 2*(x+1)*cos((x+1)^2 const arg = b.args[0]; Q = tryCombo(a, arg.clone()); if (Q) { return _.multiply(Q, doFnSub(b.fname, arg)); } } else if (g1 === FN && g2 === FN) { Q = tryCombo(a.clone(), b.clone()); if (Q) { return _.multiply(__.integration.poly_integrate(b), Q); } Q = tryCombo(b.clone(), a.clone()); if (Q) { return _.multiply(__.integration.poly_integrate(b), Q); } } else if (g1 === EX && g2 !== EX) { const p = a.power; Q = tryCombo(b, isSymbol(p) ? p.clone() : new NerdamerSymbol(p)); if (!Q) { // One more try const dc = __.integration.decompose_arg(isSymbol(p) ? p.clone() : new NerdamerSymbol(p), dx); // Consider the possibility of a^x^(n-1)*x^n dx const xp = /** @type {NerdamerSymbolType} */ (__.diff(dc[2].clone(), dx)); const dc2 = __.integration.decompose_arg(xp.clone(), dx); // If their powers equal, so if dx*p == b if ( /** @type {NerdamerSymbolType} */ (_.multiply(dc[1], dc2[1])).power.equals( /** @type {FracType} */ (b.power) ) ) { const m = _.divide(dc[0].clone(), dc2[0].clone()); let newVal = _.multiply( m.clone(), _.pow(new NerdamerSymbol(a.value), _.multiply(dc[0], new NerdamerSymbol(u))) ); newVal = _.multiply(newVal, new NerdamerSymbol(u)); return /** @type {NerdamerSymbolType} */ (__.integration.by_parts(newVal, u, 0, {})).sub( u, dc[1].clone() ); } } const integrated = /** @type {NerdamerSymbolType} */ ( __.integrate(a.sub(/** @type {NerdamerSymbolType} */ (p.clone()), new NerdamerSymbol(u)), u, 0) ); const retval = _.multiply( integrated.sub(new NerdamerSymbol(u), /** @type {NerdamerSymbolType} */ (p)), Q ); return retval; } else if (g2 === EX && g1 !== EX) { const p = b.power; Q = tryCombo(a, /** @type {NerdamerSymbolType} */ (p.clone())); const integrated = /** @type {NerdamerSymbolType} */ ( __.integrate(b.sub(/** @type {NerdamerSymbolType} */ (p), new NerdamerSymbol(u)), u, 0) ); return _.multiply(integrated.sub(new NerdamerSymbol(u), /** @type {NerdamerSymbolType} */ (p)), Q); } else if (a.isComposite() || b.isComposite()) { const f = function (sym1, sym2) { const d = __.diff(sym2, dx); const A = /** @type {FactorSubModuleType} */ (core.Algebra.Factor).factorInner(sym1); const B = /** @type {FactorSubModuleType} */ (core.Algebra.Factor).factorInner( /** @type {NerdamerSymbolType} */ (d) ); const q = _.divide(A, B); return q; }; const f1 = a.isComposite() ? a.clone().toLinear() : a.clone(); const f2 = b.isComposite() ? b.clone().toLinear() : b.clone(); Q = tryCombo(f1.clone(), f2.clone(), f); if (Q) { return _.multiply(__.integration.poly_integrate(b), Q); } Q = tryCombo(f2.clone(), f1.clone(), f); if (Q) { return _.multiply(__.integration.poly_integrate(a), Q); } } return undefined; }, // Simple integration of a single polynomial x^(n+1)/(n+1) /** * @param {NerdamerSymbolType} x * @returns {NerdamerSymbolType} */ poly_integrate(x) { const p = x.power.toString(); const m = x.multiplier.toDecimal(); const s = x.toUnitMultiplier().toLinear(); if (Number(p) === -1) { return /** @type {NerdamerSymbolType} */ ( _.multiply(new NerdamerSymbol(m), _.symfunction(LOG, [s])) ); } return /** @type {NerdamerSymbolType} */ (_.parse(format('({0})*({1})^(({2})+1)/(({2})+1)', m, s, p))); }, // If we're just spinning wheels we want to stop. This is why we // wrap integration in a try catch block and call this to stop. /** * @param {string} [msg] * @returns {never} */ stop(msg) { msg ||= 'Unable to compute integral!'; core.Utils.warn(msg); throw new NoIntegralFound(msg); }, /** * @param {NerdamerSymbolType} input * @param {NerdamerSymbolType | string} dx * @param {number} depth * @param {IntegrationOptions} opt * @returns {NerdamerSymbolType} */ partial_fraction(input, dx, depth, opt) { // TODO: This whole thing needs to be rolled into one but for now I'll leave it as two separate parts if (!isSymbol(dx)) { dx = /** @type {NerdamerSymbolType} */ (_.parse(dx)); } let result; result = new NerdamerSymbol(0); const partialFractions = /** @type {NerdamerSymbolType} */ ( /** @type {PartFracSubModuleType} */ (core.Algebra.PartFrac).partfrac( input, /** @type {NerdamerSymbolType} */ (dx) ) ); if (partialFractions.group === CB && partialFractions.isLinear()) { // Perform a quick check to make sure that all partial fractions are linear partialFractions.each(x => { if (!x.isLinear()) { __.integration.stop(); } }); partialFractions.each(x => { result = /** @type {NerdamerSymbolType} */ (_.add(result, __.integrate(x, dx, depth, opt))); }); } else { result = /** @type {NerdamerSymbolType} */ ( _.add(result, __.integrate(partialFractions, dx, depth, opt)) ); } return result; }, get_udv(symbol) { const parts = [ [ /* L*/ ], [ /* I*/ ], [ /* A*/ ], [ /* T*/ ], [ /* E*/ ], ]; // First we sort them const setSymbol = function (x) { const g = x.group; if (g === FN) { const { fname } = x; if (core.Utils.inTrig(fname) || core.Utils.inHtrig(fname)) { parts[3].push(x); } else if (core.Utils.inInverseTrig(fname)) { parts[1].push(x); } else if (fname === LOG) { parts[0].push(x); } else { __.integration.stop(); } } else if (g === S || (x.isComposite() && x.isLinear()) || (g === CB && x.isLinear())) { parts[2].push(x); } else if (g === EX || (x.isComposite() && !x.isLinear())) { parts[4].push(x); } else { __.integration.stop(); } }; if (symbol.group === CB) { symbol.each(x => { setSymbol(NerdamerSymbol.unwrapSQRT(x, true)); }); } else { setSymbol(symbol); } let u; let dv = new NerdamerSymbol(1); // Compile u and dv for (let i = 0; i < 5; i++) { const part = parts[i]; let t; const l = part.length; if (l > 0) { if (l > 1) { t = new NerdamerSymbol(1); for (let j = 0; j < l; j++) { t = /** @type {NerdamerSymbolType} */ (_.multiply(t, part[j].clone())); } } else { t = part[0].clone(); } if (u) { dv = /** @type {NerdamerSymbolType} */ (_.multiply(dv, t)); // Everything else belongs to dv } else { u = t; // The first u encountered gets chosen u.multiplier = u.multiplier.multiply(symbol.multiplier); // The first one gets the mutliplier } } } return [u, dv]; }, trig_sub(symbol, dx, depth, opt, parts, _symbols) { parts ||= __.integration.decompose_arg(symbol.clone().toLinear(), dx); const _b = parts[3]; const _ax = parts[