Browse code

move cquad tests and rewrite

Bas Nijholt authored on 01/11/2017 16:43:19
Showing 1 changed files
1 1
deleted file mode 100644
... ...
@@ -1,541 +0,0 @@
1
-# Copyright 2010 Pedro Gonnet
2
-# Copyright 2017 Christoph Groth
3
-
4
-import warnings
5
-from fractions import Fraction as Frac
6
-from collections import defaultdict
7
-import numpy as np
8
-from numpy.testing import assert_allclose
9
-from numpy.linalg import cond
10
-from scipy.linalg import norm, inv
11
-
12
-
13
-eps = np.spacing(1)
14
-
15
-def legendre(n):
16
-    """Return the first n Legendre polynomials.
17
-
18
-    The polynomials have *standard* normalization, i.e.
19
-    int_{-1}^1 dx L_n(x) L_m(x) = delta(m, n) * 2 / (2 * n + 1).
20
-
21
-    The return value is a list of list of fraction.Fraction instances.
22
-    """
23
-    result = [[Frac(1)], [Frac(0), Frac(1)]]
24
-    if n <= 2:
25
-        return result[:n]
26
-    for i in range(2, n):
27
-        # Use Bonnet's recursion formula.
28
-        new = (i + 1) * [Frac(0)]
29
-        new[1:] = (r * (2*i - 1) for r in result[-1])
30
-        new[:-2] = (n - r * (i - 1) for n, r in zip(new[:-2], result[-2]))
31
-        new[:] = (n / i for n in new)
32
-        result.append(new)
33
-    return result
34
-
35
-
36
-def newton(n):
37
-    """Compute the monomial coefficients of the Newton polynomial over the
38
-    nodes of the n-point Clenshaw-Curtis quadrature rule.
39
-    """
40
-    # The nodes of the Clenshaw-Curtis rule are x_i = -cos(i * Pi / (n-1)).
41
-    # Here, we calculate the coefficients c_i such that sum_i c_i * x^i
42
-    # = prod_i (x - x_i).  The coefficients are thus sums of products of
43
-    # cosines.
44
-    #
45
-    # This routine uses the relation
46
-    #   cos(a) cos(b) = (cos(a + b) + cos(a - b)) / 2
47
-    # to efficiently calculate the coefficients.
48
-    #
49
-    # The dictionary 'terms' descibes the terms that make up the
50
-    # monomial coefficients.  Each item ((d, a), m) corresponds to a
51
-    # term m * cos(a * Pi / n) to be added to prefactor of the
52
-    # monomial x^(n-d).
53
-
54
-    mod = 2 * (n-1)
55
-    terms = defaultdict(int)
56
-    terms[0, 0] += 1
57
-
58
-    for i in range(n):
59
-        newterms = []
60
-        for (d, a), m in terms.items():
61
-            for b in [i, -i]:
62
-                # In order to reduce the number of terms, cosine
63
-                # arguments are mapped back to the inteval [0, pi/2).
64
-                arg = (a + b) % mod
65
-                if arg > n-1:
66
-                    arg = mod - arg
67
-                if arg >= n // 2:
68
-                    if n % 2 and arg == n // 2:
69
-                        # Zero term: ignore
70
-                        continue
71
-                    newterms.append((d + 1, n - 1 - arg, -m))
72
-                else:
73
-                    newterms.append((d + 1, arg, m))
74
-        for d, s, m in newterms:
75
-            terms[d, s] += m
76
-
77
-    c = (n + 1) * [0]
78
-    for (d, a), m in terms.items():
79
-        if m and a != 0:
80
-            raise ValueError("Newton polynomial cannot be represented exactly.")
81
-        c[n - d] += m
82
-        # The check could be removed and the above line replaced by
83
-        # the following, but then the result would be no longer exact.
84
-        # c[n - d] += m * np.cos(a * np.pi / (n - 1))
85
-
86
-    cf = np.array(c, float)
87
-    assert all(int(cfe) == ce for cfe, ce in zip(cf, c)), 'Precision loss'
88
-
89
-    cf /= 2.**np.arange(n, -1, -1)
90
-    return cf
91
-
92
-
93
-def scalar_product(a, b):
94
-    """Compute the polynomial scalar product int_-1^1 dx a(x) b(x).
95
-
96
-    The args must be sequences of polynomial coefficients.  This
97
-    function is careful to use the input data type for calculations.
98
-    """
99
-    la = len(a)
100
-    lc = len(b) + la + 1
101
-
102
-    # Compute the even coefficients of the product of a and b.
103
-    c = lc * [a[0].__class__()]
104
-    for i, bi in enumerate(b):
105
-        if bi == 0:
106
-            continue
107
-        for j in range(i % 2, la, 2):
108
-            c[i + j] += a[j] * bi
109
-
110
-    # Calculate the definite integral from -1 to 1.
111
-    return 2 * sum(c[i] / (i + 1) for i in range(0, lc, 2))
112
-
113
-
114
-def calc_bdef(ns):
115
-    """Calculate the decompositions of Newton polynomials (over the nodes
116
-    of the n-point Clenshaw-Curtis quadrature rule) in terms of
117
-    Legandre polynomials.
118
-
119
-    The parameter 'ns' is a sequence of numers of points of the
120
-    quadrature rule.  The return value is a corresponding sequence of
121
-    normalized Legendre polynomial coefficients.
122
-    """
123
-    legs = legendre(max(ns) + 1)
124
-    result = []
125
-    for n in ns:
126
-        poly = []
127
-        a = list(map(Frac, newton(n)))
128
-        for b in legs[:n + 1]:
129
-            igral = scalar_product(a, b)
130
-
131
-            # Normalize & store.  (The polynomials returned by
132
-            # legendre() have standard normalization that is not
133
-            # orthonormal.)
134
-            poly.append(np.sqrt((2*len(b) - 1) / 2) * igral)
135
-
136
-        result.append(np.array(poly))
137
-    return result
138
-
139
-
140
-# Nodes and Newton polynomials.
141
-n = (5, 9, 17, 33)
142
-xi = [-np.cos(np.arange(n[j])/(n[j]-1) * np.pi) for j in range(4)]
143
-# Make `xi` perfectly anti-symmetric, important for splitting the intervals
144
-xi = [(row - row[::-1]) / 2 for row in xi]
145
-b_def = calc_bdef(n)
146
-
147
-
148
-def calc_V(xi, n):
149
-    V = [np.ones(xi.shape), xi.copy()]
150
-    for i in range(2, n):
151
-        V.append((2*i-1) / i * xi * V[-1] - (i-1) / i * V[-2])
152
-    for i in range(n):
153
-        V[i] *= np.sqrt(i + 0.5)
154
-    return np.array(V).T
155
-
156
-# compute the coefficients
157
-V = [calc_V(*args) for args in zip(xi, n)]
158
-V_inv = list(map(inv, V))
159
-Vcond = list(map(cond, V))
160
-
161
-# shift matrix
162
-T_lr = [V_inv[3] @ calc_V((xi[3] + a) / 2, n[3]) for a in [-1, 1]]
163
-
164
-# compute the integral
165
-w = np.sqrt(0.5)                # legendre
166
-
167
-
168
-k = np.arange(n[3])
169
-alpha = np.sqrt((k+1)**2 / (2*k+1) / (2*k+3))
170
-gamma = np.concatenate([[0, 0], np.sqrt(k[2:]**2 / (4*k[2:]**2-1))])
171
-
172
-def _downdate(c, nans, depth):
173
-    b = b_def[depth].copy()
174
-    m = n[depth] - 1
175
-    for i in nans:
176
-        b[m + 1] /= alpha[m]
177
-        xii = xi[depth][i]
178
-        b[m] = (b[m] + xii * b[m + 1]) / alpha[m - 1]
179
-        for j in range(m - 1, 0, -1):
180
-            b[j] = ((b[j] + xii * b[j + 1] - gamma[j + 1] * b[j + 2])
181
-                    / alpha[j - 1])
182
-        b = b[1:]
183
-
184
-        c[:m] -= c[m] / b[m] * b[:m]
185
-        c[m] = 0
186
-        m -= 1
187
-
188
-
189
-def _zero_nans(fx):
190
-    nans = []
191
-    for i in range(len(fx)):
192
-        if not np.isfinite(fx[i]):
193
-            nans.append(i)
194
-            fx[i] = 0.0
195
-    return nans
196
-
197
-
198
-def _calc_coeffs(fx, depth):
199
-    """Caution: this function modifies fx."""
200
-    nans = _zero_nans(fx)
201
-    c_new = V_inv[depth] @ fx
202
-    if nans:
203
-        fx[nans] = np.nan
204
-        _downdate(c_new, nans, depth)
205
-    return c_new
206
-
207
-
208
-class DivergentIntegralError(ValueError):
209
-    def __init__(self, msg, igral, err, nr_points):
210
-        self.igral = igral
211
-        self.err = err
212
-        self.nr_points = nr_points
213
-        super().__init__(msg)
214
-
215
-
216
-class _Interval:
217
-    __slots__ = ['a', 'b', 'c', 'c_old', 'fx', 'igral', 'err', 'tol',
218
-                 'depth', 'rdepth', 'ndiv']
219
-
220
-    @classmethod
221
-    def make_first(cls, f, a, b, tol):
222
-        points = (a+b)/2 + (b-a) * xi[3] / 2
223
-        fx = f(points)
224
-        nans = _zero_nans(fx)
225
-        ival = _Interval()
226
-        ival.c = np.zeros((4, n[3]))
227
-        ival.c[3] = V_inv[3] @ fx
228
-        ival.c[2, :n[2]] = V_inv[2] @ fx[:n[3]:2]
229
-        fx[nans] = np.nan
230
-        ival.fx = fx
231
-        ival.c_old = np.zeros(fx.shape)
232
-        ival.a = a
233
-        ival.b = b
234
-        ival.igral = (b-a) * w * ival.c[3, 0]
235
-        c_diff = norm(ival.c[3] - ival.c[2])
236
-        ival.err = (b-a) * c_diff
237
-        if c_diff / norm(ival.c[3]) > 0.1:
238
-            ival.err = max( ival.err , (b-a) * norm(ival.c[3]) )
239
-        ival.tol = tol
240
-        ival.depth = 3
241
-        ival.ndiv = 0
242
-        ival.rdepth = 1
243
-        return ival, points
244
-
245
-    def split(self, f, ndiv_max=20):
246
-        a = self.a
247
-        b = self.b
248
-        m = (a + b) / 2
249
-        f_center = self.fx[(len(self.fx)-1)//2]
250
-
251
-        ivals = []
252
-        nr_points = 0
253
-        for aa, bb, f_left, f_right, T in [
254
-                (a, m, self.fx[0], f_center, T_lr[0]),
255
-                (m, b, f_center, self.fx[-1], T_lr[1])]:
256
-            ival = _Interval()
257
-            ivals.append(ival)
258
-            ival.a = aa
259
-            ival.b = bb
260
-            ival.tol = self.tol / np.sqrt(2)
261
-            ival.depth = 0
262
-            ival.rdepth = self.rdepth + 1
263
-            ival.c = np.zeros((4, n[3]))
264
-            fx = np.concatenate(
265
-                ([f_left],
266
-                 f((aa + bb) / 2 + (bb - aa) * xi[0][1:-1] / 2),
267
-                 [f_right]))
268
-            nr_points += n[0] - 2
269
-
270
-            ival.c[0, :n[0]] = c_new = _calc_coeffs(fx, 0)
271
-            ival.fx = fx
272
-
273
-            ival.c_old = T @ self.c[self.depth]
274
-            c_diff = norm(ival.c[0] - ival.c_old)
275
-            ival.err = (bb - aa) * c_diff
276
-            ival.igral = (bb - aa) * ival.c[0, 0] * w
277
-            ival.ndiv = (self.ndiv
278
-                         + (abs(self.c[0, 0]) > 0
279
-                            and ival.c[0, 0] / self.c[0, 0] > 2))
280
-            if ival.ndiv > ndiv_max and 2*ival.ndiv > ival.rdepth:
281
-                return (aa, bb, bb-aa), nr_points
282
-
283
-        return ivals, nr_points
284
-
285
-    def refine(self, f):
286
-        """Increase degree of interval."""
287
-        self.depth = depth = self.depth + 1
288
-        a = self.a
289
-        b = self.b
290
-        points = (a+b)/2 + (b-a)*xi[depth]/2
291
-        fx = np.empty(n[depth])
292
-        fx[0:n[depth]:2] = self.fx
293
-        fx[1:n[depth]-1:2] = f(points[1:n[depth]-1:2])
294
-        fx = fx[:n[depth]]
295
-        self.c[depth, :n[depth]] = c_new = _calc_coeffs(fx, depth)
296
-        self.fx = fx
297
-        c_diff = norm(self.c[depth - 1] - self.c[depth])
298
-        self.err = (b-a) * c_diff
299
-        self.igral = (b-a) * w * c_new[0]
300
-        nc = norm(c_new)
301
-        split = nc > 0 and c_diff / nc > 0.1
302
-
303
-        return points, split, n[depth] - n[depth - 1]
304
-
305
-
306
-def algorithm_4 (f, a, b, tol):
307
-    """ALGORITHM_4 evaluates an integral using adaptive quadrature. The
308
-    algorithm uses Clenshaw-Curtis quadrature rules of increasing
309
-    degree in each interval and bisects the interval if either the
310
-    function does not appear to be smooth or a rule of maximum degree
311
-    has been reached. The error estimate is computed from the L2-norm
312
-    of the difference between two successive interpolations of the
313
-    integrand over the nodes of the respective quadrature rules.
314
-
315
-    INT = ALGORITHM_4 ( F , A , B , TOL ) approximates the integral of
316
-    F in the interval [A,B] up to the relative tolerance TOL. The
317
-    integrand F should accept a vector argument and return a vector
318
-    result containing the integrand evaluated at each element of the
319
-    argument.
320
-
321
-    [INT,ERR,NR_POINTS] = ALGORITHM_4 ( F , A , B , TOL ) returns ERR,
322
-    an estimate of the absolute integration error as well as
323
-    NR_POINTS, the number of function values for which the integrand
324
-    was evaluated. The value of ERR may be larger than the requested
325
-    tolerance, indicating that the integration may have failed.
326
-
327
-    ALGORITHM_4 halts with a warning if the integral is or appears to
328
-    be divergent.
329
-
330
-    Reference: "Increasing the Reliability of Adaptive Quadrature
331
-        Using Explicit Interpolants", P. Gonnet, ACM Transactions on
332
-        Mathematical Software, 37 (3), art. no. 26, 2008.
333
-    """
334
-
335
-    # compute the first interval
336
-    ival, points = _Interval.make_first(f, a, b, tol)
337
-    ivals = [ival]
338
-
339
-    # init some globals
340
-    igral = ival.igral
341
-    err = ival.err
342
-    igral_final = 0
343
-    err_final = 0
344
-    i_max = 0
345
-    nr_points = n[3]
346
-
347
-    # do we even need to go this way?
348
-    if err < igral * tol:
349
-        return igral, err, nr_points
350
-
351
-    # main loop
352
-    for _ in range(int(1e9)):
353
-        if ivals[i_max].depth == 3:
354
-            split = True
355
-        else:
356
-            points, split, nr_points_inc = ivals[i_max].refine(f)
357
-            nr_points += nr_points_inc
358
-
359
-        # can we safely ignore this interval?
360
-        if (points[1] <= points[0]
361
-            or points[-1] <= points[-2]
362
-            or ivals[i_max].err < (abs(ivals[i_max].igral) * eps
363
-                                   * Vcond[ivals[i_max].depth])):
364
-            err_final += ivals[i_max].err
365
-            igral_final += ivals[i_max].igral
366
-            ivals[i_max] = ivals[-1]
367
-            ivals.pop()
368
-        elif split:
369
-            result, nr_points_inc = ivals[i_max].split(f)
370
-            nr_points += nr_points_inc
371
-            if isinstance(result, tuple):
372
-                igral = np.sign(igral) * np.inf
373
-                raise DivergentIntegralError(
374
-                    'Possibly divergent integral in the interval'
375
-                    ' [{}, {}]! (h={})'.format(*result),
376
-                    igral, err, nr_points)
377
-            ivals.extend(result)
378
-            ivals[i_max] = ivals.pop()
379
-
380
-        # compute the running err and new max
381
-        i_max = 0
382
-        i_min = 0
383
-        err = err_final
384
-        igral = igral_final
385
-        for i in range(len(ivals)):
386
-            if ivals[i].err > ivals[i_max].err:
387
-                i_max = i
388
-            elif ivals[i].err < ivals[i_min].err:
389
-                i_min = i
390
-            err += ivals[i].err
391
-            igral += ivals[i].igral
392
-
393
-        # nuke smallest element if stack is larger than 200
394
-        if len(ivals) > 200:
395
-            err_final += ivals[i_min].err
396
-            igral_final += ivals[i_min].igral
397
-            ivals[i_min] = ivals[-1]
398
-            ivals.pop()
399
-            if i_max == len(ivals):
400
-                i_max = i_min
401
-
402
-        # get up and leave?
403
-        if (err == 0
404
-            or err < abs(igral) * tol
405
-            or (err_final > abs(igral) * tol
406
-                and err - err_final < abs(igral) * tol)
407
-            or not ivals):
408
-            break
409
-
410
-    return igral, err, nr_points, ivals
411
-
412
-
413
-################ Tests ################
414
-
415
-def f0(x):
416
-    return x * np.sin(1/x) * np.sqrt(abs(1 - x))
417
-
418
-
419
-def f7(x):
420
-    return x**-0.5
421
-
422
-
423
-def f24(x):
424
-    return np.floor(np.exp(x))
425
-
426
-
427
-def f21(x):
428
-    y = 0
429
-    for i in range(1, 4):
430
-        y += 1 / np.cosh(20**i * (x - 2 * i / 10))
431
-    return y
432
-
433
-
434
-def f63(x):
435
-    return abs(x - 0.987654321)**-0.45
436
-
437
-
438
-def fdiv(x):
439
-    return abs(x - 0.987654321)**-1.1
440
-
441
-
442
-def test_legendre():
443
-    legs = legendre(11)
444
-    comparisons = [(legs[0], [1], 1),
445
-                    (legs[1], [0, 1], 1),
446
-                    (legs[10], [-63, 0, 3465, 0, -30030, 0,
447
-                                90090, 0, -109395, 0, 46189], 256)]
448
-    for a, b, div in comparisons:
449
-        for c, d in zip(a, b):
450
-            assert c * div == d
451
-
452
-
453
-def test_scalar_product(n=33):
454
-    legs = legendre(n)
455
-    selection = [0, 5, 7, n-1]
456
-    for i in selection:
457
-        for j in selection:
458
-            assert (scalar_product(legs[i], legs[j])
459
-                    == ((i == j) and Frac(2, 2*i + 1)))
460
-
461
-
462
-def simple_newton(n):
463
-    """Slower than 'newton()' and prone to numerical error."""
464
-    from itertools import combinations
465
-
466
-    nodes = -np.cos(np.arange(n) / (n-1) * np.pi)
467
-    return [sum(np.prod(-np.asarray(sel))
468
-                for sel in combinations(nodes, n - d))
469
-            for d in range(n + 1)]
470
-
471
-
472
-def test_newton():
473
-    assert_allclose(newton(9), simple_newton(9), atol=1e-15)
474
-
475
-
476
-def test_b_def(depth=1):
477
-    legs = [np.array(leg, float) for leg in legendre(n[depth] + 1)]
478
-    result = np.zeros(len(legs[-1]))
479
-    for factor, leg in zip(b_def[depth], legs):
480
-        factor *= np.sqrt((2*len(leg) - 1) / 2)
481
-        result[:len(leg)] += factor * leg
482
-    assert_allclose(result, newton(n[depth]), rtol=1e-15)
483
-
484
-
485
-def test_downdate(depth=3):
486
-    fx = np.abs(xi[depth])
487
-    fx[1::2] = np.nan
488
-    c_downdated = _calc_coeffs(fx, depth)
489
-
490
-    depth -= 1
491
-    fx = np.abs(xi[depth])
492
-    c = _calc_coeffs(fx, depth)
493
-
494
-    assert_allclose(c_downdated[:len(c)], c, rtol=0, atol=1e-9)
495
-
496
-
497
-def test_integration():
498
-    old_settings = np.seterr(all='ignore')
499
-
500
-    igral, err, nr_points = algorithm_4(f0, 0, 3, 1e-5)
501
-    assert_allclose(igral, 1.98194117954329, 1e-15)
502
-    assert_allclose(err, 1.9563545589988155e-05, 1e-10)
503
-    assert nr_points == 1129
504
-
505
-    igral, err, nr_points = algorithm_4(f7, 0, 1, 1e-6)
506
-    assert_allclose(igral, 1.9999998579359648, 1e-15)
507
-    assert_allclose(err, 1.8561437334964041e-06, 1e-10)
508
-    assert nr_points == 693
509
-
510
-    igral, err, nr_points = algorithm_4(f24, 0, 3, 1e-3)
511
-    assert_allclose(igral, 17.664696186312934, 1e-15)
512
-    assert_allclose(err, 0.017602618074957457, 1e-10)
513
-    assert nr_points == 4519
514
-
515
-    igral, err, nr_points = algorithm_4(f21, 0, 1, 1e-3)
516
-    assert_allclose(igral, 0.16310022131213361, 1e-15)
517
-    assert_allclose(err, 0.00011848806384952786, 1e-10)
518
-    assert nr_points == 191
519
-
520
-    igral, err, nr_points = algorithm_4(f63, 0, 1, 1e-10)
521
-    assert_allclose(igral, 1.967971650560763, 1e-15)
522
-    assert_allclose(err, 2.9049859499240667e-09, 1e-7)
523
-    assert nr_points == 2715
524
-
525
-    try:
526
-        igral, err, nr_points = algorithm_4(fdiv, 0, 1, 1e-6)
527
-    except DivergentIntegralError as e:
528
-        assert e.igral == np.inf
529
-        assert_allclose(e.err, 284.56192231467958, 1e-10)
530
-        assert e.nr_points == 431
531
-
532
-    np.seterr(**old_settings)
533
-
534
-
535
-if __name__ == '__main__':
536
-    test_legendre()
537
-    test_scalar_product()
538
-    test_newton()
539
-    test_b_def()
540
-    test_downdate()
541
-    test_integration()
Browse code

update to cwg's latest commit 0f0fb343

Bas Nijholt authored on 25/10/2017 17:42:34
Showing 1 changed files
... ...
@@ -2,64 +2,148 @@
2 2
 # Copyright 2017 Christoph Groth
3 3
 
4 4
 import warnings
5
+from fractions import Fraction as Frac
6
+from collections import defaultdict
5 7
 import numpy as np
8
+from numpy.testing import assert_allclose
6 9
 from numpy.linalg import cond
7
-from scipy.linalg import inv, solve
8
-from scipy.linalg.blas import dgemv
10
+from scipy.linalg import norm, inv
11
+
9 12
 
10 13
 eps = np.spacing(1)
11 14
 
12
-# The following two functions allow for almost bit-per-bit equivalence
13
-# with the matlab code as interpreted by octave.
15
+def legendre(n):
16
+    """Return the first n Legendre polynomials.
14 17
 
15
-def norm(a):
16
-    return np.sqrt(a @ a)
18
+    The polynomials have *standard* normalization, i.e.
19
+    int_{-1}^1 dx L_n(x) L_m(x) = delta(m, n) * 2 / (2 * n + 1).
17 20
 
21
+    The return value is a list of list of fraction.Fraction instances.
22
+    """
23
+    result = [[Frac(1)], [Frac(0), Frac(1)]]
24
+    if n <= 2:
25
+        return result[:n]
26
+    for i in range(2, n):
27
+        # Use Bonnet's recursion formula.
28
+        new = (i + 1) * [Frac(0)]
29
+        new[1:] = (r * (2*i - 1) for r in result[-1])
30
+        new[:-2] = (n - r * (i - 1) for n, r in zip(new[:-2], result[-2]))
31
+        new[:] = (n / i for n in new)
32
+        result.append(new)
33
+    return result
34
+
35
+
36
+def newton(n):
37
+    """Compute the monomial coefficients of the Newton polynomial over the
38
+    nodes of the n-point Clenshaw-Curtis quadrature rule.
39
+    """
40
+    # The nodes of the Clenshaw-Curtis rule are x_i = -cos(i * Pi / (n-1)).
41
+    # Here, we calculate the coefficients c_i such that sum_i c_i * x^i
42
+    # = prod_i (x - x_i).  The coefficients are thus sums of products of
43
+    # cosines.
44
+    #
45
+    # This routine uses the relation
46
+    #   cos(a) cos(b) = (cos(a + b) + cos(a - b)) / 2
47
+    # to efficiently calculate the coefficients.
48
+    #
49
+    # The dictionary 'terms' descibes the terms that make up the
50
+    # monomial coefficients.  Each item ((d, a), m) corresponds to a
51
+    # term m * cos(a * Pi / n) to be added to prefactor of the
52
+    # monomial x^(n-d).
53
+
54
+    mod = 2 * (n-1)
55
+    terms = defaultdict(int)
56
+    terms[0, 0] += 1
18 57
 
19
-def mvmul(a, b):
20
-    return dgemv(1.0, a, b)
58
+    for i in range(n):
59
+        newterms = []
60
+        for (d, a), m in terms.items():
61
+            for b in [i, -i]:
62
+                # In order to reduce the number of terms, cosine
63
+                # arguments are mapped back to the inteval [0, pi/2).
64
+                arg = (a + b) % mod
65
+                if arg > n-1:
66
+                    arg = mod - arg
67
+                if arg >= n // 2:
68
+                    if n % 2 and arg == n // 2:
69
+                        # Zero term: ignore
70
+                        continue
71
+                    newterms.append((d + 1, n - 1 - arg, -m))
72
+                else:
73
+                    newterms.append((d + 1, arg, m))
74
+        for d, s, m in newterms:
75
+            terms[d, s] += m
76
+
77
+    c = (n + 1) * [0]
78
+    for (d, a), m in terms.items():
79
+        if m and a != 0:
80
+            raise ValueError("Newton polynomial cannot be represented exactly.")
81
+        c[n - d] += m
82
+        # The check could be removed and the above line replaced by
83
+        # the following, but then the result would be no longer exact.
84
+        # c[n - d] += m * np.cos(a * np.pi / (n - 1))
85
+
86
+    cf = np.array(c, float)
87
+    assert all(int(cfe) == ce for cfe, ce in zip(cf, c)), 'Precision loss'
88
+
89
+    cf /= 2.**np.arange(n, -1, -1)
90
+    return cf
91
+
92
+
93
+def scalar_product(a, b):
94
+    """Compute the polynomial scalar product int_-1^1 dx a(x) b(x).
95
+
96
+    The args must be sequences of polynomial coefficients.  This
97
+    function is careful to use the input data type for calculations.
98
+    """
99
+    la = len(a)
100
+    lc = len(b) + la + 1
21 101
 
102
+    # Compute the even coefficients of the product of a and b.
103
+    c = lc * [a[0].__class__()]
104
+    for i, bi in enumerate(b):
105
+        if bi == 0:
106
+            continue
107
+        for j in range(i % 2, la, 2):
108
+            c[i + j] += a[j] * bi
109
+
110
+    # Calculate the definite integral from -1 to 1.
111
+    return 2 * sum(c[i] / (i + 1) for i in range(0, lc, 2))
112
+
113
+
114
+def calc_bdef(ns):
115
+    """Calculate the decompositions of Newton polynomials (over the nodes
116
+    of the n-point Clenshaw-Curtis quadrature rule) in terms of
117
+    Legandre polynomials.
118
+
119
+    The parameter 'ns' is a sequence of numers of points of the
120
+    quadrature rule.  The return value is a corresponding sequence of
121
+    normalized Legendre polynomial coefficients.
122
+    """
123
+    legs = legendre(max(ns) + 1)
124
+    result = []
125
+    for n in ns:
126
+        poly = []
127
+        a = list(map(Frac, newton(n)))
128
+        for b in legs[:n + 1]:
129
+            igral = scalar_product(a, b)
22 130
 
23
-# the nodes and newton polynomials
131
+            # Normalize & store.  (The polynomials returned by
132
+            # legendre() have standard normalization that is not
133
+            # orthonormal.)
134
+            poly.append(np.sqrt((2*len(b) - 1) / 2) * igral)
135
+
136
+        result.append(np.array(poly))
137
+    return result
138
+
139
+
140
+# Nodes and Newton polynomials.
24 141
 n = (5, 9, 17, 33)
25 142
 xi = [-np.cos(np.arange(n[j])/(n[j]-1) * np.pi) for j in range(4)]
26 143
 # Make `xi` perfectly anti-symmetric, important for splitting the intervals
27 144
 xi = [(row - row[::-1]) / 2 for row in xi]
145
+b_def = calc_bdef(n)
28 146
 
29
-b_def = (np.array([0, .233284737407921723637836578544e-1,
30
-                   0, -.831479419283098085685277496071e-1,
31
-                   0, .0541462136776153483932540272848 ]),
32
-         np.array([0, .883654308363339862264532494396e-4,
33
-                   0, .238811521522368331303214066075e-3,
34
-                   0, .135365534194038370983135068211e-2,
35
-                   0, -.520710690438660595086839959882e-2,
36
-                   0, .00341659266223572272892690737979 ]),
37
-         np.array([0, .379785635776247894184454273159e-7,
38
-                   0, .655473977795402040043020497901e-7,
39
-                   0, .103479954638984842226816620692e-6,
40
-                   0, .173700624961660596894381303819e-6,
41
-                   0, .337719613424065357737699682062e-6,
42
-                   0, .877423283550614343733565759649e-6,
43
-                   0, .515657204371051131603503471028e-5,
44
-                   0, -.203244736027387801432055290742e-4,
45
-                   0, .0000134265158311651777460545854542 ]),
46
-         np.array([0, .703046511513775683031092069125e-13,
47
-                   0, .110617117381148770138566741591e-12,
48
-                   0, .146334657087392356024202217074e-12,
49
-                   0, .184948444492681259461791759092e-12,
50
-                   0, .231429962470609662207589449428e-12,
51
-                   0, .291520062115989014852816512412e-12,
52
-                   0, .373653379768759953435020783965e-12,
53
-                   0, .491840460397998449461993671859e-12,
54
-                   0, .671514395653454630785723660045e-12,
55
-                   0, .963162916392726862525650710866e-12,
56
-                   0, .147853378943890691325323722031e-11,
57
-                   0, .250420145651013003355273649380e-11,
58
-                   0, .495516257435784759806147914867e-11,
59
-                   0, .130927034711760871648068641267e-10,
60
-                   0, .779528640561654357271364483150e-10,
61
-                   0, -.309866395328070487426324760520e-9,
62
-                   0, .205572320292667201732878773151e-9]))
63 147
 
64 148
 def calc_V(xi, n):
65 149
     V = [np.ones(xi.shape), xi.copy()]
... ...
@@ -80,32 +164,44 @@ T_lr = [V_inv[3] @ calc_V((xi[3] + a) / 2, n[3]) for a in [-1, 1]]
80 164
 # compute the integral
81 165
 w = np.sqrt(0.5)                # legendre
82 166
 
83
-# set-up the downdate matrix
84
-k = np.arange(n[3])
85
-U = (np.diag(np.sqrt((k+1)**2 / (2*k+1) / (2*k+3)))
86
-     + np.diag(np.sqrt(k[2:]**2 / (4*k[2:]**2-1)), 2))
87
-
88 167
 
89
-def _calc_coeffs(fx, depth):
90
-    """Caution: this function modifies fx."""
168
+k = np.arange(n[3])
169
+alpha = np.sqrt((k+1)**2 / (2*k+1) / (2*k+3))
170
+gamma = np.concatenate([[0, 0], np.sqrt(k[2:]**2 / (4*k[2:]**2-1))])
171
+
172
+def _downdate(c, nans, depth):
173
+    b = b_def[depth].copy()
174
+    m = n[depth] - 1
175
+    for i in nans:
176
+        b[m + 1] /= alpha[m]
177
+        xii = xi[depth][i]
178
+        b[m] = (b[m] + xii * b[m + 1]) / alpha[m - 1]
179
+        for j in range(m - 1, 0, -1):
180
+            b[j] = ((b[j] + xii * b[j + 1] - gamma[j + 1] * b[j + 2])
181
+                    / alpha[j - 1])
182
+        b = b[1:]
183
+
184
+        c[:m] -= c[m] / b[m] * b[:m]
185
+        c[m] = 0
186
+        m -= 1
187
+
188
+
189
+def _zero_nans(fx):
91 190
     nans = []
92 191
     for i in range(len(fx)):
93 192
         if not np.isfinite(fx[i]):
94
-            fx[i] = 0.0
95 193
             nans.append(i)
96
-    c_new = mvmul(V_inv[depth], fx)
97
-    if len(nans) > 0:
98
-        b_new = b_def[depth].copy()
99
-        n_new = n[depth] - 1
100
-        for i in nans:
101
-            b_new[:-1] = solve(
102
-                (U[:n[depth], :n[depth]] - np.diag(np.ones(n[depth] - 1)
103
-                                                   * xi[depth][i], 1)),
104
-                b_new[1:])
105
-            b_new[-1] = 0
106
-            c_new -= c_new[n_new] / b_new[n_new] * b_new[:-1]
107
-            n_new -= 1
108
-            fx[i] = np.nan
194
+            fx[i] = 0.0
195
+    return nans
196
+
197
+
198
+def _calc_coeffs(fx, depth):
199
+    """Caution: this function modifies fx."""
200
+    nans = _zero_nans(fx)
201
+    c_new = V_inv[depth] @ fx
202
+    if nans:
203
+        fx[nans] = np.nan
204
+        _downdate(c_new, nans, depth)
109 205
     return c_new
110 206
 
111 207
 
... ...
@@ -125,15 +221,11 @@ class _Interval:
125 221
     def make_first(cls, f, a, b, tol):
126 222
         points = (a+b)/2 + (b-a) * xi[3] / 2
127 223
         fx = f(points)
128
-        nans = []
129
-        for i in range(len(fx)):
130
-            if not np.isfinite(fx[i]):
131
-                nans.append(i)
132
-                fx[i] = 0.0
224
+        nans = _zero_nans(fx)
133 225
         ival = _Interval()
134 226
         ival.c = np.zeros((4, n[3]))
135
-        ival.c[3, :n[3]] = mvmul(V_inv[3], fx)
136
-        ival.c[2, :n[2]] = mvmul(V_inv[2], fx[:n[3]:2])
227
+        ival.c[3] = V_inv[3] @ fx
228
+        ival.c[2, :n[2]] = V_inv[2] @ fx[:n[3]:2]
137 229
         fx[nans] = np.nan
138 230
         ival.fx = fx
139 231
         ival.c_old = np.zeros(fx.shape)
... ...
@@ -145,7 +237,7 @@ class _Interval:
145 237
         if c_diff / norm(ival.c[3]) > 0.1:
146 238
             ival.err = max( ival.err , (b-a) * norm(ival.c[3]) )
147 239
         ival.tol = tol
148
-        ival.depth = 4
240
+        ival.depth = 3
149 241
         ival.ndiv = 0
150 242
         ival.rdepth = 1
151 243
         return ival, points
... ...
@@ -166,7 +258,7 @@ class _Interval:
166 258
             ival.a = aa
167 259
             ival.b = bb
168 260
             ival.tol = self.tol / np.sqrt(2)
169
-            ival.depth = 1
261
+            ival.depth = 0
170 262
             ival.rdepth = self.rdepth + 1
171 263
             ival.c = np.zeros((4, n[3]))
172 264
             fx = np.concatenate(
... ...
@@ -178,7 +270,7 @@ class _Interval:
178 270
             ival.c[0, :n[0]] = c_new = _calc_coeffs(fx, 0)
179 271
             ival.fx = fx
180 272
 
181
-            ival.c_old = mvmul(T, self.c[self.depth - 1])
273
+            ival.c_old = T @ self.c[self.depth]
182 274
             c_diff = norm(ival.c[0] - ival.c_old)
183 275
             ival.err = (bb - aa) * c_diff
184 276
             ival.igral = (bb - aa) * ival.c[0, 0] * w
... ...
@@ -192,7 +284,7 @@ class _Interval:
192 284
 
193 285
     def refine(self, f):
194 286
         """Increase degree of interval."""
195
-        depth = self.depth
287
+        self.depth = depth = self.depth + 1
196 288
         a = self.a
197 289
         b = self.b
198 290
         points = (a+b)/2 + (b-a)*xi[depth]/2
... ...
@@ -206,16 +298,12 @@ class _Interval:
206 298
         self.err = (b-a) * c_diff
207 299
         self.igral = (b-a) * w * c_new[0]
208 300
         nc = norm(c_new)
209
-        self.depth = depth + 1
210
-        if nc > 0 and c_diff / nc > 0.1:
211
-            split = True
212
-        else:
213
-            split = False
301
+        split = nc > 0 and c_diff / nc > 0.1
214 302
 
215
-        return points, split, n[depth] - n[depth-1]
303
+        return points, split, n[depth] - n[depth - 1]
216 304
 
217 305
 
218
-def algorithm_4 (f, a, b, tol, until_iteration=None):
306
+def algorithm_4 (f, a, b, tol):
219 307
     """ALGORITHM_4 evaluates an integral using adaptive quadrature. The
220 308
     algorithm uses Clenshaw-Curtis quadrature rules of increasing
221 309
     degree in each interval and bisects the interval if either the
... ...
@@ -261,12 +349,8 @@ def algorithm_4 (f, a, b, tol, until_iteration=None):
261 349
         return igral, err, nr_points
262 350
 
263 351
     # main loop
264
-    if until_iteration is None:
265
-        # To simulate the while loop
266
-        until_iteration = int(1e15)
267
-
268
-    for _ in range(until_iteration):
269
-        if ivals[i_max].depth == 4:
352
+    for _ in range(int(1e9)):
353
+        if ivals[i_max].depth == 3:
270 354
             split = True
271 355
         else:
272 356
             points, split, nr_points_inc = ivals[i_max].refine(f)
... ...
@@ -276,10 +360,11 @@ def algorithm_4 (f, a, b, tol, until_iteration=None):
276 360
         if (points[1] <= points[0]
277 361
             or points[-1] <= points[-2]
278 362
             or ivals[i_max].err < (abs(ivals[i_max].igral) * eps
279
-                                   * Vcond[ivals[i_max].depth - 1])):
363
+                                   * Vcond[ivals[i_max].depth])):
280 364
             err_final += ivals[i_max].err
281 365
             igral_final += ivals[i_max].igral
282
-            ivals[i_max] = ivals.pop()
366
+            ivals[i_max] = ivals[-1]
367
+            ivals.pop()
283 368
         elif split:
284 369
             result, nr_points_inc = ivals[i_max].split(f)
285 370
             nr_points += nr_points_inc
... ...
@@ -309,7 +394,8 @@ def algorithm_4 (f, a, b, tol, until_iteration=None):
309 394
         if len(ivals) > 200:
310 395
             err_final += ivals[i_min].err
311 396
             igral_final += ivals[i_min].igral
312
-            ivals[i_min] = ivals.pop()
397
+            ivals[i_min] = ivals[-1]
398
+            ivals.pop()
313 399
             if i_max == len(ivals):
314 400
                 i_max = i_min
315 401
 
... ...
@@ -353,61 +439,103 @@ def fdiv(x):
353 439
     return abs(x - 0.987654321)**-1.1
354 440
 
355 441
 
356
-import struct
442
+def test_legendre():
443
+    legs = legendre(11)
444
+    comparisons = [(legs[0], [1], 1),
445
+                    (legs[1], [0, 1], 1),
446
+                    (legs[10], [-63, 0, 3465, 0, -30030, 0,
447
+                                90090, 0, -109395, 0, 46189], 256)]
448
+    for a, b, div in comparisons:
449
+        for c, d in zip(a, b):
450
+            assert c * div == d
451
+
452
+
453
+def test_scalar_product(n=33):
454
+    legs = legendre(n)
455
+    selection = [0, 5, 7, n-1]
456
+    for i in selection:
457
+        for j in selection:
458
+            assert (scalar_product(legs[i], legs[j])
459
+                    == ((i == j) and Frac(2, 2*i + 1)))
460
+
461
+
462
+def simple_newton(n):
463
+    """Slower than 'newton()' and prone to numerical error."""
464
+    from itertools import combinations
465
+
466
+    nodes = -np.cos(np.arange(n) / (n-1) * np.pi)
467
+    return [sum(np.prod(-np.asarray(sel))
468
+                for sel in combinations(nodes, n - d))
469
+            for d in range(n + 1)]
470
+
471
+
472
+def test_newton():
473
+    assert_allclose(newton(9), simple_newton(9), atol=1e-15)
474
+
475
+
476
+def test_b_def(depth=1):
477
+    legs = [np.array(leg, float) for leg in legendre(n[depth] + 1)]
478
+    result = np.zeros(len(legs[-1]))
479
+    for factor, leg in zip(b_def[depth], legs):
480
+        factor *= np.sqrt((2*len(leg) - 1) / 2)
481
+        result[:len(leg)] += factor * leg
482
+    assert_allclose(result, newton(n[depth]), rtol=1e-15)
483
+
484
+
485
+def test_downdate(depth=3):
486
+    fx = np.abs(xi[depth])
487
+    fx[1::2] = np.nan
488
+    c_downdated = _calc_coeffs(fx, depth)
357 489
 
358
-def float2hex(x):
359
-    return struct.pack('!d', x).hex()
490
+    depth -= 1
491
+    fx = np.abs(xi[depth])
492
+    c = _calc_coeffs(fx, depth)
360 493
 
361
-def hex2float(hex):
362
-    return struct.unpack('!d', bytes.fromhex(hex))[0]
494
+    assert_allclose(c_downdated[:len(c)], c, rtol=0, atol=1e-9)
363 495
 
364
-def assert_equal(value, hex, eps=0):
365
-    assert (float2hex(value) == hex # for NaN, etc.
366
-            or abs((value - hex2float(hex))) <= abs(eps * value))
367 496
 
368
-def test():
497
+def test_integration():
369 498
     old_settings = np.seterr(all='ignore')
370 499
 
371
-    igral, err, nr_points, _ = algorithm_4(f0, 0, 3, 1e-5)
372
-    print(igral, err, nr_points)
373
-    assert_equal(igral, '3fffb6084c1dabf4')
374
-    assert_equal(err, '3ef46042cb969374')
375
-    assert nr_points == 1419
376
-
377
-    igral, err, nr_points, _ = algorithm_4(f7, 0, 1, 1e-6)
378
-    print(igral, err, nr_points)
379
-    assert_equal(igral, '3fffffffd9fa6513')
380
-    assert_equal(err, '3ebd8955755be30c')
381
-    assert nr_points == 709
382
-
383
-    igral, err, nr_points, _ = algorithm_4(f24, 0, 3, 1e-3)
384
-    print(igral, err, nr_points)
385
-    assert_equal(igral, '4031aa1505ba7b41')
386
-    assert_equal(err, '3f9202232bd03a6a')
387
-    assert nr_points == 4515
388
-
389
-    igral, err, nr_points, _ = algorithm_4(f21, 0, 1, 1e-3)
390
-    print(igral, err, nr_points)
391
-    assert_equal(igral, '3fc4e088c36827c1')
392
-    assert_equal(err, '3f247d00177a3f07')
393
-    assert nr_points == 203
394
-
395
-    igral, err, nr_points, _ = algorithm_4(f63, 0, 1, 1e-10)
396
-    print(igral, err, nr_points)
397
-    assert_equal(igral, '3fff7ccfd769d160')
398
-    assert_equal(err, '3e28f421b487f15a', 2e-15)
500
+    igral, err, nr_points = algorithm_4(f0, 0, 3, 1e-5)
501
+    assert_allclose(igral, 1.98194117954329, 1e-15)
502
+    assert_allclose(err, 1.9563545589988155e-05, 1e-10)
503
+    assert nr_points == 1129
504
+
505
+    igral, err, nr_points = algorithm_4(f7, 0, 1, 1e-6)
506
+    assert_allclose(igral, 1.9999998579359648, 1e-15)
507
+    assert_allclose(err, 1.8561437334964041e-06, 1e-10)
508
+    assert nr_points == 693
509
+
510
+    igral, err, nr_points = algorithm_4(f24, 0, 3, 1e-3)
511
+    assert_allclose(igral, 17.664696186312934, 1e-15)
512
+    assert_allclose(err, 0.017602618074957457, 1e-10)
513
+    assert nr_points == 4519
514
+
515
+    igral, err, nr_points = algorithm_4(f21, 0, 1, 1e-3)
516
+    assert_allclose(igral, 0.16310022131213361, 1e-15)
517
+    assert_allclose(err, 0.00011848806384952786, 1e-10)
518
+    assert nr_points == 191
519
+
520
+    igral, err, nr_points = algorithm_4(f63, 0, 1, 1e-10)
521
+    assert_allclose(igral, 1.967971650560763, 1e-15)
522
+    assert_allclose(err, 2.9049859499240667e-09, 1e-7)
399 523
     assert nr_points == 2715
400 524
 
401 525
     try:
402
-        igral, err, nr_points, _ = algorithm_4(fdiv, 0, 1, 1e-6)
526
+        igral, err, nr_points = algorithm_4(fdiv, 0, 1, 1e-6)
403 527
     except DivergentIntegralError as e:
404
-        print(e.igral, e.err, e.nr_points)
405
-        assert_equal(e.igral, '7ff0000000000000')
406
-        assert_equal(e.err, '4073b48aeb356df5')
407
-        assert e.nr_points == 457
528
+        assert e.igral == np.inf
529
+        assert_allclose(e.err, 284.56192231467958, 1e-10)
530
+        assert e.nr_points == 431
408 531
 
409 532
     np.seterr(**old_settings)
410 533
 
411 534
 
412 535
 if __name__ == '__main__':
413
-    test()
536
+    test_legendre()
537
+    test_scalar_product()
538
+    test_newton()
539
+    test_b_def()
540
+    test_downdate()
541
+    test_integration()
Browse code

fix bug in algorithm_4.py and remove it in mine

Bas Nijholt authored on 02/10/2017 21:32:33
Showing 1 changed files
... ...
@@ -206,11 +206,11 @@ class _Interval:
206 206
         self.err = (b-a) * c_diff
207 207
         self.igral = (b-a) * w * c_new[0]
208 208
         nc = norm(c_new)
209
+        self.depth = depth + 1
209 210
         if nc > 0 and c_diff / nc > 0.1:
210 211
             split = True
211 212
         else:
212 213
             split = False
213
-            self.depth = depth + 1
214 214
 
215 215
         return points, split, n[depth] - n[depth-1]
216 216
 
Browse code

use same xi in algorithm_4.py

Bas Nijholt authored on 26/09/2017 15:41:20
Showing 1 changed files
... ...
@@ -23,6 +23,9 @@ def mvmul(a, b):
23 23
 # the nodes and newton polynomials
24 24
 n = (5, 9, 17, 33)
25 25
 xi = [-np.cos(np.arange(n[j])/(n[j]-1) * np.pi) for j in range(4)]
26
+# Make `xi` perfectly anti-symmetric, important for splitting the intervals
27
+xi = [(row - row[::-1]) / 2 for row in xi]
28
+
26 29
 b_def = (np.array([0, .233284737407921723637836578544e-1,
27 30
                    0, -.831479419283098085685277496071e-1,
28 31
                    0, .0541462136776153483932540272848 ]),
Browse code

return ivals with algorithm_4

Bas Nijholt authored on 26/09/2017 11:20:22
Showing 1 changed files
... ...
@@ -318,7 +318,7 @@ def algorithm_4 (f, a, b, tol, until_iteration=None):
318 318
             or not ivals):
319 319
             break
320 320
 
321
-    return igral, err, nr_points
321
+    return igral, err, nr_points, ivals
322 322
 
323 323
 
324 324
 ################ Tests ################
... ...
@@ -365,38 +365,38 @@ def assert_equal(value, hex, eps=0):
365 365
 def test():
366 366
     old_settings = np.seterr(all='ignore')
367 367
 
368
-    igral, err, nr_points = algorithm_4(f0, 0, 3, 1e-5)
368
+    igral, err, nr_points, _ = algorithm_4(f0, 0, 3, 1e-5)
369 369
     print(igral, err, nr_points)
370 370
     assert_equal(igral, '3fffb6084c1dabf4')
371 371
     assert_equal(err, '3ef46042cb969374')
372 372
     assert nr_points == 1419
373 373
 
374
-    igral, err, nr_points = algorithm_4(f7, 0, 1, 1e-6)
374
+    igral, err, nr_points, _ = algorithm_4(f7, 0, 1, 1e-6)
375 375
     print(igral, err, nr_points)
376 376
     assert_equal(igral, '3fffffffd9fa6513')
377 377
     assert_equal(err, '3ebd8955755be30c')
378 378
     assert nr_points == 709
379 379
 
380
-    igral, err, nr_points = algorithm_4(f24, 0, 3, 1e-3)
380
+    igral, err, nr_points, _ = algorithm_4(f24, 0, 3, 1e-3)
381 381
     print(igral, err, nr_points)
382 382
     assert_equal(igral, '4031aa1505ba7b41')
383 383
     assert_equal(err, '3f9202232bd03a6a')
384 384
     assert nr_points == 4515
385 385
 
386
-    igral, err, nr_points = algorithm_4(f21, 0, 1, 1e-3)
386
+    igral, err, nr_points, _ = algorithm_4(f21, 0, 1, 1e-3)
387 387
     print(igral, err, nr_points)
388 388
     assert_equal(igral, '3fc4e088c36827c1')
389 389
     assert_equal(err, '3f247d00177a3f07')
390 390
     assert nr_points == 203
391 391
 
392
-    igral, err, nr_points = algorithm_4(f63, 0, 1, 1e-10)
392
+    igral, err, nr_points, _ = algorithm_4(f63, 0, 1, 1e-10)
393 393
     print(igral, err, nr_points)
394 394
     assert_equal(igral, '3fff7ccfd769d160')
395 395
     assert_equal(err, '3e28f421b487f15a', 2e-15)
396 396
     assert nr_points == 2715
397 397
 
398 398
     try:
399
-        igral, err, nr_points = algorithm_4(fdiv, 0, 1, 1e-6)
399
+        igral, err, nr_points, _ = algorithm_4(fdiv, 0, 1, 1e-6)
400 400
     except DivergentIntegralError as e:
401 401
         print(e.igral, e.err, e.nr_points)
402 402
         assert_equal(e.igral, '7ff0000000000000')
Browse code

change while loop to range(very_big_number)

Bas Nijholt authored on 26/09/2017 10:36:11
Showing 1 changed files
... ...
@@ -212,7 +212,7 @@ class _Interval:
212 212
         return points, split, n[depth] - n[depth-1]
213 213
 
214 214
 
215
-def algorithm_4 (f, a, b, tol):
215
+def algorithm_4 (f, a, b, tol, until_iteration=None):
216 216
     """ALGORITHM_4 evaluates an integral using adaptive quadrature. The
217 217
     algorithm uses Clenshaw-Curtis quadrature rules of increasing
218 218
     degree in each interval and bisects the interval if either the
... ...
@@ -258,7 +258,11 @@ def algorithm_4 (f, a, b, tol):
258 258
         return igral, err, nr_points
259 259
 
260 260
     # main loop
261
-    while True:
261
+    if until_iteration is None:
262
+        # To simulate the while loop
263
+        until_iteration = int(1e15)
264
+
265
+    for _ in range(until_iteration):
262 266
         if ivals[i_max].depth == 4:
263 267
             split = True
264 268
         else:
Browse code

start factoring out Christoph's orignal code

Bas Nijholt authored on 26/09/2017 10:33:36
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,406 @@
1
+# Copyright 2010 Pedro Gonnet
2
+# Copyright 2017 Christoph Groth
3
+
4
+import warnings
5
+import numpy as np
6
+from numpy.linalg import cond
7
+from scipy.linalg import inv, solve
8
+from scipy.linalg.blas import dgemv
9
+
10
+eps = np.spacing(1)
11
+
12
+# The following two functions allow for almost bit-per-bit equivalence
13
+# with the matlab code as interpreted by octave.
14
+
15
+def norm(a):
16
+    return np.sqrt(a @ a)
17
+
18
+
19
+def mvmul(a, b):
20
+    return dgemv(1.0, a, b)
21
+
22
+
23
+# the nodes and newton polynomials
24
+n = (5, 9, 17, 33)
25
+xi = [-np.cos(np.arange(n[j])/(n[j]-1) * np.pi) for j in range(4)]
26
+b_def = (np.array([0, .233284737407921723637836578544e-1,
27
+                   0, -.831479419283098085685277496071e-1,
28
+                   0, .0541462136776153483932540272848 ]),
29
+         np.array([0, .883654308363339862264532494396e-4,
30
+                   0, .238811521522368331303214066075e-3,
31
+                   0, .135365534194038370983135068211e-2,
32
+                   0, -.520710690438660595086839959882e-2,
33
+                   0, .00341659266223572272892690737979 ]),
34
+         np.array([0, .379785635776247894184454273159e-7,
35
+                   0, .655473977795402040043020497901e-7,
36
+                   0, .103479954638984842226816620692e-6,
37
+                   0, .173700624961660596894381303819e-6,
38
+                   0, .337719613424065357737699682062e-6,
39
+                   0, .877423283550614343733565759649e-6,
40
+                   0, .515657204371051131603503471028e-5,
41
+                   0, -.203244736027387801432055290742e-4,
42
+                   0, .0000134265158311651777460545854542 ]),
43
+         np.array([0, .703046511513775683031092069125e-13,
44
+                   0, .110617117381148770138566741591e-12,
45
+                   0, .146334657087392356024202217074e-12,
46
+                   0, .184948444492681259461791759092e-12,
47
+                   0, .231429962470609662207589449428e-12,
48
+                   0, .291520062115989014852816512412e-12,
49
+                   0, .373653379768759953435020783965e-12,
50
+                   0, .491840460397998449461993671859e-12,
51
+                   0, .671514395653454630785723660045e-12,
52
+                   0, .963162916392726862525650710866e-12,
53
+                   0, .147853378943890691325323722031e-11,
54
+                   0, .250420145651013003355273649380e-11,
55
+                   0, .495516257435784759806147914867e-11,
56
+                   0, .130927034711760871648068641267e-10,
57
+                   0, .779528640561654357271364483150e-10,
58
+                   0, -.309866395328070487426324760520e-9,
59
+                   0, .205572320292667201732878773151e-9]))
60
+
61
+def calc_V(xi, n):
62
+    V = [np.ones(xi.shape), xi.copy()]
63
+    for i in range(2, n):
64
+        V.append((2*i-1) / i * xi * V[-1] - (i-1) / i * V[-2])
65
+    for i in range(n):
66
+        V[i] *= np.sqrt(i + 0.5)
67
+    return np.array(V).T
68
+
69
+# compute the coefficients
70
+V = [calc_V(*args) for args in zip(xi, n)]
71
+V_inv = list(map(inv, V))
72
+Vcond = list(map(cond, V))
73
+
74
+# shift matrix
75
+T_lr = [V_inv[3] @ calc_V((xi[3] + a) / 2, n[3]) for a in [-1, 1]]
76
+
77
+# compute the integral
78
+w = np.sqrt(0.5)                # legendre
79
+
80
+# set-up the downdate matrix
81
+k = np.arange(n[3])
82
+U = (np.diag(np.sqrt((k+1)**2 / (2*k+1) / (2*k+3)))
83
+     + np.diag(np.sqrt(k[2:]**2 / (4*k[2:]**2-1)), 2))
84
+
85
+
86
+def _calc_coeffs(fx, depth):
87
+    """Caution: this function modifies fx."""
88
+    nans = []
89
+    for i in range(len(fx)):
90
+        if not np.isfinite(fx[i]):
91
+            fx[i] = 0.0
92
+            nans.append(i)
93
+    c_new = mvmul(V_inv[depth], fx)
94
+    if len(nans) > 0:
95
+        b_new = b_def[depth].copy()
96
+        n_new = n[depth] - 1
97
+        for i in nans:
98
+            b_new[:-1] = solve(
99
+                (U[:n[depth], :n[depth]] - np.diag(np.ones(n[depth] - 1)
100
+                                                   * xi[depth][i], 1)),
101
+                b_new[1:])
102
+            b_new[-1] = 0
103
+            c_new -= c_new[n_new] / b_new[n_new] * b_new[:-1]
104
+            n_new -= 1
105
+            fx[i] = np.nan
106
+    return c_new
107
+
108
+
109
+class DivergentIntegralError(ValueError):
110
+    def __init__(self, msg, igral, err, nr_points):
111
+        self.igral = igral
112
+        self.err = err
113
+        self.nr_points = nr_points
114
+        super().__init__(msg)
115
+
116
+
117
+class _Interval:
118
+    __slots__ = ['a', 'b', 'c', 'c_old', 'fx', 'igral', 'err', 'tol',
119
+                 'depth', 'rdepth', 'ndiv']
120
+
121
+    @classmethod
122
+    def make_first(cls, f, a, b, tol):
123
+        points = (a+b)/2 + (b-a) * xi[3] / 2
124
+        fx = f(points)
125
+        nans = []
126
+        for i in range(len(fx)):
127
+            if not np.isfinite(fx[i]):
128
+                nans.append(i)
129
+                fx[i] = 0.0
130
+        ival = _Interval()
131
+        ival.c = np.zeros((4, n[3]))
132
+        ival.c[3, :n[3]] = mvmul(V_inv[3], fx)
133
+        ival.c[2, :n[2]] = mvmul(V_inv[2], fx[:n[3]:2])
134
+        fx[nans] = np.nan
135
+        ival.fx = fx
136
+        ival.c_old = np.zeros(fx.shape)
137
+        ival.a = a
138
+        ival.b = b
139
+        ival.igral = (b-a) * w * ival.c[3, 0]
140
+        c_diff = norm(ival.c[3] - ival.c[2])
141
+        ival.err = (b-a) * c_diff
142
+        if c_diff / norm(ival.c[3]) > 0.1:
143
+            ival.err = max( ival.err , (b-a) * norm(ival.c[3]) )
144
+        ival.tol = tol
145
+        ival.depth = 4
146
+        ival.ndiv = 0
147
+        ival.rdepth = 1
148
+        return ival, points
149
+
150
+    def split(self, f, ndiv_max=20):
151
+        a = self.a
152
+        b = self.b
153
+        m = (a + b) / 2
154
+        f_center = self.fx[(len(self.fx)-1)//2]
155
+
156
+        ivals = []
157
+        nr_points = 0
158
+        for aa, bb, f_left, f_right, T in [
159
+                (a, m, self.fx[0], f_center, T_lr[0]),
160
+                (m, b, f_center, self.fx[-1], T_lr[1])]:
161
+            ival = _Interval()
162
+            ivals.append(ival)
163
+            ival.a = aa
164
+            ival.b = bb
165
+            ival.tol = self.tol / np.sqrt(2)
166
+            ival.depth = 1
167
+            ival.rdepth = self.rdepth + 1
168
+            ival.c = np.zeros((4, n[3]))
169
+            fx = np.concatenate(
170
+                ([f_left],
171
+                 f((aa + bb) / 2 + (bb - aa) * xi[0][1:-1] / 2),
172
+                 [f_right]))
173
+            nr_points += n[0] - 2
174
+
175
+            ival.c[0, :n[0]] = c_new = _calc_coeffs(fx, 0)
176
+            ival.fx = fx
177
+
178
+            ival.c_old = mvmul(T, self.c[self.depth - 1])
179
+            c_diff = norm(ival.c[0] - ival.c_old)
180
+            ival.err = (bb - aa) * c_diff
181
+            ival.igral = (bb - aa) * ival.c[0, 0] * w
182
+            ival.ndiv = (self.ndiv
183
+                         + (abs(self.c[0, 0]) > 0
184
+                            and ival.c[0, 0] / self.c[0, 0] > 2))
185
+            if ival.ndiv > ndiv_max and 2*ival.ndiv > ival.rdepth:
186
+                return (aa, bb, bb-aa), nr_points
187
+
188
+        return ivals, nr_points
189
+
190
+    def refine(self, f):
191
+        """Increase degree of interval."""
192
+        depth = self.depth
193
+        a = self.a
194
+        b = self.b
195
+        points = (a+b)/2 + (b-a)*xi[depth]/2
196
+        fx = np.empty(n[depth])
197
+        fx[0:n[depth]:2] = self.fx
198
+        fx[1:n[depth]-1:2] = f(points[1:n[depth]-1:2])
199
+        fx = fx[:n[depth]]
200
+        self.c[depth, :n[depth]] = c_new = _calc_coeffs(fx, depth)
201
+        self.fx = fx
202
+        c_diff = norm(self.c[depth - 1] - self.c[depth])
203
+        self.err = (b-a) * c_diff
204
+        self.igral = (b-a) * w * c_new[0]
205
+        nc = norm(c_new)
206
+        if nc > 0 and c_diff / nc > 0.1:
207
+            split = True
208
+        else:
209
+            split = False
210
+            self.depth = depth + 1
211
+
212
+        return points, split, n[depth] - n[depth-1]
213
+
214
+
215
+def algorithm_4 (f, a, b, tol):
216
+    """ALGORITHM_4 evaluates an integral using adaptive quadrature. The
217
+    algorithm uses Clenshaw-Curtis quadrature rules of increasing
218
+    degree in each interval and bisects the interval if either the
219
+    function does not appear to be smooth or a rule of maximum degree
220
+    has been reached. The error estimate is computed from the L2-norm
221
+    of the difference between two successive interpolations of the
222
+    integrand over the nodes of the respective quadrature rules.
223
+
224
+    INT = ALGORITHM_4 ( F , A , B , TOL ) approximates the integral of
225
+    F in the interval [A,B] up to the relative tolerance TOL. The
226
+    integrand F should accept a vector argument and return a vector
227
+    result containing the integrand evaluated at each element of the
228
+    argument.
229
+
230
+    [INT,ERR,NR_POINTS] = ALGORITHM_4 ( F , A , B , TOL ) returns ERR,
231
+    an estimate of the absolute integration error as well as
232
+    NR_POINTS, the number of function values for which the integrand
233
+    was evaluated. The value of ERR may be larger than the requested
234
+    tolerance, indicating that the integration may have failed.
235
+
236
+    ALGORITHM_4 halts with a warning if the integral is or appears to
237
+    be divergent.
238
+
239
+    Reference: "Increasing the Reliability of Adaptive Quadrature
240
+        Using Explicit Interpolants", P. Gonnet, ACM Transactions on
241
+        Mathematical Software, 37 (3), art. no. 26, 2008.
242
+    """
243
+
244
+    # compute the first interval
245
+    ival, points = _Interval.make_first(f, a, b, tol)
246
+    ivals = [ival]
247
+
248
+    # init some globals
249
+    igral = ival.igral
250
+    err = ival.err
251
+    igral_final = 0
252
+    err_final = 0
253
+    i_max = 0
254
+    nr_points = n[3]
255
+
256
+    # do we even need to go this way?
257
+    if err < igral * tol:
258
+        return igral, err, nr_points
259
+
260
+    # main loop
261
+    while True:
262
+        if ivals[i_max].depth == 4:
263
+            split = True
264
+        else:
265
+            points, split, nr_points_inc = ivals[i_max].refine(f)
266
+            nr_points += nr_points_inc
267
+
268
+        # can we safely ignore this interval?
269
+        if (points[1] <= points[0]
270
+            or points[-1] <= points[-2]
271
+            or ivals[i_max].err < (abs(ivals[i_max].igral) * eps
272
+                                   * Vcond[ivals[i_max].depth - 1])):
273
+            err_final += ivals[i_max].err
274
+            igral_final += ivals[i_max].igral
275
+            ivals[i_max] = ivals.pop()
276
+        elif split:
277
+            result, nr_points_inc = ivals[i_max].split(f)
278
+            nr_points += nr_points_inc
279
+            if isinstance(result, tuple):
280
+                igral = np.sign(igral) * np.inf
281
+                raise DivergentIntegralError(
282
+                    'Possibly divergent integral in the interval'
283
+                    ' [{}, {}]! (h={})'.format(*result),
284
+                    igral, err, nr_points)
285
+            ivals.extend(result)
286
+            ivals[i_max] = ivals.pop()
287
+
288
+        # compute the running err and new max
289
+        i_max = 0
290
+        i_min = 0
291
+        err = err_final
292
+        igral = igral_final
293
+        for i in range(len(ivals)):
294
+            if ivals[i].err > ivals[i_max].err:
295
+                i_max = i
296
+            elif ivals[i].err < ivals[i_min].err:
297
+                i_min = i
298
+            err += ivals[i].err
299
+            igral += ivals[i].igral
300
+
301
+        # nuke smallest element if stack is larger than 200
302
+        if len(ivals) > 200:
303
+            err_final += ivals[i_min].err
304
+            igral_final += ivals[i_min].igral
305
+            ivals[i_min] = ivals.pop()
306
+            if i_max == len(ivals):
307
+                i_max = i_min
308
+
309
+        # get up and leave?
310
+        if (err == 0
311
+            or err < abs(igral) * tol
312
+            or (err_final > abs(igral) * tol
313
+                and err - err_final < abs(igral) * tol)
314
+            or not ivals):
315
+            break
316
+
317
+    return igral, err, nr_points
318
+
319
+
320
+################ Tests ################
321
+
322
+def f0(x):
323
+    return x * np.sin(1/x) * np.sqrt(abs(1 - x))
324
+
325
+
326
+def f7(x):
327
+    return x**-0.5
328
+
329
+
330
+def f24(x):
331
+    return np.floor(np.exp(x))
332
+
333
+
334
+def f21(x):
335
+    y = 0
336
+    for i in range(1, 4):
337
+        y += 1 / np.cosh(20**i * (x - 2 * i / 10))
338
+    return y
339
+
340
+
341
+def f63(x):
342
+    return abs(x - 0.987654321)**-0.45
343
+
344
+
345
+def fdiv(x):
346
+    return abs(x - 0.987654321)**-1.1
347
+
348
+
349
+import struct
350
+
351
+def float2hex(x):
352
+    return struct.pack('!d', x).hex()
353
+
354
+def hex2float(hex):
355
+    return struct.unpack('!d', bytes.fromhex(hex))[0]
356
+
357
+def assert_equal(value, hex, eps=0):
358
+    assert (float2hex(value) == hex # for NaN, etc.
359
+            or abs((value - hex2float(hex))) <= abs(eps * value))
360
+
361
+def test():
362
+    old_settings = np.seterr(all='ignore')
363
+
364
+    igral, err, nr_points = algorithm_4(f0, 0, 3, 1e-5)
365
+    print(igral, err, nr_points)
366
+    assert_equal(igral, '3fffb6084c1dabf4')
367
+    assert_equal(err, '3ef46042cb969374')
368
+    assert nr_points == 1419
369
+
370
+    igral, err, nr_points = algorithm_4(f7, 0, 1, 1e-6)
371
+    print(igral, err, nr_points)
372
+    assert_equal(igral, '3fffffffd9fa6513')
373
+    assert_equal(err, '3ebd8955755be30c')
374
+    assert nr_points == 709
375
+
376
+    igral, err, nr_points = algorithm_4(f24, 0, 3, 1e-3)
377
+    print(igral, err, nr_points)
378
+    assert_equal(igral, '4031aa1505ba7b41')
379
+    assert_equal(err, '3f9202232bd03a6a')
380
+    assert nr_points == 4515
381
+
382
+    igral, err, nr_points = algorithm_4(f21, 0, 1, 1e-3)
383
+    print(igral, err, nr_points)
384
+    assert_equal(igral, '3fc4e088c36827c1')
385
+    assert_equal(err, '3f247d00177a3f07')
386
+    assert nr_points == 203
387
+
388
+    igral, err, nr_points = algorithm_4(f63, 0, 1, 1e-10)
389
+    print(igral, err, nr_points)
390
+    assert_equal(igral, '3fff7ccfd769d160')
391
+    assert_equal(err, '3e28f421b487f15a', 2e-15)
392
+    assert nr_points == 2715
393
+
394
+    try:
395
+        igral, err, nr_points = algorithm_4(fdiv, 0, 1, 1e-6)
396
+    except DivergentIntegralError as e:
397
+        print(e.igral, e.err, e.nr_points)
398
+        assert_equal(e.igral, '7ff0000000000000')
399
+        assert_equal(e.err, '4073b48aeb356df5')
400
+        assert e.nr_points == 457
401
+
402
+    np.seterr(**old_settings)
403
+
404
+
405
+if __name__ == '__main__':
406
+    test()
Browse code

rename cquad script

Bas Nijholt authored on 20/09/2017 14:12:51
Showing 1 changed files
1 1
deleted file mode 100644
... ...
@@ -1,411 +0,0 @@
1
-# Copyright 2010 Pedro Gonnet
2
-# Copyright 2017 Christoph Groth
3
-
4
-import warnings
5
-import numpy as np
6
-from numpy.linalg import cond
7
-from scipy.linalg import inv, solve
8
-from scipy.linalg.blas import dgemv
9
-
10
-eps = np.spacing(1)
11
-
12
-# The following two functions allow for almost bit-per-bit equivalence
13
-# with the matlab code as interpreted by octave.
14
-
15
-def norm(a):
16
-    return np.sqrt(a @ a)
17
-
18
-
19
-def mvmul(a, b):
20
-    return dgemv(1.0, a, b)
21
-
22
-
23
-# the nodes and newton polynomials
24
-n = (5, 9, 17, 33)
25
-xi = [-np.cos(np.arange(n[j])/(n[j]-1) * np.pi) for j in range(4)]
26
-b_def = (np.array([0, .233284737407921723637836578544e-1,
27
-                   0, -.831479419283098085685277496071e-1,
28
-                   0, .0541462136776153483932540272848 ]),
29
-         np.array([0, .883654308363339862264532494396e-4,
30
-                   0, .238811521522368331303214066075e-3,
31
-                   0, .135365534194038370983135068211e-2,
32
-                   0, -.520710690438660595086839959882e-2,
33
-                   0, .00341659266223572272892690737979 ]),
34
-         np.array([0, .379785635776247894184454273159e-7,
35
-                   0, .655473977795402040043020497901e-7,
36
-                   0, .103479954638984842226816620692e-6,
37
-                   0, .173700624961660596894381303819e-6,
38
-                   0, .337719613424065357737699682062e-6,
39
-                   0, .877423283550614343733565759649e-6,
40
-                   0, .515657204371051131603503471028e-5,
41
-                   0, -.203244736027387801432055290742e-4,
42
-                   0, .0000134265158311651777460545854542 ]),
43
-         np.array([0, .703046511513775683031092069125e-13,
44
-                   0, .110617117381148770138566741591e-12,
45
-                   0, .146334657087392356024202217074e-12,
46
-                   0, .184948444492681259461791759092e-12,
47
-                   0, .231429962470609662207589449428e-12,
48
-                   0, .291520062115989014852816512412e-12,
49
-                   0, .373653379768759953435020783965e-12,
50
-                   0, .491840460397998449461993671859e-12,
51
-                   0, .671514395653454630785723660045e-12,
52
-                   0, .963162916392726862525650710866e-12,
53
-                   0, .147853378943890691325323722031e-11,
54
-                   0, .250420145651013003355273649380e-11,
55
-                   0, .495516257435784759806147914867e-11,
56
-                   0, .130927034711760871648068641267e-10,
57
-                   0, .779528640561654357271364483150e-10,
58
-                   0, -.309866395328070487426324760520e-9,
59
-                   0, .205572320292667201732878773151e-9]))
60
-
61
-def calc_V(xi, n):
62
-    V = [np.ones(xi.shape), xi.copy()]
63
-    for i in range(2, n):
64
-        V.append((2*i-1) / i * xi * V[-1] - (i-1) / i * V[-2])
65
-    for i in range(n):
66
-        V[i] *= np.sqrt(i + 0.5)
67
-    return np.array(V).T
68
-
69
-# compute the coefficients
70
-V = [calc_V(*args) for args in zip(xi, n)]
71
-V_inv = list(map(inv, V))
72
-Vcond = list(map(cond, V))
73
-
74
-# shift matrix
75
-T_lr = [V_inv[3] @ calc_V((xi[3] + a) / 2, n[3]) for a in [-1, 1]]
76
-
77
-# compute the integral
78
-w = np.sqrt(0.5)                # legendre
79
-
80
-# set-up the downdate matrix
81
-k = np.arange(n[3])
82
-U = (np.diag(np.sqrt((k+1)**2 / (2*k+1) / (2*k+3)))
83
-     + np.diag(np.sqrt(k[2:]**2 / (4*k[2:]**2-1)), 2))
84
-
85
-
86
-def _calc_coeffs(fx, depth):
87
-    """Caution: this function modifies fx."""
88
-    nans = []
89
-    for i in range(len(fx)):
90
-        if not np.isfinite(fx[i]):
91
-            fx[i] = 0.0
92
-            nans.append(i)
93
-    c_new = mvmul(V_inv[depth], fx)
94
-    if len(nans) > 0:
95
-        b_new = b_def[depth].copy()
96
-        n_new = n[depth] - 1
97
-        for i in nans:
98
-            b_new[:-1] = solve(
99
-                (U[:n[depth], :n[depth]] - np.diag(np.ones(n[depth] - 1)
100
-                                                   * xi[depth][i], 1)),
101
-                b_new[1:])
102
-            b_new[-1] = 0
103
-            c_new -= c_new[n_new] / b_new[n_new] * b_new[:-1]
104
-            n_new -= 1
105
-            fx[i] = np.nan
106
-    return c_new
107
-
108
-
109
-class DivergentIntegralError(ValueError):
110
-    def __init__(self, msg, igral, err, nr_points):
111
-        self.igral = igral
112
-        self.err = err
113
-        self.nr_points = nr_points
114
-        super().__init__(msg)
115
-
116
-
117
-class _Interval:
118
-    __slots__ = ['a', 'b', 'c', 'c_old', 'fx', 'igral', 'err', 'tol',
119
-                 'rdepth', 'ndiv']
120
-
121
-    @classmethod
122
-    def make_first(cls, f, a, b, tol):
123
-        points = (a+b)/2 + (b-a) * xi[3] / 2
124
-        fx = f(points)
125
-        nans = []
126
-        for i in range(len(fx)):
127
-            if not np.isfinite(fx[i]):
128
-                nans.append(i)
129
-                fx[i] = 0.0
130
-        ival = _Interval()
131
-        ival.c = np.zeros((4, n[3]))
132
-        ival.c[3, :n[3]] = mvmul(V_inv[3], fx)
133
-        ival.c[2, :n[2]] = mvmul(V_inv[2], fx[:n[3]:2])
134
-        fx[nans] = np.nan
135
-        ival.fx = fx
136
-        ival.c_old = np.zeros(fx.shape)
137
-        ival.a = a
138
-        ival.b = b
139
-        ival.igral = (b-a) * w * ival.c[3, 0]
140
-        c_diff = norm(ival.c[3] - ival.c[2])
141
-        ival.err = (b-a) * c_diff
142
-        if c_diff / norm(ival.c[3]) > 0.1:
143
-            ival.err = max( ival.err , (b-a) * norm(ival.c[3]) )
144
-        ival.tol = tol
145
-        ival.ndiv = 0
146
-        ival.rdepth = 1
147
-        return ival, points
148
-
149
-    @property
150
-    def depth(self):
151
-        return n.index(len(self.fx)) + 1
152
-
153
-    def split(self, f, ndiv_max=20):
154
-        a = self.a
155
-        b = self.b
156
-        m = (a + b) / 2
157
-        f_center = self.fx[(len(self.fx)-1)//2]
158
-
159
-        ivals = []
160
-        nr_points = 0
161
-        for aa, bb, f_left, f_right, T in [
162
-                (a, m, self.fx[0], f_center, T_lr[0]),
163
-                (m, b, f_center, self.fx[-1], T_lr[1])]:
164
-            ival = _Interval()
165
-            ivals.append(ival)
166
-            ival.a = aa
167
-            ival.b = bb
168
-            ival.tol = self.tol / np.sqrt(2)
169
-            ival.rdepth = self.rdepth + 1
170
-            ival.c = np.zeros((4, n[3]))
171
-            fx = np.concatenate(
172
-                ([f_left],
173
-                 f((aa + bb) / 2 + (bb - aa) * xi[0][1:-1] / 2),
174
-                 [f_right]))
175
-            nr_points += n[0] - 2
176
-
177
-            ival.c[0, :n[0]] = c_new = _calc_coeffs(fx, 0)
178
-            ival.fx = fx
179
-
180
-            ival.c_old = mvmul(T, self.c[self.depth - 1])
181
-            c_diff = norm(ival.c[0] - ival.c_old)
182
-            ival.err = (bb - aa) * c_diff
183
-            ival.igral = (bb - aa) * ival.c[0, 0] * w
184
-            ival.ndiv = (self.ndiv
185
-                         + (abs(self.c[0, 0]) > 0
186
-                            and ival.c[0, 0] / self.c[0, 0] > 2))
187
-            if ival.ndiv > ndiv_max and 2*ival.ndiv > ival.rdepth:
188
-                return (aa, bb, bb-aa), nr_points
189
-        for ival in ivals:
190
-            assert len(ival.fx) == n[ival.depth - 1]
191
-        return ivals, nr_points
192
-
193
-    def refine(self, f):
194
-        """Increase degree of interval."""
195
-        depth = self.depth
196
-        if depth >= 4:
197
-            raise RuntimeError('Max refinement level exceeded.')
198
-        a = self.a
199
-        b = self.b
200
-        points = (a+b)/2 + (b-a)*xi[depth]/2
201
-        fx = np.empty(n[depth])
202
-        fx[0::2] = self.fx
203
-        fx[1:-1:2] = f(points[1:n[depth]-1:2])
204
-        fx = fx[:n[depth]]
205
-        self.c[depth, :n[depth]] = c_new = _calc_coeffs(fx, depth)
206
-        self.fx = fx
207
-        c_diff = norm(self.c[depth - 1] - self.c[depth])
208
-        self.err = (b-a) * c_diff
209
-        self.igral = (b-a) * w * c_new[0]
210
-        nc = norm(c_new)
211
-        if nc > 0 and c_diff / nc > 0.1:
212
-            split = True
213
-        else:
214
-            split = False
215
-        assert len(self.fx) == n[self.depth - 1]
216
-        return points, split, n[depth] - n[depth-1]
217
-
218
-
219
-def algorithm_4 (f, a, b, tol):
220
-    """ALGORITHM_4 evaluates an integral using adaptive quadrature. The
221
-    algorithm uses Clenshaw-Curtis quadrature rules of increasing
222
-    degree in each interval and bisects the interval if either the
223
-    function does not appear to be smooth or a rule of maximum degree
224
-    has been reached. The error estimate is computed from the L2-norm
225
-    of the difference between two successive interpolations of the
226
-    integrand over the nodes of the respective quadrature rules.
227
-
228
-    INT = ALGORITHM_4 ( F , A , B , TOL ) approximates the integral of
229
-    F in the interval [A,B] up to the relative tolerance TOL. The
230
-    integrand F should accept a vector argument and return a vector
231
-    result containing the integrand evaluated at each element of the
232
-    argument.
233
-
234
-    [INT,ERR,NR_POINTS] = ALGORITHM_4 ( F , A , B , TOL ) returns ERR,
235
-    an estimate of the absolute integration error as well as
236
-    NR_POINTS, the number of function values for which the integrand
237
-    was evaluated. The value of ERR may be larger than the requested
238
-    tolerance, indicating that the integration may have failed.
239
-
240
-    ALGORITHM_4 halts with a warning if the integral is or appears to
241
-    be divergent.
242
-
243
-    Reference: "Increasing the Reliability of Adaptive Quadrature
244
-        Using Explicit Interpolants", P. Gonnet, ACM Transactions on
245
-        Mathematical Software, 37 (3), art. no. 26, 2008.
246
-    """
247
-
248
-    # compute the first interval
249
-    ival, points = _Interval.make_first(f, a, b, tol)
250
-    ivals = [ival]
251
-
252
-    # init some globals
253
-    igral = ival.igral
254
-    err = ival.err
255
-    igral_final = 0
256
-    err_final = 0
257
-    i_max = 0
258
-    nr_points = n[3]
259
-
260
-    # do we even need to go this way?
261
-    if err < igral * tol:
262
-        return igral, err, nr_points
263
-
264
-    # main loop
265
-    while True:
266
-        if ivals[i_max].depth == 4:
267
-            split = True
268
-        else:
269
-            points, split, nr_points_inc = ivals[i_max].refine(f)
270
-            nr_points += nr_points_inc
271
-
272
-        # can we safely ignore this interval?
273
-        if (points[1] <= points[0]
274
-            or points[-1] <= points[-2]
275
-            or ivals[i_max].err < (abs(ivals[i_max].igral) * eps
276
-                                   * Vcond[ivals[i_max].depth - 1])):
277
-            err_final += ivals[i_max].err
278
-            igral_final += ivals[i_max].igral
279
-            ivals[i_max] = ivals.pop()
280
-        elif split:
281
-            result, nr_points_inc = ivals[i_max].split(f)
282
-            nr_points += nr_points_inc
283
-            if isinstance(result, tuple):
284
-                igral = np.sign(igral) * np.inf
285
-                raise DivergentIntegralError(
286
-                    'Possibly divergent integral in the interval'
287
-                    ' [{}, {}]! (h={})'.format(*result),
288
-                    igral, err, nr_points)
289
-            ivals.extend(result)
290
-            ivals[i_max] = ivals.pop()
291
-
292
-        # compute the running err and new max
293
-        i_max = 0
294
-        i_min = 0
295
-        err = err_final
296
-        igral = igral_final
297
-        for i in range(len(ivals)):
298
-            if ivals[i].err > ivals[i_max].err:
299
-                i_max = i
300
-            elif ivals[i].err < ivals[i_min].err:
301
-                i_min = i
302
-            err += ivals[i].err
303
-            igral += ivals[i].igral
304
-
305
-        # nuke smallest element if stack is larger than 200
306
-        if len(ivals) > 200:
307
-            err_final += ivals[i_min].err
308
-            igral_final += ivals[i_min].igral
309
-            ivals[i_min] = ivals.pop()
310
-            if i_max == len(ivals):
311
-                i_max = i_min
312
-
313
-        # get up and leave?
314
-        if (err == 0
315
-            or err < abs(igral) * tol
316
-            or (err_final > abs(igral) * tol
317
-                and err - err_final < abs(igral) * tol)
318
-            or not ivals):
319
-            break
320
-
321
-    return igral, err, nr_points
322
-
323
-
324
-################ Tests ################
325
-
326
-def f0(x):
327
-    return x * np.sin(1/x) * np.sqrt(abs(1 - x))
328
-
329
-
330
-def f7(x):
331
-    return x**-0.5
332
-
333
-
334
-def f24(x):
335
-    return np.floor(np.exp(x))
336
-
337
-
338
-def f21(x):
339
-    y = 0
340
-    for i in range(1, 4):
341
-        y += 1 / np.cosh(20**i * (x - 2 * i / 10))
342
-    return y
343
-
344
-
345
-def f63(x):
346
-    return abs(x - 0.987654321)**-0.45
347
-
348
-
349
-def fdiv(x):
350
-    return abs(x - 0.987654321)**-1.1
351
-
352
-
353
-import struct
354
-
355
-def float2hex(x):
356
-    return struct.pack('!d', x).hex()
357
-
358
-def hex2float(hex):
359
-    return struct.unpack('!d', bytes.fromhex(hex))[0]
360
-
361
-def assert_equal(value, hex, eps=0):
362
-    assert (float2hex(value) == hex # for NaN, etc.
363
-            or abs((value - hex2float(hex))) <= abs(eps * value)),\
364
-            (abs((value - hex2float(hex))), eps, value)
365
-
366
-def test():
367
-    old_settings = np.seterr(all='ignore')
368
-
369
-    igral, err, nr_points = algorithm_4(f0, 0, 3, 1e-5)
370
-    print(igral, err, nr_points)
371
-    assert_equal(igral, '3fffb6084c1dabf4')
372
-    assert_equal(err, '3ef46042cb969374')
373
-    assert nr_points == 1419, nr_points
374
-
375
-    igral, err, nr_points = algorithm_4(f7, 0, 1, 1e-6)
376
-    print(igral, err, nr_points)
377
-    assert_equal(igral, '3fffffffd9fa6513')
378
-    assert_equal(err, '3ebd8955755be30c')
379
-    assert nr_points == 709, nr_points
380
-
381
-    igral, err, nr_points = algorithm_4(f24, 0, 3, 1e-3)
382
-    print(igral, err, nr_points)
383
-    assert_equal(igral, '4031aa1505ba7b41')
384
-    assert_equal(err, '3f9202232bd03a6a')
385
-    assert nr_points == 4515, nr_points
386
-
387
-    igral, err, nr_points = algorithm_4(f21, 0, 1, 1e-3)
388
-    print(igral, err, nr_points)
389
-    assert_equal(igral, '3fc4e088c36827c1')
390
-    assert_equal(err, '3f247d00177a3f07')
391
-    assert nr_points == 203, nr_points
392
-
393
-    igral, err, nr_points = algorithm_4(f63, 0, 1, 1e-10)
394
-    print(igral, err, nr_points)
395
-    assert_equal(igral, '3fff7ccfd769d160')
396
-    assert_equal(err, '3e28f421b487f15a', 2e-15)
397
-    assert nr_points == 2715, nr_points
398
-
399
-    try:
400
-        igral, err, nr_points = algorithm_4(fdiv, 0, 1, 1e-6)
401
-    except DivergentIntegralError as e:
402
-        print(e.igral, e.err, e.nr_points)
403
-        assert_equal(e.igral, '7ff0000000000000')
404
-        assert_equal(e.err, '4073b48aeb356df5')
405
-        assert e.nr_points == 457, e.nr_points
406
-
407
-    np.seterr(**old_settings)
408
-
409
-
410
-if __name__ == '__main__':
411
-    test()
Browse code

add Christoph's cquad

Bas Nijholt authored on 20/09/2017 14:12:27
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,411 @@
1
+# Copyright 2010 Pedro Gonnet
2
+# Copyright 2017 Christoph Groth
3
+
4
+import warnings
5
+import numpy as np
6
+from numpy.linalg import cond
7
+from scipy.linalg import inv, solve
8
+from scipy.linalg.blas import dgemv
9
+
10
+eps = np.spacing(1)
11
+
12
+# The following two functions allow for almost bit-per-bit equivalence
13
+# with the matlab code as interpreted by octave.
14
+
15
+def norm(a):
16
+    return np.sqrt(a @ a)
17
+
18
+
19
+def mvmul(a, b):
20
+    return dgemv(1.0, a, b)
21
+
22
+
23
+# the nodes and newton polynomials
24
+n = (5, 9, 17, 33)
25
+xi = [-np.cos(np.arange(n[j])/(n[j]-1) * np.pi) for j in range(4)]
26
+b_def = (np.array([0, .233284737407921723637836578544e-1,
27
+                   0, -.831479419283098085685277496071e-1,
28
+                   0, .0541462136776153483932540272848 ]),
29
+         np.array([0, .883654308363339862264532494396e-4,
30
+                   0, .238811521522368331303214066075e-3,
31
+                   0, .135365534194038370983135068211e-2,
32
+                   0, -.520710690438660595086839959882e-2,
33
+                   0, .00341659266223572272892690737979 ]),
34
+         np.array([0, .379785635776247894184454273159e-7,
35
+                   0, .655473977795402040043020497901e-7,
36
+                   0, .103479954638984842226816620692e-6,
37
+                   0, .173700624961660596894381303819e-6,
38
+                   0, .337719613424065357737699682062e-6,
39
+                   0, .877423283550614343733565759649e-6,
40
+                   0, .515657204371051131603503471028e-5,
41
+                   0, -.203244736027387801432055290742e-4,
42
+                   0, .0000134265158311651777460545854542 ]),
43
+         np.array([0, .703046511513775683031092069125e-13,
44
+                   0, .110617117381148770138566741591e-12,
45
+                   0, .146334657087392356024202217074e-12,
46
+                   0, .184948444492681259461791759092e-12,
47
+                   0, .231429962470609662207589449428e-12,
48
+                   0, .291520062115989014852816512412e-12,
49
+                   0, .373653379768759953435020783965e-12,
50
+                   0, .491840460397998449461993671859e-12,
51
+                   0, .671514395653454630785723660045e-12,
52
+                   0, .963162916392726862525650710866e-12,
53
+                   0, .147853378943890691325323722031e-11,
54
+                   0, .250420145651013003355273649380e-11,
55
+                   0, .495516257435784759806147914867e-11,
56
+                   0, .130927034711760871648068641267e-10,
57
+                   0, .779528640561654357271364483150e-10,
58
+                   0, -.309866395328070487426324760520e-9,
59
+                   0, .205572320292667201732878773151e-9]))
60
+
61
+def calc_V(xi, n):
62
+    V = [np.ones(xi.shape), xi.copy()]
63
+    for i in range(2, n):
64
+        V.append((2*i-1) / i * xi * V[-1] - (i-1) / i * V[-2])
65
+    for i in range(n):
66
+        V[i] *= np.sqrt(i + 0.5)
67
+    return np.array(V).T
68
+
69
+# compute the coefficients
70
+V = [calc_V(*args) for args in zip(xi, n)]
71
+V_inv = list(map(inv, V))
72
+Vcond = list(map(cond, V))
73
+
74
+# shift matrix
75
+T_lr = [V_inv[3] @ calc_V((xi[3] + a) / 2, n[3]) for a in [-1, 1]]
76
+
77
+# compute the integral
78
+w = np.sqrt(0.5)                # legendre
79
+
80
+# set-up the downdate matrix
81
+k = np.arange(n[3])
82
+U = (np.diag(np.sqrt((k+1)**2 / (2*k+1) / (2*k+3)))
83
+     + np.diag(np.sqrt(k[2:]**2 / (4*k[2:]**2-1)), 2))
84
+
85
+
86
+def _calc_coeffs(fx, depth):
87
+    """Caution: this function modifies fx."""
88
+    nans = []
89
+    for i in range(len(fx)):
90
+        if not np.isfinite(fx[i]):
91
+            fx[i] = 0.0
92
+            nans.append(i)
93
+    c_new = mvmul(V_inv[depth], fx)
94
+    if len(nans) > 0:
95
+        b_new = b_def[depth].copy()
96
+        n_new = n[depth] - 1
97
+        for i in nans:
98
+            b_new[:-1] = solve(
99
+                (U[:n[depth], :n[depth]] - np.diag(np.ones(n[depth] - 1)
100
+                                                   * xi[depth][i], 1)),
101
+                b_new[1:])
102
+            b_new[-1] = 0
103
+            c_new -= c_new[n_new] / b_new[n_new] * b_new[:-1]
104
+            n_new -= 1
105
+            fx[i] = np.nan
106
+    return c_new
107
+
108
+
109
+class DivergentIntegralError(ValueError):
110
+    def __init__(self, msg, igral, err, nr_points):
111
+        self.igral = igral
112
+        self.err = err
113
+        self.nr_points = nr_points
114
+        super().__init__(msg)
115
+
116
+
117
+class _Interval:
118
+    __slots__ = ['a', 'b', 'c', 'c_old', 'fx', 'igral', 'err', 'tol',
119
+                 'rdepth', 'ndiv']
120
+
121
+    @classmethod
122
+    def make_first(cls, f, a, b, tol):
123
+        points = (a+b)/2 + (b-a) * xi[3] / 2
124
+        fx = f(points)
125
+        nans = []
126
+        for i in range(len(fx)):
127
+            if not np.isfinite(fx[i]):
128
+                nans.append(i)
129
+                fx[i] = 0.0
130
+        ival = _Interval()
131
+        ival.c = np.zeros((4, n[3]))
132
+        ival.c[3, :n[3]] = mvmul(V_inv[3], fx)
133
+        ival.c[2, :n[2]] = mvmul(V_inv[2], fx[:n[3]:2])
134
+        fx[nans] = np.nan
135
+        ival.fx = fx
136
+        ival.c_old = np.zeros(fx.shape)
137
+        ival.a = a
138
+        ival.b = b
139
+        ival.igral = (b-a) * w * ival.c[3, 0]
140
+        c_diff = norm(ival.c[3] - ival.c[2])
141
+        ival.err = (b-a) * c_diff
142
+        if c_diff / norm(ival.c[3]) > 0.1:
143
+            ival.err = max( ival.err , (b-a) * norm(ival.c[3]) )
144
+        ival.tol = tol
145
+        ival.ndiv = 0
146
+        ival.rdepth = 1
147
+        return ival, points
148
+
149
+    @property
150
+    def depth(self):
151
+        return n.index(len(self.fx)) + 1
152
+
153
+    def split(self, f, ndiv_max=20):
154
+        a = self.a
155
+        b = self.b
156
+        m = (a + b) / 2
157
+        f_center = self.fx[(len(self.fx)-1)//2]
158
+
159
+        ivals = []
160
+        nr_points = 0
161
+        for aa, bb, f_left, f_right, T in [
162
+                (a, m, self.fx[0], f_center, T_lr[0]),
163
+                (m, b, f_center, self.fx[-1], T_lr[1])]:
164
+            ival = _Interval()
165
+            ivals.append(ival)
166
+            ival.a = aa
167
+            ival.b = bb
168
+            ival.tol = self.tol / np.sqrt(2)
169
+            ival.rdepth = self.rdepth + 1
170
+            ival.c = np.zeros((4, n[3]))
171
+            fx = np.concatenate(
172
+                ([f_left],
173
+                 f((aa + bb) / 2 + (bb - aa) * xi[0][1:-1] / 2),
174
+                 [f_right]))
175
+            nr_points += n[0] - 2
176
+
177
+            ival.c[0, :n[0]] = c_new = _calc_coeffs(fx, 0)
178
+            ival.fx = fx
179
+
180
+            ival.c_old = mvmul(T, self.c[self.depth - 1])
181
+            c_diff = norm(ival.c[0] - ival.c_old)
182
+            ival.err = (bb - aa) * c_diff
183
+            ival.igral = (bb - aa) * ival.c[0, 0] * w
184
+            ival.ndiv = (self.ndiv
185
+                         + (abs(self.c[0, 0]) > 0
186
+                            and ival.c[0, 0] / self.c[0, 0] > 2))
187
+            if ival.ndiv > ndiv_max and 2*ival.ndiv > ival.rdepth:
188
+                return (aa, bb, bb-aa), nr_points
189
+        for ival in ivals:
190
+            assert len(ival.fx) == n[ival.depth - 1]
191
+        return ivals, nr_points
192
+
193
+    def refine(self, f):
194
+        """Increase degree of interval."""
195
+        depth = self.depth
196
+        if depth >= 4:
197
+            raise RuntimeError('Max refinement level exceeded.')
198
+        a = self.a
199
+        b = self.b
200
+        points = (a+b)/2 + (b-a)*xi[depth]/2
201
+        fx = np.empty(n[depth])
202
+        fx[0::2] = self.fx
203
+        fx[1:-1:2] = f(points[1:n[depth]-1:2])
204
+        fx = fx[:n[depth]]
205
+        self.c[depth, :n[depth]] = c_new = _calc_coeffs(fx, depth)
206
+        self.fx = fx
207
+        c_diff = norm(self.c[depth - 1] - self.c[depth])
208
+        self.err = (b-a) * c_diff
209
+        self.igral = (b-a) * w * c_new[0]
210
+        nc = norm(c_new)
211
+        if nc > 0 and c_diff / nc > 0.1:
212
+            split = True
213
+        else:
214
+            split = False
215
+        assert len(self.fx) == n[self.depth - 1]
216
+        return points, split, n[depth] - n[depth-1]
217
+
218
+
219
+def algorithm_4 (f, a, b, tol):
220
+    """ALGORITHM_4 evaluates an integral using adaptive quadrature. The
221
+    algorithm uses Clenshaw-Curtis quadrature rules of increasing
222
+    degree in each interval and bisects the interval if either the
223
+    function does not appear to be smooth or a rule of maximum degree
224
+    has been reached. The error estimate is computed from the L2-norm
225
+    of the difference between two successive interpolations of the
226
+    integrand over the nodes of the respective quadrature rules.
227
+
228
+    INT = ALGORITHM_4 ( F , A , B , TOL ) approximates the integral of
229
+    F in the interval [A,B] up to the relative tolerance TOL. The
230
+    integrand F should accept a vector argument and return a vector
231
+    result containing the integrand evaluated at each element of the
232
+    argument.
233
+
234
+    [INT,ERR,NR_POINTS] = ALGORITHM_4 ( F , A , B , TOL ) returns ERR,
235
+    an estimate of the absolute integration error as well as
236
+    NR_POINTS, the number of function values for which the integrand
237
+    was evaluated. The value of ERR may be larger than the requested
238
+    tolerance, indicating that the integration may have failed.
239
+
240
+    ALGORITHM_4 halts with a warning if the integral is or appears to
241
+    be divergent.
242
+
243
+    Reference: "Increasing the Reliability of Adaptive Quadrature
244
+        Using Explicit Interpolants", P. Gonnet, ACM Transactions on
245
+        Mathematical Software, 37 (3), art. no. 26, 2008.
246
+    """
247
+
248
+    # compute the first interval
249
+    ival, points = _Interval.make_first(f, a, b, tol)
250
+    ivals = [ival]
251
+
252
+    # init some globals
253
+    igral = ival.igral
254
+    err = ival.err
255
+    igral_final = 0
256
+    err_final = 0
257
+    i_max = 0
258
+    nr_points = n[3]
259
+
260
+    # do we even need to go this way?
261
+    if err < igral * tol:
262
+        return igral, err, nr_points
263
+
264
+    # main loop
265
+    while True:
266
+        if ivals[i_max].depth == 4:
267
+            split = True
268
+        else:
269
+            points, split, nr_points_inc = ivals[i_max].refine(f)
270
+            nr_points += nr_points_inc
271
+
272
+        # can we safely ignore this interval?
273
+        if (points[1] <= points[0]
274
+            or points[-1] <= points[-2]
275
+            or ivals[i_max].err < (abs(ivals[i_max].igral) * eps
276
+                                   * Vcond[ivals[i_max].depth - 1])):
277
+            err_final += ivals[i_max].err
278
+            igral_final += ivals[i_max].igral
279
+            ivals[i_max] = ivals.pop()
280
+        elif split:
281
+            result, nr_points_inc = ivals[i_max].split(f)
282
+            nr_points += nr_points_inc
283
+            if isinstance(result, tuple):
284
+                igral = np.sign(igral) * np.inf
285
+                raise DivergentIntegralError(
286
+                    'Possibly divergent integral in the interval'
287
+                    ' [{}, {}]! (h={})'.format(*result),
288
+                    igral, err, nr_points)
289
+            ivals.extend(result)
290
+            ivals[i_max] = ivals.pop()
291
+
292
+        # compute the running err and new max
293
+        i_max = 0
294
+        i_min = 0
295
+        err = err_final
296
+        igral = igral_final
297
+        for i in range(len(ivals)):
298
+            if ivals[i].err > ivals[i_max].err:
299
+                i_max = i
300
+            elif ivals[i].err < ivals[i_min].err:
301
+                i_min = i
302
+            err += ivals[i].err
303
+            igral += ivals[i].igral
304
+
305
+        # nuke smallest element if stack is larger than 200
306
+        if len(ivals) > 200:
307
+            err_final += ivals[i_min].err
308
+            igral_final += ivals[i_min].igral
309
+            ivals[i_min] = ivals.pop()
310
+            if i_max == len(ivals):
311
+                i_max = i_min
312
+
313
+        # get up and leave?
314
+        if (err == 0
315
+            or err < abs(igral) * tol
316
+            or (err_final > abs(igral) * tol
317
+                and err - err_final < abs(igral) * tol)
318
+            or not ivals):
319
+            break
320
+
321
+    return igral, err, nr_points
322
+
323
+
324
+################ Tests ################
325
+
326
+def f0(x):
327
+    return x * np.sin(1/x) * np.sqrt(abs(1 - x))
328
+
329
+
330
+def f7(x):
331
+    return x**-0.5
332
+
333
+
334
+def f24(x):
335
+    return np.floor(np.exp(x))
336
+
337
+
338
+def f21(x):
339
+    y = 0
340
+    for i in range(1, 4):
341
+        y += 1 / np.cosh(20**i * (x - 2 * i / 10))
342
+    return y
343
+
344
+
345
+def f63(x):
346
+    return abs(x - 0.987654321)**-0.45
347
+
348
+
349
+def fdiv(x):
350
+    return abs(x - 0.987654321)**-1.1
351
+
352
+
353
+import struct
354
+
355
+def float2hex(x):
356
+    return struct.pack('!d', x).hex()
357
+
358
+def hex2float(hex):
359
+    return struct.unpack('!d', bytes.fromhex(hex))[0]
360
+
361
+def assert_equal(value, hex, eps=0):
362
+    assert (float2hex(value) == hex # for NaN, etc.
363
+            or abs((value - hex2float(hex))) <= abs(eps * value)),\
364
+            (abs((value - hex2float(hex))), eps, value)
365
+
366
+def test():
367
+    old_settings = np.seterr(all='ignore')
368
+
369
+    igral, err, nr_points = algorithm_4(f0, 0, 3, 1e-5)
370
+    print(igral, err, nr_points)
371
+    assert_equal(igral, '3fffb6084c1dabf4')
372
+    assert_equal(err, '3ef46042cb969374')
373
+    assert nr_points == 1419, nr_points
374
+
375
+    igral, err, nr_points = algorithm_4(f7, 0, 1, 1e-6)
376
+    print(igral, err, nr_points)
377
+    assert_equal(igral, '3fffffffd9fa6513')
378
+    assert_equal(err, '3ebd8955755be30c')
379
+    assert nr_points == 709, nr_points
380
+
381
+    igral, err, nr_points = algorithm_4(f24, 0, 3, 1e-3)
382
+    print(igral, err, nr_points)
383
+    assert_equal(igral, '4031aa1505ba7b41')
384
+    assert_equal(err, '3f9202232bd03a6a')
385
+    assert nr_points == 4515, nr_points
386
+
387
+    igral, err, nr_points = algorithm_4(f21, 0, 1, 1e-3)
388
+    print(igral, err, nr_points)
389
+    assert_equal(igral, '3fc4e088c36827c1')
390
+    assert_equal(err, '3f247d00177a3f07')
391
+    assert nr_points == 203, nr_points
392
+
393
+    igral, err, nr_points = algorithm_4(f63, 0, 1, 1e-10)
394
+    print(igral, err, nr_points)
395
+    assert_equal(igral, '3fff7ccfd769d160')
396
+    assert_equal(err, '3e28f421b487f15a', 2e-15)
397
+    assert nr_points == 2715, nr_points
398
+
399
+    try:
400
+        igral, err, nr_points = algorithm_4(fdiv, 0, 1, 1e-6)
401
+    except DivergentIntegralError as e:
402
+        print(e.igral, e.err, e.nr_points)
403
+        assert_equal(e.igral, '7ff0000000000000')
404
+        assert_equal(e.err, '4073b48aeb356df5')
405
+        assert e.nr_points == 457, e.nr_points
406
+
407
+    np.seterr(**old_settings)
408
+
409
+
410
+if __name__ == '__main__':
411
+    test()