Peano
Loading...
Searching...
No Matches
ComputeGlobalCFLCondition.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
3from .AbstractFVActionSet import AbstractFVActionSet
4
5import peano4.api
6
7
9 """!
10
11 Reduce the cells' maximum eigenvalues and derive the next global time
12 step size from the CFL condition.
13
14 exahype2.api.fv.actionsets.UpdateCell stores the maximum eigenvalue of
15 every patch it has updated in the cell data solver._max_eigenvalue. This
16 action set runs in a subsequent mesh traversal: touchCellFirstTime()
17 folds each cell's value into the static _maxEigenvalue, and
18 unprepareTraversal() turns the global maximum into the next time step
19 size and feeds it into ::exahype2::Solver's own time step bookkeeping
20 (updateCell()/finishTimeStep()/startTimeStep()). There is deliberately no
21 separate, solver-level time stamp/time step size state on top of
22 ::exahype2::Solver.
23
24 Global time stepping: all cells share one time stamp and time step size,
25 which are taken from the solver (getMinTimeStamp() and
26 getMinTimeStepSize()) rather than from per-cell bookkeeping.
27
28 The kernel updates all dimensions in one explicit Euler step, so the CFL
29 condition reads dt * sum_d lambda_d/h_d <= 1, i.e. the admissible time
30 step size is cfl_constant * h / (Dimensions * lambda_max), with h the
31 minimal volume size and lambda_max the maximum eigenvalue.
32
33 As this is global time stepping, getMinTimeStamp()==getMaxTimeStamp()
34 and getMinTimeStepSize()==getMaxTimeStepSize() always hold. Hence a
35 single updateCell() call per time step, not one per cell, is enough to
36 keep both the min and the max in sync.
37
38 """
39
40 def __init__(self, solver, cfl_constant, patch_size, guard, max_eigenvalue=None):
41 """!
42
43 @param solver: exahype2.api.Solver
44 Has to have solver._max_eigenvalue set - @see
45 exahype2.api.fv.RusanovWithExplicitEuler.
46
47 @param cfl_constant: float
48 Safety factor the admissible time step size is scaled down by,
49 relative to the theoretical CFL=1 stability limit (which already
50 accounts for the number of dimensions, see class docstring) - @see
51 exahype2.api.fv.RusanovWithExplicitEuler's own cfl_constant
52 parameter, which this is meant to be passed straight through
53 from.
54
55 @param patch_size: int
56 Number of Finite Volumes per patch and dimension. The CFL
57 condition is evaluated over the volume size, i.e. the cell size
58 divided by patch_size.
59
60 @param guard: str
61 C++ boolean expression, evaluated per cell in touchCellFirstTime,
62 guarding whether that cell contributes to the CFL condition. Has
63 to match the guard of exahype2.api.fv.actionsets.UpdateCell, as
64 only the cells updated there hold a valid maximum eigenvalue.
65
66 @param max_eigenvalue: peano4.api.datamodel.DaStGen2
67 The cell data holding the maximum eigenvalue (first entry).
68 Defaults to solver._max_eigenvalue.
69
70 """
71 AbstractFVActionSet.__init__(self, solver)
72 self.guard = guard
73 self._max_eigenvalue = max_eigenvalue if max_eigenvalue is not None else solver._max_eigenvalue
74 self.cfl_constant = cfl_constant
75 self.patch_size = patch_size
76
78 """!
79
80 prepareTraversal() is static - called once per rank, before any
81 instance of this action set is created - so it can only ever touch
82 static members, never a per-instance one. _maxEigenvalue is static
83 for exactly that reason: it is reset here, once, before every grid
84 sweep, and then accumulated into by potentially many instances of
85 this action set (one per tree, possibly running on different threads
86 concurrently) as touchCellFirstTime() visits the cells - see
87 _semaphore, which guards that concurrent access.
88
89 """
90 return " _maxEigenvalue = 0.0;\n"
91
93 """!
94
95 Called once per rank, after every instance of this action set for
96 the current grid sweep has already been destroyed (see
97 get_body_of_prepareTraversal()'s note on the static/per-instance
98 split) - the one safe place both to read _maxEigenvalue (nothing can
99 still be writing it) and to issue an MPI reduction:
100 tarch::mpi::Rank::allReduce()'s own documentation requires "only one
101 thread per rank" to ever call it, which a once-per-rank static method
102 already guarantees.
103
104 This combines two different kinds of "global", reduced two different
105 ways:
106
107 - The mesh size comes out of peano4::grid::GridStatistics, which
108 peano4::parallel::SpacetreeSet reduces over all ranks at the end
109 of every traversal (@ref page_grid_statistics). Every rank hence
110 reads the same, global value. GridStatistics holds the cell size;
111 the CFL condition needs the volume size, i.e. the cell size
112 divided by the patch size.
113 - _maxEigenvalue is a plain double, already only a local, per-rank
114 accumulation (see _semaphore) - that one *is* reduced directly via
115 tarch::mpi::Rank::allReduce(..., MPI_MAX, ...).
116
117 Once newTimeStepSize is known, it folds it into the solver's own
118 time step bookkeeping (see this class' docstring for why a single
119 updateCell() call, not one per cell, is correct for this solver).
120 newTimeStepSize is not truncated by MaxTerminalTime: the last time
121 step may overshoot the end time, and the simulation stops afterwards,
122 i.e. MaxTerminalTime is a lower bound on the final time. The time
123 stamp is advanced by the time step size the update used
124 (getMaxTimeStepSize() before updateCell()), not by newTimeStepSize,
125 which only the next update uses. The very first time step therefore
126 runs with time step size 0 and leaves the time stamp at 0: it only
127 yields the first admissible time step size.
128
129 - updateCell(newTimeStepSize, newTimeStamp) folds the new values
130 into exahype2::Solver's ...UpdatedThisTimeStep accumulators.
131 - finishTimeStep() then MPI-all-reduces that accumulator across
132 ranks - a no-op given newTimeStepSize/newTimeStamp are already
133 identical on every rank, but calling it anyway keeps the full
134 ::exahype2::Solver lifecycle honest for any future override that
135 relies on it actually running.
136 - startTimeStep(...) immediately promotes that same accumulator into
137 the stable _minTimeStamp/_maxTimeStamp/_minTimeStepSize/
138 _maxTimeStepSize fields getMinTimeStamp()/.../getMaxTimeStepSize()
139 expose, so there is no one-phase lag: the very first TimeStep
140 phase must already leave a genuinely positive getMaxTimeStepSize()
141 behind for the generated main loop's
142 tarch::la::greater(maxTimeStepSize,0.0) guard to see, or the
143 simulation could never advance past its first iteration.
144
145 """
146 return f"""
147 const peano4::grid::GridStatistics globalStatistics = peano4::parallel::SpacetreeSet::getInstance().getGridStatistics();
148 const double globalMinVolumeH = tarch::la::max( globalStatistics.getMinH() ) / {self.patch_size};
149
150 // No semaphore required here, as it is only called once per rank
151 double localMaxEigenvalue = _maxEigenvalue;
152 double globalMaxEigenvalue = localMaxEigenvalue;
153 #ifdef Parallel
154 tarch::mpi::Rank::getInstance().allReduce(
155 &localMaxEigenvalue, &globalMaxEigenvalue, 1, MPI_DOUBLE, MPI_MAX,
156 [&]() -> void {{ tarch::services::ServiceRepository::getInstance().receiveDanglingMessages(); }}
157 );
158 #endif
159
160 // The time stamp advances by the time step size the update used,
161 // newTimeStepSize is the one the next update will use
162 const double usedTimeStepSize = repositories::{self._solver.get_name_of_global_instance()}.getMaxTimeStepSize();
163 const double newTimeStamp = repositories::{self._solver.get_name_of_global_instance()}.getMaxTimeStamp() + usedTimeStepSize;
164 // The last time step is not truncated, i.e. it may overshoot MaxTerminalTime
165 const double newTimeStepSize = {self.cfl_constant} * globalMinVolumeH / globalMaxEigenvalue / Dimensions;
166
167 repositories::{self._solver.get_name_of_global_instance()}.updateCell( newTimeStepSize, newTimeStamp );
168 repositories::{self._solver.get_name_of_global_instance()}.finishTimeStep();
169 repositories::{self._solver.get_name_of_global_instance()}.startTimeStep(
170 repositories::{self._solver.get_name_of_global_instance()}.getMinTimeStamp(),
171 repositories::{self._solver.get_name_of_global_instance()}.getMaxTimeStamp(),
172 repositories::{self._solver.get_name_of_global_instance()}.getMinTimeStepSize(),
173 repositories::{self._solver.get_name_of_global_instance()}.getMaxTimeStepSize()
174 );
175 logInfo( "unprepareTraversal()", "t=" << repositories::{self._solver.get_name_of_global_instance()}.getMaxTimeStamp() << ", new dt=" << newTimeStepSize );
176"""
177
178 def get_attributes(self):
179 return """
180 static double _maxEigenvalue;
181 static tarch::multicore::BooleanSemaphore _semaphore;
182"""
183
184 def get_static_initialisations(self, full_qualified_classname):
185 return (
186 "double "
187 + full_qualified_classname
188 + "::_maxEigenvalue = 0.0;\n"
189 + "tarch::multicore::BooleanSemaphore "
190 + full_qualified_classname
191 + "::_semaphore;\n"
192 )
193
194 def get_includes(self):
195 return (
196 AbstractFVActionSet.get_includes(self)
197 + """
198#include "tarch/multicore/BooleanSemaphore.h"
199#include "tarch/multicore/Lock.h"
200#include "tarch/mpi/Rank.h"
201#include "tarch/services/ServiceRepository.h"
202#include "tarch/la/VectorOperations.h"
203#include "peano4/parallel/SpacetreeSet.h"
204#include "peano4/grid/grid.h"
205"""
206 )
207
208 def get_body_of_operation(self, operation_name):
209 result = ""
210 if operation_name == peano4.api.solversteps.ActionSet.OPERATION_TOUCH_CELL_FIRST_TIME:
211 result = f"""
212 tarch::multicore::Lock lock(_semaphore);
213 _maxEigenvalue = std::max( fineGridCell{self._max_eigenvalue.name}.getMaxEigenvalue(0), _maxEigenvalue );
214"""
215 return result
216
217 def get_guard_of_operation(self, operation_name):
218 if operation_name == peano4.api.solversteps.ActionSet.OPERATION_TOUCH_CELL_FIRST_TIME:
219 return self.guard
220 return "true"
221
223 return __name__.replace(".py", "").replace(".", "_")
Reduce the cells' maximum eigenvalues and derive the next global time step size from the CFL conditio...
get_attributes(self)
Return attributes as copied and pasted into the generated class.
get_body_of_prepareTraversal(self)
prepareTraversal() is static - called once per rank, before any instance of this action set is create...
get_body_of_operation(self, operation_name)
Return actual C++ code snippets to be inserted into C++ code.
get_action_set_name(self)
You should replicate this function in each subclass, so you get meaningful action set names (otherwis...
__init__(self, solver, cfl_constant, patch_size, guard, max_eigenvalue=None)
get_body_of_unprepareTraversal(self)
Called once per rank, after every instance of this action set for the current grid sweep has already ...