Skip to content

Instantly share code, notes, and snippets.

@borgar
Last active December 28, 2022 02:08
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save borgar/3317728 to your computer and use it in GitHub Desktop.
Save borgar/3317728 to your computer and use it in GitHub Desktop.
JavaScript port of Brent's method
/**
* Searches the interval from <tt>lowerLimit</tt> to <tt>upperLimit</tt>
* for a root (i.e., zero) of the function <tt>func</tt> with respect to
* its first argument using Brent's method root-finding algorithm.
*
* Translated from zeroin.c in http://www.netlib.org/c/brent.shar.
*
* Copyright (c) 2012 Borgar Thorsteinsson <borgar@borgar.net>
* MIT License, http://www.opensource.org/licenses/mit-license.php
*
* @param {function} function for which the root is sought.
* @param {number} the lower point of the interval to be searched.
* @param {number} the upper point of the interval to be searched.
* @param {number} the desired accuracy (convergence tolerance).
* @param {number} the maximum number of iterations.
* @returns an estimate for the root within accuracy.
*
*/
function uniroot ( func, lowerLimit, upperLimit, errorTol, maxIter ) {
var a = lowerLimit
, b = upperLimit
, c = a
, fa = func(a)
, fb = func(b)
, fc = fa
, s = 0
, fs = 0
, tol_act // Actual tolerance
, new_step // Step at this iteration
, prev_step // Distance from the last but one to the last approximation
, p // Interpolation step is calculated in the form p/q; division is delayed until the last moment
, q
;
errorTol = errorTol || 0;
maxIter = maxIter || 1000;
while ( maxIter-- > 0 ) {
prev_step = b - a;
if ( Math.abs(fc) < Math.abs(fb) ) {
// Swap data for b to be the best approximation
a = b, b = c, c = a;
fa = fb, fb = fc, fc = fa;
}
tol_act = 1e-15 * Math.abs(b) + errorTol / 2;
new_step = ( c - b ) / 2;
if ( Math.abs(new_step) <= tol_act || fb === 0 ) {
return b; // Acceptable approx. is found
}
// Decide if the interpolation can be tried
if ( Math.abs(prev_step) >= tol_act && Math.abs(fa) > Math.abs(fb) ) {
// If prev_step was large enough and was in true direction, Interpolatiom may be tried
var t1, cb, t2;
cb = c - b;
if ( a === c ) { // If we have only two distinct points linear interpolation can only be applied
t1 = fb / fa;
p = cb * t1;
q = 1.0 - t1;
}
else { // Quadric inverse interpolation
q = fa / fc, t1 = fb / fc, t2 = fb / fa;
p = t2 * (cb * q * (q - t1) - (b - a) * (t1 - 1));
q = (q - 1) * (t1 - 1) * (t2 - 1);
}
if ( p > 0 ) {
q = -q; // p was calculated with the opposite sign; make p positive
}
else {
p = -p; // and assign possible minus to q
}
if ( p < ( 0.75 * cb * q - Math.abs( tol_act * q ) / 2 ) &&
p < Math.abs( prev_step * q / 2 ) ) {
// If (b + p / q) falls in [b,c] and isn't too large it is accepted
new_step = p / q;
}
// If p/q is too large then the bissection procedure can reduce [b,c] range to more extent
}
if ( Math.abs( new_step ) < tol_act ) { // Adjust the step to be not less than tolerance
new_step = ( new_step > 0 ) ? tol_act : -tol_act;
}
a = b, fa = fb; // Save the previous approx.
b += new_step, fb = func(b); // Do step to a new approxim.
if ( (fb > 0 && fc > 0) || (fb < 0 && fc < 0) ) {
c = a, fc = fa; // Adjust c for it to have a sign opposite to that of b
}
}
}
/*
var test_counter;
function f1 (x) { test_counter++; return (Math.pow(x,2)-1)*x - 5; }
function f2 (x) { test_counter++; return Math.cos(x)-x; }
function f3 (x) { test_counter++; return Math.sin(x)-x; }
function f4 (x) { test_counter++; return (x + 3) * Math.pow(x - 1, 2); }
[
[f1, 2, 3],
[f2, 2, 3],
[f2, -1, 3],
[f3, -1, 3],
[f4, -4, 4/3]
].forEach(function (args) {
test_counter = 0;
var root = uniroot.apply( pv, args );
;;;console.log( 'uniroot:', args.slice(1), root, test_counter );
})
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment