Skip to content

Commit d8479a7

Browse files
authored
Add random temporal hyperbolic graph (#313)
* Add `rand_temporal_hyperbolic_graph` * Export `rand_temporal_hyperbolic_graph` * Add docstring * Add more spaces * Improve writing
1 parent 6dbaefc commit d8479a7

File tree

3 files changed

+113
-1
lines changed

3 files changed

+113
-1
lines changed

src/GNNGraphs/GNNGraphs.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ export rand_graph,
9090
rand_bipartite_heterograph,
9191
knn_graph,
9292
radius_graph,
93-
rand_temporal_radius_graph
93+
rand_temporal_radius_graph,
94+
rand_temporal_hyperbolic_graph
9495

9596
include("sampling.jl")
9697
export sample_neighbors

src/GNNGraphs/generate.jl

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,99 @@ function rand_temporal_radius_graph(number_nodes::Int,
362362
end
363363
return TemporalSnapshotsGNNGraph(tg)
364364
end
365+
366+
367+
function _hyperbolic_distance(nodeA::Array{Float64, 1},nodeB::Array{Float64, 1}; ζ::Real)
368+
if nodeA != nodeB
369+
a = cosh* nodeA[1]) * cosh* nodeB[1])
370+
b = sinh* nodeA[1]) * sinh* nodeB[1])
371+
c = cos(pi - abs(pi - abs(nodeA[2] - nodeB[2])))
372+
d = acosh(a - (b * c)) / ζ
373+
else
374+
d = 0.0
375+
end
376+
return d
377+
end
378+
379+
"""
380+
rand_temporal_hyperbolic_graph(number_nodes::Int,
381+
number_snapshots::Int;
382+
α::Real,
383+
R::Real,
384+
speed::Real,
385+
ζ::Real=1,
386+
self_loop = false,
387+
kws...)
388+
389+
Create a random temporal graph given `number_nodes` nodes and `number_snapshots` snapshots.
390+
First, the positions of the nodes are generated with a quasi-uniform distribution (depending on the parameter `α`) in hyperbolic space within a disk of radius `R`. Two nodes are connected if their hyperbolic distance is less than `R`. Each following snapshot is created in order to keep the same initial distribution.
391+
392+
# Arguments
393+
394+
- `number_nodes`: The number of nodes of each snapshot.
395+
- `number_snapshots`: The number of snapshots.
396+
- `α`: The parameter that controls the position of the points. If `α=ζ`, the points are uniformly distributed on the disk of radius `R`. If `α>ζ`, the points are more concentrated in the center of the disk. If `α<ζ`, the points are more concentrated at the boundary of the disk.
397+
- `R`: The radius of the disk and of connection.
398+
- `speed`: The speed to update the nodes.
399+
- `ζ`: The parameter that controls the curvature of the disk.
400+
- `self_loops`: If `true`, consider the node itself among its neighbors, in which
401+
case the graph will contain self-loops.
402+
- `kws`: Further keyword arguments will be passed to the [`GNNGraph`](@ref) constructor of each snapshot.
403+
404+
# Example
405+
406+
```julia-repl
407+
julia> n, snaps, α, R, speed, ζ = 10, 5, 1.0, 4.0, 0.1, 1.0;
408+
409+
julia> thg = rand_temporal_hyperbolic_graph(n, snaps; α, R, speed, ζ)
410+
TemporalSnapshotsGNNGraph:
411+
num_nodes: [10, 10, 10, 10, 10]
412+
num_edges: [44, 46, 48, 42, 38]
413+
num_snapshots: 5
414+
```
415+
416+
# References
417+
Section D of the paper [Dynamic Hidden-Variable Network Models](https://arxiv.org/pdf/2101.00414.pdf) and the paper
418+
[Hyperbolic Geometry of Complex Networks](https://arxiv.org/pdf/1006.5169.pdf)
419+
"""
420+
function rand_temporal_hyperbolic_graph(number_nodes::Int,
421+
number_snapshots::Int;
422+
α::Real,
423+
R::Real,
424+
speed::Real,
425+
ζ::Real=1,
426+
self_loop = false,
427+
kws...)
428+
@assert number_snapshots > 1 "The number of snapshots must be greater than 1"
429+
@assert α > 0 "α must be greater than 0"
430+
431+
probabilities = rand(number_nodes)
432+
433+
points = Array{Float64}(undef,2,number_nodes)
434+
points[1,:].= (1/α) * acosh.(1 .+ (cosh* R) - 1) * probabilities)
435+
points[2,:].= 2 * pi * rand(number_nodes)
436+
437+
tg = Vector{GNNGraph}(undef, number_snapshots)
438+
439+
for time in 1:number_snapshots
440+
adj = zeros(number_nodes,number_nodes)
441+
for i in 1:number_nodes
442+
for j in 1:number_nodes
443+
if !self_loop && i==j
444+
continue
445+
elseif _hyperbolic_distance(points[:,i],points[:,j]; ζ) <= R
446+
adj[i,j] = adj[j,i] = 1
447+
end
448+
end
449+
end
450+
tg[time] = GNNGraph(adj)
451+
452+
probabilities .= probabilities .+ (2 * speed * rand(number_nodes) .- speed)
453+
probabilities[probabilities.>1] .= 1 .- (probabilities[probabilities .> 1] .% 1)
454+
probabilities[probabilities.<0] .= abs.(probabilities[probabilities .< 0])
455+
456+
points[1,:].= (1/α) * acosh.(1 .+ (cosh* R) - 1) * probabilities)
457+
points[2,:].= points[2,:] .+ (2 * speed * rand(number_nodes) .- speed)
458+
end
459+
return TemporalSnapshotsGNNGraph(tg)
460+
end

test/GNNGraphs/generate.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,19 @@ end
104104
r2 = 0.95
105105
tg2 = rand_temporal_radius_graph(number_nodes, number_snapshots, speed, r2)
106106
@test mean(mean(degree.(tg.snapshots)))<=mean(mean(degree.(tg2.snapshots)))
107+
end
108+
109+
@testset "rand_temporal_hyperbolic_graph" begin
110+
@test GraphNeuralNetworks.GNNGraphs._hyperbolic_distance([1.0,1.0],[1.0,1.0];ζ=1)==0
111+
@test GraphNeuralNetworks.GNNGraphs._hyperbolic_distance([0.23,0.11],[0.98,0.55];ζ=1)==GraphNeuralNetworks.GNNGraphs._hyperbolic_distance([0.98,0.55],[0.23,0.11];ζ=1)
112+
number_nodes = 30
113+
number_snapshots = 5
114+
α, R, speed, ζ = 1, 1, 0.1, 1
115+
116+
tg = rand_temporal_hyperbolic_graph(number_nodes, number_snapshots; α, R, speed, ζ)
117+
@test tg.num_nodes == [number_nodes for i in 1:number_snapshots]
118+
@test tg.num_snapshots == number_snapshots
119+
R = 10
120+
tg1 = rand_temporal_hyperbolic_graph(number_nodes, number_snapshots; α, R, speed, ζ)
121+
@test mean(mean(degree.(tg1.snapshots)))<=mean(mean(degree.(tg.snapshots)))
107122
end

0 commit comments

Comments
 (0)