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

84 statements  

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

1import logging 

2import numpy as np 

3 

4from pySDC.core.Nodes import NodesGenerator 

5from pySDC.core.Errors import CollocationError 

6from pySDC.core.Lagrange import LagrangeApproximation 

7 

8 

9class CollBase(object): 

10 """ 

11 Generic collocation class, that contains everything to do integration over 

12 intervals and between nodes. 

13 It can be used to produce many kind of quadrature nodes from various 

14 distribution (awesome!). 

15 

16 It is based on the two main parameters that define the nodes : 

17 

18 - node_type : the node distribution used for the collocation method 

19 - quad_type : the type of quadrature used (inclusion of not of boundary) 

20 

21 Current implementation provides the following available parameter values 

22 for node_type : 

23 

24 - EQUID : equidistant node distribution 

25 - LEGENDRE : distribution from Legendre polynomials 

26 - CHEBY-{1,2,3,4} : distribution from Chebyshev polynomials of a given kind 

27 

28 The type of quadrature cann be GAUSS (only inner nodes), RADAU-LEFT 

29 (inclusion of the left boundary), RADAU-RIGHT (inclusion of the right 

30 boundary) and LOBATTO (inclusion of left and right boundary). 

31 

32 Here is the equivalency table with the (old) original classes implemented 

33 in pySDC : 

34 

35 +-------------------------+-----------+-------------+ 

36 | Original Class | node_type | quad_type | 

37 +=========================+===========+=============+ 

38 | Equidistant | EQUID | LOBATTO | 

39 +-------------------------+-----------+-------------+ 

40 | EquidistantInner | EQUID | GAUSS | 

41 +-------------------------+-----------+-------------+ 

42 | EquidistantNoLeft | EQUID | RADAU-RIGHT | 

43 +-------------------------+-----------+-------------+ 

44 | CollGaussLegendre | LEGENDRE | GAUSS | 

45 +-------------------------+-----------+-------------+ 

46 | CollGaussLobatto | LEGENDRE | LOBATTO | 

47 +-------------------------+-----------+-------------+ 

48 | CollGaussRadau_Left | LEGENDRE | RADAU-LEFT | 

49 +-------------------------+-----------+-------------+ 

50 | CollGaussRadau_Right | LEGENDRE | RADAU-RIGHT | 

51 +-------------------------+-----------+-------------+ 

52 

53 Attributes: 

54 num_nodes (int): number of collocation nodes 

55 tleft (float): left interval point 

56 tright (float): right interval point 

57 nodes (numpy.ndarray): array of quadrature nodes 

58 weights (numpy.ndarray): array of quadrature weights for the full interval 

59 Qmat (numpy.ndarray): matrix containing the weights for tleft to node 

60 Smat (numpy.ndarray): matrix containing the weights for node to node 

61 delta_m (numpy.ndarray): array of distances between nodes 

62 right_is_node (bool): flag to indicate whether right point is collocation node 

63 left_is_node (bool): flag to indicate whether left point is collocation node 

64 """ 

65 

66 def __init__(self, num_nodes=None, tleft=0, tright=1, node_type='LEGENDRE', quad_type=None, **kwargs): 

67 """ 

68 Initialization routine for a collocation object 

69 

70 Args: 

71 num_nodes (int): number of collocation nodes 

72 tleft (float): left interval point 

73 tright (float): right interval point 

74 """ 

75 

76 if not num_nodes > 0: 

77 raise CollocationError('At least one quadrature node required, got %s' % num_nodes) 

78 if not tleft < tright: 

79 raise CollocationError('Interval boundaries are corrupt, got %s and %s' % (tleft, tright)) 

80 

81 self.logger = logging.getLogger('collocation') 

82 

83 # Set number of nodes, left and right interval boundaries 

84 self.num_nodes = num_nodes 

85 self.tleft = tleft 

86 self.tright = tright 

87 

88 self.node_type = node_type 

89 self.quad_type = quad_type 

90 

91 # Instantiate attributes 

92 self.nodeGenerator = NodesGenerator(self.node_type, self.quad_type) 

93 if self.node_type == 'EQUID': 

94 self.order = num_nodes 

95 else: 

96 if self.quad_type == 'GAUSS': 

97 self.order = 2 * num_nodes 

98 elif self.quad_type.startswith('RADAU'): 

99 self.order = 2 * num_nodes - 1 

100 elif self.quad_type == 'LOBATTO': 

101 self.order = 2 * num_nodes - 2 

102 

103 self.left_is_node = self.quad_type in ['LOBATTO', 'RADAU-LEFT'] 

104 self.right_is_node = self.quad_type in ['LOBATTO', 'RADAU-RIGHT'] 

105 

106 self.nodes = self._getNodes 

107 self.weights = self._getWeights(tleft, tright) 

108 self.Qmat = self._gen_Qmatrix 

109 self.Smat = self._gen_Smatrix 

110 self.delta_m = self._gen_deltas 

111 

112 @staticmethod 

113 def evaluate(weights, data): 

114 """ 

115 Evaluates the quadrature over the full interval 

116 

117 Args: 

118 weights (numpy.ndarray): array of quadrature weights for the full interval 

119 data (numpy.ndarray): f(x) to be integrated 

120 

121 Returns: 

122 numpy.ndarray: integral over f(x) between tleft and tright 

123 """ 

124 if not np.size(weights) == np.size(data): 

125 raise CollocationError("Input size does not match number of weights, but is %s" % np.size(data)) 

126 

127 return np.dot(weights, data) 

128 

129 def _getWeights(self, a, b): 

130 """ 

131 Computes weights using barycentric interpolation 

132 

133 Args: 

134 a (float): left interval boundary 

135 b (float): right interval boundary 

136 

137 Returns: 

138 numpy.ndarray: weights of the collocation formula given by the nodes 

139 """ 

140 if self.nodes is None: 

141 raise CollocationError(f"Need nodes before computing weights, got {self.nodes}") 

142 

143 # Instantiate the Lagrange interpolator object 

144 approx = LagrangeApproximation(self.nodes) 

145 

146 # Compute weights 

147 tLeft = np.ravel(self.tleft)[0] 

148 tRight = np.ravel(self.tright)[0] 

149 weights = approx.getIntegrationMatrix([(tLeft, tRight)], numQuad='FEJER') 

150 

151 return np.ravel(weights) 

152 

153 @property 

154 def _getNodes(self): 

155 """ 

156 Computes nodes using an internal NodesGenerator object 

157 

158 Returns: 

159 np.ndarray: array of Gauss-Legendre nodes 

160 """ 

161 # Generate normalized nodes in [-1, 1] 

162 nodes = self.nodeGenerator.getNodes(self.num_nodes) 

163 

164 # Scale nodes to [tleft, tright] 

165 a = self.tleft 

166 b = self.tright 

167 nodes += 1.0 

168 nodes /= 2.0 

169 nodes *= b - a 

170 nodes += a 

171 

172 if self.left_is_node: 

173 nodes[0] = self.tleft 

174 if self.right_is_node: 

175 nodes[-1] = self.tright 

176 

177 return nodes 

178 

179 @property 

180 def _gen_Qmatrix(self): 

181 """ 

182 Compute tleft-to-node integration matrix for later use in collocation formulation 

183 

184 Returns: 

185 numpy.ndarray: matrix containing the weights for tleft to node 

186 """ 

187 if self.nodes is None: 

188 raise CollocationError(f"Need nodes before computing weights, got {self.nodes}") 

189 M = self.num_nodes 

190 Q = np.zeros([M + 1, M + 1]) 

191 

192 # Instantiate the Lagrange interpolator object 

193 approx = LagrangeApproximation(self.nodes) 

194 

195 # Compute tleft-to-node integration matrix 

196 tLeft = np.ravel(self.tleft)[0] 

197 intervals = [(tLeft, tau) for tau in self.nodes] 

198 intQ = approx.getIntegrationMatrix(intervals, numQuad='FEJER') 

199 

200 # Store into Q matrix 

201 Q[1:, 1:] = intQ 

202 

203 return Q 

204 

205 @property 

206 def _gen_Smatrix(self): 

207 """ 

208 Compute node-to-node integration matrix for later use in collocation formulation 

209 

210 Returns: 

211 numpy.ndarray: matrix containing the weights for node to node 

212 """ 

213 M = self.num_nodes 

214 Q = self.Qmat 

215 S = np.zeros([M + 1, M + 1]) 

216 

217 S[1, :] = Q[1, :] 

218 for m in np.arange(2, M + 1): 

219 S[m, :] = Q[m, :] - Q[m - 1, :] 

220 

221 return S 

222 

223 @property 

224 def _gen_deltas(self): 

225 """ 

226 Compute distances between the nodes 

227 

228 Returns: 

229 numpy.ndarray: distances between the nodes 

230 """ 

231 M = self.num_nodes 

232 delta = np.zeros(M) 

233 delta[0] = self.nodes[0] - self.tleft 

234 for m in np.arange(1, M): 

235 delta[m] = self.nodes[m] - self.nodes[m - 1] 

236 

237 return delta