UNPKG

nerdamer

Version:

javascript light-weight symbolic math expression evaluator

1,223 lines (1,101 loc) 74.1 kB
/* * Author : Martin Donk * Website : http://www.nerdamer.com * Email : martin.r.donk@gmail.com * Source : https://github.com/jiggzson/nerdamer */ /* global module */ if((typeof module) !== 'undefined') { var nerdamer = require('./nerdamer.core.js'); require('./Calculus.js'); require('./Algebra.js'); } (function () { //handle imports var core = nerdamer.getCore(), _ = core.PARSER, _A = core.Algebra, _C = core.Calculus, explode = _C.integration.decompose_arg, evaluate = core.Utils.evaluate, remove = core.Utils.remove, format = core.Utils.format, build = core.Utils.build, knownVariable = core.Utils.knownVariable, Symbol = core.Symbol, isSymbol = core.Utils.isSymbol, variables = core.Utils.variables, S = core.groups.S, PL = core.groups.PL, CB = core.groups.CB, CP = core.groups.CP, FN = core.groups.FN, Settings = core.Settings, range = core.Utils.range, isArray = core.Utils.isArray; // The search radius for the roots core.Settings.SOLVE_RADIUS = 1000; // The maximum number to fish for on each side of the zero core.Settings.ROOTS_PER_SIDE = 10; // Covert the number to multiples of pi if possible core.Settings.make_pi_conversions = false; // The step size core.Settings.STEP_SIZE = 0.1; // The epsilon size core.Settings.EPSILON = 1e-13; //the maximum iterations for Newton's method core.Settings.MAX_NEWTON_ITERATIONS = 200; //the maximum number of time non-linear solve tries another jump point core.Settings.MAX_NON_LINEAR_TRIES = 12; //the amount of iterations the function will start to jump at core.Settings.NON_LINEAR_JUMP_AT = 50; //the size of the jump core.Settings.NON_LINEAR_JUMP_SIZE = 100; //the original starting point for nonlinear solving core.Settings.NON_LINEAR_START = 0.01; //When points are generated as starting points for Newton's method, they are sliced into small //slices to make sure that we have convergence on the right point. This defines the //size of the slice core.Settings.NEWTON_SLICES = 200; //The epsilon used in Newton's iteration core.Settings.NEWTON_EPSILON = Number.EPSILON * 2; //The distance in which two solutions are deemed the same core.Settings.SOLUTION_PROXIMITY = 1e-14; //Indicate wheter to filter the solutions are not core.Settings.FILTER_SOLUTIONS = true; //the maximum number of recursive calls core.Settings.MAX_SOLVE_DEPTH = 10; // The tolerance that's considered close enough to zero core.Settings.ZERO_EPSILON = 1e-9; // The maximum iteration for the bisection method incase of some JS strangeness core.Settings.MAX_BISECTION_ITER = 2000; // The tolerance for the bisection method core.Settings.BI_SECTION_EPSILON = 1e-12; core.Symbol.prototype.hasTrig = function () { return this.containsFunction(['cos', 'sin', 'tan', 'cot', 'csc', 'sec']); }; core.Symbol.prototype.hasNegativeTerms = function () { if(this.isComposite()) { for(var x in this.symbols) { var sym = this.symbols[x]; if(sym.group === PL && sym.hasNegativeTerms() || this.symbols[x].power.lessThan(0)) return true; } } return false; }; /* nerdamer version 0.7.x and up allows us to make better use of operator overloading * As such we can have this data type be supported completely outside of the core. * This is an equation that has a left hand side and a right hand side */ function Equation(lhs, rhs) { if(rhs.isConstant() && lhs.isConstant() && !lhs.equals(rhs) || lhs.equals(core.Settings.IMAGINARY) && rhs.isConstant(true) || rhs.equals(core.Settings.IMAGINARY) && lhs.isConstant(true)) throw new core.exceptions.NerdamerValueError(lhs.toString() + ' does not equal ' + rhs.toString()); this.LHS = lhs; //left hand side this.RHS = rhs; //right and side } ; //UTILS ##!! Equation.prototype = { toString: function () { return this.LHS.toString() + '=' + this.RHS.toString(); }, text: function (option) { return this.LHS.text(option) + '=' + this.RHS.text(option); }, toLHS: function (expand) { expand = typeof expand === 'undefined' ? true : false; var eqn; if(!expand) { eqn = this.clone(); } else { eqn = this.removeDenom(); } var a = eqn.LHS; var b = eqn.RHS; if(a.isConstant(true) && !b.isConstant(true)) { // Swap them to avoid confusing parser and cause an infinite loop [a, b] = [b, a]; } var _t = _.subtract(a, b); var retval = expand ? _.expand(_t) : _t; // Quick workaround for issue #636 // This basically borrows the removeDenom method from the Equation class. // TODO: Make this function a stand-alone function retval = new Equation(retval, new Symbol(0)).removeDenom().LHS; return retval; }, removeDenom: function () { var a = this.LHS.clone(); var b = this.RHS.clone(); //remove the denominator on both sides var den = _.multiply(a.getDenom(), b.getDenom()); a = _.expand(_.multiply(a, den.clone())); b = _.expand(_.multiply(b, den)); //swap the groups if(b.group === CP && b.group !== CP) { var t = a; a = b; b = t; //swap } //scan to eliminate denominators if(a.group === CB) { var t = new Symbol(a.multiplier), newRHS = b.clone(); a.each(function (y) { if(y.power.lessThan(0)) newRHS = _.divide(newRHS, y); else t = _.multiply(t, y); }); a = t; b = newRHS; } else if(a.group === CP) { //the logic: loop through each and if it has a denominator then multiply it out on both ends //and then start over for(var x in a.symbols) { var sym = a.symbols[x]; if(sym.group === CB) { for(var y in sym.symbols) { var sym2 = sym.symbols[y]; if(sym2.power.lessThan(0)) { return new Equation( _.expand(_.multiply(sym2.clone().toLinear(), a)), _.expand(_.multiply(sym2.clone().toLinear(), b)) ); } } } } } return new Equation(a, b); }, clone: function () { return new Equation(this.LHS.clone(), this.RHS.clone()); }, sub: function (x, y) { var clone = this.clone(); clone.LHS = clone.LHS.sub(x.clone(), y.clone()); clone.RHS = clone.RHS.sub(x.clone(), y.clone()); return clone; }, isZero: function () { return core.Utils.evaluate(this.toLHS()).equals(0); }, latex: function (option) { return [this.LHS.latex(option), this.RHS.latex(option)].join('='); } }; //overwrite the equals function _.equals = function (a, b) { return new Equation(a, b); }; // Extend simplify (function () { var simplify = _.functions.simplify[0]; _.functions.simplify[0] = function (symbol) { if(symbol instanceof Equation) { symbol.LHS = simplify(symbol.LHS); symbol.RHS = simplify(symbol.RHS); return symbol; } // Just call the original simplify return simplify(symbol); }; })(); /** * Sets two expressions equal * @param {Symbol} symbol * @returns {Expression} */ core.Expression.prototype.equals = function (symbol) { if(symbol instanceof core.Expression) symbol = symbol.symbol; //grab the symbol if it's an expression var eq = new Equation(this.symbol, symbol); return eq; }; core.Expression.prototype.solveFor = function (x) { var symbol; if(this.symbol instanceof Equation) { //exit right away if we already have the answer //check the LHS if(this.symbol.LHS.isConstant() && this.symbol.RHS.equals(x)) return new core.Expression(this.symbol.LHS); //check the RHS if(this.symbol.RHS.isConstant() && this.symbol.LHS.equals(x)) return new core.Expression(this.symbol.RHS); //otherwise just bring it to LHS symbol = this.symbol.toLHS(); } else { symbol = this.symbol; } return solve(symbol, x).map(function (x) { return new core.Expression(x); }); }; core.Expression.prototype.expand = function () { if(this.symbol instanceof Equation) { var clone = this.symbol.clone(); clone.RHS = _.expand(clone.RHS); clone.LHS = _.expand(clone.LHS); return new core.Expression(clone); } return new core.Expression(_.expand(this.symbol)); }; core.Expression.prototype.variables = function () { if(this.symbol instanceof Equation) return core.Utils.arrayUnique(variables(this.symbol.LHS).concat(variables(this.symbol.RHS))); return variables(this.symbol); }; var setEq = function (a, b) { return _.equals(a, b); }; //link the Equation class back to the core core.Equation = Equation; //Loops through an array and attempts to fails a test. Stops if manages to fail. var checkAll = core.Utils.checkAll = function (args, test) { for(var i = 0; i < args.length; i++) if(test(args[i])) return false; return true; }; //version solve var __ = core.Solve = { version: '2.0.3', solutions: [], solve: function (eq, variable) { var solution = solve(eq, String(variable)); return new core.Vector(solution); //return new core.Vector(solve(eq.toString(), variable ? variable.toString() : variable)); }, /** * Brings the equation to LHS. A string can be supplied which will be converted to an Equation * @param {Equation|String} eqn * @returns {Symbol} */ toLHS: function (eqn, expand) { if(isSymbol(eqn)) return eqn; //If it's an equation then call its toLHS function instead if(!(eqn instanceof Equation)) { var es = eqn.split('='); //convert falsey values to zero es[1] = es[1] || '0'; eqn = new Equation(_.parse(es[0]), _.parse(es[1])); } return eqn.toLHS(expand); }, // getSystemVariables: function(eqns) { // vars = variables(eqns[0], null, null, true); // // //get all variables // for (var i = 1, l=eqns.length; i < l; i++) // vars = vars.concat(variables(eqns[i])); // //remove duplicates // vars = core.Utils.arrayUnique(vars).sort(); // // //done // return vars; // }, /** * Solve a set of circle equations. * @param {Symbol[]} eqns * @returns {Array} */ solveCircle: function (eqns, vars) { // Convert the variables to symbols var svars = vars.map(function (x) { return _.parse(x) }); var deg = []; var solutions = []; // Get the degree for the equations for(var i = 0; i < eqns.length; i++) { var d = []; for(var j = 0; j < svars.length; j++) { d.push(Number(core.Algebra.degree(eqns[i], svars[j]))); } // Store the total degree d.push(core.Utils.arraySum(d, true)); deg.push(d); } var a = eqns[0]; var b = eqns[1]; if(deg[0][2] > deg[1][2]) { [b, a] = [a, b]; [deg[1], deg[0]] = [deg[0], deg[1]]; } // Only solve it's truly a circle if(deg[0][0] === 1 && deg[0][2] === 2 && deg[1][0] === 2 && deg[1][2] === 4) { // For clarity we'll refer to the variables as x and y var x = vars[0]; var y = vars[1]; // We can now get the two points for y var y_points = solve(_.parse(b, knownVariable(x, solve(_.parse(a), x)[0])), y).map(function (x) { return x.toString(); }); // Since we now know y we can get the two x points from the first equation var x_points = [ solve(_.parse(a, knownVariable(y, y_points[0])))[0].toString() ]; if(y_points[1]) { x_points.push(solve(_.parse(a, knownVariable(y, y_points[1])))[0].toString()); } if(Settings.SOLUTIONS_AS_OBJECT) { var solutions = {}; solutions[x] = x_points; solutions[y] = y_points; } else { y_points.unshift(y); x_points.unshift(x); solutions = [x_points, y_points]; } } return solutions; }, /** * Solve a system of nonlinear equations * @param {Symbol[]} eqns The array of equations * @param {number} tries The maximum number of tries * @param {number} start The starting point where to start looking for solutions * @returns {Array} */ solveNonLinearSystem: function (eqns, tries, start) { if(tries < 0) { return [];//can't find a solution } start = typeof start === 'undefined' ? core.Settings.NON_LINEAR_START : start; //the maximum number of times to jump var max_tries = core.Settings.MAX_NON_LINEAR_TRIES; //halfway through the tries var halfway = Math.floor(max_tries / 2); //initialize the number of tries to 10 if not specified tries = typeof tries === 'undefined' ? max_tries : tries; //a point at which we check to see if we're converging. By inspection it seems that we can //use around 20 iterations to see if we're converging. If not then we retry a jump of x var jump_at = core.Settings.NON_LINEAR_JUMP_AT; //we jump by this many points at each pivot point var jump = core.Settings.NON_LINEAR_JUMP_SIZE; //used to check if we actually found a solution or if we gave up. Assume we will find a solution. var found = true; var create_subs = function (vars, matrix) { return vars.map(function (x, i) { return Number(matrix.get(i, 0)); }); }; var vars = core.Utils.arrayGetVariables(eqns); var jacobian = core.Matrix.jacobian(eqns, vars, function (x) { return build(x, vars); }, true); var max_iter = core.Settings.MAX_NEWTON_ITERATIONS; var o, y, iters, xn1, norm, lnorm, xn, d; var f_eqns = eqns.map(function (eq) { return build(eq, vars); }); var J = jacobian.map(function (e) { return build(e, vars); }, true); //initial values xn1 = core.Matrix.cMatrix(0, vars); //initialize the c matrix with something close to 0. var c = core.Matrix.cMatrix(start, vars); iters = 0; //start of algorithm do { //if we've reached the max iterations then exit if(iters > max_iter) { break; found = false; } //set the substitution object o = create_subs(vars, c); //set xn xn = c.clone(); //make all the substitutions for each of the equations f_eqns.forEach(function (f, i) { c.set(i, 0, f.apply(null, o)); }); var m = new core.Matrix(); J.each(function (fn, i, j) { var ans = fn.apply(null, o); m.set(i, j, ans); }); m = m.invert(); //preform the elimination y = _.multiply(m, c).negate(); //the callback is to avoid overflow in the coeffient denonimator //it converts it to a decimal and then back to a fraction. Some precision //is lost be it's better than overflow. d = y.subtract(xn1, function (x) { return _.parse(Number(x)); }); xn1 = xn.add(y, function (x) { return _.parse(Number(x)); }); //move c is now xn1 c = xn1; //get the norm //the expectation is that we're converging to some answer as this point regardless of where we start //this may have to be adjusted at some point because of erroneous assumptions if(iters >= jump_at) { //check the norm. If the norm is greater than one then it's time to try another point if(norm > 1) { //reset the start point at halway if(tries === halfway) start = 0; var sign = tries > halfway ? 1 : -1; //which side are we incrementing //we increment +n at one side and -n at the other. n = (tries % Math.floor(halfway)) + 1; //adjust the start point start += (sign * n * jump); //call restart return __.solveNonLinearSystem(eqns, --tries, start); } } lnorm = norm; iters++; norm = d.max(); //exit early. Revisit if we get bugs if(Number(norm) === Number(lnorm)) { break; } } while(Number(norm) >= Number.EPSILON) //return a blank set if nothing was found; if(!found) return []; //return c since that's the answer return __.systemSolutions(c, vars, true, function (x) { return core.Utils.round(Number(x), 14); }); }, systemSolutions: function (result, vars, expand_result, callback) { var solutions = core.Settings.SOLUTIONS_AS_OBJECT ? {} : []; result.each(function (e, idx) { var solution = (expand_result ? _.expand(e) : e).valueOf(); if(callback) solution = callback.call(e, solution); var variable = vars[idx]; if(core.Settings.SOLUTIONS_AS_OBJECT) { solutions[variable] = solution; } else solutions.push([variable, solution]); /*NO*/ }); //done return solutions; }, /** * Solves a system of equations by substitution. This is useful when * no distinct solution exists. e.g. a line, plane, etc. * @param {Array} eqns * @returns {Array} */ solveSystemBySubstitution: function (eqns) { // Assume at least 2 equations. The function variables will just return an empty array if undefined is provided var vars_a = variables(eqns[0]); var vars_b = variables(eqns[1]); // Check if it's a circle equation if(eqns.length === 2 && vars_a.length === 2 && core.Utils.arrayEqual(vars_a, vars_b)) { return __.solveCircle(eqns, vars_a); } return []; // return an empty set }, //https://www.lakeheadu.ca/sites/default/files/uploads/77/docs/RemaniFinal.pdf /** * Solves a systems of equations * @param {Array} eqns An array of equations * @param {Array} var_array An array of variables * @returns {Array|object} */ solveSystem: function (eqns, var_array) { //check if a var_array was specified //nerdamer.clearVars();// this deleted ALL variables: not what we want //parse all the equations to LHS. Remember that they come in as strings for(var i = 0; i < eqns.length; i++) eqns[i] = __.toLHS(eqns[i]); var l = eqns.length, m = new core.Matrix(), c = new core.Matrix(), expand_result = false, vars; if(typeof var_array === 'undefined') { //check to make sure that all the equations are linear if(!_A.allLinear(eqns)) { try { return __.solveNonLinearSystem(eqns); } catch(e) { if(e instanceof core.exceptions.DivisionByZero) { return __.solveSystemBySubstitution(eqns); } } } vars = core.Utils.arrayGetVariables(eqns); // If the system only has one variable then we solve for the first one and // then test the remaining equations with that solution. If any of the remaining // equation fails then the system has no solution if(vars.length === 1) { var n = 0, sol, e; do { var e = eqns[n].clone(); if(n > 0) { e = e.sub(vars[0], sol[0]); } sol = solve(e, vars[0]); // Skip the first one if(n === 0) continue; } while(++n < eqns.length) // Format the output var solutions; if(Settings.SOLUTIONS_AS_OBJECT) { solutions = {}; solutions[vars[0]] = sol; } else if(sol.length === 0) { solutions = sol; // No solutions } else { solutions = [vars[0], sol]; } return solutions; } // Deal with redundant equations as expressed in #562 // The fix is to remove all but the number of equations equal to the number // of variables. We then solve those and then evaluate the remaining equations // with those solutions. If the all equal true then those are just redundant // equations and we can return the solution set. if(vars.length < eqns.length) { var reduced = []; var n = eqns.length; for(var i = 0; i < n - 1; i++) { reduced.push(_.parse(eqns[i])); } var knowns = {}; var solutions = __.solveSystem(reduced, vars); // The solutions may have come back as an array if(Array.isArray(solutions)) { solutions.forEach(function (sol) { knowns[sol[0]] = sol[1]; }); } else { knowns = solutions; } // Start by assuming they will all evaluate to zero. If even one fails // then all zero will be false var all_zero = true; // Check if the last solution evalutes to zero given these solutions for(var i = n - 1; i < n; i++) { if(!_.parse(eqns[i], knowns).equals(0)) { all_zero = false; } } if(all_zero) { return solutions; } } // deletes only the variables of the linear equations in the nerdamer namespace for(var i = 0; i < vars.length; i++) { nerdamer.setVar(vars[i], "delete"); } // TODO: move this to cMatrix or something similar // populate the matrix for(var i = 0; i < l; i++) { var e = eqns[i]; //store the expression // Iterate over the columns for(var j = 0; j < vars.length; j++) { var v = vars[j]; var coeffs = []; e.each(function (x) { if(x.contains(v)) { coeffs = coeffs.concat(x.coeffs()); } }); var cf = core.Utils.arraySum(coeffs); m.set(i, j, cf); } //strip the variables from the symbol so we're left with only the zeroth coefficient //start with the symbol and remove each variable and its coefficient var num = e.clone(); vars.map(function (e) { num = num.stripVar(e, true); }); c.set(i, 0, num.negate()); } } else { /** * The idea is that we loop through each equation and then expand it. Afterwards we loop * through each term and see if and check to see if it matches one of the variables. * When a match is found we mark it. No other match should be found for that term. If it * is we stop since it's not linear. */ vars = var_array; expand_result = true; for(i = 0; i < l; i++) { //prefill c.set(i, 0, new Symbol(0)); var e = _.expand(eqns[i]).collectSymbols(); //expand and store //go trough each of the variables for(var j = 0; j < var_array.length; j++) { m.set(i, j, new Symbol(0)); var v = var_array[j]; //go through the terms and sort the variables for(var k = 0; k < e.length; k++) { var term = e[k], check = false; for(var z = 0; z < var_array.length; z++) { //check to see if terms contain multiple variables if(term.contains(var_array[z])) { if(check) core.err('Multiple variables found for term ' + term); check = true; } } //we made sure that every term contains one variable so it's safe to assume that if the //variable is found then the remainder is the coefficient. if(term.contains(v)) { var tparts = explode(remove(e, k), v); m.set(i, j, _.add(m.get(i, j), tparts[0])); } } } //all the remaining terms go to the c matrix for(k = 0; k < e.length; k++) { c.set(i, 0, _.add(c.get(i, 0), e[k])); } } //consider case (a+b)*I+u } //check if the system has a distinct solution if(vars.length !== eqns.length || m.determinant().equals(0)) { // solve the system by hand //return __.solveSystemBySubstitution(eqns, vars, m, c); throw new core.exceptions.SolveError('System does not have a distinct solution'); } // Use M^-1*c to solve system m = m.invert(); var result = m.multiply(c); //correct the sign as per issue #410 if(core.Utils.isArray(var_array)) result.each(function (x) { return x.negate(); }); return __.systemSolutions(result, vars, expand_result); }, /** * The quadratic function but only one side. * @param {Symbol} c * @param {Symbol} b * @param {Symbol} a * @returns {Symbol} */ quad: function (c, b, a) { var discriminant = _.subtract(_.pow(b.clone(), Symbol(2)), _.multiply(_.multiply(a.clone(), c.clone()), Symbol(4)))/*b^2 - 4ac*/; // Fix for #608 discriminant = _.expand(discriminant); var det = _.pow(discriminant, Symbol(0.5)); var den = _.parse(_.multiply(new Symbol(2), a.clone())); var retval = [ _.parse(format('(-({0})+({1}))/({2})', b, det, den)), _.parse(format('(-({0})-({1}))/({2})', b, det, den)) ]; return retval; }, /** * The cubic equation * http://math.stackexchange.com/questions/61725/is-there-a-systematic-way-of-solving-cubic-equations * @param {Symbol} d_o * @param {Symbol} c_o * @param {Symbol} b_o * @param {Symbol} a_o * @returns {Array} */ cubic: function (d_o, c_o, b_o, a_o) { //convert everything to text var a = a_o.text(), b = b_o.text(), c = c_o.text(), d = d_o.text(); var t = `(-(${b})^3/(27*(${a})^3)+(${b})*(${c})/(6*(${a})^2)-(${d})/(2*(${a})))`; var u = `((${c})/(3*(${a}))-(${b})^2/(9*(${a})^2))`; var v = `(${b})/(3*(${a}))`; var x = `((${t})+sqrt((${t})^2+(${u})^3))^(1/3)+((${t})-sqrt((${t})^2+(${u})^3))^(1/3)-(${v})`; // Convert a to one var w = '1/2+sqrt(3)/2*i'; // Cube root of unity return [ _.parse(x), _.parse(`(${x})(${w})`), _.parse(`(${x})(${w})^2`) ]; }, /** * The quartic equation * @param {Symbol} e * @param {Symbol} d * @param {Symbol} c * @param {Symbol} b * @param {Symbol} a * @returns {Array} */ quartic: function (e, d, c, b, a) { var scope = {}; core.Utils.arrayUnique(variables(a).concat(variables(b)) .concat(variables(c)).concat(variables(d)).concat(variables(e))) .map(function (x) { scope[x] = 1; }); a = a.toString(); b = b.toString(); c = c.toString(); d = d.toString(); e = e.toString(); var p, q, D, D0, D1, Q, x1, x2, x3, x4; /*var D = core.Utils.block('PARSE2NUMBER', function() { return _.parse(format("256*({0})^3*({4})^3-192*({0})^2*({1})*({3})*({4})^2-128*({0})^2*({2})^2*({4})^2+144*({0})^2*({2})*({3})^2*({4})"+ "-27*({0})^2*({3})^4+144*({0})*({1})^2*({2})*({4})^2-6*({0})*({1})^2*({3})^2*({4})-80*({0})*({1})*({2})^2*({3})*({4})+18*({0})*({1})*({2})*({3})^3"+ "+16*({0})*({2})^4*({4})-4*({0})*({2})^3*({3})^2-27*({1})^4*({4})^2+18*({1})^3*({2})*({3})*({4})-4*({1})^3*({3})^3-4*({1})^2*({2})^3*({4})+({1})^2*({2})^2*({3})^2", a, b, c, d, e), scope); });*/ p = _.parse(format("(8*({0})*({2})-3*({1})^2)/(8*({0})^2)", a, b, c)).toString(); //a, b, c q = _.parse(format("(({1})^3-4*({0})*({1})*({2})+8*({0})^2*({3}))/(8*({0})^3)", a, b, c, d)).toString();//a, b, c, d, e D0 = _.parse(format("12*({0})*({4})-3*({1})*({3})+({2})^2", a, b, c, d, e)).toString(); //a, b, c, d, e D1 = _.parse(format("2*({2})^3-9*({1})*({2})*({3})+27*({1})^2*({4})+27*({0})*({3})^2-72*({0})*({2})*({4})", a, b, c, d, e)).toString(); //a, b, c, d, e Q = _.parse(format("((({1})+(({1})^2-4*({0})^3)^(1/2))/2)^(1/3)", D0, D1)).toString(); //D0, D1 S = _.parse(format("(1/2)*(-(2/3)*({1})+(1/(3*({0}))*(({2})+(({3})/({2})))))^(1/2)", a, p, Q, D0)).toString(); //a, p, Q, D0 x1 = _.parse(format("-(({1})/(4*({0})))-({4})+(1/2)*sqrt(-4*({4})^2-2*({2})+(({3})/({4})))", a, b, p, q, S)); //a, b, p, q, S x2 = _.parse(format("-(({1})/(4*({0})))-({4})-(1/2)*sqrt(-4*({4})^2-2*({2})+(({3})/({4})))", a, b, p, q, S)); //a, b, p, q, S x3 = _.parse(format("-(({1})/(4*({0})))+({4})+(1/2)*sqrt(-4*({4})^2-2*({2})-(({3})/({4})))", a, b, p, q, S)); //a, b, p, q, S x4 = _.parse(format("-(({1})/(4*({0})))+({4})-(1/2)*sqrt(-4*({4})^2-2*({2})-(({3})/({4})))", a, b, p, q, S)); //a, b, p, q, S return [x1, x2, x3, x4]; }, /** * Breaks the equation up in its factors and tries to solve the smaller parts * @param {Symbol} symbol * @param {String} solve_for * @returns {Array} */ divideAndConquer: function (symbol, solve_for) { var sols = []; //see if we can solve the factors var factors = core.Algebra.Factor.factor(symbol); if(factors.group === CB) { factors.each(function (x) { x = Symbol.unwrapPARENS(x); sols = sols.concat(solve(x, solve_for)); }); } return sols; }, /** * Attempts to solve the equation assuming it's a polynomial with numeric coefficients * @param {Symbol} eq * @param {String} solve_for * @returns {Array} */ csolve: function (eq, solve_for) { return core.Utils.block('IGNORE_E', function () { var f, p, pn, n, pf, r, theta, sr, sp, roots; roots = []; f = core.Utils.decompose_fn(eq, solve_for, true); if(f.x.group === S) { p = _.parse(f.x.power); pn = Number(p); n = _.pow(_.divide(f.b.negate(), f.a), p.invert()); pf = Symbol.toPolarFormArray(n); r = pf[0]; theta = pf[1]; sr = r.toString(); sp = p.toString(); var k, root, str; for(var i = 0; i < pn; i++) { k = i; str = format('({0})*e^(2*{1}*pi*{2}*{3})', sr, k, p, core.Settings.IMAGINARY); root = _.parse(str); roots.push(root); } } return roots; }, true); }, /** * Generates starting points for the Newton solver given an expression at zero. * It beings by check if zero is a good point and starts expanding by a provided step size. * Builds on the fact that if the sign changes over an interval then a zero * must exist on that interval * @param {Symbol} symbol * @param {Number} step * @param {Array} points * @returns {Array} */ getPoints: function (symbol, step, points) { step = step || 0.01; points = points || []; var f = build(symbol); var x0 = 0; var start = Math.round(x0), last = f(start), last_sign = last / Math.abs(last), rside = core.Settings.ROOTS_PER_SIDE, // the max number of roots on right side lside = rside; // the max number of roots on left side // check around the starting point points.push(Math.floor(start / 2)); //half way from zero might be a good start points.push(Math.abs(start)); //|f(0)| could be a good start points.push(start);//|f(0)| could be a good start //adjust for log. A good starting point to include for log is 0.1 symbol.each(function (x) { if(x.containsFunction(core.Settings.LOG)) points.push(0.1); }); var left = range(-core.Settings.SOLVE_RADIUS, start, step), right = range(start, core.Settings.SOLVE_RADIUS, step); var test_side = function (side, num_roots) { var xi, val, sign; var hits = []; for(var i = 0, l = side.length; i < l; i++) { xi = side[i]; //the point being evaluated val = f(xi); sign = val / Math.abs(val); //Don't add non-numeric values if(isNaN(val) || !isFinite(val) || hits.length > num_roots) { continue; } //compare the signs. The have to be different if they cross a zero if(sign !== last_sign) { hits.push(xi); //take note of the possible zero location } last_sign = sign; } points = points.concat(hits); }; test_side(left, lside); test_side(right, rside); return points; }, /** * Implements the bisection method. Returns undefined in no solution is found * @param {number} point * @param {function} f * @returns {undefined | number} */ bisection: function (point, f) { var left = point - 1; var right = point + 1; // First test if this point is even worth evaluating. It should // be crossing the x axis so the signs should be different if(Math.sign(f(left)) !== Math.sign(f(right))) { var safety = 0; var epsilon, middle; do { epsilon = Math.abs(right - left); // Safety against an infinite loop if(safety++ > core.Settings.MAX_BISECTION_ITER || isNaN(epsilon)) { return; } // Calculate the middle point middle = (left + right) / 2; if(f(left) * f(middle) > 0) { left = middle; } else { right = middle; } } while(epsilon >= Settings.EPSILON); var solution = (left + right) / 2; // Test the solution to make sure that it's within tolerance var x_point = f(solution); if(!isNaN(x_point) && Math.abs(x_point) <= core.Settings.BI_SECTION_EPSILON) { // Returns too many junk solutions if not rounded at 13th place. return core.Utils.round(solution, 13); } } }, /** * Implements Newton's iterations. Returns undefined if no solutions if found * @param {number} point * @param {function} f * @param {function} fp * @returns {undefined|number} */ Newton: function (point, f, fp) { var maxiter = core.Settings.MAX_NEWTON_ITERATIONS, iter = 0; //first try the point itself. If it's zero viola. We're done var x0 = point, x; do { var fx0 = f(x0); //store the result of the function //if the value is zero then we're done because 0 - (0/d f(x0)) = 0 if(x0 === 0 && fx0 === 0) { x = 0; break; } iter++; if(iter > maxiter) return; //naximum iterations reached x = x0 - fx0 / fp(x0); var e = Math.abs(x - x0); x0 = x; } while(e > Settings.NEWTON_EPSILON) //check if the number is indeed zero. 1e-13 seems to give the most accurate results if(Math.abs(f(x)) <= Settings.EPSILON) return x; }, rewrite: function (rhs, lhs, for_variable) { lhs = lhs || new Symbol(0); if(rhs.isComposite() && rhs.isLinear()) { //try to isolate the square root //container for the square roots var sqrts = []; //all else var rem = []; rhs.each(function (x) { x = x.clone(); if(x.fname === 'sqrt' && x.contains(for_variable)) { sqrts.push(x); } else { rem.push(x); } }, true); if(sqrts.length === 1) { //move the remainder to the RHS lhs = _.expand(_.pow(_.subtract(lhs, core.Utils.arraySum(rem)), new Symbol(2))); //square both sides rhs = _.expand(_.pow(Symbol.unwrapSQRT(sqrts[0]), new Symbol(2))); } } else { rhs = Symbol.unwrapSQRT(_.expand(rhs)); //expand the term expression go get rid of quotients when possible } var c = 0, //a counter to see if we have all terms with the variable l = rhs.length; //try to rewrite the whole thing if(rhs.group === CP && rhs.contains(for_variable) && rhs.isLinear()) { rhs.distributeMultiplier(); var t = new Symbol(0); //first bring all the terms containing the variable to the lhs rhs.each(function (x) { if(x.contains(for_variable)) { c++; t = _.add(t, x.clone()); } else lhs = _.subtract(lhs, x.clone()); }); rhs = t; //if not all the terms contain the variable so it's in the form //a*x^2+x if(c !== l) { return __.rewrite(rhs, lhs, for_variable); } else { return [rhs, lhs]; } } else if(rhs.group === CB && rhs.contains(for_variable) && rhs.isLinear()) { if(rhs.multiplier.lessThan(0)) { rhs.multiplier = rhs.multiplier.multiply(new core.Frac(-1)); lhs.multiplier = lhs.multiplier.multiply(new core.Frac(-1)); } if(lhs.equals(0)) return new Symbol(0); else { var t = new Symbol(1); rhs.each(function (x) { if(x.contains(for_variable)) t = _.multiply(t, x.clone()); else lhs = _.divide(lhs, x.clone()); }); rhs = t; return __.rewrite(rhs, lhs, for_variable); } } else if(!rhs.isLinear() && rhs.contains(for_variable)) { var p = _.parse(rhs.power.clone().invert()); rhs = _.pow(rhs, p.clone()); lhs = _.pow(_.expand(lhs), p.clone()); return __.rewrite(rhs, lhs, for_variable); } else if(rhs.group === FN || rhs.group === S || rhs.group === PL) { return [rhs, lhs]; } }, sqrtSolve: function (symbol, v) { var sqrts = new Symbol(0); var rem = new Symbol(0); if(symbol.isComposite()) { symbol.each(function (x) { if(x.fname === 'sqrt' && x.contains(v)) { sqrts = _.add(sqrts, x.clone()); } else { rem = _.add(rem, x.clone()); } }); //quick and dirty ATM if(!sqrts.equals(0)) { var t = _.expand(_.multiply(_.parse(symbol.multiplier), _.subtract(_.pow(rem, new Symbol(2)), _.pow(sqrts, new Symbol(2))))); //square both sides var solutions = solve(t, v); //test the points. The dumb way of getting the answers solutions = solutions.filter(function (e) { if(e.isImaginary()) return e; var subs = {}; subs[v] = e; var point = evaluate(symbol, subs); if(point.equals(0)) return e; }); return solutions; } } } }; /* * * @param {String[]|String|Equation} eqns * @param {String} solve_for * @param {Array} solutions * @param {Number} depth * @param {String|Equation} fn * @returns {Array} */ var solve = function (eqns, solve_for, solutions, depth, fn) { depth = depth || 0; if(depth++ > Settings.MAX_SOLVE_DEPTH) { return solutions; } //make preparations if it's an Equation if(eqns instanceof Equation) { //if it's zero then we're done if(eqns.isZero()) { return [new Symbol(0)]; } //if the lhs = x then we're done if(eqns.LHS.equals(solve_for) && !eqns.RHS.contains(solve_for)) { return [eqns.RHS]; } //if the rhs = x then we're done if(eqns.RHS.equals(solve_for) && !eqns.LHS.contains(solve_for)) { return [eqns.LHS]; } } //unwrap the vector since what we want are the elements if(eqns instanceof core.Vector) eqns = eqns.elements; solve_for = solve_for || 'x'; //assumes x by default //If it's an array then solve it as a system of equations if(isArray(eqns)) { return __.solveSystem.apply(undefined, arguments); } // Parse out functions. Fix for issue #300 // eqns = core.Utils.evaluate(eqns);