1 | import { isConstantNode, typeOf } from '../../utils/is.js';
|
2 | import { factory } from '../../utils/factory.js';
|
3 | var name = 'derivative';
|
4 | var dependencies = ['typed', 'config', 'parse', 'simplify', 'equal', 'isZero', 'numeric', 'ConstantNode', 'FunctionNode', 'OperatorNode', 'ParenthesisNode', 'SymbolNode'];
|
5 | export var createDerivative = factory(name, dependencies, (_ref) => {
|
6 | var {
|
7 | typed,
|
8 | config,
|
9 | parse,
|
10 | simplify,
|
11 | equal,
|
12 | isZero,
|
13 | numeric,
|
14 | ConstantNode,
|
15 | FunctionNode,
|
16 | OperatorNode,
|
17 | ParenthesisNode,
|
18 | SymbolNode
|
19 | } = _ref;
|
20 |
|
21 | |
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | var derivative = typed('derivative', {
|
60 | 'Node, SymbolNode, Object': function NodeSymbolNodeObject(expr, variable, options) {
|
61 | var constNodes = {};
|
62 | constTag(constNodes, expr, variable.name);
|
63 |
|
64 | var res = _derivative(expr, constNodes);
|
65 |
|
66 | return options.simplify ? simplify(res) : res;
|
67 | },
|
68 | 'Node, SymbolNode': function NodeSymbolNode(expr, variable) {
|
69 | return this(expr, variable, {
|
70 | simplify: true
|
71 | });
|
72 | },
|
73 | 'string, SymbolNode': function stringSymbolNode(expr, variable) {
|
74 | return this(parse(expr), variable);
|
75 | },
|
76 | 'string, SymbolNode, Object': function stringSymbolNodeObject(expr, variable, options) {
|
77 | return this(parse(expr), variable, options);
|
78 | },
|
79 | 'string, string': function stringString(expr, variable) {
|
80 | return this(parse(expr), parse(variable));
|
81 | },
|
82 | 'string, string, Object': function stringStringObject(expr, variable, options) {
|
83 | return this(parse(expr), parse(variable), options);
|
84 | },
|
85 | 'Node, string': function NodeString(expr, variable) {
|
86 | return this(expr, parse(variable));
|
87 | },
|
88 | 'Node, string, Object': function NodeStringObject(expr, variable, options) {
|
89 | return this(expr, parse(variable), options);
|
90 | }
|
91 |
|
92 | |
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | });
|
105 | derivative._simplify = true;
|
106 |
|
107 | derivative.toTex = function (deriv) {
|
108 | return _derivTex.apply(null, deriv.args);
|
109 | };
|
110 |
|
111 |
|
112 |
|
113 | var _derivTex = typed('_derivTex', {
|
114 | 'Node, SymbolNode': function NodeSymbolNode(expr, x) {
|
115 | if (isConstantNode(expr) && typeOf(expr.value) === 'string') {
|
116 | return _derivTex(parse(expr.value).toString(), x.toString(), 1);
|
117 | } else {
|
118 | return _derivTex(expr.toString(), x.toString(), 1);
|
119 | }
|
120 | },
|
121 | 'Node, ConstantNode': function NodeConstantNode(expr, x) {
|
122 | if (typeOf(x.value) === 'string') {
|
123 | return _derivTex(expr, parse(x.value));
|
124 | } else {
|
125 | throw new Error("The second parameter to 'derivative' is a non-string constant");
|
126 | }
|
127 | },
|
128 | 'Node, SymbolNode, ConstantNode': function NodeSymbolNodeConstantNode(expr, x, order) {
|
129 | return _derivTex(expr.toString(), x.name, order.value);
|
130 | },
|
131 | 'string, string, number': function stringStringNumber(expr, x, order) {
|
132 | var d;
|
133 |
|
134 | if (order === 1) {
|
135 | d = '{d\\over d' + x + '}';
|
136 | } else {
|
137 | d = '{d^{' + order + '}\\over d' + x + '^{' + order + '}}';
|
138 | }
|
139 |
|
140 | return d + "\\left[".concat(expr, "\\right]");
|
141 | }
|
142 | });
|
143 | |
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | var constTag = typed('constTag', {
|
161 | 'Object, ConstantNode, string': function ObjectConstantNodeString(constNodes, node) {
|
162 | constNodes[node] = true;
|
163 | return true;
|
164 | },
|
165 | 'Object, SymbolNode, string': function ObjectSymbolNodeString(constNodes, node, varName) {
|
166 |
|
167 |
|
168 | if (node.name !== varName) {
|
169 | constNodes[node] = true;
|
170 | return true;
|
171 | }
|
172 |
|
173 | return false;
|
174 | },
|
175 | 'Object, ParenthesisNode, string': function ObjectParenthesisNodeString(constNodes, node, varName) {
|
176 | return constTag(constNodes, node.content, varName);
|
177 | },
|
178 | 'Object, FunctionAssignmentNode, string': function ObjectFunctionAssignmentNodeString(constNodes, node, varName) {
|
179 | if (node.params.indexOf(varName) === -1) {
|
180 | constNodes[node] = true;
|
181 | return true;
|
182 | }
|
183 |
|
184 | return constTag(constNodes, node.expr, varName);
|
185 | },
|
186 | 'Object, FunctionNode | OperatorNode, string': function ObjectFunctionNodeOperatorNodeString(constNodes, node, varName) {
|
187 | if (node.args.length > 0) {
|
188 | var isConst = constTag(constNodes, node.args[0], varName);
|
189 |
|
190 | for (var i = 1; i < node.args.length; ++i) {
|
191 | isConst = constTag(constNodes, node.args[i], varName) && isConst;
|
192 | }
|
193 |
|
194 | if (isConst) {
|
195 | constNodes[node] = true;
|
196 | return true;
|
197 | }
|
198 | }
|
199 |
|
200 | return false;
|
201 | }
|
202 | });
|
203 | |
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | var _derivative = typed('_derivative', {
|
212 | 'ConstantNode, Object': function ConstantNodeObject(node) {
|
213 | return createConstantNode(0);
|
214 | },
|
215 | 'SymbolNode, Object': function SymbolNodeObject(node, constNodes) {
|
216 | if (constNodes[node] !== undefined) {
|
217 | return createConstantNode(0);
|
218 | }
|
219 |
|
220 | return createConstantNode(1);
|
221 | },
|
222 | 'ParenthesisNode, Object': function ParenthesisNodeObject(node, constNodes) {
|
223 | return new ParenthesisNode(_derivative(node.content, constNodes));
|
224 | },
|
225 | 'FunctionAssignmentNode, Object': function FunctionAssignmentNodeObject(node, constNodes) {
|
226 | if (constNodes[node] !== undefined) {
|
227 | return createConstantNode(0);
|
228 | }
|
229 |
|
230 | return _derivative(node.expr, constNodes);
|
231 | },
|
232 | 'FunctionNode, Object': function FunctionNodeObject(node, constNodes) {
|
233 | if (node.args.length !== 1) {
|
234 | funcArgsCheck(node);
|
235 | }
|
236 |
|
237 | if (constNodes[node] !== undefined) {
|
238 | return createConstantNode(0);
|
239 | }
|
240 |
|
241 | var arg0 = node.args[0];
|
242 | var arg1;
|
243 | var div = false;
|
244 |
|
245 | var negative = false;
|
246 |
|
247 | var funcDerivative;
|
248 |
|
249 | switch (node.name) {
|
250 | case 'cbrt':
|
251 |
|
252 | div = true;
|
253 | funcDerivative = new OperatorNode('*', 'multiply', [createConstantNode(3), new OperatorNode('^', 'pow', [arg0, new OperatorNode('/', 'divide', [createConstantNode(2), createConstantNode(3)])])]);
|
254 | break;
|
255 |
|
256 | case 'sqrt':
|
257 | case 'nthRoot':
|
258 |
|
259 | if (node.args.length === 1) {
|
260 | div = true;
|
261 | funcDerivative = new OperatorNode('*', 'multiply', [createConstantNode(2), new FunctionNode('sqrt', [arg0])]);
|
262 | } else if (node.args.length === 2) {
|
263 |
|
264 | arg1 = new OperatorNode('/', 'divide', [createConstantNode(1), node.args[1]]);
|
265 |
|
266 | constNodes[arg1] = constNodes[node.args[1]];
|
267 | return _derivative(new OperatorNode('^', 'pow', [arg0, arg1]), constNodes);
|
268 | }
|
269 |
|
270 | break;
|
271 |
|
272 | case 'log10':
|
273 | arg1 = createConstantNode(10);
|
274 |
|
275 |
|
276 |
|
277 | case 'log':
|
278 | if (!arg1 && node.args.length === 1) {
|
279 |
|
280 | funcDerivative = arg0.clone();
|
281 | div = true;
|
282 | } else if (node.args.length === 1 && arg1 || node.args.length === 2 && constNodes[node.args[1]] !== undefined) {
|
283 |
|
284 | funcDerivative = new OperatorNode('*', 'multiply', [arg0.clone(), new FunctionNode('log', [arg1 || node.args[1]])]);
|
285 | div = true;
|
286 | } else if (node.args.length === 2) {
|
287 |
|
288 | return _derivative(new OperatorNode('/', 'divide', [new FunctionNode('log', [arg0]), new FunctionNode('log', [node.args[1]])]), constNodes);
|
289 | }
|
290 |
|
291 | break;
|
292 |
|
293 | case 'pow':
|
294 | constNodes[arg1] = constNodes[node.args[1]];
|
295 |
|
296 | return _derivative(new OperatorNode('^', 'pow', [arg0, node.args[1]]), constNodes);
|
297 |
|
298 | case 'exp':
|
299 |
|
300 | funcDerivative = new FunctionNode('exp', [arg0.clone()]);
|
301 | break;
|
302 |
|
303 | case 'sin':
|
304 |
|
305 | funcDerivative = new FunctionNode('cos', [arg0.clone()]);
|
306 | break;
|
307 |
|
308 | case 'cos':
|
309 |
|
310 | funcDerivative = new OperatorNode('-', 'unaryMinus', [new FunctionNode('sin', [arg0.clone()])]);
|
311 | break;
|
312 |
|
313 | case 'tan':
|
314 |
|
315 | funcDerivative = new OperatorNode('^', 'pow', [new FunctionNode('sec', [arg0.clone()]), createConstantNode(2)]);
|
316 | break;
|
317 |
|
318 | case 'sec':
|
319 |
|
320 | funcDerivative = new OperatorNode('*', 'multiply', [node, new FunctionNode('tan', [arg0.clone()])]);
|
321 | break;
|
322 |
|
323 | case 'csc':
|
324 |
|
325 | negative = true;
|
326 | funcDerivative = new OperatorNode('*', 'multiply', [node, new FunctionNode('cot', [arg0.clone()])]);
|
327 | break;
|
328 |
|
329 | case 'cot':
|
330 |
|
331 | negative = true;
|
332 | funcDerivative = new OperatorNode('^', 'pow', [new FunctionNode('csc', [arg0.clone()]), createConstantNode(2)]);
|
333 | break;
|
334 |
|
335 | case 'asin':
|
336 |
|
337 | div = true;
|
338 | funcDerivative = new FunctionNode('sqrt', [new OperatorNode('-', 'subtract', [createConstantNode(1), new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)])])]);
|
339 | break;
|
340 |
|
341 | case 'acos':
|
342 |
|
343 | div = true;
|
344 | negative = true;
|
345 | funcDerivative = new FunctionNode('sqrt', [new OperatorNode('-', 'subtract', [createConstantNode(1), new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)])])]);
|
346 | break;
|
347 |
|
348 | case 'atan':
|
349 |
|
350 | div = true;
|
351 | funcDerivative = new OperatorNode('+', 'add', [new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)]), createConstantNode(1)]);
|
352 | break;
|
353 |
|
354 | case 'asec':
|
355 |
|
356 | div = true;
|
357 | funcDerivative = new OperatorNode('*', 'multiply', [new FunctionNode('abs', [arg0.clone()]), new FunctionNode('sqrt', [new OperatorNode('-', 'subtract', [new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)]), createConstantNode(1)])])]);
|
358 | break;
|
359 |
|
360 | case 'acsc':
|
361 |
|
362 | div = true;
|
363 | negative = true;
|
364 | funcDerivative = new OperatorNode('*', 'multiply', [new FunctionNode('abs', [arg0.clone()]), new FunctionNode('sqrt', [new OperatorNode('-', 'subtract', [new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)]), createConstantNode(1)])])]);
|
365 | break;
|
366 |
|
367 | case 'acot':
|
368 |
|
369 | div = true;
|
370 | negative = true;
|
371 | funcDerivative = new OperatorNode('+', 'add', [new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)]), createConstantNode(1)]);
|
372 | break;
|
373 |
|
374 | case 'sinh':
|
375 |
|
376 | funcDerivative = new FunctionNode('cosh', [arg0.clone()]);
|
377 | break;
|
378 |
|
379 | case 'cosh':
|
380 |
|
381 | funcDerivative = new FunctionNode('sinh', [arg0.clone()]);
|
382 | break;
|
383 |
|
384 | case 'tanh':
|
385 |
|
386 | funcDerivative = new OperatorNode('^', 'pow', [new FunctionNode('sech', [arg0.clone()]), createConstantNode(2)]);
|
387 | break;
|
388 |
|
389 | case 'sech':
|
390 |
|
391 | negative = true;
|
392 | funcDerivative = new OperatorNode('*', 'multiply', [node, new FunctionNode('tanh', [arg0.clone()])]);
|
393 | break;
|
394 |
|
395 | case 'csch':
|
396 |
|
397 | negative = true;
|
398 | funcDerivative = new OperatorNode('*', 'multiply', [node, new FunctionNode('coth', [arg0.clone()])]);
|
399 | break;
|
400 |
|
401 | case 'coth':
|
402 |
|
403 | negative = true;
|
404 | funcDerivative = new OperatorNode('^', 'pow', [new FunctionNode('csch', [arg0.clone()]), createConstantNode(2)]);
|
405 | break;
|
406 |
|
407 | case 'asinh':
|
408 |
|
409 | div = true;
|
410 | funcDerivative = new FunctionNode('sqrt', [new OperatorNode('+', 'add', [new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)]), createConstantNode(1)])]);
|
411 | break;
|
412 |
|
413 | case 'acosh':
|
414 |
|
415 | div = true;
|
416 | funcDerivative = new FunctionNode('sqrt', [new OperatorNode('-', 'subtract', [new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)]), createConstantNode(1)])]);
|
417 | break;
|
418 |
|
419 | case 'atanh':
|
420 |
|
421 | div = true;
|
422 | funcDerivative = new OperatorNode('-', 'subtract', [createConstantNode(1), new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)])]);
|
423 | break;
|
424 |
|
425 | case 'asech':
|
426 |
|
427 | div = true;
|
428 | negative = true;
|
429 | funcDerivative = new OperatorNode('*', 'multiply', [arg0.clone(), new FunctionNode('sqrt', [new OperatorNode('-', 'subtract', [createConstantNode(1), new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)])])])]);
|
430 | break;
|
431 |
|
432 | case 'acsch':
|
433 |
|
434 | div = true;
|
435 | negative = true;
|
436 | funcDerivative = new OperatorNode('*', 'multiply', [new FunctionNode('abs', [arg0.clone()]), new FunctionNode('sqrt', [new OperatorNode('+', 'add', [new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)]), createConstantNode(1)])])]);
|
437 | break;
|
438 |
|
439 | case 'acoth':
|
440 |
|
441 | div = true;
|
442 | negative = true;
|
443 | funcDerivative = new OperatorNode('-', 'subtract', [createConstantNode(1), new OperatorNode('^', 'pow', [arg0.clone(), createConstantNode(2)])]);
|
444 | break;
|
445 |
|
446 | case 'abs':
|
447 |
|
448 | funcDerivative = new OperatorNode('/', 'divide', [new FunctionNode(new SymbolNode('abs'), [arg0.clone()]), arg0.clone()]);
|
449 | break;
|
450 |
|
451 | case 'gamma':
|
452 |
|
453 | default:
|
454 | throw new Error('Function "' + node.name + '" is not supported by derivative, or a wrong number of arguments is passed');
|
455 | }
|
456 |
|
457 | var op, func;
|
458 |
|
459 | if (div) {
|
460 | op = '/';
|
461 | func = 'divide';
|
462 | } else {
|
463 | op = '*';
|
464 | func = 'multiply';
|
465 | }
|
466 | |
467 |
|
468 |
|
469 |
|
470 |
|
471 | var chainDerivative = _derivative(arg0, constNodes);
|
472 |
|
473 | if (negative) {
|
474 | chainDerivative = new OperatorNode('-', 'unaryMinus', [chainDerivative]);
|
475 | }
|
476 |
|
477 | return new OperatorNode(op, func, [chainDerivative, funcDerivative]);
|
478 | },
|
479 | 'OperatorNode, Object': function OperatorNodeObject(node, constNodes) {
|
480 | if (constNodes[node] !== undefined) {
|
481 | return createConstantNode(0);
|
482 | }
|
483 |
|
484 | if (node.op === '+') {
|
485 |
|
486 | return new OperatorNode(node.op, node.fn, node.args.map(function (arg) {
|
487 | return _derivative(arg, constNodes);
|
488 | }));
|
489 | }
|
490 |
|
491 | if (node.op === '-') {
|
492 |
|
493 | if (node.isUnary()) {
|
494 | return new OperatorNode(node.op, node.fn, [_derivative(node.args[0], constNodes)]);
|
495 | }
|
496 |
|
497 |
|
498 | if (node.isBinary()) {
|
499 | return new OperatorNode(node.op, node.fn, [_derivative(node.args[0], constNodes), _derivative(node.args[1], constNodes)]);
|
500 | }
|
501 | }
|
502 |
|
503 | if (node.op === '*') {
|
504 |
|
505 | var constantTerms = node.args.filter(function (arg) {
|
506 | return constNodes[arg] !== undefined;
|
507 | });
|
508 |
|
509 | if (constantTerms.length > 0) {
|
510 | var nonConstantTerms = node.args.filter(function (arg) {
|
511 | return constNodes[arg] === undefined;
|
512 | });
|
513 | var nonConstantNode = nonConstantTerms.length === 1 ? nonConstantTerms[0] : new OperatorNode('*', 'multiply', nonConstantTerms);
|
514 | var newArgs = constantTerms.concat(_derivative(nonConstantNode, constNodes));
|
515 | return new OperatorNode('*', 'multiply', newArgs);
|
516 | }
|
517 |
|
518 |
|
519 | return new OperatorNode('+', 'add', node.args.map(function (argOuter) {
|
520 | return new OperatorNode('*', 'multiply', node.args.map(function (argInner) {
|
521 | return argInner === argOuter ? _derivative(argInner, constNodes) : argInner.clone();
|
522 | }));
|
523 | }));
|
524 | }
|
525 |
|
526 | if (node.op === '/' && node.isBinary()) {
|
527 | var arg0 = node.args[0];
|
528 | var arg1 = node.args[1];
|
529 |
|
530 | if (constNodes[arg1] !== undefined) {
|
531 | return new OperatorNode('/', 'divide', [_derivative(arg0, constNodes), arg1]);
|
532 | }
|
533 |
|
534 |
|
535 | if (constNodes[arg0] !== undefined) {
|
536 | return new OperatorNode('*', 'multiply', [new OperatorNode('-', 'unaryMinus', [arg0]), new OperatorNode('/', 'divide', [_derivative(arg1, constNodes), new OperatorNode('^', 'pow', [arg1.clone(), createConstantNode(2)])])]);
|
537 | }
|
538 |
|
539 |
|
540 | 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('^', 'pow', [arg1.clone(), createConstantNode(2)])]);
|
541 | }
|
542 |
|
543 | if (node.op === '^' && node.isBinary()) {
|
544 | var _arg = node.args[0];
|
545 | var _arg2 = node.args[1];
|
546 |
|
547 | if (constNodes[_arg] !== undefined) {
|
548 |
|
549 | if (isConstantNode(_arg) && (isZero(_arg.value) || equal(_arg.value, 1))) {
|
550 | return createConstantNode(0);
|
551 | }
|
552 |
|
553 |
|
554 | return new OperatorNode('*', 'multiply', [node, new OperatorNode('*', 'multiply', [new FunctionNode('log', [_arg.clone()]), _derivative(_arg2.clone(), constNodes)])]);
|
555 | }
|
556 |
|
557 | if (constNodes[_arg2] !== undefined) {
|
558 | if (isConstantNode(_arg2)) {
|
559 |
|
560 | if (isZero(_arg2.value)) {
|
561 | return createConstantNode(0);
|
562 | }
|
563 |
|
564 |
|
565 | if (equal(_arg2.value, 1)) {
|
566 | return _derivative(_arg, constNodes);
|
567 | }
|
568 | }
|
569 |
|
570 |
|
571 | var powMinusOne = new OperatorNode('^', 'pow', [_arg.clone(), new OperatorNode('-', 'subtract', [_arg2, createConstantNode(1)])]);
|
572 | return new OperatorNode('*', 'multiply', [_arg2.clone(), new OperatorNode('*', 'multiply', [_derivative(_arg, constNodes), powMinusOne])]);
|
573 | }
|
574 |
|
575 |
|
576 | return new OperatorNode('*', 'multiply', [new OperatorNode('^', 'pow', [_arg.clone(), _arg2.clone()]), new OperatorNode('+', 'add', [new OperatorNode('*', 'multiply', [_derivative(_arg, constNodes), new OperatorNode('/', 'divide', [_arg2.clone(), _arg.clone()])]), new OperatorNode('*', 'multiply', [_derivative(_arg2, constNodes), new FunctionNode('log', [_arg.clone()])])])]);
|
577 | }
|
578 |
|
579 | throw new Error('Operator "' + node.op + '" is not supported by derivative, or a wrong number of arguments is passed');
|
580 | }
|
581 | });
|
582 | |
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 |
|
589 |
|
590 | function funcArgsCheck(node) {
|
591 |
|
592 | if ((node.name === 'log' || node.name === 'nthRoot' || node.name === 'pow') && node.args.length === 2) {
|
593 | return;
|
594 | }
|
595 |
|
596 |
|
597 |
|
598 |
|
599 | for (var i = 0; i < node.args.length; ++i) {
|
600 | node.args[i] = createConstantNode(0);
|
601 | }
|
602 |
|
603 | node.compile().evaluate();
|
604 | throw new Error('Expected TypeError, but none found');
|
605 | }
|
606 | |
607 |
|
608 |
|
609 |
|
610 |
|
611 |
|
612 |
|
613 |
|
614 |
|
615 | function createConstantNode(value, valueType) {
|
616 | return new ConstantNode(numeric(value, valueType || config.number));
|
617 | }
|
618 |
|
619 | return derivative;
|
620 | }); |
\ | No newline at end of file |