From abc58c63ceead5c2b3bb01f9ac4032ce5df43717 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 27 Sep 2024 16:45:49 +0200 Subject: [PATCH 01/36] New docs --- GNNGraphs/docs/Project.toml | 6 + GNNGraphs/docs/make.jl | 37 +++ .../docs/src/api/gnngraph.md | 2 +- GNNGraphs/docs/src/api/heterograph.md | 17 ++ .../docs/src/api/temporalgraph.md | 0 GNNGraphs/docs/src/datasets.md | 10 + .../docs/src/gnngraph.md | 8 +- .../docs/src/heterograph.md | 2 +- GNNGraphs/docs/src/index.md | 15 ++ .../docs/src/temporalgraph.md | 6 +- GNNLux/docs/Project.toml | 5 + GNNLux/docs/make.jl | 26 +++ GNNLux/docs/src/api/basic.md | 8 + GNNLux/docs/src/index.md | 5 + GNNlib/docs/Project.toml | 6 + GNNlib/docs/make.jl | 41 ++++ .../docs/src/api/messagepassing.md | 2 +- .../docs/src/api/utils.md | 16 +- GNNlib/docs/src/index.md | 6 + .../docs/src/messagepassing.md | 8 +- GraphNeuralNetworks/docs/Project.toml | 18 -- GraphNeuralNetworks/docs/make.jl | 81 +++---- .../docs/src/api/heteroconv.md | 15 ++ .../docs/src/api/heterograph.md | 25 -- .../docs/src/assets/schema.png | Bin 0 -> 50745 bytes GraphNeuralNetworks/docs/src/datasets.md | 9 - GraphNeuralNetworks/docs/src/home.md | 87 +++++++ GraphNeuralNetworks/docs/src/index.md | 83 +------ docs/Project.toml | 4 + docs/logo.svg | 31 +++ docs/make-multi.jl | 44 ++++ tutorials/docs/Project.toml | 4 + tutorials/docs/make.jl | 34 +++ tutorials/docs/src/index.md | 24 ++ .../docs/src}/pluto_output/gnn_intro_pluto.md | 20 +- .../graph_classification_pluto.md | 16 +- .../pluto_output/node_classification_pluto.md | 18 +- .../temporal_graph_classification_pluto.md | 211 +++++++++++++++++ .../src/pluto_output/traffic_prediction.md | 216 ++++++++++++++++++ .../docs => tutorials}/tutorials/config.json | 0 .../docs => tutorials}/tutorials/index.md | 0 .../assets/brain_gnn.gif | Bin .../assets/graph_classification.gif | Bin .../introductory_tutorials/assets/intro_1.png | Bin .../assets/node_classsification.gif | Bin .../introductory_tutorials/assets/traffic.gif | Bin .../introductory_tutorials/gnn_intro_pluto.jl | 2 + .../graph_classification_pluto.jl | 2 + .../node_classification_pluto.jl | 2 + .../temporal_graph_classification_pluto.jl | 5 +- .../traffic_prediction.jl | 2 + 51 files changed, 959 insertions(+), 220 deletions(-) create mode 100644 GNNGraphs/docs/Project.toml create mode 100644 GNNGraphs/docs/make.jl rename {GraphNeuralNetworks => GNNGraphs}/docs/src/api/gnngraph.md (92%) create mode 100644 GNNGraphs/docs/src/api/heterograph.md rename {GraphNeuralNetworks => GNNGraphs}/docs/src/api/temporalgraph.md (100%) create mode 100644 GNNGraphs/docs/src/datasets.md rename {GraphNeuralNetworks => GNNGraphs}/docs/src/gnngraph.md (97%) rename {GraphNeuralNetworks => GNNGraphs}/docs/src/heterograph.md (98%) create mode 100644 GNNGraphs/docs/src/index.md rename {GraphNeuralNetworks => GNNGraphs}/docs/src/temporalgraph.md (89%) create mode 100644 GNNLux/docs/Project.toml create mode 100644 GNNLux/docs/make.jl create mode 100644 GNNLux/docs/src/api/basic.md create mode 100644 GNNLux/docs/src/index.md create mode 100644 GNNlib/docs/Project.toml create mode 100644 GNNlib/docs/make.jl rename {GraphNeuralNetworks => GNNlib}/docs/src/api/messagepassing.md (91%) rename {GraphNeuralNetworks => GNNlib}/docs/src/api/utils.md (66%) create mode 100644 GNNlib/docs/src/index.md rename {GraphNeuralNetworks => GNNlib}/docs/src/messagepassing.md (94%) create mode 100644 GraphNeuralNetworks/docs/src/api/heteroconv.md delete mode 100644 GraphNeuralNetworks/docs/src/api/heterograph.md create mode 100644 GraphNeuralNetworks/docs/src/assets/schema.png delete mode 100644 GraphNeuralNetworks/docs/src/datasets.md create mode 100644 GraphNeuralNetworks/docs/src/home.md create mode 100644 docs/Project.toml create mode 100644 docs/logo.svg create mode 100644 docs/make-multi.jl create mode 100644 tutorials/docs/Project.toml create mode 100644 tutorials/docs/make.jl create mode 100644 tutorials/docs/src/index.md rename {GraphNeuralNetworks/docs => tutorials/docs/src}/pluto_output/gnn_intro_pluto.md (69%) rename {GraphNeuralNetworks/docs => tutorials/docs/src}/pluto_output/graph_classification_pluto.md (77%) rename {GraphNeuralNetworks/docs => tutorials/docs/src}/pluto_output/node_classification_pluto.md (54%) create mode 100644 tutorials/docs/src/pluto_output/temporal_graph_classification_pluto.md create mode 100644 tutorials/docs/src/pluto_output/traffic_prediction.md rename {GraphNeuralNetworks/docs => tutorials}/tutorials/config.json (100%) rename {GraphNeuralNetworks/docs => tutorials}/tutorials/index.md (100%) rename {GraphNeuralNetworks/docs => tutorials}/tutorials/introductory_tutorials/assets/brain_gnn.gif (100%) rename {GraphNeuralNetworks/docs => tutorials}/tutorials/introductory_tutorials/assets/graph_classification.gif (100%) rename {GraphNeuralNetworks/docs => tutorials}/tutorials/introductory_tutorials/assets/intro_1.png (100%) rename {GraphNeuralNetworks/docs => tutorials}/tutorials/introductory_tutorials/assets/node_classsification.gif (100%) rename {GraphNeuralNetworks/docs => tutorials}/tutorials/introductory_tutorials/assets/traffic.gif (100%) rename {GraphNeuralNetworks/docs => tutorials}/tutorials/introductory_tutorials/gnn_intro_pluto.jl (99%) rename {GraphNeuralNetworks/docs => tutorials}/tutorials/introductory_tutorials/graph_classification_pluto.jl (99%) rename {GraphNeuralNetworks/docs => tutorials}/tutorials/introductory_tutorials/node_classification_pluto.jl (99%) rename {GraphNeuralNetworks/docs/tutorials_broken => tutorials/tutorials/temporalconv_tutorials}/temporal_graph_classification_pluto.jl (99%) rename {GraphNeuralNetworks/docs/tutorials_broken => tutorials/tutorials/temporalconv_tutorials}/traffic_prediction.jl (99%) diff --git a/GNNGraphs/docs/Project.toml b/GNNGraphs/docs/Project.toml new file mode 100644 index 000000000..c26fcc9b2 --- /dev/null +++ b/GNNGraphs/docs/Project.toml @@ -0,0 +1,6 @@ +[deps] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +GNNGraphs = "aed8fd31-079b-4b5a-b342-a13352159b8c" +Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" diff --git a/GNNGraphs/docs/make.jl b/GNNGraphs/docs/make.jl new file mode 100644 index 000000000..a78420c4d --- /dev/null +++ b/GNNGraphs/docs/make.jl @@ -0,0 +1,37 @@ +using Documenter +using DocumenterInterLinks +using GNNGraphs +using Graphs + +assets=[] +prettyurls = get(ENV, "CI", nothing) == "true" +mathengine = MathJax3() + +interlinks = InterLinks( + "GraphNeuralNetworks" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/graphneuralnetworks/", joinpath(dirname(dirname(@__DIR__)), "GraphNeuralNetworks", "docs", "build", "objects.inv")), + + ) + +makedocs(; + modules = [GNNGraphs], + doctest = false, + clean = true, + plugins = [interlinks], + format = Documenter.HTML(; mathengine, prettyurls, assets = assets, size_threshold=nothing), + sitename = "GNNGraphs.jl", + pages = ["Home" => "index.md", + "Graphs" => ["gnngraph.md", "heterograph.md", "temporalgraph.md"], + "Datasets" => "datasets.md", + "API Reference" => [ + "GNNGraph" => "api/gnngraph.md", + "GNNHeteroGraph" => "api/heterograph.md", + "TemporalSnapshotsGNNGraph" => "api/temporalgraph.md", + ], + ] + ) + + + + +deploydocs(;repo = "https://github.com/CarloLucibello/GraphNeuralNetworks.jl.git", +dirname = "GNNGraphs") \ No newline at end of file diff --git a/GraphNeuralNetworks/docs/src/api/gnngraph.md b/GNNGraphs/docs/src/api/gnngraph.md similarity index 92% rename from GraphNeuralNetworks/docs/src/api/gnngraph.md rename to GNNGraphs/docs/src/api/gnngraph.md index de6fc1872..b39dec18f 100644 --- a/GraphNeuralNetworks/docs/src/api/gnngraph.md +++ b/GNNGraphs/docs/src/api/gnngraph.md @@ -4,7 +4,7 @@ CurrentModule = GNNGraphs # GNNGraph -Documentation page for the graph type `GNNGraph` provided by GraphNeuralNetworks.jl and related methods. +Documentation page for the graph type `GNNGraph` provided by GNNGraphs.jl and related methods. Besides the methods documented here, one can rely on the large set of functionalities given by [Graphs.jl](https://github.com/JuliaGraphs/Graphs.jl) thanks to the fact diff --git a/GNNGraphs/docs/src/api/heterograph.md b/GNNGraphs/docs/src/api/heterograph.md new file mode 100644 index 000000000..3734d757b --- /dev/null +++ b/GNNGraphs/docs/src/api/heterograph.md @@ -0,0 +1,17 @@ +# Heterogeneous Graphs + + +## GNNHeteroGraph +Documentation page for the type `GNNHeteroGraph` representing heterogeneous graphs, where nodes and edges can have different types. + + +```@autodocs +Modules = [GNNGraphs] +Pages = ["gnnheterograph.jl"] +Private = false +``` + +```@docs +Graphs.has_edge(::GNNHeteroGraph, ::Tuple{Symbol, Symbol, Symbol}, ::Integer, ::Integer) +``` + diff --git a/GraphNeuralNetworks/docs/src/api/temporalgraph.md b/GNNGraphs/docs/src/api/temporalgraph.md similarity index 100% rename from GraphNeuralNetworks/docs/src/api/temporalgraph.md rename to GNNGraphs/docs/src/api/temporalgraph.md diff --git a/GNNGraphs/docs/src/datasets.md b/GNNGraphs/docs/src/datasets.md new file mode 100644 index 000000000..01433a333 --- /dev/null +++ b/GNNGraphs/docs/src/datasets.md @@ -0,0 +1,10 @@ +# Datasets + +GNNGraphs.jl doesn't come with its own datasets, but leverages those available in the Julia (and non-Julia) ecosystem. In particular, the [examples in the GraphNeuralNetworks.jl repository](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/tree/master/examples) make use of the [MLDatasets.jl](https://github.com/JuliaML/MLDatasets.jl) package. There you will find common graph datasets such as Cora, PubMed, Citeseer, TUDataset and [many others](https://juliaml.github.io/MLDatasets.jl/dev/datasets/graphs/). +For graphs with static structures and temporal features, datasets such as METRLA, PEMSBAY, ChickenPox, and WindMillEnergy are available. For graphs featuring both temporal structures and temporal features, the TemporalBrains dataset is suitable. + +GraphNeuralNetworks.jl provides the [`mldataset2gnngraph`](@ref) method for interfacing with MLDatasets.jl. + +```@docs +mldataset2gnngraph +``` diff --git a/GraphNeuralNetworks/docs/src/gnngraph.md b/GNNGraphs/docs/src/gnngraph.md similarity index 97% rename from GraphNeuralNetworks/docs/src/gnngraph.md rename to GNNGraphs/docs/src/gnngraph.md index cfa3a2008..fd592602f 100644 --- a/GraphNeuralNetworks/docs/src/gnngraph.md +++ b/GNNGraphs/docs/src/gnngraph.md @@ -1,6 +1,6 @@ -# Working with GNNGraph +# Static Graphs -The fundamental graph type in GraphNeuralNetworks.jl is the [`GNNGraph`](@ref). +The fundamental graph type in GNNGraphs.jl is the [`GNNGraph`](@ref). A GNNGraph `g` is a directed graph with nodes labeled from 1 to `g.num_nodes`. The underlying implementation allows for efficient application of graph neural network operators, gpu movement, and storage of node/edge/graph related feature arrays. @@ -12,7 +12,7 @@ therefore it supports most functionality from that library. A GNNGraph can be created from several different data sources encoding the graph topology: ```julia -using GraphNeuralNetworks, Graphs, SparseArrays +using GNNGraphs, Graphs, SparseArrays # Construct a GNNGraph from from a Graphs.jl's graph @@ -233,7 +233,7 @@ Moreover, a `GNNGraph` can be easily constructed from a `Graphs.Graph` or a `Gra ```julia julia> import Graphs -julia> using GraphNeuralNetworks +julia> using GNNGraphs # A Graphs.jl undirected graph julia> gu = Graphs.erdos_renyi(10, 20) diff --git a/GraphNeuralNetworks/docs/src/heterograph.md b/GNNGraphs/docs/src/heterograph.md similarity index 98% rename from GraphNeuralNetworks/docs/src/heterograph.md rename to GNNGraphs/docs/src/heterograph.md index c05b33943..d42df8478 100644 --- a/GraphNeuralNetworks/docs/src/heterograph.md +++ b/GNNGraphs/docs/src/heterograph.md @@ -6,7 +6,7 @@ Relations such as `:rate` or `:like` can connect nodes of different types. We ca Different node/edge types can store different groups of features and this makes heterographs a very flexible modeling tools -and data containers. In GraphNeuralNetworks.jl heterographs are implemented in +and data containers. In GNNGraphs.jl heterographs are implemented in the type [`GNNHeteroGraph`](@ref). diff --git a/GNNGraphs/docs/src/index.md b/GNNGraphs/docs/src/index.md new file mode 100644 index 000000000..fc64196cb --- /dev/null +++ b/GNNGraphs/docs/src/index.md @@ -0,0 +1,15 @@ +# GNNGraphs.jl + +GNNGraphs.jl is a package that provides graph data structures and helper functions specifically designed for working with graph neural networks. This package allows to store not only the graph structure, but also features associated with nodes, edges, and the graph itself. It is the core foundation for the GNNlib, GraphNeuralNetworks, and GNNLux packages. + +It supports three types of graphs: + +- **Static graph** is the basic graph type represented by [`GNNGraph`](@ref), where each node and edge can have associated features. This type of graph is used in typical graph neural network applications, where neural networks operate on both the structure of the graph and the features stored in it. It can be used to represent a graph where the structure does not change over time, but the features of the nodes and edges can change over time. + +- **Heterogeneous graph** is a graph that supports multiple types of nodes and edges, and is represented by [`GNNHeteroGraph`](@ref). Each type can have its own properties and relationships. This is useful in scenarios with different entities and interactions, such as in citation graphs or multi-relational data. + +- **Temporal graph** is a graph that changes over time, and is represented by [`TemporalSnapshotsGNNGraph`](@ref). Edges and features can change dynamically. This type of graph is useful for applications that involve tracking time-dependent relationships, such as social networks. + + + +This package depends on the package [Graphs.jl] (https://github.com/JuliaGraphs/Graphs.jl). \ No newline at end of file diff --git a/GraphNeuralNetworks/docs/src/temporalgraph.md b/GNNGraphs/docs/src/temporalgraph.md similarity index 89% rename from GraphNeuralNetworks/docs/src/temporalgraph.md rename to GNNGraphs/docs/src/temporalgraph.md index f8283c766..7fccb5e26 100644 --- a/GraphNeuralNetworks/docs/src/temporalgraph.md +++ b/GNNGraphs/docs/src/temporalgraph.md @@ -1,6 +1,6 @@ # Temporal Graphs -Temporal Graphs are graphs with time varying topologies and node features. In GraphNeuralNetworks.jl temporal graphs with fixed number of nodes over time are supported by the [`TemporalSnapshotsGNNGraph`](@ref) type. +Temporal Graphs are graphs with time varying topologies and features. In GNNGraphs.jl, temporal graphs with fixed number of nodes over time are supported by the [`TemporalSnapshotsGNNGraph`](@ref) type. ## Creating a TemporalSnapshotsGNNGraph @@ -124,10 +124,10 @@ Vector{Matrix{Float64}} ## Graph convolutions on TemporalSnapshotsGNNGraph -A graph convolutional layer can be applied to each snapshot independently, in the next example we apply a `GINConv` layer to each snapshot of a `TemporalSnapshotsGNNGraph`. The list of compatible graph convolution layers can be found [here](api/conv.md). +A graph convolutional layer can be applied to each snapshot independently, in the next example we apply a [`GINConv`](@ref) layer to each snapshot of a `TemporalSnapshotsGNNGraph`. ```jldoctest -julia> using GraphNeuralNetworks, Flux +julia> using GNNGraphs, Flux julia> snapshots = [rand_graph(10, 20; ndata = rand(3, 10)), rand_graph(10, 14; ndata = rand(3, 10))]; diff --git a/GNNLux/docs/Project.toml b/GNNLux/docs/Project.toml new file mode 100644 index 000000000..dbb31551d --- /dev/null +++ b/GNNLux/docs/Project.toml @@ -0,0 +1,5 @@ +[deps] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +GNNLux = "e8545f4d-a905-48ac-a8c4-ca114b98986d" +GNNlib = "a6a84749-d869-43f8-aacc-be26a1996e48" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" diff --git a/GNNLux/docs/make.jl b/GNNLux/docs/make.jl new file mode 100644 index 000000000..5bd9dad10 --- /dev/null +++ b/GNNLux/docs/make.jl @@ -0,0 +1,26 @@ +using Documenter +using GNNlib +using GNNLux + + + +assets=[] +prettyurls = get(ENV, "CI", nothing) == "true" +mathengine = MathJax3() + + +makedocs(; + modules = [GNNLux], + doctest = false, + clean = true, + format = Documenter.HTML(; mathengine, prettyurls, assets = assets, size_threshold=nothing), + sitename = "GNNLux.jl", + pages = ["Home" => "index.md", + "Basic" => "api/basic.md"], + ) + + + + +deploydocs(;repo = "https://github.com/CarloLucibello/GraphNeuralNetworks.jl.git", +dirname = "GNNLux") \ No newline at end of file diff --git a/GNNLux/docs/src/api/basic.md b/GNNLux/docs/src/api/basic.md new file mode 100644 index 000000000..acd7353b4 --- /dev/null +++ b/GNNLux/docs/src/api/basic.md @@ -0,0 +1,8 @@ +```@meta +CurrentModule = GNNLux +``` + +## GNNLayer +```@docs +GNNLux.GNNLayer +``` diff --git a/GNNLux/docs/src/index.md b/GNNLux/docs/src/index.md new file mode 100644 index 000000000..6fa95c3ad --- /dev/null +++ b/GNNLux/docs/src/index.md @@ -0,0 +1,5 @@ +# GNNLux.jl + +GNNLux.jl is a work-in-progress package that implements stateless graph convolutional layers, fully compatible with the [Lux.jl](https://lux.csail.mit.edu/stable/) machine learning framework. It is built on top of the GNNGraphs.jl, GNNlib.jl, and Lux.jl packages. + +The full documentation will be available soon. \ No newline at end of file diff --git a/GNNlib/docs/Project.toml b/GNNlib/docs/Project.toml new file mode 100644 index 000000000..20451275d --- /dev/null +++ b/GNNlib/docs/Project.toml @@ -0,0 +1,6 @@ +[deps] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +GNNGraphs = "aed8fd31-079b-4b5a-b342-a13352159b8c" +GNNlib = "a6a84749-d869-43f8-aacc-be26a1996e48" +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" diff --git a/GNNlib/docs/make.jl b/GNNlib/docs/make.jl new file mode 100644 index 000000000..893d8bc8c --- /dev/null +++ b/GNNlib/docs/make.jl @@ -0,0 +1,41 @@ +using Documenter +using GNNlib +using GNNGraphs +using DocumenterInterLinks + + +assets=[] +prettyurls = get(ENV, "CI", nothing) == "true" +mathengine = MathJax3() + +interlinks = InterLinks( + "NNlib" => "https://fluxml.ai/NNlib.jl/stable/", + "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnngraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv")), + "GraphNeuralNetworks" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/graphneuralnetworks/", joinpath(dirname(dirname(@__DIR__)),"GraphNeuralNetworks", "docs", "build", "objects.inv")),) + + +makedocs(; + modules = [GNNlib], + doctest = false, + clean = true, + plugins = [interlinks], + format = Documenter.HTML(; mathengine, prettyurls, assets = assets, size_threshold=nothing), + sitename = "GNNlib.jl", + pages = ["Home" => "index.md", + "Message Passing" => "messagepassing.md", + + "API Reference" => [ + + "Message Passing" => "api/messagepassing.md", + + "Utils" => "api/utils.md", + ] + + ] + ) + + + + +deploydocs(;repo = "https://github.com/CarloLucibello/GraphNeuralNetworks.jl.git", +dirname = "GNNlib") \ No newline at end of file diff --git a/GraphNeuralNetworks/docs/src/api/messagepassing.md b/GNNlib/docs/src/api/messagepassing.md similarity index 91% rename from GraphNeuralNetworks/docs/src/api/messagepassing.md rename to GNNlib/docs/src/api/messagepassing.md index aba1e0bba..03b50914e 100644 --- a/GraphNeuralNetworks/docs/src/api/messagepassing.md +++ b/GNNlib/docs/src/api/messagepassing.md @@ -1,5 +1,5 @@ ```@meta -CurrentModule = GraphNeuralNetworks +CurrentModule = GNNlib ``` # Message Passing diff --git a/GraphNeuralNetworks/docs/src/api/utils.md b/GNNlib/docs/src/api/utils.md similarity index 66% rename from GraphNeuralNetworks/docs/src/api/utils.md rename to GNNlib/docs/src/api/utils.md index 69a723874..c34861167 100644 --- a/GraphNeuralNetworks/docs/src/api/utils.md +++ b/GNNlib/docs/src/api/utils.md @@ -1,5 +1,5 @@ ```@meta -CurrentModule = GraphNeuralNetworks +CurrentModule = GNNlib ``` # Utility Functions @@ -17,18 +17,18 @@ Pages = ["utils.md"] ### Graph-wise operations ```@docs -GraphNeuralNetworks.reduce_nodes -GraphNeuralNetworks.reduce_edges -GraphNeuralNetworks.softmax_nodes -GraphNeuralNetworks.softmax_edges -GraphNeuralNetworks.broadcast_nodes -GraphNeuralNetworks.broadcast_edges +reduce_nodes +reduce_edges +softmax_nodes +softmax_edges +broadcast_nodes +broadcast_edges ``` ### Neighborhood operations ```@docs -GraphNeuralNetworks.softmax_edge_neighbors +softmax_edge_neighbors ``` ### NNlib diff --git a/GNNlib/docs/src/index.md b/GNNlib/docs/src/index.md new file mode 100644 index 000000000..d1668b933 --- /dev/null +++ b/GNNlib/docs/src/index.md @@ -0,0 +1,6 @@ +# GNNlib.jl + +GNNlib.jl is a package that provides the implementation of the basic message passing functions and +functional implementation of graph convolutional layers, which are used to build graph neural networks in both the Flux.jl and Lux.jl machine learning frameworks, created in the GraphNeuralNetworks.jl and GNNLux.jl packages, respectively. + +This package depends on GNNGraphs.jl and NNlib.jl, and is primarily intended for developers looking to create new GNN architectures. For most users, the higher-level GraphNeuralNetworks.jl and GNNLux.jl packages are recommended. \ No newline at end of file diff --git a/GraphNeuralNetworks/docs/src/messagepassing.md b/GNNlib/docs/src/messagepassing.md similarity index 94% rename from GraphNeuralNetworks/docs/src/messagepassing.md rename to GNNlib/docs/src/messagepassing.md index f59ad6561..6705b67f7 100644 --- a/GraphNeuralNetworks/docs/src/messagepassing.md +++ b/GNNlib/docs/src/messagepassing.md @@ -16,7 +16,7 @@ and to ``\gamma_x`` and ``\gamma_e`` as to the node update and edge update funct respectively. The aggregation ``\square`` is over the neighborhood ``N(i)`` of node ``i``, and it is usually equal either to ``\sum``, to `max` or to a `mean` operation. -In GraphNeuralNetworks.jl, the message passing mechanism is exposed by the [`propagate`](@ref) function. +In GNNlib.jl, the message passing mechanism is exposed by the [`propagate`](@ref) function. [`propagate`](@ref) takes care of materializing the node features on each edge, applying the message function, performing the aggregation, and returning ``\bar{\mathbf{m}}``. It is then left to the user to perform further node and edge updates, @@ -39,7 +39,7 @@ and [`NNlib.scatter`](@ref) methods. The function [`apply_edges`](@ref) can be used to broadcast node data on each edge and produce new edge data. ```julia -julia> using GraphNeuralNetworks, Graphs, Statistics +julia> using GNNlib, Graphs, Statistics julia> g = rand_graph(10, 20) GNNGraph: @@ -90,9 +90,9 @@ julia> degree(g) 1 ``` -### Implementing a custom Graph Convolutional Layer +### Implementing a custom Graph Convolutional Layer using Flux.jl -Let's implement a simple graph convolutional layer using the message passing framework. +Let's implement a simple graph convolutional layer using the message passing framework using the machine learning framework Flux.jl. The convolution reads ```math diff --git a/GraphNeuralNetworks/docs/Project.toml b/GraphNeuralNetworks/docs/Project.toml index 60f0e00d0..2f8dc9ee8 100644 --- a/GraphNeuralNetworks/docs/Project.toml +++ b/GraphNeuralNetworks/docs/Project.toml @@ -1,22 +1,4 @@ [deps] -DemoCards = "311a05b2-6137-4a5a-b473-18580a3d38b5" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" -Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c" -GNNGraphs = "aed8fd31-079b-4b5a-b342-a13352159b8c" -GNNlib = "a6a84749-d869-43f8-aacc-be26a1996e48" GraphNeuralNetworks = "cffab07f-9bc2-4db1-8861-388f63bf7694" -Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -MLDatasets = "eb30cadb-4394-5ae3-aed4-317e484a6458" -NNlib = "872c559c-99b0-510c-b3b7-b6c96a88d5cd" -Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -Pluto = "c3e4b0f8-55cb-11ea-2926-15256bba5781" -PlutoStaticHTML = "359b1769-a58e-495b-9770-312e911026ad" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" - -[compat] -DemoCards = "0.5.0" -Documenter = "1.5" diff --git a/GraphNeuralNetworks/docs/make.jl b/GraphNeuralNetworks/docs/make.jl index 869aa94f1..e2329fd17 100644 --- a/GraphNeuralNetworks/docs/make.jl +++ b/GraphNeuralNetworks/docs/make.jl @@ -1,57 +1,52 @@ +using Documenter using GraphNeuralNetworks -using GNNGraphs -using Flux -using NNlib -using Graphs -using SparseArrays -using Pluto, PlutoStaticHTML # for tutorials -using Documenter, DemoCards using DocumenterInterLinks -tutorials, tutorials_cb, tutorial_assets = makedemos("tutorials") -assets = [] -isnothing(tutorial_assets) || push!(assets, tutorial_assets) +assets=[] +prettyurls = get(ENV, "CI", nothing) == "true" +mathengine = MathJax3() interlinks = InterLinks( "NNlib" => "https://fluxml.ai/NNlib.jl/stable/", - "Graphs" => "https://juliagraphs.org/Graphs.jl/stable/") - - -DocMeta.setdocmeta!(GraphNeuralNetworks, :DocTestSetup, - :(using GraphNeuralNetworks, Graphs, SparseArrays, NNlib, Flux); - recursive = true) - -prettyurls = get(ENV, "CI", nothing) == "true" -mathengine = MathJax3() + "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnngraphs/", joinpath(dirname(dirname(@__DIR__)),"GraphNeuralNetworks.jl", "GNNGraphs", "docs", "build", "objects.inv")), + "GNNlib" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnnlib/", joinpath(dirname(dirname(@__DIR__)),"GraphNeuralNetworks.jl", "GNNlib", "docs", "build", "objects.inv")) + + ) makedocs(; - modules = [GraphNeuralNetworks, GNNGraphs, GNNlib], + modules = [GraphNeuralNetworks], doctest = false, clean = true, plugins = [interlinks], format = Documenter.HTML(; mathengine, prettyurls, assets = assets, size_threshold=nothing), sitename = "GraphNeuralNetworks.jl", - pages = ["Home" => "index.md", - "Graphs" => ["gnngraph.md", "heterograph.md", "temporalgraph.md"], - "Message Passing" => "messagepassing.md", - "Model Building" => "models.md", - "Datasets" => "datasets.md", - "Tutorials" => tutorials, - "API Reference" => [ - "GNNGraph" => "api/gnngraph.md", - "Basic Layers" => "api/basic.md", - "Convolutional Layers" => "api/conv.md", - "Pooling Layers" => "api/pool.md", - "Message Passing" => "api/messagepassing.md", - "Heterogeneous Graphs" => "api/heterograph.md", - "Temporal Graphs" => "api/temporalgraph.md", - "Utils" => "api/utils.md", - ], - "Developer Notes" => "dev.md", - "Summer Of Code" => "gsoc.md", - ]) - -tutorials_cb() - -deploydocs(repo = "github.com/CarloLucibello/GraphNeuralNetworks.jl.git") + pages = ["Monorepo" => [ + "Home" => "index.md", + "Developer guide" => "dev.md", + "Google Summer of Code" => "gsoc.md", + + + ], + "GraphNeuralNetworks.jl" =>[ + "Home" => "home.md", + "Models" => "models.md",], + + "API Reference" => [ + + "Basic" => "api/basic.md", + "Convolutional layers" => "api/conv.md", + "Pooling layers" => "api/pool.md", + "Temporal Convolutional layers" => "api/temporalconv.md", + "Hetero Convolutional layers" => "api/heteroconv.md" + + + ], + + ], + ) + + + + +deploydocs(;repo = "https://github.com/CarloLucibello/GraphNeuralNetworks.jl.git", dirname= "GraphNeuralNetworks") \ No newline at end of file diff --git a/GraphNeuralNetworks/docs/src/api/heteroconv.md b/GraphNeuralNetworks/docs/src/api/heteroconv.md new file mode 100644 index 000000000..969fbde71 --- /dev/null +++ b/GraphNeuralNetworks/docs/src/api/heteroconv.md @@ -0,0 +1,15 @@ +```@meta +CurrentModule = GraphNeuralNetworks +``` + +# Hetero Graph-Convolutional Layers + +Heterogeneous graph convolutions are implemented in the type `HeteroGraphConv`. `HeteroGraphConv` relies on standard graph convolutional layers to perform message passing on the different relations. + +## Docs + +```@autodocs +Modules = [GraphNeuralNetworks] +Pages = ["layers/heteroconv.jl"] +Private = false +``` diff --git a/GraphNeuralNetworks/docs/src/api/heterograph.md b/GraphNeuralNetworks/docs/src/api/heterograph.md deleted file mode 100644 index db03c74a4..000000000 --- a/GraphNeuralNetworks/docs/src/api/heterograph.md +++ /dev/null @@ -1,25 +0,0 @@ -# Hetereogeneous Graphs - - -## GNNHeteroGraph -Documentation page for the type `GNNHeteroGraph` representing heterogeneous graphs, where nodes and edges can have different types. - - -```@autodocs -Modules = [GNNGraphs] -Pages = ["gnnheterograph.jl"] -Private = false -``` - -```@docs -Graphs.has_edge(::GNNHeteroGraph, ::Tuple{Symbol, Symbol, Symbol}, ::Integer, ::Integer) -``` - -## Heterogeneous Graph Convolutions - -Heterogeneous graph convolutions are implemented in the type [`HeteroGraphConv`](@ref). -`HeteroGraphConv` relies on standard graph convolutional layers to perform message passing on the different relations. See the table at [this page](https://carlolucibello.github.io/GraphNeuralNetworks.jl/dev/api/conv/) for the supported layers. - -```@docs -HeteroGraphConv -``` \ No newline at end of file diff --git a/GraphNeuralNetworks/docs/src/assets/schema.png b/GraphNeuralNetworks/docs/src/assets/schema.png new file mode 100644 index 0000000000000000000000000000000000000000..6496b36dfd372a68457e6ce1e021e24f4aaf9d23 GIT binary patch literal 50745 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 literal 0 HcmV?d00001 diff --git a/GraphNeuralNetworks/docs/src/datasets.md b/GraphNeuralNetworks/docs/src/datasets.md deleted file mode 100644 index c134afe5a..000000000 --- a/GraphNeuralNetworks/docs/src/datasets.md +++ /dev/null @@ -1,9 +0,0 @@ -# Datasets - -GraphNeuralNetworks.jl doesn't come with its own datasets, but leverages those available in the Julia (and non-Julia) ecosystem. In particular, the [examples in the GraphNeuralNetworks.jl repository](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/tree/master/examples) make use of the [MLDatasets.jl](https://github.com/JuliaML/MLDatasets.jl) package. There you will find common graph datasets such as Cora, PubMed, Citeseer, TUDataset and [many others](https://juliaml.github.io/MLDatasets.jl/dev/datasets/graphs/). - -GraphNeuralNetworks.jl provides the [`mldataset2gnngraph`](@ref) method for interfacing with MLDatasets.jl. - -```@docs -mldataset2gnngraph -``` diff --git a/GraphNeuralNetworks/docs/src/home.md b/GraphNeuralNetworks/docs/src/home.md new file mode 100644 index 000000000..41cbfb9dc --- /dev/null +++ b/GraphNeuralNetworks/docs/src/home.md @@ -0,0 +1,87 @@ +# GraphNeuralNetworks + +This is the documentation page for [GraphNeuralNetworks.jl](https://github.com/CarloLucibello/GraphNeuralNetworks.jl), a graph neural network library written in Julia and based on the deep learning framework [Flux.jl](https://github.com/FluxML/Flux.jl). +GraphNeuralNetworks.jl is largely inspired by [PyTorch Geometric](https://pytorch-geometric.readthedocs.io/en/latest/), [Deep Graph Library](https://docs.dgl.ai/), +and [GeometricFlux.jl](https://fluxml.ai/GeometricFlux.jl/stable/). + +Among its features: + +* Implements common graph convolutional layers. +* Supports computations on batched graphs. +* Easy to define custom layers. +* CUDA support. +* Integration with [Graphs.jl](https://github.com/JuliaGraphs/Graphs.jl). +* [Examples](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/tree/master/examples) of node, edge, and graph level machine learning tasks. + + +## Package overview + +Let's give a brief overview of the package by solving a +graph regression problem with synthetic data. + +Usage examples on real datasets can be found in the [examples](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/tree/master/examples) folder. + +### Data preparation + +We create a dataset consisting in multiple random graphs and associated data features. + +```julia +using GraphNeuralNetworks, Graphs, Flux, CUDA, Statistics, MLUtils +using Flux: DataLoader + +all_graphs = GNNGraph[] + +for _ in 1:1000 + g = rand_graph(10, 40, + ndata=(; x = randn(Float32, 16,10)), # input node features + gdata=(; y = randn(Float32))) # regression target + push!(all_graphs, g) +end +``` + +### Model building + +We concisely define our model as a [`GNNChain`](@ref) containing two graph convolutional layers. If CUDA is available, our model will live on the gpu. + +```julia +device = CUDA.functional() ? Flux.gpu : Flux.cpu; + +model = GNNChain(GCNConv(16 => 64), + BatchNorm(64), # Apply batch normalization on node features (nodes dimension is batch dimension) + x -> relu.(x), + GCNConv(64 => 64, relu), + GlobalPool(mean), # aggregate node-wise features into graph-wise features + Dense(64, 1)) |> device + +opt = Flux.setup(Adam(1f-4), model) +``` + +### Training + +Finally, we use a standard Flux training pipeline to fit our dataset. +We use Flux's `DataLoader` to iterate over mini-batches of graphs +that are glued together into a single `GNNGraph` using the `MLUtils.batch` method. This is what happens under the hood when creating a `DataLoader` with the +`collate=true` option. + +```julia +train_graphs, test_graphs = MLUtils.splitobs(all_graphs, at=0.8) + +train_loader = DataLoader(train_graphs, + batchsize=32, shuffle=true, collate=true) +test_loader = DataLoader(test_graphs, + batchsize=32, shuffle=false, collate=true) + +loss(model, g::GNNGraph) = mean((vec(model(g, g.x)) - g.y).^2) + +loss(model, loader) = mean(loss(model, g |> device) for g in loader) + +for epoch in 1:100 + for g in train_loader + g = g |> device + grad = gradient(model -> loss(model, g), model) + Flux.update!(opt, model, grad[1]) + end + + @info (; epoch, train_loss=loss(model, train_loader), test_loss=loss(model, test_loader)) +end +``` diff --git a/GraphNeuralNetworks/docs/src/index.md b/GraphNeuralNetworks/docs/src/index.md index faa50138a..ee5918c47 100644 --- a/GraphNeuralNetworks/docs/src/index.md +++ b/GraphNeuralNetworks/docs/src/index.md @@ -1,87 +1,20 @@ -# GraphNeuralNetworks +# GraphNeuralNetworks Monorepo -This is the documentation page for [GraphNeuralNetworks.jl](https://github.com/CarloLucibello/GraphNeuralNetworks.jl), a graph neural network library written in Julia and based on the deep learning framework [Flux.jl](https://github.com/FluxML/Flux.jl). -GraphNeuralNetworks.jl is largely inspired by [PyTorch Geometric](https://pytorch-geometric.readthedocs.io/en/latest/), [Deep Graph Library](https://docs.dgl.ai/), -and [GeometricFlux.jl](https://fluxml.ai/GeometricFlux.jl/stable/). +This repository is a monorepo that contains all the code for the GraphNeuralNetworks project. The project is organized as a monorepo to facilitate code sharing and reusability across different components of the project. The monorepo contains the following packages: -Among its features: +- `GraphNeuralNetwork.jl`: Package that contains stateful graph convolutional layers based on the machine learning framework [Flux.jl](https://fluxml.ai/Flux.jl/stable/). This is fronted package for Flux users. It depends on GNNlib.jl, GNNGraphs.jl, and Flux.jl packages. -* Implements common graph convolutional layers. -* Supports computations on batched graphs. -* Easy to define custom layers. -* CUDA support. -* Integration with [Graphs.jl](https://github.com/JuliaGraphs/Graphs.jl). -* [Examples](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/tree/master/examples) of node, edge, and graph level machine learning tasks. +- `GNNLux.jl`: Package that contains stateless graph convolutional layers based on the machine learning framework [Lux.jl](https://lux.csail.mit.edu/stable/). This is fronted package for Lux users. It depends on GNNlib.jl, GNNGraphs.jl, and Lux.jl packages. +- `GNNlib.jl`: Package that contains the core graph neural network layers and utilities. It depends on GNNGraphs.jl and GNNlib.jl packages and serves for code base for GraphNeuralNetwork.jl and GNNLux.jl packages. -## Package overview +- `GNNGraphs.jl`: Package that contains the graph data structures and helper functions for working with graph data. It depends on Graphs.jl package. -Let's give a brief overview of the package by solving a -graph regression problem with synthetic data. +Here is a schema of the dependencies between the packages: -Usage examples on real datasets can be found in the [examples](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/tree/master/examples) folder. +![Monorepo schema](assets/schema.png) -### Data preparation -We create a dataset consisting in multiple random graphs and associated data features. -```julia -using GraphNeuralNetworks, Graphs, Flux, CUDA, Statistics, MLUtils -using Flux: DataLoader -all_graphs = GNNGraph[] -for _ in 1:1000 - g = rand_graph(10, 40, - ndata=(; x = randn(Float32, 16,10)), # input node features - gdata=(; y = randn(Float32))) # regression target - push!(all_graphs, g) -end -``` - -### Model building - -We concisely define our model as a [`GNNChain`](@ref) containing two graph convolutional layers. If CUDA is available, our model will live on the gpu. - -```julia -device = CUDA.functional() ? Flux.gpu : Flux.cpu; - -model = GNNChain(GCNConv(16 => 64), - BatchNorm(64), # Apply batch normalization on node features (nodes dimension is batch dimension) - x -> relu.(x), - GCNConv(64 => 64, relu), - GlobalPool(mean), # aggregate node-wise features into graph-wise features - Dense(64, 1)) |> device - -opt = Flux.setup(Adam(1f-4), model) -``` - -### Training - -Finally, we use a standard Flux training pipeline to fit our dataset. -We use Flux's `DataLoader` to iterate over mini-batches of graphs -that are glued together into a single `GNNGraph` using the [`Flux.batch`](@ref) method. This is what happens under the hood when creating a `DataLoader` with the -`collate=true` option. - -```julia -train_graphs, test_graphs = MLUtils.splitobs(all_graphs, at=0.8) - -train_loader = DataLoader(train_graphs, - batchsize=32, shuffle=true, collate=true) -test_loader = DataLoader(test_graphs, - batchsize=32, shuffle=false, collate=true) - -loss(model, g::GNNGraph) = mean((vec(model(g, g.x)) - g.y).^2) - -loss(model, loader) = mean(loss(model, g |> device) for g in loader) - -for epoch in 1:100 - for g in train_loader - g = g |> device - grad = gradient(model -> loss(model, g), model) - Flux.update!(opt, model, grad[1]) - end - - @info (; epoch, train_loss=loss(model, train_loader), test_loss=loss(model, test_loader)) -end -``` diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 000000000..8d7c1e6a6 --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,4 @@ +[deps] +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" +MultiDocumenter = "87ed4bf0-c935-4a67-83c3-2a03bee4197c" + diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 000000000..cac604fcd --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1,31 @@ + + + V9 + + + + + + + + \ No newline at end of file diff --git a/docs/make-multi.jl b/docs/make-multi.jl new file mode 100644 index 000000000..546d0b3fc --- /dev/null +++ b/docs/make-multi.jl @@ -0,0 +1,44 @@ +using MultiDocumenter + +docs = [ + MultiDocumenter.MultiDocRef( + upstream = joinpath(dirname(@__DIR__),"GraphNeuralNetworks", "docs", "build"), + path = "graphneuralnetworks", + name = "GraphNeuralNetworks", + fix_canonical_url = false), + MultiDocumenter.MultiDocRef( + upstream = joinpath(dirname(@__DIR__), "GNNGraphs", "docs", "build"), + path = "gnngraphs", + name = "GNNGraphs", + fix_canonical_url = false), + MultiDocumenter.MultiDocRef( + upstream = joinpath(dirname(@__DIR__), "GNNlib", "docs", "build"), + path = "gnnlib", + name = "GNNlib", + fix_canonical_url = false), + MultiDocumenter.MultiDocRef( + upstream = joinpath(dirname(@__DIR__), "GNNLux", "docs", "build"), + path = "gnnlux", + name = "GNNLux", + fix_canonical_url = false), + MultiDocumenter.MultiDocRef( + upstream = joinpath(dirname(@__DIR__), "tutorials", "docs", "build"), + path = "tutorials", + name = "Tutorials", + fix_canonical_url = false), +] + +outpath = joinpath(@__DIR__, "build") + +MultiDocumenter.make( + outpath, + docs; + search_engine = MultiDocumenter.SearchConfig( + index_versions = ["stable"], + engine = MultiDocumenter.FlexSearch + ), + brand_image = MultiDocumenter.BrandImage("", "logo.svg") +) + +cp(joinpath(@__DIR__, "logo.svg"), + joinpath(outpath, "logo.svg")) diff --git a/tutorials/docs/Project.toml b/tutorials/docs/Project.toml new file mode 100644 index 000000000..8e1472137 --- /dev/null +++ b/tutorials/docs/Project.toml @@ -0,0 +1,4 @@ +[deps] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Pluto = "c3e4b0f8-55cb-11ea-2926-15256bba5781" +PlutoStaticHTML = "359b1769-a58e-495b-9770-312e911026ad" diff --git a/tutorials/docs/make.jl b/tutorials/docs/make.jl new file mode 100644 index 000000000..b131d9816 --- /dev/null +++ b/tutorials/docs/make.jl @@ -0,0 +1,34 @@ +using Documenter + + +assets = [] +prettyurls = get(ENV, "CI", nothing) == "true" +mathengine = MathJax3() + +# interlinks = InterLinks( +# "NNlib" => "https://fluxml.ai/NNlib.jl/stable/", +# "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnngraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv")), +# "GraphNeuralNetworks" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/graphneuralnetworks/", joinpath(dirname(dirname(@__DIR__)), "docs", "build", "objects.inv")),) + +makedocs(; + doctest = false, + clean = true, + format = Documenter.HTML(; + mathengine, prettyurls, assets = assets, size_threshold = nothing), + sitename = "Tutorials", + pages = ["Home" => "index.md", + "Introductory tutorials" => [ + "Hands on" => "pluto_output/gnn_intro_pluto.md", + "Node classification" => "pluto_output/node_classification_pluto.md", + "Graph classification" => "pluto_output/graph_classification_pluto.md" + ], + "Temporal graph neural networks" =>[ + "Node autoregression" => "pluto_output/traffic_prediction.md", + "Temporal graph classification" => "pluto_output/temporal_graph_classification_pluto.md" + + ]]) + + + +deploydocs(; repo = "https://github.com/CarloLucibello/GraphNeuralNetworks.jl.git", + dirname = "tutorials") \ No newline at end of file diff --git a/tutorials/docs/src/index.md b/tutorials/docs/src/index.md new file mode 100644 index 000000000..8e21e6db8 --- /dev/null +++ b/tutorials/docs/src/index.md @@ -0,0 +1,24 @@ +# Tutorials + +## Introductory tutorials + + +Here are some introductory tutorials to get you started: + +- [Hands-on introduction to Graph Neural Networks](pluto_output/gnn_intro_pluto.md) +- [Node classification with GraphNeuralNetworks.jl](pluto_output/node_classification_pluto.md) +- [Graph classification with GraphNeuralNetworks.jl](pluto_output/graph_classification_pluto.md) + + + +## Temporal graph neural networks tutorials + +Here some tutorials on temporal graph neural networks: + +- [Traffic Prediction using recurrent Temporal Graph Convolutional Network](pluto_output/traffic_prediction.md) + +- [Temporal Graph classification with GraphNeuralNetworks.jl](pluto_output/temporal_graph_classification_pluto.md) + +## Contributions + +If you have a suggestion on adding new tutorials, feel free to create a new issue [here](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/issues/new). Users are invited to contribute demonstrations of their own. If you want to contribute new tutorials and looking for inspiration, checkout these tutorials from [PyTorch Geometric](https://pytorch-geometric.readthedocs.io/en/latest/notes/colabs.html). Please check out existing tutorials for more details. \ No newline at end of file diff --git a/GraphNeuralNetworks/docs/pluto_output/gnn_intro_pluto.md b/tutorials/docs/src/pluto_output/gnn_intro_pluto.md similarity index 69% rename from GraphNeuralNetworks/docs/pluto_output/gnn_intro_pluto.md rename to tutorials/docs/src/pluto_output/gnn_intro_pluto.md index e188bf1d8..9c3c1e52c 100644 --- a/GraphNeuralNetworks/docs/pluto_output/gnn_intro_pluto.md +++ b/tutorials/docs/src/pluto_output/gnn_intro_pluto.md @@ -25,11 +25,11 @@ -

This Pluto notebook is a Julia adaptation of the Pytorch Geometric tutorials that can be found here.

Recently, deep learning on graphs has emerged to one of the hottest research fields in the deep learning community. Here, Graph Neural Networks (GNNs) aim to generalize classical deep learning concepts to irregular structured data (in contrast to images or texts) and to enable neural networks to reason about objects and their relations.

This is done by following a simple neural message passing scheme, where node features \(\mathbf{x}_i^{(\ell)}\) of all nodes \(i \in \mathcal{V}\) in a graph \(\mathcal{G} = (\mathcal{V}, \mathcal{E})\) are iteratively updated by aggregating localized information from their neighbors \(\mathcal{N}(i)\):

$$\mathbf{x}_i^{(\ell + 1)} = f^{(\ell + 1)}_{\theta} \left( \mathbf{x}_i^{(\ell)}, \left\{ \mathbf{x}_j^{(\ell)} : j \in \mathcal{N}(i) \right\} \right)$$

This tutorial will introduce you to some fundamental concepts regarding deep learning on graphs via Graph Neural Networks based on the GraphNeuralNetworks.jl library. GraphNeuralNetworks.jl is an extension library to the popular deep learning framework Flux.jl, and consists of various methods and utilities to ease the implementation of Graph Neural Networks.

Let's first import the packages we need:

+

Hands-on introduction to Graph Neural Networks

This Pluto notebook is a Julia adaptation of the Pytorch Geometric tutorials that can be found here.

Recently, deep learning on graphs has emerged to one of the hottest research fields in the deep learning community. Here, Graph Neural Networks (GNNs) aim to generalize classical deep learning concepts to irregular structured data (in contrast to images or texts) and to enable neural networks to reason about objects and their relations.

This is done by following a simple neural message passing scheme, where node features \(\mathbf{x}_i^{(\ell)}\) of all nodes \(i \in \mathcal{V}\) in a graph \(\mathcal{G} = (\mathcal{V}, \mathcal{E})\) are iteratively updated by aggregating localized information from their neighbors \(\mathcal{N}(i)\):

$$\mathbf{x}_i^{(\ell + 1)} = f^{(\ell + 1)}_{\theta} \left( \mathbf{x}_i^{(\ell)}, \left\{ \mathbf{x}_j^{(\ell)} : j \in \mathcal{N}(i) \right\} \right)$$

This tutorial will introduce you to some fundamental concepts regarding deep learning on graphs via Graph Neural Networks based on the GraphNeuralNetworks.jl library. GraphNeuralNetworks.jl is an extension library to the popular deep learning framework Flux.jl, and consists of various methods and utilities to ease the implementation of Graph Neural Networks.

Let's first import the packages we need:

begin
     using Flux
@@ -162,7 +162,7 @@ Is undirected: true
         layers::NamedTuple
     end
 
-    Flux.@functor GCN # provides parameter collection, gpu movement and more
+    Flux.@layer GCN # provides parameter collection, gpu movement and more
 
     function GCN(num_features, num_classes)
         layers = (conv1 = GCNConv(num_features => 4),
@@ -195,10 +195,10 @@ end
num_classes = 4 gcn = GCN(num_features, num_classes) end -
GCN((conv1 = GCNConv(34 => 4), conv2 = GCNConv(4 => 4), conv3 = GCNConv(4 => 2), classifier = Dense(2 => 4)))
+
GCN((conv1 = GCNConv(34 => 4), conv2 = GCNConv(4 => 4), conv3 = GCNConv(4 => 2), classifier = Dense(2 => 4)))  # 182 parameters
_, h = gcn(g, g.ndata.x)
-
(Float32[0.017824104 0.0077741514 … -0.049516954 -0.047012385; -0.008411304 0.00414012 … 0.0788404 0.07529551; -0.0069731097 0.0012623081 … 0.049945038 0.047662895; 0.0035474515 0.0027243823 … -0.001492914 -0.0013506437], Float32[-0.019373894 -0.0224004 … -0.04527937 -0.043780304; -0.027381245 -0.016037654 … 0.04697653 0.04436821])
+
(Float32[-0.0068139993 0.008728906 … 0.020461287 0.016271798; -0.0019973165 -0.0064561698 … -0.0044912496 -0.004174295; 0.1469301 0.13193016 … -0.06870474 -0.03323521; -0.022454038 -0.0069215773 … 0.025904683 0.018215057], Float32[-0.055850513 -0.03927876 … 0.03876325 0.023417776; -0.11278143 -0.11275233 … 0.03937418 0.014116553])
function visualize_embeddings(h; colors = nothing)
     xs = h[1, :] |> vec
@@ -208,7 +208,7 @@ end
visualize_embeddings (generic function with 1 method)
visualize_embeddings(h, colors = labels)
- +

Remarkably, even before training the weights of our model, the model produces an embedding of nodes that closely resembles the community-structure of the graph. Nodes of the same color (community) are already closely clustered together in the embedding space, although the weights of our model are initialized completely at random and we have not yet performed any training so far! This leads to the conclusion that GNNs introduce a strong inductive bias, leading to similar embeddings for nodes that are close to each other in the input graph.

Training on the Karate Club Network

But can we do better? Let's look at an example on how to train our network parameters based on the knowledge of the community assignments of 4 nodes in the graph (one for each community).

Since everything in our model is differentiable and parameterized, we can add some labels, train the model and observe how the embeddings react. Here, we make use of a semi-supervised or transductive learning procedure: we simply train against one node per class, but are allowed to make use of the complete input graph data.

Training our model is very similar to any other Flux model. In addition to defining our network architecture, we define a loss criterion (here, logitcrossentropy), and initialize a stochastic gradient optimizer (here, Adam). After that, we perform multiple rounds of optimization, where each round consists of a forward and backward pass to compute the gradients of our model parameters w.r.t. to the loss derived from the forward pass. If you are not new to Flux, this scheme should appear familiar to you.

Note that our semi-supervised learning scenario is achieved by the following line:

loss = logitcrossentropy(ŷ[:,train_mask], y[:,train_mask])

While we compute node embeddings for all of our nodes, we only make use of the training nodes for computing the loss. Here, this is implemented by filtering the output of the classifier out and ground-truth labels data.y to only contain the nodes in the train_mask.

Let us now start training and see how our node embeddings evolve over time (best experienced by explicitly running the code):

@@ -240,7 +240,7 @@ end
ŷ, emb_final = model(g, g.ndata.x)
-
(Float32[7.2331567 7.2313447 … 9.202145 9.188894; 12.7212515 12.735689 … -2.4455047 -2.3414903; -4.593668 -4.6052985 … 7.653345 7.5694675; -8.756303 -8.760008 … -4.8976927 -4.924286], Float32[-0.99999434 -1.0 … -0.9999765 -0.9999995; -0.9980977 -0.9999941 … 0.9964582 0.98278886])
+
(Float32[-8.871021 -6.288402 … 7.8817716 7.3984337; 7.873129 5.5748186 … -8.054153 -7.562167; 0.6939411 2.6538918 … 0.1978332 0.633129; 0.42380208 -1.7143326 … -0.14687762 -0.5542332], Float32[-0.99049056 -0.9905237 … 0.99305063 0.87260294; -0.9905631 -0.40585023 … 0.9999852 0.99999404])
# train accuracy
 mean(onecold(ŷ[:, train_mask]) .== onecold(y[:, train_mask]))
@@ -248,10 +248,10 @@ mean(onecold(ŷ[:, train_mask]) .== onecold(y[:, train_mask]))
# test accuracy
 mean(onecold(ŷ[:, .!train_mask]) .== onecold(y[:, .!train_mask]))
-
0.9
+
0.8
visualize_embeddings(emb_final, colors = labels)
- +

As one can see, our 3-layer GCN model manages to linearly separating the communities and classifying most of the nodes correctly.

Furthermore, we did this all with a few lines of code, thanks to the GraphNeuralNetworks.jl which helped us out with data handling and GNN implementations.

diff --git a/GraphNeuralNetworks/docs/pluto_output/graph_classification_pluto.md b/tutorials/docs/src/pluto_output/graph_classification_pluto.md similarity index 77% rename from GraphNeuralNetworks/docs/pluto_output/graph_classification_pluto.md rename to tutorials/docs/src/pluto_output/graph_classification_pluto.md index edebc99a5..0949c4674 100644 --- a/GraphNeuralNetworks/docs/pluto_output/graph_classification_pluto.md +++ b/tutorials/docs/src/pluto_output/graph_classification_pluto.md @@ -25,8 +25,8 @@
begin
     using Flux
@@ -43,7 +43,7 @@ end;
-

This Pluto notebook is a julia adaptation of the Pytorch Geometric tutorials that can be found here.

In this tutorial session we will have a closer look at how to apply Graph Neural Networks (GNNs) to the task of graph classification. Graph classification refers to the problem of classifying entire graphs (in contrast to nodes), given a dataset of graphs, based on some structural graph properties. Here, we want to embed entire graphs, and we want to embed those graphs in such a way so that they are linearly separable given a task at hand.

The most common task for graph classification is molecular property prediction, in which molecules are represented as graphs, and the task may be to infer whether a molecule inhibits HIV virus replication or not.

The TU Dortmund University has collected a wide range of different graph classification datasets, known as the TUDatasets, which are also accessible via MLDatasets.jl. Let's load and inspect one of the smaller ones, the MUTAG dataset:

+

Graph Classification with Graph Neural Networks

This Pluto notebook is a julia adaptation of the Pytorch Geometric tutorials that can be found here.

In this tutorial session we will have a closer look at how to apply Graph Neural Networks (GNNs) to the task of graph classification. Graph classification refers to the problem of classifying entire graphs (in contrast to nodes), given a dataset of graphs, based on some structural graph properties. Here, we want to embed entire graphs, and we want to embed those graphs in such a way so that they are linearly separable given a task at hand.

The most common task for graph classification is molecular property prediction, in which molecules are represented as graphs, and the task may be to infer whether a molecule inhibits HIV virus replication or not.

The TU Dortmund University has collected a wide range of different graph classification datasets, known as the TUDatasets, which are also accessible via MLDatasets.jl. Let's load and inspect one of the smaller ones, the MUTAG dataset:

dataset = TUDataset("MUTAG")
dataset TUDataset:
@@ -102,7 +102,7 @@ end

We have some useful utilities for working with graph datasets, e.g., we can shuffle the dataset and use the first 150 graphs as training graphs, while using the remaining ones for testing:

train_data, test_data = splitobs((graphs, y), at = 150, shuffle = true) |> getobs
-
((GNNGraph{Tuple{Vector{Int64}, Vector{Int64}, Nothing}}[GNNGraph(16, 34) with x: 7×16 data, GNNGraph(22, 50) with x: 7×22 data, GNNGraph(23, 54) with x: 7×23 data, GNNGraph(11, 22) with x: 7×11 data, GNNGraph(17, 38) with x: 7×17 data, GNNGraph(13, 28) with x: 7×13 data, GNNGraph(19, 44) with x: 7×19 data, GNNGraph(16, 34) with x: 7×16 data, GNNGraph(14, 30) with x: 7×14 data, GNNGraph(18, 38) with x: 7×18 data  …  GNNGraph(12, 26) with x: 7×12 data, GNNGraph(19, 40) with x: 7×19 data, GNNGraph(19, 44) with x: 7×19 data, GNNGraph(26, 60) with x: 7×26 data, GNNGraph(20, 44) with x: 7×20 data, GNNGraph(20, 44) with x: 7×20 data, GNNGraph(17, 38) with x: 7×17 data, GNNGraph(19, 44) with x: 7×19 data, GNNGraph(19, 42) with x: 7×19 data, GNNGraph(22, 50) with x: 7×22 data], Bool[0 0 … 0 0; 1 1 … 1 1]), (GNNGraph{Tuple{Vector{Int64}, Vector{Int64}, Nothing}}[GNNGraph(26, 60) with x: 7×26 data, GNNGraph(15, 34) with x: 7×15 data, GNNGraph(11, 22) with x: 7×11 data, GNNGraph(24, 50) with x: 7×24 data, GNNGraph(17, 38) with x: 7×17 data, GNNGraph(21, 44) with x: 7×21 data, GNNGraph(17, 38) with x: 7×17 data, GNNGraph(13, 28) with x: 7×13 data, GNNGraph(12, 26) with x: 7×12 data, GNNGraph(17, 38) with x: 7×17 data  …  GNNGraph(12, 26) with x: 7×12 data, GNNGraph(23, 52) with x: 7×23 data, GNNGraph(12, 24) with x: 7×12 data, GNNGraph(23, 50) with x: 7×23 data, GNNGraph(13, 28) with x: 7×13 data, GNNGraph(18, 40) with x: 7×18 data, GNNGraph(16, 36) with x: 7×16 data, GNNGraph(13, 26) with x: 7×13 data, GNNGraph(28, 62) with x: 7×28 data, GNNGraph(11, 22) with x: 7×11 data], Bool[0 0 … 0 1; 1 1 … 1 0]))
+
((GNNGraph{Tuple{Vector{Int64}, Vector{Int64}, Nothing}}[GNNGraph(12, 26) with x: 7×12 data, GNNGraph(23, 52) with x: 7×23 data, GNNGraph(12, 26) with x: 7×12 data, GNNGraph(16, 34) with x: 7×16 data, GNNGraph(15, 32) with x: 7×15 data, GNNGraph(13, 28) with x: 7×13 data, GNNGraph(11, 22) with x: 7×11 data, GNNGraph(23, 54) with x: 7×23 data, GNNGraph(15, 34) with x: 7×15 data, GNNGraph(22, 50) with x: 7×22 data  …  GNNGraph(16, 34) with x: 7×16 data, GNNGraph(19, 44) with x: 7×19 data, GNNGraph(26, 60) with x: 7×26 data, GNNGraph(20, 44) with x: 7×20 data, GNNGraph(16, 36) with x: 7×16 data, GNNGraph(15, 34) with x: 7×15 data, GNNGraph(23, 54) with x: 7×23 data, GNNGraph(22, 50) with x: 7×22 data, GNNGraph(23, 54) with x: 7×23 data, GNNGraph(13, 26) with x: 7×13 data], Bool[0 0 … 0 1; 1 1 … 1 0]), (GNNGraph{Tuple{Vector{Int64}, Vector{Int64}, Nothing}}[GNNGraph(13, 28) with x: 7×13 data, GNNGraph(14, 28) with x: 7×14 data, GNNGraph(19, 44) with x: 7×19 data, GNNGraph(17, 38) with x: 7×17 data, GNNGraph(16, 34) with x: 7×16 data, GNNGraph(19, 44) with x: 7×19 data, GNNGraph(10, 20) with x: 7×10 data, GNNGraph(20, 44) with x: 7×20 data, GNNGraph(25, 56) with x: 7×25 data, GNNGraph(20, 46) with x: 7×20 data  …  GNNGraph(12, 26) with x: 7×12 data, GNNGraph(21, 44) with x: 7×21 data, GNNGraph(17, 38) with x: 7×17 data, GNNGraph(22, 50) with x: 7×22 data, GNNGraph(11, 22) with x: 7×11 data, GNNGraph(22, 50) with x: 7×22 data, GNNGraph(11, 22) with x: 7×11 data, GNNGraph(24, 50) with x: 7×24 data, GNNGraph(12, 26) with x: 7×12 data, GNNGraph(19, 44) with x: 7×19 data], Bool[0 1 … 1 0; 1 0 … 0 1]))
begin
     train_loader = DataLoader(train_data, batchsize = 32, shuffle = true)
@@ -123,15 +123,15 @@ end

Since graphs in graph classification datasets are usually small, a good idea is to batch the graphs before inputting them into a Graph Neural Network to guarantee full GPU utilization. In the image or language domain, this procedure is typically achieved by rescaling or padding each example into a set of equally-sized shapes, and examples are then grouped in an additional dimension. The length of this dimension is then equal to the number of examples grouped in a mini-batch and is typically referred to as the batchsize.

However, for GNNs the two approaches described above are either not feasible or may result in a lot of unnecessary memory consumption. Therefore, GraphNeuralNetworks.jl opts for another approach to achieve parallelization across a number of examples. Here, adjacency matrices are stacked in a diagonal fashion (creating a giant graph that holds multiple isolated subgraphs), and node and target features are simply concatenated in the node dimension (the last dimension).

This procedure has some crucial advantages over other batching procedures:

  1. GNN operators that rely on a message passing scheme do not need to be modified since messages are not exchanged between two nodes that belong to different graphs.

  2. There is no computational or memory overhead since adjacency matrices are saved in a sparse fashion holding only non-zero entries, i.e., the edges.

GraphNeuralNetworks.jl can batch multiple graphs into a single giant graph:

vec_gs, _ = first(train_loader)
-
(GNNGraph{Tuple{Vector{Int64}, Vector{Int64}, Nothing}}[GNNGraph(19, 44) with x: 7×19 data, GNNGraph(20, 46) with x: 7×20 data, GNNGraph(15, 34) with x: 7×15 data, GNNGraph(25, 56) with x: 7×25 data, GNNGraph(17, 38) with x: 7×17 data, GNNGraph(20, 44) with x: 7×20 data, GNNGraph(16, 34) with x: 7×16 data, GNNGraph(11, 22) with x: 7×11 data, GNNGraph(19, 44) with x: 7×19 data, GNNGraph(20, 44) with x: 7×20 data  …  GNNGraph(12, 24) with x: 7×12 data, GNNGraph(12, 26) with x: 7×12 data, GNNGraph(16, 36) with x: 7×16 data, GNNGraph(11, 22) with x: 7×11 data, GNNGraph(22, 50) with x: 7×22 data, GNNGraph(13, 28) with x: 7×13 data, GNNGraph(14, 30) with x: 7×14 data, GNNGraph(16, 34) with x: 7×16 data, GNNGraph(22, 50) with x: 7×22 data, GNNGraph(23, 54) with x: 7×23 data], Bool[0 0 … 0 0; 1 1 … 1 1])
+
(GNNGraph{Tuple{Vector{Int64}, Vector{Int64}, Nothing}}[GNNGraph(13, 28) with x: 7×13 data, GNNGraph(15, 34) with x: 7×15 data, GNNGraph(11, 22) with x: 7×11 data, GNNGraph(17, 38) with x: 7×17 data, GNNGraph(23, 54) with x: 7×23 data, GNNGraph(14, 30) with x: 7×14 data, GNNGraph(16, 34) with x: 7×16 data, GNNGraph(17, 38) with x: 7×17 data, GNNGraph(13, 28) with x: 7×13 data, GNNGraph(19, 40) with x: 7×19 data  …  GNNGraph(26, 56) with x: 7×26 data, GNNGraph(13, 28) with x: 7×13 data, GNNGraph(11, 22) with x: 7×11 data, GNNGraph(18, 38) with x: 7×18 data, GNNGraph(28, 66) with x: 7×28 data, GNNGraph(11, 22) with x: 7×11 data, GNNGraph(13, 28) with x: 7×13 data, GNNGraph(18, 40) with x: 7×18 data, GNNGraph(16, 36) with x: 7×16 data, GNNGraph(22, 50) with x: 7×22 data], Bool[1 0 … 1 0; 0 1 … 0 1])
MLUtils.batch(vec_gs)
GNNGraph:
-  num_nodes: 575
-  num_edges: 1276
+  num_nodes: 569
+  num_edges: 1258
   num_graphs: 32
   ndata:
-	x = 7×575 Matrix{Float32}
+ x = 7×569 Matrix{Float32}

Each batched graph object is equipped with a graph_indicator vector, which maps each node to its respective graph in the batch:

$$\textrm{graph\_indicator} = [1, \ldots, 1, 2, \ldots, 2, 3, \ldots ]$$

diff --git a/GraphNeuralNetworks/docs/pluto_output/node_classification_pluto.md b/tutorials/docs/src/pluto_output/node_classification_pluto.md similarity index 54% rename from GraphNeuralNetworks/docs/pluto_output/node_classification_pluto.md rename to tutorials/docs/src/pluto_output/node_classification_pluto.md index 55ac13f71..11f9b9ddc 100644 --- a/GraphNeuralNetworks/docs/pluto_output/node_classification_pluto.md +++ b/tutorials/docs/src/pluto_output/node_classification_pluto.md @@ -25,11 +25,11 @@ -

In this tutorial, we will be learning how to use Graph Neural Networks (GNNs) for node classification. Given the ground-truth labels of only a small subset of nodes, and want to infer the labels for all the remaining nodes (transductive learning).

+

Node Classification with Graph Neural Networks

In this tutorial, we will be learning how to use Graph Neural Networks (GNNs) for node classification. Given the ground-truth labels of only a small subset of nodes, and want to infer the labels for all the remaining nodes (transductive learning).

``` @@ -155,7 +155,7 @@ end; layers::NamedTuple end - Flux.@functor MLP + Flux.@layer :expand MLP function MLP(num_features, num_classes, hidden_channels; drop_rate = 0.5) layers = (hidden = Dense(num_features => hidden_channels), @@ -213,7 +213,7 @@ end

After training the model, we can call the accuracy function to see how well our model performs on unseen labels. Here, we are interested in the accuracy of the model, i.e., the ratio of correctly classified nodes:

accuracy(mlp, g.ndata.features, y, .!train_mask)
-
0.45794392523364486
+
0.45872274143302183

As one can see, our MLP performs rather bad with only about 47% test accuracy. But why does the MLP do not perform better? The main reason for that is that this model suffers from heavy overfitting due to only having access to a small amount of training nodes, and therefore generalizes poorly to unseen node representations.

It also fails to incorporate an important bias into the model: Cited papers are very likely related to the category of a document. That is exactly where Graph Neural Networks come into play and can help to boost the performance of our model.

@@ -230,7 +230,7 @@ end layers::NamedTuple end - Flux.@functor GCN # provides parameter collection, gpu movement and more + Flux.@layer GCN # provides parameter collection, gpu movement and more function GCN(num_features, num_classes, hidden_channels; drop_rate = 0.5) layers = (conv1 = GCNConv(num_features => hidden_channels), @@ -258,7 +258,7 @@ end h_untrained = gcn(g, x) |> transpose visualize_tsne(h_untrained, g.ndata.targets) end - +

We certainly can do better by training our model. The training and testing procedure is once again the same, but this time we make use of the node features xand the graph g as input to our GCN model.

@@ -304,7 +304,7 @@ end println("Test accuracy: $(test_accuracy)") end
Train accuracy: 1.0
-Test accuracy: 0.7609034267912772
+Test accuracy: 0.7706386292834891
 
@@ -316,7 +316,7 @@ Test accuracy: 0.7609034267912772 out_trained = gcn(g, x) |> transpose visualize_tsne(out_trained, g.ndata.targets) end - + ``` diff --git a/tutorials/docs/src/pluto_output/temporal_graph_classification_pluto.md b/tutorials/docs/src/pluto_output/temporal_graph_classification_pluto.md new file mode 100644 index 000000000..db5753f93 --- /dev/null +++ b/tutorials/docs/src/pluto_output/temporal_graph_classification_pluto.md @@ -0,0 +1,211 @@ +```@raw html + + + + + +

Temporal Graph classification with GraphNeuralNetworks.jl

In this tutorial, we will learn how to extend the graph classification task to the case of temporal graphs, i.e., graphs whose topology and features are time-varying.

We will design and train a simple temporal graph neural network architecture to classify subjects' gender (female or male) using the temporal graphs extracted from their brain fMRI scan signals. Given the large amount of data, we will implement the training so that it can also run on the GPU.

+ + +``` +## Import +```@raw html +
+

We start by importing the necessary libraries. We use GraphNeuralNetworks.jl, Flux.jl and MLDatasets.jl, among others.

+ +
begin
+    using Flux
+    using GraphNeuralNetworks
+    using Statistics, Random
+    using LinearAlgebra
+    using MLDatasets: TemporalBrains
+    using CUDA
+    using cuDNN
+end
+ + + +``` +## Dataset: TemporalBrains +```@raw html +
+

The TemporalBrains dataset contains a collection of functional brain connectivity networks from 1000 subjects obtained from resting-state functional MRI data from the Human Connectome Project (HCP). Functional connectivity is defined as the temporal dependence of neuronal activation patterns of anatomically separated brain regions.

The graph nodes represent brain regions and their number is fixed at 102 for each of the 27 snapshots, while the edges, representing functional connectivity, change over time. For each snapshot, the feature of a node represents the average activation of the node during that snapshot. Each temporal graph has a label representing gender ('M' for male and 'F' for female) and age group (22-25, 26-30, 31-35, and 36+). The network's edge weights are binarized, and the threshold is set to 0.6 by default.

+ +
brain_dataset = TemporalBrains()
+
dataset TemporalBrains:
+  graphs  =>    1000-element Vector{MLDatasets.TemporalSnapshotsGraph}
+ + +

After loading the dataset from the MLDatasets.jl package, we see that there are 1000 graphs and we need to convert them to the TemporalSnapshotsGNNGraph format. So we create a function called data_loader that implements the latter and splits the dataset into the training set that will be used to train the model and the test set that will be used to test the performance of the model.

+ +
function data_loader(brain_dataset)
+    graphs = brain_dataset.graphs
+    dataset = Vector{TemporalSnapshotsGNNGraph}(undef, length(graphs))
+    for i in 1:length(graphs)
+        graph = graphs[i]
+        dataset[i] = TemporalSnapshotsGNNGraph(GraphNeuralNetworks.mlgraph2gnngraph.(graph.snapshots))
+        # Add graph and node features
+        for t in 1:27
+            s = dataset[i].snapshots[t]
+            s.ndata.x = [I(102); s.ndata.x']
+        end
+        dataset[i].tgdata.g = Float32.(Flux.onehot(graph.graph_data.g, ["F", "M"]))
+    end
+    # Split the dataset into a 80% training set and a 20% test set
+    train_loader = dataset[1:200]
+    test_loader = dataset[201:250]
+    return train_loader, test_loader
+end;
+ + + +

The first part of the data_loader function calls the mlgraph2gnngraph function for each snapshot, which takes the graph and converts it to a GNNGraph. The vector of GNNGraphs is then rewritten to a TemporalSnapshotsGNNGraph.

The second part adds the graph and node features to the temporal graphs, in particular it adds the one-hot encoding of the label of the graph (in this case we directly use the identity matrix) and appends the mean activation of the node of the snapshot (which is contained in the vector dataset[i].snapshots[t].ndata.x, where i is the index indicating the subject and t is the snapshot). For the graph feature, it adds the one-hot encoding of gender.

The last part splits the dataset.

+ + +``` +## Model +```@raw html +
+

We now implement a simple model that takes a TemporalSnapshotsGNNGraph as input. It consists of a GINConv applied independently to each snapshot, a GlobalPool to get an embedding for each snapshot, a pooling on the time dimension to get an embedding for the whole temporal graph, and finally a Dense layer.

First, we start by adapting the GlobalPool to the TemporalSnapshotsGNNGraphs.

+ +
function (l::GlobalPool)(g::TemporalSnapshotsGNNGraph, x::AbstractVector)
+    h = [reduce_nodes(l.aggr, g[i], x[i]) for i in 1:(g.num_snapshots)]
+    sze = size(h[1])
+    reshape(reduce(hcat, h), sze[1], length(h))
+end
+ + + +

Then we implement the constructor of the model, which we call GenderPredictionModel, and the foward pass.

+ +
begin
+    struct GenderPredictionModel
+        gin::GINConv
+        mlp::Chain
+        globalpool::GlobalPool
+        f::Function
+        dense::Dense
+    end
+    
+    Flux.@layer GenderPredictionModel
+    
+    function GenderPredictionModel(; nfeatures = 103, nhidden = 128, activation = relu)
+        mlp = Chain(Dense(nfeatures, nhidden, activation), Dense(nhidden, nhidden, activation))
+        gin = GINConv(mlp, 0.5)
+        globalpool = GlobalPool(mean)
+        f = x -> mean(x, dims = 2)
+        dense = Dense(nhidden, 2)
+        GenderPredictionModel(gin, mlp, globalpool, f, dense)
+    end
+    
+    function (m::GenderPredictionModel)(g::TemporalSnapshotsGNNGraph)
+        h = m.gin(g, g.ndata.x)
+        h = m.globalpool(g, h)
+        h = m.f(h)
+        m.dense(h)
+    end
+    
+end
+ + + +``` +## Training +```@raw html +
+

We train the model for 100 epochs, using the Adam optimizer with a learning rate of 0.001. We use the logitbinarycrossentropy as the loss function, which is typically used as the loss in two-class classification, where the labels are given in a one-hot format. The accuracy expresses the number of correct classifications.

+ +
lossfunction(ŷ, y) = Flux.logitbinarycrossentropy(ŷ, y);
+ + +
function eval_loss_accuracy(model, data_loader)
+    error = mean([lossfunction(model(g), g.tgdata.g) for g in data_loader])
+    acc = mean([round(100 * mean(Flux.onecold(model(g)) .==     Flux.onecold(g.tgdata.g)); digits = 2) for g in data_loader])
+    return (loss = error, acc = acc)
+end;
+ + +
function train(dataset; usecuda::Bool, kws...)
+
+    if usecuda && CUDA.functional() #check if GPU is available 
+        my_device = gpu
+        @info "Training on GPU"
+    else
+        my_device = cpu
+        @info "Training on CPU"
+    end
+    
+    function report(epoch)
+        train_loss, train_acc = eval_loss_accuracy(model, train_loader)
+        test_loss, test_acc = eval_loss_accuracy(model, test_loader)
+        println("Epoch: $epoch  $((; train_loss, train_acc))  $((; test_loss, test_acc))")
+        return (train_loss, train_acc, test_loss, test_acc)
+    end
+
+    model = GenderPredictionModel() |> my_device
+
+    opt = Flux.setup(Adam(1.0f-3), model)
+
+    train_loader, test_loader = data_loader(dataset)
+    train_loader = train_loader |> my_device
+    test_loader = test_loader |> my_device
+
+    report(0)
+    for epoch in 1:100
+        for g in train_loader
+            grads = Flux.gradient(model) do model
+                ŷ = model(g)
+                lossfunction(vec(ŷ), g.tgdata.g)
+            end
+            Flux.update!(opt, model, grads[1])
+        end
+        if  epoch % 10 == 0
+            report(epoch)
+        end
+    end
+    return model
+end;
+
+ + +
train(brain_dataset; usecuda = true)
+
GenderPredictionModel(GINConv(Chain(Dense(103 => 128, relu), Dense(128 => 128, relu)), 0.5), Chain(Dense(103 => 128, relu), Dense(128 => 128, relu)), GlobalPool{typeof(mean)}(Statistics.mean), var"#4#5"(), Dense(128 => 2))  # 30_082 parameters, plus 29_824 non-trainable
+ + +

We set up the training on the GPU because training takes a lot of time, especially when working on the CPU.

+ + +``` +## Conclusions +```@raw html +
+

In this tutorial, we implemented a very simple architecture to classify temporal graphs in the context of gender classification using brain data. We then trained the model on the GPU for 100 epochs on the TemporalBrains dataset. The accuracy of the model is approximately 75-80%, but can be improved by fine-tuning the parameters and training on more data.

+ + +``` + diff --git a/tutorials/docs/src/pluto_output/traffic_prediction.md b/tutorials/docs/src/pluto_output/traffic_prediction.md new file mode 100644 index 000000000..338650dbf --- /dev/null +++ b/tutorials/docs/src/pluto_output/traffic_prediction.md @@ -0,0 +1,216 @@ +```@raw html + + + + + +

Traffic Prediction using recurrent Temporal Graph Convolutional Network

In this tutorial, we will learn how to use a recurrent Temporal Graph Convolutional Network (TGCN) to predict traffic in a spatio-temporal setting. Traffic forecasting is the problem of predicting future traffic trends on a road network given historical traffic data, such as, in our case, traffic speed and time of day.

+ + +``` +## Import +```@raw html +
+

We start by importing the necessary libraries. We use GraphNeuralNetworks.jl, Flux.jl and MLDatasets.jl, among others.

+ +
begin
+    using GraphNeuralNetworks
+    using Flux
+    using Flux.Losses: mae
+    using MLDatasets: METRLA
+    using Statistics
+    using Plots
+end
+ + + +``` +## Dataset: METR-LA +```@raw html +
+

We use the METR-LA dataset from the paper Diffusion Convolutional Recurrent Neural Network: Data-driven Traffic Forecasting, which contains traffic data from loop detectors in the highway of Los Angeles County. The dataset contains traffic speed data from March 1, 2012 to June 30, 2012. The data is collected every 5 minutes, resulting in 12 observations per hour, from 207 sensors. Each sensor is a node in the graph, and the edges represent the distances between the sensors.

+ +
dataset_metrla = METRLA(; num_timesteps = 3)
+
dataset METRLA:
+  graphs  =>    1-element Vector{MLDatasets.Graph}
+ +
 g = dataset_metrla[1]
+
Graph:
+  num_nodes   =>    207
+  num_edges   =>    1722
+  edge_index  =>    ("1722-element Vector{Int64}", "1722-element Vector{Int64}")
+  node_data   =>    (features = "34269-element Vector{Any}", targets = "34269-element Vector{Any}")
+  edge_data   =>    1722-element Vector{Float32}
+ + +

edge_data contains the weights of the edges of the graph and node_data contains a node feature vector and a target vector. The latter vectors contain batches of dimension num_timesteps, which means that they contain vectors with the node features and targets of num_timesteps time steps. Two consecutive batches are shifted by one-time step. The node features are the traffic speed of the sensors and the time of the day, and the targets are the traffic speed of the sensors in the next time step. Let's see some examples:

+ +
size(g.node_data.features[1])
+
(2, 207, 3)
+ + +

The first dimension correspond to the two features (first line the speed value and the second line the time of the day), the second to the nodes and the third to the number of timestep num_timesteps.

+ +
size(g.node_data.targets[1])
+
(1, 207, 3)
+ + +

In the case of the targets the first dimension is 1 because they store just the speed value.

+ +
g.node_data.features[1][:,1,:]
+
2×3 Matrix{Float32}:
+  1.17081    1.11647   1.15888
+ -0.876741  -0.87663  -0.87652
+ +
g.node_data.features[2][:,1,:]
+
2×3 Matrix{Float32}:
+  1.11647   1.15888  -0.876741
+ -0.87663  -0.87652  -0.87641
+ +
g.node_data.targets[1][:,1,:]
+
1×3 Matrix{Float32}:
+ 1.11647  1.15888  -0.876741
+ +
function plot_data(data,sensor)
+    p = plot(legend=false, xlabel="Time (h)", ylabel="Normalized speed")
+    plotdata = []
+    for i in 1:3:length(data)
+        push!(plotdata,data[i][1,sensor,:])
+    end
+    plotdata = reduce(vcat,plotdata)
+    plot!(p, collect(1:length(data)), plotdata, color = :green, xticks =([i for i in 0:50:250], ["$(i)" for i in 0:4:24]))
+    return p
+end
+
plot_data (generic function with 1 method)
+ +
plot_data(g.node_data.features[1:288],1)
+ + + +

Now let's construct the static graph, the temporal features and targets from the dataset.

+ +
begin
+    graph = GNNGraph(g.edge_index; edata = g.edge_data, g.num_nodes)
+    features = g.node_data.features
+    targets = g.node_data.targets
+end;  
+ + + +

Now let's construct the train_loader and data_loader.

+ +
begin
+    train_loader = zip(features[1:200], targets[1:200])
+    test_loader = zip(features[2001:2288], targets[2001:2288])
+end;
+ + + +``` +## Model: T-GCN +```@raw html +
+

We use the T-GCN model from the paper T-GCN: A Temporal Graph Convolutional Network for Traffic Prediction, which consists of a graph convolutional network (GCN) and a gated recurrent unit (GRU). The GCN is used to capture spatial features from the graph, and the GRU is used to capture temporal features from the feature time series.

+ +
model = GNNChain(TGCN(2 => 100), Dense(100, 1))
+
GNNChain(Recur(TGCNCell(2 => 100)), Dense(100 => 1))
+ + +

+ + +``` +## Training +```@raw html +
+

We train the model for 100 epochs, using the Adam optimizer with a learning rate of 0.001. We use the mean absolute error (MAE) as the loss function.

+ +
function train(graph, train_loader, model)
+
+    opt = Flux.setup(Adam(0.001), model)
+
+    for epoch in 1:100
+        for (x, y) in train_loader
+            x, y = (x, y)
+            grads = Flux.gradient(model) do model
+                ŷ = model(graph, x)
+                Flux.mae(ŷ, y) 
+            end
+            Flux.update!(opt, model, grads[1])
+        end
+        
+        if epoch % 10 == 0
+            loss = mean([Flux.mae(model(graph,x), y) for (x, y) in train_loader])
+            @show epoch, loss
+        end
+    end
+    return model
+end
+
train (generic function with 1 method)
+ +
train(graph, train_loader, model)
+
GNNChain(Recur(TGCNCell(2 => 100)), Dense(100 => 1))
+ +
function plot_predicted_data(graph,features,targets, sensor)
+    p = plot(xlabel="Time (h)", ylabel="Normalized speed")
+    prediction = []
+    grand_truth = []
+    for i in 1:3:length(features)
+        push!(grand_truth,targets[i][1,sensor,:])
+        push!(prediction, model(graph, features[i])[1,sensor,:]) 
+    end
+    prediction = reduce(vcat,prediction)
+    grand_truth = reduce(vcat, grand_truth)
+    plot!(p, collect(1:length(features)), grand_truth, color = :blue, label = "Grand Truth", xticks =([i for i in 0:50:250], ["$(i)" for i in 0:4:24]))
+    plot!(p, collect(1:length(features)), prediction, color = :red, label= "Prediction")
+    return p
+end
+
plot_predicted_data (generic function with 1 method)
+ +
plot_predicted_data(graph,features[301:588],targets[301:588], 1)
+ + +
accuracy(ŷ, y) = 1 - Statistics.norm(y-ŷ)/Statistics.norm(y)
+
accuracy (generic function with 1 method)
+ +
mean([accuracy(model(graph,x), y) for (x, y) in test_loader])
+
0.47803628f0
+ + +

The accuracy is not very good but can be improved by training using more data. We used a small subset of the dataset for this tutorial because of the computational cost of training the model. From the plot of the predictions, we can see that the model is able to capture the general trend of the traffic speed, but it is not able to capture the peaks of the traffic.

+ + +``` +## Conclusion +```@raw html +
+

In this tutorial, we learned how to use a recurrent temporal graph convolutional network to predict traffic in a spatio-temporal setting. We used the TGCN model, which consists of a graph convolutional network (GCN) and a gated recurrent unit (GRU). We then trained the model for 100 epochs on a small subset of the METR-LA dataset. The accuracy of the model is not very good, but it can be improved by training on more data.

+ + +``` + diff --git a/GraphNeuralNetworks/docs/tutorials/config.json b/tutorials/tutorials/config.json similarity index 100% rename from GraphNeuralNetworks/docs/tutorials/config.json rename to tutorials/tutorials/config.json diff --git a/GraphNeuralNetworks/docs/tutorials/index.md b/tutorials/tutorials/index.md similarity index 100% rename from GraphNeuralNetworks/docs/tutorials/index.md rename to tutorials/tutorials/index.md diff --git a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/assets/brain_gnn.gif b/tutorials/tutorials/introductory_tutorials/assets/brain_gnn.gif similarity index 100% rename from GraphNeuralNetworks/docs/tutorials/introductory_tutorials/assets/brain_gnn.gif rename to tutorials/tutorials/introductory_tutorials/assets/brain_gnn.gif diff --git a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/assets/graph_classification.gif b/tutorials/tutorials/introductory_tutorials/assets/graph_classification.gif similarity index 100% rename from GraphNeuralNetworks/docs/tutorials/introductory_tutorials/assets/graph_classification.gif rename to tutorials/tutorials/introductory_tutorials/assets/graph_classification.gif diff --git a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/assets/intro_1.png b/tutorials/tutorials/introductory_tutorials/assets/intro_1.png similarity index 100% rename from GraphNeuralNetworks/docs/tutorials/introductory_tutorials/assets/intro_1.png rename to tutorials/tutorials/introductory_tutorials/assets/intro_1.png diff --git a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/assets/node_classsification.gif b/tutorials/tutorials/introductory_tutorials/assets/node_classsification.gif similarity index 100% rename from GraphNeuralNetworks/docs/tutorials/introductory_tutorials/assets/node_classsification.gif rename to tutorials/tutorials/introductory_tutorials/assets/node_classsification.gif diff --git a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/assets/traffic.gif b/tutorials/tutorials/introductory_tutorials/assets/traffic.gif similarity index 100% rename from GraphNeuralNetworks/docs/tutorials/introductory_tutorials/assets/traffic.gif rename to tutorials/tutorials/introductory_tutorials/assets/traffic.gif diff --git a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/gnn_intro_pluto.jl b/tutorials/tutorials/introductory_tutorials/gnn_intro_pluto.jl similarity index 99% rename from GraphNeuralNetworks/docs/tutorials/introductory_tutorials/gnn_intro_pluto.jl rename to tutorials/tutorials/introductory_tutorials/gnn_intro_pluto.jl index 977f621ce..76e74ddef 100644 --- a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/gnn_intro_pluto.jl +++ b/tutorials/tutorials/introductory_tutorials/gnn_intro_pluto.jl @@ -26,6 +26,8 @@ end # ╔═╡ 03a9e023-e682-4ea3-a10b-14c4d101b291 md""" +# Hands-on introduction to Graph Neural Networks + *This Pluto notebook is a Julia adaptation of the Pytorch Geometric tutorials that can be found [here](https://pytorch-geometric.readthedocs.io/en/latest/notes/colabs.html).* Recently, deep learning on graphs has emerged to one of the hottest research fields in the deep learning community. diff --git a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/graph_classification_pluto.jl b/tutorials/tutorials/introductory_tutorials/graph_classification_pluto.jl similarity index 99% rename from GraphNeuralNetworks/docs/tutorials/introductory_tutorials/graph_classification_pluto.jl rename to tutorials/tutorials/introductory_tutorials/graph_classification_pluto.jl index 0565912dc..7e1399573 100644 --- a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/graph_classification_pluto.jl +++ b/tutorials/tutorials/introductory_tutorials/graph_classification_pluto.jl @@ -28,6 +28,8 @@ end; # ╔═╡ 15136fd8-f9b2-4841-9a95-9de7b8969687 md""" +# Graph Classification with Graph Neural Networks + *This Pluto notebook is a julia adaptation of the Pytorch Geometric tutorials that can be found [here](https://pytorch-geometric.readthedocs.io/en/latest/notes/colabs.html).* In this tutorial session we will have a closer look at how to apply **Graph Neural Networks (GNNs) to the task of graph classification**. diff --git a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/node_classification_pluto.jl b/tutorials/tutorials/introductory_tutorials/node_classification_pluto.jl similarity index 99% rename from GraphNeuralNetworks/docs/tutorials/introductory_tutorials/node_classification_pluto.jl rename to tutorials/tutorials/introductory_tutorials/node_classification_pluto.jl index edf73d4fc..23d7e13b1 100644 --- a/GraphNeuralNetworks/docs/tutorials/introductory_tutorials/node_classification_pluto.jl +++ b/tutorials/tutorials/introductory_tutorials/node_classification_pluto.jl @@ -30,6 +30,8 @@ end; # ╔═╡ ca2f0293-7eac-4d9a-9a2f-fda47fd95a99 md""" +# Node Classification with Graph Neural Networks + In this tutorial, we will be learning how to use Graph Neural Networks (GNNs) for node classification. Given the ground-truth labels of only a small subset of nodes, and want to infer the labels for all the remaining nodes (transductive learning). """ diff --git a/GraphNeuralNetworks/docs/tutorials_broken/temporal_graph_classification_pluto.jl b/tutorials/tutorials/temporalconv_tutorials/temporal_graph_classification_pluto.jl similarity index 99% rename from GraphNeuralNetworks/docs/tutorials_broken/temporal_graph_classification_pluto.jl rename to tutorials/tutorials/temporalconv_tutorials/temporal_graph_classification_pluto.jl index 6afd988c3..7a664869a 100644 --- a/GraphNeuralNetworks/docs/tutorials_broken/temporal_graph_classification_pluto.jl +++ b/tutorials/tutorials/temporalconv_tutorials/temporal_graph_classification_pluto.jl @@ -23,7 +23,10 @@ begin end # ╔═╡ 69d00ec8-da47-11ee-1bba-13a14e8a6db2 -md"In this tutorial, we will learn how to extend the graph classification task to the case of temporal graphs, i.e., graphs whose topology and features are time-varying. +md" +# Temporal Graph classification with GraphNeuralNetworks.jl + +In this tutorial, we will learn how to extend the graph classification task to the case of temporal graphs, i.e., graphs whose topology and features are time-varying. We will design and train a simple temporal graph neural network architecture to classify subjects' gender (female or male) using the temporal graphs extracted from their brain fMRI scan signals. Given the large amount of data, we will implement the training so that it can also run on the GPU. " diff --git a/GraphNeuralNetworks/docs/tutorials_broken/traffic_prediction.jl b/tutorials/tutorials/temporalconv_tutorials/traffic_prediction.jl similarity index 99% rename from GraphNeuralNetworks/docs/tutorials_broken/traffic_prediction.jl rename to tutorials/tutorials/temporalconv_tutorials/traffic_prediction.jl index 12a21155b..d259aee24 100644 --- a/GraphNeuralNetworks/docs/tutorials_broken/traffic_prediction.jl +++ b/tutorials/tutorials/temporalconv_tutorials/traffic_prediction.jl @@ -23,6 +23,8 @@ end # ╔═╡ 5fdab668-4003-11ee-33f5-3953225b0c0f md" +# Traffic Prediction using recurrent Temporal Graph Convolutional Network + In this tutorial, we will learn how to use a recurrent Temporal Graph Convolutional Network (TGCN) to predict traffic in a spatio-temporal setting. Traffic forecasting is the problem of predicting future traffic trends on a road network given historical traffic data, such as, in our case, traffic speed and time of day. " From 5c0ee4a1410400e6b452cdba29020a4924e9e49e Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 27 Sep 2024 20:22:17 +0200 Subject: [PATCH 02/36] Update --- .gitignore | 7 ++++++- GNNGraphs/docs/make.jl | 6 ------ GNNGraphs/docs/src/gnngraph.md | 2 +- GNNGraphs/docs/src/heterograph.md | 2 +- GNNGraphs/docs/src/temporalgraph.md | 2 +- GNNLux/docs/src/api/basic.md | 2 +- GNNLux/src/layers/basic.jl | 2 +- GNNlib/docs/Project.toml | 1 + GNNlib/docs/make.jl | 3 +-- GNNlib/docs/src/messagepassing.md | 2 +- GraphNeuralNetworks/docs/make.jl | 4 ++-- 11 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 91820619c..e3d549907 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,13 @@ *.swo Manifest.toml /docs/build/ +GNNGraphs/docs/build/ +GNNlib/docs/build/ +GNNLux/docs/build/ +GraphNeuralNetworks/docs/build/ +tutorials/docs/build/ .vscode LocalPreferences.toml .DS_Store docs/src/democards/gridtheme.css -test.jl \ No newline at end of file +test.jl diff --git a/GNNGraphs/docs/make.jl b/GNNGraphs/docs/make.jl index a78420c4d..bb92a5136 100644 --- a/GNNGraphs/docs/make.jl +++ b/GNNGraphs/docs/make.jl @@ -7,16 +7,10 @@ assets=[] prettyurls = get(ENV, "CI", nothing) == "true" mathengine = MathJax3() -interlinks = InterLinks( - "GraphNeuralNetworks" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/graphneuralnetworks/", joinpath(dirname(dirname(@__DIR__)), "GraphNeuralNetworks", "docs", "build", "objects.inv")), - - ) - makedocs(; modules = [GNNGraphs], doctest = false, clean = true, - plugins = [interlinks], format = Documenter.HTML(; mathengine, prettyurls, assets = assets, size_threshold=nothing), sitename = "GNNGraphs.jl", pages = ["Home" => "index.md", diff --git a/GNNGraphs/docs/src/gnngraph.md b/GNNGraphs/docs/src/gnngraph.md index fd592602f..c62e279fa 100644 --- a/GNNGraphs/docs/src/gnngraph.md +++ b/GNNGraphs/docs/src/gnngraph.md @@ -124,7 +124,7 @@ g′.e ## Edge weights It is common to denote scalar edge features as edge weights. The `GNNGraph` has specific support -for edge weights: they can be stored as part of internal representations of the graph (COO or adjacency matrix). Some graph convolutional layers, most notably the [`GCNConv`](@ref), can use the edge weights to perform weighted sums over the nodes' neighborhoods. +for edge weights: they can be stored as part of internal representations of the graph (COO or adjacency matrix). Some graph convolutional layers, most notably the `GCNConv`, can use the edge weights to perform weighted sums over the nodes' neighborhoods. ```julia julia> source = [1, 1, 2, 2, 3, 3]; diff --git a/GNNGraphs/docs/src/heterograph.md b/GNNGraphs/docs/src/heterograph.md index d42df8478..2347b5844 100644 --- a/GNNGraphs/docs/src/heterograph.md +++ b/GNNGraphs/docs/src/heterograph.md @@ -137,4 +137,4 @@ end ## Graph convolutions on heterographs -See [`HeteroGraphConv`](@ref) for how to perform convolutions on heterogeneous graphs. +See `HeteroGraphConv` for how to perform convolutions on heterogeneous graphs. diff --git a/GNNGraphs/docs/src/temporalgraph.md b/GNNGraphs/docs/src/temporalgraph.md index 7fccb5e26..02d6f6a5d 100644 --- a/GNNGraphs/docs/src/temporalgraph.md +++ b/GNNGraphs/docs/src/temporalgraph.md @@ -124,7 +124,7 @@ Vector{Matrix{Float64}} ## Graph convolutions on TemporalSnapshotsGNNGraph -A graph convolutional layer can be applied to each snapshot independently, in the next example we apply a [`GINConv`](@ref) layer to each snapshot of a `TemporalSnapshotsGNNGraph`. +A graph convolutional layer can be applied to each snapshot independently, in the next example we apply a `GINConv` layer to each snapshot of a `TemporalSnapshotsGNNGraph`. ```jldoctest julia> using GNNGraphs, Flux diff --git a/GNNLux/docs/src/api/basic.md b/GNNLux/docs/src/api/basic.md index acd7353b4..2242745d6 100644 --- a/GNNLux/docs/src/api/basic.md +++ b/GNNLux/docs/src/api/basic.md @@ -5,4 +5,4 @@ CurrentModule = GNNLux ## GNNLayer ```@docs GNNLux.GNNLayer -``` +``` \ No newline at end of file diff --git a/GNNLux/src/layers/basic.jl b/GNNLux/src/layers/basic.jl index ba12de728..6b4763459 100644 --- a/GNNLux/src/layers/basic.jl +++ b/GNNLux/src/layers/basic.jl @@ -4,7 +4,7 @@ An abstract type from which graph neural network layers are derived. It is Derived from Lux's `AbstractLuxLayer` type. -See also [`GNNChain`](@ref GNNLux.GNNChain). +See also `GNNChain`. """ abstract type GNNLayer <: AbstractLuxLayer end diff --git a/GNNlib/docs/Project.toml b/GNNlib/docs/Project.toml index 20451275d..5aa458b91 100644 --- a/GNNlib/docs/Project.toml +++ b/GNNlib/docs/Project.toml @@ -3,4 +3,5 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" GNNGraphs = "aed8fd31-079b-4b5a-b342-a13352159b8c" GNNlib = "a6a84749-d869-43f8-aacc-be26a1996e48" +Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" diff --git a/GNNlib/docs/make.jl b/GNNlib/docs/make.jl index 893d8bc8c..3720bf1e9 100644 --- a/GNNlib/docs/make.jl +++ b/GNNlib/docs/make.jl @@ -10,8 +10,7 @@ mathengine = MathJax3() interlinks = InterLinks( "NNlib" => "https://fluxml.ai/NNlib.jl/stable/", - "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnngraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv")), - "GraphNeuralNetworks" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/graphneuralnetworks/", joinpath(dirname(dirname(@__DIR__)),"GraphNeuralNetworks", "docs", "build", "objects.inv")),) + "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnngraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv"))) makedocs(; diff --git a/GNNlib/docs/src/messagepassing.md b/GNNlib/docs/src/messagepassing.md index 6705b67f7..709192319 100644 --- a/GNNlib/docs/src/messagepassing.md +++ b/GNNlib/docs/src/messagepassing.md @@ -134,7 +134,7 @@ function (l::GCN)(g::GNNGraph, x::AbstractMatrix{T}) where T end ``` -See the [`GATConv`](@ref) implementation [here](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/blob/master/src/layers/conv.jl) for a more complex example. +See the `GATConv` implementation [here](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/blob/master/src/layers/conv.jl) for a more complex example. ## Built-in message functions diff --git a/GraphNeuralNetworks/docs/make.jl b/GraphNeuralNetworks/docs/make.jl index e2329fd17..1e36cceaf 100644 --- a/GraphNeuralNetworks/docs/make.jl +++ b/GraphNeuralNetworks/docs/make.jl @@ -9,8 +9,8 @@ mathengine = MathJax3() interlinks = InterLinks( "NNlib" => "https://fluxml.ai/NNlib.jl/stable/", - "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnngraphs/", joinpath(dirname(dirname(@__DIR__)),"GraphNeuralNetworks.jl", "GNNGraphs", "docs", "build", "objects.inv")), - "GNNlib" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnnlib/", joinpath(dirname(dirname(@__DIR__)),"GraphNeuralNetworks.jl", "GNNlib", "docs", "build", "objects.inv")) + "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnngraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv")), + "GNNlib" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnnlib/", joinpath(dirname(dirname(@__DIR__)), "GNNlib", "docs", "build", "objects.inv")) ) From 3fa8da4aeea1f0bb991922826d4a5c13bd2d5f1e Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 27 Sep 2024 20:22:52 +0200 Subject: [PATCH 03/36] Actions --- .github/workflows/multidocs.yml | 74 +++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/multidocs.yml diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml new file mode 100644 index 000000000..7a3b70df9 --- /dev/null +++ b/.github/workflows/multidocs.yml @@ -0,0 +1,74 @@ +name: MultiDocumentation + +on: + push: + branches: + - pages-multidocs + tags: '*' + pull_request: + +jobs: + build_multidocs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@latest + with: + version: '1.10.4' + + # Build GNNGraphs docs + - name: Install dependencies for GNNGraphs + run: julia --project=GNNGraphs/docs/ -e 'using Pkg; Pkg.instantiate();' + - name: Build GNNGraphs docs + run: julia --project=GNNGraphs/docs/ GNNGraphs/docs/make.jl + - name: Check if objects.inv exists for GNNGraphs + run: | + if [ -f GNNGraphs/docs/build/objects.inv ]; then + echo "GNNGraphs: objects.inv exists." + else + echo "GNNGraphs: objects.inv does not exist!" && exit 1 + fi + + # Build GNNlib docs + - name: Install dependencies for GNNlib + run: julia --project=GNNlib/docs/ -e 'using Pkg; Pkg.instantiate();' + - name: Build GNNlib docs + run: julia --project=GNNlib/docs/ GNNlib/docs/make.jl + + # Build GNNLux docs + - name: Install dependencies for GNNLux + run: julia --project=GNNLux/docs/ -e ' + using Pkg; + Pkg.develop(PackageSpec(path=joinpath(pwd(), "GNNLux"))); + Pkg.instantiate();' + - name: Build GNNLux docs + run: julia --project=GNNLux/docs/ GNNLux/docs/make.jl + + # Build main docs + - name: Install dependencies for main docs + run: julia --project=GraphNeuralNetworks/docs/ -e ' + using Pkg; + Pkg.develop(PackageSpec(path=joinpath(pwd(), "GraphNeuralNetworks"))); + Pkg.instantiate();' + - name: Build main docs + run: julia --project=GraphNeuralNetworks/docs/make.jl + + # Build tutorials + - name: Install dependencies for tutorials + run: julia --project=tutorials/docs/ -e 'using Pkg; Pkg.instantiate();' + - name: Build tutorials + run: julia --project=tutorials/docs/ tutorials/docs/make.jl + + # Build and deploy multidocs + - name: Install dependencies for multidocs + run: julia --project=docs/ -e ' + using Pkg; + Pkg.develop([PackageSpec(path=pwd()), + PackageSpec(path=joinpath(pwd(), "GNNGraphs")), + PackageSpec(path=joinpath(pwd(), "GNNlib")),]); + Pkg.instantiate();' + - name: Build and deploy multidocs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} + run: julia --project=docs/ docs/make-multi.jl \ No newline at end of file From bbe07c843a8d968c0158b37aa383f073ebd26e2d Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 27 Sep 2024 20:29:17 +0200 Subject: [PATCH 04/36] Fix branch --- .github/workflows/multidocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index 7a3b70df9..6b3c9ec54 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -3,7 +3,7 @@ name: MultiDocumentation on: push: branches: - - pages-multidocs + - new-multidocs tags: '*' pull_request: From 78744f21facfd23c4374c6d09c327dd1b3dabb84 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 27 Sep 2024 20:39:09 +0200 Subject: [PATCH 05/36] Fix --- .github/workflows/multidocs.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index 6b3c9ec54..fd33d1745 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -63,9 +63,10 @@ jobs: - name: Install dependencies for multidocs run: julia --project=docs/ -e ' using Pkg; - Pkg.develop([PackageSpec(path=pwd()), - PackageSpec(path=joinpath(pwd(), "GNNGraphs")), - PackageSpec(path=joinpath(pwd(), "GNNlib")),]); + Pkg.develop([PackageSpec(path=joinpath(pwd(), "GraphNeuralNetworks")), + PackageSpec(path=joinpath(pwd(), "GNNGraphs")), + PackageSpec(path=joinpath(pwd(), "GNNlib")), + PackageSpec(path=joinpath(pwd(), "GNNLux"))]); Pkg.instantiate();' - name: Build and deploy multidocs env: From 91cf00d3991e42c458da8b186e07c27f7dea23b1 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Mon, 7 Oct 2024 07:57:31 +0200 Subject: [PATCH 06/36] Change path --- GNNlib/docs/make.jl | 2 +- GraphNeuralNetworks/docs/make.jl | 4 ++-- docs/make-multi.jl | 10 +++++----- tutorials/docs/make.jl | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/GNNlib/docs/make.jl b/GNNlib/docs/make.jl index 3720bf1e9..2b42b4c47 100644 --- a/GNNlib/docs/make.jl +++ b/GNNlib/docs/make.jl @@ -10,7 +10,7 @@ mathengine = MathJax3() interlinks = InterLinks( "NNlib" => "https://fluxml.ai/NNlib.jl/stable/", - "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnngraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv"))) + "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/GNNGraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv"))) makedocs(; diff --git a/GraphNeuralNetworks/docs/make.jl b/GraphNeuralNetworks/docs/make.jl index 1e36cceaf..69b911226 100644 --- a/GraphNeuralNetworks/docs/make.jl +++ b/GraphNeuralNetworks/docs/make.jl @@ -9,8 +9,8 @@ mathengine = MathJax3() interlinks = InterLinks( "NNlib" => "https://fluxml.ai/NNlib.jl/stable/", - "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnngraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv")), - "GNNlib" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnnlib/", joinpath(dirname(dirname(@__DIR__)), "GNNlib", "docs", "build", "objects.inv")) + "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/GNNGraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv")), + "GNNlib" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/GNNlib/", joinpath(dirname(dirname(@__DIR__)), "GNNlib", "docs", "build", "objects.inv")) ) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 546d0b3fc..a540a1c79 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -3,28 +3,28 @@ using MultiDocumenter docs = [ MultiDocumenter.MultiDocRef( upstream = joinpath(dirname(@__DIR__),"GraphNeuralNetworks", "docs", "build"), - path = "graphneuralnetworks", + path = "GraphNeuralNetworks", name = "GraphNeuralNetworks", fix_canonical_url = false), MultiDocumenter.MultiDocRef( upstream = joinpath(dirname(@__DIR__), "GNNGraphs", "docs", "build"), - path = "gnngraphs", + path = "GNNGraphs", name = "GNNGraphs", fix_canonical_url = false), MultiDocumenter.MultiDocRef( upstream = joinpath(dirname(@__DIR__), "GNNlib", "docs", "build"), - path = "gnnlib", + path = "GNNlib", name = "GNNlib", fix_canonical_url = false), MultiDocumenter.MultiDocRef( upstream = joinpath(dirname(@__DIR__), "GNNLux", "docs", "build"), - path = "gnnlux", + path = "GNNLux", name = "GNNLux", fix_canonical_url = false), MultiDocumenter.MultiDocRef( upstream = joinpath(dirname(@__DIR__), "tutorials", "docs", "build"), path = "tutorials", - name = "Tutorials", + name = "tutorials", fix_canonical_url = false), ] diff --git a/tutorials/docs/make.jl b/tutorials/docs/make.jl index b131d9816..78755a97e 100644 --- a/tutorials/docs/make.jl +++ b/tutorials/docs/make.jl @@ -7,8 +7,8 @@ mathengine = MathJax3() # interlinks = InterLinks( # "NNlib" => "https://fluxml.ai/NNlib.jl/stable/", -# "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/gnngraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv")), -# "GraphNeuralNetworks" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/graphneuralnetworks/", joinpath(dirname(dirname(@__DIR__)), "docs", "build", "objects.inv")),) +# "GNNGraphs" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/GNNGraphs/", joinpath(dirname(dirname(@__DIR__)), "GNNGraphs", "docs", "build", "objects.inv")), +# "GraphNeuralNetworks" => ("https://carlolucibello.github.io/GraphNeuralNetworks.jl/GraphNeuralNetworks/", joinpath(dirname(dirname(@__DIR__)), "docs", "build", "objects.inv")),) makedocs(; doctest = false, From 20cc1ed449e65c7c66726db44853fe4680e4bc3b Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Mon, 7 Oct 2024 08:14:24 +0200 Subject: [PATCH 07/36] Tentative --- docs/make-multi.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index a540a1c79..20e99c5b4 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -1,5 +1,16 @@ using MultiDocumenter +for (root, dirs, files) in walkdir(".") + for file in files + filepath = joinpath(root, file) + if islink(filepath) + linktarget = abspath(dirname(filepath), readlink(filepath)) + rm(filepath) + cp(linktarget, filepath; force=true) + end + end +end + docs = [ MultiDocumenter.MultiDocRef( upstream = joinpath(dirname(@__DIR__),"GraphNeuralNetworks", "docs", "build"), @@ -38,6 +49,7 @@ MultiDocumenter.make( engine = MultiDocumenter.FlexSearch ), brand_image = MultiDocumenter.BrandImage("", "logo.svg") + rootpath = "/GraphNeuralNetworks.jl/" ) cp(joinpath(@__DIR__, "logo.svg"), From ee5bbcdd978a6ec7839e8dfc6836cbde2e314838 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Mon, 7 Oct 2024 08:21:16 +0200 Subject: [PATCH 08/36] Fix --- docs/make-multi.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 20e99c5b4..458be26ea 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -48,7 +48,7 @@ MultiDocumenter.make( index_versions = ["stable"], engine = MultiDocumenter.FlexSearch ), - brand_image = MultiDocumenter.BrandImage("", "logo.svg") + brand_image = MultiDocumenter.BrandImage("", "logo.svg"), rootpath = "/GraphNeuralNetworks.jl/" ) From 558a0210f1994ca99fbc6758894a44547084ef65 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Thu, 17 Oct 2024 19:29:00 +0200 Subject: [PATCH 09/36] Some checks --- .github/workflows/multidocs.yml | 37 ++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index fd33d1745..d4693f8a1 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -34,6 +34,13 @@ jobs: run: julia --project=GNNlib/docs/ -e 'using Pkg; Pkg.instantiate();' - name: Build GNNlib docs run: julia --project=GNNlib/docs/ GNNlib/docs/make.jl + - name: Check if objects.inv exists for GNNlib + run: | + if [ -f GNNlib/docs/build/objects.inv ]; then + echo "GNNlib: objects.inv exists." + else + echo "GNNlib: objects.inv does not exist!" && exit 1 + fi # Build GNNLux docs - name: Install dependencies for GNNLux @@ -43,8 +50,15 @@ jobs: Pkg.instantiate();' - name: Build GNNLux docs run: julia --project=GNNLux/docs/ GNNLux/docs/make.jl + - name: Check if objects.inv exists for GNNLux + run: | + if [ -f GNNLux/docs/build/objects.inv ]; then + echo "GNNLux: objects.inv exists." + else + echo "GNNLux: objects.inv does not exist!" && exit 1 + fi - # Build main docs + # Build GraphNeuralNetworks docs - name: Install dependencies for main docs run: julia --project=GraphNeuralNetworks/docs/ -e ' using Pkg; @@ -52,12 +66,26 @@ jobs: Pkg.instantiate();' - name: Build main docs run: julia --project=GraphNeuralNetworks/docs/make.jl + - name: Check if objects.inv exists for GraphNeuralNetworks + run: | + if [ -f GraphNeuralNetworks/docs/build/objects.inv ]; then + echo "GraphNeuralNetworks: objects.inv exists." + else + echo "GraphNeuralNetworks: objects.inv does not exist!" && exit 1 + fi # Build tutorials - name: Install dependencies for tutorials run: julia --project=tutorials/docs/ -e 'using Pkg; Pkg.instantiate();' - name: Build tutorials run: julia --project=tutorials/docs/ tutorials/docs/make.jl + - name: Check if objects.inv exists for tutorials + run: | + if [ -f tutorials/docs/build/objects.inv ]; then + echo "tutorials: objects.inv exists." + else + echo "tutorials: objects.inv does not exist!" && exit 1 + fi # Build and deploy multidocs - name: Install dependencies for multidocs @@ -68,6 +96,13 @@ jobs: PackageSpec(path=joinpath(pwd(), "GNNlib")), PackageSpec(path=joinpath(pwd(), "GNNLux"))]); Pkg.instantiate();' + - name: Check if objects.inv exists for GraphNeuralNetworks + run: | + if [ -f GraphNeuralNetworks/docs/build/objects.inv ]; then + echo "GraphNeuralNetworks: objects.inv exists." + else + echo "GraphNeuralNetworks: objects.inv does not exist!" && exit 1 + fi - name: Build and deploy multidocs env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token From bc24a6ca01f564a6d387cf77024d4c1839624efb Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Thu, 17 Oct 2024 19:39:25 +0200 Subject: [PATCH 10/36] Add Graphs interlink --- GNNGraphs/docs/make.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GNNGraphs/docs/make.jl b/GNNGraphs/docs/make.jl index bb92a5136..9b88103b2 100644 --- a/GNNGraphs/docs/make.jl +++ b/GNNGraphs/docs/make.jl @@ -7,10 +7,14 @@ assets=[] prettyurls = get(ENV, "CI", nothing) == "true" mathengine = MathJax3() +interlinks = InterLinks( + "Graphs" => "http://juliagraphs.org/Graphs.jl/stable/") + makedocs(; modules = [GNNGraphs], doctest = false, clean = true, + plugins = [interlinks], format = Documenter.HTML(; mathengine, prettyurls, assets = assets, size_threshold=nothing), sitename = "GNNGraphs.jl", pages = ["Home" => "index.md", From 12a82d52e6b06f3476872ce900c27defe2408356 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Thu, 17 Oct 2024 19:50:49 +0200 Subject: [PATCH 11/36] FIx --- GNNGraphs/docs/src/api/gnngraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNNGraphs/docs/src/api/gnngraph.md b/GNNGraphs/docs/src/api/gnngraph.md index 088d059a4..c7cf97d23 100644 --- a/GNNGraphs/docs/src/api/gnngraph.md +++ b/GNNGraphs/docs/src/api/gnngraph.md @@ -90,5 +90,5 @@ Private = false ``` ```@docs -Graphs.induced_subgraph(::GNNGraph, ::Vector{Int}) +induced_subgraph(::GNNGraph, ::Vector{Int}) ``` \ No newline at end of file From 4a02343d03a4d8ebac20b1cd5cc73a0af5c66445 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Thu, 17 Oct 2024 20:01:21 +0200 Subject: [PATCH 12/36] ok --- GNNGraphs/docs/make.jl | 3 --- GNNGraphs/docs/src/api/gnngraph.md | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/GNNGraphs/docs/make.jl b/GNNGraphs/docs/make.jl index 9b88103b2..5f9f82d42 100644 --- a/GNNGraphs/docs/make.jl +++ b/GNNGraphs/docs/make.jl @@ -7,14 +7,11 @@ assets=[] prettyurls = get(ENV, "CI", nothing) == "true" mathengine = MathJax3() -interlinks = InterLinks( - "Graphs" => "http://juliagraphs.org/Graphs.jl/stable/") makedocs(; modules = [GNNGraphs], doctest = false, clean = true, - plugins = [interlinks], format = Documenter.HTML(; mathengine, prettyurls, assets = assets, size_threshold=nothing), sitename = "GNNGraphs.jl", pages = ["Home" => "index.md", diff --git a/GNNGraphs/docs/src/api/gnngraph.md b/GNNGraphs/docs/src/api/gnngraph.md index c7cf97d23..2d64a177b 100644 --- a/GNNGraphs/docs/src/api/gnngraph.md +++ b/GNNGraphs/docs/src/api/gnngraph.md @@ -90,5 +90,5 @@ Private = false ``` ```@docs -induced_subgraph(::GNNGraph, ::Vector{Int}) +Graphs.induced_subgraph ``` \ No newline at end of file From 2cb45fd688762b80bc5058221e29c90e892611ab Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Thu, 17 Oct 2024 20:08:57 +0200 Subject: [PATCH 13/36] fix? --- GNNGraphs/docs/make.jl | 3 ++- GNNGraphs/docs/src/api/gnngraph.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GNNGraphs/docs/make.jl b/GNNGraphs/docs/make.jl index 5f9f82d42..7992df83d 100644 --- a/GNNGraphs/docs/make.jl +++ b/GNNGraphs/docs/make.jl @@ -1,7 +1,8 @@ using Documenter using DocumenterInterLinks using GNNGraphs -using Graphs +import Graphs +using Graphs: induced_subgraph assets=[] prettyurls = get(ENV, "CI", nothing) == "true" diff --git a/GNNGraphs/docs/src/api/gnngraph.md b/GNNGraphs/docs/src/api/gnngraph.md index 2d64a177b..088d059a4 100644 --- a/GNNGraphs/docs/src/api/gnngraph.md +++ b/GNNGraphs/docs/src/api/gnngraph.md @@ -90,5 +90,5 @@ Private = false ``` ```@docs -Graphs.induced_subgraph +Graphs.induced_subgraph(::GNNGraph, ::Vector{Int}) ``` \ No newline at end of file From ff06669f91a2270cc63c3946134f33ccec514cec Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Thu, 17 Oct 2024 20:13:17 +0200 Subject: [PATCH 14/36] update julia version --- .github/workflows/multidocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index d4693f8a1..38e071ec0 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: - version: '1.10.4' + version: '1.10.5' # Build GNNGraphs docs - name: Install dependencies for GNNGraphs From bba52fc167b488fe33441371250b98c7ad42ef27 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 25 Oct 2024 13:31:59 +0200 Subject: [PATCH 15/36] Change a bit --- .github/workflows/multidocs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index 38e071ec0..75c985e86 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -12,9 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@latest + - uses: julia-actions/setup-julia@v2 with: version: '1.10.5' + - uses: julia-actions/cache@v2 # Build GNNGraphs docs - name: Install dependencies for GNNGraphs From 0109ff67111f86bdab3aae74843f7c2da009fdcb Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 25 Oct 2024 13:38:06 +0200 Subject: [PATCH 16/36] dev GNNGraphs --- .github/workflows/multidocs.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index 75c985e86..fddd0fe03 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -19,7 +19,10 @@ jobs: # Build GNNGraphs docs - name: Install dependencies for GNNGraphs - run: julia --project=GNNGraphs/docs/ -e 'using Pkg; Pkg.instantiate();' + run: julia --project=docs/ -e ' + using Pkg; + Pkg.develop(PackageSpec(path=joinpath(pwd(), "GNNGraphs"))); + Pkg.instantiate();' - name: Build GNNGraphs docs run: julia --project=GNNGraphs/docs/ GNNGraphs/docs/make.jl - name: Check if objects.inv exists for GNNGraphs From df8ab80961f6cb1d6512217808917e97d2f80fb7 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 25 Oct 2024 13:40:37 +0200 Subject: [PATCH 17/36] Fix path --- .github/workflows/multidocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index fddd0fe03..9c134de7e 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -19,7 +19,7 @@ jobs: # Build GNNGraphs docs - name: Install dependencies for GNNGraphs - run: julia --project=docs/ -e ' + run: julia --project=GNNGraphs/docs/ -e ' using Pkg; Pkg.develop(PackageSpec(path=joinpath(pwd(), "GNNGraphs"))); Pkg.instantiate();' From c7e9742fa890ed9bae3cececb2b67696d646b6e3 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 25 Oct 2024 13:54:16 +0200 Subject: [PATCH 18/36] Test --- .github/workflows/multidocs.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index 9c134de7e..5388782dc 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -45,6 +45,13 @@ jobs: else echo "GNNlib: objects.inv does not exist!" && exit 1 fi + - name: Check if objects.inv still exists for GNNGraphs + run: | + if [ -f GNNGraphs/docs/build/objects.inv ]; then + echo "GNNGraphs: objects.inv exists." + else + echo "GNNGraphs: objects.inv does not exist!" && exit 1 + fi # Build GNNLux docs - name: Install dependencies for GNNLux From a91166157886a6cff06126f4438e3bf5e14c212a Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 25 Oct 2024 13:59:37 +0200 Subject: [PATCH 19/36] Add envs --- .github/workflows/multidocs.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index 5388782dc..75fc48f00 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -24,6 +24,9 @@ jobs: Pkg.develop(PackageSpec(path=joinpath(pwd(), "GNNGraphs"))); Pkg.instantiate();' - name: Build GNNGraphs docs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} run: julia --project=GNNGraphs/docs/ GNNGraphs/docs/make.jl - name: Check if objects.inv exists for GNNGraphs run: | @@ -37,6 +40,9 @@ jobs: - name: Install dependencies for GNNlib run: julia --project=GNNlib/docs/ -e 'using Pkg; Pkg.instantiate();' - name: Build GNNlib docs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} run: julia --project=GNNlib/docs/ GNNlib/docs/make.jl - name: Check if objects.inv exists for GNNlib run: | @@ -60,6 +66,9 @@ jobs: Pkg.develop(PackageSpec(path=joinpath(pwd(), "GNNLux"))); Pkg.instantiate();' - name: Build GNNLux docs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} run: julia --project=GNNLux/docs/ GNNLux/docs/make.jl - name: Check if objects.inv exists for GNNLux run: | @@ -76,6 +85,9 @@ jobs: Pkg.develop(PackageSpec(path=joinpath(pwd(), "GraphNeuralNetworks"))); Pkg.instantiate();' - name: Build main docs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} run: julia --project=GraphNeuralNetworks/docs/make.jl - name: Check if objects.inv exists for GraphNeuralNetworks run: | @@ -89,6 +101,9 @@ jobs: - name: Install dependencies for tutorials run: julia --project=tutorials/docs/ -e 'using Pkg; Pkg.instantiate();' - name: Build tutorials + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} run: julia --project=tutorials/docs/ tutorials/docs/make.jl - name: Check if objects.inv exists for tutorials run: | From 3247938e156e77d79443d620562a516288c99d38 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Sat, 26 Oct 2024 13:10:55 +0200 Subject: [PATCH 20/36] Rewrite GraphNeuralNetwork part --- .github/workflows/multidocs.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index 75fc48f00..ea1c3c7f0 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -78,6 +78,25 @@ jobs: echo "GNNLux: objects.inv does not exist!" && exit 1 fi + # Build GraphNeuralNetworks docs + - name: Install dependencies for GraphNeuralNetworks + run: julia --project=GraphNeuralNetworks/docs/ -e ' + using Pkg; + Pkg.develop(PackageSpec(path=joinpath(pwd(), "GraphNeuralNetworks"))); + Pkg.instantiate();' + - name: Build GraphNeuralNetworks docs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} + run: julia --project=GraphNeuralNetworks/docs/ GraphNeuralNetworks/docs/make.jl + - name: Check if objects.inv exists for GraphNeuralNetworks + run: | + if [ -f GraphNeuralNetworks/docs/build/objects.inv ]; then + echo "GraphNeuralNetworks: objects.inv exists." + else + echo "GraphNeuralNetworks: objects.inv does not exist!" && exit 1 + fi + # Build GraphNeuralNetworks docs - name: Install dependencies for main docs run: julia --project=GraphNeuralNetworks/docs/ -e ' From 9fffe19777198cf60112f660747b22870ca50db7 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Sat, 26 Oct 2024 13:26:51 +0200 Subject: [PATCH 21/36] Fix typo --- GNNGraphs/docs/src/temporalgraph.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNNGraphs/docs/src/temporalgraph.md b/GNNGraphs/docs/src/temporalgraph.md index 7373ce0d6..560cfa8d6 100644 --- a/GNNGraphs/docs/src/temporalgraph.md +++ b/GNNGraphs/docs/src/temporalgraph.md @@ -91,7 +91,7 @@ GNNGraph: ``` ## Data Features -A temporal graph can stode global feautre for the entire time series in the `tgdata` filed. +A temporal graph can store global feature for the entire time series in the `tgdata` filed. Also, each snapshot can store node, edge, and graph features in the `ndata`, `edata`, and `gdata` fields, respectively. ```jldoctest From daa4fb82909085174fc2c1ef7bbf35a03fd90fda Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Sat, 26 Oct 2024 13:27:17 +0200 Subject: [PATCH 22/36] Remove old docs --- .github/workflows/docs.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index ec69b388d..000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Documentation - -on: - push: - branches: - - master - tags: '*' - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@latest - with: - version: '1.10.4' - - name: Install dependencies - run: julia --project=GraphNeuralNetworks/docs/ -e ' - using Pkg; - Pkg.develop([PackageSpec(path=joinpath(pwd(), "GraphNeuralNetworks")), - PackageSpec(path=joinpath(pwd(), "GNNGraphs")), - PackageSpec(path=joinpath(pwd(), "GNNlib"))]); - Pkg.instantiate();' - - name: Build and deploy - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} - run: julia --project=GraphNeuralNetworks/docs/ GraphNeuralNetworks/docs/make.jl From e491e19831e0bac5d51d3463560b59ba5cc37427 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Sat, 26 Oct 2024 15:34:23 +0200 Subject: [PATCH 23/36] Test branch docs --- docs/make-multi.jl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 458be26ea..c69927daa 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -54,3 +54,25 @@ MultiDocumenter.make( cp(joinpath(@__DIR__, "logo.svg"), joinpath(outpath, "logo.svg")) + +#test +outbranch = "test" +has_outbranch = true + +if !success(`git checkout --orphan $outbranch`) + has_outbranch = false + @info "Creating orphaned branch $outbranch" + if !success(`git switch --orphan $outbranch`) + @error "Cannot create new orphaned branch $outbranch." + exit(1) + end +end + +run(`git add --all`) + +if success(`git commit -m 'Aggregate documentation'`) + @info "Pushing updated documentation." + run(`git push origin --force $outbranch`) +else + @info "No changes to aggregated documentation." +end \ No newline at end of file From 7a17d648777b7df80a27cb856c4291f2f78cdf66 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Sat, 26 Oct 2024 15:51:16 +0200 Subject: [PATCH 24/36] Other test --- docs/make-multi.jl | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index c69927daa..8af990edd 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -55,24 +55,35 @@ MultiDocumenter.make( cp(joinpath(@__DIR__, "logo.svg"), joinpath(outpath, "logo.svg")) -#test + +@warn "Deploying to GitHub" +gitroot = normpath(joinpath(@__DIR__, "..")) +run(`git pull`) outbranch = "test" has_outbranch = true - -if !success(`git checkout --orphan $outbranch`) +if !success(`git checkout $outbranch`) has_outbranch = false - @info "Creating orphaned branch $outbranch" if !success(`git switch --orphan $outbranch`) @error "Cannot create new orphaned branch $outbranch." exit(1) end end - -run(`git add --all`) - +for file in readdir(gitroot; join = true) + endswith(file, ".git") && continue + rm(file; force = true, recursive = true) +end +for file in readdir(outpath) + cp(joinpath(outpath, file), joinpath(gitroot, file)) +end +run(`git add .`) if success(`git commit -m 'Aggregate documentation'`) @info "Pushing updated documentation." - run(`git push origin --force $outbranch`) + if has_outbranch + run(`git push`) + else + run(`git push -u origin $outbranch`) + end + run(`git checkout main`) else @info "No changes to aggregated documentation." -end \ No newline at end of file +end From a255ab0051767f9cc4994fc046d7ef9381748ce5 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Sat, 26 Oct 2024 15:56:18 +0200 Subject: [PATCH 25/36] Another test --- docs/make-multi.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 8af990edd..512760519 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -58,8 +58,9 @@ cp(joinpath(@__DIR__, "logo.svg"), @warn "Deploying to GitHub" gitroot = normpath(joinpath(@__DIR__, "..")) +@info "Root directory: $gitroot" run(`git pull`) -outbranch = "test" +outbranch = "test-branch" has_outbranch = true if !success(`git checkout $outbranch`) has_outbranch = false From 82dc087d5fc09eb2c8b25d530901557b47ce70d2 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Sat, 26 Oct 2024 16:02:14 +0200 Subject: [PATCH 26/36] Test --- docs/make-multi.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 512760519..286d849fa 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -58,7 +58,7 @@ cp(joinpath(@__DIR__, "logo.svg"), @warn "Deploying to GitHub" gitroot = normpath(joinpath(@__DIR__, "..")) -@info "Root directory: $gitroot" + run(`git pull`) outbranch = "test-branch" has_outbranch = true From 4130140785f02164f5b92af8e8c8475ca38c59e7 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 1 Nov 2024 15:03:12 +0100 Subject: [PATCH 27/36] New test --- .github/workflows/multidocs.yml | 2 +- docs/make-multi.jl | 27 ++++++++++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index ea1c3c7f0..9d90f9b3c 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -152,4 +152,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} - run: julia --project=docs/ docs/make-multi.jl \ No newline at end of file + run: julia --project=docs/ docs/make-multi.jl deploy \ No newline at end of file diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 286d849fa..c90caeef9 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -1,39 +1,36 @@ using MultiDocumenter -for (root, dirs, files) in walkdir(".") - for file in files - filepath = joinpath(root, file) - if islink(filepath) - linktarget = abspath(dirname(filepath), readlink(filepath)) - rm(filepath) - cp(linktarget, filepath; force=true) - end - end -end +clonedir = ("--temp" in ARGS) ? mktempdir() : joinpath(@__DIR__, "clones") +outpath = mktempdir() +@info """ +Cloning packages into: $(clonedir) +Building aggregate site into: $(outpath) +""" + docs = [ MultiDocumenter.MultiDocRef( - upstream = joinpath(dirname(@__DIR__),"GraphNeuralNetworks", "docs", "build"), + upstream = joinpath(clonedir,"GraphNeuralNetworks", "docs", "build"), path = "GraphNeuralNetworks", name = "GraphNeuralNetworks", fix_canonical_url = false), MultiDocumenter.MultiDocRef( - upstream = joinpath(dirname(@__DIR__), "GNNGraphs", "docs", "build"), + upstream = joinpath(clonedir, "GNNGraphs", "docs", "build"), path = "GNNGraphs", name = "GNNGraphs", fix_canonical_url = false), MultiDocumenter.MultiDocRef( - upstream = joinpath(dirname(@__DIR__), "GNNlib", "docs", "build"), + upstream = joinpath(clonedir, "GNNlib", "docs", "build"), path = "GNNlib", name = "GNNlib", fix_canonical_url = false), MultiDocumenter.MultiDocRef( - upstream = joinpath(dirname(@__DIR__), "GNNLux", "docs", "build"), + upstream = joinpath(clonedir, "GNNLux", "docs", "build"), path = "GNNLux", name = "GNNLux", fix_canonical_url = false), MultiDocumenter.MultiDocRef( - upstream = joinpath(dirname(@__DIR__), "tutorials", "docs", "build"), + upstream = joinpath(clonedir, "tutorials", "docs", "build"), path = "tutorials", name = "tutorials", fix_canonical_url = false), From 06e8a8cbbbf5b4c22896b52329253e22b65eeeb9 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 1 Nov 2024 15:13:59 +0100 Subject: [PATCH 28/36] Back to prevous path --- docs/make-multi.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index c90caeef9..af55e6fcf 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -1,5 +1,6 @@ using MultiDocumenter + clonedir = ("--temp" in ARGS) ? mktempdir() : joinpath(@__DIR__, "clones") outpath = mktempdir() @info """ @@ -10,27 +11,27 @@ Building aggregate site into: $(outpath) docs = [ MultiDocumenter.MultiDocRef( - upstream = joinpath(clonedir,"GraphNeuralNetworks", "docs", "build"), + upstream = joinpath(dirname(@__DIR__),"GraphNeuralNetworks", "docs", "build"), path = "GraphNeuralNetworks", name = "GraphNeuralNetworks", fix_canonical_url = false), MultiDocumenter.MultiDocRef( - upstream = joinpath(clonedir, "GNNGraphs", "docs", "build"), + upstream = joinpath(dirname(@__DIR__), "GNNGraphs", "docs", "build"), path = "GNNGraphs", name = "GNNGraphs", fix_canonical_url = false), MultiDocumenter.MultiDocRef( - upstream = joinpath(clonedir, "GNNlib", "docs", "build"), + upstream = joinpath(dirname(@__DIR__), "GNNlib", "docs", "build"), path = "GNNlib", name = "GNNlib", fix_canonical_url = false), MultiDocumenter.MultiDocRef( - upstream = joinpath(clonedir, "GNNLux", "docs", "build"), + upstream = joinpath(dirname(@__DIR__), "GNNLux", "docs", "build"), path = "GNNLux", name = "GNNLux", fix_canonical_url = false), MultiDocumenter.MultiDocRef( - upstream = joinpath(clonedir, "tutorials", "docs", "build"), + upstream = joinpath(dirname(@__DIR__), "tutorials", "docs", "build"), path = "tutorials", name = "tutorials", fix_canonical_url = false), From 7d437ca60c74df8a921c1deb059ba3457100487b Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 1 Nov 2024 16:18:17 +0100 Subject: [PATCH 29/36] Back to git error --- .github/workflows/multidocs.yml | 2 +- docs/make-multi.jl | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/multidocs.yml b/.github/workflows/multidocs.yml index 9d90f9b3c..3b90e2567 100644 --- a/.github/workflows/multidocs.yml +++ b/.github/workflows/multidocs.yml @@ -152,4 +152,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} - run: julia --project=docs/ docs/make-multi.jl deploy \ No newline at end of file + run: julia --project=docs/ docs/make-multi.jl \ No newline at end of file diff --git a/docs/make-multi.jl b/docs/make-multi.jl index af55e6fcf..286d849fa 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -1,13 +1,15 @@ using MultiDocumenter - -clonedir = ("--temp" in ARGS) ? mktempdir() : joinpath(@__DIR__, "clones") -outpath = mktempdir() -@info """ -Cloning packages into: $(clonedir) -Building aggregate site into: $(outpath) -""" - +for (root, dirs, files) in walkdir(".") + for file in files + filepath = joinpath(root, file) + if islink(filepath) + linktarget = abspath(dirname(filepath), readlink(filepath)) + rm(filepath) + cp(linktarget, filepath; force=true) + end + end +end docs = [ MultiDocumenter.MultiDocRef( From 742306f1ecf521aec24f4139d13af1bfc7e438f4 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 1 Nov 2024 17:48:46 +0100 Subject: [PATCH 30/36] As Datatoolkit --- docs/make-multi.jl | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 286d849fa..975441443 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -56,35 +56,24 @@ cp(joinpath(@__DIR__, "logo.svg"), joinpath(outpath, "logo.svg")) -@warn "Deploying to GitHub" -gitroot = normpath(joinpath(@__DIR__, "..")) - -run(`git pull`) -outbranch = "test-branch" +@warn "Deploying to GitHub as in DataToolkit" +outbranch = "multidoc-pub" has_outbranch = true -if !success(`git checkout $outbranch`) + +if !success(`git checkout --orphan $outbranch`) has_outbranch = false + @info "Creating orphaned branch $outbranch" if !success(`git switch --orphan $outbranch`) @error "Cannot create new orphaned branch $outbranch." exit(1) end end -for file in readdir(gitroot; join = true) - endswith(file, ".git") && continue - rm(file; force = true, recursive = true) -end -for file in readdir(outpath) - cp(joinpath(outpath, file), joinpath(gitroot, file)) -end -run(`git add .`) + +run(`git add --all`) + if success(`git commit -m 'Aggregate documentation'`) @info "Pushing updated documentation." - if has_outbranch - run(`git push`) - else - run(`git push -u origin $outbranch`) - end - run(`git checkout main`) + run(`git push origin --force $outbranch`) else @info "No changes to aggregated documentation." -end +end \ No newline at end of file From fabc2c623257aaeca9457e5361e3d26d2ad39eb5 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 1 Nov 2024 17:49:14 +0100 Subject: [PATCH 31/36] Change name --- docs/make-multi.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 975441443..5f177c5fb 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -57,7 +57,7 @@ cp(joinpath(@__DIR__, "logo.svg"), @warn "Deploying to GitHub as in DataToolkit" -outbranch = "multidoc-pub" +outbranch = "test-branch" has_outbranch = true if !success(`git checkout --orphan $outbranch`) From b40fdcf9beadb2a39a06166ddebf872fae7382ae Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 1 Nov 2024 17:58:36 +0100 Subject: [PATCH 32/36] remove --- docs/make-multi.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 5f177c5fb..bc0c293e2 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -1,15 +1,15 @@ using MultiDocumenter -for (root, dirs, files) in walkdir(".") - for file in files - filepath = joinpath(root, file) - if islink(filepath) - linktarget = abspath(dirname(filepath), readlink(filepath)) - rm(filepath) - cp(linktarget, filepath; force=true) - end - end -end +# for (root, dirs, files) in walkdir(".") +# for file in files +# filepath = joinpath(root, file) +# if islink(filepath) +# linktarget = abspath(dirname(filepath), readlink(filepath)) +# rm(filepath) +# cp(linktarget, filepath; force=true) +# end +# end +# end docs = [ MultiDocumenter.MultiDocRef( From f7a6df1495f58e52d3d84f8c501ec42c3aa1cde3 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 1 Nov 2024 18:47:08 +0100 Subject: [PATCH 33/36] Locally works --- GraphNeuralNetworks/docs/src/home.md | 2 +- GraphNeuralNetworks/docs/src/models.md | 6 +++--- docs/make-multi.jl | 29 +++++++++++++------------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/GraphNeuralNetworks/docs/src/home.md b/GraphNeuralNetworks/docs/src/home.md index 41cbfb9dc..61507d2c3 100644 --- a/GraphNeuralNetworks/docs/src/home.md +++ b/GraphNeuralNetworks/docs/src/home.md @@ -41,7 +41,7 @@ end ### Model building -We concisely define our model as a [`GNNChain`](@ref) containing two graph convolutional layers. If CUDA is available, our model will live on the gpu. +We concisely define our model as a [`GraphNeuralNetworks.GNNChain`](@ref) containing two graph convolutional layers. If CUDA is available, our model will live on the gpu. ```julia device = CUDA.functional() ? Flux.gpu : Flux.cpu; diff --git a/GraphNeuralNetworks/docs/src/models.md b/GraphNeuralNetworks/docs/src/models.md index 96e49055a..672a7e9f8 100644 --- a/GraphNeuralNetworks/docs/src/models.md +++ b/GraphNeuralNetworks/docs/src/models.md @@ -6,7 +6,7 @@ their models. In what follows, we discuss two different styles for model creation: the *explicit modeling* style, more verbose but more flexible, -and the *implicit modeling* style based on [`GNNChain`](@ref), more concise but less flexible. +and the *implicit modeling* style based on [`GraphNeuralNetworks.GNNChain`](@ref), more concise but less flexible. ## Explicit modeling @@ -62,11 +62,11 @@ grad = gradient(model -> sum(model(g, X)), model) ## Implicit modeling with GNNChains While very flexible, the way in which we defined `GNN` model definition in last section is a bit verbose. -In order to simplify things, we provide the [`GNNChain`](@ref) type. It is very similar +In order to simplify things, we provide the [`GraphNeuralNetworks.GNNChain`](@ref) type. It is very similar to Flux's well known `Chain`. It allows to compose layers in a sequential fashion as Chain does, propagating the output of each layer to the next one. In addition, `GNNChain` handles propagates the input graph as well, providing it as a first argument -to layers subtyping the [`GNNLayer`](@ref) abstract type. +to layers subtyping the [`GraphNeuralNetworks.GNNLayer`](@ref) abstract type. Using `GNNChain`, the previous example becomes diff --git a/docs/make-multi.jl b/docs/make-multi.jl index bc0c293e2..7c0af8965 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -1,35 +1,35 @@ using MultiDocumenter -# for (root, dirs, files) in walkdir(".") -# for file in files -# filepath = joinpath(root, file) -# if islink(filepath) -# linktarget = abspath(dirname(filepath), readlink(filepath)) -# rm(filepath) -# cp(linktarget, filepath; force=true) -# end -# end -# end +for (root, dirs, files) in walkdir(".") + for file in files + filepath = joinpath(root, file) + if islink(filepath) + linktarget = abspath(dirname(filepath), readlink(filepath)) + rm(filepath) + cp(linktarget, filepath; force=true) + end + end +end docs = [ MultiDocumenter.MultiDocRef( upstream = joinpath(dirname(@__DIR__),"GraphNeuralNetworks", "docs", "build"), - path = "GraphNeuralNetworks", + path = "graphneuralnetworks", name = "GraphNeuralNetworks", fix_canonical_url = false), MultiDocumenter.MultiDocRef( upstream = joinpath(dirname(@__DIR__), "GNNGraphs", "docs", "build"), - path = "GNNGraphs", + path = "gnngraphs", name = "GNNGraphs", fix_canonical_url = false), MultiDocumenter.MultiDocRef( upstream = joinpath(dirname(@__DIR__), "GNNlib", "docs", "build"), - path = "GNNlib", + path = "gnnlib", name = "GNNlib", fix_canonical_url = false), MultiDocumenter.MultiDocRef( upstream = joinpath(dirname(@__DIR__), "GNNLux", "docs", "build"), - path = "GNNLux", + path = "gnnlux", name = "GNNLux", fix_canonical_url = false), MultiDocumenter.MultiDocRef( @@ -55,7 +55,6 @@ MultiDocumenter.make( cp(joinpath(@__DIR__, "logo.svg"), joinpath(outpath, "logo.svg")) - @warn "Deploying to GitHub as in DataToolkit" outbranch = "test-branch" has_outbranch = true From 4ec14112207274618c861750c88bbdaca9d3f883 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 1 Nov 2024 19:48:01 +0100 Subject: [PATCH 34/36] Test push in branch --- docs/make-multi.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 7c0af8965..85d33b327 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -56,7 +56,7 @@ cp(joinpath(@__DIR__, "logo.svg"), joinpath(outpath, "logo.svg")) @warn "Deploying to GitHub as in DataToolkit" -outbranch = "test-branch" +outbranch = "branch-multidoc" has_outbranch = true if !success(`git checkout --orphan $outbranch`) From d40a3d40dd0bd08dcdbbafb1e69228369de5e33d Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Fri, 1 Nov 2024 19:58:13 +0100 Subject: [PATCH 35/36] Add check --- docs/make-multi.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/make-multi.jl b/docs/make-multi.jl index 85d33b327..c142be942 100644 --- a/docs/make-multi.jl +++ b/docs/make-multi.jl @@ -66,6 +66,8 @@ if !success(`git checkout --orphan $outbranch`) @error "Cannot create new orphaned branch $outbranch." exit(1) end +else + @info "Switched to orphaned branch $outbranch" end run(`git add --all`) From 22f032ee0bfb51ac808bdb1c7d11e9a73e7df9a9 Mon Sep 17 00:00:00 2001 From: Aurora Rossi Date: Sun, 3 Nov 2024 22:06:34 +0100 Subject: [PATCH 36/36] Change repo --- GNNGraphs/docs/make.jl | 2 +- GNNGraphs/docs/src/datasets.md | 2 +- GNNLux/docs/make.jl | 2 +- GNNlib/docs/make.jl | 2 +- GraphNeuralNetworks/docs/make.jl | 2 +- GraphNeuralNetworks/docs/src/home.md | 6 +++--- tutorials/docs/make.jl | 2 +- tutorials/docs/src/index.md | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/GNNGraphs/docs/make.jl b/GNNGraphs/docs/make.jl index 7992df83d..06b870bb1 100644 --- a/GNNGraphs/docs/make.jl +++ b/GNNGraphs/docs/make.jl @@ -29,5 +29,5 @@ makedocs(; -deploydocs(;repo = "https://github.com/CarloLucibello/GraphNeuralNetworks.jl.git", +deploydocs(;repo = "https://github.com/JuliaGraphs/GraphNeuralNetworks.jl.git", dirname = "GNNGraphs") \ No newline at end of file diff --git a/GNNGraphs/docs/src/datasets.md b/GNNGraphs/docs/src/datasets.md index 01433a333..60477d95e 100644 --- a/GNNGraphs/docs/src/datasets.md +++ b/GNNGraphs/docs/src/datasets.md @@ -1,6 +1,6 @@ # Datasets -GNNGraphs.jl doesn't come with its own datasets, but leverages those available in the Julia (and non-Julia) ecosystem. In particular, the [examples in the GraphNeuralNetworks.jl repository](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/tree/master/examples) make use of the [MLDatasets.jl](https://github.com/JuliaML/MLDatasets.jl) package. There you will find common graph datasets such as Cora, PubMed, Citeseer, TUDataset and [many others](https://juliaml.github.io/MLDatasets.jl/dev/datasets/graphs/). +GNNGraphs.jl doesn't come with its own datasets, but leverages those available in the Julia (and non-Julia) ecosystem. In particular, the [examples in the GraphNeuralNetworks.jl repository](https://github.com/JuliaGraphs/GraphNeuralNetworks.jl/tree/master/examples) make use of the [MLDatasets.jl](https://github.com/JuliaML/MLDatasets.jl) package. There you will find common graph datasets such as Cora, PubMed, Citeseer, TUDataset and [many others](https://juliaml.github.io/MLDatasets.jl/dev/datasets/graphs/). For graphs with static structures and temporal features, datasets such as METRLA, PEMSBAY, ChickenPox, and WindMillEnergy are available. For graphs featuring both temporal structures and temporal features, the TemporalBrains dataset is suitable. GraphNeuralNetworks.jl provides the [`mldataset2gnngraph`](@ref) method for interfacing with MLDatasets.jl. diff --git a/GNNLux/docs/make.jl b/GNNLux/docs/make.jl index 5bd9dad10..ce1fa551d 100644 --- a/GNNLux/docs/make.jl +++ b/GNNLux/docs/make.jl @@ -22,5 +22,5 @@ makedocs(; -deploydocs(;repo = "https://github.com/CarloLucibello/GraphNeuralNetworks.jl.git", +deploydocs(;repo = "https://github.com/JuliaGraphs/GraphNeuralNetworks.jl.git", dirname = "GNNLux") \ No newline at end of file diff --git a/GNNlib/docs/make.jl b/GNNlib/docs/make.jl index 2b42b4c47..b32bd824b 100644 --- a/GNNlib/docs/make.jl +++ b/GNNlib/docs/make.jl @@ -36,5 +36,5 @@ makedocs(; -deploydocs(;repo = "https://github.com/CarloLucibello/GraphNeuralNetworks.jl.git", +deploydocs(;repo = "https://github.com/JuliaGraphs/GraphNeuralNetworks.jl.git", dirname = "GNNlib") \ No newline at end of file diff --git a/GraphNeuralNetworks/docs/make.jl b/GraphNeuralNetworks/docs/make.jl index eb84c3dbd..6f07c5031 100644 --- a/GraphNeuralNetworks/docs/make.jl +++ b/GraphNeuralNetworks/docs/make.jl @@ -48,4 +48,4 @@ makedocs(; -deploydocs(;repo = "https://github.com/CarloLucibello/GraphNeuralNetworks.jl.git", dirname= "GraphNeuralNetworks") +deploydocs(;repo = "https://github.com/JuliaGraphs/GraphNeuralNetworks.jl.git", dirname= "GraphNeuralNetworks") diff --git a/GraphNeuralNetworks/docs/src/home.md b/GraphNeuralNetworks/docs/src/home.md index 61507d2c3..2ccebefd0 100644 --- a/GraphNeuralNetworks/docs/src/home.md +++ b/GraphNeuralNetworks/docs/src/home.md @@ -1,6 +1,6 @@ # GraphNeuralNetworks -This is the documentation page for [GraphNeuralNetworks.jl](https://github.com/CarloLucibello/GraphNeuralNetworks.jl), a graph neural network library written in Julia and based on the deep learning framework [Flux.jl](https://github.com/FluxML/Flux.jl). +This is the documentation page for [GraphNeuralNetworks.jl](https://github.com/JuliaGraphs/GraphNeuralNetworks.jl), a graph neural network library written in Julia and based on the deep learning framework [Flux.jl](https://github.com/FluxML/Flux.jl). GraphNeuralNetworks.jl is largely inspired by [PyTorch Geometric](https://pytorch-geometric.readthedocs.io/en/latest/), [Deep Graph Library](https://docs.dgl.ai/), and [GeometricFlux.jl](https://fluxml.ai/GeometricFlux.jl/stable/). @@ -11,7 +11,7 @@ Among its features: * Easy to define custom layers. * CUDA support. * Integration with [Graphs.jl](https://github.com/JuliaGraphs/Graphs.jl). -* [Examples](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/tree/master/examples) of node, edge, and graph level machine learning tasks. +* [Examples](https://github.com/JuliaGraphs/GraphNeuralNetworks.jl/tree/master/examples) of node, edge, and graph level machine learning tasks. ## Package overview @@ -19,7 +19,7 @@ Among its features: Let's give a brief overview of the package by solving a graph regression problem with synthetic data. -Usage examples on real datasets can be found in the [examples](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/tree/master/examples) folder. +Usage examples on real datasets can be found in the [examples](https://github.com/JuliaGraphs/GraphNeuralNetworks.jl/tree/master/examples) folder. ### Data preparation diff --git a/tutorials/docs/make.jl b/tutorials/docs/make.jl index 78755a97e..41a9e20dc 100644 --- a/tutorials/docs/make.jl +++ b/tutorials/docs/make.jl @@ -30,5 +30,5 @@ makedocs(; -deploydocs(; repo = "https://github.com/CarloLucibello/GraphNeuralNetworks.jl.git", +deploydocs(; repo = "https://github.com/JuliaGraphs/GraphNeuralNetworks.jl.git", dirname = "tutorials") \ No newline at end of file diff --git a/tutorials/docs/src/index.md b/tutorials/docs/src/index.md index 8e21e6db8..af1b57997 100644 --- a/tutorials/docs/src/index.md +++ b/tutorials/docs/src/index.md @@ -21,4 +21,4 @@ Here some tutorials on temporal graph neural networks: ## Contributions -If you have a suggestion on adding new tutorials, feel free to create a new issue [here](https://github.com/CarloLucibello/GraphNeuralNetworks.jl/issues/new). Users are invited to contribute demonstrations of their own. If you want to contribute new tutorials and looking for inspiration, checkout these tutorials from [PyTorch Geometric](https://pytorch-geometric.readthedocs.io/en/latest/notes/colabs.html). Please check out existing tutorials for more details. \ No newline at end of file +If you have a suggestion on adding new tutorials, feel free to create a new issue [here](https://github.com/JuliaGraphs/GraphNeuralNetworks.jl/issues/new). Users are invited to contribute demonstrations of their own. If you want to contribute new tutorials and looking for inspiration, checkout these tutorials from [PyTorch Geometric](https://pytorch-geometric.readthedocs.io/en/latest/notes/colabs.html). Please check out existing tutorials for more details. \ No newline at end of file