Skip to content

Commit 8dd7696

Browse files
Provide better support for multiplex graphs (#24)
1 parent 3f6ef48 commit 8dd7696

18 files changed

+1292
-431
lines changed

docs/src/index.md

Lines changed: 99 additions & 74 deletions
Large diffs are not rendered by default.

src/MultilayerGraphs.jl

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ export getindex,
44
δ_Ω,
55
tensoreig,
66
AbstractMultilayerGraph,
7+
AbstractMultilayerUGraph,
8+
AbstractMultilayerDiGraph,
79
IsWeighted,
810
MultilayerGraph,
911
MultilayerDiGraph,
10-
MultilayerGraph_m,
11-
MultilayerGraph_mcsc,
12-
MultilayerGraph_csc,
1312
MultiplexGraph,
13+
MultiplexDiGraph,
1414
AbstractVertex,
1515
AbstractMultilayerVertex,
1616
MultilayerVertex,
@@ -77,7 +77,13 @@ include("interlayer.jl")
7777
include("graphs_extensions.jl")
7878
include("graph_of_graphs.jl")
7979
include("abstractmultilayergraph.jl")
80-
include("multilayerdigraph.jl")
80+
include("abstractmultilayerugraph.jl")
81+
include("abstractmultilayerdigraph.jl")
82+
include("abstractmultiplexugraph.jl")
83+
include("abstractmultiplexdigraph.jl")
8184
include("multilayergraph.jl")
85+
include("multiplexgraph.jl")
86+
include("multilayerdigraph.jl")
87+
include("multiplexdigraph.jl")
8288

8389
end

src/abstractmultilayerdigraph.jl

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""
2+
AbstractMultilayerDiGraph{T,U} <: AbstractMultilayerGraph{T,U}
3+
4+
Abstract type representing an undirected multilayer graph.
5+
"""
6+
abstract type AbstractMultilayerDiGraph{T,U} <: AbstractMultilayerGraph{T,U} end
7+
8+
"""
9+
add_layer!(mg::M,layer::L; interlayers_type = "multiplex") where { T, undef, M <: AbstractMultilayerDiGraph{T, G, U}, L <: Layer{T,U,G}}
10+
11+
Add layer `layer` to `mg`. Also add `Interlayer`s of type `interlayers_type` (can only be `"multiplex"`) between the new layer and all the other ones.
12+
"""
13+
function add_layer!(
14+
mg::M, new_layer::L; new_default_interlayers_type="multiplex"
15+
) where {T,U,G<:AbstractGraph{T},M<:AbstractMultilayerDiGraph{T,U},L<:Layer{T,U,G}}
16+
#Check that the layer is directed
17+
istrait(IsDirected{typeof(new_layer.graph)}) || throw(
18+
ErrorException(
19+
"The `new_layer`'s underlying graph $(new_layer.graph) is undirected, so it is not compatible with a `AbstractMultilayerDiGraph`.",
20+
),
21+
)
22+
23+
if new_default_interlayers_type == "multiplex"
24+
_add_layer!(mg, new_layer; new_default_interlayers_type=SimpleDiGraph{T})
25+
end
26+
end
27+
28+
"""
29+
specify_interlayer!(mg::M, new_interlayer::In; symmetric_interlayer_name::Symbol) where { T, U, G<: AbstractGraph{T}, M <: AbstractMultilayerDiGraph{T, U}, In <: Interlayer{T,G}; IsDirected{M}}
30+
31+
Specify the interlayer `new_interlayer` as part of `mg`. The underlying graph of `new_interlayer` must be directed.
32+
"""
33+
function specify_interlayer!(
34+
mg::M,
35+
new_interlayer::In;
36+
symmetric_interlayer_name::String="interlayer_$(new_interlayer.layer_2)_$(new_interlayer.layer_1)",
37+
) where {T,U,G<:AbstractGraph{T},M<:AbstractMultilayerDiGraph{T,U},In<:Interlayer{T,U,G}}
38+
istrait(IsDirected{typeof(new_interlayer.graph)}) || throw(
39+
ErrorException(
40+
"The `new_interlayer`'s underlying graph $(new_interlayer.graph) is undirected, so it is not compatible with a `AbstractMultilayerDiGraph`.",
41+
),
42+
)
43+
return _specify_interlayer!(
44+
mg, new_interlayer; symmetric_interlayer_name=symmetric_interlayer_name
45+
)
46+
end
47+
48+
# Graphs.jl's internals extra overrides
49+
function Graphs.degree(
50+
mg::M, v::V
51+
) where {T,M<:AbstractMultilayerDiGraph{T,<:Real},V<:MultilayerVertex{T}}
52+
return indegree(mg, v) + outdegree(mg, v)
53+
end
54+
55+
"""
56+
add_edge!(mg::M, me::E) where { T, U <: Real, M <: AbstractMultilayerDiGraph{T,U}, E <: MultilayerEdge{MultilayerVertex{T},U}}
57+
58+
Add weighted edge `me` to `mg`.
59+
"""
60+
function Graphs.add_edge!(
61+
mg::M, me::E
62+
) where {
63+
T,U<:Real,M<:AbstractMultilayerDiGraph{T,U},E<:MultilayerEdge{MultilayerVertex{T},U}
64+
}
65+
return add_edge!(mg, src(me), dst(me), weight(me))
66+
end
67+
68+
"""
69+
add_edge!(mg::M, me::E) where { T, U, M <: AbstractMultilayerDiGraph{T,U}, E <: MultilayerEdge{MultilayerVertex{T},Nothing}}
70+
71+
Add unweighted edge `me` to `mg`.
72+
"""
73+
function Graphs.add_edge!(
74+
mg::M, me::E
75+
) where {
76+
T,U,M<:AbstractMultilayerDiGraph{T,U},E<:MultilayerEdge{MultilayerVertex{T},Nothing}
77+
}
78+
return add_edge!(mg, src(me), dst(me))
79+
end
80+
81+
"""
82+
is_directed(mg::M) where { M <: AbstractMultilayerDiGraph}
83+
84+
Returns `true` if `mg` is directed, `false` otherwise.
85+
"""
86+
Graphs.is_directed(mg::M) where {M<:AbstractMultilayerDiGraph} = true
87+
88+
"""
89+
is_directed(mg::M) where { M <: Type{ <: AbstractMultilayerDiGraph}}
90+
91+
Returns `true` if `mg` is directed, `false` otherwise.
92+
"""
93+
Graphs.is_directed(mg::M) where {M<:Type{<:AbstractMultilayerDiGraph}} = true
94+
95+
# TODO:
96+
# it may be not well inferred
97+
# function get_projected_monoplex_graph end #approach taken from https://github.com/JuliaGraphs/Graphs.jl/blob/7152d540631219fd51c43ab761ec96f12c27680e/src/core.jl#L124
98+
"""
99+
get_projected_monoplex_graph(mg::M) where {M<: AbstractMultilayerDiGraph}
100+
101+
Get projected monoplex graph (i.e. the graph that as the same nodes as `mg` but the link between node `i` and `j` has weight equal to the sum of all edges weights between the various vertices representing `i` and `j` in `mg`, accounting for both layers and interlayers). See [De Domenico et al. (2013)](https://doi.org/10.1103/PhysRevX.3.041022).
102+
"""
103+
function get_projected_monoplex_graph(mg::M) where {M<:AbstractMultilayerDiGraph}
104+
projected_monoplex_adjacency_matrix = dropdims(
105+
sum(mg.adjacency_tensor; dims=(3, 4)); dims=(3, 4)
106+
) #::Matrix{eltype(mg.adjacency_tensor)}
107+
return SimpleWeightedDiGraph{M.parameters[1],M.parameters[2]}(
108+
projected_monoplex_adjacency_matrix
109+
)
110+
end
111+
112+
# function get_overlay_monoplex_graph end #approach taken from https://github.com/JuliaGraphs/Graphs.jl/blob/7152d540631219fd51c43ab761ec96f12c27680e/src/core.jl#L124
113+
"""
114+
get_overlay_monoplex_graph(mg::M) where {M<: AbstractMultilayerDiGraph}
115+
116+
Get overlay monoplex graph (i.e. the graph that has the same nodes as `mg` but the link between node `i` and `j` has weight equal to the sum of all edges weights between the various vertices representing `i` and `j` in `mg`, accounting for both layers and interlayers). See [De Domenico et al. (2013)](https://doi.org/10.1103/PhysRevX.3.041022).
117+
"""
118+
function get_overlay_monoplex_graph(mg::M) where {M<:AbstractMultilayerDiGraph}
119+
projected_overlay_adjacency_matrix = sum([
120+
mg.adjacency_tensor[:, :, i, i] for i in 1:size(mg.adjacency_tensor, 3)
121+
])
122+
return SimpleWeightedDiGraph{M.parameters[1],M.parameters[2]}(
123+
projected_overlay_adjacency_matrix
124+
)
125+
end

src/abstractmultilayergraph.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,3 +747,29 @@ function Graphs.modularity(
747747

748748
return (1 / K) * ein"ija,ikjm,kma->"(S, B, S)[]
749749
end
750+
751+
#function get_graph_of_layers end #approach taken from https://github.com/JuliaGraphs/Graphs.jl/blob/7152d540631219fd51c43ab761ec96f12c27680e/src/core.jl#L124
752+
"""
753+
get_graph_of_layers(mg::M) where {M <: AbstractMultilayerGraph}
754+
755+
Get a [`DiGraphOfGraph`](@ref) of the layers of `mg`. the weight of each edge between layers are obtained by summing all edge weights in the corresponding interlayer. See [De Domenico et al. (2013)](https://doi.org/10.1103/PhysRevX.3.041022).
756+
"""
757+
function get_graph_of_layers(
758+
mg::M; normalization::String="total_edges"
759+
) where {M<:AbstractMultilayerGraph}
760+
num_layers = length(mg.layers)
761+
norm_factor = 1
762+
if cmp(normalization, "total_edges") == 0
763+
norm_factor = ne(mg)
764+
end
765+
adjacency_matrix = reshape(
766+
[
767+
i != j ? (1 / norm_factor) * sum(mg.adjacency_tensor[:, :, i, j]) : 0.0 for
768+
i in 1:num_layers for j in 1:num_layers
769+
],
770+
(num_layers, num_layers),
771+
)
772+
return DiGraphOfGraphs(
773+
getproperty.(collect(values(mg.layers)), :graph), adjacency_matrix
774+
)
775+
end

src/abstractmultilayerugraph.jl

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
2+
"""
3+
AbstractMultilayerUGraph{T,U} <: AbstractMultilayerGraph{T,U}
4+
5+
Abstract type representing an undirected multilayer graph.
6+
"""
7+
abstract type AbstractMultilayerUGraph{T,U} <: AbstractMultilayerGraph{T,U} end
8+
9+
"""
10+
add_layer!(mg::M,layer::L; interlayers_type = "multiplex") where { T, U, G<: AbstractGraph{T}, M <: AbstractMultilayerUGraph{T, U}, L <: Layer{T,U,G}}
11+
12+
Add layer `layer` to `mg`. Also add `Interlayer`s of type `interlayers_type` (can only be `"multiplex"`) between the new layer and all the other ones.
13+
"""
14+
function add_layer!(
15+
mg::M, new_layer::L; new_default_interlayers_type="multiplex"
16+
) where {T,U,G<:AbstractGraph{T},M<:AbstractMultilayerUGraph{T,U},L<:Layer{T,U,G}}
17+
# Check that the layer is directed
18+
!istrait(IsDirected{typeof(new_layer.graph)}) || throw(
19+
ErrorException(
20+
"The `new_layer`'s underlying graph $(new_layer.graph) is directed, so it is not compatible with a `AbstractMultilayerUGraph`.",
21+
),
22+
)
23+
24+
if new_default_interlayers_type == "multiplex"
25+
_add_layer!(mg, new_layer; new_default_interlayers_type=SimpleGraph{T})
26+
end
27+
end
28+
29+
"""
30+
specify_interlayer!(mg::M, new_interlayer::In; symmetric_interlayer_name::String) where { T, U, G<: AbstractGraph{T}, M <: AbstractMultilayerUGraph{T, U}, In <: Interlayer{T,U,G}}
31+
32+
Specify the interlayer `new_interlayer` as part of `mg`. The underlying graph of `new_interlayer` must be undirected.
33+
"""
34+
function specify_interlayer!(
35+
mg::M,
36+
new_interlayer::In;
37+
symmetric_interlayer_name::String="interlayer_$(new_interlayer.layer_2)_$(new_interlayer.layer_1)",
38+
) where {T,U,G<:AbstractGraph{T},M<:AbstractMultilayerUGraph{T,U},In<:Interlayer{T,U,G}}
39+
40+
# This error could be removed since we are already dispatching on trait
41+
!istrait(IsDirected{typeof(new_interlayer.graph)}) || throw(
42+
ErrorException(
43+
"The `new_interlayer`'s underlying graphs $(new_interlayer.graph) is directed, so it is not compatible with a `AbstractMultilayerUGraph`.",
44+
),
45+
)
46+
47+
return _specify_interlayer!(
48+
mg, new_interlayer; symmetric_interlayer_name=symmetric_interlayer_name
49+
)
50+
end
51+
52+
# Graphs.jl's internals extra overrides
53+
function Graphs.degree(
54+
mg::M, v::V
55+
) where {T,M<:AbstractMultilayerUGraph{T,<:Real},V<:MultilayerVertex{T}}
56+
return indegree(mg, v)
57+
end
58+
59+
"""
60+
add_edge!(mg::M, me::E) where { T, U <: Real, M <: AbstractMultilayerUGraph{T,U}, E <: MultilayerEdge{MultilayerVertex{T},U}}
61+
62+
Add weighted edge `me` to `mg`.
63+
"""
64+
function Graphs.add_edge!(
65+
mg::M, me::E
66+
) where {
67+
T,U<:Real,M<:AbstractMultilayerUGraph{T,U},E<:MultilayerEdge{MultilayerVertex{T},U}
68+
}
69+
return add_edge!(mg, src(me), dst(me), weight(me))
70+
end
71+
72+
"""
73+
add_edge!(mg::M, me::E) where { T, U, M <: AbstractMultilayerUGraph{T,U}, E <: MultilayerEdge{MultilayerVertex{T},Nothing}}
74+
75+
Add unweighted edge `me` to `mg`.
76+
"""
77+
function Graphs.add_edge!(
78+
mg::M, me::E
79+
) where {
80+
T,U,M<:AbstractMultilayerUGraph{T,U},E<:MultilayerEdge{MultilayerVertex{T},Nothing}
81+
}
82+
return add_edge!(mg, src(me), dst(me))
83+
end
84+
85+
"""
86+
is_directed(m::M) where { M <: AbstractMultilayerUGraph}
87+
88+
Returns `true` if `m` is directed, `false` otherwise.
89+
"""
90+
Graphs.is_directed(m::M) where {M<:AbstractMultilayerUGraph} = false
91+
92+
"""
93+
is_directed(m::M) where { M <: Type{ <: AbstractMultilayerUGraph}}
94+
95+
Returns `true` if `m` is directed, `false` otherwise.
96+
"""
97+
Graphs.is_directed(m::M) where {M<:Type{<:AbstractMultilayerUGraph}} = false
98+
99+
# Multilayer-specific functions
100+
# TODO:
101+
# it may be not well inferred
102+
# function get_projected_monoplex_graph end #approach taken from https://github.com/JuliaGraphs/Graphs.jl/blob/7152d540631219fd51c43ab761ec96f12c27680e/src/core.jl#L124
103+
"""
104+
get_projected_monoplex_graph(mg::M) where {M<: AbstractMultilayerUGraph}
105+
106+
Get projected monoplex graph (i.e. that graph that as the same nodes as `mg` but the link between node `i` and `j` has weight equal to the sum of all edges weights between the various vertices representing `i` and `j` in `mg`, accounting for both layers and interlayers). See [De Domenico et al. (2013)](https://doi.org/10.1103/PhysRevX.3.041022).
107+
"""
108+
function get_projected_monoplex_graph(mg::M) where {M<:AbstractMultilayerUGraph}
109+
projected_monoplex_adjacency_matrix = dropdims(
110+
sum(mg.adjacency_tensor; dims=(3, 4)); dims=(3, 4)
111+
)
112+
113+
isapproxsymmetric(projected_monoplex_adjacency_matrix) ||
114+
throw(ErrorException("Adjacency / distance matrices must be symmetric"))
115+
116+
symmetric_projected_monoplex_adjacency_matrix = Symmetric(
117+
projected_monoplex_adjacency_matrix
118+
)
119+
120+
return SimpleWeightedGraph{M.parameters[1],M.parameters[2]}(
121+
symmetric_projected_monoplex_adjacency_matrix
122+
)
123+
end
124+
125+
# function get_overlay_monoplex_graph end #approach taken from https://github.com/JuliaGraphs/Graphs.jl/blob/7152d540631219fd51c43ab761ec96f12c27680e/src/core.jl#L124
126+
"""
127+
get_overlay_monoplex_graph(mg::M) where {M<: AbstractMultilayerUGraph}
128+
129+
130+
Get overlay monoplex graph (i.e. the graph that has the same nodes as `mg` but the link between node `i` and `j` has weight equal to the sum of all edges weights between the various vertices representing `i` and `j` in `mg`, accounting for only within-layer edges). See [De Domenico et al. (2013)](https://doi.org/10.1103/PhysRevX.3.041022).
131+
"""
132+
function get_overlay_monoplex_graph(mg::M) where {M<:AbstractMultilayerUGraph}
133+
projected_overlay_adjacency_matrix = sum([
134+
mg.adjacency_tensor[:, :, i, i] for i in 1:size(mg.adjacency_tensor, 3)
135+
])
136+
return SimpleWeightedGraph{M.parameters[1],M.parameters[2]}(
137+
projected_overlay_adjacency_matrix
138+
)
139+
end
140+
141+
"""
142+
von_neumann_entropy(mg::M) where {T,U, M <: AbstractMultilayerUGraph{T, U}}
143+
144+
Compute the Von Neumann entropy of `mg`, according to [De Domenico et al. (2013)](https://doi.org/10.1103/PhysRevX.3.041022). Only for undirected multilayer graphs.
145+
"""
146+
function von_neumann_entropy(mg::M) where {T,U,M<:AbstractMultilayerUGraph{T,U}}
147+
num_nodes = length(nodes(mg))
148+
num_layers = length(mg.layers)
149+
150+
# multistrength tensor
151+
Δ = ein"ijkm,ik,nj,om -> njom"(
152+
mg.adjacency_tensor,
153+
ones(T, size(mg.adjacency_tensor)[[1, 3]]),
154+
δ(num_nodes),
155+
δ(num_layers),
156+
)
157+
158+
# trace of Δ
159+
tr_Δ = ein"iikk->"(Δ)[]
160+
161+
# multilayer laplacian tensor
162+
L = Δ .- mg.adjacency_tensor
163+
164+
# multilayer density tensor
165+
ρ = (1.0 / tr_Δ) .* L
166+
167+
eigvals, eigvects = tensoreig(ρ, [2, 4], [1, 3])
168+
169+
#= # Check that we are calculating the right eigenvalues
170+
lhs = ein"ijkm,ik -> jm"(ρ,eigvects[:,:,1])
171+
rhs = eigvals[1].*eigvects[:,:,1]
172+
@assert all(lhs .≈ rhs)
173+
# Indeed we are =#
174+
175+
Λ = get_diagonal_adjacency_tensor(eigvals, size(mg.adjacency_tensor))
176+
177+
# Correct for machine precision
178+
Λ[Λ .< eps()] .= 0.0
179+
180+
log2Λ = log2.(Λ)
181+
182+
log2Λ[isinf.(log2Λ)] .= 0
183+
184+
return -ein"ijkm,jimk ->"(Λ, log2Λ)[]
185+
end

0 commit comments

Comments
 (0)