Peano
Loading...
Searching...
No Matches
RusanovWithExplicitEuler.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 os
4import re
5
6import peano4.api
10import toolbox.loadbalancing.api.actionsets
11import exahype2.api
12from exahype2.api import Target
13import exahype2.api.dslhype as dslhype
14
15from ..Solver import Solver
16from ..blockstructured.BoundaryConditions import get_access_to_persistent_face_data
17from ..blockstructured.create_dynamic_mesh_refinement_action_set import (
18 create_dynamic_mesh_refinement_action_set,
19)
20from .actionsets import ApplyDirichletBoundaryConditions
21from .actionsets import ProjectPatchOntoFaces
22from .actionsets import RollOverUpdatedFaceWithSommerfeldBoundaryConditions
23from .actionsets import UpdateCell
24from .actionsets import ComputeGlobalCFLCondition
25
26
27
38
39
40
41def timeStepWithRusanov2D(patchData: "::exahype2::CellData<double, double>&") -> "void":
42 N = Integer("patchData.numberOfCells")
43 t = DataBlock([[0, N]], "patchData.t", "double")
44 dt = DataBlock([[0, N]], "patchData.dt", "double")
45 h = DataBlock([[0, N]], "patchData.cellSize", "tarch::la::Vector<2, double>")
46 x = DataBlock([[0, N]], "patchData.cellCentre", "tarch::la::Vector<2, double>")
47 maxEigenvalues = DataBlock([[0, N]], "patchData.maxEigenvalue", "double")
48
49 input = DataBlock([[0, NumberOfUnknowns + NumberOfAuxiliaryVariables], [-H, NV + H], [-H, NV + H], [0, N]], "patchData.QIn")
50 output = DataBlock([[0, NumberOfUnknowns + NumberOfAuxiliaryVariables], [0, NV], [0, NV], [0, N]], "patchData.QOut")
51 output = input[:, 0:NV, 0:NV]
52
53 if volumetricSource:
54 source = DataBlock([[0, NumberOfUnknowns], [0, NV], [0, NV], [0, N]], eval("volumetricSource", input[:, 0:NV, 0:NV], x, t))
55 output[0:NumberOfUnknowns, :, :] = output[0:NumberOfUnknowns, :, :] + dt * source
56
57 # The Rusanov flux at a face needs the eigenvalues of both adjacent
58 # volumes, i.e. the patch volumes plus the one halo layer across each
59 # face along the normal. We never evaluate the remaining halo (edges,
60 # corners), which holds no valid data.
61 maxEigenvaluesX = DataBlock([[0, 1], [-1, NV + 1], [0, NV], [0, N]], eval("maxEigenvalue", input[:, -1:NV+1, 0:NV], x, 0))
62 maxEigenvaluesY = DataBlock([[0, 1], [0, NV], [-1, NV + 1], [0, N]], eval("maxEigenvalue", input[:, 0:NV, -1:NV+1], x, 1))
63
64 lambdaLeft = max(maxEigenvaluesX[0:1, -1:NV-1, 0:NV], maxEigenvaluesX[0:1, 0:NV, 0:NV])
65 lambdaRight = max(maxEigenvaluesX[0:1, 0:NV, 0:NV], maxEigenvaluesX[0:1, 1:NV + 1, 0:NV])
66 lambdaBottom = max(maxEigenvaluesY[0:1, 0:NV, -1:NV-1], maxEigenvaluesY[0:1, 0:NV, 0:NV])
67 lambdaTop = max(maxEigenvaluesY[0:1, 0:NV, 0:NV], maxEigenvaluesY[0:1, 0:NV, 1:NV + 1])
68
69 fluxLeft = -1.0 * lambdaLeft * (input[0:NumberOfUnknowns, 0:NV, 0:NV] - input[0:NumberOfUnknowns, -1:NV-1, 0:NV])
70 fluxRight = -1.0 * lambdaRight * (input[0:NumberOfUnknowns, 1:NV+1, 0:NV] - input[0:NumberOfUnknowns, 0:NV, 0:NV])
71 output[0:NumberOfUnknowns, :, :] = output[0:NumberOfUnknowns, :, :] + dt / h(0) * (fluxLeft - fluxRight) * NV * 0.5
72
73 fluxBottom = -1.0 * lambdaBottom * (input[0:NumberOfUnknowns, 0:NV, 0:NV] - input[0:NumberOfUnknowns, 0:NV, -1:NV-1])
74 fluxTop = -1.0 * lambdaTop * (input[0:NumberOfUnknowns, 0:NV, 1:NV+1] - input[0:NumberOfUnknowns, 0:NV, 0:NV])
75 output[0:NumberOfUnknowns, :, :] = output[0:NumberOfUnknowns, :, :] + dt / h(1) * (fluxBottom - fluxTop) * NV * 0.5
76
77 if flux:
78 flux_x = DataBlock([[0, NumberOfUnknowns], [-H, NV + H], [0, NV], [0, N]], eval("flux", input[:, -1:NV+1, 0:NV], x, 0))
79 flux_y = DataBlock([[0, NumberOfUnknowns], [0, NV], [-H, NV + H], [0, N]], eval("flux", input[:, 0:NV, -1:NV+1], x, 1))
80
81 output[0:NumberOfUnknowns, :, :] = output[0:NumberOfUnknowns, :, :] + 0.5 * NV * dt / h(0) * (flux_x[:, -1:NV-1, 0:NV] - flux_x[:, 1:NV+1, 0:NV])
82 output[0:NumberOfUnknowns, :, :] = output[0:NumberOfUnknowns, :, :] + 0.5 * NV * dt / h(1) * (flux_y[:, 0:NV, -1:NV-1] - flux_y[:, 0:NV, 1:NV+1])
83
84 if nonconservativeProduct:
85 average_x = 0.5 * (input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, -1:NV, 0:NV] + input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV + 1, 0:NV])
86 delta_x = input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV + H, 0:NV] - input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, -H:NV, 0:NV]
87 nonconservative_x = DataBlock([[0, NumberOfUnknowns], [0, NV + H], [0, NV], [0, N]], eval("nonconservativeProduct", average_x, delta_x, x, h, t, dt, 0))
88 output[0:NumberOfUnknowns, :, :] = output[0:NumberOfUnknowns, :, :] + 0.5 * NV * dt / h(0) * (-nonconservative_x[:, 0:NV, 0:NV] - nonconservative_x[:, 1:NV+1, 0:NV])
89
90 average_y = 0.5 * (input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, -1:NV] + input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, 0:NV + 1])
91 delta_y = input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, 0:NV + H] - input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, -H:NV]
92 nonconservative_y = DataBlock([[0, NumberOfUnknowns], [0, NV], [0, NV + H], [0, N]], eval("nonconservativeProduct", average_y, delta_y, x, h, t, dt, 1))
93 output[0:NumberOfUnknowns, :, :] = output[0:NumberOfUnknowns, :, :] + 0.5 * NV * dt / h(1) * (-nonconservative_y[:, 0:NV, 0:NV] - nonconservative_y[:, 0:NV, 1:NV+1])
94
95 if maxEigenvalue:
96 maxEigenvaluesX2 = DataBlock([[0, 1], [0, NV], [0, NV], [0, N]], eval("maxEigenvalue", output, x, 0))
97 maxEigenvaluesY2 = DataBlock([[0, 1], [0, NV], [0, NV], [0, N]], eval("maxEigenvalue", output, x, 1))
98 maxEigenvaluesPerVolume = max(maxEigenvaluesX2, maxEigenvaluesY2)
99 maxEigenvalues = max(maxEigenvaluesPerVolume)
100
101
102
103def timeStepWithRusanov3D(patchData: "::exahype2::CellData<double, double>&") -> "void":
104 N = Integer("patchData.numberOfCells")
105 t = DataBlock([[0, N]], "patchData.t", "double")
106 dt = DataBlock([[0, N]], "patchData.dt", "double")
107 h = DataBlock([[0, N]], "patchData.cellSize", "tarch::la::Vector<3, double>")
108 x = DataBlock([[0, N]], "patchData.cellCentre", "tarch::la::Vector<3, double>")
109 maxEigenvalues = DataBlock([[0, N]], "patchData.maxEigenvalue", "double")
110
111 input = DataBlock([[0, NumberOfUnknowns + NumberOfAuxiliaryVariables], [-H, NV + H], [-H, NV + H], [-H, NV + H], [0, N]], "patchData.QIn")
112 output = DataBlock([[0, NumberOfUnknowns + NumberOfAuxiliaryVariables], [0, NV], [0, NV], [0, NV], [0, N]], "patchData.QOut")
113 output = input[:, 0:NV, 0:NV, 0:NV]
114
115 if volumetricSource:
116 source = DataBlock([[0, NumberOfUnknowns], [0, NV], [0, NV], [0, NV], [0, N]], eval("volumetricSource", input[:, 0:NV, 0:NV, 0:NV], x, t))
117 output[0:NumberOfUnknowns, :, :, :] = output[0:NumberOfUnknowns, :, :, :] + dt * source
118
119 # As in 2d: patch volumes plus the one halo layer across each face along
120 # the normal only, never the edges and corners of the halo
121 maxEigenvaluesX = DataBlock([[0, 1], [-1, NV + 1], [0, NV], [0, NV], [0, N]], eval("maxEigenvalue", input[:, -1:NV+1, 0:NV, 0:NV], x, 0))
122 maxEigenvaluesY = DataBlock([[0, 1], [0, NV], [-1, NV + 1], [0, NV], [0, N]], eval("maxEigenvalue", input[:, 0:NV, -1:NV+1, 0:NV], x, 1))
123 maxEigenvaluesZ = DataBlock([[0, 1], [0, NV], [0, NV], [-1, NV + 1], [0, N]], eval("maxEigenvalue", input[:, 0:NV, 0:NV, -1:NV+1], x, 2))
124
125 lambdaLeft = max(maxEigenvaluesX[0:1, -1:NV-1, 0:NV, 0:NV], maxEigenvaluesX[0:1, 0:NV, 0:NV, 0:NV])
126 lambdaRight = max(maxEigenvaluesX[0:1, 0:NV, 0:NV, 0:NV], maxEigenvaluesX[0:1, 1:NV + 1, 0:NV, 0:NV])
127 lambdaBottom = max(maxEigenvaluesY[0:1, 0:NV, -1:NV-1, 0:NV], maxEigenvaluesY[0:1, 0:NV, 0:NV, 0:NV])
128 lambdaTop = max(maxEigenvaluesY[0:1, 0:NV, 0:NV, 0:NV], maxEigenvaluesY[0:1, 0:NV, 1:NV + 1, 0:NV])
129 lambdaNear = max(maxEigenvaluesZ[0:1, 0:NV, 0:NV, -1:NV-1], maxEigenvaluesZ[0:1, 0:NV, 0:NV, 0:NV])
130 lambdaFar = max(maxEigenvaluesZ[0:1, 0:NV, 0:NV, 0:NV], maxEigenvaluesZ[0:1, 0:NV, 0:NV, 1:NV + 1])
131
132 fluxLeft = -1.0 * lambdaLeft * (input[0:NumberOfUnknowns, 0:NV, 0:NV, 0:NV] - input[0:NumberOfUnknowns, -1:NV-1, 0:NV, 0:NV])
133 fluxRight = -1.0 * lambdaRight * (input[0:NumberOfUnknowns, 1:NV+1, 0:NV, 0:NV] - input[0:NumberOfUnknowns, 0:NV, 0:NV, 0:NV])
134 output[0:NumberOfUnknowns, :, :, :] = output[0:NumberOfUnknowns, :, :, :] + dt / h(0) * (fluxLeft - fluxRight) * NV * 0.5
135 fluxBottom = -1.0 * lambdaBottom * (input[0:NumberOfUnknowns, 0:NV, 0:NV, 0:NV] - input[0:NumberOfUnknowns, 0:NV, -1:NV-1, 0:NV])
136 fluxTop = -1.0 * lambdaTop * (input[0:NumberOfUnknowns, 0:NV, 1:NV+1, 0:NV] - input[0:NumberOfUnknowns, 0:NV, 0:NV, 0:NV])
137 output[0:NumberOfUnknowns, :, :, :] = output[0:NumberOfUnknowns, :, :, :] + dt / h(1) * (fluxBottom - fluxTop) * NV * 0.5
138
139 fluxNear = -1.0 * lambdaNear * (input[0:NumberOfUnknowns, 0:NV, 0:NV, 0:NV] - input[0:NumberOfUnknowns, 0:NV, 0:NV, -1:NV-1])
140 fluxFar = -1.0 * lambdaFar * (input[0:NumberOfUnknowns, 0:NV, 0:NV, 1:NV+1] - input[0:NumberOfUnknowns, 0:NV, 0:NV, 0:NV])
141 output[0:NumberOfUnknowns, :, :, :] = output[0:NumberOfUnknowns, :, :, :] + dt / h(2) * (fluxNear - fluxFar) * NV * 0.5
142
143 if flux:
144 flux_x = DataBlock([[0, NumberOfUnknowns], [-H, NV + H], [0, NV], [0, NV], [0, N]], eval("flux", input[:, -1:NV+1, 0:NV, 0:NV], x, 0))
145 flux_y = DataBlock([[0, NumberOfUnknowns], [0, NV], [-H, NV + H], [0, NV], [0, N]], eval("flux", input[:, 0:NV, -1:NV+1, 0:NV], x, 1))
146 flux_z = DataBlock([[0, NumberOfUnknowns], [0, NV], [0, NV], [-H, NV + H], [0, N]], eval("flux", input[:, 0:NV, 0:NV, -1:NV+1], x, 2))
147 output[0:NumberOfUnknowns, :, :, :] = output[0:NumberOfUnknowns, :, :, :] + 0.5 * NV * dt / h(0) * (flux_x[:, -1:NV-1, 0:NV, 0:NV] - flux_x[:, 1:NV+1, 0:NV, 0:NV])
148 output[0:NumberOfUnknowns, :, :, :] = output[0:NumberOfUnknowns, :, :, :] + 0.5 * NV * dt / h(1) * (flux_y[:, 0:NV, -1:NV-1, 0:NV] - flux_y[:, 0:NV, 1:NV+1, 0:NV])
149 output[0:NumberOfUnknowns, :, :, :] = output[0:NumberOfUnknowns, :, :, :] + 0.5 * NV * dt / h(2) * (flux_z[:, 0:NV, 0:NV, -1:NV-1] - flux_z[:, 0:NV, 0:NV, 1:NV+1])
150
151 if nonconservativeProduct:
152 average_x = 0.5 * (input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, -1:NV, 0:NV, 0:NV] + input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV + 1, 0:NV, 0:NV])
153 delta_x = input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV + H, 0:NV, 0:NV] - input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, -H:NV, 0:NV, 0:NV]
154 nonconservative_x = DataBlock([[0, NumberOfUnknowns], [0, NV + H], [0, NV], [0, NV], [0, N]], eval("nonconservativeProduct", average_x, delta_x, x, h, t, dt, 0))
155 output[0:NumberOfUnknowns, :, :, :] = output[0:NumberOfUnknowns, :, :, :] + 0.5 * NV * dt / h(0) * (-nonconservative_x[:, 0:NV, 0:NV, 0:NV] - nonconservative_x[:, 1:NV+1, 0:NV, 0:NV])
156
157 average_y = 0.5 * (input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, -1:NV, 0:NV] + input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, 0:NV + 1, 0:NV])
158 delta_y = input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, 0:NV + H, 0:NV] - input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, -H:NV, 0:NV]
159 nonconservative_y = DataBlock([[0, NumberOfUnknowns], [0, NV], [0, NV + H], [0, NV], [0, N]], eval("nonconservativeProduct", average_y, delta_y, x, h, t, dt, 1))
160 output[0:NumberOfUnknowns, :, :, :] = output[0:NumberOfUnknowns, :, :, :] + 0.5 * NV * dt / h(1) * (-nonconservative_y[:, 0:NV, 0:NV, 0:NV] - nonconservative_y[:, 0:NV, 1:NV+1, 0:NV])
161
162 average_z = 0.5 * (input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, 0:NV, -1:NV] + input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, 0:NV, 0:NV + 1])
163 delta_z = input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, 0:NV, 0:NV + H] - input[0:NumberOfUnknowns + NumberOfAuxiliaryVariables, 0:NV, 0:NV, -H:NV]
164 nonconservative_z = DataBlock([[0, NumberOfUnknowns], [0, NV], [0, NV], [0, NV + H], [0, N]], eval("nonconservativeProduct", average_z, delta_z, x, h, t, dt, 2))
165 output[0:NumberOfUnknowns, :, :, :] = output[0:NumberOfUnknowns, :, :, :] + 0.5 * NV * dt / h(2) * (-nonconservative_z[:, 0:NV, 0:NV, 0:NV] - nonconservative_z[:, 0:NV, 0:NV, 1:NV+1])
166
167 if maxEigenvalue:
168 maxEigenvaluesX2 = DataBlock([[0, 1], [0, NV], [0, NV], [0, NV], [0, N]], eval("maxEigenvalue", output, x, 0))
169 maxEigenvaluesY2 = DataBlock([[0, 1], [0, NV], [0, NV], [0, NV], [0, N]], eval("maxEigenvalue", output, x, 1))
170 maxEigenvaluesZ2 = DataBlock([[0, 1], [0, NV], [0, NV], [0, NV], [0, N]], eval("maxEigenvalue", output, x, 2))
171 maxEigenvaluesXY = max(maxEigenvaluesX2, maxEigenvaluesY2)
172 maxEigenvaluesPerVolume = max(maxEigenvaluesXY, maxEigenvaluesZ2)
173 maxEigenvalues = max(maxEigenvaluesPerVolume)
174
175
177 """!
178
179 Finite Volumes with a Rusanov Riemann solver and explicit Euler time
180 stepping, realised over a block-structured mesh.
181
182 """
183
185 self,
186 solver: Solver,
187 patch_size: int,
188 overlap: int = 1,
189 cfl_constant: float = 0.9,
190 interpolation: "toolbox.blockstructured.api.InterpolateHangingFaces.Scheme" = toolbox.blockstructured.api.InterpolateHangingFaces.Scheme.Linear,
191 restriction: "toolbox.blockstructured.api.RestrictHangingFaces.Scheme" = toolbox.blockstructured.api.RestrictHangingFaces.Scheme.Averaging,
192 min_volume_size: float = None,
193 max_volume_size: float = None,
194 ):
195 """!
196
197 The constructor memorises the attributes. However, it does not do this
198 within this class, but it adds the important properties straight to the
199 solver, so anybody else working against this solver knows about the
200 properties introduced by the numerical scheme.
201
202 @see @ref __create_data_structures()
203
204 patch_size: int
205 Number of Finite Volumes per axis within one patch (i.e. within
206 one mesh cell).
207
208 overlap: int
209 Number of Finite Volumes the halo layer reconstructed around a
210 patch - from the neighbouring patches' faces - is wide.
211
212 cfl_constant: float
213 Safety factor the admissible time step size (@see
214 exahype2.api.fv.actionsets.ComputeGlobalCFLCondition,
215 which this is passed straight through to) is scaled down by,
216 relative to the theoretical CFL=1 stability limit
217 h / (Dimensions * lambda_max). 0.9 is a
218 conservative default for an explicit-Euler Finite Volume scheme;
219 this codebase has no prior precedent value to match (grep for
220 "cfl" outside this class finds only exaparty's unrelated SPH
221 solvers, which default to 0.1).
222
223 interpolation: toolbox.blockstructured.api.InterpolateHangingFaces.Scheme
224 How the scheme initialises the halo of a fine patch next to a
225 coarser patch on an adaptive mesh. Whenever the time step
226 reconstructs the halo of a fine cell, it interpolates the data of
227 the adjacent coarse face onto the fine cell's hanging face
228 (toolbox.blockstructured.api.InterpolateHangingFaces). The
229 default is linear interpolation.
230
231 restriction: toolbox.blockstructured.api.RestrictHangingFaces.Scheme
232 How the scheme hands the data of fine patches over to an adjacent
233 coarser patch. Whenever the fine cells project their data onto
234 their faces, the scheme restricts the data of the hanging faces
235 onto the coarse face (toolbox.blockstructured.api.RestrictHangingFaces).
236 The default is averaging.
237
238 min_volume_size: float
239 The time step asks for a refinement of a cell only if its volumes
240 are at least of this size (@ref exahype2.api.blockstructured.DynamicMeshRefinement).
241 None (the default) does not bound the refinement.
242
243 max_volume_size: float
244 The time step asks for an erase, i.e. coarsening, of a cell only
245 if the volumes of the coarser cell would be strictly smaller than
246 this size. None (the default) does not bound the coarsening.
247
248 On a regular mesh, there are no hanging faces, and the two schemes
249 are never used. The fluxes along a face between a coarse and a fine
250 patch are not corrected, i.e. the scheme is not exactly conservative
251 on adaptive meshes.
252
253 """
254 super().__init__(solver)
255 assert (
256 min_volume_size is None
257 or max_volume_size is None
258 or min_volume_size <= max_volume_size
259 ), f"min_volume_size={min_volume_size} has to be smaller than or equal to max_volume_size={max_volume_size}"
260 self.min_volume_size = min_volume_size
261 self.max_volume_size = max_volume_size
262 self.interpolation = interpolation
263 self.restriction = restriction
264 self.patch_size = patch_size
265 self.overlap = overlap
266 # Set in construct_output(): the generated kernels live in the project
267 # namespace, as their names are solver-specific
269 self.cfl_constant = cfl_constant
271
272 @property
273 def kernel_function_name(self) -> str:
274 """!
275 Name of the generated time step kernel. It carries the solver name,
276 as do the kernels/<SolverName>_rusanov*.h files holding it, so two
277 solvers' kernels do not clash.
278 """
279 return self._solver_solver.name + "_timeStepWithRusanov"
280
282 """!
283
284 Create the mesh data structures (a Finite Volume patch per cell,
285 plus the face-based overlap patches used to reconstruct its halo)
286 this numerical scheme requires.
287
288 Ported from the legacy exahype2.solvers.fv.FV.create_data_structures():
289 one @ref peano4.api.datamodel.Patch of patch_size^d Finite Volumes
290 per mesh cell (_patch), plus a halo-augmented copy of it
291 (_patch_with_halo, (patch_size+2*overlap)^d, @see
292 toolbox.blockstructured.api.ReconstructPatchWithHalo) and two overlap
293 patches of width 2*overlap attached to the mesh faces:
294
295 - _patch_overlap_current_time_step: the persistent halo data
296 toolbox.blockstructured.api.ReconstructPatchWithHalo reads from to
297 rebuild _patch_with_halo (@see construct_output(), which hands it
298 _patch/_patch_overlap_current_time_step/_patch_with_halo directly
299 rather than the whole solver).
300 - _patch_overlap_update: the projection of the just-updated _patch
301 accumulated by the current sweep (@see
302 exahype2.api.fv.actionsets.ProjectPatchOntoFaces), rolled over
303 into _patch_overlap_current_time_step by
304 exahype2.api.fv.actionsets.RollOverUpdatedFace once the update is
305 complete.
306 - _max_eigenvalue: one double per cell, named after the solver
307 followed by MaxEigenvalue (e.g. EulerFVSmoothMaxEigenvalue),
308 which holds the maximum eigenvalue of
309 the patch after its last update
310 (@see exahype2.api.fv.actionsets.UpdateCell).
311 exahype2.api.fv.actionsets.ComputeGlobalCFLCondition reduces these
312 values into the next time step size. It is modelled via DaStGen2
313 as an array of cardinality one. The array is a
314 peano4.api.dastgen2.Peano4SmartPointerDoubleArray, i.e. the value
315 resides on the heap.
316
317 All five are assigned onto self._solver (the shared
318 exahype2.api.Solver instance both this numerical scheme and its
319 surrounding solver's Solver.GridConstruction/Solver.Plotter
320 counterparts hold a reference to as self._solver), not onto self,
321 since the remaining FV action sets that need them - @see
322 exahype2.api.fv.actionsets.ApplyDirichletBoundaryConditions,
323 exahype2.api.fv.actionsets.UpdateCell,
324 exahype2.api.fv.actionsets.ComputeGlobalCFLCondition - are handed that
325 shared solver and look the patches up as solver._patch /
326 solver._patch_overlap_*.
327
328 Storage is hardwired to smart pointers (peano4.api.datamodel's
329 PatchToDoubleArrayWithSmartPointer), matching the legacy default
330 (Storage.SmartPointers for both cell and face data) - the legacy
331 Storage enum's other two backends (CallStack, Heap) are not
332 exposed here.
333
334 Two pieces of the legacy method are deliberately not ported yet:
335 - The cell/face label creation
336 (exahype2.grid.create_cell_label()/create_face_label()) - that
337 module has not been ported to the new API.
338 - The load/store/compute guards
339 (FV._provide_cell_data_to_compute_kernels_default_guard() and
340 friends) - they are keyed off the legacy SolverState state
341 machine, which the task-graph architecture has no equivalent of:
342 a task only ever runs within its own phase's task graph in the
343 first place, so "are we still constructing the grid" does not
344 need to be re-checked at runtime the same way.
345
346 """
347 total_unknowns = self._solver_solver.unknowns + self._solver_solver.auxiliary_variables
348 identifier = self._solver_solver._unknown_identifier()
349
351 (self.patch_size, self.patch_size, self.patch_size),
352 total_unknowns,
353 identifier,
354 )
355 self._solver_solver._patch_with_halo = peano4.api.datamodel.Patch(
356 (self.patch_size+2*self.overlap, self.patch_size+2*self.overlap, self.patch_size+2*self.overlap),
357 total_unknowns,
358 identifier + "WithHalo",
359 )
360 self._solver_solver._patch_overlap_current_time_step = peano4.api.datamodel.Patch(
361 (2 * self.overlap, self.patch_size, self.patch_size),
362 total_unknowns,
363 identifier + "CurrentTimeStep",
364 )
365 self._solver_solver._patch_overlap_update = peano4.api.datamodel.Patch(
366 (2 * self.overlap, self.patch_size, self.patch_size),
367 total_unknowns,
368 identifier + "Update",
369 )
370 self._solver_solver._max_eigenvalue = peano4.api.datamodel.DaStGen2(self._solver_solver.name + "MaxEigenvalue")
371 self._solver_solver._max_eigenvalue.data.add_attribute(
373 )
374
375
376 def __add_data_to_repository(self, peano_project):
377 """!
378
379 Consult @ref page_exahype_programmers_guide_new_solver for some information
380 how this routine fits to the typical workflow when we introduce a new
381 solver.
382
383 """
384 peano4.api.Logger().log("""
385Patch data
386----------
387{}
388Patch overlap data
389------------------
390{}
391{}""".format( str(self._solver_solver._patch), str(self._solver_solver._patch_overlap_current_time_step), str(self._solver_solver._patch_overlap_update) ) )
392
393 peano_project.datamodel.add_cell(self._solver_solver._patch)
394 peano_project.datamodel.add_cell(self._solver_solver._patch_with_halo)
395 peano_project.datamodel.add_face(self._solver_solver._patch_overlap_current_time_step)
396 peano_project.datamodel.add_face(self._solver_solver._patch_overlap_update)
397 peano_project.datamodel.add_cell(self._solver_solver._max_eigenvalue)
398
399 # The task graph compiler only uses, loads and stores data where the
400 # solver's grid selector holds, too (@see peano4.api.datamodel.DoF).
401 for patch in (
402 self._solver_solver._patch,
403 self._solver_solver._patch_with_halo,
404 self._solver_solver._patch_overlap_current_time_step,
405 self._solver_solver._patch_overlap_update,
406 self._solver_solver._max_eigenvalue,
407 ):
408 patch.grid_selector = self._solver_solver.grid_selector
409
411 self._solver_solver._patch, "double"
412 )
414 self._solver_solver._patch_with_halo, "double"
415 )
416 self._solver_solver._patch_overlap_current_time_step.generator = peano4.api.datamodel.PatchToDoubleArrayWithSmartPointer(
417 self._solver_solver._patch_overlap_current_time_step, "double"
418 )
419 self._solver_solver._patch_overlap_update.generator = peano4.api.datamodel.PatchToDoubleArrayWithSmartPointer(
420 self._solver_solver._patch_overlap_update, "double"
421 )
422
423 self._solver_solver._patch_overlap_current_time_step.generator.merge_method_definition = (
424 toolbox.blockstructured.api.get_face_merge_implementation(
425 self._solver_solver._patch_overlap_current_time_step
426 )
427 )
428 self._solver_solver._patch_overlap_current_time_step.generator.includes += """
429#include "peano4/utils/Loop.h"
430"""
431
432 # Only faces selected by the solver's grid selector hold valid data,
433 # so only those are exchanged.
434 self._solver_solver._patch_overlap_current_time_step.generator.send_condition = self._solver_solver.grid_selector.createFaceGuard()
435 self._solver_solver._patch_overlap_current_time_step.generator.receive_and_merge_condition = self._solver_solver.grid_selector.createFaceGuard()
436
437 self._solver_solver._patch_overlap_update.generator.send_condition = "false"
438 self._solver_solver._patch_overlap_update.generator.receive_and_merge_condition = "false"
439
440 def create_default_mesh_adaptation(self) -> Solver.MeshAdaptation:
441 """!
442
443 The Finite Volume schemes adapt the mesh and balance the load
444 through exahype2.api.fv.AdaptMeshAndBalanceLoad by default
445
446 """
447 from .AdaptMeshAndBalanceLoad import AdaptMeshAndBalanceLoad
448
450
452 exahype2_project,
453 peano_project,
454 time_step):
455 """!
456
457 """
458 peano4.api.Logger().log("Create time stepping task graph for Finite Volume scheme over block-structured meshes with Rusanov flux")
459
460 self.__add_data_to_repository(peano_project)
461
462 self.kernel_namespace = peano_project.namespace
463 # Construct the call first, as it validates the PDE terms' variants
464 self._solver_solver.compute_kernel_call = self.__construct_kernel_call(Target.Serial, False)
465 self.__create_time_step_functions(peano_project.directory)
466
467 reconstruction_task = peano4.api.taskgraph.Task(name=f"{self._solver.name}_Reconstruct", entity=peano4.api.taskgraph.Task.GridEntity.CELL)
468 update_cell_task = peano4.api.taskgraph.Task(name=f"{self._solver.name}_Update cell", entity=peano4.api.taskgraph.Task.GridEntity.CELL)
469 project_on_faces_task = peano4.api.taskgraph.Task(name=f"{self._solver.name}_Project on faces", entity=peano4.api.taskgraph.Task.GridEntity.CELL)
470 compute_cfl_condition_task = peano4.api.taskgraph.Task(name=f"{self._solver.name}_Compute global CFL condition", entity=peano4.api.taskgraph.Task.GridEntity.CELL, may_spawn_background_tasks=False)
471
472 time_step.add_task(reconstruction_task)
473 time_step.add_task(update_cell_task)
474 time_step.add_task(project_on_faces_task)
475 time_step.add_task(compute_cfl_condition_task)
476
477 # The load balancing is switched off throughout the time step, and
478 # switched on again only at its very end, so it may then decide on
479 # splits. Peano carries these splits out in the subsequent mesh sweep.
480 reconstruction_task.add_action_set(
481 toolbox.loadbalancing.api.actionsets.ToggleLoadBalancing(
482 enable_load_balancing=False, plug_into_prepare_traversal=True
483 )
484 )
485
486 # The very last task evaluates the adaptivity criterion if the
487 # solver has one (create_dynamic_mesh_refinement_action_set()), i.e. it
488 # collects refine and erase requests for the mesh adaptation phase,
489 # and it switches the load balancing on again and informs it that
490 # the time step has finished.
491 dynamic_mesh_refinement_task = peano4.api.taskgraph.Task(
492 name=f"{self._solver.name}_Dynamic mesh refinement",
493 entity=peano4.api.taskgraph.Task.GridEntity.CELL,
494 may_spawn_background_tasks=False,
495 )
496 dynamic_mesh_refinement_task.use_cell(self._solver_solver._patch, peano4.api.taskgraph.Task.DataAccess.READ_AND_WRITE)
497 dynamic_mesh_refinement = create_dynamic_mesh_refinement_action_set(
498 self._solver_solver,
499 self.patch_size,
500 guard=self._solver_solver.grid_selector.createCellGuard(),
501 min_volume_size=self.min_volume_size,
502 max_volume_size=self.max_volume_size,
503 )
504 if dynamic_mesh_refinement is not None:
505 dynamic_mesh_refinement_task.add_action_set(dynamic_mesh_refinement)
506 dynamic_mesh_refinement_task.add_action_set(
507 toolbox.loadbalancing.api.actionsets.LoadBalancing(enable_load_balancing=True)
508 )
509 dynamic_mesh_refinement_task.add_dependency(compute_cfl_condition_task)
510 dynamic_mesh_refinement_task.add_dependency(project_on_faces_task)
511 time_step.add_task(dynamic_mesh_refinement_task)
512
513
514 reconstruction_task.use_cell(self._solver_solver._patch, peano4.api.taskgraph.Task.DataAccess.READ_AND_WRITE)
515 reconstruction_task.use_cell(self._solver_solver._patch_with_halo, peano4.api.taskgraph.Task.DataAccess.WRITE)
516
518 reconstruction_task.use_face(
519 self._solver_solver._patch_overlap_current_time_step,
520 get_access_to_persistent_face_data(self._solver_solver, exahype2_project, peano4.api.taskgraph.Task.DataAccess.READ_AND_DISCARD),
521 )
522 reconstruction_task.add_action_set(
523 toolbox.blockstructured.api.ReconstructPatchWithHalo(
524 patch=self._solver_solver._patch,
525 patch_overlap=self._solver_solver._patch_overlap_current_time_step,
526 reconstructed_patch=self._solver_solver._patch_with_halo,
527 guard=self._solver_solver.grid_selector.createCellGuard(),
528 add_assertions_to_halo_exchange=False,
529 additional_includes=self._solver_solver._get_default_includes() + self._solver_solver.user_solver_includes,
530 )
531 )
532
534 reconstruction_task.add_action_set(
535 toolbox.blockstructured.api.InterpolateHangingFaces(
536 self._solver_solver._patch_overlap_current_time_step,
537 scheme=self.interpolation,
538 additional_includes=self._solver_solver._get_default_includes() + self._solver_solver.user_solver_includes,
539 )
540 )
541
544 ApplyDirichletBoundaryConditions.add_task_to_time_step(self._solver_solver, exahype2_project, time_step, reconstruction_task)
545
546
547 update_cell_task.add_dependency(reconstruction_task)
548 update_cell_task.use_cell(self._solver_solver._patch_with_halo, peano4.api.taskgraph.Task.DataAccess.READ_AND_DISCARD)
549 update_cell_task.use_cell(self._solver_solver._patch, peano4.api.taskgraph.Task.DataAccess.WRITE)
550 update_cell_task.use_cell(self._solver_solver._max_eigenvalue, peano4.api.taskgraph.Task.DataAccess.WRITE)
551 update_cell_task.add_action_set(
553 self._solver_solver,
554 guard=self._solver_solver.grid_selector.createCellGuard(),
555 )
556 )
557 update_cell_task.tags.add(Solver.SolutionUpdateTaskTag)
558
559
561 compute_cfl_condition_task.add_dependency(update_cell_task)
562 compute_cfl_condition_task.use_cell(self._solver_solver._max_eigenvalue, peano4.api.taskgraph.Task.DataAccess.READ_AND_DISCARD)
563 compute_cfl_condition_task.add_action_set(
565 self._solver_solver,
566 self.cfl_constant,
567 self.patch_size,
568 guard=self._solver_solver.grid_selector.createCellGuard(),
569 )
570 )
571
572
573 project_on_faces_task.add_dependency(update_cell_task)
574 project_on_faces_task.use_cell(self._solver_solver._patch, peano4.api.taskgraph.Task.DataAccess.READ_AND_WRITE)
575 project_on_faces_task.use_face(self._solver_solver._patch_overlap_update, peano4.api.taskgraph.Task.DataAccess.TEMPORARY)
576 project_on_faces_task.use_face(
577 self._solver_solver._patch_overlap_current_time_step,
578 get_access_to_persistent_face_data(self._solver_solver, exahype2_project, peano4.api.taskgraph.Task.DataAccess.WRITE),
579 )
580 project_on_faces_task.add_action_set(
582 self._solver_solver._patch,
583 self._solver_solver._patch_overlap_update,
584 guard=self._solver_solver.grid_selector.createCellGuard(),
585 includes=self._solver_solver._get_default_includes() + self._solver_solver.user_solver_includes,
586 )
587 )
588
590 project_on_faces_task.add_action_set(self.create_restriction_of_hanging_faces())
591
593 project_on_faces_task.add_action_set(
594 RollOverUpdatedFaceWithSommerfeldBoundaryConditions.create_roll_over(
595 self._solver_solver,
596 exahype2_project,
597 guard=self._solver_solver.grid_selector.createFaceGuard(),
598 includes=self._solver_solver._get_default_includes() + self._solver_solver.user_solver_includes,
599 time_step_size=f"repositories::{self._solver.get_name_of_global_instance()}.getMinTimeStepSize()",
600 )
601 )
602
603 if peano4.api.Logger().is_visualisation:
604 time_step.visualise(title="Time stepping task graph for {}".format(self._solver_solver.name))
605 if peano4.api.Logger().is_text:
606 time_step.log_as_text(title="Time stepping task graph for {}".format(self._solver_solver.name))
607
608 def create_restriction_of_hanging_faces(self) -> "toolbox.blockstructured.api.RestrictHangingFaces":
609 """!
610
611 Action set which restricts the projected fine data from the hanging
612 faces onto the coarse faces
613
614 Every task which projects the solution onto the faces has to host
615 this action set, so the faces between coarse and fine patches hold
616 consistent data afterwards. Besides the time step, this is the
617 projection at the end of the setup
618 (@ref exahype2.api.fv.SetupSimulationOverRegularGrid). The task has
619 to write all of _patch_overlap_update, as averaging clears it first.
620
621 """
622 return toolbox.blockstructured.api.RestrictHangingFaces(
623 self._solver_solver._patch_overlap_update,
624 scheme=self.restriction,
625 clear_guard=self._solver_solver.grid_selector.createFaceGuard(),
626 additional_includes=self._solver_solver._get_default_includes() + self._solver_solver.user_solver_includes,
627 )
628
629 def __create_time_step_functions(self, output_directory):
630 """!
631
632 Calling this function is delayed until all PDE term information is provided.
633 In this way we can avoid generating code that will not be used. The following
634 information is required to translate a Python function using DSLHyPE:
635 - template_parameters: the variables that are used within the Python function
636 to represent external information
637 - functor_parameters: the PDE terms that can be used by the solver
638 - function: the Python function itself
639
640 TODO: Currently the MLIR types are passed manually here, this should ideally
641 be inferred
642
643 """
644
645 template_parameters = [
649 dslhype.SyntaxTree.Argument("NumberOfAuxiliaryVariables", dslhype.SyntaxTree.TInteger())
650 ]
651
652 functor_parameters = [
653 dslhype.SyntaxTree.Argument("flux", dslhype.SyntaxTree.TCustom("const ::exahype2::fv::rusanov::FluxFunctor&"), "(!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, i32, !llvm.ptr) -> ()"),
654 dslhype.SyntaxTree.Argument("nonconservativeProduct", dslhype.SyntaxTree.TCustom("const ::exahype2::fv::rusanov::NonconservativeProductFunctor&"), "(!llvm.ptr, !llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, i32, !llvm.ptr) -> ()"),
655 dslhype.SyntaxTree.Argument("volumetricSource", dslhype.SyntaxTree.TCustom("const ::exahype2::fv::rusanov::VolumetricSourceFunctor&"), "(!llvm.ptr, !llvm.ptr, f64, !llvm.ptr) -> ()"),
656 dslhype.SyntaxTree.Argument("maxEigenvalue", dslhype.SyntaxTree.TCustom("const ::exahype2::fv::rusanov::MaxEigenvalueFunctor&"), "(!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, i32, !llvm.ptr) -> ()")
657 ]
658
659 time_step_dsl_function_2d = timeStepWithRusanov2D
660 time_step_dsl_function_3d = timeStepWithRusanov3D
661 dslhype.translate_dslhype_function(
662 function = time_step_dsl_function_2d,
663 function_namespace = self.kernel_namespace,
664 template_parameters=template_parameters,
665 functor_parameters=functor_parameters,
666 pde_terms=self._solver_solver.pde_terms,
667 output_file_name=self._solver_solver.name + "_rusanov2d",
668 target=dslhype.Target.Serial,
669 stateless=False,
670 function_name=self.kernel_function_name,
671 output_directory=output_directory,
672 )
673 dslhype.translate_dslhype_function(
674 function = time_step_dsl_function_3d,
675 function_namespace = self.kernel_namespace,
676 template_parameters=template_parameters,
677 functor_parameters=functor_parameters,
678 pde_terms=self._solver_solver.pde_terms,
679 output_file_name=self._solver_solver.name + "_rusanov3d",
680 target=dslhype.Target.Serial,
681 stateless=False,
682 function_name=self.kernel_function_name,
683 output_directory=output_directory,
684 )
685 file = open(os.path.join(output_directory, "kernels", f"{self._solver.name}_rusanov.h"), "w")
686 file.write(f"""#if Dimensions == 2
687 #include "{self._solver.name}_rusanov2d.h"
688#else
689 #include "{self._solver.name}_rusanov3d.h"
690#endif""")
691 file.close()
692
693 def __construct_kernel_call(self, target: Target, stateless: bool):
694 """!
695
696 This function constructs the calls to the compute kernels based on the
697 application PDE terms and the version chosen.
698
699 """
700 available_terms = ["flux", "nonconservativeProduct", "volumetricSource", "maxEigenvalue"]
701
702 if stateless:
703 return f"""::{"::".join(self.kernel_namespace)}::{self.kernel_function_name}<
704 {self.patch_size},
705 {self.overlap},
706 {self._solver.unknowns},
707 {self._solver.auxiliary_variables}
708 >(
709 patchData,
710 {self._solver.name}
711 );"""
712 else:
713 # Variants per term the kernel takes: {name: {stateless: PDETerm}}
714 variants = {}
715 for application_term in self._solver_solver.pde_terms:
716 if application_term.NAME in available_terms:
717 variants.setdefault(application_term.NAME, {})[application_term.stateless] = application_term
718
719 terms_with_both_variants = [name for name in variants if len(variants[name]) == 2]
720 terms_without_stateless_variant = [name for name in variants if True not in variants[name]]
721 if terms_with_both_variants and terms_without_stateless_variant:
722 raise Exception(
723 f"Solver {self._solver.name}: the PDE terms {terms_with_both_variants} are "
724 f"registered both with and without state. In this case, the generated code "
725 f"asks cellCanUseStatelessPDETerms() per cell before it invokes the kernel: "
726 f"if it returns true, the kernel is called with the stateless variants of all "
727 f"PDE terms, otherwise with the variants with state. However, the PDE terms "
728 f"{terms_without_stateless_variant} have no stateless variant, so a kernel "
729 f"call would mix stateless and stateful routines. Either add stateless "
730 f"variants of {terms_without_stateless_variant}, or register "
731 f"{terms_with_both_variants} only once."
732 )
733
734 def kernel_call(stateless: bool) -> str:
735 pde_term_calls = []
736 for term in available_terms:
737 if term not in variants:
738 continue
739 # A term registered only once is used in either case
740 application_term = variants[term].get(stateless, next(iter(variants[term].values())))
741 # Commas inside template brackets, e.g. Vector<Dimensions, double>,
742 # do not separate parameters
743 arguments = [
744 parameter.split()[-1]
745 for parameter in re.split(r",(?![^<]*>)", application_term.SIGNATURE)
746 ]
747 # Stateless terms are static and take a dummy tag (@see PDETerm)
748 if application_term.stateless:
749 arguments.append("::exahype2::Solver::Offloadable::Yes")
750 pde_term_calls.append(f"""
751 [&](
752 {application_term.SIGNATURE}
753 )->void {{
754 repositories::{self._solver.get_name_of_global_instance()}.{application_term.name}({", ".join(arguments)});
755 }}""")
756 return f"""::{"::".join(self.kernel_namespace)}::{self.kernel_function_name}<
757 {self.patch_size},
758 {self.overlap},
759 {self._solver.unknowns},
760 {self._solver.auxiliary_variables}
761>(
762 patchData,
763{",\n".join(pde_term_calls)}
764);"""
765
766 if not terms_with_both_variants:
767 return kernel_call(stateless=False)
768 return f"""if (repositories::{self._solver.get_name_of_global_instance()}.cellCanUseStatelessPDETerms(
769 patchData.cellCentre[0],
770 patchData.cellSize[0],
771 patchData.t[0],
772 patchData.dt[0]
773)) {{
774{kernel_call(stateless=True)}
775}} else {{
776{kernel_call(stateless=False)}
777}}"""
Action set implementing the cell update of the continuous Galerkin solver.
Definition UpdateCell.py:9
The concrete discretisation (Finite Volumes, ADER-DG, ...) realised by a Solver.
Definition Solver.py:60
Default mesh adaptation of the Finite Volume schemes.
Finite Volumes with a Rusanov Riemann solver and explicit Euler time stepping, realised over a block-...
patch_size
Reduce the cells' maximum eigenvalues into the next time step size.
__create_time_step_functions(self, output_directory)
Calling this function is delayed until all PDE term information is provided.
__create_data_structures(self)
Create the mesh data structures (a Finite Volume patch per cell, plus the face-based overlap patches ...
__init__(self, Solver solver, int patch_size, int overlap=1, float cfl_constant=0.9, "toolbox.blockstructured.api.InterpolateHangingFaces.Scheme" interpolation=toolbox.blockstructured.api.InterpolateHangingFaces.Scheme.Linear, "toolbox.blockstructured.api.RestrictHangingFaces.Scheme" restriction=toolbox.blockstructured.api.RestrictHangingFaces.Scheme.Averaging, float min_volume_size=None, float max_volume_size=None)
The constructor memorises the attributes.
Solver.MeshAdaptation create_default_mesh_adaptation(self)
The Finite Volume schemes adapt the mesh and balance the load through exahype2.api....
__construct_kernel_call(self, Target target, bool stateless)
This function constructs the calls to the compute kernels based on the application PDE terms and the ...
_solver
Build the reconstruction tasks befilling the patch_with_halo, which is logically temporary.
"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.
str kernel_function_name(self)
Name of the generated time step kernel.
__add_data_to_repository(self, peano_project)
Consult Introduce a new numerical scheme for some information how this routine fits to the typical wo...
cfl_constant
Reduce the cells' maximum eigenvalues into the next time step size.
construct_output(self, exahype2_project, peano_project, time_step)
Has to be implemented by subclass.
Reduce the cells' maximum eigenvalues and derive the next global time step size from the CFL conditio...
Project a Finite Volume patch's data onto the overlap held on its surrounding faces,...
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