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)
79 return "<{}.{} object>: {}".format(self.__class__.__module__,self.__class__.__name__,d)
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.
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.
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
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.
111 MassMatrix1dDocumentation =
r"""
112 Computes the (reference) element mass matrix' diagonal. The mass matrix is defined
115 @f$ \int _{(0,1)}(\phi _i, \phi _j) dx, i,j = 1 ... N > 0 @f$
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.
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.
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.
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
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
142 StiffnessOperator1dDocumentation =
r"""
143 The stiffness operator is a [p+1]x[p+1] matrix which is constructed in
144 __compute_stiffness_operator().
148 @f$ K_{ij} = <\partial _x \phi _i, \phi _j> = w_j (\partial _x \phi _j) (x_i) @f$
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
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
157 @f$ \int _{[0.1]} \Big( \partial _x f, \phi _j \Big) dx @f$
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.
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.
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.
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.
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.
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
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
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.
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.
218 https://people.maths.ox.ac.uk/trefethen/barycentric.pdf
227 result[j] = 1.0/divisor
234 Reference mass matrix. See class documentation. I moved the docu there,
235 as doxygen does not extract documentation from private routines in Python.
243 ans,err = quad(
lambda x: self.
value1d(x,row) * self.
value1d(x,col),0,1)
249 stiffness_matrix =
None
253 Computes the (reference) element stiffness matrix for an approximation of order self.__max_poly_order.
255 @see StiffnessOperator1dDocumentation which is also used to dump documentation
256 into generated C++ code.
273 compute the interpolation matrix, which used to prepare halo layer data for fine grid points at the refinement boundary.
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):
279 \phi_j(x^f_i)*u^c_i=u^c(x^f_i)
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
283 We can also use the same matrix to interpolate along the normal direction.
287 return [[[1.0]],[[1.0]],[[1.0]]]
296 for block
in range(0, 3):
299 interp_matrix[block][row][col]=self.
value1d(fine_grid_point[row],col)
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.
310 The matrix is only 1d mapping, i.e. used for "surface" of a 2D domain and have a size of Dof \times 3Dof.
312 We can also use the same matrix to interpolate along the normal direction.
317 return [[[1.0/3.0]],[[1.0/3.0]],[[1.0/3.0]]]
325 for block
in range(0, 3):
328 restriction_matrix[block][row][col]=self.
value1d(fine_grid_point[col],row)/3.0
330 return restriction_matrix
336 @todo Not yet updated atm but seems also not to be used anywhere
338 Computes the difference between the reference element mass operator
339 evaluated at point xi=1.0 and the element stiffness matrix.
341 :return: delta between the reference element mass operator at point xi=1.0 and the element stiffness matrix
343 phi1, _ = self.__evaluate(1.0)
344 Kxi = self.__stiffness_operator
352 K1[k][l] = phi1[k]*phi1[l] - Kxi[k][l]
365 @see DerivativeOperator1dDocumentation which is also used to dump documentation
366 into generated C++ code.
381 @todo Definitely useful, but not used atm
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.
387 Let us denote by P the fine grid projector (= equidistantGridProjector). The fine grid DoF
388 are computed according to:
390 u^{fine;j}_i = sum_{m} P^{j}_im u^{coarse}_m
398 Operator to express polynomial function associated with original interval with basis functions associated with subinterval j
405 fineGridProjector[m][i] = phi_i[m]
406 return fineGridProjector
412 Compute per basis function what the very left value of the polynomial would be
414 @see BasisFunctionValuesLeft1dDocumentation which defines what this routine
415 does and is also used to inject documentation into the generated C++ code.
424 @todo Not used atm, but definitely useful
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).
429 Let us denote by P the projection operator (= equidistantGridProjector). The equidistant DoF
430 are computed according to:
432 u^eq_i = sum_{m} P_im u^DG_m
435 equidistantGridProjector:
436 The corresponding degrees of freedom located at quadrature_points of an equidistant grid over (0,1).
439 subxi = mp.linspace(mp.mpf(0.0), mp.mpf(1.0), self.
__num_points)
442 phi_i, _ = self.__evaluate(subxi[i])
444 equidistantGridProjector[m][i] = phi_i[m]
445 return equidistantGridProjector
449 def snake_to_camel(word):
450 return ''.join(x.capitalize()
or '_' for x
in word.lower().split(
'_'))
452 basisDeclarations =
""
453 basisInitializers =
""
455 dictionary[
"ORDER"] = self.
order
456 dictionary[
"BASIS_DECLARATIONS"] =
""
457 dictionary[
"BASIS_INITIALIZERS"] =
""
459 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
461 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
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"
469 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
472 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double QuadratureWeights1d[] = {};\n\n".format( render_tensor_1(self.
quadrature_weights) )
474 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
476 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
479 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double MassMatrixDiagonal1d[] = {};\n\n".format( render_tensor_1(self.
__compute_mass_matrix_diagonal()) )
481 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
483 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
484 if use_multidimensional_arrays:
487 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double StiffnessOperator1d[][] = {};\n\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) )
493 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
495 dictionary[
"BASIS_DECLARATIONS"] +=
"*/\n"
498 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
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) )
504 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double DerivativeOperator1d[] = {};\n\n".format( render_tensor_2(self.
__compute_derivative_operator(),use_multidimensional_arrays) )
506 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
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) )
512 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double RestrictionMatrix1d[] = {};\n\n".format( render_tensor_3(self.
__compute_restriction_matrix(),use_multidimensional_arrays) )
514 dictionary[
"BASIS_DECLARATIONS"] +=
"/**\n"
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) )
520 dictionary[
"BASIS_DECLARATIONS"] +=
"static constexpr double InterpolationMatrix1d[] = {};\n\n".format( render_tensor_3(self.
__compute_interpolation_matrix(),use_multidimensional_arrays) )
526 assert False,
"to be implemented by subclass"
532 assert False,
"to be implemented by subclass"
538 Return the numberth shape function's value at x
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.
556 return enumerator/denominator
562 Return the numberth shape function's derivative at x
566 => d/dx u = sum_j (d/dx phi_j) u_j
568 To construct the gradient field, we make the ansatz:
570 grad u = sum_i g_i phi_i
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
576 = w_i (d/dx phi_j) (x_i) / w_i = (d/dx phi_j) (x_i)
580 where DUDX is the operator computed by this function:
582 DUDX_ij = (d/dx phi_i) (x_j)
584 It can be further written as
586 DUDX_ij = 1/w_i * K^T_ij
588 where the stiffness matrix K is defined as
590 K_ij = <d/dx phi_i, phi_j>_[0,1] = w_j (d/dx phi_j) (x_i)
592 :return: transposed derivative operator
594 :note: If you want to use this operator to compute the gradient of the solution,
595 you need to use the transpose.
606 if m!=number
and m!=l:
609 result += enumerator*denominator
612from scipy.special
import legendre
613from scipy.special
import roots_sh_legendre
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.
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.
630 super(GaussLegendreBasis,self).
__init__(polynomial_order)
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.
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
648 return [ x/2.0+0.5 for x in roots_on_minus_one_one_interval ]
651 anymore. The nicest definition of the shifted Legendre shape functions
652 can, as so often, be found on Wikipedia:
654 https://en.wikipedia.org/wiki/Legendre_polynomials#Shifted_Legendre_polynomials
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.
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
676 return [ x/2.0+0.5 for x in roots_on_minus_one_one_interval ]
679 anymore. The nicest definition of the shifted Legendre shape functions
680 can, as so often, be found on Wikipedia:
682 https://en.wikipedia.org/wiki/Legendre_polynomials#Shifted_Legendre_polynomials
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
701 super(GaussLobattoBasisWithLumpedDiagonalBasis,self).
__init__(polynomial_order)
706 assert False,
"to be implemented by subclass"
707 raise Exception(
"check implementation. See class comment")
709 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_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,...
__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.
str QuadraturePoints1dDocumentation
value1d(self, x, number)
Return the numberth shape function's value at x.
str BasisFunctionValuesLeft1dDocumentation
__compute_derivative_operator(self)
str RestrictionMatrix1dDocumentation
__compute_barycentric_weights(self)
See Eq.
str InterpolationMatrix1dDocumentation
__compute_mass_matrix_diagonal(self)
Reference mass matrix.
str QuadratureWeights1dDocumentation
derivative1d(self, x, number)
Return the numberth shape function's derivative at x.
__compute_fine_grid_projector(self, j)
__compute_stiffness_operator(self)
Computes the (reference) element stiffness matrix for an approximation of order self....
__init__(self, polynomial_order)
init_dictionary_with_default_parameters(self, dictionary, use_multidimensional_arrays)
To be implemented by subclass.
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_basis_function_values_left(self)
Compute per basis function what the very left value of the polynomial would be.
__compute_equidistant_grid_projector(self)
str StiffnessOperator1dDocumentation
str DerivativeOperator1dDocumentation
__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.