Peano
Loading...
Searching...
No Matches
peano4.api.taskgraph.compiler.PropagateUseStatements Namespace Reference

Functions

bool propagate_use_statements (List[List["peano4.api.solversteps.Step"]] sequences, Optional[List[Tuple[List["peano4.api.solversteps.Step"], List["peano4.api.solversteps.Step"]]]] edges=None)
 Derive whichever further use_cell()/use_face()/use_vertex() declarations a set of compiled step sequences need so that every submodel they write is safely handed on to whoever reads it next.
 
bool _create_submodels_read_before_written (Dict[int, List] steps_of_sequence, List[Tuple[int, int]] edges, str attribute_name, str access_attribute_name, str use_method_name)
 Add a WRITE declaration to the last step of an entry sequence for every submodel that can be read before it is ever written -.
 
bool _upgrade_discards_followed_by_reads (Dict[int, List] steps_of_sequence, List[Tuple[int, int]] edges, str attribute_name, str access_attribute_name)
 Upgrade every READ_AND_DISCARD declaration to READ_AND_WRITE if a later step still reads the submodel before any step writes it again -.
 
 _step_backward (sequence, index, steps_of_sequence, predecessors_of_sequence)
 The exact reverse of _step_forward(): yield every (sequence, index) that can immediately reach (sequence, index) - the previous step in the same sequence if there is one, or otherwise the last step of every sequence declared (via edges) to lead into this one.
 
bool _upgrade_writes_immediately_following_read_and_write (Dict[int, List] steps_of_sequence, List[Tuple[int, int]] edges, str attribute_name, str access_attribute_name)
 A plain WRITE is normally treated as a fresh, independent production that supersedes whatever came before (.
 
bool _propagate_use_statements_for_one_kind (Dict[int, List] steps_of_sequence, List[Tuple[int, int]] edges, str attribute_name, str access_attribute_name, str use_method_name)
 
 _step_forward (sequence, index, steps_of_sequence, successors_of_sequence)
 Yield every (sequence, index) immediately reachable from (sequence, index) - the next step in the same sequence if there is one, or otherwise the first step of every declared successor sequence (several, if a sequence may be followed by more than one other).
 
 _find_next_consecutive_uses (origin_sequence, origin_index, submodel, attribute_name, access_attribute_name, steps_of_sequence, successors_of_sequence)
 Breadth-first search forward from (origin_sequence, origin_index), stopping each path the moment it reaches a step that declares a READ_AND_DISCARD, WRITE or READ_AND_WRITE use of submodel (a TEMPORARY declaration is transparent to this search -.
 
bool _fix_forward_gap (origin_sequence, origin_index, submodel, steps_of_sequence, successors_of_sequence, attribute_name, access_attribute_name, use_method_name)
 

Function Documentation

◆ _create_submodels_read_before_written()

bool peano4.api.taskgraph.compiler.PropagateUseStatements._create_submodels_read_before_written ( Dict[int, List]  steps_of_sequence,
List[Tuple[int, int]]  edges,
str  attribute_name,
str  access_attribute_name,
str  use_method_name 
)
protected

Add a WRITE declaration to the last step of an entry sequence for every submodel that can be read before it is ever written -.

See also
the corresponding section of propagate_use_statements().
Parameters
steps_of_sequenceDict[int, [peano4.api.solversteps.Step]] The sequences handed over to propagate_use_statements(), each sequence identified by its index.
edges[(int, int)] The edges handed over to propagate_use_statements(), translated into pairs of sequence indices.
attribute_namestr Which kind of submodel (cell_data, face_data or vertex_data) to analyse.
access_attribute_namestr The access dictionary matching attribute_name (cell_data_access, face_data_access or vertex_data_access).
use_method_namestr The Step method that declares a submodel of this kind (use_cell, use_face or use_vertex).
Returns
bool True if this pass added at least one declaration.

Definition at line 255 of file PropagateUseStatements.py.

References peano4.api.taskgraph.compiler.PropagateUseStatements._step_forward().

Referenced by peano4.api.taskgraph.compiler.PropagateUseStatements.propagate_use_statements().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _find_next_consecutive_uses()

peano4.api.taskgraph.compiler.PropagateUseStatements._find_next_consecutive_uses (   origin_sequence,
  origin_index,
  submodel,
  attribute_name,
  access_attribute_name,
  steps_of_sequence,
  successors_of_sequence 
)
protected

Breadth-first search forward from (origin_sequence, origin_index), stopping each path the moment it reaches a step that declares a READ_AND_DISCARD, WRITE or READ_AND_WRITE use of submodel (a TEMPORARY declaration is transparent to this search -.

See also
Task.DataAccess.TEMPORARY - so a path continues straight through it, exactly as if it were silent). Returns one (step, distance, one_before_step) tuple per such path (several if the search branches across more than one successor sequence) - one_before_step is the step exactly one before the found one along that path (possibly in a different, preceding sequence), i.e. where a READ_AND_WRITE relay has to go if distance turns out to be even.

Definition at line 595 of file PropagateUseStatements.py.

References peano4.api.taskgraph.compiler.PropagateUseStatements._step_forward().

Referenced by peano4.api.taskgraph.compiler.PropagateUseStatements._fix_forward_gap(), and peano4.api.taskgraph.compiler.PropagateUseStatements._upgrade_discards_followed_by_reads().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _fix_forward_gap()

bool peano4.api.taskgraph.compiler.PropagateUseStatements._fix_forward_gap (   origin_sequence,
  origin_index,
  submodel,
  steps_of_sequence,
  successors_of_sequence,
  attribute_name,
  access_attribute_name,
  use_method_name 
)
protected

Definition at line 649 of file PropagateUseStatements.py.

References peano4.api.taskgraph.compiler.PropagateUseStatements._find_next_consecutive_uses().

Referenced by peano4.api.taskgraph.compiler.PropagateUseStatements._propagate_use_statements_for_one_kind().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _propagate_use_statements_for_one_kind()

bool peano4.api.taskgraph.compiler.PropagateUseStatements._propagate_use_statements_for_one_kind ( Dict[int, List]  steps_of_sequence,
List[Tuple[int, int]]  edges,
str  attribute_name,
str  access_attribute_name,
str  use_method_name 
)
protected

Definition at line 541 of file PropagateUseStatements.py.

References peano4.api.taskgraph.compiler.PropagateUseStatements._fix_forward_gap().

Referenced by peano4.api.taskgraph.compiler.PropagateUseStatements.propagate_use_statements().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _step_backward()

peano4.api.taskgraph.compiler.PropagateUseStatements._step_backward (   sequence,
  index,
  steps_of_sequence,
  predecessors_of_sequence 
)
protected

The exact reverse of _step_forward(): yield every (sequence, index) that can immediately reach (sequence, index) - the previous step in the same sequence if there is one, or otherwise the last step of every sequence declared (via edges) to lead into this one.

Definition at line 425 of file PropagateUseStatements.py.

Referenced by peano4.api.taskgraph.compiler.PropagateUseStatements._upgrade_writes_immediately_following_read_and_write().

Here is the caller graph for this function:

◆ _step_forward()

peano4.api.taskgraph.compiler.PropagateUseStatements._step_forward (   sequence,
  index,
  steps_of_sequence,
  successors_of_sequence 
)
protected

Yield every (sequence, index) immediately reachable from (sequence, index) - the next step in the same sequence if there is one, or otherwise the first step of every declared successor sequence (several, if a sequence may be followed by more than one other).

Definition at line 577 of file PropagateUseStatements.py.

Referenced by peano4.api.taskgraph.compiler.PropagateUseStatements._create_submodels_read_before_written(), and peano4.api.taskgraph.compiler.PropagateUseStatements._find_next_consecutive_uses().

Here is the caller graph for this function:

◆ _upgrade_discards_followed_by_reads()

bool peano4.api.taskgraph.compiler.PropagateUseStatements._upgrade_discards_followed_by_reads ( Dict[int, List]  steps_of_sequence,
List[Tuple[int, int]]  edges,
str  attribute_name,
str  access_attribute_name 
)
protected

Upgrade every READ_AND_DISCARD declaration to READ_AND_WRITE if a later step still reads the submodel before any step writes it again -.

See also
the corresponding section of propagate_use_statements().
Parameters
steps_of_sequenceDict[int, [peano4.api.solversteps.Step]] The sequences handed over to propagate_use_statements(), each sequence identified by its index.
edges[(int, int)] The edges handed over to propagate_use_statements(), translated into pairs of sequence indices.
attribute_namestr Which kind of submodel (cell_data, face_data or vertex_data) to analyse.
access_attribute_namestr The access dictionary matching attribute_name (cell_data_access, face_data_access or vertex_data_access).
Returns
bool True if this pass upgraded at least one declaration.

Definition at line 352 of file PropagateUseStatements.py.

References peano4.api.taskgraph.compiler.PropagateUseStatements._find_next_consecutive_uses().

Referenced by peano4.api.taskgraph.compiler.PropagateUseStatements.propagate_use_statements().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _upgrade_writes_immediately_following_read_and_write()

bool peano4.api.taskgraph.compiler.PropagateUseStatements._upgrade_writes_immediately_following_read_and_write ( Dict[int, List]  steps_of_sequence,
List[Tuple[int, int]]  edges,
str  attribute_name,
str  access_attribute_name 
)
protected

A plain WRITE is normally treated as a fresh, independent production that supersedes whatever came before (.

See also
_fix_forward_gap) - correct whenever a step genuinely does not care what the submodel held before it ran. But a WRITE immediately (distance 1) after a READ_AND_WRITE of the very same submodel is a different, narrower situation: the READ_AND_WRITE step explicitly asked for its result to be kept around, and the very next step then declares itself uninterested in reading it
  • one of these two declarations has to be wrong, and silently trusting the WRITE would mean the READ_AND_WRITE step's stack storage is populated (predicateForStore true) and then never loaded again (predicateForLoad false for a plain WRITE -
peano4.api.taskgraph.compiler.DeriveLoadStoreComputeFlag), which is at best wasted storage and at worst - if the very next step's own computation actually does depend on that value, just under the wrong qualifier - silently wrong numerics.

This is therefore auto-corrected rather than merely reported: every step whose own declaration of a submodel is WRITE, and that has at least one immediate predecessor (

See also
_step_backward - several, if the step is reachable from more than one sequence) that declares the same submodel READ_AND_WRITE, has its own declaration silently upgraded to READ_AND_WRITE, logged via peano4.api.Logger().log() so it is visible without changing behaviour otherwise. Run to a fixed point first, before _propagate_use_statements_for_one_kind()'s own relay-insertion pass: an upgraded declaration is exactly as valid an origin as one a task graph author wrote by hand, and doing this first means the relay pass never has to reason about a WRITE that is about to change.

This is deliberately narrower than "any WRITE reachable from an earlier WRITE/READ_AND_WRITE": a WRITE immediately after another WRITE of the same submodel is left alone, since that is exactly the legitimate "recompute this fresh every time" pattern _fix_forward_gap() already has to support (

See also
there) - only a READ_AND_WRITE predecessor, which explicitly asked for its value to survive, triggers the upgrade.
Parameters
steps_of_sequenceDict[int, [peano4.api.solversteps.Step]] The sequences handed over to propagate_use_statements(), each sequence identified by its index.
edges[(int, int)] The edges handed over to propagate_use_statements(), translated into pairs of sequence indices.
attribute_namestr Which kind of submodel (cell_data, face_data or vertex_data) to analyse.
access_attribute_namestr The access dictionary matching attribute_name (cell_data_access, face_data_access or vertex_data_access).
Returns
bool True if this pass upgraded at least one declaration.

Definition at line 443 of file PropagateUseStatements.py.

References peano4.api.taskgraph.compiler.PropagateUseStatements._step_backward().

Referenced by peano4.api.taskgraph.compiler.PropagateUseStatements.propagate_use_statements().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ propagate_use_statements()

bool peano4.api.taskgraph.compiler.PropagateUseStatements.propagate_use_statements ( List[List["peano4.api.solversteps.Step"]]  sequences,
Optional[List[Tuple[List["peano4.api.solversteps.Step"], List["peano4.api.solversteps.Step"]]]]   edges = None 
)

Derive whichever further use_cell()/use_face()/use_vertex() declarations a set of compiled step sequences need so that every submodel they write is safely handed on to whoever reads it next.

This is a postprocessing step that runs on the output of a task graph compiler (peano4.api.taskgraph.compiler), i.e. on the sequences of peano4.api.solversteps.Step instances (Observers, aka mesh traversals) a compiler such as peano4.api.taskgraph.compiler.Sequential.translate() or peano4.api.taskgraph.compiler.FuseSteps.translate() yields - one sequence per task graph. All distances below therefore count mesh traversals, not tasks: a compiler that fuses several tasks into one Step changes these distances, and only the compiled Steps know them.

Every Step carries the Task.DataAccess qualifier of each submodel it declares in its cell_data_access/face_data_access/vertex_data_access (populated by the compiler). This function reads and amends these qualifiers, and it adds use_cell()/use_face()/use_vertex() declarations to the Steps where required.

Creating submodels that are read before they are ever written

The very first pass looks for submodels that a step loads (READ_AND_DISCARD or READ_AND_WRITE) before any step has ever written them. Peano would then try to load data that does not exist yet and stop with an assertion on its stacks. A sequence without any incoming edge (from another sequence or itself) is an entry sequence, i.e. it runs first, such as a setup phase. The pass searches forward from the first step of every entry sequence, following edges, and stops each path at the first step that declares the submodel (TEMPORARY is transparent). If this step reads the submodel, the pass adds a WRITE declaration of the submodel to the last step of the entry sequence: this step then creates the submodel and stores it, and the passes below make it reach the reader. The created submodel holds whatever its default constructor yields, i.e. the steps reading it have to cope with uninitialised values - typically through guards that ensure that they only read it once a "real" WRITE has happened. The WRITE goes into the entry sequence, as this one runs only once: a WRITE within a repeating sequence would overwrite the submodel's value every time the sequence runs. A read within the entry sequence itself cannot be fixed this way and is only logged.

See also
_create_submodels_read_before_written().

Keeping submodels that a READ_AND_DISCARD would discard too early

The second pass looks for READ_AND_DISCARD declarations after which another step still reads the submodel (READ_AND_DISCARD or READ_AND_WRITE) before any step writes it again. This happens, for example, if two sibling tasks both consume the same submodel and the compiler maps them onto two subsequent mesh traversals: each task is done with the submodel, but only the latter traversal may discard it. The pass searches forward from every READ_AND_DISCARD, following edges, and stops each path at the first step that declares the submodel (TEMPORARY is transparent). If this step reads the submodel, the READ_AND_DISCARD is upgraded to READ_AND_WRITE, i.e. the submodel is stored again. The pass runs to a fixed point, and the passes below treat the upgraded declaration like any other one.

See also
_upgrade_discards_followed_by_reads().

Upgrading a WRITE that immediately follows a READ_AND_WRITE

Before any of the below runs, a narrower, separate pass first upgrades every WRITE declaration that sits immediately (distance 1, following edges the same way as everything else here) after a READ_AND_WRITE declaration of the same submodel into a READ_AND_WRITE of its own -

See also
_upgrade_writes_immediately_following_read_and_write() for why (in short: trusting the WRITE there would silently discard whatever the READ_AND_WRITE step explicitly asked to keep). This is logged via peano4.api.Logger().log() whenever it happens. A WRITE immediately after another plain WRITE is deliberately left alone by this -
that function's own docstring for why that case is different.

The rule

Every use_cell()/use_face()/use_vertex() declaration carries a Task.DataAccess qualifier - READ_AND_DISCARD, WRITE, READ_AND_WRITE or TEMPORARY. Peano only relays a submodel from one Observer to the next through its stacks if both declare it, so a submodel a step WRITEs is lost unless some later step actually reads it, and every step in between has to relay it there. Precisely:

Only a READ_AND_DISCARD or READ_AND_WRITE ever "picks up" a submodel a WRITE produced; a WRITE or READ_AND_WRITE is what obliges anything downstream in the first place (a plain READ_AND_DISCARD, or leaving the submodel alone entirely, imposes no obligation of its own). Two other qualifiers get special treatment when they turn up downstream of such an obligation:

  • A bare WRITE encountered downstream is a fresh, independent production - it does not read what came before, it simply supersedes it, so there is nothing left to protect on the way to it. The search stops there with no constraint at all (that WRITE is instead - separately - checked as an origin of its own, once the surrounding loop reaches it in turn).
  • A TEMPORARY use is transparent: it is treated exactly like a step that does not mention the submodel at all (
    See also
    Task.DataAccess.TEMPORARY) - the search simply continues straight through it.
    That leaves READ_AND_DISCARD and READ_AND_WRITE as the only uses that actually stop the search and matter. For every WRITE or READ_AND_WRITE use at some step, let distance be how many steps separate it from the next such READ_AND_DISCARD/READ_AND_WRITE use of the same submodel (distance 1 means "the very next step"):
  • an odd distance (1, 3, 5, ...): always fine, whatever lies strictly in between (nothing, in the sense above - every step there is either genuinely silent or a WRITE/TEMPORARY, neither of which counts).
  • an even distance (2, 4, 6, ...): not admissible as it stands, but always fixable: insert a READ_AND_WRITE declaration of the submodel on the step exactly one before the next READ_AND_DISCARD/ READ_AND_WRITE (an odd distance away from the origin, so the two legs it creates - origin-to-relay and relay-to-next-use - are each individually valid by the rule above). This is the only thing this function ever does to fix a violation.
  • no such READ_AND_DISCARD/READ_AND_WRITE reachable at all (every path from here either dead-ends or runs into a superseding WRITE first): nothing to do - there is nothing downstream to protect.

The freshly-inserted READ_AND_WRITE is itself fed back through the very same check (it is, for this purpose, exactly as much of an origin as a plain WRITE), so a single call already iterates this to a fixed point

  • one call is enough, calling it again changes nothing further (idempotent).

Worked examples (a single sequence of steps 0, 1, 2, 3, 4, ...)

  • Step N has a WRITE, step N+2 a READ_AND_DISCARD, nothing in between mentions the submodel: distance 2 (even) - not admissible as it stands. Fixed by inserting a READ_AND_WRITE declaration on step N+1.
  • Step N has a WRITE, step N+3 a READ_AND_DISCARD: distance 3 (odd) - already fine, nothing to do.
  • Step N has a WRITE, step N+4 a READ_AND_DISCARD: distance 4 (even) - not admissible. Fixed by inserting a READ_AND_WRITE declaration on step N+3 (one before the READ_AND_DISCARD) - not on step N+1: inserting right after step N would put the relay at distance 1 from the origin, and the next leg (relay to the eventual READ_AND_DISCARD) would then be distance 3 away, still fine, but the relay would sit doing nothing useful for two further steps; inserting right before the READ_AND_DISCARD instead keeps both legs minimal.
  • Step N has a WRITE, step N+1 WRITEs the same submodel again: N+1's WRITE supersedes N's - nothing to protect, nothing to do (even though the two are adjacent).

Sequences, and how edges connect them

Within one sequence, "the next step" is simply the next one in the list. Once a sequence's own steps run out, "the next step" continues into whichever sequence(s) edges declares reachable from it - a sequence can have several such successors (e.g. a time-stepping sequence that may be followed by either another time step or a plot, decided only at runtime), in which case the rule is checked, and fixed if need be, independently along every such continuation, since we cannot know which one will actually be taken. Two different continuations can legitimately reach the very same later step at distances of different parity (e.g. straight from setup into time-stepping versus via a plot detour first, if plotting happens to be due immediately) - both are checked and, if need be, independently fixed; a silent step is only ever revisited once per parity of how far away it was reached, which is all this rule ever cares about, so a cyclic edge structure (e.g. time-stepping and plotting following one another indefinitely) is still only walked a bounded number of times, not indefinitely. A sequence that is meant to repeat itself declares a (sequence, sequence) self-edge - the successor of its own last step is (among others, if it is also reachable from elsewhere) its own first step. A sequence with no outgoing edges at all simply ends there - whatever it last wrote and never got read is not this function's concern (it might be handed to a caller entirely outside the task-graph machinery, e.g. plotted from C++ code this Python layer does not see).

Parameters
sequences[[peano4.api.solversteps.Step]] Every step sequence to consider, typically the outcome of one compiler call per task graph. A sequence has to be listed here to be analysed at all, whether or not it appears in edges (a sequence with no edges is simply analysed on its own). The steps are modified in place.
edges[([peano4.api.solversteps.Step], [peano4.api.solversteps.Step])] Declares which sequences may run immediately after which other ones - each (predecessor, successor) pair means "successor's first step may immediately follow predecessor's last step". Sequences are identified by identity, i.e. pass the very list objects that are also handed over through sequences. Defaults to none (every sequence analysed purely on its own).
Returns
bool True if this call added or upgraded at least one declaration anywhere, False if everything was already admissible.

Definition at line 10 of file PropagateUseStatements.py.

References peano4.api.taskgraph.compiler.PropagateUseStatements._create_submodels_read_before_written(), peano4.api.taskgraph.compiler.PropagateUseStatements._propagate_use_statements_for_one_kind(), peano4.api.taskgraph.compiler.PropagateUseStatements._upgrade_discards_followed_by_reads(), and peano4.api.taskgraph.compiler.PropagateUseStatements._upgrade_writes_immediately_following_read_and_write().

Here is the call graph for this function: