diff --git a/lib/core/typed.js b/lib/core/typed.js index fc7eab1bee..4028618730 100644 --- a/lib/core/typed.js +++ b/lib/core/typed.js @@ -24,28 +24,42 @@ exports.create = function create(type) { // arguments are type-checked (so for performance it's important to put the // most used types first). typed.types = [ - { type: 'number', test: function (x) { return typeof x === 'number'; } }, - { type: 'Complex', test: function (x) { return x && x.isComplex; } }, - { type: 'BigNumber', test: function (x) { return x && x.isBigNumber; } }, - { type: 'Fraction', test: function (x) { return x && x.isFraction; } }, - { type: 'Unit', test: function (x) { return x && x.isUnit; } }, - { type: 'string', test: function (x) { return typeof x === 'string'; } }, - { type: 'Array', test: Array.isArray }, - { type: 'Matrix', test: function (x) { return x && x.isMatrix; } }, - { type: 'DenseMatrix', test: function (x) { return x && x.isDenseMatrix; } }, - { type: 'SparseMatrix', test: function (x) { return x && x.isSparseMatrix; } }, - { type: 'ImmutableDenseMatrix', test: function (x) { return x && x.isImmutableDenseMatrix; } }, - { type: 'Range', test: function (x) { return x && x.isRange; } }, - { type: 'Index', test: function (x) { return x && x.isIndex; } }, - { type: 'boolean', test: function (x) { return typeof x === 'boolean'; } }, - { type: 'ResultSet', test: function (x) { return x && x.isResultSet; } }, - { type: 'Help', test: function (x) { return x && x.isHelp; } }, - { type: 'function', test: function (x) { return typeof x === 'function';} }, - { type: 'Date', test: function (x) { return x instanceof Date; } }, - { type: 'RegExp', test: function (x) { return x instanceof RegExp; } }, - { type: 'Object', test: function (x) { return typeof x === 'object'; } }, - { type: 'null', test: function (x) { return x === null; } }, - { type: 'undefined', test: function (x) { return x === undefined; } } + { type: 'number', test: function (x) { return typeof x === 'number' } }, + { type: 'Complex', test: function (x) { return x && x.isComplex } }, + { type: 'BigNumber', test: function (x) { return x && x.isBigNumber } }, + { type: 'Fraction', test: function (x) { return x && x.isFraction } }, + { type: 'Unit', test: function (x) { return x && x.isUnit } }, + { type: 'string', test: function (x) { return typeof x === 'string' } }, + { type: 'Array', test: Array.isArray }, + { type: 'Matrix', test: function (x) { return x && x.isMatrix } }, + { type: 'DenseMatrix', test: function (x) { return x && x.isDenseMatrix } }, + { type: 'SparseMatrix', test: function (x) { return x && x.isSparseMatrix } }, + { type: 'Range', test: function (x) { return x && x.isRange } }, + { type: 'Index', test: function (x) { return x && x.isIndex } }, + { type: 'boolean', test: function (x) { return typeof x === 'boolean' } }, + { type: 'ResultSet', test: function (x) { return x && x.isResultSet } }, + { type: 'Help', test: function (x) { return x && x.isHelp } }, + { type: 'function', test: function (x) { return typeof x === 'function'} }, + { type: 'Date', test: function (x) { return x instanceof Date } }, + { type: 'RegExp', test: function (x) { return x instanceof RegExp } }, + { type: 'Object', test: function (x) { return typeof x === 'object' } }, + { type: 'null', test: function (x) { return x === null } }, + { type: 'undefined', test: function (x) { return x === undefined } }, + { type: 'OperatorNode', test: function (x) { return x && x.isOperatorNode } }, + { type: 'ConstantNode', test: function (x) { return x && x.isConstantNode } }, + { type: 'SymbolNode', test: function (x) { return x && x.isSymbolNode } }, + { type: 'ParenthesisNode', test: function (x) { return x && x.isParenthesisNode } }, + { type: 'FunctionNode', test: function (x) { return x && x.isFunctionNode } }, + { type: 'FunctionAssignmentNode', test: function (x) { return x && x.isFunctionAssignmentNode } }, + + { type: 'ArrayNode', test: function (x) { return x && x.isArrayNode } }, + { type: 'AssignmentNode', test: function (x) { return x && x.isAssignmentNode } }, + { type: 'BlockNode', test: function (x) { return x && x.isBlockNode } }, + { type: 'ConditionalNode', test: function (x) { return x && x.isConditionalNode } }, + { type: 'IndexNode', test: function (x) { return x && x.isIndexNode } }, + { type: 'RangeNode', test: function (x) { return x && x.isRangeNode } }, + { type: 'UpdateNode', test: function (x) { return x && x.isUpdateNode } }, + { type: 'Node', test: function (x) { return x && x.isNode } }, ]; // TODO: add conversion from BigNumber to number? diff --git a/lib/function/algebra/derivative.js b/lib/function/algebra/derivative.js new file mode 100644 index 0000000000..f2697c64a2 --- /dev/null +++ b/lib/function/algebra/derivative.js @@ -0,0 +1,623 @@ +'use strict'; + +function factory (type, config, load, typed, math) { + var ConstantNode = load(require('../../expression/node/ConstantNode')); + var FunctionNode = load(require('../../expression/node/FunctionNode')); + var OperatorNode = load(require('../../expression/node/OperatorNode')); + var ParenthesisNode = load(require('../../expression/node/ParenthesisNode')); + var SymbolNode = load(require('../../expression/node/SymbolNode')); + + + /** + * Takes the derivative of an expression expressed in parser Nodes. + * This uses rules of differentiation which can be found here: + * http://en.wikipedia.org/wiki/Differentiation_rules + * + * Syntax: + * + * derivative(expr, variable) + * + * Usage: + * + * math.eval('derivative(2*x, x)') + * + * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} expr + * @param {SymbolNode} variable The variable to differentiate over + * @return {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} The derivative of `expr` + */ + var derivative = typed('derivative', { + 'Node, SymbolNode': function (expr, variable) { + var constNodes = {}; + constTag(constNodes, expr, variable.name); + return _derivative(expr, constNodes); + } + }); + + /** + * Does a depth-first search on the expression tree to identify what Nodes + * are constants (e.g. 2 + 2), and stores the ones that are constants in + * constNodes. Classification is done as follows: + * + * 1. ConstantNodes are constants. + * 2. If there exists a SymbolNode, of which we are differentiating over, + * in the subtree it is not constant. + * + * @param {Object} constNodes Holds the nodes that are constant + * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node + * @param {string} varName Variable that we are differentiating + * @return {boolean} if node is constant + */ + var constTag = typed('constTag', { + 'Object, ConstantNode, string': function (constNodes, node) { + return constNodes[node] = true; + }, + + 'Object, SymbolNode, string': function (constNodes, node, varName) { + if (node.name != varName) { + return constNodes[node] = true; + } + return false; + }, + + 'Object, ParenthesisNode, string': function (constNodes, node, varName) { + return constTag(constNodes, node.content, varName); + }, + + 'Object, FunctionAssignmentNode, string': function (constNodes, node, varName) { + if (node.params.indexOf(varName) == -1) { + return constNodes[node] = true; + } + return constTag(constNodes, node.expr, varName); + }, + + 'Object, FunctionNode | OperatorNode, string': function (constNodes, node, varName) { + if (node.args.length != 0) { + var isConst = constTag(constNodes, node.args[0], varName); + for (var i = 1; i < node.args.length; ++i) { + isConst = constTag(constNodes, node.args[i], varName) && isConst; + } + + if (isConst) { + return constNodes[node] = true; + } + } + return false; + } + }); + + /** + * Applies differentiation rules. + * + * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node + * @param {Object} constNodes Holds the nodes that are constant + * @return {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} The derivative of `expr` + */ + var _derivative = typed('_derivative', { + 'ConstantNode, Object': function (node) { + return new ConstantNode('0', node.valueType); + }, + + 'SymbolNode, Object': function (node, constNodes) { + if (constNodes[node] !== undefined) { + return new ConstantNode('0', config.number); + } + return new ConstantNode('1', config.number); + }, + + 'ParenthesisNode, Object': function (node, constNodes) { + return new ParenthesisNode(_derivative(node.content, constNodes)); + }, + + 'FunctionAssignmentNode, Object': function (node, constNodes) { + if (constNodes[node] !== undefined) { + return new ConstantNode('0', config.number); + } + return _derivative(node.expr, constNodes); + }, + + 'FunctionNode, Object': function (node, constNodes) { + if (node.args.length != 1) { + funcArgsCheck(node); + } + + if (constNodes[node] !== undefined) { + return new ConstantNode('0', config.number); + } + + var arg1 = node.args[0]; + var arg2; + + var div = false; + var negative = false; + + var funcDerivative; + switch (node.name) { + case 'sqrt': + case 'nthRoot': + // d/dx(sqrt(x)) = 1 / (2*sqrt(x)) + if (node.args.length == 1) { + div = true; + funcDerivative = new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new FunctionNode('sqrt', [arg1]) + ]); + break; + } + + // Rearrange from nthRoot(x, a) -> x^(1/a) + arg2 = new OperatorNode('/', 'divide', [ + new ConstantNode('1', config.number), + node.args[1] + ]); + + // Is a variable? + constNodes[arg2] = constNodes[node.args[1]]; + + return _derivative(new OperatorNode('^', 'pow', [arg1, arg2]), constNodes); + case 'log10': + arg2 = new ConstantNode('10', config.number); + case 'log': + if (!arg2 && node.args.length == 1) { + // d/dx(log(x)) = 1 / x + funcDerivative = arg1.clone(); + } else if (arg2 || constNodes[node.args[1]] !== undefined) { + // d/dx(log(x, c)) = 1 / (x*ln(c)) + funcDerivative = new OperatorNode('*', 'multiply', [ + arg1.clone(), + new FunctionNode('log', [arg2 || node.args[1]]) + ]); + } else { + // d/dx(log(f(x), g(x))) = d/dx(log(f(x)) / log(g(x))) + return _derivative(new OperatorNode('/', 'divide', [ + new FunctionNode('log', [arg1]), + new FunctionNode('log', [node.args[1]]) + ]), constNodes); + } + + div = true; + break; + case 'exp': + // d/dx(e^x) = e^x + funcDerivative = arg1.clone(); + break; + case 'sin': + // d/dx(sin(x)) = cos(x) + funcDerivative = new FunctionNode('cos', [arg1.clone()]); + break; + case 'cos': + // d/dx(cos(x)) = -sin(x) + funcDerivative = new OperatorNode('-', 'unaryMinus', [ + new FunctionNode('sin', [arg1.clone()]) + ]); + break; + case 'tan': + // d/dx(tan(x)) = sec(x)^2 + funcDerivative = new OperatorNode('^', 'pow', [ + new FunctionNode('sec', [arg1.clone()]), + new ConstantNode('2', config.number) + ]); + break; + case 'sec': + // d/dx(sec(x)) = sec(x)tan(x) + funcDerivative = new OperatorNode('*', 'multiply', [ + node, + new FunctionNode('tan', [arg1.clone()]) + ]); + break; + case 'csc': + // d/dx(csc(x)) = -csc(x)cot(x) + negative = true; + funcDerivative = new OperatorNode('*', 'multiply', [ + node, + new FunctionNode('cot', [arg1.clone()]) + ]); + break; + case 'cot': + // d/dx(cot(x)) = -csc(x)^2 + negative = true; + funcDerivative = new OperatorNode('^', 'pow', [ + new FunctionNode('csc', [arg1.clone()]), + new ConstantNode('2', config.number) + ]); + break; + case 'asin': + // d/dx(asin(x)) = 1 / sqrt(1 - x^2) + div = true; + funcDerivative = new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new ConstantNode('1', config.number), + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]) + ]) + ]); + break; + case 'acos': + // d/dx(acos(x)) = -1 / sqrt(1 - x^2) + div = true; + negative = true; + funcDerivative = new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new ConstantNode('1', config.number), + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]) + ]) + ]); + break; + case 'atan': + // d/dx(atan(x)) = 1 / (x^2 + 1) + div = true; + funcDerivative = new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]), + new ConstantNode('1', config.number) + ]); + break; + case 'asec': + // d/dx(asec(x)) = 1 / (|x|*sqrt(x^2 - 1)) + div = true; + funcDerivative = new OperatorNode('*', 'multiply', [ + new FunctionNode('abs', [arg1.clone()]), + new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]), + new ConstantNode('1', config.number) + ]) + ]) + ]); + break; + case 'acsc': + // d/dx(acsc(x)) = -1 / (|x|*sqrt(x^2 - 1)) + div = true; + negative = true; + funcDerivative = new OperatorNode('*', 'multiply', [ + new FunctionNode('abs', [arg1.clone()]), + new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]), + new ConstantNode('1', config.number) + ]) + ]) + ]); + break; + case 'acot': + // d/dx(acot(x)) = -1 / (x^2 + 1) + div = true; + negative = true; + funcDerivative = new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]), + new ConstantNode('1', config.number) + ]); + break; + case 'sinh': + // d/dx(sinh(x)) = cosh(x) + funcDerivative = new FunctionNode('cosh', [arg1.clone()]); + break; + case 'cosh': + // d/dx(cosh(x)) = sinh(x) + funcDerivative = new FunctionNode('sinh', [arg1.clone()]); + break; + case 'tanh': + // d/dx(tanh(x)) = sech(x)^2 + funcDerivative = new OperatorNode('^', 'pow', [ + new FunctionNode('sech', [arg1.clone()]), + new ConstantNode('2', config.number) + ]); + break; + case 'sech': + // d/dx(sech(x)) = -sech(x)tanh(x) + negative = true; + funcDerivative = new OperatorNode('*', 'multiply', [ + node, + new FunctionNode('tanh', [arg1.clone()]) + ]); + break; + case 'csch': + // d/dx(csch(x)) = -csch(x)coth(x) + negative = true; + funcDerivative = new OperatorNode('*', 'multiply', [ + node, + new FunctionNode('coth', [arg1.clone()]) + ]); + break; + case 'coth': + // d/dx(coth(x)) = -csch(x)^2 + negative = true; + funcDerivative = new OperatorNode('^', 'pow', [ + new FunctionNode('csch', [arg1.clone()]), + new ConstantNode('2', config.number) + ]); + break; + case 'asinh': + // d/dx(asinh(x)) = 1 / sqrt(x^2 + 1) + div = true; + funcDerivative = new FunctionNode('sqrt', [ + new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]), + new ConstantNode('1', config.number) + ]) + ]); + break; + case 'acosh': + // d/dx(acosh(x)) = 1 / sqrt(x^2 - 1); XXX potentially only for x >= 1 (the real spectrum) + div = true; + funcDerivative = new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]), + new ConstantNode('1', config.number), + ]) + ]); + break; + case 'atanh': + // d/dx(atanh(x)) = 1 / (1 - x^2) + div = true; + funcDerivative = new OperatorNode('-', 'subtract', [ + new ConstantNode('1', config.number), + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]) + ]); + break; + case 'asech': + // d/dx(asech(x)) = -1 / (x*sqrt(1 - x^2)) + div = true; + negative = true; + funcDerivative = new OperatorNode('*', 'multiply', [ + arg1.clone(), + new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new ConstantNode('1', config.number), + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]) + ]) + ]) + ]); + break; + case 'acsch': + // d/dx(acsch(x)) = -1 / (|x|*sqrt(x^2 + 1)) + div = true; + negative = true; + funcDerivative = new OperatorNode('*', 'multiply', [ + new FunctionNode('abs', [arg1.clone()]), + new FunctionNode('sqrt', [ + new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode('2', config.number) + ]), + new ConstantNode(1) + ]) + ]) + ]); + break; + case 'acoth': + // d/dx(acoth(x)) = -1 / (1 - x^2) + div = true; + negative = true; + funcDerivative = new OperatorNode('-', 'subtract', [ + new ConstantNode(1), + new OperatorNode('^', 'pow', [ + arg1.clone(), + new ConstantNode(2) + ]) + ]); + break; + case 'gamma': // Needs digamma function, d/dx(gamma(x)) = gamma(x)digamma(x) + default: throw new Error('Function "' + node.name + '" not supported by derivative'); + } + + var op, func; + if (div) { + op = '/'; + func = 'divide'; + } else { + op = '*'; + func = 'multiply'; + } + + /* Chain rule applies to all functions: + F(x) = f(g(x)) + F'(x) = g'(x)*f'(g(x)) */ + var chainDerivative = _derivative(arg1, constNodes); + if (negative) { + chainDerivative = new OperatorNode('-', 'unaryMinus', [chainDerivative]); + } + return new OperatorNode(op, func, [chainDerivative, funcDerivative]); + }, + + 'OperatorNode, Object': function (node, constNodes) { + if (constNodes[node] !== undefined) { + return new ConstantNode('0', config.number); + } + + var arg1 = node.args[0]; + var arg2 = node.args[1]; + + switch (node.op) { + case '+': + case '-': + // d/dx(+/-f(x)) = +/-f'(x) + if (node.args.length == 1) { + return new OperatorNode(node.op, node.fn, [_derivative(arg1, constNodes)]); + } + + // Linearity of differentiation, d/dx(f(x) +/- g(x)) = f'(x) +/- g'(x) + return new OperatorNode(node.op, node.fn, [ + _derivative(arg1, constNodes), + _derivative(arg2, constNodes) + ]); + case '*': + // d/dx(c*f(x)) = c*f'(x) + if (constNodes[arg1] !== undefined || constNodes[arg2] !== undefined) { + var newArgs = (constNodes[arg1] !== undefined) + ? [arg1.clone(), _derivative(arg2, constNodes)] + : [arg2.clone(), _derivative(arg1, constNodes)]; + + return new OperatorNode('*', 'multiply', newArgs); + } + + // Product Rule, d/dx(f(x)*g(x)) = f'(x)*g(x) + f(x)*g'(x) + return new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [_derivative(arg1, constNodes), arg2.clone()]), + new OperatorNode('*', 'multiply', [arg1.clone(), _derivative(arg2, constNodes)]) + ]); + case '/': + // d/dx(f(x) / c) = f'(x) / c + if (constNodes[arg2] !== undefined) { + return new OperatorNode('/', 'divide', [_derivative(arg1, constNodes), arg2]); + } + + // Reciprocal Rule, d/dx(c / f(x)) = -c(f'(x)/f(x)^2) + if (constNodes[arg1] !== undefined) { + return new OperatorNode('*', 'multiply', [ + new OperatorNode('-', 'unaryMinus', [arg1]), + new OperatorNode('/', 'divide', [ + _derivative(arg2, constNodes), + new OperatorNode('^', 'pow', [arg2.clone(), new ConstantNode('2', config.number)]) + ]) + ]); + } + + // Quotient rule, d/dx(f(x) / g(x)) = (f'(x)g(x) - f(x)g'(x)) / g(x)^2 + return new OperatorNode('/', 'divide', [ + new OperatorNode('-', 'subtract', [ + new OperatorNode('*', 'multiply', [_derivative(arg1, constNodes), arg2.clone()]), + new OperatorNode('*', 'multiply', [arg1.clone(), _derivative(arg2, constNodes)]) + ]), + new OperatorNode('^', 'pow', [arg2.clone(), new ConstantNode('2', config.number)]) + ]); + case '^': + if (constNodes[arg1] !== undefined) { + // If is secretly constant; 0^f(x) = 1 (in JS), 1^f(x) = 1 + if (arg1.isConstantNode && (arg1.value == '0' || arg1.value == '1')) { + return new ConstantNode('0', config.number); + } + + // d/dx(c^f(x)) = c^f(x)*ln(c)*f'(x) + return new OperatorNode('*', 'multiply', [ + node, + new OperatorNode('*', 'multiply', [ + new FunctionNode('log', [arg1.clone()]), + _derivative(arg2.clone(), constNodes) + ]) + ]); + } + + if (constNodes[arg2] !== undefined) { + if (arg2.isConstantNode) { + var expValue = arg2.value; + + // If is secretly constant; f(x)^0 = 1 -> d/dx(1) = 0 + if (expValue == '0') { + return new ConstantNode('0', config.number); + } + // Ignore exponent; f(x)^1 = f(x) + if (expValue == '1') { + return _derivative(arg1, constNodes); + } + } + + // Elementary Power Rule, d/dx(f(x)^c) = c*f'(x)*f(x)^(c-1) + var powMinusOne = new OperatorNode('^', 'pow', [ + arg1.clone(), + new OperatorNode('-', 'subtract', [ + arg2, + new ConstantNode('1', config.number) + ]) + ]); + + return new OperatorNode('*', 'multiply', [ + arg2.clone(), + new OperatorNode('*', 'multiply', [ + _derivative(arg1, constNodes), + powMinusOne + ]), + ]); + } + + // Functional Power Rule, d/dx(f^g) = f^g*[f'*(g/f) + g'ln(f)] + return new OperatorNode('*', 'multiply', [ + new OperatorNode('^', 'pow', [arg1.clone(), arg2.clone()]), + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + _derivative(arg1, constNodes), + new OperatorNode('/', 'divide', [arg2.clone(), arg1.clone()]) + ]), + new OperatorNode('*', 'multiply', [ + _derivative(arg2, constNodes), + new FunctionNode('log', [arg1.clone()]) + ]) + ]) + ]); + case '%': + case 'mod': + default: throw new Error('Operator "' + node.op + '" not supported by derivative'); + } + } + }); + + /** + * Ensures the number of arguments for a function are correct, + * and will throw an error otherwise. + * + * @param {FunctionNode} node + */ + function funcArgsCheck(node) { + if ((node.name == 'log' || node.name == 'nthRoot') && node.args.length == 2) { + return; + } + + // Avoids unidentified symbol error + for (var i = 0; i < node.args.length; ++i) { + node.args[i] = new ConstantNode(0); + } + + node.compile().eval(); + throw new Error('Expected TypeError, but none found'); + } + + /** + * A transformation for the derivative function. This transformation will be + * invoked when the function is used via the expression parser of math.js. + * + * @param {Array.} args + * Expects the following arguments: [f, x] + * @param {Object} math + * @param {Object} [scope] + */ + derivative.transform = typed('derivative_transform', { + 'Array, Object, Object': function (args) { + return derivative.apply(null, args); + } + }); + + derivative.transform.rawArgs = true; + + return derivative; +} + +exports.name = 'derivative'; +exports.factory = factory; diff --git a/lib/function/algebra/index.js b/lib/function/algebra/index.js index 1f7752af92..829672006f 100644 --- a/lib/function/algebra/index.js +++ b/lib/function/algebra/index.js @@ -1,4 +1,6 @@ module.exports = [ + require('./derivative'), + // decomposition require('./decomposition/lup'), diff --git a/test/function/algebra/derivative.test.js b/test/function/algebra/derivative.test.js new file mode 100644 index 0000000000..384f876aba --- /dev/null +++ b/test/function/algebra/derivative.test.js @@ -0,0 +1,820 @@ +// test derivative +var assert = require('assert'); +var math = require('../../../index'); +var ConstantNode = math.expression.node.ConstantNode; +var OperatorNode = math.expression.node.OperatorNode; +var FunctionNode = math.expression.node.FunctionNode; +var ParenthesisNode = math.expression.node.ParenthesisNode; +var SymbolNode = math.expression.node.SymbolNode; + +describe('derivative', function() { + + it('should take the derivative of a constant', function() { + assert.deepEqual(math.eval('derivative(1, x)'), new ConstantNode(0)); + assert.deepEqual(math.eval('derivative(10000000, x)'), new ConstantNode(0)); + }); + + it('should take the derivative of a SymbolNodes', function() { + assert.deepEqual(math.eval('derivative(x, x)'), new ConstantNode(1)); + assert.deepEqual(math.eval('derivative(C, x)'), new ConstantNode(0)); + }); + + it('should maintain parenthesis of ParenthesisNodes', function() { + assert.deepEqual(math.eval('derivative((1), x)'), math.parse('(0)')); + assert.deepEqual(math.eval('derivative((x), x)'), math.parse('(1)')); + }); + + it('should take the derivative of FunctionAssignmentNodes', function() { + assert.deepEqual(math.derivative(math.parse('f(x) = 5x + x + 2'), new SymbolNode('x')), + math.parse('5*1 + 1 + 0')); + assert.deepEqual(math.derivative(math.parse('f(x) = 5 + 2'), new SymbolNode('x')), + new ConstantNode(0)); + assert.deepEqual(math.derivative(math.parse('f(y) = 5y + 2'), new SymbolNode('x')), + new ConstantNode(0)); + }); + + it('should take the derivative of a OperatorNodes with ConstantNodes', function() { + assert.deepEqual(math.eval('derivative(1 + 2, x)'), new ConstantNode(0)); + assert.deepEqual(math.eval('derivative(-100^2 + 3*3/2 - 12, x)'), new ConstantNode(0)); + }); + + it('should take the derivative of a OperatorNodes with SymbolNodes', function() { + // d/dx(-4x) = -4*1 = -4 + assert.deepEqual(math.eval('derivative(-4x, x)'), math.parse('-4*1')); + // d/dx(+4x) = +4*1 = +4 + assert.deepEqual(math.eval('derivative(+4x, x)'), math.parse('+4*1')); + + + // Linearity of differentiation + // With '+': d/dx(5x + x + 2) = 5*1 + 1 + 0 = 6 + assert.deepEqual(math.eval('derivative(5x + x + 2, x)'), math.parse('5*1 + 1 + 0')); + // With '-': d/dx(5x - x - 2) = 5*1 - 1 - 0 = 4 + assert.deepEqual(math.eval('derivative(5x - x - 2, x)'), math.parse('5*1 - 1 - 0')); + + + // d/dx(2*(x + x)) = 2*(1 + 1) + assert.deepEqual(math.eval('derivative(2(x + x), x)'), math.parse('2*(1 + 1)')); + assert.deepEqual(math.eval('derivative((x + x)*2, x)'), math.parse('2*(1 + 1)')); + + + // Product Rule, d/dx(5x*3x) = 5*(3*1*x + x*3*1) = 30x + assert.deepEqual(math.eval('derivative(5x*3x, x)'), new OperatorNode('*', 'multiply', [ + new ConstantNode(5), + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new ConstantNode(1) + ]), + new SymbolNode('x') + ]), + new OperatorNode('*', 'multiply', [ + new OperatorNode('*', 'multiply', [ + new SymbolNode('x'), + new ConstantNode(3) + ]), + new ConstantNode(1) + ]) + ]) + ])); + + + // Basic division, d/dx(7x / 2) = 7 * 1 / 2 = 7 / 2 + assert.deepEqual(math.eval('derivative(7x / 2, x)'), new OperatorNode('*', 'multiply', [ + new ConstantNode(7), + new OperatorNode('/', 'divide', [ + new ConstantNode(1), + new ConstantNode(2) + ]) + ])); + // Reciprocal Rule, d/dx(5 / (3x)) = -5 * (3 * 1) / (3 * x) ^ 2 = -5 / 3x^2 + assert.deepEqual(math.eval('derivative(5 / (3x), x)'), new OperatorNode('*', 'multiply', [ + new OperatorNode('-', 'unaryMinus', [new ConstantNode(5)]), + new OperatorNode('/', 'divide', [ + new ParenthesisNode( + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new ConstantNode(1) + ]) + ), + new OperatorNode('^', 'pow', [ + new ParenthesisNode( + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new SymbolNode('x') + ]) + ), + new ConstantNode(2) + ]) + ]) + ])); + // Quotient rule, d/dx((2x) / (3x + 2)) = ((2*1)(3x + 2) - (2x)(3*1 + 0)) / (3x + 2)^2 = 4 / (3x + 2)^2 + assert.deepEqual(math.eval('derivative((2x) / (3x + 2), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('-', 'subtract', [ + new OperatorNode('*', 'multiply', [ + new ParenthesisNode( + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]) + ), + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ) + ]), + new OperatorNode('*', 'multiply', [ + new ParenthesisNode( + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]) + ), + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new ConstantNode(1) + ]), + new ConstantNode(0) + ]) + ) + ]) + ]), + new OperatorNode('^', 'pow', [ + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ), + new ConstantNode(2) + ]) + ])); + + + // Secret constant; 0^f(x) = 1 (in JS), 1^f(x) = 1 + assert.deepEqual(math.eval('derivative(0^(2^x + x^3 + 2), x)'), new ConstantNode(0)); + assert.deepEqual(math.eval('derivative(1^(2^x + x^3 + 2), x)'), new ConstantNode(0)); + // d/dx(10^(2x + 2)) = 10^(2x + 2)*ln(10)*(2*1 + 0) + assert.deepEqual(math.eval('derivative(10^(2x + 2), x)'), new OperatorNode('*', 'multiply', [ + new OperatorNode('^', 'pow', [ + new ConstantNode(10), + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ) + ]), + new OperatorNode('*', 'multiply', [ + new FunctionNode('log', [new ConstantNode(10)]), + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + new ConstantNode(0) + ]) + ) + ]) + ])); + // Secret constant, f(x)^0 = 1 -> d/dx(f(x)^0) = 1 + assert.deepEqual(math.eval('derivative((x^x^x^x)^0, x)'), new ConstantNode(0)); + // Ignore powers of 1, d/dx((x + 2)^1) -> d/dx(x+2) = (1 + 0) = 1 + assert.deepEqual(math.eval('derivative((x+2)^1, x)'), math.parse('(1 + 0)')); + // Elementary Power Rule, d/dx(2x^2) = 2*2*1*x^(2-1) = 4x + assert.deepEqual(math.eval('derivative(2x^2, x)'), new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new OperatorNode('*', 'multiply', [ + new ConstantNode(1), + new OperatorNode('^', 'pow', [ + new SymbolNode('x'), + new OperatorNode('-', 'subtract', [ + new ConstantNode(2), + new ConstantNode(1) + ]) + ]) + ]) + ]) + ])); + // Elementary Power Rule, d/dx(2x^-2) = 2*-2*1*x^(-2-1) = -4x^-3 + assert.deepEqual(math.eval('derivative(2x^-2, x)'), new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new OperatorNode('*', 'multiply', [ + new OperatorNode('-', 'unaryMinus', [new ConstantNode(2)]), + new OperatorNode('*', 'multiply', [ + new ConstantNode(1), + new OperatorNode('^', 'pow', [ + new SymbolNode('x'), + new OperatorNode('-', 'subtract', [ + new OperatorNode('-', 'unaryMinus', [new ConstantNode(2)]), + new ConstantNode(1) + ]) + ]) + ]) + ]) + ])); + + // Functional Power Rule, d/dx((x^3 + x)^(5x + 2)) = (x^3 + x)^(5x + 2) * [(((3*1*x)^(3-1)+1) * ((5x + 2) / (x^3 + x))) + (5*1 + 0)log((x^3 + x))] + // = (x^3 + x)^(5x + 2) * [((3x^2 + 1)*(5x + 2) / (x^3 + x)) + 5log(x^3 + x)] + assert.deepEqual(math.eval('derivative((x^3 + x)^(5x + 2), x)'), new OperatorNode('*', 'multiply', [ + new OperatorNode('^', 'pow', [ + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + new SymbolNode('x'), + new ConstantNode(3) + ]), + new SymbolNode('x') + ]) + ), + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(5), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ) + ]), + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new OperatorNode('*', 'multiply', [ + new ConstantNode(1), + new OperatorNode('^', 'pow', [ + new SymbolNode('x'), + new OperatorNode('-', 'subtract', [ + new ConstantNode(3), + new ConstantNode(1) + ]) + ]) + ]) + ]), + new ConstantNode(1) + ]) + ), + new OperatorNode('/', 'divide', [ + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(5), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ), + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + new SymbolNode('x'), + new ConstantNode(3) + ]), + new SymbolNode('x') + ]) + ) + ]) + ]), + new OperatorNode('*', 'multiply', [ + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(5), + new ConstantNode(1) + ]), + new ConstantNode(0) + ]) + ), + new FunctionNode('log', [ + new ParenthesisNode( + new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + new SymbolNode('x'), + new ConstantNode(3) + ]), + new SymbolNode('x') + ]) + ) + ]) + ]) + ]) + ])); + }); + + it('should properly take the derivative of mathematical functions', function() { + assert.deepEqual(math.eval('derivative(sqrt((6x)), x)'), new OperatorNode('/', 'divide', [ + math.parse('(6 * 1)'), + math.parse('2 * sqrt((6 * x))') + ])); + assert.deepEqual(math.eval('derivative(nthRoot((6x)), x)'), new OperatorNode('/', 'divide', [ + math.parse('(6 * 1)'), + math.parse('2 * sqrt((6 * x))') + ])); + assert.deepEqual(math.eval('derivative(nthRoot(6x, 3), x)'), new OperatorNode('*', 'multiply', [ + math.parse('1/3'), + new OperatorNode('*', 'multiply', [ + math.parse('6*1'), + new OperatorNode('^', 'pow', [ + math.parse('6x'), + math.parse('1/3 - 1') + ]) + ]) + ])); + // (6 * x) ^ (1 / (2 * x)) * (((6*1)*((1/(2x))/(6x))) + ((((0*2x)-(1*(2*1)))/(2x)^2)*log(6x))) + assert.deepEqual(math.eval('derivative(nthRoot(6x, 2x), x)'), new OperatorNode('*', 'multiply', [ + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(6), + new SymbolNode('x') + ]), + new OperatorNode('/', 'divide', [ + new ConstantNode(1), + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]) + ]) + ]), + new OperatorNode('+', 'add', [ + new OperatorNode('*', 'multiply', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(6), + new ConstantNode(1) + ]), + new OperatorNode('/', 'divide', [ + new OperatorNode('/', 'divide', [ + new ConstantNode(1), + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]) + ]), + new OperatorNode('*', 'multiply', [ + new ConstantNode(6), + new SymbolNode('x') + ]) + ]) + ]), + new OperatorNode('*', 'multiply', [ + new OperatorNode('/', 'divide', [ + new OperatorNode('-', 'subtract', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(0), + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]) + ]), + new OperatorNode('*', 'multiply', [ + new ConstantNode(1), + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]) + ]) + ]), + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ]), + new FunctionNode('log', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(6), + new SymbolNode('x') + ]) + ]) + ]) + ]) + ])); + assert.deepEqual(math.eval('derivative(log((6x)), x)'), math.parse('(6*1)/(6*x)')); + assert.deepEqual(math.eval('derivative(log10((6x)), x)'), new OperatorNode('/', 'divide', [ + math.parse('(6*1)'), + math.parse('(6x)log(10)') + ])); + assert.deepEqual(math.eval('derivative(log((6x), 10), x)'), new OperatorNode('/', 'divide', [ + math.parse('(6*1)'), + math.parse('(6x)log(10)') + ])); + // d/dx(log(2x, 3x)) = ((2 * 1) / (2 * x) * log(3 * x) - log(2 * x) * (3 * 1) / (3 * x)) / log(3 * x) ^ 2 = (log(3x) - log(2x)) / (xlog(3x)^2) + assert.deepEqual(math.eval('derivative(log(2x, 3x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('-', 'subtract', [ + new OperatorNode('*', 'multiply', [ + new OperatorNode('/', 'divide', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]) + ]), + new FunctionNode('log', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new SymbolNode('x') + ]) + ]) + ]), + new OperatorNode('*', 'multiply', [ + new FunctionNode('log', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]) + ]), + new OperatorNode('/', 'divide', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new ConstantNode(1) + ]), + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new SymbolNode('x') + ]) + ]) + ]) + ]), + new OperatorNode('^', 'pow', [ + new FunctionNode('log', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(3), + new SymbolNode('x') + ]) + ]), + new ConstantNode(2) + ]) + ])); + + assert.deepEqual(math.eval('derivative(sin(2x), x)'), math.parse('2*1*cos(2x)')); + assert.deepEqual(math.eval('derivative(cos(2x), x)'), math.parse('2*1*-sin(2x)')); + assert.deepEqual(math.eval('derivative(tan(2x), x)'), math.parse('2*1*sec(2x)^2')); + assert.deepEqual(math.eval('derivative(sec(2x), x)'), new OperatorNode('*', 'multiply', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + math.parse('sec(2x)*tan(2x)') + ])); + assert.deepEqual(math.eval('derivative(csc(2x), x)'), new OperatorNode('*', 'multiply', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + ]), + math.parse('csc(2x)cot(2x)') + ])); + assert.deepEqual(math.eval('derivative(cot(2x), x)'), new OperatorNode('*', 'multiply', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + ]), + math.parse('csc(2x)^2') + ])); + assert.deepEqual(math.eval('derivative(asin(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new ConstantNode(1), + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ]) + ]) + ])); + assert.deepEqual(math.eval('derivative(acos(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]) + ]), + new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new ConstantNode(1), + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ]) + ]) + ])); + assert.deepEqual(math.eval('derivative(atan(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]), + new ConstantNode(1) + ]) + ])); + assert.deepEqual(math.eval('derivative(asec(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + new OperatorNode('*', 'multiply', [ + new FunctionNode('abs', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]) + ]), + new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]), + new ConstantNode(1) + ]) + ]) + ]) + ])); + assert.deepEqual(math.eval('derivative(acsc(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]) + ]), + new OperatorNode('*', 'multiply', [ + new FunctionNode('abs', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]) + ]), + new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]), + new ConstantNode(1) + ]) + ]) + ]) + ])); + assert.deepEqual(math.eval('derivative(acot(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]) + ]), + new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]), + new ConstantNode(1) + ]) + ])); + assert.deepEqual(math.eval('derivative(sinh(2x), x)'), math.parse('2*1*cosh(2x)')); + assert.deepEqual(math.eval('derivative(cosh(2x), x)'), math.parse('2*1*sinh(2x)')); + assert.deepEqual(math.eval('derivative(tanh(2x), x)'), math.parse('2*1*sech(2x)^2')); + assert.deepEqual(math.eval('derivative(sech(2x), x)'), new OperatorNode('*', 'multiply', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + ]), + math.parse('sech(2x)tanh(2x)') + ])); + assert.deepEqual(math.eval('derivative(csch(2x), x)'), new OperatorNode('*', 'multiply', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + ]), + math.parse('csch(2x)coth(2x)') + ])); + assert.deepEqual(math.eval('derivative(coth(2x), x)'), new OperatorNode('*', 'multiply', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + ]), + math.parse('csch(2x)^2') + ])); + assert.deepEqual(math.eval('derivative(asinh(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + new FunctionNode('sqrt', [ + new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]), + new ConstantNode(1) + ]) + ]) + ])); + assert.deepEqual(math.eval('derivative(acosh(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]), + new ConstantNode(1) + ]) + ]) + ])); + assert.deepEqual(math.eval('derivative(atanh(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]), + new OperatorNode('-', 'subtract', [ + new ConstantNode(1), + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ]) + ])); + assert.deepEqual(math.eval('derivative(asech(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]) + ]), + new OperatorNode('*', 'multiply', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new FunctionNode('sqrt', [ + new OperatorNode('-', 'subtract', [ + new ConstantNode(1), + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ]) + ]) + ]) + ])); + assert.deepEqual(math.eval('derivative(acsch(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]) + ]), + new OperatorNode('*', 'multiply', [ + new FunctionNode('abs', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]) + ]), + new FunctionNode('sqrt', [ + new OperatorNode('+', 'add', [ + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]), + new ConstantNode(1) + ]) + ]) + ]) + ])); + assert.deepEqual(math.eval('derivative(acoth(2x), x)'), new OperatorNode('/', 'divide', [ + new OperatorNode('-', 'unaryMinus', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new ConstantNode(1) + ]) + ]), + new OperatorNode('-', 'subtract', [ + new ConstantNode(1), + new OperatorNode('^', 'pow', [ + new OperatorNode('*', 'multiply', [ + new ConstantNode(2), + new SymbolNode('x') + ]), + new ConstantNode(2) + ]) + ]) + ])); + }); + + it('should throw error if expressions contain unsupported operators or functions', function() { + assert.throws(function () { math.eval('derivative(x << 2, x)'); }, /Error: Operator "<<" not supported by derivative/); + assert.throws(function () { math.eval('derivative(subset(x), x)'); }, /Error: Function "subset" not supported by derivative/); + }); + + it('should have controlled behavior on arguments errors', function() { + assert.throws(function() { + math.eval('derivative(sqrt(), x)'); + }, /TypeError: Too few arguments in function sqrt \(expected: number or Complex or BigNumber or Array or Matrix or Fraction or string or boolean or null, index: 0\)/); + assert.throws(function() { + math.eval('derivative(sqrt(12, 2x), x)'); + }, /TypeError: Too many arguments in function sqrt \(expected: 1, actual: 2\)/); + }); + + it('should throw error for incorrect argument types', function() { + assert.throws(function () { + math.eval('derivative(42, 42)'); + }, /TypeError: Unexpected type of argument in function derivative \(expected: SymbolNode, actual: ConstantNode, index: 1\)/); + + assert.throws(function () { + math.eval('derivative([1, 2; 3, 4], x)'); + }, /TypeError: Unexpected type of argument in function constTag \(expected: OperatorNode or ConstantNode or SymbolNode or ParenthesisNode or FunctionNode or FunctionAssignmentNode, actual: ArrayNode, index: 1\)/); + + assert.throws(function () { + math.eval('derivative(x + [1, 2; 3, 4], x)'); + }, /TypeError: Unexpected type of argument in function constTag \(expected: OperatorNode or ConstantNode or SymbolNode or ParenthesisNode or FunctionNode or FunctionAssignmentNode, actual: ArrayNode, index: 1\)/); + }); + + it('should throw error if incorrect number of arguments', function() { + assert.throws(function () { + math.eval('derivative(x + 2)'); + }, /TypeError: Too few arguments in function derivative \(expected: SymbolNode, index: 1\)/); + + assert.throws(function () { + math.eval('derivative(x + 2, x, "stuff", true, 42)'); + }, /TypeError: Too many arguments in function derivative \(expected: 2, actual: 5\)/); + }); + +});