nerdamer-prime
Version:
javascript light-weight symbolic math library
1,194 lines (1,134 loc) • 198 kB
JavaScript
/*
* 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[