1 | /**
|
2 | * The expression parser of math.js has support for letting functions
|
3 | * parse and evaluate arguments themselves, instead of calling them with
|
4 | * evaluated arguments.
|
5 | *
|
6 | * By adding a property `raw` with value true to a function, the function
|
7 | * will be invoked with unevaluated arguments, allowing the function
|
8 | * to process the arguments in a customized way.
|
9 | */
|
10 | const math = require('../../index')
|
11 |
|
12 | /**
|
13 | * Calculate the numeric integration of a function
|
14 | * @param {Function} f
|
15 | * @param {number} start
|
16 | * @param {number} end
|
17 | * @param {number} [step=0.01]
|
18 | */
|
19 | function integrate (f, start, end, step) {
|
20 | let total = 0
|
21 | step = step || 0.01
|
22 | for (let x = start; x < end; x += step) {
|
23 | total += f(x + step / 2) * step
|
24 | }
|
25 | return total
|
26 | }
|
27 |
|
28 | /**
|
29 | * A transformation for the integrate function. This transformation will be
|
30 | * invoked when the function is used via the expression parser of math.js.
|
31 | *
|
32 | * Syntax:
|
33 | *
|
34 | * integrate(integrand, variable, start, end)
|
35 | * integrate(integrand, variable, start, end, step)
|
36 | *
|
37 | * Usage:
|
38 | *
|
39 | * math.eval('integrate(2*x, x, 0, 2)')
|
40 | * math.eval('integrate(2*x, x, 0, 2, 0.01)')
|
41 | *
|
42 | * @param {Array.<math.expression.node.Node>} args
|
43 | * Expects the following arguments: [f, x, start, end, step]
|
44 | * @param {Object} math
|
45 | * @param {Object} [scope]
|
46 | */
|
47 | integrate.transform = function (args, math, scope) {
|
48 | // determine the variable name
|
49 | if (!args[1].isSymbolNode) {
|
50 | throw new Error('Second argument must be a symbol')
|
51 | }
|
52 | const variable = args[1].name
|
53 |
|
54 | // evaluate start, end, and step
|
55 | const start = args[2].compile().eval(scope)
|
56 | const end = args[3].compile().eval(scope)
|
57 | const step = args[4] && args[4].compile().eval(scope) // step is optional
|
58 |
|
59 | // create a new scope, linked to the provided scope. We use this new scope
|
60 | // to apply the variable.
|
61 | const fnScope = Object.create(scope)
|
62 |
|
63 | // construct a function which evaluates the first parameter f after applying
|
64 | // a value for parameter x.
|
65 | const fnCode = args[0].compile()
|
66 | const f = function (x) {
|
67 | fnScope[variable] = x
|
68 | return fnCode.eval(fnScope)
|
69 | }
|
70 |
|
71 | // execute the integration
|
72 | return integrate(f, start, end, step)
|
73 | }
|
74 |
|
75 | // mark the transform function with a "rawArgs" property, so it will be called
|
76 | // with uncompiled, unevaluated arguments.
|
77 | integrate.transform.rawArgs = true
|
78 |
|
79 | // import the function into math.js. Raw functions must be imported in the
|
80 | // math namespace, they can't be used via `eval(scope)`.
|
81 | math.import({
|
82 | integrate: integrate
|
83 | })
|
84 |
|
85 | // use the function in JavaScript
|
86 | function f (x) {
|
87 | return math.pow(x, 0.5)
|
88 | }
|
89 | console.log(math.integrate(f, 0, 1)) // outputs 0.6667254718034714
|
90 |
|
91 | // use the function via the expression parser
|
92 | console.log(math.eval('integrate(x^0.5, x, 0, 1)')) // outputs 0.6667254718034714
|
93 |
|
94 | // use the function via the expression parser (2)
|
95 | let scope = {}
|
96 | math.eval('f(x) = 2 * x', scope)
|
97 | console.log(math.eval('integrate(f(x), x, 0, 2)', scope)) // outputs 4.000000000000003
|