;$Id: fx_root.pro,v 1.8 2000/01/21 00:29:05 scottm Exp $
;
; Copyright (c) 1994-2000, Research Systems, Inc.  All rights reserved.
;       Unauthorized reproduction prohibited.
;+
; NAME:
;       FX_ROOT2
;
; PURPOSE:
;       This function computes real and complex roots (zeros) of
;       a univariate nonlinear function.
;
; CATEGORY:
;       Nonlinear Equations/Root Finding
;
; CALLING SEQUENCE:
;       Result = FX_ROOT2(X, Func)
;
; INPUTS:
;       X :      A 3-element initial guess vector of type real or complex.
;                Real initial guesses may result in real or complex roots.
;                Complex initial guesses will result in complex roots.
;
;       Func:    A scalar string specifying the name of a user-supplied IDL
;                function that defines the univariate nonlinear function.
;                This function must accept the vector argument X.
;
; KEYWORD PARAMETERS:
;       DOUBLE:  If set to a non-zero value, computations are done in
;                double precision arithmetic.
;
;       ITMAX:   Set this keyword to specify the maximum number of iterations
;                The default is 100.
;
;       STOP:    Set this keyword to specify the stopping criterion used to
;                judge the accuracy of a computed root, r(k).
;                STOP = 0 implements an absolute error criterion between two
;                successively-computed roots, |r(k) - r(k+1)|.
;                STOP = 1 implements a functional error criterion at the
;                current root, |Func(r(k))|. The default is 0.
;
;       TOL:     Set this keyword to specify the stopping error tolerance.
;                If the STOP keyword is set to 0, the algorithm stops when
;                |x(k) - x(k+1)| < TOL.
;                If the STOP keyword is set to 1, the algorithm stops when
;                |Func(x(k))| < TOL. The default is 1.0e-4.
;
; EXAMPLE:
;       Define an IDL function named FUNC.
;         function FUNC, x
;           return, exp(sin(x)^2 + cos(x)^2 - 1) - 1
;         end
;
;       Define a real 3-element initial guess vector.
;         x = [0.0, -!pi/2, !pi]
;
;       Compute a root of the function using double-precision arithmetic.
;         root = FX_ROOT2(x, 'FUNC', /double)
;
;       Check the accuracy of the computed root.
;         print, exp(sin(root)^2 + cos(root)^2 - 1) - 1
;
;       Define a complex 3-element initial guess vector.
;         x = [complex(-!pi/3, 0), complex(0, !pi), complex(0, -!pi/6)]
;
;       Compute a root of the function.
;         root = FX_ROOT2(x, 'FUNC')
;
;       Check the accuracy of the computed root.
;         print, exp(sin(root)^2 + cos(root)^2 - 1) - 1
;
; PROCEDURE:
;       FX_ROOT2 implements an optimal Muller's method using complex
;       arithmetic only when necessary.
;
; REFERENCE:
;       Numerical Recipes, The Art of Scientific Computing (Second Edition)
;       Cambridge University Press
;       ISBN 0-521-43108-5
;
; MODIFICATION HISTORY:
;       Written by:  GGS, RSI, March 1994
;       Modified:    GGS, RSI, September 1994
;                    Added support for double-precision complex inputs.
;       Modified:    Zarro (L-3Com/GSFC), November 2004, added _extra to pass
;                    keyword values to func. Renamed to FX_ROOT2
;-

function fx_root2, xi, func, double = double, itmax = itmax, $
                            stop = stop, tol = tol,_extra=extra

  on_error, 2 ;Return to caller if error occurs.

  x = xi + 0.0 ;Create an internal floating-point variable, x.
  sx = size(x)
  if sx[1] ne 3 then $
    message, 'x must be a 3-element initial guess vector.'

  ;Initialize keyword parameters.
  if keyword_set(double) ne 0 then begin
    if sx[2] eq 4 or sx[2] eq 5 then x = x + 0.0d $
    else x = dcomplex(x)
  endif
  if keyword_set(itmax)  eq 0 then itmax = 100
  if keyword_set(stop)   eq 0 then stop = 0
  if keyword_set(tol)    eq 0 then tol = 1.0e-4

  ;Initialize stopping criterion and iteration count.
  cond = 0  &  it = 0

  ;Begin to iteratively compute a root of the nonlinear function.
  while (it lt itmax and cond ne 1) do begin
    q = (x[2] - x[1])/(x[1] - x[0])
    pls = (1 + q)
    f = call_function(func, x,_extra=extra)
    a = q*f[2] - q*pls*f[1] + q^2*f[0]
    b = (2*q+1)*f[2] - pls^2*f[1] + q^2*f[0]
    c = pls*f[2]
    disc = b^2 - 4*a*c
    roc = size(disc)  ;Real or complex discriminant?
    if roc[1] ne 6 and roc[1] ne 9 then begin ;Proceed toward real root.
        if disc lt 0 then begin ;Switch to complex root.
                                ;Single-precision complex.
            if keyword_set(double) eq 0 and sx[2] ne 9 then begin
                r0 = b + complex(0, sqrt(abs(disc)))
                r1 = b - complex(0, sqrt(abs(disc)))
            endif else begin    ;Double-precision complex.
                r0 = b + dcomplex(0, sqrt(abs(disc)))
                r1 = b - dcomplex(0, sqrt(abs(disc)))
            endelse
            if abs(r0) gt abs(r1) then div = r0 $ ;Maximum modulus.
            else div = r1
        endif else begin        ; real root
            rR0 = b + sqrt(disc)
            rR1 = b - sqrt(disc)
            div = abs(rR0) ge abs(rR1) ? rR0 : rR1
        endelse
    endif else begin            ;Proceed toward complex root.
        c0 = b + sqrt(disc)
        c1 = b - sqrt(disc)
        if abs(c0) gt abs(c1) then div = c0 $ ;Maximum modulus.
        else div = c1
    endelse
    root = x[2] - (x[2] - x[1]) * (2 * c/div)
                                ;Absolute error tolerance.
    if stop eq 0 and abs(root - x[2]) le tol then begin
        cond = 1
    endif else begin
            evalFunc = call_function( func, root,_extra=extra )
                                ;Functional error tolerance.
            if stop ne 0 and abs( evalFunc ) le tol then cond = 1 $
                                ;If func(root) eq 0, we must quit or
                                ;divide by zero next iteration.
            else if evalFunc eq 0 then cond = 1
        endelse
        x = [x[1], x[2], root]
        it = it + 1
    endwhile
    if it ge itmax and cond eq 0 then $
      message, 'Algorithm failed to converge within given parameters.',/cont
    return, root
end