From 7cdcb138ce202a0cad91e933e006b3e8e1cf1c90 Mon Sep 17 00:00:00 2001 From: Jihyung Kim Date: Mon, 16 Feb 2026 20:18:24 -0500 Subject: [PATCH 01/10] Implement iFUB for weighted graph --- src/distance.jl | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ test/distance.jl | 44 ++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/src/distance.jl b/src/distance.jl index dd48270b6..5c726b582 100644 --- a/src/distance.jl +++ b/src/distance.jl @@ -115,6 +115,7 @@ diameter(eccentricities::Vector) = maximum(eccentricities) diameter(g::AbstractGraph) = diameter(g, weights(g)) + function diameter(g::AbstractGraph, ::DefaultDistance) if nv(g) == 0 return 0 @@ -129,9 +130,28 @@ function diameter(g::AbstractGraph, ::DefaultDistance) return _diameter_ifub(g) end +function diameter(g::AbstractGraph, distmx::AbstractMatrix) + # If the graph is unweighted (DefaultDistance), use the existing BFS-based iFUB + if distmx isa DefaultDistance + return diameter(g, DefaultDistance(nv(g))) # Redirects to existing _diameter_ifub logic + end + + # For weighted directed graphs, strictly implementing DiFUB requires efficient + # backward Dijkstra traversals + # For simplicity, we fall back to the naive method for directed weighted graphs + # and use the optimized iFUB for undirected weighted graphs + if is_directed(g) + return maximum(eccentricity(g, vertices(g), distmx)) + end + + return _diameter_weighted(g, distmx) +end + +#= function diameter(g::AbstractGraph, distmx::AbstractMatrix) return maximum(eccentricity(g, distmx)) end +=# function _diameter_ifub(g::AbstractGraph{T}) where {T<:Integer} nvg = nv(g) @@ -230,6 +250,68 @@ function _diameter_ifub(g::AbstractGraph{T}) where {T<:Integer} return diam end +function _diameter_weighted(g::AbstractGraph, distmx::AbstractMatrix{T}) where T <: Number + # Handle empty graph + nv(g) == 0 && return zero(T) + + # 1. Heuristic: Start from a node 'u' with high degree + u = argmax(degree(g)) + + # 2. Compute Shortest Path Tree from u + ds = dijkstra_shortest_paths(g, u, distmx) + dists = ds.dists + + # Handle disconnected components + # If u cannot reach all nodes, the graph is disconnected -> infinite diameter + valid_nodes = findall(d -> d < typemax(T), dists) + if length(valid_nodes) < nv(g) + return typemax(T) + end + + # 3. Identify distinct sorted distances + unique_dists = unique(dists[valid_nodes]) + sort!(unique_dists) + + # Initialize Lower Bound (lb) with eccentricity of u + lb = unique_dists[end] + + # Group nodes by distance + nodes_by_dist = Dict{T, Vector{Int}}() + for v in valid_nodes + d = dists[v] + if !haskey(nodes_by_dist, d) + nodes_by_dist[d] = Int[] + end + push!(nodes_by_dist[d], v) + end + + # 4. Iterate backward + num_levels = length(unique_dists) + + for i in num_levels:-1:2 + d_i = unique_dists[i] + d_prev = unique_dists[i-1] + + # Process the "Fringe" at distance d_i FIRST + fringe = nodes_by_dist[d_i] + for v in fringe + ecc_v = maximum(dijkstra_shortest_paths(g, v, distmx).dists) + if ecc_v > lb + lb = ecc_v + end + end + + # Pruning Condition: Check AFTER processing the current level + # If the found lower bound is greater than 2 * (distance of previous level), + # we can stop. Nodes in previous levels cannot form a path longer than 2*d_prev. + if lb > 2 * d_prev + return lb + end + end + + return lb +end + """ periphery(eccentricities) periphery(g, distmx=weights(g)) diff --git a/test/distance.jl b/test/distance.jl index 66a81a576..2aa9caa08 100644 --- a/test/distance.jl +++ b/test/distance.jl @@ -79,7 +79,7 @@ return maximum(eccentricity(g)) end - NUM_SAMPLES = 50 # Adjust this to change test duration + NUM_SAMPLES = 50 for i in 1:NUM_SAMPLES # Random unweighted Graphs @@ -140,4 +140,44 @@ g2 = GenericGraph(path_graph(2)) @test_logs eccentricity(g2) end -end + + @testset "Weighted Diameter Benchmarks" begin + n_bench = 3000 + + function symmetric_weights(n) + W = rand(n, n) + return (W + W') / 2 + end + + @testset "Erdős-Rényi (ER) Model" begin + p = 10 / n_bench + g = erdos_renyi(n_bench, p) + while !is_connected(g) + g = erdos_renyi(n_bench, p) + end + + distmx = symmetric_weights(n_bench) + + t_opt = @elapsed d_opt = diameter(g, distmx) + t_naive = @elapsed d_naive = maximum(eccentricity(g, vertices(g), distmx)) + + @test d_opt ≈ d_naive + @info "ER Speedup: $(round(t_naive/t_opt, digits=1))x" + end + + @testset "Barabási-Albert (BA) Model" begin + g = barabasi_albert(n_bench, 5) + while !is_connected(g) + g = barabasi_albert(n_bench, 5) + end + + distmx = symmetric_weights(n_bench) + + t_opt = @elapsed d_opt = diameter(g, distmx) + t_naive = @elapsed d_naive = maximum(eccentricity(g, vertices(g), distmx)) + + @test d_opt ≈ d_naive + @info "BA Speedup: $(round(t_naive/t_opt, digits=3))x" + end + end +end \ No newline at end of file From cb5b840da5a2d1c57fe8ccac4f0fdd0d2614470c Mon Sep 17 00:00:00 2001 From: Jihyung Kim Date: Mon, 16 Feb 2026 21:39:47 -0500 Subject: [PATCH 02/10] JuliaFormatter for formatting --- src/distance.jl | 9 ++++----- test/distance.jl | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/distance.jl b/src/distance.jl index 5c726b582..ea36d45dc 100644 --- a/src/distance.jl +++ b/src/distance.jl @@ -115,7 +115,6 @@ diameter(eccentricities::Vector) = maximum(eccentricities) diameter(g::AbstractGraph) = diameter(g, weights(g)) - function diameter(g::AbstractGraph, ::DefaultDistance) if nv(g) == 0 return 0 @@ -250,7 +249,7 @@ function _diameter_ifub(g::AbstractGraph{T}) where {T<:Integer} return diam end -function _diameter_weighted(g::AbstractGraph, distmx::AbstractMatrix{T}) where T <: Number +function _diameter_weighted(g::AbstractGraph, distmx::AbstractMatrix{T}) where {T<:Number} # Handle empty graph nv(g) == 0 && return zero(T) @@ -276,7 +275,7 @@ function _diameter_weighted(g::AbstractGraph, distmx::AbstractMatrix{T}) where T lb = unique_dists[end] # Group nodes by distance - nodes_by_dist = Dict{T, Vector{Int}}() + nodes_by_dist = Dict{T,Vector{Int}}() for v in valid_nodes d = dists[v] if !haskey(nodes_by_dist, d) @@ -287,10 +286,10 @@ function _diameter_weighted(g::AbstractGraph, distmx::AbstractMatrix{T}) where T # 4. Iterate backward num_levels = length(unique_dists) - + for i in num_levels:-1:2 d_i = unique_dists[i] - d_prev = unique_dists[i-1] + d_prev = unique_dists[i - 1] # Process the "Fringe" at distance d_i FIRST fringe = nodes_by_dist[d_i] diff --git a/test/distance.jl b/test/distance.jl index 2aa9caa08..e07c52954 100644 --- a/test/distance.jl +++ b/test/distance.jl @@ -143,41 +143,41 @@ @testset "Weighted Diameter Benchmarks" begin n_bench = 3000 - + function symmetric_weights(n) W = rand(n, n) return (W + W') / 2 end - @testset "Erdős-Rényi (ER) Model" begin + @testset "Erdős-Rényi Graph" begin p = 10 / n_bench g = erdos_renyi(n_bench, p) while !is_connected(g) g = erdos_renyi(n_bench, p) end - + distmx = symmetric_weights(n_bench) - + t_opt = @elapsed d_opt = diameter(g, distmx) t_naive = @elapsed d_naive = maximum(eccentricity(g, vertices(g), distmx)) - + @test d_opt ≈ d_naive - @info "ER Speedup: $(round(t_naive/t_opt, digits=1))x" + @info "Speedup on Erdős-Rényi Graph: $(round(t_naive/t_opt, digits=1))x" end - @testset "Barabási-Albert (BA) Model" begin + @testset "Barabási-Albert Model" begin g = barabasi_albert(n_bench, 5) while !is_connected(g) g = barabasi_albert(n_bench, 5) end - + distmx = symmetric_weights(n_bench) - + t_opt = @elapsed d_opt = diameter(g, distmx) t_naive = @elapsed d_naive = maximum(eccentricity(g, vertices(g), distmx)) - + @test d_opt ≈ d_naive - @info "BA Speedup: $(round(t_naive/t_opt, digits=3))x" + @info "Speedup Barabási-Albert Model: $(round(t_naive/t_opt, digits=3))x" end end -end \ No newline at end of file +end From 69251964e05cc8af53492e5fee0568b6ddababfd Mon Sep 17 00:00:00 2001 From: Jihyung Kim Date: Sun, 22 Feb 2026 18:05:53 -0500 Subject: [PATCH 03/10] Implement DiFUB for Weighted Directed Graphs --- src/distance.jl | 296 ++++++++++++++++++++++++++---------------------- 1 file changed, 161 insertions(+), 135 deletions(-) diff --git a/src/distance.jl b/src/distance.jl index ea36d45dc..9473f6630 100644 --- a/src/distance.jl +++ b/src/distance.jl @@ -95,8 +95,9 @@ end Given a graph and optional distance matrix, or a vector of precomputed eccentricities, return the maximum eccentricity of the graph. -An optimizied BFS algorithm (iFUB) is used for unweighted graphs, both in [undirected](https://www.sciencedirect.com/science/article/pii/S0304397512008687) -and [directed](https://link.springer.com/chapter/10.1007/978-3-642-30850-5_10) cases. +An optimizied BFS algorithm (iFUB) is used, both in [undirected](https://www.sciencedirect.com/science/article/pii/S0304397512008687) +and [directed](https://link.springer.com/chapter/10.1007/978-3-642-30850-5_10) cases. For weighted graphs, +dijkstra is used to compute shortest path trees, and the algorithm iterates over sorted distinct distance values. # Examples ```jldoctest @@ -115,52 +116,29 @@ diameter(eccentricities::Vector) = maximum(eccentricities) diameter(g::AbstractGraph) = diameter(g, weights(g)) -function diameter(g::AbstractGraph, ::DefaultDistance) - if nv(g) == 0 - return 0 - end - - connected = is_directed(g) ? is_strongly_connected(g) : is_connected(g) - - if !connected - return typemax(Int) - end - - return _diameter_ifub(g) -end - function diameter(g::AbstractGraph, distmx::AbstractMatrix) - # If the graph is unweighted (DefaultDistance), use the existing BFS-based iFUB if distmx isa DefaultDistance - return diameter(g, DefaultDistance(nv(g))) # Redirects to existing _diameter_ifub logic + return diameter(g, DefaultDistance(nv(g))) end - # For weighted directed graphs, strictly implementing DiFUB requires efficient - # backward Dijkstra traversals - # For simplicity, we fall back to the naive method for directed weighted graphs - # and use the optimized iFUB for undirected weighted graphs if is_directed(g) - return maximum(eccentricity(g, vertices(g), distmx)) + return _diameter_weighted_directed(g, distmx) + else + return _diameter_weighted_undirected(g, distmx) end - - return _diameter_weighted(g, distmx) end -#= -function diameter(g::AbstractGraph, distmx::AbstractMatrix) - return maximum(eccentricity(g, distmx)) +function diameter(g::AbstractGraph, ::DefaultDistance) + nv(g) == 0 && return 0 + + connected = is_directed(g) ? is_strongly_connected(g) : is_connected(g) + !connected && return typemax(Int) + + return _diameter_ifub(g) end -=# function _diameter_ifub(g::AbstractGraph{T}) where {T<:Integer} nvg = nv(g) - out_list = [outneighbors(g, v) for v in vertices(g)] - - if is_directed(g) - in_list = [inneighbors(g, v) for v in vertices(g)] - else - in_list = out_list - end active = trues(nvg) visited = falses(nvg) @@ -170,142 +148,190 @@ function _diameter_ifub(g::AbstractGraph{T}) where {T<:Integer} # Sort vertices by total degree (descending) to maximize pruning potential vs = collect(vertices(g)) - sort!(vs; by=v -> -(length(out_list[v]) + length(in_list[v]))) + sort!(vs; by=v -> -degree(g, v)) for u in vs - if !active[u] - continue + !active[u] && continue + + # Forward BFS + e = _fwd_bfs_eccentricity!(g, u, visited, queue) + diam = max(diam, e) + + # Backward BFS + dmax = diam - e + if dmax >= 0 + _bwd_bfs_prune!(g, u, active, distbuf, queue, dmax, e, diam) end - # Forward BFS from u - fill!(visited, false) - visited[u] = true - queue[1] = u - front = 1 - back = 2 - level_end = 1 - e = 0 - - while front < back - v = queue[front] - front += 1 - - @inbounds for w in out_list[v] - if !visited[w] - visited[w] = true - queue[back] = w - back += 1 - end - end + !any(active) && break + end - if front > level_end && front < back - e += 1 - level_end = back - 1 + return diam +end + +# iFUB Helpers + +function _fwd_bfs_eccentricity!(g, u, visited, queue) + fill!(visited, false) + visited[u] = true + queue[1] = u + front, back, level_end, e = 1, 2, 1, 0 + + while front < back + v = queue[front] + front += 1 + + @inbounds for w in outneighbors(g, v) + if !visited[w] + visited[w] = true + queue[back] = w + back += 1 end end - diam = max(diam, e) - # Backward BFS (Pruning) - dmax = diam - e + if front > level_end && front < back + e += 1 + level_end = back - 1 + end + end + return e +end - # Only prune if we have a chance to exceed the current diameter - if dmax >= 0 - fill!(distbuf, typemax(T)) - distbuf[u] = 0 - queue[1] = u - front = 1 - back = 2 - - while front < back - v = queue[front] - front += 1 - - if distbuf[v] >= dmax - continue - end - - @inbounds for w in in_list[v] - if distbuf[w] == typemax(T) - distbuf[w] = distbuf[v] + 1 - queue[back] = w - back += 1 - end - end - end +function _bwd_bfs_prune!(g, u, active, distbuf, queue, dmax, e, diam) + T = eltype(queue) + fill!(distbuf, typemax(T)) + distbuf[u] = 0 + queue[1] = u + front, back = 1, 2 - # Prune vertices that cannot lead to a longer diameter - @inbounds for v in vertices(g) - if active[v] && distbuf[v] != typemax(T) && (distbuf[v] + e <= diam) - active[v] = false - end + while front < back + v = queue[front] + front += 1 + + distbuf[v] >= dmax && continue + + @inbounds for w in inneighbors(g, v) + if distbuf[w] == typemax(T) + distbuf[w] = distbuf[v] + 1 + queue[back] = w + back += 1 end end + end - if !any(active) - break + # Prune vertices + @inbounds for v in eachindex(active) + if active[v] && distbuf[v] != typemax(T) && (distbuf[v] + e <= diam) + active[v] = false end end +end - return diam +function _safe_reverse(g::T) where {T<:AbstractGraph} + if hasmethod(reverse, Tuple{T}) + return reverse(g) + else + U = eltype(g) + rg = SimpleDiGraph{U}(nv(g)) + @inbounds for v in vertices(g) + for w in outneighbors(g, v) + add_edge!(rg, w, v) + end + end + return rg + end end -function _diameter_weighted(g::AbstractGraph, distmx::AbstractMatrix{T}) where {T<:Number} - # Handle empty graph +function _diameter_weighted_directed( + g::AbstractGraph, distmx::AbstractMatrix{T} +) where {T<:Number} nv(g) == 0 && return zero(T) + U = eltype(g) + u = U(argmax(degree(g))) - # 1. Heuristic: Start from a node 'u' with high degree - u = argmax(degree(g)) + # Compute base trees + g_rev = _safe_reverse(g) + distmx_rev = permutedims(distmx) - # 2. Compute Shortest Path Tree from u - ds = dijkstra_shortest_paths(g, u, distmx) - dists = ds.dists + dists_fwd = dijkstra_shortest_paths(g, u, distmx).dists + dists_bwd = dijkstra_shortest_paths(g_rev, u, distmx_rev).dists - # Handle disconnected components - # If u cannot reach all nodes, the graph is disconnected -> infinite diameter - valid_nodes = findall(d -> d < typemax(T), dists) - if length(valid_nodes) < nv(g) + if maximum(dists_fwd) == typemax(T) || maximum(dists_bwd) == typemax(T) return typemax(T) end - # 3. Identify distinct sorted distances - unique_dists = unique(dists[valid_nodes]) - sort!(unique_dists) + # Group fringes and initialize lower bound + unique_dists = sort!(unique(vcat(dists_fwd, dists_bwd))) + lb = max(maximum(dists_fwd), maximum(dists_bwd)) + + fringe_fwd = Dict{T,Vector{Int}}() + fringe_bwd = Dict{T,Vector{Int}}() + + @inbounds for v in vertices(g) + push!(get!(fringe_fwd, dists_fwd[v], Int[]), v) + push!(get!(fringe_bwd, dists_bwd[v], Int[]), v) + end + + # Evaluate fringes backward + for i in length(unique_dists):-1:2 + d_i = unique_dists[i] + d_prev = unique_dists[i - 1] - # Initialize Lower Bound (lb) with eccentricity of u - lb = unique_dists[end] + if haskey(fringe_fwd, d_i) + for v in fringe_fwd[d_i] + ds = dijkstra_shortest_paths(g_rev, U(v), distmx_rev) + lb = max(lb, maximum(ds.dists)) + end + end - # Group nodes by distance - nodes_by_dist = Dict{T,Vector{Int}}() - for v in valid_nodes - d = dists[v] - if !haskey(nodes_by_dist, d) - nodes_by_dist[d] = Int[] + if haskey(fringe_bwd, d_i) + for v in fringe_bwd[d_i] + ds = dijkstra_shortest_paths(g, U(v), distmx) + lb = max(lb, maximum(ds.dists)) + end end - push!(nodes_by_dist[d], v) + + lb > 2 * d_prev && break end - # 4. Iterate backward - num_levels = length(unique_dists) + return lb +end - for i in num_levels:-1:2 +function _diameter_weighted_undirected( + g::AbstractGraph, distmx::AbstractMatrix{T} +) where {T<:Number} + nv(g) == 0 && return zero(T) + U = eltype(g) + u = U(argmax(degree(g))) + + # Compute base trees + dists = dijkstra_shortest_paths(g, u, distmx).dists + + if maximum(dists) == typemax(T) + return typemax(T) + end + + # Group fringes and initialize lower bound + unique_dists = sort!(unique(dists)) + lb = maximum(dists) + + fringe = Dict{T,Vector{Int}}() + @inbounds for v in vertices(g) + push!(get!(fringe, dists[v], Int[]), v) + end + + for i in length(unique_dists):-1:2 d_i = unique_dists[i] d_prev = unique_dists[i - 1] - # Process the "Fringe" at distance d_i FIRST - fringe = nodes_by_dist[d_i] - for v in fringe - ecc_v = maximum(dijkstra_shortest_paths(g, v, distmx).dists) - if ecc_v > lb - lb = ecc_v + if haskey(fringe, d_i) + for v in fringe[d_i] + ds = dijkstra_shortest_paths(g, U(v), distmx) + lb = max(lb, maximum(ds.dists)) end end - # Pruning Condition: Check AFTER processing the current level - # If the found lower bound is greater than 2 * (distance of previous level), - # we can stop. Nodes in previous levels cannot form a path longer than 2*d_prev. - if lb > 2 * d_prev - return lb - end + lb >= 2 * d_prev && break end return lb From 8c541076b79a80d75dfbcb56e323cc1b651ff5b7 Mon Sep 17 00:00:00 2001 From: Jihyung Kim Date: Sun, 22 Feb 2026 18:11:15 -0500 Subject: [PATCH 04/10] Fix typo --- test/distance.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/distance.jl b/test/distance.jl index e07c52954..20d7801e6 100644 --- a/test/distance.jl +++ b/test/distance.jl @@ -177,7 +177,7 @@ t_naive = @elapsed d_naive = maximum(eccentricity(g, vertices(g), distmx)) @test d_opt ≈ d_naive - @info "Speedup Barabási-Albert Model: $(round(t_naive/t_opt, digits=3))x" + @info "Speedup on Barabási-Albert Model: $(round(t_naive/t_opt, digits=3))x" end end end From d140969333bb145ff8f4aaaf4c2a88f733ba0626 Mon Sep 17 00:00:00 2001 From: Jihyung Kim Date: Sun, 22 Feb 2026 19:59:58 -0500 Subject: [PATCH 05/10] Added tests for better code coverage --- fsafgsdgsdgsgsdfsdf.jl | 22 ++++++++++++++++++++++ src/distance.jl | 4 ---- test/distance.jl | 26 ++++++++++++++++++++++---- 3 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 fsafgsdgsdgsgsdfsdf.jl diff --git a/fsafgsdgsdgsgsdfsdf.jl b/fsafgsdgsdgsgsdfsdf.jl new file mode 100644 index 000000000..c18d6961b --- /dev/null +++ b/fsafgsdgsdgsgsdfsdf.jl @@ -0,0 +1,22 @@ + g4 = path_digraph(5) + adjmx1 = [0 1 0; 1 0 1; 0 1 0] # graph + adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph + adjmx3 = [0 1 0; 0 0 0; 0 0 0] + a1 = SimpleGraph(adjmx1) + a2 = SimpleDiGraph(adjmx2) + a3 = SimpleDiGraph(adjmx3) + a4 = blockdiag(complete_graph(5), complete_graph(5)); + add_edge!(a4, 1, 6) + distmx1 = [Inf 2.0 Inf; 2.0 Inf 4.2; Inf 4.2 Inf] + distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] + +for g in test_generic_graphs(a4) + @test @inferred(diameter(g)) == 3 +end + + g_di = SimpleDiGraph(3) + add_edge!(g_di, 1, 2) + add_edge!(g_di, 2, 3) + add_edge!(g_di, 3, 1) + distmx_di = [Inf 1.5 Inf; Inf Inf 2.5; Inf Inf Inf] + diameter(g_di, distmx_di) \ No newline at end of file diff --git a/src/distance.jl b/src/distance.jl index 9473f6630..ad34922c7 100644 --- a/src/distance.jl +++ b/src/distance.jl @@ -117,10 +117,6 @@ diameter(eccentricities::Vector) = maximum(eccentricities) diameter(g::AbstractGraph) = diameter(g, weights(g)) function diameter(g::AbstractGraph, distmx::AbstractMatrix) - if distmx isa DefaultDistance - return diameter(g, DefaultDistance(nv(g))) - end - if is_directed(g) return _diameter_weighted_directed(g, distmx) else diff --git a/test/distance.jl b/test/distance.jl index 20d7801e6..07ca3c0be 100644 --- a/test/distance.jl +++ b/test/distance.jl @@ -49,12 +49,30 @@ end end - @testset "Disconnected graph diameter" for g in test_generic_graphs(a3) - @test @inferred(diameter(g)) == typemax(Int) + @testset "Disconnected graph diameter" begin + for g in test_generic_graphs(a3) + @test @inferred(diameter(g)) == typemax(Int) + + distmx3 = [Inf 1.0 Inf; Inf Inf Inf; Inf Inf Inf] + @test diameter(g, distmx3) == Inf + end + + g_un_isolated = SimpleGraph(2) + distmx_isolated = [0.0 Inf; Inf 0.0] + @test diameter(g_un_isolated, distmx_isolated) == Inf end - @testset "simplegraph diameter" for g in test_generic_graphs(a4) - @test @inferred(diameter(g)) == 3 + @testset "simplegraph diameter" begin + for g in test_generic_graphs(a4) + @test @inferred(diameter(g)) == 3 + end + + g_di = SimpleDiGraph(3) + add_edge!(g_di, 1, 2) + add_edge!(g_di, 2, 3) + add_edge!(g_di, 3, 1) + distmx_di = [Inf 1.5 Inf; Inf Inf 2.5; 1.0 Inf Inf] + @test @inferred(diameter(g_di, distmx_di)) == 4.0 end @testset "Empty graph diameter" begin From 69ebeaacafb2fe3429977ea428c5179f48e3e10b Mon Sep 17 00:00:00 2001 From: Jihyung Kim Date: Sun, 22 Feb 2026 20:11:59 -0500 Subject: [PATCH 06/10] Delete fsafgsdgsdgsgsdfsdf.jl --- fsafgsdgsdgsgsdfsdf.jl | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 fsafgsdgsdgsgsdfsdf.jl diff --git a/fsafgsdgsdgsgsdfsdf.jl b/fsafgsdgsdgsgsdfsdf.jl deleted file mode 100644 index c18d6961b..000000000 --- a/fsafgsdgsdgsgsdfsdf.jl +++ /dev/null @@ -1,22 +0,0 @@ - g4 = path_digraph(5) - adjmx1 = [0 1 0; 1 0 1; 0 1 0] # graph - adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph - adjmx3 = [0 1 0; 0 0 0; 0 0 0] - a1 = SimpleGraph(adjmx1) - a2 = SimpleDiGraph(adjmx2) - a3 = SimpleDiGraph(adjmx3) - a4 = blockdiag(complete_graph(5), complete_graph(5)); - add_edge!(a4, 1, 6) - distmx1 = [Inf 2.0 Inf; 2.0 Inf 4.2; Inf 4.2 Inf] - distmx2 = [Inf 2.0 Inf; 3.2 Inf 4.2; 5.5 6.1 Inf] - -for g in test_generic_graphs(a4) - @test @inferred(diameter(g)) == 3 -end - - g_di = SimpleDiGraph(3) - add_edge!(g_di, 1, 2) - add_edge!(g_di, 2, 3) - add_edge!(g_di, 3, 1) - distmx_di = [Inf 1.5 Inf; Inf Inf 2.5; Inf Inf Inf] - diameter(g_di, distmx_di) \ No newline at end of file From f01a98dd71edbd6dff75f3f5a287d4c4165f94ca Mon Sep 17 00:00:00 2001 From: Jihyung Kim Date: Mon, 23 Feb 2026 22:54:50 -0500 Subject: [PATCH 07/10] Edit benchmark --- benchmark/benchmarks.jl | 1 + benchmark/serial/distance.jl | 38 +++++++++++++++++++++++++++++++++++ test/distance.jl | 39 ------------------------------------ 3 files changed, 39 insertions(+), 39 deletions(-) create mode 100644 benchmark/serial/distance.jl diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index b00a2e888..d5b5d53e9 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -16,6 +16,7 @@ serialbenchmarks = [ "serial/core.jl", "serial/connectivity.jl", "serial/centrality.jl", + "serial/distance.jl", "serial/edges.jl", "serial/insertions.jl", "serial/traversals.jl", diff --git a/benchmark/serial/distance.jl b/benchmark/serial/distance.jl new file mode 100644 index 000000000..39a8c6d1b --- /dev/null +++ b/benchmark/serial/distance.jl @@ -0,0 +1,38 @@ +SUITE["distance"] = BenchmarkGroup() + +let + n_bench = 300 + + symmetric_weights(n) = (W = rand(n, n); (W + W') / 2) + + # Erdős-Rényi Setup + p = 10 / n_bench + g_er = erdos_renyi(n_bench, p) + while !is_connected(g_er) + g_er = erdos_renyi(n_bench, p) + end + distmx_er = symmetric_weights(n_bench) + + # Barabási-Albert Setup + g_ba = barabasi_albert(n_bench, 5) + while !is_connected(g_ba) + g_ba = barabasi_albert(n_bench, 5) + end + distmx_ba = symmetric_weights(n_bench) + + SUITE["distance"]["weighted_diameter"] = BenchmarkGroup() + + # Erdős-Rényi + SUITE["distance"]["weighted_diameter"]["erdos_renyi_optimized"] = + @benchmarkable diameter($g_er, $distmx_er) + + SUITE["distance"]["weighted_diameter"]["erdos_renyi_naive"] = + @benchmarkable maximum(eccentricity($g_er, vertices($g_er), $distmx_er)) + + # Barabási-Albert + SUITE["distance"]["weighted_diameter"]["barabasi_albert_optimized"] = + @benchmarkable diameter($g_ba, $distmx_ba) + + SUITE["distance"]["weighted_diameter"]["barabasi_albert_naive"] = + @benchmarkable maximum(eccentricity($g_ba, vertices($g_ba), $distmx_ba)) +end \ No newline at end of file diff --git a/test/distance.jl b/test/distance.jl index 07ca3c0be..7fd2b95f5 100644 --- a/test/distance.jl +++ b/test/distance.jl @@ -159,43 +159,4 @@ @test_logs eccentricity(g2) end - @testset "Weighted Diameter Benchmarks" begin - n_bench = 3000 - - function symmetric_weights(n) - W = rand(n, n) - return (W + W') / 2 - end - - @testset "Erdős-Rényi Graph" begin - p = 10 / n_bench - g = erdos_renyi(n_bench, p) - while !is_connected(g) - g = erdos_renyi(n_bench, p) - end - - distmx = symmetric_weights(n_bench) - - t_opt = @elapsed d_opt = diameter(g, distmx) - t_naive = @elapsed d_naive = maximum(eccentricity(g, vertices(g), distmx)) - - @test d_opt ≈ d_naive - @info "Speedup on Erdős-Rényi Graph: $(round(t_naive/t_opt, digits=1))x" - end - - @testset "Barabási-Albert Model" begin - g = barabasi_albert(n_bench, 5) - while !is_connected(g) - g = barabasi_albert(n_bench, 5) - end - - distmx = symmetric_weights(n_bench) - - t_opt = @elapsed d_opt = diameter(g, distmx) - t_naive = @elapsed d_naive = maximum(eccentricity(g, vertices(g), distmx)) - - @test d_opt ≈ d_naive - @info "Speedup on Barabási-Albert Model: $(round(t_naive/t_opt, digits=3))x" - end - end end From 70f21fb9fce2283fa755fc2991a176c009f8e84b Mon Sep 17 00:00:00 2001 From: Jihyung Kim Date: Mon, 23 Feb 2026 22:56:18 -0500 Subject: [PATCH 08/10] Format done --- benchmark/serial/distance.jl | 28 ++++++++++++++++------------ test/distance.jl | 1 - 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/benchmark/serial/distance.jl b/benchmark/serial/distance.jl index 39a8c6d1b..ae885ac70 100644 --- a/benchmark/serial/distance.jl +++ b/benchmark/serial/distance.jl @@ -3,7 +3,7 @@ SUITE["distance"] = BenchmarkGroup() let n_bench = 300 - symmetric_weights(n) = (W = rand(n, n); (W + W') / 2) + symmetric_weights(n) = (W=rand(n, n); (W + W') / 2) # Erdős-Rényi Setup p = 10 / n_bench @@ -23,16 +23,20 @@ let SUITE["distance"]["weighted_diameter"] = BenchmarkGroup() # Erdős-Rényi - SUITE["distance"]["weighted_diameter"]["erdos_renyi_optimized"] = - @benchmarkable diameter($g_er, $distmx_er) - - SUITE["distance"]["weighted_diameter"]["erdos_renyi_naive"] = - @benchmarkable maximum(eccentricity($g_er, vertices($g_er), $distmx_er)) + SUITE["distance"]["weighted_diameter"]["erdos_renyi_optimized"] = @benchmarkable diameter( + $g_er, $distmx_er + ) + + SUITE["distance"]["weighted_diameter"]["erdos_renyi_naive"] = @benchmarkable maximum( + eccentricity($g_er, vertices($g_er), $distmx_er) + ) # Barabási-Albert - SUITE["distance"]["weighted_diameter"]["barabasi_albert_optimized"] = - @benchmarkable diameter($g_ba, $distmx_ba) - - SUITE["distance"]["weighted_diameter"]["barabasi_albert_naive"] = - @benchmarkable maximum(eccentricity($g_ba, vertices($g_ba), $distmx_ba)) -end \ No newline at end of file + SUITE["distance"]["weighted_diameter"]["barabasi_albert_optimized"] = @benchmarkable diameter( + $g_ba, $distmx_ba + ) + + SUITE["distance"]["weighted_diameter"]["barabasi_albert_naive"] = @benchmarkable maximum( + eccentricity($g_ba, vertices($g_ba), $distmx_ba) + ) +end diff --git a/test/distance.jl b/test/distance.jl index 7fd2b95f5..04e6215c1 100644 --- a/test/distance.jl +++ b/test/distance.jl @@ -158,5 +158,4 @@ g2 = GenericGraph(path_graph(2)) @test_logs eccentricity(g2) end - end From 5f21cf3f85ff1f64a1304a03b7ab02aa2f989658 Mon Sep 17 00:00:00 2001 From: Jihyung Kim Date: Tue, 24 Feb 2026 19:17:51 -0500 Subject: [PATCH 09/10] Added correctness tests --- CHANGELOG.md | 1 + test/distance.jl | 46 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2abfde308..7227d81d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # News ## dev - unreleased +- The iFUB algorithm now supports weighted graph diameter calculation when using `diameter` - Louvain community detection algorithm - Graph views: `ReverseView` and `UndirectedView` for directed graphs - New graph products: `strong_product`, `disjunctive_product`, `lexicographic_product`, `homomorphic_product` diff --git a/test/distance.jl b/test/distance.jl index 6259f57fd..bf7566153 100644 --- a/test/distance.jl +++ b/test/distance.jl @@ -80,7 +80,7 @@ @test @inferred(diameter(SimpleDiGraph(0))) == 0 end - @testset "iFUB diameter" begin + @testset "Unweighted graph diameter" begin # 1. Comparing against large graphs with known diameters n_large = 5000 g_path = path_graph(n_large) @@ -138,6 +138,50 @@ end end + @testset "Weighted graph diameter" begin + function diameter_naive_weighted(g, distmx) + return maximum(eccentricity(g, vertices(g), distmx)) + end + + NUM_SAMPLES = 20 + + with_logger(NullLogger()) do + for i in 1:NUM_SAMPLES + n = rand(10:50) + p = rand() * 0.2 + 0.1 + + # Undirected Weighted Graphs + g = erdos_renyi(n, p) + ccs = connected_components(g) + largest_component = ccs[argmax(length.(ccs))] + g_lscc, _ = induced_subgraph(g, largest_component) + + if nv(g_lscc) > 1 + W = rand(nv(g_lscc), nv(g_lscc)) + distmx = (W + W') / 2 + + d_new = @inferred diameter(g_lscc, distmx) + d_ref = diameter_naive_weighted(g_lscc, distmx) + @test d_new ≈ d_ref + end + + # Directed Weighted Graphs + g_dir = erdos_renyi(n, p, is_directed=true) + sccs = strongly_connected_components(g_dir) + largest_component_directed = sccs[argmax(length.(sccs))] + g_dir_lscc, _ = induced_subgraph(g_dir, largest_component_directed) + + if nv(g_dir_lscc) > 1 + distmx_dir = rand(nv(g_dir_lscc), nv(g_dir_lscc)) + + d_new_dir = @inferred diameter(g_dir_lscc, distmx_dir) + d_ref_dir = diameter_naive_weighted(g_dir_lscc, distmx_dir) + @test d_new_dir ≈ d_ref_dir + end + end + end + end + @testset "DefaultDistance" begin @test size(Graphs.DefaultDistance()) == (typemax(Int), typemax(Int)) d = @inferred(Graphs.DefaultDistance(3)) From 8e90aa0293eb4f1008a26a852b07a863265a4026 Mon Sep 17 00:00:00 2001 From: Jihyung Kim Date: Sat, 7 Mar 2026 22:00:23 -0500 Subject: [PATCH 10/10] Bug fix on Asymmetric weights on undirected graph --- src/distance.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/distance.jl b/src/distance.jl index ad34922c7..1654989db 100644 --- a/src/distance.jl +++ b/src/distance.jl @@ -117,7 +117,7 @@ diameter(eccentricities::Vector) = maximum(eccentricities) diameter(g::AbstractGraph) = diameter(g, weights(g)) function diameter(g::AbstractGraph, distmx::AbstractMatrix) - if is_directed(g) + if is_directed(g) || !issymmetric(distmx) return _diameter_weighted_directed(g, distmx) else return _diameter_weighted_undirected(g, distmx) @@ -224,6 +224,8 @@ function _bwd_bfs_prune!(g, u, active, distbuf, queue, dmax, e, diam) end function _safe_reverse(g::T) where {T<:AbstractGraph} + !is_directed(g) && return g + if hasmethod(reverse, Tuple{T}) return reverse(g) else @@ -327,7 +329,7 @@ function _diameter_weighted_undirected( end end - lb >= 2 * d_prev && break + lb > 2 * d_prev && break end return lb