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() |
... | ... |
@@ -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() |
... | ... |
@@ -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 |
|
... | ... |
@@ -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 ]), |
... | ... |
@@ -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') |
... | ... |
@@ -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: |
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() |
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() |
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() |