Peano
Loading...
Searching...
No Matches
Task.py
Go to the documentation of this file.
1# This file is part of the Peano project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3
4import enum
5from typing import Iterable, List, Optional, Set
6
7
8class Task:
9 """!
10
11 One task within a peano4.api.taskgraph.Graph
12
13 Every task works on exactly one kind of mesh entity - a vertex, a face
14 or a cell - which has to be specified through entity when the task is
15 constructed. GLOBAL tasks are the exception: they do not work on any
16 mesh entity (@ref peano4.api.taskgraph.Task.Task.GridEntity).
17
18 ## Task dependencies
19
20 Every task also holds dependencies, i.e. other Task instances that have
21 to be completed before this task can run. This sequence is empty by
22 default; use add_dependency() to add further tasks to it.
23
24 ## Task action sets
25
26 Every task furthermore holds an arbitrary number of Peano 4 action sets
27 (peano4.api.solversteps.ActionSet), i.e. the actual grid-sweep logic the
28 task realises once it is compiled into an Observer. This sequence is
29 empty by default; use add_action_set() to add further action sets to it.
30 Their order matters - see peano4.api.solversteps.Step.add_action_set().
31
32 ## Task data usage
33
34 A task's action sets are handed mesh data (fineGridCell..., fineGridFace...,
35 fineGridVertices...) as arguments once they are compiled into an Observer -
36 but only for the vertex/face/cell submodels the surrounding
37 peano4.api.solversteps.Step was told about via its own use_cell()/
38 use_face()/use_vertex(). An action set cannot make that declaration
39 itself (several tasks, and hence several Steps, can share the same
40 action set), so every task has to declare which submodels it needs
41 through use_cell()/use_face()/use_vertex() below. The task graph
42 compiler then forwards these declarations to the Step(s) it compiles
43 this task into.
44
45 ## Task repetitions
46
47 Finally, a task can be marked as repeating via task_has_to_repeat. By
48 default (None), a task runs once. Passing a non-None string works if
49 and only if it is a valid boolean C++ expression. Peano will evaluate
50 it on the global runner after the task has finished and then decide if
51 to rerun this step. An all time classic is the following expression
52
53 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 not peano4::grid::isGridStationary( peano4::parallel::SpacetreeSet::getInstance().getGridStatistics() )
55 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56
57 which means this task will repeat until the mesh becomes stationary.
58
59 ## Background tasks
60
61 Some task graph compilers can run parts of a task in the background,
62 i.e. as tasks that outlive the mesh traversal event which spawns them
63 (@ref peano4.api.taskgraph.compiler.CellTaskProducerConsumer.translate_with_sequential(), @ref peano4.api.taskgraph.compiler.CellTaskProducerConsumer.translate_with_fusion()).
64 Such compilers check whether this is safe, and leave the task as it is
65 if they cannot be sure. The flag may_spawn_background_tasks steers this
66 behaviour:
67
68 - None (default): The compilers decide whether running the task in the
69 background is safe.
70 - True: You know that running the task in the background is fine. The
71 compilers skip their checks.
72 - False: The task must never run in the background, e.g. as it
73 reduces data in unprepareTraversal() such as a global CFL condition.
74 The compilers leave the task as it is without any further check or
75 warning.
76
77 ## Task tags
78
79 Every task carries a set of tags, i.e. of plain strings. A task can carry
80 arbitrarily many tags at the same time. The task graph compilers ignore
81 them. Tags allow the code which creates a task graph to tell other code
82 what a task does, without that code having to know how the graph is
83 built. Tags are global: Add a tag via task.tags.add(tag) and find
84 all tasks with a tag via peano4.api.taskgraph.Graph.find_tasks().
85 ExaHyPE's numerical schemes, for example, tag all tasks which produce a
86 new solution with exahype2.api.Solver.Solver.SolutionUpdateTaskTag, such
87 that others can add postprocessing steps after them (@ref
88 page_exahype2_solver_manipulate_solver_steps). Use names with a prefix,
89 e.g. the name of the defining class, so tags of different parties
90 cannot clash by accident.
91
92 ## Task processing
93
94 The tasks will later on be taken by the graph compiler which maps them
95 onto a sequence of @ref peano4.api.solversteps.Step instances. How many
96 of these logical steps (grid traversals) are required depends on the
97 structure of the task graph but also the type of compiler chosen.
98 Contraray, each action set that is added to a task will remain to be a
99 real @ref peano4.api.solversteps.ActionSet, and subsequently be translated
100 into exactly one @ref peano4.api.output.ActionSet, i.e. one pair of header
101 and implementation file called MyFancyObserverOfAGridTraversal2ThisActionSet.
102
103 ## Things to read to get the full picture:
104
105 - @ref peano4.api.taskgraph This is the overall description of the task
106 graph API and should be the starting point for understanding how to use it.
107 It notably describes some design decisions re dependencies, grid
108 entities and action sets.
109 - @ref page_peano_action_sets
110 - @ref peano4.api.taskgraph
111
112 """
113
114 class GridEntity(enum.Enum):
115 """!
116
117 Which kind of mesh entity a Task works on.
118
119 GLOBAL tasks do not work on any mesh entity at all. They only plug
120 into prepareTraversal()/unprepareTraversal(), i.e. they realise
121 global (per rank) activities such as triggering the load balancing.
122 They therefore may not declare any cell, face or vertex data, may
123 not repeat, and no other task may depend on them. Task graph
124 compilers which fuse tasks append a GLOBAL task to the traversal
125 holding its last dependency
126 (@ref peano4.api.taskgraph.compiler.FuseSteps.translate()).
127
128 """
129
130 VERTEX = 1
131 FACE = 2
132 CELL = 3
133 GLOBAL = 4
134
135 class DataAccess(enum.Enum):
136 """!
137
138 How a task's action sets actually access a submodel it declares via
139 use_cell()/use_face()/use_vertex() - i.e. the qualifier that
140 accompanies every such declaration.
141
142 - READ_AND_DISCARD: the action set only reads the submodel, never
143 writes it, and does not need its value kept around afterwards -
144 this task is a final consumer, not a link in a longer chain.
145 The submodel is loaded before the step, but it is not stored
146 after the step: Its value is lost unless another task of the
147 same step writes it.
148 - WRITE: the action sets only write the submodel (e.g. fill in an
149 initial condition), without relying on whatever value it held
150 before. The submodel is stored after the step, but it is not
151 loaded before the step: The action sets see uninitialised data,
152 not the value the submodel held before.
153 - READ_AND_WRITE: the action sets both read the submodel's
154 existing value and write a new one (e.g. an in-place update).
155 The WRITE component doesn't mean necessarily that we alter the
156 content. It simply tells the task that the data should not be
157 discarded afterwards. The submodel is loaded before and stored
158 after the step.
159 - TEMPORARY: the submodel is neither meaningfully read nor written
160 by this task for its own sake - available for a task graph
161 author to say so explicitly, though nothing currently derives
162 this qualifier automatically. The submodel is neither loaded nor
163 stored.
164
165 The qualifiers matter at two levels. On the task-graph level, WRITE
166 and READ_AND_WRITE are treated identically by
167 peano4.api.taskgraph.compiler.PropagateUseStatements.propagate_use_statements():
168 both mean this task produces a genuinely new value for the
169 submodel, which someone downstream then has to actually pick up
170 (read) - see that function's documentation for the precise rule,
171 and note that the READ_AND_WRITE declarations *it* inserts to
172 satisfy that rule are indistinguishable from ones a task graph
173 author wrote by hand. READ_AND_DISCARD and TEMPORARY impose no
174 such obligation on whatever comes after.
175
176 On the level of the generated code, every qualifier translates
177 into the same use_cell()/use_face()/use_vertex() call on the
178 peano4.api.solversteps.Step this task compiles into (@ref
179 peano4.api.taskgraph.compiler.Sequential.translate()). However,
180 the qualifiers decide whether the generated code loads the
181 submodel before and stores it after a step: @ref
182 peano4.api.taskgraph.compiler.DeriveLoadStoreComputeFlag.derive_load_store_compute_flag()
183 derives these flags from the qualifiers as described per qualifier
184 above, and exahype2.api.Project.generate_Peano4_project() applies
185 it to all steps. If several tasks share a step,
186 peano4.api.taskgraph.compiler.FuseSteps merges their qualifiers
187 first.
188
189 Pick the qualifiers with care if a submodel's value has to survive
190 several steps: A task which reads data that an earlier task of
191 another step has declared READ_AND_DISCARD, or a task declaring
192 WRITE which still reads the previous value, reads garbage. The
193 Sommerfeld boundary conditions of exahype2.api are an example
194 (@ref exahype2.api.blockstructured.BoundaryConditions.get_access_to_persistent_face_data()).
195
196 """
197
198 READ_AND_DISCARD = 1
199 WRITE = 2
200 READ_AND_WRITE = 3
201 TEMPORARY = 4
202
204 self,
205 name: str,
206 entity: "Task.GridEntity",
207 task_has_to_repeat: Optional[str] = None,
208 may_spawn_background_tasks: Optional[bool] = None,
209 tags: Optional[Iterable[str]] = None,
210 ):
211 self.name = name
212 self.tags: Set[str] = set(tags) if tags is not None else set()
213 self.entity = entity
214 self.dependencies: List["Task"] = []
215 self.action_sets: List["peano4.api.solversteps.ActionSet"] = []
216 self.cell_data: List["peano4.api.datamodel.DoF"] = []
217 self.face_data: List["peano4.api.datamodel.DoF"] = []
218 self.vertex_data: List["peano4.api.datamodel.DoF"] = []
219 self.cell_data_access: dict = {}
220 self.face_data_access: dict = {}
221 self.vertex_data_access: dict = {}
222 self.has_to_repeat = task_has_to_repeat
223 self.may_spawn_background_tasks = may_spawn_background_tasks
224 assert not (entity == Task.GridEntity.GLOBAL and task_has_to_repeat is not None), (
225 f"Task {name} is GLOBAL and hence may not repeat"
226 )
227
228 def add_dependency(self, task: "Task"):
229 self.dependencies.append(task)
230
231 def use_cell(self, submodel: "peano4.api.datamodel.DoF", qualifier: "Task.DataAccess"):
232 """!
233
234 Declare that this task's action sets access a cell submodel (e.g. a
235 peano4.api.datamodel.Patch registered via
236 peano_project.datamodel.add_cell()), and how - @see Task.DataAccess
237 for what qualifier to pass. Forwarded to the
238 peano4.api.solversteps.Step this task is compiled into, i.e. to its
239 own use_cell(), so the generated Observer passes fineGridCell<name>
240 into every touch-cell/create-cell/... operation.
241
242 """
243 assert self.entity != Task.GridEntity.GLOBAL, f"Task {self.name} is GLOBAL and hence may not use cell data"
244 self.cell_data.append(submodel)
245 self.cell_data_access[submodel] = qualifier
246
247 def use_face(self, submodel: "peano4.api.datamodel.DoF", qualifier: "Task.DataAccess"):
248 """!
249
250 Declare that this task's action sets access a face submodel, and
251 how. @see use_cell() for the general mechanism and Task.DataAccess
252 for what qualifier to pass.
253
254 """
255 assert self.entity != Task.GridEntity.GLOBAL, f"Task {self.name} is GLOBAL and hence may not use face data"
256 self.face_data.append(submodel)
257 self.face_data_access[submodel] = qualifier
258
259 def use_vertex(self, submodel: "peano4.api.datamodel.DoF", qualifier: "Task.DataAccess"):
260 """!
261
262 Declare that this task's action sets access a vertex submodel, and
263 how. @see use_cell() for the general mechanism and Task.DataAccess
264 for what qualifier to pass.
265
266 """
267 assert self.entity != Task.GridEntity.GLOBAL, f"Task {self.name} is GLOBAL and hence may not use vertex data"
268 self.vertex_data.append(submodel)
269 self.vertex_data_access[submodel] = qualifier
270
271 def add_action_set(self, action_set: "peano4.api.solversteps.ActionSet"):
272 """!
273
274 A task can hold an arbitrary number of Peano 4 action sets. Eventually
275 the overarching task graph will be mapped onto a sequence of steps
276 (observers) and those steps then grab those action sets from the tasks and execute them.
277
278 It is important to note that the action set passed in can define
279 actions for multiple grid entities, while the task is specified over
280 a particular mesh entity. This seems to be contradiction but is
281 actually explained in @ref peano4.api.taskgraph.
282
283 """
284 self.action_sets.append(action_set)
How a task's action sets actually access a submodel it declares via use_cell()/use_face()/use_vertex(...
Definition Task.py:135
Which kind of mesh entity a Task works on.
Definition Task.py:114
One task within a peano4.api.taskgraph.Graph.
Definition Task.py:8
use_vertex(self, "peano4.api.datamodel.DoF" submodel, "Task.DataAccess" qualifier)
Declare that this task's action sets access a vertex submodel, and how.
Definition Task.py:259
__init__(self, str name, "Task.GridEntity" entity, Optional[str] task_has_to_repeat=None, Optional[bool] may_spawn_background_tasks=None, Optional[Iterable[str]] tags=None)
Definition Task.py:210
add_dependency(self, "Task" task)
Definition Task.py:228
add_action_set(self, "peano4.api.solversteps.ActionSet" action_set)
A task can hold an arbitrary number of Peano 4 action sets.
Definition Task.py:271
use_face(self, "peano4.api.datamodel.DoF" submodel, "Task.DataAccess" qualifier)
Declare that this task's action sets access a face submodel, and how.
Definition Task.py:247
use_cell(self, "peano4.api.datamodel.DoF" submodel, "Task.DataAccess" qualifier)
Declare that this task's action sets access a cell submodel (e.g.
Definition Task.py:231