Peano
Loading...
Searching...
No Matches
CentralDifferencesWithRungeKutta.py
Go to the documentation of this file.
1# This file is part of the ExaHyPE2 project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3import enum
4import os
5import re
6
7import peano4.api
12import toolbox.loadbalancing.api.actionsets
13import exahype2.api.dslhype as dslhype
14
15from ..MaxEigenvalue import MaxEigenvalue
16from ..Solver import Solver
17from ..rungekutta import ButcherTableau
18from ..rungekutta.LinearCombination import generate_linear_combination_kernel
19from . import FD4Kernels
20from .Quantities import resolve_quantities
21from .actionsets import AddLopsidedAdvection
22from .actionsets import ApplyDirichletBoundaryConditions
23from ..blockstructured.BoundaryConditions import get_access_to_persistent_face_data
24from ..blockstructured.BoundaryConditions import get_boundary_conditions
25from ..blockstructured.SommerfeldBoundaryConditions import SommerfeldBoundaryConditions
26from ..blockstructured.create_dynamic_mesh_refinement_action_set import (
27 create_dynamic_mesh_refinement_action_set,
28)
29from .actionsets import ComputeLinearCombination
30from .actionsets import ComputeMaxEigenvalue
31from .actionsets import ComputeTimeStepSize
32from .actionsets import EvaluateRightHandSide
33from .actionsets import ProjectPatchOntoFaces
34from .actionsets import RollOverUpdatedFaceWithSommerfeldBoundaryConditions
35
36
38 """!
39
40 Fourth-order central Finite Differences with explicit Runge-Kutta time
41 stepping, realised over a block-structured mesh
42
43 Each mesh cell hosts a patch of patch_size^d grid cells. The unknowns
44 sit in the grid cells' centres. The scheme evaluates the right-hand side
45 of
46
47 @f$ \\partial _t Q = F(Q) @f$
48
49 through fourth-order central differences (@ref
50 exahype2.api.rkfd4.FD4Kernels), and integrates in time with the explicit
51 Runge-Kutta scheme from @ref exahype2.api.rungekutta.ButcherTableau. The
52 stencils reach two grid cells into the neighbouring patches, i.e. the
53 halo is HALO_SIZE=2 grid cells wide:
54
55 @image html central-differences-with-runge-kutta-stencil.png
56
57 The script central-differences-with-runge-kutta-illustrations.py next to
58 this file generates the illustrations of this page.
59
60 ## Usage
61
62 The scheme plugs into an exahype2.api.Solver like the Finite Volumes of
63 exahype2.api.fv, but with the grid construction and the plotter of
64 exahype2.api.rkfd4:
65
66 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
67 solver.numerical_scheme = exahype2.api.rkfd4.CentralDifferencesWithRungeKutta(solver, patch_size=9, rk_order=4)
68 solver.grid_construction = exahype2.api.rkfd4.SetupSimulationOverRegularGrid(solver, volume_size=0.01)
69 solver.plotter = exahype2.api.rkfd4.PlotAsVTKFile(solver, time_in_between_plots=0.1)
70 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
71
72 The solver can have a Flux, a NonconservativeProduct and a
73 VolumetricSource. It needs an InitPointWisely term, a MaxEigenvalue term
74 unless the time step size is fixed, and either a
75 DirichletBoundaryConditions or a SommerfeldBoundaryConditions term
76 unless all axes are periodic (@ref
77 exahype2.api.blockstructured.BoundaryConditions.get_boundary_conditions()). Sommerfeld
78 conditions also require a MaxEigenvalue term, and a Butcher tableau whose
79 stage time stamps do not decrease. @ref
80 benchmarks_exahype2_euler_smooth_fd4 demonstrates fourth-order
81 convergence for the Euler equations.
82
83 ## Extending the scheme
84
85 The scheme has no artificial dissipation. Subclasses can add further
86 terms to the right-hand side:
87
88 - _right_hand_side_action_sets() returns the action sets of the task
89 which evaluates the right-hand side of one stage. Subclasses can
90 append action sets which add their terms to the stage's estimate.
91 They run after the right-hand side kernel, within the same task.
92 - Subclasses which need wider stencils override HALO_SIZE.
93
94 @ref exagrype.api.FD4WithKODissipation adds the Kreiss-Oliger
95 dissipation this way.
96
97 ## Right-hand side kernels
98
99 The constructor's argument right_hand_side_kernel selects how the
100 scheme evaluates the right-hand side
101 (CentralDifferencesWithRungeKutta.RightHandSideKernel):
102
103 - CentralDifferences: all derivatives through fourth-order central
104 differences (@ref exahype2.api.rkfd4.FD4Kernels). This is the
105 default.
106 - CentralDifferencesWithLopsidedAdvection: the central differences
107 plus advection terms @f$ v_d \\partial _d Q_q @f$, which a separate
108 kernel discretises through lopsided (upwind-biased) fourth-order
109 stencils (@ref exahype2::fd::addLopsidedAdvection()). The kernel
110 runs after the central differences within each Runge-Kutta stage.
111 You specify the velocity v, which has to be part of Q, and the
112 advected quantities q. The PDE terms of the solver must not contain
113 these advection terms themselves. As the lopsided stencils reach
114 three grid cells into the neighbouring patches, the halo is three
115 grid cells wide.
116
117 ## Data flow
118
119 __create_data_structures() documents the data per mesh cell and
120 face. Per time step, the scheme runs through the Runge-Kutta stages
121 k=0,...,s-1. Stage k
122
123 1. reconstructs its input @f$ Y_k @f$ with a halo
124 (solver._patch_with_halo). The interior is solver._patch for the
125 first stage and solver._stage_input for all others, the halo is
126 taken from the faces (solver._patch_overlap_current_time_step);
127 2. evaluates the right-hand side @f$ K_k = F(Y_k) @f$ and stores it in
128 the stage's slot of solver._rhs_estimates;
129 3. computes the input of the next stage
130 @f$ Y_{k+1} = Q^n + \\Delta t \\sum _{j \\leq k} a_{k+1,j} K_j @f$
131 into solver._stage_input and projects it onto the faces, so the
132 neighbours can reconstruct their halo from it in the next stage.
133
134 The last stage computes
135 @f$ Q^{n+1} = Q^n + \\Delta t \\sum _k b_k K_k @f$ instead, i.e. it
136 overwrites solver._patch, and projects the new solution onto the faces,
137 where the first stage of the next time step picks it up.
138
139 As the reconstruction of a stage reads the face data the previous stage
140 has written, the task graph compiler maps each stage onto a mesh
141 traversal of its own (@ref peano4.api.taskgraph.compiler.FuseSteps).
142 For a second-order Runge-Kutta scheme with Dirichlet boundary
143 conditions, the time step's task graph reads:
144
145 @image html central-differences-with-runge-kutta-time-step-task-graph.png
146
147 The boundary values are evaluated at the time stamp of the respective
148 stage, i.e. at @f$ t^n + c_k \\Delta t @f$. Otherwise, the scheme would
149 drop to first order along the boundary.
150
151 ## Time step sizes
152
153 If you pass a time_step_size, the scheme uses this fixed time step size.
154 Otherwise, it derives the time step size from the maximum eigenvalue of
155 the solution after each time step through the CFL condition with
156 cfl_constant (@ref exahype2.api.rkfd4.actionsets.ComputeTimeStepSize).
157 This requires a non-stateless MaxEigenvalue PDE term.
158
159 """
160
161
162 HALO_SIZE = 2
163
164 class RightHandSideKernel(enum.Enum):
165 """!
166 The kernels which evaluate the right-hand side, @see the class documentation
167 """
168
169 CentralDifferences = 0
170 CentralDifferencesWithLopsidedAdvection = 1
171
173 self,
174 solver: Solver,
175 patch_size: int,
176 rk_order: int,
177 time_step_size: float = None,
178 cfl_constant: float = 0.5,
179 right_hand_side_kernel: "CentralDifferencesWithRungeKutta.RightHandSideKernel" = RightHandSideKernel.CentralDifferences,
180 advection_velocity=None,
181 advected_quantities=None,
182 min_volume_size: float = None,
183 max_volume_size: float = None,
184 interpolation: "toolbox.blockstructured.api.InterpolateHangingFaces.Scheme" = toolbox.blockstructured.api.InterpolateHangingFaces.Scheme.Linear,
185 restriction: "toolbox.blockstructured.api.RestrictHangingFaces.Scheme" = toolbox.blockstructured.api.RestrictHangingFaces.Scheme.Averaging,
186 ):
187 """!
188
189 The constructor memorises the attributes and creates the data
190 structures. It does not add the data to the solver's Peano 4
191 project yet, but it assigns them to the solver, so anybody else
192 working against this solver, such as the grid construction or the
193 plotter, finds them there.
194
195 @param solver: exahype2.api.Solver
196 The solver this numerical scheme realises.
197
198 @param patch_size: int
199 Number of grid cells per axis within one patch, i.e. within one
200 mesh cell. Has to be at least HALO_SIZE, as the projection onto
201 the faces takes the HALO_SIZE outermost layers of the patch.
202
203 @param rk_order: int
204 Order of the Runge-Kutta scheme, i.e. its number of stages. Has
205 to be between one and four, @ref
206 exahype2.api.rungekutta.ButcherTableau.
207
208 @param time_step_size: float or None
209 Fixed time step size. If None, the time step size is adaptive.
210
211 @param cfl_constant: float
212 CFL constant for adaptive time step sizes. It scales the time step
213 size h / (d lambda_max), with h the grid cell size, d the
214 dimension and lambda_max the maximum eigenvalue. Ignored if
215 time_step_size is set. Fourth-order central differences with the
216 classic fourth-order Runge-Kutta scheme are stable up to roughly
217 two for linear advection without dissipation, so 0.5 is a
218 conservative default.
219
220 @param right_hand_side_kernel: CentralDifferencesWithRungeKutta.RightHandSideKernel
221 The kernel which evaluates the right-hand side, @see the class
222 documentation.
223
224 @param advection_velocity: str or List[str or int] or None
225 Only for CentralDifferencesWithLopsidedAdvection: the entries of Q
226 which hold the advection velocity, one per dimension. Pass the name
227 of a variable with one entry per dimension (e.g. "shift" for
228 exagrype.api.CCZ4Solver), or a list of names or indices. The
229 velocity may be an auxiliary variable.
230
231 @param advected_quantities: List[str or int] or None
232 Only for CentralDifferencesWithLopsidedAdvection: the unknowns
233 which are advected, as names of variables or indices of entries,
234 @see exahype2.api.rkfd4.Quantities.resolve_quantities().
235
236 @param min_volume_size: float
237 The time step asks for a refinement of a cell only if its grid
238 cells are at least of this size (@ref exahype2.api.blockstructured.DynamicMeshRefinement).
239 None (the default) does not bound the refinement.
240
241 @param max_volume_size: float
242 The time step asks for an erase, i.e. coarsening, of a cell only
243 if the grid cells of the coarser cell would be strictly smaller
244 than this size. None (the default) does not bound the coarsening.
245
246 @param interpolation: toolbox.blockstructured.api.InterpolateHangingFaces.Scheme
247 How the scheme initialises the halo of a fine patch next to a
248 coarser patch on an adaptive mesh. Whenever a Runge-Kutta stage
249 reconstructs the halo of a fine cell, it interpolates the data of
250 the adjacent coarse face onto the fine cell's hanging face
251 (toolbox.blockstructured.api.InterpolateHangingFaces). The
252 default is linear interpolation.
253
254 @param restriction: toolbox.blockstructured.api.RestrictHangingFaces.Scheme
255 How the scheme hands the data of fine patches over to an adjacent
256 coarser patch. Whenever the fine cells project their data onto
257 their faces, the scheme restricts the data of the hanging faces
258 onto the coarse face (toolbox.blockstructured.api.RestrictHangingFaces).
259 The default is averaging.
260
261 On a regular mesh, there are no hanging faces, and the two schemes
262 are never used. At resolution transitions, the halo of a fine patch
263 is only as accurate as the interpolation, i.e. the scheme drops to
264 low order there.
265
266 """
267 super().__init__(solver)
268 self.interpolation = interpolation
269 self.restriction = restriction
270 if patch_size < self.HALO_SIZEHALO_SIZE:
271 raise ValueError(
272 f"Solver {solver.name}: patch_size has to be at least {self.HALO_SIZE}, "
273 f"as the stencils reach {self.HALO_SIZE} grid cells into "
274 f"the neighbouring patches, but got {patch_size}"
275 )
276 if time_step_size is not None and time_step_size <= 0.0:
277 raise ValueError(
278 f"Solver {solver.name}: time_step_size has to be positive, "
279 f"but got {time_step_size}"
280 )
281 if cfl_constant <= 0.0:
282 raise ValueError(
283 f"Solver {solver.name}: cfl_constant has to be positive, "
284 f"but got {cfl_constant}"
285 )
286 if not isinstance(
288 ):
289 raise TypeError(
290 f"Solver {solver.name}: right_hand_side_kernel has to be a "
291 f"CentralDifferencesWithRungeKutta.RightHandSideKernel, but got "
292 f"{right_hand_side_kernel!r}"
293 )
294 self.right_hand_side_kernel = right_hand_side_kernel
296 if right_hand_side_kernel == Kernel.CentralDifferencesWithLopsidedAdvection:
297 if advection_velocity is None or advected_quantities is None:
298 raise ValueError(
299 f"Solver {solver.name}: the kernel "
300 f"CentralDifferencesWithLopsidedAdvection needs an "
301 f"advection_velocity and advected_quantities"
302 )
303 if isinstance(advection_velocity, (str, int)):
304 advection_velocity = [advection_velocity]
305 self.advection_velocity = resolve_quantities(
306 solver, advection_velocity, include_auxiliary_variables=True
307 )
308 self.advected_quantities = resolve_quantities(solver, advected_quantities)
309 # The lopsided stencils reach three grid cells into the neighbours
311 if patch_size < self.HALO_SIZEHALO_SIZE:
312 raise ValueError(
313 f"Solver {solver.name}: patch_size has to be at least "
314 f"{self.HALO_SIZE} for lopsided advection, but got {patch_size}"
315 )
316 else:
317 if advection_velocity is not None or advected_quantities is not None:
318 raise ValueError(
319 f"Solver {solver.name}: advection_velocity and "
320 f"advected_quantities are only used by the kernel "
321 f"CentralDifferencesWithLopsidedAdvection"
322 )
323 self.advection_velocity = None
324 self.advected_quantities = None
325 self.patch_size = patch_size
326 assert (
327 min_volume_size is None
328 or max_volume_size is None
329 or min_volume_size <= max_volume_size
330 ), f"min_volume_size={min_volume_size} has to be smaller than or equal to max_volume_size={max_volume_size}"
331 self.min_volume_size = min_volume_size
332 self.max_volume_size = max_volume_size
334 self.time_step_size = time_step_size
335 self.cfl_constant = cfl_constant
336 # Set in construct_output(): the generated kernels live in the project
337 # namespace
341
342 @property
343 def overlap(self) -> int:
344 """!
345 Width of the halo, i.e. number of grid cells each face holds per
346 adjacent patch. Always HALO_SIZE.
347 """
348 return self.HALO_SIZEHALO_SIZE
349
350 @property
351 def rk_order(self) -> int:
352 return self.butcher_tableau.order
353
354 @property
356 return self.time_step_size is None
357
358 @property
360 """!
361 Name of the generated right-hand side kernel. It carries the solver
362 name, as do the kernels/{SolverName}_fd4*.h files holding it, so
363 two solvers' kernels do not clash.
364 """
365 return self._solver_solver.name + "_rightHandSideWithFD4"
366
367 @property
369 return self._solver_solver.name + "_maxEigenvalueOverPatch"
370
371 def _right_hand_side_action_sets(self, stage, cell_guard):
372 """!
373
374 Action sets of the task which evaluates the right-hand side of one stage
375
376 The task reads solver._patch_with_halo, i.e. the stage's input with
377 its halo, and writes the stage's slot of solver._rhs_estimates. By
378 default, it holds one action set, which invokes the fourth-order
379 Finite Differences kernel. Subclasses can append action sets which
380 add further terms to the estimate. The action sets of one task run
381 one after the other, in the order of the list. For the kernel
382 CentralDifferencesWithLopsidedAdvection, the list holds the
383 lopsided advection as second action set.
384
385 @param stage: int
386 Number of the Runge-Kutta stage, starting with zero.
387
388 @param cell_guard: str
389 C++ boolean expression, evaluated per cell.
390
391 @return List[peano4.api.solversteps.ActionSet]
392
393 """
394 action_sets = [EvaluateRightHandSide(self._solver_solver, stage, guard=cell_guard)]
395 if (
397 == CentralDifferencesWithRungeKutta.RightHandSideKernel.CentralDifferencesWithLopsidedAdvection
398 ):
399 action_sets.append(
402 stage,
405 guard=cell_guard,
406 )
407 )
408 return action_sets
409
411 """!
412
413 Create the mesh data structures this numerical scheme requires
414
415 All of them are assigned to self._solver rather than self, so the
416 solver's grid construction and plotter, as well as the action sets,
417 find them there:
418
419 - _patch: the solution @f$ Q^n @f$, i.e. patch_size^d grid cells per
420 mesh cell, with the unknowns and the auxiliary variables per grid
421 cell.
422 - _stage_input: the input @f$ Y_k @f$ of the next Runge-Kutta stage,
423 with the same layout as _patch. It is written at the end of one
424 stage and read by the next one.
425 - _patch_with_halo: the input of the current Runge-Kutta stage
426 with its halo, i.e. (patch_size+2*HALO_SIZE)^d grid cells,
427 reconstructed from _patch or _stage_input and the faces (see
428 toolbox.blockstructured.api.ReconstructPatchWithHalo). It is
429 logically temporary.
430 - _rhs_estimates: the right-hand side estimates @f$ K_k @f$ of all
431 rk_order stages. They hold the unknowns only, as the auxiliary
432 variables do not evolve in time. The estimates of the stages are
433 stored one after the other, i.e. as rk_order patches of
434 patch_size^d grid cells each. This is why the first dimension is
435 rk_order*patch_size.
436 - _patch_overlap_current_time_step: the face data the halo is
437 reconstructed from, i.e. 2*HALO_SIZE layers along the face's
438 normal. The face data are exchanged between ranks.
439 - _patch_overlap_update: the projection of the patches onto the
440 faces which the current sweep accumulates (@ref
441 exahype2.api.rkfd4.actionsets.ProjectPatchOntoFaces). It is
442 rolled over into _patch_overlap_current_time_step once complete
443 (@ref exahype2.api.rkfd4.actionsets.RollOverUpdatedFace).
444 - _max_eigenvalue: only with adaptive time step sizes. One double
445 per cell, named after the solver followed by MaxEigenvalue. It
446 holds the maximum eigenvalue of the patch after the last time step.
447 It is modelled via DaStGen2 as array of cardinality one on the
448 heap (peano4.api.dastgen2.Peano4SmartPointerDoubleArray).
449
450 All patches use smart pointers (@ref
451 peano4.api.datamodel.PatchToDoubleArrayWithSmartPointer), like the
452 Finite Volumes of exahype2.api.fv. The legacy
453 exahype2.solvers.rkfd.CellCenteredFiniteDifferences held a third
454 face overlap with the data of the previous time step. It is
455 required for AMR and local time stepping only, so we do not create
456 it.
457
458 """
459 total_unknowns = self._solver_solver.unknowns + self._solver_solver.auxiliary_variables
460 identifier = self._solver_solver._unknown_identifier()
461 n = self.patch_size
462 h = self.HALO_SIZEHALO_SIZE
463
465 (n, n, n),
466 total_unknowns,
467 identifier,
468 )
469 self._solver_solver._stage_input = peano4.api.datamodel.Patch(
470 (n, n, n),
471 total_unknowns,
472 identifier + "StageInput",
473 )
474 self._solver_solver._patch_with_halo = peano4.api.datamodel.Patch(
475 (n + 2 * h, n + 2 * h, n + 2 * h),
476 total_unknowns,
477 identifier + "WithHalo",
478 )
479 self._solver_solver._rhs_estimates = peano4.api.datamodel.Patch(
480 (self.rk_orderrk_order * n, n, n),
481 self._solver_solver.unknowns,
482 identifier + "RhsEstimates",
483 )
484 self._solver_solver._patch_overlap_current_time_step = peano4.api.datamodel.Patch(
485 (2 * h, n, n),
486 total_unknowns,
487 identifier + "CurrentTimeStep",
488 )
489 self._solver_solver._patch_overlap_update = peano4.api.datamodel.Patch(
490 (2 * h, n, n),
491 total_unknowns,
492 identifier + "Update",
493 )
495 self._solver_solver._max_eigenvalue = peano4.api.datamodel.DaStGen2(
496 self._solver_solver.name + "MaxEigenvalue"
497 )
498 self._solver_solver._max_eigenvalue.data.add_attribute(
500 )
501 else:
502 self._solver_solver._max_eigenvalue = None
503
504 def _patches(self):
505 """!
506 All patches of this numerical scheme, i.e. all data besides the
507 maximum eigenvalue.
508 """
509 return [
510 self._solver_solver._patch,
511 self._solver_solver._stage_input,
512 self._solver_solver._patch_with_halo,
513 self._solver_solver._rhs_estimates,
514 self._solver_solver._patch_overlap_current_time_step,
515 self._solver_solver._patch_overlap_update,
516 ]
517
518 def __add_data_to_repository(self, peano_project):
519 """!
520
521 Add the data from __create_data_structures() to the Peano 4 project
522 and configure their generators
523
524 Only _patch_overlap_current_time_step is exchanged between ranks,
525 and only on the faces the solver's grid selector holds for. The
526 task graph compiler uses, loads and stores data only where the
527 solver's grid selector holds, too (@ref peano4.api.datamodel.DoF).
528
529 """
530 peano4.api.Logger().log(
531 """
532Patch data
533----------
534{}
535Right-hand side estimates
536-------------------------
537{}
538Patch overlap data
539------------------
540{}
541{}""".format(
542 str(self._solver_solver._patch),
543 str(self._solver_solver._rhs_estimates),
544 str(self._solver_solver._patch_overlap_current_time_step),
545 str(self._solver_solver._patch_overlap_update),
546 )
547 )
548
549 peano_project.datamodel.add_cell(self._solver_solver._patch)
550 peano_project.datamodel.add_cell(self._solver_solver._stage_input)
551 peano_project.datamodel.add_cell(self._solver_solver._patch_with_halo)
552 peano_project.datamodel.add_cell(self._solver_solver._rhs_estimates)
553 peano_project.datamodel.add_face(self._solver_solver._patch_overlap_current_time_step)
554 peano_project.datamodel.add_face(self._solver_solver._patch_overlap_update)
555
556 data = self._patches()
558 peano_project.datamodel.add_cell(self._solver_solver._max_eigenvalue)
559 data.append(self._solver_solver._max_eigenvalue)
560 for entry in data:
561 entry.grid_selector = self._solver_solver.grid_selector
562
563 for patch in self._patches():
565 patch, "double"
566 )
567
568 current_time_step = self._solver_solver._patch_overlap_current_time_step
569 current_time_step.generator.merge_method_definition = (
570 toolbox.blockstructured.api.get_face_merge_implementation(current_time_step)
571 )
572 current_time_step.generator.includes += """
573#include "peano4/utils/Loop.h"
574"""
575 current_time_step.generator.send_condition = (
576 self._solver_solver.grid_selector.createFaceGuard()
577 )
578 current_time_step.generator.receive_and_merge_condition = (
579 self._solver_solver.grid_selector.createFaceGuard()
580 )
581
582 self._solver_solver._patch_overlap_update.generator.send_condition = "false"
583 self._solver_solver._patch_overlap_update.generator.receive_and_merge_condition = (
584 "false"
585 )
586
587 def __create_kernels(self, output_directory):
588 """!
589
590 Translate the DSLHyPE kernels into C++
591
592 The kernels end up in the kernels/ subdirectory of output_directory:
593
594 - {SolverName}_fd4_2d.h/.cpph and {SolverName}_fd4_3d.h/.cpph hold
595 the right-hand side (@ref exahype2.api.rkfd4.FD4Kernels).
596 - {SolverName}_fd4_eigenvalue_2d.h/.cpph and
597 {SolverName}_fd4_eigenvalue_3d.h/.cpph hold the maximum
598 eigenvalue over a patch. They exist only with adaptive time step
599 sizes.
600 - {SolverName}_fd4.h includes the variants for the dimension the
601 code is compiled for.
602 - rungekutta_linear_combination.h/.cpph holds the linear combination
603 of the Runge-Kutta estimates (@ref
604 exahype2.api.rungekutta.LinearCombination).
605
606 Calling this function is delayed until all PDE term information is
607 provided, as the kernels contain only the PDE terms the solver has.
608
609 """
610 template_parameters = [
614 "NumberOfUnknowns", dslhype.SyntaxTree.TInteger()
615 ),
617 "NumberOfAuxiliaryVariables", dslhype.SyntaxTree.TInteger()
618 ),
619 ]
620 functor_parameters = [
622 "flux",
624 "const ::exahype2::fv::rusanov::FluxFunctor&"
625 ),
626 "(!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, i32, !llvm.ptr) -> ()",
627 ),
629 "nonconservativeProduct",
631 "const ::exahype2::fv::rusanov::NonconservativeProductFunctor&"
632 ),
633 "(!llvm.ptr, !llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, i32, !llvm.ptr) -> ()",
634 ),
636 "volumetricSource",
638 "const ::exahype2::fv::rusanov::VolumetricSourceFunctor&"
639 ),
640 "(!llvm.ptr, !llvm.ptr, f64, !llvm.ptr) -> ()",
641 ),
643 "maxEigenvalue",
645 "const ::exahype2::fv::rusanov::MaxEigenvalueFunctor&"
646 ),
647 "(!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, i32, !llvm.ptr) -> ()",
648 ),
649 ]
650
651 kernels = [
652 (
653 FD4Kernels.rightHandSideWithFD42D,
654 "_fd4_2d",
656 ),
657 (
658 FD4Kernels.rightHandSideWithFD43D,
659 "_fd4_3d",
661 ),
662 ]
664 kernels += [
665 (
666 FD4Kernels.maxEigenvalueOverPatch2D,
667 "_fd4_eigenvalue_2d",
669 ),
670 (
671 FD4Kernels.maxEigenvalueOverPatch3D,
672 "_fd4_eigenvalue_3d",
674 ),
675 ]
676 for function, file_suffix, function_name in kernels:
677 dslhype.translate_dslhype_function(
678 function=function,
679 function_namespace=self.kernel_namespace,
680 template_parameters=template_parameters,
681 functor_parameters=functor_parameters,
682 pde_terms=self._solver_solver.pde_terms,
683 output_file_name=self._solver_solver.name + file_suffix,
684 target=dslhype.Target.Serial,
685 stateless=False,
686 function_name=function_name,
687 output_directory=output_directory,
688 )
689
690 includes_2d = "\n".join(
691 f' #include "{self._solver.name}{file_suffix}.h"'
692 for _, file_suffix, _ in kernels
693 if file_suffix.endswith("2d")
694 )
695 includes_3d = "\n".join(
696 f' #include "{self._solver.name}{file_suffix}.h"'
697 for _, file_suffix, _ in kernels
698 if file_suffix.endswith("3d")
699 )
700 with open(
701 os.path.join(output_directory, "kernels", f"{self._solver.name}_fd4.h"), "w"
702 ) as file:
703 file.write(
704 f"""#pragma once
705#if Dimensions == 2
706{includes_2d}
707#else
708{includes_3d}
709#endif
710"""
711 )
712
713 generate_linear_combination_kernel(self.kernel_namespace, output_directory)
714
715 def __construct_kernel_call(self, function_name, additional_arguments):
716 """!
717
718 Construct the call of a generated kernel
719
720 The kernel takes a ::exahype2::CellData called patchData, the
721 additional arguments and one functor per PDE term the solver has,
722 in the order flux, nonconservativeProduct, volumetricSource,
723 maxEigenvalue (see __create_kernels()). Each functor forwards to
724 the solver instance.
725
726 If PDE terms are registered both with and without state, the
727 generated code asks cellCanUseStatelessPDETerms() before it invokes
728 the kernel, and hands over the stateless variants if it returns
729 true. This logic is a copy of the one of exahype2.api.fv, as rkfd4
730 does not depend on the Finite Volume package.
731
732 @param function_name: str
733 Unqualified name of the kernel.
734
735 @param additional_arguments: [str]
736 C++ expressions passed after patchData.
737
738 """
739 available_terms = [
740 "flux",
741 "nonconservativeProduct",
742 "volumetricSource",
743 "maxEigenvalue",
744 ]
745
746 # Variants per term the kernel takes: {name: {stateless: PDETerm}}
747 variants = {}
748 for application_term in self._solver_solver.pde_terms:
749 if application_term.NAME in available_terms:
750 variants.setdefault(application_term.NAME, {})[
751 application_term.stateless
752 ] = application_term
753
754 terms_with_both_variants = [
755 name for name in variants if len(variants[name]) == 2
756 ]
757 terms_without_stateless_variant = [
758 name for name in variants if True not in variants[name]
759 ]
760 if terms_with_both_variants and terms_without_stateless_variant:
761 raise Exception(
762 f"Solver {self._solver.name}: the PDE terms {terms_with_both_variants} are "
763 f"registered both with and without state. In this case, the generated code "
764 f"asks cellCanUseStatelessPDETerms() per cell before it invokes the kernel: "
765 f"if it returns true, the kernel is called with the stateless variants of all "
766 f"PDE terms, otherwise with the variants with state. However, the PDE terms "
767 f"{terms_without_stateless_variant} have no stateless variant, so a kernel "
768 f"call would mix stateless and stateful routines. Either add stateless "
769 f"variants of {terms_without_stateless_variant}, or register "
770 f"{terms_with_both_variants} only once."
771 )
772
773 def kernel_call(stateless: bool) -> str:
774 arguments = ["patchData"] + list(additional_arguments)
775 for term in available_terms:
776 if term not in variants:
777 continue
778 # A term registered only once is used in either case
779 application_term = variants[term].get(
780 stateless, next(iter(variants[term].values()))
781 )
782 # Commas inside template brackets, e.g. Vector<Dimensions, double>,
783 # do not separate parameters
784 term_arguments = [
785 parameter.split()[-1]
786 for parameter in re.split(
787 r",(?![^<]*>)", application_term.SIGNATURE
788 )
789 ]
790 # Stateless terms are static and take a dummy tag (@see PDETerm)
791 if application_term.stateless:
792 term_arguments.append("::exahype2::Solver::Offloadable::Yes")
793 arguments.append(
794 f"""
795 [&](
796 {application_term.SIGNATURE}
797 )->void {{
798 repositories::{self._solver.get_name_of_global_instance()}.{application_term.name}({", ".join(term_arguments)});
799 }}"""
800 )
801 return f"""::{"::".join(self.kernel_namespace)}::{function_name}<
802 {self.patch_size},
803 {self.HALO_SIZE},
804 {self._solver.unknowns},
805 {self._solver.auxiliary_variables}
806>(
807 {",".join(arguments)}
808);"""
809
810 if not terms_with_both_variants:
811 return kernel_call(stateless=False)
812 return f"""if (repositories::{self._solver.get_name_of_global_instance()}.cellCanUseStatelessPDETerms(
813 patchData.cellCentre[0],
814 patchData.cellSize[0],
815 patchData.t[0],
816 patchData.dt[0]
817)) {{
818{kernel_call(stateless=True)}
819}} else {{
820{kernel_call(stateless=False)}
821}}"""
822
823 def create_default_mesh_adaptation(self) -> Solver.MeshAdaptation:
824 """!
825
826 The finite differences adapt the mesh and balance the load through
827 exahype2.api.rkfd4.AdaptMeshAndBalanceLoad by default
828
829 """
830 from .AdaptMeshAndBalanceLoad import AdaptMeshAndBalanceLoad
831
833
835 self,
836 ) -> "toolbox.blockstructured.api.RestrictHangingFaces":
837 """!
838
839 Action set which restricts the projected fine data from the hanging
840 faces onto the coarse faces
841
842 Every task which projects the solution onto the faces has to host
843 this action set, so the faces between coarse and fine patches hold
844 consistent data afterwards. The task has to write all of
845 _patch_overlap_update, as averaging clears it first.
846
847 """
848 return toolbox.blockstructured.api.RestrictHangingFaces(
849 self._solver_solver._patch_overlap_update,
850 scheme=self.restriction,
851 clear_guard=self._solver_solver.grid_selector.createFaceGuard(),
852 additional_includes=self._solver_solver._get_default_includes()
853 + self._solver_solver.user_solver_includes,
854 )
855
856 def construct_output(self, exahype2_project, peano_project, time_step):
857 """!
858
859 Add the data to the Peano 4 project, generate the kernels and add
860 the tasks of one Runge-Kutta step to time_step
861
862 Per stage k, the time step holds the following tasks (see the class
863 documentation for the data flow):
864
865 - {SolverName}_Apply Dirichlet BCs stage k: sets the ghost grid
866 cells along the domain boundary for Dirichlet boundary conditions.
867 The task does not exist if all axes are periodic, or for Sommerfeld
868 boundary conditions, which advance the ghost grid cells within the
869 roll over of the projection onto the faces.
870 - {SolverName}_Reconstruct stage k: reconstructs the stage's input
871 with its halo.
872 - {SolverName}_Evaluate right hand side stage k.
873 - {SolverName}_Prepare stage k+1, or {SolverName}_Complete time step
874 after the last stage: computes the linear combination of the
875 estimates. Prepare stage k+1 is tagged with
876 exahype2.api.Solver.IntermediateSolutionUpdateTag, Complete time
877 step with exahype2.api.Solver.SolutionUpdateTaskTag, i.e.
878 postprocessing such as the enforcement of constraints can be
879 inserted directly after them (see @ref
880 page_exahype2_solver_manipulate_solver_steps).
881 - {SolverName}_Project stage k+1 onto faces, or {SolverName}_Project
882 solution onto faces after the last stage: projects the outcome of
883 the linear combination onto the faces. After the last stage, it
884 also computes the maximum eigenvalue of the new solution.
885
886 After the last stage, {SolverName}_Compute time step size determines
887 the time step size of the next time step and advances the solver's
888 time stamp. {SolverName}_Dynamic mesh refinement finally evaluates the
889 adaptivity criterion if the solver has one
890 (@ref exahype2.api.blockstructured.create_dynamic_mesh_refinement_action_set.create_dynamic_mesh_refinement_action_set()),
891 switches the load balancing on again and informs it that the time
892 step has finished.
893
894 """
895 peano4.api.Logger().log(
896 "Create time stepping task graph for fourth-order Finite Differences "
897 f"with Runge-Kutta of order {self.rk_order} over block-structured meshes"
898 )
899
900 if self.advection_velocity is not None and len(self.advection_velocity) != len(
901 exahype2_project.periodic_BC
902 ):
903 raise ValueError(
904 f"Solver {self._solver.name}: the advection velocity has "
905 f"{len(self.advection_velocity)} entries, but the domain has "
906 f"{len(exahype2_project.periodic_BC)} dimensions"
907 )
908
910 assert any(
911 isinstance(term, MaxEigenvalue) and not term.stateless
912 for term in self._solver_solver.pde_terms
913 ), (
914 f"Solver {self._solver.name} uses adaptive time step sizes, but has no "
915 "non-stateless MaxEigenvalue PDE term. Add one via "
916 "solver.add_pde_term(exahype2.api.MaxEigenvalue(...)), or pass a "
917 "time_step_size to CentralDifferencesWithRungeKutta."
918 )
919
920 relative_time_step_sizes = self.butcher_tableau.relative_time_step_sizes()
921 if isinstance(
922 get_boundary_conditions(self._solver_solver, exahype2_project),
923 SommerfeldBoundaryConditions,
924 ) and any(
925 later < earlier
926 for earlier, later in zip(
927 relative_time_step_sizes, relative_time_step_sizes[1:]
928 )
929 ):
930 raise ValueError(
931 f"Solver {self._solver.name} uses Sommerfeld boundary conditions, but "
932 f"the time stamps c={relative_time_step_sizes} of the stages of its "
933 f"Runge-Kutta scheme of order {self.rk_order} decrease. The roll over "
934 "advances the ghost grid cells from one stage to the next, so it "
935 "requires non-decreasing time stamps "
936 "(@ref exahype2::blockstructured::applySommerfeldBoundaryConditions())."
937 )
938
939 self.__add_data_to_repository(peano_project)
940
941 self.kernel_namespace = peano_project.namespace
942 # Construct the calls first, as they validate the PDE terms' variants
943 self._solver_solver.compute_kernel_call = self.__construct_kernel_call(
945 )
949 )
950 self.__create_kernels(peano_project.directory)
951
952 cell_guard = self._solver_solver.grid_selector.createCellGuard()
953 face_guard = self._solver_solver.grid_selector.createFaceGuard()
954 includes = (
955 self._solver_solver._get_default_includes() + self._solver_solver.user_solver_includes
956 )
957 name = self._solver_solver.name
958 weight_matrix = self.butcher_tableau.weight_matrix()
959 Access = peano4.api.taskgraph.Task.DataAccess
960 Entity = peano4.api.taskgraph.Task.GridEntity
961
962 previous_stage_task = None
963 for stage in range(self.rk_orderrk_order):
964 is_last_stage = stage == self.rk_orderrk_order - 1
965 stage_input = (
966 self._solver_solver._patch if stage == 0 else self._solver_solver._stage_input
967 )
968
969 reconstruction_task = peano4.api.taskgraph.Task(
970 name=f"{name}_Reconstruct stage {stage}", entity=Entity.CELL
971 )
972 right_hand_side_task = peano4.api.taskgraph.Task(
973 name=f"{name}_Evaluate right hand side stage {stage}",
974 entity=Entity.CELL,
975 )
976 if is_last_stage:
977 combination_task = peano4.api.taskgraph.Task(
978 name=f"{name}_Complete time step", entity=Entity.CELL
979 )
980 projection_task = peano4.api.taskgraph.Task(
981 name=f"{name}_Project solution onto faces", entity=Entity.CELL
982 )
983 else:
984 combination_task = peano4.api.taskgraph.Task(
985 name=f"{name}_Prepare stage {stage + 1}", entity=Entity.CELL
986 )
987 projection_task = peano4.api.taskgraph.Task(
988 name=f"{name}_Project stage {stage + 1} onto faces",
989 entity=Entity.CELL,
990 )
991 time_step.add_task(reconstruction_task)
992 time_step.add_task(right_hand_side_task)
993 time_step.add_task(combination_task)
994 time_step.add_task(projection_task)
995
996
997 if previous_stage_task is not None:
998 reconstruction_task.add_dependency(previous_stage_task)
999 else:
1000
1004 reconstruction_task.add_action_set(
1005 toolbox.loadbalancing.api.actionsets.ToggleLoadBalancing(
1006 enable_load_balancing=False,
1007 plug_into_prepare_traversal=True,
1008 )
1009 )
1010
1014 reconstruction_task.use_cell(
1015 stage_input,
1016 Access.READ_AND_WRITE if stage == 0 else Access.READ_AND_DISCARD,
1017 )
1018 reconstruction_task.use_cell(self._solver_solver._patch_with_halo, Access.WRITE)
1019 # Sommerfeld boundary conditions read the old face data within the
1020 # roll over, i.e. it has to survive the reconstruction
1021 reconstruction_task.use_face(
1022 self._solver_solver._patch_overlap_current_time_step,
1023 get_access_to_persistent_face_data(
1024 self._solver_solver, exahype2_project, Access.READ_AND_DISCARD
1025 ),
1026 )
1027 reconstruction_task.add_action_set(
1028 toolbox.blockstructured.api.ReconstructPatchWithHalo(
1029 patch=stage_input,
1030 patch_overlap=self._solver_solver._patch_overlap_current_time_step,
1031 reconstructed_patch=self._solver_solver._patch_with_halo,
1032 guard=cell_guard,
1033 add_assertions_to_halo_exchange=False,
1034 additional_includes=includes,
1035 )
1036 )
1037
1039 reconstruction_task.add_action_set(
1040 toolbox.blockstructured.api.InterpolateHangingFaces(
1041 self._solver_solver._patch_overlap_current_time_step,
1042 scheme=self.interpolation,
1043 additional_includes=includes,
1044 )
1045 )
1046
1050 boundary_task = ApplyDirichletBoundaryConditions.add_task_to_time_step(
1051 self._solver_solver,
1052 exahype2_project,
1053 time_step,
1054 reconstruction_task,
1055 name_suffix=f" stage {stage}",
1056 relative_time_step_size=self.butcher_tableau.relative_time_step_sizes()[
1057 stage
1058 ],
1059 )
1060 if boundary_task is not None and previous_stage_task is not None:
1061 boundary_task.add_dependency(previous_stage_task)
1062
1063
1064 right_hand_side_task.add_dependency(reconstruction_task)
1065 right_hand_side_task.use_cell(
1066 self._solver_solver._patch_with_halo, Access.READ_AND_DISCARD
1067 )
1068 right_hand_side_task.use_cell(
1069 self._solver_solver._rhs_estimates, Access.READ_AND_WRITE
1070 )
1071 for action_set in self._right_hand_side_action_sets(stage, cell_guard):
1072 right_hand_side_task.add_action_set(action_set)
1073
1074
1080 if is_last_stage:
1081 weights = self.butcher_tableau.final_estimate_weights()
1082 output = self._solver_solver._patch
1083 else:
1084 weights = weight_matrix[stage + 1]
1085 output = self._solver_solver._stage_input
1086 combination_task.add_dependency(right_hand_side_task)
1087 combination_task.use_cell(self._solver_solver._patch, Access.READ_AND_WRITE)
1088 combination_task.use_cell(
1089 self._solver_solver._rhs_estimates, Access.READ_AND_WRITE
1090 )
1091 if not is_last_stage:
1092 combination_task.use_cell(self._solver_solver._stage_input, Access.WRITE)
1093 combination_task.add_action_set(
1095 self._solver_solver, weights, output, guard=cell_guard
1096 )
1097 )
1098 combination_task.tags.add(
1099 Solver.SolutionUpdateTaskTag
1100 if is_last_stage
1101 else Solver.IntermediateSolutionUpdateTag
1102 )
1103
1104
1106 projection_task.add_dependency(combination_task)
1107 projection_task.use_cell(output, Access.READ_AND_WRITE)
1108 projection_task.use_face(
1109 self._solver_solver._patch_overlap_update, Access.TEMPORARY
1110 )
1111 projection_task.use_face(
1112 self._solver_solver._patch_overlap_current_time_step,
1113 get_access_to_persistent_face_data(
1114 self._solver_solver, exahype2_project, Access.WRITE
1115 ),
1116 )
1117 if is_last_stage and self.has_adaptive_time_step_size:
1118 projection_task.use_cell(self._solver_solver._max_eigenvalue, Access.WRITE)
1119 projection_task.add_action_set(
1120 ComputeMaxEigenvalue(self._solver_solver, guard=cell_guard)
1121 )
1122 projection_task.add_action_set(
1124 output,
1125 self._solver_solver._patch_overlap_update,
1126 guard=cell_guard,
1127 includes=includes,
1128 )
1129 )
1130
1132 projection_task.add_action_set(self.create_restriction_of_hanging_faces())
1133 # Sommerfeld boundary conditions advance the ghost grid cells
1134 # within the roll over, from the time stamp of this stage to the
1135 # one of the next stage (@ref exahype2.api.blockstructured.BoundaryConditions)
1136 next_relative_time_step_size = (
1137 1.0 if is_last_stage else relative_time_step_sizes[stage + 1]
1138 )
1139 projection_task.add_action_set(
1140 RollOverUpdatedFaceWithSommerfeldBoundaryConditions.create_roll_over(
1141 self._solver_solver,
1142 exahype2_project,
1143 guard=face_guard,
1144 includes=includes,
1145 time_step_size=(
1146 f"{next_relative_time_step_size - relative_time_step_sizes[stage]!r}"
1147 f" * repositories::{self._solver.get_name_of_global_instance()}.getMinTimeStepSize()"
1148 ),
1149 )
1150 )
1151 previous_stage_task = projection_task
1152
1153
1154 time_step_size_task = peano4.api.taskgraph.Task(
1155 name=f"{name}_Compute time step size",
1156 entity=Entity.CELL,
1157 may_spawn_background_tasks=False,
1158 )
1159 time_step_size_task.add_dependency(previous_stage_task)
1161 time_step_size_task.use_cell(
1162 self._solver_solver._max_eigenvalue, Access.READ_AND_DISCARD
1163 )
1164 time_step_size_task.add_action_set(
1166 self._solver_solver,
1167 self.patch_size,
1168 guard=cell_guard,
1169 cfl_constant=self.cfl_constant,
1170 )
1171 )
1172 else:
1173 time_step_size_task.add_action_set(
1175 self._solver_solver,
1176 self.patch_size,
1177 guard=cell_guard,
1178 time_step_size=self.time_step_size,
1179 )
1180 )
1181 time_step.add_task(time_step_size_task)
1182
1183
1188 dynamic_mesh_refinement_task = peano4.api.taskgraph.Task(
1189 name=f"{name}_Dynamic mesh refinement",
1190 entity=Entity.CELL,
1191 may_spawn_background_tasks=False,
1192 )
1193 dynamic_mesh_refinement_task.use_cell(
1194 self._solver_solver._patch, Access.READ_AND_WRITE
1195 )
1196 dynamic_mesh_refinement = create_dynamic_mesh_refinement_action_set(
1197 self._solver_solver,
1198 self.patch_size,
1199 guard=cell_guard,
1200 min_volume_size=self.min_volume_size,
1201 max_volume_size=self.max_volume_size,
1202 )
1203 if dynamic_mesh_refinement is not None:
1204 dynamic_mesh_refinement_task.add_action_set(dynamic_mesh_refinement)
1205 dynamic_mesh_refinement_task.add_action_set(
1206 toolbox.loadbalancing.api.actionsets.LoadBalancing(
1207 enable_load_balancing=True
1208 )
1209 )
1210 dynamic_mesh_refinement_task.add_dependency(time_step_size_task)
1211 dynamic_mesh_refinement_task.add_dependency(previous_stage_task)
1212 time_step.add_task(dynamic_mesh_refinement_task)
1213
1214 if peano4.api.Logger().is_visualisation:
1215 time_step.visualise(title="Time stepping task graph for {}".format(name))
1216 if peano4.api.Logger().is_text:
1217 time_step.log_as_text(title="Time stepping task graph for {}".format(name))
The concrete discretisation (Finite Volumes, ADER-DG, ...) realised by a Solver.
Definition Solver.py:60
Sommerfeld (radiative, outgoing) boundary conditions: waves leave the domain with little reflection,...
Default mesh adaptation of the fourth-order finite differences.
Fourth-order central Finite Differences with explicit Runge-Kutta time stepping, realised over a bloc...
__construct_kernel_call(self, function_name, additional_arguments)
Construct the call of a generated kernel.
"toolbox.blockstructured.api.RestrictHangingFaces" create_restriction_of_hanging_faces(self)
Action set which restricts the projected fine data from the hanging faces onto the coarse faces.
__add_data_to_repository(self, peano_project)
Add the data from __create_data_structures() to the Peano 4 project and configure their generators.
Solver.MeshAdaptation create_default_mesh_adaptation(self)
The finite differences adapt the mesh and balance the load through exahype2.api.rkfd4....
__create_data_structures(self)
Create the mesh data structures this numerical scheme requires.
__init__(self, Solver solver, int patch_size, int rk_order, float time_step_size=None, float cfl_constant=0.5, "CentralDifferencesWithRungeKutta.RightHandSideKernel" right_hand_side_kernel=RightHandSideKernel.CentralDifferences, advection_velocity=None, advected_quantities=None, float min_volume_size=None, float max_volume_size=None, "toolbox.blockstructured.api.InterpolateHangingFaces.Scheme" interpolation=toolbox.blockstructured.api.InterpolateHangingFaces.Scheme.Linear, "toolbox.blockstructured.api.RestrictHangingFaces.Scheme" restriction=toolbox.blockstructured.api.RestrictHangingFaces.Scheme.Averaging)
The constructor memorises the attributes and creates the data structures.
construct_output(self, exahype2_project, peano_project, time_step)
Add the data to the Peano 4 project, generate the kernels and add the tasks of one Runge-Kutta step t...
_right_hand_side_action_sets(self, stage, cell_guard)
Action sets of the task which evaluates the right-hand side of one stage.
Add advection terms with lopsided stencils to the right-hand side of one Runge-Kutta stage.
Combine the solution with the right-hand side estimates of the Runge-Kutta stages.
Store the maximum eigenvalue over all grid cells of a patch.
Determine the time step size of the next Runge-Kutta step and advance the solver's time stamp.
Evaluate the right-hand side of one Runge-Kutta stage over a patch.
Project a patch's data onto the overlap held on its surrounding faces, so the halo layers can be reco...
Butcher tableau of an explicit Runge-Kutta scheme.
Singleton logger for the Peano 4 Python API.
Definition Logger.py:37
Specialisation of array using Peano's tarch with a smart pointer.
Default superclass for any data model in Peano which is stored within the grid.
Definition DaStGen2.py:188
Patch implementation of a degree of freedom.
Definition Patch.py:7
One task within a peano4.api.taskgraph.Graph.
Definition Task.py:8
The task graph abstraction allows users to formalise their solver steps over small task graphs which ...
Definition __init__.py:1