Viewing contents of file '../idllib/contrib/meron/root.pro'
Function Root, fun, range, eps, relative = rel, params = pars, multi = mult, $
    error = ervl, status = stat, done = don

;+
; NAME:
;    ROOT
; VERSION:
;	3.0
; PURPOSE:
;	Finds roots of real functions.
; CATEGORY:
;	Mathematical function (general).
; CALLING SEQUENCE:
;	Result = ROOT( FUN, RANGE [, EPS [, keywords]])
; INPUTS:
;    FUN
;	Character value representing an existing IDL function.  The function
;	must return scalar values.  It is not necessery for FUN to be able to 
;	accept an array input and return an array output.  The calling sequence
;	for the function must be either
;	    Result = FUN(x)
;	or
;	    Result = FUN(x, extra)
;	where X is the variable and EXTRA may be any single entity (scalar, 
;	array, or structure) used to pass additional parameters to the function.
;    RANGE
;	Two element vector, search range.
; OPTIONAL INPUT PARAMETERS:
;    EPS
;	Allowed error.  Default is machine precision, according to data type,
;	as given by TOLER.  EPS is understood to represent absolute error 
;	unless the keyword RELATIVE is set.  The allowed error is dynamically
;	adjusted during calculation.
; KEYWORD PARAMETERS:
;    /RELATIVE
;	If set, EPS represent the allowed relative (to the size of RANGE) error.
;    PARAMS
;	An arbitrary value or variable which is passed to the function FUN.
;    MULTI
;	Specifies multiplicity of search (i.e. what is the maximal number of 
;	roots to look for.  Default is 1.  If MULTI is set to -1 (or any 
;	negative number) the search is unlimited.
;    ERROR
;	Optional output, see below.
;    STATUS
;	Optional output, see below.
;    DONE
;	Optional output, see below.
; OUTPUTS:
;	Returns a vector containing the location(s) of the root(s).  The 
;	numerical type of the result (floating, or double) is determined by the
;	numerical type of RANGE.  If no root is found, returns machine max.
; OPTIONAL OUTPUT PARAMETERS:
;    ERROR
;	The name of the variable to receive the estimated error of the root 
;	location.  Returned as vector, same length as the function output.
;    STATUS
;	The name of the variable to receive search status information.  
;	Returned as vector, same length as the function output.  
;	Possible values are:
;	    0 - No root found.
;	    1 - OK.
;	    2 - Search converged, but the result appears to be a singularity.
;		This may happen with nonsingular functions if the precision
;		demands are set to high.
;    DONE
;	The name of the variable to receive search completion information.
;	Possible values are:
;	    0 - Indicates that the number of roots prescribed by MULTI has been
;		found without all of RANGE being searched.  This does not
;		necesserily mean that there are more roots.  Disabled for 
;		MULTI = 1, or negative.
;	    1 - Indicates that search has been completed, i.e. all the roots 
;		that could be found have been found.
; COMMON BLOCKS:
;	None.
; SIDE EFFECTS:
;	None.
; RESTRICTIONS:
;	None.
; PROCEDURE:
;	Uses Golden section search  for minima of ABS(FUN(X)).  When an 
;	interval where F(X) changes sign is identified, uses weighted interval 
;	halving to pinpoint the root.
;	Uses CAST, DEFAULT, FPU_FIX, ISNUM and TOLER from MIDL.
; MODIFICATION HISTORY:
;	Created 15-FEB-92 by Mati Meron.
;	Modified 15-APR-92 by Mati Meron.  Added Quadratic Interpolation to 
;	speed up convergence for smooth functions.
;	Modified 25-JAN-94 by Mati Meron.  Added multi-root search capability.
;	Quadratic Interpolation replaced by a weighted halving to increase 
;	robustness without loosing speed.
;	Modified 5-OCT-1998 by Mati Meron.  Underflow filtering added.
;-

    on_error, 1
    
    if n_elements(range) ne 2 then message, 'Improper range!'
    rlis = Cast(range,4)
    if rlis(0) gt rlis(1) then rlis = reverse(rlis)
    sinf = machar(double = Isnum(rlis,/double,type=rtyp))
    deps = Toler(type=rtyp)
    weps = abs(Default(eps,deps,/dtype))
    if keyword_set(rel) then weps = weps*(rlis(1) - rlis(0))
    weps = weps > deps*max(abs(rlis))

    rlis = rlis + 2*[-weps,weps]
    ervl = [weps,weps]
    stat = intarr(2)
    pfl = n_elements(pars) ne 0
    cmul = Default(mult,1,/dtype)
    if cmul eq 1 then mfl = 0 else mfl = 1
    gold = (sqrt(Cast(5,rtyp,rtyp)) - 1)/2
    ncur = 1

    while ncur lt n_elements(rlis) and cmul ne 0 do begin
	csta = 0
	x = rlis(ncur-1:ncur) + (ervl(ncur-1:ncur) > weps)*[2,-2]
	if (x(1) - x(0)) ge weps then begin
	    f = x
	    if pfl then begin
		f(0) = FPU_fix(call_function(fun,x(0),pars))
		f(1) = FPU_fix(call_function(fun,x(1),pars))
	    endif else begin
		f(0) = FPU_fix(call_function(fun,x(0)))
		f(1) = FPU_fix(call_function(fun,x(1)))
	    endelse
	    x = x([0,1,1])
	    f = f([0,1,1])
	    if f(0)*f(1) gt 0 and mfl then begin
		x(1) = (1 - gold)*x(0) + gold*x(2)
		if pfl then f(1) = FPU_fix(call_function(fun,x(1),pars)) else $
		    f(1) = FPU_fix(call_function(fun,x(1)))
		if f(0)*f(1) gt 0 then begin
		    repeat begin
			x = x([0,0,1,2])
			f = f([0,0,1,2])
			x(1) = (1 - gold)*x(0) + gold*x(2)
			if pfl then f(1)=FPU_fix(call_function(fun,x(1),pars)) $
			    else f(1) = call_function(fun,x(1))
			if f(0)*f(1) gt 0 then begin
			    comp = f(2)/f(1)
			    if stat(ncur-1) ne 0 then comp = $
				comp*(x(1) - rlis(ncur-1))/(x(2) - rlis(ncur-1))
			    if stat(ncur) ne 0 then comp = $
				comp*(x(1) - rlis(ncur))/(x(2) - rlis(ncur))
			    if comp gt 1 then ind = [0,1,2] else ind = [3,2,1]
			    x = x(ind)
			    f = f(ind)
			endif else csta = 1
		    endrep until csta or abs(x(2) - x(0)) lt weps
		endif else csta = 1
	    endif else csta = f(0)*f(1) le 0
	endif

	if csta then begin
	    if f(0)*f(1) eq 0 then begin
		if f(0) eq 0 then x(1) = x(0)
		cerv = 0
	    endif else begin
		ceps = weps
		cerv = abs(x(1) - x(0))
		cfl = 0
		pow = 0.5
		while cerv ge ceps do begin
		    ind =  [0,0,1] + cfl
		    x = x(ind)
		    f = f(ind)
		    wf = abs(f([2,0]))^pow
		    wfx = x([0,2])*wf
		    x(1) = (wfx(0) + wfx(1))/(wf(0) + wf(1))
		    if pfl then f(1) = FPU_fix(call_function(fun,x(1),pars)) $
			else f(1) = FPU_fix(call_function(fun,x(1)))
		    if f(1) ne 0 then begin
			cfl = f(0)*f(1) gt 0
			nerv = abs(x(cfl+1) - x(cfl))
			erat = nerv/cerv
			if erat lt 0.5 then pow = 2*pow < 1. else pow = pow/2
			if erat eq 1 then ceps = 2*ceps
			cerv = nerv
		    endif else cerv = 0
		endwhile
		if f(1) ne 0 and (f(1) - f(0))*(f(1) - f(2)) gt 0 then csta = 2
	    endelse
	    rlis = FPU_fix([rlis(0:ncur-1),x(1),rlis(ncur:*)])
	    ervl = FPU_fix([ervl(0:ncur-1),cerv,ervl(ncur:*)])
	    stat = [stat(0:ncur-1),csta,stat(ncur:*)]
	    cmul = cmul - 1
	    if not mfl then ncur = 3
	endif else ncur = ncur + 1
    endwhile

    if ncur lt n_elements(rlis) and cmul eq 0 then don = 0 else don = 1
    found = where(stat ne 0, nfound)
    if nfound gt 0 then begin
	rlis = rlis(found)
	ervl = ervl(found)
	stat = stat(found)
    endif else begin
	rlis = sinf.xmax
	ervl = abs(range(1) - range(0))
	stat = 0
    endelse

    return, rlis
end