Mercurial > octave
comparison scripts/general/quadgk.m @ 31058:12f8fb75fc30
quadgk.m: Overhaul function to add new "ArrayValued" option (bug #62468)
* quadgk.m: Add "ArrayValued" option to docstring. Re-phrase bits of existing
documentation for clarity. Rename input validation variable "str" to "prop"
for clarity. Add decode for "arrayvalued" property. If limits of integration
are high to low then reverse them in function rather than recursively calling
quadgk(). Add FIXME note about missing input validation for several properties.
Delete variable 'h0' which was never used. New code branch if "ArrayValued"
is true which uses same code strategy as for normal quadgk, but subfunction
__quadgk_eval_array__ for evaluating the function in a vectorized manner.
Add BIST tests for "ArrayValued" input.
* quadgk.m (__quadgk_eval__): Eliminate "too_close" output and code to
calculate "too_close" which was never used.
* quadgk.m (__quadgk_eval_array__): New subfunction.
author | Michael Leitner <michael.leitner@frm2.tum.de>, Rik <rik@octave.org> |
---|---|
date | Thu, 02 Jun 2022 08:45:10 -0700 |
parents | 4298af839d20 |
children | 451fb63a10a0 |
comparison
equal
deleted
inserted
replaced
31057:b437597ec652 | 31058:12f8fb75fc30 |
---|---|
25 | 25 |
26 ## -*- texinfo -*- | 26 ## -*- texinfo -*- |
27 ## @deftypefn {} {@var{q} =} quadgk (@var{f}, @var{a}, @var{b}) | 27 ## @deftypefn {} {@var{q} =} quadgk (@var{f}, @var{a}, @var{b}) |
28 ## @deftypefnx {} {@var{q} =} quadgk (@var{f}, @var{a}, @var{b}, @var{abstol}) | 28 ## @deftypefnx {} {@var{q} =} quadgk (@var{f}, @var{a}, @var{b}, @var{abstol}) |
29 ## @deftypefnx {} {@var{q} =} quadgk (@var{f}, @var{a}, @var{b}, @var{abstol}, @var{trace}) | 29 ## @deftypefnx {} {@var{q} =} quadgk (@var{f}, @var{a}, @var{b}, @var{abstol}, @var{trace}) |
30 ## @deftypefnx {} {@var{q} =} quadgk (@var{f}, @var{a}, @var{b}, @var{prop}, @var{val}, @dots{}) | 30 ## @deftypefnx {} {@var{q} =} quadgk (@var{f}, @var{a}, @var{b}, "@var{prop}", @var{val}, @dots{}) |
31 ## @deftypefnx {} {[@var{q}, @var{err}] =} quadgk (@dots{}) | 31 ## @deftypefnx {} {[@var{q}, @var{err}] =} quadgk (@dots{}) |
32 ## | 32 ## |
33 ## Numerically evaluate the integral of @var{f} from @var{a} to @var{b} | 33 ## Numerically evaluate the integral of @var{f} from @var{a} to @var{b} |
34 ## using adaptive @nospell{Gauss-Kronrod} quadrature. | 34 ## using adaptive @nospell{Gauss-Kronrod} quadrature. |
35 ## | 35 ## |
36 ## @var{f} is a function handle, inline function, or string containing the name | 36 ## @var{f} is a function handle, inline function, or string containing the name |
37 ## of the function to evaluate. The function @var{f} must be vectorized and | 37 ## of the function to evaluate. The function @var{f} must be vectorized and |
38 ## return a vector of output values when given a vector of input values. | 38 ## return a vector of output values when given a vector of input values (See |
39 ## property @qcode{"ArrayValued"} for an exception to this rule). | |
39 ## | 40 ## |
40 ## @var{a} and @var{b} are the lower and upper limits of integration. Either | 41 ## @var{a} and @var{b} are the lower and upper limits of integration. Either |
41 ## or both limits may be infinite or contain weak end singularities. Variable | 42 ## or both limits may be infinite or contain weak end singularities. Variable |
42 ## transformation will be used to treat any infinite intervals and weaken the | 43 ## transformation will be used to treat any infinite intervals and weaken the |
43 ## singularities. For example: | 44 ## singularities. For example: |
58 ## interval and evaluating each subinterval. If @var{trace} is true then after | 59 ## interval and evaluating each subinterval. If @var{trace} is true then after |
59 ## computing each of these partial integrals display: (1) the number of | 60 ## computing each of these partial integrals display: (1) the number of |
60 ## subintervals at this step, (2) the current estimate of the error @var{err}, | 61 ## subintervals at this step, (2) the current estimate of the error @var{err}, |
61 ## (3) the current estimate for the integral @var{q}. | 62 ## (3) the current estimate for the integral @var{q}. |
62 ## | 63 ## |
63 ## The behavior of the algorithm can be configured by passing arguments | 64 ## The behavior of the algorithm can be configured by passing arguments to |
64 ## to @code{quadgk} as pairs @qcode{"@var{prop}", @var{val}}. Valid properties | 65 ## @code{quadgk} as pairs @qcode{"@var{prop}", @var{val}}. Valid properties |
65 ## are | 66 ## are |
66 ## | 67 ## |
67 ## @table @code | 68 ## @table @code |
68 ## @item AbsTol | 69 ## @item AbsTol |
69 ## Define the absolute error tolerance for the quadrature. The default | 70 ## Define the absolute error tolerance for the quadrature. The default |
71 ## | 72 ## |
72 ## @item RelTol | 73 ## @item RelTol |
73 ## Define the relative error tolerance for the quadrature. The default | 74 ## Define the relative error tolerance for the quadrature. The default |
74 ## relative tolerance is 1e-6 (1e-4 for single). | 75 ## relative tolerance is 1e-6 (1e-4 for single). |
75 ## | 76 ## |
77 ## @item ArrayValued | |
78 ## When set to true, the function @var{f} produces an array output for a scalar | |
79 ## input. The default is false which requires that @var{f} produce an output | |
80 ## that is the same size as the input. For example, | |
81 ## | |
82 ## @example | |
83 ## quadgk (@@(x) x .^ (1:5), 0, 2, "ArrayValued", 1) | |
84 ## @end example | |
85 ## | |
86 ## will integrate @code{[x.^1, x.^2, x.^3, x.^4, x.^5]} in one function call | |
87 ## rather than having to repeatedly define a single anonymous function and | |
88 ## use a normal invocation of @code{quadgk}. | |
89 ## | |
90 ## @item WayPoints | |
91 ## Specify points which will become endpoints for subintervals in the | |
92 ## algorithm which can result in significantly improved estimation of the error | |
93 ## in the integral, faster computation, or both. It can be useful to specify | |
94 ## more subintervals around a region where the integrand is rapidly changing or | |
95 ## to flag locations where there is a discontinuity in the first derivative | |
96 ## of the function. For example, the signum function has a discontinuity at | |
97 ## @code{x == 0} and by specifying a waypoint | |
98 ## | |
99 ## @example | |
100 ## quadgk (@@(x) sign (x), -0.5, 1, "Waypoints", [0]) | |
101 ## @end example | |
102 ## | |
103 ## @noindent | |
104 ## the error bound is reduced from 4e-7 to 1e-13. | |
105 ## | |
106 ## If the function has @strong{singularities} within the region of integration | |
107 ## those should not be addressed with waypoints. Instead, the overall integral | |
108 ## should be decomposed into a sum of several smaller integrals such that the | |
109 ## singularity occurs as one of the bounds of integration in the call to | |
110 ## @code{quadgk}. | |
111 ## | |
112 ## If any of the waypoints are complex then contour integration is performed as | |
113 ## documented below. | |
114 ## | |
76 ## @item MaxIntervalCount | 115 ## @item MaxIntervalCount |
77 ## @code{quadgk} initially subdivides the interval on which to perform the | 116 ## @code{quadgk} initially subdivides the interval on which to perform the |
78 ## quadrature into 10 intervals. Subintervals that have an unacceptable error | 117 ## quadrature into 10 intervals or, if WayPoints are given, at each waypoint. |
79 ## are subdivided and re-evaluated. If the number of subintervals exceeds 650 | 118 ## Subintervals that have an unacceptable error are subdivided and |
80 ## subintervals at any point then a poor convergence is signaled and the | 119 ## re-evaluated. If the number of subintervals exceeds 650 subintervals at any |
81 ## current estimate of the integral is returned. The property | 120 ## point then a poor convergence is signaled and the current estimate of the |
82 ## @qcode{"MaxIntervalCount"} can be used to alter the number of subintervals | 121 ## integral is returned. The property @qcode{"MaxIntervalCount"} can be used |
83 ## that can exist before exiting. | 122 ## to alter the number of subintervals that can exist before exiting. |
84 ## | |
85 ## @item WayPoints | |
86 ## Discontinuities in the first derivative of the function to integrate can be | |
87 ## flagged with the @qcode{"WayPoints"} property. This forces the ends of a | |
88 ## subinterval to fall on the breakpoints of the function and can result in | |
89 ## significantly improved estimation of the error in the integral, faster | |
90 ## computation, or both. For example, | |
91 ## | |
92 ## @example | |
93 ## quadgk (@@(x) abs (1 - x.^2), 0, 2, "Waypoints", 1) | |
94 ## @end example | |
95 ## | |
96 ## @noindent | |
97 ## signals the breakpoint in the integrand at @code{@var{x} = 1}. | |
98 ## | 123 ## |
99 ## @item Trace | 124 ## @item Trace |
100 ## If logically true @code{quadgk} prints information on the convergence of the | 125 ## If logically true @code{quadgk} prints information on the convergence of the |
101 ## quadrature at each iteration. | 126 ## quadrature at each iteration. |
102 ## @end table | 127 ## @end table |
103 ## | 128 ## |
104 ## If any of @var{a}, @var{b}, or @var{waypoints} is complex then the | 129 ## If any of @var{a}, @var{b}, or @var{waypoints} is complex then the |
105 ## quadrature is treated as a contour integral along a piecewise continuous | 130 ## quadrature is treated as a contour integral along a piecewise linear |
106 ## path defined by | 131 ## path defined by |
107 ## @code{[@var{a}, @var{waypoints}(1), @var{waypoints}(2), @dots{}, @var{b}]}. | 132 ## @code{[@var{a}, @var{waypoints}(1), @var{waypoints}(2), @dots{}, @var{b}]}. |
108 ## In this case the integral is assumed to have no edge singularities. For | 133 ## In this case the integral is assumed to have no edge singularities. For |
109 ## example, | 134 ## example, |
110 ## | 135 ## |
134 ## @cite{"Vectorized adaptive quadrature in @sc{matlab}"}, Journal of | 159 ## @cite{"Vectorized adaptive quadrature in @sc{matlab}"}, Journal of |
135 ## Computational and Applied Mathematics, pp.@: 131--140, Vol 211, Issue 2, | 160 ## Computational and Applied Mathematics, pp.@: 131--140, Vol 211, Issue 2, |
136 ## Feb 2008. | 161 ## Feb 2008. |
137 ## | 162 ## |
138 ## @seealso{quad, quadv, quadl, quadcc, trapz, dblquad, triplequad, integral, | 163 ## @seealso{quad, quadv, quadl, quadcc, trapz, dblquad, triplequad, integral, |
139 ## integral2, integral3} | 164 ## integral2, integral3} |
140 ## @end deftypefn | 165 ## @end deftypefn |
141 | 166 |
142 function [q, err] = quadgk (f, a, b, varargin) | 167 function [q, err] = quadgk (f, a, b, varargin) |
143 | 168 |
144 if (nargin < 3) | 169 if (nargin < 3) |
145 print_usage (); | 170 print_usage (); |
146 endif | |
147 | |
148 if (b < a) | |
149 ## Reverse integration | |
150 [q, err] = quadgk (f, b, a, varargin{:}); | |
151 q = -q; | |
152 return; | |
153 endif | 171 endif |
154 | 172 |
155 abstol = []; | 173 abstol = []; |
156 reltol = []; | 174 reltol = []; |
157 waypoints = []; | 175 waypoints = []; |
158 maxint = 650; | 176 maxint = 650; |
177 arrayvalued = false; | |
159 trace = false; | 178 trace = false; |
160 | 179 |
161 ## Parse options if present. | 180 ## Parse options if present. |
162 if (nargin > 3) | 181 if (nargin > 3) |
163 if (! ischar (varargin{1})) | 182 if (! ischar (varargin{1})) |
179 idx = 1; | 198 idx = 1; |
180 while (idx < nargin - 3) | 199 while (idx < nargin - 3) |
181 if (! ischar (varargin{idx})) | 200 if (! ischar (varargin{idx})) |
182 error ("quadgk: property PROP must be a string"); | 201 error ("quadgk: property PROP must be a string"); |
183 endif | 202 endif |
184 str = varargin{idx++}; | 203 prop = varargin{idx++}; |
185 switch (tolower (str)) | 204 switch (tolower (prop)) |
186 case "reltol" | 205 case "reltol" |
187 reltol = varargin{idx++}; | 206 reltol = varargin{idx++}; |
188 case "abstol" | 207 case "abstol" |
189 abstol = varargin{idx++}; | 208 abstol = varargin{idx++}; |
190 case "waypoints" | 209 case "waypoints" |
191 waypoints = varargin{idx++}(:); | 210 waypoints = varargin{idx++}(:); |
192 if (isreal (waypoints)) | |
193 waypoints(waypoints < a | waypoints > b) = []; | |
194 endif | |
195 case "maxintervalcount" | 211 case "maxintervalcount" |
196 maxint = varargin{idx++}; | 212 maxint = varargin{idx++}; |
213 case "arrayvalued" | |
214 arrayvalued = varargin{idx++}; | |
197 case "trace" | 215 case "trace" |
198 trace = varargin{idx++}; | 216 trace = varargin{idx++}; |
199 otherwise | 217 otherwise |
200 error ("quadgk: unknown property '%s'", str); | 218 error ("quadgk: unknown property '%s'", prop); |
201 endswitch | 219 endswitch |
202 endwhile | 220 endwhile |
203 endif | 221 endif |
222 endif | |
223 | |
224 reverse = 1; | |
225 contour = iscomplex (a) || iscomplex (b) || iscomplex (waypoints); | |
226 if ((b < a) && ! contour) | |
227 ## Reverse integration | |
228 [b, a] = deal (a, b); | |
229 waypoints = sort (waypoints(waypoints > a & waypoints < b)); | |
230 reverse = -1; | |
204 endif | 231 endif |
205 | 232 |
206 issingle = (isa (a, "single") || isa (b, "single") | 233 issingle = (isa (a, "single") || isa (b, "single") |
207 || isa (waypoints, "single")); | 234 || isa (waypoints, "single")); |
208 | 235 |
216 reltol = ifelse (issingle, 1e-4, 1e-6); | 243 reltol = ifelse (issingle, 1e-4, 1e-6); |
217 elseif (! isscalar (reltol) || reltol < 0) | 244 elseif (! isscalar (reltol) || reltol < 0) |
218 error ("quadgk: RELTOL must be a scalar >=0"); | 245 error ("quadgk: RELTOL must be a scalar >=0"); |
219 endif | 246 endif |
220 | 247 |
248 ## FIXME: No validation of inputs MaxIntervalCount, Waypoints, ArrayValued, | |
249 ## Trace. | |
250 | |
221 ## Convert function given as a string to a function handle | 251 ## Convert function given as a string to a function handle |
222 if (ischar (f)) | 252 if (ischar (f)) |
223 f = @(x) feval (f, x); | 253 f = @(x) feval (f, x); |
224 endif | 254 endif |
225 | 255 |
226 ## Use variable substitution to weaken endpoint singularities and | 256 ## Use variable substitution to weaken endpoint singularities and |
227 ## to perform integration with endpoints at infinity. | 257 ## to perform integration with endpoints at infinity. |
228 ## No transform for contour integrals. | 258 ## No transform for contour integrals. |
229 if (iscomplex (a) || iscomplex (b) || iscomplex (waypoints)) | 259 if (contour) |
230 ## contour integral, no transform | 260 ## contour integral, no transform |
231 subs = [a; waypoints; b]; | 261 subs = [a; waypoints; b]; |
232 h = sum (abs (diff (subs))); | 262 h = sum (abs (diff (subs))); |
233 h0 = h; | |
234 trans = @(t) t; | 263 trans = @(t) t; |
264 ## Ensure f is always vectorized even if specified as, e.g., f = @(x) 1; | |
265 f = @(t) f (t) + 0*t; | |
235 elseif (isinf (a) && isinf (b)) | 266 elseif (isinf (a) && isinf (b)) |
236 ## Standard infinite to finite integral transformation. | 267 ## Standard infinite to finite integral transformation. |
237 ## \int_{-\infinity_^\infinity f(x) dx = \int_-1^1 f (g(t)) g'(t) dt | 268 ## \int_{-\infinity_^\infinity f(x) dx = \int_-1^1 f (g(t)) g'(t) dt |
238 ## where | 269 ## where |
239 ## g(t) = t / (1 - t^2) | 270 ## g(t) = t / (1 - t^2) |
245 subs = [-1; trans(waypoints); 1]; | 276 subs = [-1; trans(waypoints); 1]; |
246 else | 277 else |
247 subs = linspace (-1, 1, 11)'; | 278 subs = linspace (-1, 1, 11)'; |
248 endif | 279 endif |
249 h = 2; | 280 h = 2; |
250 h0 = b - a; | |
251 trans = @(t) t ./ (1 - t.^2); | 281 trans = @(t) t ./ (1 - t.^2); |
252 f = @(t) f (t ./ (1 - t .^ 2)) .* (1 + t .^ 2) ./ ((1 - t .^ 2) .^ 2); | 282 f = @(t) f (t ./ (1 - t .^ 2)) .* (1 + t .^ 2) ./ ((1 - t .^ 2) .^ 2); |
253 elseif (isinf (a)) | 283 elseif (isinf (a)) |
254 ## Formula defined in Shampine paper as two separate steps. | 284 ## Formula defined in Shampine paper as two separate steps. |
255 ## One to weaken singularity at finite end, then a second to transform to | 285 ## One to weaken singularity at finite end, then a second to transform to |
271 subs = [-1; trans(tmp); 0]; | 301 subs = [-1; trans(tmp); 0]; |
272 else | 302 else |
273 subs = linspace (-1, 0, 11)'; | 303 subs = linspace (-1, 0, 11)'; |
274 endif | 304 endif |
275 h = 1; | 305 h = 1; |
276 h0 = b - a; | |
277 trans = @(t) b - (t ./ (1 + t)).^2; | 306 trans = @(t) b - (t ./ (1 + t)).^2; |
278 f = @(s) - 2 * s .* f (b - (s ./ (1 + s)) .^ 2) ./ ((1 + s) .^ 3); | 307 f = @(s) - 2 * s .* f (b - (s ./ (1 + s)) .^ 2) ./ ((1 + s) .^ 3); |
279 elseif (isinf (b)) | 308 elseif (isinf (b)) |
280 ## Formula defined in Shampine paper as two separate steps. | 309 ## Formula defined in Shampine paper as two separate steps. |
281 ## One to weaken singularity at finite end, then a second to transform to | 310 ## One to weaken singularity at finite end, then a second to transform to |
296 subs = [0; trans(tmp); 1]; | 325 subs = [0; trans(tmp); 1]; |
297 else | 326 else |
298 subs = linspace (0, 1, 11)'; | 327 subs = linspace (0, 1, 11)'; |
299 endif | 328 endif |
300 h = 1; | 329 h = 1; |
301 h0 = b - a; | |
302 trans = @(t) a + (t ./ (1 - t)).^2; | 330 trans = @(t) a + (t ./ (1 - t)).^2; |
303 f = @(s) 2 * s .* f (a + (s ./ (1 - s)) .^ 2) ./ ((1 - s) .^ 3); | 331 f = @(s) 2 * s .* f (a + (s ./ (1 - s)) .^ 2) ./ ((1 - s) .^ 3); |
304 else | 332 else |
305 ## Davis, Rabinowitz, "Methods of Numerical Integration" p441 2ed. | 333 ## Davis, Rabinowitz, "Methods of Numerical Integration" p441 2ed. |
306 ## Presented in section 5 of the Shampine paper as | 334 ## Presented in section 5 of the Shampine paper as |
318 subs = [-1; trans(waypoints, a, b); 1]; | 346 subs = [-1; trans(waypoints, a, b); 1]; |
319 else | 347 else |
320 subs = linspace (-1, 1, 11)'; | 348 subs = linspace (-1, 1, 11)'; |
321 endif | 349 endif |
322 h = 2; | 350 h = 2; |
323 h0 = b - a; | |
324 trans = @(t) ((b - a) ./ 4) * t .* (3 - t.^2) + (b + a) ./ 2; | 351 trans = @(t) ((b - a) ./ 4) * t .* (3 - t.^2) + (b + a) ./ 2; |
325 f = @(t) f((b - a) ./ 4 .* t .* (3 - t.^2) + (b + a) ./ 2) .* ... | 352 f = @(t) f((b - a) ./ 4 .* t .* (3 - t.^2) + (b + a) ./ 2) .* ... |
326 3 .* (b - a) ./ 4 .* (1 - t.^2); | 353 3 .* (b - a) ./ 4 .* (1 - t.^2); |
327 endif | 354 endif |
328 | 355 |
329 ## Split interval into at least 10 subinterval with a 15 point | 356 ## Split interval into at least 10 subinterval with a 15 point |
330 ## Gauss-Kronrod rule giving a minimum of 150 function evaluations. | 357 ## Gauss-Kronrod rule giving a minimum of 150 function evaluations. |
331 while (length (subs) < 11) | 358 while (numel (subs) < 11) |
332 subs = [subs.' ; subs(1:end-1).' + diff(subs.') ./ 2, NaN](:)(1 : end - 1); | 359 subs = [subs.' ; subs(1:end-1).' + diff(subs.') ./ 2, NaN](:)(1:end-1); |
333 endwhile | 360 endwhile |
334 subs = [subs(1:end-1), subs(2:end)]; | 361 subs = [subs(1:end-1), subs(2:end)]; |
335 | 362 |
336 warn_id = "Octave:quadgk:warning-termination"; | 363 warn_id = "Octave:quadgk:warning-termination"; |
337 | 364 |
338 if (issingle) | 365 if (! arrayvalued) |
339 eps1 = eps ("single"); | 366 ## Initial evaluation of the integrand on the subintervals. |
367 [q_subs, q_errs] = __quadgk_eval__ (f, subs, trans); | |
368 q0 = sum (q_subs); | |
369 err0 = sum (q_errs); | |
370 | |
371 first = true; | |
372 while (true) | |
373 ## Quit if any evaluations are not finite (Inf or NaN). | |
374 if (any (! isfinite (q_subs))) | |
375 warning (warn_id, "quadgk: non-finite integrand encountered"); | |
376 q = q0; | |
377 err = err0; | |
378 break; | |
379 endif | |
380 | |
381 tol = max (abstol, reltol .* abs (q0)); | |
382 | |
383 ## If the global error estimate is met then exit. | |
384 if (err0 <= tol) | |
385 q = q0; | |
386 err = err0; | |
387 break; | |
388 endif | |
389 | |
390 ## Accept the subintervals that meet the convergence criteria. | |
391 idx = find (abs (q_errs) < tol .* abs (diff (subs, 1, 2)) ./ h); | |
392 if (first) | |
393 q = sum (q_subs(idx)); | |
394 err = sum (q_errs(idx)); | |
395 first = false; | |
396 else | |
397 q0 = q + sum (q_subs); | |
398 err0 = err + sum (q_errs); | |
399 q += sum (q_subs(idx)); | |
400 err += sum (q_errs(idx)); | |
401 endif | |
402 subs(idx,:) = []; | |
403 | |
404 ## If no remaining subintervals then exit. | |
405 if (isempty (subs)) | |
406 break; | |
407 endif | |
408 | |
409 if (trace) | |
410 disp ([rows(subs), err, q0]); | |
411 endif | |
412 | |
413 ## Split remaining subintervals in two | |
414 mid = (subs(:,1) + subs(:,2)) ./ 2; | |
415 subs = [subs(:,1), mid; mid, subs(:,2)]; | |
416 | |
417 ## If the maximum subinterval count is met, then | |
418 ## accept remaining subinterval and exit. | |
419 if (rows (subs) > maxint) | |
420 warning (warn_id, "quadgk: maximum interval count (%d) exceeded", maxint); | |
421 q += sum (q_subs); | |
422 err += sum (q_errs); | |
423 break; | |
424 endif | |
425 | |
426 ## Evaluation of the integrand on the remaining subintervals | |
427 [q_subs, q_errs] = __quadgk_eval__ (f, subs, trans); | |
428 endwhile | |
429 | |
430 if (nargout < 2 && err > max (abstol, reltol * abs (q))) | |
431 warning (warn_id, | |
432 "quadgk: Error tolerance not met. Estimated error %g", err); | |
433 endif | |
434 | |
435 ## Reverse integral if necessary. | |
436 q = reverse * q; | |
437 | |
340 else | 438 else |
341 eps1 = eps ("double"); | 439 ## f is array-valued |
342 endif | 440 sz = size (f (subs(1))); |
343 | 441 |
344 ## Initial evaluation of the integrand on the subintervals | 442 ## Initial evaluation of the integrand on the subintervals |
345 [q_subs, q_errs] = __quadgk_eval__ (f, subs, eps1, trans); | 443 [q_subs, q_errs] = __quadgk_eval_array__ (f, subs, trans, prod (sz)); |
346 q0 = sum (q_subs); | 444 q0 = sum (q_subs, 1); |
347 err0 = sum (q_errs); | 445 err0 = sum (q_errs, 1); |
348 | 446 |
349 first = true; | 447 first = true; |
350 while (true) | 448 while (true) |
351 ## Quit if any evaluations are not finite (Inf or NaN). | 449 ## Quit if any evaluations are not finite (Inf or NaN). |
352 if (any (! isfinite (q_subs))) | 450 if (any (! isfinite (q_subs)(:))) |
353 warning (warn_id, "quadgk: non-finite integrand encountered"); | 451 warning (warn_id, "quadgk: non-finite integrand encountered"); |
354 q = q0; | 452 q = q0; |
355 err = err0; | 453 err = err0; |
356 break; | 454 break; |
455 endif | |
456 | |
457 tol = max (abstol, reltol .* abs (q0)); | |
458 | |
459 ## If the global error estimate is met then exit | |
460 if (err0 <= tol) | |
461 q = q0; | |
462 err = err0; | |
463 break; | |
464 endif | |
465 | |
466 ## Accept subintervals that meet the convergence criteria in all entries. | |
467 idx = find (all (abs (q_errs) < tol .* abs (diff (subs, 1, 2)) ./ h, 2)); | |
468 if (first) | |
469 q = sum (q_subs(idx,:), 1); | |
470 err = sum (q_errs(idx,:), 1); | |
471 first = false; | |
472 else | |
473 q0 = q + sum (q_subs, 1); | |
474 err0 = err + sum (q_errs, 1); | |
475 q += sum (q_subs(idx,:), 1); | |
476 err += sum (q_errs(idx,:), 1); | |
477 endif | |
478 subs(idx,:) = []; | |
479 | |
480 ## If no remaining subintervals exit | |
481 if (isempty (subs)) | |
482 break; | |
483 endif | |
484 | |
485 if (trace) | |
486 disp ([rows(subs), err(1, 1), q0(1, 1)]); # print only first entry | |
487 endif | |
488 | |
489 ## Split remaining subintervals in two | |
490 mid = (subs(:,1) + subs(:,2)) ./ 2; | |
491 subs = [subs(:,1), mid; mid, subs(:,2)]; | |
492 | |
493 ## If the maximum subinterval count is met accept remaining subinterval | |
494 ## and exit | |
495 if (rows (subs) > maxint) | |
496 warning (warn_id, "quadgk: maximum interval count (%d) exceeded", maxint); | |
497 q += sum (q_subs, 1); | |
498 err += sum (q_errs, 1); | |
499 break; | |
500 endif | |
501 | |
502 ## Evaluation of the integrand on the remaining subintervals | |
503 [q_subs, q_errs] = __quadgk_eval_array__ (f, subs, trans, prod (sz)); | |
504 endwhile | |
505 | |
506 i = find (err > max (abstol, reltol * abs (q)), 1); | |
507 if (nargout < 2 && length (i) > 0) | |
508 ## like ind2sub, only as vector. | |
509 j = mod (floor ((i-1)./cumprod ([1 sz(1:end-1)])),sz)+1; | |
510 s = ["(" sprintf("%d,",j)(1:end-1) ")"]; | |
511 warning (warn_id, | |
512 "quadgk: Error tolerance not met. First entry at index %s with estimated error %g", s, err(i)); | |
357 endif | 513 endif |
358 | 514 |
359 tol = max (abstol, reltol .* abs (q0)); | 515 q = reverse * reshape (q, sz); |
360 | 516 err = reshape (err, sz); |
361 ## If the global error estimate is met then exit | |
362 if (err0 <= tol) | |
363 q = q0; | |
364 err = err0; | |
365 break; | |
366 endif | |
367 | |
368 ## Accept the subintervals that meet the convergence criteria. | |
369 idx = find (abs (q_errs) < tol .* abs (diff (subs, [], 2)) ./ h); | |
370 if (first) | |
371 q = sum (q_subs(idx)); | |
372 err = sum (q_errs(idx)); | |
373 first = false; | |
374 else | |
375 q0 = q + sum (q_subs); | |
376 err0 = err + sum (q_errs); | |
377 q += sum (q_subs(idx)); | |
378 err += sum (q_errs(idx)); | |
379 endif | |
380 subs(idx,:) = []; | |
381 | |
382 ## If no remaining subintervals exit | |
383 if (rows (subs) == 0) | |
384 break; | |
385 endif | |
386 | |
387 if (trace) | |
388 disp ([rows(subs), err, q0]); | |
389 endif | |
390 | |
391 ## Split remaining subintervals in two | |
392 mid = (subs(:,2) + subs(:,1)) ./ 2; | |
393 subs = [subs(:,1), mid; mid, subs(:,2)]; | |
394 | |
395 ## If the maximum subinterval count is met accept remaining subinterval | |
396 ## and exit | |
397 if (rows (subs) > maxint) | |
398 warning (warn_id, "quadgk: maximum interval count (%d) exceeded", maxint); | |
399 q += sum (q_subs); | |
400 err += sum (q_errs); | |
401 break; | |
402 endif | |
403 | |
404 ## Evaluation of the integrand on the remaining subintervals | |
405 [q_subs, q_errs] = __quadgk_eval__ (f, subs, eps1, trans); | |
406 endwhile | |
407 | |
408 if (nargout < 2 && err > max (abstol, reltol * abs (q))) | |
409 warning (warn_id, | |
410 "quadgk: Error tolerance not met. Estimated error %g", err); | |
411 endif | 517 endif |
412 | 518 |
413 endfunction | 519 endfunction |
414 | 520 |
415 ## FIXME: too_close output is never used in function that calls this one. | 521 function [q, err] = __quadgk_eval__ (f, subs, trans) |
416 function [q, err, too_close] = __quadgk_eval__ (f, subs, eps1, trans) | |
417 | 522 |
418 ## A (15,7) point pair of Gauss-Kronrod quadrature rules. | 523 ## A (15,7) point pair of Gauss-Kronrod quadrature rules. |
419 ## The abscissa and weights are copied directly from dqk15w.f from quadpack. | 524 ## The abscissa and weights are copied directly from dqk15w.f from quadpack. |
420 | 525 |
421 persistent abscissa = [-0.9914553711208126e+00, -0.9491079123427585e+00, ... | 526 persistent abscissa = [-0.9914553711208126e+00, -0.9491079123427585e+00, ... |
441 diag ([0.1294849661688697e+00, 0.2797053914892767e+00, ... | 546 diag ([0.1294849661688697e+00, 0.2797053914892767e+00, ... |
442 0.3818300505051889e+00, 0.4179591836734694e+00, ... | 547 0.3818300505051889e+00, 0.4179591836734694e+00, ... |
443 0.3818300505051889e+00, 0.2797053914892767e+00, ... | 548 0.3818300505051889e+00, 0.2797053914892767e+00, ... |
444 0.1294849661688697e+00]); | 549 0.1294849661688697e+00]); |
445 | 550 |
446 halfwidth = diff (subs, [], 2) ./ 2; | 551 halfwidth = diff (subs, 1, 2) ./ 2; |
447 center = sum (subs, 2) ./ 2; | 552 center = sum (subs, 2) ./ 2; |
448 t = (halfwidth * abscissa) + center; | 553 t = (halfwidth * abscissa) + center; |
449 x = trans ([t(:,1), t(:,end)]); | 554 x = trans ([t(:,1), t(:,end)]); |
450 | 555 |
451 ## Shampine suggests 100 * eps1, beginning of section 6. | |
452 if (any (abs (diff (x, [], 2) ./ max (abs (x), [], 2))) < 100 * eps1) | |
453 too_close = true; | |
454 q = 0; | |
455 err = 0; | |
456 return; | |
457 endif | |
458 | |
459 too_close = false; | |
460 y = reshape (f (t(:)), size (t)); | 556 y = reshape (f (t(:)), size (t)); |
461 | 557 |
462 ## This is faster than using bsxfun as the * operator can use a | 558 ## This is faster than using bsxfun as the * operator can use a |
463 ## single BLAS call, rather than rows (sub) calls to the @times function. | 559 ## single BLAS call, rather than rows (sub) calls to the @times function. |
464 q = sum (y * weights15, 2) .* halfwidth; | 560 q = sum (y * weights15, 2) .* halfwidth; |
465 err = abs (sum (y(:,2:2:end) * weights7, 2) .* halfwidth - q); | 561 err = abs (sum (y(:,2:2:end) * weights7, 2) .* halfwidth - q); |
562 | |
563 endfunction | |
564 | |
565 function [q, err] = __quadgk_eval_array__ (f, subs, trans, nel) | |
566 | |
567 ## A (15,7) point pair of Gauss-Kronrod quadrature rules. | |
568 ## The abscissa and weights are copied directly from dqk15w.f from quadpack. | |
569 | |
570 persistent abscissa = [-0.9914553711208126e+00, -0.9491079123427585e+00, ... | |
571 -0.8648644233597691e+00, -0.7415311855993944e+00, ... | |
572 -0.5860872354676911e+00, -0.4058451513773972e+00, ... | |
573 -0.2077849550078985e+00, 0.0000000000000000e+00, ... | |
574 0.2077849550078985e+00, 0.4058451513773972e+00, ... | |
575 0.5860872354676911e+00, 0.7415311855993944e+00, ... | |
576 0.8648644233597691e+00, 0.9491079123427585e+00, ... | |
577 0.9914553711208126e+00]; | |
578 | |
579 persistent weights15 = ... | |
580 [0.2293532201052922e-01, 0.6309209262997855e-01, ... | |
581 0.1047900103222502e+00, 0.1406532597155259e+00, ... | |
582 0.1690047266392679e+00, 0.1903505780647854e+00, ... | |
583 0.2044329400752989e+00, 0.2094821410847278e+00, ... | |
584 0.2044329400752989e+00, 0.1903505780647854e+00, ... | |
585 0.1690047266392679e+00, 0.1406532597155259e+00, ... | |
586 0.1047900103222502e+00, 0.6309209262997855e-01, ... | |
587 0.2293532201052922e-01]; | |
588 | |
589 persistent weights7 = ... | |
590 [0.1294849661688697e+00, 0.2797053914892767e+00, ... | |
591 0.3818300505051889e+00, 0.4179591836734694e+00, ... | |
592 0.3818300505051889e+00, 0.2797053914892767e+00, ... | |
593 0.1294849661688697e+00]; | |
594 | |
595 halfwidth = diff (subs, 1, 2) ./ 2; | |
596 center = sum (subs, 2) ./ 2; | |
597 t = (halfwidth * abscissa) + center; | |
598 x = trans ([t(:,1), t(:,end)]); | |
599 | |
600 y = zeros (nel, columns(t), rows(t)); | |
601 for i = 1:rows (t) | |
602 for j = 1:columns(t) | |
603 y(:,j,i) = f (t(i,j))(:); | |
604 endfor | |
605 endfor | |
606 y = permute (y, [2 3 1]); | |
607 | |
608 q = reshape (weights15 * y(:,:), [rows(t), nel]) .* halfwidth; | |
609 err = abs (reshape (weights7 * y(2:2:end,:), rows (t), nel) .* halfwidth - q); | |
466 | 610 |
467 endfunction | 611 endfunction |
468 | 612 |
469 function t = __quadgk_finite_waypoint__ (x, a, b) | 613 function t = __quadgk_finite_waypoint__ (x, a, b) |
470 c = (-4 .* x + 2.* (b + a)) ./ (b - a); | 614 c = (-4 .* x + 2.* (b + a)) ./ (b - a); |
500 %!assert (quadgk (@(x) exp (-x .^ 2),-Inf,0), sqrt (pi)/2, -1e-6) | 644 %!assert (quadgk (@(x) exp (-x .^ 2),-Inf,0), sqrt (pi)/2, -1e-6) |
501 %!test | 645 %!test |
502 %! f = @(x) x .^ 5 .* exp (-x) .* sin (x); | 646 %! f = @(x) x .^ 5 .* exp (-x) .* sin (x); |
503 %! assert (quadgk (f, 0, Inf, "RelTol", 1e-8, "AbsTol", 1e-12), -15, -1e-8); | 647 %! assert (quadgk (f, 0, Inf, "RelTol", 1e-8, "AbsTol", 1e-12), -15, -1e-8); |
504 | 648 |
505 %!test <*62412> | 649 ## Test vector-valued functions |
650 %!assert (quadgk (@(x) [(sin (x)), (sin (2 * x))], 0, pi, "arrayvalued", 1), | |
651 %! [2, 0], 1e-6) | |
652 | |
653 ## Test matrix-valued functions | |
654 %!assert (quadgk (@(x) [ x,x,x; x,1./sqrt(x),x; x,x,x ], 0, 1, "arrayvalued",1), | |
655 %! [0.5,0.5,0.5; 0.5,2,0.5; 0.5,0.5,0.5], 15*1e-6); | |
656 | |
657 ## Bug #62412 | |
658 %!warning <Error tolerance not met> | |
506 %! f = @(t) -1 ./ t.^1.1; | 659 %! f = @(t) -1 ./ t.^1.1; |
507 %! fail ("quadgk (f, 1, Inf)", "warning", "Error tolerance not met"); | 660 %! quadgk (f, 1, Inf); |
508 %! [q, err] = quadgk (f, 1, Inf); | |
509 %! assert (err > 1e-5); | |
510 | 661 |
511 ## Test input validation | 662 ## Test input validation |
512 %!error quadgk (@sin) | 663 %!error quadgk (@sin) |
513 %!error quadgk (@sin, 0) | 664 %!error quadgk (@sin, 0) |
514 %!error <can not pass additional arguments> quadgk (@sin, 0, 1, 1e-6, true, 4) | 665 %!error <can not pass additional arguments> quadgk (@sin, 0, 1, 1e-6, true, 4) |