From 458dba1031b23d1b9ea9540e1c00e899582aa89a Mon Sep 17 00:00:00 2001 From: CarloLucibello Date: Mon, 27 Jul 2026 18:34:41 +0200 Subject: [PATCH 1/3] Remove escaping [sources] paths from test/Project.toml (fixes #700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test/Project.toml files carried `[sources]` entries pointing to sibling packages via relative paths that escape the package directory (e.g. `GNNlib = {path = "../../GNNlib"}`). These are shipped inside the registered tarball, so when an end user runs `Pkg.test` on an installed release, `../../GNNlib` resolves to a nonexistent path and Pkg errors with "expected package GNNlib to exist at path .../packages/ GraphNeuralNetworks/GNNlib". The escaping sibling sources are redundant for local development — the top-level [workspace] already links the sibling packages as dev checkouts — so removing them keeps local `Pkg.test` resolving siblings to the local paths, while a registry install resolves them from the registry (per the existing [compat] bounds). Each test/Project.toml keeps only its own non-escaping self-source. Co-Authored-By: Claude Opus 4.8 (1M context) --- GNNLux/test/Project.toml | 2 -- GNNlib/test/Project.toml | 1 - GraphNeuralNetworks/test/Project.toml | 2 -- 3 files changed, 5 deletions(-) diff --git a/GNNLux/test/Project.toml b/GNNLux/test/Project.toml index e6556f557..6ed69cd72 100644 --- a/GNNLux/test/Project.toml +++ b/GNNLux/test/Project.toml @@ -22,9 +22,7 @@ TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [sources] -GNNGraphs = {path = "../../GNNGraphs"} GNNLux = {path = ".."} -GNNlib = {path = "../../GNNlib"} [compat] NNlib = "0.9.38" diff --git a/GNNlib/test/Project.toml b/GNNlib/test/Project.toml index ad4fe15da..6df94bff1 100644 --- a/GNNlib/test/Project.toml +++ b/GNNlib/test/Project.toml @@ -19,7 +19,6 @@ TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [sources] -GNNGraphs = {path = "../../GNNGraphs"} GNNlib = {path = ".."} [compat] diff --git a/GraphNeuralNetworks/test/Project.toml b/GraphNeuralNetworks/test/Project.toml index 9efc8f3a1..a37e4e330 100644 --- a/GraphNeuralNetworks/test/Project.toml +++ b/GraphNeuralNetworks/test/Project.toml @@ -18,6 +18,4 @@ TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [sources] -GNNGraphs = {path = "../../GNNGraphs"} -GNNlib = {path = "../../GNNlib"} GraphNeuralNetworks = {path = ".."} From 1f6aeeb3af18468e014c2a444396875b43e5c4fa Mon Sep 17 00:00:00 2001 From: CarloLucibello Date: Tue, 28 Jul 2026 09:33:33 +0200 Subject: [PATCH 2/3] Fix docs and TGCN Mooncake CI failures (#700) Docs: the GNNlib temporal-cell forward passes (tgcn, gconv_gru, gconv_lstm, dcgru) had docstrings but no @docs block, so Documenter's checkdocs aborted the build with "4 docstrings not included in the manual". Convert them to plain comments, matching the convention for the other functional convs (gcn_conv, gat_conv, ... are exported but intentionally undocumented in GNNlib). Tests: the TGCNCell/TGCN gradient checks errored intermittently on the Julia 1.12 ubuntu runner. Mooncake returns exactly-zero gradients for some z-gate params when differentiating the graph-conv path from an all-zeros initial state (~3% of random inputs); Zygote and finite differences agree to ~1e-8, so it is an upstream Mooncake bug, not a layer-math issue. Skip Mooncake for these two items via ad_backends = [Flux.AutoZygote()], the same pattern GConvGRU/GConvLSTM/ DCGRU already use. Test/docs only: no changelog entry or version bump. Co-Authored-By: Claude Opus 4.8 --- GNNlib/src/layers/temporalconv.jl | 50 ++++++++----------- .../test/layers/temporalconv.jl | 35 ++++++++----- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/GNNlib/src/layers/temporalconv.jl b/GNNlib/src/layers/temporalconv.jl index d74b489e5..7149648b8 100644 --- a/GNNlib/src/layers/temporalconv.jl +++ b/GNNlib/src/layers/temporalconv.jl @@ -8,15 +8,13 @@ and the Lux (`GNNLux`) frontends build such a carrier and call into these functions, so the recurrence math lives in a single place. =# -""" - tgcn(l, cz, cr, ch, h) - -GRU gating of the T-GCN cell. `cz`, `cr`, `ch` are the spatial-convolution -outputs of the three gates (computed by the frontend), `h` is the current -hidden state, and `l` carries the three gate `Dense` sub-modules -(`l.dense_z`, `l.dense_r`, `l.dense_h`, each exposing `weight`, `bias`, `σ`). -Returns the updated hidden state. -""" +# tgcn(l, cz, cr, ch, h) +# +# GRU gating of the T-GCN cell. `cz`, `cr`, `ch` are the spatial-convolution +# outputs of the three gates (computed by the frontend), `h` is the current +# hidden state, and `l` carries the three gate `Dense` sub-modules +# (`l.dense_z`, `l.dense_r`, `l.dense_h`, each exposing `weight`, `bias`, `σ`). +# Returns the updated hidden state. function tgcn(l, cz, cr, ch, h) z = l.dense_z.σ.(l.dense_z.weight * vcat(cz, h) .+ l.dense_z.bias) r = l.dense_r.σ.(l.dense_r.weight * vcat(cr, h) .+ l.dense_r.bias) @@ -25,13 +23,11 @@ function tgcn(l, cz, cr, ch, h) return h end -""" - gconv_gru(l, g, x, h) - -Forward pass of the GConvGRU cell. `l` carries the six `ChebConv` sub-modules -(`conv_x_r`, `conv_h_r`, `conv_x_z`, `conv_h_z`, `conv_x_h`, `conv_h_h`). -Returns the updated hidden state. -""" +# gconv_gru(l, g, x, h) +# +# Forward pass of the GConvGRU cell. `l` carries the six `ChebConv` sub-modules +# (`conv_x_r`, `conv_h_r`, `conv_x_z`, `conv_h_z`, `conv_x_h`, `conv_h_h`). +# Returns the updated hidden state. function gconv_gru(l, g::GNNGraph, x, h) r = NNlib.sigmoid_fast.(cheb_conv(l.conv_x_r, g, x) .+ cheb_conv(l.conv_h_r, g, h)) z = NNlib.sigmoid_fast.(cheb_conv(l.conv_x_z, g, x) .+ cheb_conv(l.conv_h_z, g, h)) @@ -40,13 +36,11 @@ function gconv_gru(l, g::GNNGraph, x, h) return h end -""" - gconv_lstm(l, g, x, h, c) - -Forward pass of the GConvLSTM cell. `l` carries the eight `ChebConv` sub-modules -and the four peephole scalings/biases (`w_i`, `b_i`, `w_f`, `b_f`, `w_c`, `b_c`, -`w_o`, `b_o`). Returns the updated `(h, c)` state. -""" +# gconv_lstm(l, g, x, h, c) +# +# Forward pass of the GConvLSTM cell. `l` carries the eight `ChebConv` sub-modules +# and the four peephole scalings/biases (`w_i`, `b_i`, `w_f`, `b_f`, `w_c`, `b_c`, +# `w_o`, `b_o`). Returns the updated `(h, c)` state. function gconv_lstm(l, g::GNNGraph, x, h, c) # input gate i = cheb_conv(l.conv_x_i, g, x) .+ cheb_conv(l.conv_h_i, g, h) .+ l.w_i .* c .+ l.b_i @@ -63,12 +57,10 @@ function gconv_lstm(l, g::GNNGraph, x, h, c) return h, c end -""" - dcgru(l, g, x, h) - -Forward pass of the DCGRU cell. `l` carries the three `DConv` sub-modules -(`dconv_u`, `dconv_r`, `dconv_c`). Returns the updated hidden state. -""" +# dcgru(l, g, x, h) +# +# Forward pass of the DCGRU cell. `l` carries the three `DConv` sub-modules +# (`dconv_u`, `dconv_r`, `dconv_c`). Returns the updated hidden state. function dcgru(l, g::GNNGraph, x, h) h̃ = vcat(x, h) z = NNlib.sigmoid_fast.(d_conv(l.dconv_u, g, h̃)) diff --git a/GraphNeuralNetworks/test/layers/temporalconv.jl b/GraphNeuralNetworks/test/layers/temporalconv.jl index 47ab38813..6b3059cf1 100644 --- a/GraphNeuralNetworks/test/layers/temporalconv.jl +++ b/GraphNeuralNetworks/test/layers/temporalconv.jl @@ -25,17 +25,23 @@ end @testitem "TGCNCell" setup=[TemporalConvTestModule, TestModule] begin using .TemporalConvTestModule, .TestModule - + + # Mooncake is skipped here: it intermittently returns zeroed gradients for + # some parameters when differentiating the graph-conv path from an all-zeros + # initial state (Zygote and finite differences agree). Reference against + # Zygote only, matching the other temporal cells. + ad_backends = [Flux.AutoZygote()] + # Test with default activation function cell = GraphNeuralNetworks.TGCNCell(in_channel => out_channel) y, h = cell(g, g.x) @test y === h @test size(h) == (out_channel, g.num_nodes) # with no initial state - test_gradients(cell, g, g.x, loss=cell_loss, rtol=RTOL_HIGH) + test_gradients(cell, g, g.x, loss=cell_loss, rtol=RTOL_HIGH; ad_backends) # with initial state - test_gradients(cell, g, g.x, h, loss=cell_loss, rtol=RTOL_HIGH) - + test_gradients(cell, g, g.x, h, loss=cell_loss, rtol=RTOL_HIGH; ad_backends) + # Test with custom activation function custom_activation = tanh cell_custom = GraphNeuralNetworks.TGCNCell(in_channel => out_channel, act = custom_activation) @@ -45,14 +51,19 @@ end # Test that outputs differ when using different activation functions @test !isapprox(y, y_custom, rtol=RTOL_HIGH) # with no initial state - test_gradients(cell_custom, g, g.x, loss=cell_loss, rtol=RTOL_HIGH) + test_gradients(cell_custom, g, g.x, loss=cell_loss, rtol=RTOL_HIGH; ad_backends) # with initial state - test_gradients(cell_custom, g, g.x, h_custom, loss=cell_loss, rtol=RTOL_HIGH) + test_gradients(cell_custom, g, g.x, h_custom, loss=cell_loss, rtol=RTOL_HIGH; ad_backends) end @testitem "TGCN" setup=[TemporalConvTestModule, TestModule] begin using .TemporalConvTestModule, .TestModule - + + # Mooncake is skipped here (see the TGCNCell test item): it intermittently + # returns zeroed gradients when differentiating the TGCN graph-conv path from + # an all-zeros initial state. Reference against Zygote only. + ad_backends = [Flux.AutoZygote()] + # Test with default activation function layer = TGCN(in_channel => out_channel) x = rand(Float32, in_channel, timesteps, g.num_nodes) @@ -61,9 +72,9 @@ end @test layer isa GNNRecurrence @test size(y) == (out_channel, timesteps, g.num_nodes) # with no initial state - test_gradients(layer, g, x, rtol = RTOL_HIGH) + test_gradients(layer, g, x, rtol = RTOL_HIGH; ad_backends) # with initial state - test_gradients(layer, g, x, state0, rtol = RTOL_HIGH) + test_gradients(layer, g, x, state0, rtol = RTOL_HIGH; ad_backends) # Test with custom activation function custom_activation = tanh @@ -74,15 +85,15 @@ end # Test that outputs differ when using different activation functions @test !isapprox(y, y_custom, rtol = RTOL_HIGH) # with no initial state - test_gradients(layer_custom, g, x, rtol = RTOL_HIGH) + test_gradients(layer_custom, g, x, rtol = RTOL_HIGH; ad_backends) # with initial state - test_gradients(layer_custom, g, x, state0, rtol = RTOL_HIGH) + test_gradients(layer_custom, g, x, state0, rtol = RTOL_HIGH; ad_backends) # interplay with GNNChain model = GNNChain(TGCN(in_channel => out_channel), Dense(out_channel, 1)) y = model(g, x) @test size(y) == (1, timesteps, g.num_nodes) - test_gradients(model, g, x, rtol = RTOL_HIGH, atol = ATOL_LOW) + test_gradients(model, g, x, rtol = RTOL_HIGH, atol = ATOL_LOW; ad_backends) end @testitem "GConvLSTMCell" setup=[TemporalConvTestModule, TestModule] begin From 84756a907fffa6589b4d8d71f58efc17f16c9185 Mon Sep 17 00:00:00 2001 From: CarloLucibello Date: Tue, 28 Jul 2026 12:11:30 +0200 Subject: [PATCH 3/3] Link upstream Mooncake issue in TGCN test comments Traced the flaky TGCNCell/TGCN Mooncake failure to an upstream bug in Mooncake's gradient through NNlib.sigmoid (reported as chalk-lab/Mooncake.jl#1257), not the all-zeros initial state as first thought. Update the ad_backends skip comments to reflect the real cause and link the issue. Co-Authored-By: Claude Opus 4.8 --- GraphNeuralNetworks/test/layers/temporalconv.jl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/GraphNeuralNetworks/test/layers/temporalconv.jl b/GraphNeuralNetworks/test/layers/temporalconv.jl index 6b3059cf1..3f04362b8 100644 --- a/GraphNeuralNetworks/test/layers/temporalconv.jl +++ b/GraphNeuralNetworks/test/layers/temporalconv.jl @@ -26,10 +26,11 @@ end @testitem "TGCNCell" setup=[TemporalConvTestModule, TestModule] begin using .TemporalConvTestModule, .TestModule - # Mooncake is skipped here: it intermittently returns zeroed gradients for - # some parameters when differentiating the graph-conv path from an all-zeros - # initial state (Zygote and finite differences agree). Reference against - # Zygote only, matching the other temporal cells. + # Mooncake is skipped here: it returns wrong gradients through the cell's + # sigmoid gates (`Dense(_, sigmoid)`) on a fraction of inputs, so the check is + # flaky (Zygote and finite differences agree). Upstream Mooncake bug with + # NNlib.sigmoid: https://github.com/chalk-lab/Mooncake.jl/issues/1257. + # Reference against Zygote only, matching the other temporal cells. ad_backends = [Flux.AutoZygote()] # Test with default activation function @@ -59,9 +60,9 @@ end @testitem "TGCN" setup=[TemporalConvTestModule, TestModule] begin using .TemporalConvTestModule, .TestModule - # Mooncake is skipped here (see the TGCNCell test item): it intermittently - # returns zeroed gradients when differentiating the TGCN graph-conv path from - # an all-zeros initial state. Reference against Zygote only. + # Mooncake is skipped here (see the TGCNCell test item and + # https://github.com/chalk-lab/Mooncake.jl/issues/1257): it returns wrong + # gradients through the cell's sigmoid gates on some inputs. Zygote only. ad_backends = [Flux.AutoZygote()] # Test with default activation function