Peano
Loading...
Searching...
No Matches
LagrangeBasis.py
Go to the documentation of this file.
1# This file is part of the ExaHyPE2 project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3import mpmath as mp
4#import numpy as np
5from sympy.integrals.quadrature import gauss_legendre, gauss_lobatto # quadrature_points by default in [-1,1]
6
7from abc import abstractmethod
8
9import re
10
11PREC_DIGITS = 256
12RENDER_DIGITS = 64
13
14mp.dps = PREC_DIGITS
15mp.pretty = False
16
18 """
19
20 Method representing a Lagrange basis which is used as a factory mechanism
21 to produce all the vectors and matrices that we need within the DG solvers.
22 Never use this class directly, but always rely on GaussLegendre or
23 GaussLobatto. The only two things that are relevant for a user are the two
24 routines quadrature_points() and quadrature_weights() as well as the routine
25 to befill a map with replacement rules: init_dictionary_with_default_parameters()
26
27 """
28
29 def __init__(self,num_points):
30 self.__num_points = num_points
31 self.__max_poly_order = num_points - 1
32 # quadrature_points, quadrature_weights
34 # interpolation weights
36 # operators
39 self.__K1 = self.__compute_K1()
40 self.__inverted_predictor_lhs_operator = mp.inverse(self.__K1).tolist()
41 self.__basis_function_values_left,_ = self.__evaluate(mp.mpf(0.0))
42 self.__basis_function_values_right,_ = self.__evaluate(mp.mpf(1.0))
45 self.__fine_grid_projector = [None]*3
46 for j in range(0,3):
48
49 #for tracer
50 self.order = num_points-1
51
52
53 def __str__(self):
54 d = {}
55 self._init_dictionary_with_default_parameters(d)
56 return "<{}.{} object>: {}".format(self.__class__.__module__,self.__class__.__name__,d)
57 __repr__ = __str__
58
59
60 # private methods
61 def __evaluate(self,xi):
62 """
63 Evaluates a lagrange polynomial and its first derivative at a given point.
64
65 Args:
66 xi:
67 The reference element point the basis functions are evaluated at.
68 Here, xi refers to the greek letter that is often used as a reference element coordinate.
69 Returns:
70 phi:
71 Basis function values.
72 phi_xi:
73 First derivatives of the basis functions.
74 """
75 phi = [mp.mpf(1.)]*(self.__num_points)
76 phi_xi = [mp.mpf(0.)]*(self.__num_points)
77 xin = self.__quadrature_points
78 for m in range(0,self.__num_points):
79 for j in range(0,self.__num_points):
80 if j == m:
81 continue
82 phi[m] = phi[m]*(xi-xin[j])/(xin[m]-xin[j])
83 for i in range(0,self.__num_points):
84 if i == m:
85 continue
86 tmp = 1.;
87 for j in range(0,self.__num_points):
88 if j == i:
89 continue
90 if j == m:
91 continue
92 tmp = tmp*(xi-xin[j])/(xin[m]-xin[j])
93 phi_xi[m] += tmp/(xin[m]-xin[i])
94 return phi, phi_xi
95
96
98 """
99 See Eq. 3.2 in
100 https://people.maths.ox.ac.uk/trefethen/barycentric.pdf
101 """
102 result = [0.0]*self.__num_points
103 for j,xj in enumerate(self.__quadrature_points):
104 divisor = 1.0
105 for k,xk in enumerate(self.__quadrature_points):
106 if j != k:
107 divisor *= (xj-xk)
108 result[j] = 1.0/divisor
109 return result
110
111
113 """
114 Computes the (reference) element mass matrix for an approximation of
115 order self.__max_poly_order.
116
117 We evaluate the scalar product:
118
119 (phi_i, phi_j)_[0,1], i,j = 1 ... N > 0
120
121 where phi_i,phi_j are Lagrange basis functions associated with
122 N support points x_i and x_j within the unit-interval.
123 They are polynomials of maximum order N-1.
124
125 We evaluate the scalar product via a degree N Gauss quadrature rule that uses basis function support points
126 as evaluation points.
127 If we use Legendre nodes as support points, this is an exact evaluation if
128 we use Lobatto nodes, this is an approximation.
129
130 :note:
131
132 :return: The (reference) element mass matrix. Exact for Gauss-Legrende nodes, inexact for Gauss-Lobatto (diagnoal lumping).
133 """
134 # init matrix with zeros
135 MM = [[mp.mpf(0) for _ in range(self.__num_points)] for _ in range(self.__num_points)]
136
137 for i in range(0,self.__num_points):
138 MM[i][i] = self.__quadrature_weights[i]
139
140 #for i in range(0,self.__num_points):
141 # phi, _ = self.__evaluate(self.__quadrature_points[i])
142 # for k in range(0,self.__num_points):
143 # for l in range(0,self.__num_points):
144 # MM[k][l] += self.__quadrature_weights[i]*phi[k]*phi[l]
145 return MM
146
147
149 """
150 Computes the (reference) element stiffness matrix for an approximation of order self.__max_poly_order.
151
152 Computes:
153
154 K_ij = <d/dx phi_i, phi_j>_[0,1] = w_j (d/dx phi_j) (x_i)
155
156 where phi_i,phi_j are Lagrange basis functions associated with
157 N support points x_i and x_j within the unit-interval.
158 They are polynomials of maximum order N-1.
159
160 :note: Exact for both Lobatto and Legendre nodes due to the reduced degree in one scalar product operand.
161
162 :return: The (reference) element stiffness matrix.
163 """
164 # init matrix with zero
165 Kxi = [[mp.mpf(0) for _ in range(self.__num_points)] for _ in range(self.__num_points)]
166
167 for j in range(0,self.__num_points):
168 phi, phi_xi = self.__evaluate(self.__quadrature_points[j])
169 for i in range(0,self.__num_points):
170 Kxi[i][j] = self.__quadrature_weights[j]*phi_xi[i]
171
172 #for i in range(0,self.__num_points):
173 # phi, phi_xi = self.__evaluate(self.__quadrature_points[i])
174 # for k in range(0,self.__num_points):
175 # Kxi[k][i] = self.__quadrature_weights[i]*phi_xi[k]*phi[i]
176 return Kxi
177
178
179 def __compute_K1(self):
180 """
181 Computes the difference between the reference element mass operator
182 evaluated at point xi=1.0 and the element stiffness matrix.
183
184 :return: delta between the reference element mass operator at point xi=1.0 and the element stiffness matrix
185 """
186 phi1, _ = self.__evaluate(1.0)
187 Kxi = self.__stiffness_operator
188
189 K1 = [[mp.mpf(0) for _ in range(self.__num_points)] for _ in range(self.__num_points)]
190 #FRm = [[mp.mpf(0) for _ in range(self.__num_points)] for _ in range(self.__num_points)]
191
192 for k in range(0, self.__num_points):
193 for l in range(0, self.__num_points):
194 #FRm[k][l] = phi1[k]*phi1[l]
195 K1[k][l] = phi1[k]*phi1[l] - Kxi[k][l]
196
197 #K1_orig = np.subtract(FRm,self.__stiffness_operator)
198 #for k in range(0, self.__num_points):
199 # for l in range(0, self.__num_points):
200 # print(K1[k][l] - K1_orig[k][l])
201
202 return K1
203
204
206 """
207 Computes basis function derivatives at each support point.
208 Transposes the resulting operator.
209
210 u = sum_i u_i phi_i
211
212 => d/dx u = sum_j (d/dx phi_j) u_j
213
214 To construct the gradient field, we make the ansatz:
215
216 grad u = sum_i g_i phi_i
217
218 =>
219
220 g_i = ( d/dx u, phi_i )_[0,1] / (phi_i,phi_i)_[0,1] = sum_j (d/dx phi_j, phi_i)_[0,1] / w_i
221
222 = w_i (d/dx phi_j) (x_i) / w_i = (d/dx phi_j) (x_i)
223
224 = DUDX^T
225
226 where DUDX is the operator computed by this function:
227
228 DUDX_ij = (d/dx phi_i) (x_j)
229
230 It can be further written as
231
232 DUDX_ij = 1/w_i * K^T_ij
233
234 where the stiffness matrix K is defined as
235
236 K_ij = <d/dx phi_i, phi_j>_[0,1] = w_j (d/dx phi_j) (x_i)
237
238 :return: transposed derivative operator
239
240 :note: If you want to use this operator to compute the gradient of the solution,
241 you need to use the transpose.
242 """
243 # matmul
244 # @todo: get rid of this numpy dependency, as mass matrix is diagonal
245 #dudx_orig = np.dot(mp.inverse(self.__mass_matrix).tolist(),np.transpose(self.__stiffness_operator))
246 dudx = [[mp.mpf(0) for _ in range(self.__num_points)] for _ in range(self.__num_points)]
247 for i in range(0, self.__num_points):
248 phi, phi_xi = self.__evaluate(self.__quadrature_points[i])
249 for j in range(0, self.__num_points):
250 dudx[i][j] = phi_xi[j]
251 #print(dudx[i][j] - dudx_orig[i][j])
252 return dudx
253
254
256 """
257 Transforms the degrees of freedom located on a coarse grid edge
258 quadrature_points to degrees of freedoms located on quadrature_points of a fine grid edge.
259 The difference in levels is 1.
260
261 Let us denote by P the fine grid projector (= equidistantGridProjector). The fine grid DoF
262 are computed according to:
263
264 u^{fine;j}_i = sum_{m} P^{j}_im u^{coarse}_m
265
266 Args:
267 j:
268 subinterval index
269
270 Returns:
271 fineGridProjector:
272 Operator to express polynomial function associated with original interval with basis functions associated with subinterval j
273 """
274 fineGridProjector = [[mp.mpf(0) for _ in range(self.__num_points)] for _ in range(self.__num_points)] # 4 x 4 for self.__max_poly_order=3
275
276 for i in range(0, self.__num_points): # Eq. basis
277 phi_i, _ = self.__evaluate((self.__quadrature_points[i]+j)/mp.mpf(3.0)) # comma after phi_i is important
278 for m in range(0, self.__num_points): # DG basis
279 fineGridProjector[m][i] = phi_i[m]
280 return fineGridProjector
281
282
284 """
285 Transforms the degrees of freedom located at non-equidistant Lagrange support points
286 quadrature_points to degrees of freedoms located at quadrature_points of an equidistant grid over (0,1).
287
288 Let us denote by P the projection operator (= equidistantGridProjector). The equidistant DoF
289 are computed according to:
290
291 u^eq_i = sum_{m} P_im u^DG_m
292
293 Returns:
294 equidistantGridProjector:
295 The corresponding degrees of freedom located at quadrature_points of an equidistant grid over (0,1).
296 """
297 equidistantGridProjector = [[mp.mpf(0) for _ in range(self.__num_points)] for _ in range(self.__num_points)] # 4 x 4 for self.__max_poly_order=3
298 subxi = mp.linspace(mp.mpf(0.0), mp.mpf(1.0), self.__num_points)
299
300 for i in range(0, self.__num_points): # Eq. basis
301 phi_i, _ = self.__evaluate(subxi[i]) # comma after phi_i is important
302 for m in range(0, self.__num_points): # DG basis
303 equidistantGridProjector[m][i] = phi_i[m]
304 return equidistantGridProjector
305
306
307 # private class methods
308 @classmethod
309 def __make_initializer_list(cls,rendered_tensor):
310 """
311
312 This implementation is a little bit nasty: We use the resulting strings within
313 jinja2 templates which are then repeatedly resolved/rendered. Therefore, we have
314 to be carefully not to write {{ where we want to have two brackets. Instead, we
315 have to write { { (not the space) so the render engine does not consider an
316 initialisation list with nested arrays as a jinja2 token.
317
318 :param rendered_tensor: (nested) list of str elements
319 :return: C++ initializer list for tensor
320
321 """
322 def descend(current):
323 if type(current) is list:
324 result =" {"
325 for element in current[:-1]:
326 result += descend(element)
327 result += ","
328 result += descend(current[-1])
329 result += "} "
330 return result
331 else:
332 return current
333 return descend(rendered_tensor)
334
335
336 @classmethod
337 def __render_tensor_1(cls,tensor):
338 """
339 Converts nested list or numpy matrix to nested list of strings.
340 :param tensor: list or numpy vector storing mpmath numbers
341 """
342 result = []
343 for elem in tensor:
344 result.append(mp.nstr(elem,n=RENDER_DIGITS))
345 return result
346
347
348 @classmethod
349 def __render_tensor_2(cls,tensor,multiDimArray=True):
350 """
351 Converts nested list or numpy matrix to nested list of strings.
352 :param tensor: nested list or numpy matrix storing mpmath numbers
353 """
354 result = []
355 for row in tensor:
356 resultRow = []
357 for elem in row:
358 resultRow.append(mp.nstr(elem,n=RENDER_DIGITS))
359 if multiDimArray:
360 result.append(resultRow)
361 else:
362 result += resultRow
363 return result
364
365
366 @classmethod
367 def __render_tensor_3(cls,tensor,multiDimArray=True):
368 """
369 Converts nested list or numpy matrix to nested list of strings.
370 :param tensor: nested list or numpy matrix storing mpmath numbers
371 """
372 result = []
373 for r1 in tensor:
374 tmp1 = []
375 for r2 in r1:
376 tmp2 = []
377 for elem in r2:
378 tmp2.append(mp.nstr(elem,n=RENDER_DIGITS))
379 if multiDimArray:
380 tmp1.append(tmp2)
381 else:
382 result += tmp2
383 if multiDimArray:
384 result.append(tmp1)
385 return result
386
387
388 # protected
389 @abstractmethod
391 """
392 To be implemented by subclass.
393 """
394 assert False, "To be implemented by subclass."
395
396
397 def _transform_and_sort_points_and_weights(self,quadrature_points,quadrature_weights):
398 """
399 Transform quadrature quadrature_points and quadrature_weights for interval [-1,1]
400 to quadrature_points and quadrature_weights for interval [0,1].
401 """
402 transformed_quadrature_weights = []
403 transformed_quadrature_points = []
404 # weight
405 for i,w in enumerate(quadrature_weights):
406 x = quadrature_points[i]
407 wT = mp.mpf(w)/mp.mpf(2.0)
408 xT = (mp.mpf(x)+mp.mpf(1.0))/mp.mpf(2.0)
409 transformed_quadrature_weights.append(wT)
410 transformed_quadrature_points.append(xT)
411 # sort
412 sorted_transformed_quadrature_points = sorted(transformed_quadrature_points)
413 sorted_transformed_quadrature_weights = []
414 for i,xs in enumerate(sorted_transformed_quadrature_points):
415 n = transformed_quadrature_points.index(xs)
416 ws = transformed_quadrature_weights[n]
417 sorted_transformed_quadrature_weights.append(ws)
418 return sorted_transformed_quadrature_points, sorted_transformed_quadrature_weights
419
420
421 def init_dictionary_with_default_parameters(self,d,multiDimArrays=False):
422 def snake_to_camel(word):
423 return ''.join(x.capitalize() or '_' for x in word.lower().split('_'))
424
425 basisDeclarations = ""
426 basisInitializers = ""
427 for var in ["quadrature_points","quadrature_weights","barycentric_weights","basis_function_values_left","basis_function_values_right","derivative_operator","mass_matrix","stiffness_operator","inverted_predictor_lhs_operator","equidistant_grid_projector","fine_grid_projector"]:
428 var_key = "_LagrangeBasis__" + var # prefix for privat variables of class LagrangeBasis
429 var_name = snake_to_camel(var) # C++ name
430 if var == "quadrature_points":
431 var_name = "QuadraturePoints1d"
432 if var in ["quadrature_points","quadrature_weights","barycentric_weights","basis_function_values_left","basis_function_values_right"]:
433 basisDeclarations += "const double {var_name}[{order}+1];\n".format(indent=" "*2,var_name=var_name,order=self.__max_poly_order)
434 basisInitializers += "{var_name}{initializer},\n".format(var_name=var_name,\
435 initializer=LagrangeBasis.__make_initializer_list(LagrangeBasis.__render_tensor_1(getattr(self,var_key))))
436 elif var in ["fine_grid_projector"]:
437 if multiDimArrays:
438 basisDeclarations += "const double {var_name}[3][{order}+1][{order}+1];\n".format(indent=" "*2,var_name=var_name,order=self.__max_poly_order)
439 else:
440 basisDeclarations += "const double {var_name}[3][({order}+1)*({order}+1)];\n".format(indent=" "*2,var_name=var_name,order=self.__max_poly_order)
441 basisInitializers += "{var_name}{initializer},\n".format(var_name=var_name,\
442 initializer="{" + \
443 ",".join([LagrangeBasis.__make_initializer_list(LagrangeBasis.__render_tensor_2(elem,multiDimArrays)) for elem in self.__fine_grid_projector]) +\
444 "}")
445 else:
446 if multiDimArrays:
447 basisDeclarations += "const double {var_name}[{order}+1][{order}+1];\n".format(indent=" "*2,var_name=var_name,order=self.__max_poly_order)
448 else:
449 basisDeclarations += "const double {var_name}[({order}+1)*({order}+1)];\n".format(indent=" "*2,var_name=var_name,order=self.__max_poly_order)
450 basisInitializers += "{var_name}{initializer},\n".format(var_name=var_name,\
451 initializer=LagrangeBasis.__make_initializer_list(LagrangeBasis.__render_tensor_2(getattr(self,var_key),multiDimArrays)))
452
453 d["ORDER"] = self.__max_poly_order
454 d["BASIS_DECLARATIONS"] = basisDeclarations
455 d["BASIS_INITIALIZERS"] = basisInitializers
456
457 # public
458 def quadrature_points(self,render=True):
459 return LagrangeBasis.__render_tensor_1(self.__quadrature_points) if render else self.__quadrature_points
460
461 def quadrature_weights(self,render=True):
462 return LagrangeBasis.__render_tensor_1(self.__quadrature_weights) if render else self.__quadrature_weights
463
464
467 x,w = gauss_legendre(num_points,PREC_DIGITS)
469
470 def __str__(LagrangeBasis):
471 return "GaussLegendreBasis"
472
475 x,w = gauss_lobatto(num_points,PREC_DIGITS)
477
478 def __str__(LagrangeBasis):
479 return "GaussLobattoBasis"
_compute_quadrature_points_and_weights(self, num_points)
To be implemented by subclass.
_compute_quadrature_points_and_weights(self, num_points)
To be implemented by subclass.
Method representing a Lagrange basis which is used as a factory mechanism to produce all the vectors ...
__make_initializer_list(cls, rendered_tensor)
This implementation is a little bit nasty: We use the resulting strings within jinja2 templates which...
_compute_quadrature_points_and_weights(self, num_points)
To be implemented by subclass.
__compute_derivative_operator(self)
Computes basis function derivatives at each support point.
__render_tensor_1(cls, tensor)
Converts nested list or numpy matrix to nested list of strings.
__compute_mass_matrix(self)
Computes the (reference) element mass matrix for an approximation of order self.__max_poly_order.
__render_tensor_3(cls, tensor, multiDimArray=True)
Converts nested list or numpy matrix to nested list of strings.
_transform_and_sort_points_and_weights(self, quadrature_points, quadrature_weights)
Transform quadrature quadrature_points and quadrature_weights for interval [-1,1] to quadrature_point...
__compute_fine_grid_projector(self, j)
Transforms the degrees of freedom located on a coarse grid edge quadrature_points to degrees of freed...
__compute_K1(self)
Computes the difference between the reference element mass operator evaluated at point xi=1....
init_dictionary_with_default_parameters(self, d, multiDimArrays=False)
__compute_stiffness_operator(self)
Computes the (reference) element stiffness matrix for an approximation of order self....
__evaluate(self, xi)
Evaluates a lagrange polynomial and its first derivative at a given point.
__compute_equidistant_grid_projector(self)
Transforms the degrees of freedom located at non-equidistant Lagrange support points quadrature_point...
__render_tensor_2(cls, tensor, multiDimArray=True)
Converts nested list or numpy matrix to nested list of strings.