diff --git a/src/core/layers.jl b/src/core/layers.jl new file mode 100644 index 0000000..1f07cb6 --- /dev/null +++ b/src/core/layers.jl @@ -0,0 +1,82 @@ + +using Random + +""" + HGNNConv(in_ft::Int, out_ft::Int; bias::Bool=true) +""" + +""" +class HGNN_conv(nn.Module): + def __init__(self, in_ft, out_ft, bias=True): + super(HGNN_conv, self).__init__() + + self.weight = Parameter(torch.Tensor(in_ft, out_ft)) + if bias: + self.bias = Parameter(torch.Tensor(out_ft)) + else: + self.register_parameter('bias', None) + self.reset_parameters() + + def reset_parameters(self): + stdv = 1. / math.sqrt(self.weight.size(1)) + self.weight.data.uniform_(-stdv, stdv) + if self.bias is not None: + self.bias.data.uniform_(-stdv, stdv) + + def forward(self, x: torch.Tensor, G: torch.Tensor): + x = x.matmul(self.weight) + if self.bias is not None: + x = x + self.bias + x = G.matmul(x) + return x +""" +# weight is different: what type of data we use in lux machine learning + +struct HGNNConv{T: Real} <: GNNLayer{T} + in_ft::Int + out_ft::Int + weight::AbstractArray{T, 2} # i'm not sure but we want dimention od in_ft*out_ft + bias:: Union{Nothing, Vector{T}} +end + +function HGNN_conv{T}( + in_ft::Int, + out_ft::Int; + weight::Union{AbstractArray{}, Nothing} = nothing, + initialBias::Union{Vector{T}, Nothing} = nothing, + use_bias::Bool = true +) where {T<:Real} + + if weight !== nothing + @assert size(weight) == (in_ft, out_ft) "Weight matrix must be of size (in_ft, out_ft)" + else + stdv = 1. / sqrt(out_ft) + lo, hi = -stdv, stdv + weight = lo .+ (hi-lo) .* rand(T, in_ft, out_ft) + end + + if use_bias === false + bias = vector{Nothing}(out_ft, 1) + else + if initialbias === nothing + bias = lo .+ (hi-lo) .* rand(T, out_ft, 1) + else + @assert length(initialBias) == out_ft "Bias vector must be of length out_ft" + bias = initialBias + end + end + + HGNN_conv{T}( + weight = weight, + bias = bias, + ) +end + +function forward(HGNNConv::HGNNConv{T}, x::Matrix{T}, G::Matrix{T}) where {T<:Real} + x = x * HGNNConv.weight + if HGNNConv.bias !== nothing + x = x .+ HGNNConv.bias + end + x = G * x + return x +end diff --git a/src/core/transform.jl b/src/core/transform.jl index 9a91413..5b66a71 100644 --- a/src/core/transform.jl +++ b/src/core/transform.jl @@ -1293,6 +1293,13 @@ end # TODO: clique only defined for undirected graph # Is there a way to adapt this for dihypergraphs? +# function clique_negative_sample( +# hg::HGNNDiHypergraph{T, D}, +# n::Int, +# rng::AbstractRNG; +# max_trials::Int = 10 +# ) where {T <: Real, D <: AbstractDict{Int, T}} +# end """ negative_sample_hyperedge( @@ -1369,4 +1376,507 @@ function negative_sample_hyperedge( else throw("negative_sample not implemented for strategy of type $S; please call a sampling function directly.") end -end \ No newline at end of file +end + +function random_split_vertices( + hg::HGNNHypergraph{T,D}, + fracs::AbstractVector{<:Real}, + rng::AbstractRNG +) where {T <: Real, D <: AbstractDict{Int, T}} + # For all f ∈ fracs, 0 < f <= 1 + @assert all(fracs .> 0) && all(fracs .<= 1) + # Fractions must sum to 1 + @assert abs(sum(fracs) - 1) <= 1e-5 + + num_choices = round.(fracs .* hg.num_vertices) + rand_inds = shuffle(rng, Vector(1:hg.num_vertices)) + + partitions = Vector{Int}[] + start_point = 1 + + # Provide the (approximate) right amount of (randomly selected) vertex indices per partition + for i in 1:(length(num_choices) - 1) + part = rand_inds[start_point:start_point + num_choices[i] - 1] + start_point += num_choices + push!(partitions, sort(part)) + end + push!(partitions, sort(rand_inds[start_point:end])) + + res = HGNNHypergraph{T,D}[] + + # Partition v2he and he2v, being careful of indices + for part in partitions + v2he = hg.v2he[part] + he2v = D[] + + vmap = Dict{Int, Int}(x => i for (i, x) in enumerate(part)) + hemap = Dict{Int, Int}() + hgmap = Dict{Int, Int}() + + for (i, he) in enumerate(hg.he2v) + newhe = filter(((k,v), ) -> k in part, he) + if length(newhe) > 0 + newhe = D(vmap[k] => v for (k, v) in newhe) + push!(he2v, newhe) + hemap[i] = length(he2v) + end + end + + for i in eachindex(v2he) + v2he[i] = D(hemap[k] => v for (k, v) in v2he[i]) + end + + unique_hgids = sort(collect(Set(hg.hypergraph_ids[part]))) + for (i, e) in enumerate(unique_hgids) + hgmap[e] = i + end + hypergraph_ids = [hgmap[x] for x in hg.hypergraph_ids[part]] + + push!( + res, + HGNNHypergraph{T,D}( + v2he, + he2v, + length(v2he), + length(he2v), + length(unique_hgids), + hypergraph_ids, + getobs(hg.vdata, part), + getobs(hg.hedata, collect(keys(hemap))), + getobs(hg.hgdata, unique_hgids) + ) + ) + end + + return res +end + +function random_split_vertices( + hg::HGNNDiHypergraph{T,D}, + fracs::AbstractVector{<:Real}, + rng::AbstractRNG +) where {T <: Real, D <: AbstractDict{Int, T}} + # For all f ∈ fracs, 0 < f <= 1 + @assert all(fracs .> 0) && all(fracs .<= 1) + # Fractions must sum to 1 + @assert abs(sum(fracs) - 1) <= 1e-5 + + num_choices = round.(fracs .* hg.num_vertices) + rand_inds = shuffle(rng, Vector(1:hg.num_vertices)) + + partitions = Vector{Int}[] + start_point = 1 + + # Provide the (approximate) right amount of (randomly selected) vertex indices per partition + for i in 1:(length(num_choices) - 1) + part = rand_inds[start_point:start_point + num_choices[i] - 1] + start_point += num_choices + push!(partitions, sort(part)) + end + push!(partitions, sort(rand_inds[start_point:end])) + + res = HGNNDiHypergraph{T,D}[] + + # Partition v2he and he2v, being careful of indices + for part in partitions + v2he_tail = hg.hg_tail.v2he[part] + v2he_head = hg.hg_head.v2he[part] + + he2v_tail = D[] + he2v_head = D[] + + vmap = Dict{Int, Int}(x => i for (i, x) in enumerate(part)) + hemap = Dict{Int, Int}() + hgmap = Dict{Int, Int}() + + for i in 1:hg.num_hyperedges + he_tail = hg.hg_tail.he2v[i] + he_head = hg.hg_head.he2v[i] + + newhe_tail = filter(((k,v), ) -> k in part, he_tail) + newhe_head = filter(((k,v), ) -> k in part, he_head) + if length(newhe_tail) > 0 || length(newhe_head) > 0 + newhe_tail = D(vmap[k] => v for (k, v) in newhe_tail) + newhe_head = D(vmap[k] => v for (k, v) in newhe_head) + push!(he2v_tail, newhe_tail) + push!(he2v_head, newhe_head) + hemap[i] = length(he2v) + end + end + + for i in eachindex(v2he_tail) + v2he_tail[i] = D(hemap[k] => v for (k, v) in v2he_tail[i]) + v2he_head[i] = D(hemap[k] => v for (k, v) in v2he_head[i]) + end + + unique_hgids = sort(collect(Set(hg.hypergraph_ids[part]))) + for (i, e) in enumerate(unique_hgids) + hgmap[e] = i + end + + hypergraph_ids = [hgmap[x] for x in hg.hypergraph_ids[part]] + + push!( + res, + HGNNHypergraph{T,D}( + Hypergraph{T, Nothing, Nothing, D}( + v2he_tail, + he2v_tail, + Vector{Nothing}(nothing, length(part)), + Vector{Nothing}(nothing, length(newhe_tail)) + ), + Hypergraph{T, Nothing, Nothing, D}( + v2he_head, + he2v_head, + Vector{Nothing}(nothing, length(part)), + Vector{Nothing}(nothing, length(newhe_head)) + ), + length(part), + length(he2v_tail), + length(unique_hgids), + hypergraph_ids, + getobs(hg.vdata, part), + getobs(hg.hedata, collect(keys(hemap))), + getobs(hg.hgdata, unique_hgids) + ) + ) + end + + return res + +end + +function random_split_hyperedges( + hg::HGNNHypergraph{T,D}, + fracs::AbstractVector{<:Real}, + rng::AbstractRNG +) where {T <: Real, D <: AbstractDict{Int, T}} + # For all f ∈ fracs, 0 < f <= 1 + @assert all(fracs .> 0) && all(fracs .<= 1) + # Fractions must sum to 1 + @assert abs(sum(fracs) - 1) <= 1e-5 + + num_choices = round.(fracs .* hg.num_hyperedges) + rand_inds = shuffle(rng, Vector(1:hg.num_hyperedges)) + + partitions = Vector{Int}[] + start_point = 1 + + # Provide the (approximate) right amount of (randomly selected) vertex indices per partition + for i in 1:(length(num_choices) - 1) + part = rand_inds[start_point:start_point + num_choices[i] - 1] + start_point += num_choices + push!(partitions, sort(part)) + end + push!(partitions, sort(rand_inds[start_point:end])) + + res = HGNNHypergraph{T,D}[] + + # Partition v2he and he2v, being careful of indices + for part in partitions + he2v = hg.he2v[part] + v2he = D[] + + hemap = Dict{Int, Int}(x => i for (i, x) in enumerate(part)) + vmap = Dict{Int, Int}() + hgmap = Dict{Int, Int}() + + for (i, v) in enumerate(hg.v2he) + newv = filter(((key,val), ) -> key in part, v) + if length(newv) > 0 + newv = D(hemap[key] => val for (key, val) in newv) + push!(v2he, newv) + vmap[i] = length(v2he) + end + end + + for i in eachindex(he2v) + he2v[i] = D(vmap[key] => val for (key, val) in he2v[i]) + end + + rel_vs = collect(keys(vmap)) + + unique_hgids = sort(collect(Set(hg.hypergraph_ids[rel_vs]))) + for (i, e) in enumerate(unique_hgids) + hgmap[e] = i + end + + hypergraph_ids = [hgmap[x] for x in hg.hypergraph_ids[rel_vs]] + + push!( + res, + HGNNHypergraph{T,D}( + v2he, + he2v, + length(v2he), + length(he2v), + length(unique_hgids), + hypergraph_ids, + getobs(hg.vdata, rel_vs), + getobs(hg.hedata, part), + getobs(hg.hgdata, unique_hgids) + ) + ) + end + + return res +end + +function random_split_hyperedges( + hg::HGNNDiHypergraph{T,D}, + fracs::AbstractVector{<:Real}, + rng::AbstractRNG +) where {T <: Real, D <: AbstractDict{Int, T}} + # For all f ∈ fracs, 0 < f <= 1 + @assert all(fracs .> 0) && all(fracs .<= 1) + # Fractions must sum to 1 + @assert abs(sum(fracs) - 1) <= 1e-5 + + num_choices = round.(fracs .* hg.num_hyperedges) + rand_inds = shuffle(rng, Vector(1:hg.num_hyperedges)) + + partitions = Vector{Int}[] + start_point = 1 + + # Provide the (approximate) right amount of (randomly selected) vertex indices per partition + for i in 1:(length(num_choices) - 1) + part = rand_inds[start_point:start_point + num_choices[i] - 1] + start_point += num_choices + push!(partitions, sort(part)) + end + push!(partitions, sort(rand_inds[start_point:end])) + + res = HGNNDiHypergraph{T,D}[] + + # Partition v2he and he2v, being careful of indices + for part in partitions + he2v_tail = hg.hg_tail.he2v[part] + he2v_head = hg.hg_head.he2v[part] + + v2he_tail = D[] + v2he_head = D[] + + vmap = Dict{Int, Int}() + hemap = Dict{Int, Int}(x => i for (i, x) in enumerate(part)) + hgmap = Dict{Int, Int}() + + for i in 1:hg.num_vertices + v_tail = hg.hg_tail.v2he[i] + v_head = hg.hg_head.v2he[i] + + newv_tail = filter(((k,v), ) -> k in part, v_tail) + newv_head = filter(((k,v), ) -> k in part, v_head) + if length(newv_tail) > 0 || length(newv_head) > 0 + newv_tail = D(vmap[k] => v for (k, v) in newv_tail) + newv_head = D(vmap[k] => v for (k, v) in newv_head) + push!(v2he_tail, newv_tail) + push!(v2he_tail, newv_head) + vmap[i] = length(v2he) + end + end + + for i in eachindex(he2v_tail) + he2v_tail[i] = D(vmap[k] => v for (k, v) in he2v_tail[i]) + he2v_head[i] = D(vmap[k] => v for (k, v) in he2v_head[i]) + end + + unique_hgids = sort(collect(Set(hg.hypergraph_ids[part]))) + for (i, e) in enumerate(unique_hgids) + hgmap[e] = i + end + + rel_vs = collect(keys(vmap)) + unique_hgids = sort(collect(Set(hg.hypergraph_ids[rel_vs]))) + for (i, e) in enumerate(unique_hgids) + hgmap[e] = i + end + + hypergraph_ids = [hgmap[x] for x in hg.hypergraph_ids[rel_vs]] + + push!( + res, + HGNNHypergraph{T,D}( + Hypergraph{T, Nothing, Nothing, D}( + v2he_tail, + he2v_tail, + Vector{Nothing}(nothing, length(newv_tail)), + Vector{Nothing}(nothing, length(part)) + ), + Hypergraph{T, Nothing, Nothing, D}( + v2he_head, + he2v_head, + Vector{Nothing}(nothing, length(newv_head)), + Vector{Nothing}(nothing, length(part)) + ), + length(v2he_tail), + length(part), + length(unique_hgids), + hypergraph_ids, + getobs(hg.vdata, collect(keys(vmap))), + getobs(hg.hedata, part), + getobs(hg.hgdata, unique_hgids) + ) + ) + end + + return res +end + +function random_split_hypergraphs( + hg::HGNNHypergraph{T,D}, + fracs::AbstractVector{<:Real}, + rng::AbstractRNG +) where {T <: Real, D <: AbstractDict{Int, T}} + # For all f ∈ fracs, 0 < f <= 1 + @assert all(fracs .> 0) && all(fracs .<= 1) + # Fractions must sum to 1 + @assert abs(sum(fracs) - 1) <= 1e-5 + + num_choices = round.(fracs .* hg.num_hypergraphs) + rand_inds = shuffle(rng, Vector(1:hg.num_hypergraphs)) + + partitions = Vector{Int}[] + start_point = 1 + + # Provide the (approximate) right amount of (randomly selected) vertex indices per partition + for i in 1:(length(num_choices) - 1) + part = rand_inds[start_point:start_point + num_choices[i] - 1] + start_point += num_choices + push!(partitions, sort(part)) + end + push!(partitions, sort(rand_inds[start_point:end])) + + res = HGNNHypergraph{T,D}[] + + # Partition v2he and he2v, being careful of indices + for part in partitions + rel_vs = [i for (i, e) in enumerate(hg.hypergraph_ids) if e in part] + + v2he = hg.v2he[rel_vs] + he2v = D[] + + vmap = Dict{Int, Int}(x => i for (i, x) in enumerate(rel_vs)) + hemap = Dict{Int, Int}() + hgmap = Dict{Int, Int}(x => i for (i, x) in enumerate(part)) + + for (i, he) in enumerate(hg.he2v) + newhe = filter(((k,v), ) -> k in rel_vs, he) + if length(newhe) > 0 + newhe = D(vmap[k] => v for (k, v) in newhe) + push!(he2v, newhe) + hemap[i] = length(he2v) + end + end + + for i in eachindex(v2he) + v2he[i] = D(hemap[k] => v for (k, v) in v2he[i]) + end + + hypergraph_ids = [hgmap[hg.hypergraph_ids[v]] for v in rel_vs] + + push!( + res, + HGNNHypergraph{T,D}( + v2he, + he2v, + length(v2he), + length(he2v), + length(part), + hypergraph_ids, + getobs(hg.vdata, rel_vs), + getobs(hg.hedata, collect(keys(hemap))), + getobs(hg.hgdata, part) + ) + ) + end + + return res +end + +function random_split_hypergraphs( + hg::HGNNDiHypergraph{T,D}, + fracs::AbstractVector{<:Real}, + rng::AbstractRNG +) where {T <: Real, D <: AbstractDict{Int, T}} + # For all f ∈ fracs, 0 < f <= 1 + @assert all(fracs .> 0) && all(fracs .<= 1) + # Fractions must sum to 1 + @assert abs(sum(fracs) - 1) <= 1e-5 + + num_choices = round.(fracs .* hg.num_hypergraphs) + rand_inds = shuffle(rng, Vector(1:hg.num_hypergraphs)) + + partitions = Vector{Int}[] + start_point = 1 + + # Provide the (approximate) right amount of (randomly selected) vertex indices per partition + for i in 1:(length(num_choices) - 1) + part = rand_inds[start_point:start_point + num_choices[i] - 1] + start_point += num_choices + push!(partitions, sort(part)) + end + push!(partitions, sort(rand_inds[start_point:end])) + + res = HGNNDiHypergraph{T,D}[] + + # Partition v2he and he2v, being careful of indices + for part in partitions + rel_vs = [i for (i, e) in enumerate(hg.hypergraph_ids) if e in part] + + v2he_tail = hg.hg_tail.v2he[rel_vs] + v2he_head = hg.hg_head.v2he[rel_vs] + he2v_tail = D[] + he2v_head = D[] + + vmap = Dict{Int, Int}(x => i for (i, x) in enumerate(rel_vs)) + hemap = Dict{Int, Int}() + hgmap = Dict{Int, Int}(x => i for (i, x) in enumerate(part)) + + for i in 1:hg.num_hyperedges + newhe_tail = filter(((k,v), ) -> k in rel_vs, hg.hg_tail.he2v[i]) + newhe_head = filter(((k,v), ) -> k in rel_vs, hg.hg_head.he2v[i]) + + if length(newhe_tail) > 0 || length(newhe_head) > 0 + newhe_tail = D(vmap[k] => v for (k, v) in newhe_tail) + newhe_head = D(vmap[k] => v for (k, v) in newhe_head) + push!(he2v_tail, newhe_tail) + push!(he2v_head, newhe_head) + hemap[i] = length(he2v) + end + end + + for i in eachindex(v2he_tail) + v2he_tail[i] = D(hemap[k] => v for (k, v) in v2he_tail[i]) + v2he_head[i] = D(hemap[k] => v for (k, v) in v2he_head[i]) + end + + hypergraph_ids = [hgmap[x] for x in hg.hypergraph_ids[rel_vs]] + + push!( + res, + HGNNHypergraph{T,D}( + Hypergraph{T, Nothing, Nothing, D}( + v2he_tail, + he2v_tail, + Vector{Nothing}(nothing, length(newv_tail)), + Vector{Nothing}(nothing, length(part)) + ), + Hypergraph{T, Nothing, Nothing, D}( + v2he_head, + he2v_head, + Vector{Nothing}(nothing, length(newv_head)), + Vector{Nothing}(nothing, length(part)) + ), + length(v2he_tail), + length(he2v_tail), + length(part), + hypergraph_ids, + getobs(hg.vdata, rel_vs), + getobs(hg.hedata, collect(keys(hemap))), + getobs(hg.hgdata, part) + ) + ) + end + + return res +end