HyperGraphNeuralNetworks

Documentation for HyperGraphNeuralNetworks.

HyperGraphNeuralNetworks.HGNNHypergraphType

HGNNHypergraph{T<:Real, D<:AbstractDict{Int,T}} <: AbstractHGNNHypergraph{Union{T, Nothing}}

An undirected hypergraph type for use in hypergraph neural networks

Constructors

HGNNHypergraph{T,D}(
    h::AbstractSimpleHypergraph;
    hypergraph_ids::Union{Nothing, AbstractVector{<:Integer}} = nothing,
    vdata = nothing,
    hedata = nothing,
    hgdata = nothing
) where {T<:Real, D<:AbstractDict{Int, T}}

HGNNHypergraph{T}(
    h::AbstractSimpleHypergraph;
    hypergraph_ids::Union{Nothing, AbstractVector{<:Integer}} = nothing,
    vdata::Union{DataStore, Nothing} = nothing,
    hedata::Union{DataStore, Nothing} = nothing,
    hgdata::Union{DataStore, Nothing} = nothing
) where {T<:Real}

HGNNHypergraph(
    h::AbstractSimpleHypergraph;
    hypergraph_ids::Union{Nothing, AbstractVector{<:Integer}} = nothing,
    vdata = nothing,
    hedata = nothing,
    hgdata = nothing
)

Construct an `HGNNHypergraph` from a previously constructed hypergraph. Optionally, the user can specify
what hypergraph each vertex belongs to (if multiple distinct hypergraphs are included), as well as vertex,
hyperedge, and hypergraph features.

HGNNHypergraph{T,D}(
    incidence::Matrix{Union{T, Nothing}};
    hypergraph_ids::Union{Nothing, AbstractVector{<:Integer}} = nothing,
    vdata = nothing,
    hedata = nothing,
    hgdata = nothing
) where {T<:Real, D<:AbstractDict{Int, T}}

HGNNHypergraph{T}(
    incidence::Matrix{Union{T, Nothing}};
    hypergraph_ids::Union{Nothing, AbstractVector{<:Integer}} = nothing,
    vdata = nothing,
    hedata = nothing,
    hgdata = nothing
) where {T<:Real}

HGNNHypergraph(
    incidence::Matrix{Union{T, Nothing}};
    hypergraph_ids::Union{Nothing, AbstractVector{<:Integer}} = nothing,
    vdata = nothing,
    hedata = nothing,
    hgdata = nothing
) where {T<:Real}

Construct an `HGNNHypergraph` from an incidence matrix. The incidence matrix is an `M`x`N` matrix, where `M` is the
number of vertices and `N` is the number of hyperedges.

function HGNNHypergraph(num_nodes::T; vdata=nothing, kws...) where {T<:Integer}

Construct an `HGNNHypergraph` with no hyperedges and `num_nodes` vertices.

function HGNNHypergraph(; num_nodes=nothing, vdata=nothing, kws...)

Construct an `HGNNHypergraph` with minimal (perhaps no) information.

Arguments

* `T` : type of weight values stored in the hypergraph's incidence matrix
* `D` : dictionary type for storing values; the default is `Dict{Int, T}`
* `hypergraph_ids` : Nothing (implying that all vertices belong to the same hypergraph) or a vector of ID integers
* `vdata` : an optional DataStore (from GNNGraphs.jl) containing vertex-level features. Each entry in `vdata`
    should have `M` entries/observations, where `M` is the number of vertices in the hypergraph
* `hedata` : an optional DataStore containing hyperedge-level features. Each entry in `hedata` should have `N`
    entries/observations, where `N` is the number of hyperedges in the hypergraph
* `hgdata` : an optional DataStore containing hypergraph-level features. Each entry in `hgdata` should have `G`
    entries/observations, where `G` is the number of hypergraphs in the HGNNHypergraph (note: the maximum index
    in `hypergraph_ids` should be `G`)
* `incidence` : a matrix representation; rows are vertices and columns are hyperedges
* `num_nodes` : the number of vertices in the hypergraph (i.e., `M`)
source
Base.copyMethod
copy(hg::HGNNDiHypergraph; deep=false)

Create a copy of hg. If deep is true, then copy will be a deep copy (equivalent to deepcopy(hg)), otherwise it will be a shallow copy with the same underlying hypergraph data.

source
Base.copyMethod
copy(hg::HGNNHypergraph; deep=false)

Create a copy of `hg`. If `deep` is `true`, then copy will be a deep copy (equivalent to `deepcopy(hg)`),
therwise it will be a shallow copy with the same underlying hypergraph data.
source
HyperGraphNeuralNetworks.add_hyperedgeMethod
add_hyperedge(
    hg::HGNNDiHypergraph{T, D},
    features::DataStore;
    vertices_tail::D = D(),
    vertices_head::D = D(),
) where {T <: Real, D <: AbstractDict{Int,T}}

Adds a hyperedge to a given `HGNNDiHypergraph`. Because `HGNNDiHypergraph` is immutable, this creates a new
`HGNNDiHypergraph`. Optionally, existing vertices can be added to the tail and/or head of the hyperedge. The
paramaters `vertices_tail` and `vertices_head` represent dictionaries of vertex identifiers and values stored at
the tail and head of hyperedges, respectively. Note that the `features` DataStore is not optional; however, if `hg`
has no `hedata` (i.e., if `hedata` is nothing), this can be empty.
source
HyperGraphNeuralNetworks.add_hyperedgeMethod
add_hyperedge(
    hg::HGNNHypergraph{T, D},
    features::DataStore;
    vertices::D = D(),
) where {T <: Real, D <: AbstractDict{Int,T}}

Adds a hyperedge to a given `HGNNHypergraph`. Because `HGNNHypergraph` is immutable, this creates a new
`HGNNHypergraph`. Optionally, existing vertices can be added to the created hyperedge. The paramater `vertices`
represents a dictionary of vertex identifiers andvalues stored at the hyperedges. Note that the `features`
DataStore is not optional; however, if `hg` has no `hedata` (i.e., if `hedata` is nothing), this can be empty.
source
HyperGraphNeuralNetworks.add_self_loopsMethod
add_self_loops(
    hg::HGNNDiHypergraph{T, D};
    add_repeated_hyperedge::Bool = false
) where {T<:Real, D<:AbstractDict{Int,T}}

Add self-loops (hyperedges with the tail and the head both containing only a single vertex `v`) to a directed
hypergraph. If `add_repeated_hyperedge` is true (default is false), then new self-loops will be added, even when
a self-loop already exists for some vertex.

NOTE: this function will throw an AssertionError if hg.hedata is not empty
source
HyperGraphNeuralNetworks.add_self_loopsMethod
add_self_loops(
    hg::HGNNHypergraph{T, D};
    add_repeated_hyperedge::Bool = false
) where {T<:Real, D<:AbstractDict{Int,T}}

Add self-loops (hyperedges containing a single vertex) to an undirected hypergraph. If `add_repeated_hyperedge` is
true (default is false), then new self-loops will be added, even when a self-loop already exists for some vertex.

NOTE: this function will throw an AssertionError if hg.hedata is not empty
source
HyperGraphNeuralNetworks.add_vertexMethod
add_vertex(
    hg::HGNNDiHypergraph{T, D},
    features::DataStore;
    hyperedges_tail::D = D(),
    hyperedges_head::D = D(),
    hypergraph_id::Int = 1
) where {T <: Real, D <: AbstractDict{Int,T}}

Create a new HGNNDiHypergraph that adds a vertex to an existing directed hypergraph `hg`. Note that the `features`
DataStore is not optional, but if the input hypergraph has no vertex data, this can be empty. Optionally, the
vertex can be added to existing hyperedges. The `hyperedges_tail` and `hyperedges_head` parameters include
dictionaries of hyperedge identifiers and values stored at the hyperedges.
source
HyperGraphNeuralNetworks.add_vertexMethod
add_vertex(
    hg::HGNNHypergraph{T, D},
    features::DataStore;
    hyperedges::D = D(),
    hypergraph_id::Int = 1
) where {T <: Real, D <: AbstractDict{Int,T}}

Create a new HGNNHypergraph that adds a vertex to an existing hypergraph `hg`. Note that the `features` DataStore
is not optional, but if the input hypergraph has no vertex data, this can be empty. Optionally, the vertex can be
added to existing hyperedges. The `hyperedges` parameter presents a dictionary of hyperedge identifiers and values
stored at the hyperedges.
source
HyperGraphNeuralNetworks.all_neighborsMethod
all_neighbors(hg::H; same_side::Bool=false) where {H <: AbstractHGNNDiHypergraph}

Returns all neighbors for all vertices in a directed hypergraph. The set of all neighbors for a vertex is the union
of the set of all incoming neighbors and outgoing neighbors. Note that the definition of `incoming` and `outgoing`
neighbor depends on if `same_side` is true or false; see below.

Args:
    hg::H where {H <: AbstractHGNNDiHypergraph} : Hypergraph
    same_side::Bool : If true, return the neighbors within the same side of a hyperedge; i.e., if vertex
    `i` and vertex `j` are both in the tail of hyperedge `e`, they are neighbors. If false, instead return
    the neighbors on the opposite side of the hyperedge; i.e., if vertex `i` is in the tail of `e` and vertex
    `j` is in the head of `e`, then `i` and `j` are neighbors. Default is false.
source
HyperGraphNeuralNetworks.all_neighborsMethod
all_neighbors(hg::H, i::Int; same_side::Bool=false) where {H <: AbstractHGNNDiHypergraph}

Returns the neighbors for vertex `i` in a directed hypergraph `hg`. The set of all neighbors for a vertex is the
union of the set of all incoming neighbors and outgoing neighbors. Note that the definition of `incoming` and
`outgoing` neighbor depends on if `same_side` is true or false; see below.

Args:
    hg::H where {H <: AbstractHGNNDiHypergraph} : Hypergraph
    i::Int : Vertex index
    same_side::Bool : If true, return the neighbors within the same side of a hyperedge; i.e., if vertex
    `i` and vertex `j` are both in the tail of hyperedge `e`, they are neighbors. If false, instead return
    the neighbors on the opposite side of the hyperedge; i.e., if vertex `i` is in the tail of `e` and vertex
    `j` is in the head of `e`, then `i` and `j` are neighbors. Default is false.
source
HyperGraphNeuralNetworks.check_num_hyperedgesMethod
check_num_hyperedges(hg::H, x::AbstractArray) where {H <: AbstractHGNNDiHypergraph}
check_num_hyperedges(hg::H, x::Union{Tuple, NamedTuple}) where {H <: AbstractHGNNDiHypergraph}

Ensure that an array abstract array, tuple, or named tuple (i.e., tensor) `x` has the appropriate final dimension,
equal to the number of hyperedges in the associated directed hypergraph `hg`
source
HyperGraphNeuralNetworks.check_num_hyperedgesMethod
check_num_hyperedges(hg::H, x::AbstractArray) where {H <: AbstractHGNNHypergraph}
check_num_hyperedges(hg::H, x::Union{Tuple, NamedTuple}) where {H <: AbstractHGNNHypergraph}

Ensure that an array abstract array, tuple, or named tuple (i.e., tensor) `x` has the appropriate final dimension,
equal to the number of hyperedges in the associated hypergraph `hg`
source
HyperGraphNeuralNetworks.check_num_verticesMethod
check_num_vertices(hg::H, x::AbstractArray) where {H <: AbstractHGNNDiHypergraph}
check_num_vertices(hg::H, x::Union{Tuple, NamedTuple}) where {H <: AbstractHGNNDiHypergraph}

Ensure that an array abstract array, tuple, or named tuple (i.e., tensor) `x` has the appropriate final dimension,
equal to the number of vertices in the associated directed hypergraph `hg`
source
HyperGraphNeuralNetworks.check_num_verticesMethod
check_num_vertices(hg::H, x::AbstractArray) where {H <: AbstractHGNNHypergraph}
check_num_vertices(hg::H, x::Union{Tuple, NamedTuple}) where {H <: AbstractHGNNHypergraph}

Ensure that an array abstract array, tuple, or named tuple (i.e., tensor) `x` has the appropriate final dimension,
equal to the number of vertices in the associated hypergraph `hg`
source
HyperGraphNeuralNetworks.complex_incidence_matrixMethod
complex_incidence_matrix(hg::H) where {H <: AbstractDirectedHypergraph}

Convert the matrix representation of an undirected hypergraph `hg` into a single complex-valued incidence matrix
`M`. The (i,j)-th element of `M` is 1 if vertex `i` is in the head of hyperedge `j`, -im if `i` is in the tail of
`j`, and 0 otherwise. If there are any hyperedges where any vertex is in both the tail and the head, an error is
thrown.

Reference:
    Fiorini, S., Coniglio, S., Ciavotta, M., Del Bue, A., Let There be Direction in Hypergraph Neural Networks.
    Transactions on Machine Learning Research, 2024.
source
HyperGraphNeuralNetworks.degreeMethod
degree(hg::H) where {H <: AbstractHGNNDiHypergraph}
degree(hg::H, i::Int) where {H <: AbstractHGNNDiHypergraph}
degree(hg::H, inds::AbstractVector{Int}) where {H <: AbstractHGNNDiHypergraph}

Return the degree of all vertices (if no index is provided) or of a specific group of indices
For directed hypergraphs, the total degree is the sum of the incoming degree (see `indegree`) and the
outgoing degree (see `outdegree`).
source
HyperGraphNeuralNetworks.degreeMethod
degree(hg::H) where {H <: AbstractHGNNHypergraph}
degree(hg::H, i::Int) where {H <: AbstractHGNNHypergraph}
degree(hg::H, inds::AbstractVector{Int}) where {H <: AbstractHGNNHypergraph}

Return the degree of all vertices (if no index is provided) or of a specific group of vertices.
source
HyperGraphNeuralNetworks.erdos_renyi_hypergraphMethod
erdos_renyi_hypergraph(
    nVertices::Int,
    nEdges::Int,
    HType::Type{H};
    seed::Int = -1,
    kws...
) where {H <: AbstractHGNNHypergraph}

erdos_renyi_hypergraph(
    nVertices::Int,
    nEdges::Int,
    HType::Type{H};
    seed::Int = -1,
    no_self_loops::Bool = false,
    kws...
) where {H <: AbstractHGNNDiHypergraph}

Generate a *random* undirected hypergraph (in the style of Erdős–Rényi random graphs) without any structural
constraints. See `SimpleHypergraphs.random_model`. The user can optionally seed the random number
generator with kwarg `seed`. The default value is -1; if the value is greater than or equal to 0, then `seed` will
be used.
source
HyperGraphNeuralNetworks.get_hyperedge_weightMethod
get_hyperedge_weight(hg::H, he_ind::Int; side::Symbol = :both) where {H <: AbstractHGNNDiHypergraph}
get_hyperedge_weight(hg::H, he_ind::Int, op::Function; side::Symbol = :both) where {H <: AbstractHGNNDiHypergraph}

Obtain the non-`nothing` weights associated with a directed hyperedge in `hg` given by index `he_ind`. See
`get_hyperedge_weights` for more detail.

TODO: add check for array bounds
source
HyperGraphNeuralNetworks.get_hyperedge_weightMethod
get_hyperedge_weight(hg::H, he_ind::Int) where {H <: AbstractHGNNHypergraph}
get_hyperedge_weight(hg::H, he_ind::Int, op::Function) where {H <: AbstractHGNNHypergraph}

Obtain the non-`nothing` weights associated with a hyperedge in `hg` given by index `he_ind`. See
`get_hyperedge_weights` for more detail

TODO: add check for array bounds
source
HyperGraphNeuralNetworks.get_hyperedge_weightsMethod
get_hyperedge_weights(hg::H; side::Symbol = :both) where {H <: AbstractHGNNDiHypergraph}
get_hyperedge_weights(hg::H, op::Function; side::Symbol = :both) where {H <: AbstractHGNNDiHypergraph}

Return the weights of a directed hypergraph `hg`. The user can choose to obtain the weights of the hyperedge tails
(`side=:tail`), the heads (`side=:head`), or both (`side=:both`). The tail weights and head weights are both
vectors of vectors, where the `i`th element of one such vector corresponds to the non-`nothing` weights of the tail
or head of the `i`th hyperedge. If function `op` is provided, then the weights are transformed using `op` before
being returned. Note that `op` should take only one argument.

TODO: how to handle case where user wants a single weight value per hyperedge, using :both?
source
HyperGraphNeuralNetworks.get_hyperedge_weightsMethod
get_hyperedge_weights(hg::H) where {H <: AbstractHGNNHypergraph}
get_hyperedge_weights(hg::H, op::Function) where {H <: AbstractHGNNHypergraph}

Get the weights of each hyperedge in the hypergraph `hg`. This function returns a vector of vectors, where each
element contains the non-`nothing` weights of the associated hyperedge. If the function `op` is provided, then
the weights are transformed using `op` before being returned. Note that `op` should take only one argument.
source
HyperGraphNeuralNetworks.hyperedge_indexMethod
hyperedge_index(hg::H) where {H <: AbstractHGNNHypergraph}

Obtain the hyperedge index of a directed hypergraph `hg`. The index is returned as two vectors of vectors, one for
the hyperedge tails and the other for the hyperedge heads. The `i`th element of `ind_tail` contains the indices of
the vertices present in the tail of hyperedge `i`, and likewise, the `i`th element of `ind_head` contains the
indices of the vertices in the head of hyperedge `i`.
source
HyperGraphNeuralNetworks.hyperedge_indexMethod
hyperedge_index(hg::H) where {H <: AbstractHGNNHypergraph}

Obtain the hyperedge index of an undirected hypergraph `hg`. The index is a vector of vectors, where the `i`th
element of the index contains the indices of all vertices present in hyperedge `i`.
source
HyperGraphNeuralNetworks.hyperedge_neighborsMethod
hyperedge_neighbors(hg::H; directed::Bool=false) where {H <: AbstractHGNNDiHypergraph}

Returns the neighbors of each hyperedge in directed hypergraph `hg`. If `directed` is true (default false), then
hyperedge `i` is neighbors with hyperedge `j` if and only if there is at least one vertex in the head of `i` that
is also in the tail of `j`. Otherwise, two hyperedges are neighbors if they share any vertices, irrespective of
whether those vertices are in the hyperedges' heads or tails.
source
HyperGraphNeuralNetworks.hyperedge_neighborsMethod
hyperedge_neighbors(hg::H) where {H <: AbstractHGNNHypergraph}

Returns the neighbors of each hyperedge in an undirected hypergraph `hg`. A hyperedge `e` is neighbors with a
hyperedge `f` if there is at least one vertex contained in both `e` and `f`.
source
HyperGraphNeuralNetworks.hyperedge_neighborsMethod
hyperedge_neighbors(hg::H, i::Int; directed::Bool=false) where {H <: AbstractHGNNDiHypergraph}

Returns the neighbors of hyperedge `i` in directed hypergraph `hg`. If `directed` is true (default false), then
hyperedge `i` is neighbors with hyperedge `j` if and only if there is at least one vertex in the head of `i` that
is also in the tail of `j`. Otherwise, two hyperedges are neighbors if they share any vertices, irrespective of
whether those vertices are in the hyperedges' heads or tails.
source
HyperGraphNeuralNetworks.hyperedge_neighborsMethod
hyperedge_neighbors(hg::H, i::Int) where {H <: AbstractHGNNHypergraph}

Returns the neighbors of hyperedge `i` in an undirected hypergraph `hg`. A hyperedge `i` is neighbors with a
hyperedge `e` if there is at least one vertex contained in both `i` and `e`.
source
HyperGraphNeuralNetworks.hyperedge_weight_matrixMethod
hyperedge_weight_matrix(h::H; weighting_function::Function=sum) where {H <: AbstractDirectedHypergraph}

Return two NxN diagonal weight matrices for the directed hypergraph `hg`, where N is the number of hyperedges in
`hg`. One matrix is based on the hyperedge tails, and the other is based on the hyperedge heads. Because
SimpleHypergraphs hypergraphs can have different weights for each vertex-hyperedge pair, the weight of a hyperedge
is ambiguous. The user can specify a `weighting_function` (default is `sum`) that operates on each row of the
hypergraph tail/head weighted incidence matrix.
source
HyperGraphNeuralNetworks.hyperedge_weight_matrixMethod
hyperedge_weight_matrix(hg::H; weighting_function::Function=sum) where {H <: AbstractSimpleHypergraph}

Return an NxN diagonal weight matrix for the undirected hypergraph `hg`, where N is the number of hyperedges in
`hg`. Because SimpleHypergraphs hypergraphs can have different weights for each vertex-hyperedge pair, the weight
of a hyperedge is ambiguous. The user can specify a `weighting_function` (default is `sum`) that operates on each
column of the hypergraph weighted incidence matrix.
source
HyperGraphNeuralNetworks.hypergraph_idsMethod
hypergraph_ids(hg::H) where {H <: AbstractHGNNHypergraph}
hypergraph_ids(hg::H) where {H <: AbstractHGNNDiHypergraph}

Return a vector containing the graph membership of each vertex in the hypergraph hg.

source
HyperGraphNeuralNetworks.in_neighborsMethod
in_neighbors(hg::H; same_side::Bool=false) where {H <: AbstractHGNNDiHypergraph}

Return the incoming neighbors for a directed hypergraph `hg`. Note that the definition of `incoming` depends on
if `same_side` is true or false; see below.

Args:
    hg::H where {H <: AbstractHGNNDiHypergraph} : Hypergraph
    same_side::Bool : If true, return the neighbors that share a hyperedge head with each vertex; i.e., if vertex
    `i` and vertex `j` are both in the head of some hyperedge `e`, they are incoming neighbors. If false, instead
    return the neighbors on the opposite side of the hyperedge; i.e., if vertex `i` is in the head of `e` and vertex
    `j` is in the tail of `e`, then `j` is an incoming neighbor to `i`. Default is false.
source
HyperGraphNeuralNetworks.in_neighborsMethod
in_neighbors(hg::H, i::Int; same_side::Bool=false) where {H <: AbstractHGNNDiHypergraph}

Return the incoming neighbors for a vertex `i` of a directed hypergraph `hg`. Note that the definition of
`incoming` depends on if `same_side` is true or false; see below.

Args:
    hg::H where {H <: AbstractHGNNDiHypergraph} : Hypergraph
    i::Int : Vertex index
    same_side::Bool : If true, return the neighbors that share a hyperedge head with each vertex; i.e., if vertex
    `i` and vertex `j` are both in the head of some hyperedge `e`, they are incoming neighbors. If false, instead
    return the neighbors on the opposite side of the hyperedge; i.e., if vertex `i` is in the head of `e` and vertex
    `j` is in the tail of `e`, then `j` is an incoming neighbor to `i`. Default is false.
source
HyperGraphNeuralNetworks.incidence_matrixMethod
incidence_matrix(hg::H) where {H <: AbstractDirectedHypergraph}

Convert the matrix representation of an undirected hypergraph `hg` into two incidence matrices, `Mt` (the tail
incidence matrix) and `Mh` (the head incidence matrix). The (i,j)-th element of `Mt` is 1 if vertex `i` is in the
tail of hyperedge `j` and 0 otherwise. Likewise, `Mh`[i, j] is 1 if `i` is in the head of `j` and 0 otherwise
source
HyperGraphNeuralNetworks.incidence_matrixMethod
incidence_matrix(hg::H) where {H <: AbstractSimpleHypergraph}

Convert the matrix representation of an undirected hypergraph `hg` to an incidence matrix. The (i,j)-th element of
the incidence matrix `M` is 1 if vertex `i` is in hyperedge `j` and 0 otherwise.
source
HyperGraphNeuralNetworks.indegreeMethod
indegree(hg::H) where {H <: AbstractHGNNDiHypergraph}
indegree(hg::H, i::Int) where {H <: AbstractHGNNDiHypergraph}
indegree(hg::H, inds::AbstractVector{Int}) where {H <: AbstractHGNNDiHypergraph}

Return the incoming degree of all vertices (if no index is provided) or of a specific group of indices for a
directed hypergraph. The incoming degree is the number of directed hyperedges containing a vertex in the head.
source
HyperGraphNeuralNetworks.normalized_laplacianMethod
normalized_laplacian(
    h::H;
    weighting_function::Function=sum,
    combining_function::Function=_matrix_avg
)

Returns the normalized Laplacian for an undirected hypergraph `hg`.

Because of the ambiguity of defining hyperedge weight in the SimpleHypergraphs formalism, the user can
specify a `weighting_function` that, for each hyperedge, acts on the associated row of the matrix representation of
`hg`; the default is `sum`. The user can also specify a `combining_function` for how the hyperedge tail and head
weights should be combined to a single matrix; the default is to average the two matrices.

Ln = I - Qn,
Qn = Dv^(-1/2) A W De^(-1) A* Dv^(-1/2),

where
    Ln = normalized signed Laplacian (MxM)
    I = identity matrix (MxM)
    Qn = normalized Laplacian (MxM)
    Dv = vertex degree matrix (MxM) <-- obtained by summing the tail and head degree matrices
    De = hyperedge degree matrix (NxN) <-- obtained by summing the tail and head degree matrices
    A = incidence matrix (MxN)
    A* = conjugate transpose of A (NxM)
    W = hyperedge weight matrix (NxN) <-- obtained by applying `combining_function` (default: averaging) to the
        tail and head weight matrices

    M = # vertices in `hg`
    N = # hyperedges in `hg`

Reference:
    Fiorini, S., Coniglio, S., Ciavotta, M., Del Bue, A., Let There be Direction in Hypergraph Neural Networks.
    Transactions on Machine Learning Research, 2024.
source
HyperGraphNeuralNetworks.normalized_laplacianMethod
normalized_laplacian(hg::H; weighting_function::Function=sum) where {H <: AbstractSimpleHypergraph}

Returns the normalized Laplacian for an undirected hypergraph `hg`.

Because of the ambiguity of defining hyperedge weight in the SimpleHypergraphs formalism, the user can
specify a `weighting_function` that, for each hyperedge, acts on the associated row of the matrix representation of
`hg`; the default is `sum`.

Ln = I - Qn,
Qn = Dv^(-1/2) A W De^(-1) A* Dv^(-1/2),

where
    Ln = normalized signed Laplacian (MxM)
    I = identity matrix (MxM)
    Qn = normalized Laplacian (MxM)
    Dv = vertex degree matrix (MxM)
    De = hyperedge degree matrix (NxN)
    A = incidence matrix (MxN)
    A* = transpose of A (NxM)
    W = hyperedge weight matrix (NxN)

    M = # vertices in `hg`
    N = # hyperedges in `hg`

Reference:
    Fiorini, S., Coniglio, S., Ciavotta, M., Del Bue, A., Let There be Direction in Hypergraph Neural Networks.
    Transactions on Machine Learning Research, 2024.
source
HyperGraphNeuralNetworks.out_neighborsMethod
out_neighbors(hg::H; same_side::Bool=false) where {H <: AbstractHGNNDiHypergraph}

Return the outgoing neighbors for a directed hypergraph `hg`. Note that the definition of `outgoing` depends on
if `same_side` is true or false; see below.

Args:
    hg::H where {H <: AbstractHGNNDiHypergraph} : Hypergraph
    same_side::Bool : If true, return the neighbors that share a hyperedge tail with each vertex; i.e., if vertex
    `i` and vertex `j` are both in the tail of some hyperedge `e`, they are outgoing neighbors. If false, instead
    return the neighbors on the opposite side of the hyperedge; i.e., if vertex `i` is in the tail of `e` and vertex
    `j` is in the head of `e`, then `j` is an outgoing neighbor to `i`. Default is false.
source
HyperGraphNeuralNetworks.out_neighborsMethod
out_neighbors(hg::H, i::Int; same_side::Bool=false) where {H <: AbstractHGNNDiHypergraph}

Return the outgoing neighbors for a vertex `i` of a directed hypergraph `hg`. Note that the definition of
`outgoing` depends on if `same_side` is true or false; see below.

Args:
    hg::H where {H <: AbstractHGNNDiHypergraph} : Hypergraph
    i::Int : Vertex index
    same_side::Bool : If true, return the neighbors that share a hyperedge tail with each vertex; i.e., if vertex
    `i` and vertex `j` are both in the tail of some hyperedge `e`, they are outgoing neighbors. If false, instead
    return the neighbors on the opposite side of the hyperedge; i.e., if vertex `i` is in the tail of `e` and vertex
    `j` is in the head of `e`, then `j` is an outgoing neighbor to `i`. Default is false.
source
HyperGraphNeuralNetworks.outdegreeMethod
outdegree(hg::H) where {H <: AbstractHGNNDiHypergraph}
outdegree(hg::H, i::Int) where {H <: AbstractHGNNDiHypergraph}
outdegree(hg::H, inds::AbstractVector{Int}) where {H <: AbstractHGNNDiHypergraph}

Return the outgoing degree of all vertices (if no index is provided) or of a specific group of indices for a
directed hypergraph. The outgoing degree is the number of directed hyperedges containing a vertex in the tail.
source
HyperGraphNeuralNetworks.random_dregular_hypergraphMethod
random_dregular_hypergraph(
    nVertices::Int,
    nEdges::Int,
    d::Int,
    HType::Type{H};
    seed::Int = -1,
    kws...
) where {H <: AbstractHGNNHypergraph}

random_dregular_hypergraph(
    nVertices::Int,
    nEdges::Int,
    d::Int,
    HType::Type{H};
    seed::Int = -1,
    no_self_loops::Bool = false,
    kws...
) where {H <: AbstractHGNNDiHypergraph}

Generates a *d*-regular hypergraph, where each node has degree *d*. See `SimpleHypergraphs.random_dregular_model`.
The user can optionally seed the random number generator with kwarg `seed`. The default value is -1; if the value
is greater than or equal to 0, then `seed` will be used.
source
HyperGraphNeuralNetworks.random_kuniform_hypergraphMethod
random_kuniform_hypergraph(
    nVertices::Int,
    nEdges::Int,
    k::Int,
    HType::Type{H};
    seed::Int = -1,
    kws...
) where {H <: AbstractHGNNHypergraph}

random_kuniform_hypergraph(
    nVertices::Int,
    nEdges::Int,
    k::Int,
    HType::Type{H};
    seed::Int = -1,
    no_self_loops::Bool = false,
    kws...
) where {H <: AbstractHGNNDiHypergraph}

Generates a *k*-uniform hypergraph, i.e. an hypergraph where each hyperedge has size *k*. For a directed hypergraph,
each hyperedge has size *k = k_tail + k_head*, where *k_tail* and *k_head* are not necessarily equal. See 
`SimpleHypergraphs.random_kuniform_model`. The user can optionally seed the random number generator with kwarg
`seed`. The default value is -1; if the value is greater than or equal to 0, then `seed` will be used.
source
HyperGraphNeuralNetworks.random_preferential_hypergraphMethod
random_preferential_hypergraph(
    nVertices::Int,
    p::Real,
    HType::Type{HO};
    seed::Int = -1,
    HTypeStart::Type{HI} = Hypergraph,
    hg::HI = random_model(5,5, HI),
    kws...
) where {HI<:AbstractSimpleHypergraph, HO<:AbstractHGNNHypergraph}

Generate a hypergraph with a preferential attachment rule between nodes, as presented in
*Avin, C., Lotker, Z., and Peleg, D. Random preferential attachment hyper-graphs. Computer Science 23 (2015).*
See `SimpleHypergraphs.random_preferential_model` for more details. The user can optionally seed the random number
generator with kwarg `seed`. The default value is -1; if the value is greater than or equal to 0, then `seed` will
be used.
source
HyperGraphNeuralNetworks.remove_hyperedgesMethod
remove_hyperedges(hg::HGNNHypergraph, to_remove::AbstractVector{Int})

Removes a set of hyperedges (`to_remove`) from an undirected hypergraph `hg` by index
Note that the index of v2he will be shifted down after the hyperedges removal.
source
HyperGraphNeuralNetworks.remove_hyperedgesMethod
remove_hyperedges(hg::HGNNDiHypergraph, to_remove::AbstractVector{Int})

Removes a set of hyperedges (`to_remove`) from a directed hypergraph `hg` by index
Note that the index of both v2he_tail and v2he_head will be shifted down after 
the vertices removal.
source
HyperGraphNeuralNetworks.remove_multi_hyperedgesMethod
remove_multi_hyperedges(hg::HGNNHypergraph)

Remove duplicate hyperedges, i.e., hyperedges ei = (ti, hi), ej = (tj, hj), where t represents a hyperedge tail, h
represents a hyperedge head, and ti = tj and hi = hj, from a directed hypergraph `hg`.
source
HyperGraphNeuralNetworks.remove_self_loopsMethod
remove_self_loops(hg::HGNNDiHypergraph{T, D}) where {T<:Real, D<:AbstractDict{Int,T}}

Remove self-loops (hyperedges where the tail and head both contain only a single, shared vertex `v`) from a
directed hypergraph `hg`.
source
HyperGraphNeuralNetworks.remove_vertexMethod
remove_vertex(hg::HGNNHypergraph, v::Int)

Removes the vertex `v` from a given `HGNNHypergraph` `hg`. Note that this creates a new HGNNHypergraph, as
HGNNHypergraph objects are immutable.
source
HyperGraphNeuralNetworks.remove_vertexMethod
remove_vertex(hg::HGNNDiHypergraph, v::Int)

Removes the vertex `v` from a given `HGNNDiHypergraph` `hg`. Note that this creates a new HGNNDiHypergraph, as
HGNNDiHypergraph objects are immutable.
source
HyperGraphNeuralNetworks.remove_verticesMethod
remove_vertices(hg::HGNNHypergraph, to_remove::AbstractVector{Int})

Removes a set of vertices (`to_remove`) from an undirected hypergraph `hg` by index
Note that the index of he2v will be shifted down after the vertices removal.
source
HyperGraphNeuralNetworks.remove_verticesMethod
remove_vertices(hg::HGNNDiHypergraph, to_remove::AbstractVector{Int})

Removes a set of vertices (`to_remove`) from a directed hypergraph `hg` by index
Note that the index of both he2v_tail and he2v_head will be shifted down after 
the vertices removal.
source
HyperGraphNeuralNetworks.to_undirectedMethod
to_undirected(hg::HGNNDiHypergraph{T,D}) where {T <: Real, D <: AbstractDict{Int, T}}

Converts a directed hypergraph into an undirected hypergraph.
Tail and head hyperedges are combined; that is, for all hyperedges he_orig in
the directed hypergraph h, all vertices in the head or tail are added to a
corresponding undirected hyperedge he_new in the undirected hypergraph h'.

Vertex, hyperedge, and hypergraph features, as well as the hypergraph IDs, are undisturbed.

Because vertex-hyperedge weights are restricted to real numbers, we cannot
combine the weights, so we simply set the values to 1.0 if a given vertex
is in a given hyperedge
source
HyperGraphNeuralNetworks.vertex_weight_matrixMethod
vertex_weight_matrix(hg::H; weighting_function::Function=sum) where {H <: AbstractDirectedHypergraph}

Return two NxN diagonal weight matrices for the directed hypergraph `hg`, where N is the number of vertices in
`hg`. One matrix is based on the hyperedge tails that each vertex is included in; the other is based on the
hyperedge heads that each vertex is included in. Because SimpleHypergraphs hypergraphs can have different weights
for each vertex-hyperedge pair, the weight of a vertex is ambiguous. The user can specify a `weighting_function`
(default is `sum`) that operates on each row of the hypergraph tail/head weighted incidence matrix.
source
HyperGraphNeuralNetworks.vertex_weight_matrixMethod
vertex_weight_matrix(hg::H; weighting_function::Function=sum) where {H <: AbstractSimpleHypergraph}

Return an NxN diagonal weight matrix for the undirected hypergraph `hg`, where N is the number of vertices in `hg`.
Because SimpleHypergraphs hypergraphs can have different weights for each vertex-hyperedge pair, the weight of a
vertex is ambiguous. The user can specify a `weighting_function` (default is `sum`) that operates on each row of
the hypergraph weighted incidence matrix.
source
SimpleHypergraphs.add_hyperedge!Method
add_hyperedge!(::HGNNDiHypergraph{T, D}; ::D = D()) where {T <: Real, D <: AbstractDict{Int,T}}

This function is not implemented for HGNNDiHypergraph.
    
The basic hypergraph structure of HGNNDiHypergraph (i.e., the number of vertices, the hyperedges, and the hypergraph
IDs) are not mutable. Users can change the features in the `vdata`, `hedata`, and `hgdata` DataStore
objects, but the number of vertices, number of hyperedges, and number of hypergraphs cannot change.

To create a new HGNNDiHypergraph object with an additional hyperedge, use `add_hyperedge`.
source
SimpleHypergraphs.add_hyperedge!Method
add_hyperedge!(::HGNNHypergraph{T, D}; ::D = D()) where {T <: Real, D <: AbstractDict{Int,T}}

This function is not implemented for HGNNHypergraph.
    
The basic hypergraph structure of HGNNHypergraph (i.e., the number of vertices, the hyperedges, and the hypergraph
IDs) are not mutable. Users can change the features in the `vdata`, `hedata`, and `hgdata` DataStore
objects, but the number of vertices, number of hyperedges, and number of hypergraphs cannot change.

To create a new HGNNHypergraph object with an additional hyperedge, use `add_hyperedge`.
source
SimpleHypergraphs.add_vertex!Method
(::HGNNDiHypergraph{T, D}; ::D = D()) where {T <: Real, D <: AbstractDict{Int,T}}

This function is not implemented for HGNNDiHypergraph.
    
The basic hypergraph structure of HGNNDiHypergraph (i.e., the number of vertices, the hyperedges, and the
hypergraph IDs) are not mutable. Users can change the features in the `vdata`, `hedata`, and `hgdata` DataStore
objects, but the number of vertices, number of hyperedges, and number of hypergraphs cannot change.

To create a new HGNNDiHypergraph object with an additional vertex, use `add_vertex`.
source
SimpleHypergraphs.add_vertex!Method
add_vertex!(::HGNNHypergraph{T, D}; ::D = D()) where {T <: Real, D <: AbstractDict{Int,T}}

This function is not implemented for HGNNHypergraph.
    
The basic hypergraph structure of HGNNHypergraph (i.e., the number of vertices, the hyperedges, and the hypergraph
IDs) are not mutable. Users can change the features in the `vdata`, `hedata`, and `hgdata` DataStore
objects, but the number of vertices, number of hyperedges, and number of hypergraphs cannot change.

To create a new HGNNHypergraph object with an additional vertex, use `add_vertex`.
source
SimpleHypergraphs.remove_hyperedge!Method
remove_hyperedge!(::HGNNHypergraph, ::Int)

This function is not implemented for HGNNHypergraph.
    
The basic hypergraph structure of HGNNHypergraph (i.e., the number of vertices, the hyperedges, and the hypergraph
IDs) are not mutable. Users can change the features in the `vdata`, `hedata`, and `hgdata` DataStore
objects, but the number of vertices, number of hyperedges, and number of hypergraphs cannot change.

To create a new HGNNHypergraph object with a hyperedge removed, use `remove_hyperedge`.
source
SimpleHypergraphs.remove_hyperedge!Method
remove_hyperedge!(::HGNNHypergraph, ::Int)

This function is not implemented for HGNNHypergraph.
    
The basic hypergraph structure of HGNNHypergraph (i.e., the number of vertices, the hyperedges, and the hypergraph
IDs) are not mutable. Users can change the features in the `vdata`, `hedata`, and `hgdata` DataStore
objects, but the number of vertices, number of hyperedges, and number of hypergraphs cannot change.

To create a new HGNNHypergraph object with a hyperedge removed, use `remove_hyperedge`.
source
SimpleHypergraphs.remove_vertex!Method
remove_vertex!(::HGNNDiHypergraph, ::Int)

This function is not implemented for HGNNDiHypergraph.
    
The basic hypergraph structure of HGNNDiHypergraph (i.e., the number of vertices, the hyperedges, and the hypergraph
IDs) are not mutable. Users can change the features in the `vdata`, `hedata`, and `hgdata` DataStore
objects, but the number of vertices, number of hyperedges, and number of hypergraphs cannot change.

To create a new HGNNDiHypergraph object with a vertex removed, use `remove_vertex`.
source
SimpleHypergraphs.remove_vertex!Method
remove_vertex!(::HGNNHypergraph, ::Int)

This function is not implemented for HGNNHypergraph.
    
The basic hypergraph structure of HGNNHypergraph (i.e., the number of vertices, the hyperedges, and the hypergraph
IDs) are not mutable. Users can change the features in the `vdata`, `hedata`, and `hgdata` DataStore
objects, but the number of vertices, number of hyperedges, and number of hypergraphs cannot change.

To create a new HGNNHypergraph object with a vertex removed, use `remove_vertex`.
source