Peano
Loading...
Searching...
No Matches
DiscontinuousGalerkinDiscretisation.py
Go to the documentation of this file.
1# This file is part of the Peano multigrid project. For conditions of
2# distribution and use, please see the copyright notice at www.peano-framework.org
3import peano4.api
4import jinja2
6import dastgen2
7
8import os
9
10from .Solver import Solver
11from multigrid.petsc.api.actionsets.EnumerateDoFs import EnumerateDoFs
12from multigrid.petsc.api.actionsets.InitCellDoFs import InitCellDoFs
13from multigrid.petsc.api.actionsets.ProjectPETScSolutionBackOntoMesh import (
14 ProjectPETScSolutionOnCellsBackOntoMesh,
15)
16from multigrid.petsc.api.actionsets.PlotDGDataInPeanoBlockFormat import (
17 PlotDGDataInPeanoBlockFormat,
18)
19from multigrid.petsc.api.actionsets.PlotExactSolution import PlotExactSolution
20from multigrid.petsc.api.actionsets.ImposeDirichletBoundaryConditionsWithInteriorPenaltyMethod import (
21 ImposeDirichletBoundaryConditionsWithInteriorPenaltyMethod,
22)
23
24from abc import abstractmethod
25
26from peano4.api.solversteps.ActionSet import ActionSet
27
28
30 """!
31
32 Trigger assembly of PETSc matrix and also assemble rhs vector
33
34 """
35
36 TemplateTouchCellFirstTime = """
37 if (fineGridCell{{SOLVER_NAME}}PETScData.getType() == celldata::{{SOLVER_NAME}}PETScData::Type::Interior) {
38 std::pair<int,int> localCellIndex = std::make_pair(_spacetreeId, fineGridCell{{SOLVER_NAME}}PETScData.getUnknownBaseNumber());
39 int globalCellIndex = repositories::{{SOLVER_INSTANCE}}.getLocalToGlobalMap().getGlobalIndex(localCellIndex, ::petsc::LocalToGlobalMap::Type::Cell);
40
41 // ================================
42 // Left-hand side A_cc contribution
43 // ================================
44 auto lhsCellMatrix = repositories::{{SOLVER_INSTANCE}}.getLhsMatrix(marker.x(),marker.h());
45
46 logTraceInWith3Arguments( "touchCellFirstTime()", globalCellIndex, lhsCellMatrix.toString(), marker.toString() );
47
48 for (int row=0; row<repositories::{{SOLVER_INSTANCE}}.DoFsPerCell; row++)
49 for (int col=0; col<repositories::{{SOLVER_INSTANCE}}.DoFsPerCell; col++) {
50 repositories::{{SOLVER_INSTANCE}}.getLinearEquationSystem().insert(
51 globalCellIndex+row,
52 globalCellIndex+col,
53 lhsCellMatrix(row,col)
54 );
55 }
56 logTraceOut( "touchCellFirstTime()" );
57
58 // ================================
59 // Right-hand side vector contribution (multiply rhs (mass) matrix with rhs initial values
60 // ================================
61 auto rhsCellMatrix = repositories::{{SOLVER_INSTANCE}}.getRhsMatrix(marker.x(),marker.h());
62
63 for (int row=0; row<repositories::{{SOLVER_INSTANCE}}.DoFsPerCell; row++) {
64 double rhs = 0.0;
65 for (int col=0; col<repositories::{{SOLVER_INSTANCE}}.DoFsPerCell; col++) {
66 rhs += rhsCellMatrix(row,col) * fineGridCell{{SOLVER_NAME}}.getRhs(col);
67 }
68 repositories::{{SOLVER_INSTANCE}}.getLinearEquationSystem().insert(
69 globalCellIndex+row, rhs
70 );
71 }
72
73 auto projectionCellToFaceMatrix = repositories::{{SOLVER_INSTANCE}}.getProjectionOfCellDataOntoFace(marker.x(),marker.h());
74 auto projectionFaceToCellMatrix = repositories::{{SOLVER_INSTANCE}}.getProjectionOfRiemannSolutionOntoCell(marker.x(),marker.h());
75 int dofsPerFace = repositories::{{SOLVER_INSTANCE}}.NodesPerFace*repositories::{{SOLVER_INSTANCE}}.FaceUnknowns;
76
77 for (int d=0; d<Dimensions; d++) {
78 // =================================
79 // Project to left face along axis d
80 // =================================
81 if (
82 fineGridFaces{{SOLVER_NAME}}PETScData(d).getType()==facedata::{{SOLVER_NAME}}PETScData::Type::Interior
83 or
84 fineGridFaces{{SOLVER_NAME}}PETScData(d).getType()==facedata::{{SOLVER_NAME}}PETScData::Type::Boundary
85 ) {
86 std::pair<int,int> localFirstDoFIndexOfLeftFace = std::make_pair(_spacetreeId, fineGridFaces{{SOLVER_NAME}}PETScData(d).getUnknownBaseNumber());
87 int globalFirstDoFIndexOfLeftFace = repositories::{{SOLVER_INSTANCE}}.getLocalToGlobalMap().getGlobalIndex(localFirstDoFIndexOfLeftFace, ::petsc::LocalToGlobalMap::Type::Face);
88
89 assertion1(globalFirstDoFIndexOfLeftFace>=0, marker.toString());
90 logTraceInWith5Arguments( "touchCellFirstTime::LeftProjection", globalFirstDoFIndexOfLeftFace, globalCellIndex , projectionCellToFaceMatrix, projectionFaceToCellMatrix, marker.toString() );
91
92 for (int row=0; row<dofsPerFace; row++ ) {
93 for (int col=0; col<repositories::{{SOLVER_INSTANCE}}.DoFsPerCell; col++) {
94 logTraceIn("CellToFace");
95 repositories::{{SOLVER_INSTANCE}}.getLinearEquationSystem().insert(
96 globalFirstDoFIndexOfLeftFace + row + dofsPerFace,
97 globalCellIndex+col,
98 projectionCellToFaceMatrix(d * dofsPerFace + row,col)
99 );
100 logTraceOut("CellToFace");
101
102 logTraceInWith1Argument("FaceToCellInsertion", projectionFaceToCellMatrix(col, d * dofsPerFace + row));
103 repositories::{{SOLVER_INSTANCE}}.getLinearEquationSystem().insert(
104 globalCellIndex+col,
105 globalFirstDoFIndexOfLeftFace + row + 2 * dofsPerFace,
106 projectionFaceToCellMatrix(col, d * dofsPerFace + row)
107 );
108 logTraceOut("FaceToCell");
109 }
110 }
111 logTraceOut( "touchCellFirstTime::LeftProjection" );
112 }
113
114 if (
115 fineGridFaces{{SOLVER_NAME}}PETScData(d+Dimensions).getType()==facedata::{{SOLVER_NAME}}PETScData::Type::Interior
116 or
117 fineGridFaces{{SOLVER_NAME}}PETScData(d+Dimensions).getType()==facedata::{{SOLVER_NAME}}PETScData::Type::Boundary
118 ) {
119 std::pair<int,int> localFirstDoFIndexOfRightFace = std::make_pair(_spacetreeId, fineGridFaces{{SOLVER_NAME}}PETScData(d+Dimensions).getUnknownBaseNumber());
120 int globalFirstDoFIndexOfRightFace = repositories::{{SOLVER_INSTANCE}}.getLocalToGlobalMap().getGlobalIndex(localFirstDoFIndexOfRightFace, ::petsc::LocalToGlobalMap::Type::Face);
121
122 assertion1(globalFirstDoFIndexOfRightFace>=0, marker.toString());
123
124 logTraceInWith2Arguments( "touchCellFirstTime::RightProjection", globalFirstDoFIndexOfRightFace, marker.toString() );
125
126 for (int row=0; row<dofsPerFace; row++ ) {
127 for (int col=0; col<repositories::{{SOLVER_INSTANCE}}.DoFsPerCell; col++) {
128 repositories::{{SOLVER_INSTANCE}}.getLinearEquationSystem().insert(
129 globalFirstDoFIndexOfRightFace + row,
130 globalCellIndex+col,
131 projectionCellToFaceMatrix(d * dofsPerFace + row + Dimensions*dofsPerFace,col)
132 );
133 repositories::{{SOLVER_INSTANCE}}.getLinearEquationSystem().insert(
134 globalCellIndex+col,
135 globalFirstDoFIndexOfRightFace + row + 2 * dofsPerFace,
136 projectionFaceToCellMatrix(col, d * dofsPerFace + row + Dimensions*dofsPerFace)
137 );
138 }
139 }
140 } // end of if interior face
141
142 logTraceOut( "touchCellFirstTime::RightProjection" );
143 }
144
145 }
146 """
147
148 TemplateTouchFaceFirstTime = """
149 auto RiemannSolverMatrix = repositories::{{SOLVER_INSTANCE}}.getRiemannSolver(marker.x(),marker.h());
150 int dofsPerFace = repositories::{{SOLVER_INSTANCE}}.NodesPerFace*repositories::{{SOLVER_INSTANCE}}.FaceUnknowns;
151 if ( fineGridFace{{SOLVER_NAME}}PETScData.getType() == facedata::{{SOLVER_NAME}}PETScData::Type::Interior ) {
152 std::pair<int,int> localFirstDoFIndex = std::make_pair(_spacetreeId, fineGridFace{{SOLVER_NAME}}PETScData.getUnknownBaseNumber());
153 int globalFirstDoFIndex = repositories::{{SOLVER_INSTANCE}}.getLocalToGlobalMap().getGlobalIndex(localFirstDoFIndex, ::petsc::LocalToGlobalMap::Type::Face);
154
155 assertion( globalFirstDoFIndex>=0 );
156
157 logTraceInWith2Arguments("touchFaceFirstTime", globalFirstDoFIndex, RiemannSolverMatrix);
158 for (int row=0; row<dofsPerFace; row++)
159
160 // we have col<2*dofsPerFace here since we have left and right projections here.
161 // ie, we have a certain number of "true" dofsPerFace and then twice as many
162 // helper dofs to hold the projections.
163 // In this loop, we are setting the values of the true face Dofs, using the
164 // left and right projections. This is why we get the global index of the
165 // face and offset by 2*dofsPerFace: this will index the true dofs,
166 // which are enumerated AFTER the projections.
167 for (int col=0; col<2*dofsPerFace; col++) {
168 repositories::{{SOLVER_INSTANCE}}.getLinearEquationSystem().insert(
169 globalFirstDoFIndex + 2*dofsPerFace + row,
170 globalFirstDoFIndex + col,
171 RiemannSolverMatrix(row, col)
172 );
173 }
174
175 // 3*dofsPerFace so that each of the -, + and solution variables receive an identity here.
176 for (int i=0; i<3*dofsPerFace; i++)
177 {
178 repositories::{{SOLVER_INSTANCE}}.getLinearEquationSystem().insert(
179 globalFirstDoFIndex + i,
180 globalFirstDoFIndex + i,
181 -1
182 );
183 }
184
185 logTraceOut("touchFaceFirstTime");
186
187 }
188 else if ( fineGridFace{{SOLVER_NAME}}PETScData.getType() == facedata::{{SOLVER_NAME}}PETScData::Type::Boundary ){
189 // add some more identities!
190 }
191 """
192
194 self,
195 solver,
196 ):
197 """!
198
199 Yet to be written
200
201 """
202
203 super(AssemblePetscMatrix, self).__init__()
204 self.d = {}
205 self.d["SOLVER_INSTANCE"] = solver.instance_name()
206 self.d["SOLVER_NAME"] = solver.typename()
207
208 def get_body_of_operation(self, operation_name):
209 """!
210
211 Provide C++ code snippet for peano4.api.solversteps.ActionSet.OPERATION_TOUCH_VERTEX_FIRST_TIME
212 Provide C++ code snippet for peano4.api.solversteps.ActionSet.OPERATION_TOUCH_CELL_FIRST_TIME
213
214 Only touchVertexFirstTime is an event where this action set actually
215 does something: It inserts the template TemplateTouchVertexFirstTime and
216 replaces it with entries from the dictionary. The latter is befilled
217 in init().
218
219 We actually do something during touchVertexFirstTime and touchCellFirstTime. We insert the
220 appropriate template into each.
221
222 """
223 result = ""
224 if (
225 operation_name
226 == peano4.api.solversteps.ActionSet.OPERATION_TOUCH_CELL_FIRST_TIME
227 ):
228 result = jinja2.Template(self.TemplateTouchCellFirstTime).render(**self.d)
229 pass
230 if (
231 operation_name
232 == peano4.api.solversteps.ActionSet.OPERATION_TOUCH_FACE_FIRST_TIME
233 ):
234 result = jinja2.Template(self.TemplateTouchFaceFirstTime).render(**self.d)
235 pass
236 return result
237
239 """!
240
241 Configure name of generated C++ action set
242
243 This action set will end up in the directory observers with a name that
244 reflects how the observer (initialisation) is mapped onto this action
245 set. The name pattern is ObserverName2ActionSetIdentifier where this
246 routine co-determines the ActionSetIdentifier. We make is reflect the
247 Python class name.
248
249 """
250 return __name__.replace(".py", "").replace(".", "_")
251
253 """!
254
255 The action set that Peano will generate that corresponds to this class
256 should not be modified by users and can safely be overwritten every time
257 we run the Python toolkit.
258
259 """
260 return False
261
262 def get_includes(self):
263 """!
264
265 Consult petsc.Project for details
266 """
267 return """
268#include "repositories/SolverRepository.h"
269#include "tarch/la/Matrix.h"
270"""
271
272 def get_attributes(self):
273 """!"""
274 return f"""
275 int _spacetreeId;
276"""
277
279 """!
280
281 Define body of constructor
282
283 @see get_attributes()
284
285 """
286 return f"""
287 _spacetreeId = treeNumber;
288"""
289
290
292 r"""!
293
294 Discontinuous Galerkin Solver with PETSc
295
296 ## The weak formulation
297
298 To translate a PDE into an equation system, we express the solution
299
300 @f$ u = \sum _i u_i \phi _i @f$
301
302 as sum over shape functions @f$ \phi _i @f$.
303 The shape functions in this class are locally continuous, but their linear
304 combination is not. We test this setup against test functions @f$ v @f$.
305 Each test function is actually any of the shape functions @f$ \phi _i @f$.
306 Drawing both shapes and test functions from the same function space means that
307 we follow a ***Ritz-Galerkin*** approach.
308
309
310 Let @f$ \mathcal{L}(u) @f$ be the differential operator on the left-hand side
311 of the PDE, i.e. let's assume that we solve
312
313 @f$
314 \mathcal{L}(u) = f.
315 @f$
316
317 A weak formulation means that we multiple both sides with a test function and
318 then integrate over the whole thing. As we take all these tests from the same
319 function space as the shape functions, we know that they all have local
320 support.Consequently, our test integral "shrinks" down to the support of the
321 test function
322
323 @f$
324 \int _\Omega \mathcal{L}(u) \cdot v dx = \int_{\text{cell}_v} \mathcal{L}(u) \cdot v dx
325 @f$
326
327 as the product disappears everywhere outside of the cell that hosts the test
328 function of interest.
329
330
331 Once again, we can integrate by parts to get rid of the highest derivative in
332 our partial differential operator @f$ \mathcal{L}(u) @f$, but this time, we will
333 get an expression of the form
334
335
336\f{eqnarray*}{
337 \int _\Omega \mathcal{L}(u) \cdot v dx
338 &=&
339 - \int _\Omega \left( \tilde{\mathcal{L}}(u), \nabla v \right) dx
340 + \int _{\partial \text{cell}_v} (n,\tilde{\mathcal{M}}(u)) v dS(x)
341 \\
342 &=&
343 - \int _{\text{cell}_v} \left( \tilde{\mathcal{L}}(u), \nabla v \right) dx
344 + \int _{\partial \text{cell}_v} (n,\tilde{\mathcal{M}}(\hat u )) v dS(x)
345 \\
346 & = & \int _{\text{cell}_v} f \cdot v dx.
347\f}
348
349 We reduced the order of the deriative within @f$ \mathcal{L}(u) @f$ and got a
350 new operator @f$ \tilde{\mathcal{L}} @f$.
351 If @f$ \mathcal{L}(u) @f$ is dominated by a second derivative, @f$ \tilde{\mathcal{L}} @f$
352 hosts only first-order derivatives.
353 But this is a Pyrrhic victory:
354 We now get an additional PDE term over the cell faces.
355
356
357 We conclude that
358
359 1. the jump terms along the faces do not disappear, i.e. they do not cancel out
360 from left and right as they do in a Continuous Galerkin formulation.
361 2. Even worse, they refer to a value @f$ u @f$ which technically does not
362 exist, as there is no (unique) solution along the face. We only have a
363 left @f$ u^- @f$ and a right @f$ u^+ @f$ solution. Therefore, I explicitly
364 write @f$ \hat u @f$ in the equation above. It denotes that we haven't yet
365 decided what the function looks like along the face.
366 3. We now have cell integrals to evaluate, and there are additional face
367 integrals. The latter ones couple neighbouring cells.
368
369 We end up with a linear equation system
370
371 @f$ A x = b @f$
372
373 where x has @f$ |\mathbb{C}| \cdot K \cdot (p+1)^d @f$ entries. @f$ \mathbb{C} @f$
374 is set of cells in the mesh.
375
376
377 ## An operator language and temporary variables
378
379 Before we dive into examples or discuss how to map this formalism onto code, we
380 introduce an operator language. That is, we break down the overall computation
381 into small tasks. These tasks will be found 1:1 in the code later on. Furthermore,
382 we gain some freedom by breaking things down into tiny bits and pieces: We can
383 alter individual ones or puzzle them together in various ways.
384
385 We introduce a few operators (functions) to break down the overall computational
386 scheme into a sequence/set of computations:
387
388 - The operator @f$ A_{cc} @f$ takes the dofs within a cell and computes the
389 term @f$ - \int _{\text{cell}_v} \left( \tilde{\mathcal{L}}(u), \nabla v \right) dx @f$.
390 It takes the @f$ K (p+1)^d @f$ values per cell and yields @f$ K (p+1)^d @f$
391 equations/updates from the cell. In classes like petsc.solvers.DiscontinuousGalerkinDiscretisation
392 this matrix is called cell_cell_lhs_matrix.
393 - Let's introduce temporary variables on the face. Those are the
394 @f$ u^+ @f$ and @f$ u^- @f$ values, i.e. we store @f$ 2K (p+1)^{d-1} @f$
395 degrees of freedom on the face. Those are not really degrees of freedom.
396 They will be mere projection, i.e. simply encode what the solution along
397 the face is.
398 - The operator @f$ A_{cf} @f$ takes the solution from a cell and projects it
399 onto a face. Let @f$ u^+ @f$ and @f$ u^- @f$ denote the solution left or
400 right of a face (from the face's point of view). For each of the @f$ 2d @f$
401 faces of a cell, the operator @f$ P_{cf} @f$ yields either the
402 @f$ u^+ @f$ or @f$ u^- @f$.
403 - The operator @f$ A_{ff} @f$ operates on a face. It takes
404 @f$ u^+ @f$ and @f$ u^- @f$ and yields something
405 that "reconstructs" the solution and applies the flux operator on it.
406 This is the Riemann solver and hence linear algebraises the
407 @f$ \tilde{\mathcal{M}}(\hat u ) @f$ from above.
408 - The operator @f$ A_{fc} @f$ takes the @f$ \hat u @f$ on the face and yields
409 the updates for the cell. It evaluates @f$ \int _{\partial \text{cell}_v} (n,\tilde{\mathcal{M}}(\hat u )) v dS(x) @f$.
410
411
412 <div style="background-color: #cfc ; padding: 10px; border: 1px solid green;">
413 Within Peano, it is important to distinguish data on cell and faces carefully
414 from each other. Furthermore
415 (i) we can read data associated with faces within cells;
416 (ii) we can never read data from neighbouring cells within a cell;
417 (iii) we can never access adjacent cell data from a face.
418 </div>
419
420
421
422 The overall scheme in operator notation thus reads
423
424 @f$
425 A_{cc} + A_{fc} \circ A_{ff} \circ A_{cf}
426 @f$
427
428
429 i.e. we evaluate the cell contributions and add the face contributions
430 which in turn results from a concatenation of three operators:
431 Project solution onto face, solve a Riemann problem there, and then
432 take the result and project it back onto the cell. The projection onto
433 the face is a purely geoemtric thing: Once we know the polynomials
434 chosen within the cell, we can evaluate what the solution looks like
435 depending on the weights (unknowns within the degree of freedom).
436 With that knowledge, we can determine what the solution on the face
437 looks like. We use the same polynomial space there (restricted to the
438 submanifold) and therefore can express the face representation through
439 polynomial weights (left and right of the face), too. For each pair of
440 the polynomial weights, we can solve the Riemann problem and store the
441 outcome in further weights. That is, we represent the outcome of the
442 Riemann problem again in the polynomial space on the faces. Finally,
443 we take this representation, test it against the test functions,
444 integrate over the face and write the contributions back into the cell.
445
446
447
448
449 Effectively, our approach blows up the equation system to solve into
450
451 @f$
452 Ax =
453 \begin{bmatrix}
454 A_{cc} & A_{fc} & 0 & 0 \\
455 0 & id & -A_{ff}|_+ & -A_{ff}|_- \\
456 -id & 0 & A_{cf}|_+ & 0 \\
457 -id & 0 & 0 & A_{cf}|_-
458 \end{bmatrix}
459 \begin{bmatrix}
460 x_{c} \\ x_f \\ x^+ \\ x^-
461 \end{bmatrix}
462 =
463 \begin{bmatrix}
464 b_{c} \\ 0 \\ 0 \\ 0
465 \end{bmatrix}
466 @f$
467
468
469 @todo We need a nice illustration here of all the function spaces
470
471 where we have to specify the involved matrices further.
472 The @f$ x_c @f$ are the real degrees of freedom, i.e. the weights within
473 the cells which effectively span the solution.
474 The @f$ x_\pm @f$ encode the projection of these polynomials onto the faces.
475 These are not real degrees of freedom, as they always result directly from
476 the @f$ x_c @f$.
477 The @f$ x_f @f$ encode the reconstructed solution on the faces and hold the
478 average of the @f$ x_\pm @f$ values.
479 This setup means our total matrix is no longer square.
480 @f$ A_{cc} @f$ is square, with @f$ |\mathbb{C}| \cdot (p+1)^d @f$ rows,
481 where @f$ |\mathbb{C}| @f$ is the number of cells in the mesh.
482 @f$ A_{fc} @f$ has the same number of rows, but @f$ |\mathbb{F}| \cdot (p+1)^{d-1} @f$
483 columns, since this part has to "hit" @f$ x_f @f$.
484 @f$ A_{ff} @f$ has @f$ |\mathbb{F}| \cdot (p+1)^{d-1} @f$ rows, and twice as many
485 columns (note that @f$ | x_\pm| \ = \ 2 |x_f| @f$ ).
486 Finally, @f$ A_{cf} @f$ has @f$ |\mathbb{C}| \cdot (p+1)^d @f$ rows and
487 @f$ 2|\mathbb{F}| \cdot (p+1)^{d-1} @f$ columns, since this part "hits"
488 @f$ x_\pm @f$.
489
490 Our global matrix is no longer square. We have
491 @f$ 2 |\mathbb{C}| \cdot (p+1)^d + |\mathbb{F}| \cdot (p+1)^{d-1} @f$ rows
492 and
493 @f$ |\mathbb{C}| \cdot (p+1)^d + 3|\mathbb{F}| \cdot (p+1)^{d-1} @f$ columns.
494
495
496
497
498 <div style="background-color: #cfc ; padding: 10px; border: 1px solid green;">
499 The write-down as one big matrix as above works if and only if the
500 chosen Riemann solver yield a linear combination of the @f$ u^-/u^+ @f$
501 or @f$ x^-/x^+ @f$ values respectively. In many sophisticated solvers,
502 it will not be linear. In this case, the system becomes a non-linear
503 equation system.
504 </div>
505
506 ## Data storage
507
508 The data storage within a cell is not prescribed by Peano, i.e. you can,
509 in principle decide if you want to reorder the @ref page_multigrid_terminology "nodes within the cell"
510 or you can decide to order the degrees of freedom overall as SoA as opposed
511 to AoS. At the end of the day, Peano maintains an array of doubles per
512 cell and does not really care about the semantics of those. However, we
513 strongly recommend that you stick to the default configuration where
514 possible:
515
516 @image html degree_of_freedom_semantics.png
517
518 - Nodes are enumerated following our @ref page_multigrid_terminology "dimension-generic conventions".
519 - Data are stored as an Array of Structs (AoS), i.e. first all the unknowns
520 of the first node, then all of the second, and so forth.
521
522 If you change any ordering, you have to take this into account for all
523 associated operators (such as projections onto the faces), but you also
524 will have to amend the plotters which are tied to these conventions.
525
526 Data on the faces is
527
528 - stored as AoS by default;
529 - per face, we first store the left projection @f$ x^- @f$, then the right projection @f$ x^+ @f$
530 and then the outcome of the flux or associated reconstruction. That is, all the @f$ x^- @f$
531 are held en bloc first before we hold @f$ x^+ @f$.
532 - Per @f$ x^\pm @f$, the storage of the nodes and unknowns
533 follows again the @ref page_multigrid_terminology "dimension-generic conventions".
534
535 It is important to recognise that we store three types of quantities
536 per unknown: Its left projection, its right projection, and its
537 outcome which can be either the flux or a reconstructed
538 income. The semantics of the outcome depend upon your operators, i.e.
539 they are not baked into the formalism. Furthermore, there is no
540 assumption about the spacing along the faces. Naturally, you might
541 assume that a Gauss-Lobatto layout within the cell implies a
542 Gauss-Lobatto distribution on the faces. However, no such thing
543 is encoded within the code, as we solely work with operators. They
544 have to encode the correct spacing.
545
546 Another assumptions is baked into the realisation as a default:
547 The polynomial degree on the facet result @f$ x_f @f$ equals the
548 polynomial degree within the cell.
549
550 @todo Different polynomial degrees are simple to realise. This is
551 something to do in the future. Note that +/- are to be
552 taken from the same space as the cell, as they are projections,
553 but maybe we want to alter this, and even provide completely
554 different degrees for the result of the Riemann solve.
555
556
557
558 ## Examples
559
560 Multiple examples are shipped with Peano which demonstrate how to use the
561 plain Discontiuous Galerking solver:
562
563 - benchmarks/multigrid/petsc/poisson/dg.py Hosts a simple solver for the
564 Poisson equation. All the rationale are discussed in @ref benchmarks_documentation_multigrid_petsc_poisson.
565
566
567
568 """
569
571 self,
572 name,
573 polynomial_degree,
574 dimensions,
575 cell_unknowns,
576 face_unknowns,
577 min_h,
578 max_h,
579 cell_cell_lhs_matrix,
580 cell_cell_rhs_matrix,
581 cell_cell_lhs_matrix_scaling,
582 cell_cell_rhs_matrix_scaling,
583 cell_to_face_matrix,
584 face_to_cell_matrix,
585 cell_to_face_matrix_scaling,
586 face_to_cell_matrix_scaling,
587 face_face_Riemann_problem_matrix,
588 quadrature_points_in_unit_interval,
589 gaussian_integration_points,
590 gaussian_integration_weights,
591 ):
592 r"""!
593
594 Constructor of DG class
595
596
597 ## Cell to face projection
598
599 Consult @ref page_multigrid_terminology "Peano's enumeration for multigrid" page
600 for info on the ordering. For 2d with a layout as below
601
602 @image documentation/Doxygen/Multigrid/face-ordering.png
603
604 the matrix is applied to
605
606 @f$
607 P_{cf}
608 \begin{bmatrix}
609 x_0^+ \\ x_1^+ \\ x_2^- \\ x_3^-
610 \end{bmatrix}
611 =
612 x_{c}
613 @f$
614
615
616 ## Attributes
617
618
619 @param cell_cell_lhs_matrix: [Float] or []
620 Pass in [] if you prefer to assemble the matrix per cell yourself. This
621 is the @f$ \tilde{\mathcal{L}} @f$ part above. The ordering of the
622 degrees of freedom follows the discussion above. The matrix is from
623 @f$ K(p+1) \times K(p+1) @f$.
624
625 @param cell_cell_rhs_matrix: [Float] or []
626 Pass in [] if you prefer to assemble the matrix per cell yourself. This
627 matrix arises from @f$ \int f \cdot v \ dx @f$ and is applied to the nodal
628 values of the PDE within the quadrature nodes of a cell. The matrix is
629 from @f$ K(p+1) \times K(p+1) @f$.
630
631 @param cell_cell_lhs_matrix_scaling: Positive integer
632 The lhs matrix is scaled with @f$ h^x @f$ where the x is defined by this
633 parameter. If you don't specify an lhs matrix, i.e. you prefer to inject
634 this matrix manually, you can leave this parameter None, as it is not
635 used.
636
637 @param cell_cell_rhs_matrix_scaling: Positive integer
638 The rhs matrix is scaled with @f$ h^x @f$ where the x is defined by this
639 parameter. If you don't specify a rhs matrix, i.e. you prefer to inject
640 this matrix manually, you can leave this parameter None, as it is not
641 used.
642
643 @param cell_to_face_matrix: [Float]
644 If you study the global equation system above, you recognise that
645 this matrix is applied to the left or right solutions of a cell's
646 faces.
647
648 @param face_face_Riemann_problem_matrix: [Float] or []
649 Pass in [] if you prefer to assemble the matrix per cell yourself. This
650 matrix represents the actual Riemann solver. It accepts the left projection
651 @f$ u^- @f$ and @f$ u^+ @f$ and yields a "reconstructed" value at this
652 point passed into the differential operator. Therefore, this is a band
653 matrix.
654
655
656 """
657 super(DiscontinuousGalerkinDiscretisation, self).__init__(name, min_h, max_h)
658
659 self.polynomial_degree = polynomial_degree
660 self.dimensions = dimensions
661 self.cell_unknowns = cell_unknowns
662 self.face_unknowns = face_unknowns
663
664 self.cell_cell_lhs_matrix = cell_cell_lhs_matrix
665 self.cell_cell_rhs_matrix = cell_cell_rhs_matrix
666 self.cell_to_face_matrix = cell_to_face_matrix
667 self.face_to_cell_matrix = face_to_cell_matrix
668 self.face_face_Riemann_problem_matrix = face_face_Riemann_problem_matrix
669
670 self.cell_cell_lhs_matrix_scaling = cell_cell_lhs_matrix_scaling
671 self.cell_cell_rhs_matrix_scaling = cell_cell_rhs_matrix_scaling
672 self.cell_to_face_matrix_scaling = cell_to_face_matrix_scaling
673 self.face_to_cell_matrix_scaling = face_to_cell_matrix_scaling
674
675 self.quadrature_points_in_unit_interval = quadrature_points_in_unit_interval
676 self.gaussian_integration_points = gaussian_integration_points
677 self.gaussian_integration_weights = gaussian_integration_weights
678
679 assert len(quadrature_points_in_unit_interval) == len(
680 gaussian_integration_points,
681 "Something has gone wrong. These should all be same length for fixed polynomial degree",
682 )
683 assert len(quadrature_points_in_unit_interval) == len(
684 gaussian_integration_weights,
685 "Something has gone wrong. These should all be same length for fixed polynomial degree",
686 )
687
689 self._cell_pde_data.data.add_attribute(
692 )
693 )
694 self._cell_pde_data.data.add_attribute(
697 )
698 )
699 self._cell_pde_data.data.add_attribute(
702 )
703 )
704
705 pass
706
707 @property
708 def nodes_per_cell(self):
709 return (self.polynomial_degree + 1) ** self.dimensions
710
711 @property
712 def nodes_per_face(self):
713 return (self.polynomial_degree + 1) ** (self.dimensions - 1)
714
715 def add_to_Peano4_datamodel(self, datamodel, verbose):
716 """!
717
718 Initialise index model
719
720 There is an index data model, where all the vertices, faces and cells
721 are properly enumerated. This one is taken care of by the superclass.
722 The thing we have to add our our PDE-specific unknowns.
723
724 """
725 super(DiscontinuousGalerkinDiscretisation, self).add_to_Peano4_datamodel(
726 datamodel, verbose
727 )
728 datamodel.add_cell(self._cell_pde_data)
729
730 def add_use_statements(self, observer):
731 super(DiscontinuousGalerkinDiscretisation, self).add_use_statements(observer)
732 observer.use_cell(self._cell_pde_data)
733 pass
734
735 def add_to_plot(self, observer):
736 """!
737
738 Tell the project's observer how to plot the data
739
740 Nothing fancy here. We add plotters from Peano's toolbox to visualise
741 solution and right-hand side.
742
743 """
744 observer.add_action_set(PlotDGDataInPeanoBlockFormat(self))
745 pass
746
747 def add_to_create_grid(self, observer):
749 pass
750
752 """!
753
754 Solution initialisation
755
756 Close to trivial: Just add the action set petsc.actionsets.InitVertexDoFs
757 to the observer.
758
759 """
760 observer.add_action_set(
761 EnumerateDoFs(
762 self,
763 boundary_vertices_hold_data=False,
764 boundary_faces_hold_data=True,
765 )
766 )
767
768 observer.add_action_set(InitCellDoFs(self))
769 pass
770
771 def add_to_assemble(self, observer):
772 observer.add_action_set(AssemblePetscMatrix(self))
773 observer.add_action_set(
774 ImposeDirichletBoundaryConditionsWithInteriorPenaltyMethod(self)
775 )
776 pass
777
778 def add_to_init_petsc(self, observer):
779 pass
780
781 def add_to_map_solution_onto_mesh(self, observer):
782 observer.add_action_set(ProjectPETScSolutionOnCellsBackOntoMesh(self))
783 observer.add_action_set(PlotExactSolution(self))
784 pass
785
786 def add_implementation_files_to_project(self, namespace, output, subdirectory=""):
787 """
788
789 The solver creates two classes: An abstract base class and its
790 implementations. The abstract base class will be overwritten if
791 there is one in the directory. I pipe all the Python constants
792 into this base class, so they are available in the C++ code.
793
794 The implementation file will not be overwritten, as I assume that
795 the users will write their own domain-specific stuff in there. If
796 it does not exist yet, I'll create an empty stub which can be
797 befilled with something meaningful.
798
799 Besides the creation of these two files, I also add the files to the
800 project artifacts and the makefile, so they are captured by the build
801 system.
802
803 """
804 templatefile_prefix = os.path.dirname(os.path.realpath(__file__)) + "/"
805 templatefile_prefix += self.__class__.__name__
806
807 if subdirectory:
808 subdirectory += "/"
809
810 dictionary = {
811 "SOLVER_INCLUDES": "",
812 "MIN_H": self.min_hmin_h,
813 "MAX_H": self.max_hmax_h,
814 "POLYNOMIAL_DEGREE": self.polynomial_degree,
815 "CELL_UNKNOWNS": self.cell_unknowns,
816 "FACE_UNKNOWNS": self.face_unknowns,
817 "CELL_CELL_LHS_MATRIX": self.cell_cell_lhs_matrix,
818 "CELL_CELL_RHS_MATRIX": self.cell_cell_rhs_matrix,
819 "FACE_FACE_RIEMANN_PROBLEM_MATRIX": self.face_face_Riemann_problem_matrix,
820 "CELL_TO_FACE_MATRIX": self.cell_to_face_matrix,
821 "FACE_TO_CELL_MATRIX": self.face_to_cell_matrix,
822 "QUADRATURE_POINTS_IN_UNIT_INTERVAL": self.quadrature_points_in_unit_interval,
823 "GAUSSIAN_INTEGRATION_POINTS": self.gaussian_integration_points,
824 "GAUSSIAN_INTEGRATION_WEIGHTS": self.gaussian_integration_weights,
825 "CELL_CELL_LHS_MATRIX_SCALING": self.cell_cell_lhs_matrix_scaling,
826 "CELL_CELL_RHS_MATRIX_SCALING": self.cell_cell_rhs_matrix_scaling,
827 "CELL_TO_FACE_MATRIX_SCALING": self.cell_to_face_matrix_scaling,
828 "FACE_TO_CELL_MATRIX_SCALING": self.face_to_cell_matrix_scaling,
829 "SOLVER_NAME": self.typename(),
830 }
831
832 generated_abstract_header_file = (
834 templatefile_prefix + ".Abstract.template.h",
835 templatefile_prefix + ".Abstract.template.cpp",
836 "Abstract" + self.typename(),
837 namespace,
838 subdirectory + ".",
839 dictionary,
840 True,
841 True,
842 )
843 )
844 generated_solver_files = (
846 templatefile_prefix + ".template.h",
847 templatefile_prefix + ".template.cpp",
848 self.typename(),
849 namespace,
850 subdirectory + ".",
851 dictionary,
852 False,
853 True,
854 )
855 )
856
857 output.add(generated_abstract_header_file)
858 output.add(generated_solver_files)
859 output.makefile.add_h_file(
860 subdirectory + "Abstract" + self._name + ".h", generated=True
861 )
862 output.makefile.add_h_file(subdirectory + self._name + ".h", generated=True)
863 output.makefile.add_cpp_file(
864 subdirectory + "Abstract" + self._name + ".cpp", generated=True
865 )
866 output.makefile.add_cpp_file(subdirectory + self._name + ".cpp", generated=True)
867
868 @property
870 """!
871
872 Nothing is associated with the vertex. There's nothing to be done here.
873
874 """
875 return 0
876
877 @property
879 """!
880
881 In the DG scheme, we have a projection from the left and we have a
882 projection from the right. These values are proper projections, i.e.
883 they do not carry any semantics. Then we have to solve the Riemann
884 problem, and need one more unknown to store the outcome of that one.
885
886 """
887 return 3 * self.face_unknowns * self.nodes_per_face
888
889 @property
891 """!
892
893 This is the actual unknowns per cell
894
895 """
896 return self.cell_unknowns * self.nodes_per_cell
Trigger assembly of PETSc matrix and also assemble rhs vector.
get_attributes(self)
Return attributes as copied and pasted into the generated class.
get_body_of_operation(self, operation_name)
Provide C++ code snippet for peano4.api.solversteps.ActionSet.OPERATION_TOUCH_VERTEX_FIRST_TIME Provi...
user_should_modify_template(self)
The action set that Peano will generate that corresponds to this class should not be modified by user...
add_implementation_files_to_project(self, namespace, output, subdirectory="")
The solver creates two classes: An abstract base class and its implementations.
add_to_create_grid(self, observer)
Add whatever action set you want to use to observer.
__init__(self, name, polynomial_degree, dimensions, cell_unknowns, face_unknowns, min_h, max_h, cell_cell_lhs_matrix, cell_cell_rhs_matrix, cell_cell_lhs_matrix_scaling, cell_cell_rhs_matrix_scaling, cell_to_face_matrix, face_to_cell_matrix, cell_to_face_matrix_scaling, face_to_cell_matrix_scaling, face_face_Riemann_problem_matrix, quadrature_points_in_unit_interval, gaussian_integration_points, gaussian_integration_weights)
Constructor of DG class.
add_use_statements(self, observer)
Register the index numbers to be used in each and every mesh traversal.
add_to_assemble(self, observer)
Add whatever action set you want to use to observer.
number_of_matrix_entries_per_face(self)
In the DG scheme, we have a projection from the left and we have a projection from the right.
add_to_map_solution_onto_mesh(self, observer)
Add whatever action set you want to use to observer.
Abstract base class for all PETSc solvers.
Definition Solver.py:12
number_of_matrix_entries_per_cell(self)
Definition Solver.py:251
Specialisation of array using Peano's tarch.
Default superclass for any data model in Peano which is stored within the grid.
Definition DaStGen2.py:188
Action set (reactions to events)
Definition ActionSet.py:6