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