20def derive_load_store_compute_flag(steps: List[
"peano4.api.solversteps.Step"]) ->
None:
23 For every submodel used anywhere in steps, derive a
24 ::peano4::grid::constructLoadStoreComputeFlag(...) expression from
25 which of its using Steps are active, and assign it to that submodel's
26 generator.load_store_compute_flag - the runtime counterpart to
27 peano4.api.taskgraph.compiler.PropagateUseStatements.propagate_use_statements(),
28 which has to run before whichever compiler produces these steps: it
29 decides *which* tasks have to declare a submodel at all; this function
30 decides, for every Step that does, what its generated Observer should
31 actually do with the data once the submodel's stack storage puts it
32 there (load a real value, or start from a fresh dummy; store the
33 result back, or discard it) - by asking, at runtime, which Step is
36 This has to run *after* every algorithmic phase has actually been
37 compiled (@see peano4.api.taskgraph.compiler.Sequential.translate(),
38 or whichever compiler a caller configured instead), not before: a
39 compiled Step's name, and which submodels it ends up using with which
40 qualifier, are the compiler's call to make - Sequential.translate()
41 happens to map one Task to one identically-qualified Step today, but
42 it explicitly documents itself as the naive "no fusion, no reordering"
43 baseline that smarter compilers (task fusion, dead-task elimination,
44 reordering) are meant to be measured against and eventually replace.
45 Reasoning about Tasks before compilation - e.g. by predicting a future
46 Step's name from a Task's - would silently assume every such compiler
47 keeps that 1:1 mapping, which is exactly what they need not do. Working
48 from the compiled Steps themselves keeps this function correct no
49 matter which compiler produced them.
51 Unlike propagate_use_statements(), this needs no notion of order,
52 distance or edges between graphs - it only has to know, per submodel,
53 which Steps touch it and how; a flat scan over every Step given is
56 ## Building the three predicates
58 ::peano4::grid::constructLoadStoreComputeFlag(predicateToUseData,
59 predicateForLoad, predicateForStore) takes three C++ boolean
60 expressions and combines them into the 5-way
61 peano4::grid::LoadStoreComputeFlag enum
62 (@see src/peano4/grid/LoadStoreComputeFlag.h/.cpp). For a given
63 submodel, each predicate is an "or"-chain of
65 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
66 peano4::parallel::Node::getInstance().getCurrentProgramStep() == repositories::StepRepository::toProgramStep(repositories::StepRepository::Steps::<name>)
67 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69 terms - one per Step that qualifies, <name> being that Step's own
70 .name (exactly as the compiler set it - @see
71 peano4.api.solversteps.Step, src/peano4/api/solversteps/StepsToStepRepository.py
72 for how a Step's name turns into a repositories::StepRepository::Steps
75 - predicateToUseData: every Step that declares the submodel at all,
76 whichever Task.DataAccess qualifier it carries - all four qualifiers
77 (including TEMPORARY) contribute here, since all of them mean the
78 Step needs the submodel to be considered "in play" while it runs,
79 even if - as for TEMPORARY - it never actually looks at the value.
80 - predicateForLoad: only Steps qualified READ_AND_DISCARD or
81 READ_AND_WRITE - the ones that actually need the real, previously
83 - predicateForStore: only Steps qualified WRITE or READ_AND_WRITE -
84 the ones that produce a value worth keeping.
86 TEMPORARY therefore only ever contributes to predicateToUseData - @see
87 Task.DataAccess.TEMPORARY. A predicate with no contributing Step at
88 all becomes the literal "false".
92 If a submodel carries a grid_selector (@see
93 peano4.api.datamodel.DoF, peano4.api.toolbox.GridSelector), the
94 selector's guard for the submodel's kind (createCellGuard(),
95 createFaceGuard() or createVertexGuard()) is AND-combined with each of
98 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
99 ::peano4::grid::constructLoadStoreComputeFlag((guard) and (use), (guard) and (load), (store guard) and (store))
100 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
102 That is, data are only used, loaded or stored if the selector holds
103 and the steps above conclude that they are needed. The guards are
104 evaluated within the generated loadStoreComputeFlag(marker), i.e. they
105 can query the marker.
107 Peano's stacks require that a datum stored in one mesh sweep is
108 loaded in the next one. The store predicate hence uses the selector's
109 store guard (createCellStoreGuard(), createFaceStoreGuard() or
110 createVertexStoreGuard()). It anticipates the guard's value in the next
111 sweep. For a selector which depends on the refinement state, such as
112 peano4.api.toolbox.FineGridSelector, it differs from the guard if the
113 mesh changes. This keeps stores and loads consistent on dynamically
116 This does not touch generator.send_condition/receive_and_merge_condition -
117 those are a separate, MPI rank-boundary concern this function has
118 nothing to say about.
122 Every submodel this function touches gets
123 '#include "repositories/StepRepository.h"',
124 '#include "repositories/SolverRepository.h"' and
125 '#include "peano4/parallel/Node.h"' appended to its generator.includes
126 (skipped if already present) - the generated
127 constructLoadStoreComputeFlag(...) expression references
128 repositories::StepRepository::toProgramStep/::Steps (declared in the
129 first header) and peano4::parallel::Node::getInstance() (declared in
130 the third); the second is added alongside the first to match this
131 codebase's existing convention of always pairing the two (@see
132 exahype2.api.generators.DefaultSimulationMain,
133 mghype.matrixfree.api.MatrixFreeMain.template.cpp,
134 mghype.petsc.api.PETScMain.template.cpp).
138 Submodels modelled via DaStGen2 (@ref peano4.api.datamodel.DaStGen2.DaStGen2)
139 do not hold the load/store/compute flag in their generator but in their
140 peano4_mpi_and_storage_aspect (@ref peano4.api.dastgen2.MPIAndStorageAspect.MPIAndStorageAspect).
141 For these, the function writes the flag into the aspect, and appends the
142 includes above to the aspect's implementation_file_includes, as the
143 aspect evaluates the flag within the implementation file. Adding them to
144 the header might yield cyclic includes.
146 ## Combining with a pre-existing expression
148 If a submodel's generator.load_store_compute_flag already holds something
149 other than its factory default (@see _DEFAULT_LOAD_STORE_COMPUTE_FLAG) -
150 because a task graph author set it explicitly, or an earlier call to this
151 very function already ran - the newly derived
152 constructLoadStoreComputeFlag(...) call is not allowed to simply
153 overwrite it. Instead the two are AND-combined, existing expression
154 first, new one parenthesised: "existing and (new)". This makes repeated
155 calls (e.g. once per algorithmic phase, if a caller preferred that over
156 passing every phase's steps at once) accumulate rather than clobber each
159 @note peano4::grid::LoadStoreComputeFlag is a plain enum class with no
160 operator&& (or any other combining function) defined anywhere in the
161 C++ sources as of this writing - an "existing and (new)" expression
162 built from two such enum-valued sub-expressions will not type-check
163 until such an operator is added on the C++ side. This function produces
164 exactly that string regardless, since the string-level behaviour is
165 what was asked for; the missing operator is a separate, outstanding
168 @param steps: [peano4.api.solversteps.Step]
169 Every compiled Step to consider, across every algorithmic phase (e.g.
170 setup_simulation_steps + time_step_steps + plot_steps) - has to be
171 the full set the surrounding project will actually run, so every
172 submodel's predicates see every Step that might touch it.
175 for attribute_name, access_attribute_name, guard_routine_name
in (
176 (
"cell_data",
"cell_data_access",
"createCellGuard"),
177 (
"face_data",
"face_data_access",
"createFaceGuard"),
178 (
"vertex_data",
"vertex_data_access",
"createVertexGuard"),
229 steps: List[
"peano4.api.solversteps.Step"],
231 access_attribute_name: str,
232 guard_routine_name: str,
234 use_terms: Dict[
"peano4.api.datamodel.DoF", List[str]] = {}
235 load_terms: Dict[
"peano4.api.datamodel.DoF", List[str]] = {}
236 store_terms: Dict[
"peano4.api.datamodel.DoF", List[str]] = {}
239 access = getattr(step, access_attribute_name)
240 for submodel
in getattr(step, attribute_name):
242 use_terms.setdefault(submodel, []).append(expression)
244 qualifier = access[submodel]
245 if qualifier
in (Task.DataAccess.READ_AND_DISCARD, Task.DataAccess.READ_AND_WRITE):
246 load_terms.setdefault(submodel, []).append(expression)
247 if qualifier
in (Task.DataAccess.WRITE, Task.DataAccess.READ_AND_WRITE):
248 store_terms.setdefault(submodel, []).append(expression)
251 for submodel, terms
in use_terms.items():
252 use_expression =
" or ".join(terms)
253 load_expression =
" or ".join(load_terms.get(submodel, []))
or "false"
254 store_expression =
" or ".join(store_terms.get(submodel, []))
or "false"
257 grid_selector = getattr(submodel,
"grid_selector",
None)
258 if grid_selector
is not None:
259 guard = getattr(grid_selector, guard_routine_name)()
260 store_guard_routine_name = guard_routine_name.replace(
"Guard",
"StoreGuard")
262 getattr(grid_selector, store_guard_routine_name)()
263 if hasattr(grid_selector, store_guard_routine_name)
266 use_expression = f
"({guard}) and ({use_expression})"
267 load_expression = f
"({guard}) and ({load_expression})"
268 store_expression = f
"({store_guard}) and ({store_expression})"
270 f
"::peano4::grid::constructLoadStoreComputeFlag({use_expression}, {load_expression}, {store_expression})"
274 existing_flag = holder.load_store_compute_flag
275 if existing_flag
is not None and existing_flag != _DEFAULT_LOAD_STORE_COMPUTE_FLAG:
279 holder.load_store_compute_flag = f
"{existing_flag} and ({new_flag})"
281 holder.load_store_compute_flag = new_flag