3from .AbstractFVActionSet
import AbstractFVActionSet
11 Reduce the cells' maximum eigenvalues and derive the next global time
12 step size from the CFL condition.
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
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.
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.
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.
40 def __init__(self, solver, cfl_constant, patch_size, guard, max_eigenvalue=None):
43 @param solver: exahype2.api.Solver
44 Has to have solver._max_eigenvalue set - @see
45 exahype2.api.fv.RusanovWithExplicitEuler.
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
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.
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.
66 @param max_eigenvalue: peano4.api.datamodel.DaStGen2
67 The cell data holding the maximum eigenvalue (first entry).
68 Defaults to solver._max_eigenvalue.
71 AbstractFVActionSet.__init__(self, solver)
73 self.
_max_eigenvalue = max_eigenvalue
if max_eigenvalue
is not None else solver._max_eigenvalue
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.
90 return " _maxEigenvalue = 0.0;\n"
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
104 This combines two different kinds of "global", reduced two different
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, ...).
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.
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.
147 const peano4::grid::GridStatistics globalStatistics = peano4::parallel::SpacetreeSet::getInstance().getGridStatistics();
148 const double globalMinVolumeH = tarch::la::max( globalStatistics.getMinH() ) / {self.patch_size};
150 // No semaphore required here, as it is only called once per rank
151 double localMaxEigenvalue = _maxEigenvalue;
152 double globalMaxEigenvalue = localMaxEigenvalue;
154 tarch::mpi::Rank::getInstance().allReduce(
155 &localMaxEigenvalue, &globalMaxEigenvalue, 1, MPI_DOUBLE, MPI_MAX,
156 [&]() -> void {{ tarch::services::ServiceRepository::getInstance().receiveDanglingMessages(); }}
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;
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()
175 logInfo( "unprepareTraversal()", "t=" << repositories::{self._solver.get_name_of_global_instance()}.getMaxTimeStamp() << ", new dt=" << newTimeStepSize );
180 static double _maxEigenvalue;
181 static tarch::multicore::BooleanSemaphore _semaphore;
187 + full_qualified_classname
188 +
"::_maxEigenvalue = 0.0;\n"
189 +
"tarch::multicore::BooleanSemaphore "
190 + full_qualified_classname
196 AbstractFVActionSet.get_includes(self)
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"
210 if operation_name == peano4.api.solversteps.ActionSet.OPERATION_TOUCH_CELL_FIRST_TIME:
212 tarch::multicore::Lock lock(_semaphore);
213 _maxEigenvalue = std::max( fineGridCell{self._max_eigenvalue.name}.getMaxEigenvalue(0), _maxEigenvalue );
218 if operation_name == peano4.api.solversteps.ActionSet.OPERATION_TOUCH_CELL_FIRST_TIME:
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_guard_of_operation(self, operation_name)
Return the guard of an operation.
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...
get_static_initialisations(self, full_qualified_classname)
__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 ...
get_includes(self)
Return include statements that you need.