## Copyright (C) 2010 Pedro Gonnet ## ## This file is part of Octave. ## ## Octave is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or (at ## your option) any later version. ## ## Octave 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 GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Function File} address@hidden, @var{err}, @var{nr_points}] =} cquad (@var{f}, @var{a}, @var{b}, @var{tol}) ## @deftypefnx {Function File} address@hidden, @var{err}, @var{nr_points}] =} cquad (@var{f}, @var{a}, @var{b}, @var{tol}, @var{sing}) ## Numerically evaluates an integral using the doubly-adaptive ## quadrature described by P. Gonnet in @cite{"Increasing the ## Reliability of Adaptive Quadrature Using Explicit Interpolants", ## ACM Transactions on Mathematical Software, in Press, 2010}. ## The algorithm uses Clenshaw-Curtis quadrature rules of increasing ## degree in each interval and bisects the interval if either the ## function does not appear to be smooth or a rule of maximum ## degree has been reached. The error estimate is computed from the ## L2-norm of the difference between two successive interpolations ## of the integrand over the nodes of the respective quadrature rules. ## ## For example, ## ## @example ## int = cquad ( f , a , b , 1.0e-6 ); ## @end example ## ## @noindent computes the integral of a function @var{f} in the interval ## address@hidden,@var{b}] to the relative precision of six ## decimal digits. ## The integrand @var{f} should accept a vector argument and return a vector ## result containing the integrand evaluated at each element of the ## argument, for example ## ## @example ## f = @@(x) x .* sin ( 1 ./ x ) .* sqrt ( abs ( 1 - x ) ); ## @end example ## ## If the integrand has known singularieites or discontinuities ## in any of its derivatives inside the interval, ## as does the above example at x=1, these can be specified in ## the additional argument @var{sing} as follows ## ## @example ## int = cquad ( f , a , b , 1.0e-6 , [ 1 ] ); ## @end example ## ## The two additional output variables @var{err} and @var{nr_points} ## return an estimate of the absolute integration error and ## the number of points at which the integrand was evaluated ## respectively. ## If the adaptive integration did not converge, the value of ## @var{err} will be larger than the requested tolerance. It is ## therefore recommended to verify this value for difficult ## integrands. ## ## If either @var{a} or @var{b} are @code{+/-Inf}, @code{cquad} ## integrates @var{f} by substituting the variable of integration ## with @code{x=tan(pi/2*u)}. ## ## @code{cquad} is capable of dealing with non-numerical ## values of the integrand such as @code{NaN}, @code{Inf} ## or @code{-Inf}, as the above example at x=0. ## If the integral diverges and @code{cquad} detects this, ## a warning is issued and @code{Inf} or @code{-Inf} is returned. ## ## Note that @code{cquad} is a general purpose quadrature algorithm ## and as such may be less efficient for smooth or otherwise ## well-behaved integrand than other methods such as ## @code{quadgk} or @code{trapz}. ## ## @seealso{triplequad, dblquad, quadgk, quadl, quadv, trapz} ## @end deftypefn ## Author: Pedro G. Gonnet ## Keywords: Quadrature, Integration function [ int , err , nr_points ] = cquad ( f , a , b , tol , sing ) % declare persistent variables persistent n xi bee ... V Vinv Vcond T_left T_right w U % have the persistent variables been declared already? if ( ~exist ('U') || isempty (U) ) % the nodes of the different rules and the coefficients % of their newton polynomials n = [4,8,16,32]; xi{1} = -cos([0:n(1)]/n(1)*pi)'; xi{2} = -cos([0:n(2)]/n(2)*pi)'; xi{3} = -cos([0:n(3)]/n(3)*pi)'; xi{4} = -cos([0:n(4)]/n(4)*pi)'; bee{1} = [0., .233284737407921723637836578544e-1, 0., -.831479419283098085685277496071e-1, 0.,0.0541462136776153483932540272848 ]'; bee{2} = [0., .883654308363339862264532494396e-4, 0., .238811521522368331303214066075e-3, 0., .135365534194038370983135068211e-2, 0., -.520710690438660595086839959882e-2, 0.,0.00341659266223572272892690737979 ]'; bee{3} = [0., .379785635776247894184454273159e-7, 0., .655473977795402040043020497901e-7, 0., .103479954638984842226816620692e-6, 0., .173700624961660596894381303819e-6, 0., .337719613424065357737699682062e-6, 0., .877423283550614343733565759649e-6, 0., .515657204371051131603503471028e-5, 0.,-.203244736027387801432055290742e-4, 0.,0.0000134265158311651777460545854542 ]'; bee{4} = [0., .703046511513775683031092069125e-13, 0., .110617117381148770138566741591e-12, 0., .146334657087392356024202217074e-12, 0., .184948444492681259461791759092e-12, 0., .231429962470609662207589449428e-12, 0., .291520062115989014852816512412e-12, 0., .373653379768759953435020783965e-12, 0., .491840460397998449461993671859e-12, 0., .671514395653454630785723660045e-12, 0., .963162916392726862525650710866e-12, 0., .147853378943890691325323722031e-11, 0., .250420145651013003355273649380e-11, 0., .495516257435784759806147914867e-11, 0., .130927034711760871648068641267e-10, 0., .779528640561654357271364483150e-10, 0., -.309866395328070487426324760520e-9, 0., .205572320292667201732878773151e-9]'; % compute the Vandermonde-like Legendre matrices xil = (xi{4} - 1) / 2; xir = (xi{4} + 1) / 2; Vx = ones ( n(4)+1 ); Vl = ones ( n(4)+1 ); Vr = ones ( n(4)+1 ); Vx(:,2) = xi{4}; Vl(:,2) = xil; Vr(:,2) = xir; for i=3:n(4)+1 Vx(:,i) = (2*i-3) / (i-1) * xi{4} .* Vx(:,i-1) - (i-2) / (i-1) * Vx(:,i-2); Vl(:,i) = (2*i-3) / (i-1) * xil .* Vl(:,i-1) - (i-2) / (i-1) * Vl(:,i-2); Vr(:,i) = (2*i-3) / (i-1) * xir .* Vr(:,i-1) - (i-2) / (i-1) * Vr(:,i-2); endfor; for i=1:n(4)+1 Vx(:,i) = Vx(:,i) * sqrt(4*i-2)/2; Vl(:,i) = Vl(:,i) * sqrt(4*i-2)/2; Vr(:,i) = Vr(:,i) * sqrt(4*i-2)/2; endfor; V{4} = Vx; Vinv{4} = inv ( V{4} ); V{3} = V{4}(1:2:end,1:n(3)+1); Vinv{3} = inv ( V{3} ); V{2} = V{4}(1:4:end,1:n(2)+1); Vinv{2} = inv ( V{2} ); V{1} = V{4}(1:8:end,1:n(1)+1); Vinv{1} = inv ( V{1} ); Vcond = [ cond(V{1}) , cond(V{2}) , cond(V{3}) , cond(V{4}) ]; % compute the shift matrices T_left = Vinv{4} * Vl; T_right = Vinv{4} * Vr; % compute the integration vector w = [ sqrt(2) , zeros(1,n(4)) ] / 2; % legendre % set-up the downdate matrix k = [0:n(4)]'; U = diag(sqrt((k+1).^2 ./ (2*k+1) ./ (2*k+3))) + diag(sqrt(k(3:end).^2 ./ (4*k(3:end).^2-1)),2); endif; % if exist('n') % create the original datatype ivals = struct( ... 'a', [], 'b',[], ... 'c', [], ... 'fx', [], ... 'int', [], ... 'err', [], ... 'tol', [], ... 'depth', [], ... 'rdepth', [], ... 'ndiv', [] ); % onvert function given as a string to a function handle (from quadgk) if ( ischar (f) ) f = @(x) feval (f, x); endif; % get the interval(s) if ( ~exist ('sing') || isempty (sing) ), iv = [ a ; b ]; else iv = [ a ; sort(sing(:)) ; b ]; endif; % if a or b are +/- Inf, transform the interval % NOTE: there are probably better ways of doing this, e.g. what % quadgk or Waldvogel does! if ( isinf (a) || isinf (b) ) f = @(x) f ( tan ( pi / 2 * x ) ) .* ( 1 + tan ( pi * x / 2 ).^2 ) * pi / 2; if ( isinf (a) ), a = -1; else a = 2 * atan (a) / pi; endif; if ( isinf (b) ), b = 1; else b = 2 * atan (b) / pi; endif; iv = [ a ; 2 * atan( iv(2:end-1) ) / pi ; b ]; endif; % compute the first interval(s) for k=1:length(iv)-1 % evaluate f in the interval m = (iv(k) + iv(k+1)) / 2; h = (iv(k+1) - iv(k)) / 2; points = m + h * xi{4}; fx = f (points); % check for nans and clean them up nans = find ( ~isfinite ( fx ) ); fx(nans) = 0; % compute the coefficients of the two highest degree interpolations ivals(k).c = zeros(n(4)+1,4); ivals(k).c(1:n(4)+1,4) = Vinv{4} * fx; ivals(k).c(1:n(3)+1,3) = Vinv{3} * fx([1:2:n(4)+1]); % re-instate the nans fx(nans) = NaN; % store the data ivals(k).fx = fx; ivals(k).a = iv(k); ivals(k).b = iv(k+1); ivals(k).tol = tol; ivals(k).depth = 4; ivals(k).ndiv = 0; ivals(k).rdepth = 1; % compute the integral ivals(k).int = 2 * h * w * ivals(k).c(:,4); % compute the error estimate c_diff = norm(ivals(k).c(:,4) - ivals(k).c(:,3)); ivals(k).err = 2 * h * c_diff; if ( c_diff / norm(ivals(k).c(:,4)) > 0.1 ), ivals(k).err = max( ivals(k).err , 2 * h * norm(ivals(k).c(:,4)) ); endif; endfor; % init some globals int = sum( [ ivals(:).int ] ); err = sum( [ ivals(:).err ] ); nr_ivals = length(ivals); int_final = 0; err_final = 0; err_excess = 0; nr_points = (nr_ivals * n(4)) + 1; ndiv_max = 20; [ dummy , i_max ] = max( [ ivals(:).err ] ); % do we even need to go this way? if ( err < int * tol ), return; endif; % main loop while ( true ) % get some global data iv = ivals(i_max); a = iv.a; b = iv.b; depth = iv.depth; split = 0; % check the depth of the interval. if it is less than 4, % then try to increase the degree. if this fails or the % interval is at depth 4, split the interval. % interval of depth 1 if ( depth < 4 ) % adjust the depth iv.depth = depth+1; depth = depth + 1; % get the new function values points = (a+b)/2 + (b-a)*xi{depth}/2; fx(1:2:n(depth)+1) = iv.fx; fx(2:2:n(depth)) = f (points(2:2:n(depth))); fx = fx(1:n(depth)+1); nr_points = nr_points + n(depth)-n(depth-1); % purge nans and compute the coefficients nans = find( ~isfinite ( fx ) ); fx(nans) = 0; c_new = Vinv{depth} * fx; % downdate to remove any nans if ( length(nans) > 0 ) b_new = bee{depth}; n_new = n(depth); for i=nans' b_new(1:end-1) = (U(1:n(depth)+1,1:n(depth)+1) - diag(ones(n(depth),1)*xi{depth}(i),1)) \ b_new(2:end); b_new(end) = 0; c_new = c_new - c_new(n_new+1) / b_new(n_new+1) * b_new(1:end-1); n_new = n_new - 1; endfor; endif; fx(nans) = NaN; % store the function values iv.fx = fx; % compute the error estimate nc = norm(c_new); iv.c(1:n(depth)+1,depth) = c_new; c_diff = norm(iv.c(:,depth-1) - iv.c(:,depth)); iv.err = (b-a) * c_diff; % compute the new integral iv.int = (b-a) * w(1) * c_new(1); % split this interval prematurely? if ( nc > 0 && c_diff / nc > 0.1 ), split = 1; endif; % interval of any other depth else split = 1; endif; % can we safely ignore this interval? % discard if the nodes are below machine resolution or the % error estimate falls below the local tolerance if ( points(2) <= points(1) || points(end) <= points(end-1) || ... iv.err < abs(iv.int) * eps * Vcond(iv.depth) ) err_final = err_final + iv.err; int_final = int_final + iv.int; ivals(i_max) = ivals(nr_ivals); nr_ivals = nr_ivals - 1; % if we have to split the interval... elseif ( split ) % get the center m = (a + b) / 2; % construct the left interval ivl = struct (); ivl.a = a; ivl.b = m; ivl.tol = iv.tol / sqrt(2); ivl.depth = 1; ivl.rdepth = iv.rdepth + 1; ivl.c = zeros ( n(4)+1 , 4 ); fx = [ iv.fx(1) ; f( (a+m)/2 + (m-a)*xi{1}(2:end-1)/2 ) ; iv.fx((end+1)/2) ]; nr_points = nr_points + n(1)-1; nans = find ( ~isfinite( fx ) ); fx(nans) = 0; c_new = Vinv{1} * fx; if ( length(nans) > 0 ) b_new = bee{1}; n_new = n(1); for i=nans b_new(1:end-1) = (U(1:n(1)+1,1:n(1)+1) - diag(ones(n(1),1)*xi{1}(i),1)) \ b_new(2:end); b_new(end) = 0; c_new = c_new - c_new(n_new+1) / b_new(n_new+1) * b_new(1:end-1); n_new = n_new - 1; endfor; endif; fx(nans) = NaN; ivl.fx = fx; ivl.c(1:n(1)+1,1) = c_new; nc = norm(c_new); c_diff = norm(ivl.c(:,1) - T_left * ivl.c(:,depth)); ivl.err = (m - a) * c_diff; ivl.int = (m - a) * ivl.c(1,1) * w(1); % check for divergence ivl.ndiv = iv.ndiv + (abs(iv.c(1,1)) > 0 && ivl.c(1,1) / iv.c(1,1) > 2); if ( ivl.ndiv > ndiv_max && 2*ivl.ndiv > ivl.rdepth ), warning (sprintf ('possibly divergent integral in the interval [%e,%e]! (h=%e)',a,m,m-a)); int = sign(int) * Inf; return; endif; % construct the right interval ivr = struct (); ivr.a = m; ivr.b = b; ivr.tol = iv.tol / sqrt(2); ivr.depth = 1; ivr.rdepth = iv.rdepth + 1; ivr.c = zeros(n(4)+1,4); fx = [ iv.fx((end+1)/2) ; f((m+b)/2+(b-m)*xi{1}(2:end-1)/2) ; iv.fx(end) ]; nr_points = nr_points + n(1)-1; nans = find ( ~isfinite( fx ) ); fx(nans) = 0; c_new = Vinv{1} * fx; if ( length(nans) > 0 ) b_new = bee{1}; n_new = n(1); for i=nans b_new(1:end-1) = (U(1:n(1)+1,1:n(1)+1) - diag(ones(n(1),1)*xi{1}(i),1)) \ b_new(2:end); b_new(end) = 0; c_new = c_new - c_new(n_new+1) / b_new(n_new+1) * b_new(1:end-1); n_new = n_new - 1; endfor; endif; fx(nans) = NaN; ivr.fx = fx; ivr.c(1:n(1)+1,1) = c_new; nc = norm(c_new); c_diff = norm(ivr.c(:,1) - T_right * iv.c(:,depth)); ivr.err = (b - m) * c_diff; ivr.int = (b - m) * ivr.c(1,1) * w(1); % check for divergence ivr.ndiv = iv.ndiv + (abs(iv.c(1,1)) > 0 && ivr.c(1,1) / iv.c(1,1) > 2); if ( ivr.ndiv > ndiv_max && 2*ivr.ndiv > ivr.rdepth ), warning (sprintf ('possibly divergent integral in the interval [%e,%e]! (h=%e)',m,b,b-m)); int = sign(int) * Inf; return; endif; % store the intervals ivals(i_max) = ivl; nr_ivals = nr_ivals + 1; ivals(nr_ivals) = ivr; % otherwise, just store the interval else ivals(i_max) = iv; endif; % compute the running err and new max [ dummy , i_max ] = max ( [ ivals(1:nr_ivals).err ] ); err = err_final + sum( [ ivals(1:nr_ivals).err ] ); int = int_final + sum( [ ivals(1:nr_ivals).int ] ); % nuke smallest element if stack is larger than 200 if ( nr_ivals > 200 ) [ dummy , i_min ] = min ( [ ivals(1:nr_ivals).err ] ); err_final = err_final + ivals(i_min).err; int_final = int_final + ivals(i_min).int; ivals(i_min) = ivals(nr_ivals); if ( i_max == nr_ivals ), i_max = i_min; endif; nr_ivals = nr_ivals - 1; endif; % get up and leave? if ( err == 0 || err < abs(int) * tol || (err_final > abs(int) * tol && err - err_final < abs(int) * tol) || nr_ivals == 0 ) break; endif; endwhile; % main loop % clean-up and tabulate err = err + err_excess; end %!assert (cquad (@sin,-pi,pi, 1e-6), 0, 1e-6) %!assert (cquad (inline('sin'),-pi,pi, 1e-6), 0, 1e-6) %!assert (cquad ('sin',-pi,pi, 1e-6), 0, 1e-6) %!assert (cquad (@sin,-pi,0, 1e-6), -2, 2e-6) %!assert (cquad (@sin,0,pi, 1e-6), 2, 2e-6) %!assert (cquad (@(x) 1./sqrt(x), 0, 1,1e-6), 2, 2e-6) %!assert (cquad (@(x) abs (1 - x.^2), 0, 2, 1e-6, [ 1 ]), 2, 2e-6) %!assert (cquad (@(x) 1./(sqrt(x).*(x+1)), 0, Inf, 1e-6), pi, 3.1e-6) %!assert (cquad (@(x) exp(-x .^ 2), -Inf, Inf, 1e-6), sqrt(pi), 1e-6)