brent-zero-generator
Version:
Brent's root finding algorithm, as a generator function.
136 lines (113 loc) • 3.98 kB
JavaScript
/**
* zero_generator2() seeks a root in an interval [a, b].
*
* zero_generator() is based on Brent's function `zero()`.
* Generator function `zero_generator` returns a zero `x` in
* the interval [a,b], to within a tolerance `6 macheps |x| + 2 t`,
* where `macheps` is the relative machine precision, and `t` is a
* positive tolerance. The function assumes that `f(a)` and `f(b)` have
* different signs.
*
* @param {number} a
* @param {number} b
* @param {number} [macheps=Number.EPSILON] - The macheps (Default: Number.EPSILON)
* @param {number} [t=0.0]
* Used to determine how close the bounds bracketing root finding
* must converge before quitting. (Default: 0.0)
* @returns {Generator<number,number[],number>}
*/
function* zero_generator2(a, b, macheps=Number.EPSILON, t=0.0) {
let fa = yield a;
let fb = yield b;
let [c, fc] = [a, fa];
let e = b - a;
let d = e;
while (true) {
if (Math.abs(fc) < Math.abs(fb)) {
[a, fa] = [b, fb];
[b, fb] = [c, fc];
[c, fc] = [a, fa];
}
const tol = 2 * macheps * Math.abs(b) + t;
const m = 0.5 * (c - b);
if (Math.abs(m) <= tol || fb == 0.0)
return [b, fb, c, fc];
// See if bisection is forced
if (Math.abs(e) < tol || Math.abs(fa) <= Math.abs(fb)) {
d = e = m;
} else {
const s = fb / fa;
let p, q;
if (a == c) {
// Linear interpolation
p = 2 * m * s;
q = 1 - s;
} else {
// Inverse quadratic interpolation
q = fa / fc;
const r = fb / fc;
p = s * (2 * m * q * (q - r) - (b - a) * (r - 1));
q = (q - 1) * (r - 1) * (s - 1);
}
if (p > 0)
q = -q;
else
p = -p;
if (2 * p < 3 * m * q - Math.abs(tol * q) && p < Math.abs(0.5 * e * q)) {
e = d;
d = p / q;
} else {
d = e = m;
}
}
[a, fa] = [b, fb];
b += (Math.abs(d) > tol ? d : (m > 0 ? tol : -tol));
fb = yield b;
if ( (fb > 0) == (fc > 0) ) {
[c, fc] = [a, fa];
d = e = b - a;
}
}
} /* zero_generator2 */
/**
* zero_generator() simplifies the return value from
* zero_generator2(). Its main purpose is not to break
* the API from version 1.0.
*
* @param {number} a
* @param {number} b
* @param {number} [macheps=Number.EPSILON] - The macheps (Default: Number.EPSILON)
* @param {number} [t=0.0]
* Used to determine how close the bounds bracketing root finding
* must converge before quitting. (Default: 0.0)
* @returns {Generator<number,number,number>}
*/
function* zero_generator(a, b, macheps=Number.EPSILON, t=0.0) {
const gen = zero_generator2(a, b, macheps, t);
const res = yield* gen;
return res[0];
}
/**
* @param {(x:number)=>number} f - The function for which roots are desired.
* @param {number} a - The lower value used to bracket root finding into the function.
* @param {number} b - The upper value used to bracket root finding into the function.
* @param {number=} macheps - The macheps (default: Number.EPSILON)
* @param {number} t - Used to determine how close the bounds bracketing
* root finding must converge before quitting. (default: 0.0)
* @returns Returns a input value resulting in a suitable root if successful. Returns false on failure.
*/
function zero(f, a, b, macheps=Number.EPSILON, t=0.0) {
const gen = zero_generator(a, b, macheps, t);
let result = gen.next();
while ( ! result.done ) {
const x = result.value;
const y = f(x);
result = gen.next(y)
}
return result.value;
}
module.exports = {
zero_generator2,
zero_generator,
zero,
};