├── .editorconfig
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── README.mdtex
├── benchmark
├── index.js
└── package.json
├── examples
├── comparison.js
├── mathjs.js
├── oscillatory.js
└── vector.js
├── images
├── 1-z-32ebeece91.png
├── a-2217a6870d.png
├── b-224c764dec.png
├── int_0011-frac1xcosleftfrac1xrightdx-c5d6a6f216.png
├── int_ab-fx-dx-49d001614b.png
├── oint-fracdzz-2pi-i-3243136d9d.png
└── z_0-0-227c53dd15.png
├── index.js
├── package.json
├── test
├── index.js
├── util
│ ├── array-equal.js
│ └── contour-integrand.js
└── vector.js
└── vector.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 |
3 | # Logs
4 | logs
5 | *.log
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # Compiled binary addons (http://nodejs.org/api/addons.html)
22 | build/Release
23 | build
24 |
25 | # Dependency directory
26 | # Deployed apps should consider commenting this line out:
27 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
28 | node_modules
29 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *.mdtex
2 | examples
3 | docs
4 | images
5 | benchmark
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5"
4 | - "5.1"
5 | - "4"
6 | - "4.2"
7 | - "4.1"
8 | - "4.0"
9 | - "0.12"
10 | - "0.11"
11 | - "0.10"
12 | - "iojs"
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Ricky Reusser
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # integrate-adaptive-simpson
2 |
3 | [](https://travis-ci.org/scijs/integrate-adaptive-simpson) [](http://badge.fury.io/js/integrate-adaptive-simpson) [](https://david-dm.org/scijs/integrate-adaptive-simpson) [](https://github.com/Flet/semistandard)
4 |
5 | > Compute a definite integral of one variable using [Simpson's Rule](https://en.wikipedia.org/wiki/Simpson%27s_rule) with adaptive quadrature
6 |
7 | ## Introduction
8 |
9 | This module computes the definite integral

using [Romberg Integration](https://en.wikipedia.org/wiki/Romberg%27s_method) based on [Simpson's Rule](https://en.wikipedia.org/wiki/Simpson%27s_rule). That is, it uses [Richardson Extrapolation](https://en.wikipedia.org/wiki/Richardson_extrapolation) to estimate the error and recursively subdivide intervals until the error tolerance is met. The code is adapted from the pseudocode in [Romberg Integration and Adaptive Quadrature](http://www.math.utk.edu/~ccollins/refs/Handouts/rich.pdf).
10 |
11 | ## Install
12 |
13 | ```bash
14 | $ npm install integrate-adaptive-simpson
15 | ```
16 |
17 | ## Example
18 |
19 | To compute the definite integral 
execute:
20 |
21 | ```javascript
22 | var integrate = require('integrate-adaptive-simpson');
23 |
24 | function f (x) {
25 | return Math.cos(1 / x) / x;
26 | }
27 |
28 | integrate(f, 0.01, 1, 1e-8);
29 | // => -0.3425527480294604
30 | ```
31 |
32 | To integrate a vector function, you may import the vectorized version. To compute a contour integral of, say,
about
, that is, 
33 |
34 | ```javascript
35 | var integrate = require('integrate-adaptive-simpson/vector');
36 |
37 | integrate(function (f, theta) {
38 | // z = unit circle:
39 | var c = Math.cos(theta);
40 | var s = Math.sin(theta);
41 |
42 | // dz:
43 | var dzr = -s;
44 | var dzi = c;
45 |
46 | // 1 / z at this point on the unit circle:
47 | var fr = c / (c * c + s * s);
48 | var fi = -s / (c * c + s * s);
49 |
50 | // Multiply f(z) * dz:
51 | f[0] = fr * dzr - fi * dzi;
52 | f[1] = fr * dzi + fi * dzr;
53 | }, 0, Math.PI * 2);
54 |
55 | // => [ 0, 6.283185307179586 ]
56 | ```
57 |
58 | ## API
59 |
60 | #### `require('integrate-adaptive-simpson')( f, a, b [, tol, maxdepth]] )`
61 |
62 | Compute the definite integral of scalar function f from a to b.
63 |
64 | **Arguments:**
65 | - `f`: The function to be integrated. A function of one variable that returns a value.
66 | - `a`: The lower limit of integration,
.
67 | - `b`: The upper limit of integration,
.
68 | - `tol`: The relative error required for an interval to be subdivided, based on Richardson extraplation. Default tolerance is `1e-8`. Be careful—the total accumulated error may be significantly less and result in more function evaluations than necessary.
69 | - `maxdepth`: The maximum recursion depth. Default depth is `20`. If reached, computation continues and a warning is output to the console.
70 |
71 | **Returns**: The computed value of the definite integral.
72 |
73 | #### `require('integrate-adaptive-simpson/vector')( f, a, b [, tol, maxdepth]] )`
74 |
75 | Compute the definite integral of vector function f from a to b.
76 |
77 | **Arguments:**
78 | - `f`: The function to be integrated. The first argument is an array of length `n` into which the output must be written. The second argument is the scalar value of the independent variable.
79 | - `a`: The lower limit of integration,
.
80 | - `b`: The upper limit of integration,
.
81 | - `tol`: The relative error required for an interval to be subdivided, based on Richardson extraplation. Default tolerance is `1e-8`.
82 | - `maxdepth`: The maximum recursion depth. Default depth is `20`. If reached, computation continues and a warning is output to the console.
83 |
84 | **Returns**: An `Array` representing The computed value of the definite integral.
85 |
86 | ## References
87 | Colins, C., [Romberg Integration and Adaptive Quadrature Course Notes](http://www.math.utk.edu/~ccollins/refs/Handouts/rich.pdf).
88 |
89 | ## License
90 |
91 | (c) 2015 Scijs Authors. MIT License.
92 |
--------------------------------------------------------------------------------
/README.mdtex:
--------------------------------------------------------------------------------
1 | # integrate-adaptive-simpson
2 |
3 | [](https://travis-ci.org/scijs/integrate-adaptive-simpson) [](http://badge.fury.io/js/integrate-adaptive-simpson) [](https://david-dm.org/scijs/integrate-adaptive-simpson) [](https://github.com/Flet/semistandard)
4 |
5 | > Compute a definite integral of one variable using [Simpson's Rule](https://en.wikipedia.org/wiki/Simpson%27s_rule) with adaptive quadrature
6 |
7 | ## Introduction
8 |
9 | This module computes the definite integral $$\int_a^b f(x) \, dx$$ using [Romberg Integration](https://en.wikipedia.org/wiki/Romberg%27s_method) based on [Simpson's Rule](https://en.wikipedia.org/wiki/Simpson%27s_rule). That is, it uses [Richardson Extrapolation](https://en.wikipedia.org/wiki/Richardson_extrapolation) to estimate the error and recursively subdivide intervals until the error tolerance is met. The code is adapted from the pseudocode in [Romberg Integration and Adaptive Quadrature](http://www.math.utk.edu/~ccollins/refs/Handouts/rich.pdf).
10 |
11 | ## Install
12 |
13 | ```bash
14 | $ npm install integrate-adaptive-simpson
15 | ```
16 |
17 | ## Example
18 |
19 | To compute the definite integral $$\int_{0.01}^{1} \frac{1}{x}\cos\left(\frac{1}{x}\right)\,dx,$$ execute:
20 |
21 | ```javascript
22 | var integrate = require('integrate-adaptive-simpson');
23 |
24 | function f (x) {
25 | return Math.cos(1 / x) / x);
26 | }
27 |
28 | intiegrate(f, 0.01, 1, 1e-8);
29 | // => -0.3425527480294604
30 | ```
31 |
32 | To integrate a vector function, you may import the vectorized version. To compute a contour integral of, say, $1 / z$ about $z_0 = 0$, that is, $$\oint \frac{dz}{z} = 2\pi i,$$
33 |
34 | ```javascript
35 | var integrate = require('integrate-adaptive-simpson/vector');
36 |
37 | integrate(function (f, theta) {
38 | // z = unit circle:
39 | var c = Math.cos(theta);
40 | var s = Math.sin(theta);
41 |
42 | // dz:
43 | var dzr = -s;
44 | var dzi = c;
45 |
46 | // 1 / z at this point on the unit circle:
47 | var fr = c / (c * c + s * s);
48 | var fi = -s / (c * c + s * s);
49 |
50 | // Multiply f(z) * dz:
51 | f[0] = fr * dzr - fi * dzi;
52 | f[1] = fr * dzi + fi * dzr;
53 | }, 0, Math.PI * 2);
54 |
55 | // => [ 0, 6.283185307179586 ]
56 | ```
57 |
58 | ## API
59 |
60 | #### `require('integrate-adaptive-simpson')( f, a, b [, tol, maxdepth]] )`
61 |
62 | Compute the definite integral of scalar function f from a to b.
63 |
64 | **Arguments:**
65 | - `f`: The function to be integrated. A function of one variable that returns a value.
66 | - `a`: The lower limit of integration, $a$.
67 | - `b`: The upper limit of integration, $b$.
68 | - `tol`: The relative error required for an interval to be subdivided, based on Richardson extraplation. Default tolerance is `1e-8`. Be careful—the total accumulated error may be significantly less and result in more function evaluations than necessary.
69 | - `maxdepth`: The maximum recursion depth. Default depth is `20`. If reached, computation continues and a warning is output to the console.
70 |
71 | **Returns**: The computed value of the definite integral.
72 |
73 | #### `require('integrate-adaptive-simpson/vector')( f, a, b [, tol, maxdepth]] )`
74 |
75 | Compute the definite integral of vector function f from a to b.
76 |
77 | **Arguments:**
78 | - `f`: The function to be integrated. The first argument is an array of length `n` into which the output must be written. The second argument is the scalar value of the independent variable.
79 | - `a`: The lower limit of integration, $a$.
80 | - `b`: The upper limit of integration, $b$.
81 | - `tol`: The relative error required for an interval to be subdivided, based on Richardson extraplation. Default tolerance is `1e-8`.
82 | - `maxdepth`: The maximum recursion depth. Default depth is `20`. If reached, computation continues and a warning is output to the console.
83 |
84 | **Returns**: An `Array` representing The computed value of the definite integral.
85 |
86 | ## References
87 | Colins, C., [Romberg Integration and Adaptive Quadrature Course Notes](http://www.math.utk.edu/~ccollins/refs/Handouts/rich.pdf).
88 |
89 | ## License
90 |
91 | (c) 2015 Scijs Authors. MIT License.
92 |
--------------------------------------------------------------------------------
/benchmark/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Benchmark = require('scijs-benchmark');
4 | var integrateVersion105 = require('integrate-adaptive-simpson');
5 | var integrateCurrent = require('../');
6 | var table = require('table').default;
7 |
8 | var bench = new Benchmark({
9 | maxDuration: 1000,
10 | maxSamples: 1000,
11 | getTime: process.hrtime,
12 | getTimeDiff: function (t1, t2) {
13 | return (t2[0] - t1[0]) * 1e3 + (t2[1] - t1[1]) * 1e-6;
14 | }
15 | });
16 |
17 | function f (x) {
18 | return Math.cos(1 / x) / x;
19 | }
20 |
21 | bench
22 | .measure('version 1.0.5', function () {
23 | integrateVersion105(f, 0.01, 1, 1e-10, 22);
24 | })
25 |
26 | .measure('current version', function () {
27 | integrateCurrent(f, 0.01, 1, 1e-10, 22);
28 | })
29 |
30 | .run(function (err, results) {
31 | if (err) {
32 | console.log('Error: ', err);
33 | }
34 | console.log(table(bench.toTable()));
35 | });
36 |
37 |
--------------------------------------------------------------------------------
/benchmark/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "benchmark",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "integrate-adaptive-simpson": "1.0.5",
13 | "scijs-benchmark": "git@github.com:rreusser/scijs-benchmark.git",
14 | "table": "^3.7.8"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/comparison.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var adsimp = require('../lib');
4 | var simpson = require('integrate-simpson');
5 | var value, evaluations;
6 |
7 | function f (x) {
8 | evaluations++;
9 | return Math.sin(x);
10 | }
11 |
12 | evaluations = 0;
13 | value = simpson(f, 0, Math.PI, 22);
14 | console.log('\nSimpson integration (' + evaluations + ' evaluations):');
15 | console.log('Absolute error:', Math.abs(value - 2));
16 |
17 | // Adaptive integration requires about half the number of evaluations
18 | // acheive the same absolute error:
19 | evaluations = 0;
20 | value = adsimp(f, 0, Math.PI, 1e-4);
21 | console.log('\nAdaptive Simpson integration (' + evaluations + ' evaluations):');
22 | console.log('Absolute error:', Math.abs(value - 2));
23 |
24 | // Adaptive integration with default tolerance:
25 | evaluations = 0;
26 | value = adsimp(f, 0, Math.PI);
27 | console.log('\nAdaptive simpson integration with defaults(' + evaluations + ' evaluations):');
28 | console.log('Absolute error:', Math.abs(value - 2));
29 |
--------------------------------------------------------------------------------
/examples/mathjs.js:
--------------------------------------------------------------------------------
1 | //
2 | // In order to run this example, you'll need to run:
3 | // $ npm install mathjs
4 | //
5 |
6 | var mathjs = require('mathjs');
7 | var integrate = require('../');
8 | var parser = mathjs.parser();
9 |
10 | parser.eval('f(x) = cos(1/x)/x');
11 | var value = integrate(parser.get('f'), 0.01, 1, 0.001);
12 |
13 | console.log('integral of cos(1/x)/x from 0.01 to 1:');
14 | console.log('expected approimxate value: -0.342553');
15 | console.log('value = ', value);
16 |
--------------------------------------------------------------------------------
/examples/oscillatory.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var romberg = require('../');
4 | var simpson = require('integrate-simpson');
5 | var value, evaluations;
6 |
7 | function f (x) {
8 | evaluations++;
9 | return Math.cos(1 / x) / x;
10 | }
11 |
12 | // Calculated via wolfram alpha, using a roundabout way of getting a few extra digits of (hopeful) precision:
13 | // http://www.wolframalpha.com/input/?i=%28integral+of+%28cos%281%2Fx%29%2Fx%29+from+x%3D0.01+to+x%3D1%29+%2B+0.342553+-+2.51956e-7
14 | var actual = -0.34255274804359265;
15 |
16 | evaluations = 0;
17 | value = simpson(f, 0.01, 1, 29118);
18 | console.log('\nSimpson integration (' + evaluations + ' evaluations):');
19 | console.log('Absolute error:', Math.abs(value - actual));
20 |
21 | // Adaptive integration requires about half the number of evaluations
22 | // acheive the same absolute error:
23 | evaluations = 0;
24 | value = romberg(f, 0.01, 1, 3.1e-6);
25 | console.log('\nAdaptive Simpson integration (' + evaluations + ' evaluations):');
26 | console.log('Absolute error:', Math.abs(value - actual));
27 |
--------------------------------------------------------------------------------
/examples/vector.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var integrate = require('../vector');
4 |
5 | var res = integrate(function (f, theta) {
6 | var c = Math.cos(theta);
7 | var s = Math.sin(theta);
8 |
9 | var dzr = -s;
10 | var dzi = c;
11 |
12 | var fr = c / (c * c + s * s);
13 | var fi = -s / (c * c + s * s);
14 |
15 | f[0] = fr * dzr - fi * dzi;
16 | f[1] = fr * dzi + fi * dzr;
17 | }, 0, Math.PI * 2);
18 |
19 | console.log('integral 1 / z about 0 =', res);
20 |
21 |
--------------------------------------------------------------------------------
/images/1-z-32ebeece91.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scijs/integrate-adaptive-simpson/7795a57232536f2dee6b492b1194d36aa2437bf3/images/1-z-32ebeece91.png
--------------------------------------------------------------------------------
/images/a-2217a6870d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scijs/integrate-adaptive-simpson/7795a57232536f2dee6b492b1194d36aa2437bf3/images/a-2217a6870d.png
--------------------------------------------------------------------------------
/images/b-224c764dec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scijs/integrate-adaptive-simpson/7795a57232536f2dee6b492b1194d36aa2437bf3/images/b-224c764dec.png
--------------------------------------------------------------------------------
/images/int_0011-frac1xcosleftfrac1xrightdx-c5d6a6f216.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scijs/integrate-adaptive-simpson/7795a57232536f2dee6b492b1194d36aa2437bf3/images/int_0011-frac1xcosleftfrac1xrightdx-c5d6a6f216.png
--------------------------------------------------------------------------------
/images/int_ab-fx-dx-49d001614b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scijs/integrate-adaptive-simpson/7795a57232536f2dee6b492b1194d36aa2437bf3/images/int_ab-fx-dx-49d001614b.png
--------------------------------------------------------------------------------
/images/oint-fracdzz-2pi-i-3243136d9d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scijs/integrate-adaptive-simpson/7795a57232536f2dee6b492b1194d36aa2437bf3/images/oint-fracdzz-2pi-i-3243136d9d.png
--------------------------------------------------------------------------------
/images/z_0-0-227c53dd15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scijs/integrate-adaptive-simpson/7795a57232536f2dee6b492b1194d36aa2437bf3/images/z_0-0-227c53dd15.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = integrate;
4 |
5 | // This algorithm adapted from pseudocode in:
6 | // http://www.math.utk.edu/~ccollins/refs/Handouts/rich.pdf
7 | function adsimp (f, a, b, fa, fm, fb, V0, tol, maxdepth, depth, state) {
8 | if (state.nanEncountered) {
9 | return NaN;
10 | }
11 |
12 | var h, f1, f2, sl, sr, s2, m, V1, V2, err;
13 |
14 | h = b - a;
15 | f1 = f(a + h * 0.25);
16 | f2 = f(b - h * 0.25);
17 |
18 | // Simple check for NaN:
19 | if (isNaN(f1)) {
20 | state.nanEncountered = true;
21 | return;
22 | }
23 |
24 | // Simple check for NaN:
25 | if (isNaN(f2)) {
26 | state.nanEncountered = true;
27 | return;
28 | }
29 |
30 | sl = h * (fa + 4 * f1 + fm) / 12;
31 | sr = h * (fm + 4 * f2 + fb) / 12;
32 | s2 = sl + sr;
33 | err = (s2 - V0) / 15;
34 |
35 | if (depth > maxdepth) {
36 | state.maxDepthCount++;
37 | return s2 + err;
38 | } else if (Math.abs(err) < tol) {
39 | return s2 + err;
40 | } else {
41 | m = a + h * 0.5;
42 |
43 | V1 = adsimp(f, a, m, fa, f1, fm, sl, tol * 0.5, maxdepth, depth + 1, state);
44 |
45 | if (isNaN(V1)) {
46 | state.nanEncountered = true;
47 | return NaN;
48 | }
49 |
50 | V2 = adsimp(f, m, b, fm, f2, fb, sr, tol * 0.5, maxdepth, depth + 1, state);
51 |
52 | if (isNaN(V2)) {
53 | state.nanEncountered = true;
54 | return NaN;
55 | }
56 |
57 | return V1 + V2;
58 | }
59 | }
60 |
61 | function integrate (f, a, b, tol, maxdepth) {
62 | var state = {
63 | maxDepthCount: 0,
64 | nanEncountered: false
65 | };
66 |
67 | if (tol === undefined) {
68 | tol = 1e-8;
69 | }
70 | if (maxdepth === undefined) {
71 | maxdepth = 20;
72 | }
73 |
74 | var fa = f(a);
75 | var fm = f(0.5 * (a + b));
76 | var fb = f(b);
77 |
78 | var V0 = (fa + 4 * fm + fb) * (b - a) / 6;
79 |
80 | var result = adsimp(f, a, b, fa, fm, fb, V0, tol, maxdepth, 1, state);
81 |
82 | if (state.maxDepthCount > 0 && console && console.warn) {
83 | console.warn('integrate-adaptive-simpson: Warning: maximum recursion depth (' + maxdepth + ') reached ' + state.maxDepthCount + ' times');
84 | }
85 |
86 | if (state.nanEncountered && console && console.warn) {
87 | console.warn('integrate-adaptive-simpson: Warning: NaN encountered. Halting early.');
88 | }
89 |
90 | return result;
91 | }
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "integrate-adaptive-simpson",
3 | "version": "1.1.1",
4 | "description": "Integrate a system of ODEs using the Second Order Runge-Kutta (Midpoint) method",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "semistandard && mocha",
8 | "lint": "semistandard"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git://github.com/scijs/integrate-adaptive-simpson.git"
13 | },
14 | "keywords": [
15 | "scijs",
16 | "integral",
17 | "definite-integral",
18 | "integration",
19 | "calculus",
20 | "adaptive",
21 | "romberg"
22 | ],
23 | "author": "Ricky Reusser",
24 | "license": "MIT",
25 | "devDependencies": {
26 | "almost-equal": "^1.1.0",
27 | "chai": "^3.0.0",
28 | "integrate-simpson": "^1.0.3",
29 | "mocha": "^2.2.5",
30 | "semistandard": "^7.0.5",
31 | "sinon": "^1.17.3"
32 | },
33 | "dependencies": {}
34 | }
35 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | /* globals it, describe */
2 | 'use strict';
3 |
4 | var adaptiveSimpson = require('../');
5 | var assert = require('chai').assert;
6 | var sinon = require('sinon');
7 | // var simpson = require('integrate-simpson');
8 |
9 | describe('Adaptive Simpson integration', function () {
10 | it('integrates a constant exactly', function () {
11 | var evaluations = 0;
12 | var value = adaptiveSimpson(function () {
13 | evaluations++;
14 | return 2;
15 | }, 1, 3);
16 |
17 | assert.closeTo(value, 4, 0, '= 4');
18 |
19 | // Five evaluations means exact evaluation + error estimate = 0:
20 | assert.equal(evaluations, 5, 'evaluates the function five times');
21 | });
22 |
23 | it('integrates a cubic exactly', function () {
24 | var evaluations = 0;
25 | var value = adaptiveSimpson(function (x) {
26 | evaluations++;
27 | return x * x * x - x * x;
28 | }, 1, 3);
29 |
30 | assert.closeTo(value, 34 / 3, 1e-10, '= 34/3');
31 |
32 | // Five evaluations means exact evaluation + error estimate = 0:
33 | assert.equal(evaluations, 5, 'evaluates the function five times');
34 | });
35 |
36 | it('integrates an irrational function', function () {
37 | var evaluations = 0;
38 | var f = function (x) {
39 | evaluations++;
40 | return Math.sin(x);
41 | };
42 | var value = adaptiveSimpson(f, 0, Math.PI);
43 | assert.closeTo(value, 2, 1e-10, '= 2');
44 | });
45 |
46 | it('stops immediately when NaN encountered', function () {
47 | var nanEvaluations = 0;
48 | var f = function (x) {
49 | if (x > 0.2 && x < 0.3) {
50 | nanEvaluations++;
51 | return NaN;
52 | }
53 | return Math.sin(x);
54 | };
55 | var value = adaptiveSimpson(f, 0, Math.PI);
56 | assert.equal(nanEvaluations, 1);
57 | assert.isNaN(value);
58 | });
59 |
60 | it('stops quickly when infinity encountered', function () {
61 | // Inifnity carries through to NaN pretty quickly, so we'll avoid a separate
62 | // check, but it will bail pretty quickly:
63 | var infinityEvaluations = 0;
64 | var f = function (x) {
65 | if (x > 0.2 && x < 0.3) {
66 | infinityEvaluations++;
67 | return Infinity;
68 | }
69 | return Math.sin(x);
70 | };
71 | var value = adaptiveSimpson(f, 0, Math.PI, 1e-10, 5);
72 |
73 | // This would be like 100000 if not caught:
74 | assert(infinityEvaluations < 10);
75 | assert.isNaN(value);
76 | });
77 |
78 | it('integrates an oscillatory function', function () {
79 | // See: http://www.wolframalpha.com/input/?i=integrate+cos%281%2Fx%29%2Fx+from+x%3D0.01+to+x%3D1
80 | var evaluations = 0;
81 | var f = function (x) {
82 | evaluations++;
83 | return Math.cos(1 / x) / x;
84 | };
85 |
86 | // Selected this tolerance with trial and error to minimize evaluations:
87 | var v1 = adaptiveSimpson(f, 0.01, 1, 6e-4, 20);
88 | assert.closeTo(v1, -0.342553, 1e-5, '= 2');
89 | });
90 |
91 | it('integrates 3 * x + 10 from 0 to 10', function () {
92 | var g = function (x) {
93 | return 3 * x + 10;
94 | };
95 | var value = adaptiveSimpson(g, 0, 10, 1);
96 | assert.closeTo(value, 250, 1e-5, '= 250');
97 |
98 | value = adaptiveSimpson(g, 0, 10, 0.9);
99 | assert.closeTo(value, 250, 1e-5, '= 250');
100 | });
101 |
102 | it('Prints one console warn when tolerance exceeded', function () {
103 | var _origWarn = console.warn;
104 | console.warn = sinon.spy(_origWarn);
105 |
106 | var f = function (x) {
107 | return Math.cos(1 / x) / x;
108 | };
109 |
110 | // Selected this tolerance with trial and error to minimize evaluations:
111 | var v1 = adaptiveSimpson(f, 0.01, 1, 0, 15);
112 | assert.closeTo(v1, -0.342553, 1e-3, '= 2');
113 | assert.equal(console.warn.callCount, 1, 'console.warn called once');
114 |
115 | console.warn = _origWarn;
116 | });
117 | });
118 |
--------------------------------------------------------------------------------
/test/util/array-equal.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var almostEqual = require('almost-equal');
4 |
5 | module.exports = arrayEqual;
6 |
7 | function arrayEqual (computed, expected, t1, t2) {
8 | t1 = t1 === undefined ? almostEqual.FLT_EPSILON : t1;
9 | t2 = t2 === undefined ? almostEqual.FLT_EPSILON : t2;
10 |
11 | if (computed.length !== expected.length) {
12 | throw new Error('Length of computed (' + computed.length + ') not equal to length of expected (' + expected.length + ')');
13 | }
14 |
15 | for (var i = 0; i < computed.length; i++) {
16 | if (!almostEqual(computed[i], expected[i], t1, t2)) {
17 | throw new Error('Arrays not equal. computed[' + i + '] !~ expected[' + i + '] (' + computed[i] + ' !~ ' + expected[i] + ')');
18 | }
19 | }
20 |
21 | return true;
22 | }
23 |
--------------------------------------------------------------------------------
/test/util/contour-integrand.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = contourIntegrand;
4 |
5 | function contourIntegrand (f, z0r, z0i, r) {
6 | // Defaults:
7 | r = r === undefined ? 1 : r;
8 | z0r = z0r === undefined ? 0 : z0r;
9 | z0i = z0i === undefined ? 0 : z0i;
10 |
11 | if (f.length === 2) {
12 | return function (out, theta) {
13 | // Compute the unit circle:
14 | var c = Math.cos(theta);
15 | var s = Math.sin(theta);
16 | var dzr = -r * s;
17 | var dzi = r * c;
18 | var a = z0r + r * c;
19 | var b = z0i + r * s;
20 |
21 | // Evaluate the function:
22 | var y = f(a, b);
23 |
24 | // Compute the integrand:
25 | out[0] = y[0] * dzr - y[1] * dzi;
26 | out[1] = y[0] * dzi + y[1] * dzr;
27 | };
28 | } else if (f.length === 3) {
29 | // Avoid allocating storage over and over:
30 | var y = [];
31 |
32 | return function (out, theta) {
33 | // Compute the unit circle:
34 | var c = Math.cos(theta);
35 | var s = Math.sin(theta);
36 | var dzr = -r * s;
37 | var dzi = r * c;
38 | var a = z0r + r * c;
39 | var b = z0i + r * s;
40 |
41 | // Evaluate the function:
42 | f(y, a, b);
43 |
44 | // Compute the integrand:
45 | out[0] = y[0] * dzr - y[1] * dzi;
46 | out[1] = y[0] * dzi + y[1] * dzr;
47 | };
48 | } else {
49 | throw new Error('f must be a function accepting either two or three arguments');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/test/vector.js:
--------------------------------------------------------------------------------
1 | /* globals it, describe */
2 | 'use strict';
3 |
4 | var integrate = require('../vector');
5 | var assert = require('chai').assert;
6 | var arrayEqual = require('./util/array-equal');
7 | var contourIntegrand = require('./util/contour-integrand');
8 |
9 | describe('Vector adaptive Simpson integration', function () {
10 | it('integrates f(z) = 1 + i around the unit complex circle', function () {
11 | var result = integrate(contourIntegrand(function (a, b) {
12 | return [1, 1];
13 | }), 0, Math.PI * 2);
14 |
15 | assert(arrayEqual(result, [0, 0]));
16 | });
17 |
18 | it('integrates f(z) = z around the unit complex circle', function () {
19 | var result = integrate(contourIntegrand(function (a, b) {
20 | return [a, b];
21 | }), 0, Math.PI * 2);
22 |
23 | assert(arrayEqual(result, [0, 0]));
24 | });
25 |
26 | it('integrates f(z) = z^2 around the unit complex circle', function () {
27 | var result = integrate(contourIntegrand(function (a, b) {
28 | return [a * a - b * b, 2 * a * b];
29 | }), 0, Math.PI * 2);
30 |
31 | assert(arrayEqual(result, [0, 0]));
32 | });
33 |
34 | it('integrates f(z) = z^2 around the unit complex circle', function () {
35 | var result = integrate(contourIntegrand(function (a, b) {
36 | return [a * a - b * b, 2 * a * b];
37 | }), 0, Math.PI * 2);
38 |
39 | assert(arrayEqual(result, [0, 0]));
40 | });
41 |
42 | it('computes Res 1 / z about z0 = 0', function () {
43 | var result = integrate(contourIntegrand(function (a, b) {
44 | var denom = a * a + b * b;
45 | return [a / denom, -b / denom];
46 | }), 0, Math.PI * 2);
47 |
48 | assert(arrayEqual(result, [0, Math.PI * 2]));
49 | });
50 |
51 | it('computes Res 1 / z about z0 = 10 + 10i', function () {
52 | var result = integrate(contourIntegrand(function (f, a, b) {
53 | var denom = a * a + b * b;
54 | f[0] = a / denom;
55 | f[1] = -b / denom;
56 | }, 2, 2, 1), 0, Math.PI * 2);
57 |
58 | assert(arrayEqual(result, [0, 0]));
59 | });
60 |
61 | it('computes Res 1 / (z - 10 + 10i) about z0 = 0', function () {
62 | var result = integrate(contourIntegrand(function (a, b) {
63 | var denom = a * a + b * b;
64 | return [a / denom, -b / denom];
65 | }, 10, -10, 1), 0, Math.PI * 2);
66 |
67 | assert(arrayEqual(result, [0, 0]));
68 | });
69 |
70 | it('computes Res 1 / (z - 10 + 10i) about z0 = 10 - 10i', function () {
71 | var result = integrate(contourIntegrand(function (a, b) {
72 | var a0 = a - 10;
73 | var b0 = b + 10;
74 | var denom = a0 * a0 + b0 * b0;
75 | return [a0 / denom, -b0 / denom];
76 | }, 10, -10, 1), 0, Math.PI * 2);
77 |
78 | assert(arrayEqual(result, [0, Math.PI * 2]));
79 | });
80 |
81 | it('computes Res 1 / (z - 10 + 10i) about z0 = 10 - 10i (radius = 2)', function () {
82 | var result = integrate(contourIntegrand(function (a, b) {
83 | var a0 = a - 10;
84 | var b0 = b + 10;
85 | var denom = a0 * a0 + b0 * b0;
86 | return [a0 / denom, -b0 / denom];
87 | }, 10, -10, 2), 0, Math.PI * 2);
88 |
89 | assert(arrayEqual(result, [0, Math.PI * 2]));
90 | });
91 |
92 | it('computes Res 1 / z^2 about z0 = 0', function () {
93 | var result = integrate(contourIntegrand(function (a, b) {
94 | var denom = Math.pow(a * a + b * b, 2);
95 | return [
96 | (a * a - b * b) / denom,
97 | -2 * a * b / denom
98 | ];
99 | }), 0, Math.PI * 2);
100 |
101 | assert(arrayEqual(result, [0, 0]));
102 | });
103 |
104 | it('computes Res 1 / z^3 about z0 = 0', function () {
105 | var result = integrate(contourIntegrand(function (a, b) {
106 | var denom = Math.pow(a * a + b * b, 3);
107 | return [
108 | (a * a * a - 3 * a * b * b) / denom,
109 | (b * b * b - 3 * a * a * b) / denom
110 | ];
111 | }), 0, Math.PI * 2);
112 |
113 | assert(arrayEqual(result, [0, 0]));
114 | });
115 | });
116 |
--------------------------------------------------------------------------------
/vector.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = integrate;
4 |
5 | // This algorithm adapted from pseudocode in:
6 | // http://www.math.utk.edu/~ccollins/refs/Handouts/rich.pdf
7 | function adsimp (out, f, a, b, n, fa, fm, fb, V0, tol, maxdepth, depth, state) {
8 | var i;
9 | if (state.nanEncountered) {
10 | return NaN;
11 | }
12 |
13 | var h, f1, f2, sl, sr, m, V1, V2, l2err;
14 |
15 | sl = new Array(n);
16 | sr = new Array(n);
17 |
18 | h = b - a;
19 | f1 = new Array(n);
20 | f2 = new Array(n);
21 |
22 | f(f1, a + h * 0.25);
23 | f(f2, b - h * 0.25);
24 |
25 | l2err = 0;
26 | for (i = 0; i < n; i++) {
27 | // Simple check for NaN:
28 | if (isNaN(f1[i])) {
29 | state.nanEncountered = true;
30 | return;
31 | }
32 |
33 | // Simple check for NaN:
34 | if (isNaN(f2[i])) {
35 | state.nanEncountered = true;
36 | return;
37 | }
38 |
39 | sl[i] = h * (fa[i] + 4 * f1[i] + fm[i]) / 12;
40 | sr[i] = h * (fm[i] + 4 * f2[i] + fb[i]) / 12;
41 | state.s2[i] = sl[i] + sr[i];
42 | state.err[i] = Math.pow((state.s2[i] - V0[i]) / 15, 2);
43 | l2err += state.err[i];
44 | }
45 |
46 | l2err = Math.sqrt(l2err);
47 |
48 | if (depth > maxdepth) {
49 | state.maxDepthCount++;
50 | for (i = 0; i < n; i++) {
51 | out[i] = state.s2[i] + state.err[i];
52 | }
53 | } else if (l2err < tol) {
54 | for (i = 0; i < n; i++) {
55 | out[i] = state.s2[i] + state.err[i];
56 | }
57 | } else {
58 | m = a + h * 0.5;
59 |
60 | V1 = new Array(n);
61 | adsimp(V1, f, a, m, n, fa, f1, fm, sl, tol * 0.5, maxdepth, depth + 1, state);
62 |
63 | for (i = 0; i < n; i++) {
64 | if (isNaN(V1[i])) {
65 | state.nanEncountered = true;
66 | return NaN;
67 | }
68 | }
69 |
70 | V2 = new Array(n);
71 | adsimp(V2, f, m, b, n, fm, f2, fb, sr, tol * 0.5, maxdepth, depth + 1, state);
72 |
73 | for (i = 0; i < n; i++) {
74 | if (isNaN(V2[i])) {
75 | state.nanEncountered = true;
76 | return NaN;
77 | }
78 | }
79 |
80 | for (i = 0; i < n; i++) {
81 | out[i] = V1[i] + V2[i];
82 | }
83 | }
84 | }
85 |
86 | function integrate (f, a, b, tol, maxdepth) {
87 | var i, n, result;
88 |
89 | var state = {
90 | maxDepthCount: 0,
91 | nanEncountered: false
92 | };
93 |
94 | if (tol === undefined) {
95 | tol = 1e-8;
96 | }
97 | if (maxdepth === undefined) {
98 | maxdepth = 20;
99 | }
100 |
101 | var fa = [];
102 | var fm = [];
103 | var fb = [];
104 |
105 | f(fa, a);
106 | f(fm, 0.5 * (a + b));
107 | f(fb, b);
108 |
109 | // Get the dimensionality based on function evaluation:
110 | n = fa.length;
111 |
112 | // Avoid a bit of garbage collection. Could do way better, but this
113 | // is a start...
114 | state.err = [];
115 | state.s2 = [];
116 |
117 | var V0 = [];
118 | for (i = 0; i < n; i++) {
119 | V0[i] = (fa[i] + 4 * fm[i] + fb[i]) * (b - a) / 6;
120 | }
121 |
122 | result = [];
123 | for (i = 0; i < n; i++) {
124 | result[i] = 0;
125 | }
126 |
127 | adsimp(result, f, a, b, n, fa, fm, fb, V0, tol, maxdepth, 1, state);
128 |
129 | if (state.maxDepthCount > 0 && console && console.warn) {
130 | console.warn('integrate-adaptive-simpson: Warning: maximum recursion depth (' + maxdepth + ') reached ' + state.maxDepthCount + ' times');
131 | }
132 |
133 | if (state.nanEncountered && console && console.warn) {
134 | console.warn('integrate-adaptive-simpson: Warning: NaN encountered. Halting early.');
135 | }
136 |
137 | return result;
138 | }
139 |
--------------------------------------------------------------------------------