nerdamer
Version:
javascript light-weight symbolic math expression evaluator
1,122 lines (1,052 loc) • 151 kB
JavaScript
/* global module */
/*
* Author : Martin Donk
* Website : http://www.nerdamer.com
* Email : martin.r.donk@gmail.com
* Source : https://github.com/jiggzson/nerdamer
*/
if((typeof module) !== 'undefined' && typeof nerdamer === 'undefined') {
var nerdamer = require('./nerdamer.core.js');
require('./Algebra.js');
}
(function () {
"use strict";
var core = nerdamer.getCore(),
_ = core.PARSER,
Frac = core.Frac,
Settings = core.Settings,
isSymbol = core.Utils.isSymbol,
FN = core.groups.FN,
Symbol = core.Symbol,
text = core.Utils.text,
inBrackets = core.Utils.inBrackets,
isInt = core.Utils.isInt,
format = core.Utils.format,
even = core.Utils.even,
evaluate = core.Utils.evaluate,
N = core.groups.N,
S = core.groups.S,
FN = core.groups.FN,
PL = core.groups.PL,
CP = core.groups.CP,
CB = core.groups.CB,
EX = core.groups.EX,
P = core.groups.P,
LOG = Settings.LOG,
EXP = 'exp',
ABS = 'abs',
SQRT = 'sqrt',
SIN = 'sin',
COS = 'cos',
TAN = 'tan',
SEC = 'sec',
CSC = 'csc',
COT = 'cot',
ASIN = 'asin',
ACOS = 'acos',
ATAN = 'atan',
ASEC = 'asec',
ACSC = 'acsc',
ACOT = 'acot',
SINH = 'sinh',
COSH = 'cosh',
TANH = 'tanh',
CSCH = 'csch',
SECH = 'sech',
COTH = 'coth',
ASECH = 'asech',
ACSCH = 'acsch',
ACOTH = 'acoth';
//custom errors
function NoIntegralFound(msg) {
this.message = msg || "";
}
NoIntegralFound.prototype = new Error();
//Preparations
Symbol.prototype.hasIntegral = function () {
return this.containsFunction('integrate');
};
//transforms a function
Symbol.prototype.fnTransform = function () {
if(this.group !== FN)
return this;
var retval, a = this.args[0];
var m = new Symbol(this.multiplier);
var 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)) {
var n = this.power / 2;
//convert to a double angle
var double_angle = _.pow(this.clone().toLinear(), _.parse(2)).fnTransform();
//raise to the n and expand
var transformed = _.expand(_.pow(double_angle, _.parse(n)));
retval = new Symbol(0);
transformed.each(function (s) {
var t = s.fnTransform();
retval = _.add(retval, t);
}, true);
}
else
retval = sym;
return _.multiply(retval, m);
};
Symbol.prototype.hasTrig = function () {
if(this.isConstant(true) || this.group === S)
return false;
if(this.fname && (core.Utils.in_trig(this.fname) || core.Utils.in_inverse_trig(this.fname)))
return true;
if(this.symbols) {
for(var x in this.symbols)
if(this.symbols[x].hasTrig())
return true;
}
return false;
};
core.Expression.prototype.hasIntegral = function () {
return this.symbol.hasIntegral();
};
/**
* Attempts to rewrite a symbol under one common denominator
* @param {Symbol} symbol
*/
core.Utils.toCommonDenominator = function (symbol) {
//transform x/a+x -> (ax+x)/a
if(symbol.isComposite() && symbol.isLinear()) {
var m = new Symbol(symbol.multiplier);
var denominator = new Symbol(1);
var numerator = new Symbol(0);
symbol.each(function (x) {
denominator = _.multiply(denominator, x.getDenom());
}, true);
//remove the denomitor in each term
symbol.each(function (x) {
var num = x.getNum();
var den = x.getDenom();
var factor = _.multiply(num, _.divide(denominator.clone(), den));
numerator = _.add(numerator, factor);
});
var retval = _.multiply(m, core.Algebra.divide(_.expand(numerator), _.expand(denominator)));
return retval;
}
return symbol;
};
//A function to check if a function name is an inverse trig function
core.Utils.in_inverse_trig = function (x) {
var inv_trig_fns = [ASIN, ACOS, ATAN, ACSC, ASEC, ACOT];
return inv_trig_fns.indexOf(x) !== -1;
};
//A function to check if a function name is a trig function
core.Utils.in_trig = function (x) {
var trig_fns = [COS, SIN, TAN, SEC, CSC, COT];
return trig_fns.indexOf(x) !== -1;
};
core.Utils.in_htrig = function (x) {
var trig_fns = [SINH, COSH, TANH, ACSCH, ASECH, ACOTH];
return trig_fns.indexOf(x) !== -1;
};
// Matrix functions
core.Matrix.jacobian = function (eqns, vars) {
var jacobian = new core.Matrix();
//get the variables if not supplied
if(!vars) {
vars = core.Utils.arrayGetVariables(eqns);
}
vars.forEach(function (v, i) {
eqns.forEach(function (eq, j) {
var e = core.Calculus.diff(eq.clone(), v);
jacobian.set(j, i, e);
});
});
return jacobian;
};
core.Matrix.prototype.max = function () {
var max = new Symbol(0);
this.each(function (x) {
var e = x.abs();
if(e.gt(max))
max = e;
});
return max;
};
core.Matrix.cMatrix = function (value, vars) {
var m = new core.Matrix();
//make an initial guess
vars.forEach(function (v, i) {
m.set(i, 0, _.parse(value));
});
return m;
};
var all_functions = core.Utils.all_functions = function (arr) {
for(var i = 0, l = arr.length; i < l; i++)
if(arr[i].group !== FN)
return false;
return true;
},
cosAsinBtransform = core.Utils.cosAsinBtranform = function (symbol1, symbol2) {
var a, b;
a = symbol1.args[0];
b = symbol2.args[0];
return _.parse(format('(sin(({0})+({1}))-sin(({0})-({1})))/2', a, b));
},
cosAsinAtransform = core.Utils.cosAsinAtranform = function (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 _.multiply(symbol1, symbol2);
var a;
a = symbol1.args[0];
return _.parse(format('(sin(2*({0})))/2', a));
},
sinAsinBtransform = core.Utils.cosAsinBtranform = function (symbol1, symbol2) {
var a, b;
a = symbol1.args[0];
b = symbol2.args[0];
return _.parse(format('(cos(({0})+({1}))-cos(({0})-({1})))/2', a, b));
},
trigTransform = core.Utils.trigTransform = function (arr) {
var map = {}, symbol, t,
retval = new Symbol(1);
for(var i = 0, l = arr.length; i < l; i++) {
symbol = arr[i];
if(symbol.group === FN) {
var fname = symbol.fname;
if(fname === COS && map[SIN]) {
if(map[SIN].args[0].toString() !== symbol.args[0].toString()) {
t = cosAsinBtransform(symbol, map[SIN]);
}
else {
t = cosAsinAtransform(symbol, map[SIN]);
}
delete map[SIN];
retval = _.multiply(retval, t);
}
else if(fname === SIN && map[COS]) {
if(map[COS].args[0].toString() !== symbol.args[0].toString()) {
t = cosAsinBtransform(symbol, map[COS]);
}
else {
t = cosAsinAtransform(symbol, map[COS]);
}
delete map[COS];
retval = _.multiply(retval, t);
}
else if(fname === SIN && map[SIN]) {
if(map[SIN].args[0].toString() !== symbol.args[0].toString()) {
t = sinAsinBtransform(symbol, map[SIN]);
delete map[SIN];
}
else {
//This should actually be redundant code but let's put just in case
t = _.multiply(symbol, map[SIN]);
delete map[SIN];
}
retval = t;
}
else {
map[fname] = symbol;
}
}
else
retval = _.multiply(retval, symbol);
}
//put back the remaining functions
for(var x in map)
retval = _.multiply(retval, map[x]);
return retval;
};
core.Settings.integration_depth = 10;
core.Settings.max_lim_depth = 10;
var __ = core.Calculus = {
version: '1.4.6',
sum: function (fn, index, start, end) {
if(!(index.group === core.groups.S))
throw new core.exceptions.NerdamerTypeError('Index must be symbol. ' + text(index) + ' provided');
index = index.value;
var retval;
if(core.Utils.isNumericSymbol(start) && core.Utils.isNumericSymbol(end)) {
var modifier = end - start < 200 ? '' : 'PARSE2NUMBER';
start = Number(start);
end = Number(end);
retval = core.Utils.block(modifier, function () {
var f = fn.text(),
subs = {'~': true}, //lock subs. Is this even being used?
retval = new core.Symbol(0);
for(var i = start; i <= end; i++) {
subs[index] = new Symbol(i);
var ans = _.parse(f, subs);
retval = _.add(retval, ans);
}
return retval;
});
}
else {
retval = _.symfunction('sum', arguments);
}
return retval;
},
product: function (fn, index, start, end) {
if(!(index.group === core.groups.S))
throw new core.exceptions.NerdamerTypeError('Index must be symbol. ' + text(index) + ' provided');
index = index.value;
var retval;
if(core.Utils.isNumericSymbol(start) && core.Utils.isNumericSymbol(end)) {
var modifier = end - start < 200 ? '' : 'PARSE2NUMBER';
retval = core.Utils.block(modifier, function () {
start = Number(start);
end = Number(end.multiplier);
var f = fn.text(),
subs = {},
retval = new core.Symbol(1);
for(var i = start; i <= end; i++) {
subs[index] = new Symbol(i);
retval = _.multiply(retval, _.parse(f, subs));
}
return retval;
});
}
else {
retval = _.symfunction('product', arguments);
}
return retval;
},
diff: function (symbol, wrt, nth) {
if(core.Utils.isVector(symbol)) {
var vector = new core.Vector([]);
symbol.each(function (x) {
vector.elements.push(__.diff(x, wrt, nth));
});
return vector;
}
else if(core.Utils.isMatrix(symbol)) {
var matrix = new core.Matrix();
symbol.each(function (x, i, j) {
matrix.set(i, j, __.diff(x, wrt, nth));
});
return matrix;
}
var d = isSymbol(wrt) ? wrt.text() : wrt;
//the nth derivative
nth = isSymbol(nth) ? nth.multiplier : nth || 1;
if(d === undefined)
d = core.Utils.variables(symbol)[0];
//unwrap sqrt
if(symbol.group === FN && symbol.fname === SQRT) {
var s = symbol.args[0],
sp = symbol.power.clone();
//these groups go to zero anyway so why waste time?
if(s.group !== N || s.group !== P) {
s.power = isSymbol(s.power) ? _.multiply(s.power, _.multiply(new Symbol(1 / 2)), sp) : s.power.multiply(new Frac(0.5)).multiply(sp);
s.multiplier = s.multiplier.multiply(symbol.multiplier);
}
symbol = s;
}
if(symbol.group === FN && !isSymbol(symbol.power)) {
var a = derive(_.parse(symbol));
var 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(symbol) {
if(symbol.value === d || symbol.contains(d, true)) {
symbol.multiplier = symbol.multiplier.multiply(symbol.power);
symbol.power = symbol.power.subtract(new Frac(1));
if(symbol.power.equals(0)) {
symbol = Symbol(symbol.multiplier);
}
}
return symbol;
}
function derive(symbol) {
var g = symbol.group, a, b, cp;
if(g === N || g === S && symbol.value !== d || g === P) {
symbol = Symbol(0);
}
else if(g === S) {
symbol = polydiff(symbol);
}
else if(g === CB) {
var m = symbol.multiplier.clone();
symbol.toUnitMultiplier();
var retval = _.multiply(product_rule(symbol), polydiff(symbol));
retval.multiplier = retval.multiplier.multiply(m);
return retval;
}
else if(g === FN && symbol.power.equals(1)) {
// Table of known derivatives
switch(symbol.fname) {
case LOG:
cp = symbol.clone();
symbol = symbol.args[0].clone();//get the arguments
symbol.power = symbol.power.negate();
symbol.multiplier = cp.multiplier.divide(symbol.multiplier);
break;
case COS:
//cos -> -sin
symbol.fname = SIN;
symbol.multiplier.negate();
break;
case SIN:
//sin -> cos
symbol.fname = COS;
break;
case TAN:
//tan -> sec^2
symbol.fname = SEC;
symbol.power = new Frac(2);
break;
case SEC:
// Use a clone if this gives errors
symbol = qdiff(symbol, TAN);
break;
case CSC:
symbol = qdiff(symbol, '-cot');
break;
case COT:
symbol.fname = CSC;
symbol.multiplier.negate();
symbol.power = new Frac(2);
break;
case ASIN:
symbol = _.parse('(sqrt(1-(' + text(symbol.args[0]) + ')^2))^(-1)');
break;
case ACOS:
symbol = _.parse('-(sqrt(1-(' + text(symbol.args[0]) + ')^2))^(-1)');
break;
case ATAN:
symbol = _.parse('(1+(' + text(symbol.args[0]) + ')^2)^(-1)');
break;
case ABS:
m = symbol.multiplier.clone();
symbol.toUnitMultiplier();
//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 = symbol.args[0].clone();
b.toUnitMultiplier();
symbol = _.parse(inBrackets(text(symbol.args[0])) + '/abs' + inBrackets(text(b)));
symbol.multiplier = m;
break;
case 'parens':
//see product rule: f'.g goes to zero since f' will return zero. This way we only get back
//1*g'
symbol = Symbol(1);
break;
case 'cosh':
//cosh -> -sinh
symbol.fname = 'sinh';
break;
case 'sinh':
//sinh -> cosh
symbol.fname = 'cosh';
break;
case TANH:
//tanh -> sech^2
symbol.fname = SECH;
symbol.power = new Frac(2);
break;
case SECH:
// Use a clone if this gives errors
symbol = qdiff(symbol, '-tanh');
break;
case CSCH:
var arg = String(symbol.args[0]);
return _.parse('-coth(' + arg + ')*csch(' + arg + ')');
break;
case COTH:
var arg = String(symbol.args[0]);
return _.parse('-csch(' + arg + ')^2');
break;
case 'asinh':
symbol = _.parse('(sqrt(1+(' + text(symbol.args[0]) + ')^2))^(-1)');
break;
case 'acosh':
symbol = _.parse('(sqrt(-1+(' + text(symbol.args[0]) + ')^2))^(-1)');
break;
case 'atanh':
symbol = _.parse('(1-(' + text(symbol.args[0]) + ')^2)^(-1)');
break;
case ASECH:
var arg = String(symbol.args[0]);
symbol = _.parse('-1/(sqrt(1/(' + arg + ')^2-1)*(' + arg + ')^2)');
break;
case ACOTH:
symbol = _.parse('-1/((' + symbol.args[0] + ')^2-1)');
break;
case ACSCH:
var arg = String(symbol.args[0]);
symbol = _.parse('-1/(sqrt(1/(' + arg + ')^2+1)*(' + arg + ')^2)');
break;
case ASEC:
var arg = String(symbol.args[0]);
symbol = _.parse('1/(sqrt(1-1/(' + arg + ')^2)*(' + arg + ')^2)');
break;
case ACSC:
var arg = String(symbol.args[0]);
symbol = _.parse('-1/(sqrt(1-1/(' + arg + ')^2)*(' + arg + ')^2)');
break;
case ACOT:
symbol = _.parse('-1/((' + symbol.args[0] + ')^2+1)');
break;
case 'S':
var arg = String(symbol.args[0]);
symbol = _.parse('sin((pi*(' + arg + ')^2)/2)');
break;
case 'C':
var arg = String(symbol.args[0]);
symbol = _.parse('cos((pi*(' + arg + ')^2)/2)');
break;
case 'Si':
var arg = symbol.args[0];
symbol = _.parse('sin(' + arg + ')/(' + arg + ')');
break;
case 'Shi':
var arg = symbol.args[0];
symbol = _.parse('sinh(' + arg + ')/(' + arg + ')');
break;
case 'Ci':
var arg = symbol.args[0];
symbol = _.parse('cos(' + arg + ')/(' + arg + ')');
break;
case 'Chi':
var arg = symbol.args[0];
symbol = _.parse('cosh(' + arg + ')/(' + arg + ')');
break;
case 'Ei':
var arg = symbol.args[0];
symbol = _.parse('e^(' + arg + ')/(' + arg + ')');
break;
case 'Li':
var arg = symbol.args[0];
symbol = _.parse('1/' + Settings.LOG + '(' + arg + ')');
break;
case 'erf':
symbol = _.parse('(2*e^(-(' + symbol.args[0] + ')^2))/sqrt(pi)');
break;
case 'atan2':
var x_ = String(symbol.args[0]),
y_ = String(symbol.args[1]);
symbol = _.parse('(' + y_ + ')/((' + y_ + ')^2+(' + x_ + ')^2)');
break;
case 'sign':
symbol = new Symbol(0);
break;
case 'sinc':
symbol = _.parse(format('(({0})*cos({0})-sin({0}))*({0})^(-2)', symbol.args[0]));
break;
case Settings.LOG10:
symbol = _.parse('1/((' + symbol.args[0] + ')*' + Settings.LOG + '(10))');
break;
default:
symbol = _.symfunction('diff', [symbol, wrt]);
}
}
else if(g === EX || g === FN && isSymbol(symbol.power)) {
var value;
if(g === EX) {
value = symbol.value;
}
else if(g === FN && symbol.contains(d)) {
value = symbol.fname + inBrackets(text(symbol.args[0]));
}
else {
value = symbol.value + inBrackets(text(symbol.args[0]));
}
a = _.multiply(_.parse(LOG + inBrackets(value)), symbol.power.clone());
b = __.diff(_.multiply(_.parse(LOG + inBrackets(value)), symbol.power.clone()), d);
symbol = _.multiply(symbol, b);
}
else if(g === FN && !symbol.power.equals(1)) {
b = symbol.clone();
b.toLinear();
b.toUnitMultiplier();
symbol = _.multiply(polydiff(symbol.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.
var c = symbol.clone();
var result = new Symbol(0);
for(var x in symbol.symbols) {
result = _.add(result, __.diff(symbol.symbols[x].clone(), d));
}
symbol = _.multiply(polydiff(c), result);
}
symbol.updateHash();
return symbol;
}
;
function qdiff(symbol, val, altVal) {
return _.multiply(symbol, _.parse(val + inBrackets(altVal || text(symbol.args[0]))));
}
;
function product_rule(symbol) {
//grab all the symbols within the CB symbol
var symbols = symbol.collectSymbols(),
result = new Symbol(0),
l = symbols.length;
//loop over all the symbols
for(var i = 0; i < l; i++) {
var df = __.diff(symbols[i].clone(), d);
for(var j = 0; j < l; j++) {
//skip the symbol of which we just pulled the derivative
if(i !== j) {
//multiply out the remaining symbols
df = _.multiply(df, symbols[j].clone());
}
}
//add the derivative to the result
result = _.add(result, df);
}
return result; //done
}
;
},
integration: {
u_substitution: function (symbols, dx) {
function try_combo(a, b, f) {
var d = __.diff(b, dx);
var q = f ? f(a, b) : _.divide(a.clone(), d);
if(!q.contains(dx, true))
return q;
return null;
}
function do_fn_sub(fname, arg) {
var subbed = __.integrate(_.symfunction(fname, [new Symbol(u)]), u, 0);
subbed = subbed.sub(new Symbol(u), arg);
subbed.updateHash();
return subbed;
}
var a = symbols[0].clone(),
b = symbols[1].clone(),
g1 = a.group,
g2 = b.group,
//may cause problems if person is using this already. Will need
//to find algorithm for detecting conflict
u = '__u__',
Q;
if(g1 === FN && g2 !== FN) {
//e.g. 2*x*cos(x^2)
var arg = a.args[0];
Q = try_combo(b, arg.clone());
if(Q)
return _.multiply(Q, do_fn_sub(a.fname, arg));
Q = try_combo(b, a);
if(Q) {
return __.integration.poly_integrate(a);
}
}
else if(g2 === FN && g1 !== FN) {
//e.g. 2*(x+1)*cos((x+1)^2
var arg = b.args[0];
Q = try_combo(a, arg.clone());
if(Q)
return _.multiply(Q, do_fn_sub(b.fname, arg));
}
else if(g1 === FN && g2 === FN) {
Q = try_combo(a.clone(), b.clone());
if(Q)
return _.multiply(__.integration.poly_integrate(b), Q);
Q = try_combo(b.clone(), a.clone());
if(Q)
return _.multiply(__.integration.poly_integrate(b), Q);
}
else if(g1 === EX && g2 !== EX) {
var p = a.power;
Q = try_combo(b, p.clone());
if(!Q) {
//one more try
var dc = __.integration.decompose_arg(p.clone(), dx);
//consider the possibility of a^x^(n-1)*x^n dx
var xp = __.diff(dc[2].clone(), dx);
var dc2 = __.integration.decompose_arg(xp.clone(), dx);
//if their powers equal, so if dx*p == b
if(_.multiply(dc[1], dc2[1]).power.equals(b.power)) {
var m = _.divide(dc[0].clone(), dc2[0].clone());
var new_val = _.multiply(m.clone(), _.pow(new Symbol(a.value), _.multiply(dc[0], new Symbol(u))));
new_val = _.multiply(new_val, new Symbol(u));
return __.integration.by_parts(new_val, u, 0, {}).sub(u, dc[1].clone());
}
}
var integrated = __.integrate(a.sub(p.clone(), new Symbol(u)), u, 0),
retval = _.multiply(integrated.sub(new Symbol(u), p), Q);
return retval;
}
else if(g2 === EX && g1 !== EX) {
var p = b.power;
Q = try_combo(a, p.clone());
var integrated = __.integrate(b.sub(p, new Symbol(u)), u, 0);
return _.multiply(integrated.sub(new Symbol(u), p), Q);
}
else if(a.isComposite() || b.isComposite()) {
var f = function (a, b) {
var d = __.diff(b, dx);
var A = core.Algebra.Factor.factor(a),
B = core.Algebra.Factor.factor(d);
var q = _.divide(A, B);
return q;
};
var f1 = a.isComposite() ? a.clone().toLinear() : a.clone(),
f2 = b.isComposite() ? b.clone().toLinear() : b.clone();
Q = try_combo(f1.clone(), f2.clone(), f);
if(Q)
return _.multiply(__.integration.poly_integrate(b), Q);
Q = try_combo(f2.clone(), f1.clone(), f);
if(Q)
return _.multiply(__.integration.poly_integrate(a), Q);
}
},
//simple integration of a single polynomial x^(n+1)/(n+1)
poly_integrate: function (x) {
var p = x.power.toString(),
m = x.multiplier.toDecimal(),
s = x.toUnitMultiplier().toLinear();
if(Number(p) === -1) {
return _.multiply(new Symbol(m), _.symfunction(LOG, [s]));
}
return _.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.
stop: function (msg) {
msg = msg || 'Unable to compute integral!';
core.Utils.warn(msg);
throw new NoIntegralFound(msg);
},
partial_fraction: function (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 = _.parse(dx);
var result, partial_fractions;
result = new Symbol(0);
partial_fractions = core.Algebra.PartFrac.partfrac(input, dx);
if(partial_fractions.group === CB && partial_fractions.isLinear()) {
//perform a quick check to make sure that all partial fractions are linear
partial_fractions.each(function (x) {
if(!x.isLinear())
__.integration.stop();
});
partial_fractions.each(function (x) {
result = _.add(result, __.integrate(x, dx, depth, opt));
});
}
else {
result = _.add(result, __.integrate(partial_fractions, dx, depth, opt));
}
return result;
},
get_udv: function (symbol) {
var parts = [[/*L*/], [/*I*/], [/*A*/], [/*T*/], [/*E*/]];
//first we sort them
var setSymbol = function (x) {
var g = x.group;
if(g === FN) {
var fname = x.fname;
if(core.Utils.in_trig(fname) || core.Utils.in_htrig(fname))
parts[3].push(x);
else if(core.Utils.in_inverse_trig(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(function (x) {
setSymbol(Symbol.unwrapSQRT(x, true));
});
else
setSymbol(symbol);
var u, dv = new Symbol(1);
//compile u and dv
for(var i = 0; i < 5; i++) {
var part = parts[i], t,
l = part.length;
if(l > 0) {
if(l > 1) {
t = new Symbol(1);
for(var j = 0; j < l; j++)
t = _.multiply(t, part[j].clone());
}
else
t = part[0].clone();
if(!u) {
u = t;//the first u encountered gets chosen
u.multiplier = u.multiplier.multiply(symbol.multiplier); //the first one gets the mutliplier
}
else
dv = _.multiply(dv, t); //everything else belongs to dv
}
}
return [u, dv];
},
trig_sub: function (symbol, dx, depth, opt, parts, symbols) {
parts = parts || __.integration.decompose_arg(symbol.clone().toLinear(), dx);
var b = parts[3],
ax = parts[2],
a = parts[0],
x = parts[1];
if(x.power.equals(2) && a.greaterThan(0)) {
//use tan(x)
var t = core.Utils.getU(symbol), //get an appropriate u
u = _.parse(TAN + inBrackets(t)), //u
du = _.parse(SEC + inBrackets(t) + '^2'), //du
f = _.multiply(symbol.sub(x, u), du);
var integral = __.integrate(f, t, depth, opt).sub(u, x);
core.Utils.clearU(u);
return integral;
}
},
by_parts: function (symbol, dx, depth, o) {
o.previous = o.previous || [];
var udv, u, dv, du, v, vdu, uv, retval, integral_vdu, m, c, vdu_s;
//first LIATE
udv = __.integration.get_udv(symbol);
u = udv[0];
dv = udv[1];
du = Symbol.unwrapSQRT(_.expand(__.diff(u.clone(), dx)), true);
c = du.clone().stripVar(dx);
//strip any coefficients
du = _.divide(du, c.clone());
v = __.integrate(dv.clone(), dx, depth || 0);
vdu = _.multiply(v.clone(), du);
vdu_s = vdu.toString();
//currently only supports e^x*(some trig)
if(o.previous.indexOf(vdu_s) !== -1 && (core.Utils.in_trig(u.fname)) && dv.isE()) {
//We're going to exploit the fact that vdu can never be constant
//to work out way out of this cycle. We'll return the length of
//the this.previous array until we're back at level one
o.is_cyclic = true;
//return the integral.
return new Symbol(1);
}
else
o.previous.push(vdu_s);
uv = _.multiply(u, v);
//clear the multiplier so we're dealing with a bare integral
m = vdu.multiplier.clone();
vdu.toUnitMultiplier();
integral_vdu = _.multiply(__.integrate(vdu.clone(), dx, depth, o), c);
integral_vdu.multiplier = integral_vdu.multiplier.multiply(m);
retval = _.subtract(uv, integral_vdu);
//we know that there cannot be constants so they're a holdover from a cyclic integral
if(o.is_cyclic) {
//start popping the previous stack so we know how deep in we are
o.previous.pop();
if(o.previous.length === 0) {
retval = _.expand(retval);
var rem = new Symbol(0);
retval.each(function (x) {
if(!x.contains(dx))
rem = _.add(rem, x.clone());
});
//get the actual uv
retval = _.divide(_.subtract(retval, rem.clone()), _.subtract(new Symbol(1), rem));
}
}
return retval;
},
/*
* dependents: [Solve, integrate]
*/
decompose_arg: core.Utils.decompose_fn
},
//TODO: nerdamer.integrate('-e^(-a*t)*sin(t)', 't') -> gives incorrect output
integrate: function (original_symbol, dt, depth, opt) {
//assume integration wrt independent variable if expression only has one variable
if(!dt) {
var vars = core.Utils.variables(original_symbol);
if(vars.length === 1)
dt = vars[0];
//defaults to x
dt = dt || 'x';
}
//add support for integrating vectors
if(core.Utils.isVector(original_symbol)) {
var vector = new core.Vector([]);
original_symbol.each(function (x) {
vector.elements.push(__.integrate(x, dt));
});
return vector;
}
if(!isNaN(dt))
_.error('variable expected but received ' + dt);
//get rid of constants right away
if(original_symbol.isConstant(true))
return _.multiply(original_symbol.clone(), _.parse(dt));
//configurations options for integral. This is needed for tracking extra options
//e.g. cyclic integrals or additional settings
opt = opt || {};
return core.Utils.block('PARSE2NUMBER', function () {
//make a note of the original symbol. Set only if undefined
depth = depth || 0;
var dx = isSymbol(dt) ? dt.toString() : dt,
//we don't want the symbol in sqrt form. x^(1/2) is prefererred
symbol = Symbol.unwrapSQRT(original_symbol.clone(), true),
g = symbol.group,
retval;
try {
//We stop integration after x amount of recursive calls
if(++depth > core.Settings.integration_depth)
__.integration.stop('Maximum depth reached. Exiting!');
//constants. We first eliminate anything that doesn't have dx. Everything after this has
//to have dx or else it would have been taken care of below
if(!symbol.contains(dx, true)) {
retval = _.multiply(symbol.clone(), _.parse(dx));
}
//e.g. 2*x
else if(g === S) {
retval = __.integration.poly_integrate(symbol, dx, depth);
}
else if(g === EX) {
if(symbol.previousGroup === FN && !(symbol.fname === 'sqrt' || symbol.fname === Settings.PARENTHESIS))
__.integration.stop();
//check the base
if(symbol.contains(dx) && symbol.previousGroup !== FN) {
//if the symbol also contains dx then we stop since we currently
//don't know what to do with it e.g. x^x
if(symbol.power.contains(dx))
__.integration.stop();
else {
var t = __.diff(symbol.clone().toLinear(), dx);
if(t.contains(dx))
__.integration.stop();
//since at this point it's the base only then we do standard single poly integration
//e.g. x^y
retval = __.integration.poly_integrate(symbol, dx, depth);
}
}
//e.g. a^x or 9^x
else {
var a = __.diff(symbol.power.clone(), dx);
if(a.contains(dx)) {
var aa = a.stripVar(dx),
x = _.divide(a.clone(), aa.clone());
if(x.group === S && x.isLinear()) {
aa.multiplier = aa.multiplier.divide(new Frac(2));
return _.parse(format('({2})*(sqrt(pi)*erf(sqrt(-{0})*{1}))/(2*sqrt(-{0}))', aa, dx, symbol.multiplier));
}
else
__.integration.stop();
}
if(symbol.isE()) {
if(a.isLinear())
retval = symbol;
else {
if(a.isE() && a.power.group === S && a.power.power.equals(1))
retval = _.multiply(_.symfunction('Ei', [symbol.power.clone()]), symbol.power);
else
__.integration.stop();
}
}
else {
var d = _.symfunction(LOG, [_.parse(symbol.value)]);
retval = _.divide(symbol, d);
}
retval = _.divide(retval, a);
}
}
else if(symbol.isComposite() && symbol.isLinear()) {
var m = _.parse(symbol.multiplier);
symbol.toUnitMultiplier();
retval = new Symbol(0);
symbol.each(function (x) {
retval = _.add(retval, __.integrate(x, dx, depth));
});
retval = _.multiply(m, retval);
}
else if(g === CP) {
if(symbol.power.greaterThan(1))
symbol = _.expand(symbol)