Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions src/distance.jl
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,14 @@ end
"""
diameter(eccentricities)
diameter(g, distmx=weights(g))
diameter(g::Union{SimpleGraph, SimpleDiGraph})

Given a graph and optional distance matrix, or a vector of precomputed
eccentricities, return the maximum eccentricity of the graph.

For unweighted `SimpleGraph` and `SimpleDiGraph`, an optimized BFS algorithm
(iFUB) is used to avoid computing eccentricities for all vertices.

# Examples
```jldoctest
julia> using Graphs
Expand All @@ -107,10 +111,117 @@ julia> diameter(path_graph(5))
```
"""
diameter(eccentricities::Vector) = maximum(eccentricities)

function diameter(g::AbstractGraph, distmx::AbstractMatrix=weights(g))
return maximum(eccentricity(g, distmx))
end

function diameter(g::Union{SimpleGraph,SimpleDiGraph})
if nv(g) <= 1
return 0
end
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

# Data structures
active = trues(nvg)
visited = falses(nvg)
queue = Vector{T}(undef, nvg)
distbuf = fill(typemax(T), nvg)
diam = 0

# 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])))

for u in vs
if !active[u]
continue
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

if front > level_end && front < back
e += 1
level_end = back - 1
end
end
diam = max(diam, e)

# --- Backward BFS (Pruning) ---
dmax = diam - e

# 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 current distance >= dmax, we cannot close the loop to beat diam
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

# Prune vertices that cannot possibly be part of a diametral path > diam
@inbounds for v in vertices(g)
if active[v] && distbuf[v] != typemax(T) && (distbuf[v] + e <= diam)
active[v] = false
end
end
end

if !any(active)
break
end
end

return diam
end

"""
periphery(eccentricities)
periphery(g, distmx=weights(g))
Expand Down
59 changes: 59 additions & 0 deletions test/distance.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
adjmx2 = [0 1 0; 1 0 1; 1 1 0] # digraph
a1 = SimpleGraph(adjmx1)
a2 = SimpleDiGraph(adjmx2)
a3 = blockdiag(complete_graph(5), complete_graph(5));
add_edge!(a3, 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]

Expand Down Expand Up @@ -44,6 +46,63 @@
@test @inferred(center(z)) == center(g, distmx2) == [2]
end
end

@testset "$(typeof(g))" for g in test_generic_graphs(a3)
@test @inferred(diameter(g)) == 3
end

@testset "iFUB diameter" begin

# 1. Tests comparing against large graphs with known diameters
n_large = 5000
g_path = path_graph(n_large)
@test diameter(g_path) == n_large - 1

g_cycle = cycle_graph(n_large)
@test diameter(g_cycle) == floor(Int, n_large / 2)

g_star = star_graph(n_large)
@test diameter(g_star) == 2

# 2. Tests comparing against the old slow implementation for random graphs
function diameter_naive(g)
return maximum(eccentricity(g))
end

NUM_SAMPLES = 50 # Adjust this to change test duration

Random.seed!(42)
for i in 1:NUM_SAMPLES
# Random unweighted Graphs
n = rand(10:1000) # Small to Medium size graphs
p = rand() * 0.1 + 0.005 # Sparse to medium density

# Undirected 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
d_new = @inferred diameter(g_lscc)
d_ref = diameter_naive(g_lscc)
@test d_new == d_ref
end

# Directed 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
d_new_dir = @inferred diameter(g_dir_lscc)
d_ref_dir = diameter_naive(g_dir_lscc)
@test d_new_dir == d_ref_dir
end
end
end

@testset "DefaultDistance" begin
@test size(Graphs.DefaultDistance()) == (typemax(Int), typemax(Int))
d = @inferred(Graphs.DefaultDistance(3))
Expand Down
4 changes: 4 additions & 0 deletions test/formattttt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using JuliaFormatter

format_file("src/distance.jl")
format_file("test/distance.jl")
Loading