2
2
# Copyright 2017 Christoph Groth
3
3
4
4
from collections import defaultdict
5
- from fractions import Fraction as Frac
5
+ from fractions import Fraction
6
+ from functools import partial
7
+ from typing import Callable , List , Tuple , Union
6
8
7
9
import numpy as np
10
+ from numpy import float64 , ndarray
8
11
from numpy .testing import assert_allclose
9
12
from scipy .linalg import inv , norm
10
13
11
14
eps = np .spacing (1 )
12
15
13
16
14
- def legendre (n ) :
17
+ def legendre (n : int ) -> List [ List [ Fraction ]] :
15
18
"""Return the first n Legendre polynomials.
16
19
17
20
The polynomials have *standard* normalization, i.e.
18
21
int_{-1}^1 dx L_n(x) L_m(x) = delta(m, n) * 2 / (2 * n + 1).
19
22
20
23
The return value is a list of list of fraction.Fraction instances.
21
24
"""
22
- result = [[Frac (1 )], [Frac (0 ), Frac (1 )]]
25
+ result = [[Fraction (1 )], [Fraction (0 ), Fraction (1 )]]
23
26
if n <= 2 :
24
27
return result [:n ]
25
28
for i in range (2 , n ):
26
29
# Use Bonnet's recursion formula.
27
- new = (i + 1 ) * [Frac (0 )]
30
+ new = (i + 1 ) * [Fraction (0 )]
28
31
new [1 :] = (r * (2 * i - 1 ) for r in result [- 1 ])
29
32
new [:- 2 ] = (n - r * (i - 1 ) for n , r in zip (new [:- 2 ], result [- 2 ]))
30
33
new [:] = (n / i for n in new )
31
34
result .append (new )
32
35
return result
33
36
34
37
35
- def newton (n ) :
38
+ def newton (n : int ) -> ndarray :
36
39
"""Compute the monomial coefficients of the Newton polynomial over the
37
40
nodes of the n-point Clenshaw-Curtis quadrature rule.
38
41
"""
@@ -89,7 +92,7 @@ def newton(n):
89
92
return cf
90
93
91
94
92
- def scalar_product (a , b ) :
95
+ def scalar_product (a : List [ Fraction ] , b : List [ Fraction ]) -> Fraction :
93
96
"""Compute the polynomial scalar product int_-1^1 dx a(x) b(x).
94
97
95
98
The args must be sequences of polynomial coefficients. This
@@ -110,7 +113,7 @@ def scalar_product(a, b):
110
113
return 2 * sum (c [i ] / (i + 1 ) for i in range (0 , lc , 2 ))
111
114
112
115
113
- def calc_bdef (ns ) :
116
+ def calc_bdef (ns : Tuple [ int , int , int , int ]) -> List [ ndarray ] :
114
117
"""Calculate the decompositions of Newton polynomials (over the nodes
115
118
of the n-point Clenshaw-Curtis quadrature rule) in terms of
116
119
Legandre polynomials.
@@ -123,7 +126,7 @@ def calc_bdef(ns):
123
126
result = []
124
127
for n in ns :
125
128
poly = []
126
- a = list (map (Frac , newton (n )))
129
+ a = list (map (Fraction , newton (n )))
127
130
for b in legs [: n + 1 ]:
128
131
igral = scalar_product (a , b )
129
132
@@ -145,7 +148,7 @@ def calc_bdef(ns):
145
148
b_def = calc_bdef (n )
146
149
147
150
148
- def calc_V (xi , n ) :
151
+ def calc_V (xi : ndarray , n : int ) -> ndarray :
149
152
V = [np .ones (xi .shape ), xi .copy ()]
150
153
for i in range (2 , n ):
151
154
V .append ((2 * i - 1 ) / i * xi * V [- 1 ] - (i - 1 ) / i * V [- 2 ])
@@ -183,7 +186,7 @@ def calc_V(xi, n):
183
186
gamma = np .concatenate ([[0 , 0 ], np .sqrt (k [2 :] ** 2 / (4 * k [2 :] ** 2 - 1 ))])
184
187
185
188
186
- def _downdate (c , nans , depth ) :
189
+ def _downdate (c : ndarray , nans : List [ int ] , depth : int ) -> None :
187
190
# This is algorithm 5 from the thesis of Pedro Gonnet.
188
191
b = b_def [depth ].copy ()
189
192
m = n [depth ] - 1
@@ -200,7 +203,7 @@ def _downdate(c, nans, depth):
200
203
m -= 1
201
204
202
205
203
- def _zero_nans (fx ) :
206
+ def _zero_nans (fx : ndarray ) -> List [ int ] :
204
207
nans = []
205
208
for i in range (len (fx )):
206
209
if not np .isfinite (fx [i ]):
@@ -209,7 +212,7 @@ def _zero_nans(fx):
209
212
return nans
210
213
211
214
212
- def _calc_coeffs (fx , depth ) :
215
+ def _calc_coeffs (fx : ndarray , depth : int ) -> ndarray :
213
216
"""Caution: this function modifies fx."""
214
217
nans = _zero_nans (fx )
215
218
c_new = V_inv [depth ] @ fx
@@ -220,7 +223,7 @@ def _calc_coeffs(fx, depth):
220
223
221
224
222
225
class DivergentIntegralError (ValueError ):
223
- def __init__ (self , msg , igral , err , nr_points ) :
226
+ def __init__ (self , msg : str , igral : float64 , err : None , nr_points : int ) -> None :
224
227
self .igral = igral
225
228
self .err = err
226
229
self .nr_points = nr_points
@@ -230,19 +233,23 @@ def __init__(self, msg, igral, err, nr_points):
230
233
class _Interval :
231
234
__slots__ = ["a" , "b" , "c" , "fx" , "igral" , "err" , "depth" , "rdepth" , "ndiv" , "c00" ]
232
235
233
- def __init__ (self , a , b , depth , rdepth ):
236
+ def __init__ (
237
+ self , a : Union [int , float ], b : Union [int , float ], depth : int , rdepth : int
238
+ ) -> None :
234
239
self .a = a
235
240
self .b = b
236
241
self .depth = depth
237
242
self .rdepth = rdepth
238
243
239
- def points (self ):
244
+ def points (self ) -> ndarray :
240
245
a = self .a
241
246
b = self .b
242
247
return (a + b ) / 2 + (b - a ) * xi [self .depth ] / 2
243
248
244
249
@classmethod
245
- def make_first (cls , f , a , b , depth = 2 ):
250
+ def make_first (
251
+ cls , f : Union [partial , Callable ], a : int , b : int , depth : int = 2
252
+ ) -> Tuple ["_Interval" , int ]:
246
253
ival = _Interval (a , b , depth , 1 )
247
254
fx = f (ival .points ())
248
255
ival .c = _calc_coeffs (fx , depth )
@@ -251,7 +258,7 @@ def make_first(cls, f, a, b, depth=2):
251
258
ival .ndiv = 0
252
259
return ival , n [depth ]
253
260
254
- def calc_igral_and_err (self , c_old ) :
261
+ def calc_igral_and_err (self , c_old : ndarray ) -> float :
255
262
self .c = c_new = _calc_coeffs (self .fx , self .depth )
256
263
c_diff = np .zeros (max (len (c_old ), len (c_new )))
257
264
c_diff [: len (c_old )] = c_old
@@ -262,7 +269,9 @@ def calc_igral_and_err(self, c_old):
262
269
self .err = w * c_diff
263
270
return c_diff
264
271
265
- def split (self , f ):
272
+ def split (
273
+ self , f : Union [partial , Callable ]
274
+ ) -> Union [Tuple [Tuple [float , float , float ], int ], Tuple [List ["_Interval" ], int ]]:
266
275
m = (self .a + self .b ) / 2
267
276
f_center = self .fx [(len (self .fx ) - 1 ) // 2 ]
268
277
@@ -287,7 +296,7 @@ def split(self, f):
287
296
288
297
return ivals , nr_points
289
298
290
- def refine (self , f ) :
299
+ def refine (self , f : Union [ partial , Callable ]) -> Tuple [ ndarray , bool , int ] :
291
300
"""Increase degree of interval."""
292
301
self .depth = depth = self .depth + 1
293
302
points = self .points ()
@@ -299,7 +308,9 @@ def refine(self, f):
299
308
return points , split , n [depth ] - n [depth - 1 ]
300
309
301
310
302
- def algorithm_4 (f , a , b , tol , N_loops = int (1e9 )):
311
+ def algorithm_4 (
312
+ f : Union [partial , Callable ], a : int , b : int , tol : float , N_loops : int = int (1e9 )
313
+ ) -> Tuple [float64 , float , int , List ["_Interval" ]]:
303
314
"""ALGORITHM_4 evaluates an integral using adaptive quadrature. The
304
315
algorithm uses Clenshaw-Curtis quadrature rules of increasing
305
316
degree in each interval and bisects the interval if either the
@@ -403,37 +414,39 @@ def algorithm_4(f, a, b, tol, N_loops=int(1e9)):
403
414
return igral , err , nr_points , ivals
404
415
405
416
406
- ################ Tests ################
417
+ # ############### Tests ################
407
418
408
419
409
- def f0 (x ) :
420
+ def f0 (x : Union [ float64 , ndarray ]) -> Union [ float64 , ndarray ] :
410
421
return x * np .sin (1 / x ) * np .sqrt (abs (1 - x ))
411
422
412
423
413
- def f7 (x ) :
424
+ def f7 (x : Union [ float64 , ndarray ]) -> Union [ float64 , ndarray ] :
414
425
return x ** - 0.5
415
426
416
427
417
- def f24 (x ) :
428
+ def f24 (x : Union [ float64 , ndarray ]) -> Union [ float64 , ndarray ] :
418
429
return np .floor (np .exp (x ))
419
430
420
431
421
- def f21 (x ) :
432
+ def f21 (x : Union [ float64 , ndarray ]) -> Union [ float64 , ndarray ] :
422
433
y = 0
423
434
for i in range (1 , 4 ):
424
435
y += 1 / np .cosh (20 ** i * (x - 2 * i / 10 ))
425
436
return y
426
437
427
438
428
- def f63 (x , alpha , beta ):
439
+ def f63 (
440
+ x : Union [float64 , ndarray ], alpha : float , beta : float
441
+ ) -> Union [float64 , ndarray ]:
429
442
return abs (x - beta ) ** alpha
430
443
431
444
432
445
def F63 (x , alpha , beta ):
433
446
return (x - beta ) * abs (x - beta ) ** alpha / (alpha + 1 )
434
447
435
448
436
- def fdiv (x ) :
449
+ def fdiv (x : Union [ float64 , ndarray ]) -> Union [ float64 , ndarray ] :
437
450
return abs (x - 0.987654321 ) ** - 1.1
438
451
439
452
@@ -461,7 +474,9 @@ def test_scalar_product(n=33):
461
474
selection = [0 , 5 , 7 , n - 1 ]
462
475
for i in selection :
463
476
for j in selection :
464
- assert scalar_product (legs [i ], legs [j ]) == ((i == j ) and Frac (2 , 2 * i + 1 ))
477
+ assert scalar_product (legs [i ], legs [j ]) == (
478
+ (i == j ) and Fraction (2 , 2 * i + 1 )
479
+ )
465
480
466
481
467
482
def simple_newton (n ):
0 commit comments