... | ... |
@@ -154,21 +154,21 @@ def calc_V(xi, n): |
154 | 154 |
V[i] *= np.sqrt(i + 0.5) |
155 | 155 |
return np.array(V).T |
156 | 156 |
|
157 |
-# compute the coefficients |
|
157 |
+# Compute the Vandermonde-like matrix and its inverse. |
|
158 | 158 |
V = [calc_V(*args) for args in zip(xi, n)] |
159 | 159 |
V_inv = list(map(inv, V)) |
160 | 160 |
Vcond = list(map(cond, V)) |
161 | 161 |
|
162 |
-# shift matrix |
|
162 |
+# Compute the shift matrices. |
|
163 | 163 |
T_lr = [V_inv[3] @ calc_V((xi[3] + a) / 2, n[3]) for a in [-1, 1]] |
164 | 164 |
|
165 | 165 |
# If the relative difference between two consecutive approximations is |
166 | 166 |
# lower than this value, the error estimate is considered reliable. |
167 | 167 |
# See section 6.2 of Pedro Gonnet's thesis. |
168 | 168 |
hint = 0.1 |
169 |
- |
|
170 |
-# compute the integral |
|
171 |
-w = np.sqrt(0.5) # legendre |
|
169 |
+ndiv_max = 20 |
|
170 |
+max_ivals = 200 |
|
171 |
+sqrt_one_half = np.sqrt(0.5) |
|
172 | 172 |
|
173 | 173 |
|
174 | 174 |
k = np.arange(n[3]) |
... | ... |
@@ -176,6 +176,7 @@ alpha = np.sqrt((k+1)**2 / (2*k+1) / (2*k+3)) |
176 | 176 |
gamma = np.concatenate([[0, 0], np.sqrt(k[2:]**2 / (4*k[2:]**2-1))]) |
177 | 177 |
|
178 | 178 |
def _downdate(c, nans, depth): |
179 |
+ # This is algorithm 5 from the thesis of Pedro Gonnet. |
|
179 | 180 |
b = b_def[depth].copy() |
180 | 181 |
m = n[depth] - 1 |
181 | 182 |
for i in nans: |
... | ... |
@@ -220,78 +221,80 @@ class DivergentIntegralError(ValueError): |
220 | 221 |
|
221 | 222 |
|
222 | 223 |
class _Interval: |
223 |
- __slots__ = ['a', 'b', 'c', 'fx', 'igral', 'err', 'depth', 'rdepth', 'ndiv'] |
|
224 |
+ __slots__ = ['a', 'b', 'c', 'fx', 'igral', 'err', 'depth', 'rdepth', 'ndiv', |
|
225 |
+ 'c00'] |
|
226 |
+ |
|
227 |
+ def __init__(self, a, b, depth, rdepth): |
|
228 |
+ self.a = a |
|
229 |
+ self.b = b |
|
230 |
+ self.depth = depth |
|
231 |
+ self.rdepth = rdepth |
|
232 |
+ |
|
233 |
+ def points(self): |
|
234 |
+ a = self.a |
|
235 |
+ b = self.b |
|
236 |
+ return (a + b) / 2 + (b - a) * xi[self.depth] / 2 |
|
224 | 237 |
|
225 | 238 |
@classmethod |
226 | 239 |
def make_first(cls, f, a, b, depth=2): |
227 |
- points = (a+b)/2 + (b-a) * xi[depth] / 2 |
|
228 |
- fx = f(points) |
|
229 |
- ival = _Interval() |
|
230 |
- ival.c = np.zeros((4, n[3])) |
|
231 |
- ival.c[depth, :n[depth]] = _calc_coeffs(fx, depth) |
|
240 |
+ ival = _Interval(a, b, depth, 1) |
|
241 |
+ fx = f(ival.points()) |
|
242 |
+ ival.c = _calc_coeffs(fx, depth) |
|
243 |
+ ival.c00 = 0.0 |
|
232 | 244 |
ival.fx = fx |
233 |
- ival.a = a |
|
234 |
- ival.b = b |
|
235 |
- ival.depth = depth |
|
236 | 245 |
ival.ndiv = 0 |
237 | 246 |
ival.rdepth = 1 |
238 |
- return ival, len(points) |
|
239 |
- |
|
240 |
- def split(self, f, ndiv_max=20): |
|
241 |
- a = self.a |
|
242 |
- b = self.b |
|
243 |
- m = (a + b) / 2 |
|
244 |
- f_center = self.fx[(len(self.fx)-1)//2] |
|
245 |
- |
|
246 |
- ivals = [] |
|
247 |
- nr_points = 0 |
|
248 |
- for aa, bb, f_left, f_right, T in [ |
|
249 |
- (a, m, self.fx[0], f_center, T_lr[0]), |
|
250 |
- (m, b, f_center, self.fx[-1], T_lr[1])]: |
|
251 |
- ival = _Interval() |
|
252 |
- ivals.append(ival) |
|
253 |
- ival.a = aa |
|
254 |
- ival.b = bb |
|
255 |
- ival.depth = 0 |
|
256 |
- ival.rdepth = self.rdepth + 1 |
|
257 |
- ival.c = np.zeros((4, n[3])) |
|
258 |
- fx = np.concatenate( |
|
259 |
- ([f_left], |
|
260 |
- f((aa + bb) / 2 + (bb - aa) * xi[0][1:-1] / 2), |
|
261 |
- [f_right])) |
|
262 |
- nr_points += n[0] - 2 |
|
263 |
- |
|
264 |
- ival.c[0, :n[0]] = c_new = _calc_coeffs(fx, 0) |
|
247 |
+ return ival, n[depth] |
|
248 |
+ |
|
249 |
+ def calc_igral_and_err(self, c_old): |
|
250 |
+ self.c = c_new = _calc_coeffs(self.fx, self.depth) |
|
251 |
+ c_diff = np.zeros(max(len(c_old), len(c_new))) |
|
252 |
+ c_diff[:len(c_old)] = c_old |
|
253 |
+ c_diff[:len(c_new)] -= c_new |
|
254 |
+ c_diff = norm(c_diff) |
|
255 |
+ w = self.b - self.a |
|
256 |
+ self.igral = w * c_new[0] * sqrt_one_half |
|
257 |
+ self.err = w * c_diff |
|
258 |
+ return c_diff |
|
259 |
+ |
|
260 |
+ def split(self, f): |
|
261 |
+ m = (self.a + self.b) / 2 |
|
262 |
+ f_center = self.fx[(len(self.fx) - 1) // 2] |
|
263 |
+ |
|
264 |
+ rdepth = self.rdepth + 1 |
|
265 |
+ ivals = [_Interval(self.a, m, 0, rdepth), |
|
266 |
+ _Interval(m, self.b, 0, rdepth)] |
|
267 |
+ points = np.concatenate([ival.points()[1:-1] for ival in ivals]) |
|
268 |
+ nr_points = len(points) |
|
269 |
+ fxs = np.empty((2, n[0])) |
|
270 |
+ fxs[:, 0] = self.fx[0], f_center |
|
271 |
+ fxs[:, -1] = f_center, self.fx[-1] |
|
272 |
+ fxs[:, 1:-1] = f(points).reshape((2, -1)) |
|
273 |
+ |
|
274 |
+ for ival, fx, T in zip(ivals, fxs, T_lr): |
|
265 | 275 |
ival.fx = fx |
276 |
+ ival.calc_igral_and_err(T[:, :self.c.shape[0]] @ self.c) |
|
266 | 277 |
|
267 |
- c_old = T @ self.c[self.depth] |
|
268 |
- c_diff = norm(ival.c[0] - c_old) |
|
269 |
- ival.err = (bb - aa) * c_diff |
|
270 |
- ival.igral = (bb - aa) * ival.c[0, 0] * w |
|
271 |
- ival.ndiv = (self.ndiv |
|
272 |
- + (abs(self.c[0, 0]) > 0 |
|
273 |
- and ival.c[0, 0] / self.c[0, 0] > 2)) |
|
278 |
+ ival.c00 = ival.c[0] |
|
279 |
+ ival.ndiv = (self.ndiv + (self.c00 and ival.c00 / self.c00 > 2)) |
|
274 | 280 |
if ival.ndiv > ndiv_max and 2*ival.ndiv > ival.rdepth: |
275 |
- return (aa, bb, bb-aa), nr_points |
|
281 |
+ # Signal a divergent integral. |
|
282 |
+ return (ival.a, ival.b, ival.b - ival.a), nr_points |
|
276 | 283 |
|
277 | 284 |
return ivals, nr_points |
278 | 285 |
|
279 | 286 |
def refine(self, f): |
280 | 287 |
"""Increase degree of interval.""" |
281 | 288 |
self.depth = depth = self.depth + 1 |
282 |
- a = self.a |
|
283 |
- b = self.b |
|
284 |
- points = (a+b)/2 + (b-a)*xi[depth]/2 |
|
289 |
+ points = self.points() |
|
285 | 290 |
fx = np.empty(n[depth]) |
286 | 291 |
fx[0:n[depth]:2] = self.fx |
287 | 292 |
fx[1:n[depth]-1:2] = f(points[1:n[depth]-1:2]) |
288 |
- fx = fx[:n[depth]] |
|
289 |
- self.c[depth, :n[depth]] = c_new = _calc_coeffs(fx, depth) |
|
290 | 293 |
self.fx = fx |
291 |
- c_diff = norm(self.c[depth - 1] - self.c[depth]) |
|
292 |
- self.err = (b-a) * c_diff |
|
293 |
- self.igral = (b-a) * w * c_new[0] |
|
294 |
- nc = norm(c_new) |
|
294 |
+ |
|
295 |
+ c_diff = self.calc_igral_and_err(self.c) |
|
296 |
+ |
|
297 |
+ nc = norm(self.c) |
|
295 | 298 |
split = nc > 0 and c_diff / nc > hint |
296 | 299 |
|
297 | 300 |
return points, split, n[depth] - n[depth - 1] |
... | ... |
@@ -326,15 +329,13 @@ def algorithm_4 (f, a, b, tol): |
326 | 329 |
Mathematical Software, 37 (3), art. no. 26, 2008. |
327 | 330 |
""" |
328 | 331 |
|
329 |
- # initialize the first interval |
|
330 | 332 |
ival, nr_points = _Interval.make_first(f, a, b) |
331 | 333 |
|
332 | 334 |
ivals = [ival] |
333 |
- igral_final = 0 |
|
334 |
- err_final = 0 |
|
335 |
+ igral_excess = 0 |
|
336 |
+ err_excess = 0 |
|
335 | 337 |
i_max = 0 |
336 | 338 |
|
337 |
- # main loop |
|
338 | 339 |
while True: |
339 | 340 |
if ivals[i_max].depth == 3: |
340 | 341 |
split = True |
... | ... |
@@ -342,13 +343,14 @@ def algorithm_4 (f, a, b, tol): |
342 | 343 |
points, split, nr_points_inc = ivals[i_max].refine(f) |
343 | 344 |
nr_points += nr_points_inc |
344 | 345 |
|
345 |
- # can we safely ignore this interval? |
|
346 | 346 |
if (points[1] <= points[0] |
347 | 347 |
or points[-1] <= points[-2] |
348 | 348 |
or ivals[i_max].err < (abs(ivals[i_max].igral) * eps |
349 | 349 |
* Vcond[ivals[i_max].depth])): |
350 |
- err_final += ivals[i_max].err |
|
351 |
- igral_final += ivals[i_max].igral |
|
350 |
+ # The interval is too small, remove it while remembering the excess |
|
351 |
+ # integral and error. |
|
352 |
+ err_excess += ivals[i_max].err |
|
353 |
+ igral_excess += ivals[i_max].igral |
|
352 | 354 |
ivals[i_max] = ivals[-1] |
353 | 355 |
ivals.pop() |
354 | 356 |
elif split: |
... | ... |
@@ -363,11 +365,11 @@ def algorithm_4 (f, a, b, tol): |
363 | 365 |
ivals.extend(result) |
364 | 366 |
ivals[i_max] = ivals.pop() |
365 | 367 |
|
366 |
- # compute the running err and new max |
|
368 |
+ # Compute the total error and new max. |
|
367 | 369 |
i_max = 0 |
368 | 370 |
i_min = 0 |
369 |
- err = err_final |
|
370 |
- igral = igral_final |
|
371 |
+ err = err_excess |
|
372 |
+ igral = igral_excess |
|
371 | 373 |
for i in range(len(ivals)): |
372 | 374 |
if ivals[i].err > ivals[i_max].err: |
373 | 375 |
i_max = i |
... | ... |
@@ -376,24 +378,22 @@ def algorithm_4 (f, a, b, tol): |
376 | 378 |
err += ivals[i].err |
377 | 379 |
igral += ivals[i].igral |
378 | 380 |
|
379 |
- # nuke smallest element if stack is larger than 200 |
|
380 |
- if len(ivals) > 200: |
|
381 |
- err_final += ivals[i_min].err |
|
382 |
- igral_final += ivals[i_min].igral |
|
381 |
+ # If there are too many intervals, remove the one with smallest |
|
382 |
+ # contribution to the error. |
|
383 |
+ if len(ivals) > max_ivals: |
|
384 |
+ err_excess += ivals[i_min].err |
|
385 |
+ igral_excess += ivals[i_min].igral |
|
383 | 386 |
ivals[i_min] = ivals[-1] |
384 | 387 |
ivals.pop() |
385 | 388 |
if i_max == len(ivals): |
386 | 389 |
i_max = i_min |
387 | 390 |
|
388 |
- # get up and leave? |
|
389 | 391 |
if (err == 0 |
390 | 392 |
or err < abs(igral) * tol |
391 |
- or (err_final > abs(igral) * tol |
|
392 |
- and err - err_final < abs(igral) * tol) |
|
393 |
+ or (err_excess > abs(igral) * tol |
|
394 |
+ and err - err_excess < abs(igral) * tol) |
|
393 | 395 |
or not ivals): |
394 |
- break |
|
395 |
- |
|
396 |
- return igral, err, nr_points, ivals |
|
396 |
+ return igral, err, nr_points |
|
397 | 397 |
|
398 | 398 |
|
399 | 399 |
################ Tests ################ |
... | ... |
@@ -417,16 +417,16 @@ def f21(x): |
417 | 417 |
return y |
418 | 418 |
|
419 | 419 |
|
420 |
-def f63(x, u, e): |
|
421 |
- return abs(x-u)**e |
|
420 |
+def f63(x, alpha, beta): |
|
421 |
+ return abs(x - beta) ** alpha |
|
422 | 422 |
|
423 | 423 |
|
424 |
-def F63(x, u, e): |
|
425 |
- return (x-u) * abs(x-u)**e / (e+1) |
|
424 |
+def F63(x, alpha, beta): |
|
425 |
+ return (x - beta) * abs(x - beta) ** alpha / (alpha + 1) |
|
426 | 426 |
|
427 | 427 |
|
428 | 428 |
def fdiv(x): |
429 |
- return abs(x - 0.987654321)**-1.1 |
|
429 |
+ return abs(x - 0.987654321) ** -1.1 |
|
430 | 430 |
|
431 | 431 |
|
432 | 432 |
def f_one_with_nan(x): |
... | ... |
@@ -531,33 +531,33 @@ def test_integration(): |
531 | 531 |
|
532 | 532 |
def test_analytic(n=200): |
533 | 533 |
def f(x): |
534 |
- return f63(x, u, e) |
|
534 |
+ return f63(x, alpha, beta) |
|
535 | 535 |
|
536 | 536 |
def F(x): |
537 |
- return F63(x, u, e) |
|
537 |
+ return F63(x, alpha, beta) |
|
538 | 538 |
|
539 | 539 |
old_settings = np.seterr(all='ignore') |
540 | 540 |
|
541 | 541 |
np.random.seed(123) |
542 | 542 |
params = np.empty((n, 2)) |
543 |
- params[:, 0] = np.random.random_sample(n) |
|
544 |
- params[:, 1] = np.linspace(-0.5, -1.5, n) |
|
543 |
+ params[:, 0] = np.linspace(-0.5, -1.5, n) |
|
544 |
+ params[:, 1] = np.random.random_sample(n) |
|
545 | 545 |
|
546 | 546 |
false_negatives = 0 |
547 | 547 |
false_positives = 0 |
548 | 548 |
|
549 |
- for u, e in params: |
|
549 |
+ for alpha, beta in params: |
|
550 | 550 |
try: |
551 | 551 |
igral, err, nr_points = algorithm_4(f, 0, 1, 1e-3) |
552 | 552 |
except DivergentIntegralError: |
553 |
- assert e < -0.8 |
|
554 |
- false_negatives += e > -1 |
|
553 |
+ assert alpha < -0.8 |
|
554 |
+ false_negatives += alpha > -1 |
|
555 | 555 |
else: |
556 |
- if e <= -1: |
|
556 |
+ if alpha <= -1: |
|
557 | 557 |
false_positives += 1 |
558 | 558 |
else: |
559 | 559 |
igral_exact = F(1) - F(0) |
560 |
- assert e < -0.8 or abs(igral - igral_exact) < err |
|
560 |
+ assert alpha < -0.8 or abs(igral - igral_exact) < err |
|
561 | 561 |
|
562 | 562 |
assert false_negatives < 0.05 * n |
563 | 563 |
assert false_positives < 0.05 * n |