changeset 1683:6b33b21b5f59 octave-forge

Complete replacement for the old wrapper function. Now fzero actually implements the Brent's root-finding method or, if necessary, falls back to fsolve(). This function has been written by a student of mine, Lukasz Bodzon.
author przykry2004
date Fri, 03 Sep 2004 14:25:39 +0000
parents 97f699fcf9e9
children c33e02c85731
files main/optim/fzero.m
diffstat 1 files changed, 410 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/main/optim/fzero.m	Fri Sep 03 13:40:13 2004 +0000
+++ b/main/optim/fzero.m	Fri Sep 03 14:25:39 2004 +0000
@@ -1,4 +1,4 @@
-## Copyright (C) 2000 Paul Kienzle
+## Copyright (C) 2004 Łukasz Bodzon, <lllopezzz@o2.pl>
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published by
@@ -7,26 +7,422 @@
 ##
 ## This program is distributed in the hope that it will be useful,
 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 ## GNU General Public License for more details.
 ##
 ## You should have received a copy of the GNU General Public License
 ## along with this program; if not, write to the Free Software
-## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+## REVISION HISTORY
+##
+## 2004-07-20, Piotr Krzyzanowski, <piotr.krzyzanowski@mimuw.edu.pl>:
+## Options parameter and fall back to fsolve if only scalar APPROX argument
+## supplied
+##
+## 2004-07-01, Lukasz Bodzon:
+## Replaced f(a)*f(b) < 0 criterion by a more robust
+## sign(f(a)) ~= sign(f(b))
+##
+## 2004-06-18, Lukasz Bodzon:
+## Original implementation of Brent's method of finding a zero of a scalar
+## function
 
-## usage: x = fzero("f", x0)
+## -*- texinfo -*-
+## @deftypefn {Function File} {} [X, FX, INFO] = fzero (FCN, APPROX, OPTIONS)
+##
+## Given FCN, the name of a function of the form `F (X)', and an initial
+## approximation APPROX, `fzero' solves the scalar nonlinear equation such that
+## `F(X) == 0'. Depending on APPROX, `fzero' uses different algorithms to solve
+## the problem: either the Brent's method or the Powell's method of `fsolve'.
+##
+## @table @asis
+## @item INPUT ARGUMENTS
+## @end table
+##
+## @table @asis
+## @item APPROX can be a vector with two components, 
+## @example
+## A = APPROX(1) and B = APPROX(2),
+## @end example
+## which localizes the zero of F, that is, it is assumed that X lies between A and
+## B. If APPROX is a scalar, it is treated as an initial guess for X.
+##
+## If APPROX is a vector of length 2 and F takes different signs at A and B,
+## F(A)*F(B) < 0, then the Brent's zero finding algorithm [1] is used with error
+## tolerance criterion 
+## @example
+## reltol*|X|+abstol (see OPTIONS). 
+## @end example
+## This algorithm combines
+## superlinear convergence (for sufficiently regular functions) with the
+## robustness of bisection.
 ##
-## Find x such that f(x) = 0, starting at x0.
-## If x0 is a range, start at the mid-point of the range.
-## Returns NaN if the solution is not found.
+## Whether F has identical signs at A and B, or APPROX is a single scalar value,
+## then `fzero' falls back to another method and `fsolve(FCN, X0)' is called, with
+## the starting value X0 equal to (A+B)/2 or APPROX, respectively. Only absolute
+## residual tolerance, abstol, is used then, due to the limitations of the `fsolve_options'
+## function. See OPTIONS and `help fsolve' for details.
+##
+## @item OPTIONS is a structure, with the following fields:
+##
+## @table @asis
+## @item 'abstol' - absolute (error for Brent's or residual for fsolve)
+## tolerance. Default = 1e-6.
+##
+## @item 'reltol' - relative error tolerance (only Brent's method). Default = 1e-6.
+##
+## @item 'prl' - print level, how much diagnostics to print. Default = 0, no
+## diagnostics output.
+## @end table
+##
+## If OPTIONS argument is omitted, or a specific field is not present in the
+## OPTIONS structure, default values will be used.
+## @end table
+##
+## @table @asis
+## @item OUTPUT ARGUMENTS
+## @end table
+##
+## @table @asis
+## @item The computed approximation to the zero of FCN is returned in X. FX is then equal
+## to FCN(X). If the iteration converged, INFO == 1.
+## @end table
+##
+## @table @asis
+## @item EXAMPLES
+## @end table
+##
+## @example
+## fzero('sin',[-2 1]) will use Brent's method to find the solution to
+## sin(x) = 0 in the interval [-2, 1]
+## @end example
+##
+## @example
+## [x, fx, info] = fzero('sin',-2) will use fsolve to find a solution to
+## sin(x)=0 near -2.
+## @end example
+##
+## @example
+## options.abstol = 1e-2; fzero('sin',-2, options) will use fsolve to
+## find a solution to sin(x)=0 near -2 with the absolute tolerance 1e-2.
+## @end example
 ##
-## Note: This is a simple wrapper around the fsolve built-in function.
+## @table @asis
+## @item REFERENCES
+## [1] Brent, R. P. "Algorithms for minimization without derivatives" (1971).
+## @end table
+## @end deftypefn
+## @seealso{fsolve}
+
+function [Z, FZ, INFO] =fzero(Func,bracket,options)
+
+	if (nargin < 2 || !isstr(Func) || !((rows(bracket) == 1 & (columns(bracket) == 1 || columns(bracket)==2)) || (rows(bracket') == 1 & (columns(bracket') == 1 || columns(bracket')==2))))
+		help('fzero');
+		return;
+	endif
+
+	set_default_options = false;
+	if (nargin >= 2) 			% check for the options
+		if (nargin == 2)
+			set_default_options = true;
+		 else 				% nargin > 2
+			if ~isstruct(options)
+				warning('Options incorrect. Setting default values.');
+				set_default_options = true;
+			end
+		end
+	end
+
+	if set_default_options
+		options.do = 1; 		% a hack to turn (otherwise unset yet) options parameter
+						%  into a structure
+	end
+
+	if ~isfield(options,'abstol')
+		options.abstol = 1e-6;
+	end
+	if ~isfield(options,'reltol')
+		options.reltol = 1e-6;
+	end
+	% if ~isfield(options,'maxit')
+	% options.maxit = 100;
+	% end
+	if ~isfield(options,'prl')
+		options.prl = 0; 		% no diagnostics output
+	end
+
+	fcount = 0; 				% counts function evaluations
+	if (length(bracket) > 1)
+		a = bracket(1); b = bracket(2);
+		use_brent = true;
+	 else
+		b = bracket;
+		use_brent = false;
+	end
+
+
+	if (use_brent)
+
+		fa=feval(Func,a); fcount=fcount+1;
+		fb=feval(Func,b); fcount=fcount+1;
+
+		BOO=true;
+		tol=options.reltol*abs(b)+options.abstol;
+
+		% check if one of the endpoints is the solution
+		if (fa == 0.0)
+			BOO = false;
+			c = b = a;
+			fc = fb = fa;
+		end
+		if (fb == 0.0)
+			BOO = false;
+			c = a = b;
+			fc = fa = fb;
+		end
 
-function [x, fval, status] = fzero(f,x0)
+		if ((sign(fa) == sign(fb)) & BOO)
+			warning ("fzero: equal signs at both ends of the interval.\n\
+			Using fsolve('%s',%g) instead", Func, 0.5*(a+b));
+			use_brent = false;
+			b = 0.5*(a+b);
+		endif
+	end
+
+
+
+	if (use_brent) 				% it is reasonable to call Brent's method
+		if options.prl > 0
+			fprintf(stderr,"============================\n");
+			fprintf(stderr,"fzero: using Brent's method\n");
+			fprintf(stderr,"============================\n");
+		end
+		c=a;
+		fc=fa;
+		d=b-a;
+		e=d;
+
+		while (BOO == true) 		% convergence check
+
+			if (sign(fb) == sign(fc)) % rename a, b, c and adjust bounding interval
+				c=a;
+				fc=fa;
+				d=b-a;
+				e=d;
+			endif,
+
+			## We are preventing overflow and division by zero
+			## while computing the new approximation by
+			## linear interpolation.
+			## After this step, we lose the chance for using
+			## inverse quadratic interpolation (a==c).
+
+			if (abs(fc) < abs(fb))
+				a=b;
+				b=c;
+				c=a;
+				fa=fb;
+				fb=fc;
+				fc=fa;
+			endif,
+
+			tol=options.reltol*abs(b)+options.abstol;
+			m=0.5*(c-b);
+			if options.prl > 0
+				fprintf(stderr,'fzero: [%d feval] X = %8.4e\n', fcount, b);
+				if options.prl > 1
+					fprintf(stderr,'fzero: m = %8.4e e = %8.4e [tol = %8.4e]\n', m, e, tol);
+				end
+			end
+
+			if (abs(m) > tol & fb != 0)
+
+			## The second condition in following if-instruction
+			## prevents overflow and division by zero
+			## while computing the new approximation by
+			## inverse quadratic interpolation.
+
+				if (abs(e) < tol | abs(fa) <= abs(fb))
+					d=m; 			% bisection
+					e=m;
+
+				 else
+					s=fb/fa;
+
+					if (a == c) 		% attempt linear interpolation
+						p=2*m*s; 	%  (the secant method)
+						q=1-s;
+
+					 else 			% attempt inverse quadratic interpolation
+						q=fa/fc;
+						r=fb/fc;
+						p=s*(2*m*q*(q-r)-(b-a)*(r-1));
+						q=(q-1)*(r-1)*(s-1);
+					endif,
+
+					if (p > 0) 		% fit signs
+						q=-q; 		%  to the sign of (c-b)
 
-  [x, info] = fsolve (f, mean (x0));
-  if info != 1
-    x = NaN;
-  endif
+					 else
+						p=-p;
+					endif,
+
+					s=e;
+					e=d;
+
+					if (2*p < 3*m*q-abs(tol*q) & p < abs(0.5*s*q))
+						d=p/q; 		% accept interpolation
+
+					 else 			% interpolation failed;
+						d=m; 		%  take the bisection step
+						e=m;
+					endif,
+
+				endif,
+
+				a=b;
+				fa=fb;
+
+				if (abs(d) > tol)	 	% the step we take is never shorter
+					b=b+d; 			%  than tol
+
+				 else
+
+					if (m > 0) 		% fit signs
+						b=b+tol; 	%  to the sign of (c-b)
+
+				 	 else
+						b=b-tol;
+					endif,
+
+				endif,
+
+				fb=feval(Func,b); fcount=fcount+1;
+
+		 	 else
+				BOO=false;
+			endif,
+
+		endwhile,
+		Z=b;
+		FZ = fb;
+		if abs(FZ) > 100*tol 	% large value of the residual may indicate a discontinuity point
+			INFO = -5;
+	 	 else
+			INFO = 1;
+		end
+		%
+		% TODO: test if Z may be a singular point of F (ie F is discontinuous at Z
+		% Then return INFO = -5
+		%
+		if (options.prl > 0 )
+			fprintf(stderr,"\nfzero: summary\n");
+			switch(INFO)
+		 	 case 1
+				MSG = "Solution converged within specified tolerance";
+		 	 case -5
+				MSG = strcat("Probably a discontinuity/singularity point of '", ...
+				Func, "'\n encountered close to X = ", sprintf('%8.4e',Z),...
+				".\n Value of the residual at X, |F(X)| = ",...
+				sprintf('%8.4e',abs(FZ)), ...
+				".\n Another possibility is that you use too large tolerance parameters",...
+				".\n Currently TOL = ", sprintf('%8.4e', tol), ...
+				".\n Try fzero with smaller tolerance values");
+		 	 otherwise
+				MSG = "Something strange happened"
+			endswitch
+			fprintf(stderr,' %s.\n', MSG);
+			fprintf(stderr,' %d function evaluations.\n', fcount);
+		end
 
-endfunction
\ No newline at end of file
+	 else 				% fall back to fsolve
+		if options.prl > 0
+			fprintf(stderr,"============================\n");
+			fprintf(stderr,"fzero: using fsolve\n");
+			fprintf(stderr,"============================\n");
+		end
+		% check for zeros in APPROX
+		fb=feval(Func,b); fcount=fcount+1;
+		tol_save = fsolve_options('tolerance');
+		fsolve_options('tolerance',options.abstol);
+		[Z, INFO, MSG] = fsolve(Func, b);
+		fsolve_options('tolerance',tol_save);
+		FZ = feval(Func,Z);
+		if options.prl > 0
+			fprintf(stderr,"\nfzero: summary\n");
+			fprintf(stderr,' %s.\n', MSG);
+		end
+	end
+endfunction;
+
+%!## usage and error testing
+%!##	the Brent's method
+%!test 
+%! options.abstol=0;
+%! assert (fzero('sin',[-1,2],options), 0)
+%!test 
+%! options.abstol=0.01;
+%! options.reltol=1e-3;
+%! assert (abs(fzero('tan',[-0.5,1.41],options)), 0, 0.01)
+%!test 
+%! options.abstol=1e-3;
+%! assert (abs(fzero('atan',[-(10^300),10^290],options)), 0, 1e-3)
+%!test				# comparision between the Brent's method and the Powell's method of `fsolve'
+%! testfun=inline('(x-1)^3','x');
+%! options.abstol=0;
+%! options.reltol=eps;
+%! assert (abs(fzero(testfun,[0,3],options)), 1, abs(1-fsolve(testfun,0)))
+%!test
+%! testfun=inline('x.^2-100','x');
+%! options.abstol=1e-4;
+%! assert (abs(fzero(testfun,[-9,300],options)),10,1e-4)
+%!##	`fsolve'
+%!test 
+%! options.abstol=0.01;
+%! assert (abs(fzero('tan',-0.5,options)), 0, 0.01)
+%!test 
+%! options.abstol=0;
+%! assert (fzero('sin',[0.5,1],options), 0)
+%!
+%!demo
+%! bracket=[-1,1.2]; 
+%! [X,FX,MSG]=fzero('tan',bracket)
+%!demo
+%! bracket=1; 	# `fsolve' will be used
+%! [X,FX,MSG]=fzero('sin',bracket)
+%!demo
+%! bracket=[-1,2]; 
+%! options.abstol=0; options.prl=1; 
+%! X=fzero('sin',bracket,options)
+%!demo
+%! bracket=[0.5,1]; 
+%! options.abstol=0; options.reltol=eps; options.prl=1; 
+%! fzero('sin',bracket,options)
+%!demo
+%! demofun=inline('2*x.*exp(-4)+1 - 2*exp(-4*x)','x'); 
+%! bracket=[0, 1]; 
+%! options.abstol=1e-14; options.reltol=eps; options.prl=2;
+%! [X,FX]=fzero(demofun,bracket,options)
+%!demo
+%! demofun=inline('x^51','x');
+%! bracket=[-12,10];
+%! # too large tolerance parameters
+%! options.abstol=1; options.reltol=1; options.prl=1;
+%! [X,FX]=fzero(demofun,bracket,options)
+%!demo
+%! # points of discontinuity inside the bracket
+%! demofun=inline('0.5*(sign(x-1e-7)+sign(x+1e-7))','x');
+%! bracket=[-5,7];
+%! options.prl=1;
+%! [X,FX]=fzero(demofun,bracket,options)
+%!demo
+%! demofun=inline('2*x.*exp(-x.^2)','x');
+%! bracket=1;
+%! options.abstol=1e-14; options.prl=2;
+%! [X,FX]=fzero(demofun,bracket,options)
+%!demo
+%! demofun=inline('2*x.*exp(-x.^2)','x');
+%! bracket=[-10,1];
+%! options.abstol=1e-14; options.prl=2;
+%! [X,FX]=fzero(demofun,bracket,options)