Skip to content

Commit 1823e0d

Browse files
CI for GNNGraphs.jl (#451)
* CI from GNNGraphs.jl * cleanup * fix perturb_edges * fix spelling * import * fix runtest * fix * workflow
1 parent a590094 commit 1823e0d

File tree

10 files changed

+172
-54
lines changed

10 files changed

+172
-54
lines changed

.github/workflows/tests_GNNGraphs.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: GNNGraphs
2+
on:
3+
pull_request:
4+
branches:
5+
- master
6+
push:
7+
branches:
8+
- master
9+
jobs:
10+
test:
11+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
12+
runs-on: ${{ matrix.os }}
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
version:
17+
- '1.10' # Replace this with the minimum Julia version that your package supports.
18+
- '1' # '1' will automatically expand to the latest stable 1.x release of Julia.
19+
# - 'pre'
20+
os:
21+
- ubuntu-latest
22+
arch:
23+
- x64
24+
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: julia-actions/setup-julia@v2
28+
with:
29+
version: ${{ matrix.version }}
30+
arch: ${{ matrix.arch }}
31+
- uses: julia-actions/cache@v2
32+
- uses: julia-actions/julia-buildpkg@v1
33+
- name: Install Julia dependencies and run tests
34+
shell: julia --project=monorepo {0}
35+
run: |
36+
using Pkg
37+
# dev mono repo versions
38+
pkg"registry up"
39+
Pkg.update()
40+
pkg"dev ./GNNGraphs"
41+
Pkg.test("GNNGraphs"; coverage=true)
42+
- uses: julia-actions/julia-processcoverage@v1
43+
with:
44+
# directories: ./GNNGraphs/src, ./GNNGraphs/ext
45+
directories: ./GNNGraphs/src
46+
- uses: codecov/codecov-action@v4
47+
with:
48+
files: lcov.info

.github/workflows/CI.yml renamed to .github/workflows/tests_GraphNeuralNetworks.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: CI
1+
name: GNN
22
on:
33
pull_request:
44
branches:
@@ -8,15 +8,15 @@ on:
88
- master
99
jobs:
1010
test:
11-
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
11+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
1212
runs-on: ${{ matrix.os }}
1313
strategy:
1414
fail-fast: false
1515
matrix:
1616
version:
17-
# - '1.9' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'.
18-
- '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia.
19-
- 'nightly'
17+
- '1.10' # Replace this with the minimum Julia version that your package supports.
18+
- '1' # '1' will automatically expand to the latest stable 1.x release of Julia.
19+
# - 'pre'
2020
os:
2121
- ubuntu-latest
2222
arch:

GNNGraphs/src/GNNGraphs.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import KrylovKit
1212
using ChainRulesCore
1313
using LinearAlgebra, Random, Statistics
1414
import MLUtils
15-
using MLUtils: getobs, numobs, ones_like, zeros_like, chunk, batch
15+
using MLUtils: getobs, numobs, ones_like, zeros_like, chunk, batch, rand_like
1616
import Functors
1717
using LuxDeviceUtils: get_device, cpu_device, LuxCPUDevice
1818

@@ -77,6 +77,7 @@ export add_nodes,
7777
to_bidirected,
7878
to_unidirected,
7979
random_walk_pe,
80+
perturb_edges,
8081
remove_nodes,
8182
ppr_diffusion,
8283
drop_nodes,

GNNGraphs/src/query.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ function _degree((s, t)::Tuple, T::Type, dir::Symbol, edge_weight::Nothing, num_
349349
end
350350

351351
function _degree((s, t)::Tuple, T::Type, dir::Symbol, edge_weight::AbstractVector, num_nodes::Int)
352-
degs = fill!(similar(s, T, num_nodes), 0)
352+
degs = zeros_like(s, T, num_nodes)
353353

354354
if dir [:out, :both]
355355
degs = degs .+ NNlib.scatter(+, edge_weight, s, dstsize = (num_nodes,))

GNNGraphs/src/transform.jl

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,74 @@ function add_edges(g::GNNHeteroGraph{<:COO_T},
503503
end
504504

505505

506+
"""
507+
perturb_edges([rng], g::GNNGraph, perturb_ratio)
508+
509+
Return a new graph obtained from `g` by adding random edges, based on a specified `perturb_ratio`.
510+
The `perturb_ratio` determines the fraction of new edges to add relative to the current number of edges in the graph.
511+
These new edges are added without creating self-loops.
512+
Optionally, a random `seed` can be provided to ensure reproducible perturbations.
513+
514+
The function returns a new `GNNGraph` instance that shares some of the underlying data with `g` but includes the additional edges.
515+
The nodes for the new edges are selected randomly, and no edge data (`edata`) or weights (`w`) are assigned to these new edges.
516+
517+
# Arguments
518+
519+
- `g::GNNGraph`: The graph to be perturbed.
520+
- `perturb_ratio`: The ratio of the number of new edges to add relative to the current number of edges in the graph. For example, a `perturb_ratio` of 0.1 means that 10% of the current number of edges will be added as new random edges.
521+
- `rng`: An optionalrandom number generator to ensure reproducible results.
522+
523+
# Examples
524+
525+
```julia
526+
julia> g = GNNGraph((s, t, w))
527+
GNNGraph:
528+
num_nodes: 4
529+
num_edges: 5
530+
531+
julia> perturbed_g = perturb_edges(g, 0.2)
532+
GNNGraph:
533+
num_nodes: 4
534+
num_edges: 6
535+
```
536+
"""
537+
perturb_edges(g::GNNGraph{<:COO_T}, perturb_ratio::AbstractFloat) =
538+
perturb_edges(Random.default_rng(), g, perturb_ratio)
539+
540+
function perturb_edges(rng::AbstractRNG, g::GNNGraph{<:COO_T}, perturb_ratio::AbstractFloat)
541+
@assert perturb_ratio >= 0 && perturb_ratio <= 1 "perturb_ratio must be between 0 and 1"
542+
543+
num_current_edges = g.num_edges
544+
num_edges_to_add = ceil(Int, num_current_edges * perturb_ratio)
545+
546+
if num_edges_to_add == 0
547+
return g
548+
end
549+
550+
num_nodes = g.num_nodes
551+
@assert num_nodes > 1 "Graph must contain at least 2 nodes to add edges"
552+
553+
snew = ceil.(Int, rand_like(rng, ones(num_nodes), Float32, num_edges_to_add) .* num_nodes)
554+
tnew = ceil.(Int, rand_like(rng, ones(num_nodes), Float32, num_edges_to_add) .* num_nodes)
555+
556+
mask_loops = snew .!= tnew
557+
snew = snew[mask_loops]
558+
tnew = tnew[mask_loops]
559+
560+
while length(snew) < num_edges_to_add
561+
n = num_edges_to_add - length(snew)
562+
snewnew = ceil.(Int, rand_like(rng, ones(num_nodes), Float32, n) .* num_nodes)
563+
tnewnew = ceil.(Int, rand_like(rng, ones(num_nodes), Float32, n) .* num_nodes)
564+
mask_new_loops = snewnew .!= tnewnew
565+
snewnew = snewnew[mask_new_loops]
566+
tnewnew = tnewnew[mask_new_loops]
567+
snew = [snew; snewnew]
568+
tnew = [tnew; tnewnew]
569+
end
570+
571+
return add_edges(g, (snew, tnew, nothing))
572+
end
573+
506574

507575
### TODO Cannot implement this since GNNGraph is immutable (cannot change num_edges). make it mutable
508576
# function Graphs.add_edge!(g::GNNGraph{<:COO_T}, snew::T, tnew::T; edata=nothing) where T<:Union{Integer, AbstractVector}

GNNGraphs/test/runtests.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ tests = [
3636
"sampling",
3737
"gnnheterograph",
3838
"temporalsnapshotsgnngraph",
39-
"ext/SimpleWeightedGraphs/SimpleWeightedGraphs"
39+
"ext/SimpleWeightedGraphs"
4040
]
4141

4242
!CUDA.functional() && @warn("CUDA unavailable, not testing GPU support")
@@ -49,7 +49,6 @@ for graph_type in (:coo, :dense, :sparse)
4949
# global TEST_GPU = false
5050

5151
@testset "$t" for t in tests
52-
t == "GNNGraphs/sampling" && GRAPH_T != :coo && continue
5352
include("$t.jl")
5453
end
5554
end

GNNGraphs/test/sampling.jl

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,48 @@
1-
@testset "sample_neighbors" begin
2-
# replace = false
3-
dir = :in
4-
nodes = 2:3
5-
g = rand_graph(10, 40, bidirected = false, graph_type = GRAPH_T)
6-
sg = sample_neighbors(g, nodes; dir)
7-
@test sg.num_nodes == 10
8-
@test sg.num_edges == sum(degree(g, i; dir) for i in nodes)
9-
@test size(sg.edata.EID) == (sg.num_edges,)
10-
@test length(union(sg.edata.EID)) == length(sg.edata.EID)
11-
adjlist = adjacency_list(g; dir)
12-
s, t = edge_index(sg)
13-
@test all(t .∈ Ref(nodes))
14-
for i in nodes
15-
@test sort(neighbors(sg, i; dir)) == sort(neighbors(g, i; dir))
16-
end
1+
if GRAPH_T == :coo
2+
@testset "sample_neighbors" begin
3+
# replace = false
4+
dir = :in
5+
nodes = 2:3
6+
g = rand_graph(10, 40, bidirected = false, graph_type = GRAPH_T)
7+
sg = sample_neighbors(g, nodes; dir)
8+
@test sg.num_nodes == 10
9+
@test sg.num_edges == sum(degree(g, i; dir) for i in nodes)
10+
@test size(sg.edata.EID) == (sg.num_edges,)
11+
@test length(union(sg.edata.EID)) == length(sg.edata.EID)
12+
adjlist = adjacency_list(g; dir)
13+
s, t = edge_index(sg)
14+
@test all(t .∈ Ref(nodes))
15+
for i in nodes
16+
@test sort(neighbors(sg, i; dir)) == sort(neighbors(g, i; dir))
17+
end
1718

18-
# replace = true
19-
dir = :out
20-
nodes = 2:3
21-
K = 2
22-
g = rand_graph(10, 40, bidirected = false, graph_type = GRAPH_T)
23-
sg = sample_neighbors(g, nodes, K; dir, replace = true)
24-
@test sg.num_nodes == 10
25-
@test sg.num_edges == sum(K for i in nodes)
26-
@test size(sg.edata.EID) == (sg.num_edges,)
27-
adjlist = adjacency_list(g; dir)
28-
s, t = edge_index(sg)
29-
@test all(s .∈ Ref(nodes))
30-
for i in nodes
31-
@test issubset(neighbors(sg, i; dir), adjlist[i])
32-
end
19+
# replace = true
20+
dir = :out
21+
nodes = 2:3
22+
K = 2
23+
g = rand_graph(10, 40, bidirected = false, graph_type = GRAPH_T)
24+
sg = sample_neighbors(g, nodes, K; dir, replace = true)
25+
@test sg.num_nodes == 10
26+
@test sg.num_edges == sum(K for i in nodes)
27+
@test size(sg.edata.EID) == (sg.num_edges,)
28+
adjlist = adjacency_list(g; dir)
29+
s, t = edge_index(sg)
30+
@test all(s .∈ Ref(nodes))
31+
for i in nodes
32+
@test issubset(neighbors(sg, i; dir), adjlist[i])
33+
end
3334

34-
# dropnodes = true
35-
dir = :in
36-
nodes = 2:3
37-
g = rand_graph(10, 40, bidirected = false, graph_type = GRAPH_T)
38-
g = GNNGraph(g, ndata = (x1 = rand(10),), edata = (e1 = rand(40),))
39-
sg = sample_neighbors(g, nodes; dir, dropnodes = true)
40-
@test sg.num_edges == sum(degree(g, i; dir) for i in nodes)
41-
@test size(sg.edata.EID) == (sg.num_edges,)
42-
@test size(sg.ndata.NID) == (sg.num_nodes,)
43-
@test sg.edata.e1 == g.edata.e1[sg.edata.EID]
44-
@test sg.ndata.x1 == g.ndata.x1[sg.ndata.NID]
45-
@test length(union(sg.ndata.NID)) == length(sg.ndata.NID)
46-
end
35+
# dropnodes = true
36+
dir = :in
37+
nodes = 2:3
38+
g = rand_graph(10, 40, bidirected = false, graph_type = GRAPH_T)
39+
g = GNNGraph(g, ndata = (x1 = rand(10),), edata = (e1 = rand(40),))
40+
sg = sample_neighbors(g, nodes; dir, dropnodes = true)
41+
@test sg.num_edges == sum(degree(g, i; dir) for i in nodes)
42+
@test size(sg.edata.EID) == (sg.num_edges,)
43+
@test size(sg.ndata.NID) == (sg.num_nodes,)
44+
@test sg.edata.e1 == g.edata.e1[sg.edata.EID]
45+
@test sg.ndata.x1 == g.ndata.x1[sg.ndata.NID]
46+
@test length(union(sg.ndata.NID)) == length(sg.ndata.NID)
47+
end
48+
end

GNNGraphs/test/transform.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ end
181181
s, t = [1, 2, 3, 4, 5], [2, 3, 4, 5, 1]
182182
g = GNNGraph((s, t))
183183
rng = MersenneTwister(42)
184-
g_per = perturb_edges(g, 0.5, rng=rng)
184+
g_per = perturb_edges(rng, g, 0.5)
185185
@test g_per.num_edges == 8
186186
end end
187187

0 commit comments

Comments
 (0)