From 86a8c4f0df7844e40309b40bad8a9a043d2dc08b Mon Sep 17 00:00:00 2001 From: jonaswa Date: Tue, 4 Jul 2023 17:38:21 +0200 Subject: [PATCH 01/16] Added edge-betweenness.jl to centralities --- src/centrality/edge-betweenness.jl | 102 +++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/centrality/edge-betweenness.jl diff --git a/src/centrality/edge-betweenness.jl b/src/centrality/edge-betweenness.jl new file mode 100644 index 000000000..4807c12bf --- /dev/null +++ b/src/centrality/edge-betweenness.jl @@ -0,0 +1,102 @@ +""" + edge_betweenness_centrality(g, k) + +Compute the [edge betweenness centrality](https://en.wikipedia.org/wiki/Centrality#Betweenness_centrality) of an edge `e`. +It is defined as the sum of the fraction of all-pairs shortest paths that pass through `e` +`` +bc(e) = \\sum_{s, t \\in V} +\\frac{\\sigma_{st}(e)}{\\sigma_{st}} +``. + +where `V`, is the set of nodes, \\frac{\\sigma_{st}} is the number of shortest-paths, and \\frac{\\sigma_{st}(e)} is the number of those paths passing through edge. + +### Optional Arguments +- `normalize=true`: If true, normalize the betweenness values by the +total number of possible distinct paths between all pairs in the graphs. +For an undirected graph, this number is ``\\frac{(|V|-1)(|V|-2)}{2}`` +and for a directed graph, ``{(|V|-1)(|V|-2)}``. + + +### References +- Brandes 2001 & Brandes 2008 + +# Examples +```jldoctest +julia> using Graphs + +julia> edge_betweenness_centrality(star_graph(5)) +Dict{Graphs.SimpleGraphs.SimpleEdge{Int64}, Float64} with 4 entries: + Edge 1 => 2 => 0.4 + Edge 1 => 3 => 0.4 + Edge 1 => 4 => 0.4 + Edge 1 => 5 => 0.4 + +julia> edge_betweenness_centrality(path_digraph(6)) +Dict{Graphs.SimpleGraphs.SimpleEdge{Int64}, Float64} with 5 entries: + Edge 4 => 5 => 0.266667 + Edge 1 => 2 => 0.166667 + Edge 3 => 4 => 0.3 + Edge 5 => 6 => 0.166667 + Edge 2 => 3 => 0.266667 +``` +""" + +function edge_betweenness_centrality( + g::AbstractGraph, vs=vertices(g), distmx::AbstractMatrix=weights(g); normalize=true +) + edge_betweenness = Dict(edges(g) .=> 0.0) + for o in vs + state = dijkstra_shortest_paths(g, o, distmx; allpaths=true, trackvertices=true) + _accumulate_edges!(edge_betweenness, state) + end + _rescale_e!(edge_betweenness, nv(g), normalize, is_directed(g)) + + return edge_betweenness +end + +function _accumulate_edges!(edge_betweenness::AbstractDict, state::Graphs.AbstractPathState) + σ = state.pathcounts + pred = state.predecessors + seen = state.closest_vertices + δ = Dict(seen .=> 0.0) + + while length(seen) > 0 + w = pop!(seen) + + coeff = (1.0 + δ[w]) / σ[w] + for v in pred[w] + c = σ[v] * coeff + if Edge(v, w) ∉ edge_betweenness.keys + edge_betweenness[Edge(w, v)] += c + else + edge_betweenness[Edge(v, w)] += c + δ[v] += c + end + end + end + return nothing +end + +function _rescale_e!( + edge_betweenness::AbstractDict, n::Integer, normalize::Bool, directed::Bool=false +) + if normalize + if n <= 1 + scale = nothing # no normalization b=0 for all nodes + else + scale = 1 / (n * (n - 1)) + end + else # rescale by 2 for undirected graphs + if !directed + scale = 0.5 + else + scale = nothing + end + end + if scale !== nothing + for (k, v) in edge_betweenness + edge_betweenness[k] *= scale + end + end + return nothing +end From a6c45608d1ead623e73c8f22d74387675300aa1a Mon Sep 17 00:00:00 2001 From: jwassmer <34073918+jwassmer@users.noreply.github.com> Date: Wed, 5 Jul 2023 07:08:41 +0200 Subject: [PATCH 02/16] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Simon Schölly --- src/centrality/edge-betweenness.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/centrality/edge-betweenness.jl b/src/centrality/edge-betweenness.jl index 4807c12bf..6cdb00b28 100644 --- a/src/centrality/edge-betweenness.jl +++ b/src/centrality/edge-betweenness.jl @@ -42,7 +42,7 @@ Dict{Graphs.SimpleGraphs.SimpleEdge{Int64}, Float64} with 5 entries: """ function edge_betweenness_centrality( - g::AbstractGraph, vs=vertices(g), distmx::AbstractMatrix=weights(g); normalize=true + g::AbstractGraph, vs=vertices(g), distmx::AbstractMatrix=weights(g); normalize::Bool=true ) edge_betweenness = Dict(edges(g) .=> 0.0) for o in vs @@ -66,7 +66,7 @@ function _accumulate_edges!(edge_betweenness::AbstractDict, state::Graphs.Abstra coeff = (1.0 + δ[w]) / σ[w] for v in pred[w] c = σ[v] * coeff - if Edge(v, w) ∉ edge_betweenness.keys + if Edge(v, w) ∉ keys(edge_betweenness) edge_betweenness[Edge(w, v)] += c else edge_betweenness[Edge(v, w)] += c From c56a44586ac2ff517bc8d1f99ddb3a7289c27c50 Mon Sep 17 00:00:00 2001 From: jonaswa Date: Wed, 5 Jul 2023 12:14:35 +0200 Subject: [PATCH 03/16] bc-dict to sparsematrix & new test for weighted g --- src/centrality/edge-betweenness.jl | 87 ++++++++++++++++-------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/src/centrality/edge-betweenness.jl b/src/centrality/edge-betweenness.jl index 6cdb00b28..5b179f88f 100644 --- a/src/centrality/edge-betweenness.jl +++ b/src/centrality/edge-betweenness.jl @@ -13,8 +13,8 @@ where `V`, is the set of nodes, \\frac{\\sigma_{st}} is the number of shortest-p ### Optional Arguments - `normalize=true`: If true, normalize the betweenness values by the total number of possible distinct paths between all pairs in the graphs. -For an undirected graph, this number is ``\\frac{(|V|-1)(|V|-2)}{2}`` -and for a directed graph, ``{(|V|-1)(|V|-2)}``. +For an undirected graph, this number is ``2/(|V|(|V|-1))`` +and for a directed graph, ````1/(|V|(|V|-1))````. ### References @@ -25,28 +25,42 @@ and for a directed graph, ``{(|V|-1)(|V|-2)}``. julia> using Graphs julia> edge_betweenness_centrality(star_graph(5)) -Dict{Graphs.SimpleGraphs.SimpleEdge{Int64}, Float64} with 4 entries: - Edge 1 => 2 => 0.4 - Edge 1 => 3 => 0.4 - Edge 1 => 4 => 0.4 - Edge 1 => 5 => 0.4 +5×5 SparseMatrixCSC{Float64, Int64} with 8 stored entries: + ⋅ 0.4 0.4 0.4 0.4 + 0.4 ⋅ ⋅ ⋅ ⋅ + 0.4 ⋅ ⋅ ⋅ ⋅ + 0.4 ⋅ ⋅ ⋅ ⋅ + 0.4 ⋅ ⋅ ⋅ ⋅ -julia> edge_betweenness_centrality(path_digraph(6)) -Dict{Graphs.SimpleGraphs.SimpleEdge{Int64}, Float64} with 5 entries: - Edge 4 => 5 => 0.266667 - Edge 1 => 2 => 0.166667 - Edge 3 => 4 => 0.3 - Edge 5 => 6 => 0.166667 - Edge 2 => 3 => 0.266667 +julia> edge_betweenness_centrality(path_digraph(6), normalize=false) +6×6 SparseMatrixCSC{Float64, Int64} with 5 stored entries: + ⋅ 5.0 ⋅ ⋅ ⋅ ⋅ + ⋅ ⋅ 8.0 ⋅ ⋅ ⋅ + ⋅ ⋅ ⋅ 9.0 ⋅ ⋅ + ⋅ ⋅ ⋅ ⋅ 8.0 ⋅ + ⋅ ⋅ ⋅ ⋅ ⋅ 5.0 + ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ + +julia>g = SimpleWeightedDiGraph([1, 2, 3, 2, 2], [2, 3, 1, 4, 1], [1, 2, 3, 4, 5]; combine=+) +julia>edge_betweenness_centrality(g; normalize=false) +4×4 SparseMatrixCSC{Float64, Int64} with 5 stored entries: + ⋅ 5.0 ⋅ ⋅ + 0.5 ⋅ 2.5 3.0 + 3.5 ⋅ ⋅ ⋅ + ⋅ ⋅ ⋅ ⋅ ``` """ - function edge_betweenness_centrality( - g::AbstractGraph, vs=vertices(g), distmx::AbstractMatrix=weights(g); normalize::Bool=true + g::AbstractGraph; + vs=vertices(g), + distmx::AbstractMatrix=weights(g), + normalize::Bool=true, ) - edge_betweenness = Dict(edges(g) .=> 0.0) - for o in vs - state = dijkstra_shortest_paths(g, o, distmx; allpaths=true, trackvertices=true) + edge_betweenness = spzeros(nv(g), nv(g)) + for source in vs + state = dijkstra_shortest_paths( + g, source, distmx; allpaths=true, trackvertices=true + ) _accumulate_edges!(edge_betweenness, state) end _rescale_e!(edge_betweenness, nv(g), normalize, is_directed(g)) @@ -54,7 +68,9 @@ function edge_betweenness_centrality( return edge_betweenness end -function _accumulate_edges!(edge_betweenness::AbstractDict, state::Graphs.AbstractPathState) +function _accumulate_edges!( + edge_betweenness::AbstractSparseMatrix, state::Graphs.AbstractPathState +) σ = state.pathcounts pred = state.predecessors seen = state.closest_vertices @@ -66,37 +82,28 @@ function _accumulate_edges!(edge_betweenness::AbstractDict, state::Graphs.Abstra coeff = (1.0 + δ[w]) / σ[w] for v in pred[w] c = σ[v] * coeff - if Edge(v, w) ∉ keys(edge_betweenness) - edge_betweenness[Edge(w, v)] += c - else - edge_betweenness[Edge(v, w)] += c - δ[v] += c - end + edge_betweenness[v, w] += c + δ[v] += c end end return nothing end function _rescale_e!( - edge_betweenness::AbstractDict, n::Integer, normalize::Bool, directed::Bool=false + edge_betweenness::AbstractSparseMatrix, + n::Integer, + normalize::Bool, + directed::Bool=false, ) + scale = 1 if normalize - if n <= 1 - scale = nothing # no normalization b=0 for all nodes - else - scale = 1 / (n * (n - 1)) + if n > 1 + scale *= 1 / (n * (n - 1)) end - else # rescale by 2 for undirected graphs if !directed - scale = 0.5 - else - scale = nothing - end - end - if scale !== nothing - for (k, v) in edge_betweenness - edge_betweenness[k] *= scale + scale *= 2 end end + edge_betweenness .*= scale return nothing end From 10b31bcfc2651f50a1ab1e7eb44f9d2a78f17474 Mon Sep 17 00:00:00 2001 From: jonaswa Date: Wed, 5 Jul 2023 13:17:09 +0200 Subject: [PATCH 04/16] fixed ebc over random subset of k vertices. --- src/centrality/edge-betweenness.jl | 37 +++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/centrality/edge-betweenness.jl b/src/centrality/edge-betweenness.jl index 5b179f88f..bf0e6a6e1 100644 --- a/src/centrality/edge-betweenness.jl +++ b/src/centrality/edge-betweenness.jl @@ -50,12 +50,20 @@ julia>edge_betweenness_centrality(g; normalize=false) ⋅ ⋅ ⋅ ⋅ ``` """ + +## +using Graphs, Random, SparseArrays, Plots +GLOBAL_RNG = Random.GLOBAL_RNG + +include("../../src/utils.jl") + function edge_betweenness_centrality( - g::AbstractGraph; - vs=vertices(g), - distmx::AbstractMatrix=weights(g), + g::AbstractGraph, + vs::AbstractArray=vertices(g), + distmx::AbstractMatrix=weights(g); normalize::Bool=true, ) + k = length(vs) edge_betweenness = spzeros(nv(g), nv(g)) for source in vs state = dijkstra_shortest_paths( @@ -63,11 +71,27 @@ function edge_betweenness_centrality( ) _accumulate_edges!(edge_betweenness, state) end - _rescale_e!(edge_betweenness, nv(g), normalize, is_directed(g)) + _rescale_e!(edge_betweenness, nv(g), normalize, is_directed(g), k) return edge_betweenness end +function edge_betweenness_centrality( + g::AbstractGraph, + k::Integer, + distmx::AbstractMatrix=weights(g); + normalize=true, + rng::Union{Nothing,AbstractRNG}=nothing, + seed::Union{Nothing,Integer}=nothing, +) + return edge_betweenness_centrality( + g, + sample(collect_if_not_vector(vertices(g)), k; rng=rng, seed=seed), + distmx; + normalize=normalize, + ) +end + function _accumulate_edges!( edge_betweenness::AbstractSparseMatrix, state::Graphs.AbstractPathState ) @@ -93,9 +117,10 @@ function _rescale_e!( edge_betweenness::AbstractSparseMatrix, n::Integer, normalize::Bool, - directed::Bool=false, + directed::Bool, + k::Integer, ) - scale = 1 + scale = n / k if normalize if n > 1 scale *= 1 / (n * (n - 1)) From b8d09d30b561dedfda41c576ef5e8d9403a7b465 Mon Sep 17 00:00:00 2001 From: jonaswa Date: Wed, 5 Jul 2023 14:13:44 +0200 Subject: [PATCH 05/16] added test-file for edge-betweenness --- test/centrality/edge-betweenness.jl | 116 ++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 test/centrality/edge-betweenness.jl diff --git a/test/centrality/edge-betweenness.jl b/test/centrality/edge-betweenness.jl new file mode 100644 index 000000000..87256ef59 --- /dev/null +++ b/test/centrality/edge-betweenness.jl @@ -0,0 +1,116 @@ +using Test +using Graphs +using Random +using StableRNGs +using DelimitedFiles + +include("../../src/centrality/edge-betweenness.jl") + +function testgraphs(g) + return if is_directed(g) + [g, DiGraph{UInt8}(g), DiGraph{Int16}(g)] + else + [g, Graph{UInt8}(g), Graph{Int16}(g)] + end +end +testgraphs(gs...) = vcat((testgraphs(g) for g in gs)...) +testdigraphs = testgraphs + +testdir = "test/" + +################################### + +## +rng = StableRNG(1) +# self loops +s1 = GenericGraph(SimpleGraph(Edge.([(1, 2), (2, 3), (3, 3)]))) +s2 = GenericDiGraph(SimpleDiGraph(Edge.([(1, 2), (2, 3), (3, 3)]))) + +g3 = GenericGraph(path_graph(5)) + +gint = loadgraph(joinpath(testdir, "testdata", "graph-50-500.jgz"), "graph-50-500") + +c = vec(readdlm(joinpath(testdir, "testdata", "graph-50-500-bc.txt"), ',')) +## + +for g in test_generic_graphs(gint) + z = @inferred(betweenness_centrality(g)) + @test map(Float32, z) == map(Float32, c) + + y = @inferred(betweenness_centrality(g, endpoints=true, normalize=false)) + @test round.(y[1:3], digits=4) == + round.([122.10760591498584, 159.0072453120582, 176.39547945994505], digits=4) + + x = @inferred(betweenness_centrality(g, 3, rng=rng)) + x2 = @inferred(betweenness_centrality(g, collect(1:20))) + + @test length(x) == 50 + @test length(x2) == 50 +end + +@test @inferred(betweenness_centrality(s1)) == [0, 1, 0] +@test @inferred(betweenness_centrality(s2)) == [0, 0.5, 0] + +g = GenericGraph(path_graph(2)) +z = @inferred(betweenness_centrality(g; normalize=true)) +@test z[1] == z[2] == 0.0 +z2 = @inferred(betweenness_centrality(g, vertices(g))) +z3 = @inferred(betweenness_centrality(g, collect(vertices(g)))) +@test z == z2 == z3 + +z = @inferred(betweenness_centrality(g3; normalize=false)) +@test z[1] == z[5] == 0.0 + +# Weighted Graph tests +g = GenericGraph(SimpleGraph(Edge.([(1, 2), (2, 3), (2, 5), (3, 4), (4, 5), (5, 6)]))) + +distmx = [ + 0.0 2.0 0.0 0.0 0.0 0.0 + 2.0 0.0 4.2 0.0 1.2 0.0 + 0.0 4.2 0.0 5.5 0.0 0.0 + 0.0 0.0 5.5 0.0 0.9 0.0 + 0.0 1.2 0.0 0.9 0.0 0.6 + 0.0 0.0 0.0 0.0 0.6 0.0 +] + +@test isapprox( + betweenness_centrality(g, vertices(g), distmx; normalize=false), + [0.0, 6.0, 0.0, 0.0, 6.0, 0.0], +) +@test isapprox( + betweenness_centrality(g, vertices(g), distmx; normalize=false, endpoints=true), + [5.0, 11.0, 5.0, 5.0, 11.0, 5.0], +) +@test isapprox( + betweenness_centrality(g, vertices(g), distmx; normalize=true), + [0.0, 0.6000000000000001, 0.0, 0.0, 0.6000000000000001, 0.0], +) +@test isapprox( + betweenness_centrality(g, vertices(g), distmx; normalize=true, endpoints=true), + [0.5, 1.1, 0.5, 0.5, 1.1, 0.5], +) + +adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph +a2 = SimpleDiGraph(adjmx2) +for g in test_generic_graphs(a2) + distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] + c2 = [0.24390243902439027, 0.27027027027027023, 0.1724137931034483] + @test isapprox( + betweenness_centrality(g, vertices(g), distmx2; normalize=false), [0.0, 1.0, 0.0] + ) + @test isapprox( + betweenness_centrality(g, vertices(g), distmx2; normalize=false, endpoints=true), + [4.0, 5.0, 4.0], + ) + @test isapprox( + betweenness_centrality(g, vertices(g), distmx2; normalize=true), [0.0, 0.5, 0.0] + ) + @test isapprox( + betweenness_centrality(g, vertices(g), distmx2; normalize=true, endpoints=true), + [2.0, 2.5, 2.0], + ) +end +# test #1405 / #1406 +g = GenericGraph(grid([50, 50])) +z = betweenness_centrality(g; normalize=false) +@test maximum(z) < nv(g) * (nv(g) - 1) \ No newline at end of file From 50b48c7cf9a68478ec16e28fcf99c29458416bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sch=C3=B6lly?= Date: Wed, 5 Jul 2023 10:19:18 +0200 Subject: [PATCH 06/16] Use GenericGraph for testing centrality algorithms (#272) --- src/centrality/betweenness.jl | 5 ++--- src/centrality/stress.jl | 2 +- src/core.jl | 20 +++++++++++++++----- test/centrality/betweenness.jl | 29 ++++++++++------------------- test/centrality/closeness.jl | 8 ++++---- test/centrality/degree.jl | 2 +- test/centrality/eigenvector.jl | 4 ++-- test/centrality/katz.jl | 2 +- test/centrality/pagerank.jl | 4 ++-- test/centrality/radiality.jl | 4 ++-- test/centrality/stress.jl | 4 ++-- 11 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/centrality/betweenness.jl b/src/centrality/betweenness.jl index 4ea88698c..150a29493 100644 --- a/src/centrality/betweenness.jl +++ b/src/centrality/betweenness.jl @@ -81,7 +81,7 @@ function betweenness_centrality( ) return betweenness_centrality( g, - sample(vertices(g), k; rng=rng, seed=seed), + sample(collect_if_not_vector(vertices(g)), k; rng=rng, seed=seed), distmx; normalize=normalize, endpoints=endpoints, @@ -124,8 +124,7 @@ function _accumulate_endpoints!( v1 = collect(Base.OneTo(n_v)) v2 = state.dists S = reverse(state.closest_vertices) - s = vertices(g)[si] - betweenness[s] += length(S) - 1 # 289 + betweenness[si] += length(S) - 1 # 289 for w in S coeff = (1.0 + δ[w]) / σ[w] diff --git a/src/centrality/stress.jl b/src/centrality/stress.jl index fe335c855..90ef320d1 100644 --- a/src/centrality/stress.jl +++ b/src/centrality/stress.jl @@ -51,7 +51,7 @@ function stress_centrality( rng::Union{Nothing,AbstractRNG}=nothing, seed::Union{Nothing,Integer}=nothing, ) - return stress_centrality(g, sample(vertices(g), k; rng=rng, seed=seed)) + return stress_centrality(g, sample(collect_if_not_vector(vertices(g)), k; rng=rng, seed=seed)) end function _stress_accumulate_basic!( diff --git a/src/core.jl b/src/core.jl index 80cbaa562..93b61f0bb 100644 --- a/src/core.jl +++ b/src/core.jl @@ -44,6 +44,10 @@ julia> add_vertices!(g, 2) """ add_vertices!(g::AbstractGraph, n::Integer) = sum([add_vertex!(g) for i in 1:n]) +# TODO the behaviour of indegree (and as well outdegree and degree) is very +# badly documented for the case indegree(g, vs) where vs is not a single vertex +# but rather a collection of vertices + """ indegree(g[, v]) @@ -68,7 +72,7 @@ julia> indegree(g) ``` """ indegree(g::AbstractGraph, v::Integer) = length(inneighbors(g, v)) -indegree(g::AbstractGraph, v::AbstractVector=vertices(g)) = [indegree(g, x) for x in v] +indegree(g::AbstractGraph, vs=vertices(g)) = [indegree(g, x) for x in vs] """ outdegree(g[, v]) @@ -94,7 +98,7 @@ julia> outdegree(g) ``` """ outdegree(g::AbstractGraph, v::Integer) = length(outneighbors(g, v)) -outdegree(g::AbstractGraph, v::AbstractVector=vertices(g)) = [outdegree(g, x) for x in v] +outdegree(g::AbstractGraph, vs=vertices(g)) = [outdegree(g, x) for x in vs] """ degree(g[, v]) @@ -122,10 +126,16 @@ julia> degree(g) ``` """ function degree end -@traitfn degree(g::::IsDirected, v::Integer) = indegree(g, v) + outdegree(g, v) -@traitfn degree(g::::(!IsDirected), v::Integer) = indegree(g, v) -degree(g::AbstractGraph, v::AbstractVector=vertices(g)) = [degree(g, x) for x in v] +function degree(g::AbstractGraph, v::Integer) + if !is_directed(g) + return outdegree(g, v) + end + return indegree(g, v) + outdegree(g, v) +end + +degree(g::AbstractGraph, vs=vertices(g)) = [degree(g, x) for x in vs] + """ Δout(g) diff --git a/test/centrality/betweenness.jl b/test/centrality/betweenness.jl index e6d199bf6..d05454be5 100644 --- a/test/centrality/betweenness.jl +++ b/test/centrality/betweenness.jl @@ -1,17 +1,15 @@ @testset "Betweenness" begin rng = StableRNG(1) # self loops - s2 = SimpleDiGraph(3) - add_edge!(s2, 1, 2) - add_edge!(s2, 2, 3) - add_edge!(s2, 3, 3) - s1 = SimpleGraph(s2) - g3 = path_graph(5) + s1 = GenericGraph(SimpleGraph(Edge.([(1,2), (2,3), (3, 3)]))) + s2 = GenericDiGraph(SimpleDiGraph(Edge.([(1,2), (2,3), (3, 3)]))) + + g3 = GenericGraph(path_graph(5)) gint = loadgraph(joinpath(testdir, "testdata", "graph-50-500.jgz"), "graph-50-500") c = vec(readdlm(joinpath(testdir, "testdata", "graph-50-500-bc.txt"), ',')) - for g in testdigraphs(gint) + for g in test_generic_graphs(gint) z = @inferred(betweenness_centrality(g)) @test map(Float32, z) == map(Float32, c) @@ -29,25 +27,18 @@ @test @inferred(betweenness_centrality(s1)) == [0, 1, 0] @test @inferred(betweenness_centrality(s2)) == [0, 0.5, 0] - g = SimpleGraph(2) - add_edge!(g, 1, 2) + g = GenericGraph(path_graph(2)) z = @inferred(betweenness_centrality(g; normalize=true)) @test z[1] == z[2] == 0.0 z2 = @inferred(betweenness_centrality(g, vertices(g))) - z3 = @inferred(betweenness_centrality(g, [vertices(g);])) + z3 = @inferred(betweenness_centrality(g, collect(vertices(g)))) @test z == z2 == z3 z = @inferred(betweenness_centrality(g3; normalize=false)) @test z[1] == z[5] == 0.0 # Weighted Graph tests - g = Graph(6) - add_edge!(g, 1, 2) - add_edge!(g, 2, 3) - add_edge!(g, 3, 4) - add_edge!(g, 2, 5) - add_edge!(g, 5, 6) - add_edge!(g, 5, 4) + g = GenericGraph(SimpleGraph(Edge.([(1, 2), (2, 3), (2, 5), (3, 4), (4, 5), (5, 6)]))) distmx = [ 0.0 2.0 0.0 0.0 0.0 0.0 @@ -77,7 +68,7 @@ adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph a2 = SimpleDiGraph(adjmx2) - for g in testdigraphs(a2) + for g in test_generic_graphs(a2) distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] c2 = [0.24390243902439027, 0.27027027027027023, 0.1724137931034483] @test isapprox( @@ -99,7 +90,7 @@ ) end # test #1405 / #1406 - g = grid([50, 50]) + g = GenericGraph(grid([50, 50])) z = betweenness_centrality(g; normalize=false) @test maximum(z) < nv(g) * (nv(g) - 1) end diff --git a/test/centrality/closeness.jl b/test/centrality/closeness.jl index 620676bee..2d610fd24 100644 --- a/test/centrality/closeness.jl +++ b/test/centrality/closeness.jl @@ -5,7 +5,7 @@ add_edge!(g5, 1, 3) add_edge!(g5, 3, 4) - for g in testdigraphs(g5) + for g in test_generic_graphs(g5) y = @inferred(closeness_centrality(g; normalize=false)) z = @inferred(closeness_centrality(g)) @test y == [0.75, 0.6666666666666666, 1.0, 0.0] @@ -14,7 +14,7 @@ adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph a2 = SimpleDiGraph(adjmx2) - for g in testdigraphs(a2) + for g in test_generic_graphs(a2) distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] c2 = [0.24390243902439027, 0.27027027027027023, 0.1724137931034483] y = @inferred(closeness_centrality(g, distmx2; normalize=false)) @@ -25,7 +25,7 @@ g5 = SimpleGraph(5) add_edge!(g5, 1, 2) - for g in testgraphs(g5) + for g in test_generic_graphs(g5) z = @inferred(closeness_centrality(g)) @test z[1] == z[2] == 0.25 @test z[3] == z[4] == z[5] == 0.0 @@ -33,7 +33,7 @@ adjmx1 = [0 1 0; 1 0 1; 0 1 0] # graph a1 = SimpleGraph(adjmx1) - for g in testgraphs(a1) + for g in test_generic_graphs(a1) distmx1 = [Inf 2.0 Inf; 2.0 Inf 4.2; Inf 4.2 Inf] c1 = [0.24390243902439027, 0.3225806451612903, 0.1923076923076923] y = @inferred(closeness_centrality(g, distmx1; normalize=false)) diff --git a/test/centrality/degree.jl b/test/centrality/degree.jl index beab2f2c4..07a6f8eb0 100644 --- a/test/centrality/degree.jl +++ b/test/centrality/degree.jl @@ -4,7 +4,7 @@ add_edge!(g5, 2, 3) add_edge!(g5, 1, 3) add_edge!(g5, 3, 4) - for g in testdigraphs(g5) + for g in test_generic_graphs(g5) @test @inferred(degree_centrality(g)) == [0.6666666666666666, 0.6666666666666666, 1.0, 0.3333333333333333] @test @inferred(indegree_centrality(g, normalize=false)) == [0.0, 1.0, 2.0, 1.0] diff --git a/test/centrality/eigenvector.jl b/test/centrality/eigenvector.jl index de75cc8df..af13508b7 100644 --- a/test/centrality/eigenvector.jl +++ b/test/centrality/eigenvector.jl @@ -2,7 +2,7 @@ g1 = smallgraph(:house) g2 = cycle_digraph(4) - for g in testgraphs(g1) + for g in test_generic_graphs(g1) y = @inferred(eigenvector_centrality(g)) @test round.(y, digits=3) == round.( @@ -16,7 +16,7 @@ digits=3, ) end - for g in testdigraphs(g2) + for g in test_generic_graphs(g2) y = @inferred(eigenvector_centrality(g)) @test round.(y, digits=3) == round.([0.5, 0.5, 0.5, 0.5], digits=3) end diff --git a/test/centrality/katz.jl b/test/centrality/katz.jl index c559c0051..fafa52d9a 100644 --- a/test/centrality/katz.jl +++ b/test/centrality/katz.jl @@ -4,7 +4,7 @@ add_edge!(g5, 2, 3) add_edge!(g5, 1, 3) add_edge!(g5, 3, 4) - for g in testdigraphs(g5) + for g in test_generic_graphs(g5) z = @inferred(katz_centrality(g, 0.4)) @test round.(z, digits=2) == [0.32, 0.44, 0.62, 0.56] end diff --git a/test/centrality/pagerank.jl b/test/centrality/pagerank.jl index f0134e9bc..087f17bbf 100644 --- a/test/centrality/pagerank.jl +++ b/test/centrality/pagerank.jl @@ -23,7 +23,7 @@ add_edge!(g6, 1, 3) add_edge!(g6, 3, 4) for α in [0.75, 0.85] - for g in testdigraphs(g5) + for g in test_generic_graphs(g5) @test pagerank(g)[3] ≈ 0.318 atol = 0.001 @test length(@inferred(pagerank(g))) == nv(g) @test_throws ErrorException pagerank(g, 2) @@ -31,7 +31,7 @@ @test isapprox(pagerank(g, α), dense_pagerank_solver(g, α), atol=0.001) end - for g in testgraphs(g6) + for g in test_generic_graphs(g6) @test length(@inferred(pagerank(g))) == nv(g) @test_throws ErrorException pagerank(g, 2) @test_throws ErrorException pagerank(g, α, 2) diff --git a/test/centrality/radiality.jl b/test/centrality/radiality.jl index d8b2ec571..b2033a8f3 100644 --- a/test/centrality/radiality.jl +++ b/test/centrality/radiality.jl @@ -2,7 +2,7 @@ gint = loadgraph(joinpath(testdir, "testdata", "graph-50-500.jgz"), "graph-50-500") c = vec(readdlm(joinpath(testdir, "testdata", "graph-50-500-rc.txt"), ',')) - for g in testdigraphs(gint) + for g in test_generic_graphs(gint) z = @inferred(radiality_centrality(g)) @test z == c end @@ -10,7 +10,7 @@ g1 = cycle_graph(4) add_vertex!(g1) add_edge!(g1, 4, 5) - for g in testgraphs(g1) + for g in test_generic_graphs(g1) z = @inferred(radiality_centrality(g)) @test z ≈ [5//6, 3//4, 5//6, 11//12, 2//3] end diff --git a/test/centrality/stress.jl b/test/centrality/stress.jl index db94a8f35..1d5b0ed98 100644 --- a/test/centrality/stress.jl +++ b/test/centrality/stress.jl @@ -3,7 +3,7 @@ gint = loadgraph(joinpath(testdir, "testdata", "graph-50-500.jgz"), "graph-50-500") c = vec(readdlm(joinpath(testdir, "testdata", "graph-50-500-sc.txt"), ',')) - for g in testdigraphs(gint) + for g in test_generic_graphs(gint) z = @inferred(stress_centrality(g)) @test z == c @@ -16,7 +16,7 @@ g1 = cycle_graph(4) add_vertex!(g1) add_edge!(g1, 4, 5) - for g in testgraphs(g1) + for g in test_generic_graphs(g1) z = @inferred(stress_centrality(g)) @test z == [4, 2, 4, 10, 0] end From be10f8001bdd3f91835481804f2cb1e3fd77fa05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sch=C3=B6lly?= Date: Wed, 5 Jul 2023 10:47:02 +0200 Subject: [PATCH 07/16] Use GenericGraph for testing cycles algorithms (#274) --- src/cycles/basis.jl | 2 +- src/cycles/johnson.jl | 4 ++++ test/cycles/basis.jl | 12 ++++++------ test/cycles/hawick-james.jl | 13 +++++++------ test/cycles/incremental.jl | 4 ++++ test/cycles/johnson.jl | 3 +++ test/cycles/karp.jl | 6 +++--- test/cycles/limited_length.jl | 16 +++++++++------- 8 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/cycles/basis.jl b/src/cycles/basis.jl index f4d26a769..e26692384 100644 --- a/src/cycles/basis.jl +++ b/src/cycles/basis.jl @@ -29,7 +29,7 @@ julia> cycle_basis(g) ### References * Paton, K. An algorithm for finding a fundamental set of cycles of a graph. Comm. ACM 12, 9 (Sept 1969), 514-518. [https://dl.acm.org/citation.cfm?id=363232] """ -function cycle_basis(g::AbstractSimpleGraph, root=nothing) +function cycle_basis(g::AbstractGraph, root=nothing) T = eltype(g) cycles = Vector{Vector{T}}() diff --git a/src/cycles/johnson.jl b/src/cycles/johnson.jl index 6f0d34983..52892942e 100644 --- a/src/cycles/johnson.jl +++ b/src/cycles/johnson.jl @@ -1,3 +1,7 @@ +# TODO most functions here do not work for general abstract graphs yet +# as most of them relay on induced_subgraph, which expects the graph type +# to be modifiable. + abstract type Visitor{T<:Integer} end """ diff --git a/test/cycles/basis.jl b/test/cycles/basis.jl index 7d17e3f8c..804aa1afd 100644 --- a/test/cycles/basis.jl +++ b/test/cycles/basis.jl @@ -8,7 +8,7 @@ # No Edges ex = Graph(1) expected_cyclebasis = Array{Int64,1}[] - @testset "no edges" for g in testgraphs(ex) + @testset "no edges" for g in test_generic_graphs(ex) ex_cyclebasis = @inferred cycle_basis(g) @test isempty(ex_cyclebasis) end @@ -17,7 +17,7 @@ elist = [(1, 1)] ex = Graph(SimpleEdge.(elist)) expected_cyclebasis = Array{Int64,1}[[1]] - @testset "one self-edge" for g in testgraphs(ex) + @testset "one self-edge" for g in test_generic_graphs(ex) ex_cyclebasis = cycle_basis(g) evaluate(ex_cyclebasis, expected_cyclebasis) end @@ -26,7 +26,7 @@ elist = [(1, 2), (2, 3), (3, 4), (4, 1), (1, 5)] ex = Graph(SimpleEdge.(elist)) expected_cyclebasis = Array{Int64,1}[[1, 2, 3, 4]] - @testset "one cycle" for g in testgraphs(ex) + @testset "one cycle" for g in test_generic_graphs(ex) ex_cyclebasis = cycle_basis(g) evaluate(ex_cyclebasis, expected_cyclebasis) end @@ -35,7 +35,7 @@ elist = [(1, 2), (1, 3), (2, 3), (2, 4), (3, 4)] ex = Graph(SimpleEdge.(elist)) expected_cyclebasis = Array{Int64,1}[[2, 3, 4], [2, 1, 3]] - @testset "2 of 3 cycles w/ basis" for g in testgraphs(ex) + @testset "2 of 3 cycles w/ basis" for g in test_generic_graphs(ex) ex_cyclebasis = cycle_basis(g) evaluate(ex_cyclebasis, expected_cyclebasis) end @@ -44,7 +44,7 @@ elist = [(1, 2), (1, 3), (2, 3), (2, 4), (3, 4), (1, 5), (5, 6), (6, 4)] ex = Graph(SimpleEdge.(elist)) expected_cyclebasis = Array{Int64,1}[[2, 4, 3], [1, 5, 6, 4, 3], [1, 2, 3]] - @testset "root argument" for g in testgraphs(ex) + @testset "root argument" for g in test_generic_graphs(ex) ex_cyclebasis = @inferred cycle_basis(g, 3) evaluate(ex_cyclebasis, expected_cyclebasis) end @@ -52,7 +52,7 @@ @testset "two isolated cycles" begin ex = blockdiag(cycle_graph(3), cycle_graph(4)) expected_cyclebasis = [[1, 2, 3], [4, 5, 6, 7]] - for g in testgraphs(ex) + for g in test_generic_graphs(ex) found_cyclebasis = @inferred cycle_basis(g) evaluate(expected_cyclebasis, found_cyclebasis) end diff --git a/test/cycles/hawick-james.jl b/test/cycles/hawick-james.jl index 636298f18..9cf987b51 100644 --- a/test/cycles/hawick-james.jl +++ b/test/cycles/hawick-james.jl @@ -11,7 +11,7 @@ @testset "subset" for g in testgraphs(ex1) expected_circuits = Vector{Int}[[2, 3, 4, 5], [2, 3, 5]] - ex1_circuits = simplecycles_hawick_james(g) + ex1_circuits = simplecycles_hawick_james(GenericDiGraph(g)) @test issubset(expected_circuits, ex1_circuits) @test issubset(ex1_circuits, expected_circuits) @@ -20,7 +20,7 @@ add_edge!(g, 1, 1) add_edge!(g, 3, 3) - ex1_circuits_self = simplecycles_hawick_james(g) + ex1_circuits_self = simplecycles_hawick_james(GenericDiGraph(g)) @test issubset(expected_circuits, ex1_circuits_self) @test [1] ∈ ex1_circuits_self && [3] ∈ ex1_circuits_self @@ -28,14 +28,14 @@ # Path DiGraph ex2_size = 10 - ex2 = testgraphs(path_digraph(ex2_size)) + ex2 = test_generic_graphs(path_digraph(ex2_size)) @testset "empty" for g in ex2 @test isempty(simplecycles_hawick_james(g)) end # Complete DiGraph ex3_size = 5 - ex3 = testgraphs(complete_digraph(ex3_size)) + ex3 = test_generic_graphs(complete_digraph(ex3_size)) @testset "length" for g in ex3 ex3_circuits = simplecycles_hawick_james(g) @test length(ex3_circuits) == length(unique(ex3_circuits)) @@ -62,7 +62,7 @@ add_edge!(ex4, src, dest) add_edge!(ex4, dest, src) end - @testset "membership" for g in testgraphs(ex4) + @testset "membership" for g in test_generic_graphs(ex4) ex4_output = simplecycles_hawick_james(g) @test [1, 2] ∈ ex4_output && [8, 9] ∈ ex4_output end @@ -72,8 +72,9 @@ (n, k) in [(14, 18), (10, 22), (7, 16)] g = erdos_renyi(n, k; is_directed=true, rng=StableRNG(seed)) + # TODO simplecycles(g) does not yet work with GenericDiGraph cycles1 = simplecycles(g) - cycles2 = simplecycles_hawick_james(g) + cycles2 = simplecycles_hawick_james(GenericDiGraph(g)) foreach(sort!, cycles1) foreach(sort!, cycles2) sort!(cycles1) diff --git a/test/cycles/incremental.jl b/test/cycles/incremental.jl index c20bc815b..f8bd5b37d 100644 --- a/test/cycles/incremental.jl +++ b/test/cycles/incremental.jl @@ -1,3 +1,7 @@ +# The test here cannot be done with GenericDiGraph, as the functions under test +# modify the graph. We probably need another generic graph type with more relaxed +# constraints for modifying graphs. + @testset "ICT" begin Gempty = SimpleDiGraph(3) Gsomedges = SimpleDiGraph(6) diff --git a/test/cycles/johnson.jl b/test/cycles/johnson.jl index aff6666ab..5e7a2882f 100644 --- a/test/cycles/johnson.jl +++ b/test/cycles/johnson.jl @@ -1,3 +1,6 @@ +# TODO we cannot run the test here with GenericGraph yet, +# as the functions don't work for arbitrary graphs yet. + @testset "Cycles" begin rng = StableRNG(1) completedg = complete_digraph(4) diff --git a/test/cycles/karp.jl b/test/cycles/karp.jl index a45324f27..faabac923 100644 --- a/test/cycles/karp.jl +++ b/test/cycles/karp.jl @@ -46,7 +46,7 @@ add_edge!(g1, 11, 7) add_edge!(g1, 12, 9) - @testset "simple digraphs" for g in testgraphs(g1) + @testset "simple digraphs" for g in test_generic_graphs(g1) c, λ = karp_minimum_cycle_mean(g, w) @test c == [9, 11, 7] @test λ == 0.9 @@ -78,7 +78,7 @@ 1.0 Inf 0.0 Inf ] - @testset "tricky case" for g in testgraphs(tricky) + @testset "tricky case" for g in test_generic_graphs(tricky) c, λ = karp_minimum_cycle_mean(g, distmx) @test λ == 0.0 @test sort(c) == [3, 4] @@ -97,7 +97,7 @@ Inf -1 ] - @testset "multiple SCCs" for g in testgraphs(multi) + @testset "multiple SCCs" for g in test_generic_graphs(multi) c, λ = karp_minimum_cycle_mean(g, distmx) @test λ == -1.0 @test c == [2] diff --git a/test/cycles/limited_length.jl b/test/cycles/limited_length.jl index f9c529033..051c06671 100644 --- a/test/cycles/limited_length.jl +++ b/test/cycles/limited_length.jl @@ -3,7 +3,7 @@ pathdg = path_digraph(5) cycledg = cycle_digraph(5) - @testset "complete digraph" for g in testgraphs(completedg) + @testset "complete digraph" for g in test_generic_graphs(completedg) @test length(simplecycles_limited_length(g, 0)) == 0 @test length(simplecycles_limited_length(g, 1)) == 0 @test length(simplecycles_limited_length(g, 2)) == 6 @@ -13,7 +13,7 @@ @test length(simplecycles_limited_length(g, 4, typemax(Int))) == 20 end - @testset "path digraph" for g in testgraphs(pathdg) + @testset "path digraph" for g in test_generic_graphs(pathdg) @test length(simplecycles_limited_length(g, 1)) == 0 @test length(simplecycles_limited_length(g, 2)) == 0 @test length(simplecycles_limited_length(g, 3)) == 0 @@ -21,7 +21,7 @@ @test length(simplecycles_limited_length(g, 5)) == 0 end - @testset "cycle digraph" for g in testgraphs(cycledg) + @testset "cycle digraph" for g in test_generic_graphs(cycledg) @test length(simplecycles_limited_length(g, 1)) == 0 @test length(simplecycles_limited_length(g, 2)) == 0 @test length(simplecycles_limited_length(g, 3)) == 0 @@ -30,12 +30,12 @@ end @testset "self loops" begin - selfloopg = DiGraph([ + selfloopg = GenericDiGraph(DiGraph([ 0 1 0 0 0 0 1 0 1 0 1 0 0 0 0 1 - ]) + ])) cycles = simplecycles_limited_length(selfloopg, nv(selfloopg)) @test [3] in cycles @test [4] in cycles @@ -46,10 +46,12 @@ @testset "octahedral graph" begin octag = smallgraph(:octahedral) octadg = DiGraph(octag) + # TODO simplecycleslength does currently not yet work with GenericDiGraph. + # This is probably because it uses induced_subgraph which fails on that graph type. octalengths, _ = simplecycleslength(octadg) for k in 1:6 - @test sum(octalengths[1:k]) == length(simplecycles_limited_length(octag, k)) - @test sum(octalengths[1:k]) == length(simplecycles_limited_length(octadg, k)) + @test sum(octalengths[1:k]) == length(simplecycles_limited_length(GenericGraph(octag), k)) + @test sum(octalengths[1:k]) == length(simplecycles_limited_length(GenericDiGraph(octadg), k)) end end end From 2d79c3a3c6dc6b995531ce26cd97c4464a5d241d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sch=C3=B6lly?= Date: Wed, 5 Jul 2023 10:54:39 +0200 Subject: [PATCH 08/16] Use GenericGraph for testing shortestpaths algorithms (#275) --- src/shortestpaths/bellman-ford.jl | 6 ++-- src/shortestpaths/johnson.jl | 2 +- src/shortestpaths/yen.jl | 4 +++ test/shortestpaths/astar.jl | 13 ++++++-- test/shortestpaths/bellman-ford.jl | 8 ++--- test/shortestpaths/desopo-pape.jl | 43 +++++++++++++------------- test/shortestpaths/dijkstra.jl | 12 ++++---- test/shortestpaths/floyd-warshall.jl | 10 +++---- test/shortestpaths/johnson.jl | 6 ++-- test/shortestpaths/spfa.jl | 45 ++++++++++++++-------------- test/shortestpaths/yen.jl | 2 ++ 11 files changed, 85 insertions(+), 66 deletions(-) diff --git a/src/shortestpaths/bellman-ford.jl b/src/shortestpaths/bellman-ford.jl index 8cf4f4692..0e034700b 100644 --- a/src/shortestpaths/bellman-ford.jl +++ b/src/shortestpaths/bellman-ford.jl @@ -48,7 +48,8 @@ function bellman_ford_shortest_paths( for i in vertices(graph) no_changes = true new_active .= false - for u in vertices(graph)[active] + for u in vertices(graph) + active[u] || continue for v in outneighbors(graph, u) relax_dist = distmx[u, v] + dists[u] if dists[v] > relax_dist @@ -80,9 +81,10 @@ function has_negative_edge_cycle( g::AbstractGraph{U}, distmx::AbstractMatrix{T} ) where {T<:Real} where {U<:Integer} try - bellman_ford_shortest_paths(g, vertices(g), distmx) + bellman_ford_shortest_paths(g, collect_if_not_vector(vertices(g)), distmx) catch e isa(e, NegativeCycleError) && return true + rethrow() end return false end diff --git a/src/shortestpaths/johnson.jl b/src/shortestpaths/johnson.jl index 22296dfc7..4ddb2404b 100644 --- a/src/shortestpaths/johnson.jl +++ b/src/shortestpaths/johnson.jl @@ -28,7 +28,7 @@ function johnson_shortest_paths( nvg = nv(g) type_distmx = typeof(distmx) # Change when parallel implementation of Bellman Ford available - wt_transform = bellman_ford_shortest_paths(g, vertices(g), distmx).dists + wt_transform = bellman_ford_shortest_paths(g, collect_if_not_vector(vertices(g)), distmx).dists @compat if !ismutable(distmx) && type_distmx != Graphs.DefaultDistance distmx = sparse(distmx) # Change reference, not value diff --git a/src/shortestpaths/yen.jl b/src/shortestpaths/yen.jl index aca6d77d7..2cb10c449 100644 --- a/src/shortestpaths/yen.jl +++ b/src/shortestpaths/yen.jl @@ -1,3 +1,7 @@ +# TODO this algorithm does not work with abitrary AbstractGraph yet, +# as it relies on rem_edge! and deepcopy + + """ struct YenState{T, U} diff --git a/test/shortestpaths/astar.jl b/test/shortestpaths/astar.jl index 591635e44..b1af146ff 100644 --- a/test/shortestpaths/astar.jl +++ b/test/shortestpaths/astar.jl @@ -4,12 +4,21 @@ d1 = float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]) d2 = sparse(float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0])) - for g in testgraphs(g3), dg in testdigraphs(g4) + @testset "SimpleGraph and SimpleDiGraph" for g in testgraphs(g3), dg in testdigraphs(g4) @test @inferred(a_star(g, 1, 4, d1)) == @inferred(a_star(dg, 1, 4, d1)) == @inferred(a_star(g, 1, 4, d2)) @test isempty(@inferred(a_star(dg, 4, 1))) end + @testset "GenericGraph and GenricDiGraph with SimpleEdge" for g in test_generic_graphs(g3), dg in test_generic_graphs(g4) + zero_heuristic = n -> 0 + Eg = SimpleEdge{eltype(g)} + Edg = SimpleEdge{eltype(dg)} + @test @inferred(a_star(g, 1, 4, d1, zero_heuristic, Eg)) == + @inferred(a_star(dg, 1, 4, d1, zero_heuristic, Edg)) == + @inferred(a_star(g, 1, 4, d2, zero_heuristic, Eg)) + @test isempty(@inferred(a_star(dg, 4, 1, weights(dg), zero_heuristic, Edg))) + end # test for #1258 g = complete_graph(4) @@ -21,5 +30,5 @@ s::Int d::Int end - @test eltype(a_star(g, 1, 4, w, n -> 0, MyFavoriteEdgeType)) == MyFavoriteEdgeType + @test eltype(a_star(GenericGraph(g), 1, 4, w, n -> 0, MyFavoriteEdgeType)) == MyFavoriteEdgeType end diff --git a/test/shortestpaths/bellman-ford.jl b/test/shortestpaths/bellman-ford.jl index ac2060047..77d8bac0d 100644 --- a/test/shortestpaths/bellman-ford.jl +++ b/test/shortestpaths/bellman-ford.jl @@ -3,7 +3,7 @@ d1 = float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]) d2 = sparse(float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0])) - for g in testdigraphs(g4) + for g in test_generic_graphs(g4) y = @inferred(bellman_ford_shortest_paths(g, 2, d1)) z = @inferred(bellman_ford_shortest_paths(g, 2, d2)) @test y.dists == z.dists == [Inf, 0, 6, 17, 33] @@ -24,7 +24,7 @@ # Negative Cycle gx = complete_graph(3) - for g in testgraphs(gx) + for g in test_generic_graphs(gx) d = [1 -3 1; -3 1 1; 1 1 1] @test_throws Graphs.NegativeCycleError bellman_ford_shortest_paths(g, 1, d) @test has_negative_edge_cycle(g, d) @@ -37,7 +37,7 @@ # Negative cycle of length 3 in graph of diameter 4 gx = complete_graph(4) d = [1 -1 1 1; 1 1 1 -1; 1 1 1 1; 1 1 1 1] - for g in testgraphs(gx) + for g in test_generic_graphs(gx) @test_throws Graphs.NegativeCycleError bellman_ford_shortest_paths(g, 1, d) @test has_negative_edge_cycle(g, d) end @@ -56,7 +56,7 @@ d3 = [CustomReal(i, 3) for i in d1] d4 = sparse(d3) - for g in testdigraphs(g4) + for g in test_generic_graphs(g4) y = @inferred(bellman_ford_shortest_paths(g, 2, d3)) z = @inferred(bellman_ford_shortest_paths(g, 2, d4)) @test getfield.(y.dists, :val) == getfield.(z.dists, :val) == [Inf, 0, 6, 17, 33] diff --git a/test/shortestpaths/desopo-pape.jl b/test/shortestpaths/desopo-pape.jl index 037e7b6ff..6c45b56ab 100644 --- a/test/shortestpaths/desopo-pape.jl +++ b/test/shortestpaths/desopo-pape.jl @@ -2,7 +2,7 @@ g4 = path_digraph(5) d1 = float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]) d2 = sparse(float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0])) - @testset "generic tests: $g" for g in testdigraphs(g4) + @testset "generic tests: $(typeof(g))" for g in test_generic_graphs(g4) y = @inferred(desopo_pape_shortest_paths(g, 2, d1)) z = @inferred(desopo_pape_shortest_paths(g, 2, d2)) @test y.parents == z.parents == [0, 0, 2, 3, 4] @@ -13,7 +13,7 @@ add_edge!(gx, 2, 4) d = ones(Int, 5, 5) d[2, 3] = 100 - @testset "cycles: $g" for g in testgraphs(gx) + @testset "cycles: $(typeof(g))" for g in test_generic_graphs(gx) z = @inferred(desopo_pape_shortest_paths(g, 1, d)) @test z.dists == [0, 1, 3, 2, 3] @test z.parents == [0, 1, 4, 2, 4] @@ -28,7 +28,7 @@ add_edge!(G, 3, 4) add_edge!(G, 4, 5) - @testset "more cycles: $g" for g in testgraphs(G) + @testset "more cycles: $(typeof(g))" for g in test_generic_graphs(G) y = @inferred(desopo_pape_shortest_paths(g, 1, m)) @test y.parents == [0, 1, 1, 3, 3] @test y.dists == [0, 2, 2, 3, 4] @@ -43,7 +43,7 @@ add_edge!(G, 2, 4) add_edge!(G, 4, 5) m = [0 10 2 0 15; 10 9 0 1 0; 2 0 1 0 0; 0 1 0 0 2; 15 0 0 2 0] - @testset "self loops: $g" for g in testgraphs(G) + @testset "self loops: $(typeof(g))" for g in test_generic_graphs(G) z = @inferred(desopo_pape_shortest_paths(g, 1, m)) y = @inferred(dijkstra_shortest_paths(g, 1, m)) @test isapprox(z.dists, y.dists) @@ -54,7 +54,7 @@ add_edge!(G, 1, 3) add_edge!(G, 4, 5) inf = typemax(eltype(G)) - @testset "disconnected: $g" for g in testgraphs(G) + @testset "disconnected: $(typeof(G))" for g in test_generic_graphs(G) z = @inferred(desopo_pape_shortest_paths(g, 1)) @test z.dists == [0, 1, 1, inf, inf] @test z.parents == [0, 1, 1, 0, 0] @@ -62,7 +62,7 @@ G = SimpleGraph(3) inf = typemax(eltype(G)) - @testset "empty: $g" for g in testgraphs(G) + @testset "empty: $(typeof(g))" for g in test_generic_graphs(G) z = @inferred(desopo_pape_shortest_paths(g, 1)) @test z.dists == [0, inf, inf] @test z.parents == [0, 0, 0] @@ -73,7 +73,7 @@ rng = StableRNG(seed) nvg = Int(ceil(250 * rand(rng))) neg = Int(floor((nvg * (nvg - 1) / 2) * rand(rng))) - g = SimpleGraph(nvg, neg; rng=rng) + g = GenericGraph(SimpleGraph(nvg, neg; rng=rng)) z = desopo_pape_shortest_paths(g, 1) y = dijkstra_shortest_paths(g, 1) @test isapprox(z.dists, y.dists) @@ -85,7 +85,7 @@ rng = StableRNG(seed) nvg = Int(ceil(250 * rand(rng))) neg = Int(floor((nvg * (nvg - 1) / 2) * rand(rng))) - g = SimpleDiGraph(nvg, neg; rng=rng) + g = GenericDiGraph(SimpleDiGraph(nvg, neg; rng=rng)) z = desopo_pape_shortest_paths(g, 1) y = dijkstra_shortest_paths(g, 1) @test isapprox(z.dists, y.dists) @@ -93,42 +93,42 @@ end @testset "misc graphs" begin - G = complete_graph(9) + G = GenericGraph(complete_graph(9)) z = desopo_pape_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z.dists, y.dists) - G = complete_digraph(9) + G = GenericDiGraph(complete_digraph(9)) z = desopo_pape_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z.dists, y.dists) - G = cycle_graph(9) + G = GenericGraph(cycle_graph(9)) z = desopo_pape_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z.dists, y.dists) - G = cycle_digraph(9) + G = GenericDiGraph(cycle_digraph(9)) z = desopo_pape_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z.dists, y.dists) - G = star_graph(9) + G = GenericGraph(star_graph(9)) z = desopo_pape_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z.dists, y.dists) - G = wheel_graph(9) + G = GenericGraph(wheel_graph(9)) z = desopo_pape_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z.dists, y.dists) - G = roach_graph(9) + G = GenericGraph(roach_graph(9)) z = desopo_pape_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z.dists, y.dists) - G = clique_graph(5, 19) + G = GenericGraph(clique_graph(5, 19)) z = desopo_pape_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z.dists, y.dists) @@ -158,16 +158,17 @@ :truncatedtetrahedron, :truncatedtetrahedron_dir, ] - G = smallgraph(s) - z = desopo_pape_shortest_paths(G, 1) - y = dijkstra_shortest_paths(G, 1) + GS = smallgraph(s) + GG = is_directed(GS) ? GenericDiGraph(GS) : GenericGraph(GS) + z = desopo_pape_shortest_paths(GG, 1) + y = dijkstra_shortest_paths(GG, 1) @test isapprox(z.dists, y.dists) end @testset "errors" begin - g = Graph() + g = GenericGraph(Graph()) @test_throws DomainError desopo_pape_shortest_paths(g, 1) - g = Graph(5) + g = GenericGraph(Graph(5)) @test_throws DomainError desopo_pape_shortest_paths(g, 6) end end diff --git a/test/shortestpaths/dijkstra.jl b/test/shortestpaths/dijkstra.jl index 5a47f9128..a4f0f21b2 100644 --- a/test/shortestpaths/dijkstra.jl +++ b/test/shortestpaths/dijkstra.jl @@ -3,7 +3,7 @@ d1 = float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]) d2 = sparse(float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0])) - for g in testdigraphs(g4) + for g in test_generic_graphs(g4) y = @inferred(dijkstra_shortest_paths(g, 2, d1)) z = @inferred(dijkstra_shortest_paths(g, 2, d2)) @@ -28,7 +28,7 @@ add_edge!(gx, 2, 4) d = ones(Int, 5, 5) d[2, 3] = 100 - for g in testgraphs(gx) + for g in test_generic_graphs(gx) z = @inferred(dijkstra_shortest_paths(g, 1, d)) @test z.dists == [0, 1, 3, 2, 3] @test z.parents == [0, 1, 4, 2, 4] @@ -59,7 +59,7 @@ 1.0 0.0 3.0 0.0 ] - for g in testgraphs(G) + for g in test_generic_graphs(G) ds = @inferred(dijkstra_shortest_paths(g, 2, w)) # this loop reconstructs the shortest path for vertices 1, 3 and 4 @test spaths(ds, [1, 3, 4], 2) == Array[[2 1], [2 3], [2 1 4]] @@ -81,7 +81,7 @@ add_edge!(G, 3, 5) add_edge!(G, 3, 4) add_edge!(G, 4, 5) - for g in testgraphs(G) + for g in test_generic_graphs(G) ds = @inferred(dijkstra_shortest_paths(g, 1, m; allpaths=true)) @test ds.pathcounts == [1.0, 1.0, 1.0, 1.0, 2.0] @test ds.predecessors == [[], [1], [1], [3], [3, 4]] @@ -97,7 +97,7 @@ add_edge!(G, 1, 2) add_edge!(G, 1, 3) add_edge!(G, 4, 5) - for g in testgraphs(G) + for g in test_generic_graphs(G) dm = @inferred(dijkstra_shortest_paths(g, 1; allpaths=true, trackvertices=true)) @test dm.closest_vertices == [1, 2, 3, 4, 5] end @@ -107,7 +107,7 @@ add_edge!(G, 1, 3) m = float([0 2 2 0 0 1; 2 0 1 0 0 0; 2 1 0 4 0 0; 0 0 4 0 1 0; 0 0 0 1 0 1; 1 0 0 0 1 0]) - for g in testgraphs(G) + for g in test_generic_graphs(G) ds = @inferred(dijkstra_shortest_paths(g, 3, m;maxdist=3.0)) @test ds.dists == [2, 1, 0, Inf, Inf, 3] end diff --git a/test/shortestpaths/floyd-warshall.jl b/test/shortestpaths/floyd-warshall.jl index 0eb0f2992..89d013e52 100644 --- a/test/shortestpaths/floyd-warshall.jl +++ b/test/shortestpaths/floyd-warshall.jl @@ -1,7 +1,7 @@ @testset "Floyd Warshall" begin g3 = path_graph(5) d = [0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0] - for g in testgraphs(g3) + for g in test_generic_graphs(g3) z = @inferred(floyd_warshall_shortest_paths(g, d)) @test z.dists[3, :][:] == [7, 6, 0, 11, 27] @test z.parents[3, :][:] == [2, 3, 0, 3, 4] @@ -14,7 +14,7 @@ end g4 = path_digraph(4) d = ones(4, 4) - for g in testdigraphs(g4) + for g in test_generic_graphs(g4) z = @inferred(floyd_warshall_shortest_paths(g, d)) @test length(enumerate_paths(z, 4, 3)) == 0 @test length(enumerate_paths(z, 4, 1)) == 0 @@ -23,7 +23,7 @@ g5 = DiGraph([1 1 1 0 1; 0 1 0 1 1; 0 1 1 0 0; 1 0 1 1 0; 0 0 0 1 1]) d = [0 3 8 0 -4; 0 0 0 1 7; 0 4 0 0 0; 2 0 -5 0 0; 0 0 0 6 0] - for g in testdigraphs(g5) + for g in test_generic_graphs(g5) z = @inferred(floyd_warshall_shortest_paths(g, d)) @test z.dists == [0 1 -3 2 -4; 3 0 -4 1 -1; 7 4 0 5 3; 2 -1 -5 0 -2; 8 5 1 6 0] end @@ -32,7 +32,7 @@ g = SimpleGraph(2) add_edge!(g, 1, 2) add_edge!(g, 2, 2) - @test enumerate_paths(floyd_warshall_shortest_paths(g)) == + @test enumerate_paths(floyd_warshall_shortest_paths(GenericGraph(g))) == Vector{Vector{Int}}[[[], [1, 2]], [[2, 1], []]] g = SimpleDiGraph(2) @@ -40,7 +40,7 @@ add_edge!(g, 1, 2) add_edge!(g, 2, 1) add_edge!(g, 2, 2) - @test enumerate_paths(floyd_warshall_shortest_paths(g)) == + @test enumerate_paths(floyd_warshall_shortest_paths(GenericDiGraph(g))) == Vector{Vector{Int}}[[[], [1, 2]], [[2, 1], []]] end end diff --git a/test/shortestpaths/johnson.jl b/test/shortestpaths/johnson.jl index 20bb04f2d..a43536080 100644 --- a/test/shortestpaths/johnson.jl +++ b/test/shortestpaths/johnson.jl @@ -1,7 +1,7 @@ @testset "Johnson" begin g3 = path_graph(5) d = Symmetric([0 1 2 3 4; 1 0 6 7 8; 2 6 0 11 12; 3 7 11 0 16; 4 8 12 16 0]) - for g in testgraphs(g3) + for g in test_generic_graphs(g3) z = @inferred(johnson_shortest_paths(g, d)) @test z.dists[3, :][:] == [7, 6, 0, 11, 27] @test z.parents[3, :][:] == [2, 3, 0, 3, 4] @@ -14,7 +14,7 @@ end g4 = path_digraph(4) - for g in testdigraphs(g4) + for g in test_generic_graphs(g4) z = @inferred(johnson_shortest_paths(g)) @test length(enumerate_paths(z, 4, 3)) == 0 @test length(enumerate_paths(z, 4, 1)) == 0 @@ -23,7 +23,7 @@ g5 = DiGraph([1 1 1 0 1; 0 1 0 1 1; 0 1 1 0 0; 1 0 1 1 0; 0 0 0 1 1]) d = [0 3 8 0 -4; 0 0 0 1 7; 0 4 0 0 0; 2 0 -5 0 0; 0 0 0 6 0] - for g in testdigraphs(g5) + for g in test_generic_graphs(g5) z = @inferred(johnson_shortest_paths(g, d)) @test z.dists == [0 1 -3 2 -4; 3 0 -4 1 -1; 7 4 0 5 3; 2 -1 -5 0 -2; 8 5 1 6 0] end diff --git a/test/shortestpaths/spfa.jl b/test/shortestpaths/spfa.jl index f0265baad..e6cee66df 100644 --- a/test/shortestpaths/spfa.jl +++ b/test/shortestpaths/spfa.jl @@ -3,7 +3,7 @@ g4 = path_digraph(5) d1 = float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]) - for g in testdigraphs(g4) + for g in test_generic_graphs(g4) y = @inferred(spfa_shortest_paths(g, 2, d1)) @test y == [Inf, 0, 6, 17, 33] end @@ -13,7 +13,7 @@ add_edge!(gx, 2, 4) d = ones(Int, 5, 5) d[2, 3] = 100 - for g in testgraphs(gx) + for g in test_generic_graphs(gx) z = @inferred(spfa_shortest_paths(g, 1, d)) @test z == [0, 1, 3, 2, 3] end @@ -28,7 +28,7 @@ add_edge!(G, 3, 4) add_edge!(G, 4, 5) - for g in testgraphs(G) + for g in test_generic_graphs(G) y = @inferred(spfa_shortest_paths(g, 1, m)) @test y == [0, 2, 2, 3, 4] end @@ -44,7 +44,7 @@ add_edge!(G, 2, 4) add_edge!(G, 4, 5) m = [0 10 2 0 15; 10 9 0 1 0; 2 0 1 0 0; 0 1 0 0 2; 15 0 0 2 0] - for g in testgraphs(G) + for g in test_generic_graphs(G) z = @inferred(spfa_shortest_paths(g, 1, m)) y = @inferred(dijkstra_shortest_paths(g, 1, m)) @test isapprox(z, y.dists) @@ -57,7 +57,7 @@ add_edge!(G, 1, 3) add_edge!(G, 4, 5) inf = typemax(eltype(G)) - for g in testgraphs(G) + for g in test_generic_graphs(G) z = @inferred(spfa_shortest_paths(g, 1)) @test z == [0, 1, 1, inf, inf] end @@ -66,7 +66,7 @@ @testset "Empty graph" begin G = SimpleGraph(3) inf = typemax(eltype(G)) - for g in testgraphs(G) + for g in test_generic_graphs(G) z = @inferred(spfa_shortest_paths(g, 1)) @test z == [0, inf, inf] end @@ -78,7 +78,7 @@ rng = StableRNG(seed) nvg = Int(ceil(250 * rand(rng))) neg = Int(floor((nvg * (nvg - 1) / 2) * rand(rng))) - g = SimpleGraph(nvg, neg; rng=rng) + g = GenericGraph(SimpleGraph(nvg, neg; rng=rng)) z = spfa_shortest_paths(g, 1) y = dijkstra_shortest_paths(g, 1) @test isapprox(z, y.dists) @@ -90,7 +90,7 @@ rng = StableRNG(seed) nvg = Int(ceil(250 * rand(rng))) neg = Int(floor((nvg * (nvg - 1) / 2) * rand(rng))) - g = SimpleDiGraph(nvg, neg; rng=rng) + g = GenericDiGraph(SimpleDiGraph(nvg, neg; rng=rng)) z = spfa_shortest_paths(g, 1) y = dijkstra_shortest_paths(g, 1) @test isapprox(z, y.dists) @@ -99,42 +99,42 @@ end @testset "Different types of graph" begin - G = complete_graph(9) + G = GenericGraph(complete_graph(9)) z = spfa_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z, y.dists) - G = complete_digraph(9) + G = GenericDiGraph(complete_digraph(9)) z = spfa_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z, y.dists) - G = cycle_graph(9) + G = GenericGraph(cycle_graph(9)) z = spfa_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z, y.dists) - G = cycle_digraph(9) + G = GenericDiGraph(cycle_digraph(9)) z = spfa_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z, y.dists) - G = star_graph(9) + G = GenericGraph(star_graph(9)) z = spfa_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z, y.dists) - G = wheel_graph(9) + G = GenericGraph(wheel_graph(9)) z = spfa_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z, y.dists) - G = roach_graph(9) + G = GenericGraph(roach_graph(9)) z = spfa_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z, y.dists) - G = clique_graph(5, 19) + G = GenericGraph(clique_graph(5, 19)) z = spfa_shortest_paths(G, 1) y = dijkstra_shortest_paths(G, 1) @test isapprox(z, y.dists) @@ -164,9 +164,10 @@ :truncatedtetrahedron, :truncatedtetrahedron_dir, ] - G = smallgraph(s) - z = spfa_shortest_paths(G, 1) - y = dijkstra_shortest_paths(G, 1) + GS = smallgraph(s) + GG = is_directed(GS) ? GenericDiGraph(GS) : GenericGraph(GS) + z = spfa_shortest_paths(GG, 1) + y = dijkstra_shortest_paths(GG, 1) @test isapprox(z, y.dists) end end @@ -179,7 +180,7 @@ d2 = sparse( float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]) ) - for g in testdigraphs(g4) + for g in test_generic_graphs(g4) y = @inferred(spfa_shortest_paths(g, 2, d1)) z = @inferred(spfa_shortest_paths(g, 2, d2)) @test y == z == [Inf, 0, 6, 17, 33] @@ -198,7 +199,7 @@ @testset "Negative Cycle" begin # Negative Cycle 1 gx = complete_graph(3) - for g in testgraphs(gx) + for g in test_generic_graphs(gx) d = [1 -3 1; -3 1 1; 1 1 1] @test_throws Graphs.NegativeCycleError spfa_shortest_paths(g, 1, d) @test has_negative_edge_cycle_spfa(g, d) @@ -211,7 +212,7 @@ # Negative cycle of length 3 in graph of diameter 4 gx = complete_graph(4) d = [1 -1 1 1; 1 1 1 -1; 1 1 1 1; 1 1 1 1] - for g in testgraphs(gx) + for g in test_generic_graphs(gx) @test_throws Graphs.NegativeCycleError spfa_shortest_paths(g, 1, d) @test has_negative_edge_cycle_spfa(g, d) end diff --git a/test/shortestpaths/yen.jl b/test/shortestpaths/yen.jl index aa1b83413..2014f6ccb 100644 --- a/test/shortestpaths/yen.jl +++ b/test/shortestpaths/yen.jl @@ -1,3 +1,5 @@ +# TODO yen_k_shortest_paths does not wort for GenericGraph yet + @testset "Yen" begin g4 = path_digraph(5) d1 = float([0 1 2 3 4; 5 0 6 7 8; 9 10 0 11 12; 13 14 15 0 16; 17 18 19 20 0]) From a77bc5fe7bda0a9c70c2c7ef8b1ca2155b9ea1a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sch=C3=B6lly?= Date: Wed, 5 Jul 2023 10:56:17 +0200 Subject: [PATCH 09/16] Use GenericGraph for testing spanningtrees algorithms (#276) --- test/spanningtrees/boruvka.jl | 24 +++++++++++++++--------- test/spanningtrees/kruskal.jl | 16 ++++++++++------ test/spanningtrees/prim.jl | 4 ++-- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/test/spanningtrees/boruvka.jl b/test/spanningtrees/boruvka.jl index d91d6173f..dfabbaebe 100644 --- a/test/spanningtrees/boruvka.jl +++ b/test/spanningtrees/boruvka.jl @@ -14,17 +14,19 @@ max_vec_mst = Vector{Edge}([Edge(1, 4), Edge(2, 4), Edge(1, 3)]) cost_max_vec_mst = sum(distmx[src(e), dst(e)] for e in max_vec_mst) - for g in testgraphs(g4) + for g in test_generic_graphs(g4) # Testing Boruvka's algorithm res1 = boruvka_mst(g, distmx) - g1t = SimpleGraph(res1.mst) + edges1 = [Edge(src(e), dst(e)) for e in res1.mst] + g1t = GenericGraph(SimpleGraph(edges1)) @test res1.weight == cost_mst # acyclic graphs have n - c edges @test nv(g1t) - length(connected_components(g1t)) == ne(g1t) @test nv(g1t) == nv(g) res2 = boruvka_mst(g, distmx; minimize=false) - g2t = SimpleGraph(res2.mst) + edges2 = [Edge(src(e), dst(e)) for e in res2.mst] + g2t = GenericGraph(SimpleGraph(edges2)) @test res2.weight == cost_max_vec_mst @test nv(g2t) - length(connected_components(g2t)) == ne(g2t) @test nv(g2t) == nv(g) @@ -53,15 +55,17 @@ ]) weight_max_vec2 = sum(distmx_sec[src(e), dst(e)] for e in max_vec2) - for g in testgraphs(gx) + for g in test_generic_graphs(gx) res3 = boruvka_mst(g, distmx_sec) - g3t = SimpleGraph(res3.mst) + edges3 = [Edge(src(e), dst(e)) for e in res3.mst] + g3t = GenericGraph(SimpleGraph(edges3)) @test res3.weight == weight_vec2 @test nv(g3t) - length(connected_components(g3t)) == ne(g3t) @test nv(g3t) == nv(gx) res4 = boruvka_mst(g, distmx_sec; minimize=false) - g4t = SimpleGraph(res4.mst) + edges4 = [Edge(src(e), dst(e)) for e in res4.mst] + g4t = GenericGraph(SimpleGraph(edges4)) @test res4.weight == weight_max_vec2 @test nv(g4t) - length(connected_components(g4t)) == ne(g4t) @test nv(g4t) == nv(gx) @@ -114,15 +118,17 @@ ]) weight_max_vec3 = sum(distmx_third[src(e), dst(e)] for e in max_vec3) - for g in testgraphs(gd) + for g in test_generic_graphs(gd) res5 = boruvka_mst(g, distmx_third) - g5t = SimpleGraph(res5.mst) + edges5 = [Edge(src(e), dst(e)) for e in res5.mst] + g5t = GenericGraph(SimpleGraph(edges5)) @test res5.weight == weight_vec3 @test nv(g5t) - length(connected_components(g5t)) == ne(g5t) @test nv(g5t) == nv(gd) res6 = boruvka_mst(g, distmx_third; minimize=false) - g6t = SimpleGraph(res6.mst) + edges6 = [Edge(src(e), dst(e)) for e in res6.mst] + g6t = GenericGraph(SimpleGraph(edges6)) @test res6.weight == weight_max_vec3 @test nv(g6t) - length(connected_components(g6t)) == ne(g6t) @test nv(g6t) == nv(gd) diff --git a/test/spanningtrees/kruskal.jl b/test/spanningtrees/kruskal.jl index 6109a9dc6..b45eb8cb6 100644 --- a/test/spanningtrees/kruskal.jl +++ b/test/spanningtrees/kruskal.jl @@ -10,11 +10,14 @@ vec_mst = Vector{Edge}([Edge(1, 2), Edge(3, 4), Edge(2, 3)]) max_vec_mst = Vector{Edge}([Edge(2, 4), Edge(1, 4), Edge(1, 3)]) - for g in testgraphs(g4) + for g in test_generic_graphs(g4) # Testing Kruskal's algorithm mst = @inferred(kruskal_mst(g, distmx)) - @test mst == vec_mst - @test @inferred(kruskal_mst(g, distmx, minimize=false)) == max_vec_mst + max_mst = @inferred(kruskal_mst(g, distmx, minimize=false)) + # GenericEdge currently does not implement any comparison operators + # so instead we compare tuples of source and target vertices + @test sort([(src(e), dst(e)) for e in mst]) == sort([(src(e), dst(e)) for e in vec_mst]) + @test sort([(src(e), dst(e)) for e in max_mst]) == sort([(src(e), dst(e)) for e in max_vec_mst]) end # second test distmx_sec = [ @@ -35,9 +38,10 @@ max_vec2 = Vector{Edge}([ Edge(5, 7), Edge(1, 7), Edge(4, 7), Edge(3, 7), Edge(5, 8), Edge(2, 3), Edge(5, 6) ]) - for g in testgraphs(gx) + for g in test_generic_graphs(gx) mst2 = @inferred(kruskal_mst(g, distmx_sec)) - @test mst2 == vec2 - @test @inferred(kruskal_mst(g, distmx_sec, minimize=false)) == max_vec2 + max_mst2 = @inferred(kruskal_mst(g, distmx_sec, minimize=false)) + @test sort([(src(e), dst(e)) for e in mst2]) == sort([(src(e), dst(e)) for e in vec2]) + @test sort([(src(e), dst(e)) for e in max_mst2]) == sort([(src(e), dst(e)) for e in max_vec2]) end end diff --git a/test/spanningtrees/prim.jl b/test/spanningtrees/prim.jl index 7ee038777..b71078b3b 100644 --- a/test/spanningtrees/prim.jl +++ b/test/spanningtrees/prim.jl @@ -9,7 +9,7 @@ ] vec_mst = Vector{Edge}([Edge(1, 2), Edge(2, 3), Edge(3, 4)]) - for g in testgraphs(g4) + for g in test_generic_graphs(g4) # Testing Prim's algorithm mst = @inferred(prim_mst(g, distmx)) @test mst == vec_mst @@ -31,7 +31,7 @@ Edge(8, 2), Edge(1, 3), Edge(3, 4), Edge(6, 5), Edge(8, 6), Edge(3, 7), Edge(1, 8) ]) gx = SimpleGraph(distmx_sec) - for g in testgraphs(gx) + for g in test_generic_graphs(gx) mst2 = @inferred(prim_mst(g, distmx_sec)) @test mst2 == vec2 end From 45788ab545616fe36ebe0b960901095bd4501a96 Mon Sep 17 00:00:00 2001 From: jonaswa Date: Wed, 5 Jul 2023 15:26:23 +0200 Subject: [PATCH 10/16] test file check --- src/centrality/edge-betweenness.jl | 9 +- test/centrality/edge-betweenness.jl | 169 ++++++++++------------------ 2 files changed, 62 insertions(+), 116 deletions(-) diff --git a/src/centrality/edge-betweenness.jl b/src/centrality/edge-betweenness.jl index bf0e6a6e1..5b91c98d8 100644 --- a/src/centrality/edge-betweenness.jl +++ b/src/centrality/edge-betweenness.jl @@ -50,16 +50,9 @@ julia>edge_betweenness_centrality(g; normalize=false) ⋅ ⋅ ⋅ ⋅ ``` """ - -## -using Graphs, Random, SparseArrays, Plots -GLOBAL_RNG = Random.GLOBAL_RNG - -include("../../src/utils.jl") - function edge_betweenness_centrality( g::AbstractGraph, - vs::AbstractArray=vertices(g), + vs=vertices(g), distmx::AbstractMatrix=weights(g); normalize::Bool=true, ) diff --git a/test/centrality/edge-betweenness.jl b/test/centrality/edge-betweenness.jl index 87256ef59..8c62648e5 100644 --- a/test/centrality/edge-betweenness.jl +++ b/test/centrality/edge-betweenness.jl @@ -1,116 +1,69 @@ -using Test -using Graphs -using Random -using StableRNGs -using DelimitedFiles -include("../../src/centrality/edge-betweenness.jl") +@testset "Edge Betweenness" begin + rng = StableRNG(1) + # self loops + s1 = GenericGraph(SimpleGraph(Edge.([(1, 2), (2, 3), (3, 3)]))) + s2 = GenericDiGraph(SimpleDiGraph(Edge.([(1, 2), (2, 3), (3, 3)]))) + + g3 = GenericGraph(path_graph(5)) + + @test @inferred(edge_betweenness_centrality(s1)) == + sparse([1, 2, 3, 2], [2, 1, 2, 3], [2 / 3, 2 / 3, 2 / 3, 2 / 3], 3, 3) + @test @inferred(edge_betweenness_centrality(s2)) == + sparse([1, 2], [2, 3], [1 / 3, 1 / 3], 3, 3) + + g = GenericGraph(path_graph(2)) + z = @inferred(edge_betweenness_centrality(g; normalize=true)) + @test z[1, 2] == z[2, 1] == 1.0 + z2 = @inferred(edge_betweenness_centrality(g, vertices(g))) + z3 = @inferred(edge_betweenness_centrality(g, collect(vertices(g)))) + @test z == z2 == z3 + z = @inferred(edge_betweenness_centrality(g3; normalize=false)) + @test z[1, 2] == z[5, 4] == 4.0 + + ## + # Weighted Graph tests + g = GenericGraph(SimpleGraph(Edge.([(1, 2), (2, 3), (2, 5), (3, 4), (4, 5), (5, 6)]))) + + distmx = [ + 0.0 2.0 0.0 0.0 0.0 0.0 + 2.0 0.0 4.2 0.0 1.2 0.0 + 0.0 4.2 0.0 5.5 0.0 0.0 + 0.0 0.0 5.5 0.0 0.9 0.0 + 0.0 1.2 0.0 0.9 0.0 0.6 + 0.0 0.0 0.0 0.0 0.6 0.0 + ] -function testgraphs(g) - return if is_directed(g) - [g, DiGraph{UInt8}(g), DiGraph{Int16}(g)] - else - [g, Graph{UInt8}(g), Graph{Int16}(g)] - end -end -testgraphs(gs...) = vcat((testgraphs(g) for g in gs)...) -testdigraphs = testgraphs - -testdir = "test/" - -################################### - -## -rng = StableRNG(1) -# self loops -s1 = GenericGraph(SimpleGraph(Edge.([(1, 2), (2, 3), (3, 3)]))) -s2 = GenericDiGraph(SimpleDiGraph(Edge.([(1, 2), (2, 3), (3, 3)]))) - -g3 = GenericGraph(path_graph(5)) - -gint = loadgraph(joinpath(testdir, "testdata", "graph-50-500.jgz"), "graph-50-500") - -c = vec(readdlm(joinpath(testdir, "testdata", "graph-50-500-bc.txt"), ',')) -## - -for g in test_generic_graphs(gint) - z = @inferred(betweenness_centrality(g)) - @test map(Float32, z) == map(Float32, c) - - y = @inferred(betweenness_centrality(g, endpoints=true, normalize=false)) - @test round.(y[1:3], digits=4) == - round.([122.10760591498584, 159.0072453120582, 176.39547945994505], digits=4) - - x = @inferred(betweenness_centrality(g, 3, rng=rng)) - x2 = @inferred(betweenness_centrality(g, collect(1:20))) - - @test length(x) == 50 - @test length(x2) == 50 -end - -@test @inferred(betweenness_centrality(s1)) == [0, 1, 0] -@test @inferred(betweenness_centrality(s2)) == [0, 0.5, 0] - -g = GenericGraph(path_graph(2)) -z = @inferred(betweenness_centrality(g; normalize=true)) -@test z[1] == z[2] == 0.0 -z2 = @inferred(betweenness_centrality(g, vertices(g))) -z3 = @inferred(betweenness_centrality(g, collect(vertices(g)))) -@test z == z2 == z3 + @test isapprox( + nonzeros(edge_betweenness_centrality(g, vertices(g), distmx; normalize=false)), + [5.0, 5.0, 4.0, 8.0, 4.0, 1.0, 1.0, 4.0, 8.0, 4.0, 5.0, 5.0], + ) -z = @inferred(betweenness_centrality(g3; normalize=false)) -@test z[1] == z[5] == 0.0 + @test isapprox( + nonzeros(edge_betweenness_centrality(g, vertices(g), distmx; normalize=true)), + [5.0, 5.0, 4.0, 8.0, 4.0, 1.0, 1.0, 4.0, 8.0, 4.0, 5.0, 5.0] / + (nv(g) * (nv(g) - 1)) * 2, + ) -# Weighted Graph tests -g = GenericGraph(SimpleGraph(Edge.([(1, 2), (2, 3), (2, 5), (3, 4), (4, 5), (5, 6)]))) + adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph + a2 = SimpleDiGraph(adjmx2) -distmx = [ - 0.0 2.0 0.0 0.0 0.0 0.0 - 2.0 0.0 4.2 0.0 1.2 0.0 - 0.0 4.2 0.0 5.5 0.0 0.0 - 0.0 0.0 5.5 0.0 0.9 0.0 - 0.0 1.2 0.0 0.9 0.0 0.6 - 0.0 0.0 0.0 0.0 0.6 0.0 -] + for g in test_generic_graphs(a2) + distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] + c2 = [0.24390243902439027, 0.27027027027027023, 0.1724137931034483] -@test isapprox( - betweenness_centrality(g, vertices(g), distmx; normalize=false), - [0.0, 6.0, 0.0, 0.0, 6.0, 0.0], -) -@test isapprox( - betweenness_centrality(g, vertices(g), distmx; normalize=false, endpoints=true), - [5.0, 11.0, 5.0, 5.0, 11.0, 5.0], -) -@test isapprox( - betweenness_centrality(g, vertices(g), distmx; normalize=true), - [0.0, 0.6000000000000001, 0.0, 0.0, 0.6000000000000001, 0.0], -) -@test isapprox( - betweenness_centrality(g, vertices(g), distmx; normalize=true, endpoints=true), - [0.5, 1.1, 0.5, 0.5, 1.1, 0.5], -) + @test isapprox( + nonzeros(edge_betweenness_centrality(g, vertices(g), distmx2; normalize=false)), + [1.0, 1.0, 2.0, 1.0, 2.0], + ) -adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph -a2 = SimpleDiGraph(adjmx2) -for g in test_generic_graphs(a2) - distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] - c2 = [0.24390243902439027, 0.27027027027027023, 0.1724137931034483] - @test isapprox( - betweenness_centrality(g, vertices(g), distmx2; normalize=false), [0.0, 1.0, 0.0] - ) - @test isapprox( - betweenness_centrality(g, vertices(g), distmx2; normalize=false, endpoints=true), - [4.0, 5.0, 4.0], - ) - @test isapprox( - betweenness_centrality(g, vertices(g), distmx2; normalize=true), [0.0, 0.5, 0.0] - ) - @test isapprox( - betweenness_centrality(g, vertices(g), distmx2; normalize=true, endpoints=true), - [2.0, 2.5, 2.0], - ) + @test isapprox( + nonzeros(edge_betweenness_centrality(g, vertices(g), distmx2; normalize=true)), + [1.0, 1.0, 2.0, 1.0, 2.0] * (1 / 6), + ) + end + # test #1405 / #1406 + g = GenericGraph(grid([50, 50])) + z = edge_betweenness_centrality(g; normalize=false) + @test maximum(z) < nv(g) * (nv(g) - 1) end -# test #1405 / #1406 -g = GenericGraph(grid([50, 50])) -z = betweenness_centrality(g; normalize=false) -@test maximum(z) < nv(g) * (nv(g) - 1) \ No newline at end of file From 894b9f723fa148130bf57862196c53843a82bd7e Mon Sep 17 00:00:00 2001 From: jonaswa Date: Wed, 5 Jul 2023 17:20:49 +0200 Subject: [PATCH 11/16] added ebc to graphs.jl and to runtests.jl --- src/Graphs.jl | 2 ++ test/runtests.jl | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Graphs.jl b/src/Graphs.jl index c8d209cf5..b808ea7da 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -250,6 +250,7 @@ export desopo_pape_shortest_paths, # centrality + edge_betweenness_centrality, betweenness_centrality, closeness_centrality, degree_centrality, @@ -504,6 +505,7 @@ include("operators.jl") include("persistence/common.jl") include("persistence/lg.jl") include("centrality/betweenness.jl") +include("centrality/edge-betweenness.jl") include("centrality/closeness.jl") include("centrality/stress.jl") include("centrality/degree.jl") diff --git a/test/runtests.jl b/test/runtests.jl index cbb8763bb..4bf800cad 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,7 +31,9 @@ end @testset "Code quality (JET.jl)" begin if VERSION >= v"1.9" @assert get_pkg_version("JET") >= v"0.8.4" - JET.test_package(Graphs; target_defined_modules=true, ignore_missing_comparison=true) + JET.test_package( + Graphs; target_defined_modules=true, ignore_missing_comparison=true + ) end end @@ -70,7 +72,7 @@ function test_generic_graphs(g; eltypes=[UInt8, Int16], skip_if_too_large::Bool= SG = is_directed(g) ? SimpleDiGraph : SimpleGraph GG = is_directed(g) ? GenericDiGraph : GenericGraph result = GG[] - for T in eltypes + for T in eltypes if skip_if_too_large && nv(g) > typemax(T) continue end @@ -79,8 +81,11 @@ function test_generic_graphs(g; eltypes=[UInt8, Int16], skip_if_too_large::Bool= return result end -test_large_generic_graphs(g; skip_if_too_large::Bool=false) = test_generic_graphs(g; eltypes=[UInt16, Int32], skip_if_too_large=skip_if_too_large) - +function test_large_generic_graphs(g; skip_if_too_large::Bool=false) + return test_generic_graphs( + g; eltypes=[UInt16, Int32], skip_if_too_large=skip_if_too_large + ) +end tests = [ "simplegraphs/runtests", @@ -124,6 +129,7 @@ tests = [ "community/clique_percolation", "community/assortativity", "community/rich_club", + "centrality/edge-betweenness", "centrality/betweenness", "centrality/closeness", "centrality/degree", From 0b52ed85961989912e63ec106016ba0a9f2617b4 Mon Sep 17 00:00:00 2001 From: jonaswa Date: Wed, 5 Jul 2023 17:34:25 +0200 Subject: [PATCH 12/16] using AbstractSparseMatrix in graphs.jl --- src/Graphs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graphs.jl b/src/Graphs.jl index b808ea7da..e4dbe8afb 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -35,7 +35,7 @@ using Random: seed!, shuffle, shuffle! -using SparseArrays: SparseMatrixCSC, nonzeros, nzrange, rowvals +using SparseArrays: SparseMatrixCSC, nonzeros, nzrange, rowvals, AbstractSparseMatrix import SparseArrays: blockdiag, sparse import Base: adjoint, From 00abd735b3611df0289b2f2e5e1e0e3704f9b157 Mon Sep 17 00:00:00 2001 From: jonaswa Date: Wed, 5 Jul 2023 17:39:12 +0200 Subject: [PATCH 13/16] added spzeros to graphs.jl --- src/Graphs.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Graphs.jl b/src/Graphs.jl index e4dbe8afb..66e2b2feb 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -35,7 +35,8 @@ using Random: seed!, shuffle, shuffle! -using SparseArrays: SparseMatrixCSC, nonzeros, nzrange, rowvals, AbstractSparseMatrix +using SparseArrays: + SparseMatrixCSC, nonzeros, nzrange, rowvals, spzeros, AbstractSparseMatrix import SparseArrays: blockdiag, sparse import Base: adjoint, From a4016b7daa7b6ff724e608a049b4c17833cae063 Mon Sep 17 00:00:00 2001 From: jonaswa Date: Wed, 5 Jul 2023 18:21:41 +0200 Subject: [PATCH 14/16] change doctest bc abstractarray not recongnized --- src/centrality/edge-betweenness.jl | 39 ++++++++++++------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/src/centrality/edge-betweenness.jl b/src/centrality/edge-betweenness.jl index 5b91c98d8..d55c01f3a 100644 --- a/src/centrality/edge-betweenness.jl +++ b/src/centrality/edge-betweenness.jl @@ -24,31 +24,22 @@ and for a directed graph, ````1/(|V|(|V|-1))````. ```jldoctest julia> using Graphs -julia> edge_betweenness_centrality(star_graph(5)) -5×5 SparseMatrixCSC{Float64, Int64} with 8 stored entries: - ⋅ 0.4 0.4 0.4 0.4 - 0.4 ⋅ ⋅ ⋅ ⋅ - 0.4 ⋅ ⋅ ⋅ ⋅ - 0.4 ⋅ ⋅ ⋅ ⋅ - 0.4 ⋅ ⋅ ⋅ ⋅ +julia> Matrix(edge_betweenness_centrality(star_graph(5))) +5×5 Matrix{Float64}: + 0.0 0.4 0.4 0.4 0.4 + 0.4 0.0 0.0 0.0 0.0 + 0.4 0.0 0.0 0.0 0.0 + 0.4 0.0 0.0 0.0 0.0 + 0.4 0.0 0.0 0.0 0.0 -julia> edge_betweenness_centrality(path_digraph(6), normalize=false) -6×6 SparseMatrixCSC{Float64, Int64} with 5 stored entries: - ⋅ 5.0 ⋅ ⋅ ⋅ ⋅ - ⋅ ⋅ 8.0 ⋅ ⋅ ⋅ - ⋅ ⋅ ⋅ 9.0 ⋅ ⋅ - ⋅ ⋅ ⋅ ⋅ 8.0 ⋅ - ⋅ ⋅ ⋅ ⋅ ⋅ 5.0 - ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ - -julia>g = SimpleWeightedDiGraph([1, 2, 3, 2, 2], [2, 3, 1, 4, 1], [1, 2, 3, 4, 5]; combine=+) -julia>edge_betweenness_centrality(g; normalize=false) -4×4 SparseMatrixCSC{Float64, Int64} with 5 stored entries: - ⋅ 5.0 ⋅ ⋅ - 0.5 ⋅ 2.5 3.0 - 3.5 ⋅ ⋅ ⋅ - ⋅ ⋅ ⋅ ⋅ -``` + julia> Matrix(edge_betweenness_centrality(path_digraph(6), normalize=false)) + 6×6 Matrix{Float64}: + 0.0 5.0 0.0 0.0 0.0 0.0 + 0.0 0.0 8.0 0.0 0.0 0.0 + 0.0 0.0 0.0 9.0 0.0 0.0 + 0.0 0.0 0.0 0.0 8.0 0.0 + 0.0 0.0 0.0 0.0 0.0 5.0 + 0.0 0.0 0.0 0.0 0.0 0.0 """ function edge_betweenness_centrality( g::AbstractGraph, From a1539e4877db7a82a0eabe0409d6ad0429e1ea55 Mon Sep 17 00:00:00 2001 From: jonaswa Date: Wed, 5 Jul 2023 22:08:23 +0200 Subject: [PATCH 15/16] update doc for weighted ebc & vs to kwarg --- src/centrality/edge-betweenness.jl | 55 ++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/src/centrality/edge-betweenness.jl b/src/centrality/edge-betweenness.jl index d55c01f3a..99755bc99 100644 --- a/src/centrality/edge-betweenness.jl +++ b/src/centrality/edge-betweenness.jl @@ -1,7 +1,11 @@ """ - edge_betweenness_centrality(g, k) + edge_betweenness_centrality(g [, vertices, distmx]; [normalize]) + edge_betweenness_centrality(g, k [, distmx]; [normalize, rng]) -Compute the [edge betweenness centrality](https://en.wikipedia.org/wiki/Centrality#Betweenness_centrality) of an edge `e`. +Compute the [edge betweenness centrality](https://en.wikipedia.org/wiki/Centrality#Betweenness_centrality) +of every edge `g` in a graph `g` or use a specified subset of vertices `vs`, or a random subset of `k` +vertices to estimate the edge betweenness of the edges. Including more nodes return better results. +Return a Sparse Matrix representing the centrality calculated for each edge in `g`. It is defined as the sum of the fraction of all-pairs shortest paths that pass through `e` `` bc(e) = \\sum_{s, t \\in V} @@ -11,10 +15,14 @@ bc(e) = \\sum_{s, t \\in V} where `V`, is the set of nodes, \\frac{\\sigma_{st}} is the number of shortest-paths, and \\frac{\\sigma_{st}(e)} is the number of those paths passing through edge. ### Optional Arguments -- `normalize=true`: If true, normalize the betweenness values by the -total number of possible distinct paths between all pairs in the graphs. -For an undirected graph, this number is ``2/(|V|(|V|-1))`` -and for a directed graph, ````1/(|V|(|V|-1))````. +`normalize=true` : If set to true, the edge betweenness values will be normalized by the total number of possible distinct paths between all pairs of nodes in the graph. +For undirected graphs, the normalization factor is calculated as ``2 / (|V|(|V|-1))``, where |V| is the number of vertices. For directed graphs, the normalization factor +is calculated as ``1 / (|V|(|V|-1))``. +`vs=vertices(g)`: A subset of nodes in the graph g for which the edge betweenness centrality is to be estimated. By including more nodes in this subset, +you can achieve a better estimate of the edge betweenness centrality. +`distmx=weights(g)`: The weights of the edges in the graph g represented as a matrix. This argument allows you to specify custom weights for the edges. +The weights can be used to influence the calculation of betweenness centrality, giving higher importance to certain edges over others. +`rng`: A random number generator used for selecting k vertices. This argument allows you to provide a custom random number generator that will be used for the vertex selection process. ### References @@ -40,11 +48,31 @@ julia> Matrix(edge_betweenness_centrality(star_graph(5))) 0.0 0.0 0.0 0.0 8.0 0.0 0.0 0.0 0.0 0.0 0.0 5.0 0.0 0.0 0.0 0.0 0.0 0.0 + + julia> g = SimpleGraph(Edge.([(1, 2), (2, 3), (2, 5), (3, 4), (4, 5), (5, 6)])); + julia> distmx = [ + 0.0 2.0 0.0 0.0 0.0 0.0 + 2.0 0.0 4.2 0.0 1.2 0.0 + 0.0 4.2 0.0 5.5 0.0 0.0 + 0.0 0.0 5.5 0.0 0.9 0.0 + 0.0 1.2 0.0 0.9 0.0 0.6 + 0.0 0.0 0.0 0.0 0.6 0.0 + ]; + + julia> Matrix(edge_betweenness_centrality(g; distmx=distmx, normalize=true)) + 6×6 Matrix{Float64}: + 0.0 0.333333 … 0.0 0.0 + 0.333333 0.0 0.533333 0.0 + 0.0 0.266667 0.0 0.0 + 0.0 0.0 0.266667 0.0 + 0.0 0.533333 0.0 0.333333 + 0.0 0.0 … 0.333333 0.0 """ + function edge_betweenness_centrality( - g::AbstractGraph, + g::AbstractGraph; vs=vertices(g), - distmx::AbstractMatrix=weights(g); + distmx::AbstractMatrix=weights(g), normalize::Bool=true, ) k = length(vs) @@ -62,16 +90,15 @@ end function edge_betweenness_centrality( g::AbstractGraph, - k::Integer, - distmx::AbstractMatrix=weights(g); + k::Integer; + distmx::AbstractMatrix=weights(g), normalize=true, rng::Union{Nothing,AbstractRNG}=nothing, - seed::Union{Nothing,Integer}=nothing, ) return edge_betweenness_centrality( - g, - sample(collect_if_not_vector(vertices(g)), k; rng=rng, seed=seed), - distmx; + g; + vs=sample(collect_if_not_vector(vertices(g)), k; rng=rng), + distmx=distmx, normalize=normalize, ) end From 921d8376500fd23f97f1924689459248c63b56a9 Mon Sep 17 00:00:00 2001 From: jonaswa Date: Wed, 5 Jul 2023 22:26:01 +0200 Subject: [PATCH 16/16] update ebc test such that vertices is kwarg --- src/centrality/edge-betweenness.jl | 8 ++++---- test/centrality/edge-betweenness.jl | 24 ++++++++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/centrality/edge-betweenness.jl b/src/centrality/edge-betweenness.jl index 99755bc99..c9dd81fc6 100644 --- a/src/centrality/edge-betweenness.jl +++ b/src/centrality/edge-betweenness.jl @@ -1,10 +1,10 @@ """ - edge_betweenness_centrality(g [, vertices, distmx]; [normalize]) - edge_betweenness_centrality(g, k [, distmx]; [normalize, rng]) + edge_betweenness_centrality(g[, vertices, distmx]; [normalize]) + edge_betweenness_centrality(g, k[, distmx]; [normalize, rng]) Compute the [edge betweenness centrality](https://en.wikipedia.org/wiki/Centrality#Betweenness_centrality) -of every edge `g` in a graph `g` or use a specified subset of vertices `vs`, or a random subset of `k` -vertices to estimate the edge betweenness of the edges. Including more nodes return better results. +of every edge `e` in a graph `g`. Or use a random subset of `k<|V|` vertices +to get an estimate of the edge betweenness centrality. Including more nodes yields better more accurate estimates. Return a Sparse Matrix representing the centrality calculated for each edge in `g`. It is defined as the sum of the fraction of all-pairs shortest paths that pass through `e` `` diff --git a/test/centrality/edge-betweenness.jl b/test/centrality/edge-betweenness.jl index 8c62648e5..881893cca 100644 --- a/test/centrality/edge-betweenness.jl +++ b/test/centrality/edge-betweenness.jl @@ -15,8 +15,8 @@ g = GenericGraph(path_graph(2)) z = @inferred(edge_betweenness_centrality(g; normalize=true)) @test z[1, 2] == z[2, 1] == 1.0 - z2 = @inferred(edge_betweenness_centrality(g, vertices(g))) - z3 = @inferred(edge_betweenness_centrality(g, collect(vertices(g)))) + z2 = @inferred(edge_betweenness_centrality(g; vs=vertices(g))) + z3 = @inferred(edge_betweenness_centrality(g, nv(g))) @test z == z2 == z3 z = @inferred(edge_betweenness_centrality(g3; normalize=false)) @test z[1, 2] == z[5, 4] == 4.0 @@ -35,12 +35,16 @@ ] @test isapprox( - nonzeros(edge_betweenness_centrality(g, vertices(g), distmx; normalize=false)), + nonzeros( + edge_betweenness_centrality(g; vs=vertices(g), distmx=distmx, normalize=false) + ), [5.0, 5.0, 4.0, 8.0, 4.0, 1.0, 1.0, 4.0, 8.0, 4.0, 5.0, 5.0], ) @test isapprox( - nonzeros(edge_betweenness_centrality(g, vertices(g), distmx; normalize=true)), + nonzeros( + edge_betweenness_centrality(g; vs=vertices(g), distmx=distmx, normalize=true) + ), [5.0, 5.0, 4.0, 8.0, 4.0, 1.0, 1.0, 4.0, 8.0, 4.0, 5.0, 5.0] / (nv(g) * (nv(g) - 1)) * 2, ) @@ -53,12 +57,20 @@ c2 = [0.24390243902439027, 0.27027027027027023, 0.1724137931034483] @test isapprox( - nonzeros(edge_betweenness_centrality(g, vertices(g), distmx2; normalize=false)), + nonzeros( + edge_betweenness_centrality( + g; vs=vertices(g), distmx=distmx2, normalize=false + ), + ), [1.0, 1.0, 2.0, 1.0, 2.0], ) @test isapprox( - nonzeros(edge_betweenness_centrality(g, vertices(g), distmx2; normalize=true)), + nonzeros( + edge_betweenness_centrality( + g; vs=vertices(g), distmx=distmx2, normalize=true + ), + ), [1.0, 1.0, 2.0, 1.0, 2.0] * (1 / 6), ) end