# GNNGraph

Documentation page for the graph type `GNNGraph`

provided by GraphNeuralNetworks.jl and related methods.

Besides the methods documented here, one can rely on the large set of functionalities given by Graphs.jl thanks to the fact that `GNNGraph`

inherits from `Graphs.AbstractGraph`

.

## Index

`GraphNeuralNetworks.GNNGraphs.GNNGraph`

`Base.intersect`

`GraphNeuralNetworks.GNNGraphs.add_edges`

`GraphNeuralNetworks.GNNGraphs.add_nodes`

`GraphNeuralNetworks.GNNGraphs.add_self_loops`

`GraphNeuralNetworks.GNNGraphs.adjacency_list`

`GraphNeuralNetworks.GNNGraphs.edge_index`

`GraphNeuralNetworks.GNNGraphs.getgraph`

`GraphNeuralNetworks.GNNGraphs.graph_indicator`

`GraphNeuralNetworks.GNNGraphs.has_isolated_nodes`

`GraphNeuralNetworks.GNNGraphs.has_multi_edges`

`GraphNeuralNetworks.GNNGraphs.is_bidirected`

`GraphNeuralNetworks.GNNGraphs.knn_graph`

`GraphNeuralNetworks.GNNGraphs.negative_sample`

`GraphNeuralNetworks.GNNGraphs.normalized_laplacian`

`GraphNeuralNetworks.GNNGraphs.radius_graph`

`GraphNeuralNetworks.GNNGraphs.rand_edge_split`

`GraphNeuralNetworks.GNNGraphs.rand_graph`

`GraphNeuralNetworks.GNNGraphs.remove_multi_edges`

`GraphNeuralNetworks.GNNGraphs.remove_self_loops`

`GraphNeuralNetworks.GNNGraphs.sample_neighbors`

`GraphNeuralNetworks.GNNGraphs.scaled_laplacian`

`GraphNeuralNetworks.GNNGraphs.set_edge_weight`

`GraphNeuralNetworks.GNNGraphs.to_bidirected`

`GraphNeuralNetworks.GNNGraphs.to_unidirected`

`Graphs.LinAlg.adjacency_matrix`

`Graphs.degree`

`Graphs.has_self_loops`

`Graphs.inneighbors`

`Graphs.outneighbors`

`MLUtils.batch`

`MLUtils.unbatch`

`SparseArrays.blockdiag`

## GNNGraph type

`GraphNeuralNetworks.GNNGraphs.GNNGraph`

— Type```
GNNGraph(data; [graph_type, ndata, edata, gdata, num_nodes, graph_indicator, dir])
GNNGraph(g::GNNGraph; [ndata, edata, gdata])
```

A type representing a graph structure that also stores feature arrays associated to nodes, edges, and the graph itself.

A `GNNGraph`

can be constructed out of different `data`

objects expressing the connections inside the graph. The internal representation type is determined by `graph_type`

.

When constructed from another `GNNGraph`

, the internal graph representation is preserved and shared. The node/edge/graph features are retained as well, unless explicitely set by the keyword arguments `ndata`

, `edata`

, and `gdata`

.

A `GNNGraph`

can also represent multiple graphs batched togheter (see `Flux.batch`

or `SparseArrays.blockdiag`

). The field `g.graph_indicator`

contains the graph membership of each node.

`GNNGraph`

s are always directed graphs, therefore each edge is defined by a source node and a target node (see `edge_index`

). Self loops (edges connecting a node to itself) and multiple edges (more than one edge between the same pair of nodes) are supported.

A `GNNGraph`

is a Graphs.jl's `AbstractGraph`

, therefore it supports most functionality from that library.

**Arguments**

`data`

: Some data representing the graph topology. Possible type are- An adjacency matrix
- An adjacency list.
- A tuple containing the source and target vectors (COO representation)
- A Graphs.jl' graph.

`graph_type`

: A keyword argument that specifies the underlying representation used by the GNNGraph. Currently supported values are`:coo`

. Graph represented as a tuple`(source, target)`

, such that the`k`

-th edge connects the node`source[k]`

to node`target[k]`

. Optionally, also edge weights can be given:`(source, target, weights)`

.`:sparse`

. A sparse adjacency matrix representation.`:dense`

. A dense adjacency matrix representation.

`:coo`

, currently the most supported type.`dir`

: The assumed edge direction when given adjacency matrix or adjacency list input data`g`

. Possible values are`:out`

and`:in`

. Default`:out`

.`num_nodes`

: The number of nodes. If not specified, inferred from`g`

. Default`nothing`

.`graph_indicator`

: For batched graphs, a vector containing the graph assignment of each node. Default`nothing`

.`ndata`

: Node features. An array or named tuple of arrays whose last dimension has size`num_nodes`

.`edata`

: Edge features. An array or named tuple of arrays whose last dimension has size`num_edges`

.`gdata`

: Graph features. An array or named tuple of arrays whose last dimension has size`num_graphs`

.

**Examples**

```
using Flux, GraphNeuralNetworks
# Construct from adjacency list representation
data = [[2,3], [1,4,5], [1], [2,5], [2,4]]
g = GNNGraph(data)
# Number of nodes, edges, and batched graphs
g.num_nodes # 5
g.num_edges # 10
g.num_graphs # 1
# Same graph in COO representation
s = [1,1,2,2,2,3,4,4,5,5]
t = [2,3,1,4,5,3,2,5,2,4]
g = GNNGraph(s, t)
# From a Graphs' graph
g = GNNGraph(erdos_renyi(100, 20))
# Add 2 node feature arrays
g = GNNGraph(g, ndata = (x=rand(100, g.num_nodes), y=rand(g.num_nodes)))
# Add node features and edge features with default names `x` and `e`
g = GNNGraph(g, ndata = rand(100, g.num_nodes), edata = rand(16, g.num_edges))
g.ndata.x # or just g.x
g.ndata.e # or just g.e
# Send to gpu
g = g |> gpu
# Collect edges' source and target nodes.
# Both source and target are vectors of length num_edges
source, target = edge_index(g)
```

## Query

`GraphNeuralNetworks.GNNGraphs.adjacency_list`

— Method```
adjacency_list(g; dir=:out)
adjacency_list(g, nodes; dir=:out)
```

Return the adjacency list representation (a vector of vectors) of the graph `g`

.

Calling `a`

the adjacency list, if `dir=:out`

than `a[i]`

will contain the neighbors of node `i`

through outgoing edges. If `dir=:in`

, it will contain neighbors from incoming edges instead.

If `nodes`

is given, return the neighborhood of the nodes in `nodes`

only.

`GraphNeuralNetworks.GNNGraphs.edge_index`

— Method`edge_index(g::GNNGraph)`

Return a tuple containing two vectors, respectively storing the source and target nodes for each edges in `g`

.

`s, t = edge_index(g)`

`GraphNeuralNetworks.GNNGraphs.graph_indicator`

— Method`graph_indicator(g)`

Return a vector containing the graph membership (an integer from `1`

to `g.num_graphs`

) of each node in the graph.

`GraphNeuralNetworks.GNNGraphs.has_isolated_nodes`

— Method`has_isolated_nodes(g::GNNGraph; dir=:out)`

Return true if the graph `g`

contains nodes with out-degree (if `dir=:out`

) or in-degree (if `dir=:in`

) equal to zero.

`GraphNeuralNetworks.GNNGraphs.has_multi_edges`

— Method`has_multi_edges(g::GNNGraph)`

Return `true`

if `g`

has any multiple edges.

`GraphNeuralNetworks.GNNGraphs.is_bidirected`

— Method`is_bidirected(g::GNNGraph)`

Check if the directed graph `g`

essentially corresponds to an undirected graph, i.e. if for each edge it also contains the reverse edge.

`GraphNeuralNetworks.GNNGraphs.normalized_laplacian`

— Function`normalized_laplacian(g, T=Float32; add_self_loops=false, dir=:out)`

Normalized Laplacian matrix of graph `g`

.

**Arguments**

`g`

: A`GNNGraph`

.`T`

: result element type.`add_self_loops`

: add self-loops while calculating the matrix.`dir`

: the edge directionality considered (:out, :in, :both).

`GraphNeuralNetworks.GNNGraphs.scaled_laplacian`

— Function`scaled_laplacian(g, T=Float32; dir=:out)`

Scaled Laplacian matrix of graph `g`

, defined as $\hat{L} = \frac{2}{\lambda_{max}} L - I$ where $L$ is the normalized Laplacian matrix.

**Arguments**

`g`

: A`GNNGraph`

.`T`

: result element type.`dir`

: the edge directionality considered (:out, :in, :both).

`Graphs.LinAlg.adjacency_matrix`

— Function`adjacency_matrix(g::GNNGraph, T=eltype(g); dir=:out, weighted=true)`

Return the adjacency matrix `A`

for the graph `g`

.

If `dir=:out`

, `A[i,j] > 0`

denotes the presence of an edge from node `i`

to node `j`

. If `dir=:in`

instead, `A[i,j] > 0`

denotes the presence of an edge from node `j`

to node `i`

.

User may specify the eltype `T`

of the returned matrix.

If `weighted=true`

, the `A`

will contain the edge weights if any, otherwise the elements of `A`

will be either 0 or 1.

`Graphs.degree`

— Method`degree(g::GNNGraph, T=nothing; dir=:out, edge_weight=true)`

Return a vector containing the degrees of the nodes in `g`

.

**Arguments**

`g`

: A graph.`T`

: Element type of the returned vector. If`nothing`

, is chosen based on the graph type and will be an integer if`edge_weight=false`

.`dir`

: For`dir=:out`

the degree of a node is counted based on the outgoing edges. For`dir=:in`

, the ingoing edges are used. If`dir=:both`

we have the sum of the two.`edge_weight`

: If`true`

and the graph contains weighted edges, the degree will be weighted. Set to`false`

instead to just count the number of outgoing/ingoing edges. In alternative, you can also pass a vector of weights to be used instead of the graph's own weights.

`Graphs.has_self_loops`

— Method`has_self_loops(g::GNNGraph)`

Return `true`

if `g`

has any self loops.

`Graphs.outneighbors`

— Function`outneighbors(g, v)`

Return a list of all neighbors connected to vertex `v`

by an outgoing edge.

**Implementation Notes**

Returns a reference to the current graph's internal structures, not a copy. Do not modify result. If the graph is modified, the behavior is undefined: the array behind this reference may be modified too, but this is not guaranteed.

**Examples**

```
julia> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]);
julia> outneighbors(g, 4)
1-element Array{Int64,1}:
5
```

`Graphs.inneighbors`

— Function`inneighbors(g, v)`

Return a list of all neighbors connected to vertex `v`

by an incoming edge.

**Implementation Notes**

Returns a reference to the current graph's internal structures, not a copy. Do not modify result. If the graph is modified, the behavior is undefined: the array behind this reference may be modified too, but this is not guaranteed.

**Examples**

```
julia> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]);
julia> inneighbors(g, 4)
2-element Array{Int64,1}:
3
5
```

## Transform

`GraphNeuralNetworks.GNNGraphs.add_edges`

— Method`add_edges(g::GNNGraph, s::AbstractVector, t::AbstractVector; [edata])`

Add to graph `g`

the edges with source nodes `s`

and target nodes `t`

. Optionally, pass the features `edata`

for the new edges.

`GraphNeuralNetworks.GNNGraphs.add_nodes`

— Method`add_nodes(g::GNNGraph, n; [ndata])`

Add `n`

new nodes to graph `g`

. In the new graph, these nodes will have indexes from `g.num_nodes + 1`

to `g.num_nodes + n`

.

`GraphNeuralNetworks.GNNGraphs.add_self_loops`

— Method`add_self_loops(g::GNNGraph)`

Return a graph with the same features as `g`

but also adding edges connecting the nodes to themselves.

Nodes with already existing self-loops will obtain a second self-loop.

If the graphs has edge weights, the new edges will have weight 1.

`GraphNeuralNetworks.GNNGraphs.getgraph`

— Method`getgraph(g::GNNGraph, i; nmap=false)`

Return the subgraph of `g`

induced by those nodes `j`

for which `g.graph_indicator[j] == i`

or, if `i`

is a collection, `g.graph_indicator[j] ∈ i`

. In other words, it extract the component graphs from a batched graph.

If `nmap=true`

, return also a vector `v`

mapping the new nodes to the old ones. The node `i`

in the subgraph will correspond to the node `v[i]`

in `g`

.

`GraphNeuralNetworks.GNNGraphs.negative_sample`

— Method```
negative_sample(g::GNNGraph;
num_neg_edges = g.num_edges,
bidirected = is_bidirected(g))
```

Return a graph containing random negative edges (i.e. non-edges) from graph `g`

as edges.

If `bidirected=true`

, the output graph will be bidirected and there will be no leakage from the origin graph.

See also `is_bidirected`

.

`GraphNeuralNetworks.GNNGraphs.rand_edge_split`

— Method`rand_edge_split(g::GNNGraph, frac; bidirected=is_bidirected(g)) -> g1, g2`

Randomly partition the edges in `g`

to form two graphs, `g1`

and `g2`

. Both will have the same number of nodes as `g`

. `g1`

will contain a fraction `frac`

of the original edges, while `g2`

wil contain the rest.

If `bidirected = true`

makes sure that an edge and its reverse go into the same split. This option is supported only for bidirected graphs with no self-loops and multi-edges.

`rand_edge_split`

is tipically used to create train/test splits in link prediction tasks.

`GraphNeuralNetworks.GNNGraphs.remove_multi_edges`

— Method`remove_multi_edges(g::GNNGraph; aggr=+)`

Remove multiple edges (also called parallel edges or repeated edges) from graph `g`

. Possible edge features are aggregated according to `aggr`

, that can take value `+`

,`min`

, `max`

or `mean`

.

See also `remove_self_loops`

, `has_multi_edges`

, and `to_bidirected`

.

`GraphNeuralNetworks.GNNGraphs.remove_self_loops`

— Method`remove_self_loops(g::GNNGraph)`

Return a graph constructed from `g`

where self-loops (edges from a node to itself) are removed.

See also `add_self_loops`

and `remove_multi_edges`

.

`GraphNeuralNetworks.GNNGraphs.set_edge_weight`

— Method`set_edge_weight(g::GNNGraph, w::AbstractVector)`

Set `w`

as edge weights in the returned graph.

`GraphNeuralNetworks.GNNGraphs.to_bidirected`

— Method`to_bidirected(g)`

Adds a reverse edge for each edge in the graph, then calls `remove_multi_edges`

with `mean`

aggregation to simplify the graph.

See also `is_bidirected`

.

**Examples**

```
julia> s, t = [1, 2, 3, 3, 4], [2, 3, 4, 4, 4];
julia> w = [1.0, 2.0, 3.0, 4.0, 5.0];
julia> e = [10.0, 20.0, 30.0, 40.0, 50.0];
julia> g = GNNGraph(s, t, w, edata = e)
GNNGraph:
num_nodes = 4
num_edges = 5
edata:
e => (5,)
julia> g2 = to_bidirected(g)
GNNGraph:
num_nodes = 4
num_edges = 7
edata:
e => (7,)
julia> edge_index(g2)
([1, 2, 2, 3, 3, 4, 4], [2, 1, 3, 2, 4, 3, 4])
julia> get_edge_weight(g2)
7-element Vector{Float64}:
1.0
1.0
2.0
2.0
3.5
3.5
5.0
julia> g2.edata.e
7-element Vector{Float64}:
10.0
10.0
20.0
20.0
35.0
35.0
50.0
```

`GraphNeuralNetworks.GNNGraphs.to_unidirected`

— Method`to_unidirected(g::GNNGraph)`

Return a graph that for each multiple edge between two nodes in `g`

keeps only an edge in one direction.

`MLUtils.batch`

— Method`batch(gs::Vector{<:GNNGraph})`

Batch together multiple `GNNGraph`

s into a single one containing the total number of original nodes and edges.

Equivalent to `SparseArrays.blockdiag`

. See also `Flux.unbatch`

.

**Examples**

```
julia> g1 = rand_graph(4, 6, ndata=ones(8, 4))
GNNGraph:
num_nodes = 4
num_edges = 6
ndata:
x => (8, 4)
julia> g2 = rand_graph(7, 4, ndata=zeros(8, 7))
GNNGraph:
num_nodes = 7
num_edges = 4
ndata:
x => (8, 7)
julia> g12 = Flux.batch([g1, g2])
GNNGraph:
num_nodes = 11
num_edges = 10
num_graphs = 2
ndata:
x => (8, 11)
julia> g12.ndata.x
8×11 Matrix{Float64}:
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
```

`MLUtils.unbatch`

— Method`unbatch(g::GNNGraph)`

Opposite of the `Flux.batch`

operation, returns an array of the individual graphs batched together in `g`

.

See also `Flux.batch`

and `getgraph`

.

**Examples**

```
julia> gbatched = Flux.batch([rand_graph(5, 6), rand_graph(10, 8), rand_graph(4,2)])
GNNGraph:
num_nodes = 19
num_edges = 16
num_graphs = 3
julia> Flux.unbatch(gbatched)
3-element Vector{GNNGraph{Tuple{Vector{Int64}, Vector{Int64}, Nothing}}}:
GNNGraph:
num_nodes = 5
num_edges = 6
GNNGraph:
num_nodes = 10
num_edges = 8
GNNGraph:
num_nodes = 4
num_edges = 2
```

`SparseArrays.blockdiag`

— Method`blockdiag(xs::GNNGraph...)`

Equivalent to `Flux.batch`

.

## Generate

`GraphNeuralNetworks.GNNGraphs.knn_graph`

— Method```
knn_graph(points::AbstractMatrix,
k::Int;
graph_indicator = nothing,
self_loops = false,
dir = :in,
kws...)
```

Create a `k`

-nearest neighbor graph where each node is linked to its `k`

closest `points`

.

**Arguments**

`points`

: A num*features × num*nodes matrix storing the Euclidean positions of the nodes.`k`

: The number of neighbors considered in the kNN algorithm.`graph_indicator`

: Either nothing or a vector containing the graph assignment of each node, in which case the returned graph will be a batch of graphs.`self_loops`

: If`true`

, consider the node itself among its`k`

nearest neighbors, in which case the graph will contain self-loops.`dir`

: The direction of the edges. If`dir=:in`

edges go from the`k`

neighbors to the central node. If`dir=:out`

we have the opposite direction.`kws`

: Further keyword arguments will be passed to the`GNNGraph`

constructor.

**Examples**

```
julia> n, k = 10, 3;
julia> x = rand(3, n);
julia> g = knn_graph(x, k)
GNNGraph:
num_nodes = 10
num_edges = 30
julia> graph_indicator = [1,1,1,1,1,2,2,2,2,2];
julia> g = knn_graph(x, k; graph_indicator)
GNNGraph:
num_nodes = 10
num_edges = 30
num_graphs = 2
```

`GraphNeuralNetworks.GNNGraphs.radius_graph`

— Method```
radius_graph(points::AbstractMatrix,
r::AbstractFloat;
graph_indicator = nothing,
self_loops = false,
dir = :in,
kws...)
```

Create a graph where each node is linked to its neighbors within a given distance `r`

.

**Arguments**

`points`

: A num*features × num*nodes matrix storing the Euclidean positions of the nodes.`r`

: The radius.`graph_indicator`

: Either nothing or a vector containing the graph assignment of each node, in which case the returned graph will be a batch of graphs.`self_loops`

: If`true`

, consider the node itself among its neighbors, in which case the graph will contain self-loops.`dir`

: The direction of the edges. If`dir=:in`

edges go from the neighbors to the central node. If`dir=:out`

we have the opposite direction.`kws`

: Further keyword arguments will be passed to the`GNNGraph`

constructor.

**Examples**

```
julia> n, r = 10, 0.75;
julia> x = rand(3, n);
julia> g = radius_graph(x, r)
GNNGraph:
num_nodes = 10
num_edges = 46
julia> graph_indicator = [1,1,1,1,1,2,2,2,2,2];
julia> g = radius_graph(x, r; graph_indicator)
GNNGraph:
num_nodes = 10
num_edges = 20
num_graphs = 2
```

`GraphNeuralNetworks.GNNGraphs.rand_graph`

— Method`rand_graph(n, m; bidirected=true, seed=-1, kws...)`

Generate a random (Erdós-Renyi) `GNNGraph`

with `n`

nodes and `m`

edges.

If `bidirected=true`

the reverse edge of each edge will be present. If `bidirected=false`

instead, `m`

unrelated edges are generated. In any case, the output graph will contain no self-loops or multi-edges.

Use a `seed > 0`

for reproducibility.

Additional keyword arguments will be passed to the `GNNGraph`

constructor.

**Examples**

```
julia> g = rand_graph(5, 4, bidirected=false)
GNNGraph:
num_nodes = 5
num_edges = 4
julia> edge_index(g)
([1, 3, 3, 4], [5, 4, 5, 2])
# In the bidirected case, edge data will be duplicated on the reverse edges if needed.
julia> g = rand_graph(5, 4, edata=rand(16, 2))
GNNGraph:
num_nodes = 5
num_edges = 4
edata:
e => (16, 4)
# Each edge has a reverse
julia> edge_index(g)
([1, 3, 3, 4], [3, 4, 1, 3])
```

## Operators

`Base.intersect`

— Function`intersect(g, h)`

Return a graph with edges that are only in both graph `g`

and graph `h`

.

**Implementation Notes**

This function may produce a graph with 0-degree vertices. Preserves the eltype of the input graph.

**Examples**

```
julia> g1 = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]);
julia> g2 = SimpleDiGraph([0 1 0; 0 0 1; 1 0 0]);
julia> foreach(println, edges(intersect(g1, g2)))
Edge 1 => 2
Edge 2 => 3
Edge 3 => 1
```

## Sampling

`GraphNeuralNetworks.GNNGraphs.sample_neighbors`

— Function`sample_neighbors(g, nodes, K=-1; dir=:in, replace=false, dropnodes=false)`

Sample neighboring edges of the given nodes and return the induced subgraph. For each node, a number of inbound (or outbound when `dir = :out`

`) edges will be randomly chosen. If`

dropnodes=false`, the graph returned will then contain all the nodes in the original graph, but only the sampled edges.

The returned graph will contain an edge feature `EID`

corresponding to the id of the edge in the original graph. If `dropnodes=true`

, it will also contain a node feature `NID`

with the node ids in the original graph.

**Arguments**

`g`

. The graph.`nodes`

. A list of node IDs to sample neighbors from.`K`

. The maximum number of edges to be sampled for each node. If -1, all the neighboring edges will be selected.`dir`

. Determines whether to sample inbound (`:in`

) or outbound (``:out`

) edges (Default`:in`

).`replace`

. If`true`

, sample with replacement.`dropnodes`

. If`true`

, the resulting subgraph will contain only the nodes involved in the sampled edges.

**Examples**

```
julia> g = rand_graph(20, 100)
GNNGraph:
num_nodes = 20
num_edges = 100
julia> sample_neighbors(g, 2:3)
GNNGraph:
num_nodes = 20
num_edges = 9
edata:
EID => (9,)
julia> sg = sample_neighbors(g, 2:3, dropnodes=true)
GNNGraph:
num_nodes = 10
num_edges = 9
ndata:
NID => (10,)
edata:
EID => (9,)
julia> sg.ndata.NID
10-element Vector{Int64}:
2
3
17
14
18
15
16
20
7
10
julia> sample_neighbors(g, 2:3, 5, replace=true)
GNNGraph:
num_nodes = 20
num_edges = 10
edata:
EID => (10,)
```