Skip to content

Instantly share code, notes, and snippets.

@kenkehoe
Created October 1, 2014 20:43
Show Gist options
  • Select an option

  • Save kenkehoe/352d15f5e71f103a24eb to your computer and use it in GitHub Desktop.

Select an option

Save kenkehoe/352d15f5e71f103a24eb to your computer and use it in GitHub Desktop.
Find the zero of a 1-D function up to specified tolerance with IDL.
function ZBRENT, x1, x2, FUNC_NAME=func_name, $
MAX_ITERATIONS=maxit, TOLERANCE=TOL
;+
; NAME:
; ZBRENT
; PURPOSE:
; Find the zero of a 1-D function up to specified tolerance with IDL.
; EXPLANTION:
; This routine assumes that the function is known to have a zero.
; Adapted from procedure of the same name in "Numerical Recipes" by
; Press et al. (1992), Section 9.3
;
; CALLING:
; x_zero = ZBRENT( x1, x2, FUNC_NAME="name", MaX_Iter=, Tolerance= )
;
; INPUTS:
; x1, x2 = scalars, 2 points which bracket location of function zero,
; that is, F(x1) < 0 < F(x2).
; Note: computations are performed with
; same precision (single/double) as the inputs and user supplied function.
;
; REQUIRED INPUT KEYWORD:
; FUNC_NAME = function name (string)
; Calling mechanism should be: F = func_name( px )
; where: px = scalar independent variable, input.
; F = scalar value of function at px,
; should be same precision (single/double) as input.
;
; OPTIONAL INPUT KEYWORDS:
; MAX_ITER = maximum allowed number iterations, default=100.
; TOLERANCE = desired accuracy of minimum location, default = 1.e-3.
;
; OUTPUTS:
; Returns the location of zero, with accuracy of specified tolerance.
;
; PROCEDURE:
; Brent's method to find zero of a function by using bracketing,
; bisection, and inverse quadratic interpolation,
;
; EXAMPLE:
; Find the root of the COSINE function between 1. and 2. radians
;
; IDL> print, zbrent( 1, 2, FUNC = 'COS')
;
; and the result will be !PI/2 within the specified tolerance
; MODIFICATION HISTORY:
; Written, Frank Varosi NASA/GSFC 1992.
; FV.1994, mod to check for single/double prec. and set zeps accordingly.
; Converted to IDL V5.0 W. Landsman September 1997
; Use MACHAR() to define machine precision W. Landsman September 2002
; kenkehoe
;-
if N_params() LT 2 then begin
print,'Syntax - result = ZBRENT( x1, x2, FUNC_NAME = ,'
print,' [ MAX_ITER = , TOLERANCE = ])'
return, -1
endif
if N_elements( TOL ) NE 1 then TOL = 1.e-3
if N_elements( maxit ) NE 1 then maxit = 100
if size(x1,/TNAME) EQ 'DOUBLE' OR size(x2,/TNAME) EQ 'DOUBLE' then begin
xa = double( x1 )
xb = double( x2 )
zeps = (machar(/DOUBLE)).eps ;machine epsilon in double.
endif else begin
xa = x1
xb = x2
zeps = (machar(/DOUBLE)).eps ;machine epsilon, in single
endelse
fa = call_function( func_name, xa )
fb = call_function( func_name, xb )
fc = fb
if (fb*fa GT 0) then begin
message,"root must be bracketed by the 2 inputs",/INFO
return,xa
endif
for iter = 1,maxit do begin
if (fb*fc GT 0) then begin
xc = xa
fc = fa
Din = xb - xa
Dold = Din
endif
if (abs( fc ) LT abs( fb )) then begin
xa = xb & xb = xc & xc = xa
fa = fb & fb = fc & fc = fa
endif
TOL1 = 0.5*TOL + 2*abs( xb ) * zeps ;Convergence check
xm = (xc - xb)/2.
if (abs( xm ) LE TOL1) OR (fb EQ 0) then return,xb
if (abs( Dold ) GE TOL1) AND (abs( fa ) GT abs( fb )) then begin
S = fb/fa ;attempt inverse quadratic interpolation
if (xa EQ xc) then begin
p = 2 * xm * S
q = 1-S
endif else begin
T = fa/fc
R = fb/fc
p = S * (2*xm*T*(T-R) - (xb-xa)*(R-1) )
q = (T-1)*(R-1)*(S-1)
endelse
if (p GT 0) then q = -q
p = abs( p )
test = ( 3*xm*q - abs( q*TOL1 ) ) < abs( Dold*q )
if (2*p LT test) then begin
Dold = Din ;accept interpolation
Din = p/q
endif else begin
Din = xm ;use bisection instead
Dold = xm
endelse
endif else begin
Din = xm ;Bounds decreasing to slowly, use bisection
Dold = xm
endelse
xa = xb
fa = fb ;evaluate new trial root.
if (abs( Din ) GT TOL1) then xb = xb + Din $
else xb = xb + TOL1 * (1-2*(xm LT 0))
fb = call_function( func_name, xb )
endfor
message,"exceeded maximum number of iterations: "+strtrim(iter,2),/INFO
return, xb
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment