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