2 This file is part of the MGHyPE project within Peano 4. For conditions of distribution and
3 use, please see the copyright notice at www.peano-framework.org
11from .Solver
import Solver
13from .actionsets.InitHigherOrderDofs
import InitHigherOrderDofs
14from .actionsets.DGSolver.UpdateResidual
import UpdateResidual
15from .actionsets.DGSolver.ProjectOntoFaces
import ProjectOntoFaces
16from .actionsets.DGSolver.UpdateFaceSolution
import UpdateFaceSolution
17from .actionsets.DGSolver.ProjectIntoCellAndUpdateCellSolution
import ProjectIntoCellAndUpdateCellSolution
18from .actionsets.PlotDGDataInPeanoBlockFormat
import PlotDGDataInPeanoBlockFormat
24 Most efficient solver we have for this type of problem class
26 You can find each of the action sets here:
27 solvers.api.actionsets.DGSolver.InitHigherOrderDofs.InitHigherOrderDofs
29 The text below specifies the semantics and behaviour of this matrix-free
30 DG solver. A tutorial running step by step through the steps how to use
31 it can be found @ref tutorials_multigrid_matrixfree_discontinuous_galerkin_poisson "in the tutorials section".
32 The solver in general tackles a PDE
34 @f$ \mathcal{L}(u) = f @f$
36 resulting in a linear equation system
40 Throughout the discussion, we @ref page_multigrid_unfolding "unfold the compute steps over the compute mesh".
44 This solver holds \f$ K^c (p+1)^d \f$ unknowns (doubles) per cell for the
45 solution, \f$ K^c (p+1)^d \f$ per cell for the right-hand side, and the
46 same number for the residual. Holding the residual in the cell allows us
47 to separate some algorithm steps into different action sets. In particular,
48 we project the face solution back into the cells after updating the cell
49 solution (in separate sweeps).
50 These are the primary variables, i.e. those directly resulting from the
51 PDE formulation. The correspond to the unknowns directly from @f$ u @f$
52 and the right-hand side.
54 Further to that, we hold auxiliary variables on the faces:
55 Our Riemann solver accepts the left and the right solution. Here, each
56 degree of freedom has @f$ K^f_{f \gets c} @f$ unknowns. It will return a result
57 (the flux), which has @f$ K^f @f$ unknowns.
58 Per face node, we hence hold, in total, \f$ ( K^f + 2K^f_{f \gets c} ) (p+1)^{d-1} \f$ unknowns.
59 That is the solution for each unknown per face node, as
60 well as a projection from the left and right for each unknown per cell.
61 \f$ K^c \f$ and \f$ K^f \f$ can be different, but often they are the same.
62 The user can choose how many of the \f$ K^c \f$ to project onto the face
63 by modifying \f$ K^f_{f \gets c} \f$.
67 With the auxiliary variables, in place, we can write down the DG
73 A^{cc} & & & P_{c \gets f} \\
74 P_{f \gets c} & id & & \\
75 P_{f \gets c} & & id \\
98 in line with @ref xxxx. The @f$ q @f$ is the result of the Riemann solver.
102 We now realise a Block-Jacobi smoother subject to two relaxation parameters
103 \f$ \omega _c, \omega _f \in [0,1] \f$. We tackle the first line in the
104 block system with one smoothing step of a stationary iterative scheme, solve two and three exact (as they are
105 trivial with the identity on the diagonal) and apply Jacobi to the last
106 row again. As the first and last block are updated in parallel by the
107 Jacobi scheme, the resuling algorithm is globally a Jacobi relaxation, too.
109 - Per cell, we tackle the equation
111 A^{cc} u^c + P_{c \gets f} q = M b
113 which yields the update rule
115 u|_c & \gets & u|_c + \omega _c \tilde {A}^{-1}\left( Mb - A^{cc} u^c - P_{c \gets f} q \right).
117 Here, the \f$ q \f$ is the solution of the PDE term on the faces which we
118 assume to be fixed. At this point, we are quite generic and dump the
119 responsibility onto the user to give us an approximation of @f$ A_{cc} @f$
120 that we can invert. Notably, we highlight that the user may give us
121 @f$ \tilde{A} = A^{cc} @f$ in a DG sense, as we cannot invert @f$ A^{cc} @f$.
122 - Per face, we use a plain Jacobi. We assume that we have an explicit
123 expression of the Riemann solution (simple averaging would be an example)
124 and hence \f$ \hat A = id \f$. We consequently get
126 q|_f & \gets & q|_f + \omega _f \left( \mathcal{R}(u^-, u^+) - q|_f \right) \\
127 & = & (1-\omega _f) q|_f + \omega _f \mathcal{R}(u^-, u^+).
132 ## Algorithmical blueprint
134 1. Enter cell: Whenever a cell is entered, we create a temporary
135 \f$ K^c(p+1)^d \f$ array (on the heap).
137 1. Into this temporary array
138 \f$ r \f$, we write \f$ Mb \f$. This is the mass matrix (right-hand side
139 matrix of the class constructor, as we are not restricted to a
140 traditional mass matrix) times the right-hand sides within the
145 2. Once complete, we substract \f$ P^{c \gets f} q|_f \f$. That is, we take the
146 solution on the face and we project it (cell from face) onto the
147 cell, where we substract it from the previous \f$ r \f$ value. We also
148 do the same with the cell-to-cell couplings. So:
150 r \gets Mb - Pq|_f -A^{cc}u^c.
152 We also do the same with the cell couplings
153 3. The third step is where the MFlops come from: We take the user's
154 approximation of the cell
155 matrix and explicitly invert it. For a linear, invariant matrix
156 \f$ A \f$, we could do so once, but the solver itself makes no
157 assumption if the matrix is invariant or not. So it inverts it
158 every step and uses BLAS routines or this. The result is multiplied
159 with the residual as accumulated so far:
161 r \gets \tilde{A}^{-1} \left( Mb - Pq|_f -A^{cc}u^c \right).
163 4. We run over the solution within the cell and scale each entry
164 with \f$ (1-\omega _c) \f$. If \f$ \omega _c =1 \f$, this means we
165 set the entries to zero. In general, the entries however will
166 only be damped. After that, we add the \f$ r \f$ values to each
167 point. This gives us a new solution guess.
168 5. We project the updated solution \f$ u|_c \f$ onto the faces, i.e.
169 obtain a new \f$ u|_f^{\pm } \f$. This is realised within the
170 action set ProjectOntoFaces.
171 2. touchFaceFirstTime(): Whenever a face is touched, we solve the Riemann
172 problem and update the Riemann guess accordingly. Initially, we assume
173 that the \f$ q \f$ entries on the face are all set to zero.
175 1. We evaluate per node on the face the expression \f$ \mathcal{R}(u^-,u^+) \f$.
176 For this, we loop over the \f$ (p+1)^{d-1} \f$ points and call the
177 C++ solver's routine. The user can thus implement the actual
178 Riemann solver themself.
179 2. We take the result and update the value on the face according to
181 q|_f \gets (1-\omega _f) q|_f + \omega _f \mathcal{R}(u^-, u^+).
186 All of this functionality can be found in the subpackage actionsets.UpdateResidual.
192 There is a @ref tutorials_multigrid_matrixfree_discontinuous_galerkin_poisson "very simple Poisson solver tutorial"
193 in the tutorials section which illustrates how to use this solver.
195 The user needs to supply a few crucial matrices, referred to in the maths above. These are noted and their
196 shapes discussed in the parameters section of the documentation of this class.
200 Inside the cells we store \f$ K^c (p+1)^{d} \f$ values for the solution, and the same number for the rhs
201 and residual. We store the residual in the cell so that we can calculate the update to the cell solution
202 separately from the projection of face solution back into the cells, but crucially are able to update
203 the global residual before we apply the preconditioner.
204 On each face we store \f$ K^f (p+1)^{d-1} \f$ values
205 for the solution, and then \f$ 2K^f_{f \gets c} (p+1)^{d-1} \f$ for the projections in a separate array.
206 We note that this factor of two comes from the fact that we have projections from cells on both the left
207 and right. The user is free to choose how many variables from the cell they want to project onto the face.
208 The value K^f_{f \gets c} is exposed in dg.py as the argument ``` --projections_per_face" ```.
210 ### Matrices we pass in to the solver class
211 See documentation for this class itself...
214 So far we have three non-trivial action sets.
216 ### InitHigherOrderDofs
217 Code for this is in actionsets.InitHigherOrderDofs. So far nothing happens, other than determining which cells/faces are
218 on the boundary and putting in initial guesses for each cell and face dof. We pass over to the implementation
219 cpp file which should be left in situ in the directory for the application.
221 ### UpdateFaceSolution
222 Code for this is in actionsets.DGSolver.UpdateFaceSolution. This action set only contains something nontrivial at
223 touchFaceFirstTime. We apply the Riemann matrix that is present in the Abstract Solver class. This action set goes first,
224 the idea being that we updated the face solution using the updated face projections from the previous sweep.
226 ### ProjectIntoCellAndUpdateCellSolution
227 Code for this is in actionsets.DGSolver.ProjectIntoCellAndUpdateCellSolution. This action set works when we touch the cell for the first time.
228 We project the newly updated face solution back into the cells. In particular we:
229 - Write the updated face solution into the residual stored in the cell. This finishes the cell update from the previous sweep.
230 - Update the global residual
231 - Update the cell solution, which is projected back onto faces in the next action set.
234 Code for this is in actionsets.DGSolver.ProjectOntoFaces. This occurs strictly after ProjectIntoCellAndUpdateCellSolution, so that we project
235 freshly updated Cell values onto the Faces.
238 Code for this is in actionsets.DGSolver.UpdateResidual. We do something nontrivial in touchCellFirstTime, namely perform the cell update
239 rule as above. We set the residual to be \f$ Mb - A^{cc} u^c \f$.
242 @param solver_includes C++ code snippet
243 Arbitrary C++ code snippet. Usually used to include additional files
244 (therefore the name), though you can use it for preprocessing macros
245 or using statements as well. Has to be a valid C++ expression, i.e.
247 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
249 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
256 unknowns_per_cell_node,
257 solutions_per_face_node,
258 projections_per_face_node,
262 assembly_matrix_scaling,
265 face_from_cell_projection,
266 face_from_cell_projection_scaling,
267 cell_from_face_projection,
268 cell_from_face_projection_scaling,
273 approximate_system_matrix,
274 approximate_system_matrix_scaling,
277 custom_init_actionset = None,
278 custom_plot_actionset = None,
279 custom_init_cell = "",
280 enable_plotting = True,
281 plot_predicate = "true",
282 solver_includes = "",
283 use_smart_pointer = True,
284 track_solution_between_cycles = False,
290 Please consult @ref tutorials_peano4_mask_out_data_usage "the tutorial on data masking"
291 for a discussion of the load_store_compute_flag settings.
293 @param poly_degree: Integer
294 The degree of the polynomial that we use. Has to be bigger or equal to one.
296 @param unknowns_per_cell_node: Positive Integer
297 This is the @f$ K^c @f$ from the solver description.
299 @param solutions_per_face_node: Positive Integer
300 This is the @f$ K^f @f$ from the solver description.
302 @param projections_per_face_node: Positive Integer
303 This is the @f$ K^f_{f \gets c} @f$ from the solver description.
305 @param cell_relaxation: Float
306 This is the @f$ \omega _c @f$ from the description above and typically
307 chosen form @f$ ]0,1[ @f$ though some scenarios benefit from a slight
310 @param assembly_matrix: \f$ \mathbb{R}^{K^c * (p+1)^{d} \times K^c * (p+1)^{d}}\f$
311 This is the cell-to-cell matrix which defines how the cell nodes couple
312 to one another. Is is a square matrix. You can pass in None. In this
313 case, the generated C++ code will not compile, but you will be allowed
314 to fill it in manually.
316 @param assembly_matrix_scaling: \f$ \mathbb{R}^{K^c * (p+1)^{d} \times K^c * (p+1)^{d}}\f$
317 This is a matrix of ints that prescribes how we scale each element in the assembly_matrix
318 ie for each i,j, we do: assembly_matrix[i,j] *= ( h )^( assembly_matrix_scaling[i,j] )
321 @param mass_matrix: \f$ \mathbb{R}^{K^c * (p+1)^{d} \times K^c * (p+1)^{d}}\f$
323 @param mass_matrix_scaling: \f$ \mathbb{R}^{K^c * (p+1)^{d} \times K^c * (p+1)^{d}}\f$
324 This is a matrix of ints that prescribes how we scale each element in the mass_matrix
325 ie for each i,j, we do: mass_matrix[i,j] *= ( h )^( mass_matrix_scaling[i,j] )
327 @param face_from_cell: \f$ \mathbb{R}^{2d * 2K^f_{f \gets c} * (p+1)^{d-1} \times K^c * (p+1)^{d}}\f$
328 This matrix projects the cell solution onto all of the faces. Note the
329 extra factor of @f$ 2d @f$. The structure of this matrix is quite important. The rows
330 should be structured in blocks of \f$ 2K^f_{f \gets c} * (p+1)^{d-1} \f$, ie one for each
331 face, in the familiar Peano ordering. We note that this is the same as the number of projections
332 we store per face. We note that, since the 0th face has left-side projections which
333 should be set outside of the cell, that at least half of the rows of the matrix should
335 We also note that each face contains multiple nodes, and they should be ordered sequentially
336 in the typical Peano fashion, i.e. greedily along each coordinate axis until full, starting
337 from the negative. For each face, with two nodes, with two values (q and u) being projected onto the face,
338 the rows should be ordered as follows:
339 1. Negative projection of q onto node 0
340 2. Negative projection of q onto node 1
341 3. Negative projection of u onto node 0
342 4. Negative projection of u onto node 1
343 5. Positive projection of q onto node 0
344 6. Positive projection of q onto node 1
345 7. Positive projection of u onto node 0
346 8. Positive projection of u onto node 1
347 In two dimensions, all of the negative projections should be 0 on faces 0 and 1. Ditto for the
348 positive projections on faces 2 and 3.
350 @param cell_from_face: \f$ \mathbb{R}^{K^c * (p+1)^{d} \times 2d K^f * (p+1)^{d-1}}\f$
351 This matrix projects the face solution onto the cells. Note the
352 extra factor of @f$ 2d @f$, similar to face_from_cell matrix. Some clever indexing
353 is required in the backend here. The enumeration of the cols should follow a similar
354 pattern to the rows of face_from_cell.
356 @param cell_from_face_scaling: \f$ \mathbb{R}^{K^c * (p+1)^{d} \times 2d K^f * (p+1)^{d-1}}\f$
357 This is a matrix of ints that prescribes how we scale each element in the cell_from_face
358 ie for each i,j, we do: cell_from_face[i,j] *= ( h )^( cell_from_face_scaling[i,j] )
360 @param Riemann_matrix: \f$ \mathbb{R}^{ K^f (p+1)^{d-1} \times 2K^f_{f \gets c} (p+1)^{d-1}} @f$
361 The matrix that combines the left/right projections on the cell and outputs a value
362 which determines @f$ q @f$.
364 @param face_relaxation: Float
365 This is the @f$ \omega _f @f$ from above. Chosen from @f$ ]0,1[ @f$.
367 @param approximate_system_matrix: @f$ \mathbb{R}^{K^c (p+1)^d \times K^c (p+1)^d} @f$
368 Approximation @f$ \tilde{A} @f$ which will be internally inverted, so it
369 has to be non-singular.
371 @param approximate_system_matrix_scaling: @f$ \mathbb{R}^{K^c (p+1)^d \times K^c (p+1)^d} @f$
373 @param integration_points: [Float]
374 Sequence of polynomial_degree+1 entries which identify the position of
375 the integration points along the unit interval.
378 super( DiscontinuousGalerkinDiscretisationPointWiseRiemannSolver, self ).
__init__(name, min_h, max_h)
427 if use_smart_pointer:
432 if (track_solution_between_cycles):
443 if (track_solution_between_cycles):
465 self.
_cell_data.peano4_mpi_and_storage_aspect.implementation_file_includes += self._additional_includes()
466 self.
_face_data.peano4_mpi_and_storage_aspect.implementation_file_includes += self._additional_includes()
469 self._face_data.peano4_mpi_and_storage_aspect.merge_implementation = self.get_face_merge_method()
471 self.preprocessing_action_set =
None
472 self.postprocessing_action_set =
None
474 self._basic_descend_order = 0
494 Goard when to hold data in mesh
496 Compare with the discussion in the @ref tutorials_peano4_mask_out_data_usage "Peano tutorial on when and how to load data".
497 We don't need the data throughout the mesh construction, where it only
498 blows up the memory consumption and makes the load balancing more expensive
499 as we exchange data via MPI which, at this point, doesn't hold anything
504 not marker.hasBeenRefined()
506 repositories::{}.getState()!=Abstract{}::State::GridConstruction
507 )""".format( self.instance_name(),
514 Goard when to hold data in mesh
516 Compare with the discussion in the @ref tutorials_peano4_mask_out_data_usage "Peano tutorial on when and how to load data".
517 We don't need the data throughout the mesh construction, where it only
518 blows up the memory consumption and makes the load balancing more expensive
519 as we exchange data via MPI which, at this point, doesn't hold anything
524 not marker.hasBeenRefined()
526 repositories::{}.getState()!=Abstract{}::State::GridConstruction
528 repositories::{}.getState()!=Abstract{}::State::InitSolution
529 )""".format( self.instance_name(),
531 self.instance_name(),
537#include "repositories/SolverRepository.h"
543 super( DiscontinuousGalerkinDiscretisationPointWiseRiemannSolver, self ).
add_to_Peano4_datamodel(datamodel,verbose)
546 super( DiscontinuousGalerkinDiscretisationPointWiseRiemannSolver, self ).
add_use_statements(observer)
551 Tell the project's observer how to plot the data
553 Nothing fancy here. We add plotters from Peano's toolbox to visualise
554 solution and right-hand side.
557 if self._enable_plotting:
561 if self._custom_plot_actionset:
562 for name, getter
in self._custom_plot_actionset:
574 Solution initialisation
576 Is given the project's observer and asks the solver to add all of its
577 action sets to this one.
579 @param observer: Observer
580 Observer object of the underlying project representing its algorithmic
581 step. Routine is asked to add its own action sets to this step.
584 if self._custom_init_actionset
is None:
587 observer.add_action_set( self._custom_init_actionset(self) )
593 I ignore the order of the action sets here and instead derive my
594 own ordering starting from _basic_descend_order.
597 descend_order = observer.highest_descend_invocation_order()
598 if self.preprocessing_action_set !=
None:
599 self.preprocessing_action_set.descend_invocation_order = 0
600 observer.add_action_set( self.preprocessing_action_set )
601 observer.add_action_set( self._action_set_update_face_solution )
602 observer.add_action_set( self._action_set_project_into_cell_and_update_cell_solution )
603 observer.add_action_set( self._action_set_project_onto_faces )
604 observer.add_action_set( self._action_set_update_residual )
605 if self.postprocessing_action_set !=
None:
606 self.postprocessing_action_set.descend_invocation_order = descend_order + 5
607 observer.add_action_set( self.postprocessing_action_set )
613 The solver creates two classes: An abstract base class and its
614 implementations. The abstract base class will be overwritten if
615 there is one in the directory. I pipe all the Python constants
616 into this base class, so they are available in the C++ code.
619 templatefile_prefix = os.path.dirname( os.path.realpath(__file__) ) +
"/templates/"
620 templatefile_prefix += self.__class__.__name__
625 dictionary = self.create_dictionary()
628 templatefile_prefix +
".Abstract.template.h",
629 templatefile_prefix +
".Abstract.template.cpp",
630 "Abstract" + self.typename(),
637 templatefile_prefix +
".template.h",
638 templatefile_prefix +
".template.cpp",
646 output.add( generated_abstract_header_file )
647 output.add( generated_solver_files )
648 output.makefile.add_h_file( subdirectory +
"Abstract" + self._name +
".h", generated=
True )
649 output.makefile.add_h_file( subdirectory + self._name +
".h", generated=
True )
650 output.makefile.add_cpp_file( subdirectory +
"Abstract" + self._name +
".cpp", generated=
True )
651 output.makefile.add_cpp_file( subdirectory + self._name +
".cpp", generated=
True )
656 The actual merge of faces is simple, as we compute the numerical flux
657 redundantly, i.e. we really only have to copy stuff one way or the
661 return jinja2.Template(
"""
662 if (!marker.willBeRefined()
664 context == ::peano4::grid::TraversalObserver::SendReceiveContext::BoundaryExchange
666 ) { //todo change this to use the face's own enum
669 getLProjection() + neighbour.getLProjection()
672 getRProjection() + neighbour.getRProjection()
675 """).render(**self.create_dictionary())
679 "SOLVER_INCLUDES" : self._solver_includes,
682 "SOLVER_NAME": self.typename(),
683 "SOLVER_INSTANCE": self.instance_name(),
684 "CELL_CARDINALITY" : self._unknowns_per_cell,
685 "FACE_CARDINALITY_SOLUTION" : self._unknowns_per_face_solution,
686 "FACE_CARDINALITY_PROJECTION" : self._unknowns_per_face_projection,
687 "VERTEX_CARDINALITY" : 1,
688 "NODES_PER_CELL" : (self._poly_degree + 1) ** self._dimensions,
689 "NODES_PER_FACE" : (self._poly_degree + 1) ** (self._dimensions-1),
690 "UNKNOWNS_PER_CELL_NODE" : self._unknowns_per_cell_node,
691 "SOLUTIONS_PER_FACE_NODE" : self._solutions_per_face_node,
692 "PROJECTIONS_PER_FACE_NODE" : self._projections_per_face_node,
693 "RHS_MATRIX" : self._mass_matrix,
694 "RHS_MATRIX_SCALING" : self._mass_matrix_scaling,
695 "ASSEMBLY_MATRIX" : self._assembly_matrix,
696 "ASSEMBLY_MATRIX_SCALING" : self._assembly_matrix_scaling,
697 "FACE_FROM_CELL_PROJECTION" : self._face_from_cell_projection,
698 "FACE_FROM_CELL_PROJECTION_SCALING": self._face_from_cell_projection_scaling,
699 "CELL_FROM_FACE_PROJECTION" : self._cell_from_face_projection,
700 "CELL_FROM_FACE_PROJECTION_SCALING" : self._cell_from_face_projection_scaling,
701 "RIEMANN_MATRIX" : self._riemann_matrix,
702 "BOUNDARY_MATRIX" : self._boundary_matrix,
703 "APPROX_SYSTEM_MATRIX" : self._approximate_system_matrix,
704 "APPROX_SYSTEM_MATRIX_SCALING": self._approximate_system_matrix_scaling,
705 "CELL_RELAXATION" : self._cell_relaxation,
706 "FACE_RELAXATION" : self._face_relaxation,
707 "POLY_DEGREE" : self._poly_degree,
708 "SOLVER_TOLERANCE" : self._solver_tolerance,
709 "CUSTOM_INIT_CELL" : self._custom_init_cell,
710 "INTEGRATION_POINTS" : self._integration_points,
711 "USE_SMART_POINTER" : self._use_smart_pointer,
712 "TRACK_SOLUTION_BETWEEN_CYCLES": self._track_solution_between_cycles
Specialisation of array using Peano's tarch.
Specialisation of array using Peano's tarch with a smart pointer.
Most efficient solver we have for this type of problem class.
_face_from_cell_projection_scaling
_unknowns_per_face_solution
_face_from_cell_projection
_projections_per_face_node
_approximate_system_matrix_scaling
__init__(self, name, dimensions, poly_degree, unknowns_per_cell_node, solutions_per_face_node, projections_per_face_node, min_h, max_h, assembly_matrix, assembly_matrix_scaling, mass_matrix, mass_matrix_scaling, face_from_cell_projection, face_from_cell_projection_scaling, cell_from_face_projection, cell_from_face_projection_scaling, riemann_matrix, boundary_matrix, cell_relaxation, face_relaxation, approximate_system_matrix, approximate_system_matrix_scaling, integration_points, solver_tolerance, custom_init_actionset=None, custom_plot_actionset=None, custom_init_cell="", enable_plotting=True, plot_predicate="true", solver_includes="", use_smart_pointer=True, track_solution_between_cycles=False)
Construct the solver.
_track_solution_between_cycles
_cell_from_face_projection_scaling
_cell_from_face_projection
_unknowns_per_face_projection
_approximate_system_matrix
Abstract base class for matrix free solvers.
Init the degrees of freedom of a higher order scheme where nodes sit in cells.
get_face_merge_method(self)
The actual merge of faces is simple, as we compute the numerical flux redundantly,...
_load_cell_and_face_data_guard(self)
Goard when to hold data in mesh.
add_to_create_grid(self, observer)
add_to_Peano4_datamodel(self, datamodel, verbose)
add_use_statements(self, observer)
add_to_solve(self, observer)
I ignore the order of the action sets here and instead derive my own ordering starting from _basic_de...
_hold_cell_and_face_data_guard(self)
Goard when to hold data in mesh.
_additional_includes(self)
add_to_init_solution(self, observer)
Solution initialisation.
add_implementation_files_to_project(self, namespace, output, subdirectory="", abstract_overwrite=True)
The solver creates two classes: An abstract base class and its implementations.
add_to_plot(self, observer)
Tell the project's observer how to plot the data.