Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

r""" 

Gauss-Legendre integration for vector-valued functions 

  

Routine to perform Gauss-Legendre integration for vector-functions. 

  

AUTHORS: 

  

- Nils Bruin (2017-06-06): initial version 

  

EXAMPLES:: 

  

NOTE: 

  

The code here is directly based on mpmath (see http://mpmath.org), but has a highly 

optimized routine to compute the nodes. 

""" 

  

#***************************************************************************** 

# Copyright (C) 2017 Nils Bruin <nbruin@sfu.ca> 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 2 of the License, or 

# (at your option) any later version. 

# http://www.gnu.org/licenses/ 

#***************************************************************************** 

  

#as it turns out, computing the nodes can easily turn out to be more 

#expensive than computing the integrals. So it's worth optimizing this. 

#making the function into a cython routine helps a little bit. If we really 

#want to we can optimize this further, probably to a point where 

#we don't have to bother with node computation routines that have a better order 

#than this naive approach (which is quadratic) 

from __future__ import absolute_import, division, print_function 

from sage.libs.mpfr cimport * 

import math 

from sage.rings.real_mpfr import RealField 

from sage.misc.cachefunc import cached_function 

from sage.rings.real_mpfr cimport RealNumber, RealField_class 

  

@cached_function 

def nodes(degree,prec): 

r""" 

Compute the integration nodes and weights for the Gauss-Legendre quadrature scheme. 

  

INPUT: 

  

- ``degree`` -- integer. The number of nodes. Must be 3 or even. 

  

- ``prec`` -- integer (minimal value 53). Binary precision with which the nodes and weights are computed. 

  

OUTPUT: 

  

A list of (node,weight) pairs. 

  

EXAMPLE: 

  

The nodes for the Gauss-Legendre scheme are roots of Legendre polynomials. 

The weights can be computed by a straightforward formula (note that evaluating 

a derivative of a Legendre polynomial isn't particularly numerically stable, so the results 

from this routine are actually more accurate than what the values the closed formula produces):: 

  

sage: from sage.numerical.gauss_legendre import nodes 

sage: L1=nodes(24,53) 

sage: P=RR['x'](sage.functions.orthogonal_polys.legendre_P(24,x)) 

sage: Pdif=P.diff() 

sage: L2=[( (r+1)/2,1/(1-r^2)/Pdif(r)^2) for r,_ in RR['x'](P).roots()] 

sage: all((a[0]-b[0]).abs() < 10^-15 and (a[1]-b[1]).abs() < 10^-9 for a,b in zip(L1,L2)) 

True 

""" 

cdef long j,j1,n 

cdef RealNumber r,t1,t2,t3,t4,a,w 

cdef mpfr_t u,v 

cdef RealField_class R 

if prec < 53: 

prec = 53 

if degree !=3 and degree % 2 !=0: 

raise ValueError("degree=%s not supported (degree must be 3 or even)"%degree) 

R = RealField(int(prec*3/2)) 

Rout = RealField(prec) 

mpfr_init2(u,R.__prec) 

mpfr_init2(v,R.__prec) 

ZERO = R.zero() 

ONE = R.one() 

HALF = ONE/2 

TWO = 2*ONE 

rnd = R.rnd 

epsilon = R(1)>>(prec+8) 

if degree == 1: 

x = (R(3)/5).sqrt() 

w = R(5)/18 

nodes = [((1-x)/2,w),(HALF,R(4)/9),((1+x)/2,w)] 

else: 

nodes = [] 

n = degree 

upto = n//2+1 

for j in xrange(1,upto): 

r = R(math.cos(math.pi*(j-0.25)/(n+0.5))) 

while True: 

t1,t2=ONE,ZERO 

for j1 in xrange(1,n+1): 

mpfr_mul(u,r.value,t1.value,rnd) 

mpfr_mul_si(u,u,2*j1-1,rnd) 

mpfr_mul_si(v,t2.value,j1-1,rnd) 

mpfr_sub(u,u,v,rnd) 

mpfr_div_si(u,u,j1,rnd) 

t2=t1 

t1=R._new() 

mpfr_set(t1.value,u,rnd) 

t4 = R(n)*(r*t1-t2)/(r**2-ONE) 

a = t1/t4 

r = r-a 

if a.abs()<epsilon: 

break 

x = r 

w = ONE/((ONE-r**2)*t4**2) 

nodes.append(((ONE+x)/TWO,w)) 

nodes.append(((ONE-x)/TWO,w)) 

nodes=[(Rout(x),Rout(w)) for x,w in nodes] 

nodes.sort() 

mpfr_clear(u) 

mpfr_clear(v) 

return nodes 

  

def estimate_error(results,prec,epsilon): 

r""" 

Routine to estimate the error in a list of quadrature approximations. 

 

The method used is based on Borwein, Bailey, and Girgensohn. As mentioned in 

mpmath: Although not very conservative, this method seems to be very robust in 

practice. 

  

The routine takes a list of vector results and, under assumption that these 

vectors approximate a given vector approximately quadratically, gives an estimate 

of the maximum norm of the error in the last approximation. 

  

INPUT: 

 

- ``results`` -- list. List of approximations to estimate the error from. Should be at least length 2. 

  

- ``prec`` -- integer. Binary precision at which computations are happening. 

  

- ``epsilon`` -- multiprecision float. Default error estimate in case of insufficient data. 

  

OUTPUT: 

  

An estimate of the error. 

  

EXAMPLES:: 

  

sage: from sage.numerical.gauss_legendre import estimate_error 

sage: prec = 200 

sage: K = RealField(prec) 

sage: V = VectorSpace(K,2) 

sage: a = V([1,-1]) 

sage: b = V([1,1/2]) 

sage: L = [a+2^(-2^i)*b for i in [0..5]] 

sage: estimate_error(L,prec,K(2^(-prec))) 

2.328235...e-10 

""" 

if len(results)==2: 

return max((results[0][i]-results[1][i]).abs() for i in xrange(len(results[0]))) 

e = [] 

ZERO = 0*epsilon 

for i in xrange(len(results[0])): 

try: 

if results[-1][i] == results[-2][i] == results[-3][i]: 

e.append(0*epsilon) 

D1 = (results[-1][i]-results[-2][i]).abs().log() 

D2 = (results[-1][i]-results[-3][i]).abs().log() 

except ValueError: 

e.append(epsilon) 

#we follow mpmath in clipping the precision 

D4 = min(ZERO,max(D1**2/D2,2*D1,ZERO-prec)) 

e.append(D4.exp()) 

return max(e) 

  

def integrate_vector(f,prec,epsilon=None): 

r""" 

Integrate a one-argument vector-valued function numerically using Gauss-Legendre. 

  

This function uses the Gauss-Legendre quadrature scheme to approximate 

the integral of f(t) for t=0..1. 

  

INPUT: 

  

- `f` -- callable. Vector-valued integrand. 

  

- `prec` -- integer. Binary precision to be used. 

  

- `epsilon` -- Multiprecision float. Target error bound. 

  

OUTPUT: 

  

Vector approximating value of the integral. 

  

EXAMPLES:: 

  

sage: from sage.numerical.gauss_legendre import integrate_vector 

sage: prec=200 

sage: K=RealField(prec) 

sage: V=VectorSpace(K,2) 

sage: epsilon=K(2^(-prec+4)) 

sage: f=lambda t:V((1+t^2,1/(1+t^2))) 

sage: I=integrate_vector(f,prec,epsilon) 

sage: J=V((4/3,pi/4)) 

sage: max(c.abs() for c in (I-J)) < epsilon 

True 

  

We can also use complex-valued integrands:: 

  

sage: prec=200 

sage: Kreal=RealField(prec) 

sage: K=ComplexField(prec) 

sage: V=VectorSpace(K,2) 

sage: epsilon=Kreal(2^(-prec+4)) 

sage: f=lambda t: V((t,K(exp(2*pi*t*K.0)))) 

sage: I=integrate_vector(f,prec,epsilon) 

sage: J=V((1/2,0)) 

sage: max(c.abs() for c in (I-J)) < epsilon 

True 

""" 

results = [] 

cdef long degree = 3 

Rout = RealField(prec) 

if epsilon is None: 

epsilon = Rout(2)**(-prec+3) 

while True: 

nodelist = nodes(degree,prec) 

I = nodelist[0][1]*f(nodelist[0][0]) 

for i in xrange(1,len(nodelist)): 

I += nodelist[i][1]*f(nodelist[i][0]) 

results.append(I) 

if degree > 3: 

err = estimate_error(results,prec,epsilon) 

if err <= epsilon: 

return I 

#double the degree to double expected precision 

degree *= 2