diff --git a/GNNGraphs/Project.toml b/GNNGraphs/Project.toml index 804f057a5..6f54e28b7 100644 --- a/GNNGraphs/Project.toml +++ b/GNNGraphs/Project.toml @@ -1,6 +1,6 @@ name = "GNNGraphs" uuid = "aed8fd31-079b-4b5a-b342-a13352159b8c" -version = "1.6.1-DEV" +version = "1.6.2-DEV" authors = ["Carlo Lucibello and contributors"] [workspace] diff --git a/GNNGraphs/src/transform.jl b/GNNGraphs/src/transform.jl index 12d438528..79b87eb74 100644 --- a/GNNGraphs/src/transform.jl +++ b/GNNGraphs/src/transform.jl @@ -280,6 +280,15 @@ function remove_nodes(g::GNNGraph, p::AbstractFloat) return remove_nodes(g, nodes_to_remove) end +function _graph_indicator_after_node_growth(g::GNNGraph, n_new_nodes::Integer) + n_new_nodes <= 0 && return g.graph_indicator + g.num_graphs > 1 && throw(ArgumentError( + "Cannot add nodes to a batched graph without specifying which graph they belong to.", + )) + isnothing(g.graph_indicator) && return nothing + return cat_features(g.graph_indicator, ones_like(g.graph_indicator, n_new_nodes)) +end + """ add_edges(g::GNNGraph, s::AbstractVector, t::AbstractVector; [edata]) add_edges(g::GNNGraph, (s, t); [edata]) @@ -290,7 +299,8 @@ Optionally, pass the edge weight `w` and the features `edata` for the new edges Returns a new graph sharing part of the underlying data with `g`. If the `s` or `t` contain nodes that are not already present in the graph, -they are added to the graph as well. +they are added to the graph as well. Adding new nodes to a batched graph is +not supported because their graph assignment is ambiguous. # Examples @@ -333,6 +343,9 @@ function add_edges(g::GNNGraph{<:COO_T}, data::COO_T; edata = nothing) end @assert minimum(snew) >= 1 @assert minimum(tnew) >= 1 + num_nodes = max(maximum(snew), maximum(tnew), g.num_nodes) + graph_indicator = _graph_indicator_after_node_growth(g, num_nodes - g.num_nodes) + num_new = length(snew) edata = normalize_graphdata(edata, default_name = :e, n = num_new) edata = cat_features(g.edata, edata) @@ -343,7 +356,6 @@ function add_edges(g::GNNGraph{<:COO_T}, data::COO_T; edata = nothing) w = get_edge_weight(g) w = cat_features(w, wnew, g.num_edges, num_new) - num_nodes = max(maximum(snew), maximum(tnew), g.num_nodes) if num_nodes > g.num_nodes ndata_new = normalize_graphdata((;), default_name = :x, n = num_nodes - g.num_nodes) ndata = cat_features(g.ndata, ndata_new) @@ -353,7 +365,7 @@ function add_edges(g::GNNGraph{<:COO_T}, data::COO_T; edata = nothing) return GNNGraph((s, t, w), num_nodes, length(s), g.num_graphs, - g.graph_indicator, + graph_indicator, ndata, edata, g.gdata) end @@ -553,15 +565,17 @@ end Add `n` new nodes to graph `g`. In the new graph, these nodes will have indexes from `g.num_nodes + 1` -to `g.num_nodes + n`. +to `g.num_nodes + n`. Adding nodes to a batched graph is not supported +because their graph assignment is ambiguous. """ function add_nodes(g::GNNGraph{<:COO_T}, n::Integer; ndata = (;)) + graph_indicator = _graph_indicator_after_node_growth(g, n) ndata = normalize_graphdata(ndata, default_name = :x, n = n) ndata = cat_features(g.ndata, ndata) GNNGraph(g.graph, g.num_nodes + n, g.num_edges, g.num_graphs, - g.graph_indicator, + graph_indicator, ndata, g.edata, g.gdata) end diff --git a/GNNGraphs/test/transform.jl b/GNNGraphs/test/transform.jl index a8f5742aa..518714f2f 100644 --- a/GNNGraphs/test/transform.jl +++ b/GNNGraphs/test/transform.jl @@ -467,6 +467,40 @@ end end end +@testitem "node growth and batched graph indicators" setup=[GraphsTestModule] begin + using .GraphsTestModule + + g1 = GNNGraph(([1, 2], [2, 3]); num_nodes = 3) + g2 = GNNGraph(([1, 2], [2, 1]); num_nodes = 4) + g = batch([g1, g2]) + + @test g.num_nodes == length(g.graph_indicator) == 7 + @test_throws ArgumentError add_nodes(g, 2) + @test_throws ArgumentError add_edges(g, [8], [1]) + @test add_nodes(g, 0).graph_indicator == g.graph_indicator + @test add_edges(g, Int[], Int[]) === g + + # Adding edges between existing nodes does not grow the batched graph. + g_with_edge = add_edges(g, [1], [2]) + @test g_with_edge.num_nodes == length(g_with_edge.graph_indicator) == 7 + + # A single graph with explicit membership can safely extend that membership. + single = GNNGraph(([1], [2]); num_nodes = 2, graph_indicator = [1, 1]) + single_with_nodes = add_nodes(single, 2) + single_with_edge = add_edges(single, [3], [1]) + @test single_with_nodes.graph_indicator == [1, 1, 1, 1] + @test single_with_edge.graph_indicator == [1, 1, 1] + + # Unbatched graphs represent membership implicitly, so their stored field stays `nothing`. + unbatched = GNNGraph(([1], [2]); num_nodes = 2) + unbatched_with_nodes = add_nodes(unbatched, 2) + unbatched_with_edge = add_edges(unbatched, [3], [1]) + @test isnothing(unbatched_with_nodes.graph_indicator) + @test length(graph_indicator(unbatched_with_nodes)) == unbatched_with_nodes.num_nodes + @test isnothing(unbatched_with_edge.graph_indicator) + @test length(graph_indicator(unbatched_with_edge)) == unbatched_with_edge.num_nodes +end + @testitem "remove_self_loops" setup=[GraphsTestModule] begin using .GraphsTestModule for GRAPH_T in GRAPH_TYPES