Coverage for pySDC/core/Nodes.py: 100%

78 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-29 09:02 +0000

1import numpy as np 

2from scipy.linalg import eigh_tridiagonal 

3 

4NODE_TYPES = ['EQUID', 'LEGENDRE', 'CHEBY-1', 'CHEBY-2', 'CHEBY-3', 'CHEBY-4'] 

5 

6QUAD_TYPES = ['GAUSS', 'RADAU-LEFT', 'RADAU-RIGHT', 'LOBATTO'] 

7 

8 

9class NodesError(Exception): 

10 """Exception class to handle error in NodesGenerator class""" 

11 

12 pass 

13 

14 

15class NodesGenerator(object): 

16 """ 

17 Class that can be used to generate generic distribution of nodes derived 

18 from Gauss quadrature rule. 

19 Its implementation is fully inspired from a `book of W. Gautschi <https://doi.org/10.1093/oso/9780198506720.001.0001>`_. 

20 

21 Attributes 

22 ---------- 

23 node_type : str 

24 The type of node distribution 

25 quad_type : str 

26 The quadrature type 

27 

28 """ 

29 

30 def __init__(self, node_type='LEGENDRE', quad_type='LOBATTO'): 

31 """ 

32 

33 Parameters 

34 ---------- 

35 node_type : str, optional 

36 The type of node distribution, can be 

37 

38 - EQUID : equidistant nodes 

39 - LEGENDRE : node distribution from Legendre polynomials 

40 - CHEBY-1 : node distribution from Chebychev polynomials (1st kind) 

41 - CHEBY-2 : node distribution from Chebychev polynomials (2nd kind) 

42 - CHEBY-3 : node distribution from Chebychev polynomials (3rd kind) 

43 - CHEBY-4 : node distribution from Chebychev polynomials (4th kind) 

44 

45 The default is 'LEGENDRE'. 

46 

47 quad_type : str, optional 

48 The quadrature type, can be 

49 

50 - GAUSS : inner point only, no node at boundary 

51 - RADAU-LEFT : only left boundary as node 

52 - RADAU-RIGHT : only right boundary as node 

53 - LOBATTO : left and right boundary as node 

54 

55 The default is 'LOBATTO'. 

56 """ 

57 

58 # Check argument validity 

59 for arg, vals in zip(['node_type', 'quad_type'], [NODE_TYPES, QUAD_TYPES]): 

60 val = eval(arg) 

61 if val not in vals: 

62 raise NodesError(f"{arg}='{val}' not implemented, must be in {vals}") 

63 

64 # Store attributes 

65 self.node_type = node_type 

66 self.quad_type = quad_type 

67 

68 def getNodes(self, num_nodes): 

69 """ 

70 Computes a given number of quadrature nodes. 

71 

72 Parameters 

73 ---------- 

74 num_nodes : int 

75 Number of nodes to compute. 

76 

77 Returns 

78 ------- 

79 nodes : np.1darray 

80 Nodes located in [-1, 1], in increasing order. 

81 """ 

82 # Check number of nodes 

83 if self.quad_type in ['LOBATTO', 'RADAU-LEFT'] and num_nodes < 2: 

84 raise NodesError(f"num_nodes must be larger than 2 for {self.quad_type}, but got {num_nodes}") 

85 elif num_nodes < 1: 

86 raise NodesError("you surely want at least one node ;)") 

87 

88 # Equidistant nodes 

89 if self.node_type == 'EQUID': 

90 if self.quad_type == 'GAUSS': 

91 return np.linspace(-1, 1, num=num_nodes + 2)[1:-1] 

92 elif self.quad_type == 'LOBATTO': 

93 return np.linspace(-1, 1, num=num_nodes) 

94 elif self.quad_type == 'RADAU-RIGHT': 

95 return np.linspace(-1, 1, num=num_nodes + 1)[1:] 

96 elif self.quad_type == 'RADAU-LEFT': 

97 return np.linspace(-1, 1, num=num_nodes + 1)[:-1] 

98 

99 # Quadrature nodes linked to orthogonal polynomials 

100 alpha, beta = self.getTridiagCoefficients(num_nodes) 

101 nodes = eigh_tridiagonal(alpha, np.sqrt(beta[1:]))[0] 

102 nodes.sort() 

103 

104 return nodes 

105 

106 def getOrthogPolyCoefficients(self, num_coeff): 

107 """ 

108 Produces a given number of analytic three-term recurrence coefficients. 

109 

110 Parameters 

111 ---------- 

112 num_coeff : int 

113 Number of coefficients to compute. 

114 

115 Returns 

116 ------- 

117 alpha : np.1darray 

118 The alpha coefficients of the three-term recurrence. 

119 beta : np.1darray 

120 The beta coefficients of the three-term recurrence. 

121 """ 

122 if self.node_type == 'LEGENDRE': 

123 k = np.arange(num_coeff, dtype=float) 

124 alpha = 0 * k 

125 beta = k**2 / (4 * k**2 - 1) 

126 beta[0] = 2 

127 elif self.node_type == 'CHEBY-1': 

128 alpha = np.zeros(num_coeff) 

129 beta = np.full(num_coeff, 0.25) 

130 beta[0] = np.pi 

131 if num_coeff > 1: 

132 beta[1] = 0.5 

133 elif self.node_type == 'CHEBY-2': 

134 alpha = np.zeros(num_coeff) 

135 beta = np.full(num_coeff, 0.25) 

136 beta[0] = np.pi / 2 

137 elif self.node_type == 'CHEBY-3': 

138 alpha = np.zeros(num_coeff) 

139 alpha[0] = 0.5 

140 beta = np.full(num_coeff, 0.25) 

141 beta[0] = np.pi 

142 elif self.node_type == 'CHEBY-4': 

143 alpha = np.zeros(num_coeff) 

144 alpha[0] = -0.5 

145 beta = np.full(num_coeff, 0.25) 

146 beta[0] = np.pi 

147 return alpha, beta 

148 

149 def evalOrthogPoly(self, t, alpha, beta): 

150 """ 

151 Evaluates the two higher order orthogonal polynomials corresponding 

152 to the given (alpha,beta) coefficients. 

153 

154 Parameters 

155 ---------- 

156 t : float or np.1darray 

157 The point where to evaluate the orthogonal polynomials. 

158 alpha : np.1darray 

159 The alpha coefficients of the three-term recurrence. 

160 beta : np.1darray 

161 The beta coefficients of the three-term recurrence. 

162 

163 Returns 

164 ------- 

165 pi[0] : float or np.1darray 

166 The second higher order orthogonal polynomial evaluation. 

167 pi[1] : float or np.1darray 

168 The higher oder orthogonal polynomial evaluation. 

169 """ 

170 t = np.asarray(t, dtype=float) 

171 pi = np.array([np.zeros_like(t) for i in range(3)]) 

172 pi[1:] += 1 

173 for alpha_j, beta_j in zip(alpha, beta): 

174 pi[2] *= t - alpha_j 

175 pi[0] *= beta_j 

176 pi[2] -= pi[0] 

177 pi[0] = pi[1] 

178 pi[1] = pi[2] 

179 return pi[0], pi[1] 

180 

181 def getTridiagCoefficients(self, num_nodes): 

182 """ 

183 Computes recurrence coefficients for the tridiagonal Jacobian matrix, 

184 taking into account the quadrature type. 

185 

186 Parameters 

187 ---------- 

188 num_nodes : int 

189 Number of nodes that should be computed from those coefficients. 

190 

191 Returns 

192 ------- 

193 alpha : np.1darray 

194 The modified alpha coefficients of the three-term recurrence. 

195 beta : np.1darray 

196 The modified beta coefficients of the three-term recurrence. 

197 """ 

198 # Coefficients for Gauss quadrature type 

199 alpha, beta = self.getOrthogPolyCoefficients(num_nodes) 

200 

201 # If not Gauss quadrature type, modify the alpha/beta coefficients 

202 if self.quad_type.startswith('RADAU'): 

203 b = -1.0 if self.quad_type.endswith('LEFT') else 1.0 

204 b1, b2 = self.evalOrthogPoly(b, alpha[:-1], beta[:-1])[:2] 

205 alpha[-1] = b - beta[-1] * b1 / b2 

206 elif self.quad_type == 'LOBATTO': 

207 a, b = -1.0, 1.0 

208 a2, a1 = self.evalOrthogPoly(a, alpha[:-1], beta[:-1])[:2] 

209 b2, b1 = self.evalOrthogPoly(b, alpha[:-1], beta[:-1])[:2] 

210 alpha[-1], beta[-1] = np.linalg.solve([[a1, a2], [b1, b2]], [a * a1, b * b1]) 

211 return alpha, beta