Browse code

cwg's code to commit 4d84d696d59f99b6fb29519cafc7a9082700dfe9

Bas Nijholt authored on 29/11/2017 14:17:18
Showing 1 changed files
... ...
@@ -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