11from scipy.integrate
import quad
13from abc
import abstractmethod
15from .LagrangeBasis
import LagrangeBasis
16from .LagrangeBasis
import render_tensor_1
17from .LagrangeBasis
import render_tensor_2
18from .LagrangeBasis
import render_tensor_3
20from numpy
import number
28 Lagrange Basis with a diagonal mass matrix
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
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.
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.
48 ## Generated vectors and matrices
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.
55 - MassMatrixDiagonal1d
58 - InterpolationMatrix1d
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.
71 super(LagrangeBasisWithDiagonalMassMatrix,self).
__init__(polynomial_order)
77 return "<{}.{} object>: {}".format(self.__class__.__module__,self.__class__.__name__,d)
83 QuadraturePoints1dDocumentation =
"""
84 The quadrature points are an array of size [p+1] if p is the order of the
85 chosen polyomial. The double array defines the spacing of the quadrature points
86 over the unit interval. See the helper routine exahype2::dg::getQuadraturePoint()
87 for an example how the points are used to identify the actual location in
88 space of the quadrature points.
90 As we work in a tensor product world, the 2d and 3d quadrature points can be
91 simply constructed by multiplying the respective coordinate system indices.
95 QuadratureWeights1dDocumentation =
"""
96 The quadrature weights are an array of size [p+1] if p is the order of the
97 chosen polyomial. The double array defines the integral over the polynomial
98 over the unit interval. We use Lagrangian bases here, i.e. only one polynomial
99 of the basis is 1 in a quadrature point, while all the others are 0. However,
100 the fact that it is 1 in the point, does not tell us anything about the
101 integral over this particular polynomial. That's the weight associated with
104 As we work in a tensor product world, the 2d and 3d weights can be
105 simply constructed by multiplying the respective coordinate system indices.
109 MassMatrix1dDocumentation =
"""
110 Computes the (reference) element mass matrix' diagonal. The mass matrix is defined
113 @f$ \int _{(0,1)}(\phi _i, \phi _j) dx, i,j = 1 ... N > 0 @f$
115 where @f$ \phi _i,\phi _j @f$ are Lagrange basis functions associated with
116 the quadrature within the unit-interval. They are polynomials of maximum order N-1.
118 Our implementation is quite some overkill, as I assemble the whole mass matrix
119 and then extract only the diagonal. If we use Legendre nodes as support points,
120 this diagonal is an exact evaluation, i.e. the mass matrix is diagonal. If
121 we use Lobatto nodes, it is an approximation.
123 Please note that this is a 1d mass matrix: If we have a polynomial of order p,
124 we have @f$ (p+1)^d @f$ dofs in total per cell. My matrix has only the dimensions
125 @f$ (p+1) \times (p+1) @f$ so it effectively only models how the dofs along one
126 horizontal or vertical line within the cell do interact. This is totally sufficient
127 if we know that we have a tensor product approach of the shape functions and a
128 diagonal mass matrix.
130 @todo I don't know if we need renormalisation for Lobatto nodes, i.e. if we
131 should ensure that the row sum is preserved
133 This is an operation which maps nodal data into a weak space, i.e. the outcome has
134 to be scaled with h to be correct. It stems from a 1d integral. If you want to
135 compute the real mass matrix output, you have to multiply the outcome along each
140 StiffnessOperator1dDocumentation =
"""
141 The stiffness operator is a [p+1]x[p+1] matrix which is constructed in
142 __compute_stiffness_operator().
146 @f$ K_{ij} = <\partial _x \phi _i, \phi _j> = w_j (\partial _x \phi _j) (x_i) @f$
148 where @f$ \phi _i, \phi _j @f$ are Lagrange basis functions associated with
149 the support (quadrature) points @f$ x_i @f$ and @f$ x_j @f$ within the unit
152 If you have a vector of p+1 entries, it defines a polynomial (aka function f) over the unit
153 interval. If you multiply this vector if @f$ K @f$, you get the outcome of
155 @f$ \int _{[0.1]} \Big( \partial _x f, \phi _j \Big) dx @f$
157 for the different tests @f$ \phi _j @f$. If your interval is not the unit
158 one but has side length $h$, you have to multiply the outcome with $h$ as
159 you integrate. At the same time, the derivative however has to be rescaled
160 with @f$ h^{-1} @f$ and the two h-recalibrations thus cancel out.
161 If you have a 2d or 3d function space, you still have to multiple with these
162 shape functions (see discussion around mass matrix), and you will get an
163 additional @f$ h^{d-1} @f$ scaling of the outcome.
165 We note that the outcome is exact for both Lobatto and Legendre nodes due to
166 the reduced degree in one scalar product operand.
170 BasisFunctionValuesLeft1dDocumentation =
"""
171 This is a vector of p+1 entries which tells you for a 1d interval
172 what the function on the left side is. Assume all Lagrange polynomials
173 are defined over the unit interval. The first entry in this matrix
174 tells you what the leftmost polynomial's value is at x=0. The second
175 entry discusses the impact of the second polynomial. If you have a
176 vector of p+1 shape function weights, you can compute the scalar
177 product of this vector and this weight vector, and you'll know what
178 the solution at the point x=0 is.
180 If the rightmost value, i.e. the value at x=1 is of interest, the order
181 within this vector has to be inverted.
183 This is a nodal 1d operation, i.e. there are no integrals or similar
184 involved. We take the real 1d polyomial from the shape function.
187 DerivativeOperator1dDocumentation =
"""
188 Matrix which is given the p+1 weights of the shape function
189 along a 1d line. It then returns the p+1 gradients at the integration
192 This is a point-wise operator, i.e. it works directly with the weights
193 of the shape function in the function space. It does not involve any
194 integral et al. Furthermore, it is exact given a certain Lagrange
198 InterpolationMatrix1dDocumentation =
"""
199 This is essentially a @f$ 3 \cdot (order+1)^d-1 @f$ matrix split up
200 into a three-dimensional tensor. The documentation/content of this
201 tensor can be found on the page about DG AMR which is held in exahype
202 within the file exahype2/dg/Riemann.h.
205 RestrictionMatrix1dDocumentation =
"""
206 This is essentially a @f$ 3 \cdot (order+1)^d-1 @f$ matrix split up
207 into a three-dimensional tensor. The documentation/content of this
208 tensor can be found on the page about DG AMR which is held in exahype
209 within the file exahype2/dg/Riemann.h.
216 https://people.maths.ox.ac.uk/trefethen/barycentric.pdf
225 result[j] = 1.0/divisor
232 Reference mass matrix. See class documentation. I moved the docu there,
233 as doxygen does not extract documentation from private routines in Python.
240 ans,err = quad(
lambda x: self.
value1d(x,row) * self.
value1d(x,col),0,1)
241 mass_matrix[row][col] = ans
249 Computes the (reference) element stiffness matrix for an approximation of order self.__max_poly_order.
251 @see StiffnessOperator1dDocumentation which is also used to dump documentation
252 into generated C++ code.
260 stiffness_matrix[row][col] = ans
262 return stiffness_matrix
268 compute the interpolation matrix, which used to prepare halo layer data for fine grid points at the refinement boundary.
270 The matrix is only 1d mapping, i.e. used for "surface" of a 2D domain and have a size of 3Dof \times Dof.
271 Its elements are \phi_j(x^f_i), i.e. the value of basis functions for coarse grid on fine grid point.
272 In practive we multiply this matrix to vector of weight in coarse grid (u^c_i):
274 \phi_j(x^f_i)*u^c_i=u^c(x^f_i)
276 u^c(x) is the original solution on coarse grid. Thus the result is actually the evaluation of coarse solution on fine grid position
278 We can also use the same matrix to interpolate along the normal direction.
282 return [[[1.0]],[[1.0]],[[1.0]]]
291 for block
in range(0, 3):
294 interp_matrix[block][row][col]=self.
value1d(fine_grid_point[row],col)
302 compute the restriction matrix, which used to prepare halo layer data for coarse cells at the refinement boundary.
303 the matrix formulation depends on the interpolation scheme.
305 The matrix is only 1d mapping, i.e. used for "surface" of a 2D domain and have a size of Dof \times 3Dof.
307 We can also use the same matrix to interpolate along the normal direction.
312 return [[[1.0/3.0]],[[1.0/3.0]],[[1.0/3.0]]]
320 for block
in range(0, 3):
323 restriction_matrix[block][row][col]=self.
value1d(fine_grid_point[col],row)/3.0
325 return restriction_matrix
331 @todo Not yet updated atm but seems also not to be used anywhere
333 Computes the difference between the reference element mass operator
334 evaluated at point xi=1.0 and the element stiffness matrix.
336 :return: delta between the reference element mass operator at point xi=1.0 and the element stiffness matrix
338 phi1, _ = self.__evaluate(1.0)
339 Kxi = self.__stiffness_operator
347 K1[k][l] = phi1[k]*phi1[l] - Kxi[k][l]
360 @see DerivativeOperator1dDocumentation which is also used to dump documentation
361 into generated C++ code.
376 @todo Definitely useful, but not used atm
378 Transforms the degrees of freedom located on a coarse grid edge
379 quadrature_points to degrees of freedoms located on quadrature_points of a fine grid edge.
380 The difference in levels is 1.
382 Let us denote by P the fine grid projector (= equidistantGridProjector). The fine grid DoF
383 are computed according to:
385 u^{fine;j}_i = sum_{m} P^{j}_im u^{coarse}_m
393 Operator to express polynomial function associated with original interval with basis functions associated with subinterval j
400 fineGridProjector[m][i] = phi_i[m]
401 return fineGridProjector
407 Compute per basis function what the very left value of the polynomial would be
409 @see BasisFunctionValuesLeft1dDocumentation which defines what this routine
410 does and is also used to inject documentation into the generated C++ code.
419 @todo Not used atm, but definitely useful
421 Transforms the degrees of freedom located at non-equidistant Lagrange support points
422 quadrature_points to degrees of freedoms located at quadrature_points of an equidistant grid over (0,1).
424 Let us denote by P the projection operator (= equidistantGridProjector). The equidistant DoF
425 are computed according to:
427 u^eq_i = sum_{m} P_im u^DG_m
430 equidistantGridProjector:
431 The corresponding degrees of freedom located at quadrature_points of an equidistant grid over (0,1).
434 subxi = mp.linspace(mp.mpf(0.0), mp.mpf(1.0), self.
__num_points)
437 phi_i, _ = self.__evaluate(subxi[i])
439 equidistantGridProjector[m][i] = phi_i[m]
440 return equidistantGridProjector
444 def snake_to_camel(word):
445 return ''.join(x.capitalize()
or '_' for x
in word.lower().split(
'_'))
447 basisDeclarations =
""
448 basisInitializers =
""
450 dictionary[
"ORDER"] = self.
order
451 dictionary[
"BASIS_DECLARATIONS"] =
""
452 dictionary[
"BASIS_INITIALIZERS"] =
""
454 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
456 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
459 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double QuadraturePoints1d[] = {};\n\n".format( render_tensor_1(self.
quadrature_points) )
461 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
463 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
466 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double QuadratureWeights1d[] = {};\n\n".format( render_tensor_1(self.
quadrature_weights) )
468 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
470 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
473 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double MassMatrixDiagonal1d[] = {};\n\n".format( render_tensor_1(self.
__compute_mass_matrix_diagonal()) )
475 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
477 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
478 if use_multidimensional_arrays:
481 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double StiffnessOperator1d[][] = {};\n\n".format( render_tensor_2(self.
__compute_stiffness_operator(),use_multidimensional_arrays) )
485 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double StiffnessOperator1d[] = {};\n\n".format( render_tensor_2(self.
__compute_stiffness_operator(),use_multidimensional_arrays) )
487 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
489 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
492 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
494 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
495 if use_multidimensional_arrays:
496 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double DerivativeOperator1d[][] = {};\n\n".format( render_tensor_2(self.
__compute_derivative_operator(),use_multidimensional_arrays) )
498 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double DerivativeOperator1d[] = {};\n\n".format( render_tensor_2(self.
__compute_derivative_operator(),use_multidimensional_arrays) )
500 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
502 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
503 if use_multidimensional_arrays:
504 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double RestrictionMatrix1d[][][] = {};\n\n".format( render_tensor_3(self.
__compute_restriction_matrix(),use_multidimensional_arrays) )
506 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double RestrictionMatrix1d[] = {};\n\n".format( render_tensor_3(self.
__compute_restriction_matrix(),use_multidimensional_arrays) )
508 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
510 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
511 if use_multidimensional_arrays:
512 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double InterpolationMatrix1d[][][] = {};\n\n".format( render_tensor_3(self.
__compute_interpolation_matrix(),use_multidimensional_arrays) )
514 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double InterpolationMatrix1d[] = {};\n\n".format( render_tensor_3(self.
__compute_interpolation_matrix(),use_multidimensional_arrays) )
520 assert False,
"to be implemented by subclass"
526 assert False,
"to be implemented by subclass"
532 Return the numberth shape function's value at x
534 This equals Dominic's old evaluate function, but that one evaluated all the
535 polynomials in one rush, whereas this one only looks at shape function number
536 number. Also, Dominic's version did both the value and the derivative. I
537 split this one up into two version.
550 return enumerator/denominator
556 Return the numberth shape function's derivative at x
560 => d/dx u = sum_j (d/dx phi_j) u_j
562 To construct the gradient field, we make the ansatz:
564 grad u = sum_i g_i phi_i
568 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
570 = w_i (d/dx phi_j) (x_i) / w_i = (d/dx phi_j) (x_i)
574 where DUDX is the operator computed by this function:
576 DUDX_ij = (d/dx phi_i) (x_j)
578 It can be further written as
580 DUDX_ij = 1/w_i * K^T_ij
582 where the stiffness matrix K is defined as
584 K_ij = <d/dx phi_i, phi_j>_[0,1] = w_j (d/dx phi_j) (x_i)
586 :return: transposed derivative operator
588 :note: If you want to use this operator to compute the gradient of the solution,
589 you need to use the transpose.
600 if m!=number
and m!=l:
603 result += enumerator*denominator
606from scipy.special
import legendre
607from scipy.special
import roots_sh_legendre
612 The Gauss-Legendre Basis is by construction the only basis which
613 yields diagonal mass matrices. I rely solely on SciPy here to
614 construct these functions, as I'm interested in numerical values
615 only, and as the normal SciPy accuracy is sufficient.
617 There might be more elegant implementations using SymPy which
618 exploit the special structure behind Gauss-Legendre. But I prefer
619 here to stick to one Python package only.
624 super(GaussLegendreBasis,self).
__init__(polynomial_order)
631 It seems to be a little bit weird that I have to use the polynomial
632 order plus one here, but you have to read the docu to understand
633 that we are evaluating the polynomial of a certain order to get the
634 roots. So the argument has to be equal to the number of quadrature
635 points that I wannt have.
637 I originally used the roots_legendre and then did recalibrate the
638 result manually. Today, SciPy directly offers a shifted Legendre
639 function which I can use directly. So I don't need an expression like
642 return [ x/2.0+0.5 for x in roots_on_minus_one_one_interval ]
645 anymore. The nicest definition of the shifted Legendre shape functions
646 can, as so often, be found on Wikipedia:
648 https://en.wikipedia.org/wiki/Legendre_polynomials#Shifted_Legendre_polynomials
659 It seems to be a little bit weird that I have to use the polynomial
660 order plus one here, but you have to read the docu to understand
661 that we are evaluating the polynomial of a certain order to get the
662 roots. So the argument has to be equal to the number of quadrature
663 points that I wannt have.
665 I originally used the roots_legendre and then did recalibrate the
666 result manually. Today, SciPy directly offers a shifted Legendre
667 function which I can use directly. So I don't need an expression like
670 return [ x/2.0+0.5 for x in roots_on_minus_one_one_interval ]
673 anymore. The nicest definition of the shifted Legendre shape functions
674 can, as so often, be found on Wikipedia:
676 https://en.wikipedia.org/wiki/Legendre_polynomials#Shifted_Legendre_polynomials
687 The Gauss-Lobatto quadrature points do not yield a diagonal mass
688 matrix. They yield a full matrix. However, there are codes which
689 ignore this fact and lump the basis. Mathematically, this is
690 equivalent to the Gauss-Lobatto quadrature because this leads to a diagonal matrix that is easy to invert
695 super(GaussLobattoBasisWithLumpedDiagonalBasis,self).
__init__(polynomial_order)
700 assert False,
"to be implemented by subclass"
701 raise Exception(
"check implementation. See class comment")
703 return self._transform_and_sort_points_and_weights(x,w)
The Gauss-Legendre Basis is by construction the only basis which yields diagonal mass matrices.
quadrature_weights(self)
It seems to be a little bit weird that I have to use the polynomial order plus one here,...
quadrature_points(self)
It seems to be a little bit weird that I have to use the polynomial order plus one here,...
__init__(self, polynomial_order)
The Gauss-Lobatto quadrature points do not yield a diagonal mass matrix.
__init__(self, polynomial_order)
Lagrange Basis with a diagonal mass matrix.
__compute_equidistant_grid_projector(self)
str QuadraturePoints1dDocumentation
str BasisFunctionValuesLeft1dDocumentation
__compute_derivative_operator(self)
derivative1d(self, x, number)
Return the numberth shape function's derivative at x.
str StiffnessOperator1dDocumentation
__compute_stiffness_operator(self)
Computes the (reference) element stiffness matrix for an approximation of order self....
str QuadratureWeights1dDocumentation
str InterpolationMatrix1dDocumentation
__compute_fine_grid_projector(self, j)
__compute_restriction_matrix(self)
compute the restriction matrix, which used to prepare halo layer data for coarse cells at the refinem...
value1d(self, x, number)
Return the numberth shape function's value at x.
str RestrictionMatrix1dDocumentation
__init__(self, polynomial_order)
str DerivativeOperator1dDocumentation
str MassMatrix1dDocumentation
__compute_interpolation_matrix(self)
compute the interpolation matrix, which used to prepare halo layer data for fine grid points at the r...
__compute_barycentric_weights(self)
See Eq.
__compute_mass_matrix_diagonal(self)
Reference mass matrix.
__compute_basis_function_values_left(self)
Compute per basis function what the very left value of the polynomial would be.
init_dictionary_with_default_parameters(self, dictionary, use_multidimensional_arrays)
To be implemented by subclass.
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.