Peano
Loading...
Searching...
No Matches
LagrangeBasisWithDiagonalMassMatrix.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
3
4
5#import mpmath as mp
6#import numpy as np
7#from sympy.integrals.quadrature import gauss_legendre, gauss_lobatto # quadrature_points by default in [-1,1]
8
9
10import scipy
11from scipy.integrate import quad
12
13from abc import abstractmethod
14
15from .LagrangeBasis import LagrangeBasis
16from .LagrangeBasis import render_tensor_1
17from .LagrangeBasis import render_tensor_2
18from .LagrangeBasis import render_tensor_3
19
20from numpy import number
21
22
23
24
26 r"""
27
28 Lagrange Basis with a diagonal mass matrix
29
30
31 Class representing an abstract Lagrange basis which is used as a factory
32 mechanism to produce vectors and matrices that we need within DG solvers.
33 Never use this class directly, but always a subclass. As with all basis
34 classes, the most relevant
35 routine for a user is init_dictionary_with_default_parameters() which is
36 a routine to befill a map with replacement rules, i.e. with the precomputed
37 matrices and vectors. The class assumes that the mass matrix is a diagonal
38 matrix.
39 This is not true in general, i.e. holds only for Gauss-Legendre sampling
40 points, but you might want to work with lumped matrices, and then this
41 class is the right starting point.
42
43 Further to the initialisation, some using classes use quadrature_points_and_weights().
44 The exahype2.solvers.rkdg.RungeKuttaDG solver for example uses it to
45 initialise the mapping of the plotter properly.
46
47
48 ## Generated vectors and matrices
49
50 I decided to "outsource" the documentation of the individual fields to
51 class attributes of their own. This way, they are visible both in the
52 doxygen documentation and I can pipe them into the generated C++ code.
53
54 - QuadraturePoints1d
55 - MassMatrixDiagonal1d
56 - StiffnessOperator
57 - RestrictionMatrix1d
58 - InterpolationMatrix1d
59
60
61 ## Realisation
62
63 The first version of this utility class has been written by Dominic E.
64 Charrier. It relied on NumPy and SymPy in several places. The current
65 version is stripped down and solely uses SciPy.
66
67
68 """
69
70 def __init__(self,polynomial_order):
71 super(LagrangeBasisWithDiagonalMassMatrix,self).__init__(polynomial_order)
72 self.stiffness_matrix = None
73 self.mass_matrix = None
74
75
76 def __str__(self):
77 d = {}
79 return "<{}.{} object>: {}".format(self.__class__.__module__,self.__class__.__name__,d)
80
81
82 __repr__ = __str__
83
84
85 QuadraturePoints1dDocumentation = r"""
86 The quadrature points are an array of size [p+1] if p is the order of the
87 chosen polyomial. The double array defines the spacing of the quadrature points
88 over the unit interval. See the helper routine exahype2::dg::getQuadraturePoint()
89 for an example how the points are used to identify the actual location in
90 space of the quadrature points.
91
92 As we work in a tensor product world, the 2d and 3d quadrature points can be
93 simply constructed by multiplying the respective coordinate system indices.
94"""
95
96
97 QuadratureWeights1dDocumentation = r"""
98 The quadrature weights are an array of size [p+1] if p is the order of the
99 chosen polyomial. The double array defines the integral over the polynomial
100 over the unit interval. We use Lagrangian bases here, i.e. only one polynomial
101 of the basis is 1 in a quadrature point, while all the others are 0. However,
102 the fact that it is 1 in the point, does not tell us anything about the
103 integral over this particular polynomial. That's the weight associated with
104 it.
105
106 As we work in a tensor product world, the 2d and 3d weights can be
107 simply constructed by multiplying the respective coordinate system indices.
108"""
109
110
111 MassMatrix1dDocumentation = r"""
112 Computes the (reference) element mass matrix' diagonal. The mass matrix is defined
113 via
114
115 @f$ \int _{(0,1)}(\phi _i, \phi _j) dx, i,j = 1 ... N > 0 @f$
116
117 where @f$ \phi _i,\phi _j @f$ are Lagrange basis functions associated with
118 the quadrature within the unit-interval. They are polynomials of maximum order N-1.
119
120 Our implementation is quite some overkill, as I assemble the whole mass matrix
121 and then extract only the diagonal. If we use Legendre nodes as support points,
122 this diagonal is an exact evaluation, i.e. the mass matrix is diagonal. If
123 we use Lobatto nodes, it is an approximation.
124
125 Please note that this is a 1d mass matrix: If we have a polynomial of order p,
126 we have @f$ (p+1)^d @f$ dofs in total per cell. My matrix has only the dimensions
127 @f$ (p+1) \times (p+1) @f$ so it effectively only models how the dofs along one
128 horizontal or vertical line within the cell do interact. This is totally sufficient
129 if we know that we have a tensor product approach of the shape functions and a
130 diagonal mass matrix.
131
132 @todo I don't know if we need renormalisation for Lobatto nodes, i.e. if we
133 should ensure that the row sum is preserved
134
135 This is an operation which maps nodal data into a weak space, i.e. the outcome has
136 to be scaled with h to be correct. It stems from a 1d integral. If you want to
137 compute the real mass matrix output, you have to multiply the outcome along each
138 coordinate axis.
139"""
140
141
142 StiffnessOperator1dDocumentation = r"""
143 The stiffness operator is a [p+1]x[p+1] matrix which is constructed in
144 __compute_stiffness_operator().
145
146 The matrix holds
147
148 @f$ K_{ij} = <\partial _x \phi _i, \phi _j> = w_j (\partial _x \phi _j) (x_i) @f$
149
150 where @f$ \phi _i, \phi _j @f$ are Lagrange basis functions associated with
151 the support (quadrature) points @f$ x_i @f$ and @f$ x_j @f$ within the unit
152 interval.
153
154 If you have a vector of p+1 entries, it defines a polynomial (aka function f) over the unit
155 interval. If you multiply this vector if @f$ K @f$, you get the outcome of
156
157 @f$ \int _{[0.1]} \Big( \partial _x f, \phi _j \Big) dx @f$
158
159 for the different tests @f$ \phi _j @f$. If your interval is not the unit
160 one but has side length $h$, you have to multiply the outcome with $h$ as
161 you integrate. At the same time, the derivative however has to be rescaled
162 with @f$ h^{-1} @f$ and the two h-recalibrations thus cancel out.
163 If you have a 2d or 3d function space, you still have to multiple with these
164 shape functions (see discussion around mass matrix), and you will get an
165 additional @f$ h^{d-1} @f$ scaling of the outcome.
166
167 We note that the outcome is exact for both Lobatto and Legendre nodes due to
168 the reduced degree in one scalar product operand.
169"""
170
171
172 BasisFunctionValuesLeft1dDocumentation = r"""
173 This is a vector of p+1 entries which tells you for a 1d interval
174 what the function on the left side is. Assume all Lagrange polynomials
175 are defined over the unit interval. The first entry in this matrix
176 tells you what the leftmost polynomial's value is at x=0. The second
177 entry discusses the impact of the second polynomial. If you have a
178 vector of p+1 shape function weights, you can compute the scalar
179 product of this vector and this weight vector, and you'll know what
180 the solution at the point x=0 is.
181
182 If the rightmost value, i.e. the value at x=1 is of interest, the order
183 within this vector has to be inverted.
184
185 This is a nodal 1d operation, i.e. there are no integrals or similar
186 involved. We take the real 1d polyomial from the shape function.
187"""
188
189 DerivativeOperator1dDocumentation = r"""
190 Matrix which is given the p+1 weights of the shape function
191 along a 1d line. It then returns the p+1 gradients at the integration
192 points.
193
194 This is a point-wise operator, i.e. it works directly with the weights
195 of the shape function in the function space. It does not involve any
196 integral et al. Furthermore, it is exact given a certain Lagrange
197 polynomial.
198"""
199
200 InterpolationMatrix1dDocumentation = r"""
201 This is essentially a @f$ 3 \cdot (order+1)^d-1 @f$ matrix split up
202 into a three-dimensional tensor. The documentation/content of this
203 tensor can be found on the page about DG AMR which is held in exahype
204 within the file exahype2/dg/Riemann.h.
205"""
206
207 RestrictionMatrix1dDocumentation = r"""
208 This is essentially a @f$ 3 \cdot (order+1)^d-1 @f$ matrix split up
209 into a three-dimensional tensor. The documentation/content of this
210 tensor can be found on the page about DG AMR which is held in exahype
211 within the file exahype2/dg/Riemann.h.
212"""
213
215 r"""
216
217 See Eq. 3.2 in
218 https://people.maths.ox.ac.uk/trefethen/barycentric.pdf
219
220 """
221 result = [0.0]*self.__num_points
222 for j,xj in enumerate(self.__quadrature_points):
223 divisor = 1.0
224 for k,xk in enumerate(self.__quadrature_points):
225 if j != k:
226 divisor *= (xj-xk)
227 result[j] = 1.0/divisor
228 return result
229
230
232 r"""
233
234 Reference mass matrix. See class documentation. I moved the docu there,
235 as doxygen does not extract documentation from private routines in Python.
236
237 """
238 if self.mass_matrix is None:
239 self.mass_matrix = [ [0.0 for _ in range(0,self.dofs_per_axisdofs_per_axis)] for _ in range(0,self.dofs_per_axisdofs_per_axis) ]
240
241 for row in range(0,self.dofs_per_axisdofs_per_axis):
242 for col in range(0,self.dofs_per_axisdofs_per_axis):
243 ans,err = quad(lambda x: self.value1d(x,row) * self.value1d(x,col),0,1)
244 self.mass_matrix[row][col] = ans
245
246 return [ self.mass_matrix[i][i] for i in range(0,self.dofs_per_axisdofs_per_axis) ]
247
248
249 stiffness_matrix = None
251 r"""
252
253 Computes the (reference) element stiffness matrix for an approximation of order self.__max_poly_order.
254
255 @see StiffnessOperator1dDocumentation which is also used to dump documentation
256 into generated C++ code.
257
258 """
259 if self.stiffness_matrix is None:
260 self.stiffness_matrix = [ [0.0 for _ in range(0,self.dofs_per_axisdofs_per_axis)] for _ in range(0,self.dofs_per_axisdofs_per_axis) ]
261
262 for row in range(0,self.dofs_per_axisdofs_per_axis):
263 for col in range(0,self.dofs_per_axisdofs_per_axis):
264 ans,err = quad(lambda x: self.derivative1d(x,row) * self.value1d(x,col),0,1)
265 self.stiffness_matrix[row][col] = ans
266
267 return self.stiffness_matrix
268
269
271 r"""
272
273 compute the interpolation matrix, which used to prepare halo layer data for fine grid points at the refinement boundary.
274
275 The matrix is only 1d mapping, i.e. used for "surface" of a 2D domain and have a size of 3Dof \times Dof.
276 Its elements are \phi_j(x^f_i), i.e. the value of basis functions for coarse grid on fine grid point.
277 In practive we multiply this matrix to vector of weight in coarse grid (u^c_i):
278
279 \phi_j(x^f_i)*u^c_i=u^c(x^f_i)
280
281 u^c(x) is the original solution on coarse grid. Thus the result is actually the evaluation of coarse solution on fine grid position
282
283 We can also use the same matrix to interpolate along the normal direction.
284
285 """
287 return [[[1.0]],[[1.0]],[[1.0]]]
288 else:
289 interp_matrix = [ [0.0 for _ in range(0,self.dofs_per_axisdofs_per_axis)] for _ in range(0,3*self.dofs_per_axisdofs_per_axis) ]
290 interp_matrix = [ [[0.0 for _ in range(0,self.dofs_per_axisdofs_per_axis)] for _ in range(0,self.dofs_per_axisdofs_per_axis) ] for _ in range(0,3) ]
291 fine_grid_point = [0.0 for _ in range(0,3*self.dofs_per_axisdofs_per_axis) ]
292 for index in range(0,self.dofs_per_axisdofs_per_axis):
293 fine_grid_point[index ]=self.quadrature_points[index]/3.0
294 fine_grid_point[index+ self.dofs_per_axisdofs_per_axis]=self.quadrature_points[index]/3.0+1.0/3.0
295 fine_grid_point[index+2*self.dofs_per_axisdofs_per_axis]=self.quadrature_points[index]/3.0+2.0/3.0
296 for block in range(0, 3):
297 for row in range(0, self.dofs_per_axisdofs_per_axis):
298 for col in range(0, self.dofs_per_axisdofs_per_axis):
299 interp_matrix[block][row][col]=self.value1d(fine_grid_point[row],col)
300
301 return interp_matrix
302
303
305 r"""
306
307 compute the restriction matrix, which used to prepare halo layer data for coarse cells at the refinement boundary.
308 the matrix formulation depends on the interpolation scheme.
309
310 The matrix is only 1d mapping, i.e. used for "surface" of a 2D domain and have a size of Dof \times 3Dof.
311
312 We can also use the same matrix to interpolate along the normal direction.
313
314
315 """
317 return [[[1.0/3.0]],[[1.0/3.0]],[[1.0/3.0]]]
318 else:
319 restriction_matrix = [ [[0.0 for _ in range(0,self.dofs_per_axisdofs_per_axis)] for _ in range(0,self.dofs_per_axisdofs_per_axis) ] for _ in range(0,3) ]
320 fine_grid_point = [0.0 for _ in range(0,3*self.dofs_per_axisdofs_per_axis) ]
321 for index in range(0,self.dofs_per_axisdofs_per_axis):
322 fine_grid_point[index ]=self.quadrature_points[index]/3.0
323 fine_grid_point[index+ self.dofs_per_axisdofs_per_axis]=self.quadrature_points[index]/3.0+1.0/3.0
324 fine_grid_point[index+2*self.dofs_per_axisdofs_per_axis]=self.quadrature_points[index]/3.0+2.0/3.0
325 for block in range(0, 3):
326 for row in range(0, self.dofs_per_axisdofs_per_axis):
327 for col in range(0, self.dofs_per_axisdofs_per_axis):
328 restriction_matrix[block][row][col]=self.value1d(fine_grid_point[col],row)/3.0
329
330 return restriction_matrix
331
332
333 def __compute_K1(self):
334 r"""
335
336 @todo Not yet updated atm but seems also not to be used anywhere
337
338 Computes the difference between the reference element mass operator
339 evaluated at point xi=1.0 and the element stiffness matrix.
340
341 :return: delta between the reference element mass operator at point xi=1.0 and the element stiffness matrix
342 """
343 phi1, _ = self.__evaluate(1.0)
344 Kxi = self.__stiffness_operator
345
346 K1 = [[mp.mpf(0) for _ in range(self.__num_points)] for _ in range(self.__num_points)]
347 #FRm = [[mp.mpf(0) for _ in range(self.__num_points)] for _ in range(self.__num_points)]
348
349 for k in range(0, self.__num_points):
350 for l in range(0, self.__num_points):
351 #FRm[k][l] = phi1[k]*phi1[l]
352 K1[k][l] = phi1[k]*phi1[l] - Kxi[k][l]
353
354 #K1_orig = np.subtract(FRm,self.__stiffness_operator)
355 #for k in range(0, self.__num_points):
356 # for l in range(0, self.__num_points):
357 # print(K1[k][l] - K1_orig[k][l])
358
359 return K1
360
361
363 r"""
364
365 @see DerivativeOperator1dDocumentation which is also used to dump documentation
366 into generated C++ code.
367
368 """
369 dQdx = [ [0.0 for _ in range(0,self.dofs_per_axisdofs_per_axis)] for _ in range(0,self.dofs_per_axisdofs_per_axis) ]
370
371 for row in range(0,self.dofs_per_axisdofs_per_axis):
372 for col in range(0,self.dofs_per_axisdofs_per_axis):
373 dQdx[row][col] = self.derivative1d( self.quadrature_points[row], col)
374
375 return dQdx
376
377
379 r"""
380
381 @todo Definitely useful, but not used atm
382
383 Transforms the degrees of freedom located on a coarse grid edge
384 quadrature_points to degrees of freedoms located on quadrature_points of a fine grid edge.
385 The difference in levels is 1.
386
387 Let us denote by P the fine grid projector (= equidistantGridProjector). The fine grid DoF
388 are computed according to:
389
390 u^{fine;j}_i = sum_{m} P^{j}_im u^{coarse}_m
391
392 Args:
393 j:
394 subinterval index
395
396 Returns:
397 fineGridProjector:
398 Operator to express polynomial function associated with original interval with basis functions associated with subinterval j
399 """
400 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
401
402 for i in range(0, self.__num_points): # Eq. basis
403 phi_i, _ = self.__evaluate((self.__quadrature_points[i]+j)/mp.mpf(3.0)) # comma after phi_i is important
404 for m in range(0, self.__num_points): # DG basis
405 fineGridProjector[m][i] = phi_i[m]
406 return fineGridProjector
407
408
410 r"""
411
412 Compute per basis function what the very left value of the polynomial would be
413
414 @see BasisFunctionValuesLeft1dDocumentation which defines what this routine
415 does and is also used to inject documentation into the generated C++ code.
416
417 """
418 return [ self.value1d(0.0, number) for number in range(self.dofs_per_axisdofs_per_axis) ]
419
420
422 """
423
424 @todo Not used atm, but definitely useful
425
426 Transforms the degrees of freedom located at non-equidistant Lagrange support points
427 quadrature_points to degrees of freedoms located at quadrature_points of an equidistant grid over (0,1).
428
429 Let us denote by P the projection operator (= equidistantGridProjector). The equidistant DoF
430 are computed according to:
431
432 u^eq_i = sum_{m} P_im u^DG_m
433
434 Returns:
435 equidistantGridProjector:
436 The corresponding degrees of freedom located at quadrature_points of an equidistant grid over (0,1).
437 """
438 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
439 subxi = mp.linspace(mp.mpf(0.0), mp.mpf(1.0), self.__num_points)
440
441 for i in range(0, self.__num_points): # Eq. basis
442 phi_i, _ = self.__evaluate(subxi[i]) # comma after phi_i is important
443 for m in range(0, self.__num_points): # DG basis
444 equidistantGridProjector[m][i] = phi_i[m]
445 return equidistantGridProjector
446
447
448 def init_dictionary_with_default_parameters(self,dictionary,use_multidimensional_arrays):
449 def snake_to_camel(word):
450 return ''.join(x.capitalize() or '_' for x in word.lower().split('_'))
451
452 basisDeclarations = ""
453 basisInitializers = ""
454
455 dictionary["ORDER"] = self.order
456 dictionary["BASIS_DECLARATIONS"] = ""
457 dictionary["BASIS_INITIALIZERS"] = ""
458
459 dictionary["BASIS_DECLARATIONS"] += "/**\n"
460 dictionary["BASIS_DECLARATIONS"] += self.QuadraturePoints1dDocumentation
461 dictionary["BASIS_DECLARATIONS"] += "*/\n"
462 #dictionary["BASIS_DECLARATIONS"] += "static const double QuadraturePoints1d[DGOrder+1];\n\n"
463 #dictionary["BASIS_INITIALIZERS"] += " QuadraturePoints1d{},\n".format( render_tensor_1(self.quadrature_points) )
464 dictionary["BASIS_DECLARATIONS"] += "#pragma omp declare target\n"
465 dictionary["BASIS_DECLARATIONS"] += "static constexpr double QuadraturePoints1d[] = {};\n\n".format( render_tensor_1(self.quadrature_points) )
466 dictionary["BASIS_DECLARATIONS"] += "#pragma omp end declare target\n"
467 dictionary["BASIS_DECLARATIONS"] += "/**\n"
468 dictionary["BASIS_DECLARATIONS"] += self.QuadratureWeights1dDocumentation
469 dictionary["BASIS_DECLARATIONS"] += "*/\n"
470 #dictionary["BASIS_DECLARATIONS"] += "const double QuadratureWeights1d[DGOrder+1];\n\n"
471 #dictionary["BASIS_INITIALIZERS"] += " QuadratureWeights1d{},\n".format( render_tensor_1(self.quadrature_weights) )
472 dictionary["BASIS_DECLARATIONS"] += "static constexpr double QuadratureWeights1d[] = {};\n\n".format( render_tensor_1(self.quadrature_weights) )
473
474 dictionary["BASIS_DECLARATIONS"] += "/**\n"
475 dictionary["BASIS_DECLARATIONS"] += self.MassMatrix1dDocumentation
476 dictionary["BASIS_DECLARATIONS"] += "*/\n"
477 #dictionary["BASIS_DECLARATIONS"] += "const double MassMatrixDiagonal1d[DGOrder+1];\n\n"
478 #dictionary["BASIS_INITIALIZERS"] += " MassMatrixDiagonal1d{},\n".format( render_tensor_1(self.__compute_mass_matrix_diagonal()) )
479 dictionary["BASIS_DECLARATIONS"] += "static constexpr double MassMatrixDiagonal1d[] = {};\n\n".format( render_tensor_1(self.__compute_mass_matrix_diagonal()) )
480
481 dictionary["BASIS_DECLARATIONS"] += "/**\n"
482 dictionary["BASIS_DECLARATIONS"] += self.StiffnessOperator1dDocumentation
483 dictionary["BASIS_DECLARATIONS"] += "*/\n"
484 if use_multidimensional_arrays:
485 #dictionary["BASIS_DECLARATIONS"] += "const double StiffnessOperator1d[DGOrder+1][DGOrder+1];\n\n"
486 #dictionary["BASIS_INITIALIZERS"] += " StiffnessOperator1d{},\n".format( render_tensor_2(self.__compute_stiffness_operator(),use_multidimensional_arrays) )
487 dictionary["BASIS_DECLARATIONS"] += "static constexpr double StiffnessOperator1d[][] = {};\n\n".format( render_tensor_2(self.__compute_stiffness_operator(),use_multidimensional_arrays) )
488 else:
489 #dictionary["BASIS_DECLARATIONS"] += "const double StiffnessOperator1d[(DGOrder+1)*(DGOrder+1)];\n\n"
490 #dictionary["BASIS_INITIALIZERS"] += " StiffnessOperator1d{},\n".format( render_tensor_2(self.__compute_stiffness_operator(),use_multidimensional_arrays) )
491 dictionary["BASIS_DECLARATIONS"] += "static constexpr double StiffnessOperator1d[] = {};\n\n".format( render_tensor_2(self.__compute_stiffness_operator(),use_multidimensional_arrays) )
492
493 dictionary["BASIS_DECLARATIONS"] += "/**\n"
494 dictionary["BASIS_DECLARATIONS"] += self.BasisFunctionValuesLeft1dDocumentation
495 dictionary["BASIS_DECLARATIONS"] += "*/\n"
496 dictionary["BASIS_DECLARATIONS"] += "static constexpr double BasisFunctionValuesLeft1d[] = {};\n\n".format( render_tensor_1(self.__compute_basis_function_values_left()) )
497
498 dictionary["BASIS_DECLARATIONS"] += "/**\n"
499 dictionary["BASIS_DECLARATIONS"] += self.DerivativeOperator1dDocumentation
500 dictionary["BASIS_DECLARATIONS"] += "*/\n"
501 if use_multidimensional_arrays:
502 dictionary["BASIS_DECLARATIONS"] += "static constexpr double DerivativeOperator1d[][] = {};\n\n".format( render_tensor_2(self.__compute_derivative_operator(),use_multidimensional_arrays) )
503 else:
504 dictionary["BASIS_DECLARATIONS"] += "static constexpr double DerivativeOperator1d[] = {};\n\n".format( render_tensor_2(self.__compute_derivative_operator(),use_multidimensional_arrays) )
505
506 dictionary["BASIS_DECLARATIONS"] += "/**\n"
507 dictionary["BASIS_DECLARATIONS"] += self.RestrictionMatrix1dDocumentation
508 dictionary["BASIS_DECLARATIONS"] += "*/\n"
509 if use_multidimensional_arrays:
510 dictionary["BASIS_DECLARATIONS"] += "static constexpr double RestrictionMatrix1d[][][] = {};\n\n".format( render_tensor_3(self.__compute_restriction_matrix(),use_multidimensional_arrays) )
511 else:
512 dictionary["BASIS_DECLARATIONS"] += "static constexpr double RestrictionMatrix1d[] = {};\n\n".format( render_tensor_3(self.__compute_restriction_matrix(),use_multidimensional_arrays) )
513
514 dictionary["BASIS_DECLARATIONS"] += "/**\n"
515 dictionary["BASIS_DECLARATIONS"] += self.InterpolationMatrix1dDocumentation
516 dictionary["BASIS_DECLARATIONS"] += "*/\n"
517 if use_multidimensional_arrays:
518 dictionary["BASIS_DECLARATIONS"] += "static constexpr double InterpolationMatrix1d[][][] = {};\n\n".format( render_tensor_3(self.__compute_interpolation_matrix(),use_multidimensional_arrays) )
519 else:
520 dictionary["BASIS_DECLARATIONS"] += "static constexpr double InterpolationMatrix1d[] = {};\n\n".format( render_tensor_3(self.__compute_interpolation_matrix(),use_multidimensional_arrays) )
521
522
523 @property
524 @abstractmethod
526 assert False, "to be implemented by subclass"
527
528
529 @property
530 @abstractmethod
532 assert False, "to be implemented by subclass"
533
534
535 def value1d(self,x,number):
536 r"""
537
538 Return the numberth shape function's value at x
539
540 This equals Dominic's old evaluate function, but that one evaluated all the
541 polynomials in one rush, whereas this one only looks at shape function number
542 number. Also, Dominic's version did both the value and the derivative. I
543 split this one up into two version.
544
545 """
546 enumerator = 1.0
547 for i in range(0,self.dofs_per_axisdofs_per_axis):
548 if i!=number:
549 enumerator *= (x-self.quadrature_points[i])
550
551 denominator = 1.0
552 for i in range(0,self.dofs_per_axisdofs_per_axis):
553 if i!=number:
554 denominator *= (self.quadrature_points[number]-self.quadrature_points[i])
555
556 return enumerator/denominator
557
558
559 def derivative1d(self,x,number):
560 r"""
561
562 Return the numberth shape function's derivative at x
563
564 u = sum_i u_i phi_i
565
566 => d/dx u = sum_j (d/dx phi_j) u_j
567
568 To construct the gradient field, we make the ansatz:
569
570 grad u = sum_i g_i phi_i
571
572 =>
573
574 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
575
576 = w_i (d/dx phi_j) (x_i) / w_i = (d/dx phi_j) (x_i)
577
578 = DUDX^T
579
580 where DUDX is the operator computed by this function:
581
582 DUDX_ij = (d/dx phi_i) (x_j)
583
584 It can be further written as
585
586 DUDX_ij = 1/w_i * K^T_ij
587
588 where the stiffness matrix K is defined as
589
590 K_ij = <d/dx phi_i, phi_j>_[0,1] = w_j (d/dx phi_j) (x_i)
591
592 :return: transposed derivative operator
593
594 :note: If you want to use this operator to compute the gradient of the solution,
595 you need to use the transpose.
596
597
598 """
599
600 result = 0.0
601 for l in range(0,self.dofs_per_axisdofs_per_axis):
602 if l!=number:
603 enumerator = 1.0
604 denominator = 1.0/(self.quadrature_points[number]-self.quadrature_points[l])
605 for m in range(0,self.dofs_per_axisdofs_per_axis):
606 if m!=number and m!=l:
607 enumerator *= x-self.quadrature_points[m]
608 denominator *= 1.0/(self.quadrature_points[number]-self.quadrature_points[m])
609 result += enumerator*denominator
610 return result
611
612from scipy.special import legendre
613from scipy.special import roots_sh_legendre
614
616 r"""
617
618 The Gauss-Legendre Basis is by construction the only basis which
619 yields diagonal mass matrices. I rely solely on SciPy here to
620 construct these functions, as I'm interested in numerical values
621 only, and as the normal SciPy accuracy is sufficient.
622
623 There might be more elegant implementations using SymPy which
624 exploit the special structure behind Gauss-Legendre. But I prefer
625 here to stick to one Python package only.
626
627 """
628
629 def __init__(self,polynomial_order):
630 super(GaussLegendreBasis,self).__init__(polynomial_order)
631
632
633 @property
635 r"""
636
637 It seems to be a little bit weird that I have to use the polynomial
638 order plus one here, but you have to read the docu to understand
639 that we are evaluating the polynomial of a certain order to get the
640 roots. So the argument has to be equal to the number of quadrature
641 points that I wannt have.
642
643 I originally used the roots_legendre and then did recalibrate the
644 result manually. Today, SciPy directly offers a shifted Legendre
645 function which I can use directly. So I don't need an expression like
646
647
648 return [ x/2.0+0.5 for x in roots_on_minus_one_one_interval ]
649
650
651 anymore. The nicest definition of the shifted Legendre shape functions
652 can, as so often, be found on Wikipedia:
653
654 https://en.wikipedia.org/wiki/Legendre_polynomials#Shifted_Legendre_polynomials
655
656 """
657 roots, weights = scipy.special.roots_sh_legendre(self._polynomial_order_polynomial_order+1)
658 return roots
659
660
661 @property
663 r"""
664
665 It seems to be a little bit weird that I have to use the polynomial
666 order plus one here, but you have to read the docu to understand
667 that we are evaluating the polynomial of a certain order to get the
668 roots. So the argument has to be equal to the number of quadrature
669 points that I wannt have.
670
671 I originally used the roots_legendre and then did recalibrate the
672 result manually. Today, SciPy directly offers a shifted Legendre
673 function which I can use directly. So I don't need an expression like
674
675
676 return [ x/2.0+0.5 for x in roots_on_minus_one_one_interval ]
677
678
679 anymore. The nicest definition of the shifted Legendre shape functions
680 can, as so often, be found on Wikipedia:
681
682 https://en.wikipedia.org/wiki/Legendre_polynomials#Shifted_Legendre_polynomials
683
684 """
685 roots, weights = scipy.special.roots_sh_legendre(self._polynomial_order_polynomial_order+1)
686 return weights
687
688
689
691 r"""
692
693 The Gauss-Lobatto quadrature points do not yield a diagonal mass
694 matrix. They yield a full matrix. However, there are codes which
695 ignore this fact and lump the basis. Mathematically, this is
696 equivalent to the Gauss-Lobatto quadrature because this leads to a diagonal matrix that is easy to invert
697
698 """
699
700 def __init__(self,polynomial_order):
701 super(GaussLobattoBasisWithLumpedDiagonalBasis,self).__init__(polynomial_order)
702
703
704 @property
706 assert False, "to be implemented by subclass"
707 raise Exception( "check implementation. See class comment")
708 x,w = quadrature.gauss_lobatto(self.dofs_per_axisdofs_per_axis,PREC_DIGITS)
709 return self._transform_and_sort_points_and_weights(x,w)
710
The Gauss-Legendre Basis is by construction the only basis which yields diagonal mass matrices.
quadrature_points(self)
It seems to be a little bit weird that I have to use the polynomial order plus one here,...
quadrature_weights(self)
It seems to be a little bit weird that I have to use the polynomial order plus one here,...
derivative1d(self, x, number)
Return the numberth shape function's derivative at x.
__compute_stiffness_operator(self)
Computes the (reference) element stiffness matrix for an approximation of order self....
init_dictionary_with_default_parameters(self, dictionary, use_multidimensional_arrays)
To be implemented by subclass.
__compute_interpolation_matrix(self)
compute the interpolation matrix, which used to prepare halo layer data for fine grid points at the r...
__compute_basis_function_values_left(self)
Compute per basis function what the very left value of the polynomial would be.
__compute_restriction_matrix(self)
compute the restriction matrix, which used to prepare halo layer data for coarse cells at the refinem...
Abstract base class for all Lagrange bases available in ExaHyPE 2.
init_dictionary_with_default_parameters(self, dictionary, use_multidimensional_arrays)
To be implemented by subclass.