From cf19856118e9a48ec2717751a773574c7db7fbc4 Mon Sep 17 00:00:00 2001 From: Anton Reinhard Date: Mon, 28 Aug 2023 14:56:13 +0200 Subject: [PATCH] Add Node Documentation --- .gitea/workflows/julia-package-ci.yml | 1 + src/node/compare.jl | 20 ++++++++ src/node/create.jl | 30 ++++++++++++ src/node/print.jl | 10 ++++ src/node/properties.jl | 67 +++++++++++++++++++++++---- src/node/type.jl | 45 +++++++++++++++++- src/node/validate.jl | 25 +++++++++- 7 files changed, 187 insertions(+), 11 deletions(-) diff --git a/.gitea/workflows/julia-package-ci.yml b/.gitea/workflows/julia-package-ci.yml index fe35084..b90260a 100644 --- a/.gitea/workflows/julia-package-ci.yml +++ b/.gitea/workflows/julia-package-ci.yml @@ -181,6 +181,7 @@ jobs: - name: Webhook Trigger uses: https://github.com/zzzze/webhook-trigger@master + continue-on-error: true with: data: "{\"event\":\"action_completed\", \"download_url\":\"deckardcain.local:8099/something\"}" webhook_url: ${{ secrets.WEBHOOK_URL }} diff --git a/src/node/compare.jl b/src/node/compare.jl index 3743690..d2e2c04 100644 --- a/src/node/compare.jl +++ b/src/node/compare.jl @@ -1,15 +1,35 @@ +""" + ==(e1::Edge, e2::Edge) + +Equality comparison between two edges. +""" function ==(e1::Edge, e2::Edge) return e1.edge[1] == e2.edge[1] && e1.edge[2] == e2.edge[2] end +""" + ==(n1::Node, n2::Node) + +Fallback equality comparison between two nodes. For equal node types, the more specific versions of this function will be called. +""" function ==(n1::Node, n2::Node) return false end +""" + ==(n1::ComputeTaskNode, n2::ComputeTaskNode) + +Equality comparison between two [`ComputeTaskNode`](@ref)s. +""" function ==(n1::ComputeTaskNode, n2::ComputeTaskNode) return n1.id == n2.id end +""" + ==(n1::DataTaskNode, n2::DataTaskNode) + +Equality comparison between two [`DataTaskNode`](@ref)s. +""" function ==(n1::DataTaskNode, n2::DataTaskNode) return n1.id == n2.id end diff --git a/src/node/create.jl b/src/node/create.jl index b13d990..dd01d05 100644 --- a/src/node/create.jl +++ b/src/node/create.jl @@ -1,23 +1,53 @@ +""" + make_node(t::AbstractTask) + +Fallback implementation of `make_node` for an [`AbstractTask`](@ref), throwing an error. +""" function make_node(t::AbstractTask) return error("Cannot make a node from this task type") end +""" + make_node(t::AbstractDataTask) + +Construct and return a new [`DataTaskNode`](@ref) with the given task. +""" function make_node(t::AbstractDataTask) return DataTaskNode(t) end +""" + make_node(t::AbstractComputeTask) + +Construct and return a new [`ComputeTaskNode`](@ref) with the given task. +""" function make_node(t::AbstractComputeTask) return ComputeTaskNode(t) end +""" + make_edge(n1::Node, n2::Node) + +Fallback implementation of `make_edge` throwing an error. If you got this error it likely means you tried to construct an edge between two nodes of the same type. +""" function make_edge(n1::Node, n2::Node) return error("Can only create edges from compute to data node or reverse") end +""" + make_edge(n1::ComputeTaskNode, n2::DataTaskNode) + +Construct and return a new [`Edge`](@ref) pointing from `n1` (child) to `n2` (parent). +""" function make_edge(n1::ComputeTaskNode, n2::DataTaskNode) return Edge((n1, n2)) end +""" + make_edge(n1::DataTaskNode, n2::ComputeTaskNode) + +Construct and return a new [`Edge`](@ref) pointing from `n1` (child) to `n2` (parent). +""" function make_edge(n1::DataTaskNode, n2::ComputeTaskNode) return Edge((n1, n2)) end diff --git a/src/node/print.jl b/src/node/print.jl index debaa88..3a6ee1a 100644 --- a/src/node/print.jl +++ b/src/node/print.jl @@ -1,7 +1,17 @@ +""" + show(io::IO, n::Node) + +Print a short string representation of the node to io. +""" function show(io::IO, n::Node) return print(io, "Node(", n.task, ")") end +""" + show(io::IO, e::Edge) + +Print a short string representation of the edge to io. +""" function show(io::IO, e::Edge) return print(io, "Edge(", e.edge[1], ", ", e.edge[2], ")") end diff --git a/src/node/properties.jl b/src/node/properties.jl index abbe252..2edb400 100644 --- a/src/node/properties.jl +++ b/src/node/properties.jl @@ -1,17 +1,46 @@ +""" + is_entry_node(node::Node) + +Return whether this node is an entry node in its graph, i.e., it has no children. +""" is_entry_node(node::Node) = length(node.children) == 0 + +""" + is_exit_node(node::Node) + +Return whether this node is an exit node of its graph, i.e., it has no parents. +""" is_exit_node(node::Node) = length(node.parents) == 0 -# children = prerequisite nodes, nodes that need to execute before the task, edges point into this task +""" + children(node::Node) + +Return a copy of the node's children so it can safely be muted without changing the node in the graph. + +A node's children are its prerequisite nodes, nodes that need to execute before the task of this node. +""" function children(node::Node) return copy(node.children) end -# parents = subsequent nodes, nodes that need this node to execute, edges point from this task +""" + parents(node::Node) + +Return a copy of the node's parents so it can safely be muted without changing the node in the graph. + +A node's parents are its subsequent nodes, nodes that need this node to execute. +""" function parents(node::Node) return copy(node.parents) end -# siblings = all children of any parents, no duplicates, includes the node itself +""" + siblings(node::Node) + +Return a vector of all siblings of this node. + +A node's siblings are all children of any of its parents. The result contains no duplicates and includes the node itself. +""" function siblings(node::Node) result = Set{Node}() push!(result, node) @@ -22,7 +51,16 @@ function siblings(node::Node) return result end -# partners = all parents of any children, no duplicates, includes the node itself +""" + partners(node::Node) + +Return a vector of all partners of this node. + +A node's partners are all parents of any of its children. The result contains no duplicates and includes the node itself. + +Note: This is very slow when there are multiple children with many parents. +This is less of a problem in [`siblings(node::Node)`](@ref) because (depending on the model) there are no nodes with a large number of children, or only a single one. +""" function partners(node::Node) result = Set{Node}() push!(result, node) @@ -33,8 +71,11 @@ function partners(node::Node) return result end -# alternative version to partners(Node), avoiding allocation of a new set -# works on the given set and returns nothing +""" + partners(node::Node, set::Set{Node}) + +Alternative version to [`partners(node::Node)`](@ref), avoiding allocation of a new set. Works on the given set and returns `nothing`. +""" function partners(node::Node, set::Set{Node}) push!(set, node) for child in node.children @@ -43,10 +84,20 @@ function partners(node::Node, set::Set{Node}) return nothing end -function is_parent(potential_parent, node) +""" + is_parent(potential_parent::Node, node::Node) + +Return whether the `potential_parent` is a parent of `node`. +""" +function is_parent(potential_parent::Node, node::Node) return potential_parent in node.parents end -function is_child(potential_child, node) +""" + is_child(potential_child::Node, node::Node) + +Return whether the `potential_child` is a child of `node`. +""" +function is_child(potential_child::Node, node::Node) return potential_child in node.children end diff --git a/src/node/type.jl b/src/node/type.jl index 45ff267..db32f1b 100644 --- a/src/node/type.jl +++ b/src/node/type.jl @@ -5,12 +5,33 @@ using Base.Threads # TODO: reliably find out how many threads we're running with (nthreads() returns 1 when precompiling :/) rng = [Random.MersenneTwister(0) for _ in 1:32] +""" + Node + +The abstract base type of every node. + +See [`DataTaskNode`](@ref), [`ComputeTaskNode`](@ref) and [`make_node`](@ref). +""" abstract type Node end # declare this type here because it's needed # the specific operations are declared in graph.jl abstract type Operation end +""" + DataTaskNode <: Node + +Any node that transfers data and does no computation. + +# Fields +`.task`: The node's data task type. Usually [`DataTask`](@ref).\\ +`.parents`: A vector of the node's parents (i.e. nodes that depend on this one).\\ +`.children`: A vector of the node's children (i.e. nodes that this one depends on).\\ +`.id`: The node's id. Improves the speed of comparisons.\\ +`.nodeReduction`: Either this node's [`NodeReduction`](@ref) or `missing`, if none. There can only be at most one.\\ +`.nodeSplit`: Either this node's [`NodeSplit`](@ref) or `missing`, if none. There can only be at most one.\\ +`.nodeFusion`: Either this node's [`NodeFusion`](@ref) or `missing`, if none. There can only be at most one for DataTaskNodes. +""" mutable struct DataTaskNode <: Node task::AbstractDataTask @@ -33,7 +54,20 @@ mutable struct DataTaskNode <: Node nodeFusion::Union{Operation, Missing} end -# same as DataTaskNode +""" + ComputeTaskNode <: Node + +Any node that transfers data and does no computation. + +# Fields +`.task`: The node's data task type. Usually [`DataTask`](@ref).\\ +`.parents`: A vector of the node's parents (i.e. nodes that depend on this one).\\ +`.children`: A vector of the node's children (i.e. nodes that this one depends on).\\ +`.id`: The node's id. Improves the speed of comparisons.\\ +`.nodeReduction`: Either this node's [`NodeReduction`](@ref) or `missing`, if none. There can only be at most one.\\ +`.nodeSplit`: Either this node's [`NodeSplit`](@ref) or `missing`, if none. There can only be at most one.\\ +`.nodeFusion`: A vector of this node's [`NodeFusion`](@ref)s. For a ComputeTaskNode there can be any number of these, unlike the DataTaskNodes. +""" mutable struct ComputeTaskNode <: Node task::AbstractComputeTask parents::Vector{Node} @@ -66,6 +100,15 @@ ComputeTaskNode(t::AbstractComputeTask) = ComputeTaskNode( Vector{NodeFusion}(), ) +""" + Edge + +Type of an edge in the graph. Edges can only exist between a [`DataTaskNode`](@ref) and a [`ComputeTaskNode`](@ref) or vice versa, not between two of the same type of node. + +An edge always points from child to parent: `child = e.edge[1]` and `parent = e.edge[2]`. + +The child is the prerequisite node of the parent. +""" struct Edge # edge points from child to parent edge::Union{ diff --git a/src/node/validate.jl b/src/node/validate.jl index 5cd1244..a16df17 100644 --- a/src/node/validate.jl +++ b/src/node/validate.jl @@ -1,3 +1,12 @@ +""" + is_valid_node(graph::DAG, node::Node) + +Verify that a given node is valid in the graph. Call like `@test is_valid_node(g, n)`. Uses `@assert` to fail if something is invalid but also provide an error message. + +This function is very performance intensive and should only be used when testing or debugging. + +See also this function's specific versions for the concrete Node types [`is_valid(graph::DAG, node::ComputeTaskNode)`](@ref) and [`is_valid(graph::DAG, node::DataTaskNode)`](@ref). +""" function is_valid_node(graph::DAG, node::Node) @assert node in graph "Node is not part of the given graph!" @@ -22,7 +31,13 @@ function is_valid_node(graph::DAG, node::Node) return true end -# call with @assert +""" + is_valid(graph::DAG, node::ComputeTaskNode) + +Verify that the given compute node is valid in the graph. Call with `@assert` or `@test` when testing or debugging. + +This also calls [`is_valid_node(graph::DAG, node::Node)`](@ref). +""" function is_valid(graph::DAG, node::ComputeTaskNode) @assert is_valid_node(graph, node) @@ -32,7 +47,13 @@ function is_valid(graph::DAG, node::ComputeTaskNode) return true end -# call with @assert +""" + is_valid(graph::DAG, node::DataTaskNode) + +Verify that the given compute node is valid in the graph. Call with `@assert` or `@test` when testing or debugging. + +This also calls [`is_valid_node(graph::DAG, node::Node)`](@ref). +""" function is_valid(graph::DAG, node::DataTaskNode) @assert is_valid_node(graph, node)