## 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_1 xi_2 xi_3 xi_4 b_1_def b_2_def b_3_def b_4_def ... V_1 V_2 V_3 V_4 V_1_inv V_2_inv V_3_inv V_4_inv xx 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)'; b_1_def = [0., .233284737407921723637836578544e-1, 0., -.831479419283098085685277496071e-1, 0.,0.0541462136776153483932540272848 ]'; b_2_def = [0., .883654308363339862264532494396e-4, 0., .238811521522368331303214066075e-3, 0., .135365534194038370983135068211e-2, 0., -.520710690438660595086839959882e-2, 0.,0.00341659266223572272892690737979 ]'; b_3_def = [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 ]'; b_4_def = [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 V_1 = [ ones(size(xi_1)) xi_1 ]; V_2 = [ ones(size(xi_2)) xi_2 ]; V_3 = [ ones(size(xi_3)) xi_3 ]; V_4 = [ ones(size(xi_4)) xi_4 ]; xil = (xi_4 - 1) / 2; xir = (xi_4 + 1) / 2; Vl = [ ones(size(xil)) xil ]; Vr = [ ones(size(xir)) xir ]; xx = linspace(-1,1,500)'; Vx = [ ones(size(xx)) xx ]; for i=3:n(1)+1 V_1 = [ V_1 , ((2*i-3) / (i-1) * xi_1 .* V_1(:,i-1) - (i-2) / (i-1) * V_1(:,i-2)) ]; endfor; for i=3:n(2)+1 V_2 = [ V_2 , ((2*i-3) / (i-1) * xi_2 .* V_2(:,i-1) - (i-2) / (i-1) * V_2(:,i-2)) ]; endfor; for i=3:n(3)+1 V_3 = [ V_3 , ((2*i-3) / (i-1) * xi_3 .* V_3(:,i-1) - (i-2) / (i-1) * V_3(:,i-2)) ]; endfor; for i=3:n(4)+1 V_4 = [ V_4 , ((2*i-3) / (i-1) * xi_4 .* V_4(:,i-1) - (i-2) / (i-1) * V_4(:,i-2)) ]; Vx = [ Vx , ((2*i-3) / (i-1) * xx .* Vx(:,i-1) - (i-2) / (i-1) * Vx(:,i-2)) ]; Vl = [ Vl , ((2*i-3) / (i-1) * xil .* Vl(:,i-1) - (i-2) / (i-1) * Vl(:,i-2)) ]; Vr = [ Vr , ((2*i-3) / (i-1) * xir .* Vr(:,i-1) - (i-2) / (i-1) * Vr(:,i-2)) ]; endfor; for i=1:n(1)+1, V_1(:,i) = V_1(:,i) * sqrt(4*i-2)/2; endfor; for i=1:n(2)+1, V_2(:,i) = V_2(:,i) * sqrt(4*i-2)/2; endfor; for i=1:n(3)+1, V_3(:,i) = V_3(:,i) * sqrt(4*i-2)/2; endfor; for i=1:n(4)+1 V_4(:,i) = V_4(:,i) * sqrt(4*i-2)/2; 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_1_inv = inv(V_1); V_2_inv = inv(V_2); V_3_inv = inv(V_3); V_4_inv = inv(V_4); Vcond = [ cond(V_1) , cond(V_2) , cond(V_3) , cond(V_4) ]; % compute the shift matrices T_left = V_4_inv * Vl; T_right = V_4_inv * 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 = []; for i=1:length(fx), if ( ~isfinite (fx(i)) ), nans = [ i , nans ]; fx(i) = 0.0; endif; endfor; % 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) = V_4_inv * fx; ivals(k).c(1:n(3)+1,3) = V_3_inv * fx([1:2:n(4)+1]); % re-instate the nans for i=nans, fx(i) = NaN; endfor; % 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(1).c(:,4)) > 0.1 ), ivals(1).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 a = ivals(i_max).a; b = ivals(i_max).b; depth = ivals(i_max).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 == 1 ) % get the new function values points = (a+b)/2 + (b-a)*xi_2/2; fx(1:2:n(2)+1) = ivals(i_max).fx; fx(2:2:n(2)) = f (points(2:2:n(2))); fx = fx(1:n(2)+1); nr_points = nr_points + n(2)-n(1); % purge nans and compute the coefficients nans = []; for i=1:length(fx), if ( ~isfinite (fx(i)) ), fx(i) = 0.0; nans = [ i , nans ]; endif; endfor; c_new = V_2_inv * fx; % downdate to remove any nans if ( length(nans) > 0 ) b_new = b_2_def; n_new = n(2); for i=nans b_new(1:end-1) = (U(1:n(2)+1,1:n(2)+1) - diag(ones(n(2),1)*xi_2(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; fx(i) = NaN; endfor; endif; % store the function values ivals(i_max).fx = fx; % compute the error estimate nc = norm(c_new); ivals(i_max).c(1:n(2)+1,2) = c_new; c_diff = norm(ivals(i_max).c(:,1) - ivals(i_max).c(:,2)); ivals(i_max).err = (b-a) * c_diff; % compute the new integral ivals(i_max).int = (b-a) * w(1) * c_new(1); % split this interval prematurely? if ( nc > 0 && c_diff / nc > 0.1 ), split = 1; else ivals(i_max).depth = 2; endif; % interval of depth 2 elseif ( depth == 2 ) % get the new function values points = (a+b)/2 + (b-a)*xi_3/2; fx(1:2:n(3)+1) = ivals(i_max).fx; fx(2:2:n(3)) = f (points(2:2:n(3))); fx = fx(1:n(3)+1); nr_points = nr_points + n(3)-n(2); % purge nans and compute the coefficients nans = []; for i=1:length(fx), if ( ~isfinite (fx(i)) ), fx(i) = 0.0; nans = [ i , nans ]; endif; endfor; c_new = V_3_inv * fx; % downdate to remove any nans if ( length(nans) > 0 ) b_new = b_3_def; n_new = n(3); for i=nans b_new(1:end-1) = (U(1:n(3)+1,1:n(3)+1) - diag(ones(n(3),1)*xi_3(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; fx(i) = NaN; endfor; endif; % store the function values ivals(i_max).fx = fx; % compute the error estimate nc = norm(c_new); ivals(i_max).c(1:n(3)+1,3) = c_new; c_diff = norm(ivals(i_max).c(:,2) - ivals(i_max).c(:,3)); ivals(i_max).err = (b-a) * c_diff; % compute the new integral ivals(i_max).int = (b-a) * w(1) * c_new(1); % split this interval prematurely? if ( nc > 0 && c_diff / nc > 0.1 ), split = 1; else ivals(i_max).depth = 3; endif; % interval of depth 3 elseif ( depth == 3 ) % get the new function values points = (a+b)/2 + (b-a)*xi_4/2; fx(1:2:n(4)+1) = ivals(i_max).fx; fx(2:2:n(4)) = f (points(2:2:n(4))); fx = fx(1:n(4)+1); nr_points = nr_points + n(4)-n(3); % purge nans and compute the coefficients nans = []; for i=1:length(fx), if ( ~isfinite (fx(i)) ), fx(i) = 0.0; nans = [ i , nans ]; endif; endfor; c_new = V_4_inv * fx; % downdate to remove any nans if ( length(nans) > 0 ) b_new = b_4_def; n_new = n(4); for i=nans b_new(1:end-1) = (U(1:n(4)+1,1:n(4)+1) - diag(ones(n(4),1)*xi_4(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; fx(i) = NaN; endfor; endif; % store the function values ivals(i_max).fx = fx; % compute the error estimate nc = norm(c_new); ivals(i_max).c(:,4) = c_new; c_diff = norm(ivals(i_max).c(:,3) - ivals(i_max).c(:,4)); ivals(i_max).err = (b-a) * c_diff; % compute the new integral ivals(i_max).int = (b-a) * w(1) * c_new(1); % split this interval prematurely? if ( nc > 0 && c_diff / nc > 0.1 ), split = 1; else, ivals(i_max).depth = 4; 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) || ... ivals(i_max).err < abs(ivals(i_max).int) * eps * Vcond(ivals(i_max).depth) ) err_final = err_final + ivals(i_max).err; int_final = int_final + ivals(i_max).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 nr_ivals = nr_ivals + 1; ivals(nr_ivals).a = a; ivals(nr_ivals).b = m; ivals(nr_ivals).tol = ivals(i_max).tol / sqrt(2); ivals(nr_ivals).depth = 1; ivals(nr_ivals).rdepth = ivals(i_max).rdepth + 1; ivals(nr_ivals).c = zeros(n(4)+1,4); fx = [ ivals(i_max).fx(1) ; f( (a+m)/2 + (m-a)*xi_1(2:end-1)/2 ) ; ivals(i_max).fx((end+1)/2) ]; nr_points = nr_points + n(1)-1; nans = []; for i=1:length(fx), if ( ~isfinite (fx(i)) ), fx(i) = 0.0; nans = [ i , nans ]; endif; endfor; c_new = V_1_inv * fx; if ( length(nans) > 0 ) b_new = b_1_def; 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; fx(i) = NaN; endfor; endif; ivals(nr_ivals).fx = fx; ivals(nr_ivals).c(1:n(1)+1,1) = c_new; nc = norm(c_new); c_diff = norm(ivals(nr_ivals).c(:,1) - T_left * ivals(i_max).c(:,depth)); ivals(nr_ivals).err = (m - a) * c_diff; ivals(nr_ivals).int = (m - a) * ivals(nr_ivals).c(1,1) * w(1); % check for divergence ivals(nr_ivals).ndiv = ivals(i_max).ndiv + (abs(ivals(i_max).c(1,1)) > 0 && ivals(nr_ivals).c(1,1) / ivals(i_max).c(1,1) > 2); if ( ivals(nr_ivals).ndiv > ndiv_max && 2*ivals(nr_ivals).ndiv > ivals(nr_ivals).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 nr_ivals = nr_ivals + 1; ivals(nr_ivals).a = m; ivals(nr_ivals).b = b; ivals(nr_ivals).tol = ivals(i_max).tol / sqrt(2); ivals(nr_ivals).depth = 1; ivals(nr_ivals).rdepth = ivals(i_max).rdepth + 1; ivals(nr_ivals).c = zeros(n(4)+1,4); fx = [ ivals(i_max).fx((end+1)/2) ; f((m+b)/2+(b-m)*xi_1(2:end-1)/2) ; ivals(i_max).fx(end) ]; nr_points = nr_points + n(1)-1; nans = []; for i=1:length(fx), if ( ~isfinite (fx(i)) ), fx(i) = 0.0; nans = [ i , nans ]; endif; endfor; c_new = V_1_inv * fx; if ( length(nans) > 0 ) b_new = b_1_def; 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; fx(i) = NaN; endfor; endif; ivals(nr_ivals).fx = fx; ivals(nr_ivals).c(1:n(1)+1,1) = c_new; nc = norm(c_new); c_diff = norm(ivals(nr_ivals).c(:,1) - T_right * ivals(i_max).c(:,depth)); ivals(nr_ivals).err = (b - m) * c_diff; ivals(nr_ivals).int = (b - m) * ivals(nr_ivals).c(1,1) * w(1); % check for divergence ivals(nr_ivals).ndiv = ivals(i_max).ndiv + (abs(ivals(i_max).c(1,1)) > 0 && ivals(nr_ivals).c(1,1) / ivals(i_max).c(1,1) > 2); if ( ivals(nr_ivals).ndiv > ndiv_max && 2*ivals(nr_ivals).ndiv > ivals(nr_ivals).rdepth ), warning (sprintf ('possibly divergent integral in the interval [%e,%e]! (h=%e)',m,b,b-m)); int = sign(int) * Inf; return; endif; % move the last interval back up ivals(i_max) = ivals(nr_ivals); nr_ivals = nr_ivals - 1; 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 500 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)