11READ_AND_DISCARD = Task.DataAccess.READ_AND_DISCARD
12WRITE = Task.DataAccess.WRITE
13READ_AND_WRITE = Task.DataAccess.READ_AND_WRITE
14TEMPORARY = Task.DataAccess.TEMPORARY
19 Minimal stand-in for a peano4.api.datamodel.Patch: propagate_use_statements()
20 only ever needs submodel identity, never anything Patch-specific.
31 """A step sequence of `length` steps. Returned twice: once as the
32 sequence handed to propagate_use_statements(), once as the list of
33 steps the tests inspect - both are the same list object."""
36 for i
in range(length)
42 step.use_cell(submodel)
43 step.cell_data_access[submodel] = qualifier
47 step.use_face(submodel)
48 step.face_data_access[submodel] = qualifier
52 step.use_vertex(submodel)
53 step.vertex_data_access[submodel] = qualifier
56def _access(task, attribute_name="cell_data_access"):
57 return getattr(task, attribute_name)
66 """Distance 1, WRITE then READ_AND_DISCARD: nothing to do."""
70 _use_cell(tasks[1], submodel, READ_AND_DISCARD)
72 changed = propagate_use_statements([graph])
74 assert changed
is False
75 assert tasks[1].cell_data.count(submodel) == 1
79 """Distance 1, WRITE then READ_AND_WRITE: also fine."""
83 _use_cell(tasks[1], submodel, READ_AND_WRITE)
85 changed = propagate_use_statements([graph])
87 assert changed
is False
91 """Distance 1, WRITE then WRITE again: the second WRITE is a fresh,
92 independent production (e.g. ProjectPatchOntoFaces recomputing face
93 data from scratch every time step) - it does not need to read what
94 the first WRITE produced, so there is nothing to protect and nothing
95 to do, even though the two are immediately adjacent."""
101 changed = propagate_use_statements([graph])
103 assert changed
is False
107 """A WRITE at N is immediately followed by another WRITE at N+1 - N's
108 own search stops right there with nothing to do (superseded). N+1 is
109 itself a WRITE, though, so it is independently checked too, on its
110 own terms: N+1 to the READ_AND_DISCARD at N+2 is distance 1 (odd),
116 _use_cell(tasks[2], submodel, READ_AND_DISCARD)
118 changed = propagate_use_statements([graph])
120 assert changed
is False
124 """Worked example 1: task N writes, task N+2 reads, nothing in between
125 mentions the submodel (distance 2, even) - fixed by inserting a
126 READ_AND_WRITE declaration on task N+1."""
130 _use_cell(tasks[2], submodel, READ_AND_DISCARD)
132 changed = propagate_use_statements([graph])
134 assert changed
is True
135 assert submodel
in tasks[1].cell_data
136 assert _access(tasks[1])[submodel] == READ_AND_WRITE
140 """Worked example 2: task N writes, task N+3 reads (distance 3, odd) -
141 already fine as it stands."""
145 _use_cell(tasks[3], submodel, READ_AND_DISCARD)
147 changed = propagate_use_statements([graph])
149 assert changed
is False
150 assert submodel
not in tasks[1].cell_data
151 assert submodel
not in tasks[2].cell_data
155 """Worked example 3: task N writes, task N+4 reads (distance 4, even) -
156 fixed by inserting a READ_AND_WRITE declaration on task N+3, not N+1."""
160 _use_cell(tasks[4], submodel, READ_AND_DISCARD)
162 changed = propagate_use_statements([graph])
164 assert changed
is True
165 assert submodel
not in tasks[1].cell_data
166 assert submodel
not in tasks[2].cell_data
167 assert submodel
in tasks[3].cell_data
168 assert _access(tasks[3])[submodel] == READ_AND_WRITE
172 """A TEMPORARY use in between is treated exactly like a task that does
173 not mention the submodel at all: task N writes, task N+1 already has
174 a (user-declared) TEMPORARY use of the same submodel, task N+3 reads -
175 the distance that matters is still N to N+3 (distance 3, odd, fine),
176 not N to N+1 (which would wrongly look like an immediate non-read)."""
181 _use_cell(tasks[3], submodel, READ_AND_DISCARD)
183 changed = propagate_use_statements([graph])
185 assert changed
is False
186 assert submodel
not in tasks[2].cell_data
190 """A READ_AND_DISCARD never obliges anything downstream - even an
191 immediately following task that does not mention the submodel at all
195 _use_cell(tasks[0], submodel, READ_AND_DISCARD)
197 changed = propagate_use_statements([graph])
199 assert changed
is False
200 assert submodel
not in tasks[1].cell_data
201 assert submodel
not in tasks[2].cell_data
205 """Same for TEMPORARY."""
210 changed = propagate_use_statements([graph])
212 assert changed
is False
216 """READ_AND_WRITE both reads and writes, so - like a plain WRITE - it
217 still obliges whatever comes next (distance 2 here, needs a relay)."""
220 _use_cell(tasks[0], submodel, READ_AND_WRITE)
221 _use_cell(tasks[2], submodel, READ_AND_DISCARD)
223 changed = propagate_use_statements([graph])
225 assert changed
is True
226 assert _access(tasks[1])[submodel] == READ_AND_WRITE
230 """A second call once everything is already admissible must change
231 nothing and report False."""
235 _use_cell(tasks[4], submodel, READ_AND_DISCARD)
237 first_changed = propagate_use_statements([graph])
238 state_after_first = [list(task.cell_data)
for task
in tasks]
239 second_changed = propagate_use_statements([graph])
240 state_after_second = [list(task.cell_data)
for task
in tasks]
242 assert first_changed
is True
243 assert second_changed
is False
244 assert state_after_first == state_after_second
252 _use_cell(tasks[0], cell_submodel, WRITE)
253 _use_cell(tasks[2], cell_submodel, READ_AND_DISCARD)
254 _use_face(tasks[0], face_submodel, WRITE)
255 _use_face(tasks[2], face_submodel, READ_AND_DISCARD)
257 _use_vertex(tasks[2], vertex_submodel, READ_AND_DISCARD)
259 propagate_use_statements([graph])
261 assert tasks[1].cell_data == [cell_submodel]
262 assert tasks[1].face_data == [face_submodel]
263 assert tasks[1].vertex_data == [vertex_submodel]
267 """A graph does not need any edge at all to be analysed - it is simply
268 treated as a self-contained sequence, exactly like the old
269 PropagateUseStatements used to for a single already-compiled Step
274 _use_cell(tasks[2], submodel, READ_AND_DISCARD)
276 changed = propagate_use_statements([graph])
278 assert changed
is True
279 assert submodel
in tasks[1].cell_data
288 """A's last task writes; B's first task is at distance 2 across the
289 (A, B) edge (one silent task in between, in A) - the relay has to be
290 inserted into A (the task exactly one before B's first task), not B."""
295 _use_cell(tasks_b[0], submodel, READ_AND_DISCARD)
297 changed = propagate_use_statements([graph_a, graph_b], edges=[(graph_a, graph_b)])
299 assert changed
is True
300 assert submodel
in tasks_a[1].cell_data
301 assert _access(tasks_a[1])[submodel] == READ_AND_WRITE
302 assert tasks_b[0].cell_data.count(submodel) == 1
306 """A (graph, graph) self-edge is what replaces the old Graph.cyclic
307 flag: a graph's last task writing and its own first task reading, at
308 distance 1 across the wrap-around, is already valid."""
311 _use_cell(tasks[0], submodel, READ_AND_DISCARD)
314 changed = propagate_use_statements([graph], edges=[(graph, graph)])
316 assert changed
is False
320 """Same wrap-around, but with a genuine gap needing a relay: the last
321 task writes, the graph's own first task is silent, and the real reader
322 is the *second* task - distance 2 via the wrap (last -> first (silent,
323 distance 1) -> second (read, distance 2)), matching worked example 1
324 but reached through the self-edge instead of a plain successor."""
327 _use_cell(tasks[1], submodel, READ_AND_DISCARD)
330 changed = propagate_use_statements([graph], edges=[(graph, graph)])
332 assert changed
is True
333 assert submodel
in tasks[0].cell_data
334 assert _access(tasks[0])[submodel] == READ_AND_WRITE
338 """Without a (graph, graph) edge, a graph's own last task is a dead
339 end - nothing wraps back to its first task, so a write at the last
340 task with no further use is simply left alone."""
345 changed = propagate_use_statements([graph])
347 assert changed
is False
348 assert submodel
not in tasks[0].cell_data
352 """Setup -> TimeStep and Setup -> Plot: a submodel Setup last writes has
353 to reach a real reader down *either* branch, and each branch is fixed
354 on its own terms (here: TimeStep's reader sits at distance 2, needing a
355 relay; Plot's sits at distance 1, already valid)."""
357 time_step, time_step_tasks =
_chain_graph(
"TimeStep", 2)
360 _use_cell(setup_tasks[0], submodel, WRITE)
361 _use_cell(time_step_tasks[1], submodel, READ_AND_DISCARD)
362 _use_cell(plot_tasks[0], submodel, READ_AND_DISCARD)
364 changed = propagate_use_statements(
365 [setup, time_step, plot],
366 edges=[(setup, time_step), (setup, plot)],
369 assert changed
is True
370 assert submodel
in time_step_tasks[0].cell_data
371 assert _access(time_step_tasks[0])[submodel] == READ_AND_WRITE
372 assert plot_tasks[0].cell_data.count(submodel) == 1
376 """TimeStep and Plot may follow one another in either order - a
377 submodel TimeStep's last task writes and only Plot's first task reads,
378 at distance 1, is already valid down that path. (Self-edges are
379 deliberately left out here - a single-task graph whose only task both
380 writes and self-repeats would conflict with itself immediately, which
381 is a separate scenario covered by the self-edge tests above.)"""
382 time_step, time_step_tasks =
_chain_graph(
"TimeStep", 1)
385 _use_cell(time_step_tasks[0], submodel, WRITE)
386 _use_cell(plot_tasks[0], submodel, READ_AND_DISCARD)
388 changed = propagate_use_statements(
390 edges=[(time_step, plot), (plot, time_step)],
393 assert changed
is False
402 """A READ_AND_WRITE explicitly asks for its value to be kept around -
403 a WRITE immediately after it would otherwise silently discard that
404 value (predicateForLoad false for a plain WRITE), so it gets upgraded
405 to READ_AND_WRITE instead of being left as a superseding WRITE."""
408 _use_cell(tasks[0], submodel, READ_AND_WRITE)
411 changed = propagate_use_statements([graph])
413 assert changed
is True
414 assert _access(tasks[1])[submodel] == READ_AND_WRITE
415 assert tasks[1].cell_data.count(submodel) == 1
419 """Contrast with the above: a WRITE immediately after another plain
420 WRITE is the legitimate "recompute this fresh every time" pattern and
421 is deliberately left alone - only a READ_AND_WRITE predecessor (which
422 explicitly asked to survive) triggers the upgrade."""
428 changed = propagate_use_statements([graph])
430 assert changed
is False
431 assert _access(tasks[1])[submodel] == WRITE
435 """The same upgrade, but with the READ_AND_WRITE the last task of one
436 graph and the WRITE the first task of a successor graph."""
440 _use_cell(tasks_a[0], submodel, READ_AND_WRITE)
443 changed = propagate_use_statements([graph_a, graph_b], edges=[(graph_a, graph_b)])
445 assert changed
is True
446 assert _access(tasks_b[0])[submodel] == READ_AND_WRITE
450 """The wrap-around counterpart of test_write_immediately_after_write_is_not_upgraded():
451 a single-task graph whose one task WRITEs and self-repeats (e.g.
452 ProjectPatchOntoFaces recomputing face data fresh every time step) must
453 not get upgraded just because it is - via the self-edge - immediately
454 "after" its own previous WRITE."""
459 changed = propagate_use_statements([graph], edges=[(graph, graph)])
461 assert changed
is False
462 assert _access(tasks[0])[submodel] == WRITE
466 """Mirrors exahype2.api.fv.RusanovWithExplicitEuler's own task graph:
467 task 0 READ_AND_WRITEs, task 1 WRITEs (upgraded to READ_AND_WRITE by
468 this pass), task 2 READ_AND_WRITEs again - once task 1 is upgraded,
469 every remaining gap is distance 1 (odd), so no relay is ever needed."""
472 _use_cell(tasks[0], submodel, READ_AND_WRITE)
474 _use_cell(tasks[2], submodel, READ_AND_WRITE)
476 changed = propagate_use_statements([graph])
478 assert changed
is True
479 assert _access(tasks[1])[submodel] == READ_AND_WRITE
480 assert tasks[0].cell_data.count(submodel) == 1
481 assert tasks[2].cell_data.count(submodel) == 1
485 """A TEMPORARY use does not relay the submodel through the stacks. If
486 the relay has to go onto a step that already declares the submodel
487 TEMPORARY, that declaration is upgraded to READ_AND_WRITE."""
492 _use_cell(tasks[2], submodel, READ_AND_DISCARD)
494 changed = propagate_use_statements([graph])
496 assert changed
is True
497 assert tasks[1].cell_data.count(submodel) == 1
498 assert _access(tasks[1])[submodel] == READ_AND_WRITE
507 """Four tasks (cell WRITE, vertex, cell, vertex READ_AND_DISCARD) are
508 three tasks apart, which would be fine. FuseSteps however compiles them
509 into the traversals [A], [B, C], [D], i.e. the read is two traversals
510 after the write, and a relay is required on the fused step [B, C]."""
516 task_a =
Task(
"A", Task.GridEntity.CELL)
517 task_b =
Task(
"B", Task.GridEntity.VERTEX)
518 task_c =
Task(
"C", Task.GridEntity.CELL)
519 task_d =
Task(
"D", Task.GridEntity.VERTEX)
520 for task
in (task_a, task_b, task_c, task_d):
522 task_b.add_dependency(task_a)
523 task_c.add_dependency(task_b)
524 task_d.add_dependency(task_c)
525 task_a.use_cell(submodel, WRITE)
526 task_d.use_cell(submodel, READ_AND_DISCARD)
528 steps = FuseSteps.translate(graph,
"Test",
None)
529 assert [step.name
for step
in steps] == [
"Test_A",
"Test_B__C",
"Test_D"]
531 changed = propagate_use_statements([steps])
533 assert changed
is True
534 assert _access(steps[1])[submodel] == READ_AND_WRITE
543 """Setup never touches Q, the time step reads it first: the last setup
546 time_step, time_step_steps =
_chain_graph(
"TimeStep", 2)
548 _use_cell(time_step_steps[0], submodel, READ_AND_WRITE)
549 _use_cell(time_step_steps[1], submodel, WRITE)
551 changed = propagate_use_statements(
553 edges=[(setup, time_step), (time_step, time_step)],
556 assert changed
is True
557 assert _access(setup_steps[1])[submodel] == WRITE
558 assert submodel
not in _access(setup_steps[0])
562 """Setup may be followed by a plot first, which does not use Q: the
563 WRITE goes into setup, and the plot relays Q to the time step."""
565 time_step, time_step_steps =
_chain_graph(
"TimeStep", 2)
568 _use_cell(time_step_steps[0], submodel, READ_AND_WRITE)
569 _use_cell(time_step_steps[1], submodel, READ_AND_WRITE)
571 propagate_use_statements(
572 [setup, time_step, plot],
578 (time_step, time_step),
583 assert _access(setup_steps[0])[submodel] == WRITE
584 assert _access(plot_steps[0])[submodel] == READ_AND_WRITE
588 """There is no earlier sequence to create Q in: nothing changes."""
591 _use_cell(setup_steps[0], submodel, READ_AND_DISCARD)
593 changed = propagate_use_statements([setup])
595 assert changed
is False
596 assert submodel
not in _access(setup_steps[1])
600 """Setup writes Q, so nothing has to be created."""
602 time_step, time_step_steps =
_chain_graph(
"TimeStep", 1)
604 _use_cell(setup_steps[0], submodel, WRITE)
605 _use_cell(time_step_steps[0], submodel, READ_AND_WRITE)
607 propagate_use_statements(
609 edges=[(setup, time_step), (time_step, time_step)],
612 assert submodel
not in _access(setup_steps[1])
or _access(setup_steps[1])[submodel] == READ_AND_WRITE
621 """Two sibling consumers in subsequent steps: the first must keep Q."""
625 _use_cell(tasks[1], submodel, READ_AND_DISCARD)
626 _use_cell(tasks[2], submodel, READ_AND_DISCARD)
628 changed = propagate_use_statements([graph])
630 assert changed
is True
631 assert _access(tasks[1])[submodel] == READ_AND_WRITE
632 assert _access(tasks[2])[submodel] == READ_AND_DISCARD
636 """The next use produces Q afresh: discarding is fine."""
640 _use_cell(tasks[1], submodel, READ_AND_DISCARD)
643 propagate_use_statements([graph])
645 assert _access(tasks[1])[submodel] == READ_AND_DISCARD
649 """The upgraded declaration is an origin like any other: a reader two
650 steps later gets a relay in between."""
654 _use_cell(tasks[1], submodel, READ_AND_DISCARD)
655 _use_cell(tasks[3], submodel, READ_AND_DISCARD)
657 propagate_use_statements([graph])
659 assert _access(tasks[1])[submodel] == READ_AND_WRITE
660 assert _access(tasks[2])[submodel] == READ_AND_WRITE
One task within a peano4.api.taskgraph.Graph.
Minimal stand-in for a peano4.api.datamodel.Patch: propagate_use_statements() only ever needs submode...
The Python-side model of "what happens during one mesh sweep".
Compilers are simple functions.
The task graph abstraction allows users to formalise their solver steps over small task graphs which ...
_use_cell(step, submodel, qualifier)
test_write_before_read_needs_no_creation()
Setup writes Q, so nothing has to be created.
test_write_immediately_after_readwrite_across_an_edge_is_upgraded()
The same upgrade, but with the READ_AND_WRITE the last task of one graph and the WRITE the first task...
test_immediate_read_after_write_is_already_valid()
Distance 1, WRITE then READ_AND_DISCARD: nothing to do.
test_time_step_and_plot_composed_in_both_directions()
TimeStep and Plot may follow one another in either order - a submodel TimeStep's last task writes and...
test_immediate_readwrite_after_write_is_already_valid()
Distance 1, WRITE then READ_AND_WRITE: also fine.
test_distance_three_needs_no_change()
Worked example 2: task N writes, task N+3 reads (distance 3, odd) - already fine as it stands.
test_readwrite_origin_is_treated_like_write()
READ_AND_WRITE both reads and writes, so - like a plain WRITE - it still obliges whatever comes next ...
test_is_idempotent()
A second call once everything is already admissible must change nothing and report False.
test_cell_face_and_vertex_are_propagated_independently()
test_branching_successor_graphs_are_each_fixed_independently()
Setup -> TimeStep and Setup -> Plot: a submodel Setup last writes has to reach a real reader down eit...
test_without_the_self_edge_the_wrap_around_is_not_considered()
Without a (graph, graph) edge, a graph's own last task is a dead end - nothing wraps back to its firs...
_chain_graph(name_prefix, length)
A step sequence of length steps.
test_discard_followed_by_read_at_even_distance_gets_a_relay()
The upgraded declaration is an origin like any other: a reader two steps later gets a relay in betwee...
test_relay_upgrades_a_temporary_declaration()
A TEMPORARY use does not relay the submodel through the stacks.
_access(task, attribute_name="cell_data_access")
test_read_before_write_within_entry_sequence_is_not_changed()
There is no earlier sequence to create Q in: nothing changes.
test_read_origin_triggers_nothing()
A READ_AND_DISCARD never obliges anything downstream - even an immediately following task that does n...
test_discard_followed_by_read_is_upgraded()
Two sibling consumers in subsequent steps: the first must keep Q.
test_distance_two_inserts_readwrite_in_between()
Worked example 1: task N writes, task N+2 reads, nothing in between mentions the submodel (distance 2...
test_discard_followed_by_write_is_not_upgraded()
The next use produces Q afresh: discarding is fine.
test_gap_across_a_cross_graph_edge_is_fixed_in_the_right_graph()
A's last task writes; B's first task is at distance 2 across the (A, B) edge (one silent task in betw...
test_write_immediately_after_readwrite_is_upgraded()
A READ_AND_WRITE explicitly asks for its value to be kept around - a WRITE immediately after it would...
test_temporary_origin_triggers_nothing()
Same for TEMPORARY.
test_distance_four_inserts_readwrite_immediately_before_the_read()
Worked example 3: task N writes, task N+4 reads (distance 4, even) - fixed by inserting a READ_AND_WR...
test_write_immediately_after_write_is_not_upgraded()
Contrast with the above: a WRITE immediately after another plain WRITE is the legitimate "recompute t...
test_distances_count_fused_steps_not_tasks()
Four tasks (cell WRITE, vertex, cell, vertex READ_AND_DISCARD) are three tasks apart,...
_use_vertex(step, submodel, qualifier)
test_read_before_any_write_adds_write_to_last_step_of_entry_sequence()
Setup never touches Q, the time step reads it first: the last setup step creates it.
test_self_edge_propagates_across_the_wrap_around()
A (graph, graph) self-edge is what replaces the old Graph.cyclic flag: a graph's last task writing an...
test_read_before_any_write_via_plot_detour_also_relays_through_plot()
Setup may be followed by a plot first, which does not use Q: the WRITE goes into setup,...
test_self_edge_wrap_around_gap_is_fixed()
Same wrap-around, but with a genuine gap needing a relay: the last task writes, the graph's own first...
test_graph_with_no_edges_is_analysed_on_its_own()
A graph does not need any edge at all to be analysed - it is simply treated as a self-contained seque...
test_write_then_write_then_read_finds_the_real_read_past_the_superseding_write()
A WRITE at N is immediately followed by another WRITE at N+1 - N's own search stops right there with ...
test_temporary_declaration_is_transparent_to_the_search()
A TEMPORARY use in between is treated exactly like a task that does not mention the submodel at all: ...
_use_face(step, submodel, qualifier)
test_upgrade_runs_before_relay_insertion_so_no_relay_is_needed()
Mirrors exahype2.api.fv.RusanovWithExplicitEuler's own task graph: task 0 READ_AND_WRITEs,...
test_write_immediately_after_write_via_self_edge_is_not_upgraded()
The wrap-around counterpart of test_write_immediately_after_write_is_not_upgraded(): a single-task gr...
test_immediate_write_after_write_supersedes_needs_no_relay()
Distance 1, WRITE then WRITE again: the second WRITE is a fresh, independent production (e....