# 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.DataStore`

`GraphNeuralNetworks.GNNGraphs.GNNGraph`

`Base.intersect`

`GraphNeuralNetworks.GNNGraphs.add_edges`

`GraphNeuralNetworks.GNNGraphs.add_edges`

`GraphNeuralNetworks.GNNGraphs.add_nodes`

`GraphNeuralNetworks.GNNGraphs.add_self_loops`

`GraphNeuralNetworks.GNNGraphs.add_self_loops`

`GraphNeuralNetworks.GNNGraphs.adjacency_list`

`GraphNeuralNetworks.GNNGraphs.edge_index`

`GraphNeuralNetworks.GNNGraphs.edge_index`

`GraphNeuralNetworks.GNNGraphs.getgraph`

`GraphNeuralNetworks.GNNGraphs.graph_indicator`

`GraphNeuralNetworks.GNNGraphs.graph_indicator`

`GraphNeuralNetworks.GNNGraphs.has_isolated_nodes`

`GraphNeuralNetworks.GNNGraphs.has_multi_edges`

`GraphNeuralNetworks.GNNGraphs.is_bidirected`

`GraphNeuralNetworks.GNNGraphs.khop_adj`

`GraphNeuralNetworks.GNNGraphs.knn_graph`

`GraphNeuralNetworks.GNNGraphs.laplacian_lambda_max`

`GraphNeuralNetworks.GNNGraphs.negative_sample`

`GraphNeuralNetworks.GNNGraphs.normalized_laplacian`

`GraphNeuralNetworks.GNNGraphs.radius_graph`

`GraphNeuralNetworks.GNNGraphs.rand_bipartite_heterograph`

`GraphNeuralNetworks.GNNGraphs.rand_edge_split`

`GraphNeuralNetworks.GNNGraphs.rand_graph`

`GraphNeuralNetworks.GNNGraphs.rand_heterograph`

`GraphNeuralNetworks.GNNGraphs.random_walk_pe`

`GraphNeuralNetworks.GNNGraphs.remove_edges`

`GraphNeuralNetworks.GNNGraphs.remove_multi_edges`

`GraphNeuralNetworks.GNNGraphs.remove_nodes`

`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.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.

The feature arrays are stored in the fields `ndata`

, `edata`

, and `gdata`

as `DataStore`

objects offering a convenient dictionary-like and namedtuple-like interface. The features can be passed at construction time or added later.

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, CUDA
# 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 at creation time
g = GNNGraph(g, ndata = (x=rand(100, g.num_nodes), y=rand(g.num_nodes)))
# Add 1 edge feature array, after the graph creation
g.edata.z = rand(16, g.num_edges)
# 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.edata.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)
```

## DataStore

`GraphNeuralNetworks.GNNGraphs.DataStore`

— Type```
DataStore([n, data])
DataStore([n,] k1 = x1, k2 = x2, ...)
```

A container for feature arrays. The optional argument `n`

enforces that `numobs(x) == n`

for each array contained in the datastore.

At construction time, the `data`

can be provided as any iterables of pairs of symbols and arrays or as keyword arguments:

```
julia> ds = DataStore(3, x = rand(2, 3), y = rand(3))
DataStore(3) with 2 elements:
y = 3-element Vector{Float64}
x = 2×3 Matrix{Float64}
julia> ds = DataStore(3, Dict(:x => rand(2, 3), :y => rand(3))); # equivalent to above
julia> ds = DataStore(3, (x = rand(2, 3), y = rand(30)))
ERROR: AssertionError: DataStore: data[y] has 30 observations, but n = 3
Stacktrace:
[1] DataStore(n::Int64, data::Dict{Symbol, Any})
@ GraphNeuralNetworks.GNNGraphs ~/.julia/dev/GraphNeuralNetworks/src/GNNGraphs/datastore.jl:54
[2] DataStore(n::Int64, data::NamedTuple{(:x, :y), Tuple{Matrix{Float64}, Vector{Float64}}})
@ GraphNeuralNetworks.GNNGraphs ~/.julia/dev/GraphNeuralNetworks/src/GNNGraphs/datastore.jl:73
[3] top-level scope
@ REPL[13]:1
julia> ds = DataStore(x = rand(2, 3), y = rand(30)) # no checks
DataStore() with 2 elements:
y = 30-element Vector{Float64}
x = 2×3 Matrix{Float64}
```

The `DataStore`

has an interface similar to both dictionaries and named tuples. Arrays can be accessed and added using either the indexing or the property syntax:

```
julia> ds = DataStore(x = ones(2, 3), y = zeros(3))
DataStore() with 2 elements:
y = 3-element Vector{Float64}
x = 2×3 Matrix{Float64}
julia> ds.x # same as `ds[:x]`
2×3 Matrix{Float64}:
1.0 1.0 1.0
1.0 1.0 1.0
julia> ds.z = zeros(3) # Add new feature array `z`. Same as `ds[:z] = rand(3)`
3-element Vector{Float64}:
0.0
0.0
0.0
```

The `DataStore`

can be iterated over, and the keys and values can be accessed using `keys(ds)`

and `values(ds)`

. `map(f, ds)`

applies the function `f`

to each feature array:

```
julia> ds = DataStore(a = zeros(2), b = zeros(2));
julia> ds2 = map(x -> x .+ 1, ds)
julia> ds2.a
2-element Vector{Float64}:
1.0
1.0
```

## 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.edge_index`

— Method`edge_index(g::GNNHeteroGraph, [edge_t])`

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

of type `edge_t = (src_t, rel_t, trg_t)`

.

If `edge_t`

is not provided, it will error if `g`

has more than one edge type.

`GraphNeuralNetworks.GNNGraphs.graph_indicator`

— Method`graph_indicator(g::GNNGraph; edges=false)`

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

to `g.num_graphs`

) of each node in the graph. If `edges=true`

, return the graph membership of each edge instead.

`GraphNeuralNetworks.GNNGraphs.graph_indicator`

— Method`graph_indicator(g::GNNHeteroGraph, [node_t])`

Return a Dict of vectors containing the graph membership (an integer from `1`

to `g.num_graphs`

) of each node in the graph for each node type. If `node_t`

is provided, return the graph membership of each node of type `node_t`

instead.

See also `batch`

.

`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.khop_adj`

— Function`khop_adj(g::GNNGraph,k::Int,T::DataType=eltype(g); dir=:out, weighted=true)`

Return $A^k$ where $A$ is the adjacency matrix of the graph 'g'.

`GraphNeuralNetworks.GNNGraphs.laplacian_lambda_max`

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

Return the largest eigenvalue of the normalized symmetric Laplacian of the graph `g`

.

If the graph is batched from multiple graphs, return the list of the largest eigenvalue for each graph.

`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`

.

The gradient is propagated through this function only if `edge_weight`

is `true`

or a vector.

**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`

. Default`nothing`

.`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. Finally, you can also pass a vector of weights to be used instead of the graph's own weights. Default`true`

.

`Graphs.degree`

— Method`degree(g::GNNHeteroGraph, edge_type::EType; dir = :in)`

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

GNNHeteroGraph given `edge_type`

.

**Arguments**

`g`

: A graph.`edge_type`

: A tuple of symbols`(source_t, edge_t, target_t)`

representing the edge type.`T`

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

, is chosen based on the graph type. Default`nothing`

.`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. Default`dir = :out`

.

`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> using Graphs
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 Vector{Int64}:
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> using Graphs
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 Vector{Int64}:
3
5
```

## Transform

`GraphNeuralNetworks.GNNGraphs.add_edges`

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

Add to graph `g`

the edges with source nodes `s`

and target nodes `t`

. Optionally, pass the edge weight `w`

and the features `edata`

for the new edges. Returns a new graph sharing part of the underlying data with `g`

.

If the `s`

or `t`

contain nodes that are not already present in the graph, they are added to the graph as well.

**Examples**

```
julia> s, t = [1, 2, 3, 3, 4], [2, 3, 4, 4, 4];
julia> w = Float32[1.0, 2.0, 3.0, 4.0, 5.0];
julia> g = GNNGraph((s, t, w))
GNNGraph:
num_nodes: 4
num_edges: 5
julia> add_edges(g, ([2, 3], [4, 1], [10.0, 20.0]))
GNNGraph:
num_nodes: 4
num_edges: 7
```

```
julia> g = GNNGraph()
GNNGraph:
num_nodes: 0
num_edges: 0
julia> add_edges(g, [1,2], [2,3])
GNNGraph:
num_nodes: 3
num_edges: 2
```

`GraphNeuralNetworks.GNNGraphs.add_edges`

— Method```
add_edges(g::GNNHeteroGraph, edge_t, s, t; [edata, num_nodes])
add_edges(g::GNNHeteroGraph, edge_t => (s, t); [edata, num_nodes])
add_edges(g::GNNHeteroGraph, edge_t => (s, t, w); [edata, num_nodes])
```

Add to heterograph `g`

edges of type `edge_t`

with source node vector `s`

and target node vector `t`

. Optionally, pass the edge weights `w`

or the features `edata`

for the new edges. `edge_t`

is a triplet of symbols `(src_t, rel_t, dst_t)`

.

If the edge type is not already present in the graph, it is added. If it involves new node types, they are added to the graph as well. In this case, a dictionary or named tuple of `num_nodes`

can be passed to specify the number of nodes of the new types, otherwise the number of nodes is inferred from the maximum node id in `s`

and `t`

.

`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.add_self_loops`

— Method```
add_self_loops(g::GNNHeteroGraph, edge_t::EType)
add_self_loops(g::GNNHeteroGraph)
```

If the source node type is the same as the destination node type in `edge_t`

, return a graph with the same features as `g`

but also add self-loops of the specified type, `edge_t`

. Otherwise, it returns `g`

unchanged.

Nodes with already existing self-loops of type `edge_t`

will obtain a second set of self-loops of the same type.

If the graph has edge weights for edges of type `edge_t`

, the new edges will have weight 1.

If no edges of type `edge_t`

exist, or all existing edges have no weight, then all new self loops will have no weight.

If `edge_t`

is not passed as argument, for the entire graph self-loop is added to each node for every edge type in the graph where the source and destination node types are the same. This iterates over all edge types present in the graph, applying the self-loop addition logic to each applicable edge type.

`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.random_walk_pe`

— Method`random_walk_pe(g, walk_length)`

Return the random walk positional encoding from the paper Graph Neural Networks with Learnable Structural and Positional Representations of the given graph `g`

and the length of the walk `walk_length`

as a matrix of size `(walk_length, g.num_nodes)`

.

`GraphNeuralNetworks.GNNGraphs.remove_edges`

— Method`remove_edges(g::GNNGraph, edges_to_remove::AbstractVector{<:Integer})`

Remove specified edges from a GNNGraph.

**Arguments**

`g`

: The input graph from which edges will be removed.`edges_to_remove`

: Vector of edge indices to be removed.

**Returns**

A new GNNGraph with the specified edges removed.

**Example**

```
julia> using GraphNeuralNetworks
# Construct a GNNGraph
julia> g = GNNGraph([1, 1, 2, 2, 3], [2, 3, 1, 3, 1])
GNNGraph:
num_nodes: 3
num_edges: 5
# Remove the second edge
julia> g_new = remove_edges(g, [2]);
julia> g_new
GNNGraph:
num_nodes: 3
num_edges: 4
```

`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_nodes`

— Method`remove_nodes(g::GNNGraph, nodes_to_remove::AbstractVector)`

Remove specified nodes, and their associated edges, from a GNNGraph. This operation reindexes the remaining nodes to maintain a continuous sequence of node indices, starting from 1. Similarly, edges are reindexed to account for the removal of edges connected to the removed nodes.

**Arguments**

`g`

: The input graph from which nodes (and their edges) will be removed.`nodes_to_remove`

: Vector of node indices to be removed.

**Returns**

A new GNNGraph with the specified nodes and all edges associated with these nodes removed.

**Example**

```
using GraphNeuralNetworks
g = GNNGraph([1, 1, 2, 2, 3], [2, 3, 1, 3, 1])
# Remove nodes with indices 2 and 3, for example
g_new = remove_nodes(g, [2, 3])
# g_new now does not contain nodes 2 and 3, and any edges that were connected to these nodes.
println(g_new)
```

`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
```

**References**

Section B paragraphs 1 and 2 of the paper Dynamic Hidden-Variable Network Models

`GraphNeuralNetworks.GNNGraphs.rand_bipartite_heterograph`

— Function```
rand_bipartite_heterograph(n1, n2, m; [bidirected, seed, node_t, edge_t, kws...])
rand_bipartite_heterograph((n1, n2), m; ...)
rand_bipartite_heterograph((n1, n2), (m1, m2); ...)
```

Construct an `GNNHeteroGraph`

with number of nodes and edges specified by `n1`

, `n2`

and `m1`

and `m2`

respectively.

See `rand_heterograph`

for a more general version.

**Keyword arguments**

`bidirected`

: whether to generate a bidirected graph. Default is`true`

.`seed`

: random seed. Default is`-1`

(no seed).`node_t`

: node types. If`bipartite=true`

, this should be a tuple of two node types, otherwise it should be a single node type.`edge_t`

: edge types. If`bipartite=true`

, this should be a tuple of two edge types, otherwise it should be a single edge type.

`GraphNeuralNetworks.GNNGraphs.rand_graph`

— Method`rand_graph(n, m; bidirected=true, seed=-1, edge_weight = nothing, 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.

A vector can be passed as `edge_weight`

. Its length has to be equal to `m`

in the directed case, and `m÷2`

in the bidirected one.

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])
```

`GraphNeuralNetworks.GNNGraphs.rand_heterograph`

— Function`rand_heterograph(n, m; seed=-1, bidirected=false, kws...)`

Construct an `GNNHeteroGraph`

with number of nodes and edges specified by `n`

and `m`

respectively. `n`

and `m`

can be any iterable of pairs specifing node/edge types and their numbers.

Use a `seed > 0`

for reproducibility.

Setting `bidirected=true`

will generate a bidirected graph, i.e. each edge will have a reverse edge. Therefore, for each edge type `(:A, :rel, :B)`

a corresponding reverse edge type `(:B, :rel, :A)`

will be generated.

Additional keyword arguments will be passed to the `GNNHeteroGraph`

constructor.

**Examples**

```
julia> g = rand_heterograph((:user => 10, :movie => 20),
(:user, :rate, :movie) => 30)
GNNHeteroGraph:
num_nodes: (:user => 10, :movie => 20)
num_edges: ((:user, :rate, :movie) => 30,)
```

## 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> using Graphs
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,)
```