cottax.core.maths.executable module¶
The proof that a graph is executable: every entry, at every depth, is answered by one problem whose edges lie right against its cycle. Typed separately: an ExecutableGraph is always executable.
- body_of[G: NestingGraph](g: G)[source]¶
The graph without its problems: what is run, never answered.
- Return type:
G
- check_no_requirements(b: ExecutableGraph)[source]¶
No requirement is left: every condition is a problem’s, held by unknowns.
N_P = N_C
- Return type:
Iterator[Violation]
- check_body_is_dag(b: ExecutableGraph)[source]¶
With the problems taken out no cycle is left: functionally acyclic.
|S_k| = 1 ∀ S_k ∊ SCC[G N_P]
- Return type:
Iterator[Violation]
- check_one_problem_per_level(b: ExecutableGraph)[source]¶
No two problems nested in the same thing – both outermost, or both answered inside one problem – are on one cycle.
The mirror of check_nesting_ends_on_one_cycle: a nested pair must share a cycle at the outer’s level, siblings must not.
q ≁ q’ in N A(q) ∀ q ≠ q’ ∊ N_P with A(q) = A(q’)
Same parent (within) is the same A(·): in a forest, equal ancestor sets means equal chains.
- Return type:
Iterator[Violation]
- check_all_conditions_on_cycle(b: ExecutableGraph)[source]¶
Every problem iterates something, and every condition it holds is produced there: on its cycle.
|S_p| > 1 and C_p ⊆ ⋃_{k ∊ S_p} O_k ∀ p ∊ N_P
A condition nothing on the cycle produces is a constant to the problem: no unknown can move it. A read nothing produces at all (a boundary input) is one.
- Return type:
Iterator[Violation]
- check_other_reads_off_cycle(b: ExecutableGraph)[source]¶
What a problem reads beyond its conditions is not produced on its cycle.
(I_p C_p) ∩ O(S_p) = ∅ ∀ p ∊ N_P
- Return type:
Iterator[Violation]
- check_other_owns_off_cycle(b: ExecutableGraph)[source]¶
What a problem owns beyond its (iterable) unknowns is not read on its cycle. (O_p U_p) ∩ I(S_p) = ∅ ∀ p ∊ N_P
- Return type:
Iterator[Violation]
- class ExecutableGraph[N: Label, V: Place](graph: NestingGraph[N, V])[source]¶
Bases:
ProvedGraph[N,V],Generic[N,V]A graph proved executable: one problem per level, its body a DAG, and every problem’s edges against its cycle – conditions on it, everything else off it.
- checks: PerLevel[tuple[Callable[[Any], object], ...]] = (<function check_no_requirements>, <function check_one_problem_per_level>, <function check_body_is_dag>, <function check_all_conditions_on_cycle>, <function check_other_reads_off_cycle>, <function check_other_owns_off_cycle>)¶
Callable [self] -> iterable of violations, which are raised at construction unless skipped (see unchecked).
- class RequirementUnanswered(node: Label, conditions: tuple[Place, ...])[source]¶
Bases:
Violation- template: AbstractClassVar[str] = '{node!r} holds {conditions} against nothing: it has no unknowns, so no driver can move it -- `Determine` it by a variable on its cycle, or drop it'¶
e.g. ‘output {place!r} is owned by both {other!r} and {node!r}’
- Type:
Wording
- class SeveralOutermost(cycle: tuple[Label, ...], problems: tuple[Label, ...])[source]¶
Bases:
Violation- template: AbstractClassVar[str] = 'cycle {cycle} declares several problems ({problems}) with nothing saying which is outer -- one driver answers one problem, so `Combine` them into a single problem over every unknown, or `Nest` the others inside one of them. Which is a modelling decision, not something the blocking can read off the graph'¶
e.g. ‘output {place!r} is owned by both {other!r} and {node!r}’
- Type:
Wording
- class NoProblem(cycle: tuple[Label, ...])[source]¶
Bases:
Violation- template: AbstractClassVar[str] = 'coupled cycle {cycle} declares no problem, so it has no conditions and there is nothing to hand a driver -- `Cut` a variable in the cycle and drive the iteration from outside, or declare the problem to drive within the cycle'¶
e.g. ‘output {place!r} is owned by both {other!r} and {node!r}’
- Type:
Wording
- class StillCyclic(problem: Label, cycle: tuple[Label, ...])[source]¶
Bases:
Violation- template: AbstractClassVar[str] = "inside {problem!r}'s iteration, {cycle} is still cyclic with no problem on it, so there is no condition map to build for it. Cut a variable and pick a driver (inside / outside), or nest a problem that answers it in {problem!r}"¶
e.g. ‘output {place!r} is owned by both {other!r} and {node!r}’
- Type:
Wording
- class OutsideCycle(problem: Label, unknowns: tuple[Place, ...])[source]¶
Bases:
Violation- template: AbstractClassVar[str] = '{problem!r} has no body and is in no cycle at its level: nothing it reads depends on {unknowns}, so no iteration can determine them -- a problem is answered by a condition computed from its unknowns (e.g. r_u(u) = 0), not by a value'¶
e.g. ‘output {place!r} is owned by both {other!r} and {node!r}’
- Type:
Wording
- class Constant(problem: Label, places: tuple[Place, ...])[source]¶
Bases:
Violation- template: AbstractClassVar[str] = '{problem!r} reads {places} as a condition, but nothing in its cycle produces it, so no unknown of the problem can move it -- a constant is not a condition; compute it from the unknowns, or drop it'¶
e.g. ‘output {place!r} is owned by both {other!r} and {node!r}’
- Type:
Wording
- class ReadInsideCycle(problem: Label, places: tuple[Place, ...])[source]¶
Bases:
Violation- template: AbstractClassVar[str] = '{problem!r} reads {places} without holding against it, and it is produced inside the block it drives -- a driver reads its data before the block runs, so it must come from outside (an earlier block, or a boundary input)'¶
e.g. ‘output {place!r} is owned by both {other!r} and {node!r}’
- Type:
Wording
- class OwnReadInsideCycle(problem: Label, places: tuple[Place, ...], readers: tuple[Label, ...])[source]¶
Bases:
Violation- template: AbstractClassVar[str] = '{problem!r} owns {places} without answering it, and it is read by {readers} inside the block it drives -- a report is bound after convergence, so a reader on the cycle would iterate against a value that does not exist yet'¶
e.g. ‘output {place!r} is owned by both {other!r} and {node!r}’
- Type:
Wording