From 0feda84294a28e61691a3757cac819f98f34a9cb Mon Sep 17 00:00:00 2001 From: Paul K Date: Tue, 19 Nov 2024 21:16:30 +0200 Subject: [PATCH 1/6] Make derivative 5-8 times faster --- src/core/create.js | 2 + src/core/function/typed.js | 2 + src/entry/typeChecks.js | 1 + src/function/algebra/derivative.js | 71 +++++++++++++++++------------- src/utils/is.js | 4 ++ test/benchmark/derivative.js | 30 +++++++++++++ test/benchmark/index.js | 1 + test/node-tests/doc.test.js | 1 + test/typescript-tests/testTypes.ts | 1 + test/unit-tests/core/typed.test.js | 6 +++ types/index.d.ts | 2 + 11 files changed, 90 insertions(+), 31 deletions(-) create mode 100644 test/benchmark/derivative.js diff --git a/src/core/create.js b/src/core/create.js index bb3a7bbe04..fee36fce71 100644 --- a/src/core/create.js +++ b/src/core/create.js @@ -34,6 +34,7 @@ import { isObject, isObjectNode, isObjectWrappingMap, + isSet, isOperatorNode, isParenthesisNode, isPartitionedMap, @@ -132,6 +133,7 @@ export function create (factories, config) { isMap, isPartitionedMap, isObjectWrappingMap, + isSet, isNull, isUndefined, diff --git a/src/core/function/typed.js b/src/core/function/typed.js index 966a1a4f78..e4a337c193 100644 --- a/src/core/function/typed.js +++ b/src/core/function/typed.js @@ -75,6 +75,7 @@ import { isRegExp, isRelationalNode, isResultSet, + isSet, isSparseMatrix, isString, isSymbolNode, @@ -164,6 +165,7 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi { name: 'SymbolNode', test: isSymbolNode }, { name: 'Map', test: isMap }, + { name: 'Set', test: isSet }, { name: 'Object', test: isObject } // order 'Object' last, it matches on other classes too ]) diff --git a/src/entry/typeChecks.js b/src/entry/typeChecks.js index caa9629f7e..b94dc787dc 100644 --- a/src/entry/typeChecks.js +++ b/src/entry/typeChecks.js @@ -32,6 +32,7 @@ export { isMap, isPartitionedMap, isObjectWrappingMap, + isSet, isObjectNode, isOperatorNode, isParenthesisNode, diff --git a/src/function/algebra/derivative.js b/src/function/algebra/derivative.js index aad2f323b3..3b3c521131 100644 --- a/src/function/algebra/derivative.js +++ b/src/function/algebra/derivative.js @@ -71,7 +71,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ * @return {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} The derivative of `expr` */ function plainDerivative (expr, variable, options = { simplify: true }) { - const constNodes = {} + const constNodes = new Set() constTag(constNodes, expr, variable.name) const res = _derivative(expr, constNodes) return options.simplify ? simplify(res) : res @@ -96,7 +96,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ 'Node, SymbolNode, ConstantNode': function (expr, variable, {order}) { let res = expr for (let i = 0; i < order; i++) { - let constNodes = {} + let constNodes = new Set() constTag(constNodes, expr, variable.name) res = _derivative(res, constNodes) } @@ -151,41 +151,41 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ * 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 {Set} 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 */ // TODO: can we rewrite constTag into a pure function? const constTag = typed('constTag', { - 'Object, ConstantNode, string': function (constNodes, node) { - constNodes[node] = true + 'Set, ConstantNode, string': function (constNodes, node) { + constNodes.add(node) return true }, - 'Object, SymbolNode, string': function (constNodes, node, varName) { + 'Set, SymbolNode, string': function (constNodes, node, varName) { // Treat other variables like constants. For reasoning, see: // https://en.wikipedia.org/wiki/Partial_derivative if (node.name !== varName) { - constNodes[node] = true + constNodes.add(node) return true } return false }, - 'Object, ParenthesisNode, string': function (constNodes, node, varName) { + 'Set, ParenthesisNode, string': function (constNodes, node, varName) { return constTag(constNodes, node.content, varName) }, - 'Object, FunctionAssignmentNode, string': function (constNodes, node, varName) { + 'Set, FunctionAssignmentNode, string': function (constNodes, node, varName) { if (!node.params.includes(varName)) { - constNodes[node] = true + constNodes.add(node) return true } return constTag(constNodes, node.expr, varName) }, - 'Object, FunctionNode | OperatorNode, string': function (constNodes, node, varName) { + 'Set, FunctionNode | OperatorNode, string': function (constNodes, node, varName) { if (node.args.length > 0) { let isConst = constTag(constNodes, node.args[0], varName) for (let i = 1; i < node.args.length; ++i) { @@ -193,7 +193,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ } if (isConst) { - constNodes[node] = true + constNodes.add(node) return true } } @@ -209,30 +209,30 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ * @return {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} The derivative of `expr` */ const _derivative = typed('_derivative', { - 'ConstantNode, Object': function (node) { + 'ConstantNode, Set': function (node) { return createConstantNode(0) }, - 'SymbolNode, Object': function (node, constNodes) { - if (constNodes[node] !== undefined) { + 'SymbolNode, Set': function (node, constNodes) { + if (constNodes.has(node)) { return createConstantNode(0) } return createConstantNode(1) }, - 'ParenthesisNode, Object': function (node, constNodes) { + 'ParenthesisNode, Set': function (node, constNodes) { return new ParenthesisNode(_derivative(node.content, constNodes)) }, - 'FunctionAssignmentNode, Object': function (node, constNodes) { - if (constNodes[node] !== undefined) { + 'FunctionAssignmentNode, Set': function (node, constNodes) { + if (constNodes.has(node)) { return createConstantNode(0) } return _derivative(node.expr, constNodes) }, - 'FunctionNode, Object': function (node, constNodes) { - if (constNodes[node] !== undefined) { + 'FunctionNode, Set': function (node, constNodes) { + if (constNodes.has(node)) { return createConstantNode(0) } @@ -275,7 +275,11 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ ]) // Is a variable? - constNodes[arg1] = constNodes[node.args[1]] + if (constNodes.has(node.args[1])) { + constNodes.add(arg1) + } else { + constNodes.delete(arg1) + } return _derivative(new OperatorNode('^', 'pow', [arg0, arg1]), constNodes) } @@ -289,7 +293,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ funcDerivative = arg0.clone() div = true } else if ((node.args.length === 1 && arg1) || - (node.args.length === 2 && constNodes[node.args[1]] !== undefined)) { + (node.args.length === 2 && constNodes.has(node.args[1]))) { // d/dx(log(x, c)) = 1 / (x*ln(c)) funcDerivative = new OperatorNode('*', 'multiply', [ arg0.clone(), @@ -306,7 +310,12 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ break case 'pow': if (node.args.length === 2) { - constNodes[arg1] = constNodes[node.args[1]] + if (constNodes.has(node.args[1])) { + constNodes.add(arg1) + } else { + constNodes.delete(arg1) + } + // Pass to pow operator node parser return _derivative(new OperatorNode('^', 'pow', [arg0, node.args[1]]), constNodes) } @@ -592,8 +601,8 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ return new OperatorNode(op, func, [chainDerivative, funcDerivative]) }, - 'OperatorNode, Object': function (node, constNodes) { - if (constNodes[node] !== undefined) { + 'OperatorNode, Set': function (node, constNodes) { + if (constNodes.has(node)) { return createConstantNode(0) } @@ -624,12 +633,12 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ if (node.op === '*') { // d/dx(c*f(x)) = c*f'(x) const constantTerms = node.args.filter(function (arg) { - return constNodes[arg] !== undefined + return constNodes.has(arg) }) if (constantTerms.length > 0) { const nonConstantTerms = node.args.filter(function (arg) { - return constNodes[arg] === undefined + return !constNodes.has(arg) }) const nonConstantNode = nonConstantTerms.length === 1 @@ -656,12 +665,12 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ const arg1 = node.args[1] // d/dx(f(x) / c) = f'(x) / c - if (constNodes[arg1] !== undefined) { + if (constNodes.has(arg1)) { return new OperatorNode('/', 'divide', [_derivative(arg0, constNodes), arg1]) } // Reciprocal Rule, d/dx(c / f(x)) = -c(f'(x)/f(x)^2) - if (constNodes[arg0] !== undefined) { + if (constNodes.has(arg0)) { return new OperatorNode('*', 'multiply', [ new OperatorNode('-', 'unaryMinus', [arg0]), new OperatorNode('/', 'divide', [ @@ -685,7 +694,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ const arg0 = node.args[0] const arg1 = node.args[1] - if (constNodes[arg0] !== undefined) { + if (constNodes.has(arg0)) { // If is secretly constant; 0^f(x) = 1 (in JS), 1^f(x) = 1 if (isConstantNode(arg0) && (isZero(arg0.value) || equal(arg0.value, 1))) { return createConstantNode(0) @@ -701,7 +710,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ ]) } - if (constNodes[arg1] !== undefined) { + if (constNodes.has(arg1)) { if (isConstantNode(arg1)) { // If is secretly constant; f(x)^0 = 1 -> d/dx(1) = 0 if (isZero(arg1.value)) { diff --git a/src/utils/is.js b/src/utils/is.js index 5bc6db84ce..0054831867 100644 --- a/src/utils/is.js +++ b/src/utils/is.js @@ -253,6 +253,10 @@ export function isChain (x) { return (x && x.constructor.prototype.isChain === true) || false } +export function isSet (x) { + return x instanceof Set +} + export function typeOf (x) { const t = typeof x diff --git a/test/benchmark/derivative.js b/test/benchmark/derivative.js new file mode 100644 index 0000000000..a915b34c7a --- /dev/null +++ b/test/benchmark/derivative.js @@ -0,0 +1,30 @@ +// test performance of derivative + +import Benchmark from 'benchmark' +import { derivative, parse } from '../../lib/esm/index.js' + +let expr = parse('0') +for (let i = 1; i <= 5; i++) { + for (let j = 1; j <= 5; j++) { + expr = parse(`${expr} + sin(${i + j} * x ^ ${i} + ${i * j} * y ^ ${j})`) + } +} + +const results = [] + +const suite = new Benchmark.Suite() +suite + .add('ddf', function () { + const res = derivative(derivative(expr, parse('x'), { simplify: false }), parse('x'), { simplify: false }) + results.push(res) + }) + .add('df ', function () { + const res = derivative(expr, parse('x'), { simplify: false }) + results.push(res) + }) + .on('cycle', function (event) { + console.log(String(event.target)) + }) + .on('complete', function () { + }) + .run() diff --git a/test/benchmark/index.js b/test/benchmark/index.js index 7ada9f253d..221e4722a9 100644 --- a/test/benchmark/index.js +++ b/test/benchmark/index.js @@ -1,6 +1,7 @@ // run all benchmarks import './expression_parser.js' import './algebra.js' +import './derivative.js' import './roots.js' import './matrix_operations.js' import './prime.js' diff --git a/test/node-tests/doc.test.js b/test/node-tests/doc.test.js index dd73eb8534..e80b021d09 100644 --- a/test/node-tests/doc.test.js +++ b/test/node-tests/doc.test.js @@ -184,6 +184,7 @@ const knownUndocumented = new Set([ 'isMap', 'isPartitionedMap', 'isObjectWrappingMap', + 'isSet', 'isNull', 'isUndefined', 'isAccessorNode', diff --git a/test/typescript-tests/testTypes.ts b/test/typescript-tests/testTypes.ts index 735ac1148b..68939254f0 100644 --- a/test/typescript-tests/testTypes.ts +++ b/test/typescript-tests/testTypes.ts @@ -2316,6 +2316,7 @@ Factory Test math.isMap, math.isPartitionedMap, math.isObjectWrappingMap, + math.isSet, math.isNull, math.isUndefined, math.isAccessorNode, diff --git a/test/unit-tests/core/typed.test.js b/test/unit-tests/core/typed.test.js index 39908b3c3b..16a771c78c 100644 --- a/test/unit-tests/core/typed.test.js +++ b/test/unit-tests/core/typed.test.js @@ -209,6 +209,12 @@ describe('typed', function () { assert.strictEqual(math.isObjectWrappingMap(new PartitionedMap(new Map(), new Map(), new Set(['x']))), false) }) + it('should test whether a value is a Set', function () { + assert.strictEqual(math.isSet({}), false) + assert.strictEqual(math.isSet(new Set()), true) + assert.strictEqual(math.isSet(new Set(['x', 'y'])), true) + }) + it('should test whether a value is undefined', function () { assert.strictEqual(math.isUndefined(undefined), true) assert.strictEqual(math.isUndefined(math.matrix()), false) diff --git a/types/index.d.ts b/types/index.d.ts index 16f4d16192..edab4d5b18 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -3439,6 +3439,8 @@ export interface MathJsInstance extends MathJsFactory { x: unknown ): x is ObjectWrappingMap + isSet(x: unknown): x is Set + isNull(x: unknown): x is null isUndefined(x: unknown): x is undefined From 2eef5fcb5465590cdb13c8a2e183f803397af9ab Mon Sep 17 00:00:00 2001 From: Paul K Date: Thu, 21 Nov 2024 19:25:38 +0200 Subject: [PATCH 2/6] Refactor: replace constNodes with a memoized function --- AUTHORS | 1 + src/core/create.js | 2 - src/core/function/typed.js | 2 - src/entry/typeChecks.js | 1 - src/function/algebra/derivative.js | 157 ++++++++---------- src/utils/is.js | 4 - test/node-tests/doc.test.js | 1 - test/typescript-tests/testTypes.ts | 1 - test/unit-tests/core/typed.test.js | 6 - .../function/algebra/derivative.test.js | 6 +- types/index.d.ts | 2 - 11 files changed, 76 insertions(+), 107 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6adbc67a81..9b0148b442 100644 --- a/AUTHORS +++ b/AUTHORS @@ -256,5 +256,6 @@ Jmar L. Pineda <63294460+Galm007@users.noreply.github.com> gauravchawhan <117282267+gauravchawhan@users.noreply.github.com> Neeraj Kumawat <42885320+nkumawat34@users.noreply.github.com> Emmanuel Ferdman +Paul K # Generated by tools/update-authors.js diff --git a/src/core/create.js b/src/core/create.js index fee36fce71..bb3a7bbe04 100644 --- a/src/core/create.js +++ b/src/core/create.js @@ -34,7 +34,6 @@ import { isObject, isObjectNode, isObjectWrappingMap, - isSet, isOperatorNode, isParenthesisNode, isPartitionedMap, @@ -133,7 +132,6 @@ export function create (factories, config) { isMap, isPartitionedMap, isObjectWrappingMap, - isSet, isNull, isUndefined, diff --git a/src/core/function/typed.js b/src/core/function/typed.js index e4a337c193..966a1a4f78 100644 --- a/src/core/function/typed.js +++ b/src/core/function/typed.js @@ -75,7 +75,6 @@ import { isRegExp, isRelationalNode, isResultSet, - isSet, isSparseMatrix, isString, isSymbolNode, @@ -165,7 +164,6 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi { name: 'SymbolNode', test: isSymbolNode }, { name: 'Map', test: isMap }, - { name: 'Set', test: isSet }, { name: 'Object', test: isObject } // order 'Object' last, it matches on other classes too ]) diff --git a/src/entry/typeChecks.js b/src/entry/typeChecks.js index b94dc787dc..caa9629f7e 100644 --- a/src/entry/typeChecks.js +++ b/src/entry/typeChecks.js @@ -32,7 +32,6 @@ export { isMap, isPartitionedMap, isObjectWrappingMap, - isSet, isObjectNode, isOperatorNode, isParenthesisNode, diff --git a/src/function/algebra/derivative.js b/src/function/algebra/derivative.js index 3b3c521131..d044d7ab17 100644 --- a/src/function/algebra/derivative.js +++ b/src/function/algebra/derivative.js @@ -71,9 +71,19 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ * @return {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} The derivative of `expr` */ function plainDerivative (expr, variable, options = { simplify: true }) { - const constNodes = new Set() - constTag(constNodes, expr, variable.name) - const res = _derivative(expr, constNodes) + const cache = new Map() + const variableName = variable.name + function isConstCached (node) { + const cached = cache.get(node) + if (cached !== undefined) { + return cached + } + const res = _isConst(isConstCached, node, variableName) + cache.set(node, res) + return res + } + + const res = _derivative(expr, isConstCached) return options.simplify ? simplify(res) : res } @@ -96,9 +106,8 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ 'Node, SymbolNode, ConstantNode': function (expr, variable, {order}) { let res = expr for (let i = 0; i < order; i++) { - let constNodes = new Set() - constTag(constNodes, expr, variable.name) - res = _derivative(res, constNodes) + + res = _derivative(res, isConst) } return res } @@ -143,60 +152,51 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ }) /** - * 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: + * Checks if a node is constants (e.g. 2 + 2). + * Accepts (usually memoized) version of self as the first parameter for recursive calls. + * 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 {Set} constNodes Holds the nodes that are constant + * @param {function} isConst Function that tells whether sub-expression is a constant * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node * @param {string} varName Variable that we are differentiating * @return {boolean} if node is constant */ - // TODO: can we rewrite constTag into a pure function? - const constTag = typed('constTag', { - 'Set, ConstantNode, string': function (constNodes, node) { - constNodes.add(node) + const _isConst = typed('_isConst', { + 'function, ConstantNode, string': function () { return true }, - 'Set, SymbolNode, string': function (constNodes, node, varName) { + 'function, SymbolNode, string': function (isConst, node, varName) { // Treat other variables like constants. For reasoning, see: // https://en.wikipedia.org/wiki/Partial_derivative - if (node.name !== varName) { - constNodes.add(node) - return true - } - return false + return node.name !== varName }, - 'Set, ParenthesisNode, string': function (constNodes, node, varName) { - return constTag(constNodes, node.content, varName) + 'function, ParenthesisNode, string': function (isConst, node, varName) { + return isConst(node.content, varName) }, - 'Set, FunctionAssignmentNode, string': function (constNodes, node, varName) { + 'function, FunctionAssignmentNode, string': function (isConst, node, varName) { if (!node.params.includes(varName)) { - constNodes.add(node) return true } - return constTag(constNodes, node.expr, varName) + return isConst(node.expr, varName) }, - 'Set, FunctionNode | OperatorNode, string': function (constNodes, node, varName) { + 'function, FunctionNode | OperatorNode, string': function (isConst, node, varName) { if (node.args.length > 0) { - let isConst = constTag(constNodes, node.args[0], varName) - for (let i = 1; i < node.args.length; ++i) { - isConst = constTag(constNodes, node.args[i], varName) && isConst + let allConst = true + for (let i = 0; i < node.args.length && allConst; ++i) { + allConst = isConst(node.args[i], varName) && allConst } - if (isConst) { - constNodes.add(node) - return true - } + return allConst } + // TODO: add a comment explaining why false (and why is this reachable) return false } }) @@ -205,34 +205,34 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ * Applies differentiation rules. * * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node - * @param {Object} constNodes Holds the nodes that are constant + * @param {function} isConst Function that tells if a node is constant * @return {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} The derivative of `expr` */ const _derivative = typed('_derivative', { - 'ConstantNode, Set': function (node) { + 'ConstantNode, function': function () { return createConstantNode(0) }, - 'SymbolNode, Set': function (node, constNodes) { - if (constNodes.has(node)) { + 'SymbolNode, function': function (node, isConst) { + if (isConst(node)) { return createConstantNode(0) } return createConstantNode(1) }, - 'ParenthesisNode, Set': function (node, constNodes) { - return new ParenthesisNode(_derivative(node.content, constNodes)) + 'ParenthesisNode, function': function (node, isConst) { + return new ParenthesisNode(_derivative(node.content, isConst)) }, - 'FunctionAssignmentNode, Set': function (node, constNodes) { - if (constNodes.has(node)) { + 'FunctionAssignmentNode, function': function (node, isConst) { + if (isConst(node)) { return createConstantNode(0) } - return _derivative(node.expr, constNodes) + return _derivative(node.expr, isConst) }, - 'FunctionNode, Set': function (node, constNodes) { - if (constNodes.has(node)) { + 'FunctionNode, function': function (node, isConst) { + if (isConst(node)) { return createConstantNode(0) } @@ -274,14 +274,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ node.args[1] ]) - // Is a variable? - if (constNodes.has(node.args[1])) { - constNodes.add(arg1) - } else { - constNodes.delete(arg1) - } - - return _derivative(new OperatorNode('^', 'pow', [arg0, arg1]), constNodes) + return _derivative(new OperatorNode('^', 'pow', [arg0, arg1]), isConst) } break case 'log10': @@ -293,7 +286,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ funcDerivative = arg0.clone() div = true } else if ((node.args.length === 1 && arg1) || - (node.args.length === 2 && constNodes.has(node.args[1]))) { + (node.args.length === 2 && isConst(node.args[1]))) { // d/dx(log(x, c)) = 1 / (x*ln(c)) funcDerivative = new OperatorNode('*', 'multiply', [ arg0.clone(), @@ -305,19 +298,13 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ return _derivative(new OperatorNode('/', 'divide', [ new FunctionNode('log', [arg0]), new FunctionNode('log', [node.args[1]]) - ]), constNodes) + ]), isConst) } break case 'pow': if (node.args.length === 2) { - if (constNodes.has(node.args[1])) { - constNodes.add(arg1) - } else { - constNodes.delete(arg1) - } - // Pass to pow operator node parser - return _derivative(new OperatorNode('^', 'pow', [arg0, node.args[1]]), constNodes) + return _derivative(new OperatorNode('^', 'pow', [arg0, node.args[1]]), isConst) } break case 'exp': @@ -594,22 +581,22 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ /* Apply chain rule to all functions: F(x) = f(g(x)) F'(x) = g'(x)*f'(g(x)) */ - let chainDerivative = _derivative(arg0, constNodes) + let chainDerivative = _derivative(arg0, isConst) if (negative) { chainDerivative = new OperatorNode('-', 'unaryMinus', [chainDerivative]) } return new OperatorNode(op, func, [chainDerivative, funcDerivative]) }, - 'OperatorNode, Set': function (node, constNodes) { - if (constNodes.has(node)) { + 'OperatorNode, function': function (node, isConst) { + if (isConst(node)) { return createConstantNode(0) } if (node.op === '+') { // d/dx(sum(f(x)) = sum(f'(x)) return new OperatorNode(node.op, node.fn, node.args.map(function (arg) { - return _derivative(arg, constNodes) + return _derivative(arg, isConst) })) } @@ -617,15 +604,15 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ // d/dx(+/-f(x)) = +/-f'(x) if (node.isUnary()) { return new OperatorNode(node.op, node.fn, [ - _derivative(node.args[0], constNodes) + _derivative(node.args[0], isConst) ]) } // Linearity of differentiation, d/dx(f(x) +/- g(x)) = f'(x) +/- g'(x) if (node.isBinary()) { return new OperatorNode(node.op, node.fn, [ - _derivative(node.args[0], constNodes), - _derivative(node.args[1], constNodes) + _derivative(node.args[0], isConst), + _derivative(node.args[1], isConst) ]) } } @@ -633,19 +620,19 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ if (node.op === '*') { // d/dx(c*f(x)) = c*f'(x) const constantTerms = node.args.filter(function (arg) { - return constNodes.has(arg) + return isConst(arg) }) if (constantTerms.length > 0) { const nonConstantTerms = node.args.filter(function (arg) { - return !constNodes.has(arg) + return !isConst(arg) }) const nonConstantNode = nonConstantTerms.length === 1 ? nonConstantTerms[0] : new OperatorNode('*', 'multiply', nonConstantTerms) - const newArgs = constantTerms.concat(_derivative(nonConstantNode, constNodes)) + const newArgs = constantTerms.concat(_derivative(nonConstantNode, isConst)) return new OperatorNode('*', 'multiply', newArgs) } @@ -654,7 +641,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ return new OperatorNode('+', 'add', node.args.map(function (argOuter) { return new OperatorNode('*', 'multiply', node.args.map(function (argInner) { return (argInner === argOuter) - ? _derivative(argInner, constNodes) + ? _derivative(argInner, isConst) : argInner.clone() })) })) @@ -665,16 +652,16 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ const arg1 = node.args[1] // d/dx(f(x) / c) = f'(x) / c - if (constNodes.has(arg1)) { - return new OperatorNode('/', 'divide', [_derivative(arg0, constNodes), arg1]) + if (isConst(arg1)) { + return new OperatorNode('/', 'divide', [_derivative(arg0, isConst), arg1]) } // Reciprocal Rule, d/dx(c / f(x)) = -c(f'(x)/f(x)^2) - if (constNodes.has(arg0)) { + if (isConst(arg0)) { return new OperatorNode('*', 'multiply', [ new OperatorNode('-', 'unaryMinus', [arg0]), new OperatorNode('/', 'divide', [ - _derivative(arg1, constNodes), + _derivative(arg1, isConst), new OperatorNode('^', 'pow', [arg1.clone(), createConstantNode(2)]) ]) ]) @@ -683,8 +670,8 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ // 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(arg0, constNodes), arg1.clone()]), - new OperatorNode('*', 'multiply', [arg0.clone(), _derivative(arg1, constNodes)]) + new OperatorNode('*', 'multiply', [_derivative(arg0, isConst), arg1.clone()]), + new OperatorNode('*', 'multiply', [arg0.clone(), _derivative(arg1, isConst)]) ]), new OperatorNode('^', 'pow', [arg1.clone(), createConstantNode(2)]) ]) @@ -694,7 +681,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ const arg0 = node.args[0] const arg1 = node.args[1] - if (constNodes.has(arg0)) { + if (isConst(arg0)) { // If is secretly constant; 0^f(x) = 1 (in JS), 1^f(x) = 1 if (isConstantNode(arg0) && (isZero(arg0.value) || equal(arg0.value, 1))) { return createConstantNode(0) @@ -705,12 +692,12 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ node, new OperatorNode('*', 'multiply', [ new FunctionNode('log', [arg0.clone()]), - _derivative(arg1.clone(), constNodes) + _derivative(arg1.clone(), isConst) ]) ]) } - if (constNodes.has(arg1)) { + if (isConst(arg1)) { if (isConstantNode(arg1)) { // If is secretly constant; f(x)^0 = 1 -> d/dx(1) = 0 if (isZero(arg1.value)) { @@ -718,7 +705,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ } // Ignore exponent; f(x)^1 = f(x) if (equal(arg1.value, 1)) { - return _derivative(arg0, constNodes) + return _derivative(arg0, isConst) } } @@ -734,7 +721,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ return new OperatorNode('*', 'multiply', [ arg1.clone(), new OperatorNode('*', 'multiply', [ - _derivative(arg0, constNodes), + _derivative(arg0, isConst), powMinusOne ]) ]) @@ -745,11 +732,11 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ new OperatorNode('^', 'pow', [arg0.clone(), arg1.clone()]), new OperatorNode('+', 'add', [ new OperatorNode('*', 'multiply', [ - _derivative(arg0, constNodes), + _derivative(arg0, isConst), new OperatorNode('/', 'divide', [arg1.clone(), arg0.clone()]) ]), new OperatorNode('*', 'multiply', [ - _derivative(arg1, constNodes), + _derivative(arg1, isConst), new FunctionNode('log', [arg0.clone()]) ]) ]) diff --git a/src/utils/is.js b/src/utils/is.js index 0054831867..5bc6db84ce 100644 --- a/src/utils/is.js +++ b/src/utils/is.js @@ -253,10 +253,6 @@ export function isChain (x) { return (x && x.constructor.prototype.isChain === true) || false } -export function isSet (x) { - return x instanceof Set -} - export function typeOf (x) { const t = typeof x diff --git a/test/node-tests/doc.test.js b/test/node-tests/doc.test.js index e80b021d09..dd73eb8534 100644 --- a/test/node-tests/doc.test.js +++ b/test/node-tests/doc.test.js @@ -184,7 +184,6 @@ const knownUndocumented = new Set([ 'isMap', 'isPartitionedMap', 'isObjectWrappingMap', - 'isSet', 'isNull', 'isUndefined', 'isAccessorNode', diff --git a/test/typescript-tests/testTypes.ts b/test/typescript-tests/testTypes.ts index 68939254f0..735ac1148b 100644 --- a/test/typescript-tests/testTypes.ts +++ b/test/typescript-tests/testTypes.ts @@ -2316,7 +2316,6 @@ Factory Test math.isMap, math.isPartitionedMap, math.isObjectWrappingMap, - math.isSet, math.isNull, math.isUndefined, math.isAccessorNode, diff --git a/test/unit-tests/core/typed.test.js b/test/unit-tests/core/typed.test.js index 16a771c78c..39908b3c3b 100644 --- a/test/unit-tests/core/typed.test.js +++ b/test/unit-tests/core/typed.test.js @@ -209,12 +209,6 @@ describe('typed', function () { assert.strictEqual(math.isObjectWrappingMap(new PartitionedMap(new Map(), new Map(), new Set(['x']))), false) }) - it('should test whether a value is a Set', function () { - assert.strictEqual(math.isSet({}), false) - assert.strictEqual(math.isSet(new Set()), true) - assert.strictEqual(math.isSet(new Set(['x', 'y'])), true) - }) - it('should test whether a value is undefined', function () { assert.strictEqual(math.isUndefined(undefined), true) assert.strictEqual(math.isUndefined(math.matrix()), false) diff --git a/test/unit-tests/function/algebra/derivative.test.js b/test/unit-tests/function/algebra/derivative.test.js index 0f798abef7..b06fb515cc 100644 --- a/test/unit-tests/function/algebra/derivative.test.js +++ b/test/unit-tests/function/algebra/derivative.test.js @@ -126,7 +126,7 @@ describe('derivative', function () { compareString(derivativeWithoutSimplify('nthRoot(6x)', 'x'), '6 * 1 / (2 * sqrt(6 x))') compareString(derivativeWithoutSimplify('nthRoot(6x, 3)', 'x'), '1 / 3 * 6 * 1 * (6 x) ^ (1 / 3 - 1)') - compareString(derivativeWithoutSimplify('nthRoot((6x), (2x))', 'x'), '(6 x) ^ (1 / (2 x)) * ((6 * 1) * 1 / (2 x) / (6 x) + (0 * (2 x) - 1 * (2 * 1)) / (2 x) ^ 2 * log((6 x)))') + compareString(derivativeWithoutSimplify('nthRoot((6x), (2x))', 'x'), '(6 x) ^ (1 / (2 x)) * ((6 * 1) * 1 / (2 x) / (6 x) + -1 * (2 * 1) / (2 x) ^ 2 * log((6 x)))') compareString(derivativeWithoutSimplify('log((6*x))', 'x'), '(6 * 1) / (6 * x)') compareString(derivativeWithoutSimplify('log10((6x))', 'x'), '(6 * 1) / ((6 x) * log(10))') compareString(derivativeWithoutSimplify('log((6x), 10)', 'x'), '(6 * 1) / ((6 x) * log(10))') @@ -270,11 +270,11 @@ describe('derivative', function () { assert.throws(function () { derivative('[1, 2; 3, 4]', 'x') - }, /TypeError: Unexpected type of argument in function constTag \(expected: ConstantNode or FunctionNode or FunctionAssignmentNode or OperatorNode or ParenthesisNode or SymbolNode, actual:.*ArrayNode.*, index: 1\)/) + }, /TypeError: Unexpected type of argument in function _derivative \(expected: ConstantNode or FunctionNode or FunctionAssignmentNode or OperatorNode or ParenthesisNode or SymbolNode, actual:.*ArrayNode.*, index: 0\)/) assert.throws(function () { derivative('x + [1, 2; 3, 4]', 'x') - }, /TypeError: Unexpected type of argument in function constTag \(expected: ConstantNode or FunctionNode or FunctionAssignmentNode or OperatorNode or ParenthesisNode or SymbolNode, actual:.*ArrayNode.*, index: 1\)/) + }, /TypeError: Unexpected type of argument in function _derivative \(expected: ConstantNode or FunctionNode or FunctionAssignmentNode or OperatorNode or ParenthesisNode or SymbolNode, actual:.*ArrayNode.*, index: 0\)/) }) it('should throw error if incorrect number of arguments', function () { diff --git a/types/index.d.ts b/types/index.d.ts index edab4d5b18..16f4d16192 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -3439,8 +3439,6 @@ export interface MathJsInstance extends MathJsFactory { x: unknown ): x is ObjectWrappingMap - isSet(x: unknown): x is Set - isNull(x: unknown): x is null isUndefined(x: unknown): x is undefined From 56d9b73d0db55245f223046a6be2f761f258b43d Mon Sep 17 00:00:00 2001 From: Paul K Date: Thu, 21 Nov 2024 20:23:18 +0200 Subject: [PATCH 3/6] Reduce memory pressure in benchmark/derivative --- test/benchmark/derivative.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/benchmark/derivative.js b/test/benchmark/derivative.js index a915b34c7a..6d8fed5f12 100644 --- a/test/benchmark/derivative.js +++ b/test/benchmark/derivative.js @@ -12,15 +12,17 @@ for (let i = 1; i <= 5; i++) { const results = [] +Benchmark.options.minSamples = 100 + const suite = new Benchmark.Suite() suite .add('ddf', function () { const res = derivative(derivative(expr, parse('x'), { simplify: false }), parse('x'), { simplify: false }) - results.push(res) + results.splice(0, 1, res) }) .add('df ', function () { const res = derivative(expr, parse('x'), { simplify: false }) - results.push(res) + results.splice(0,1, res) }) .on('cycle', function (event) { console.log(String(event.target)) From fc3bd6cd3d7b01960185a1d299b2f3a238872cce Mon Sep 17 00:00:00 2001 From: Paul K Date: Thu, 21 Nov 2024 20:37:18 +0200 Subject: [PATCH 4/6] eslint --- test/benchmark/derivative.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/benchmark/derivative.js b/test/benchmark/derivative.js index 6d8fed5f12..6a8a3726bd 100644 --- a/test/benchmark/derivative.js +++ b/test/benchmark/derivative.js @@ -22,7 +22,7 @@ suite }) .add('df ', function () { const res = derivative(expr, parse('x'), { simplify: false }) - results.splice(0,1, res) + results.splice(0, 1, res) }) .on('cycle', function (event) { console.log(String(event.target)) From 8615737b790b2739f4d3163b22f00164269d0a5b Mon Sep 17 00:00:00 2001 From: Paul Korzhyk Date: Wed, 27 Nov 2024 20:02:03 +0200 Subject: [PATCH 5/6] Remove TODO and allConst --- src/function/algebra/derivative.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/function/algebra/derivative.js b/src/function/algebra/derivative.js index d044d7ab17..cddc4923e2 100644 --- a/src/function/algebra/derivative.js +++ b/src/function/algebra/derivative.js @@ -189,14 +189,13 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ 'function, FunctionNode | OperatorNode, string': function (isConst, node, varName) { if (node.args.length > 0) { - let allConst = true - for (let i = 0; i < node.args.length && allConst; ++i) { - allConst = isConst(node.args[i], varName) && allConst + for (let i = 0; i < node.args.length; ++i) { + if (!isConst(node.args[i], varName)) { + return false + } } - - return allConst + return true } - // TODO: add a comment explaining why false (and why is this reachable) return false } }) From fbaf4e70fe63540ab92af52981d197ddf5b36411 Mon Sep 17 00:00:00 2001 From: Paul Korzhyk Date: Thu, 28 Nov 2024 13:43:53 +0200 Subject: [PATCH 6/6] isConst(FunctionNode|OperatorNode): replace every loop with `every` --- src/function/algebra/derivative.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/function/algebra/derivative.js b/src/function/algebra/derivative.js index cddc4923e2..119f3facd9 100644 --- a/src/function/algebra/derivative.js +++ b/src/function/algebra/derivative.js @@ -188,15 +188,7 @@ export const createDerivative = /* #__PURE__ */ factory(name, dependencies, ({ }, 'function, FunctionNode | OperatorNode, string': function (isConst, node, varName) { - if (node.args.length > 0) { - for (let i = 0; i < node.args.length; ++i) { - if (!isConst(node.args[i], varName)) { - return false - } - } - return true - } - return false + return node.args.every(arg => isConst(arg, varName)) } })